topchester-ai 0.67.0 → 0.68.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/bin.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as runTopchesterCli } from "./cli-DfgLjXLl.mjs";
2
+ import { t as runTopchesterCli } from "./cli-CRhzHqMB.mjs";
3
3
  //#region src/bin.ts
4
4
  await runTopchesterCli();
5
5
  //#endregion
@@ -421,6 +421,7 @@ function toAiSdkToolSet(definitions) {
421
421
  //#region src/agent/tools/process-runner.ts
422
422
  const DEFAULT_MAX_OUTPUT_BYTES$1 = 4e4;
423
423
  const DEFAULT_MAX_OUTPUT_LINES = 1e3;
424
+ const TERMINAL_BENCH_MAX_OUTPUT_BYTES = 2e4;
424
425
  async function runProcess(options) {
425
426
  const startedAt = Date.now();
426
427
  const pathEnv = options.pathEnv ?? process.env.PATH ?? "";
@@ -786,6 +787,8 @@ async function runBashCommand(workspaceRoot, args, options = {}) {
786
787
  pathEnv: options.pathEnv,
787
788
  timeoutMs: args.timeout_ms,
788
789
  abortSignal: options.abortSignal,
790
+ outputLimitBytes: options.benchmarkProfile === "terminal-bench" ? TERMINAL_BENCH_MAX_OUTPUT_BYTES : void 0,
791
+ outputLimitLines: options.benchmarkProfile === "terminal-bench" ? 500 : void 0,
789
792
  env: { NO_COLOR: "1" },
790
793
  missingExecutableLabel: "bash"
791
794
  });
@@ -3585,7 +3588,10 @@ const inspectCommandTool = defineTool({
3585
3588
  description: "Run a narrowly validated read-only command for repository orientation.",
3586
3589
  prompt: "inspect_command: run a safe read-only discovery command inside the workspace for quick repo orientation; prefer read_file, list_files, grep, and find_file for exact file tasks, and do not use it for builds, tests, installs, network, shell scripts, edits, or user-requested specific commands such as node --version, which node, or pnpm --version. To use it, reply with only JSON: {\"tool\":\"inspect_command\",\"args\":{\"command\":\"pwd && rg --files docs/plans | head -20\",\"workdir\":\".\",\"timeout_ms\":10000}}",
3587
3590
  argsSchema: inspectCommandArgsSchema,
3588
- execute: (context, args) => inspectWorkspaceCommand(context.workspaceRoot, args, { pathEnv: context.pathEnv })
3591
+ execute: (context, args) => inspectWorkspaceCommand(context.workspaceRoot, args, {
3592
+ pathEnv: context.pathEnv,
3593
+ benchmarkProfile: context.benchmarkProfile
3594
+ })
3589
3595
  });
3590
3596
  async function inspectWorkspaceCommand(workspaceRoot, args, options = {}) {
3591
3597
  const startedAt = Date.now();
@@ -3597,7 +3603,8 @@ async function inspectWorkspaceCommand(workspaceRoot, args, options = {}) {
3597
3603
  const result = await executePlan(decision.plan, {
3598
3604
  cwd,
3599
3605
  pathEnv: options.pathEnv ?? process.env.PATH ?? "",
3600
- deadlineAt
3606
+ deadlineAt,
3607
+ benchmarkProfile: options.benchmarkProfile
3601
3608
  });
3602
3609
  const durationMs = Date.now() - startedAt;
3603
3610
  return {
@@ -3635,14 +3642,16 @@ async function executePlan(plan, context) {
3635
3642
  timedOut: true,
3636
3643
  truncated
3637
3644
  };
3645
+ const outputLimits = getOutputLimits(context.benchmarkProfile);
3638
3646
  const result = await executePipeline(entry.pipeline, {
3639
3647
  ...context,
3640
3648
  timeoutMs: remainingMs
3641
3649
  });
3642
- stdout = appendBoundedOutput(stdout, result.stdout).output;
3643
- const nextStderr = appendBoundedOutput(stderr, result.stderr);
3650
+ const nextStdout = appendBoundedOutput(stdout, result.stdout, outputLimits);
3651
+ stdout = nextStdout.output;
3652
+ const nextStderr = appendBoundedOutput(stderr, result.stderr, outputLimits);
3644
3653
  stderr = nextStderr.output;
3645
- truncated = truncated || result.truncated || nextStderr.truncated;
3654
+ truncated = truncated || result.truncated || nextStdout.truncated || nextStderr.truncated;
3646
3655
  timedOut = timedOut || result.timedOut;
3647
3656
  lastExitCode = result.exitCode;
3648
3657
  if (result.missingExecutable || result.timedOut) return {
@@ -3682,7 +3691,8 @@ async function executePipeline(pipeline, context) {
3682
3691
  timeoutMs: remainingMs
3683
3692
  });
3684
3693
  input = result.stdout;
3685
- const nextStderr = appendBoundedOutput(stderr, result.stderr);
3694
+ const outputLimits = getOutputLimits(context.benchmarkProfile);
3695
+ const nextStderr = appendBoundedOutput(stderr, result.stderr, outputLimits);
3686
3696
  stderr = nextStderr.output;
3687
3697
  exitCode = result.exitCode;
3688
3698
  timedOut = timedOut || result.timedOut;
@@ -3719,6 +3729,8 @@ async function executeSimpleCommand(command, input, context) {
3719
3729
  cwd: context.cwd,
3720
3730
  pathEnv: context.pathEnv,
3721
3731
  timeoutMs: context.timeoutMs,
3732
+ outputLimitBytes: context.benchmarkProfile === "terminal-bench" ? TERMINAL_BENCH_MAX_OUTPUT_BYTES : void 0,
3733
+ outputLimitLines: context.benchmarkProfile === "terminal-bench" ? 500 : void 0,
3722
3734
  missingExecutableLabel: "inspect_command"
3723
3735
  });
3724
3736
  return {
@@ -3741,6 +3753,13 @@ function shouldExecuteEntry(entry, previousExitCode) {
3741
3753
  function getRemainingTimeoutMs(deadlineAt) {
3742
3754
  return deadlineAt - Date.now();
3743
3755
  }
3756
+ function getOutputLimits(benchmarkProfile) {
3757
+ if (benchmarkProfile !== "terminal-bench") return {};
3758
+ return {
3759
+ maxBytes: TERMINAL_BENCH_MAX_OUTPUT_BYTES,
3760
+ maxLines: 500
3761
+ };
3762
+ }
3744
3763
  function formatInspectCommandContent(result) {
3745
3764
  const sections = [];
3746
3765
  sections.push(result.stdout.trimEnd() || "(no output)");
@@ -4110,18 +4129,28 @@ const planTodoTool = defineTool({
4110
4129
  });
4111
4130
  //#endregion
4112
4131
  //#region src/agent/tools/read-file.ts
4113
- const MAX_UTF8_READ_BYTES = 512 * 1024;
4132
+ const DEFAULT_MAX_UTF8_READ_BYTES = 512 * 1024;
4133
+ const TERMINAL_BENCH_MAX_UTF8_READ_BYTES = 64 * 1024;
4114
4134
  const SAMPLE_BYTES = 256;
4115
4135
  const readFileTool = defineTool({
4116
4136
  name: "read_file",
4117
4137
  description: "Read a UTF-8 file inside the workspace.",
4118
- prompt: "read_file: read a UTF-8 file inside the workspace. To use it, reply with only JSON: {\"tool\":\"read_file\",\"args\":{\"path\":\"package.json\"}}",
4119
- argsSchema: z.object({ path: z.string() }),
4138
+ prompt: "read_file: read a UTF-8 file inside the workspace. For large files, use offset and limit to read a focused byte range. To use it, reply with only JSON: {\"tool\":\"read_file\",\"args\":{\"path\":\"package.json\"}}",
4139
+ argsSchema: z.object({
4140
+ path: z.string(),
4141
+ offset: z.number().int().min(0).optional(),
4142
+ limit: z.number().int().min(1).max(DEFAULT_MAX_UTF8_READ_BYTES).optional()
4143
+ }),
4120
4144
  parallelSafe: true,
4121
4145
  mutatesWorkspace: false,
4122
4146
  resourceKeys: (args) => [`file:${args.path}`],
4123
4147
  execute: async (context, args) => {
4124
- const result = await readWorkspaceFile(context.workspaceRoot, args.path);
4148
+ const result = await readWorkspaceFile(context.workspaceRoot, args.path, {
4149
+ offset: args.offset,
4150
+ limit: args.limit,
4151
+ benchmarkProfile: context.benchmarkProfile,
4152
+ cache: context.readFileCache
4153
+ });
4125
4154
  const projectInstructions = await resolveToolProjectInstructions(context, {
4126
4155
  targetPath: args.path,
4127
4156
  skipWhenTargetIsInstructionFile: true
@@ -4133,14 +4162,19 @@ const readFileTool = defineTool({
4133
4162
  };
4134
4163
  }
4135
4164
  });
4136
- async function readWorkspaceFile(workspaceRoot, path) {
4165
+ async function readWorkspaceFile(workspaceRoot, path, options = {}) {
4137
4166
  const resolvedWorkspace = resolve(workspaceRoot);
4138
4167
  const resolvedPath = isAbsolute(path) ? resolve(path) : resolve(resolvedWorkspace, path);
4139
4168
  const relativePath = relative(resolvedWorkspace, resolvedPath);
4169
+ const maxReadBytes = getReadFileMaxBytes(options.benchmarkProfile);
4140
4170
  if (relativePath.startsWith("..") || isAbsolute(relativePath)) throw new Error(`read_file can only read files inside the workspace: ${path}`);
4141
4171
  const fileStat = await stat(resolvedPath);
4142
4172
  if (!fileStat.isFile()) throw new Error(`read_file can only read regular files: ${path}`);
4143
- if (fileStat.size > MAX_UTF8_READ_BYTES) {
4173
+ const offset = options.offset ?? 0;
4174
+ const requestedLimit = options.limit;
4175
+ const hasExplicitRange = options.offset !== void 0 || requestedLimit !== void 0;
4176
+ if (offset > fileStat.size) throw new Error(`read_file offset ${offset} is beyond end of file ${relativePath || "."} (${fileStat.size} bytes).`);
4177
+ if (!hasExplicitRange && fileStat.size > maxReadBytes) {
4144
4178
  const [sample, hash] = await Promise.all([readFileSample(resolvedPath, SAMPLE_BYTES), hashFile$2(resolvedPath)]);
4145
4179
  return {
4146
4180
  tool: "read_file",
@@ -4149,16 +4183,39 @@ async function readWorkspaceFile(workspaceRoot, path) {
4149
4183
  reason: "too_large",
4150
4184
  relativePath: relativePath || ".",
4151
4185
  bytes: fileStat.size,
4186
+ maxReadBytes,
4152
4187
  sample
4153
4188
  }),
4154
4189
  hash,
4155
4190
  skipped: "too_large",
4156
4191
  bytes: fileStat.size,
4157
- warning: `Skipped reading ${relativePath || "."}: file is ${fileStat.size} bytes, above the ${MAX_UTF8_READ_BYTES} byte read_file limit.`
4192
+ warning: `Skipped reading ${relativePath || "."}: file is ${fileStat.size} bytes, above the ${maxReadBytes} byte read_file limit.`
4158
4193
  };
4159
4194
  }
4160
- const bytes = await readFile(resolvedPath);
4161
- const hash = `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
4195
+ const hashPromise = hashFile$2(resolvedPath);
4196
+ const bytes = hasExplicitRange ? await readFileRange(resolvedPath, offset, Math.min(requestedLimit ?? maxReadBytes, maxReadBytes)) : await readFile(resolvedPath);
4197
+ const hash = await hashPromise;
4198
+ const length = bytes.length;
4199
+ const truncated = hasExplicitRange ? offset + length < fileStat.size : false;
4200
+ const cacheKey = makeReadFileCacheKey(relativePath || ".", hash, fileStat.size, offset, length);
4201
+ if (options.cache?.seen.has(cacheKey)) return {
4202
+ tool: "read_file",
4203
+ path: relativePath || ".",
4204
+ content: formatDedupedReadSummary({
4205
+ relativePath: relativePath || ".",
4206
+ bytes: fileStat.size,
4207
+ hash,
4208
+ offset,
4209
+ length
4210
+ }),
4211
+ hash,
4212
+ bytes: fileStat.size,
4213
+ offset,
4214
+ length,
4215
+ deduped: true,
4216
+ truncated,
4217
+ warning: `Skipped repeated read of unchanged ${relativePath || "."} bytes ${offset}-${offset + length}.`
4218
+ };
4162
4219
  if (looksBinary$1(bytes) || !isValidUtf8(bytes)) {
4163
4220
  const sample = bytes.subarray(0, SAMPLE_BYTES);
4164
4221
  return {
@@ -4168,6 +4225,7 @@ async function readWorkspaceFile(workspaceRoot, path) {
4168
4225
  reason: "binary",
4169
4226
  relativePath: relativePath || ".",
4170
4227
  bytes: fileStat.size,
4228
+ maxReadBytes,
4171
4229
  sample
4172
4230
  }),
4173
4231
  hash,
@@ -4177,12 +4235,36 @@ async function readWorkspaceFile(workspaceRoot, path) {
4177
4235
  };
4178
4236
  }
4179
4237
  const content = bytes.toString("utf8");
4180
- return {
4238
+ options.cache?.seen.set(cacheKey, {
4239
+ hash,
4240
+ bytes: fileStat.size,
4241
+ offset,
4242
+ length
4243
+ });
4244
+ const result = {
4181
4245
  tool: "read_file",
4182
4246
  path: relativePath || ".",
4183
- content,
4247
+ content: formatReadFileContent({
4248
+ content,
4249
+ relativePath: relativePath || ".",
4250
+ bytes: fileStat.size,
4251
+ offset,
4252
+ length,
4253
+ truncated,
4254
+ ranged: hasExplicitRange
4255
+ }),
4184
4256
  hash
4185
4257
  };
4258
+ if (hasExplicitRange) {
4259
+ result.bytes = fileStat.size;
4260
+ result.offset = offset;
4261
+ result.length = length;
4262
+ result.truncated = truncated;
4263
+ }
4264
+ return result;
4265
+ }
4266
+ function createReadFileCache() {
4267
+ return { seen: /* @__PURE__ */ new Map() };
4186
4268
  }
4187
4269
  function looksBinary$1(bytes) {
4188
4270
  return bytes.subarray(0, Math.min(bytes.length, SAMPLE_BYTES)).includes(0);
@@ -4196,14 +4278,28 @@ function isValidUtf8(bytes) {
4196
4278
  }
4197
4279
  }
4198
4280
  function formatSkippedReadSummary(options) {
4199
- const reason = options.reason === "binary" ? "read_file did not return file contents because this file appears to be binary or non-UTF-8." : `read_file did not return file contents because this file is ${options.bytes} bytes, above the ${MAX_UTF8_READ_BYTES} byte limit.`;
4281
+ const reason = options.reason === "binary" ? "read_file did not return file contents because this file appears to be binary or non-UTF-8." : `read_file did not return file contents because this file is ${options.bytes} bytes, above the ${options.maxReadBytes} byte limit.`;
4200
4282
  const sampleHex = options.sample.length > 0 ? options.sample.toString("hex").match(/.{1,2}/g)?.join(" ") : "";
4201
4283
  return [
4202
4284
  reason,
4203
4285
  `path: ${options.relativePath}`,
4204
4286
  `bytes: ${options.bytes}`,
4205
4287
  sampleHex ? `first_${options.sample.length}_bytes_hex: ${sampleHex}` : "first_bytes_hex: <empty>",
4206
- "Use shell inspection tools such as `file`, `xxd`, `readelf`, `objdump`, `strings`, or a focused parser instead of reading the whole file into context."
4288
+ "Use read_file with offset and limit for a focused byte range, or shell inspection tools such as `file`, `xxd`, `readelf`, `objdump`, `strings`, or a focused parser instead of reading the whole file into context."
4289
+ ].join("\n");
4290
+ }
4291
+ function formatReadFileContent(options) {
4292
+ if (!options.ranged) return options.content;
4293
+ return [`read_file range: ${options.relativePath} bytes ${options.offset}-${options.offset + options.length} of ${options.bytes}${options.truncated ? " (truncated)" : ""}`, options.content].join("\n");
4294
+ }
4295
+ function formatDedupedReadSummary(options) {
4296
+ return [
4297
+ "read_file did not return file contents because this exact unchanged file range was already shown in this session.",
4298
+ `path: ${options.relativePath}`,
4299
+ `bytes: ${options.bytes}`,
4300
+ `hash: ${options.hash}`,
4301
+ `range: ${options.offset}-${options.offset + options.length}`,
4302
+ "Use a different offset/limit if you need another part of the file."
4207
4303
  ].join("\n");
4208
4304
  }
4209
4305
  async function readFileSample(path, maxBytes) {
@@ -4216,6 +4312,16 @@ async function readFileSample(path, maxBytes) {
4216
4312
  await handle.close();
4217
4313
  }
4218
4314
  }
4315
+ async function readFileRange(path, offset, maxBytes) {
4316
+ const handle = await open(path, "r");
4317
+ try {
4318
+ const sample = Buffer.alloc(maxBytes);
4319
+ const { bytesRead } = await handle.read(sample, 0, maxBytes, offset);
4320
+ return sample.subarray(0, bytesRead);
4321
+ } finally {
4322
+ await handle.close();
4323
+ }
4324
+ }
4219
4325
  async function hashFile$2(path) {
4220
4326
  const hash = createHash("sha256");
4221
4327
  await new Promise((resolvePromise, reject) => {
@@ -4226,6 +4332,12 @@ async function hashFile$2(path) {
4226
4332
  });
4227
4333
  return `sha256:${hash.digest("hex")}`;
4228
4334
  }
4335
+ function getReadFileMaxBytes(benchmarkProfile) {
4336
+ return benchmarkProfile === "terminal-bench" ? TERMINAL_BENCH_MAX_UTF8_READ_BYTES : DEFAULT_MAX_UTF8_READ_BYTES;
4337
+ }
4338
+ function makeReadFileCacheKey(path, hash, bytes, offset, length) {
4339
+ return `${path}\0${hash}\0${bytes}\0${offset}\0${length}`;
4340
+ }
4229
4341
  //#endregion
4230
4342
  //#region src/agent/tools/command-policy.ts
4231
4343
  const PACKAGE_MANAGERS = new Set([
@@ -4667,18 +4779,29 @@ const runValidatorTool = defineTool({
4667
4779
  timeout_ms: z.number().int().min(100).max(6e5).optional().default(12e4)
4668
4780
  }),
4669
4781
  requiresExclusiveWorkspace: true,
4670
- execute: async (context, args) => runValidatorCommand(context.workspaceRoot, args, context.pathEnv, context.abortSignal)
4782
+ execute: async (context, args) => runValidatorCommand(context.workspaceRoot, args, {
4783
+ pathEnv: context.pathEnv,
4784
+ abortSignal: context.abortSignal,
4785
+ benchmarkProfile: context.benchmarkProfile
4786
+ })
4671
4787
  });
4672
- async function runValidatorCommand(workspaceRoot, args, pathEnv, abortSignal) {
4788
+ async function runValidatorCommand(workspaceRoot, args, pathEnvOrOptions, legacyAbortSignal) {
4789
+ const options = typeof pathEnvOrOptions === "string" ? {
4790
+ pathEnv: pathEnvOrOptions,
4791
+ abortSignal: legacyAbortSignal,
4792
+ benchmarkProfile: void 0
4793
+ } : pathEnvOrOptions ?? {};
4673
4794
  const decision = await validateValidatorCommand(args, { workspaceRoot });
4674
4795
  if (!decision.allowed) throw new Error(decision.reason);
4675
4796
  const result = await runProcess({
4676
4797
  executable: decision.plan.executable,
4677
4798
  args: decision.plan.args,
4678
4799
  cwd: decision.plan.cwd,
4679
- pathEnv,
4800
+ pathEnv: options.pathEnv,
4680
4801
  timeoutMs: args.timeout_ms,
4681
- abortSignal,
4802
+ abortSignal: options.abortSignal,
4803
+ outputLimitBytes: options.benchmarkProfile === "terminal-bench" ? TERMINAL_BENCH_MAX_OUTPUT_BYTES : void 0,
4804
+ outputLimitLines: options.benchmarkProfile === "terminal-bench" ? 500 : void 0,
4682
4805
  env: {
4683
4806
  CI: "1",
4684
4807
  NO_COLOR: "1"
@@ -12149,6 +12272,7 @@ async function executeToolCall(workspaceRoot, call, options = {}) {
12149
12272
  projectInstructions: options.projectInstructions,
12150
12273
  currentUserMessage: options.currentUserMessage,
12151
12274
  benchmarkProfile: options.benchmarkProfile,
12275
+ readFileCache: options.readFileCache,
12152
12276
  eventSink: options.eventSink,
12153
12277
  abortSignal: options.abortSignal,
12154
12278
  toolCallId: options.toolCallId
@@ -13403,6 +13527,7 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
13403
13527
  const requireFinishTask = isFinishTaskRequiredByEnv() && isToolAllowed(permissions, "finish_task");
13404
13528
  const projectInstructionToolState = { shownSourceKeys: /* @__PURE__ */ new Set() };
13405
13529
  const persistedProjectInstructionKeys = /* @__PURE__ */ new Set();
13530
+ const readFileCache = createReadFileCache();
13406
13531
  try {
13407
13532
  for (let toolCalls = 0; toolCalls <= maxToolCallsPerTurn; toolCalls += 1) {
13408
13533
  const startedAt = Date.now();
@@ -13588,6 +13713,7 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
13588
13713
  projectInstructions: projectInstructionToolState,
13589
13714
  currentUserMessage: message,
13590
13715
  benchmarkProfile: options.benchmarkProfile,
13716
+ readFileCache,
13591
13717
  abortSignal,
13592
13718
  toolCallId: entry.toolCallId,
13593
13719
  toolCatalog,
@@ -13659,6 +13785,7 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
13659
13785
  projectInstructions: projectInstructionToolState,
13660
13786
  currentUserMessage: message,
13661
13787
  benchmarkProfile: options.benchmarkProfile,
13788
+ readFileCache,
13662
13789
  abortSignal,
13663
13790
  toolCallId: entry.toolCallId,
13664
13791
  toolCatalog
@@ -13737,6 +13864,7 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
13737
13864
  projectInstructions: projectInstructionToolState,
13738
13865
  currentUserMessage: message,
13739
13866
  benchmarkProfile: options.benchmarkProfile,
13867
+ readFileCache,
13740
13868
  abortSignal,
13741
13869
  toolCallId: toolCall.id,
13742
13870
  toolCatalog,
@@ -16646,4 +16774,4 @@ function formatDryRunSyncStatus(status) {
16646
16774
  //#endregion
16647
16775
  export { runTopchesterCli as t };
16648
16776
 
16649
- //# sourceMappingURL=cli-DfgLjXLl.mjs.map
16777
+ //# sourceMappingURL=cli-CRhzHqMB.mjs.map