@theokit/sdk-tools 0.12.0 → 0.14.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.d.cts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { CustomTool, ConfigurationError } from '@theokit/sdk';
2
+ import { InteractiveProvider } from '@theokit/sdk/interactive';
2
3
  import { FilesystemProvider } from '@theokit/sdk/filesystem';
3
4
 
4
5
  /**
@@ -153,6 +154,33 @@ interface CreateGlobToolOptions {
153
154
  }
154
155
  declare function createGlobTool(opts: CreateGlobToolOptions): CustomTool;
155
156
 
157
+ /**
158
+ * `interactive_shell` + `write_stdin` — built-in tools for driving an interactive
159
+ * session (a REPL, `git rebase -i`, any command that PROMPTS for stdin).
160
+ *
161
+ * Surface-agnostic by construction: both tools depend on an INJECTED
162
+ * `InteractiveProvider` (`@theokit/sdk/interactive`) exactly as `read_file`
163
+ * depends on `FilesystemProvider`. The HOST supplies the backend — a local
164
+ * `@theokit/sdk-pty` (terminal), a container/E2B backend (cluster/web), or a
165
+ * Tauri backend (desktop) — so the SAME tool runs on every theokit surface with
166
+ * NO native dependency here. When no backend is injected (or it cannot allocate
167
+ * a session) the tool returns `{ ok: false, error: 'interactive_unavailable' }`
168
+ * and the agent falls back to non-interactive exec.
169
+ *
170
+ * Return shapes (always a JSON string):
171
+ * interactive_shell → `{ ok: true, session_id, output }` | `{ ok: false, error }`
172
+ * write_stdin → `{ ok: true, output, alive }` | `{ ok: false, error }`
173
+ */
174
+
175
+ interface CreateInteractiveShellToolOptions {
176
+ /** The interactive backend, or a per-request resolver of one (injected — never a direct native dep). */
177
+ interactive: InteractiveProvider<unknown>;
178
+ }
179
+ /** Start an interactive session; returns a `session_id` to drive with `write_stdin`. */
180
+ declare function createInteractiveShellTool(opts: CreateInteractiveShellToolOptions): CustomTool;
181
+ /** Write to a live interactive session's stdin and read the output it produces. */
182
+ declare function createWriteStdinTool(opts: CreateInteractiveShellToolOptions): CustomTool;
183
+
156
184
  /**
157
185
  * Composable command-permission policy layer (M3-6).
158
186
  *
@@ -182,6 +210,34 @@ declare function commandDenialReason(command: string, policies: CommandPolicy[])
182
210
  /** `true` when no policy denies `command` (an empty policy array allows everything). */
183
211
  declare function isCommandAllowed(command: string, policies: CommandPolicy[]): boolean;
184
212
 
213
+ /**
214
+ * Context-tolerant single-occurrence text matching (`seek_sequence` parity, ported
215
+ * from the AgentBuilder Codex clone, M10/M13). Where a naive `edit_file` matches
216
+ * `old_string` byte-for-byte (or with a single whitespace-normalized fallback),
217
+ * this matcher is safe AND forgiving:
218
+ *
219
+ * Stage 1 — exact byte substring, with an **ambiguity guard**: if the target
220
+ * appears more than once it throws instead of silently editing the first hit
221
+ * (a too-short `old_string` editing the wrong location is the classic footgun).
222
+ * Stage 2 — line-based match down a **strictness ladder** (exact → rstrip → trim
223
+ * → unicode-normalize), stopping at the first rung that yields exactly ONE
224
+ * match; a rung matching 2+ positions throws (ambiguous), never picks one.
225
+ *
226
+ * Pure. Preserves the file's CRLF/LF style on the replaced region. Throws typed
227
+ * {@link ContextMatchError} (`empty | ambiguous | not_found`) so the caller maps to
228
+ * its own `{ ok: false, error }` shape.
229
+ */
230
+ type ContextMatchReason = "empty" | "ambiguous" | "not_found";
231
+ declare class ContextMatchError extends Error {
232
+ readonly reason: ContextMatchReason;
233
+ constructor(reason: ContextMatchReason, message: string);
234
+ }
235
+ /**
236
+ * Return `content` with the single occurrence of `find` replaced by `replace`, using the two-stage
237
+ * context-tolerant matcher. Throws {@link ContextMatchError} on empty/ambiguous/not-found.
238
+ */
239
+ declare function replaceUnique(content: string, find: string, replace: string): string;
240
+
185
241
  /**
186
242
  * SSRF guard for network tools (M3-1).
187
243
  *
@@ -975,4 +1031,4 @@ interface CreateWriteFileToolOptions {
975
1031
  }
976
1032
  declare function createWriteFileTool(opts: CreateWriteFileToolOptions): CustomTool;
977
1033
 
978
- 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, ReasoningTools, 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, withName, withShellExitGuidance, withToolResultGuidance };
1034
+ export { CatastrophicCommandError, type CommandPolicy, ContextMatchError, type ContextMatchReason, type CreateApplyPatchToolOptions, type CreateBraveWebSearchAdapterOptions, type CreateEditFileToolOptions, type CreateGenericHttpSearchAdapterOptions, type CreateGitDiffToolOptions, type CreateGlobToolOptions, type CreateInteractiveShellToolOptions, 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, ReasoningTools, 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, createInteractiveShellTool, createListDirTool, createPlanModeTool, createQuestionTool, createReadFileTool, createRunVitestTool, createSearchTextTool, createSessionArtifactStore, createShellTool, createTodolistTool, createWebFetchTool, createWebSearchTool, createWriteFileTool, createWriteStdinTool, denyCatastrophicCommands, formatCode, formatDiff, formatError, formatFileList, injectGuidance, isBlockedIp, isCommandAllowed, renderToolList, replaceUnique, resolveAndScreen, screenedFetch, todoItemsToPlanNodes, truncateOutput, withDefaultGuidance, withDescription, withName, withShellExitGuidance, withToolResultGuidance };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { CustomTool, ConfigurationError } from '@theokit/sdk';
2
+ import { InteractiveProvider } from '@theokit/sdk/interactive';
2
3
  import { FilesystemProvider } from '@theokit/sdk/filesystem';
3
4
 
4
5
  /**
@@ -153,6 +154,33 @@ interface CreateGlobToolOptions {
153
154
  }
154
155
  declare function createGlobTool(opts: CreateGlobToolOptions): CustomTool;
155
156
 
157
+ /**
158
+ * `interactive_shell` + `write_stdin` — built-in tools for driving an interactive
159
+ * session (a REPL, `git rebase -i`, any command that PROMPTS for stdin).
160
+ *
161
+ * Surface-agnostic by construction: both tools depend on an INJECTED
162
+ * `InteractiveProvider` (`@theokit/sdk/interactive`) exactly as `read_file`
163
+ * depends on `FilesystemProvider`. The HOST supplies the backend — a local
164
+ * `@theokit/sdk-pty` (terminal), a container/E2B backend (cluster/web), or a
165
+ * Tauri backend (desktop) — so the SAME tool runs on every theokit surface with
166
+ * NO native dependency here. When no backend is injected (or it cannot allocate
167
+ * a session) the tool returns `{ ok: false, error: 'interactive_unavailable' }`
168
+ * and the agent falls back to non-interactive exec.
169
+ *
170
+ * Return shapes (always a JSON string):
171
+ * interactive_shell → `{ ok: true, session_id, output }` | `{ ok: false, error }`
172
+ * write_stdin → `{ ok: true, output, alive }` | `{ ok: false, error }`
173
+ */
174
+
175
+ interface CreateInteractiveShellToolOptions {
176
+ /** The interactive backend, or a per-request resolver of one (injected — never a direct native dep). */
177
+ interactive: InteractiveProvider<unknown>;
178
+ }
179
+ /** Start an interactive session; returns a `session_id` to drive with `write_stdin`. */
180
+ declare function createInteractiveShellTool(opts: CreateInteractiveShellToolOptions): CustomTool;
181
+ /** Write to a live interactive session's stdin and read the output it produces. */
182
+ declare function createWriteStdinTool(opts: CreateInteractiveShellToolOptions): CustomTool;
183
+
156
184
  /**
157
185
  * Composable command-permission policy layer (M3-6).
158
186
  *
@@ -182,6 +210,34 @@ declare function commandDenialReason(command: string, policies: CommandPolicy[])
182
210
  /** `true` when no policy denies `command` (an empty policy array allows everything). */
183
211
  declare function isCommandAllowed(command: string, policies: CommandPolicy[]): boolean;
184
212
 
213
+ /**
214
+ * Context-tolerant single-occurrence text matching (`seek_sequence` parity, ported
215
+ * from the AgentBuilder Codex clone, M10/M13). Where a naive `edit_file` matches
216
+ * `old_string` byte-for-byte (or with a single whitespace-normalized fallback),
217
+ * this matcher is safe AND forgiving:
218
+ *
219
+ * Stage 1 — exact byte substring, with an **ambiguity guard**: if the target
220
+ * appears more than once it throws instead of silently editing the first hit
221
+ * (a too-short `old_string` editing the wrong location is the classic footgun).
222
+ * Stage 2 — line-based match down a **strictness ladder** (exact → rstrip → trim
223
+ * → unicode-normalize), stopping at the first rung that yields exactly ONE
224
+ * match; a rung matching 2+ positions throws (ambiguous), never picks one.
225
+ *
226
+ * Pure. Preserves the file's CRLF/LF style on the replaced region. Throws typed
227
+ * {@link ContextMatchError} (`empty | ambiguous | not_found`) so the caller maps to
228
+ * its own `{ ok: false, error }` shape.
229
+ */
230
+ type ContextMatchReason = "empty" | "ambiguous" | "not_found";
231
+ declare class ContextMatchError extends Error {
232
+ readonly reason: ContextMatchReason;
233
+ constructor(reason: ContextMatchReason, message: string);
234
+ }
235
+ /**
236
+ * Return `content` with the single occurrence of `find` replaced by `replace`, using the two-stage
237
+ * context-tolerant matcher. Throws {@link ContextMatchError} on empty/ambiguous/not-found.
238
+ */
239
+ declare function replaceUnique(content: string, find: string, replace: string): string;
240
+
185
241
  /**
186
242
  * SSRF guard for network tools (M3-1).
187
243
  *
@@ -975,4 +1031,4 @@ interface CreateWriteFileToolOptions {
975
1031
  }
976
1032
  declare function createWriteFileTool(opts: CreateWriteFileToolOptions): CustomTool;
977
1033
 
978
- 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, ReasoningTools, 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, withName, withShellExitGuidance, withToolResultGuidance };
1034
+ export { CatastrophicCommandError, type CommandPolicy, ContextMatchError, type ContextMatchReason, type CreateApplyPatchToolOptions, type CreateBraveWebSearchAdapterOptions, type CreateEditFileToolOptions, type CreateGenericHttpSearchAdapterOptions, type CreateGitDiffToolOptions, type CreateGlobToolOptions, type CreateInteractiveShellToolOptions, 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, ReasoningTools, 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, createInteractiveShellTool, createListDirTool, createPlanModeTool, createQuestionTool, createReadFileTool, createRunVitestTool, createSearchTextTool, createSessionArtifactStore, createShellTool, createTodolistTool, createWebFetchTool, createWebSearchTool, createWriteFileTool, createWriteStdinTool, denyCatastrophicCommands, formatCode, formatDiff, formatError, formatFileList, injectGuidance, isBlockedIp, isCommandAllowed, renderToolList, replaceUnique, resolveAndScreen, screenedFetch, todoItemsToPlanNodes, truncateOutput, withDefaultGuidance, withDescription, withName, withShellExitGuidance, withToolResultGuidance };
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ import { existsSync, statSync, mkdirSync, writeFileSync, realpathSync, readFileS
6
6
  import { safeFilenameForId, safePathJoin as safePathJoin$1 } from '@theokit/sdk/path-safety';
7
7
  import { replaceFileAtomic } from '@theokit/sdk/persistence';
8
8
  import { spawn } from 'child_process';
9
+ import { resolveInteractive, InteractiveUnavailableError, NoSuchSessionError } from '@theokit/sdk/interactive';
9
10
  import { lookup } from 'dns/promises';
10
11
  import { isIP } from 'net';
11
12
  import { resolveFilesystem, FileNotFoundError, FilesystemSecurityError, FilesystemReadOnlyError, StaleFileError, FilesystemError } from '@theokit/sdk/filesystem';
@@ -263,6 +264,90 @@ function createSessionArtifactStore(options) {
263
264
  }
264
265
  return { write, read, has, list, path };
265
266
  }
267
+
268
+ // src/internal/context-match.ts
269
+ var ContextMatchError = class extends Error {
270
+ reason;
271
+ constructor(reason, message) {
272
+ super(message);
273
+ this.name = "ContextMatchError";
274
+ this.reason = reason;
275
+ }
276
+ };
277
+ function normalizeUnicode(s) {
278
+ return s.replace(/[‐-―−]/g, "-").replace(/[‘-‛]/g, "'").replace(/[“-‟]/g, '"').replace(/[ -    ]/g, " ");
279
+ }
280
+ var LINE_MATCH_LADDER = [
281
+ (s) => s,
282
+ // exact
283
+ (s) => s.replace(/\s+$/, ""),
284
+ // rstrip (ignore trailing whitespace)
285
+ (s) => s.trim(),
286
+ // trim (ignore leading + trailing)
287
+ (s) => normalizeUnicode(s).trim()
288
+ // unicode + trim (loosest)
289
+ ];
290
+ function matchesAt(lines, pattern, i, norm) {
291
+ for (let p = 0; p < pattern.length; p++) {
292
+ const lineAt = lines[i + p];
293
+ const patAt = pattern[p];
294
+ if (lineAt === void 0 || patAt === void 0 || norm(lineAt) !== norm(patAt)) return false;
295
+ }
296
+ return true;
297
+ }
298
+ function findHits(lines, pattern, norm) {
299
+ const hits = [];
300
+ for (let i = 0; i + pattern.length <= lines.length; i++) {
301
+ if (matchesAt(lines, pattern, i, norm)) hits.push(i);
302
+ }
303
+ return hits;
304
+ }
305
+ function seekUniqueLineMatch(lines, pattern, find) {
306
+ if (pattern.length === 0 || pattern.length > lines.length) return null;
307
+ for (const norm of LINE_MATCH_LADDER) {
308
+ const hits = findHits(lines, pattern, norm);
309
+ if (hits.length === 1) return hits[0] ?? null;
310
+ if (hits.length > 1) {
311
+ throw new ContextMatchError(
312
+ "ambiguous",
313
+ `target text is ambiguous (multiple matches); include more context:
314
+ ${find}`
315
+ );
316
+ }
317
+ }
318
+ return null;
319
+ }
320
+ function replaceUnique(content, find, replace) {
321
+ if (find === "") {
322
+ throw new ContextMatchError(
323
+ "empty",
324
+ "empty find is not a valid target; provide the text to replace"
325
+ );
326
+ }
327
+ const first = content.indexOf(find);
328
+ if (first !== -1) {
329
+ if (content.indexOf(find, first + 1) !== -1) {
330
+ throw new ContextMatchError(
331
+ "ambiguous",
332
+ `target text is ambiguous (multiple matches); include more context:
333
+ ${find}`
334
+ );
335
+ }
336
+ return content.slice(0, first) + replace + content.slice(first + find.length);
337
+ }
338
+ const lines = content.split("\n");
339
+ const at = seekUniqueLineMatch(lines, find.split("\n"), find);
340
+ if (at === null) {
341
+ throw new ContextMatchError("not_found", `target text not found:
342
+ ${find}`);
343
+ }
344
+ const patternLen = find.split("\n").length;
345
+ const matchedCrlf = patternLen > 0 && lines.slice(at, at + patternLen).every((l) => l.endsWith("\r"));
346
+ const replaceLines = matchedCrlf ? replace.split("\n").map((l) => l.endsWith("\r") ? l : `${l}\r`) : replace.split("\n");
347
+ return [...lines.slice(0, at), ...replaceLines, ...lines.slice(at + patternLen)].join("\n");
348
+ }
349
+
350
+ // src/edit-file.ts
266
351
  function createEditFileTool(opts) {
267
352
  const { projectRoot } = opts;
268
353
  return Tool.create({
@@ -304,26 +389,36 @@ function createEditFileTool(opts) {
304
389
  const exactIdx = content.indexOf(old_string);
305
390
  if (exactIdx !== -1) {
306
391
  await copyFile(absolutePath, `${absolutePath}.bak`);
307
- const result2 = content.slice(0, exactIdx) + new_string + content.slice(exactIdx + old_string.length);
308
- await writeFile(absolutePath, result2, "utf-8");
392
+ const result = content.slice(0, exactIdx) + new_string + content.slice(exactIdx + old_string.length);
393
+ await writeFile(absolutePath, result, "utf-8");
309
394
  return JSON.stringify({ ok: true, replacements: 1 });
310
395
  }
311
396
  const normalizedContent = normalizeWhitespace(content);
312
397
  const normalizedOld = normalizeWhitespace(old_string);
313
398
  const normalizedIdx = normalizedContent.indexOf(normalizedOld);
314
- if (normalizedIdx === -1) {
315
- return JSON.stringify({ ok: false, error: "no_match", path });
399
+ if (normalizedIdx !== -1) {
400
+ const span = findOriginalSpan(
401
+ content,
402
+ normalizedContent,
403
+ normalizedIdx,
404
+ normalizedOld.length
405
+ );
406
+ await copyFile(absolutePath, `${absolutePath}.bak`);
407
+ const result = content.slice(0, span.start) + new_string + content.slice(span.end);
408
+ await writeFile(absolutePath, result, "utf-8");
409
+ return JSON.stringify({ ok: true, replacements: 1 });
410
+ }
411
+ try {
412
+ const result = replaceUnique(content, old_string, new_string);
413
+ await copyFile(absolutePath, `${absolutePath}.bak`);
414
+ await writeFile(absolutePath, result, "utf-8");
415
+ return JSON.stringify({ ok: true, replacements: 1 });
416
+ } catch (err) {
417
+ if (err instanceof ContextMatchError) {
418
+ return JSON.stringify({ ok: false, error: "no_match", path });
419
+ }
420
+ throw err;
316
421
  }
317
- const span = findOriginalSpan(
318
- content,
319
- normalizedContent,
320
- normalizedIdx,
321
- normalizedOld.length
322
- );
323
- await copyFile(absolutePath, `${absolutePath}.bak`);
324
- const result = content.slice(0, span.start) + new_string + content.slice(span.end);
325
- await writeFile(absolutePath, result, "utf-8");
326
- return JSON.stringify({ ok: true, replacements: 1 });
327
422
  }
328
423
  });
329
424
  }
@@ -595,6 +690,60 @@ function globToRegex(pattern) {
595
690
  }
596
691
  return new RegExp(`^${regexStr}$`);
597
692
  }
693
+ function toErrorJson(err) {
694
+ if (err instanceof InteractiveUnavailableError) {
695
+ return JSON.stringify({ ok: false, error: "interactive_unavailable" });
696
+ }
697
+ if (err instanceof NoSuchSessionError) {
698
+ return JSON.stringify({ ok: false, error: "no_such_session" });
699
+ }
700
+ throw err;
701
+ }
702
+ function createInteractiveShellTool(opts) {
703
+ const { interactive } = opts;
704
+ return Tool.create({
705
+ name: "interactive_shell",
706
+ description: "Start an interactive shell session for a command that PROMPTS for input or is a REPL (python, node, `git rebase -i`, a `read` prompt) \u2014 NOT for one-shot commands (use shell_exec). Returns a session_id; drive it with write_stdin, reading the incremental output each step. Returns { ok, session_id, output } or { ok: false, error }.",
707
+ inputSchema: z.object({
708
+ command: z.string().min(1).describe("Command to run interactively, e.g. 'python3' or 'bash -i'."),
709
+ yield_time_ms: z.number().int().positive().optional().describe("How long to wait for startup output before returning (clamped by the backend).")
710
+ }),
711
+ handler: async ({ command, yield_time_ms }, ctx) => {
712
+ try {
713
+ const backend = await resolveInteractive(interactive, ctx ?? {});
714
+ const { sessionId, output } = await backend.startInteractive(command, {
715
+ yieldMs: yield_time_ms
716
+ });
717
+ return JSON.stringify({ ok: true, session_id: sessionId, output });
718
+ } catch (err) {
719
+ return toErrorJson(err);
720
+ }
721
+ }
722
+ });
723
+ }
724
+ function createWriteStdinTool(opts) {
725
+ const { interactive } = opts;
726
+ return Tool.create({
727
+ name: "write_stdin",
728
+ description: "Write input to a live interactive session (from interactive_shell) and read the output it produces during the wait window. Include a trailing newline to submit a line. Returns { ok, output, alive } (alive:false means the session exited) or { ok: false, error }.",
729
+ inputSchema: z.object({
730
+ session_id: z.string().min(1).describe("The session_id returned by interactive_shell."),
731
+ input: z.string().describe("Text to write to stdin (add a trailing '\\n' to submit a line)."),
732
+ yield_time_ms: z.number().int().positive().optional().describe("How long to wait for output before returning (clamped by the backend).")
733
+ }),
734
+ handler: async ({ session_id, input, yield_time_ms }, ctx) => {
735
+ try {
736
+ const backend = await resolveInteractive(interactive, ctx ?? {});
737
+ const { output, alive } = await backend.writeStdin(session_id, input, {
738
+ yieldMs: yield_time_ms
739
+ });
740
+ return JSON.stringify({ ok: true, output, alive });
741
+ } catch (err) {
742
+ return toErrorJson(err);
743
+ }
744
+ }
745
+ });
746
+ }
598
747
  var CatastrophicCommandError = class extends ConfigurationError {
599
748
  name = "CatastrophicCommandError";
600
749
  constructor(reason) {
@@ -2247,6 +2396,6 @@ async function isBinaryFile(absolutePath) {
2247
2396
  }
2248
2397
  }
2249
2398
 
2250
- export { CatastrophicCommandError, DEFAULT_TOOL_GUIDANCE, ReadTracker, ReasoningTools, 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, withName, withShellExitGuidance, withToolResultGuidance };
2399
+ export { CatastrophicCommandError, ContextMatchError, DEFAULT_TOOL_GUIDANCE, ReadTracker, ReasoningTools, RedirectBlockedError, SsrfBlockedError, buildEnvContext, buildRepoMap, catastrophicShellReason, commandDenialReason, createApplyPatchTool, createBraveWebSearchAdapter, createEditFileTool, createGenericHttpSearchAdapter, createGitDiffTool, createGlobTool, createInteractiveShellTool, createListDirTool, createPlanModeTool, createQuestionTool, createReadFileTool, createRunVitestTool, createSearchTextTool, createSessionArtifactStore, createShellTool, createTodolistTool, createWebFetchTool, createWebSearchTool, createWriteFileTool, createWriteStdinTool, denyCatastrophicCommands, formatCode, formatDiff, formatError, formatFileList, injectGuidance, isBlockedIp, isCommandAllowed, renderToolList, replaceUnique, resolveAndScreen, screenedFetch, todoItemsToPlanNodes, truncateOutput, withDefaultGuidance, withDescription, withName, withShellExitGuidance, withToolResultGuidance };
2251
2400
  //# sourceMappingURL=index.js.map
2252
2401
  //# sourceMappingURL=index.js.map