@cryer/star-cli 0.3.13 → 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 (32) hide show
  1. package/README.md +17 -5
  2. package/README.zh-CN.md +15 -5
  3. package/dist/{chunk-6PLZ3FLG.js → chunk-2ISBRSBA.js} +375 -235
  4. package/dist/chunk-2ISBRSBA.js.map +1 -0
  5. package/dist/{chunk-QELCMXQX.js → chunk-4HANB3DJ.js} +381 -212
  6. package/dist/chunk-4HANB3DJ.js.map +1 -0
  7. package/dist/{chunk-OF42OVM4.js → chunk-54KH4JSO.js} +204 -125
  8. package/dist/chunk-54KH4JSO.js.map +1 -0
  9. package/dist/{chunk-RD4S5LKM.js → chunk-CECMCDZG.js} +43 -30
  10. package/dist/chunk-CECMCDZG.js.map +1 -0
  11. package/dist/{chunk-MVAMPVTA.js → chunk-KILMI3S5.js} +13 -5
  12. package/dist/{chunk-MVAMPVTA.js.map → chunk-KILMI3S5.js.map} +1 -1
  13. package/dist/{chunk-NMB3TNQU.js → chunk-NRLGGA7L.js} +507 -175
  14. package/dist/chunk-NRLGGA7L.js.map +1 -0
  15. package/dist/loop-M64D5YII.js +17 -0
  16. package/dist/main.js +177 -131
  17. package/dist/main.js.map +1 -1
  18. package/dist/{provider-EWMUPBLH.js → provider-3Z4B7RMF.js} +2 -2
  19. package/dist/{repl-ZABBT33D.js → repl-N2H7JDSH.js} +617 -327
  20. package/dist/repl-N2H7JDSH.js.map +1 -0
  21. package/dist/{tools-4OLADVDR.js → tools-5RKM7ZI3.js} +3 -3
  22. package/package.json +1 -1
  23. package/dist/chunk-6PLZ3FLG.js.map +0 -1
  24. package/dist/chunk-NMB3TNQU.js.map +0 -1
  25. package/dist/chunk-OF42OVM4.js.map +0 -1
  26. package/dist/chunk-QELCMXQX.js.map +0 -1
  27. package/dist/chunk-RD4S5LKM.js.map +0 -1
  28. package/dist/loop-DWQ5WTPD.js +0 -17
  29. package/dist/repl-ZABBT33D.js.map +0 -1
  30. /package/dist/{loop-DWQ5WTPD.js.map → loop-M64D5YII.js.map} +0 -0
  31. /package/dist/{provider-EWMUPBLH.js.map → provider-3Z4B7RMF.js.map} +0 -0
  32. /package/dist/{tools-4OLADVDR.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";
@@ -390,6 +233,29 @@ function registerSensitivePatterns(patterns) {
390
233
  extraPatterns.length = 0;
391
234
  extraPatterns.push(...patterns.map((p) => p.trim()).filter((p) => p.length > 0));
392
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
+ }
393
259
  function globToRegExp(pattern) {
394
260
  const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
395
261
  return new RegExp(`^${escaped}$`, "i");
@@ -817,6 +683,163 @@ var TaskManager = class extends EventEmitter2 {
817
683
  };
818
684
  var defaultTaskManager = new TaskManager();
819
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
+
820
843
  // src/tools/fs/snapshots.ts
821
844
  import { randomUUID } from "crypto";
822
845
  import { mkdir, readFile, rm, stat, writeFile } from "fs/promises";
@@ -876,6 +899,7 @@ async function captureSnapshot(filePath, toolName, snapshotContext) {
876
899
  let content = null;
877
900
  let existed = false;
878
901
  let contentTooLarge = false;
902
+ let captureFailed = false;
879
903
  const st = await stat(filePath).catch(() => null);
880
904
  if (st?.isFile()) {
881
905
  existed = true;
@@ -885,7 +909,7 @@ async function captureSnapshot(filePath, toolName, snapshotContext) {
885
909
  try {
886
910
  content = await readFile(filePath);
887
911
  } catch {
888
- existed = false;
912
+ captureFailed = true;
889
913
  }
890
914
  }
891
915
  }
@@ -895,6 +919,7 @@ async function captureSnapshot(filePath, toolName, snapshotContext) {
895
919
  existed,
896
920
  content,
897
921
  contentTooLarge: contentTooLarge || void 0,
922
+ captureFailed: captureFailed || void 0,
898
923
  toolName,
899
924
  timestamp: Date.now(),
900
925
  turn: snapshotContext?.turn ?? activeTurn,
@@ -919,7 +944,14 @@ async function pushSnapshot(snapshot) {
919
944
  }
920
945
  }
921
946
  async function removeFromStore(ids) {
922
- 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
+ }
923
955
  }
924
956
  function listSnapshots() {
925
957
  return [...stack];
@@ -1045,6 +1077,7 @@ async function rewindToSnapshot(id, includeSubagent = false) {
1045
1077
  }
1046
1078
 
1047
1079
  // src/tools/todo.ts
1080
+ import { randomUUID as randomUUID2 } from "crypto";
1048
1081
  import { mkdir as mkdir2, readFile as readFile2, rename, rm as rm2, writeFile as writeFile2 } from "fs/promises";
1049
1082
  import path4 from "path";
1050
1083
  import { z as z2 } from "zod";
@@ -1074,10 +1107,16 @@ function setTodoSession(id) {
1074
1107
  }
1075
1108
  var TodoStore = class {
1076
1109
  items = /* @__PURE__ */ new Map();
1110
+ persistence;
1111
+ tmpSuffix = `${process.pid}-${randomUUID2()}`;
1112
+ saveQueue = Promise.resolve();
1077
1113
  // Bumped on every replace() — the agent loop watches it to detect
1078
1114
  // todo_write landings, so a list left untouched for many steps (with open
1079
1115
  // items) can be flagged as stale and the model reminded to reconcile it.
1080
1116
  revisionValue = 0;
1117
+ constructor(options = {}) {
1118
+ this.persistence = options.persistence ?? "file";
1119
+ }
1081
1120
  get revision() {
1082
1121
  return this.revisionValue;
1083
1122
  }
@@ -1092,6 +1131,7 @@ var TodoStore = class {
1092
1131
  }
1093
1132
  }
1094
1133
  async load(cwd, sessionId) {
1134
+ if (this.persistence === "memory") return;
1095
1135
  let raw;
1096
1136
  try {
1097
1137
  raw = await readFile2(fileFor(cwd), "utf8");
@@ -1116,23 +1156,27 @@ var TodoStore = class {
1116
1156
  } catch {
1117
1157
  }
1118
1158
  }
1119
- // Atomic write (tmp + rename): a crash or failed save can never leave a
1120
- // truncated todos.json behind. An empty or fully completed list has no
1121
- // resume value — rehydrating it would only restore finished work — so the
1122
- // file is deleted instead of persisted.
1123
- async save(cwd) {
1124
- if (!persistGuard()) {
1125
- return;
1126
- }
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();
1127
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) {
1128
1172
  const file = fileFor(cwd);
1129
1173
  if (items.length === 0 || items.every((it) => it.status === "done")) {
1130
1174
  await rm2(file, { force: true });
1131
1175
  return;
1132
1176
  }
1133
1177
  await mkdir2(path4.join(cwd, ".star"), { recursive: true });
1134
- const tmp = `${file}.tmp`;
1135
- 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)}
1136
1180
  `);
1137
1181
  await rename(tmp, file);
1138
1182
  }
@@ -1209,7 +1253,7 @@ function createTodoTools(store = defaultStore) {
1209
1253
 
1210
1254
  // src/core/image-resize.ts
1211
1255
  import { execFile } from "child_process";
1212
- import { randomUUID as randomUUID2 } from "crypto";
1256
+ import { randomUUID as randomUUID3 } from "crypto";
1213
1257
  import { readFile as readFile3, unlink, writeFile as writeFile3 } from "fs/promises";
1214
1258
  import os3 from "os";
1215
1259
  import path5 from "path";
@@ -1298,8 +1342,8 @@ async function downsampleImageIfNeeded(image) {
1298
1342
  if (!dims || Math.max(dims.width, dims.height) <= MAX_IMAGE_DIMENSION) {
1299
1343
  return { image, oversized: false, resized: false };
1300
1344
  }
1301
- const inputFile = path5.join(os3.tmpdir(), `star-image-${randomUUID2()}.src`);
1302
- 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`);
1303
1347
  try {
1304
1348
  const [hasConvert, hasFfmpeg] = process.platform === "linux" ? await Promise.all([commandExists("convert"), commandExists("ffmpeg")]) : [false, false];
1305
1349
  const plan = resizeImagePlan(process.platform, inputFile, outputFile, MAX_IMAGE_DIMENSION, {
@@ -1377,6 +1421,21 @@ async function walkFiles(root, skipDirs = SKIP_DIRS, options = {}) {
1377
1421
  const out = [];
1378
1422
  const limit = createLimiter(WALK_CONCURRENCY);
1379
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
+ };
1380
1439
  const caseKey = (p) => process.platform === "win32" ? p.toLowerCase() : p;
1381
1440
  const visited = /* @__PURE__ */ new Set();
1382
1441
  const rootReal = await realpath(root).catch(() => null);
@@ -1404,7 +1463,7 @@ async function walkFiles(root, skipDirs = SKIP_DIRS, options = {}) {
1404
1463
  return realKey === boundaryKey || realKey.startsWith(`${boundaryKey}${path6.sep}`) ? real : null;
1405
1464
  }
1406
1465
  async function walkDir(dir, relBase, parentLayers) {
1407
- if (signal?.aborted) {
1466
+ if (signal?.aborted || stopped) {
1408
1467
  return;
1409
1468
  }
1410
1469
  const entries = await limit(() => readdir(dir, { withFileTypes: true })).catch(() => null);
@@ -1419,8 +1478,9 @@ async function walkFiles(root, skipDirs = SKIP_DIRS, options = {}) {
1419
1478
  }
1420
1479
  }
1421
1480
  const subs = [];
1481
+ const batch = [];
1422
1482
  for (const entry of entries) {
1423
- if (signal?.aborted) {
1483
+ if (signal?.aborted || stopped) {
1424
1484
  break;
1425
1485
  }
1426
1486
  const abs = path6.join(dir, entry.name);
@@ -1463,22 +1523,26 @@ async function walkFiles(root, skipDirs = SKIP_DIRS, options = {}) {
1463
1523
  continue;
1464
1524
  }
1465
1525
  if (options.withMtime === false) {
1466
- out.push(
1467
- 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
1468
1529
  );
1469
1530
  continue;
1470
1531
  }
1471
1532
  const st = targetStat ?? await limit(() => stat2(abs)).catch(() => null);
1472
1533
  if (st) {
1473
- out.push(
1474
- 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
1475
1537
  );
1476
1538
  }
1477
1539
  }
1478
1540
  }
1541
+ await emitBatch(batch);
1479
1542
  await Promise.all(subs);
1480
1543
  }
1481
1544
  await walkDir(root, "", []);
1545
+ await callbackChain;
1482
1546
  out.sort((a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0);
1483
1547
  return { files: out, aborted: signal?.aborted ?? false };
1484
1548
  }
@@ -1677,6 +1741,10 @@ function createIgnorePredicate(cwd) {
1677
1741
 
1678
1742
  // src/tools/web/fetch.ts
1679
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";
1680
1748
  import { z as z3 } from "zod";
1681
1749
  var DEFAULT_MAX_CHARS = 2e4;
1682
1750
  var MAX_BYTES = 2 * 1024 * 1024;
@@ -1696,47 +1764,121 @@ var configuredAllowPrivateHosts = false;
1696
1764
  function setWebFetchAllowPrivateHosts(allow) {
1697
1765
  configuredAllowPrivateHosts = allow;
1698
1766
  }
1699
- function isPrivateIpv4(ip) {
1700
- const parts = ip.split(".").map(Number);
1701
- if (parts.length !== 4 || parts.some((n) => !Number.isInteger(n) || n < 0 || n > 255)) {
1702
- return false;
1703
- }
1704
- const [a = 0, b = 0] = parts;
1705
- 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(".");
1706
1810
  }
1707
1811
  function isPrivateAddress(address) {
1708
- let ip = address.toLowerCase();
1709
- if (ip.startsWith("::ffff:")) ip = ip.slice("::ffff:".length);
1710
- if (ip.includes(".")) return isPrivateIpv4(ip);
1711
- if (ip === "::1" || ip === "::") return true;
1712
- if (ip.startsWith("fc") || ip.startsWith("fd")) return true;
1713
- if (/^fe[89ab]/.test(ip)) return true;
1714
- 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;
1715
1818
  }
1716
1819
  function isLocalHostname(hostname) {
1717
1820
  const h = hostname.toLowerCase().replace(/\.$/, "");
1718
1821
  return h === "localhost" || h.endsWith(".localhost") || h.endsWith(".local") || h.endsWith(".internal");
1719
1822
  }
1720
- async function privateHostRefusal(url, resolve) {
1823
+ async function validateTarget(url, resolve) {
1721
1824
  const hostname = url.hostname.replace(/^\[|\]$/g, "");
1722
1825
  if (isLocalHostname(hostname)) {
1723
- return `"${hostname}" is a local/internal hostname`;
1826
+ return { refusal: `"${hostname}" is a local/internal hostname` };
1724
1827
  }
1725
1828
  let addresses;
1726
1829
  try {
1727
1830
  addresses = await resolve(hostname);
1728
1831
  } catch (err) {
1729
1832
  const message = err instanceof Error ? err.message : String(err);
1730
- return `could not resolve "${hostname}" (${message})`;
1833
+ return { refusal: `could not resolve "${hostname}" (${message})` };
1731
1834
  }
1732
1835
  const blocked = addresses.find(isPrivateAddress);
1733
1836
  if (blocked !== void 0) {
1734
- return `"${hostname}" resolves to a private/reserved address (${blocked})`;
1735
- }
1736
- if (addresses.length === 0) {
1737
- return `could not resolve "${hostname}"`;
1738
- }
1739
- 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
+ });
1740
1882
  }
1741
1883
  function safeCodePoint(n, raw) {
1742
1884
  if (!Number.isInteger(n) || n < 0 || n > 1114111) {
@@ -1881,7 +2023,7 @@ function truncate(s, maxChars) {
1881
2023
  }
1882
2024
  function createWebFetchTool(options = {}) {
1883
2025
  const resolve = options.resolver ?? defaultResolver;
1884
- const fetchImpl = options.fetchImpl ?? ((...args) => fetch(...args));
2026
+ const fetchImpl = options.fetchImpl;
1885
2027
  const allowPrivate = () => options.allowPrivateHosts ?? configuredAllowPrivateHosts;
1886
2028
  return {
1887
2029
  name: "web_fetch",
@@ -1901,20 +2043,26 @@ function createWebFetchTool(options = {}) {
1901
2043
  isError: true
1902
2044
  };
1903
2045
  }
1904
- if (!allowPrivate()) {
1905
- const refusal = await privateHostRefusal(url, resolve);
1906
- if (refusal !== null) {
1907
- return { content: `ERROR: refused to fetch ${args.url}: ${refusal}`, isError: true };
1908
- }
1909
- }
1910
2046
  const maxChars = args.maxChars ?? DEFAULT_MAX_CHARS;
1911
2047
  const timeoutSignal = AbortSignal.timeout(TIMEOUT_MS);
1912
2048
  const signal = ctx.abortSignal ? AbortSignal.any([timeoutSignal, ctx.abortSignal]) : timeoutSignal;
1913
2049
  let res;
1914
2050
  let current = url;
1915
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
+ }
1916
2064
  try {
1917
- res = await fetchImpl(current, { signal, redirect: "manual" });
2065
+ res = fetchImpl ? await fetchImpl(current, { signal, redirect: "manual" }) : await nativeFetch(current, signal, target);
1918
2066
  } catch (err) {
1919
2067
  if (ctx.abortSignal?.aborted) {
1920
2068
  return { content: "ERROR: request aborted", isError: true };
@@ -1944,15 +2092,6 @@ function createWebFetchTool(options = {}) {
1944
2092
  isError: true
1945
2093
  };
1946
2094
  }
1947
- if (!allowPrivate()) {
1948
- const refusal = await privateHostRefusal(next, resolve);
1949
- if (refusal !== null) {
1950
- return {
1951
- content: `ERROR: refused redirect to ${next.href}: ${refusal}`,
1952
- isError: true
1953
- };
1954
- }
1955
- }
1956
2095
  current = next;
1957
2096
  continue;
1958
2097
  }
@@ -2006,6 +2145,23 @@ var webFetchTool = createWebFetchTool();
2006
2145
  export {
2007
2146
  defaultAgentTasks,
2008
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,
2009
2165
  responseBodyOf,
2010
2166
  MAX_IMAGE_DIMENSION,
2011
2167
  probeImageDimensions,
@@ -2014,31 +2170,15 @@ export {
2014
2170
  stripOversizedImages,
2015
2171
  stripAllImages,
2016
2172
  downsampleImageIfNeeded,
2017
- registerSensitivePatterns,
2018
- isSensitivePath,
2019
- isSecretEnvName,
2020
2173
  SKIP_DIRS,
2021
2174
  WALK_CONCURRENCY,
2022
2175
  realpathForCheck,
2023
2176
  walkFiles,
2024
2177
  matchesGlob,
2025
2178
  createIgnorePredicate,
2026
- starHome,
2027
- globalConfigPath,
2028
- sessionsDir,
2029
- userMemoryPath,
2030
- gitTreesDir,
2031
- envFilePath,
2032
- projectConfigPath,
2033
2179
  setWebFetchAllowPrivateHosts,
2034
2180
  decodeEntities,
2035
2181
  webFetchTool,
2036
- loadEnvFile,
2037
- upsertEnvContent,
2038
- MAX_OUTPUT,
2039
- resolveShell,
2040
- bashTool,
2041
- defaultTaskManager,
2042
2182
  MAX_SNAPSHOT_CONTENT_BYTES,
2043
2183
  setSnapshotHooks,
2044
2184
  nextSnapshotId,
@@ -2066,4 +2206,4 @@ export {
2066
2206
  resetTodos,
2067
2207
  createTodoTools
2068
2208
  };
2069
- //# sourceMappingURL=chunk-6PLZ3FLG.js.map
2209
+ //# sourceMappingURL=chunk-2ISBRSBA.js.map