@theokit/sdk-tools 0.22.2 → 0.24.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
@@ -26,6 +26,11 @@ import { InteractiveProvider } from '@theokit/sdk/interactive';
26
26
  */
27
27
 
28
28
  interface CreateApplyPatchToolOptions {
29
+ /** M76 — nome exposto ao modelo. Omitido ⇒ o literal de hoje (aditivo). O nome é contrato: chave
30
+ * de approval, o que o modelo vê e o que o telemetry registra. */
31
+ name?: string;
32
+ /** M76 — descrição exposta ao modelo. Omitida ⇒ o literal de hoje (aditivo). */
33
+ description?: string;
29
34
  /** Absolute path to the project root. Every hunk path is gated against this boundary. */
30
35
  projectRoot: string;
31
36
  }
@@ -101,6 +106,11 @@ declare function createSessionArtifactStore(options: SessionArtifactStoreOptions
101
106
  */
102
107
 
103
108
  interface CreateCurrentTimeToolOptions {
109
+ /** M76 — nome exposto ao modelo. Omitido ⇒ o literal de hoje (aditivo). O nome é contrato: chave
110
+ * de approval, o que o modelo vê e o que o telemetry registra. */
111
+ name?: string;
112
+ /** M76 — descrição exposta ao modelo. Omitida ⇒ o literal de hoje (aditivo). */
113
+ description?: string;
104
114
  /** Injectable clock — defaults to `() => new Date()`. Pass a fixed clock for deterministic tests. */
105
115
  clock?: () => Date;
106
116
  }
@@ -177,6 +187,11 @@ declare function formatError(message: string, code?: string): string;
177
187
  */
178
188
 
179
189
  interface CreateGitDiffToolOptions {
190
+ /** M76 — nome exposto ao modelo. Omitido ⇒ o literal de hoje (aditivo). O nome é contrato: chave
191
+ * de approval, o que o modelo vê e o que o telemetry registra. */
192
+ name?: string;
193
+ /** M76 — descrição exposta ao modelo. Omitida ⇒ o literal de hoje (aditivo). */
194
+ description?: string;
180
195
  projectRoot: string;
181
196
  timeoutMs?: number;
182
197
  maxStdoutBytes?: number;
@@ -209,6 +224,15 @@ interface CreateGitStatusToolOptions {
209
224
  timeoutMs?: number;
210
225
  /** Cap on captured stdout; excess sets `truncated: true`. Default 5 MB. */
211
226
  maxStdoutBytes?: number;
227
+ /**
228
+ * Backend de execução injetado (`@theokit/sdk/sandbox`) — quando presente, `git status` roda via
229
+ * `SandboxBackend.execute`; omitido ⇒ o `git` local (inalterado).
230
+ *
231
+ * Simetria com `createGitDiffTool`, apontada pelo review do M76: sem isto `git_diff` rodaria
232
+ * confinado e `git_status` não, na mesma sessão — e a assimetria seria invisível até alguém
233
+ * perceber que uma das duas escapa do sandbox.
234
+ */
235
+ sandbox?: SandboxProvider;
212
236
  /**
213
237
  * M76 — nome exposto ao modelo. Omitido ⇒ `"git_status"` (aditivo).
214
238
  *
@@ -217,6 +241,14 @@ interface CreateGitStatusToolOptions {
217
241
  name?: string;
218
242
  /** M76 — descrição exposta ao modelo. Omitida ⇒ o literal abaixo (aditivo). */
219
243
  description?: string;
244
+ /**
245
+ * Incluir a linha de branch (`-b`) no início da saída. Default `true`.
246
+ *
247
+ * Sem ela o agente vê o que mudou mas não ONDE — e "estou na branch certa?" é a pergunta que
248
+ * precede qualquer commit. O consumidor (agent-builder) já dependia disso; omiti-la faria a
249
+ * migração perder comportamento em silêncio, que é o que a deleção de código local não pode custar.
250
+ */
251
+ includeBranch?: boolean;
220
252
  }
221
253
  declare function createGitStatusTool(opts: CreateGitStatusToolOptions): CustomTool;
222
254
 
@@ -232,6 +264,11 @@ declare function createGitStatusTool(opts: CreateGitStatusToolOptions): CustomTo
232
264
  */
233
265
 
234
266
  interface CreateGlobToolOptions {
267
+ /** M76 — nome exposto ao modelo. Omitido ⇒ o literal de hoje (aditivo). O nome é contrato: chave
268
+ * de approval, o que o modelo vê e o que o telemetry registra. */
269
+ name?: string;
270
+ /** M76 — descrição exposta ao modelo. Omitida ⇒ o literal de hoje (aditivo). */
271
+ description?: string;
235
272
  /** Absolute path to the project root. */
236
273
  projectRoot: string;
237
274
  /** Optional injected filesystem (`@theokit/sdk/filesystem`) — when provided, the walk reads through the
@@ -259,6 +296,11 @@ declare function createGlobTool(opts: CreateGlobToolOptions): CustomTool;
259
296
  */
260
297
 
261
298
  interface CreateInteractiveShellToolOptions {
299
+ /** M76 — nome exposto ao modelo. Omitido ⇒ o literal de hoje (aditivo). O nome é contrato: chave
300
+ * de approval, o que o modelo vê e o que o telemetry registra. */
301
+ name?: string;
302
+ /** M76 — descrição exposta ao modelo. Omitida ⇒ o literal de hoje (aditivo). */
303
+ description?: string;
262
304
  /** The interactive backend, or a per-request resolver of one (injected — never a direct native dep). */
263
305
  interactive: InteractiveProvider<unknown>;
264
306
  }
@@ -654,6 +696,11 @@ interface PlanModeToolWithStore {
654
696
  * @public
655
697
  */
656
698
  interface PlanModeToolOptions {
699
+ /** M76 — nome exposto ao modelo. Omitido ⇒ o literal de hoje (aditivo). O nome é contrato: chave
700
+ * de approval, o que o modelo vê e o que o telemetry registra. */
701
+ name?: string;
702
+ /** M76 — descrição exposta ao modelo. Omitida ⇒ o literal de hoje (aditivo). */
703
+ description?: string;
657
704
  /** Store the submitted `plan` is persisted to on `exit`. */
658
705
  artifactStore: SessionArtifactStore;
659
706
  /** Artifact id under which the plan is stored. Default `"plan"`. */
@@ -678,7 +725,13 @@ interface QuestionToolOptions {
678
725
  * `CustomTool.handler` aponta como o problema que `ctx.context` existe para resolver. Este campo
679
726
  * permanece como fallback, para quem constrói a tool com um asker fixo (retrocompatível).
680
727
  */
681
- askUser?: (question: string) => Promise<string>;
728
+ askUser?: (question: string, threadId?: string) => Promise<string>;
729
+ /**
730
+ * Chamado quando a pergunta é ABANDONADA (timeout ou cancelamento da run), para que o lado da UI
731
+ * libere o slot. Sem ele o timeout deixa a pergunta pendente para sempre — a UI segue mostrando
732
+ * um prompt que ninguém aguarda e a próxima pergunta falha com "já há uma pendente".
733
+ */
734
+ onAbandon?: (threadId?: string) => void;
682
735
  /** Maximum time to wait for user response in ms. Default: 300_000 (5 min). */
683
736
  timeoutMs?: number;
684
737
  /**
@@ -765,6 +818,11 @@ declare class ReadTracker {
765
818
  */
766
819
 
767
820
  interface CreateReadFileToolOptions {
821
+ /** M76 — nome exposto ao modelo. Omitido ⇒ o literal de hoje (aditivo). O nome é contrato: chave
822
+ * de approval, o que o modelo vê e o que o telemetry registra. */
823
+ name?: string;
824
+ /** M76 — descrição exposta ao modelo. Omitida ⇒ o literal de hoje (aditivo). */
825
+ description?: string;
768
826
  /** Absolute path to the project root. Every read is gated against this boundary. */
769
827
  projectRoot: string;
770
828
  /**
@@ -844,6 +902,11 @@ declare class ReasoningTools {
844
902
  */
845
903
 
846
904
  interface CreateRunVitestToolOptions {
905
+ /** M76 — nome exposto ao modelo. Omitido ⇒ o literal de hoje (aditivo). O nome é contrato: chave
906
+ * de approval, o que o modelo vê e o que o telemetry registra. */
907
+ name?: string;
908
+ /** M76 — descrição exposta ao modelo. Omitida ⇒ o literal de hoje (aditivo). */
909
+ description?: string;
847
910
  projectRoot: string;
848
911
  timeoutMs?: number;
849
912
  maxStdoutBytes?: number;
@@ -1088,6 +1151,11 @@ declare function createUpdatePlanTool(): CustomTool;
1088
1151
  */
1089
1152
 
1090
1153
  interface CreateWebFetchToolOptions {
1154
+ /** M76 — nome exposto ao modelo. Omitido ⇒ o literal de hoje (aditivo). O nome é contrato: chave
1155
+ * de approval, o que o modelo vê e o que o telemetry registra. */
1156
+ name?: string;
1157
+ /** M76 — descrição exposta ao modelo. Omitida ⇒ o literal de hoje (aditivo). */
1158
+ description?: string;
1091
1159
  /** Default timeout in ms. */
1092
1160
  defaultTimeoutMs?: number;
1093
1161
  /**
@@ -1127,6 +1195,11 @@ interface WebSearchResult {
1127
1195
  }
1128
1196
  type WebSearchCallback = (query: string, maxResults: number) => Promise<WebSearchResult[]>;
1129
1197
  interface CreateWebSearchToolOptions {
1198
+ /** M76 — nome exposto ao modelo. Omitido ⇒ o literal de hoje (aditivo). O nome é contrato: chave
1199
+ * de approval, o que o modelo vê e o que o telemetry registra. */
1200
+ name?: string;
1201
+ /** M76 — descrição exposta ao modelo. Omitida ⇒ o literal de hoje (aditivo). */
1202
+ description?: string;
1130
1203
  /** Search provider callback — consumer injects the implementation. */
1131
1204
  search: WebSearchCallback;
1132
1205
  /** Default max results if not specified by the LLM. */
@@ -1216,6 +1289,11 @@ type WriteToolContext = {
1216
1289
  context?: unknown;
1217
1290
  };
1218
1291
  interface CreateWriteFileToolOptions {
1292
+ /** M76 — nome exposto ao modelo. Omitido ⇒ o literal de hoje (aditivo). O nome é contrato: chave
1293
+ * de approval, o que o modelo vê e o que o telemetry registra. */
1294
+ name?: string;
1295
+ /** M76 — descrição exposta ao modelo. Omitida ⇒ o literal de hoje (aditivo). */
1296
+ description?: string;
1219
1297
  /** Absolute path to the project root. Every write is gated against this boundary. */
1220
1298
  projectRoot: string;
1221
1299
  /**
package/dist/index.d.ts CHANGED
@@ -26,6 +26,11 @@ import { InteractiveProvider } from '@theokit/sdk/interactive';
26
26
  */
27
27
 
28
28
  interface CreateApplyPatchToolOptions {
29
+ /** M76 — nome exposto ao modelo. Omitido ⇒ o literal de hoje (aditivo). O nome é contrato: chave
30
+ * de approval, o que o modelo vê e o que o telemetry registra. */
31
+ name?: string;
32
+ /** M76 — descrição exposta ao modelo. Omitida ⇒ o literal de hoje (aditivo). */
33
+ description?: string;
29
34
  /** Absolute path to the project root. Every hunk path is gated against this boundary. */
30
35
  projectRoot: string;
31
36
  }
@@ -101,6 +106,11 @@ declare function createSessionArtifactStore(options: SessionArtifactStoreOptions
101
106
  */
102
107
 
103
108
  interface CreateCurrentTimeToolOptions {
109
+ /** M76 — nome exposto ao modelo. Omitido ⇒ o literal de hoje (aditivo). O nome é contrato: chave
110
+ * de approval, o que o modelo vê e o que o telemetry registra. */
111
+ name?: string;
112
+ /** M76 — descrição exposta ao modelo. Omitida ⇒ o literal de hoje (aditivo). */
113
+ description?: string;
104
114
  /** Injectable clock — defaults to `() => new Date()`. Pass a fixed clock for deterministic tests. */
105
115
  clock?: () => Date;
106
116
  }
@@ -177,6 +187,11 @@ declare function formatError(message: string, code?: string): string;
177
187
  */
178
188
 
179
189
  interface CreateGitDiffToolOptions {
190
+ /** M76 — nome exposto ao modelo. Omitido ⇒ o literal de hoje (aditivo). O nome é contrato: chave
191
+ * de approval, o que o modelo vê e o que o telemetry registra. */
192
+ name?: string;
193
+ /** M76 — descrição exposta ao modelo. Omitida ⇒ o literal de hoje (aditivo). */
194
+ description?: string;
180
195
  projectRoot: string;
181
196
  timeoutMs?: number;
182
197
  maxStdoutBytes?: number;
@@ -209,6 +224,15 @@ interface CreateGitStatusToolOptions {
209
224
  timeoutMs?: number;
210
225
  /** Cap on captured stdout; excess sets `truncated: true`. Default 5 MB. */
211
226
  maxStdoutBytes?: number;
227
+ /**
228
+ * Backend de execução injetado (`@theokit/sdk/sandbox`) — quando presente, `git status` roda via
229
+ * `SandboxBackend.execute`; omitido ⇒ o `git` local (inalterado).
230
+ *
231
+ * Simetria com `createGitDiffTool`, apontada pelo review do M76: sem isto `git_diff` rodaria
232
+ * confinado e `git_status` não, na mesma sessão — e a assimetria seria invisível até alguém
233
+ * perceber que uma das duas escapa do sandbox.
234
+ */
235
+ sandbox?: SandboxProvider;
212
236
  /**
213
237
  * M76 — nome exposto ao modelo. Omitido ⇒ `"git_status"` (aditivo).
214
238
  *
@@ -217,6 +241,14 @@ interface CreateGitStatusToolOptions {
217
241
  name?: string;
218
242
  /** M76 — descrição exposta ao modelo. Omitida ⇒ o literal abaixo (aditivo). */
219
243
  description?: string;
244
+ /**
245
+ * Incluir a linha de branch (`-b`) no início da saída. Default `true`.
246
+ *
247
+ * Sem ela o agente vê o que mudou mas não ONDE — e "estou na branch certa?" é a pergunta que
248
+ * precede qualquer commit. O consumidor (agent-builder) já dependia disso; omiti-la faria a
249
+ * migração perder comportamento em silêncio, que é o que a deleção de código local não pode custar.
250
+ */
251
+ includeBranch?: boolean;
220
252
  }
221
253
  declare function createGitStatusTool(opts: CreateGitStatusToolOptions): CustomTool;
222
254
 
@@ -232,6 +264,11 @@ declare function createGitStatusTool(opts: CreateGitStatusToolOptions): CustomTo
232
264
  */
233
265
 
234
266
  interface CreateGlobToolOptions {
267
+ /** M76 — nome exposto ao modelo. Omitido ⇒ o literal de hoje (aditivo). O nome é contrato: chave
268
+ * de approval, o que o modelo vê e o que o telemetry registra. */
269
+ name?: string;
270
+ /** M76 — descrição exposta ao modelo. Omitida ⇒ o literal de hoje (aditivo). */
271
+ description?: string;
235
272
  /** Absolute path to the project root. */
236
273
  projectRoot: string;
237
274
  /** Optional injected filesystem (`@theokit/sdk/filesystem`) — when provided, the walk reads through the
@@ -259,6 +296,11 @@ declare function createGlobTool(opts: CreateGlobToolOptions): CustomTool;
259
296
  */
260
297
 
261
298
  interface CreateInteractiveShellToolOptions {
299
+ /** M76 — nome exposto ao modelo. Omitido ⇒ o literal de hoje (aditivo). O nome é contrato: chave
300
+ * de approval, o que o modelo vê e o que o telemetry registra. */
301
+ name?: string;
302
+ /** M76 — descrição exposta ao modelo. Omitida ⇒ o literal de hoje (aditivo). */
303
+ description?: string;
262
304
  /** The interactive backend, or a per-request resolver of one (injected — never a direct native dep). */
263
305
  interactive: InteractiveProvider<unknown>;
264
306
  }
@@ -654,6 +696,11 @@ interface PlanModeToolWithStore {
654
696
  * @public
655
697
  */
656
698
  interface PlanModeToolOptions {
699
+ /** M76 — nome exposto ao modelo. Omitido ⇒ o literal de hoje (aditivo). O nome é contrato: chave
700
+ * de approval, o que o modelo vê e o que o telemetry registra. */
701
+ name?: string;
702
+ /** M76 — descrição exposta ao modelo. Omitida ⇒ o literal de hoje (aditivo). */
703
+ description?: string;
657
704
  /** Store the submitted `plan` is persisted to on `exit`. */
658
705
  artifactStore: SessionArtifactStore;
659
706
  /** Artifact id under which the plan is stored. Default `"plan"`. */
@@ -678,7 +725,13 @@ interface QuestionToolOptions {
678
725
  * `CustomTool.handler` aponta como o problema que `ctx.context` existe para resolver. Este campo
679
726
  * permanece como fallback, para quem constrói a tool com um asker fixo (retrocompatível).
680
727
  */
681
- askUser?: (question: string) => Promise<string>;
728
+ askUser?: (question: string, threadId?: string) => Promise<string>;
729
+ /**
730
+ * Chamado quando a pergunta é ABANDONADA (timeout ou cancelamento da run), para que o lado da UI
731
+ * libere o slot. Sem ele o timeout deixa a pergunta pendente para sempre — a UI segue mostrando
732
+ * um prompt que ninguém aguarda e a próxima pergunta falha com "já há uma pendente".
733
+ */
734
+ onAbandon?: (threadId?: string) => void;
682
735
  /** Maximum time to wait for user response in ms. Default: 300_000 (5 min). */
683
736
  timeoutMs?: number;
684
737
  /**
@@ -765,6 +818,11 @@ declare class ReadTracker {
765
818
  */
766
819
 
767
820
  interface CreateReadFileToolOptions {
821
+ /** M76 — nome exposto ao modelo. Omitido ⇒ o literal de hoje (aditivo). O nome é contrato: chave
822
+ * de approval, o que o modelo vê e o que o telemetry registra. */
823
+ name?: string;
824
+ /** M76 — descrição exposta ao modelo. Omitida ⇒ o literal de hoje (aditivo). */
825
+ description?: string;
768
826
  /** Absolute path to the project root. Every read is gated against this boundary. */
769
827
  projectRoot: string;
770
828
  /**
@@ -844,6 +902,11 @@ declare class ReasoningTools {
844
902
  */
845
903
 
846
904
  interface CreateRunVitestToolOptions {
905
+ /** M76 — nome exposto ao modelo. Omitido ⇒ o literal de hoje (aditivo). O nome é contrato: chave
906
+ * de approval, o que o modelo vê e o que o telemetry registra. */
907
+ name?: string;
908
+ /** M76 — descrição exposta ao modelo. Omitida ⇒ o literal de hoje (aditivo). */
909
+ description?: string;
847
910
  projectRoot: string;
848
911
  timeoutMs?: number;
849
912
  maxStdoutBytes?: number;
@@ -1088,6 +1151,11 @@ declare function createUpdatePlanTool(): CustomTool;
1088
1151
  */
1089
1152
 
1090
1153
  interface CreateWebFetchToolOptions {
1154
+ /** M76 — nome exposto ao modelo. Omitido ⇒ o literal de hoje (aditivo). O nome é contrato: chave
1155
+ * de approval, o que o modelo vê e o que o telemetry registra. */
1156
+ name?: string;
1157
+ /** M76 — descrição exposta ao modelo. Omitida ⇒ o literal de hoje (aditivo). */
1158
+ description?: string;
1091
1159
  /** Default timeout in ms. */
1092
1160
  defaultTimeoutMs?: number;
1093
1161
  /**
@@ -1127,6 +1195,11 @@ interface WebSearchResult {
1127
1195
  }
1128
1196
  type WebSearchCallback = (query: string, maxResults: number) => Promise<WebSearchResult[]>;
1129
1197
  interface CreateWebSearchToolOptions {
1198
+ /** M76 — nome exposto ao modelo. Omitido ⇒ o literal de hoje (aditivo). O nome é contrato: chave
1199
+ * de approval, o que o modelo vê e o que o telemetry registra. */
1200
+ name?: string;
1201
+ /** M76 — descrição exposta ao modelo. Omitida ⇒ o literal de hoje (aditivo). */
1202
+ description?: string;
1130
1203
  /** Search provider callback — consumer injects the implementation. */
1131
1204
  search: WebSearchCallback;
1132
1205
  /** Default max results if not specified by the LLM. */
@@ -1216,6 +1289,11 @@ type WriteToolContext = {
1216
1289
  context?: unknown;
1217
1290
  };
1218
1291
  interface CreateWriteFileToolOptions {
1292
+ /** M76 — nome exposto ao modelo. Omitido ⇒ o literal de hoje (aditivo). O nome é contrato: chave
1293
+ * de approval, o que o modelo vê e o que o telemetry registra. */
1294
+ name?: string;
1295
+ /** M76 — descrição exposta ao modelo. Omitida ⇒ o literal de hoje (aditivo). */
1296
+ description?: string;
1219
1297
  /** Absolute path to the project root. Every write is gated against this boundary. */
1220
1298
  projectRoot: string;
1221
1299
  /**
package/dist/index.js CHANGED
@@ -225,8 +225,8 @@ ${chunk.oldLines.join("\n")}`
225
225
  function createApplyPatchTool(opts) {
226
226
  const { projectRoot } = opts;
227
227
  return Tool.create({
228
- name: "apply_patch",
229
- description: "Apply a Codex-style V4A patch. The patch is `*** Begin Patch` \u2026 `*** End Patch` wrapping one or more hunks: `*** Add File: <path>` (then `+`lines), `*** Delete File: <path>`, or `*** Update File: <path>` (optional `*** Move to: <path>`) with `@@`-anchored `+` (add) / `-` (remove) / ` ` (context) lines. Read a file first so your context/removed lines match. Applied atomically \u2014 a mismatch anywhere aborts the whole patch with zero writes; each path is security-checked. Returns { ok, files_patched } or { ok: false, error }.",
228
+ name: opts.name ?? "apply_patch",
229
+ description: opts.description ?? "Apply a Codex-style V4A patch. The patch is `*** Begin Patch` \u2026 `*** End Patch` wrapping one or more hunks: `*** Add File: <path>` (then `+`lines), `*** Delete File: <path>`, or `*** Update File: <path>` (optional `*** Move to: <path>`) with `@@`-anchored `+` (add) / `-` (remove) / ` ` (context) lines. Read a file first so your context/removed lines match. Applied atomically \u2014 a mismatch anywhere aborts the whole patch with zero writes; each path is security-checked. Returns { ok, files_patched } or { ok: false, error }.",
230
230
  inputSchema: z.object({
231
231
  patch: z.string().min(1).describe("V4A patch: *** Begin Patch \u2026 *** End Patch.")
232
232
  }),
@@ -427,8 +427,8 @@ function formatInTimezone(now, tz) {
427
427
  function createCurrentTimeTool(opts = {}) {
428
428
  const clock = opts.clock ?? (() => /* @__PURE__ */ new Date());
429
429
  return Tool.create({
430
- name: "current_time",
431
- description: "Get the current date and time. Returns { current_time, iso, timezone } as a JSON string, where current_time is 'YYYY-MM-DD HH:MM:SS <timezone>' and iso is the ISO-8601 instant. Pass an optional IANA timezone (e.g. 'America/Sao_Paulo', 'Europe/Lisbon'); defaults to UTC. Never state the date or time from memory \u2014 always call this. Returns { ok: false, error: 'invalid_timezone' } for an unknown timezone.",
430
+ name: opts.name ?? "current_time",
431
+ description: opts.description ?? "Get the current date and time. Returns { current_time, iso, timezone } as a JSON string, where current_time is 'YYYY-MM-DD HH:MM:SS <timezone>' and iso is the ISO-8601 instant. Pass an optional IANA timezone (e.g. 'America/Sao_Paulo', 'Europe/Lisbon'); defaults to UTC. Never state the date or time from memory \u2014 always call this. Returns { ok: false, error: 'invalid_timezone' } for an unknown timezone.",
432
432
  inputSchema: z.object({
433
433
  timezone: z.string().optional().describe("IANA timezone, e.g. 'America/Sao_Paulo' or 'Europe/Lisbon'. Defaults to UTC.")
434
434
  }),
@@ -718,6 +718,9 @@ function attachChildSettlers(child, gate, onClose, onError, resolve) {
718
718
  }
719
719
 
720
720
  // src/internal/git-exec.ts
721
+ function shq(arg) {
722
+ return `'${arg.replaceAll("'", `'\\''`)}'`;
723
+ }
721
724
  function formatGitResult(result, timeoutMs) {
722
725
  if (result.kind === "timeout") {
723
726
  return JSON.stringify({ ok: false, error: "timeout", timeoutMs });
@@ -799,9 +802,6 @@ function ehProibidoEmQualquerProfundidade(path) {
799
802
  // src/git-diff.ts
800
803
  var DEFAULT_TIMEOUT_MS = 3e4;
801
804
  var DEFAULT_MAX_STDOUT_BYTES = 5 * 1024 * 1024;
802
- function shq(arg) {
803
- return `'${arg.replace(/'/g, `'\\''`)}'`;
804
- }
805
805
  async function diffViaSandbox(sandbox, ctx, cached, path, projectRoot, timeoutMs) {
806
806
  const scopeCheck = checkPathScope(path, projectRoot);
807
807
  if (scopeCheck !== null) return scopeCheck;
@@ -822,8 +822,8 @@ function createGitDiffTool(opts) {
822
822
  sandbox
823
823
  } = opts;
824
824
  return Tool.create({
825
- name: "git_diff",
826
- 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 }.",
825
+ name: opts.name ?? "git_diff",
826
+ description: opts.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 }.",
827
827
  inputSchema: z.object({
828
828
  path: z.string().optional().describe("Optional project-relative file or dir scope."),
829
829
  cached: z.boolean().optional().describe("If true, show staged changes (git diff --cached). Default false.")
@@ -857,26 +857,40 @@ function createGitStatusTool(opts) {
857
857
  inputSchema: z.object({
858
858
  path: z.string().optional().describe("Optional project-relative path to scope the status report.")
859
859
  }),
860
- handler: async ({ path }) => {
860
+ handler: async ({ path }, ctx) => {
861
861
  if (!existsSync(join(projectRoot, ".git"))) {
862
862
  return JSON.stringify({ ok: false, error: "not_a_repo" });
863
863
  }
864
864
  const scopeCheck = checkPathScope(path, projectRoot);
865
865
  if (scopeCheck !== null) return scopeCheck;
866
- const args = ["status", "--porcelain"];
866
+ const args = ["status", "--porcelain=v1"];
867
+ if (opts.includeBranch !== false) args.push("-b");
867
868
  if (path !== void 0 && path !== "") args.push("--", path);
869
+ if (opts.sandbox !== void 0) {
870
+ return statusViaSandbox(opts.sandbox, ctx, args, timeoutMs);
871
+ }
868
872
  const result = await runGitProcess(projectRoot, args, timeoutMs, maxStdoutBytes);
869
873
  return formatGitResult(result, timeoutMs);
870
874
  }
871
875
  });
872
876
  }
877
+ async function statusViaSandbox(sandbox, ctx, args, timeoutMs) {
878
+ const command = ["git", ...args].map(shq).join(" ");
879
+ const backend = await resolveSandbox(sandbox, ctx ?? {});
880
+ const r = await backend.execute(command, { timeoutMs });
881
+ if (r.timedOut) return JSON.stringify({ ok: false, error: "timeout", timeoutMs });
882
+ if (r.exitCode !== 0) {
883
+ return /not a git repository/i.test(r.stderr) ? JSON.stringify({ ok: false, error: "not_a_repo" }) : JSON.stringify({ ok: false, error: "git_failed", stderr: r.stderr });
884
+ }
885
+ return JSON.stringify({ ok: true, diff: r.stdout, truncated: false });
886
+ }
873
887
  var DEFAULT_EXCLUDES = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".theo"]);
874
888
  var MAX_BACKEND_WALK_DEPTH = 64;
875
889
  function createGlobTool(opts) {
876
890
  const { projectRoot, filesystem } = opts;
877
891
  return Tool.create({
878
- name: "glob_files",
879
- 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 }.",
892
+ name: opts.name ?? "glob_files",
893
+ description: opts.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 }.",
880
894
  inputSchema: z.object({
881
895
  pattern: z.string().min(1).describe("Glob pattern (e.g. '**/*.ts', 'src/**/*.json')."),
882
896
  cwd: z.string().optional().describe("Project-relative subdirectory to search from.")
@@ -995,8 +1009,8 @@ function toErrorJson(err) {
995
1009
  function createInteractiveShellTool(opts) {
996
1010
  const { interactive } = opts;
997
1011
  return Tool.create({
998
- name: "interactive_shell",
999
- 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 }.",
1012
+ name: opts.name ?? "interactive_shell",
1013
+ description: opts.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 }.",
1000
1014
  inputSchema: z.object({
1001
1015
  command: z.string().min(1).describe("Command to run interactively, e.g. 'python3' or 'bash -i'."),
1002
1016
  yield_time_ms: z.number().int().positive().optional().describe("How long to wait for startup output before returning (clamped by the backend).")
@@ -1700,8 +1714,8 @@ function createPlanModeTool(options) {
1700
1714
  }
1701
1715
  const { artifactStore, artifactId = "plan" } = options;
1702
1716
  return {
1703
- name: "plan_mode",
1704
- description: DESCRIPTION,
1717
+ name: options.name ?? "plan_mode",
1718
+ description: options.description ?? DESCRIPTION,
1705
1719
  inputSchema: planModeSchema(true),
1706
1720
  handler: async (input) => {
1707
1721
  if (input.action === "enter") {
@@ -1759,10 +1773,14 @@ function createQuestionTool(opts) {
1759
1773
  setTimeout(() => reject(new Error("timeout")), timeoutMs);
1760
1774
  });
1761
1775
  try {
1762
- const answer = await Promise.race([askUser(String(input.question ?? "")), timeout]);
1776
+ const answer = await Promise.race([
1777
+ askUser(String(input.question ?? ""), ctx?.threadId),
1778
+ timeout
1779
+ ]);
1763
1780
  return JSON.stringify({ ok: true, answer });
1764
1781
  } catch (err) {
1765
1782
  if (err instanceof Error && err.message === "timeout") {
1783
+ opts.onAbandon?.(ctx?.threadId);
1766
1784
  return JSON.stringify({
1767
1785
  ok: false,
1768
1786
  error: "timeout",
@@ -1807,8 +1825,8 @@ function createReadFileTool(opts) {
1807
1825
  const numbered = lineNumbers === true ? " Returns a cat -n numbered view (`<n>\\t<line>`)." : "";
1808
1826
  const abs = allowAbsolute === true ? " Absolute paths outside the project are honored." : "";
1809
1827
  return Tool.create({
1810
- name: "read_file",
1811
- description: "Read a 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." + numbered + abs + " By default returns the whole file; use the optional offset (1-based first line) + limit to page through a large file, or search_text to locate a symbol. Refuses 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 }.",
1828
+ name: opts.name ?? "read_file",
1829
+ description: opts.description ?? "Read a 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." + numbered + abs + " By default returns the whole file; use the optional offset (1-based first line) + limit to page through a large file, or search_text to locate a symbol. Refuses 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 }.",
1812
1830
  inputSchema: z.object({
1813
1831
  path: z.string().min(1).describe("File path (project-relative; absolute when allowed)."),
1814
1832
  offset: z.number().int().min(1).optional().describe("1-based first line to read (default 1)."),
@@ -1982,8 +2000,8 @@ function createRunVitestTool(opts) {
1982
2000
  maxStdoutBytes = DEFAULT_MAX_STDOUT_BYTES2
1983
2001
  } = opts;
1984
2002
  return Tool.create({
1985
- name: "run_vitest",
1986
- 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.",
2003
+ name: opts.name ?? "run_vitest",
2004
+ description: opts.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.",
1987
2005
  inputSchema: z.object({
1988
2006
  path: z.string().optional().describe("Optional vitest pattern or file path (project-relative).")
1989
2007
  }),
@@ -2580,8 +2598,8 @@ function createWebFetchTool(opts) {
2580
2598
  const fetchImpl = opts?.fetchImpl;
2581
2599
  const lookup = opts?.lookup;
2582
2600
  return Tool.create({
2583
- name: "web_fetch",
2584
- 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 }.",
2601
+ name: opts?.name ?? "web_fetch",
2602
+ description: opts?.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 }.",
2585
2603
  inputSchema: z.object({
2586
2604
  url: z.string().min(1).describe("URL to fetch (http or https only)."),
2587
2605
  timeout_ms: z.number().int().positive().optional().describe("Timeout in milliseconds (default 30000).")
@@ -2667,8 +2685,8 @@ function createWebFetchTool(opts) {
2667
2685
  function createWebSearchTool(opts) {
2668
2686
  const { search, defaultMaxResults = 5 } = opts;
2669
2687
  return Tool.create({
2670
- name: "web_search",
2671
- 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 }.",
2688
+ name: opts.name ?? "web_search",
2689
+ description: opts.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 }.",
2672
2690
  inputSchema: z.object({
2673
2691
  query: z.string().min(1).describe("Search query."),
2674
2692
  max_results: z.number().int().positive().max(20).optional().describe("Maximum results to return (default 5, max 20).")
@@ -2754,8 +2772,8 @@ function createWriteFileTool(opts) {
2754
2772
  }
2755
2773
  const guard = opts.requireReadBeforeWrite ? opts.readTracker : void 0;
2756
2774
  return Tool.create({
2757
- name: "write_file",
2758
- 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 }.",
2775
+ name: opts.name ?? "write_file",
2776
+ description: opts.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 }.",
2759
2777
  inputSchema: z.object({
2760
2778
  path: z.string().min(1).describe("Project-relative file path."),
2761
2779
  content: z.string().describe("UTF-8 content to write.")