@theokit/sdk-tools 0.8.0 → 0.9.1
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 +22 -0
- package/dist/index.cjs +216 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +63 -1
- package/dist/index.d.ts +63 -1
- package/dist/index.js +215 -45
- 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.
|
|
@@ -423,6 +424,13 @@ interface CreateListDirToolOptions {
|
|
|
423
424
|
projectRoot: string;
|
|
424
425
|
/** Maximum number of entries returned per call. Default 500. */
|
|
425
426
|
max?: number;
|
|
427
|
+
/**
|
|
428
|
+
* SE31 — optional pluggable filesystem backend (`@theokit/sdk/filesystem`), or
|
|
429
|
+
* a per-request resolver. When provided, listings route through the backend
|
|
430
|
+
* (its own boundary + `basePath`) instead of the local `projectRoot`; omitted
|
|
431
|
+
* ⇒ identical current behavior (multi-tenant / per-request roots).
|
|
432
|
+
*/
|
|
433
|
+
filesystem?: FilesystemProvider;
|
|
426
434
|
}
|
|
427
435
|
declare function createListDirTool(opts: CreateListDirToolOptions): CustomTool;
|
|
428
436
|
|
|
@@ -504,6 +512,25 @@ interface QuestionTool {
|
|
|
504
512
|
}
|
|
505
513
|
declare function createQuestionTool(opts: QuestionToolOptions): QuestionTool;
|
|
506
514
|
|
|
515
|
+
/**
|
|
516
|
+
* `ReadTracker` — SE32 read-before-write safety.
|
|
517
|
+
*
|
|
518
|
+
* A per-run map of `path → mtimeMs` recorded whenever the read tool reads a
|
|
519
|
+
* file. The write tool (with `requireReadBeforeWrite` on) consults it before an
|
|
520
|
+
* overwrite: a file that was never read, or that changed on disk since it was
|
|
521
|
+
* read, is refused instead of silently clobbered. Scope one tracker per run /
|
|
522
|
+
* session — it is deliberately NOT a global singleton (no cross-run leak).
|
|
523
|
+
*
|
|
524
|
+
* @public
|
|
525
|
+
*/
|
|
526
|
+
declare class ReadTracker {
|
|
527
|
+
private readonly seen;
|
|
528
|
+
/** Record the mtime observed when `path` was read. */
|
|
529
|
+
record(path: string, mtimeMs: number): void;
|
|
530
|
+
/** The mtime last recorded for `path`, or `undefined` if never read. */
|
|
531
|
+
expected(path: string): number | undefined;
|
|
532
|
+
}
|
|
533
|
+
|
|
507
534
|
/**
|
|
508
535
|
* `read_file` — built-in tool for coding agents.
|
|
509
536
|
*
|
|
@@ -533,6 +560,19 @@ declare function createQuestionTool(opts: QuestionToolOptions): QuestionTool;
|
|
|
533
560
|
interface CreateReadFileToolOptions {
|
|
534
561
|
/** Absolute path to the project root. Every read is gated against this boundary. */
|
|
535
562
|
projectRoot: string;
|
|
563
|
+
/**
|
|
564
|
+
* SE32 — optional read-before-write tracker. When provided, a successful read
|
|
565
|
+
* records the file's mtime so a paired `write_file` (with
|
|
566
|
+
* `requireReadBeforeWrite`) can refuse a blind or stale overwrite.
|
|
567
|
+
*/
|
|
568
|
+
readTracker?: ReadTracker;
|
|
569
|
+
/**
|
|
570
|
+
* SE31 — optional pluggable filesystem backend (`@theokit/sdk/filesystem`), or
|
|
571
|
+
* a per-request resolver. When provided, reads route through the backend (its
|
|
572
|
+
* own boundary + `basePath`) instead of the local `projectRoot`; omitted ⇒
|
|
573
|
+
* identical current behavior (multi-tenant / per-request roots).
|
|
574
|
+
*/
|
|
575
|
+
filesystem?: FilesystemProvider;
|
|
536
576
|
}
|
|
537
577
|
declare function createReadFileTool(opts: CreateReadFileToolOptions): CustomTool;
|
|
538
578
|
|
|
@@ -862,10 +902,32 @@ declare function createGenericHttpSearchAdapter(opts?: CreateGenericHttpSearchAd
|
|
|
862
902
|
* 'binary_file' }` on refusal
|
|
863
903
|
*/
|
|
864
904
|
|
|
905
|
+
/** The run-scoped context a `defineTool` handler receives as its 2nd argument. */
|
|
906
|
+
type WriteToolContext = {
|
|
907
|
+
signal?: AbortSignal;
|
|
908
|
+
context?: unknown;
|
|
909
|
+
};
|
|
865
910
|
interface CreateWriteFileToolOptions {
|
|
866
911
|
/** Absolute path to the project root. Every write is gated against this boundary. */
|
|
867
912
|
projectRoot: string;
|
|
913
|
+
/**
|
|
914
|
+
* SE31 — optional pluggable filesystem backend (`@theokit/sdk/filesystem`), or
|
|
915
|
+
* a per-request resolver `(ctx) => FilesystemBackend`. When provided, writes
|
|
916
|
+
* route through it (its own boundary + `readOnly` + per-request root) instead
|
|
917
|
+
* of the local project fs. Omitted ⇒ identical current behavior (local
|
|
918
|
+
* `projectRoot`). The `.env`/`.git` policy still applies (storage-independent).
|
|
919
|
+
*/
|
|
920
|
+
filesystem?: FilesystemProvider<WriteToolContext>;
|
|
921
|
+
/**
|
|
922
|
+
* SE32 — when true (and a {@link ReadTracker} is supplied), refuse to
|
|
923
|
+
* overwrite an existing file that was not read first, or that changed on disk
|
|
924
|
+
* since it was read (`read_required` / `stale_file`). A NEW file writes
|
|
925
|
+
* freely. Default OFF (unchanged behavior).
|
|
926
|
+
*/
|
|
927
|
+
requireReadBeforeWrite?: boolean;
|
|
928
|
+
/** SE32 — the per-run tracker populated by the paired `read_file` tool. */
|
|
929
|
+
readTracker?: ReadTracker;
|
|
868
930
|
}
|
|
869
931
|
declare function createWriteFileTool(opts: CreateWriteFileToolOptions): CustomTool;
|
|
870
932
|
|
|
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 };
|
|
933
|
+
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.
|
|
@@ -423,6 +424,13 @@ interface CreateListDirToolOptions {
|
|
|
423
424
|
projectRoot: string;
|
|
424
425
|
/** Maximum number of entries returned per call. Default 500. */
|
|
425
426
|
max?: number;
|
|
427
|
+
/**
|
|
428
|
+
* SE31 — optional pluggable filesystem backend (`@theokit/sdk/filesystem`), or
|
|
429
|
+
* a per-request resolver. When provided, listings route through the backend
|
|
430
|
+
* (its own boundary + `basePath`) instead of the local `projectRoot`; omitted
|
|
431
|
+
* ⇒ identical current behavior (multi-tenant / per-request roots).
|
|
432
|
+
*/
|
|
433
|
+
filesystem?: FilesystemProvider;
|
|
426
434
|
}
|
|
427
435
|
declare function createListDirTool(opts: CreateListDirToolOptions): CustomTool;
|
|
428
436
|
|
|
@@ -504,6 +512,25 @@ interface QuestionTool {
|
|
|
504
512
|
}
|
|
505
513
|
declare function createQuestionTool(opts: QuestionToolOptions): QuestionTool;
|
|
506
514
|
|
|
515
|
+
/**
|
|
516
|
+
* `ReadTracker` — SE32 read-before-write safety.
|
|
517
|
+
*
|
|
518
|
+
* A per-run map of `path → mtimeMs` recorded whenever the read tool reads a
|
|
519
|
+
* file. The write tool (with `requireReadBeforeWrite` on) consults it before an
|
|
520
|
+
* overwrite: a file that was never read, or that changed on disk since it was
|
|
521
|
+
* read, is refused instead of silently clobbered. Scope one tracker per run /
|
|
522
|
+
* session — it is deliberately NOT a global singleton (no cross-run leak).
|
|
523
|
+
*
|
|
524
|
+
* @public
|
|
525
|
+
*/
|
|
526
|
+
declare class ReadTracker {
|
|
527
|
+
private readonly seen;
|
|
528
|
+
/** Record the mtime observed when `path` was read. */
|
|
529
|
+
record(path: string, mtimeMs: number): void;
|
|
530
|
+
/** The mtime last recorded for `path`, or `undefined` if never read. */
|
|
531
|
+
expected(path: string): number | undefined;
|
|
532
|
+
}
|
|
533
|
+
|
|
507
534
|
/**
|
|
508
535
|
* `read_file` — built-in tool for coding agents.
|
|
509
536
|
*
|
|
@@ -533,6 +560,19 @@ declare function createQuestionTool(opts: QuestionToolOptions): QuestionTool;
|
|
|
533
560
|
interface CreateReadFileToolOptions {
|
|
534
561
|
/** Absolute path to the project root. Every read is gated against this boundary. */
|
|
535
562
|
projectRoot: string;
|
|
563
|
+
/**
|
|
564
|
+
* SE32 — optional read-before-write tracker. When provided, a successful read
|
|
565
|
+
* records the file's mtime so a paired `write_file` (with
|
|
566
|
+
* `requireReadBeforeWrite`) can refuse a blind or stale overwrite.
|
|
567
|
+
*/
|
|
568
|
+
readTracker?: ReadTracker;
|
|
569
|
+
/**
|
|
570
|
+
* SE31 — optional pluggable filesystem backend (`@theokit/sdk/filesystem`), or
|
|
571
|
+
* a per-request resolver. When provided, reads route through the backend (its
|
|
572
|
+
* own boundary + `basePath`) instead of the local `projectRoot`; omitted ⇒
|
|
573
|
+
* identical current behavior (multi-tenant / per-request roots).
|
|
574
|
+
*/
|
|
575
|
+
filesystem?: FilesystemProvider;
|
|
536
576
|
}
|
|
537
577
|
declare function createReadFileTool(opts: CreateReadFileToolOptions): CustomTool;
|
|
538
578
|
|
|
@@ -862,10 +902,32 @@ declare function createGenericHttpSearchAdapter(opts?: CreateGenericHttpSearchAd
|
|
|
862
902
|
* 'binary_file' }` on refusal
|
|
863
903
|
*/
|
|
864
904
|
|
|
905
|
+
/** The run-scoped context a `defineTool` handler receives as its 2nd argument. */
|
|
906
|
+
type WriteToolContext = {
|
|
907
|
+
signal?: AbortSignal;
|
|
908
|
+
context?: unknown;
|
|
909
|
+
};
|
|
865
910
|
interface CreateWriteFileToolOptions {
|
|
866
911
|
/** Absolute path to the project root. Every write is gated against this boundary. */
|
|
867
912
|
projectRoot: string;
|
|
913
|
+
/**
|
|
914
|
+
* SE31 — optional pluggable filesystem backend (`@theokit/sdk/filesystem`), or
|
|
915
|
+
* a per-request resolver `(ctx) => FilesystemBackend`. When provided, writes
|
|
916
|
+
* route through it (its own boundary + `readOnly` + per-request root) instead
|
|
917
|
+
* of the local project fs. Omitted ⇒ identical current behavior (local
|
|
918
|
+
* `projectRoot`). The `.env`/`.git` policy still applies (storage-independent).
|
|
919
|
+
*/
|
|
920
|
+
filesystem?: FilesystemProvider<WriteToolContext>;
|
|
921
|
+
/**
|
|
922
|
+
* SE32 — when true (and a {@link ReadTracker} is supplied), refuse to
|
|
923
|
+
* overwrite an existing file that was not read first, or that changed on disk
|
|
924
|
+
* since it was read (`read_required` / `stale_file`). A NEW file writes
|
|
925
|
+
* freely. Default OFF (unchanged behavior).
|
|
926
|
+
*/
|
|
927
|
+
requireReadBeforeWrite?: boolean;
|
|
928
|
+
/** SE32 — the per-run tracker populated by the paired `read_file` tool. */
|
|
929
|
+
readTracker?: ReadTracker;
|
|
868
930
|
}
|
|
869
931
|
declare function createWriteFileTool(opts: CreateWriteFileToolOptions): CustomTool;
|
|
870
932
|
|
|
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 };
|
|
933
|
+
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFile, copyFile, mkdir, writeFile, readdir, open, stat } from 'fs/promises';
|
|
2
2
|
import { dirname, join, relative, resolve, sep } from 'path';
|
|
3
|
-
import {
|
|
3
|
+
import { Tool, ConfigurationError } from '@theokit/sdk';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { existsSync, statSync, mkdirSync, writeFileSync, realpathSync, readFileSync, lstatSync, readlinkSync, readdirSync } from 'fs';
|
|
6
6
|
import { replaceFileAtomic } from '@theokit/sdk/internal/persistence';
|
|
@@ -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, FileNotFoundError, FilesystemSecurityError, FilesystemReadOnlyError, StaleFileError, FilesystemError } from '@theokit/sdk/filesystem';
|
|
11
12
|
|
|
12
13
|
// src/apply-patch.ts
|
|
13
14
|
var PathTraversalError = class extends ConfigurationError {
|
|
@@ -102,7 +103,7 @@ function isForbiddenPath(input) {
|
|
|
102
103
|
// src/apply-patch.ts
|
|
103
104
|
function createApplyPatchTool(opts) {
|
|
104
105
|
const { projectRoot } = opts;
|
|
105
|
-
return
|
|
106
|
+
return Tool.create({
|
|
106
107
|
name: "apply_patch",
|
|
107
108
|
description: "Apply a unified diff patch to project files. Each file in the diff is security-checked against the project root. Creates .bak backups before modifying. Returns { ok, files_patched } or { ok: false, error }.",
|
|
108
109
|
inputSchema: z.object({
|
|
@@ -264,7 +265,7 @@ function createSessionArtifactStore(options) {
|
|
|
264
265
|
}
|
|
265
266
|
function createEditFileTool(opts) {
|
|
266
267
|
const { projectRoot } = opts;
|
|
267
|
-
return
|
|
268
|
+
return Tool.create({
|
|
268
269
|
name: "edit_file",
|
|
269
270
|
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 }.",
|
|
270
271
|
inputSchema: z.object({
|
|
@@ -441,7 +442,7 @@ function createGitDiffTool(opts) {
|
|
|
441
442
|
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
442
443
|
maxStdoutBytes = DEFAULT_MAX_STDOUT_BYTES
|
|
443
444
|
} = opts;
|
|
444
|
-
return
|
|
445
|
+
return Tool.create({
|
|
445
446
|
name: "git_diff",
|
|
446
447
|
description: "Return the unified diff of the project's working tree (or staged changes when cached=true). Scoped to a single file when 'path' is provided. Requires the project to be a git repository. Returns { ok, diff, truncated? } or { ok: false, error }.",
|
|
447
448
|
inputSchema: z.object({
|
|
@@ -523,7 +524,7 @@ function runGitProcess(cwd, args, timeoutMs, maxStdoutBytes) {
|
|
|
523
524
|
var DEFAULT_EXCLUDES = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".theo"]);
|
|
524
525
|
function createGlobTool(opts) {
|
|
525
526
|
const { projectRoot } = opts;
|
|
526
|
-
return
|
|
527
|
+
return Tool.create({
|
|
527
528
|
name: "glob_files",
|
|
528
529
|
description: "Find files by glob pattern across the project \u2014 fast at any repo size. Use glob_files when you know the filename SHAPE; use search_text when you know the file CONTENT; use read_file when you know the exact path. The pattern supports * and ** wildcards (e.g. '**/*.ts', 'src/**/*.json'); node_modules/.git/dist/.theo are excluded and results are relative paths. Returns { ok, files } or { ok: false, error }.",
|
|
529
530
|
inputSchema: z.object({
|
|
@@ -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);
|
|
@@ -1082,26 +1087,61 @@ function withShellExitGuidance(tool) {
|
|
|
1082
1087
|
}
|
|
1083
1088
|
var DEFAULT_MAX_ENTRIES = 500;
|
|
1084
1089
|
function createListDirTool(opts) {
|
|
1085
|
-
const { projectRoot, max = DEFAULT_MAX_ENTRIES } = opts;
|
|
1086
|
-
return
|
|
1090
|
+
const { projectRoot, max = DEFAULT_MAX_ENTRIES, filesystem } = opts;
|
|
1091
|
+
return Tool.create({
|
|
1087
1092
|
name: "list_dir",
|
|
1088
1093
|
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.`,
|
|
1089
1094
|
inputSchema: z.object({
|
|
1090
1095
|
path: z.string().min(1).describe("Project-relative directory path. Use '.' for root.")
|
|
1091
1096
|
}),
|
|
1092
|
-
handler: async ({ path }) => {
|
|
1097
|
+
handler: async ({ path }, ctx) => {
|
|
1093
1098
|
const relative2 = path === "" || path === "." ? "." : path;
|
|
1094
1099
|
if (relative2 !== "." && isForbiddenPath(relative2)) {
|
|
1095
1100
|
return JSON.stringify({ ok: false, error: "forbidden_path", path });
|
|
1096
1101
|
}
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
return
|
|
1102
|
+
if (filesystem) {
|
|
1103
|
+
const backend = await resolveFilesystem(filesystem, ctx ?? {});
|
|
1104
|
+
return listViaBackend(backend, relative2, path, max);
|
|
1105
|
+
}
|
|
1106
|
+
return listViaLocalFs(projectRoot, relative2, path, max);
|
|
1102
1107
|
}
|
|
1103
1108
|
});
|
|
1104
1109
|
}
|
|
1110
|
+
async function listViaLocalFs(projectRoot, relative2, originalPath, max) {
|
|
1111
|
+
const boundary = resolveDirBoundary(relative2, projectRoot, originalPath);
|
|
1112
|
+
if ("error" in boundary) return boundary.error;
|
|
1113
|
+
const readResult = await readDirSafe(boundary.absolutePath, originalPath);
|
|
1114
|
+
if ("error" in readResult) return readResult.error;
|
|
1115
|
+
return formatListing(readResult.dirents, max);
|
|
1116
|
+
}
|
|
1117
|
+
async function listViaBackend(backend, relative2, originalPath, max) {
|
|
1118
|
+
let names;
|
|
1119
|
+
try {
|
|
1120
|
+
names = await backend.list(relative2);
|
|
1121
|
+
} catch (err) {
|
|
1122
|
+
if (err instanceof FileNotFoundError) {
|
|
1123
|
+
return JSON.stringify({ ok: false, error: "not_found", path: originalPath });
|
|
1124
|
+
}
|
|
1125
|
+
if (err instanceof FilesystemSecurityError) {
|
|
1126
|
+
return JSON.stringify({ ok: false, error: "path_traversal", path: originalPath });
|
|
1127
|
+
}
|
|
1128
|
+
throw err;
|
|
1129
|
+
}
|
|
1130
|
+
const totalCount = names.length;
|
|
1131
|
+
const windowed = names.slice(0, max);
|
|
1132
|
+
const entries = await Promise.all(
|
|
1133
|
+
windowed.map(async (name) => {
|
|
1134
|
+
const child = relative2 === "." ? name : `${relative2}/${name}`;
|
|
1135
|
+
let type = "file";
|
|
1136
|
+
try {
|
|
1137
|
+
type = (await backend.stat(child)).isDirectory ? "directory" : "file";
|
|
1138
|
+
} catch {
|
|
1139
|
+
}
|
|
1140
|
+
return { name, type };
|
|
1141
|
+
})
|
|
1142
|
+
);
|
|
1143
|
+
return JSON.stringify({ ok: true, entries, truncated: totalCount > max, totalCount });
|
|
1144
|
+
}
|
|
1105
1145
|
function resolveDirBoundary(relative2, projectRoot, originalPath) {
|
|
1106
1146
|
try {
|
|
1107
1147
|
const absolutePath = relative2 === "." ? projectRoot : safePathJoin(projectRoot, relative2);
|
|
@@ -1262,29 +1302,65 @@ function createQuestionTool(opts) {
|
|
|
1262
1302
|
var MAX_FILE_SIZE = 5 * 1024 * 1024;
|
|
1263
1303
|
var BINARY_PROBE_BYTES = 8 * 1024;
|
|
1264
1304
|
function createReadFileTool(opts) {
|
|
1265
|
-
const { projectRoot } = opts;
|
|
1266
|
-
return
|
|
1305
|
+
const { projectRoot, readTracker, filesystem } = opts;
|
|
1306
|
+
return Tool.create({
|
|
1267
1307
|
name: "read_file",
|
|
1268
1308
|
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 }.",
|
|
1269
1309
|
inputSchema: z.object({
|
|
1270
1310
|
path: z.string().min(1).describe("Project-relative file path.")
|
|
1271
1311
|
}),
|
|
1272
|
-
handler: async ({ path }) => {
|
|
1312
|
+
handler: async ({ path }, ctx) => {
|
|
1273
1313
|
if (isForbiddenPath(path)) {
|
|
1274
1314
|
return JSON.stringify({ ok: false, error: "forbidden_path", path });
|
|
1275
1315
|
}
|
|
1316
|
+
if (filesystem) {
|
|
1317
|
+
const backend = await resolveFilesystem(filesystem, ctx ?? {});
|
|
1318
|
+
return readViaBackend(backend, path, (mtimeMs) => readTracker?.record(path, mtimeMs));
|
|
1319
|
+
}
|
|
1276
1320
|
const boundary = resolveBoundary(path, projectRoot);
|
|
1277
1321
|
if ("error" in boundary) return boundary.error;
|
|
1278
1322
|
const opened = await openHandleSafe(boundary.absolutePath, path);
|
|
1279
1323
|
if ("error" in opened) return opened.error;
|
|
1280
1324
|
try {
|
|
1281
|
-
return await readContent(
|
|
1325
|
+
return await readContent(
|
|
1326
|
+
opened.handle,
|
|
1327
|
+
path,
|
|
1328
|
+
(mtimeMs) => readTracker?.record(path, mtimeMs)
|
|
1329
|
+
);
|
|
1282
1330
|
} finally {
|
|
1283
1331
|
await opened.handle.close();
|
|
1284
1332
|
}
|
|
1285
1333
|
}
|
|
1286
1334
|
});
|
|
1287
1335
|
}
|
|
1336
|
+
async function readViaBackend(backend, path, onRead) {
|
|
1337
|
+
try {
|
|
1338
|
+
const stat2 = await backend.stat(path);
|
|
1339
|
+
if (stat2.size > MAX_FILE_SIZE) {
|
|
1340
|
+
return JSON.stringify({
|
|
1341
|
+
ok: false,
|
|
1342
|
+
error: "too_large",
|
|
1343
|
+
path,
|
|
1344
|
+
size: stat2.size,
|
|
1345
|
+
limit: MAX_FILE_SIZE
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
const content = await backend.readFile(path);
|
|
1349
|
+
if (content.includes("\0")) {
|
|
1350
|
+
return JSON.stringify({ ok: false, error: "binary_file", path, size: stat2.size });
|
|
1351
|
+
}
|
|
1352
|
+
onRead?.(stat2.mtimeMs);
|
|
1353
|
+
return JSON.stringify({ ok: true, content, size: stat2.size });
|
|
1354
|
+
} catch (err) {
|
|
1355
|
+
if (err instanceof FileNotFoundError) {
|
|
1356
|
+
return JSON.stringify({ ok: false, error: "not_found", path });
|
|
1357
|
+
}
|
|
1358
|
+
if (err instanceof FilesystemSecurityError) {
|
|
1359
|
+
return JSON.stringify({ ok: false, error: "path_traversal", path });
|
|
1360
|
+
}
|
|
1361
|
+
throw err;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1288
1364
|
function resolveBoundary(path, projectRoot) {
|
|
1289
1365
|
try {
|
|
1290
1366
|
const absolutePath = safePathJoin(projectRoot, path);
|
|
@@ -1309,7 +1385,7 @@ async function openHandleSafe(absolutePath, path) {
|
|
|
1309
1385
|
throw err;
|
|
1310
1386
|
}
|
|
1311
1387
|
}
|
|
1312
|
-
async function readContent(handle, path) {
|
|
1388
|
+
async function readContent(handle, path, onRead) {
|
|
1313
1389
|
const stat2 = await handle.stat();
|
|
1314
1390
|
if (stat2.size > MAX_FILE_SIZE) {
|
|
1315
1391
|
return JSON.stringify({
|
|
@@ -1324,6 +1400,7 @@ async function readContent(handle, path) {
|
|
|
1324
1400
|
return JSON.stringify({ ok: false, error: "binary_file", path, size: stat2.size });
|
|
1325
1401
|
}
|
|
1326
1402
|
const content = await handle.readFile({ encoding: "utf-8" });
|
|
1403
|
+
onRead?.(stat2.mtimeMs);
|
|
1327
1404
|
return JSON.stringify({ ok: true, content, size: stat2.size });
|
|
1328
1405
|
}
|
|
1329
1406
|
async function isBinaryProbe(handle, size) {
|
|
@@ -1336,6 +1413,26 @@ async function isBinaryProbe(handle, size) {
|
|
|
1336
1413
|
}
|
|
1337
1414
|
return false;
|
|
1338
1415
|
}
|
|
1416
|
+
|
|
1417
|
+
// src/read-tracker.ts
|
|
1418
|
+
var ReadTracker = class {
|
|
1419
|
+
seen = /* @__PURE__ */ new Map();
|
|
1420
|
+
/** Record the mtime observed when `path` was read. */
|
|
1421
|
+
record(path, mtimeMs) {
|
|
1422
|
+
this.seen.set(path, mtimeMs);
|
|
1423
|
+
}
|
|
1424
|
+
/** The mtime last recorded for `path`, or `undefined` if never read. */
|
|
1425
|
+
expected(path) {
|
|
1426
|
+
return this.seen.get(path);
|
|
1427
|
+
}
|
|
1428
|
+
};
|
|
1429
|
+
function evaluateReadBeforeWrite(tracker, path, currentMtimeMs) {
|
|
1430
|
+
if (currentMtimeMs === null) return "ok";
|
|
1431
|
+
const recorded = tracker.expected(path);
|
|
1432
|
+
if (recorded === void 0) return "read_required";
|
|
1433
|
+
if (recorded !== currentMtimeMs) return "stale";
|
|
1434
|
+
return "ok";
|
|
1435
|
+
}
|
|
1339
1436
|
var DEFAULT_TIMEOUT_MS2 = 12e4;
|
|
1340
1437
|
var DEFAULT_MAX_STDOUT_BYTES2 = 10 * 1024 * 1024;
|
|
1341
1438
|
function createRunVitestTool(opts) {
|
|
@@ -1344,7 +1441,7 @@ function createRunVitestTool(opts) {
|
|
|
1344
1441
|
timeoutMs = DEFAULT_TIMEOUT_MS2,
|
|
1345
1442
|
maxStdoutBytes = DEFAULT_MAX_STDOUT_BYTES2
|
|
1346
1443
|
} = opts;
|
|
1347
|
-
return
|
|
1444
|
+
return Tool.create({
|
|
1348
1445
|
name: "run_vitest",
|
|
1349
1446
|
description: "Run the project's vitest suite, optionally scoped to a file or pattern via 'path'. Returns parsed { ok, summary } or { ok: false, error }. Vitest stdout warnings are stripped \u2014 the parser extracts the trailing JSON report.",
|
|
1350
1447
|
inputSchema: z.object({
|
|
@@ -1453,7 +1550,7 @@ function createSearchTextTool(opts) {
|
|
|
1453
1550
|
maxMatches = DEFAULT_MAX_MATCHES,
|
|
1454
1551
|
maxFileSize = DEFAULT_MAX_FILE_SIZE
|
|
1455
1552
|
} = opts;
|
|
1456
|
-
return
|
|
1553
|
+
return Tool.create({
|
|
1457
1554
|
name: "search_text",
|
|
1458
1555
|
description: `Search file CONTENTS for a LITERAL, CASE-SENSITIVE query across the project tree (the query is matched as a substring, not a regex). 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 }.`,
|
|
1459
1556
|
inputSchema: z.object({
|
|
@@ -1564,7 +1661,7 @@ var MAX_TIMEOUT_MS = 3e5;
|
|
|
1564
1661
|
var MAX_OUTPUT_BYTES = 5 * 1024 * 1024;
|
|
1565
1662
|
function createShellTool(opts) {
|
|
1566
1663
|
const { projectRoot, defaultTimeoutMs = DEFAULT_TIMEOUT_MS3, allowCatastrophic = false } = opts;
|
|
1567
|
-
return
|
|
1664
|
+
return Tool.create({
|
|
1568
1665
|
name: "shell_exec",
|
|
1569
1666
|
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 }.",
|
|
1570
1667
|
inputSchema: z.object({
|
|
@@ -1801,7 +1898,7 @@ function createWebFetchTool(opts) {
|
|
|
1801
1898
|
const maxRedirects = opts?.maxRedirects;
|
|
1802
1899
|
const fetchImpl = opts?.fetchImpl;
|
|
1803
1900
|
const lookup = opts?.lookup;
|
|
1804
|
-
return
|
|
1901
|
+
return Tool.create({
|
|
1805
1902
|
name: "web_fetch",
|
|
1806
1903
|
description: "Fetch the contents of a URL via HTTP/HTTPS. Use only for URLs the user provided or that you are confident help with the task; never invent or guess URLs. Rejects non-http(s) URLs and is SSRF-guarded by default (private/loopback/link-local/cloud-metadata hosts are refused with an ssrf_blocked error). The response body is capped at 1 MB. Returns { ok, content, status_code, content_type } or { ok: false, error }.",
|
|
1807
1904
|
inputSchema: z.object({
|
|
@@ -1888,7 +1985,7 @@ function createWebFetchTool(opts) {
|
|
|
1888
1985
|
}
|
|
1889
1986
|
function createWebSearchTool(opts) {
|
|
1890
1987
|
const { search, defaultMaxResults = 5 } = opts;
|
|
1891
|
-
return
|
|
1988
|
+
return Tool.create({
|
|
1892
1989
|
name: "web_search",
|
|
1893
1990
|
description: "Search the web for a query \u2014 use when you need current information beyond the repo or your training cutoff (library docs, an error message, an API). Returns a list of results with title, URL, and snippet; follow up with web_fetch on a promising result to read it in full. The search provider is injected by the consumer. Returns { ok, results } or { ok: false, error }.",
|
|
1894
1991
|
inputSchema: z.object({
|
|
@@ -1968,38 +2065,111 @@ function createGenericHttpSearchAdapter(opts = {}) {
|
|
|
1968
2065
|
}
|
|
1969
2066
|
var BINARY_PROBE_BYTES3 = 8 * 1024;
|
|
1970
2067
|
function createWriteFileTool(opts) {
|
|
1971
|
-
const { projectRoot } = opts;
|
|
1972
|
-
|
|
2068
|
+
const { projectRoot, filesystem } = opts;
|
|
2069
|
+
if (opts.requireReadBeforeWrite && !opts.readTracker) {
|
|
2070
|
+
throw new Error(
|
|
2071
|
+
"createWriteFileTool: requireReadBeforeWrite is true but no readTracker was provided \u2014 pass the same ReadTracker instance to createReadFileTool and createWriteFileTool."
|
|
2072
|
+
);
|
|
2073
|
+
}
|
|
2074
|
+
const guard = opts.requireReadBeforeWrite ? opts.readTracker : void 0;
|
|
2075
|
+
return Tool.create({
|
|
1973
2076
|
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
|
|
2077
|
+
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
2078
|
inputSchema: z.object({
|
|
1976
2079
|
path: z.string().min(1).describe("Project-relative file path."),
|
|
1977
2080
|
content: z.string().describe("UTF-8 content to write.")
|
|
1978
2081
|
}),
|
|
1979
|
-
handler: async ({ path, content }) => {
|
|
2082
|
+
handler: async ({ path, content }, ctx) => {
|
|
1980
2083
|
if (isForbiddenPath(path)) {
|
|
1981
2084
|
return JSON.stringify({ ok: false, error: "forbidden_path", path });
|
|
1982
2085
|
}
|
|
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 });
|
|
2086
|
+
if (filesystem) {
|
|
2087
|
+
const backend = await resolveFilesystem(filesystem, ctx ?? {});
|
|
2088
|
+
return writeViaBackend(backend, path, content, guard);
|
|
1995
2089
|
}
|
|
1996
|
-
|
|
1997
|
-
await writeFile(absolutePath, content, "utf-8");
|
|
1998
|
-
const bytes = Buffer.byteLength(content, "utf-8");
|
|
1999
|
-
return JSON.stringify({ ok: true, path, bytes });
|
|
2090
|
+
return writeViaLocalFs(projectRoot, path, content, guard);
|
|
2000
2091
|
}
|
|
2001
2092
|
});
|
|
2002
2093
|
}
|
|
2094
|
+
function readBeforeWriteError(guard, path, currentMtimeMs) {
|
|
2095
|
+
if (!guard) return null;
|
|
2096
|
+
const decision = evaluateReadBeforeWrite(guard, path, currentMtimeMs);
|
|
2097
|
+
if (decision === "read_required")
|
|
2098
|
+
return JSON.stringify({ ok: false, error: "read_required", path });
|
|
2099
|
+
if (decision === "stale") return JSON.stringify({ ok: false, error: "stale_file", path });
|
|
2100
|
+
return null;
|
|
2101
|
+
}
|
|
2102
|
+
async function writeViaLocalFs(projectRoot, path, content, guard) {
|
|
2103
|
+
let absolutePath;
|
|
2104
|
+
try {
|
|
2105
|
+
absolutePath = safePathJoin(projectRoot, path);
|
|
2106
|
+
assertNoSymlinkEscape(absolutePath, projectRoot);
|
|
2107
|
+
} catch (err) {
|
|
2108
|
+
if (err instanceof PathTraversalError || err instanceof ForbiddenPathError) {
|
|
2109
|
+
return JSON.stringify({ ok: false, error: "path_traversal", path });
|
|
2110
|
+
}
|
|
2111
|
+
throw err;
|
|
2112
|
+
}
|
|
2113
|
+
const rbw = readBeforeWriteError(guard, path, await statMtimeOrNull(absolutePath));
|
|
2114
|
+
if (rbw) return rbw;
|
|
2115
|
+
if (await isBinaryFile(absolutePath)) {
|
|
2116
|
+
return JSON.stringify({ ok: false, error: "binary_file", path });
|
|
2117
|
+
}
|
|
2118
|
+
await mkdir(dirname(absolutePath), { recursive: true });
|
|
2119
|
+
await writeFile(absolutePath, content, "utf-8");
|
|
2120
|
+
const bytes = Buffer.byteLength(content, "utf-8");
|
|
2121
|
+
return JSON.stringify({ ok: true, path, bytes });
|
|
2122
|
+
}
|
|
2123
|
+
async function statMtimeOrNull(absolutePath) {
|
|
2124
|
+
try {
|
|
2125
|
+
return (await stat(absolutePath)).mtimeMs;
|
|
2126
|
+
} catch (err) {
|
|
2127
|
+
if (err.code === "ENOENT") return null;
|
|
2128
|
+
throw err;
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
async function backendMtimeOrNull(backend, path) {
|
|
2132
|
+
try {
|
|
2133
|
+
return (await backend.stat(path)).mtimeMs;
|
|
2134
|
+
} catch (err) {
|
|
2135
|
+
if (err instanceof FileNotFoundError) return null;
|
|
2136
|
+
throw err;
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
async function writeViaBackend(backend, path, content, guard) {
|
|
2140
|
+
try {
|
|
2141
|
+
let expectedMtime;
|
|
2142
|
+
if (guard) {
|
|
2143
|
+
const current = await backendMtimeOrNull(backend, path);
|
|
2144
|
+
const rbw = readBeforeWriteError(guard, path, current);
|
|
2145
|
+
if (rbw) return rbw;
|
|
2146
|
+
expectedMtime = current ?? void 0;
|
|
2147
|
+
}
|
|
2148
|
+
const stat2 = await backend.writeFile(
|
|
2149
|
+
path,
|
|
2150
|
+
content,
|
|
2151
|
+
expectedMtime !== void 0 ? { expectedMtime } : void 0
|
|
2152
|
+
);
|
|
2153
|
+
return JSON.stringify({ ok: true, path, bytes: stat2.size });
|
|
2154
|
+
} catch (err) {
|
|
2155
|
+
return backendErrorToJson(err, path);
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
function backendErrorToJson(err, path) {
|
|
2159
|
+
if (err instanceof FilesystemSecurityError) {
|
|
2160
|
+
return JSON.stringify({ ok: false, error: "path_traversal", path });
|
|
2161
|
+
}
|
|
2162
|
+
if (err instanceof FilesystemReadOnlyError) {
|
|
2163
|
+
return JSON.stringify({ ok: false, error: "read_only", path });
|
|
2164
|
+
}
|
|
2165
|
+
if (err instanceof StaleFileError) {
|
|
2166
|
+
return JSON.stringify({ ok: false, error: "stale_file", path });
|
|
2167
|
+
}
|
|
2168
|
+
if (err instanceof FilesystemError) {
|
|
2169
|
+
return JSON.stringify({ ok: false, error: "write_failed", path });
|
|
2170
|
+
}
|
|
2171
|
+
throw err;
|
|
2172
|
+
}
|
|
2003
2173
|
async function isBinaryFile(absolutePath) {
|
|
2004
2174
|
let handle;
|
|
2005
2175
|
try {
|
|
@@ -2022,6 +2192,6 @@ async function isBinaryFile(absolutePath) {
|
|
|
2022
2192
|
}
|
|
2023
2193
|
}
|
|
2024
2194
|
|
|
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 };
|
|
2195
|
+
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
2196
|
//# sourceMappingURL=index.js.map
|
|
2027
2197
|
//# sourceMappingURL=index.js.map
|