@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.
- package/README.md +17 -5
- package/README.zh-CN.md +15 -5
- package/dist/{chunk-LSYGHLRU.js → chunk-2ISBRSBA.js} +479 -296
- package/dist/chunk-2ISBRSBA.js.map +1 -0
- package/dist/{chunk-F5RWZMQY.js → chunk-4HANB3DJ.js} +458 -221
- package/dist/chunk-4HANB3DJ.js.map +1 -0
- package/dist/chunk-4IRY44TG.js +0 -0
- package/dist/{chunk-Q5FJN4LN.js → chunk-54KH4JSO.js} +213 -125
- package/dist/chunk-54KH4JSO.js.map +1 -0
- package/dist/{chunk-MYHD3SZB.js → chunk-CECMCDZG.js} +43 -30
- package/dist/chunk-CECMCDZG.js.map +1 -0
- package/dist/{chunk-MVAMPVTA.js → chunk-KILMI3S5.js} +13 -5
- package/dist/{chunk-MVAMPVTA.js.map → chunk-KILMI3S5.js.map} +1 -1
- package/dist/{chunk-2GAUAYCU.js → chunk-NRLGGA7L.js} +507 -175
- package/dist/chunk-NRLGGA7L.js.map +1 -0
- package/dist/loop-M64D5YII.js +17 -0
- package/dist/main.js +177 -131
- package/dist/main.js.map +1 -1
- package/dist/{provider-EWMUPBLH.js → provider-3Z4B7RMF.js} +2 -2
- package/dist/{repl-SSCURTAH.js → repl-N2H7JDSH.js} +617 -327
- package/dist/repl-N2H7JDSH.js.map +1 -0
- package/dist/{tools-BH55BVIS.js → tools-5RKM7ZI3.js} +3 -3
- package/package.json +16 -25
- package/dist/chunk-2GAUAYCU.js.map +0 -1
- package/dist/chunk-F5RWZMQY.js.map +0 -1
- package/dist/chunk-LSYGHLRU.js.map +0 -1
- package/dist/chunk-MYHD3SZB.js.map +0 -1
- package/dist/chunk-Q5FJN4LN.js.map +0 -1
- package/dist/loop-BYXBNFFY.js +0 -17
- package/dist/repl-SSCURTAH.js.map +0 -1
- /package/dist/{loop-BYXBNFFY.js.map → loop-M64D5YII.js.map} +0 -0
- /package/dist/{provider-EWMUPBLH.js.map → provider-3Z4B7RMF.js.map} +0 -0
- /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
|
-
|
|
529
|
-
|
|
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
|
-
|
|
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
|
|
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)
|
|
1047
|
-
//
|
|
1048
|
-
//
|
|
1049
|
-
|
|
1050
|
-
|
|
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
|
|
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
|
|
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-${
|
|
1229
|
-
const outputFile = path5.join(os3.tmpdir(), `star-image-${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1674
|
+
return compiledGlob(normalized).test(base);
|
|
1570
1675
|
}
|
|
1571
|
-
return
|
|
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
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
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
|
-
|
|
1668
|
-
|
|
1669
|
-
if (
|
|
1670
|
-
if (ip
|
|
1671
|
-
if (ip
|
|
1672
|
-
|
|
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
|
|
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
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
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
|
|
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-
|
|
2209
|
+
//# sourceMappingURL=chunk-2ISBRSBA.js.map
|