@dev-anywhere/proxy 0.1.1 → 0.1.3
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 +4 -1
- package/dist/{chunk-TG7JPHE5.js → chunk-JGGDVMY5.js} +2 -2
- package/dist/{chunk-QXOARRC2.js → chunk-ODK6N2NP.js} +2 -2
- package/dist/{chunk-DFLQ3TFT.js → chunk-RFBTVZ2X.js} +16 -1
- package/dist/chunk-RFBTVZ2X.js.map +1 -0
- package/dist/{chunk-CDKXSDAV.js → chunk-TX6HNHDB.js} +2 -2
- package/dist/index.js +3 -3
- package/dist/serve.js +242 -51
- package/dist/serve.js.map +1 -1
- package/dist/session-worker.js +2 -2
- package/dist/{terminal-GIU6MXOR.js → terminal-ES6I5W32.js} +4 -4
- package/package.json +3 -3
- package/dist/chunk-DFLQ3TFT.js.map +0 -1
- /package/dist/{chunk-TG7JPHE5.js.map → chunk-JGGDVMY5.js.map} +0 -0
- /package/dist/{chunk-QXOARRC2.js.map → chunk-ODK6N2NP.js.map} +0 -0
- /package/dist/{chunk-CDKXSDAV.js.map → chunk-TX6HNHDB.js.map} +0 -0
- /package/dist/{terminal-GIU6MXOR.js.map → terminal-ES6I5W32.js.map} +0 -0
package/dist/serve.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
KnownContentBlockSchema,
|
|
6
6
|
SeqCounter,
|
|
7
7
|
StreamJsonEventSchema
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-ODK6N2NP.js";
|
|
9
9
|
import {
|
|
10
10
|
createFSM,
|
|
11
11
|
defineFSM,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
serviceLogger,
|
|
15
15
|
shouldReleaseApprovalWait,
|
|
16
16
|
stateAfterApprovalRelease
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-JGGDVMY5.js";
|
|
18
18
|
import {
|
|
19
19
|
spawnScript
|
|
20
20
|
} from "./chunk-ZUWAB67J.js";
|
|
@@ -46,7 +46,7 @@ import {
|
|
|
46
46
|
serializeWorkerMsg,
|
|
47
47
|
sessionPaths,
|
|
48
48
|
tildify
|
|
49
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-RFBTVZ2X.js";
|
|
50
50
|
|
|
51
51
|
// src/serve.ts
|
|
52
52
|
import { createServer as createServer2 } from "net";
|
|
@@ -702,6 +702,7 @@ function loadConfig(options) {
|
|
|
702
702
|
hookPort: parsePort(process.env.DEV_ANYWHERE_HOOK_PORT, "DEV_ANYWHERE_HOOK_PORT") ?? defaultHookPortForProfile(PROFILE_NAME),
|
|
703
703
|
claudeBin,
|
|
704
704
|
codexBin,
|
|
705
|
+
previewRoots: uniqueAbsolutePaths(fromFile.previewRoots ?? []),
|
|
705
706
|
agentCliSuggestions: {
|
|
706
707
|
claude: uniqueAbsolutePaths([
|
|
707
708
|
process.env.CLAUDE_BIN,
|
|
@@ -1083,7 +1084,7 @@ function extractConversationText(msg) {
|
|
|
1083
1084
|
return null;
|
|
1084
1085
|
}
|
|
1085
1086
|
async function extractTitleAndCwd(filePath) {
|
|
1086
|
-
return new Promise((
|
|
1087
|
+
return new Promise((resolve3) => {
|
|
1087
1088
|
const rl = createInterface({
|
|
1088
1089
|
input: createReadStream(filePath, { encoding: "utf-8" }),
|
|
1089
1090
|
crlfDelay: Infinity
|
|
@@ -1111,10 +1112,10 @@ async function extractTitleAndCwd(filePath) {
|
|
|
1111
1112
|
}
|
|
1112
1113
|
});
|
|
1113
1114
|
rl.on("close", () => {
|
|
1114
|
-
if (!resolved)
|
|
1115
|
-
else
|
|
1115
|
+
if (!resolved) resolve3({ title, cwd });
|
|
1116
|
+
else resolve3({ title, cwd });
|
|
1116
1117
|
});
|
|
1117
|
-
rl.on("error", () =>
|
|
1118
|
+
rl.on("error", () => resolve3({ title, cwd }));
|
|
1118
1119
|
});
|
|
1119
1120
|
}
|
|
1120
1121
|
async function collectJsonlFiles(root) {
|
|
@@ -1136,7 +1137,7 @@ async function collectJsonlFiles(root) {
|
|
|
1136
1137
|
return files;
|
|
1137
1138
|
}
|
|
1138
1139
|
async function extractCodexTitleAndCwd(filePath) {
|
|
1139
|
-
return new Promise((
|
|
1140
|
+
return new Promise((resolve3) => {
|
|
1140
1141
|
const rl = createInterface({
|
|
1141
1142
|
input: createReadStream(filePath, { encoding: "utf-8" }),
|
|
1142
1143
|
crlfDelay: Infinity
|
|
@@ -1160,8 +1161,8 @@ async function extractCodexTitleAndCwd(filePath) {
|
|
|
1160
1161
|
} catch {
|
|
1161
1162
|
}
|
|
1162
1163
|
});
|
|
1163
|
-
rl.on("close", () =>
|
|
1164
|
-
rl.on("error", () =>
|
|
1164
|
+
rl.on("close", () => resolve3({ id, title, cwd }));
|
|
1165
|
+
rl.on("error", () => resolve3({ id, title, cwd }));
|
|
1165
1166
|
});
|
|
1166
1167
|
}
|
|
1167
1168
|
function extractCodexUserText(payload) {
|
|
@@ -1726,16 +1727,16 @@ var WorkerRegistry = class {
|
|
|
1726
1727
|
return workerPid;
|
|
1727
1728
|
}
|
|
1728
1729
|
connect(sessionId, sockPath) {
|
|
1729
|
-
return new Promise((
|
|
1730
|
+
return new Promise((resolve3) => {
|
|
1730
1731
|
const sock = connect(sockPath);
|
|
1731
1732
|
sock.on("connect", () => {
|
|
1732
1733
|
this.sockets.set(sessionId, sock);
|
|
1733
1734
|
createWorkerReader(sock, (msg) => this.handleWorkerMessage(sessionId, msg));
|
|
1734
1735
|
sock.on("close", () => this.onDisconnect(sessionId));
|
|
1735
1736
|
sock.on("error", () => this.onDisconnect(sessionId));
|
|
1736
|
-
|
|
1737
|
+
resolve3(sock);
|
|
1737
1738
|
});
|
|
1738
|
-
sock.on("error", () =>
|
|
1739
|
+
sock.on("error", () => resolve3(null));
|
|
1739
1740
|
});
|
|
1740
1741
|
}
|
|
1741
1742
|
// 枚举 DATA_DIR 下所有 session 目录,尝试连接存活的 worker.sock;失败则清理 stale socket。
|
|
@@ -2060,7 +2061,7 @@ function terminateSessionByOwnership(deps, sessionId) {
|
|
|
2060
2061
|
}
|
|
2061
2062
|
|
|
2062
2063
|
// src/serve/clipboard-image-upload.ts
|
|
2063
|
-
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
2064
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, statSync, writeFileSync as writeFileSync4 } from "fs";
|
|
2064
2065
|
import { isAbsolute as isAbsolute3, join as join5, relative, resolve } from "path";
|
|
2065
2066
|
import { nanoid as nanoid3 } from "nanoid";
|
|
2066
2067
|
var MAX_CLIPBOARD_IMAGE_BYTES = 10 * 1024 * 1024;
|
|
@@ -2093,15 +2094,50 @@ function decodeBase64Image(dataBase64) {
|
|
|
2093
2094
|
}
|
|
2094
2095
|
return buffer;
|
|
2095
2096
|
}
|
|
2096
|
-
function
|
|
2097
|
-
const root = resolve(
|
|
2098
|
-
const uploadDir = resolve(root,
|
|
2097
|
+
function resolveChildDir(rootPath, ...segments) {
|
|
2098
|
+
const root = resolve(rootPath);
|
|
2099
|
+
const uploadDir = resolve(root, ...segments);
|
|
2099
2100
|
const relativePath = relative(root, uploadDir);
|
|
2100
2101
|
if (!relativePath || relativePath.startsWith("..") || isAbsolute3(relativePath)) {
|
|
2101
2102
|
throw new Error("\u4F1A\u8BDD\u8DEF\u5F84\u65E0\u6548");
|
|
2102
2103
|
}
|
|
2103
2104
|
return uploadDir;
|
|
2104
2105
|
}
|
|
2106
|
+
function resolveSessionClipboardDir(dataDir, sessionId) {
|
|
2107
|
+
return resolveChildDir(dataDir, sessionId, "clipboard");
|
|
2108
|
+
}
|
|
2109
|
+
function normalizeGitignoreLine(line) {
|
|
2110
|
+
return line.trim().replace(/^\/+/, "").replace(/\/+$/, "");
|
|
2111
|
+
}
|
|
2112
|
+
function ensureProjectClipboardIgnored(cwd) {
|
|
2113
|
+
const gitignorePath = join5(cwd, ".gitignore");
|
|
2114
|
+
if (!existsSync5(gitignorePath)) return;
|
|
2115
|
+
try {
|
|
2116
|
+
const current = readFileSync5(gitignorePath, "utf-8");
|
|
2117
|
+
const alreadyIgnored = current.split(/\r?\n/).some((line) => normalizeGitignoreLine(line) === ".dev-anywhere");
|
|
2118
|
+
if (alreadyIgnored) return;
|
|
2119
|
+
const separator = current.length > 0 && !current.endsWith("\n") ? "\n" : "";
|
|
2120
|
+
writeFileSync4(gitignorePath, `${current}${separator}.dev-anywhere/
|
|
2121
|
+
`);
|
|
2122
|
+
} catch {
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
function trySaveProjectClipboardImage(options) {
|
|
2126
|
+
if (!options.cwd) return null;
|
|
2127
|
+
try {
|
|
2128
|
+
const cwd = resolve(options.cwd);
|
|
2129
|
+
if (!statSync(cwd).isDirectory()) return null;
|
|
2130
|
+
const clipboardRoot = resolve(cwd, ".dev-anywhere", "clipboard");
|
|
2131
|
+
const uploadDir = resolveChildDir(clipboardRoot, options.sessionId);
|
|
2132
|
+
const path = join5(uploadDir, options.fileName);
|
|
2133
|
+
mkdirSync4(uploadDir, { recursive: true });
|
|
2134
|
+
writeFileSync4(path, options.buffer, { mode: 384 });
|
|
2135
|
+
ensureProjectClipboardIgnored(cwd);
|
|
2136
|
+
return { success: true, path: relative(cwd, path) };
|
|
2137
|
+
} catch {
|
|
2138
|
+
return null;
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2105
2141
|
function saveClipboardImageUpload(request, options = {}) {
|
|
2106
2142
|
const extension = IMAGE_EXTENSIONS.get(request.mimeType);
|
|
2107
2143
|
if (!extension) {
|
|
@@ -2113,12 +2149,19 @@ function saveClipboardImageUpload(request, options = {}) {
|
|
|
2113
2149
|
};
|
|
2114
2150
|
}
|
|
2115
2151
|
try {
|
|
2116
|
-
const dataDir = options.dataDir ?? DATA_DIR;
|
|
2117
|
-
const uploadDir = resolveSessionClipboardDir(dataDir, request.sessionId);
|
|
2118
2152
|
const buffer = decodeBase64Image(request.dataBase64);
|
|
2119
2153
|
const now = options.now ?? Date.now;
|
|
2120
2154
|
const suffix = options.randomSuffix?.() ?? nanoid3(6);
|
|
2121
2155
|
const fileName = `pasted-${formatTimestamp(now())}-${suffix}.${extension}`;
|
|
2156
|
+
const projectResult = trySaveProjectClipboardImage({
|
|
2157
|
+
cwd: options.cwd,
|
|
2158
|
+
sessionId: request.sessionId,
|
|
2159
|
+
fileName,
|
|
2160
|
+
buffer
|
|
2161
|
+
});
|
|
2162
|
+
if (projectResult) return projectResult;
|
|
2163
|
+
const dataDir = options.dataDir ?? DATA_DIR;
|
|
2164
|
+
const uploadDir = resolveSessionClipboardDir(dataDir, request.sessionId);
|
|
2122
2165
|
const path = join5(uploadDir, fileName);
|
|
2123
2166
|
mkdirSync4(uploadDir, { recursive: true });
|
|
2124
2167
|
writeFileSync4(path, buffer, { mode: 384 });
|
|
@@ -2133,6 +2176,108 @@ function saveClipboardImageUpload(request, options = {}) {
|
|
|
2133
2176
|
}
|
|
2134
2177
|
}
|
|
2135
2178
|
|
|
2179
|
+
// src/serve/image-preview.ts
|
|
2180
|
+
import { readFileSync as readFileSync6, realpathSync, statSync as statSync2 } from "fs";
|
|
2181
|
+
import { tmpdir } from "os";
|
|
2182
|
+
import { isAbsolute as isAbsolute4, relative as relative2, resolve as resolve2 } from "path";
|
|
2183
|
+
var MAX_IMAGE_PREVIEW_BYTES = 10 * 1024 * 1024;
|
|
2184
|
+
function isInsideRoot(realFilePath, realRootPath) {
|
|
2185
|
+
const rel = relative2(realRootPath, realFilePath);
|
|
2186
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute4(rel);
|
|
2187
|
+
}
|
|
2188
|
+
function allowedRoots(options) {
|
|
2189
|
+
return [options.cwd, options.tmpDir ?? tmpdir(), ...options.previewRoots ?? []].map((root) => root.trim()).filter(Boolean).flatMap((root) => {
|
|
2190
|
+
try {
|
|
2191
|
+
return [realpathSync(root)];
|
|
2192
|
+
} catch {
|
|
2193
|
+
return [];
|
|
2194
|
+
}
|
|
2195
|
+
});
|
|
2196
|
+
}
|
|
2197
|
+
function resolvePreviewPath(rawPath, options) {
|
|
2198
|
+
const candidate = isAbsolute4(rawPath) ? resolve2(rawPath) : resolve2(options.cwd, rawPath);
|
|
2199
|
+
const realCandidate = realpathSync(candidate);
|
|
2200
|
+
if (!allowedRoots(options).some((root) => isInsideRoot(realCandidate, root))) {
|
|
2201
|
+
throw Object.assign(new Error("\u56FE\u7247\u8DEF\u5F84\u4E0D\u5728\u5141\u8BB8\u9884\u89C8\u7684\u76EE\u5F55\u5185"), {
|
|
2202
|
+
errorCode: ControlErrorCode.INVALID_PATH
|
|
2203
|
+
});
|
|
2204
|
+
}
|
|
2205
|
+
return realCandidate;
|
|
2206
|
+
}
|
|
2207
|
+
function detectImageMime(buffer) {
|
|
2208
|
+
if (buffer.length >= 8 && buffer[0] === 137 && buffer[1] === 80 && buffer[2] === 78 && buffer[3] === 71 && buffer[4] === 13 && buffer[5] === 10 && buffer[6] === 26 && buffer[7] === 10) {
|
|
2209
|
+
return "image/png";
|
|
2210
|
+
}
|
|
2211
|
+
if (buffer.length >= 3 && buffer[0] === 255 && buffer[1] === 216 && buffer[2] === 255) {
|
|
2212
|
+
return "image/jpeg";
|
|
2213
|
+
}
|
|
2214
|
+
if (buffer.length >= 12 && buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WEBP") {
|
|
2215
|
+
return "image/webp";
|
|
2216
|
+
}
|
|
2217
|
+
if (buffer.length >= 6 && (buffer.subarray(0, 6).toString("ascii") === "GIF87a" || buffer.subarray(0, 6).toString("ascii") === "GIF89a")) {
|
|
2218
|
+
return "image/gif";
|
|
2219
|
+
}
|
|
2220
|
+
return void 0;
|
|
2221
|
+
}
|
|
2222
|
+
function errorCode(err) {
|
|
2223
|
+
if (err instanceof Error && "errorCode" in err && typeof err.errorCode === "string") {
|
|
2224
|
+
return err.errorCode;
|
|
2225
|
+
}
|
|
2226
|
+
return classifyPathError(err);
|
|
2227
|
+
}
|
|
2228
|
+
function loadImagePreview(request, options) {
|
|
2229
|
+
try {
|
|
2230
|
+
const resolvedPath = resolvePreviewPath(request.path, options);
|
|
2231
|
+
const stat2 = statSync2(resolvedPath);
|
|
2232
|
+
if (!stat2.isFile()) {
|
|
2233
|
+
return {
|
|
2234
|
+
success: false,
|
|
2235
|
+
sessionId: request.sessionId,
|
|
2236
|
+
path: request.path,
|
|
2237
|
+
error: "\u8DEF\u5F84\u4E0D\u662F\u56FE\u7247\u6587\u4EF6",
|
|
2238
|
+
errorCode: ControlErrorCode.INVALID_PATH
|
|
2239
|
+
};
|
|
2240
|
+
}
|
|
2241
|
+
const maxBytes = options.maxBytes ?? MAX_IMAGE_PREVIEW_BYTES;
|
|
2242
|
+
if (stat2.size > maxBytes) {
|
|
2243
|
+
return {
|
|
2244
|
+
success: false,
|
|
2245
|
+
sessionId: request.sessionId,
|
|
2246
|
+
path: request.path,
|
|
2247
|
+
error: "\u56FE\u7247\u8D85\u8FC7 10MB \u9650\u5236",
|
|
2248
|
+
errorCode: ControlErrorCode.UNKNOWN
|
|
2249
|
+
};
|
|
2250
|
+
}
|
|
2251
|
+
const buffer = readFileSync6(resolvedPath);
|
|
2252
|
+
const mimeType = detectImageMime(buffer);
|
|
2253
|
+
if (!mimeType) {
|
|
2254
|
+
return {
|
|
2255
|
+
success: false,
|
|
2256
|
+
sessionId: request.sessionId,
|
|
2257
|
+
path: request.path,
|
|
2258
|
+
error: "\u4E0D\u652F\u6301\u8FD9\u79CD\u56FE\u7247\u683C\u5F0F",
|
|
2259
|
+
errorCode: ControlErrorCode.UNKNOWN
|
|
2260
|
+
};
|
|
2261
|
+
}
|
|
2262
|
+
return {
|
|
2263
|
+
success: true,
|
|
2264
|
+
sessionId: request.sessionId,
|
|
2265
|
+
path: request.path,
|
|
2266
|
+
mimeType,
|
|
2267
|
+
dataBase64: buffer.toString("base64"),
|
|
2268
|
+
size: buffer.length
|
|
2269
|
+
};
|
|
2270
|
+
} catch (err) {
|
|
2271
|
+
return {
|
|
2272
|
+
success: false,
|
|
2273
|
+
sessionId: request.sessionId,
|
|
2274
|
+
path: request.path,
|
|
2275
|
+
error: err instanceof Error ? err.message : String(err),
|
|
2276
|
+
errorCode: errorCode(err)
|
|
2277
|
+
};
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2136
2281
|
// src/serve/pty-input.ts
|
|
2137
2282
|
function serializeRawPtyInput(sessionId, data) {
|
|
2138
2283
|
return serializeIpc({ type: "pty_input", sessionId, data });
|
|
@@ -2226,12 +2371,17 @@ var RelayInputHandlers = class {
|
|
|
2226
2371
|
serviceLogger.warn({ sessionId }, "Clipboard image upload rejected: session not found");
|
|
2227
2372
|
return;
|
|
2228
2373
|
}
|
|
2229
|
-
const result = saveClipboardImageUpload(
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2374
|
+
const result = saveClipboardImageUpload(
|
|
2375
|
+
{
|
|
2376
|
+
sessionId,
|
|
2377
|
+
mimeType: typeof msg.mimeType === "string" ? msg.mimeType : "",
|
|
2378
|
+
dataBase64: typeof msg.dataBase64 === "string" ? msg.dataBase64 : "",
|
|
2379
|
+
fileName: typeof msg.fileName === "string" ? msg.fileName : void 0
|
|
2380
|
+
},
|
|
2381
|
+
{
|
|
2382
|
+
cwd: session.cwd
|
|
2383
|
+
}
|
|
2384
|
+
);
|
|
2235
2385
|
this.deps.relayConnection.sendRaw(
|
|
2236
2386
|
JSON.stringify({
|
|
2237
2387
|
type: "clipboard_image_upload_response",
|
|
@@ -2242,6 +2392,43 @@ var RelayInputHandlers = class {
|
|
|
2242
2392
|
);
|
|
2243
2393
|
serviceLogger.info({ sessionId, success: result.success }, "Clipboard image upload handled");
|
|
2244
2394
|
}
|
|
2395
|
+
onImagePreviewRequest(msg) {
|
|
2396
|
+
const sessionId = msg.sessionId;
|
|
2397
|
+
const requestId = msg.requestId;
|
|
2398
|
+
const path = msg.path;
|
|
2399
|
+
if (!sessionId || !path) return;
|
|
2400
|
+
const session = this.deps.sessionManager.getSession(sessionId);
|
|
2401
|
+
if (!session) {
|
|
2402
|
+
this.deps.relayConnection.sendRaw(
|
|
2403
|
+
JSON.stringify({
|
|
2404
|
+
type: "image_preview_response",
|
|
2405
|
+
requestId,
|
|
2406
|
+
sessionId,
|
|
2407
|
+
success: false,
|
|
2408
|
+
path,
|
|
2409
|
+
error: "\u4F1A\u8BDD\u4E0D\u5B58\u5728",
|
|
2410
|
+
errorCode: ControlErrorCode.SESSION_NOT_FOUND
|
|
2411
|
+
})
|
|
2412
|
+
);
|
|
2413
|
+
serviceLogger.warn({ sessionId }, "Image preview rejected: session not found");
|
|
2414
|
+
return;
|
|
2415
|
+
}
|
|
2416
|
+
const result = loadImagePreview(
|
|
2417
|
+
{ sessionId, path },
|
|
2418
|
+
{
|
|
2419
|
+
cwd: session.cwd,
|
|
2420
|
+
previewRoots: this.deps.previewRoots
|
|
2421
|
+
}
|
|
2422
|
+
);
|
|
2423
|
+
this.deps.relayConnection.sendRaw(
|
|
2424
|
+
JSON.stringify({
|
|
2425
|
+
type: "image_preview_response",
|
|
2426
|
+
requestId,
|
|
2427
|
+
...result
|
|
2428
|
+
})
|
|
2429
|
+
);
|
|
2430
|
+
serviceLogger.info({ sessionId, success: result.success }, "Image preview handled");
|
|
2431
|
+
}
|
|
2245
2432
|
};
|
|
2246
2433
|
|
|
2247
2434
|
// src/serve/relay-history-handlers.ts
|
|
@@ -2441,14 +2628,14 @@ var RelayPermissionHandlers = class {
|
|
|
2441
2628
|
|
|
2442
2629
|
// src/serve/relay-resource-handlers.ts
|
|
2443
2630
|
import { homedir as homedir4 } from "os";
|
|
2444
|
-
import { accessSync, constants, statSync } from "fs";
|
|
2631
|
+
import { accessSync, constants, statSync as statSync3 } from "fs";
|
|
2445
2632
|
function errorMessage(err) {
|
|
2446
2633
|
return err instanceof Error ? err.message : String(err);
|
|
2447
2634
|
}
|
|
2448
2635
|
function validateExecutablePath(path) {
|
|
2449
2636
|
const normalized = path.trim();
|
|
2450
2637
|
if (!normalized.startsWith("/")) throw new Error("CLI \u8DEF\u5F84\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84");
|
|
2451
|
-
const stat2 =
|
|
2638
|
+
const stat2 = statSync3(normalized);
|
|
2452
2639
|
if (!stat2.isFile()) throw new Error("CLI \u8DEF\u5F84\u4E0D\u662F\u53EF\u6267\u884C\u6587\u4EF6");
|
|
2453
2640
|
accessSync(normalized, constants.X_OK);
|
|
2454
2641
|
return normalized;
|
|
@@ -2557,8 +2744,8 @@ var RelayResourceHandlers = class {
|
|
|
2557
2744
|
};
|
|
2558
2745
|
|
|
2559
2746
|
// src/serve/relay-session-create-handler.ts
|
|
2560
|
-
import { rmSync, statSync as
|
|
2561
|
-
import { isAbsolute as
|
|
2747
|
+
import { rmSync, statSync as statSync4 } from "fs";
|
|
2748
|
+
import { isAbsolute as isAbsolute5 } from "path";
|
|
2562
2749
|
import { nanoid as nanoid4 } from "nanoid";
|
|
2563
2750
|
|
|
2564
2751
|
// src/serve/hosted-pty-registry.ts
|
|
@@ -2848,11 +3035,11 @@ function validateSessionCwd(cwd) {
|
|
|
2848
3035
|
return { message: "\u8BF7\u8F93\u5165\u5DE5\u4F5C\u76EE\u5F55", code: ControlErrorCode.INVALID_PATH };
|
|
2849
3036
|
}
|
|
2850
3037
|
const trimmed = cwd.trim();
|
|
2851
|
-
if (!
|
|
3038
|
+
if (!isAbsolute5(trimmed)) {
|
|
2852
3039
|
return { message: "\u5DE5\u4F5C\u76EE\u5F55\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84", code: ControlErrorCode.INVALID_PATH };
|
|
2853
3040
|
}
|
|
2854
3041
|
try {
|
|
2855
|
-
const stat2 =
|
|
3042
|
+
const stat2 = statSync4(trimmed);
|
|
2856
3043
|
return stat2.isDirectory() ? null : { message: "\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u662F\u76EE\u5F55", code: ControlErrorCode.PATH_NOT_DIRECTORY };
|
|
2857
3044
|
} catch (err) {
|
|
2858
3045
|
return {
|
|
@@ -3101,7 +3288,8 @@ var RelayRouter = class {
|
|
|
3101
3288
|
relayConnection: deps.relayConnection,
|
|
3102
3289
|
terminalSockets: deps.terminalSockets,
|
|
3103
3290
|
hostedPtyRegistry: deps.hostedPtyRegistry,
|
|
3104
|
-
jsonObserver: deps.jsonObserver
|
|
3291
|
+
jsonObserver: deps.jsonObserver,
|
|
3292
|
+
previewRoots: deps.getPreviewRoots?.()
|
|
3105
3293
|
});
|
|
3106
3294
|
this.resourceHandlers = new RelayResourceHandlers({
|
|
3107
3295
|
relaySend: deps.relaySend,
|
|
@@ -3159,6 +3347,7 @@ var RelayRouter = class {
|
|
|
3159
3347
|
user_input: (msg) => this.inputHandlers.onUserInput(msg),
|
|
3160
3348
|
remote_input_raw: (msg) => this.inputHandlers.onRemoteInputRaw(msg),
|
|
3161
3349
|
clipboard_image_upload: (msg) => this.inputHandlers.onClipboardImageUpload(msg),
|
|
3350
|
+
image_preview_request: (msg) => this.inputHandlers.onImagePreviewRequest(msg),
|
|
3162
3351
|
tool_approve: (msg) => this.permissionHandlers.onToolApprove(msg),
|
|
3163
3352
|
tool_deny: (msg) => this.permissionHandlers.onToolDeny(msg),
|
|
3164
3353
|
proxy_info_request: (msg) => this.resourceHandlers.onProxyInfoRequest(msg),
|
|
@@ -3349,11 +3538,11 @@ var PermissionBroker = class {
|
|
|
3349
3538
|
message: "Duplicate permission request id."
|
|
3350
3539
|
});
|
|
3351
3540
|
}
|
|
3352
|
-
return new Promise((
|
|
3541
|
+
return new Promise((resolve3) => {
|
|
3353
3542
|
this.pending.set(request.requestId, {
|
|
3354
3543
|
...request,
|
|
3355
3544
|
source: "hook",
|
|
3356
|
-
resolve:
|
|
3545
|
+
resolve: resolve3,
|
|
3357
3546
|
createdAt: Date.now()
|
|
3358
3547
|
});
|
|
3359
3548
|
});
|
|
@@ -3648,14 +3837,14 @@ function touchSessionActivity(sessionManager, relay, sessionId, now = Date.now()
|
|
|
3648
3837
|
|
|
3649
3838
|
// src/serve/service-files.ts
|
|
3650
3839
|
import { execSync } from "child_process";
|
|
3651
|
-
import { existsSync as
|
|
3840
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
|
|
3652
3841
|
import { hostname } from "os";
|
|
3653
3842
|
import { connect as connect2 } from "net";
|
|
3654
3843
|
function tryConnectSocket(sockPath) {
|
|
3655
|
-
return new Promise((
|
|
3844
|
+
return new Promise((resolve3) => {
|
|
3656
3845
|
const s = connect2(sockPath);
|
|
3657
|
-
s.on("connect", () =>
|
|
3658
|
-
s.on("error", () =>
|
|
3846
|
+
s.on("connect", () => resolve3(s));
|
|
3847
|
+
s.on("error", () => resolve3(null));
|
|
3659
3848
|
});
|
|
3660
3849
|
}
|
|
3661
3850
|
function isProcessAlive(pid) {
|
|
@@ -3667,7 +3856,7 @@ function isProcessAlive(pid) {
|
|
|
3667
3856
|
}
|
|
3668
3857
|
}
|
|
3669
3858
|
async function cleanupStaleResources() {
|
|
3670
|
-
if (
|
|
3859
|
+
if (existsSync6(SOCK_PATH)) {
|
|
3671
3860
|
const existing = await tryConnectSocket(SOCK_PATH);
|
|
3672
3861
|
if (existing) {
|
|
3673
3862
|
existing.destroy();
|
|
@@ -3679,8 +3868,8 @@ async function cleanupStaleResources() {
|
|
|
3679
3868
|
unlinkSync2(SOCK_PATH);
|
|
3680
3869
|
serviceLogger.info("Removed stale socket file");
|
|
3681
3870
|
}
|
|
3682
|
-
if (
|
|
3683
|
-
const pidStr =
|
|
3871
|
+
if (existsSync6(PID_PATH)) {
|
|
3872
|
+
const pidStr = readFileSync7(PID_PATH, "utf-8").trim();
|
|
3684
3873
|
const pid = parseInt(pidStr, 10);
|
|
3685
3874
|
if (!isNaN(pid) && isProcessAlive(pid)) {
|
|
3686
3875
|
const msg = `Another service is already running with PID ${pid}`;
|
|
@@ -4055,7 +4244,7 @@ function handleTerminalConnection(socket, deps) {
|
|
|
4055
4244
|
|
|
4056
4245
|
// src/serve/hook-registry.ts
|
|
4057
4246
|
import { createHash, randomBytes } from "crypto";
|
|
4058
|
-
import { existsSync as
|
|
4247
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync8, renameSync as renameSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
4059
4248
|
import { dirname as dirname4 } from "path";
|
|
4060
4249
|
import { z } from "zod";
|
|
4061
4250
|
var PersistedHookSessionBindingSchema = z.object({
|
|
@@ -4116,10 +4305,10 @@ var HookRegistry = class {
|
|
|
4116
4305
|
}
|
|
4117
4306
|
}
|
|
4118
4307
|
load() {
|
|
4119
|
-
if (!this.persistPath || !
|
|
4308
|
+
if (!this.persistPath || !existsSync7(this.persistPath)) return;
|
|
4120
4309
|
try {
|
|
4121
4310
|
const parsed = PersistedHookRegistrySchema.parse(
|
|
4122
|
-
JSON.parse(
|
|
4311
|
+
JSON.parse(readFileSync8(this.persistPath, "utf8"))
|
|
4123
4312
|
);
|
|
4124
4313
|
this.bindingsBySession.clear();
|
|
4125
4314
|
for (const binding of parsed.bindings) {
|
|
@@ -4189,7 +4378,7 @@ var HookServer = class {
|
|
|
4189
4378
|
this.writeJson(res, 500, { error: "internal_error" });
|
|
4190
4379
|
});
|
|
4191
4380
|
});
|
|
4192
|
-
return new Promise((
|
|
4381
|
+
return new Promise((resolve3, reject) => {
|
|
4193
4382
|
const onError = (err) => {
|
|
4194
4383
|
this.server?.off("listening", onListening);
|
|
4195
4384
|
reject(err);
|
|
@@ -4197,7 +4386,7 @@ var HookServer = class {
|
|
|
4197
4386
|
const onListening = () => {
|
|
4198
4387
|
this.server?.off("error", onError);
|
|
4199
4388
|
serviceLogger.info({ host: this.host, port: this.options.port }, "Hook server listening");
|
|
4200
|
-
|
|
4389
|
+
resolve3();
|
|
4201
4390
|
};
|
|
4202
4391
|
this.server.once("error", onError);
|
|
4203
4392
|
this.server.once("listening", onListening);
|
|
@@ -4208,8 +4397,8 @@ var HookServer = class {
|
|
|
4208
4397
|
if (!this.server) return Promise.resolve();
|
|
4209
4398
|
const server = this.server;
|
|
4210
4399
|
this.server = null;
|
|
4211
|
-
return new Promise((
|
|
4212
|
-
server.close((err) => err ? reject(err) :
|
|
4400
|
+
return new Promise((resolve3, reject) => {
|
|
4401
|
+
server.close((err) => err ? reject(err) : resolve3());
|
|
4213
4402
|
});
|
|
4214
4403
|
}
|
|
4215
4404
|
getListeningPort() {
|
|
@@ -4323,7 +4512,7 @@ var HookServer = class {
|
|
|
4323
4512
|
this.writeJson(res, 200, payload);
|
|
4324
4513
|
}
|
|
4325
4514
|
readBody(req) {
|
|
4326
|
-
return new Promise((
|
|
4515
|
+
return new Promise((resolve3, reject) => {
|
|
4327
4516
|
let body = "";
|
|
4328
4517
|
let size = 0;
|
|
4329
4518
|
req.setEncoding("utf8");
|
|
@@ -4336,7 +4525,7 @@ var HookServer = class {
|
|
|
4336
4525
|
}
|
|
4337
4526
|
body += chunk;
|
|
4338
4527
|
});
|
|
4339
|
-
req.on("end", () =>
|
|
4528
|
+
req.on("end", () => resolve3(body));
|
|
4340
4529
|
req.on("error", reject);
|
|
4341
4530
|
});
|
|
4342
4531
|
}
|
|
@@ -4484,6 +4673,7 @@ async function startService(options) {
|
|
|
4484
4673
|
let proxyConfig = loadConfig({ relayName: options?.relayName });
|
|
4485
4674
|
const getProviderEnv = () => buildProviderEnv(proxyConfig, process.env);
|
|
4486
4675
|
const getAgentCliSuggestions = () => proxyConfig.agentCliSuggestions;
|
|
4676
|
+
const getPreviewRoots = () => proxyConfig.previewRoots;
|
|
4487
4677
|
const setAgentCliPath = (provider, path) => {
|
|
4488
4678
|
const field = provider === "claude" ? "claudeBin" : "codexBin";
|
|
4489
4679
|
const existing = proxyConfig.agentCliSuggestions[provider] ?? [];
|
|
@@ -4611,7 +4801,8 @@ async function startService(options) {
|
|
|
4611
4801
|
agentStatusRegistry,
|
|
4612
4802
|
getProviderEnv,
|
|
4613
4803
|
getAgentCliSuggestions,
|
|
4614
|
-
setAgentCliPath
|
|
4804
|
+
setAgentCliPath,
|
|
4805
|
+
getPreviewRoots
|
|
4615
4806
|
});
|
|
4616
4807
|
relayConnection.on("message", (msg) => relayRouter.handle(msg));
|
|
4617
4808
|
relayConnection.on("connected", () => {
|