copilot-relay 0.4.7 → 0.4.8
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 +1 -1
- package/dist/main.js +30 -14
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,7 +72,7 @@ scripts. It asks GitHub with the stored token, so no relay needs to run.
|
|
|
72
72
|
|
|
73
73
|
- [Configure models and deep checks](wiki/EN-Configuration.md) · [中文](wiki/ZH-Configuration.md)
|
|
74
74
|
- Run at login: [macOS](wiki/EN-macOS-LaunchAgent.md) · [Windows](wiki/EN-Windows-Service.md) · [Linux](wiki/EN-Linux-systemd.md)
|
|
75
|
-
- [Troubleshoot](wiki/EN-Logging-Troubleshooting.md) · [Understand the architecture](wiki/EN-Architecture.md) · [Contribute](wiki/EN-Development.md)
|
|
75
|
+
- [Commands](wiki/EN-Commands.md) · [Prompt caching](wiki/EN-Prompt-Caching.md) · [Troubleshoot](wiki/EN-Logging-Troubleshooting.md) · [Understand the architecture](wiki/EN-Architecture.md) · [Contribute](wiki/EN-Development.md)
|
|
76
76
|
|
|
77
77
|
Unofficial research project; not affiliated with GitHub or Anthropic. Upstream
|
|
78
78
|
services and compatibility can change. Model and tool support are not guaranteed;
|
package/dist/main.js
CHANGED
|
@@ -49,6 +49,24 @@ var PromptTooLongError = class extends HTTPError {
|
|
|
49
49
|
}
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/lib/file-identity.ts
|
|
54
|
+
/** True when two stats of a path name the same file: the same device and file index. */
|
|
55
|
+
const sameFile$1 = (left, right) => left.dev === right.dev && left.ino === right.ino;
|
|
56
|
+
/**
|
|
57
|
+
* True when a stat of an open handle and a stat of its path name the same file.
|
|
58
|
+
*
|
|
59
|
+
* On Windows the two can disagree on `dev` for the same file: on Node 22.13.1 a path stat
|
|
60
|
+
* reports `dev` 0 while a handle stat reports the volume serial number. There a `dev` of 0 on
|
|
61
|
+
* either side counts as unreported and the file index (`ino`) alone decides, which is weaker
|
|
62
|
+
* evidence than both. Two nonzero `dev` values must still match, and other platforms compare both.
|
|
63
|
+
*/
|
|
64
|
+
const sameOpenedFile = (left, right, platform = process.platform) => {
|
|
65
|
+
if (left.ino !== right.ino) return false;
|
|
66
|
+
if (platform === "win32" && (left.dev === 0 || right.dev === 0)) return true;
|
|
67
|
+
return left.dev === right.dev;
|
|
68
|
+
};
|
|
69
|
+
|
|
52
70
|
//#endregion
|
|
53
71
|
//#region src/lib/paths.ts
|
|
54
72
|
const homeDir = os.homedir();
|
|
@@ -371,7 +389,6 @@ const formatLogValue = (value) => typeof value === "string" ? value : inspect(va
|
|
|
371
389
|
});
|
|
372
390
|
const escapeLogLineSeparators = (value) => value.replaceAll("\r", "\\r").replaceAll("\n", "\\n").replaceAll(String.fromCodePoint(8232), "\\u2028").replaceAll(String.fromCodePoint(8233), "\\u2029");
|
|
373
391
|
const isMissing$2 = (error) => error.code === "ENOENT";
|
|
374
|
-
const sameFile$2 = (left, right) => left.dev === right.dev && left.ino === right.ino;
|
|
375
392
|
const ensurePrivateDirectory = async (directory) => {
|
|
376
393
|
await fs.mkdir(directory, { mode: 448 }).catch((error) => {
|
|
377
394
|
if (error.code !== "EEXIST") throw error;
|
|
@@ -382,7 +399,7 @@ const ensurePrivateDirectory = async (directory) => {
|
|
|
382
399
|
const handle = await fs.open(directory, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
383
400
|
try {
|
|
384
401
|
const opened = await handle.stat();
|
|
385
|
-
if (!opened.isDirectory() || !
|
|
402
|
+
if (!opened.isDirectory() || !sameOpenedFile(observed, opened)) throw new Error("Log directory changed while opening");
|
|
386
403
|
await handle.chmod(448);
|
|
387
404
|
} finally {
|
|
388
405
|
await handle.close();
|
|
@@ -401,10 +418,10 @@ let activeLog;
|
|
|
401
418
|
let logIdleClose;
|
|
402
419
|
const maxLogWriteBytes = 256 * 1024;
|
|
403
420
|
const hasMode = (stats, mode) => process.platform === "win32" || (stats.mode & 511) === mode;
|
|
404
|
-
const isSameDirectory = (current, checked) => current !== void 0 && current.isDirectory() && sameFile$
|
|
421
|
+
const isSameDirectory = (current, checked) => current !== void 0 && current.isDirectory() && sameFile$1(current, checked) && hasMode(current, 448);
|
|
405
422
|
const isStillActive = async (active) => {
|
|
406
423
|
const [current, ...directories] = await Promise.all([active.filePath, ...logDirectories].map((target) => fs.lstat(target).catch(() => void 0)));
|
|
407
|
-
return current !== void 0 && current.isFile() && current.nlink === 1 &&
|
|
424
|
+
return current !== void 0 && current.isFile() && current.nlink === 1 && sameOpenedFile(current, active.identity) && hasMode(current, 384) && active.directories.every((checked, index) => isSameDirectory(directories[index], checked));
|
|
408
425
|
};
|
|
409
426
|
const openPrivateLog = async (filePath, directories) => {
|
|
410
427
|
const observed = await fs.lstat(filePath).catch((error) => {
|
|
@@ -416,7 +433,7 @@ const openPrivateLog = async (filePath, directories) => {
|
|
|
416
433
|
try {
|
|
417
434
|
const opened = await handle.stat();
|
|
418
435
|
const current = await fs.lstat(filePath);
|
|
419
|
-
if (!opened.isFile() || opened.nlink !== 1 || current.isSymbolicLink() || !
|
|
436
|
+
if (!opened.isFile() || opened.nlink !== 1 || current.isSymbolicLink() || !sameOpenedFile(opened, current) || observed && !sameOpenedFile(observed, opened)) throw new Error("Log file changed while opening");
|
|
420
437
|
if (process.platform !== "win32") await handle.chmod(384);
|
|
421
438
|
return {
|
|
422
439
|
filePath,
|
|
@@ -2296,7 +2313,6 @@ const markDiscardedResponse = (response) => {
|
|
|
2296
2313
|
};
|
|
2297
2314
|
const credentialPattern = /(?:gh[pousr]_|github_pat_|sk-|eyJ)[A-Za-z0-9_-]+/;
|
|
2298
2315
|
const uuidPattern = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
|
|
2299
|
-
const sameFile$1 = (left, right) => left.dev === right.dev && left.ino === right.ino;
|
|
2300
2316
|
const noFollow = process.platform === "win32" ? 0 : fs.constants.O_NOFOLLOW;
|
|
2301
2317
|
const safeValue = (value) => typeof value === "string" && /^[A-Za-z0-9_.:/[\]-]{1,160}$/.test(value) && !/(?:gh[pousr]_|github_pat_|sk-|eyJ)[A-Za-z0-9_-]+/.test(value) ? value : void 0;
|
|
2302
2318
|
const errorName = (error) => error instanceof Error && [
|
|
@@ -2444,7 +2460,7 @@ const privateDirectory = async (directory) => {
|
|
|
2444
2460
|
const handle = await fs.open(directory, fs.constants.O_RDONLY | noFollow);
|
|
2445
2461
|
try {
|
|
2446
2462
|
const opened = await handle.stat();
|
|
2447
|
-
if (!opened.isDirectory() || !
|
|
2463
|
+
if (!opened.isDirectory() || !sameOpenedFile(observed, opened)) throw new Error("Capture directory changed.");
|
|
2448
2464
|
await handle.chmod(448);
|
|
2449
2465
|
return opened;
|
|
2450
2466
|
} finally {
|
|
@@ -2456,7 +2472,7 @@ const createPrivateFile = async (file) => {
|
|
|
2456
2472
|
try {
|
|
2457
2473
|
const opened = await handle.stat();
|
|
2458
2474
|
const current = await fs.lstat(file);
|
|
2459
|
-
if (!opened.isFile() || opened.nlink !== 1 || current.isSymbolicLink() || !
|
|
2475
|
+
if (!opened.isFile() || opened.nlink !== 1 || current.isSymbolicLink() || !sameOpenedFile(opened, current)) throw new Error("Unsafe capture file.");
|
|
2460
2476
|
return handle;
|
|
2461
2477
|
} catch (error) {
|
|
2462
2478
|
await handle.close();
|
|
@@ -2914,7 +2930,7 @@ var RequestTrace = class RequestTrace {
|
|
|
2914
2930
|
}
|
|
2915
2931
|
const opened = await handle.stat();
|
|
2916
2932
|
const current = await fs.lstat(file);
|
|
2917
|
-
if (!current.isFile() || current.isSymbolicLink() || opened.nlink !== 1 || !
|
|
2933
|
+
if (!current.isFile() || current.isSymbolicLink() || opened.nlink !== 1 || !sameOpenedFile(opened, current)) throw new Error("Capture file changed.");
|
|
2918
2934
|
await handle.writeFile(buffer);
|
|
2919
2935
|
}).catch(() => {
|
|
2920
2936
|
this.manifest.captureError = "capture_write_failed";
|
|
@@ -2967,7 +2983,7 @@ var RequestTrace = class RequestTrace {
|
|
|
2967
2983
|
await this.checkDirectories();
|
|
2968
2984
|
const manifest = path.join(this.captureDirectory, "meta.json");
|
|
2969
2985
|
const current = await fs.lstat(manifest);
|
|
2970
|
-
if (!current.isFile() || current.isSymbolicLink() || current.nlink !== 1 || !this.metadataStat || !
|
|
2986
|
+
if (!current.isFile() || current.isSymbolicLink() || current.nlink !== 1 || !this.metadataStat || !sameOpenedFile(current, this.metadataStat) || current.size !== this.metadataStat.size || current.mtimeMs !== this.metadataStat.mtimeMs || current.ctimeMs !== this.metadataStat.ctimeMs) throw new Error("Unsafe capture metadata.");
|
|
2971
2987
|
await fs.rename(path.join(this.captureDirectory, "meta.next.json"), manifest);
|
|
2972
2988
|
}
|
|
2973
2989
|
}).catch(() => {
|
|
@@ -3005,7 +3021,7 @@ const readRetentionManifest = async (file) => {
|
|
|
3005
3021
|
const handle = await fs.open(file, fs.constants.O_RDONLY | noFollow);
|
|
3006
3022
|
try {
|
|
3007
3023
|
const opened = await handle.stat();
|
|
3008
|
-
if (!
|
|
3024
|
+
if (!sameOpenedFile(observed, opened) || opened.nlink !== 1 || opened.size !== observed.size) throw new Error("Capture metadata changed.");
|
|
3009
3025
|
const bytes = Buffer.alloc(opened.size);
|
|
3010
3026
|
let offset = 0;
|
|
3011
3027
|
while (offset < bytes.length) {
|
|
@@ -3014,7 +3030,7 @@ const readRetentionManifest = async (file) => {
|
|
|
3014
3030
|
offset += bytesRead;
|
|
3015
3031
|
}
|
|
3016
3032
|
const current = await fs.lstat(file);
|
|
3017
|
-
if (!
|
|
3033
|
+
if (!sameOpenedFile(opened, current) || current.size !== opened.size || current.mtimeMs !== opened.mtimeMs) throw new Error("Capture metadata changed.");
|
|
3018
3034
|
return JSON.parse(bytes.toString("utf8"));
|
|
3019
3035
|
} finally {
|
|
3020
3036
|
await handle.close();
|
|
@@ -7741,7 +7757,7 @@ const readCaptureFile = async (directory, file, maximum, optional = false) => {
|
|
|
7741
7757
|
const handle = await fs.open(filePath, fs.constants.O_RDONLY | (process.platform === "win32" ? 0 : fs.constants.O_NOFOLLOW));
|
|
7742
7758
|
try {
|
|
7743
7759
|
const opened = await handle.stat();
|
|
7744
|
-
requireValid(opened.isFile() && opened
|
|
7760
|
+
requireValid(opened.isFile() && sameOpenedFile(opened, before) && opened.size === before.size);
|
|
7745
7761
|
const bytes = Buffer.alloc(opened.size);
|
|
7746
7762
|
let offset = 0;
|
|
7747
7763
|
while (offset < bytes.length) {
|
|
@@ -7750,7 +7766,7 @@ const readCaptureFile = async (directory, file, maximum, optional = false) => {
|
|
|
7750
7766
|
offset += read.bytesRead;
|
|
7751
7767
|
}
|
|
7752
7768
|
const after = await fs.lstat(filePath);
|
|
7753
|
-
requireValid(!after.isSymbolicLink() && after
|
|
7769
|
+
requireValid(!after.isSymbolicLink() && sameOpenedFile(after, opened) && after.size === opened.size && after.mtimeMs === opened.mtimeMs);
|
|
7754
7770
|
return bytes;
|
|
7755
7771
|
} finally {
|
|
7756
7772
|
await handle.close();
|