topchester-ai 0.66.0 → 0.67.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-BS67FTk8.mjs";
2
+ import { t as runTopchesterCli } from "./cli-DfgLjXLl.mjs";
3
3
  //#region src/bin.ts
4
4
  await runTopchesterCli();
5
5
  //#endregion
@@ -9,8 +9,9 @@ import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
9
9
  import { generateText, stepCountIs, streamText, tool } from "ai";
10
10
  import { ZodError, z } from "zod";
11
11
  import { execFile, spawn } from "node:child_process";
12
- import { accessSync, constants, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
12
+ import { accessSync, constants, createReadStream, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
13
13
  import { createHash, randomUUID } from "node:crypto";
14
+ import { TextDecoder as TextDecoder$1 } from "node:util";
14
15
  import { parseDocument } from "yaml";
15
16
  import { fileURLToPath } from "node:url";
16
17
  import pino from "pino";
@@ -4107,6 +4108,10 @@ const planTodoTool = defineTool({
4107
4108
  };
4108
4109
  }
4109
4110
  });
4111
+ //#endregion
4112
+ //#region src/agent/tools/read-file.ts
4113
+ const MAX_UTF8_READ_BYTES = 512 * 1024;
4114
+ const SAMPLE_BYTES = 256;
4110
4115
  const readFileTool = defineTool({
4111
4116
  name: "read_file",
4112
4117
  description: "Read a UTF-8 file inside the workspace.",
@@ -4133,15 +4138,94 @@ async function readWorkspaceFile(workspaceRoot, path) {
4133
4138
  const resolvedPath = isAbsolute(path) ? resolve(path) : resolve(resolvedWorkspace, path);
4134
4139
  const relativePath = relative(resolvedWorkspace, resolvedPath);
4135
4140
  if (relativePath.startsWith("..") || isAbsolute(relativePath)) throw new Error(`read_file can only read files inside the workspace: ${path}`);
4141
+ const fileStat = await stat(resolvedPath);
4142
+ if (!fileStat.isFile()) throw new Error(`read_file can only read regular files: ${path}`);
4143
+ if (fileStat.size > MAX_UTF8_READ_BYTES) {
4144
+ const [sample, hash] = await Promise.all([readFileSample(resolvedPath, SAMPLE_BYTES), hashFile$2(resolvedPath)]);
4145
+ return {
4146
+ tool: "read_file",
4147
+ path: relativePath || ".",
4148
+ content: formatSkippedReadSummary({
4149
+ reason: "too_large",
4150
+ relativePath: relativePath || ".",
4151
+ bytes: fileStat.size,
4152
+ sample
4153
+ }),
4154
+ hash,
4155
+ skipped: "too_large",
4156
+ bytes: fileStat.size,
4157
+ warning: `Skipped reading ${relativePath || "."}: file is ${fileStat.size} bytes, above the ${MAX_UTF8_READ_BYTES} byte read_file limit.`
4158
+ };
4159
+ }
4136
4160
  const bytes = await readFile(resolvedPath);
4161
+ const hash = `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
4162
+ if (looksBinary$1(bytes) || !isValidUtf8(bytes)) {
4163
+ const sample = bytes.subarray(0, SAMPLE_BYTES);
4164
+ return {
4165
+ tool: "read_file",
4166
+ path: relativePath || ".",
4167
+ content: formatSkippedReadSummary({
4168
+ reason: "binary",
4169
+ relativePath: relativePath || ".",
4170
+ bytes: fileStat.size,
4171
+ sample
4172
+ }),
4173
+ hash,
4174
+ skipped: "binary",
4175
+ bytes: fileStat.size,
4176
+ warning: `Skipped reading ${relativePath || "."}: file appears to be binary or non-UTF-8.`
4177
+ };
4178
+ }
4137
4179
  const content = bytes.toString("utf8");
4138
4180
  return {
4139
4181
  tool: "read_file",
4140
4182
  path: relativePath || ".",
4141
4183
  content,
4142
- hash: `sha256:${createHash("sha256").update(bytes).digest("hex")}`
4184
+ hash
4143
4185
  };
4144
4186
  }
4187
+ function looksBinary$1(bytes) {
4188
+ return bytes.subarray(0, Math.min(bytes.length, SAMPLE_BYTES)).includes(0);
4189
+ }
4190
+ function isValidUtf8(bytes) {
4191
+ try {
4192
+ new TextDecoder$1("utf-8", { fatal: true }).decode(bytes);
4193
+ return true;
4194
+ } catch {
4195
+ return false;
4196
+ }
4197
+ }
4198
+ 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.`;
4200
+ const sampleHex = options.sample.length > 0 ? options.sample.toString("hex").match(/.{1,2}/g)?.join(" ") : "";
4201
+ return [
4202
+ reason,
4203
+ `path: ${options.relativePath}`,
4204
+ `bytes: ${options.bytes}`,
4205
+ 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."
4207
+ ].join("\n");
4208
+ }
4209
+ async function readFileSample(path, maxBytes) {
4210
+ const handle = await open(path, "r");
4211
+ try {
4212
+ const sample = Buffer.alloc(maxBytes);
4213
+ const { bytesRead } = await handle.read(sample, 0, maxBytes, 0);
4214
+ return sample.subarray(0, bytesRead);
4215
+ } finally {
4216
+ await handle.close();
4217
+ }
4218
+ }
4219
+ async function hashFile$2(path) {
4220
+ const hash = createHash("sha256");
4221
+ await new Promise((resolvePromise, reject) => {
4222
+ const stream = createReadStream(path);
4223
+ stream.on("data", (chunk) => hash.update(chunk));
4224
+ stream.on("error", reject);
4225
+ stream.on("end", resolvePromise);
4226
+ });
4227
+ return `sha256:${hash.digest("hex")}`;
4228
+ }
4145
4229
  //#endregion
4146
4230
  //#region src/agent/tools/command-policy.ts
4147
4231
  const PACKAGE_MANAGERS = new Set([
@@ -14087,6 +14171,14 @@ function getToolCallDisplayDiff(result) {
14087
14171
  function readMaxToolCallsPerTurn() {
14088
14172
  const raw = process.env[MAX_TOOL_CALLS_PER_TURN_ENV]?.trim();
14089
14173
  if (!raw) return DEFAULT_MAX_TOOL_CALLS_PER_TURN;
14174
+ if ([
14175
+ "0",
14176
+ "off",
14177
+ "false",
14178
+ "none",
14179
+ "unlimited",
14180
+ "disabled"
14181
+ ].includes(raw.toLowerCase())) return Number.POSITIVE_INFINITY;
14090
14182
  const parsed = Number(raw);
14091
14183
  if (!Number.isInteger(parsed) || parsed <= 0) return DEFAULT_MAX_TOOL_CALLS_PER_TURN;
14092
14184
  return parsed;
@@ -16554,4 +16646,4 @@ function formatDryRunSyncStatus(status) {
16554
16646
  //#endregion
16555
16647
  export { runTopchesterCli as t };
16556
16648
 
16557
- //# sourceMappingURL=cli-BS67FTk8.mjs.map
16649
+ //# sourceMappingURL=cli-DfgLjXLl.mjs.map