@theokit/sdk-tools 0.8.0 → 0.9.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/CHANGELOG.md +16 -0
- package/dist/index.cjs +129 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +49 -1
- package/dist/index.d.ts +49 -1
- package/dist/index.js +127 -24
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CustomTool, ConfigurationError } from '@theokit/sdk';
|
|
2
|
+
import { FilesystemProvider } from '@theokit/sdk/filesystem';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* `apply_patch` — built-in tool for coding agents.
|
|
@@ -504,6 +505,25 @@ interface QuestionTool {
|
|
|
504
505
|
}
|
|
505
506
|
declare function createQuestionTool(opts: QuestionToolOptions): QuestionTool;
|
|
506
507
|
|
|
508
|
+
/**
|
|
509
|
+
* `ReadTracker` — SE32 read-before-write safety.
|
|
510
|
+
*
|
|
511
|
+
* A per-run map of `path → mtimeMs` recorded whenever the read tool reads a
|
|
512
|
+
* file. The write tool (with `requireReadBeforeWrite` on) consults it before an
|
|
513
|
+
* overwrite: a file that was never read, or that changed on disk since it was
|
|
514
|
+
* read, is refused instead of silently clobbered. Scope one tracker per run /
|
|
515
|
+
* session — it is deliberately NOT a global singleton (no cross-run leak).
|
|
516
|
+
*
|
|
517
|
+
* @public
|
|
518
|
+
*/
|
|
519
|
+
declare class ReadTracker {
|
|
520
|
+
private readonly seen;
|
|
521
|
+
/** Record the mtime observed when `path` was read. */
|
|
522
|
+
record(path: string, mtimeMs: number): void;
|
|
523
|
+
/** The mtime last recorded for `path`, or `undefined` if never read. */
|
|
524
|
+
expected(path: string): number | undefined;
|
|
525
|
+
}
|
|
526
|
+
|
|
507
527
|
/**
|
|
508
528
|
* `read_file` — built-in tool for coding agents.
|
|
509
529
|
*
|
|
@@ -533,6 +553,12 @@ declare function createQuestionTool(opts: QuestionToolOptions): QuestionTool;
|
|
|
533
553
|
interface CreateReadFileToolOptions {
|
|
534
554
|
/** Absolute path to the project root. Every read is gated against this boundary. */
|
|
535
555
|
projectRoot: string;
|
|
556
|
+
/**
|
|
557
|
+
* SE32 — optional read-before-write tracker. When provided, a successful read
|
|
558
|
+
* records the file's mtime so a paired `write_file` (with
|
|
559
|
+
* `requireReadBeforeWrite`) can refuse a blind or stale overwrite.
|
|
560
|
+
*/
|
|
561
|
+
readTracker?: ReadTracker;
|
|
536
562
|
}
|
|
537
563
|
declare function createReadFileTool(opts: CreateReadFileToolOptions): CustomTool;
|
|
538
564
|
|
|
@@ -862,10 +888,32 @@ declare function createGenericHttpSearchAdapter(opts?: CreateGenericHttpSearchAd
|
|
|
862
888
|
* 'binary_file' }` on refusal
|
|
863
889
|
*/
|
|
864
890
|
|
|
891
|
+
/** The run-scoped context a `defineTool` handler receives as its 2nd argument. */
|
|
892
|
+
type WriteToolContext = {
|
|
893
|
+
signal?: AbortSignal;
|
|
894
|
+
context?: unknown;
|
|
895
|
+
};
|
|
865
896
|
interface CreateWriteFileToolOptions {
|
|
866
897
|
/** Absolute path to the project root. Every write is gated against this boundary. */
|
|
867
898
|
projectRoot: string;
|
|
899
|
+
/**
|
|
900
|
+
* SE31 — optional pluggable filesystem backend (`@theokit/sdk/filesystem`), or
|
|
901
|
+
* a per-request resolver `(ctx) => FilesystemBackend`. When provided, writes
|
|
902
|
+
* route through it (its own boundary + `readOnly` + per-request root) instead
|
|
903
|
+
* of the local project fs. Omitted ⇒ identical current behavior (local
|
|
904
|
+
* `projectRoot`). The `.env`/`.git` policy still applies (storage-independent).
|
|
905
|
+
*/
|
|
906
|
+
filesystem?: FilesystemProvider<WriteToolContext>;
|
|
907
|
+
/**
|
|
908
|
+
* SE32 — when true (and a {@link ReadTracker} is supplied), refuse to
|
|
909
|
+
* overwrite an existing file that was not read first, or that changed on disk
|
|
910
|
+
* since it was read (`read_required` / `stale_file`). A NEW file writes
|
|
911
|
+
* freely. Default OFF (unchanged behavior).
|
|
912
|
+
*/
|
|
913
|
+
requireReadBeforeWrite?: boolean;
|
|
914
|
+
/** SE32 — the per-run tracker populated by the paired `read_file` tool. */
|
|
915
|
+
readTracker?: ReadTracker;
|
|
868
916
|
}
|
|
869
917
|
declare function createWriteFileTool(opts: CreateWriteFileToolOptions): CustomTool;
|
|
870
918
|
|
|
871
|
-
export { CatastrophicCommandError, type CommandPolicy, type CreateApplyPatchToolOptions, type CreateBraveWebSearchAdapterOptions, type CreateEditFileToolOptions, type CreateGenericHttpSearchAdapterOptions, type CreateGitDiffToolOptions, type CreateGlobToolOptions, type CreateListDirToolOptions, type CreateReadFileToolOptions, type CreateRunVitestToolOptions, type CreateSearchTextToolOptions, type CreateShellToolOptions, type CreateWebFetchToolOptions, type CreateWebSearchToolOptions, type CreateWriteFileToolOptions, DEFAULT_TOOL_GUIDANCE, type EnvContextOptions, type PlanModeTool, type PlanModeToolOptions, type PlanModeToolWithStore, type PlanNode, type QuestionTool, type QuestionToolOptions, RedirectBlockedError, type RepoMapOptions, type ResolveAndScreenOptions, type ScreenedFetchOptions, type SessionArtifactStore, type SessionArtifactStoreOptions, SsrfBlockedError, type TodoItem, type TodolistTool, type ToolGuidanceMap, type TruncationOptions, type TruncationResult, type VitestSummary, type WebSearchCallback, type WebSearchResult, buildEnvContext, buildRepoMap, catastrophicShellReason, commandDenialReason, createApplyPatchTool, createBraveWebSearchAdapter, createEditFileTool, createGenericHttpSearchAdapter, createGitDiffTool, createGlobTool, createListDirTool, createPlanModeTool, createQuestionTool, createReadFileTool, createRunVitestTool, createSearchTextTool, createSessionArtifactStore, createShellTool, createTodolistTool, createWebFetchTool, createWebSearchTool, createWriteFileTool, denyCatastrophicCommands, formatCode, formatDiff, formatError, formatFileList, injectGuidance, isBlockedIp, isCommandAllowed, renderToolList, resolveAndScreen, screenedFetch, todoItemsToPlanNodes, truncateOutput, withDefaultGuidance, withDescription, withShellExitGuidance, withToolResultGuidance };
|
|
919
|
+
export { CatastrophicCommandError, type CommandPolicy, type CreateApplyPatchToolOptions, type CreateBraveWebSearchAdapterOptions, type CreateEditFileToolOptions, type CreateGenericHttpSearchAdapterOptions, type CreateGitDiffToolOptions, type CreateGlobToolOptions, type CreateListDirToolOptions, type CreateReadFileToolOptions, type CreateRunVitestToolOptions, type CreateSearchTextToolOptions, type CreateShellToolOptions, type CreateWebFetchToolOptions, type CreateWebSearchToolOptions, type CreateWriteFileToolOptions, DEFAULT_TOOL_GUIDANCE, type EnvContextOptions, type PlanModeTool, type PlanModeToolOptions, type PlanModeToolWithStore, type PlanNode, type QuestionTool, type QuestionToolOptions, ReadTracker, RedirectBlockedError, type RepoMapOptions, type ResolveAndScreenOptions, type ScreenedFetchOptions, type SessionArtifactStore, type SessionArtifactStoreOptions, SsrfBlockedError, type TodoItem, type TodolistTool, type ToolGuidanceMap, type TruncationOptions, type TruncationResult, type VitestSummary, type WebSearchCallback, type WebSearchResult, buildEnvContext, buildRepoMap, catastrophicShellReason, commandDenialReason, createApplyPatchTool, createBraveWebSearchAdapter, createEditFileTool, createGenericHttpSearchAdapter, createGitDiffTool, createGlobTool, createListDirTool, createPlanModeTool, createQuestionTool, createReadFileTool, createRunVitestTool, createSearchTextTool, createSessionArtifactStore, createShellTool, createTodolistTool, createWebFetchTool, createWebSearchTool, createWriteFileTool, denyCatastrophicCommands, formatCode, formatDiff, formatError, formatFileList, injectGuidance, isBlockedIp, isCommandAllowed, renderToolList, resolveAndScreen, screenedFetch, todoItemsToPlanNodes, truncateOutput, withDefaultGuidance, withDescription, withShellExitGuidance, withToolResultGuidance };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CustomTool, ConfigurationError } from '@theokit/sdk';
|
|
2
|
+
import { FilesystemProvider } from '@theokit/sdk/filesystem';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* `apply_patch` — built-in tool for coding agents.
|
|
@@ -504,6 +505,25 @@ interface QuestionTool {
|
|
|
504
505
|
}
|
|
505
506
|
declare function createQuestionTool(opts: QuestionToolOptions): QuestionTool;
|
|
506
507
|
|
|
508
|
+
/**
|
|
509
|
+
* `ReadTracker` — SE32 read-before-write safety.
|
|
510
|
+
*
|
|
511
|
+
* A per-run map of `path → mtimeMs` recorded whenever the read tool reads a
|
|
512
|
+
* file. The write tool (with `requireReadBeforeWrite` on) consults it before an
|
|
513
|
+
* overwrite: a file that was never read, or that changed on disk since it was
|
|
514
|
+
* read, is refused instead of silently clobbered. Scope one tracker per run /
|
|
515
|
+
* session — it is deliberately NOT a global singleton (no cross-run leak).
|
|
516
|
+
*
|
|
517
|
+
* @public
|
|
518
|
+
*/
|
|
519
|
+
declare class ReadTracker {
|
|
520
|
+
private readonly seen;
|
|
521
|
+
/** Record the mtime observed when `path` was read. */
|
|
522
|
+
record(path: string, mtimeMs: number): void;
|
|
523
|
+
/** The mtime last recorded for `path`, or `undefined` if never read. */
|
|
524
|
+
expected(path: string): number | undefined;
|
|
525
|
+
}
|
|
526
|
+
|
|
507
527
|
/**
|
|
508
528
|
* `read_file` — built-in tool for coding agents.
|
|
509
529
|
*
|
|
@@ -533,6 +553,12 @@ declare function createQuestionTool(opts: QuestionToolOptions): QuestionTool;
|
|
|
533
553
|
interface CreateReadFileToolOptions {
|
|
534
554
|
/** Absolute path to the project root. Every read is gated against this boundary. */
|
|
535
555
|
projectRoot: string;
|
|
556
|
+
/**
|
|
557
|
+
* SE32 — optional read-before-write tracker. When provided, a successful read
|
|
558
|
+
* records the file's mtime so a paired `write_file` (with
|
|
559
|
+
* `requireReadBeforeWrite`) can refuse a blind or stale overwrite.
|
|
560
|
+
*/
|
|
561
|
+
readTracker?: ReadTracker;
|
|
536
562
|
}
|
|
537
563
|
declare function createReadFileTool(opts: CreateReadFileToolOptions): CustomTool;
|
|
538
564
|
|
|
@@ -862,10 +888,32 @@ declare function createGenericHttpSearchAdapter(opts?: CreateGenericHttpSearchAd
|
|
|
862
888
|
* 'binary_file' }` on refusal
|
|
863
889
|
*/
|
|
864
890
|
|
|
891
|
+
/** The run-scoped context a `defineTool` handler receives as its 2nd argument. */
|
|
892
|
+
type WriteToolContext = {
|
|
893
|
+
signal?: AbortSignal;
|
|
894
|
+
context?: unknown;
|
|
895
|
+
};
|
|
865
896
|
interface CreateWriteFileToolOptions {
|
|
866
897
|
/** Absolute path to the project root. Every write is gated against this boundary. */
|
|
867
898
|
projectRoot: string;
|
|
899
|
+
/**
|
|
900
|
+
* SE31 — optional pluggable filesystem backend (`@theokit/sdk/filesystem`), or
|
|
901
|
+
* a per-request resolver `(ctx) => FilesystemBackend`. When provided, writes
|
|
902
|
+
* route through it (its own boundary + `readOnly` + per-request root) instead
|
|
903
|
+
* of the local project fs. Omitted ⇒ identical current behavior (local
|
|
904
|
+
* `projectRoot`). The `.env`/`.git` policy still applies (storage-independent).
|
|
905
|
+
*/
|
|
906
|
+
filesystem?: FilesystemProvider<WriteToolContext>;
|
|
907
|
+
/**
|
|
908
|
+
* SE32 — when true (and a {@link ReadTracker} is supplied), refuse to
|
|
909
|
+
* overwrite an existing file that was not read first, or that changed on disk
|
|
910
|
+
* since it was read (`read_required` / `stale_file`). A NEW file writes
|
|
911
|
+
* freely. Default OFF (unchanged behavior).
|
|
912
|
+
*/
|
|
913
|
+
requireReadBeforeWrite?: boolean;
|
|
914
|
+
/** SE32 — the per-run tracker populated by the paired `read_file` tool. */
|
|
915
|
+
readTracker?: ReadTracker;
|
|
868
916
|
}
|
|
869
917
|
declare function createWriteFileTool(opts: CreateWriteFileToolOptions): CustomTool;
|
|
870
918
|
|
|
871
|
-
export { CatastrophicCommandError, type CommandPolicy, type CreateApplyPatchToolOptions, type CreateBraveWebSearchAdapterOptions, type CreateEditFileToolOptions, type CreateGenericHttpSearchAdapterOptions, type CreateGitDiffToolOptions, type CreateGlobToolOptions, type CreateListDirToolOptions, type CreateReadFileToolOptions, type CreateRunVitestToolOptions, type CreateSearchTextToolOptions, type CreateShellToolOptions, type CreateWebFetchToolOptions, type CreateWebSearchToolOptions, type CreateWriteFileToolOptions, DEFAULT_TOOL_GUIDANCE, type EnvContextOptions, type PlanModeTool, type PlanModeToolOptions, type PlanModeToolWithStore, type PlanNode, type QuestionTool, type QuestionToolOptions, RedirectBlockedError, type RepoMapOptions, type ResolveAndScreenOptions, type ScreenedFetchOptions, type SessionArtifactStore, type SessionArtifactStoreOptions, SsrfBlockedError, type TodoItem, type TodolistTool, type ToolGuidanceMap, type TruncationOptions, type TruncationResult, type VitestSummary, type WebSearchCallback, type WebSearchResult, buildEnvContext, buildRepoMap, catastrophicShellReason, commandDenialReason, createApplyPatchTool, createBraveWebSearchAdapter, createEditFileTool, createGenericHttpSearchAdapter, createGitDiffTool, createGlobTool, createListDirTool, createPlanModeTool, createQuestionTool, createReadFileTool, createRunVitestTool, createSearchTextTool, createSessionArtifactStore, createShellTool, createTodolistTool, createWebFetchTool, createWebSearchTool, createWriteFileTool, denyCatastrophicCommands, formatCode, formatDiff, formatError, formatFileList, injectGuidance, isBlockedIp, isCommandAllowed, renderToolList, resolveAndScreen, screenedFetch, todoItemsToPlanNodes, truncateOutput, withDefaultGuidance, withDescription, withShellExitGuidance, withToolResultGuidance };
|
|
919
|
+
export { CatastrophicCommandError, type CommandPolicy, type CreateApplyPatchToolOptions, type CreateBraveWebSearchAdapterOptions, type CreateEditFileToolOptions, type CreateGenericHttpSearchAdapterOptions, type CreateGitDiffToolOptions, type CreateGlobToolOptions, type CreateListDirToolOptions, type CreateReadFileToolOptions, type CreateRunVitestToolOptions, type CreateSearchTextToolOptions, type CreateShellToolOptions, type CreateWebFetchToolOptions, type CreateWebSearchToolOptions, type CreateWriteFileToolOptions, DEFAULT_TOOL_GUIDANCE, type EnvContextOptions, type PlanModeTool, type PlanModeToolOptions, type PlanModeToolWithStore, type PlanNode, type QuestionTool, type QuestionToolOptions, ReadTracker, RedirectBlockedError, type RepoMapOptions, type ResolveAndScreenOptions, type ScreenedFetchOptions, type SessionArtifactStore, type SessionArtifactStoreOptions, SsrfBlockedError, type TodoItem, type TodolistTool, type ToolGuidanceMap, type TruncationOptions, type TruncationResult, type VitestSummary, type WebSearchCallback, type WebSearchResult, buildEnvContext, buildRepoMap, catastrophicShellReason, commandDenialReason, createApplyPatchTool, createBraveWebSearchAdapter, createEditFileTool, createGenericHttpSearchAdapter, createGitDiffTool, createGlobTool, createListDirTool, createPlanModeTool, createQuestionTool, createReadFileTool, createRunVitestTool, createSearchTextTool, createSessionArtifactStore, createShellTool, createTodolistTool, createWebFetchTool, createWebSearchTool, createWriteFileTool, denyCatastrophicCommands, formatCode, formatDiff, formatError, formatFileList, injectGuidance, isBlockedIp, isCommandAllowed, renderToolList, resolveAndScreen, screenedFetch, todoItemsToPlanNodes, truncateOutput, withDefaultGuidance, withDescription, withShellExitGuidance, withToolResultGuidance };
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import { safeFilenameForId, safePathJoin as safePathJoin$1 } from '@theokit/sdk/
|
|
|
8
8
|
import { spawn } from 'child_process';
|
|
9
9
|
import { lookup } from 'dns/promises';
|
|
10
10
|
import { isIP } from 'net';
|
|
11
|
+
import { resolveFilesystem, FilesystemSecurityError, FilesystemReadOnlyError, StaleFileError, FilesystemError, FileNotFoundError } from '@theokit/sdk/filesystem';
|
|
11
12
|
|
|
12
13
|
// src/apply-patch.ts
|
|
13
14
|
var PathTraversalError = class extends ConfigurationError {
|
|
@@ -1052,7 +1053,10 @@ function withToolResultGuidance(tool, guidance) {
|
|
|
1052
1053
|
name: tool.name,
|
|
1053
1054
|
description: tool.description,
|
|
1054
1055
|
inputSchema: tool.inputSchema,
|
|
1055
|
-
handler: async (input) =>
|
|
1056
|
+
handler: async (input) => {
|
|
1057
|
+
const out = await tool.handler(input);
|
|
1058
|
+
return typeof out === "string" ? injectGuidance(out, guidance) : out;
|
|
1059
|
+
}
|
|
1056
1060
|
};
|
|
1057
1061
|
}
|
|
1058
1062
|
function withDefaultGuidance(tool) {
|
|
@@ -1066,6 +1070,7 @@ function withShellExitGuidance(tool) {
|
|
|
1066
1070
|
inputSchema: tool.inputSchema,
|
|
1067
1071
|
handler: async (input) => {
|
|
1068
1072
|
const out = await tool.handler(input);
|
|
1073
|
+
if (typeof out !== "string") return out;
|
|
1069
1074
|
let parsed;
|
|
1070
1075
|
try {
|
|
1071
1076
|
parsed = JSON.parse(out);
|
|
@@ -1262,7 +1267,7 @@ function createQuestionTool(opts) {
|
|
|
1262
1267
|
var MAX_FILE_SIZE = 5 * 1024 * 1024;
|
|
1263
1268
|
var BINARY_PROBE_BYTES = 8 * 1024;
|
|
1264
1269
|
function createReadFileTool(opts) {
|
|
1265
|
-
const { projectRoot } = opts;
|
|
1270
|
+
const { projectRoot, readTracker } = opts;
|
|
1266
1271
|
return defineTool({
|
|
1267
1272
|
name: "read_file",
|
|
1268
1273
|
description: "Read a project-relative text file as UTF-8. ALWAYS read a file before you edit it (edit_file) or overwrite it (write_file), so your old_string / new content matches the real bytes exactly. Returns the WHOLE file (there is no offset or line-range parameter); to locate a symbol inside a large file, use search_text instead of re-reading. Refuses paths that escape the project root, sensitive files (.env, .git/, node_modules/, .theo/, lock files), and binary files (null byte in the first 8 KB); caps at 5 MB. Returns { ok, content, size } or { ok: false, error }.",
|
|
@@ -1278,7 +1283,11 @@ function createReadFileTool(opts) {
|
|
|
1278
1283
|
const opened = await openHandleSafe(boundary.absolutePath, path);
|
|
1279
1284
|
if ("error" in opened) return opened.error;
|
|
1280
1285
|
try {
|
|
1281
|
-
return await readContent(
|
|
1286
|
+
return await readContent(
|
|
1287
|
+
opened.handle,
|
|
1288
|
+
path,
|
|
1289
|
+
(mtimeMs) => readTracker?.record(path, mtimeMs)
|
|
1290
|
+
);
|
|
1282
1291
|
} finally {
|
|
1283
1292
|
await opened.handle.close();
|
|
1284
1293
|
}
|
|
@@ -1309,7 +1318,7 @@ async function openHandleSafe(absolutePath, path) {
|
|
|
1309
1318
|
throw err;
|
|
1310
1319
|
}
|
|
1311
1320
|
}
|
|
1312
|
-
async function readContent(handle, path) {
|
|
1321
|
+
async function readContent(handle, path, onRead) {
|
|
1313
1322
|
const stat2 = await handle.stat();
|
|
1314
1323
|
if (stat2.size > MAX_FILE_SIZE) {
|
|
1315
1324
|
return JSON.stringify({
|
|
@@ -1324,6 +1333,7 @@ async function readContent(handle, path) {
|
|
|
1324
1333
|
return JSON.stringify({ ok: false, error: "binary_file", path, size: stat2.size });
|
|
1325
1334
|
}
|
|
1326
1335
|
const content = await handle.readFile({ encoding: "utf-8" });
|
|
1336
|
+
onRead?.(stat2.mtimeMs);
|
|
1327
1337
|
return JSON.stringify({ ok: true, content, size: stat2.size });
|
|
1328
1338
|
}
|
|
1329
1339
|
async function isBinaryProbe(handle, size) {
|
|
@@ -1336,6 +1346,26 @@ async function isBinaryProbe(handle, size) {
|
|
|
1336
1346
|
}
|
|
1337
1347
|
return false;
|
|
1338
1348
|
}
|
|
1349
|
+
|
|
1350
|
+
// src/read-tracker.ts
|
|
1351
|
+
var ReadTracker = class {
|
|
1352
|
+
seen = /* @__PURE__ */ new Map();
|
|
1353
|
+
/** Record the mtime observed when `path` was read. */
|
|
1354
|
+
record(path, mtimeMs) {
|
|
1355
|
+
this.seen.set(path, mtimeMs);
|
|
1356
|
+
}
|
|
1357
|
+
/** The mtime last recorded for `path`, or `undefined` if never read. */
|
|
1358
|
+
expected(path) {
|
|
1359
|
+
return this.seen.get(path);
|
|
1360
|
+
}
|
|
1361
|
+
};
|
|
1362
|
+
function evaluateReadBeforeWrite(tracker, path, currentMtimeMs) {
|
|
1363
|
+
if (currentMtimeMs === null) return "ok";
|
|
1364
|
+
const recorded = tracker.expected(path);
|
|
1365
|
+
if (recorded === void 0) return "read_required";
|
|
1366
|
+
if (recorded !== currentMtimeMs) return "stale";
|
|
1367
|
+
return "ok";
|
|
1368
|
+
}
|
|
1339
1369
|
var DEFAULT_TIMEOUT_MS2 = 12e4;
|
|
1340
1370
|
var DEFAULT_MAX_STDOUT_BYTES2 = 10 * 1024 * 1024;
|
|
1341
1371
|
function createRunVitestTool(opts) {
|
|
@@ -1968,38 +1998,111 @@ function createGenericHttpSearchAdapter(opts = {}) {
|
|
|
1968
1998
|
}
|
|
1969
1999
|
var BINARY_PROBE_BYTES3 = 8 * 1024;
|
|
1970
2000
|
function createWriteFileTool(opts) {
|
|
1971
|
-
const { projectRoot } = opts;
|
|
2001
|
+
const { projectRoot, filesystem } = opts;
|
|
2002
|
+
if (opts.requireReadBeforeWrite && !opts.readTracker) {
|
|
2003
|
+
throw new Error(
|
|
2004
|
+
"createWriteFileTool: requireReadBeforeWrite is true but no readTracker was provided \u2014 pass the same ReadTracker instance to createReadFileTool and createWriteFileTool."
|
|
2005
|
+
);
|
|
2006
|
+
}
|
|
2007
|
+
const guard = opts.requireReadBeforeWrite ? opts.readTracker : void 0;
|
|
1972
2008
|
return defineTool({
|
|
1973
2009
|
name: "write_file",
|
|
1974
|
-
description: "Write UTF-8 content to a project-relative file, creating parent directories as needed. OVERWRITES any existing file at the path. Prefer editing an existing file with edit_file over rewriting it; use write_file to create a NEW file or fully replace a small one. If the file already exists, read_file it first so you do not discard content you have not seen. Refuses paths that escape the
|
|
2010
|
+
description: "Write UTF-8 content to a project-relative file, creating parent directories as needed. OVERWRITES any existing file at the path. Prefer editing an existing file with edit_file over rewriting it; use write_file to create a NEW file or fully replace a small one. If the file already exists, read_file it first so you do not discard content you have not seen. Refuses paths that escape the write root and sensitive files (.env, .git/, node_modules/, .theo/, lock files); the default local root also refuses binary-file overwrites. Returns { ok, path, bytes } or { ok: false, error }.",
|
|
1975
2011
|
inputSchema: z.object({
|
|
1976
2012
|
path: z.string().min(1).describe("Project-relative file path."),
|
|
1977
2013
|
content: z.string().describe("UTF-8 content to write.")
|
|
1978
2014
|
}),
|
|
1979
|
-
handler: async ({ path, content }) => {
|
|
2015
|
+
handler: async ({ path, content }, ctx) => {
|
|
1980
2016
|
if (isForbiddenPath(path)) {
|
|
1981
2017
|
return JSON.stringify({ ok: false, error: "forbidden_path", path });
|
|
1982
2018
|
}
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
assertNoSymlinkEscape(absolutePath, projectRoot);
|
|
1987
|
-
} catch (err) {
|
|
1988
|
-
if (err instanceof PathTraversalError || err instanceof ForbiddenPathError) {
|
|
1989
|
-
return JSON.stringify({ ok: false, error: "path_traversal", path });
|
|
1990
|
-
}
|
|
1991
|
-
throw err;
|
|
1992
|
-
}
|
|
1993
|
-
if (await isBinaryFile(absolutePath)) {
|
|
1994
|
-
return JSON.stringify({ ok: false, error: "binary_file", path });
|
|
2019
|
+
if (filesystem) {
|
|
2020
|
+
const backend = await resolveFilesystem(filesystem, ctx ?? {});
|
|
2021
|
+
return writeViaBackend(backend, path, content, guard);
|
|
1995
2022
|
}
|
|
1996
|
-
|
|
1997
|
-
await writeFile(absolutePath, content, "utf-8");
|
|
1998
|
-
const bytes = Buffer.byteLength(content, "utf-8");
|
|
1999
|
-
return JSON.stringify({ ok: true, path, bytes });
|
|
2023
|
+
return writeViaLocalFs(projectRoot, path, content, guard);
|
|
2000
2024
|
}
|
|
2001
2025
|
});
|
|
2002
2026
|
}
|
|
2027
|
+
function readBeforeWriteError(guard, path, currentMtimeMs) {
|
|
2028
|
+
if (!guard) return null;
|
|
2029
|
+
const decision = evaluateReadBeforeWrite(guard, path, currentMtimeMs);
|
|
2030
|
+
if (decision === "read_required")
|
|
2031
|
+
return JSON.stringify({ ok: false, error: "read_required", path });
|
|
2032
|
+
if (decision === "stale") return JSON.stringify({ ok: false, error: "stale_file", path });
|
|
2033
|
+
return null;
|
|
2034
|
+
}
|
|
2035
|
+
async function writeViaLocalFs(projectRoot, path, content, guard) {
|
|
2036
|
+
let absolutePath;
|
|
2037
|
+
try {
|
|
2038
|
+
absolutePath = safePathJoin(projectRoot, path);
|
|
2039
|
+
assertNoSymlinkEscape(absolutePath, projectRoot);
|
|
2040
|
+
} catch (err) {
|
|
2041
|
+
if (err instanceof PathTraversalError || err instanceof ForbiddenPathError) {
|
|
2042
|
+
return JSON.stringify({ ok: false, error: "path_traversal", path });
|
|
2043
|
+
}
|
|
2044
|
+
throw err;
|
|
2045
|
+
}
|
|
2046
|
+
const rbw = readBeforeWriteError(guard, path, await statMtimeOrNull(absolutePath));
|
|
2047
|
+
if (rbw) return rbw;
|
|
2048
|
+
if (await isBinaryFile(absolutePath)) {
|
|
2049
|
+
return JSON.stringify({ ok: false, error: "binary_file", path });
|
|
2050
|
+
}
|
|
2051
|
+
await mkdir(dirname(absolutePath), { recursive: true });
|
|
2052
|
+
await writeFile(absolutePath, content, "utf-8");
|
|
2053
|
+
const bytes = Buffer.byteLength(content, "utf-8");
|
|
2054
|
+
return JSON.stringify({ ok: true, path, bytes });
|
|
2055
|
+
}
|
|
2056
|
+
async function statMtimeOrNull(absolutePath) {
|
|
2057
|
+
try {
|
|
2058
|
+
return (await stat(absolutePath)).mtimeMs;
|
|
2059
|
+
} catch (err) {
|
|
2060
|
+
if (err.code === "ENOENT") return null;
|
|
2061
|
+
throw err;
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
async function backendMtimeOrNull(backend, path) {
|
|
2065
|
+
try {
|
|
2066
|
+
return (await backend.stat(path)).mtimeMs;
|
|
2067
|
+
} catch (err) {
|
|
2068
|
+
if (err instanceof FileNotFoundError) return null;
|
|
2069
|
+
throw err;
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
async function writeViaBackend(backend, path, content, guard) {
|
|
2073
|
+
try {
|
|
2074
|
+
let expectedMtime;
|
|
2075
|
+
if (guard) {
|
|
2076
|
+
const current = await backendMtimeOrNull(backend, path);
|
|
2077
|
+
const rbw = readBeforeWriteError(guard, path, current);
|
|
2078
|
+
if (rbw) return rbw;
|
|
2079
|
+
expectedMtime = current ?? void 0;
|
|
2080
|
+
}
|
|
2081
|
+
const stat2 = await backend.writeFile(
|
|
2082
|
+
path,
|
|
2083
|
+
content,
|
|
2084
|
+
expectedMtime !== void 0 ? { expectedMtime } : void 0
|
|
2085
|
+
);
|
|
2086
|
+
return JSON.stringify({ ok: true, path, bytes: stat2.size });
|
|
2087
|
+
} catch (err) {
|
|
2088
|
+
return backendErrorToJson(err, path);
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
function backendErrorToJson(err, path) {
|
|
2092
|
+
if (err instanceof FilesystemSecurityError) {
|
|
2093
|
+
return JSON.stringify({ ok: false, error: "path_traversal", path });
|
|
2094
|
+
}
|
|
2095
|
+
if (err instanceof FilesystemReadOnlyError) {
|
|
2096
|
+
return JSON.stringify({ ok: false, error: "read_only", path });
|
|
2097
|
+
}
|
|
2098
|
+
if (err instanceof StaleFileError) {
|
|
2099
|
+
return JSON.stringify({ ok: false, error: "stale_file", path });
|
|
2100
|
+
}
|
|
2101
|
+
if (err instanceof FilesystemError) {
|
|
2102
|
+
return JSON.stringify({ ok: false, error: "write_failed", path });
|
|
2103
|
+
}
|
|
2104
|
+
throw err;
|
|
2105
|
+
}
|
|
2003
2106
|
async function isBinaryFile(absolutePath) {
|
|
2004
2107
|
let handle;
|
|
2005
2108
|
try {
|
|
@@ -2022,6 +2125,6 @@ async function isBinaryFile(absolutePath) {
|
|
|
2022
2125
|
}
|
|
2023
2126
|
}
|
|
2024
2127
|
|
|
2025
|
-
export { CatastrophicCommandError, DEFAULT_TOOL_GUIDANCE, RedirectBlockedError, SsrfBlockedError, buildEnvContext, buildRepoMap, catastrophicShellReason, commandDenialReason, createApplyPatchTool, createBraveWebSearchAdapter, createEditFileTool, createGenericHttpSearchAdapter, createGitDiffTool, createGlobTool, createListDirTool, createPlanModeTool, createQuestionTool, createReadFileTool, createRunVitestTool, createSearchTextTool, createSessionArtifactStore, createShellTool, createTodolistTool, createWebFetchTool, createWebSearchTool, createWriteFileTool, denyCatastrophicCommands, formatCode, formatDiff, formatError, formatFileList, injectGuidance, isBlockedIp, isCommandAllowed, renderToolList, resolveAndScreen, screenedFetch, todoItemsToPlanNodes, truncateOutput, withDefaultGuidance, withDescription, withShellExitGuidance, withToolResultGuidance };
|
|
2128
|
+
export { CatastrophicCommandError, DEFAULT_TOOL_GUIDANCE, ReadTracker, RedirectBlockedError, SsrfBlockedError, buildEnvContext, buildRepoMap, catastrophicShellReason, commandDenialReason, createApplyPatchTool, createBraveWebSearchAdapter, createEditFileTool, createGenericHttpSearchAdapter, createGitDiffTool, createGlobTool, createListDirTool, createPlanModeTool, createQuestionTool, createReadFileTool, createRunVitestTool, createSearchTextTool, createSessionArtifactStore, createShellTool, createTodolistTool, createWebFetchTool, createWebSearchTool, createWriteFileTool, denyCatastrophicCommands, formatCode, formatDiff, formatError, formatFileList, injectGuidance, isBlockedIp, isCommandAllowed, renderToolList, resolveAndScreen, screenedFetch, todoItemsToPlanNodes, truncateOutput, withDefaultGuidance, withDescription, withShellExitGuidance, withToolResultGuidance };
|
|
2026
2129
|
//# sourceMappingURL=index.js.map
|
|
2027
2130
|
//# sourceMappingURL=index.js.map
|