@theokit/sdk-tools 0.20.1 → 0.22.0
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/dist/index.cjs +178 -209
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +111 -4
- package/dist/index.d.ts +111 -4
- package/dist/index.js +151 -183
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,106 +1,18 @@
|
|
|
1
1
|
import { rm, mkdir, writeFile, readFile, readdir, open, stat, copyFile } from 'fs/promises';
|
|
2
|
-
import { dirname, relative, join, isAbsolute
|
|
2
|
+
import { dirname, relative, join, isAbsolute } from 'path';
|
|
3
3
|
import { Tool, ConfigurationError } from '@theokit/sdk';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import {
|
|
6
|
-
import { safeFilenameForId, safePathJoin as safePathJoin$1 } from '@theokit/sdk/path-safety';
|
|
5
|
+
import { safePathJoin, assertNoSymlinkEscape, PathTraversalError, ForbiddenPathError, isForbiddenPath, safeFilenameForId } from '@theokit/sdk/path-safety';
|
|
7
6
|
import { replaceFileAtomic } from '@theokit/sdk/persistence';
|
|
8
7
|
import { resolveFilesystem, FileNotFoundError, FilesystemSecurityError, FilesystemReadOnlyError, StaleFileError, FilesystemError } from '@theokit/sdk/filesystem';
|
|
9
|
-
import {
|
|
8
|
+
import { existsSync, statSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from 'fs';
|
|
10
9
|
import { resolveSandbox } from '@theokit/sdk/sandbox';
|
|
10
|
+
import { spawn } from 'child_process';
|
|
11
11
|
import { resolveInteractive, InteractiveUnavailableError, NoSuchSessionError } from '@theokit/sdk/interactive';
|
|
12
12
|
import { lookup } from 'dns/promises';
|
|
13
13
|
import { isIP } from 'net';
|
|
14
14
|
|
|
15
15
|
// src/apply-patch.ts
|
|
16
|
-
var PathTraversalError = class extends ConfigurationError {
|
|
17
|
-
name = "PathTraversalError";
|
|
18
|
-
constructor(input, resolvedPath) {
|
|
19
|
-
super(`Path traversal attempt: ${input} \u2192 ${resolvedPath}`, {
|
|
20
|
-
code: "path_traversal"
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
var ForbiddenPathError = class extends ConfigurationError {
|
|
25
|
-
name = "ForbiddenPathError";
|
|
26
|
-
constructor(path) {
|
|
27
|
-
super(
|
|
28
|
-
`Path '${path}' is in the sensitive-file blocklist (.env, .git/, node_modules/, .theo/, lock files)`,
|
|
29
|
-
{
|
|
30
|
-
code: "forbidden_path"
|
|
31
|
-
}
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
function safePathJoin(base, ...parts) {
|
|
36
|
-
if (base === "") {
|
|
37
|
-
throw new Error("safePathJoin: base must be non-empty");
|
|
38
|
-
}
|
|
39
|
-
const baseResolved = resolve(base);
|
|
40
|
-
const target = resolve(base, ...parts);
|
|
41
|
-
if (target !== baseResolved && !target.startsWith(baseResolved + sep)) {
|
|
42
|
-
throw new PathTraversalError(parts.join("/"), target);
|
|
43
|
-
}
|
|
44
|
-
return target;
|
|
45
|
-
}
|
|
46
|
-
function assertNoSymlinkEscape(path, base) {
|
|
47
|
-
let baseResolved;
|
|
48
|
-
try {
|
|
49
|
-
baseResolved = realpathSync(base);
|
|
50
|
-
} catch {
|
|
51
|
-
baseResolved = resolve(base);
|
|
52
|
-
}
|
|
53
|
-
const resolved = realpathOfDeepestExisting(path);
|
|
54
|
-
if (resolved === void 0) return;
|
|
55
|
-
if (resolved !== baseResolved && !resolved.startsWith(baseResolved + sep)) {
|
|
56
|
-
throw new PathTraversalError(`symlink ${path}`, resolved);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
function realpathOfDeepestExisting(path) {
|
|
60
|
-
try {
|
|
61
|
-
return realpathSync(path);
|
|
62
|
-
} catch {
|
|
63
|
-
}
|
|
64
|
-
try {
|
|
65
|
-
const stat3 = lstatSync(path);
|
|
66
|
-
if (stat3.isSymbolicLink()) {
|
|
67
|
-
const target = readlinkSync(path);
|
|
68
|
-
const parentReal = realpathOfDeepestExisting(dirname(path));
|
|
69
|
-
const parentBase = parentReal ?? dirname(path);
|
|
70
|
-
return resolve(parentBase, target);
|
|
71
|
-
}
|
|
72
|
-
} catch {
|
|
73
|
-
}
|
|
74
|
-
let cursor = dirname(path);
|
|
75
|
-
let suffix = path.slice(cursor.length);
|
|
76
|
-
while (cursor !== dirname(cursor)) {
|
|
77
|
-
try {
|
|
78
|
-
const real = realpathSync(cursor);
|
|
79
|
-
return resolve(real, `.${suffix}`);
|
|
80
|
-
} catch {
|
|
81
|
-
suffix = path.slice(dirname(cursor).length);
|
|
82
|
-
cursor = dirname(cursor);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return void 0;
|
|
86
|
-
}
|
|
87
|
-
var LOCK_FILES = /* @__PURE__ */ new Set(["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lockb"]);
|
|
88
|
-
function isForbiddenPath(input) {
|
|
89
|
-
const normalized = input.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
90
|
-
if (normalized.length === 0) return false;
|
|
91
|
-
const segments = normalized.split("/").filter((s) => s.length > 0);
|
|
92
|
-
if (segments.length === 0) return false;
|
|
93
|
-
const first = segments[0];
|
|
94
|
-
if (first === ".env.example") return false;
|
|
95
|
-
if (first === ".env") return true;
|
|
96
|
-
if (/^\.env\./.test(first)) return true;
|
|
97
|
-
if (first === ".git") return true;
|
|
98
|
-
if (first === "node_modules") return true;
|
|
99
|
-
if (first === ".theo") return true;
|
|
100
|
-
const basename = segments[segments.length - 1];
|
|
101
|
-
if (LOCK_FILES.has(basename)) return true;
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
16
|
|
|
105
17
|
// src/internal/v4a-patch.ts
|
|
106
18
|
var BEGIN = "*** Begin Patch";
|
|
@@ -462,7 +374,7 @@ function createSessionArtifactStore(options) {
|
|
|
462
374
|
const idStrategy = options.idStrategy ?? ((id) => safeFilenameForId(id));
|
|
463
375
|
const extension = options.extension ?? ".md";
|
|
464
376
|
function path(id) {
|
|
465
|
-
return safePathJoin
|
|
377
|
+
return safePathJoin(dir, `${idStrategy(id)}${extension}`);
|
|
466
378
|
}
|
|
467
379
|
async function write(id, content) {
|
|
468
380
|
const target = path(id);
|
|
@@ -697,8 +609,8 @@ function editScopeError(path, projectRoot) {
|
|
|
697
609
|
function createEditFileTool(opts) {
|
|
698
610
|
const { projectRoot, filesystem } = opts;
|
|
699
611
|
return Tool.create({
|
|
700
|
-
name: "edit_file",
|
|
701
|
-
description: "Make an exact string replacement in a project-relative file. Replaces the FIRST occurrence of old_string with new_string (a whitespace-normalized fallback is attempted if the exact match fails) and writes a .bak backup first. Read the file first so old_string matches the on-disk text exactly; include enough surrounding context to make it unique \u2014 only the first match is replaced, so a too-short old_string can edit the wrong location. old_string must be non-empty and differ from new_string; to change every occurrence, call edit_file repeatedly. Returns { ok, replacements } or { ok: false, error }.",
|
|
612
|
+
name: opts.name ?? "edit_file",
|
|
613
|
+
description: opts.description ?? "Make an exact string replacement in a project-relative file. Replaces the FIRST occurrence of old_string with new_string (a whitespace-normalized fallback is attempted if the exact match fails) and writes a .bak backup first. Read the file first so old_string matches the on-disk text exactly; include enough surrounding context to make it unique \u2014 only the first match is replaced, so a too-short old_string can edit the wrong location. old_string must be non-empty and differ from new_string; to change every occurrence, call edit_file repeatedly. Returns { ok, replacements } or { ok: false, error }.",
|
|
702
614
|
inputSchema: z.object({
|
|
703
615
|
path: z.string().min(1).describe("Project-relative file path."),
|
|
704
616
|
old_string: z.string().min(1).describe("String to find in the file."),
|
|
@@ -770,21 +682,6 @@ function formatError(message, code) {
|
|
|
770
682
|
return `> **Error:** ${prefix}${message}`;
|
|
771
683
|
}
|
|
772
684
|
|
|
773
|
-
// src/path-scope.ts
|
|
774
|
-
function checkPathScope(path, projectRoot) {
|
|
775
|
-
if (path === void 0 || path === "") return null;
|
|
776
|
-
try {
|
|
777
|
-
const abs = safePathJoin(projectRoot, path);
|
|
778
|
-
assertNoSymlinkEscape(abs, projectRoot);
|
|
779
|
-
return null;
|
|
780
|
-
} catch (err) {
|
|
781
|
-
if (err instanceof PathTraversalError || err instanceof ForbiddenPathError) {
|
|
782
|
-
return JSON.stringify({ ok: false, error: "path_traversal", path });
|
|
783
|
-
}
|
|
784
|
-
throw err;
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
|
|
788
685
|
// src/subprocess.ts
|
|
789
686
|
function createSettleGate(timer) {
|
|
790
687
|
let done = false;
|
|
@@ -798,25 +695,104 @@ function createSettleGate(timer) {
|
|
|
798
695
|
}
|
|
799
696
|
};
|
|
800
697
|
}
|
|
801
|
-
function armTimeoutKill(child, timeoutMs, onTimeout,
|
|
698
|
+
function armTimeoutKill(child, timeoutMs, onTimeout, resolve) {
|
|
802
699
|
const timer = setTimeout(() => {
|
|
803
700
|
gate.fire(() => {
|
|
804
701
|
try {
|
|
805
702
|
process.kill(-(child.pid ?? 0), "SIGKILL");
|
|
806
703
|
} catch {
|
|
807
704
|
}
|
|
808
|
-
|
|
705
|
+
resolve(onTimeout());
|
|
809
706
|
});
|
|
810
707
|
}, timeoutMs);
|
|
811
708
|
const gate = createSettleGate(timer);
|
|
812
709
|
return gate;
|
|
813
710
|
}
|
|
814
|
-
function attachChildSettlers(child, gate, onClose, onError,
|
|
711
|
+
function attachChildSettlers(child, gate, onClose, onError, resolve) {
|
|
815
712
|
child.on("close", (code) => {
|
|
816
|
-
gate.fire(() =>
|
|
713
|
+
gate.fire(() => resolve(onClose(code)));
|
|
817
714
|
});
|
|
818
715
|
child.on("error", (err) => {
|
|
819
|
-
gate.fire(() =>
|
|
716
|
+
gate.fire(() => resolve(onError(err)));
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// src/internal/git-exec.ts
|
|
721
|
+
function formatGitResult(result, timeoutMs) {
|
|
722
|
+
if (result.kind === "timeout") {
|
|
723
|
+
return JSON.stringify({ ok: false, error: "timeout", timeoutMs });
|
|
724
|
+
}
|
|
725
|
+
if (result.kind === "error") {
|
|
726
|
+
return JSON.stringify({ ok: false, error: "git_failed", stderr: result.stderr });
|
|
727
|
+
}
|
|
728
|
+
return JSON.stringify({ ok: true, diff: result.stdout, truncated: result.truncated });
|
|
729
|
+
}
|
|
730
|
+
function runGitProcess(cwd, args, timeoutMs, maxStdoutBytes) {
|
|
731
|
+
return new Promise((resolve) => {
|
|
732
|
+
const child = spawn("git", args, { cwd, detached: true, stdio: ["ignore", "pipe", "pipe"] });
|
|
733
|
+
const stdoutChunks = [];
|
|
734
|
+
const stderrChunks = [];
|
|
735
|
+
let stdoutBytes = 0;
|
|
736
|
+
let truncated = false;
|
|
737
|
+
const gate = armTimeoutKill(
|
|
738
|
+
child,
|
|
739
|
+
timeoutMs,
|
|
740
|
+
() => ({ kind: "timeout" }),
|
|
741
|
+
resolve
|
|
742
|
+
);
|
|
743
|
+
child.stdout.on("data", (chunk) => {
|
|
744
|
+
if (gate.settled()) return;
|
|
745
|
+
if (stdoutBytes >= maxStdoutBytes) {
|
|
746
|
+
truncated = true;
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
const remaining = maxStdoutBytes - stdoutBytes;
|
|
750
|
+
if (chunk.length > remaining) {
|
|
751
|
+
stdoutChunks.push(chunk.subarray(0, remaining));
|
|
752
|
+
stdoutBytes = maxStdoutBytes;
|
|
753
|
+
truncated = true;
|
|
754
|
+
} else {
|
|
755
|
+
stdoutChunks.push(chunk);
|
|
756
|
+
stdoutBytes += chunk.length;
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
child.stderr.on("data", (chunk) => {
|
|
760
|
+
stderrChunks.push(chunk);
|
|
761
|
+
});
|
|
762
|
+
attachChildSettlers(
|
|
763
|
+
child,
|
|
764
|
+
gate,
|
|
765
|
+
(code) => {
|
|
766
|
+
const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
|
|
767
|
+
const stderr = Buffer.concat(stderrChunks).toString("utf-8");
|
|
768
|
+
return code === 0 ? { kind: "ok", stdout, truncated } : { kind: "error", stderr };
|
|
769
|
+
},
|
|
770
|
+
(err) => ({ kind: "error", stderr: err.message }),
|
|
771
|
+
resolve
|
|
772
|
+
);
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// src/path-scope.ts
|
|
777
|
+
function checkPathScope(path, projectRoot) {
|
|
778
|
+
if (path === void 0 || path === "") return null;
|
|
779
|
+
try {
|
|
780
|
+
const abs = safePathJoin(projectRoot, path);
|
|
781
|
+
assertNoSymlinkEscape(abs, projectRoot);
|
|
782
|
+
return null;
|
|
783
|
+
} catch (err) {
|
|
784
|
+
if (err instanceof PathTraversalError || err instanceof ForbiddenPathError) {
|
|
785
|
+
return JSON.stringify({ ok: false, error: "path_traversal", path });
|
|
786
|
+
}
|
|
787
|
+
throw err;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
var SEGMENTOS_SENSIVEIS = /* @__PURE__ */ new Set([".env", ".git", "node_modules", ".theo"]);
|
|
791
|
+
function ehProibidoEmQualquerProfundidade(path) {
|
|
792
|
+
const segs = path.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
793
|
+
return segs.some((s) => {
|
|
794
|
+
if (s === ".env.example") return false;
|
|
795
|
+
return SEGMENTOS_SENSIVEIS.has(s) || /^\.env\./.test(s);
|
|
820
796
|
});
|
|
821
797
|
}
|
|
822
798
|
|
|
@@ -873,58 +849,25 @@ function buildDiffArgs(cached, path) {
|
|
|
873
849
|
if (path !== void 0 && path !== "") args.push("--", path);
|
|
874
850
|
return args;
|
|
875
851
|
}
|
|
876
|
-
function
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
const child = spawn("git", args, { cwd, detached: true, stdio: ["ignore", "pipe", "pipe"] });
|
|
888
|
-
const stdoutChunks = [];
|
|
889
|
-
const stderrChunks = [];
|
|
890
|
-
let stdoutBytes = 0;
|
|
891
|
-
let truncated = false;
|
|
892
|
-
const gate = armTimeoutKill(
|
|
893
|
-
child,
|
|
894
|
-
timeoutMs,
|
|
895
|
-
() => ({ kind: "timeout" }),
|
|
896
|
-
resolve2
|
|
897
|
-
);
|
|
898
|
-
child.stdout.on("data", (chunk) => {
|
|
899
|
-
if (gate.settled()) return;
|
|
900
|
-
if (stdoutBytes >= maxStdoutBytes) {
|
|
901
|
-
truncated = true;
|
|
902
|
-
return;
|
|
903
|
-
}
|
|
904
|
-
const remaining = maxStdoutBytes - stdoutBytes;
|
|
905
|
-
if (chunk.length > remaining) {
|
|
906
|
-
stdoutChunks.push(chunk.subarray(0, remaining));
|
|
907
|
-
stdoutBytes = maxStdoutBytes;
|
|
908
|
-
truncated = true;
|
|
909
|
-
} else {
|
|
910
|
-
stdoutChunks.push(chunk);
|
|
911
|
-
stdoutBytes += chunk.length;
|
|
852
|
+
function createGitStatusTool(opts) {
|
|
853
|
+
const { projectRoot, timeoutMs = 3e4, maxStdoutBytes = 5 * 1024 * 1024 } = opts;
|
|
854
|
+
return Tool.create({
|
|
855
|
+
name: opts.name ?? "git_status",
|
|
856
|
+
description: opts.description ?? "Show the working-tree status in porcelain format: staged, unstaged and untracked paths, one per line with a two-character status code. Use before committing, or to see what changed without reading the full diff. Optional 'path' scopes the report to a subdirectory.",
|
|
857
|
+
inputSchema: z.object({
|
|
858
|
+
path: z.string().optional().describe("Optional project-relative path to scope the status report.")
|
|
859
|
+
}),
|
|
860
|
+
handler: async ({ path }) => {
|
|
861
|
+
if (!existsSync(join(projectRoot, ".git"))) {
|
|
862
|
+
return JSON.stringify({ ok: false, error: "not_a_repo" });
|
|
912
863
|
}
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
(code) => {
|
|
921
|
-
const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
|
|
922
|
-
const stderr = Buffer.concat(stderrChunks).toString("utf-8");
|
|
923
|
-
return code === 0 ? { kind: "ok", stdout, truncated } : { kind: "error", stderr };
|
|
924
|
-
},
|
|
925
|
-
(err) => ({ kind: "error", stderr: err.message }),
|
|
926
|
-
resolve2
|
|
927
|
-
);
|
|
864
|
+
const scopeCheck = checkPathScope(path, projectRoot);
|
|
865
|
+
if (scopeCheck !== null) return scopeCheck;
|
|
866
|
+
const args = ["status", "--porcelain"];
|
|
867
|
+
if (path !== void 0 && path !== "") args.push("--", path);
|
|
868
|
+
const result = await runGitProcess(projectRoot, args, timeoutMs, maxStdoutBytes);
|
|
869
|
+
return formatGitResult(result, timeoutMs);
|
|
870
|
+
}
|
|
928
871
|
});
|
|
929
872
|
}
|
|
930
873
|
var DEFAULT_EXCLUDES = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".theo"]);
|
|
@@ -1596,15 +1539,17 @@ var DEFAULT_MAX_ENTRIES = 500;
|
|
|
1596
1539
|
function createListDirTool(opts) {
|
|
1597
1540
|
const { projectRoot, max = DEFAULT_MAX_ENTRIES, filesystem } = opts;
|
|
1598
1541
|
return Tool.create({
|
|
1599
|
-
name: "list_dir",
|
|
1600
|
-
description: `Return the direct entries of a project-relative directory. Refuses paths outside the project root or in the sensitive-file blocklist (.env, .git/, node_modules/, .theo/, lock files). Caps at ${String(max)} entries by default; result carries truncated + totalCount.`,
|
|
1542
|
+
name: opts.name ?? "list_dir",
|
|
1543
|
+
description: opts.description ?? `Return the direct entries of a project-relative directory. Refuses paths outside the project root or in the sensitive-file blocklist (.env, .git/, node_modules/, .theo/, lock files). Caps at ${String(max)} entries by default; result carries truncated + totalCount.`,
|
|
1601
1544
|
inputSchema: z.object({
|
|
1602
1545
|
path: z.string().min(1).describe("Project-relative directory path. Use '.' for root.")
|
|
1603
1546
|
}),
|
|
1604
1547
|
handler: async ({ path }, ctx) => {
|
|
1605
1548
|
const relative3 = path === "" || path === "." ? "." : path;
|
|
1606
|
-
|
|
1607
|
-
|
|
1549
|
+
const veredito = decidirEscopo(relative3, path, opts.allowAbsolute === true);
|
|
1550
|
+
if (veredito.erro !== void 0) return veredito.erro;
|
|
1551
|
+
if (veredito.raizAbsoluta !== void 0) {
|
|
1552
|
+
return listViaLocalFs(veredito.raizAbsoluta, ".", path, max);
|
|
1608
1553
|
}
|
|
1609
1554
|
if (filesystem) {
|
|
1610
1555
|
const backend = await resolveFilesystem(filesystem, ctx ?? {});
|
|
@@ -1614,6 +1559,16 @@ function createListDirTool(opts) {
|
|
|
1614
1559
|
}
|
|
1615
1560
|
});
|
|
1616
1561
|
}
|
|
1562
|
+
function decidirEscopo(relative3, original, allowAbsolute) {
|
|
1563
|
+
const recusa = (error) => ({
|
|
1564
|
+
erro: JSON.stringify({ ok: false, error, path: original })
|
|
1565
|
+
});
|
|
1566
|
+
if (relative3 !== "." && isForbiddenPath(relative3)) return recusa("forbidden_path");
|
|
1567
|
+
if (!isAbsolute(relative3)) return {};
|
|
1568
|
+
if (!allowAbsolute) return recusa("path_traversal");
|
|
1569
|
+
if (ehProibidoEmQualquerProfundidade(relative3)) return recusa("forbidden_path");
|
|
1570
|
+
return { raizAbsoluta: relative3 };
|
|
1571
|
+
}
|
|
1617
1572
|
async function listViaLocalFs(projectRoot, relative3, originalPath, max) {
|
|
1618
1573
|
const boundary = resolveDirBoundary(relative3, projectRoot, originalPath);
|
|
1619
1574
|
if ("error" in boundary) return boundary.error;
|
|
@@ -1774,6 +1729,11 @@ function createPlanModeTool(options) {
|
|
|
1774
1729
|
}
|
|
1775
1730
|
|
|
1776
1731
|
// src/question.ts
|
|
1732
|
+
function askerDoContexto(context) {
|
|
1733
|
+
if (typeof context !== "object" || context === null) return void 0;
|
|
1734
|
+
const candidato = context.askUser;
|
|
1735
|
+
return typeof candidato === "function" ? candidato : void 0;
|
|
1736
|
+
}
|
|
1777
1737
|
function createQuestionTool(opts) {
|
|
1778
1738
|
const timeoutMs = opts.timeoutMs ?? 3e5;
|
|
1779
1739
|
return {
|
|
@@ -1786,12 +1746,20 @@ function createQuestionTool(opts) {
|
|
|
1786
1746
|
},
|
|
1787
1747
|
required: ["question"]
|
|
1788
1748
|
},
|
|
1789
|
-
handler: async (input) => {
|
|
1749
|
+
handler: async (input, ctx) => {
|
|
1750
|
+
const askUser = askerDoContexto(ctx?.context) ?? opts.askUser;
|
|
1751
|
+
if (askUser === void 0) {
|
|
1752
|
+
return JSON.stringify({
|
|
1753
|
+
ok: false,
|
|
1754
|
+
error: "no_asker",
|
|
1755
|
+
message: "No asker available: pass `askUser` to createQuestionTool, or provide `context.askUser` via SendOptions.context."
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1790
1758
|
const timeout = new Promise((_, reject) => {
|
|
1791
1759
|
setTimeout(() => reject(new Error("timeout")), timeoutMs);
|
|
1792
1760
|
});
|
|
1793
1761
|
try {
|
|
1794
|
-
const answer = await Promise.race([
|
|
1762
|
+
const answer = await Promise.race([askUser(input.question), timeout]);
|
|
1795
1763
|
return JSON.stringify({ ok: true, answer });
|
|
1796
1764
|
} catch (err) {
|
|
1797
1765
|
if (err instanceof Error && err.message === "timeout") {
|
|
@@ -2076,7 +2044,7 @@ function appendCapped(chunks, chunk, current, cap) {
|
|
|
2076
2044
|
return current + chunk.length;
|
|
2077
2045
|
}
|
|
2078
2046
|
function runProcess(cwd, command, args, timeoutMs, maxStdoutBytes) {
|
|
2079
|
-
return new Promise((
|
|
2047
|
+
return new Promise((resolve) => {
|
|
2080
2048
|
const child = spawn(command, args, {
|
|
2081
2049
|
cwd,
|
|
2082
2050
|
detached: true,
|
|
@@ -2089,7 +2057,7 @@ function runProcess(cwd, command, args, timeoutMs, maxStdoutBytes) {
|
|
|
2089
2057
|
child,
|
|
2090
2058
|
timeoutMs,
|
|
2091
2059
|
() => ({ kind: "timeout" }),
|
|
2092
|
-
|
|
2060
|
+
resolve
|
|
2093
2061
|
);
|
|
2094
2062
|
child.stdout.on("data", (chunk) => {
|
|
2095
2063
|
if (gate.settled()) return;
|
|
@@ -2108,7 +2076,7 @@ function runProcess(cwd, command, args, timeoutMs, maxStdoutBytes) {
|
|
|
2108
2076
|
exitCode: code ?? 0
|
|
2109
2077
|
}),
|
|
2110
2078
|
(err) => ({ kind: "spawn_error", message: err.message }),
|
|
2111
|
-
|
|
2079
|
+
resolve
|
|
2112
2080
|
);
|
|
2113
2081
|
});
|
|
2114
2082
|
}
|
|
@@ -2129,8 +2097,8 @@ function createSearchTextTool(opts) {
|
|
|
2129
2097
|
const queryKind = regex ? "a JavaScript REGULAR EXPRESSION" : "LITERAL, CASE-SENSITIVE text";
|
|
2130
2098
|
const queryMatch = regex ? "matched as a regex" : "matched as a substring, not a regex";
|
|
2131
2099
|
return Tool.create({
|
|
2132
|
-
name: "search_text",
|
|
2133
|
-
description: `Search file CONTENTS for ${queryKind} across the project tree (the query is ${queryMatch}). Use search_text when you know the content; use glob_files when you know the filename shape; use read_file when you know the exact path. Skips sensitive dirs (.env/.git/node_modules/.theo), binary files, and files over 1 MB; 'path' scopes the search to a subdirectory. Returns up to ${String(maxMatches)} matches as { file, line, preview } \u2014 cite locations to the user as file:line. Returns { ok, matches } or { ok: false, error }.`,
|
|
2100
|
+
name: opts.name ?? "search_text",
|
|
2101
|
+
description: opts.description ?? `Search file CONTENTS for ${queryKind} across the project tree (the query is ${queryMatch}). Use search_text when you know the content; use glob_files when you know the filename shape; use read_file when you know the exact path. Skips sensitive dirs (.env/.git/node_modules/.theo), binary files, and files over 1 MB; 'path' scopes the search to a subdirectory. Returns up to ${String(maxMatches)} matches as { file, line, preview } \u2014 cite locations to the user as file:line. Returns { ok, matches } or { ok: false, error }.`,
|
|
2134
2102
|
inputSchema: z.object({
|
|
2135
2103
|
query: regex ? z.string().min(1).describe("A JavaScript regular expression, e.g. 'function\\\\s+main'.") : z.string().min(1).describe("Literal text to search for. Case-sensitive."),
|
|
2136
2104
|
path: z.string().optional().describe(
|
|
@@ -2338,8 +2306,8 @@ function createShellTool(opts) {
|
|
|
2338
2306
|
sandbox
|
|
2339
2307
|
} = opts;
|
|
2340
2308
|
return Tool.create({
|
|
2341
|
-
name: "shell_exec",
|
|
2342
|
-
description: "Execute a shell command in the project directory. Use this for terminal operations \u2014 running tests, git, package managers, build tools. Do NOT use it for file operations (reading, writing, editing, finding files): prefer the specialized read_file/write_file/edit_file/glob_files/search_text tools, which are path-checked and safer. Only commit, push, or change git state when the user explicitly asks. timeout_ms defaults to 30000 (max 300000); stdout/stderr are capped (~5 MB). Returns { ok, stdout, stderr, exit_code } or { ok: false, error }.",
|
|
2309
|
+
name: opts.name ?? "shell_exec",
|
|
2310
|
+
description: opts.description ?? "Execute a shell command in the project directory. Use this for terminal operations \u2014 running tests, git, package managers, build tools. Do NOT use it for file operations (reading, writing, editing, finding files): prefer the specialized read_file/write_file/edit_file/glob_files/search_text tools, which are path-checked and safer. Only commit, push, or change git state when the user explicitly asks. timeout_ms defaults to 30000 (max 300000); stdout/stderr are capped (~5 MB). Returns { ok, stdout, stderr, exit_code } or { ok: false, error }.",
|
|
2343
2311
|
inputSchema: z.object({
|
|
2344
2312
|
command: z.string().min(1).describe("Shell command to execute."),
|
|
2345
2313
|
timeout_ms: z.number().int().positive().optional().describe("Timeout in milliseconds (default 30000, max 300000).")
|
|
@@ -2359,7 +2327,7 @@ function createShellTool(opts) {
|
|
|
2359
2327
|
});
|
|
2360
2328
|
}
|
|
2361
2329
|
function runShell(cwd, command, timeoutMs) {
|
|
2362
|
-
return new Promise((
|
|
2330
|
+
return new Promise((resolve) => {
|
|
2363
2331
|
const child = spawn("/bin/sh", ["-c", command], {
|
|
2364
2332
|
cwd,
|
|
2365
2333
|
detached: true,
|
|
@@ -2373,7 +2341,7 @@ function runShell(cwd, command, timeoutMs) {
|
|
|
2373
2341
|
child,
|
|
2374
2342
|
timeoutMs,
|
|
2375
2343
|
() => ({ kind: "timeout" }),
|
|
2376
|
-
(result) =>
|
|
2344
|
+
(result) => resolve(formatResult(result, timeoutMs))
|
|
2377
2345
|
);
|
|
2378
2346
|
child.stdout.on("data", (chunk) => {
|
|
2379
2347
|
if (gate.settled()) return;
|
|
@@ -2411,7 +2379,7 @@ function runShell(cwd, command, timeoutMs) {
|
|
|
2411
2379
|
exitCode: code
|
|
2412
2380
|
}),
|
|
2413
2381
|
(err) => ({ kind: "error", message: err.message }),
|
|
2414
|
-
(result) =>
|
|
2382
|
+
(result) => resolve(formatResult(result, timeoutMs))
|
|
2415
2383
|
);
|
|
2416
2384
|
});
|
|
2417
2385
|
}
|
|
@@ -2905,6 +2873,6 @@ async function isBinaryFile(absolutePath) {
|
|
|
2905
2873
|
}
|
|
2906
2874
|
}
|
|
2907
2875
|
|
|
2908
|
-
export { CatastrophicCommandError, ContextMatchError, DEFAULT_TOOL_GUIDANCE, ReadTracker, ReasoningTools, RedirectBlockedError, SsrfBlockedError, buildEnvContext, buildRepoMap, catastrophicShellReason, commandDenialReason, createApplyPatchTool, createBraveWebSearchAdapter, createCurrentTimeTool, createEditFileTool, createGenericHttpSearchAdapter, createGitDiffTool, createGlobTool, createInteractiveShellTool, createListDirTool, createPlanModeTool, createQuestionTool, createReadFileTool, createRunVitestTool, createSearchTextTool, createSessionArtifactStore, createShellTool, createTodolistTool, createUpdatePlanTool, createWebFetchTool, createWebSearchTool, createWriteFileTool, createWriteStdinTool, denyCatastrophicCommands, formatCode, formatDiff, formatError, formatFileList, injectGuidance, isBlockedIp, isCommandAllowed, renderToolList, replaceUnique, resolveAndScreen, screenedFetch, todoItemsToPlanNodes, truncateOutput, withDefaultGuidance, withDescription, withName, withShellExitGuidance, withToolResultGuidance };
|
|
2876
|
+
export { CatastrophicCommandError, ContextMatchError, DEFAULT_TOOL_GUIDANCE, ReadTracker, ReasoningTools, RedirectBlockedError, SsrfBlockedError, buildEnvContext, buildRepoMap, catastrophicShellReason, commandDenialReason, createApplyPatchTool, createBraveWebSearchAdapter, createCurrentTimeTool, createEditFileTool, createGenericHttpSearchAdapter, createGitDiffTool, createGitStatusTool, createGlobTool, createInteractiveShellTool, createListDirTool, createPlanModeTool, createQuestionTool, createReadFileTool, createRunVitestTool, createSearchTextTool, createSessionArtifactStore, createShellTool, createTodolistTool, createUpdatePlanTool, createWebFetchTool, createWebSearchTool, createWriteFileTool, createWriteStdinTool, denyCatastrophicCommands, formatCode, formatDiff, formatError, formatFileList, injectGuidance, isBlockedIp, isCommandAllowed, renderToolList, replaceUnique, resolveAndScreen, screenedFetch, todoItemsToPlanNodes, truncateOutput, withDefaultGuidance, withDescription, withName, withShellExitGuidance, withToolResultGuidance };
|
|
2909
2877
|
//# sourceMappingURL=index.js.map
|
|
2910
2878
|
//# sourceMappingURL=index.js.map
|