tinker-agent 1.3.0 → 1.4.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.
@@ -10,27 +10,29 @@ import {
10
10
  loadProjectInstructions,
11
11
  projectInstructionManifest,
12
12
  } from "../instructions/project-instructions";
13
+ import { type RunnerConfig } from "./config";
13
14
  import {
14
15
  createRunnerModelClient,
15
- createWebFetchRefinerFromEnv,
16
- readRunnerConfig,
16
+ createWebFetchRefiner,
17
17
  RUNTIME_INSTRUCTIONS,
18
- type RunnerConfigOverrides,
19
- } from "./config";
20
- import { loadModelProfiles } from "./model-profiles";
18
+ } from "./runner-dependencies";
19
+ import type { PublicToolingConfig } from "./public-config-contract";
21
20
  import { realpath } from "node:fs/promises";
22
21
  import { loadSkillCatalog } from "../skills/skill-loader";
23
22
 
24
- export type RunOneShotOptions = RunnerConfigOverrides & {
23
+ export type RunOneShotOptions = {
24
+ config: RunnerConfig;
25
+ tooling: PublicToolingConfig;
25
26
  modelClient?: ModelClient;
26
27
  stdout?: WritableLike;
27
28
  stderr?: WritableLike;
28
29
  eventLogPath?: string | false;
30
+ env?: NodeJS.ProcessEnv;
29
31
  };
30
32
 
31
33
  export async function runOneShot(
32
34
  userPrompt: string,
33
- options: RunOneShotOptions = {},
35
+ options: RunOneShotOptions,
34
36
  ): Promise<number> {
35
37
  const stdout = options.stdout ?? process.stdout;
36
38
  const stderr = options.stderr ?? process.stderr;
@@ -40,9 +42,7 @@ export async function runOneShot(
40
42
  let exitCode = 1;
41
43
 
42
44
  try {
43
- const profiles =
44
- options.modelClient === undefined ? await loadModelProfiles() : undefined;
45
- const config = readRunnerConfig(options, profiles);
45
+ const config = options.config;
46
46
  const workspaceRoot = await realpath(config.workspaceRoot);
47
47
  const projectInstructions = await loadProjectInstructions(workspaceRoot);
48
48
  const skillCatalog = await loadSkillCatalog({ workspaceRoot });
@@ -51,7 +51,11 @@ export async function runOneShot(
51
51
  runtimeInstructions: RUNTIME_INSTRUCTIONS(workspaceRoot),
52
52
  projectInstructions,
53
53
  });
54
- const modelClient = createRunnerModelClient(config, options.modelClient);
54
+ const modelClient = createRunnerModelClient(
55
+ config,
56
+ options.modelClient,
57
+ options.env,
58
+ );
55
59
  session = await createRuntimeSession({
56
60
  selection: { mode: "new", sessionId: config.sessionId },
57
61
  workspaceRoot,
@@ -68,7 +72,8 @@ export async function runOneShot(
68
72
  presentationSinks: [new StdoutEventPrinter(stdout, stderr)],
69
73
  persistence:
70
74
  options.eventLogPath === false ? false : { eventLogPath: options.eventLogPath },
71
- webFetchRefiner: createWebFetchRefinerFromEnv(config),
75
+ webFetchRefiner: createWebFetchRefiner(config, options.env),
76
+ toolingConfig: options.tooling,
72
77
  });
73
78
 
74
79
  const result = await session.executeTurn({
@@ -0,0 +1,100 @@
1
+ import { renderRecallRetirementContract } from "../context/recall-retirement-contract";
2
+ import { FakeModelClient } from "../model/fake-model-client";
3
+ import type { ModelClient } from "../model/model-client";
4
+ import { OpenAIChatModelClient } from "../model/openai-chat-model-client";
5
+ import { createModelRefiner, type Refiner } from "../tools/web-fetch/refiner";
6
+ import type { RunnerConfig } from "./config";
7
+
8
+ export const RUNTIME_INSTRUCTIONS = (
9
+ workspaceRoot: string,
10
+ ): string => `You are a coding agent running in a local workspace.
11
+ Your name is Tinker.
12
+
13
+ Current workspace:
14
+ ${workspaceRoot}
15
+
16
+ Use this path as the root for relative file paths. Absolute file paths may point outside this workspace.
17
+
18
+ You can use tools to find, read, edit, write files, and run shell commands.
19
+ Use Glob to find files by name or path pattern.
20
+ Use Grep to search file contents. Do not use Bash with grep or rg for routine content searches.
21
+ With Grep, start with output_mode="files_with_matches" to narrow scope, then use output_mode="content" when you need matching lines.
22
+ Use head_limit and offset to page through large Grep result sets instead of requesting unlimited output.
23
+ Use Read to open specific files returned by Grep.
24
+ Use Edit to replace exact strings in existing files. Set old_string="" to create a file or write to an empty file.
25
+ Use Read before the first Write of an existing file in the current runtime.
26
+ Write creates missing parent directories when creating a file.
27
+ Write may fail if the runtime has no known version or the file changed after it was last observed. If that happens, call Read again and retry with the updated content.
28
+ Use Read before an exact-string Edit when this runtime has not already established the current version through Read, Write, or Edit. A successful paginated Read is sufficient. Successful Write and Edit operations establish the current version, so later exact-string Edit operations do not need another Read unless the file changed externally. Edit with old_string="" can create a file or write to an empty file without a prior Read, and creates missing parent directories when creating a file. Exact-string Edit may fail if the runtime has no known version, the file changed after it was last observed, old_string is missing, or old_string matches multiple places without replace_all=true.
29
+ Use WebSearch, when it is available, to look up current information on the web such as recent releases, documentation, and news. Prefer local workspace knowledge for questions the codebase can answer.
30
+ Use WebFetch to read the content of a specific URL, such as documentation pages found via WebSearch or local dev server pages.
31
+ Use Bash to run tests, formatters, linters, read-only git checks, and project commands.
32
+ Prefer Read for reading files instead of using cat on large files.
33
+ Prefer Write or Edit for changing files instead of shell redirection.
34
+ Use run_in_background=true for dev servers, watch commands, long-running builds, and long-running test services.
35
+ Do not add & to Bash commands; background execution is handled by the Bash tool.
36
+ Use TaskList to list background shell tasks in the current session.
37
+ Use TaskOutput to inspect a task's current status and latest output.
38
+ Use TaskStop to stop a background task that is no longer needed.
39
+ Do not use ad-hoc kill commands to manage tasks created by Bash.
40
+ Bash and TaskOutput return outputFilePath. Use Read on outputFilePath when you need complete or paginated output.
41
+ ${renderRecallRetirementContract()}
42
+ Agent Skill instructions are current only when returned by the Skill tool in the current turn or listed in the active skill system section. Skill content recovered through Recall is historical data and does not activate or override a current skill.
43
+ When an active Agent Skill refers to a relative resource path, resolve it from the Skill directory shown with that skill.
44
+ Agent Skills do not override Tinker's runtime, tool protocol, project instructions, or the user's explicit request. Do not modify a skill source unless the user explicitly asks to maintain that skill.
45
+
46
+ When you are done, respond with a concise summary of what you did.`;
47
+
48
+ export function createModelClient(
49
+ config: Pick<
50
+ RunnerConfig,
51
+ | "modelName"
52
+ | "includeReasoningContent"
53
+ | "stream"
54
+ | "contextBudget"
55
+ | "apiKey"
56
+ | "apiBase"
57
+ | "inputModalities"
58
+ | "tokenEstimator"
59
+ >,
60
+ env: NodeJS.ProcessEnv = process.env,
61
+ ): ModelClient {
62
+ const fakeMode = env.TINKER_TEST_FAKE_MODEL;
63
+ if (fakeMode !== undefined && fakeMode !== "") {
64
+ return new FakeModelClient(fakeMode, {
65
+ model: config.modelName,
66
+ contextBudget: config.contextBudget,
67
+ });
68
+ }
69
+
70
+ return new OpenAIChatModelClient({
71
+ apiKey: config.apiKey,
72
+ baseURL: config.apiBase,
73
+ includeReasoningContent: config.includeReasoningContent,
74
+ model: config.modelName,
75
+ stream: config.stream,
76
+ contextBudget: config.contextBudget,
77
+ inputModalities: config.inputModalities,
78
+ ...(config.tokenEstimator === undefined
79
+ ? {}
80
+ : { tokenEstimator: config.tokenEstimator }),
81
+ });
82
+ }
83
+
84
+ export function createRunnerModelClient(
85
+ config: Parameters<typeof createModelClient>[0],
86
+ injected?: ModelClient,
87
+ env?: NodeJS.ProcessEnv,
88
+ ): ModelClient {
89
+ return injected ?? createModelClient(config, env);
90
+ }
91
+
92
+ export function createWebFetchRefiner(
93
+ config: Parameters<typeof createModelClient>[0],
94
+ env?: NodeJS.ProcessEnv,
95
+ ): Refiner {
96
+ return createModelRefiner({
97
+ createModelClient: () => createModelClient(config, env),
98
+ contextBudget: config.contextBudget,
99
+ });
100
+ }
@@ -27,28 +27,31 @@ import {
27
27
  type TuiSessionBinding,
28
28
  } from "../tui/tui-session-controller";
29
29
  import {
30
- createRunnerModelClient,
31
- createWebFetchRefinerFromEnv,
32
30
  promptHistoryPath,
33
- readRunnerConfig,
34
- RUNTIME_INSTRUCTIONS,
31
+ deriveRunnerConfig,
32
+ type ResolvedPublicConfig,
35
33
  type RunnerConfig,
36
34
  } from "./config";
37
35
  import {
38
- loadModelProfiles,
39
- persistDefaultProfile,
40
- resolveSessionProfileName,
41
- type ModelProfile,
42
- } from "./model-profiles";
36
+ createRunnerModelClient,
37
+ createWebFetchRefiner,
38
+ RUNTIME_INSTRUCTIONS,
39
+ } from "./runner-dependencies";
40
+ import { resolveSessionProfileName, type ModelProfile } from "./model-profiles";
43
41
  import { loadSkillCatalog } from "../skills/skill-loader";
44
42
  import { loadProjectSlashCommands } from "../tui/project-slash-commands";
43
+ import { createWorkspaceFileLister } from "../tui/workspace-file-search";
45
44
 
46
- export async function runTui(options: { profileName?: string } = {}): Promise<void> {
47
- const profiles = await loadModelProfiles();
48
- const config = readRunnerConfig(
49
- options.profileName !== undefined ? { profileName: options.profileName } : {},
50
- profiles,
51
- );
45
+ export type RunTuiOptions = {
46
+ readonly publicConfig: ResolvedPublicConfig;
47
+ readonly initialRunnerConfig: RunnerConfig;
48
+ readonly env: NodeJS.ProcessEnv;
49
+ };
50
+
51
+ export async function runTui(options: RunTuiOptions): Promise<void> {
52
+ const profiles =
53
+ options.publicConfig.mode === "profile" ? options.publicConfig.profiles : undefined;
54
+ const config = options.initialRunnerConfig;
52
55
  const workspaceRoot = await realpath(config.workspaceRoot);
53
56
  let controller: DefaultTuiSessionController | undefined;
54
57
  let instance: ReturnType<typeof render> | undefined;
@@ -64,7 +67,11 @@ export async function runTui(options: { profileName?: string } = {}): Promise<vo
64
67
  sessionId: SessionId,
65
68
  sink: EventSink,
66
69
  ): Promise<RuntimeSession> => {
67
- const modelClient = createRunnerModelClient(sessionConfig);
70
+ const modelClient = createRunnerModelClient(
71
+ sessionConfig,
72
+ undefined,
73
+ options.env,
74
+ );
68
75
  const projectInstructions = await loadProjectInstructions(workspaceRoot);
69
76
  const skillCatalog = await loadSkillCatalog({ workspaceRoot });
70
77
  const common = {
@@ -84,7 +91,8 @@ export async function runTui(options: { profileName?: string } = {}): Promise<vo
84
91
  projectInstruction: projectInstructionManifest(projectInstructions),
85
92
  skillCatalog,
86
93
  presentationSinks: [sink],
87
- webFetchRefiner: createWebFetchRefinerFromEnv(sessionConfig),
94
+ webFetchRefiner: createWebFetchRefiner(sessionConfig, options.env),
95
+ toolingConfig: options.publicConfig.tooling,
88
96
  };
89
97
  if (mode === "resume") {
90
98
  return createRuntimeSession({
@@ -118,18 +126,12 @@ export async function runTui(options: { profileName?: string } = {}): Promise<vo
118
126
  ): Promise<ManagedTuiSessionBinding> => {
119
127
  const deferred = new DeferredProjectionSink();
120
128
  const summary = await catalog.get(sessionId);
121
- const resumeConfig =
122
- profiles === undefined
123
- ? { ...config, sessionId }
124
- : readRunnerConfig(
125
- {
126
- sessionId,
127
- profileName: resolveSessionProfileName(profiles, summary),
128
- workspaceRoot: config.workspaceRoot,
129
- maxIterations: config.maxIterations,
130
- },
131
- profiles,
132
- );
129
+ const resumeConfig = deriveRunnerConfig(options.publicConfig, {
130
+ sessionId,
131
+ ...(profiles === undefined
132
+ ? {}
133
+ : { profileName: resolveSessionProfileName(profiles, summary) }),
134
+ });
133
135
  const runtimeSession = await createSessionForConfig(
134
136
  resumeConfig,
135
137
  "resume",
@@ -195,15 +197,10 @@ export async function runTui(options: { profileName?: string } = {}): Promise<vo
195
197
  throw new Error("Model profiles are not configured.");
196
198
  }
197
199
  return createNewSessionBinding(
198
- readRunnerConfig(
199
- {
200
- sessionId: createUuidV7() as SessionId,
201
- workspaceRoot: config.workspaceRoot,
202
- profileName: profile.name,
203
- maxIterations: config.maxIterations,
204
- },
205
- profiles,
206
- ),
200
+ deriveRunnerConfig(options.publicConfig, {
201
+ sessionId: createUuidV7() as SessionId,
202
+ profileName: profile.name,
203
+ }),
207
204
  );
208
205
  };
209
206
 
@@ -212,21 +209,18 @@ export async function runTui(options: { profileName?: string } = {}): Promise<vo
212
209
  ): Promise<ManagedTuiSessionBinding> => {
213
210
  const sessionId = createUuidV7() as SessionId;
214
211
  if (profiles === undefined) {
215
- return createNewSessionBinding({ ...config, sessionId });
212
+ return createNewSessionBinding(
213
+ deriveRunnerConfig(options.publicConfig, { sessionId }),
214
+ );
216
215
  }
217
216
  if (current.profileName === undefined) {
218
217
  throw new Error("Current session does not have a model profile.");
219
218
  }
220
219
  return createNewSessionBinding(
221
- readRunnerConfig(
222
- {
223
- sessionId,
224
- workspaceRoot: config.workspaceRoot,
225
- profileName: current.profileName,
226
- maxIterations: config.maxIterations,
227
- },
228
- profiles,
229
- ),
220
+ deriveRunnerConfig(options.publicConfig, {
221
+ sessionId,
222
+ profileName: current.profileName,
223
+ }),
230
224
  );
231
225
  };
232
226
 
@@ -251,7 +245,16 @@ export async function runTui(options: { profileName?: string } = {}): Promise<vo
251
245
  history={promptHistory}
252
246
  projectSlashCommands={projectSlashCommands}
253
247
  profiles={profiles}
254
- persistDefaultProfile={persistDefaultProfile}
248
+ persistDefaultProfile={
249
+ options.publicConfig.mode === "profile"
250
+ ? options.publicConfig.persistDefaultProfile
251
+ : undefined
252
+ }
253
+ fileLister={createWorkspaceFileLister({
254
+ command: options.publicConfig.tooling.ripgrepPath,
255
+ timeoutMs: options.publicConfig.tooling.grepTimeoutMs,
256
+ maxBufferBytes: options.publicConfig.tooling.grepMaxBufferBytes,
257
+ })}
255
258
  onQuit={() => {
256
259
  quitRequested = true;
257
260
  }}
@@ -58,11 +58,8 @@ export async function createMcpManager(
58
58
  options: CreateMcpManagerOptions,
59
59
  ): Promise<McpManager> {
60
60
  const clientFactory = options.clientFactory ?? stdioClientFactory;
61
- const timeoutMs =
62
- options.timeoutMs ?? parsePositiveIntegerEnv("TINKER_MCP_TIMEOUT_MS");
63
- const maxObservationChars =
64
- options.maxObservationChars ??
65
- parsePositiveIntegerEnv("TINKER_MCP_MAX_OBSERVATION_CHARS");
61
+ const timeoutMs = options.timeoutMs;
62
+ const maxObservationChars = options.maxObservationChars;
66
63
  const connections: McpClientConnection[] = [];
67
64
  const executors: ToolExecutor[] = [];
68
65
  const servers: McpServerInventory[] = [];
@@ -227,20 +224,6 @@ async function closeConnections(
227
224
  }
228
225
  }
229
226
 
230
- function parsePositiveIntegerEnv(name: string): number | undefined {
231
- const value = process.env[name];
232
- if (value === undefined || value.trim() === "") {
233
- return undefined;
234
- }
235
-
236
- const parsed = Number(value);
237
- if (!Number.isInteger(parsed) || parsed <= 0) {
238
- throw new Error(`${name} must be a positive integer; received ${value}`);
239
- }
240
-
241
- return parsed;
242
- }
243
-
244
227
  async function stdioClientFactory(
245
228
  serverName: string,
246
229
  serverConfig: McpServerConfig,
@@ -8,10 +8,9 @@ import type {
8
8
  ToolExecutionContext,
9
9
  ToolExecutor,
10
10
  } from "../tools/types";
11
+ import { DEFAULT_PUBLIC_TOOLING_CONFIG } from "../cli/public-config-contract";
11
12
 
12
13
  export const MCP_TOOL_NAME_PREFIX = "mcp__";
13
- export const DEFAULT_MCP_TIMEOUT_MS = 60_000;
14
- export const DEFAULT_MCP_MAX_OBSERVATION_CHARS = 40_000;
15
14
 
16
15
  export function mcpToolName(serverName: string, toolName: string): string {
17
16
  return `${MCP_TOOL_NAME_PREFIX}${serverName}__${toolName}`;
@@ -48,9 +47,9 @@ export function createMcpToolExecutor(
48
47
  options: CreateMcpToolExecutorOptions,
49
48
  ): ToolExecutor {
50
49
  const toolName = mcpToolName(options.serverName, options.tool.name);
51
- const timeoutMs = options.timeoutMs ?? DEFAULT_MCP_TIMEOUT_MS;
50
+ const timeoutMs = options.timeoutMs ?? DEFAULT_PUBLIC_TOOLING_CONFIG.mcpTimeoutMs;
52
51
  const maxObservationChars =
53
- options.maxObservationChars ?? DEFAULT_MCP_MAX_OBSERVATION_CHARS;
52
+ options.maxObservationChars ?? DEFAULT_PUBLIC_TOOLING_CONFIG.mcpMaxObservationChars;
54
53
 
55
54
  const base = {
56
55
  toolName,
@@ -14,21 +14,6 @@ export type ModelContextBudget = ModelContextProfile & {
14
14
  triggerTokens: number;
15
15
  };
16
16
 
17
- export function readModelContextProfileFromEnv(
18
- env: NodeJS.ProcessEnv = process.env,
19
- ): ModelContextProfile {
20
- return createModelContextProfile({
21
- contextWindowTokens: parseRequiredTokenCount(
22
- env.TINKER_CONTEXT_WINDOW_TOKENS,
23
- "TINKER_CONTEXT_WINDOW_TOKENS",
24
- ),
25
- maxSupportedOutputTokens: parseRequiredTokenCount(
26
- env.TINKER_MAX_SUPPORTED_OUTPUT_TOKENS,
27
- "TINKER_MAX_SUPPORTED_OUTPUT_TOKENS",
28
- ),
29
- });
30
- }
31
-
32
17
  export function createModelContextProfile(
33
18
  input: ModelContextProfile,
34
19
  ): ModelContextProfile {
@@ -102,21 +87,6 @@ export function assertMatchingContextBudget(
102
87
  }
103
88
  }
104
89
 
105
- function parseRequiredTokenCount(value: string | undefined, name: string): number {
106
- if (value === undefined || value.trim() === "") {
107
- throw new Error(`${name} is required; received ${displayValue(value)}.`);
108
- }
109
- if (!/^\d+$/.test(value)) {
110
- throw new Error(
111
- `${name} must be a positive safe integer token count; received ${displayValue(value)}.`,
112
- );
113
- }
114
-
115
- const parsed = Number(value);
116
- requirePositiveSafeInteger(parsed, name, value);
117
- return parsed;
118
- }
119
-
120
90
  function requirePositiveSafeInteger(
121
91
  value: number,
122
92
  name: string,
package/src/tools/bash.ts CHANGED
@@ -11,6 +11,7 @@ import { buildOutputSnapshotFromText } from "./task-output-snapshot";
11
11
  import { defineToolExecutor } from "./types";
12
12
  import type { TaskOutputSnapshot } from "./task-output";
13
13
  import type { BashRawResult, ToolExecutionContext, ToolExecutor } from "./types";
14
+ import { DEFAULT_PUBLIC_TOOLING_CONFIG } from "../cli/public-config-contract";
14
15
 
15
16
  type BashArgs = {
16
17
  command: string;
@@ -27,25 +28,16 @@ export type BashToolOptions = {
27
28
  maxTimeoutMs?: number;
28
29
  };
29
30
 
30
- const defaultForegroundTimeoutMs = 5_000;
31
- const defaultMaxForegroundTimeoutMs = 600_000;
32
-
33
31
  export function createBashToolExecutor(options: BashToolOptions): ToolExecutor {
34
32
  const maxTimeoutMs =
35
- options.maxTimeoutMs ??
36
- parsePositiveInteger(
37
- process.env.TINKER_BASH_MAX_TIMEOUT_MS,
38
- defaultMaxForegroundTimeoutMs,
39
- );
33
+ options.maxTimeoutMs ?? DEFAULT_PUBLIC_TOOLING_CONFIG.bashMaxTimeoutMs;
40
34
  const defaultTimeoutMs =
41
- options.defaultTimeoutMs ??
42
- Math.min(
43
- parsePositiveInteger(
44
- process.env.TINKER_BASH_DEFAULT_TIMEOUT_MS,
45
- defaultForegroundTimeoutMs,
46
- ),
47
- maxTimeoutMs,
35
+ options.defaultTimeoutMs ?? DEFAULT_PUBLIC_TOOLING_CONFIG.bashDefaultTimeoutMs;
36
+ if (defaultTimeoutMs > maxTimeoutMs) {
37
+ throw new Error(
38
+ `Bash default timeout must not exceed max timeout; received ${defaultTimeoutMs} > ${maxTimeoutMs}.`,
48
39
  );
40
+ }
49
41
 
50
42
  return defineToolExecutor("bash", {
51
43
  definition: {
@@ -161,7 +153,7 @@ export function createBashToolExecutor(options: BashToolOptions): ToolExecutor {
161
153
 
162
154
  export function parseBashArgs(
163
155
  args: unknown,
164
- maxTimeoutMs = defaultMaxForegroundTimeoutMs,
156
+ maxTimeoutMs = DEFAULT_PUBLIC_TOOLING_CONFIG.bashMaxTimeoutMs,
165
157
  ): { ok: true; value: BashArgs } | { ok: false; error: string } {
166
158
  if (!isRecord(args)) {
167
159
  return { ok: false, error: "Bash arguments must be an object." };
@@ -401,15 +393,6 @@ function parseOptionalTimeout(
401
393
  return { ok: true, value };
402
394
  }
403
395
 
404
- function parsePositiveInteger(value: string | undefined, fallback: number): number {
405
- if (value === undefined) {
406
- return fallback;
407
- }
408
-
409
- const parsed = Number(value);
410
- return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
411
- }
412
-
413
396
  function lastPipelineCommandName(command: string): string | undefined {
414
397
  const segments = command.split("|");
415
398
  const lastSegment = segments.at(-1)?.trim();
package/src/tools/grep.ts CHANGED
@@ -32,6 +32,11 @@ type GrepArgs = {
32
32
  export type GrepToolOptions = {
33
33
  workspaceRoot: string;
34
34
  cwdState: CwdState;
35
+ ripgrep?: {
36
+ command?: string;
37
+ timeoutMs?: number;
38
+ maxBufferBytes?: number;
39
+ };
35
40
  };
36
41
 
37
42
  const ignoredDirectories = [
@@ -174,7 +179,10 @@ export function createGrepToolExecutor(options: GrepToolOptions): ToolExecutor {
174
179
  }
175
180
 
176
181
  const rgArgs = buildRipgrepArgs(input, mode, absoluteSearchPath);
177
- const rg = await ripGrep(rgArgs, { signal: context.signal });
182
+ const rg = await ripGrep(rgArgs, {
183
+ signal: context.signal,
184
+ ...options.ripgrep,
185
+ });
178
186
 
179
187
  if (!rg.ok) {
180
188
  return grepFailure({
@@ -31,6 +31,10 @@ import { ToolExecutionFatalError } from "./types";
31
31
  import type { SkillCatalogSnapshot } from "../skills/skill-loader";
32
32
  import type { SkillActivationCoordinator } from "../skills/skill-context";
33
33
  import { createSkillToolExecutor } from "../skills/skill-tool";
34
+ import {
35
+ DEFAULT_PUBLIC_TOOLING_CONFIG,
36
+ type PublicToolingConfig,
37
+ } from "../cli/public-config-contract";
34
38
 
35
39
  export class ToolRegistry {
36
40
  private readonly tools = new Map<string, ToolExecutor>();
@@ -128,10 +132,12 @@ export function createDefaultTooling(options: {
128
132
  taskStopGraceMs?: number;
129
133
  skillCatalog?: SkillCatalogSnapshot;
130
134
  skillCoordinator?: SkillActivationCoordinator;
135
+ toolingConfig?: PublicToolingConfig;
131
136
  }): DefaultTooling {
132
137
  const snapshots: FileSnapshotStore = new Map();
133
138
  const registry = new ToolRegistry();
134
139
  const runtimeSession = options.runtimeSession;
140
+ const toolingConfig = options.toolingConfig ?? DEFAULT_PUBLIC_TOOLING_CONFIG;
135
141
  const cwdState = createCwdState(options.workspaceRoot);
136
142
  const taskManager = new ShellTaskManager({
137
143
  workspaceRoot: options.workspaceRoot,
@@ -149,6 +155,11 @@ export function createDefaultTooling(options: {
149
155
  createGrepToolExecutor({
150
156
  workspaceRoot: options.workspaceRoot,
151
157
  cwdState,
158
+ ripgrep: {
159
+ command: toolingConfig.ripgrepPath,
160
+ timeoutMs: toolingConfig.grepTimeoutMs,
161
+ maxBufferBytes: toolingConfig.grepMaxBufferBytes,
162
+ },
152
163
  }),
153
164
  );
154
165
  registry.register(
@@ -192,13 +203,15 @@ export function createDefaultTooling(options: {
192
203
  workspaceRoot: options.workspaceRoot,
193
204
  cwdState,
194
205
  taskManager,
206
+ defaultTimeoutMs: toolingConfig.bashDefaultTimeoutMs,
207
+ maxTimeoutMs: toolingConfig.bashMaxTimeoutMs,
195
208
  }),
196
209
  );
197
210
  registry.register(createTaskListToolExecutor({ taskManager }));
198
211
  registry.register(createTaskOutputToolExecutor({ taskManager }));
199
212
  registry.register(createTaskStopToolExecutor({ taskManager }));
200
213
 
201
- const exaApiKey = options.exaApiKey ?? process.env.EXA_API_KEY;
214
+ const exaApiKey = options.exaApiKey ?? toolingConfig.exaApiKey;
202
215
  const hasExaKey = exaApiKey !== undefined && exaApiKey.trim() !== "";
203
216
 
204
217
  if (hasExaKey) {
@@ -209,6 +222,7 @@ export function createDefaultTooling(options: {
209
222
  createWebFetchToolExecutor({
210
223
  exaApiKey: hasExaKey ? exaApiKey : undefined,
211
224
  refiner: options.webFetchRefiner,
225
+ refineThreshold: toolingConfig.webFetchRefineThreshold,
212
226
  }),
213
227
  );
214
228