@cryer/star-cli 0.3.12 → 0.3.14

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.
Files changed (33) hide show
  1. package/README.md +17 -5
  2. package/README.zh-CN.md +15 -5
  3. package/dist/{chunk-LSYGHLRU.js → chunk-2ISBRSBA.js} +479 -296
  4. package/dist/chunk-2ISBRSBA.js.map +1 -0
  5. package/dist/{chunk-F5RWZMQY.js → chunk-4HANB3DJ.js} +458 -221
  6. package/dist/chunk-4HANB3DJ.js.map +1 -0
  7. package/dist/chunk-4IRY44TG.js +0 -0
  8. package/dist/{chunk-Q5FJN4LN.js → chunk-54KH4JSO.js} +213 -125
  9. package/dist/chunk-54KH4JSO.js.map +1 -0
  10. package/dist/{chunk-MYHD3SZB.js → chunk-CECMCDZG.js} +43 -30
  11. package/dist/chunk-CECMCDZG.js.map +1 -0
  12. package/dist/{chunk-MVAMPVTA.js → chunk-KILMI3S5.js} +13 -5
  13. package/dist/{chunk-MVAMPVTA.js.map → chunk-KILMI3S5.js.map} +1 -1
  14. package/dist/{chunk-2GAUAYCU.js → chunk-NRLGGA7L.js} +507 -175
  15. package/dist/chunk-NRLGGA7L.js.map +1 -0
  16. package/dist/loop-M64D5YII.js +17 -0
  17. package/dist/main.js +177 -131
  18. package/dist/main.js.map +1 -1
  19. package/dist/{provider-EWMUPBLH.js → provider-3Z4B7RMF.js} +2 -2
  20. package/dist/{repl-SSCURTAH.js → repl-N2H7JDSH.js} +617 -327
  21. package/dist/repl-N2H7JDSH.js.map +1 -0
  22. package/dist/{tools-BH55BVIS.js → tools-5RKM7ZI3.js} +3 -3
  23. package/package.json +16 -25
  24. package/dist/chunk-2GAUAYCU.js.map +0 -1
  25. package/dist/chunk-F5RWZMQY.js.map +0 -1
  26. package/dist/chunk-LSYGHLRU.js.map +0 -1
  27. package/dist/chunk-MYHD3SZB.js.map +0 -1
  28. package/dist/chunk-Q5FJN4LN.js.map +0 -1
  29. package/dist/loop-BYXBNFFY.js +0 -17
  30. package/dist/repl-SSCURTAH.js.map +0 -1
  31. /package/dist/{loop-BYXBNFFY.js.map → loop-M64D5YII.js.map} +0 -0
  32. /package/dist/{provider-EWMUPBLH.js.map → provider-3Z4B7RMF.js.map} +0 -0
  33. /package/dist/{tools-BH55BVIS.js.map → tools-5RKM7ZI3.js.map} +0 -0
@@ -125,163 +125,6 @@ function formatAgentTaskUpdate(task) {
125
125
  return text;
126
126
  }
127
127
 
128
- // src/core/http-error.ts
129
- function responseBodyOf(error) {
130
- const body = error.responseBody;
131
- return typeof body === "string" && body.length > 0 ? body : void 0;
132
- }
133
-
134
- // src/core/image.ts
135
- var MAX_IMAGE_DIMENSION = 2e3;
136
- var OVERSIZED_IMAGE_FALLBACK_BYTES = 5 * 1024 * 1024;
137
- var IMAGE_REMOVED_PLACEHOLDER = "[image removed: too large for the model]";
138
- var IMAGE_UNSUPPORTED_PLACEHOLDER = "[image removed: the model cannot read images]";
139
- var PNG_SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10];
140
- function probePng(buf) {
141
- if (buf.length < 24) return null;
142
- for (let i = 0; i < PNG_SIGNATURE.length; i++) {
143
- if (buf[i] !== PNG_SIGNATURE[i]) return null;
144
- }
145
- if (buf.toString("ascii", 12, 16) !== "IHDR") return null;
146
- return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20) };
147
- }
148
- var JPEG_SOF_MARKERS = /* @__PURE__ */ new Set([
149
- 192,
150
- 193,
151
- 194,
152
- 195,
153
- 197,
154
- 198,
155
- 199,
156
- 201,
157
- 202,
158
- 203,
159
- 205,
160
- 206,
161
- 207
162
- ]);
163
- function probeJpeg(buf) {
164
- if (buf.length < 4 || buf[0] !== 255 || buf[1] !== 216) return null;
165
- let offset = 2;
166
- while (offset + 3 < buf.length) {
167
- if (buf[offset] !== 255) return null;
168
- const marker = buf[offset + 1];
169
- if (marker === void 0) return null;
170
- if (marker === 216 || marker === 1 || marker >= 208 && marker <= 215) {
171
- offset += 2;
172
- continue;
173
- }
174
- const length = buf.readUInt16BE(offset + 2);
175
- if (length < 2) return null;
176
- if (JPEG_SOF_MARKERS.has(marker)) {
177
- if (offset + 9 > buf.length) return null;
178
- return { height: buf.readUInt16BE(offset + 5), width: buf.readUInt16BE(offset + 7) };
179
- }
180
- offset += 2 + length;
181
- }
182
- return null;
183
- }
184
- function probeGif(buf) {
185
- if (buf.length < 10) return null;
186
- const signature = buf.toString("ascii", 0, 6);
187
- if (signature !== "GIF87a" && signature !== "GIF89a") return null;
188
- return { width: buf.readUInt16LE(6), height: buf.readUInt16LE(8) };
189
- }
190
- function probeWebp(buf) {
191
- if (buf.length < 30) return null;
192
- if (buf.toString("ascii", 0, 4) !== "RIFF" || buf.toString("ascii", 8, 12) !== "WEBP") {
193
- return null;
194
- }
195
- const chunk = buf.toString("ascii", 12, 16);
196
- if (chunk === "VP8X") {
197
- return { width: 1 + buf.readUIntLE(24, 3), height: 1 + buf.readUIntLE(27, 3) };
198
- }
199
- if (chunk === "VP8 ") {
200
- return {
201
- width: buf.readUInt16LE(26) & 16383,
202
- height: buf.readUInt16LE(28) & 16383
203
- };
204
- }
205
- if (chunk === "VP8L") {
206
- if (buf[20] !== 47) return null;
207
- const b1 = buf[21];
208
- const b2 = buf[22];
209
- const b3 = buf[23];
210
- const b4 = buf[24];
211
- return {
212
- width: 1 + ((b2 & 63) << 8 | b1),
213
- height: 1 + ((b4 & 15) << 10 | b3 << 2 | (b2 & 192) >> 6)
214
- };
215
- }
216
- return null;
217
- }
218
- function probeImageDimensions(buf) {
219
- return probePng(buf) ?? probeJpeg(buf) ?? probeGif(buf) ?? probeWebp(buf);
220
- }
221
- var IMAGE_SIZE_KEYWORDS = /too[\s-]*(large|big)|exceeds?|dimensions?|resolution|size\s*limit|max(?:imum)?[\s-]*size/i;
222
- function isOversizedImageError(error) {
223
- const status = error.statusCode;
224
- if (status === void 0 || status < 400 || status >= 500) return false;
225
- const text = [error.message, responseBodyOf(error)].filter((part) => typeof part === "string").join("\n");
226
- return /image/i.test(text) && IMAGE_SIZE_KEYWORDS.test(text);
227
- }
228
- var VISION_UNSUPPORTED_KEYWORDS = /vision\s*encoder|can(?:not|'t) (?:read|process|handle|accept) images?|does(?:\s*not|n't) support (?:image|vision|multimodal)|images?(?:\s*input)? (?:are|is) not supported|not a vision|no vision (?:capability|support|encoder)|multimodal(?:\s*input)? (?:is )?not supported/i;
229
- function isVisionUnsupportedError(error) {
230
- const status = error.statusCode;
231
- if (status === void 0 || status < 400 || status >= 500) return false;
232
- const text = [error.message, responseBodyOf(error)].filter((part) => typeof part === "string").join("\n");
233
- return /image|vision|multimodal/i.test(text) && VISION_UNSUPPORTED_KEYWORDS.test(text);
234
- }
235
- function imagePartBytes(image) {
236
- if (typeof image === "string") {
237
- const base64 = image.startsWith("data:") ? image.slice(image.indexOf(",") + 1) : image;
238
- try {
239
- return Buffer.from(base64, "base64");
240
- } catch {
241
- return null;
242
- }
243
- }
244
- if (image instanceof Uint8Array) return Buffer.from(image);
245
- return null;
246
- }
247
- function isOversizedImageData(buf) {
248
- const dims = probeImageDimensions(buf);
249
- if (dims) return Math.max(dims.width, dims.height) > MAX_IMAGE_DIMENSION;
250
- return buf.length > OVERSIZED_IMAGE_FALLBACK_BYTES;
251
- }
252
- function stripOversizedImages(messages) {
253
- let removed = 0;
254
- const result = messages.map((message) => {
255
- if (!Array.isArray(message.content)) return message;
256
- let changed = false;
257
- const content = message.content.map((part) => {
258
- if (part.type !== "image") return part;
259
- const bytes = imagePartBytes(part.image);
260
- if (!bytes || !isOversizedImageData(bytes)) return part;
261
- changed = true;
262
- removed++;
263
- return { type: "text", text: IMAGE_REMOVED_PLACEHOLDER };
264
- });
265
- return changed ? { ...message, content } : message;
266
- });
267
- return { messages: removed > 0 ? result : messages, removed };
268
- }
269
- function stripAllImages(messages) {
270
- let removed = 0;
271
- const result = messages.map((message) => {
272
- if (!Array.isArray(message.content)) return message;
273
- let changed = false;
274
- const content = message.content.map((part) => {
275
- if (part.type !== "image") return part;
276
- changed = true;
277
- removed++;
278
- return { type: "text", text: IMAGE_UNSUPPORTED_PLACEHOLDER };
279
- });
280
- return changed ? { ...message, content } : message;
281
- });
282
- return { messages: removed > 0 ? result : messages, removed };
283
- }
284
-
285
128
  // src/config/paths.ts
286
129
  import os from "os";
287
130
  import path from "path";
@@ -372,6 +215,95 @@ function upsertEnvContent(content, key, value) {
372
215
  `;
373
216
  }
374
217
 
218
+ // src/core/sensitive.ts
219
+ var SENSITIVE_BASENAMES = /* @__PURE__ */ new Set([
220
+ ".env",
221
+ "id_rsa",
222
+ "id_dsa",
223
+ "id_ecdsa",
224
+ "id_ed25519",
225
+ ".npmrc",
226
+ ".netrc",
227
+ ".pgpass"
228
+ ]);
229
+ var SENSITIVE_SUFFIXES = [".pem", ".key", ".p12", ".pfx"];
230
+ var SENSITIVE_ENV_EXEMPTIONS = /* @__PURE__ */ new Set([".env.example", ".env.sample", ".env.template"]);
231
+ var extraPatterns = [];
232
+ function registerSensitivePatterns(patterns) {
233
+ extraPatterns.length = 0;
234
+ extraPatterns.push(...patterns.map((p) => p.trim()).filter((p) => p.length > 0));
235
+ }
236
+ function sensitiveExcludePatterns() {
237
+ return [
238
+ ".env",
239
+ ".env.*",
240
+ "!.env.example",
241
+ "!.env.sample",
242
+ "!.env.template",
243
+ "id_rsa",
244
+ "id_dsa",
245
+ "id_ecdsa",
246
+ "id_ed25519",
247
+ ".npmrc",
248
+ ".netrc",
249
+ ".pgpass",
250
+ "*.pem",
251
+ "*.key",
252
+ "*.p12",
253
+ "*.pfx",
254
+ "**/.aws/credentials",
255
+ "**/.kube/config",
256
+ ...extraPatterns
257
+ ];
258
+ }
259
+ function globToRegExp(pattern) {
260
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
261
+ return new RegExp(`^${escaped}$`, "i");
262
+ }
263
+ function pathSegments(p) {
264
+ return p.replace(/\\/g, "/").split("/").filter(Boolean).map((s) => s.toLowerCase());
265
+ }
266
+ function isSensitivePath(p) {
267
+ const segments = pathSegments(p);
268
+ const base = segments[segments.length - 1] ?? "";
269
+ const normalized = p.replace(/\\/g, "/").replace(/\/+$/, "");
270
+ for (const pattern of extraPatterns) {
271
+ const re = globToRegExp(pattern);
272
+ if (re.test(normalized) || re.test(base)) {
273
+ return true;
274
+ }
275
+ }
276
+ if (SENSITIVE_ENV_EXEMPTIONS.has(base)) {
277
+ return false;
278
+ }
279
+ if (base === ".env" || base.startsWith(".env.")) {
280
+ return true;
281
+ }
282
+ if (SENSITIVE_BASENAMES.has(base)) {
283
+ return true;
284
+ }
285
+ if (SENSITIVE_SUFFIXES.some((suffix) => base.endsWith(suffix))) {
286
+ return true;
287
+ }
288
+ const dir = segments[segments.length - 2] ?? "";
289
+ if (dir === ".aws" && base === "credentials") {
290
+ return true;
291
+ }
292
+ if (dir === ".kube" && base === "config") {
293
+ return true;
294
+ }
295
+ return false;
296
+ }
297
+ var SECRET_ENV_NAME = /(?:api[_-]?key|secret|token|password|passwd|credential|private[_-]?key|access[_-]?key|(?:^|[_-])key$)/i;
298
+ var SECRET_ENV_ALLOWLIST = /* @__PURE__ */ new Set([
299
+ "TOKENIZERS_PARALLELISM",
300
+ "GOOGLE_APPLICATION_CREDENTIALS",
301
+ "AWS_SHARED_CREDENTIALS_FILE"
302
+ ]);
303
+ function isSecretEnvName(name) {
304
+ return SECRET_ENV_NAME.test(name) && !SECRET_ENV_ALLOWLIST.has(name.toUpperCase());
305
+ }
306
+
375
307
  // src/tools/bash.ts
376
308
  var MAX_OUTPUT = 3e4;
377
309
  var HALF_OUTPUT = MAX_OUTPUT / 2;
@@ -444,6 +376,7 @@ function killTree(child) {
444
376
  spawn("taskkill", ["/pid", String(child.pid), "/t", "/f"], {
445
377
  windowsHide: true,
446
378
  stdio: "ignore"
379
+ }).on("error", () => {
447
380
  }).unref();
448
381
  }
449
382
  return;
@@ -523,10 +456,16 @@ function decodeOutput(buf, platform = process.platform) {
523
456
  }
524
457
  return text;
525
458
  }
526
- function childEnv() {
459
+ function childEnv(platform = process.platform) {
527
460
  const env = { ...process.env };
528
- for (const key of getEnvFileKeys()) {
529
- delete env[key];
461
+ const fileKeys = getEnvFileKeys();
462
+ for (const key of Object.keys(env)) {
463
+ if (fileKeys.has(key) || isSecretEnvName(key)) {
464
+ delete env[key];
465
+ }
466
+ }
467
+ if (platform === "win32" && env.MSYS_NO_PATHCONV === void 0 && env.MSYS2_ARG_CONV_EXCL === void 0) {
468
+ env.MSYS_NO_PATHCONV = "1";
530
469
  }
531
470
  return env;
532
471
  }
@@ -744,6 +683,163 @@ var TaskManager = class extends EventEmitter2 {
744
683
  };
745
684
  var defaultTaskManager = new TaskManager();
746
685
 
686
+ // src/core/http-error.ts
687
+ function responseBodyOf(error) {
688
+ const body = error.responseBody;
689
+ return typeof body === "string" && body.length > 0 ? body : void 0;
690
+ }
691
+
692
+ // src/core/image.ts
693
+ var MAX_IMAGE_DIMENSION = 2e3;
694
+ var OVERSIZED_IMAGE_FALLBACK_BYTES = 5 * 1024 * 1024;
695
+ var IMAGE_REMOVED_PLACEHOLDER = "[image removed: too large for the model]";
696
+ var IMAGE_UNSUPPORTED_PLACEHOLDER = "[image removed: the model cannot read images]";
697
+ var PNG_SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10];
698
+ function probePng(buf) {
699
+ if (buf.length < 24) return null;
700
+ for (let i = 0; i < PNG_SIGNATURE.length; i++) {
701
+ if (buf[i] !== PNG_SIGNATURE[i]) return null;
702
+ }
703
+ if (buf.toString("ascii", 12, 16) !== "IHDR") return null;
704
+ return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20) };
705
+ }
706
+ var JPEG_SOF_MARKERS = /* @__PURE__ */ new Set([
707
+ 192,
708
+ 193,
709
+ 194,
710
+ 195,
711
+ 197,
712
+ 198,
713
+ 199,
714
+ 201,
715
+ 202,
716
+ 203,
717
+ 205,
718
+ 206,
719
+ 207
720
+ ]);
721
+ function probeJpeg(buf) {
722
+ if (buf.length < 4 || buf[0] !== 255 || buf[1] !== 216) return null;
723
+ let offset = 2;
724
+ while (offset + 3 < buf.length) {
725
+ if (buf[offset] !== 255) return null;
726
+ const marker = buf[offset + 1];
727
+ if (marker === void 0) return null;
728
+ if (marker === 216 || marker === 1 || marker >= 208 && marker <= 215) {
729
+ offset += 2;
730
+ continue;
731
+ }
732
+ const length = buf.readUInt16BE(offset + 2);
733
+ if (length < 2) return null;
734
+ if (JPEG_SOF_MARKERS.has(marker)) {
735
+ if (offset + 9 > buf.length) return null;
736
+ return { height: buf.readUInt16BE(offset + 5), width: buf.readUInt16BE(offset + 7) };
737
+ }
738
+ offset += 2 + length;
739
+ }
740
+ return null;
741
+ }
742
+ function probeGif(buf) {
743
+ if (buf.length < 10) return null;
744
+ const signature = buf.toString("ascii", 0, 6);
745
+ if (signature !== "GIF87a" && signature !== "GIF89a") return null;
746
+ return { width: buf.readUInt16LE(6), height: buf.readUInt16LE(8) };
747
+ }
748
+ function probeWebp(buf) {
749
+ if (buf.length < 30) return null;
750
+ if (buf.toString("ascii", 0, 4) !== "RIFF" || buf.toString("ascii", 8, 12) !== "WEBP") {
751
+ return null;
752
+ }
753
+ const chunk = buf.toString("ascii", 12, 16);
754
+ if (chunk === "VP8X") {
755
+ return { width: 1 + buf.readUIntLE(24, 3), height: 1 + buf.readUIntLE(27, 3) };
756
+ }
757
+ if (chunk === "VP8 ") {
758
+ return {
759
+ width: buf.readUInt16LE(26) & 16383,
760
+ height: buf.readUInt16LE(28) & 16383
761
+ };
762
+ }
763
+ if (chunk === "VP8L") {
764
+ if (buf[20] !== 47) return null;
765
+ const b1 = buf[21];
766
+ const b2 = buf[22];
767
+ const b3 = buf[23];
768
+ const b4 = buf[24];
769
+ return {
770
+ width: 1 + ((b2 & 63) << 8 | b1),
771
+ height: 1 + ((b4 & 15) << 10 | b3 << 2 | (b2 & 192) >> 6)
772
+ };
773
+ }
774
+ return null;
775
+ }
776
+ function probeImageDimensions(buf) {
777
+ return probePng(buf) ?? probeJpeg(buf) ?? probeGif(buf) ?? probeWebp(buf);
778
+ }
779
+ var IMAGE_SIZE_KEYWORDS = /too[\s-]*(large|big)|exceeds?|dimensions?|resolution|size\s*limit|max(?:imum)?[\s-]*size/i;
780
+ function isOversizedImageError(error) {
781
+ const status = error.statusCode;
782
+ if (status === void 0 || status < 400 || status >= 500) return false;
783
+ const text = [error.message, responseBodyOf(error)].filter((part) => typeof part === "string").join("\n");
784
+ return /image/i.test(text) && IMAGE_SIZE_KEYWORDS.test(text);
785
+ }
786
+ var VISION_UNSUPPORTED_KEYWORDS = /vision\s*encoder|can(?:not|'t) (?:read|process|handle|accept) images?|does(?:\s*not|n't) support (?:image|vision|multimodal)|images?(?:\s*input)? (?:are|is) not supported|not a vision|no vision (?:capability|support|encoder)|multimodal(?:\s*input)? (?:is )?not supported/i;
787
+ function isVisionUnsupportedError(error) {
788
+ const status = error.statusCode;
789
+ if (status === void 0 || status < 400 || status >= 500) return false;
790
+ const text = [error.message, responseBodyOf(error)].filter((part) => typeof part === "string").join("\n");
791
+ return /image|vision|multimodal/i.test(text) && VISION_UNSUPPORTED_KEYWORDS.test(text);
792
+ }
793
+ function imagePartBytes(image) {
794
+ if (typeof image === "string") {
795
+ const base64 = image.startsWith("data:") ? image.slice(image.indexOf(",") + 1) : image;
796
+ try {
797
+ return Buffer.from(base64, "base64");
798
+ } catch {
799
+ return null;
800
+ }
801
+ }
802
+ if (image instanceof Uint8Array) return Buffer.from(image);
803
+ return null;
804
+ }
805
+ function isOversizedImageData(buf) {
806
+ const dims = probeImageDimensions(buf);
807
+ if (dims) return Math.max(dims.width, dims.height) > MAX_IMAGE_DIMENSION;
808
+ return buf.length > OVERSIZED_IMAGE_FALLBACK_BYTES;
809
+ }
810
+ function stripOversizedImages(messages) {
811
+ let removed = 0;
812
+ const result = messages.map((message) => {
813
+ if (!Array.isArray(message.content)) return message;
814
+ let changed = false;
815
+ const content = message.content.map((part) => {
816
+ if (part.type !== "image") return part;
817
+ const bytes = imagePartBytes(part.image);
818
+ if (!bytes || !isOversizedImageData(bytes)) return part;
819
+ changed = true;
820
+ removed++;
821
+ return { type: "text", text: IMAGE_REMOVED_PLACEHOLDER };
822
+ });
823
+ return changed ? { ...message, content } : message;
824
+ });
825
+ return { messages: removed > 0 ? result : messages, removed };
826
+ }
827
+ function stripAllImages(messages) {
828
+ let removed = 0;
829
+ const result = messages.map((message) => {
830
+ if (!Array.isArray(message.content)) return message;
831
+ let changed = false;
832
+ const content = message.content.map((part) => {
833
+ if (part.type !== "image") return part;
834
+ changed = true;
835
+ removed++;
836
+ return { type: "text", text: IMAGE_UNSUPPORTED_PLACEHOLDER };
837
+ });
838
+ return changed ? { ...message, content } : message;
839
+ });
840
+ return { messages: removed > 0 ? result : messages, removed };
841
+ }
842
+
747
843
  // src/tools/fs/snapshots.ts
748
844
  import { randomUUID } from "crypto";
749
845
  import { mkdir, readFile, rm, stat, writeFile } from "fs/promises";
@@ -803,6 +899,7 @@ async function captureSnapshot(filePath, toolName, snapshotContext) {
803
899
  let content = null;
804
900
  let existed = false;
805
901
  let contentTooLarge = false;
902
+ let captureFailed = false;
806
903
  const st = await stat(filePath).catch(() => null);
807
904
  if (st?.isFile()) {
808
905
  existed = true;
@@ -812,7 +909,7 @@ async function captureSnapshot(filePath, toolName, snapshotContext) {
812
909
  try {
813
910
  content = await readFile(filePath);
814
911
  } catch {
815
- existed = false;
912
+ captureFailed = true;
816
913
  }
817
914
  }
818
915
  }
@@ -822,6 +919,7 @@ async function captureSnapshot(filePath, toolName, snapshotContext) {
822
919
  existed,
823
920
  content,
824
921
  contentTooLarge: contentTooLarge || void 0,
922
+ captureFailed: captureFailed || void 0,
825
923
  toolName,
826
924
  timestamp: Date.now(),
827
925
  turn: snapshotContext?.turn ?? activeTurn,
@@ -846,7 +944,14 @@ async function pushSnapshot(snapshot) {
846
944
  }
847
945
  }
848
946
  async function removeFromStore(ids) {
849
- if (ids.length > 0) await hooks?.onRemove?.(ids);
947
+ if (ids.length === 0) return;
948
+ try {
949
+ await hooks?.onRemove?.(ids);
950
+ } catch (err) {
951
+ if (process.env.STAR_DEBUG) {
952
+ console.error("[star] failed to remove snapshot checkpoint:", err);
953
+ }
954
+ }
850
955
  }
851
956
  function listSnapshots() {
852
957
  return [...stack];
@@ -972,6 +1077,7 @@ async function rewindToSnapshot(id, includeSubagent = false) {
972
1077
  }
973
1078
 
974
1079
  // src/tools/todo.ts
1080
+ import { randomUUID as randomUUID2 } from "crypto";
975
1081
  import { mkdir as mkdir2, readFile as readFile2, rename, rm as rm2, writeFile as writeFile2 } from "fs/promises";
976
1082
  import path4 from "path";
977
1083
  import { z as z2 } from "zod";
@@ -1001,10 +1107,16 @@ function setTodoSession(id) {
1001
1107
  }
1002
1108
  var TodoStore = class {
1003
1109
  items = /* @__PURE__ */ new Map();
1110
+ persistence;
1111
+ tmpSuffix = `${process.pid}-${randomUUID2()}`;
1112
+ saveQueue = Promise.resolve();
1004
1113
  // Bumped on every replace() — the agent loop watches it to detect
1005
1114
  // todo_write landings, so a list left untouched for many steps (with open
1006
1115
  // items) can be flagged as stale and the model reminded to reconcile it.
1007
1116
  revisionValue = 0;
1117
+ constructor(options = {}) {
1118
+ this.persistence = options.persistence ?? "file";
1119
+ }
1008
1120
  get revision() {
1009
1121
  return this.revisionValue;
1010
1122
  }
@@ -1019,6 +1131,7 @@ var TodoStore = class {
1019
1131
  }
1020
1132
  }
1021
1133
  async load(cwd, sessionId) {
1134
+ if (this.persistence === "memory") return;
1022
1135
  let raw;
1023
1136
  try {
1024
1137
  raw = await readFile2(fileFor(cwd), "utf8");
@@ -1043,23 +1156,27 @@ var TodoStore = class {
1043
1156
  } catch {
1044
1157
  }
1045
1158
  }
1046
- // Atomic write (tmp + rename): a crash or failed save can never leave a
1047
- // truncated todos.json behind. An empty or fully completed list has no
1048
- // resume value — rehydrating it would only restore finished work — so the
1049
- // file is deleted instead of persisted.
1050
- async save(cwd) {
1051
- if (!persistGuard()) {
1052
- return;
1053
- }
1159
+ // Atomic write (unique tmp + rename) with per-store serialization: parallel
1160
+ // saves cannot share a temp file or finish out of order. An empty or fully
1161
+ // completed list has no resume value, so the file is deleted instead.
1162
+ save(cwd) {
1163
+ if (this.persistence === "memory" || !persistGuard()) return Promise.resolve();
1054
1164
  const items = this.list();
1165
+ const sessionId = boundSessionId;
1166
+ const saving = this.saveQueue.catch(() => {
1167
+ }).then(() => this.saveNow(cwd, items, sessionId));
1168
+ this.saveQueue = saving;
1169
+ return saving;
1170
+ }
1171
+ async saveNow(cwd, items, sessionId) {
1055
1172
  const file = fileFor(cwd);
1056
1173
  if (items.length === 0 || items.every((it) => it.status === "done")) {
1057
1174
  await rm2(file, { force: true });
1058
1175
  return;
1059
1176
  }
1060
1177
  await mkdir2(path4.join(cwd, ".star"), { recursive: true });
1061
- const tmp = `${file}.tmp`;
1062
- await writeFile2(tmp, `${JSON.stringify({ sessionId: boundSessionId, items }, null, 2)}
1178
+ const tmp = `${file}.${this.tmpSuffix}.tmp`;
1179
+ await writeFile2(tmp, `${JSON.stringify({ sessionId, items }, null, 2)}
1063
1180
  `);
1064
1181
  await rename(tmp, file);
1065
1182
  }
@@ -1136,7 +1253,7 @@ function createTodoTools(store = defaultStore) {
1136
1253
 
1137
1254
  // src/core/image-resize.ts
1138
1255
  import { execFile } from "child_process";
1139
- import { randomUUID as randomUUID2 } from "crypto";
1256
+ import { randomUUID as randomUUID3 } from "crypto";
1140
1257
  import { readFile as readFile3, unlink, writeFile as writeFile3 } from "fs/promises";
1141
1258
  import os3 from "os";
1142
1259
  import path5 from "path";
@@ -1225,8 +1342,8 @@ async function downsampleImageIfNeeded(image) {
1225
1342
  if (!dims || Math.max(dims.width, dims.height) <= MAX_IMAGE_DIMENSION) {
1226
1343
  return { image, oversized: false, resized: false };
1227
1344
  }
1228
- const inputFile = path5.join(os3.tmpdir(), `star-image-${randomUUID2()}.src`);
1229
- const outputFile = path5.join(os3.tmpdir(), `star-image-${randomUUID2()}.png`);
1345
+ const inputFile = path5.join(os3.tmpdir(), `star-image-${randomUUID3()}.src`);
1346
+ const outputFile = path5.join(os3.tmpdir(), `star-image-${randomUUID3()}.png`);
1230
1347
  try {
1231
1348
  const [hasConvert, hasFfmpeg] = process.platform === "linux" ? await Promise.all([commandExists("convert"), commandExists("ffmpeg")]) : [false, false];
1232
1349
  const plan = resizeImagePlan(process.platform, inputFile, outputFile, MAX_IMAGE_DIMENSION, {
@@ -1253,69 +1370,20 @@ async function downsampleImageIfNeeded(image) {
1253
1370
  }
1254
1371
  }
1255
1372
 
1256
- // src/core/sensitive.ts
1257
- var SENSITIVE_BASENAMES = /* @__PURE__ */ new Set([
1258
- ".env",
1259
- "id_rsa",
1260
- "id_dsa",
1261
- "id_ecdsa",
1262
- "id_ed25519",
1263
- ".npmrc",
1264
- ".netrc",
1265
- ".pgpass"
1266
- ]);
1267
- var SENSITIVE_SUFFIXES = [".pem", ".key", ".p12", ".pfx"];
1268
- var SENSITIVE_ENV_EXEMPTIONS = /* @__PURE__ */ new Set([".env.example", ".env.sample", ".env.template"]);
1269
- var extraPatterns = [];
1270
- function registerSensitivePatterns(patterns) {
1271
- extraPatterns.length = 0;
1272
- extraPatterns.push(...patterns.map((p) => p.trim()).filter((p) => p.length > 0));
1273
- }
1274
- function globToRegExp(pattern) {
1275
- const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
1276
- return new RegExp(`^${escaped}$`, "i");
1277
- }
1278
- function pathSegments(p) {
1279
- return p.replace(/\\/g, "/").split("/").filter(Boolean).map((s) => s.toLowerCase());
1280
- }
1281
- function isSensitivePath(p) {
1282
- const segments = pathSegments(p);
1283
- const base = segments[segments.length - 1] ?? "";
1284
- const normalized = p.replace(/\\/g, "/").replace(/\/+$/, "");
1285
- for (const pattern of extraPatterns) {
1286
- const re = globToRegExp(pattern);
1287
- if (re.test(normalized) || re.test(base)) {
1288
- return true;
1289
- }
1290
- }
1291
- if (SENSITIVE_ENV_EXEMPTIONS.has(base)) {
1292
- return false;
1293
- }
1294
- if (base === ".env" || base.startsWith(".env.")) {
1295
- return true;
1296
- }
1297
- if (SENSITIVE_BASENAMES.has(base)) {
1298
- return true;
1299
- }
1300
- if (SENSITIVE_SUFFIXES.some((suffix) => base.endsWith(suffix))) {
1301
- return true;
1302
- }
1303
- const dir = segments[segments.length - 2] ?? "";
1304
- if (dir === ".aws" && base === "credentials") {
1305
- return true;
1306
- }
1307
- if (dir === ".kube" && base === "config") {
1308
- return true;
1309
- }
1310
- return false;
1311
- }
1312
-
1313
1373
  // src/tools/fs/util.ts
1314
1374
  import { readFileSync, statSync } from "fs";
1315
1375
  import { readdir, realpath, stat as stat2 } from "fs/promises";
1316
1376
  import path6 from "path";
1317
1377
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
1318
1378
  var WALK_CONCURRENCY = 16;
1379
+ async function realpathForCheck(filePath) {
1380
+ const resolved = await realpath(filePath).catch(() => null);
1381
+ if (resolved !== null) {
1382
+ return resolved;
1383
+ }
1384
+ const parent = await realpath(path6.dirname(filePath)).catch(() => null);
1385
+ return parent === null ? null : path6.join(parent, path6.basename(filePath));
1386
+ }
1319
1387
  function createLimiter(concurrency) {
1320
1388
  let active = 0;
1321
1389
  const waiting = [];
@@ -1353,6 +1421,21 @@ async function walkFiles(root, skipDirs = SKIP_DIRS, options = {}) {
1353
1421
  const out = [];
1354
1422
  const limit = createLimiter(WALK_CONCURRENCY);
1355
1423
  const signal = options.signal;
1424
+ let stopped = false;
1425
+ let callbackChain = Promise.resolve();
1426
+ const emitBatch = (files) => {
1427
+ if (!options.onBatch || files.length === 0) return callbackChain;
1428
+ files.sort((a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0);
1429
+ callbackChain = callbackChain.then(async () => {
1430
+ if (stopped) return;
1431
+ if (await options.onBatch?.(files) === false) stopped = true;
1432
+ });
1433
+ return callbackChain;
1434
+ };
1435
+ const collect = (file, batch) => {
1436
+ batch.push(file);
1437
+ if (options.collect !== false) out.push(file);
1438
+ };
1356
1439
  const caseKey = (p) => process.platform === "win32" ? p.toLowerCase() : p;
1357
1440
  const visited = /* @__PURE__ */ new Set();
1358
1441
  const rootReal = await realpath(root).catch(() => null);
@@ -1380,7 +1463,7 @@ async function walkFiles(root, skipDirs = SKIP_DIRS, options = {}) {
1380
1463
  return realKey === boundaryKey || realKey.startsWith(`${boundaryKey}${path6.sep}`) ? real : null;
1381
1464
  }
1382
1465
  async function walkDir(dir, relBase, parentLayers) {
1383
- if (signal?.aborted) {
1466
+ if (signal?.aborted || stopped) {
1384
1467
  return;
1385
1468
  }
1386
1469
  const entries = await limit(() => readdir(dir, { withFileTypes: true })).catch(() => null);
@@ -1395,8 +1478,9 @@ async function walkFiles(root, skipDirs = SKIP_DIRS, options = {}) {
1395
1478
  }
1396
1479
  }
1397
1480
  const subs = [];
1481
+ const batch = [];
1398
1482
  for (const entry of entries) {
1399
- if (signal?.aborted) {
1483
+ if (signal?.aborted || stopped) {
1400
1484
  break;
1401
1485
  }
1402
1486
  const abs = path6.join(dir, entry.name);
@@ -1439,22 +1523,26 @@ async function walkFiles(root, skipDirs = SKIP_DIRS, options = {}) {
1439
1523
  continue;
1440
1524
  }
1441
1525
  if (options.withMtime === false) {
1442
- out.push(
1443
- resolved === void 0 ? { abs, rel, mtimeMs: 0 } : { abs, rel, mtimeMs: 0, resolved }
1526
+ collect(
1527
+ resolved === void 0 ? { abs, rel, mtimeMs: 0 } : { abs, rel, mtimeMs: 0, resolved },
1528
+ batch
1444
1529
  );
1445
1530
  continue;
1446
1531
  }
1447
1532
  const st = targetStat ?? await limit(() => stat2(abs)).catch(() => null);
1448
1533
  if (st) {
1449
- out.push(
1450
- resolved === void 0 ? { abs, rel, mtimeMs: st.mtimeMs } : { abs, rel, mtimeMs: st.mtimeMs, resolved }
1534
+ collect(
1535
+ resolved === void 0 ? { abs, rel, mtimeMs: st.mtimeMs } : { abs, rel, mtimeMs: st.mtimeMs, resolved },
1536
+ batch
1451
1537
  );
1452
1538
  }
1453
1539
  }
1454
1540
  }
1541
+ await emitBatch(batch);
1455
1542
  await Promise.all(subs);
1456
1543
  }
1457
1544
  await walkDir(root, "", []);
1545
+ await callbackChain;
1458
1546
  out.sort((a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0);
1459
1547
  return { files: out, aborted: signal?.aborted ?? false };
1460
1548
  }
@@ -1562,13 +1650,30 @@ function globToRegExp2(pattern) {
1562
1650
  const { re } = parseSegment(0);
1563
1651
  return new RegExp(`^${re}$`);
1564
1652
  }
1653
+ var GLOB_CACHE_MAX = 500;
1654
+ var globCache = /* @__PURE__ */ new Map();
1655
+ function compiledGlob(pattern) {
1656
+ const hit = globCache.get(pattern);
1657
+ if (hit !== void 0) {
1658
+ globCache.delete(pattern);
1659
+ globCache.set(pattern, hit);
1660
+ return hit;
1661
+ }
1662
+ const re = globToRegExp2(pattern);
1663
+ if (globCache.size >= GLOB_CACHE_MAX) {
1664
+ const oldest = globCache.keys().next().value;
1665
+ if (oldest !== void 0) globCache.delete(oldest);
1666
+ }
1667
+ globCache.set(pattern, re);
1668
+ return re;
1669
+ }
1565
1670
  function matchesGlob(pattern, relPath) {
1566
1671
  const normalized = pattern.replace(/\\/g, "/");
1567
1672
  if (!normalized.includes("/")) {
1568
1673
  const base = relPath.split("/").pop() ?? relPath;
1569
- return globToRegExp2(normalized).test(base);
1674
+ return compiledGlob(normalized).test(base);
1570
1675
  }
1571
- return globToRegExp2(normalized).test(relPath);
1676
+ return compiledGlob(normalized).test(relPath);
1572
1677
  }
1573
1678
  var IGNORE_FILE = ".starignore";
1574
1679
  var ignoreCache = /* @__PURE__ */ new Map();
@@ -1636,6 +1741,10 @@ function createIgnorePredicate(cwd) {
1636
1741
 
1637
1742
  // src/tools/web/fetch.ts
1638
1743
  import dns from "dns";
1744
+ import http from "http";
1745
+ import https from "https";
1746
+ import { BlockList, isIPv4, isIPv6 } from "net";
1747
+ import { Readable } from "stream";
1639
1748
  import { z as z3 } from "zod";
1640
1749
  var DEFAULT_MAX_CHARS = 2e4;
1641
1750
  var MAX_BYTES = 2 * 1024 * 1024;
@@ -1655,47 +1764,121 @@ var configuredAllowPrivateHosts = false;
1655
1764
  function setWebFetchAllowPrivateHosts(allow) {
1656
1765
  configuredAllowPrivateHosts = allow;
1657
1766
  }
1658
- function isPrivateIpv4(ip) {
1659
- const parts = ip.split(".").map(Number);
1660
- if (parts.length !== 4 || parts.some((n) => !Number.isInteger(n) || n < 0 || n > 255)) {
1661
- return false;
1662
- }
1663
- const [a = 0, b = 0] = parts;
1664
- return a === 0 || a === 10 || a === 127 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 169 && b === 254;
1767
+ var reservedIpv4 = new BlockList();
1768
+ for (const [address, prefix] of [
1769
+ ["0.0.0.0", 8],
1770
+ ["10.0.0.0", 8],
1771
+ ["100.64.0.0", 10],
1772
+ ["127.0.0.0", 8],
1773
+ ["169.254.0.0", 16],
1774
+ ["172.16.0.0", 12],
1775
+ ["192.0.0.0", 24],
1776
+ ["192.0.2.0", 24],
1777
+ ["192.88.99.0", 24],
1778
+ ["192.168.0.0", 16],
1779
+ ["198.18.0.0", 15],
1780
+ ["198.51.100.0", 24],
1781
+ ["203.0.113.0", 24],
1782
+ ["224.0.0.0", 4],
1783
+ ["240.0.0.0", 4]
1784
+ ]) {
1785
+ reservedIpv4.addSubnet(address, prefix, "ipv4");
1786
+ }
1787
+ var reservedIpv6 = new BlockList();
1788
+ for (const [address, prefix] of [
1789
+ ["::", 96],
1790
+ ["100::", 64],
1791
+ ["64:ff9b::", 96],
1792
+ ["64:ff9b:1::", 48],
1793
+ ["2001::", 23],
1794
+ ["2001:db8::", 32],
1795
+ ["2002::", 16],
1796
+ ["fc00::", 7],
1797
+ ["fe80::", 10],
1798
+ ["fec0::", 10],
1799
+ ["ff00::", 8]
1800
+ ]) {
1801
+ reservedIpv6.addSubnet(address, prefix, "ipv6");
1802
+ }
1803
+ function mappedIpv4(ip) {
1804
+ const dotted = /^(?:::|(?:0:){5})ffff:(\d+\.\d+\.\d+\.\d+)$/i.exec(ip);
1805
+ if (dotted?.[1]) return dotted[1];
1806
+ const hexadecimal = /^(?:::|(?:0:){5})ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i.exec(ip);
1807
+ if (!hexadecimal) return null;
1808
+ const value = Number.parseInt(hexadecimal[1] ?? "0", 16) * 65536 + Number.parseInt(hexadecimal[2] ?? "0", 16);
1809
+ return [value >>> 24 & 255, value >>> 16 & 255, value >>> 8 & 255, value & 255].join(".");
1665
1810
  }
1666
1811
  function isPrivateAddress(address) {
1667
- let ip = address.toLowerCase();
1668
- if (ip.startsWith("::ffff:")) ip = ip.slice("::ffff:".length);
1669
- if (ip.includes(".")) return isPrivateIpv4(ip);
1670
- if (ip === "::1" || ip === "::") return true;
1671
- if (ip.startsWith("fc") || ip.startsWith("fd")) return true;
1672
- if (/^fe[89ab]/.test(ip)) return true;
1673
- return false;
1812
+ const ip = address.replace(/^\[|\]$/g, "").split("%")[0]?.toLowerCase() ?? "";
1813
+ const mapped = mappedIpv4(ip);
1814
+ if (mapped !== null) return !isIPv4(mapped) || reservedIpv4.check(mapped, "ipv4");
1815
+ if (isIPv4(ip)) return reservedIpv4.check(ip, "ipv4");
1816
+ if (isIPv6(ip)) return reservedIpv6.check(ip, "ipv6");
1817
+ return true;
1674
1818
  }
1675
1819
  function isLocalHostname(hostname) {
1676
1820
  const h = hostname.toLowerCase().replace(/\.$/, "");
1677
1821
  return h === "localhost" || h.endsWith(".localhost") || h.endsWith(".local") || h.endsWith(".internal");
1678
1822
  }
1679
- async function privateHostRefusal(url, resolve) {
1823
+ async function validateTarget(url, resolve) {
1680
1824
  const hostname = url.hostname.replace(/^\[|\]$/g, "");
1681
1825
  if (isLocalHostname(hostname)) {
1682
- return `"${hostname}" is a local/internal hostname`;
1826
+ return { refusal: `"${hostname}" is a local/internal hostname` };
1683
1827
  }
1684
1828
  let addresses;
1685
1829
  try {
1686
1830
  addresses = await resolve(hostname);
1687
1831
  } catch (err) {
1688
1832
  const message = err instanceof Error ? err.message : String(err);
1689
- return `could not resolve "${hostname}" (${message})`;
1833
+ return { refusal: `could not resolve "${hostname}" (${message})` };
1690
1834
  }
1691
1835
  const blocked = addresses.find(isPrivateAddress);
1692
1836
  if (blocked !== void 0) {
1693
- return `"${hostname}" resolves to a private/reserved address (${blocked})`;
1694
- }
1695
- if (addresses.length === 0) {
1696
- return `could not resolve "${hostname}"`;
1697
- }
1698
- return null;
1837
+ return { refusal: `"${hostname}" resolves to a private/reserved address (${blocked})` };
1838
+ }
1839
+ const address = addresses[0];
1840
+ if (address === void 0) {
1841
+ return { refusal: `could not resolve "${hostname}"` };
1842
+ }
1843
+ return { target: { address, family: isIPv4(address) ? 4 : 6 } };
1844
+ }
1845
+ function nativeFetch(url, signal, target) {
1846
+ return new Promise((resolve, reject) => {
1847
+ const transport = url.protocol === "https:" ? https : http;
1848
+ const hostname = url.hostname.replace(/^\[|\]$/g, "");
1849
+ const request = transport.request(url, {
1850
+ method: "GET",
1851
+ signal,
1852
+ servername: isIPv6(hostname) || isIPv4(hostname) ? "" : hostname,
1853
+ lookup: target ? (_hostname, options, callback) => {
1854
+ if (options.all) {
1855
+ callback(null, [{ address: target.address, family: target.family }]);
1856
+ } else {
1857
+ callback(null, target.address, target.family);
1858
+ }
1859
+ } : void 0,
1860
+ headers: { "accept-encoding": "identity" }
1861
+ });
1862
+ request.once("error", reject);
1863
+ request.once("response", (incoming) => {
1864
+ const headers = new Headers();
1865
+ for (const [name, value] of Object.entries(incoming.headers)) {
1866
+ if (Array.isArray(value)) {
1867
+ for (const item of value) headers.append(name, item);
1868
+ } else if (value !== void 0) {
1869
+ headers.set(name, value);
1870
+ }
1871
+ }
1872
+ resolve(
1873
+ new Response(Readable.toWeb(incoming), {
1874
+ status: incoming.statusCode ?? 500,
1875
+ statusText: incoming.statusMessage,
1876
+ headers
1877
+ })
1878
+ );
1879
+ });
1880
+ request.end();
1881
+ });
1699
1882
  }
1700
1883
  function safeCodePoint(n, raw) {
1701
1884
  if (!Number.isInteger(n) || n < 0 || n > 1114111) {
@@ -1840,7 +2023,7 @@ function truncate(s, maxChars) {
1840
2023
  }
1841
2024
  function createWebFetchTool(options = {}) {
1842
2025
  const resolve = options.resolver ?? defaultResolver;
1843
- const fetchImpl = options.fetchImpl ?? ((...args) => fetch(...args));
2026
+ const fetchImpl = options.fetchImpl;
1844
2027
  const allowPrivate = () => options.allowPrivateHosts ?? configuredAllowPrivateHosts;
1845
2028
  return {
1846
2029
  name: "web_fetch",
@@ -1860,20 +2043,26 @@ function createWebFetchTool(options = {}) {
1860
2043
  isError: true
1861
2044
  };
1862
2045
  }
1863
- if (!allowPrivate()) {
1864
- const refusal = await privateHostRefusal(url, resolve);
1865
- if (refusal !== null) {
1866
- return { content: `ERROR: refused to fetch ${args.url}: ${refusal}`, isError: true };
1867
- }
1868
- }
1869
2046
  const maxChars = args.maxChars ?? DEFAULT_MAX_CHARS;
1870
2047
  const timeoutSignal = AbortSignal.timeout(TIMEOUT_MS);
1871
2048
  const signal = ctx.abortSignal ? AbortSignal.any([timeoutSignal, ctx.abortSignal]) : timeoutSignal;
1872
2049
  let res;
1873
2050
  let current = url;
1874
2051
  for (let hops = 0; ; hops += 1) {
2052
+ let target;
2053
+ if (!allowPrivate()) {
2054
+ const validated = await validateTarget(current, resolve);
2055
+ if ("refusal" in validated) {
2056
+ const prefix = hops === 0 ? "refused to fetch" : "refused redirect to";
2057
+ return {
2058
+ content: `ERROR: ${prefix} ${current.href}: ${validated.refusal}`,
2059
+ isError: true
2060
+ };
2061
+ }
2062
+ target = validated.target;
2063
+ }
1875
2064
  try {
1876
- res = await fetchImpl(current, { signal, redirect: "manual" });
2065
+ res = fetchImpl ? await fetchImpl(current, { signal, redirect: "manual" }) : await nativeFetch(current, signal, target);
1877
2066
  } catch (err) {
1878
2067
  if (ctx.abortSignal?.aborted) {
1879
2068
  return { content: "ERROR: request aborted", isError: true };
@@ -1903,15 +2092,6 @@ function createWebFetchTool(options = {}) {
1903
2092
  isError: true
1904
2093
  };
1905
2094
  }
1906
- if (!allowPrivate()) {
1907
- const refusal = await privateHostRefusal(next, resolve);
1908
- if (refusal !== null) {
1909
- return {
1910
- content: `ERROR: refused redirect to ${next.href}: ${refusal}`,
1911
- isError: true
1912
- };
1913
- }
1914
- }
1915
2095
  current = next;
1916
2096
  continue;
1917
2097
  }
@@ -1965,6 +2145,23 @@ var webFetchTool = createWebFetchTool();
1965
2145
  export {
1966
2146
  defaultAgentTasks,
1967
2147
  formatAgentTaskUpdate,
2148
+ starHome,
2149
+ globalConfigPath,
2150
+ sessionsDir,
2151
+ userMemoryPath,
2152
+ gitTreesDir,
2153
+ envFilePath,
2154
+ projectConfigPath,
2155
+ loadEnvFile,
2156
+ upsertEnvContent,
2157
+ registerSensitivePatterns,
2158
+ sensitiveExcludePatterns,
2159
+ isSensitivePath,
2160
+ isSecretEnvName,
2161
+ MAX_OUTPUT,
2162
+ resolveShell,
2163
+ bashTool,
2164
+ defaultTaskManager,
1968
2165
  responseBodyOf,
1969
2166
  MAX_IMAGE_DIMENSION,
1970
2167
  probeImageDimensions,
@@ -1973,29 +2170,15 @@ export {
1973
2170
  stripOversizedImages,
1974
2171
  stripAllImages,
1975
2172
  downsampleImageIfNeeded,
1976
- registerSensitivePatterns,
1977
- isSensitivePath,
1978
2173
  SKIP_DIRS,
1979
2174
  WALK_CONCURRENCY,
2175
+ realpathForCheck,
1980
2176
  walkFiles,
1981
2177
  matchesGlob,
1982
2178
  createIgnorePredicate,
1983
- starHome,
1984
- globalConfigPath,
1985
- sessionsDir,
1986
- userMemoryPath,
1987
- gitTreesDir,
1988
- envFilePath,
1989
- projectConfigPath,
1990
2179
  setWebFetchAllowPrivateHosts,
1991
2180
  decodeEntities,
1992
2181
  webFetchTool,
1993
- loadEnvFile,
1994
- upsertEnvContent,
1995
- MAX_OUTPUT,
1996
- resolveShell,
1997
- bashTool,
1998
- defaultTaskManager,
1999
2182
  MAX_SNAPSHOT_CONTENT_BYTES,
2000
2183
  setSnapshotHooks,
2001
2184
  nextSnapshotId,
@@ -2023,4 +2206,4 @@ export {
2023
2206
  resetTodos,
2024
2207
  createTodoTools
2025
2208
  };
2026
- //# sourceMappingURL=chunk-LSYGHLRU.js.map
2209
+ //# sourceMappingURL=chunk-2ISBRSBA.js.map