@spencer-kit/coder-studio 0.4.3 → 0.4.5
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/CHANGELOG.md +14 -0
- package/dist/esm/bin.mjs +1415 -463
- package/dist/esm/bin.mjs.map +4 -4
- package/dist/esm/server-runner.mjs +1377 -425
- package/dist/esm/server-runner.mjs.map +4 -4
- package/dist/esm/update-worker.mjs +118 -3
- package/dist/esm/update-worker.mjs.map +2 -2
- package/dist/web/assets/components-BGEBLvHB.css +1 -0
- package/dist/web/assets/components-CFZItGai.js +109 -0
- package/dist/web/assets/components-CFZItGai.js.map +1 -0
- package/dist/web/assets/main-ShJ51B7m.js +2 -0
- package/dist/web/assets/main-ShJ51B7m.js.map +1 -0
- package/dist/web/assets/ui-preview-CKpnmBtY.js +17 -0
- package/dist/web/assets/ui-preview-CKpnmBtY.js.map +1 -0
- package/dist/web/index.html +3 -3
- package/dist/web/ui-preview.html +3 -3
- package/package.json +1 -1
- package/src/update-worker.test.ts +53 -12
- package/src/update-worker.ts +158 -3
- package/dist/web/assets/components-AKM1pxhf.css +0 -1
- package/dist/web/assets/components-BZf_jLGv.js +0 -110
- package/dist/web/assets/components-BZf_jLGv.js.map +0 -1
- package/dist/web/assets/main-CEv6hML1.js +0 -2
- package/dist/web/assets/main-CEv6hML1.js.map +0 -1
- package/dist/web/assets/ui-preview-Cd3euuid.js +0 -17
- package/dist/web/assets/ui-preview-Cd3euuid.js.map +0 -1
package/dist/esm/bin.mjs
CHANGED
|
@@ -953,10 +953,10 @@ function resolveSpawnArgv(argv, deps = {}) {
|
|
|
953
953
|
}
|
|
954
954
|
const restArgs = argv.slice(1);
|
|
955
955
|
const readFileSync10 = deps.readFileSync ?? ((file) => fs2.readFileSync(file, "utf8"));
|
|
956
|
-
const
|
|
956
|
+
const existsSync16 = deps.existsSync ?? fs2.existsSync;
|
|
957
957
|
const pathEnv = deps.pathEnv ?? process.env.Path ?? process.env.PATH ?? "";
|
|
958
958
|
const pathExt = deps.pathExt ?? process.env.PATHEXT ?? DEFAULT_PATHEXT;
|
|
959
|
-
const resolved = resolveExecutablePath(command, pathEnv, pathExt,
|
|
959
|
+
const resolved = resolveExecutablePath(command, pathEnv, pathExt, existsSync16);
|
|
960
960
|
if (!resolved) {
|
|
961
961
|
return [...argv];
|
|
962
962
|
}
|
|
@@ -1005,17 +1005,17 @@ function expandShimVars(value, dp0Dir) {
|
|
|
1005
1005
|
function parsePathExt(pathExt) {
|
|
1006
1006
|
return pathExt.split(";").map((entry) => entry.trim().toLowerCase()).filter((entry) => entry.length > 0);
|
|
1007
1007
|
}
|
|
1008
|
-
function resolveExecutablePath(command, pathEnv, pathExt,
|
|
1008
|
+
function resolveExecutablePath(command, pathEnv, pathExt, existsSync16) {
|
|
1009
1009
|
const hasExt = path3.win32.extname(command).length > 0;
|
|
1010
1010
|
const extensions = parsePathExt(pathExt);
|
|
1011
1011
|
if (path3.win32.isAbsolute(command)) {
|
|
1012
|
-
if (
|
|
1012
|
+
if (existsSync16(command)) {
|
|
1013
1013
|
return command;
|
|
1014
1014
|
}
|
|
1015
1015
|
if (!hasExt) {
|
|
1016
1016
|
for (const ext of extensions) {
|
|
1017
1017
|
const candidate = command + ext;
|
|
1018
|
-
if (
|
|
1018
|
+
if (existsSync16(candidate)) {
|
|
1019
1019
|
return candidate;
|
|
1020
1020
|
}
|
|
1021
1021
|
}
|
|
@@ -1026,14 +1026,14 @@ function resolveExecutablePath(command, pathEnv, pathExt, existsSync15) {
|
|
|
1026
1026
|
for (const dir of dirs) {
|
|
1027
1027
|
if (hasExt) {
|
|
1028
1028
|
const candidate = path3.win32.join(dir, command);
|
|
1029
|
-
if (
|
|
1029
|
+
if (existsSync16(candidate)) {
|
|
1030
1030
|
return candidate;
|
|
1031
1031
|
}
|
|
1032
1032
|
continue;
|
|
1033
1033
|
}
|
|
1034
1034
|
for (const ext of extensions) {
|
|
1035
1035
|
const candidate = path3.win32.join(dir, command + ext);
|
|
1036
|
-
if (
|
|
1036
|
+
if (existsSync16(candidate)) {
|
|
1037
1037
|
return candidate;
|
|
1038
1038
|
}
|
|
1039
1039
|
}
|
|
@@ -1119,6 +1119,281 @@ var init_session_store = __esm({
|
|
|
1119
1119
|
}
|
|
1120
1120
|
});
|
|
1121
1121
|
|
|
1122
|
+
// packages/server/src/uploads/paths.ts
|
|
1123
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
1124
|
+
import { lstat, mkdir } from "node:fs/promises";
|
|
1125
|
+
import path4 from "node:path";
|
|
1126
|
+
function sanitizeOriginalName(input2) {
|
|
1127
|
+
let sanitized = "";
|
|
1128
|
+
for (const char of input2.trim()) {
|
|
1129
|
+
sanitized += KEEP_FILENAME_CHAR.test(char) ? char : "_";
|
|
1130
|
+
}
|
|
1131
|
+
sanitized = sanitized.replace(/^\.+/, "");
|
|
1132
|
+
if (sanitized.length === 0 || /^[_\s]*$/.test(sanitized)) {
|
|
1133
|
+
return "file";
|
|
1134
|
+
}
|
|
1135
|
+
if (sanitized.length <= MAX_FILENAME_LENGTH) {
|
|
1136
|
+
return sanitized;
|
|
1137
|
+
}
|
|
1138
|
+
const lastDot = sanitized.lastIndexOf(".");
|
|
1139
|
+
if (lastDot > 0 && sanitized.length - lastDot <= 16) {
|
|
1140
|
+
const ext = sanitized.slice(lastDot);
|
|
1141
|
+
const stem = sanitized.slice(0, MAX_FILENAME_LENGTH - ext.length);
|
|
1142
|
+
return stem + ext;
|
|
1143
|
+
}
|
|
1144
|
+
return sanitized.slice(0, MAX_FILENAME_LENGTH);
|
|
1145
|
+
}
|
|
1146
|
+
function validateWorkspaceId(id) {
|
|
1147
|
+
if (!WORKSPACE_ID_RE.test(id)) {
|
|
1148
|
+
throw new Error(`invalid workspace id: ${JSON.stringify(id)}`);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
async function assertDirectorySegmentSafe(segmentPath) {
|
|
1152
|
+
const info = await lstat(segmentPath);
|
|
1153
|
+
if (info.isSymbolicLink()) {
|
|
1154
|
+
throw new Error(`symlinked upload path segment is not allowed: ${segmentPath}`);
|
|
1155
|
+
}
|
|
1156
|
+
if (!info.isDirectory()) {
|
|
1157
|
+
throw new Error(`upload path segment is not a directory: ${segmentPath}`);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
async function ensureSafeUploadDir(rootDir, targetDir2) {
|
|
1161
|
+
const resolvedRoot = path4.resolve(rootDir);
|
|
1162
|
+
const resolvedTarget = path4.resolve(targetDir2);
|
|
1163
|
+
if (resolvedTarget !== resolvedRoot && !resolvedTarget.startsWith(`${resolvedRoot}${path4.sep}`)) {
|
|
1164
|
+
throw new Error(`target dir escaped uploads root: ${resolvedTarget}`);
|
|
1165
|
+
}
|
|
1166
|
+
try {
|
|
1167
|
+
await assertDirectorySegmentSafe(resolvedRoot);
|
|
1168
|
+
} catch (error) {
|
|
1169
|
+
const code = error.code;
|
|
1170
|
+
if (code !== "ENOENT") {
|
|
1171
|
+
throw error;
|
|
1172
|
+
}
|
|
1173
|
+
await mkdir(resolvedRoot, { recursive: true });
|
|
1174
|
+
await assertDirectorySegmentSafe(resolvedRoot);
|
|
1175
|
+
}
|
|
1176
|
+
const relative5 = path4.relative(resolvedRoot, resolvedTarget);
|
|
1177
|
+
if (!relative5) {
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
let current = resolvedRoot;
|
|
1181
|
+
for (const segment of relative5.split(path4.sep)) {
|
|
1182
|
+
current = path4.join(current, segment);
|
|
1183
|
+
try {
|
|
1184
|
+
await assertDirectorySegmentSafe(current);
|
|
1185
|
+
continue;
|
|
1186
|
+
} catch (error) {
|
|
1187
|
+
const code = error.code;
|
|
1188
|
+
if (code !== "ENOENT") {
|
|
1189
|
+
throw error;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
try {
|
|
1193
|
+
await mkdir(current);
|
|
1194
|
+
} catch (error) {
|
|
1195
|
+
const code = error.code;
|
|
1196
|
+
if (code !== "EEXIST") {
|
|
1197
|
+
throw error;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
await assertDirectorySegmentSafe(current);
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
function generateBucketPath(input2) {
|
|
1204
|
+
validateWorkspaceId(input2.workspaceId);
|
|
1205
|
+
const now = input2.now ?? /* @__PURE__ */ new Date();
|
|
1206
|
+
const dateStr = now.toISOString().slice(0, 10);
|
|
1207
|
+
const dir = path4.join(input2.uploadsDir, input2.workspaceId, dateStr);
|
|
1208
|
+
const sanitizedName = sanitizeOriginalName(input2.originalName);
|
|
1209
|
+
const uuid8 = randomUUID2().replace(/-/g, "").slice(0, 8);
|
|
1210
|
+
const absolutePath = path4.resolve(dir, `${uuid8}-${sanitizedName}`);
|
|
1211
|
+
const uploadsRoot = `${path4.resolve(input2.uploadsDir)}${path4.sep}`;
|
|
1212
|
+
if (!absolutePath.startsWith(uploadsRoot)) {
|
|
1213
|
+
throw new Error(`generated upload path escaped uploads root: ${absolutePath}`);
|
|
1214
|
+
}
|
|
1215
|
+
return {
|
|
1216
|
+
dir,
|
|
1217
|
+
absolutePath,
|
|
1218
|
+
uuid8,
|
|
1219
|
+
sanitizedName
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
var MAX_FILENAME_LENGTH, KEEP_FILENAME_CHAR, WORKSPACE_ID_RE;
|
|
1223
|
+
var init_paths = __esm({
|
|
1224
|
+
"packages/server/src/uploads/paths.ts"() {
|
|
1225
|
+
"use strict";
|
|
1226
|
+
MAX_FILENAME_LENGTH = 64;
|
|
1227
|
+
KEEP_FILENAME_CHAR = /[a-zA-Z0-9._一-鿿 \-]/;
|
|
1228
|
+
WORKSPACE_ID_RE = /^[a-zA-Z0-9_-]+$/;
|
|
1229
|
+
}
|
|
1230
|
+
});
|
|
1231
|
+
|
|
1232
|
+
// packages/server/src/routes/appearance-assets.ts
|
|
1233
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
1234
|
+
import { createReadStream, createWriteStream } from "node:fs";
|
|
1235
|
+
import { rm, stat } from "node:fs/promises";
|
|
1236
|
+
import { isAbsolute, join as join2, relative, resolve as resolve2, sep } from "node:path";
|
|
1237
|
+
import { pipeline } from "node:stream/promises";
|
|
1238
|
+
function isAllowedAppearanceMime(mime2) {
|
|
1239
|
+
return ALLOWED_APPEARANCE_MIME_TYPES.has(mime2);
|
|
1240
|
+
}
|
|
1241
|
+
function isPathInsideRoot(rootPath, targetPath) {
|
|
1242
|
+
const rel = relative(rootPath, targetPath);
|
|
1243
|
+
return rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
|
|
1244
|
+
}
|
|
1245
|
+
function resolveAssetStoragePath(uploadsDir, storagePath) {
|
|
1246
|
+
const resolvedUploadsDir = resolve2(uploadsDir);
|
|
1247
|
+
const resolvedStoragePath = resolve2(storagePath);
|
|
1248
|
+
return isPathInsideRoot(resolvedUploadsDir, resolvedStoragePath) ? resolvedStoragePath : null;
|
|
1249
|
+
}
|
|
1250
|
+
async function cleanupWrittenFile(filePath) {
|
|
1251
|
+
if (!filePath) {
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
await rm(filePath, { force: true });
|
|
1255
|
+
}
|
|
1256
|
+
async function rejectAndCleanup(reply, filePath, statusCode, error) {
|
|
1257
|
+
await cleanupWrittenFile(filePath);
|
|
1258
|
+
return reply.status(statusCode).send({ ok: false, error });
|
|
1259
|
+
}
|
|
1260
|
+
function registerAppearanceAssetsRoutes(app, deps) {
|
|
1261
|
+
app.post("/api/appearance-assets", async (request, reply) => {
|
|
1262
|
+
if (!request.isMultipart()) {
|
|
1263
|
+
return reply.status(400).send({ ok: false, error: "expected_multipart" });
|
|
1264
|
+
}
|
|
1265
|
+
let writtenPath;
|
|
1266
|
+
let pendingRecord;
|
|
1267
|
+
try {
|
|
1268
|
+
const parts = request.parts();
|
|
1269
|
+
for await (const part of parts) {
|
|
1270
|
+
if (part.type !== "file") {
|
|
1271
|
+
continue;
|
|
1272
|
+
}
|
|
1273
|
+
if (part.fieldname !== "file") {
|
|
1274
|
+
part.file.resume();
|
|
1275
|
+
return rejectAndCleanup(reply, writtenPath, 400, "file_required");
|
|
1276
|
+
}
|
|
1277
|
+
if (pendingRecord) {
|
|
1278
|
+
part.file.resume();
|
|
1279
|
+
return rejectAndCleanup(reply, writtenPath, 400, "too_many_files");
|
|
1280
|
+
}
|
|
1281
|
+
if (!isAllowedAppearanceMime(part.mimetype)) {
|
|
1282
|
+
part.file.resume();
|
|
1283
|
+
return rejectAndCleanup(reply, writtenPath, 400, "invalid_file_type");
|
|
1284
|
+
}
|
|
1285
|
+
const assetId = randomUUID3();
|
|
1286
|
+
const createdAt = Date.now();
|
|
1287
|
+
const dateStr = new Date(createdAt).toISOString().slice(0, 10);
|
|
1288
|
+
const safeName = sanitizeOriginalName(part.filename || "file");
|
|
1289
|
+
const fileName = part.filename?.trim() ? part.filename.trim() : safeName;
|
|
1290
|
+
const dir = join2(deps.uploadsDir, APPEARANCE_ASSET_BUCKET, dateStr);
|
|
1291
|
+
const storagePath = join2(dir, `${assetId}-${safeName}`);
|
|
1292
|
+
try {
|
|
1293
|
+
await ensureSafeUploadDir(deps.uploadsDir, dir);
|
|
1294
|
+
await pipeline(part.file, createWriteStream(storagePath));
|
|
1295
|
+
} catch (error) {
|
|
1296
|
+
request.log.warn({ err: error }, "appearance asset write failed");
|
|
1297
|
+
return rejectAndCleanup(reply, storagePath, 500, "write_failed");
|
|
1298
|
+
}
|
|
1299
|
+
if (part.file.truncated) {
|
|
1300
|
+
return rejectAndCleanup(reply, storagePath, 413, "file_too_large");
|
|
1301
|
+
}
|
|
1302
|
+
let fileSize;
|
|
1303
|
+
try {
|
|
1304
|
+
const fileStat = await stat(storagePath);
|
|
1305
|
+
fileSize = fileStat.size;
|
|
1306
|
+
} catch (error) {
|
|
1307
|
+
request.log.warn({ err: error }, "appearance asset stat failed");
|
|
1308
|
+
return rejectAndCleanup(reply, storagePath, 500, "write_failed");
|
|
1309
|
+
}
|
|
1310
|
+
writtenPath = storagePath;
|
|
1311
|
+
pendingRecord = {
|
|
1312
|
+
id: assetId,
|
|
1313
|
+
fileName,
|
|
1314
|
+
mime: part.mimetype,
|
|
1315
|
+
size: fileSize,
|
|
1316
|
+
storagePath,
|
|
1317
|
+
createdAt
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
} catch (error) {
|
|
1321
|
+
if (error.code === "FST_REQ_FILE_TOO_LARGE") {
|
|
1322
|
+
return rejectAndCleanup(reply, writtenPath, 413, "file_too_large");
|
|
1323
|
+
}
|
|
1324
|
+
request.log.warn({ err: error }, "appearance asset parse failed");
|
|
1325
|
+
return rejectAndCleanup(reply, writtenPath, 400, "parse_failed");
|
|
1326
|
+
}
|
|
1327
|
+
if (!pendingRecord) {
|
|
1328
|
+
return rejectAndCleanup(reply, writtenPath, 400, "file_required");
|
|
1329
|
+
}
|
|
1330
|
+
try {
|
|
1331
|
+
deps.repo.set(pendingRecord);
|
|
1332
|
+
} catch (error) {
|
|
1333
|
+
request.log.warn({ err: error }, "appearance asset metadata write failed");
|
|
1334
|
+
return rejectAndCleanup(reply, writtenPath, 500, "write_failed");
|
|
1335
|
+
}
|
|
1336
|
+
return reply.send({
|
|
1337
|
+
ok: true,
|
|
1338
|
+
asset: {
|
|
1339
|
+
assetId: pendingRecord.id,
|
|
1340
|
+
url: `/api/appearance-assets/${pendingRecord.id}`,
|
|
1341
|
+
mime: pendingRecord.mime,
|
|
1342
|
+
size: pendingRecord.size
|
|
1343
|
+
}
|
|
1344
|
+
});
|
|
1345
|
+
});
|
|
1346
|
+
app.get(
|
|
1347
|
+
"/api/appearance-assets/:assetId",
|
|
1348
|
+
async (request, reply) => {
|
|
1349
|
+
const record = deps.repo.get(request.params.assetId);
|
|
1350
|
+
if (!record) {
|
|
1351
|
+
return reply.status(404).send({ ok: false, error: "not_found" });
|
|
1352
|
+
}
|
|
1353
|
+
const storagePath = resolveAssetStoragePath(deps.uploadsDir, record.storagePath);
|
|
1354
|
+
if (!storagePath) {
|
|
1355
|
+
return reply.status(404).send({ ok: false, error: "not_found" });
|
|
1356
|
+
}
|
|
1357
|
+
let fileSize;
|
|
1358
|
+
try {
|
|
1359
|
+
const fileStat = await stat(storagePath);
|
|
1360
|
+
if (!fileStat.isFile()) {
|
|
1361
|
+
return reply.status(404).send({ ok: false, error: "not_found" });
|
|
1362
|
+
}
|
|
1363
|
+
fileSize = fileStat.size;
|
|
1364
|
+
} catch {
|
|
1365
|
+
return reply.status(404).send({ ok: false, error: "not_found" });
|
|
1366
|
+
}
|
|
1367
|
+
reply.header("Content-Type", record.mime).header("Content-Length", String(fileSize)).header("Cache-Control", "no-store").header("X-Content-Type-Options", "nosniff");
|
|
1368
|
+
return reply.send(createReadStream(storagePath));
|
|
1369
|
+
}
|
|
1370
|
+
);
|
|
1371
|
+
app.delete(
|
|
1372
|
+
"/api/appearance-assets/:assetId",
|
|
1373
|
+
async (request, reply) => {
|
|
1374
|
+
const record = deps.repo.get(request.params.assetId);
|
|
1375
|
+
if (!record) {
|
|
1376
|
+
return reply.status(404).send({ ok: false, error: "not_found" });
|
|
1377
|
+
}
|
|
1378
|
+
const storagePath = resolveAssetStoragePath(deps.uploadsDir, record.storagePath);
|
|
1379
|
+
if (storagePath) {
|
|
1380
|
+
await rm(storagePath, { force: true });
|
|
1381
|
+
}
|
|
1382
|
+
deps.repo.delete(request.params.assetId);
|
|
1383
|
+
return reply.send({ ok: true });
|
|
1384
|
+
}
|
|
1385
|
+
);
|
|
1386
|
+
}
|
|
1387
|
+
var ALLOWED_APPEARANCE_MIME_TYPES, APPEARANCE_ASSET_BUCKET;
|
|
1388
|
+
var init_appearance_assets = __esm({
|
|
1389
|
+
"packages/server/src/routes/appearance-assets.ts"() {
|
|
1390
|
+
"use strict";
|
|
1391
|
+
init_paths();
|
|
1392
|
+
ALLOWED_APPEARANCE_MIME_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/webp"]);
|
|
1393
|
+
APPEARANCE_ASSET_BUCKET = "appearance/default";
|
|
1394
|
+
}
|
|
1395
|
+
});
|
|
1396
|
+
|
|
1122
1397
|
// packages/server/src/fs/image.ts
|
|
1123
1398
|
import { extname } from "path";
|
|
1124
1399
|
function getImageTypeInfo(filePath) {
|
|
@@ -1143,8 +1418,8 @@ var init_image = __esm({
|
|
|
1143
1418
|
});
|
|
1144
1419
|
|
|
1145
1420
|
// packages/server/src/fs/path-safety.ts
|
|
1146
|
-
import
|
|
1147
|
-
function
|
|
1421
|
+
import path5 from "node:path";
|
|
1422
|
+
function isPathInsideRoot2(rootPath, targetPath, pathApi = path5) {
|
|
1148
1423
|
const rel = pathApi.relative(rootPath, targetPath);
|
|
1149
1424
|
return rel !== ".." && !rel.startsWith(`..${pathApi.sep}`) && !pathApi.isAbsolute(rel);
|
|
1150
1425
|
}
|
|
@@ -1155,19 +1430,19 @@ var init_path_safety = __esm({
|
|
|
1155
1430
|
});
|
|
1156
1431
|
|
|
1157
1432
|
// packages/server/src/fs/file-io.ts
|
|
1158
|
-
import * as
|
|
1433
|
+
import * as path6 from "node:path";
|
|
1159
1434
|
import { createHash } from "crypto";
|
|
1160
1435
|
import {
|
|
1161
1436
|
readFile as fsReadFile,
|
|
1162
1437
|
rename as fsRename,
|
|
1163
1438
|
writeFile as fsWriteFile,
|
|
1164
|
-
mkdir,
|
|
1165
|
-
rm,
|
|
1166
|
-
stat
|
|
1439
|
+
mkdir as mkdir2,
|
|
1440
|
+
rm as rm2,
|
|
1441
|
+
stat as stat2
|
|
1167
1442
|
} from "fs/promises";
|
|
1168
1443
|
async function statSafe(path14) {
|
|
1169
1444
|
try {
|
|
1170
|
-
return await
|
|
1445
|
+
return await stat2(path14);
|
|
1171
1446
|
} catch {
|
|
1172
1447
|
return null;
|
|
1173
1448
|
}
|
|
@@ -1178,7 +1453,7 @@ async function createFile(rootPath, relPath) {
|
|
|
1178
1453
|
if (existing) {
|
|
1179
1454
|
throw { code: "already_exists", message: "File already exists" };
|
|
1180
1455
|
}
|
|
1181
|
-
await
|
|
1456
|
+
await mkdir2(path6.dirname(abs), { recursive: true });
|
|
1182
1457
|
await fsWriteFile(abs, "", "utf-8");
|
|
1183
1458
|
}
|
|
1184
1459
|
async function createDirectory(rootPath, relPath) {
|
|
@@ -1187,7 +1462,7 @@ async function createDirectory(rootPath, relPath) {
|
|
|
1187
1462
|
if (existing) {
|
|
1188
1463
|
throw { code: "already_exists", message: "Directory already exists" };
|
|
1189
1464
|
}
|
|
1190
|
-
await
|
|
1465
|
+
await mkdir2(abs, { recursive: true });
|
|
1191
1466
|
}
|
|
1192
1467
|
async function deleteEntry(rootPath, relPath) {
|
|
1193
1468
|
const abs = resolveSafe(rootPath, relPath);
|
|
@@ -1195,15 +1470,15 @@ async function deleteEntry(rootPath, relPath) {
|
|
|
1195
1470
|
if (!existing) {
|
|
1196
1471
|
throw { code: "not_found", message: "Target not found" };
|
|
1197
1472
|
}
|
|
1198
|
-
await
|
|
1473
|
+
await rm2(abs, { recursive: true });
|
|
1199
1474
|
}
|
|
1200
1475
|
async function renameEntry(rootPath, fromPath, toPath) {
|
|
1201
1476
|
const fromAbs = resolveSafe(rootPath, fromPath);
|
|
1202
1477
|
const toAbs = resolveSafe(rootPath, toPath);
|
|
1203
1478
|
const source = await statSafe(fromAbs);
|
|
1204
1479
|
const target = await statSafe(toAbs);
|
|
1205
|
-
const fromParent =
|
|
1206
|
-
const toParent =
|
|
1480
|
+
const fromParent = path6.dirname(fromAbs);
|
|
1481
|
+
const toParent = path6.dirname(toAbs);
|
|
1207
1482
|
if (!source) {
|
|
1208
1483
|
throw { code: "not_found", message: "Source not found" };
|
|
1209
1484
|
}
|
|
@@ -1218,10 +1493,10 @@ async function renameEntry(rootPath, fromPath, toPath) {
|
|
|
1218
1493
|
}
|
|
1219
1494
|
await fsRename(fromAbs, toAbs);
|
|
1220
1495
|
}
|
|
1221
|
-
function resolveSafe(root, relPath, pathApi =
|
|
1496
|
+
function resolveSafe(root, relPath, pathApi = path6) {
|
|
1222
1497
|
const absRoot = pathApi.resolve(root);
|
|
1223
1498
|
const abs = pathApi.resolve(absRoot, relPath);
|
|
1224
|
-
if (!
|
|
1499
|
+
if (!isPathInsideRoot2(absRoot, abs, pathApi)) {
|
|
1225
1500
|
throw { code: "path_escape", message: "Path escapes workspace root" };
|
|
1226
1501
|
}
|
|
1227
1502
|
return abs;
|
|
@@ -1269,7 +1544,7 @@ async function writeFile(rootPath, relPath, content, baseHash) {
|
|
|
1269
1544
|
};
|
|
1270
1545
|
}
|
|
1271
1546
|
}
|
|
1272
|
-
await
|
|
1547
|
+
await mkdir2(path6.dirname(abs), { recursive: true });
|
|
1273
1548
|
await fsWriteFile(abs, content, "utf-8");
|
|
1274
1549
|
const newHash = createHash("sha256").update(content).digest("hex");
|
|
1275
1550
|
return { newHash };
|
|
@@ -1433,11 +1708,11 @@ var init_status_parser = __esm({
|
|
|
1433
1708
|
|
|
1434
1709
|
// packages/server/src/git/cli.ts
|
|
1435
1710
|
import { execFile } from "child_process";
|
|
1436
|
-
import { mkdir as
|
|
1711
|
+
import { mkdir as mkdir3, mkdtemp, rm as rm3, writeFile as writeFile2 } from "fs/promises";
|
|
1437
1712
|
import os2 from "os";
|
|
1438
|
-
import
|
|
1713
|
+
import path7 from "path";
|
|
1439
1714
|
async function runGit(cwd, args, options = {}) {
|
|
1440
|
-
return new Promise((
|
|
1715
|
+
return new Promise((resolve6, reject) => {
|
|
1441
1716
|
const gitArgs = [
|
|
1442
1717
|
...options.config?.flatMap(([key, value]) => ["-c", `${key}=${value}`]) ?? [],
|
|
1443
1718
|
...args
|
|
@@ -1464,7 +1739,7 @@ async function runGit(cwd, args, options = {}) {
|
|
|
1464
1739
|
if (err) {
|
|
1465
1740
|
reject(new GitError(err.message, stderr));
|
|
1466
1741
|
} else {
|
|
1467
|
-
|
|
1742
|
+
resolve6({ stdout, stderr });
|
|
1468
1743
|
}
|
|
1469
1744
|
}
|
|
1470
1745
|
);
|
|
@@ -1923,10 +2198,10 @@ async function prepareGitAuthExecution(auth, remoteMetadata) {
|
|
|
1923
2198
|
}
|
|
1924
2199
|
};
|
|
1925
2200
|
}
|
|
1926
|
-
const tempDir = await mkdtemp(
|
|
1927
|
-
const hooksDir =
|
|
1928
|
-
const askPassPath =
|
|
1929
|
-
await
|
|
2201
|
+
const tempDir = await mkdtemp(path7.join(os2.tmpdir(), "coder-studio-git-auth-"));
|
|
2202
|
+
const hooksDir = path7.join(tempDir, "hooks");
|
|
2203
|
+
const askPassPath = path7.join(tempDir, "askpass.sh");
|
|
2204
|
+
await mkdir3(hooksDir, { recursive: true, mode: 448 });
|
|
1930
2205
|
await writeFile2(
|
|
1931
2206
|
askPassPath,
|
|
1932
2207
|
[
|
|
@@ -1955,7 +2230,7 @@ async function prepareGitAuthExecution(auth, remoteMetadata) {
|
|
|
1955
2230
|
["credential.username", auth.username]
|
|
1956
2231
|
],
|
|
1957
2232
|
cleanup: async () => {
|
|
1958
|
-
await
|
|
2233
|
+
await rm3(tempDir, { recursive: true, force: true });
|
|
1959
2234
|
}
|
|
1960
2235
|
};
|
|
1961
2236
|
}
|
|
@@ -2203,8 +2478,8 @@ var init_image_revision = __esm({
|
|
|
2203
2478
|
});
|
|
2204
2479
|
|
|
2205
2480
|
// packages/server/src/routes/file-asset.ts
|
|
2206
|
-
import { createReadStream } from "fs";
|
|
2207
|
-
import { realpath, stat as
|
|
2481
|
+
import { createReadStream as createReadStream2 } from "fs";
|
|
2482
|
+
import { realpath, stat as stat3 } from "fs/promises";
|
|
2208
2483
|
function registerFileAssetRoutes(app, deps) {
|
|
2209
2484
|
app.get(
|
|
2210
2485
|
"/api/file",
|
|
@@ -2248,7 +2523,7 @@ function registerFileAssetRoutes(app, deps) {
|
|
|
2248
2523
|
realpath(workspace.path),
|
|
2249
2524
|
realpath(absPath)
|
|
2250
2525
|
]);
|
|
2251
|
-
if (!
|
|
2526
|
+
if (!isPathInsideRoot2(realWorkspacePath, realAssetPath)) {
|
|
2252
2527
|
return reply.status(400).send({ ok: false, error: "path_escape" });
|
|
2253
2528
|
}
|
|
2254
2529
|
} catch {
|
|
@@ -2256,7 +2531,7 @@ function registerFileAssetRoutes(app, deps) {
|
|
|
2256
2531
|
}
|
|
2257
2532
|
let fileSize;
|
|
2258
2533
|
try {
|
|
2259
|
-
const stats = await
|
|
2534
|
+
const stats = await stat3(absPath);
|
|
2260
2535
|
if (!stats.isFile()) {
|
|
2261
2536
|
return reply.status(404).send({ ok: false, error: "not_a_file" });
|
|
2262
2537
|
}
|
|
@@ -2265,7 +2540,7 @@ function registerFileAssetRoutes(app, deps) {
|
|
|
2265
2540
|
return reply.status(404).send({ ok: false, error: "not_found" });
|
|
2266
2541
|
}
|
|
2267
2542
|
reply.header("Content-Type", typeInfo.mime).header("Content-Length", String(fileSize)).header("Cache-Control", "no-store").header("X-Content-Type-Options", "nosniff");
|
|
2268
|
-
return reply.send(
|
|
2543
|
+
return reply.send(createReadStream2(absPath));
|
|
2269
2544
|
}
|
|
2270
2545
|
);
|
|
2271
2546
|
}
|
|
@@ -2335,7 +2610,7 @@ var init_render_markdown = __esm({
|
|
|
2335
2610
|
});
|
|
2336
2611
|
|
|
2337
2612
|
// packages/server/src/preview/resource-loader.ts
|
|
2338
|
-
import { readFile as readFile2, realpath as realpath2, stat as
|
|
2613
|
+
import { readFile as readFile2, realpath as realpath2, stat as stat4 } from "node:fs/promises";
|
|
2339
2614
|
import { posix as posix2 } from "node:path";
|
|
2340
2615
|
import mime from "mime-types";
|
|
2341
2616
|
function resolvePreviewResourcePath(entryPath, requestedPath) {
|
|
@@ -2353,10 +2628,10 @@ async function loadPreviewResource(workspaceRootPath, workspaceRelativePath) {
|
|
|
2353
2628
|
realpath2(workspaceRootPath),
|
|
2354
2629
|
realpath2(absolutePath)
|
|
2355
2630
|
]);
|
|
2356
|
-
if (!
|
|
2631
|
+
if (!isPathInsideRoot2(realWorkspacePath, realAssetPath)) {
|
|
2357
2632
|
throw new Error("path_escape");
|
|
2358
2633
|
}
|
|
2359
|
-
const [bytes, stats] = await Promise.all([readFile2(realAssetPath),
|
|
2634
|
+
const [bytes, stats] = await Promise.all([readFile2(realAssetPath), stat4(realAssetPath)]);
|
|
2360
2635
|
if (!stats.isFile()) {
|
|
2361
2636
|
throw new Error("not_a_file");
|
|
2362
2637
|
}
|
|
@@ -2500,118 +2775,8 @@ var init_constants = __esm({
|
|
|
2500
2775
|
}
|
|
2501
2776
|
});
|
|
2502
2777
|
|
|
2503
|
-
// packages/server/src/uploads/paths.ts
|
|
2504
|
-
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
2505
|
-
import { lstat, mkdir as mkdir3 } from "node:fs/promises";
|
|
2506
|
-
import path7 from "node:path";
|
|
2507
|
-
function sanitizeOriginalName(input2) {
|
|
2508
|
-
let sanitized = "";
|
|
2509
|
-
for (const char of input2.trim()) {
|
|
2510
|
-
sanitized += KEEP_FILENAME_CHAR.test(char) ? char : "_";
|
|
2511
|
-
}
|
|
2512
|
-
sanitized = sanitized.replace(/^\.+/, "");
|
|
2513
|
-
if (sanitized.length === 0 || /^[_\s]*$/.test(sanitized)) {
|
|
2514
|
-
return "file";
|
|
2515
|
-
}
|
|
2516
|
-
if (sanitized.length <= MAX_FILENAME_LENGTH) {
|
|
2517
|
-
return sanitized;
|
|
2518
|
-
}
|
|
2519
|
-
const lastDot = sanitized.lastIndexOf(".");
|
|
2520
|
-
if (lastDot > 0 && sanitized.length - lastDot <= 16) {
|
|
2521
|
-
const ext = sanitized.slice(lastDot);
|
|
2522
|
-
const stem = sanitized.slice(0, MAX_FILENAME_LENGTH - ext.length);
|
|
2523
|
-
return stem + ext;
|
|
2524
|
-
}
|
|
2525
|
-
return sanitized.slice(0, MAX_FILENAME_LENGTH);
|
|
2526
|
-
}
|
|
2527
|
-
function validateWorkspaceId(id) {
|
|
2528
|
-
if (!WORKSPACE_ID_RE.test(id)) {
|
|
2529
|
-
throw new Error(`invalid workspace id: ${JSON.stringify(id)}`);
|
|
2530
|
-
}
|
|
2531
|
-
}
|
|
2532
|
-
async function assertDirectorySegmentSafe(segmentPath) {
|
|
2533
|
-
const info = await lstat(segmentPath);
|
|
2534
|
-
if (info.isSymbolicLink()) {
|
|
2535
|
-
throw new Error(`symlinked upload path segment is not allowed: ${segmentPath}`);
|
|
2536
|
-
}
|
|
2537
|
-
if (!info.isDirectory()) {
|
|
2538
|
-
throw new Error(`upload path segment is not a directory: ${segmentPath}`);
|
|
2539
|
-
}
|
|
2540
|
-
}
|
|
2541
|
-
async function ensureSafeUploadDir(rootDir, targetDir2) {
|
|
2542
|
-
const resolvedRoot = path7.resolve(rootDir);
|
|
2543
|
-
const resolvedTarget = path7.resolve(targetDir2);
|
|
2544
|
-
if (resolvedTarget !== resolvedRoot && !resolvedTarget.startsWith(`${resolvedRoot}${path7.sep}`)) {
|
|
2545
|
-
throw new Error(`target dir escaped uploads root: ${resolvedTarget}`);
|
|
2546
|
-
}
|
|
2547
|
-
try {
|
|
2548
|
-
await assertDirectorySegmentSafe(resolvedRoot);
|
|
2549
|
-
} catch (error) {
|
|
2550
|
-
const code = error.code;
|
|
2551
|
-
if (code !== "ENOENT") {
|
|
2552
|
-
throw error;
|
|
2553
|
-
}
|
|
2554
|
-
await mkdir3(resolvedRoot, { recursive: true });
|
|
2555
|
-
await assertDirectorySegmentSafe(resolvedRoot);
|
|
2556
|
-
}
|
|
2557
|
-
const relative3 = path7.relative(resolvedRoot, resolvedTarget);
|
|
2558
|
-
if (!relative3) {
|
|
2559
|
-
return;
|
|
2560
|
-
}
|
|
2561
|
-
let current = resolvedRoot;
|
|
2562
|
-
for (const segment of relative3.split(path7.sep)) {
|
|
2563
|
-
current = path7.join(current, segment);
|
|
2564
|
-
try {
|
|
2565
|
-
await assertDirectorySegmentSafe(current);
|
|
2566
|
-
continue;
|
|
2567
|
-
} catch (error) {
|
|
2568
|
-
const code = error.code;
|
|
2569
|
-
if (code !== "ENOENT") {
|
|
2570
|
-
throw error;
|
|
2571
|
-
}
|
|
2572
|
-
}
|
|
2573
|
-
try {
|
|
2574
|
-
await mkdir3(current);
|
|
2575
|
-
} catch (error) {
|
|
2576
|
-
const code = error.code;
|
|
2577
|
-
if (code !== "EEXIST") {
|
|
2578
|
-
throw error;
|
|
2579
|
-
}
|
|
2580
|
-
}
|
|
2581
|
-
await assertDirectorySegmentSafe(current);
|
|
2582
|
-
}
|
|
2583
|
-
}
|
|
2584
|
-
function generateBucketPath(input2) {
|
|
2585
|
-
validateWorkspaceId(input2.workspaceId);
|
|
2586
|
-
const now = input2.now ?? /* @__PURE__ */ new Date();
|
|
2587
|
-
const dateStr = now.toISOString().slice(0, 10);
|
|
2588
|
-
const dir = path7.join(input2.uploadsDir, input2.workspaceId, dateStr);
|
|
2589
|
-
const sanitizedName = sanitizeOriginalName(input2.originalName);
|
|
2590
|
-
const uuid8 = randomUUID2().replace(/-/g, "").slice(0, 8);
|
|
2591
|
-
const absolutePath = path7.resolve(dir, `${uuid8}-${sanitizedName}`);
|
|
2592
|
-
const uploadsRoot = `${path7.resolve(input2.uploadsDir)}${path7.sep}`;
|
|
2593
|
-
if (!absolutePath.startsWith(uploadsRoot)) {
|
|
2594
|
-
throw new Error(`generated upload path escaped uploads root: ${absolutePath}`);
|
|
2595
|
-
}
|
|
2596
|
-
return {
|
|
2597
|
-
dir,
|
|
2598
|
-
absolutePath,
|
|
2599
|
-
uuid8,
|
|
2600
|
-
sanitizedName
|
|
2601
|
-
};
|
|
2602
|
-
}
|
|
2603
|
-
var MAX_FILENAME_LENGTH, KEEP_FILENAME_CHAR, WORKSPACE_ID_RE;
|
|
2604
|
-
var init_paths = __esm({
|
|
2605
|
-
"packages/server/src/uploads/paths.ts"() {
|
|
2606
|
-
"use strict";
|
|
2607
|
-
MAX_FILENAME_LENGTH = 64;
|
|
2608
|
-
KEEP_FILENAME_CHAR = /[a-zA-Z0-9._一-鿿 \-]/;
|
|
2609
|
-
WORKSPACE_ID_RE = /^[a-zA-Z0-9_-]+$/;
|
|
2610
|
-
}
|
|
2611
|
-
});
|
|
2612
|
-
|
|
2613
2778
|
// packages/server/src/uploads/cleanup.ts
|
|
2614
|
-
import { readdir, rm as
|
|
2779
|
+
import { readdir, rm as rm4, rmdir, stat as stat5, unlink } from "node:fs/promises";
|
|
2615
2780
|
import path8 from "node:path";
|
|
2616
2781
|
async function listFilesRecursive(root) {
|
|
2617
2782
|
let entries;
|
|
@@ -2633,7 +2798,7 @@ async function listFilesRecursive(root) {
|
|
|
2633
2798
|
if (!entry.isFile()) {
|
|
2634
2799
|
continue;
|
|
2635
2800
|
}
|
|
2636
|
-
const fileStat = await
|
|
2801
|
+
const fileStat = await stat5(childPath);
|
|
2637
2802
|
files.push({
|
|
2638
2803
|
absPath: childPath,
|
|
2639
2804
|
size: fileStat.size,
|
|
@@ -2666,7 +2831,7 @@ async function pruneEmptyDirectories(root) {
|
|
|
2666
2831
|
async function deleteWorkspaceUploads(uploadsDir, workspaceId) {
|
|
2667
2832
|
validateWorkspaceId(workspaceId);
|
|
2668
2833
|
const bucket = path8.join(uploadsDir, workspaceId);
|
|
2669
|
-
await
|
|
2834
|
+
await rm4(bucket, { recursive: true, force: true });
|
|
2670
2835
|
}
|
|
2671
2836
|
async function enforceBucketCap(uploadsDir, workspaceId, capBytes, logger) {
|
|
2672
2837
|
validateWorkspaceId(workspaceId);
|
|
@@ -2749,9 +2914,9 @@ var init_cleanup = __esm({
|
|
|
2749
2914
|
});
|
|
2750
2915
|
|
|
2751
2916
|
// packages/server/src/routes/uploads.ts
|
|
2752
|
-
import { createWriteStream } from "node:fs";
|
|
2753
|
-
import { rm as
|
|
2754
|
-
import { pipeline } from "node:stream/promises";
|
|
2917
|
+
import { createWriteStream as createWriteStream2 } from "node:fs";
|
|
2918
|
+
import { rm as rm5, stat as stat6, writeFile as writeFile3 } from "node:fs/promises";
|
|
2919
|
+
import { pipeline as pipeline2 } from "node:stream/promises";
|
|
2755
2920
|
function inferClipboardFilename(filename, mimeType, now) {
|
|
2756
2921
|
const trimmed = filename?.trim();
|
|
2757
2922
|
if (trimmed) {
|
|
@@ -2771,7 +2936,7 @@ function inferClipboardFilename(filename, mimeType, now) {
|
|
|
2771
2936
|
return `screenshot-${hhmmss}.${ext}`;
|
|
2772
2937
|
}
|
|
2773
2938
|
async function cleanupWrittenFiles(files) {
|
|
2774
|
-
await Promise.all(files.map((file) =>
|
|
2939
|
+
await Promise.all(files.map((file) => rm5(file.path, { force: true })));
|
|
2775
2940
|
}
|
|
2776
2941
|
function getRequestLogger(request) {
|
|
2777
2942
|
const logger = request.log;
|
|
@@ -2780,7 +2945,7 @@ function getRequestLogger(request) {
|
|
|
2780
2945
|
}
|
|
2781
2946
|
return void 0;
|
|
2782
2947
|
}
|
|
2783
|
-
async function
|
|
2948
|
+
async function rejectAndCleanup2(reply, written, statusCode, error) {
|
|
2784
2949
|
await cleanupWrittenFiles(written);
|
|
2785
2950
|
return reply.status(statusCode).send({ ok: false, error });
|
|
2786
2951
|
}
|
|
@@ -2793,7 +2958,7 @@ function getActiveWorkspace(deps, workspaceId) {
|
|
|
2793
2958
|
async function ensureWorkspaceStillActive(deps, workspaceId, reply, written) {
|
|
2794
2959
|
const workspace = getActiveWorkspace(deps, workspaceId);
|
|
2795
2960
|
if (!workspace) {
|
|
2796
|
-
await
|
|
2961
|
+
await rejectAndCleanup2(reply, written, 404, "workspace_not_found");
|
|
2797
2962
|
return null;
|
|
2798
2963
|
}
|
|
2799
2964
|
return workspace;
|
|
@@ -2820,22 +2985,22 @@ function registerUploadsRoute(app, deps) {
|
|
|
2820
2985
|
if (part.type === "field" && part.fieldname === "workspaceId") {
|
|
2821
2986
|
const lockedWorkspaceId = lockWorkspaceId(workspaceId, String(part.value));
|
|
2822
2987
|
if (lockedWorkspaceId === "mismatch") {
|
|
2823
|
-
return
|
|
2988
|
+
return rejectAndCleanup2(reply, written, 400, "workspace_mismatch");
|
|
2824
2989
|
}
|
|
2825
2990
|
workspaceId = lockedWorkspaceId;
|
|
2826
2991
|
if (!getActiveWorkspace(deps, workspaceId)) {
|
|
2827
|
-
return
|
|
2992
|
+
return rejectAndCleanup2(reply, written, 404, "workspace_not_found");
|
|
2828
2993
|
}
|
|
2829
2994
|
workspaceValidated = true;
|
|
2830
2995
|
continue;
|
|
2831
2996
|
}
|
|
2832
2997
|
if (part.type === "field" && part.fieldname === "files") {
|
|
2833
2998
|
if (!workspaceId) {
|
|
2834
|
-
return
|
|
2999
|
+
return rejectAndCleanup2(reply, written, 400, "workspace_required");
|
|
2835
3000
|
}
|
|
2836
3001
|
fileCount += 1;
|
|
2837
3002
|
if (fileCount > MAX_FILES_PER_BATCH) {
|
|
2838
|
-
return
|
|
3003
|
+
return rejectAndCleanup2(reply, written, 400, "too_many_files");
|
|
2839
3004
|
}
|
|
2840
3005
|
const now2 = /* @__PURE__ */ new Date();
|
|
2841
3006
|
const originalName2 = inferClipboardFilename(void 0, part.mimetype, now2);
|
|
@@ -2852,20 +3017,20 @@ function registerUploadsRoute(app, deps) {
|
|
|
2852
3017
|
await ensureSafeUploadDir(deps.uploadsDir, target2.dir);
|
|
2853
3018
|
await writeFile3(target2.absolutePath, String(part.value));
|
|
2854
3019
|
} catch (error) {
|
|
2855
|
-
await
|
|
3020
|
+
await rm5(target2.absolutePath, { force: true });
|
|
2856
3021
|
await cleanupWrittenFiles(written);
|
|
2857
3022
|
logger?.warn({ err: error }, "upload write failed");
|
|
2858
3023
|
return reply.status(500).send({ ok: false, error: "write_failed" });
|
|
2859
3024
|
}
|
|
2860
3025
|
try {
|
|
2861
|
-
const fileStat = await
|
|
3026
|
+
const fileStat = await stat6(target2.absolutePath);
|
|
2862
3027
|
written.push({
|
|
2863
3028
|
path: target2.absolutePath,
|
|
2864
3029
|
originalName: originalName2,
|
|
2865
3030
|
size: fileStat.size
|
|
2866
3031
|
});
|
|
2867
3032
|
} catch (error) {
|
|
2868
|
-
await
|
|
3033
|
+
await rm5(target2.absolutePath, { force: true });
|
|
2869
3034
|
await cleanupWrittenFiles(written);
|
|
2870
3035
|
logger?.warn({ err: error }, "upload stat failed");
|
|
2871
3036
|
return reply.status(500).send({ ok: false, error: "write_failed" });
|
|
@@ -2880,12 +3045,12 @@ function registerUploadsRoute(app, deps) {
|
|
|
2880
3045
|
}
|
|
2881
3046
|
if (!workspaceId) {
|
|
2882
3047
|
part.file.resume();
|
|
2883
|
-
return
|
|
3048
|
+
return rejectAndCleanup2(reply, written, 400, "workspace_required");
|
|
2884
3049
|
}
|
|
2885
3050
|
fileCount += 1;
|
|
2886
3051
|
if (fileCount > MAX_FILES_PER_BATCH) {
|
|
2887
3052
|
part.file.resume();
|
|
2888
|
-
return
|
|
3053
|
+
return rejectAndCleanup2(reply, written, 400, "too_many_files");
|
|
2889
3054
|
}
|
|
2890
3055
|
const now = /* @__PURE__ */ new Date();
|
|
2891
3056
|
const originalName = inferClipboardFilename(part.filename, part.mimetype, now);
|
|
@@ -2901,27 +3066,27 @@ function registerUploadsRoute(app, deps) {
|
|
|
2901
3066
|
}
|
|
2902
3067
|
try {
|
|
2903
3068
|
await ensureSafeUploadDir(deps.uploadsDir, target.dir);
|
|
2904
|
-
await
|
|
3069
|
+
await pipeline2(part.file, createWriteStream2(target.absolutePath));
|
|
2905
3070
|
} catch (error) {
|
|
2906
|
-
await
|
|
3071
|
+
await rm5(target.absolutePath, { force: true });
|
|
2907
3072
|
await cleanupWrittenFiles(written);
|
|
2908
3073
|
logger?.warn({ err: error }, "upload write failed");
|
|
2909
3074
|
return reply.status(500).send({ ok: false, error: "write_failed" });
|
|
2910
3075
|
}
|
|
2911
3076
|
if (part.file.truncated) {
|
|
2912
|
-
await
|
|
3077
|
+
await rm5(target.absolutePath, { force: true });
|
|
2913
3078
|
await cleanupWrittenFiles(written);
|
|
2914
3079
|
return reply.status(413).send({ ok: false, error: "file_too_large" });
|
|
2915
3080
|
}
|
|
2916
3081
|
try {
|
|
2917
|
-
const fileStat = await
|
|
3082
|
+
const fileStat = await stat6(target.absolutePath);
|
|
2918
3083
|
written.push({
|
|
2919
3084
|
path: target.absolutePath,
|
|
2920
3085
|
originalName,
|
|
2921
3086
|
size: fileStat.size
|
|
2922
3087
|
});
|
|
2923
3088
|
} catch (error) {
|
|
2924
|
-
await
|
|
3089
|
+
await rm5(target.absolutePath, { force: true });
|
|
2925
3090
|
await cleanupWrittenFiles(written);
|
|
2926
3091
|
logger?.warn({ err: error }, "upload stat failed");
|
|
2927
3092
|
return reply.status(500).send({ ok: false, error: "write_failed" });
|
|
@@ -2936,13 +3101,13 @@ function registerUploadsRoute(app, deps) {
|
|
|
2936
3101
|
return reply.status(400).send({ ok: false, error: "parse_failed" });
|
|
2937
3102
|
}
|
|
2938
3103
|
if (!workspaceId) {
|
|
2939
|
-
return
|
|
3104
|
+
return rejectAndCleanup2(reply, written, 400, "workspace_required");
|
|
2940
3105
|
}
|
|
2941
3106
|
if (!workspaceValidated) {
|
|
2942
|
-
return
|
|
3107
|
+
return rejectAndCleanup2(reply, written, 404, "workspace_not_found");
|
|
2943
3108
|
}
|
|
2944
3109
|
if (written.length === 0) {
|
|
2945
|
-
return
|
|
3110
|
+
return rejectAndCleanup2(reply, written, 400, "no_files");
|
|
2946
3111
|
}
|
|
2947
3112
|
if (!await ensureWorkspaceStillActive(deps, workspaceId, reply, written)) {
|
|
2948
3113
|
return;
|
|
@@ -2962,7 +3127,105 @@ var init_uploads = __esm({
|
|
|
2962
3127
|
}
|
|
2963
3128
|
});
|
|
2964
3129
|
|
|
3130
|
+
// packages/server/src/storage/repositories/json-file-store.ts
|
|
3131
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
3132
|
+
import { dirname as dirname3 } from "node:path";
|
|
3133
|
+
function hasCode(error, code) {
|
|
3134
|
+
return Boolean(
|
|
3135
|
+
error && typeof error === "object" && "code" in error && error.code === code
|
|
3136
|
+
);
|
|
3137
|
+
}
|
|
3138
|
+
function readJsonFile(filePath) {
|
|
3139
|
+
try {
|
|
3140
|
+
return JSON.parse(readFileSync3(filePath, "utf-8"));
|
|
3141
|
+
} catch (error) {
|
|
3142
|
+
if (hasCode(error, "ENOENT")) {
|
|
3143
|
+
return void 0;
|
|
3144
|
+
}
|
|
3145
|
+
throw error;
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
3148
|
+
function writeJsonFileAtomic(filePath, value) {
|
|
3149
|
+
mkdirSync2(dirname3(filePath), { recursive: true });
|
|
3150
|
+
const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
3151
|
+
try {
|
|
3152
|
+
writeFileSync2(tempPath, JSON.stringify(value, null, 2) + "\n", "utf-8");
|
|
3153
|
+
renameSync(tempPath, filePath);
|
|
3154
|
+
} catch (error) {
|
|
3155
|
+
rmSync(tempPath, { force: true });
|
|
3156
|
+
throw error;
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
var init_json_file_store = __esm({
|
|
3160
|
+
"packages/server/src/storage/repositories/json-file-store.ts"() {
|
|
3161
|
+
"use strict";
|
|
3162
|
+
}
|
|
3163
|
+
});
|
|
3164
|
+
|
|
3165
|
+
// packages/server/src/storage/repositories/appearance-asset-repo.ts
|
|
3166
|
+
function isRecord(value) {
|
|
3167
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3168
|
+
}
|
|
3169
|
+
function normalizeAppearanceAssetFile(value) {
|
|
3170
|
+
if (isRecord(value) && value.version === 1 && isRecord(value.assets)) {
|
|
3171
|
+
return value.assets;
|
|
3172
|
+
}
|
|
3173
|
+
if (isRecord(value)) {
|
|
3174
|
+
return value;
|
|
3175
|
+
}
|
|
3176
|
+
return {};
|
|
3177
|
+
}
|
|
3178
|
+
var AppearanceAssetRepo;
|
|
3179
|
+
var init_appearance_asset_repo = __esm({
|
|
3180
|
+
"packages/server/src/storage/repositories/appearance-asset-repo.ts"() {
|
|
3181
|
+
"use strict";
|
|
3182
|
+
init_json_file_store();
|
|
3183
|
+
AppearanceAssetRepo = class {
|
|
3184
|
+
constructor(options) {
|
|
3185
|
+
this.options = options;
|
|
3186
|
+
}
|
|
3187
|
+
options;
|
|
3188
|
+
loadFileAssets() {
|
|
3189
|
+
const parsed = readJsonFile(
|
|
3190
|
+
this.options.filePath
|
|
3191
|
+
);
|
|
3192
|
+
if (parsed !== void 0) {
|
|
3193
|
+
return { ...normalizeAppearanceAssetFile(parsed) };
|
|
3194
|
+
}
|
|
3195
|
+
return {};
|
|
3196
|
+
}
|
|
3197
|
+
saveFileAssets(assets) {
|
|
3198
|
+
const payload = {
|
|
3199
|
+
version: 1,
|
|
3200
|
+
assets
|
|
3201
|
+
};
|
|
3202
|
+
writeJsonFileAtomic(this.options.filePath, payload);
|
|
3203
|
+
}
|
|
3204
|
+
get(id) {
|
|
3205
|
+
return this.loadFileAssets()[id];
|
|
3206
|
+
}
|
|
3207
|
+
set(record) {
|
|
3208
|
+
const next = this.loadFileAssets();
|
|
3209
|
+
next[record.id] = record;
|
|
3210
|
+
this.saveFileAssets(next);
|
|
3211
|
+
}
|
|
3212
|
+
delete(id) {
|
|
3213
|
+
const next = this.loadFileAssets();
|
|
3214
|
+
if (!Object.prototype.hasOwnProperty.call(next, id)) {
|
|
3215
|
+
return;
|
|
3216
|
+
}
|
|
3217
|
+
delete next[id];
|
|
3218
|
+
this.saveFileAssets(next);
|
|
3219
|
+
}
|
|
3220
|
+
list() {
|
|
3221
|
+
return Object.values(this.loadFileAssets());
|
|
3222
|
+
}
|
|
3223
|
+
};
|
|
3224
|
+
}
|
|
3225
|
+
});
|
|
3226
|
+
|
|
2965
3227
|
// packages/server/src/app.ts
|
|
3228
|
+
import { join as join3, resolve as resolve3 } from "node:path";
|
|
2966
3229
|
import compress from "@fastify/compress";
|
|
2967
3230
|
import cors from "@fastify/cors";
|
|
2968
3231
|
import multipart from "@fastify/multipart";
|
|
@@ -2970,6 +3233,10 @@ import staticPlugin from "@fastify/static";
|
|
|
2970
3233
|
import websocket from "@fastify/websocket";
|
|
2971
3234
|
import Fastify from "fastify";
|
|
2972
3235
|
async function buildFastifyApp(deps) {
|
|
3236
|
+
const stateRoot = deps.config.stateDir === IN_MEMORY_STATE_DIR ? resolve3(deps.config.uploadsDir, "..") : deps.config.stateDir;
|
|
3237
|
+
const appearanceAssetRepo = deps.appearanceAssetRepo ?? new AppearanceAssetRepo({
|
|
3238
|
+
filePath: join3(stateRoot, "state", "appearance-assets.json")
|
|
3239
|
+
});
|
|
2973
3240
|
const app = Fastify({
|
|
2974
3241
|
logger: deps.logger ?? {
|
|
2975
3242
|
level: "info",
|
|
@@ -3052,6 +3319,10 @@ async function buildFastifyApp(deps) {
|
|
|
3052
3319
|
registerFileAssetRoutes(app, {
|
|
3053
3320
|
workspaceMgr: deps.workspaceMgr
|
|
3054
3321
|
});
|
|
3322
|
+
registerAppearanceAssetsRoutes(app, {
|
|
3323
|
+
uploadsDir: deps.config.uploadsDir,
|
|
3324
|
+
repo: appearanceAssetRepo
|
|
3325
|
+
});
|
|
3055
3326
|
const previewSessions = new PreviewSessionStore();
|
|
3056
3327
|
registerPreviewRoutes(app, {
|
|
3057
3328
|
workspaceMgr: deps.workspaceMgr,
|
|
@@ -3105,11 +3376,14 @@ async function buildFastifyApp(deps) {
|
|
|
3105
3376
|
var init_app = __esm({
|
|
3106
3377
|
"packages/server/src/app.ts"() {
|
|
3107
3378
|
"use strict";
|
|
3379
|
+
init_state_paths();
|
|
3108
3380
|
init_auth();
|
|
3109
3381
|
init_session_store();
|
|
3382
|
+
init_appearance_assets();
|
|
3110
3383
|
init_file_asset();
|
|
3111
3384
|
init_preview();
|
|
3112
3385
|
init_uploads();
|
|
3386
|
+
init_appearance_asset_repo();
|
|
3113
3387
|
init_constants();
|
|
3114
3388
|
init_web_ui_routing();
|
|
3115
3389
|
}
|
|
@@ -3346,12 +3620,12 @@ var init_auto_fetch = __esm({
|
|
|
3346
3620
|
}
|
|
3347
3621
|
acquireWorkspaceOperation(workspaceId) {
|
|
3348
3622
|
const state = this.getOrCreateState(workspaceId);
|
|
3349
|
-
return new Promise((
|
|
3623
|
+
return new Promise((resolve6) => {
|
|
3350
3624
|
const grant = () => {
|
|
3351
3625
|
state.inFlight = true;
|
|
3352
3626
|
state.nextFetchAt = void 0;
|
|
3353
3627
|
let released = false;
|
|
3354
|
-
|
|
3628
|
+
resolve6(() => {
|
|
3355
3629
|
if (released) {
|
|
3356
3630
|
return;
|
|
3357
3631
|
}
|
|
@@ -4354,7 +4628,7 @@ var init_manager = __esm({
|
|
|
4354
4628
|
// packages/server/src/provider-runtime/command-runner.ts
|
|
4355
4629
|
import { spawn as spawn2 } from "node:child_process";
|
|
4356
4630
|
async function runCommandAsString(file, args, options) {
|
|
4357
|
-
return new Promise((
|
|
4631
|
+
return new Promise((resolve6, reject) => {
|
|
4358
4632
|
const child = spawn2(file, args, {
|
|
4359
4633
|
cwd: options?.cwd,
|
|
4360
4634
|
env: options?.env,
|
|
@@ -4381,7 +4655,7 @@ async function runCommandAsString(file, args, options) {
|
|
|
4381
4655
|
const stdout = Buffer.concat(stdoutChunks).toString("utf8");
|
|
4382
4656
|
const stderr = Buffer.concat(stderrChunks).toString("utf8");
|
|
4383
4657
|
if (code === 0) {
|
|
4384
|
-
|
|
4658
|
+
resolve6({ stdout, stderr });
|
|
4385
4659
|
return;
|
|
4386
4660
|
}
|
|
4387
4661
|
reject(
|
|
@@ -4500,9 +4774,9 @@ var init_definitions = __esm({
|
|
|
4500
4774
|
});
|
|
4501
4775
|
|
|
4502
4776
|
// packages/server/src/lsp-tools/install-manager.ts
|
|
4503
|
-
import { randomUUID as
|
|
4504
|
-
import { mkdirSync as
|
|
4505
|
-
import { dirname as
|
|
4777
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
4778
|
+
import { mkdirSync as mkdirSync3 } from "node:fs";
|
|
4779
|
+
import { dirname as dirname4, join as join4 } from "node:path";
|
|
4506
4780
|
function toSnapshotStep(step) {
|
|
4507
4781
|
return {
|
|
4508
4782
|
id: step.id,
|
|
@@ -4666,7 +4940,7 @@ var init_install_manager = __esm({
|
|
|
4666
4940
|
async prepare(input2) {
|
|
4667
4941
|
const definition = getLspToolDefinition(input2.serverKind);
|
|
4668
4942
|
const managed = definition.managed;
|
|
4669
|
-
const jobId =
|
|
4943
|
+
const jobId = randomUUID4();
|
|
4670
4944
|
const platform = this.deps.platform ?? process.platform;
|
|
4671
4945
|
if (!managed || input2.workspace.targetRuntime !== "native") {
|
|
4672
4946
|
return {
|
|
@@ -4735,13 +5009,13 @@ var init_install_manager = __esm({
|
|
|
4735
5009
|
}
|
|
4736
5010
|
};
|
|
4737
5011
|
}
|
|
4738
|
-
const installRoot =
|
|
4739
|
-
const executablePath = input2.serverKind === "python" ?
|
|
5012
|
+
const installRoot = join4(this.deps.manifestStore.getRoot(), input2.serverKind, managed.version);
|
|
5013
|
+
const executablePath = input2.serverKind === "python" ? join4(
|
|
4740
5014
|
installRoot,
|
|
4741
5015
|
"venv",
|
|
4742
5016
|
platform === "win32" ? "Scripts" : "bin",
|
|
4743
5017
|
platform === "win32" ? "pylsp.exe" : "pylsp"
|
|
4744
|
-
) : input2.serverKind === "go" ?
|
|
5018
|
+
) : input2.serverKind === "go" ? join4(installRoot, "bin", platform === "win32" ? "gopls.exe" : "gopls") : join4(installRoot, "bin", platform === "win32" ? "rust-analyzer.exe" : "rust-analyzer");
|
|
4745
5019
|
const plannedSteps = this.planInstallSteps({
|
|
4746
5020
|
serverKind: input2.serverKind,
|
|
4747
5021
|
installRoot,
|
|
@@ -4768,16 +5042,16 @@ var init_install_manager = __esm({
|
|
|
4768
5042
|
const platform = this.deps.platform ?? process.platform;
|
|
4769
5043
|
job.status = "running";
|
|
4770
5044
|
this.jobs.set(job.jobId, job);
|
|
4771
|
-
const installRoot =
|
|
4772
|
-
const executablePath = serverKind === "python" ?
|
|
5045
|
+
const installRoot = join4(this.deps.manifestStore.getRoot(), serverKind, managed.version);
|
|
5046
|
+
const executablePath = serverKind === "python" ? join4(
|
|
4773
5047
|
installRoot,
|
|
4774
5048
|
"venv",
|
|
4775
5049
|
platform === "win32" ? "Scripts" : "bin",
|
|
4776
5050
|
platform === "win32" ? "pylsp.exe" : "pylsp"
|
|
4777
|
-
) : serverKind === "go" ?
|
|
5051
|
+
) : serverKind === "go" ? join4(installRoot, "bin", platform === "win32" ? "gopls.exe" : "gopls") : join4(installRoot, "bin", platform === "win32" ? "rust-analyzer.exe" : "rust-analyzer");
|
|
4778
5052
|
const commandExists = this.deps.commandExists ?? ((command) => checkCommandAvailable(command, this.deps));
|
|
4779
5053
|
const pythonCommand = serverKind === "python" ? await resolveManagedPythonCommand(commandExists, platform) : null;
|
|
4780
|
-
|
|
5054
|
+
mkdirSync3(dirname4(executablePath), { recursive: true });
|
|
4781
5055
|
for (const step of job.steps) {
|
|
4782
5056
|
job.currentStepId = step.id;
|
|
4783
5057
|
step.status = "running";
|
|
@@ -4844,9 +5118,9 @@ var init_install_manager = __esm({
|
|
|
4844
5118
|
}
|
|
4845
5119
|
planInstallSteps(input2) {
|
|
4846
5120
|
if (input2.serverKind === "python") {
|
|
4847
|
-
const venvRoot =
|
|
4848
|
-
const binDir =
|
|
4849
|
-
const pipPath =
|
|
5121
|
+
const venvRoot = join4(input2.installRoot, "venv");
|
|
5122
|
+
const binDir = join4(venvRoot, input2.platform === "win32" ? "Scripts" : "bin");
|
|
5123
|
+
const pipPath = join4(binDir, input2.platform === "win32" ? "pip.exe" : "pip");
|
|
4850
5124
|
return [
|
|
4851
5125
|
{
|
|
4852
5126
|
id: "create-python-venv",
|
|
@@ -4883,7 +5157,7 @@ var init_install_manager = __esm({
|
|
|
4883
5157
|
args: ["install", `golang.org/x/tools/gopls@${input2.version}`],
|
|
4884
5158
|
env: {
|
|
4885
5159
|
...process.env,
|
|
4886
|
-
GOBIN:
|
|
5160
|
+
GOBIN: join4(input2.installRoot, "bin")
|
|
4887
5161
|
}
|
|
4888
5162
|
},
|
|
4889
5163
|
{
|
|
@@ -4943,7 +5217,7 @@ var init_install_manager = __esm({
|
|
|
4943
5217
|
// packages/server/src/lsp-tools/manager.ts
|
|
4944
5218
|
import { existsSync as existsSync3 } from "node:fs";
|
|
4945
5219
|
import { createRequire as createRequire2 } from "node:module";
|
|
4946
|
-
import { join as
|
|
5220
|
+
import { join as join5 } from "node:path";
|
|
4947
5221
|
function parseOverrideArgs(raw, envVarName) {
|
|
4948
5222
|
try {
|
|
4949
5223
|
const parsed = JSON.parse(raw);
|
|
@@ -5077,8 +5351,8 @@ var init_manager2 = __esm({
|
|
|
5077
5351
|
}
|
|
5078
5352
|
try {
|
|
5079
5353
|
const packageJsonPath = require3.resolve(`${definition.bundled.packageName}/package.json`);
|
|
5080
|
-
const packageRoot =
|
|
5081
|
-
const entryPath =
|
|
5354
|
+
const packageRoot = join5(packageJsonPath, "..");
|
|
5355
|
+
const entryPath = join5(packageRoot, definition.bundled.entry);
|
|
5082
5356
|
if (!existsSync3(entryPath)) {
|
|
5083
5357
|
return null;
|
|
5084
5358
|
}
|
|
@@ -5130,8 +5404,8 @@ var init_manager2 = __esm({
|
|
|
5130
5404
|
});
|
|
5131
5405
|
|
|
5132
5406
|
// packages/server/src/lsp-tools/manifest-store.ts
|
|
5133
|
-
import { existsSync as existsSync4, mkdirSync as
|
|
5134
|
-
import { join as
|
|
5407
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
5408
|
+
import { join as join6 } from "node:path";
|
|
5135
5409
|
var FileManifestStore;
|
|
5136
5410
|
var init_manifest_store = __esm({
|
|
5137
5411
|
"packages/server/src/lsp-tools/manifest-store.ts"() {
|
|
@@ -5150,29 +5424,29 @@ var init_manifest_store = __esm({
|
|
|
5150
5424
|
return null;
|
|
5151
5425
|
}
|
|
5152
5426
|
try {
|
|
5153
|
-
return JSON.parse(
|
|
5427
|
+
return JSON.parse(readFileSync4(path14, "utf8"));
|
|
5154
5428
|
} catch {
|
|
5155
5429
|
return null;
|
|
5156
5430
|
}
|
|
5157
5431
|
}
|
|
5158
5432
|
write(serverKind, manifest) {
|
|
5159
5433
|
const path14 = this.pathFor(serverKind);
|
|
5160
|
-
|
|
5161
|
-
|
|
5434
|
+
mkdirSync4(join6(this.root, serverKind), { recursive: true });
|
|
5435
|
+
writeFileSync3(path14, JSON.stringify(manifest, null, 2));
|
|
5162
5436
|
}
|
|
5163
5437
|
pathFor(serverKind) {
|
|
5164
|
-
return
|
|
5438
|
+
return join6(this.root, serverKind, "manifest.json");
|
|
5165
5439
|
}
|
|
5166
5440
|
};
|
|
5167
5441
|
}
|
|
5168
5442
|
});
|
|
5169
5443
|
|
|
5170
5444
|
// packages/server/src/lsp-tools/tool-root.ts
|
|
5171
|
-
import { mkdirSync as
|
|
5172
|
-
import { join as
|
|
5445
|
+
import { mkdirSync as mkdirSync5 } from "node:fs";
|
|
5446
|
+
import { join as join7 } from "node:path";
|
|
5173
5447
|
function resolveLspToolRoot(stateDir) {
|
|
5174
|
-
const root =
|
|
5175
|
-
|
|
5448
|
+
const root = join7(stateDir, "lsp-tools");
|
|
5449
|
+
mkdirSync5(root, { recursive: true });
|
|
5176
5450
|
return root;
|
|
5177
5451
|
}
|
|
5178
5452
|
var init_tool_root = __esm({
|
|
@@ -5182,8 +5456,8 @@ var init_tool_root = __esm({
|
|
|
5182
5456
|
});
|
|
5183
5457
|
|
|
5184
5458
|
// packages/server/src/provider-runtime/e2e-provider-mock.ts
|
|
5185
|
-
import { chmodSync, existsSync as existsSync5, mkdirSync as
|
|
5186
|
-
import { dirname as
|
|
5459
|
+
import { chmodSync, existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
5460
|
+
import { dirname as dirname5, join as join8 } from "node:path";
|
|
5187
5461
|
function createE2EProviderMockOverrides(env = process.env) {
|
|
5188
5462
|
const statePath = env.CODER_STUDIO_E2E_PROVIDER_STATE_PATH;
|
|
5189
5463
|
if (!statePath) {
|
|
@@ -5268,7 +5542,7 @@ function readMockState(statePath) {
|
|
|
5268
5542
|
if (!existsSync5(statePath)) {
|
|
5269
5543
|
return {};
|
|
5270
5544
|
}
|
|
5271
|
-
const raw =
|
|
5545
|
+
const raw = readFileSync5(statePath, "utf8");
|
|
5272
5546
|
if (!raw.trim()) {
|
|
5273
5547
|
return {};
|
|
5274
5548
|
}
|
|
@@ -5283,22 +5557,22 @@ function readMockState(statePath) {
|
|
|
5283
5557
|
function writeMockState(statePath, updater) {
|
|
5284
5558
|
const nextState = readMockState(statePath);
|
|
5285
5559
|
updater(nextState);
|
|
5286
|
-
|
|
5287
|
-
|
|
5560
|
+
mkdirSync6(dirname5(statePath), { recursive: true });
|
|
5561
|
+
writeFileSync4(statePath, JSON.stringify(nextState, null, 2));
|
|
5288
5562
|
return nextState;
|
|
5289
5563
|
}
|
|
5290
5564
|
function ensureProviderCommand(binDir, providerId) {
|
|
5291
|
-
|
|
5292
|
-
const scriptPath =
|
|
5293
|
-
|
|
5565
|
+
mkdirSync6(binDir, { recursive: true });
|
|
5566
|
+
const scriptPath = join8(binDir, providerId);
|
|
5567
|
+
writeFileSync4(scriptPath, PROVIDER_COMMAND_SCRIPTS[providerId], "utf8");
|
|
5294
5568
|
chmodSync(scriptPath, 493);
|
|
5295
5569
|
}
|
|
5296
5570
|
function appendDebugLog(path14, line) {
|
|
5297
5571
|
if (!path14) {
|
|
5298
5572
|
return;
|
|
5299
5573
|
}
|
|
5300
|
-
|
|
5301
|
-
|
|
5574
|
+
mkdirSync6(dirname5(path14), { recursive: true });
|
|
5575
|
+
writeFileSync4(path14, `${line}
|
|
5302
5576
|
`, { flag: "a" });
|
|
5303
5577
|
}
|
|
5304
5578
|
var PROVIDER_INSTALL_PACKAGES, PROVIDER_COMMAND_SCRIPTS;
|
|
@@ -5333,7 +5607,7 @@ done
|
|
|
5333
5607
|
});
|
|
5334
5608
|
|
|
5335
5609
|
// packages/server/src/provider-runtime/install-manager.ts
|
|
5336
|
-
import { randomUUID as
|
|
5610
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
5337
5611
|
function getErrorDetails2(error) {
|
|
5338
5612
|
if (error instanceof Error) {
|
|
5339
5613
|
const record = error;
|
|
@@ -5473,7 +5747,7 @@ var init_install_manager2 = __esm({
|
|
|
5473
5747
|
);
|
|
5474
5748
|
if (missingProviderCommands.length === 0) {
|
|
5475
5749
|
return {
|
|
5476
|
-
jobId:
|
|
5750
|
+
jobId: randomUUID5(),
|
|
5477
5751
|
providerId: provider.id,
|
|
5478
5752
|
strategyIds: [],
|
|
5479
5753
|
status: "succeeded",
|
|
@@ -5537,7 +5811,7 @@ var init_install_manager2 = __esm({
|
|
|
5537
5811
|
}
|
|
5538
5812
|
}
|
|
5539
5813
|
}
|
|
5540
|
-
const jobId =
|
|
5814
|
+
const jobId = randomUUID5();
|
|
5541
5815
|
if (remainingPrerequisites.size > 0) {
|
|
5542
5816
|
const failedStep = this.createCheckStep(
|
|
5543
5817
|
"prerequisite",
|
|
@@ -6108,41 +6382,6 @@ var init_provider_config = __esm({
|
|
|
6108
6382
|
}
|
|
6109
6383
|
});
|
|
6110
6384
|
|
|
6111
|
-
// packages/server/src/storage/repositories/json-file-store.ts
|
|
6112
|
-
import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, renameSync, rmSync, writeFileSync as writeFileSync4 } from "node:fs";
|
|
6113
|
-
import { dirname as dirname5 } from "node:path";
|
|
6114
|
-
function hasCode(error, code) {
|
|
6115
|
-
return Boolean(
|
|
6116
|
-
error && typeof error === "object" && "code" in error && error.code === code
|
|
6117
|
-
);
|
|
6118
|
-
}
|
|
6119
|
-
function readJsonFile(filePath) {
|
|
6120
|
-
try {
|
|
6121
|
-
return JSON.parse(readFileSync5(filePath, "utf-8"));
|
|
6122
|
-
} catch (error) {
|
|
6123
|
-
if (hasCode(error, "ENOENT")) {
|
|
6124
|
-
return void 0;
|
|
6125
|
-
}
|
|
6126
|
-
throw error;
|
|
6127
|
-
}
|
|
6128
|
-
}
|
|
6129
|
-
function writeJsonFileAtomic(filePath, value) {
|
|
6130
|
-
mkdirSync6(dirname5(filePath), { recursive: true });
|
|
6131
|
-
const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
6132
|
-
try {
|
|
6133
|
-
writeFileSync4(tempPath, JSON.stringify(value, null, 2) + "\n", "utf-8");
|
|
6134
|
-
renameSync(tempPath, filePath);
|
|
6135
|
-
} catch (error) {
|
|
6136
|
-
rmSync(tempPath, { force: true });
|
|
6137
|
-
throw error;
|
|
6138
|
-
}
|
|
6139
|
-
}
|
|
6140
|
-
var init_json_file_store = __esm({
|
|
6141
|
-
"packages/server/src/storage/repositories/json-file-store.ts"() {
|
|
6142
|
-
"use strict";
|
|
6143
|
-
}
|
|
6144
|
-
});
|
|
6145
|
-
|
|
6146
6385
|
// packages/server/src/storage/repositories/session-repo.ts
|
|
6147
6386
|
function rowToSession(row) {
|
|
6148
6387
|
return {
|
|
@@ -6179,11 +6418,11 @@ function sessionToRow(session) {
|
|
|
6179
6418
|
title: session.title ?? null
|
|
6180
6419
|
};
|
|
6181
6420
|
}
|
|
6182
|
-
function
|
|
6421
|
+
function isRecord2(value) {
|
|
6183
6422
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
6184
6423
|
}
|
|
6185
6424
|
function isSession(value) {
|
|
6186
|
-
if (!
|
|
6425
|
+
if (!isRecord2(value)) {
|
|
6187
6426
|
return false;
|
|
6188
6427
|
}
|
|
6189
6428
|
return typeof value.id === "string" && typeof value.workspaceId === "string" && typeof value.terminalId === "string" && typeof value.providerId === "string" && typeof value.startedAt === "number" && typeof value.lastActiveAt === "number" && (value.capability === "full" || value.capability === "limited" || value.capability === "unsupported") && typeof value.state === "string";
|
|
@@ -6195,7 +6434,7 @@ function normalizeStoredSession(session) {
|
|
|
6195
6434
|
};
|
|
6196
6435
|
}
|
|
6197
6436
|
function normalizeSessionFile(value) {
|
|
6198
|
-
if (
|
|
6437
|
+
if (isRecord2(value) && value.version === 1 && isRecord2(value.sessions)) {
|
|
6199
6438
|
const normalized = {};
|
|
6200
6439
|
for (const entry of Object.values(value.sessions)) {
|
|
6201
6440
|
if (isSession(entry)) {
|
|
@@ -6213,7 +6452,7 @@ function normalizeSessionFile(value) {
|
|
|
6213
6452
|
}
|
|
6214
6453
|
return normalized;
|
|
6215
6454
|
}
|
|
6216
|
-
if (
|
|
6455
|
+
if (isRecord2(value)) {
|
|
6217
6456
|
const normalized = {};
|
|
6218
6457
|
for (const [sessionId, entry] of Object.entries(value)) {
|
|
6219
6458
|
if (isSession(entry)) {
|
|
@@ -6519,7 +6758,87 @@ var init_state_shadow_comparator = __esm({
|
|
|
6519
6758
|
function generateSessionId() {
|
|
6520
6759
|
return `sess_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
6521
6760
|
}
|
|
6522
|
-
|
|
6761
|
+
function readTerminalEscapeSequence(text, start) {
|
|
6762
|
+
const match = text.slice(start).match(TERMINAL_ESCAPE_SEQUENCE_PATTERN);
|
|
6763
|
+
return match?.[0] ?? null;
|
|
6764
|
+
}
|
|
6765
|
+
function classifyRecentInputEcho(bytes) {
|
|
6766
|
+
const text = bytes.toString("utf8");
|
|
6767
|
+
let opaque = false;
|
|
6768
|
+
let remainingVisibleText = "";
|
|
6769
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
6770
|
+
const char = text[index];
|
|
6771
|
+
if (char === "\x1B") {
|
|
6772
|
+
const escape = readTerminalEscapeSequence(text, index);
|
|
6773
|
+
if (escape) {
|
|
6774
|
+
opaque = true;
|
|
6775
|
+
index += escape.length - 1;
|
|
6776
|
+
continue;
|
|
6777
|
+
}
|
|
6778
|
+
}
|
|
6779
|
+
if (char === "\r" || char === "\n" || char === " ") {
|
|
6780
|
+
opaque = true;
|
|
6781
|
+
continue;
|
|
6782
|
+
}
|
|
6783
|
+
if (char === "\x7F" || char === "\b") {
|
|
6784
|
+
opaque = true;
|
|
6785
|
+
continue;
|
|
6786
|
+
}
|
|
6787
|
+
if (char < " ") {
|
|
6788
|
+
opaque = true;
|
|
6789
|
+
continue;
|
|
6790
|
+
}
|
|
6791
|
+
remainingVisibleText += char;
|
|
6792
|
+
}
|
|
6793
|
+
return { opaque, remainingVisibleText };
|
|
6794
|
+
}
|
|
6795
|
+
function extractVisibleTerminalText(bytes) {
|
|
6796
|
+
const text = bytes.toString("utf8");
|
|
6797
|
+
let visibleText = "";
|
|
6798
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
6799
|
+
const char = text[index];
|
|
6800
|
+
if (char === "\x1B") {
|
|
6801
|
+
const escape = readTerminalEscapeSequence(text, index);
|
|
6802
|
+
if (escape) {
|
|
6803
|
+
index += escape.length - 1;
|
|
6804
|
+
continue;
|
|
6805
|
+
}
|
|
6806
|
+
continue;
|
|
6807
|
+
}
|
|
6808
|
+
if (char === "\x7F" || char === "\b") {
|
|
6809
|
+
visibleText = visibleText.slice(0, -1);
|
|
6810
|
+
continue;
|
|
6811
|
+
}
|
|
6812
|
+
if (char === "\r" || char === "\n") {
|
|
6813
|
+
visibleText += "\n";
|
|
6814
|
+
continue;
|
|
6815
|
+
}
|
|
6816
|
+
if (char === " ") {
|
|
6817
|
+
visibleText += " ";
|
|
6818
|
+
continue;
|
|
6819
|
+
}
|
|
6820
|
+
if (char < " ") {
|
|
6821
|
+
continue;
|
|
6822
|
+
}
|
|
6823
|
+
visibleText += char;
|
|
6824
|
+
}
|
|
6825
|
+
return visibleText;
|
|
6826
|
+
}
|
|
6827
|
+
function commonPrefixLength(left, right) {
|
|
6828
|
+
const maxLength = Math.min(left.length, right.length);
|
|
6829
|
+
let index = 0;
|
|
6830
|
+
while (index < maxLength && left[index] === right[index]) {
|
|
6831
|
+
index += 1;
|
|
6832
|
+
}
|
|
6833
|
+
return index;
|
|
6834
|
+
}
|
|
6835
|
+
function chunkHasLineRepaintControl(text) {
|
|
6836
|
+
return text.includes("\r") || /\x1b\[[0-9;?]*K/.test(text) || /\x1b\[[0-9;?]*[ABCDGHF]/.test(text);
|
|
6837
|
+
}
|
|
6838
|
+
function visibleOutputHasMultipleLines(visibleOutput) {
|
|
6839
|
+
return visibleOutput.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).length > 1;
|
|
6840
|
+
}
|
|
6841
|
+
var NOOP_SESSION_LOGGER, RECENT_INPUT_ECHO_WINDOW_MS, RECENT_INPUT_ECHO_MAX_EVENTS, RECENT_INPUT_ECHO_MAX_BYTES, RECENT_OPAQUE_INPUT_ECHO_WINDOW_MS, RESUME_OUTPUT_AGGREGATION_WINDOW_MS, RESUME_OUTPUT_AGGREGATION_MAX_BYTES, TERMINAL_ESCAPE_SEQUENCE_PATTERN, SessionManager, ActiveSession;
|
|
6523
6842
|
var init_manager3 = __esm({
|
|
6524
6843
|
"packages/server/src/session/manager.ts"() {
|
|
6525
6844
|
"use strict";
|
|
@@ -6532,6 +6851,13 @@ var init_manager3 = __esm({
|
|
|
6532
6851
|
warn: () => {
|
|
6533
6852
|
}
|
|
6534
6853
|
};
|
|
6854
|
+
RECENT_INPUT_ECHO_WINDOW_MS = 3e3;
|
|
6855
|
+
RECENT_INPUT_ECHO_MAX_EVENTS = 12;
|
|
6856
|
+
RECENT_INPUT_ECHO_MAX_BYTES = 8192;
|
|
6857
|
+
RECENT_OPAQUE_INPUT_ECHO_WINDOW_MS = 200;
|
|
6858
|
+
RESUME_OUTPUT_AGGREGATION_WINDOW_MS = 75;
|
|
6859
|
+
RESUME_OUTPUT_AGGREGATION_MAX_BYTES = 4096;
|
|
6860
|
+
TERMINAL_ESCAPE_SEQUENCE_PATTERN = /^\x1b(?:\[[0-9;?<>]*[ -/]*[@-~]|\][^\x07\x1b]*(?:\x07|\x1b\\)|P[\s\S]*?\x1b\\|[@-_])/;
|
|
6535
6861
|
SessionManager = class {
|
|
6536
6862
|
constructor(deps) {
|
|
6537
6863
|
this.deps = deps;
|
|
@@ -6708,6 +7034,7 @@ var init_manager3 = __esm({
|
|
|
6708
7034
|
this.applyTerminalInputActivity(session, activity, text, { armTurnCompletion: true });
|
|
6709
7035
|
}
|
|
6710
7036
|
applyTerminalInputActivity(session, activity, text, options) {
|
|
7037
|
+
const completedSynchronously = !options.armTurnCompletion && session.state === "idle" && !session.awaitingTurnCompletion;
|
|
6711
7038
|
if (activity === "control" || activity === "typing") {
|
|
6712
7039
|
return;
|
|
6713
7040
|
}
|
|
@@ -6716,16 +7043,10 @@ var init_manager3 = __esm({
|
|
|
6716
7043
|
session.awaitingTurnCompletion = true;
|
|
6717
7044
|
session.sawOutputSinceTurnStart = false;
|
|
6718
7045
|
}
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
session.state = "running";
|
|
6722
|
-
session.lastActiveAt = Date.now();
|
|
6723
|
-
this.deps.db.update(session.id, {
|
|
6724
|
-
state: "running",
|
|
6725
|
-
lastActiveAt: session.lastActiveAt
|
|
6726
|
-
});
|
|
6727
|
-
this.emitStateChanged(session, prev2, "running");
|
|
7046
|
+
if (completedSynchronously) {
|
|
7047
|
+
return;
|
|
6728
7048
|
}
|
|
7049
|
+
this.transitionSessionToRunning(session);
|
|
6729
7050
|
return;
|
|
6730
7051
|
}
|
|
6731
7052
|
if (activity !== "submit") return;
|
|
@@ -6738,17 +7059,11 @@ var init_manager3 = __esm({
|
|
|
6738
7059
|
session.sawOutputSinceTurnStart = false;
|
|
6739
7060
|
}
|
|
6740
7061
|
const titleChanged = this.maybeAssignTitle(session, submittedText);
|
|
6741
|
-
const prev = session.state;
|
|
6742
7062
|
const shouldResume = session.state === "idle" || session.state === "starting";
|
|
6743
|
-
if (shouldResume) {
|
|
6744
|
-
session
|
|
6745
|
-
session.lastActiveAt = Date.now();
|
|
6746
|
-
this.deps.db.update(session.id, {
|
|
6747
|
-
state: "running",
|
|
6748
|
-
lastActiveAt: session.lastActiveAt
|
|
6749
|
-
});
|
|
6750
|
-
this.emitStateChanged(session, prev, "running");
|
|
7063
|
+
if (shouldResume && !completedSynchronously) {
|
|
7064
|
+
this.transitionSessionToRunning(session);
|
|
6751
7065
|
} else if (titleChanged) {
|
|
7066
|
+
const prev = session.state;
|
|
6752
7067
|
this.emitStateChanged(session, prev, session.state);
|
|
6753
7068
|
}
|
|
6754
7069
|
}
|
|
@@ -6762,9 +7077,11 @@ var init_manager3 = __esm({
|
|
|
6762
7077
|
}
|
|
6763
7078
|
const text = activity === "submit" || activity === "internal_submit" ? submittedText ?? bytes.toString("utf-8") : void 0;
|
|
6764
7079
|
const rollbackArm = this.armTurnCompletionBeforeWrite(session, activity);
|
|
7080
|
+
const rollbackRecentInput = this.recordRecentInputBeforeWrite(session, bytes, activity);
|
|
6765
7081
|
try {
|
|
6766
7082
|
this.deps.terminalMgr.write(session.terminalId, bytes);
|
|
6767
7083
|
} catch (error) {
|
|
7084
|
+
rollbackRecentInput?.();
|
|
6768
7085
|
rollbackArm?.();
|
|
6769
7086
|
throw error;
|
|
6770
7087
|
}
|
|
@@ -6826,6 +7143,19 @@ var init_manager3 = __esm({
|
|
|
6826
7143
|
this.deps.db.update(session.id, { title });
|
|
6827
7144
|
return true;
|
|
6828
7145
|
}
|
|
7146
|
+
transitionSessionToRunning(session) {
|
|
7147
|
+
if (session.state === "running") {
|
|
7148
|
+
return;
|
|
7149
|
+
}
|
|
7150
|
+
const prev = session.state;
|
|
7151
|
+
session.state = "running";
|
|
7152
|
+
session.lastActiveAt = Date.now();
|
|
7153
|
+
this.deps.db.update(session.id, {
|
|
7154
|
+
state: "running",
|
|
7155
|
+
lastActiveAt: session.lastActiveAt
|
|
7156
|
+
});
|
|
7157
|
+
this.emitStateChanged(session, prev, "running");
|
|
7158
|
+
}
|
|
6829
7159
|
/**
|
|
6830
7160
|
* Handle terminal exit event
|
|
6831
7161
|
*/
|
|
@@ -6887,6 +7217,197 @@ var init_manager3 = __esm({
|
|
|
6887
7217
|
session.sawOutputSinceTurnStart = previousSawOutputSinceTurnStart;
|
|
6888
7218
|
};
|
|
6889
7219
|
}
|
|
7220
|
+
recordRecentInputBeforeWrite(session, bytes, activity) {
|
|
7221
|
+
if (activity === "system") {
|
|
7222
|
+
return null;
|
|
7223
|
+
}
|
|
7224
|
+
const { opaque, remainingVisibleText } = classifyRecentInputEcho(bytes);
|
|
7225
|
+
if (!opaque && remainingVisibleText.length === 0) {
|
|
7226
|
+
return null;
|
|
7227
|
+
}
|
|
7228
|
+
const recentInput = {
|
|
7229
|
+
at: Date.now(),
|
|
7230
|
+
activity,
|
|
7231
|
+
byteLength: bytes.byteLength,
|
|
7232
|
+
opaque,
|
|
7233
|
+
remainingVisibleText
|
|
7234
|
+
};
|
|
7235
|
+
session.recentInputEchoes.push(recentInput);
|
|
7236
|
+
this.pruneRecentInputEchoes(session, recentInput.at);
|
|
7237
|
+
return () => {
|
|
7238
|
+
const index = session.recentInputEchoes.indexOf(recentInput);
|
|
7239
|
+
if (index !== -1) {
|
|
7240
|
+
session.recentInputEchoes.splice(index, 1);
|
|
7241
|
+
}
|
|
7242
|
+
};
|
|
7243
|
+
}
|
|
7244
|
+
pruneRecentInputEchoes(session, now = Date.now()) {
|
|
7245
|
+
session.recentInputEchoes = session.recentInputEchoes.filter(
|
|
7246
|
+
(entry) => now - entry.at <= RECENT_INPUT_ECHO_WINDOW_MS && (entry.opaque || entry.remainingVisibleText.length > 0)
|
|
7247
|
+
);
|
|
7248
|
+
let totalBytes = session.recentInputEchoes.reduce((sum, entry) => sum + entry.byteLength, 0);
|
|
7249
|
+
while (session.recentInputEchoes.length > RECENT_INPUT_ECHO_MAX_EVENTS || totalBytes > RECENT_INPUT_ECHO_MAX_BYTES) {
|
|
7250
|
+
const removed = session.recentInputEchoes.shift();
|
|
7251
|
+
if (!removed) {
|
|
7252
|
+
break;
|
|
7253
|
+
}
|
|
7254
|
+
totalBytes -= removed.byteLength;
|
|
7255
|
+
}
|
|
7256
|
+
}
|
|
7257
|
+
clearPendingResumeAggregation(session) {
|
|
7258
|
+
if (session.pendingResumeAggregation?.timer) {
|
|
7259
|
+
clearTimeout(session.pendingResumeAggregation.timer);
|
|
7260
|
+
}
|
|
7261
|
+
session.pendingResumeAggregation = null;
|
|
7262
|
+
}
|
|
7263
|
+
consumeRecentLiteralEcho(session, visibleOutput) {
|
|
7264
|
+
let offset = 0;
|
|
7265
|
+
let matchedLiteralEcho = false;
|
|
7266
|
+
for (const entry of session.recentInputEchoes) {
|
|
7267
|
+
if (offset >= visibleOutput.length) {
|
|
7268
|
+
break;
|
|
7269
|
+
}
|
|
7270
|
+
if (entry.remainingVisibleText.length === 0) {
|
|
7271
|
+
continue;
|
|
7272
|
+
}
|
|
7273
|
+
const currentVisibleText = entry.remainingVisibleText;
|
|
7274
|
+
const matchLength = commonPrefixLength(visibleOutput.slice(offset), currentVisibleText);
|
|
7275
|
+
if (matchLength === 0) {
|
|
7276
|
+
break;
|
|
7277
|
+
}
|
|
7278
|
+
entry.remainingVisibleText = currentVisibleText.slice(matchLength);
|
|
7279
|
+
offset += matchLength;
|
|
7280
|
+
matchedLiteralEcho = true;
|
|
7281
|
+
if (matchLength < currentVisibleText.length) {
|
|
7282
|
+
break;
|
|
7283
|
+
}
|
|
7284
|
+
}
|
|
7285
|
+
return {
|
|
7286
|
+
matchedLiteralEcho,
|
|
7287
|
+
leftoverVisibleOutput: visibleOutput.slice(offset)
|
|
7288
|
+
};
|
|
7289
|
+
}
|
|
7290
|
+
hasRecentNonSubmitInput(session, now) {
|
|
7291
|
+
return session.recentInputEchoes.some(
|
|
7292
|
+
(entry) => entry.activity !== "submit" && entry.activity !== "internal_submit" && now - entry.at <= RECENT_OPAQUE_INPUT_ECHO_WINDOW_MS
|
|
7293
|
+
);
|
|
7294
|
+
}
|
|
7295
|
+
hasRecentOpaqueNonSubmitInput(session, now) {
|
|
7296
|
+
return session.recentInputEchoes.some(
|
|
7297
|
+
(entry) => entry.opaque && entry.activity !== "submit" && entry.activity !== "internal_submit" && now - entry.at <= RECENT_OPAQUE_INPUT_ECHO_WINDOW_MS
|
|
7298
|
+
);
|
|
7299
|
+
}
|
|
7300
|
+
hasRecentControlInput(session, now) {
|
|
7301
|
+
return session.recentInputEchoes.some(
|
|
7302
|
+
(entry) => entry.activity === "control" && now - entry.at <= RECENT_OPAQUE_INPUT_ECHO_WINDOW_MS
|
|
7303
|
+
);
|
|
7304
|
+
}
|
|
7305
|
+
getRecentVisibleInputText(session) {
|
|
7306
|
+
return session.recentInputEchoes.filter((entry) => entry.activity !== "submit" && entry.activity !== "internal_submit").map((entry) => entry.remainingVisibleText).join("");
|
|
7307
|
+
}
|
|
7308
|
+
isLikelyPureInputRepaint(session, chunk, visibleOutput, now) {
|
|
7309
|
+
if (!this.hasRecentNonSubmitInput(session, now)) {
|
|
7310
|
+
return false;
|
|
7311
|
+
}
|
|
7312
|
+
if (!visibleOutput.trim()) {
|
|
7313
|
+
return true;
|
|
7314
|
+
}
|
|
7315
|
+
const chunkText = chunk.toString("utf8");
|
|
7316
|
+
if (chunkText.includes("\n")) {
|
|
7317
|
+
return false;
|
|
7318
|
+
}
|
|
7319
|
+
if (visibleOutputHasMultipleLines(visibleOutput)) {
|
|
7320
|
+
return false;
|
|
7321
|
+
}
|
|
7322
|
+
if (!chunkHasLineRepaintControl(chunkText)) {
|
|
7323
|
+
return false;
|
|
7324
|
+
}
|
|
7325
|
+
if (this.hasRecentOpaqueNonSubmitInput(session, now)) {
|
|
7326
|
+
return true;
|
|
7327
|
+
}
|
|
7328
|
+
const recentVisibleInputText = this.getRecentVisibleInputText(session);
|
|
7329
|
+
const trimmedVisibleOutput = visibleOutput.trim();
|
|
7330
|
+
if (!recentVisibleInputText || !trimmedVisibleOutput) {
|
|
7331
|
+
return false;
|
|
7332
|
+
}
|
|
7333
|
+
return recentVisibleInputText === trimmedVisibleOutput || recentVisibleInputText.startsWith(trimmedVisibleOutput) || trimmedVisibleOutput.endsWith(recentVisibleInputText);
|
|
7334
|
+
}
|
|
7335
|
+
assessTerminalOutput(session, chunk) {
|
|
7336
|
+
const now = Date.now();
|
|
7337
|
+
this.pruneRecentInputEchoes(session, now);
|
|
7338
|
+
if (session.recentInputEchoes.length === 0) {
|
|
7339
|
+
const hasVisibleText = extractVisibleTerminalText(chunk).trim().length > 0;
|
|
7340
|
+
return {
|
|
7341
|
+
shouldResumeRunning: hasVisibleText,
|
|
7342
|
+
countsAsTurnOutput: hasVisibleText,
|
|
7343
|
+
shouldAggregateForResume: false
|
|
7344
|
+
};
|
|
7345
|
+
}
|
|
7346
|
+
const visibleOutput = extractVisibleTerminalText(chunk);
|
|
7347
|
+
const { matchedLiteralEcho, leftoverVisibleOutput } = this.consumeRecentLiteralEcho(
|
|
7348
|
+
session,
|
|
7349
|
+
visibleOutput
|
|
7350
|
+
);
|
|
7351
|
+
const hasRecentNonSubmitInput = this.hasRecentNonSubmitInput(session, now);
|
|
7352
|
+
const hasRecentControlInput = this.hasRecentControlInput(session, now);
|
|
7353
|
+
this.pruneRecentInputEchoes(session, now);
|
|
7354
|
+
const trimmedLeftoverVisibleOutput = leftoverVisibleOutput.trim();
|
|
7355
|
+
const trimmedVisibleOutput = visibleOutput.trim();
|
|
7356
|
+
const suppressImmediateResumeAfterControl = hasRecentControlInput && !matchedLiteralEcho;
|
|
7357
|
+
const chunkText = chunk.toString("utf8");
|
|
7358
|
+
const shouldAggregateForResume = session.state === "idle" && hasRecentNonSubmitInput && !matchedLiteralEcho && trimmedVisibleOutput.length > 0 && chunkHasLineRepaintControl(chunkText) && !visibleOutputHasMultipleLines(visibleOutput);
|
|
7359
|
+
const shouldResumeRunning = suppressImmediateResumeAfterControl ? false : trimmedLeftoverVisibleOutput.length > 0 ? !this.isLikelyPureInputRepaint(session, chunk, leftoverVisibleOutput, now) : !matchedLiteralEcho && trimmedVisibleOutput.length > 0 && !this.isLikelyPureInputRepaint(session, chunk, visibleOutput, now);
|
|
7360
|
+
const countsAsTurnOutput = !suppressImmediateResumeAfterControl && (trimmedLeftoverVisibleOutput.length > 0 ? !this.isLikelyPureInputRepaint(session, chunk, leftoverVisibleOutput, now) : !matchedLiteralEcho && trimmedVisibleOutput.length > 0 && !this.isLikelyPureInputRepaint(session, chunk, visibleOutput, now));
|
|
7361
|
+
return {
|
|
7362
|
+
shouldResumeRunning,
|
|
7363
|
+
countsAsTurnOutput,
|
|
7364
|
+
shouldAggregateForResume
|
|
7365
|
+
};
|
|
7366
|
+
}
|
|
7367
|
+
flushPendingResumeAggregation(session) {
|
|
7368
|
+
const pending = session.pendingResumeAggregation;
|
|
7369
|
+
if (!pending) {
|
|
7370
|
+
return;
|
|
7371
|
+
}
|
|
7372
|
+
this.clearPendingResumeAggregation(session);
|
|
7373
|
+
if (session.state !== "idle") {
|
|
7374
|
+
return;
|
|
7375
|
+
}
|
|
7376
|
+
const combinedChunk = Buffer.concat(pending.chunks, pending.byteLength);
|
|
7377
|
+
const outputAssessment = this.assessTerminalOutput(session, combinedChunk);
|
|
7378
|
+
if (outputAssessment.countsAsTurnOutput) {
|
|
7379
|
+
session.sawOutputSinceTurnStart = true;
|
|
7380
|
+
}
|
|
7381
|
+
}
|
|
7382
|
+
schedulePendingResumeAggregation(session, chunk) {
|
|
7383
|
+
const pending = session.pendingResumeAggregation;
|
|
7384
|
+
if (!pending) {
|
|
7385
|
+
const nextPending = {
|
|
7386
|
+
startedAt: Date.now(),
|
|
7387
|
+
chunks: [chunk],
|
|
7388
|
+
byteLength: chunk.byteLength,
|
|
7389
|
+
timer: null
|
|
7390
|
+
};
|
|
7391
|
+
nextPending.timer = setTimeout(() => {
|
|
7392
|
+
const activeSession = this.sessions.get(session.id);
|
|
7393
|
+
if (!activeSession) {
|
|
7394
|
+
return;
|
|
7395
|
+
}
|
|
7396
|
+
this.flushPendingResumeAggregation(activeSession);
|
|
7397
|
+
}, RESUME_OUTPUT_AGGREGATION_WINDOW_MS);
|
|
7398
|
+
session.pendingResumeAggregation = nextPending;
|
|
7399
|
+
return;
|
|
7400
|
+
}
|
|
7401
|
+
pending.chunks.push(chunk);
|
|
7402
|
+
pending.byteLength += chunk.byteLength;
|
|
7403
|
+
const combinedChunk = Buffer.concat(pending.chunks, pending.byteLength);
|
|
7404
|
+
const visibleOutput = extractVisibleTerminalText(combinedChunk);
|
|
7405
|
+
const combinedText = combinedChunk.toString("utf8");
|
|
7406
|
+
const shouldFlushNow = pending.byteLength >= RESUME_OUTPUT_AGGREGATION_MAX_BYTES || combinedText.includes("\n") || visibleOutputHasMultipleLines(visibleOutput);
|
|
7407
|
+
if (shouldFlushNow) {
|
|
7408
|
+
this.flushPendingResumeAggregation(session);
|
|
7409
|
+
}
|
|
7410
|
+
}
|
|
6890
7411
|
flushPendingPtyIdle(session) {
|
|
6891
7412
|
const ptyState = this.comparators.get(session.id)?.snapshot().ptyState;
|
|
6892
7413
|
if (ptyState !== "idle") {
|
|
@@ -6895,6 +7416,7 @@ var init_manager3 = __esm({
|
|
|
6895
7416
|
this.transitionSessionToIdle(session);
|
|
6896
7417
|
}
|
|
6897
7418
|
transitionSessionToIdle(activeSession) {
|
|
7419
|
+
this.clearPendingResumeAggregation(activeSession);
|
|
6898
7420
|
const prev = activeSession.state;
|
|
6899
7421
|
if (prev !== "running" && prev !== "starting") {
|
|
6900
7422
|
return;
|
|
@@ -6957,7 +7479,24 @@ var init_manager3 = __esm({
|
|
|
6957
7479
|
return;
|
|
6958
7480
|
}
|
|
6959
7481
|
const activeSession = this.sessions.get(session.id);
|
|
6960
|
-
if (activeSession
|
|
7482
|
+
if (!activeSession) {
|
|
7483
|
+
return;
|
|
7484
|
+
}
|
|
7485
|
+
if (activeSession.pendingResumeAggregation && activeSession.state !== "idle") {
|
|
7486
|
+
this.clearPendingResumeAggregation(activeSession);
|
|
7487
|
+
}
|
|
7488
|
+
if (activeSession.pendingResumeAggregation) {
|
|
7489
|
+
this.schedulePendingResumeAggregation(activeSession, event.chunk);
|
|
7490
|
+
detector.feed(event.chunk);
|
|
7491
|
+
return;
|
|
7492
|
+
}
|
|
7493
|
+
const outputAssessment = this.assessTerminalOutput(activeSession, event.chunk);
|
|
7494
|
+
if (outputAssessment.shouldAggregateForResume) {
|
|
7495
|
+
this.schedulePendingResumeAggregation(activeSession, event.chunk);
|
|
7496
|
+
detector.feed(event.chunk);
|
|
7497
|
+
return;
|
|
7498
|
+
}
|
|
7499
|
+
if (outputAssessment.countsAsTurnOutput) {
|
|
6961
7500
|
activeSession.sawOutputSinceTurnStart = true;
|
|
6962
7501
|
}
|
|
6963
7502
|
detector.feed(event.chunk);
|
|
@@ -6974,6 +7513,7 @@ var init_manager3 = __esm({
|
|
|
6974
7513
|
this.comparators.delete(sessionId);
|
|
6975
7514
|
}
|
|
6976
7515
|
finishSession(session, exitCode) {
|
|
7516
|
+
this.clearPendingResumeAggregation(session);
|
|
6977
7517
|
const prev = session.state;
|
|
6978
7518
|
session.state = "ended";
|
|
6979
7519
|
session.endedAt = Date.now();
|
|
@@ -7005,6 +7545,8 @@ var init_manager3 = __esm({
|
|
|
7005
7545
|
latestSubmittedUserInput;
|
|
7006
7546
|
awaitingTurnCompletion = false;
|
|
7007
7547
|
sawOutputSinceTurnStart = false;
|
|
7548
|
+
recentInputEchoes = [];
|
|
7549
|
+
pendingResumeAggregation = null;
|
|
7008
7550
|
constructor(data) {
|
|
7009
7551
|
this.id = data.id;
|
|
7010
7552
|
this.workspaceId = data.workspaceId;
|
|
@@ -7047,17 +7589,17 @@ var init_manager3 = __esm({
|
|
|
7047
7589
|
});
|
|
7048
7590
|
|
|
7049
7591
|
// packages/server/src/storage/repositories/auth-login-block-repo.ts
|
|
7050
|
-
function
|
|
7592
|
+
function isRecord3(value) {
|
|
7051
7593
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
7052
7594
|
}
|
|
7053
7595
|
function isAuthLoginBlockRecord(value) {
|
|
7054
|
-
if (!
|
|
7596
|
+
if (!isRecord3(value)) {
|
|
7055
7597
|
return false;
|
|
7056
7598
|
}
|
|
7057
7599
|
return typeof value.ip === "string" && typeof value.failedCount === "number" && typeof value.firstFailedAt === "number" && typeof value.lastFailedAt === "number" && (typeof value.blockedUntil === "number" || value.blockedUntil === null);
|
|
7058
7600
|
}
|
|
7059
7601
|
function normalizeFailures(value) {
|
|
7060
|
-
if (!
|
|
7602
|
+
if (!isRecord3(value)) {
|
|
7061
7603
|
return {};
|
|
7062
7604
|
}
|
|
7063
7605
|
const normalized = {};
|
|
@@ -7071,9 +7613,9 @@ function normalizeFailures(value) {
|
|
|
7071
7613
|
return normalized;
|
|
7072
7614
|
}
|
|
7073
7615
|
function normalizeState(value) {
|
|
7074
|
-
if (
|
|
7616
|
+
if (isRecord3(value) && value.version === 1) {
|
|
7075
7617
|
const blocks = {};
|
|
7076
|
-
if (
|
|
7618
|
+
if (isRecord3(value.blocks)) {
|
|
7077
7619
|
for (const entry of Object.values(value.blocks)) {
|
|
7078
7620
|
if (isAuthLoginBlockRecord(entry)) {
|
|
7079
7621
|
blocks[entry.ip] = entry;
|
|
@@ -7086,7 +7628,7 @@ function normalizeState(value) {
|
|
|
7086
7628
|
failures: normalizeFailures(value.failures)
|
|
7087
7629
|
};
|
|
7088
7630
|
}
|
|
7089
|
-
if (
|
|
7631
|
+
if (isRecord3(value)) {
|
|
7090
7632
|
const blocks = {};
|
|
7091
7633
|
for (const [ip, entry] of Object.entries(value)) {
|
|
7092
7634
|
if (isAuthLoginBlockRecord(entry)) {
|
|
@@ -7192,17 +7734,17 @@ var init_auth_login_block_repo = __esm({
|
|
|
7192
7734
|
});
|
|
7193
7735
|
|
|
7194
7736
|
// packages/server/src/storage/repositories/auth-session-repo.ts
|
|
7195
|
-
function
|
|
7737
|
+
function isRecord4(value) {
|
|
7196
7738
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
7197
7739
|
}
|
|
7198
7740
|
function isAuthSession(value) {
|
|
7199
|
-
if (!
|
|
7741
|
+
if (!isRecord4(value)) {
|
|
7200
7742
|
return false;
|
|
7201
7743
|
}
|
|
7202
7744
|
return typeof value.token === "string" && typeof value.createdAt === "number" && typeof value.lastSeenAt === "number";
|
|
7203
7745
|
}
|
|
7204
7746
|
function normalizeSessionFile2(value) {
|
|
7205
|
-
if (
|
|
7747
|
+
if (isRecord4(value) && value.version === 1 && isRecord4(value.sessions)) {
|
|
7206
7748
|
const normalized = {};
|
|
7207
7749
|
for (const entry of Object.values(value.sessions)) {
|
|
7208
7750
|
if (isAuthSession(entry)) {
|
|
@@ -7220,7 +7762,7 @@ function normalizeSessionFile2(value) {
|
|
|
7220
7762
|
}
|
|
7221
7763
|
return normalized;
|
|
7222
7764
|
}
|
|
7223
|
-
if (
|
|
7765
|
+
if (isRecord4(value)) {
|
|
7224
7766
|
const normalized = {};
|
|
7225
7767
|
for (const [token, entry] of Object.entries(value)) {
|
|
7226
7768
|
if (isAuthSession(entry)) {
|
|
@@ -7295,14 +7837,14 @@ var init_auth_session_repo = __esm({
|
|
|
7295
7837
|
});
|
|
7296
7838
|
|
|
7297
7839
|
// packages/server/src/storage/repositories/provider-config-repo.ts
|
|
7298
|
-
function
|
|
7840
|
+
function isRecord5(value) {
|
|
7299
7841
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
7300
7842
|
}
|
|
7301
7843
|
function normalizeProviderConfigFile(value) {
|
|
7302
|
-
if (
|
|
7844
|
+
if (isRecord5(value) && value.version === 1 && isRecord5(value.providers)) {
|
|
7303
7845
|
return value.providers;
|
|
7304
7846
|
}
|
|
7305
|
-
if (
|
|
7847
|
+
if (isRecord5(value)) {
|
|
7306
7848
|
return value;
|
|
7307
7849
|
}
|
|
7308
7850
|
return {};
|
|
@@ -7376,14 +7918,14 @@ var init_provider_config_repo = __esm({
|
|
|
7376
7918
|
});
|
|
7377
7919
|
|
|
7378
7920
|
// packages/server/src/storage/repositories/settings-repo.ts
|
|
7379
|
-
function
|
|
7921
|
+
function isRecord6(value) {
|
|
7380
7922
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
7381
7923
|
}
|
|
7382
7924
|
function normalizeSettingsFile(value) {
|
|
7383
|
-
if (
|
|
7925
|
+
if (isRecord6(value) && value.version === 1 && isRecord6(value.settings)) {
|
|
7384
7926
|
return { ...value.settings };
|
|
7385
7927
|
}
|
|
7386
|
-
if (
|
|
7928
|
+
if (isRecord6(value)) {
|
|
7387
7929
|
return { ...value };
|
|
7388
7930
|
}
|
|
7389
7931
|
return {};
|
|
@@ -7534,11 +8076,11 @@ var init_supervisor_repo = __esm({
|
|
|
7534
8076
|
});
|
|
7535
8077
|
|
|
7536
8078
|
// packages/server/src/storage/repositories/terminal-repo.ts
|
|
7537
|
-
function
|
|
8079
|
+
function isRecord7(value) {
|
|
7538
8080
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
7539
8081
|
}
|
|
7540
8082
|
function isTerminal(value) {
|
|
7541
|
-
if (!
|
|
8083
|
+
if (!isRecord7(value)) {
|
|
7542
8084
|
return false;
|
|
7543
8085
|
}
|
|
7544
8086
|
return typeof value.id === "string" && typeof value.workspaceId === "string" && (value.kind === "agent" || value.kind === "shell") && typeof value.cwd === "string" && Array.isArray(value.argv) && typeof value.cols === "number" && typeof value.rows === "number" && typeof value.alive === "boolean" && typeof value.createdAt === "number";
|
|
@@ -7550,7 +8092,7 @@ function normalizeTerminal(value) {
|
|
|
7550
8092
|
};
|
|
7551
8093
|
}
|
|
7552
8094
|
function normalizeTerminalFile(value) {
|
|
7553
|
-
if (
|
|
8095
|
+
if (isRecord7(value) && value.version === 1 && isRecord7(value.terminals)) {
|
|
7554
8096
|
const normalized = {};
|
|
7555
8097
|
for (const entry of Object.values(value.terminals)) {
|
|
7556
8098
|
if (isTerminal(entry)) {
|
|
@@ -7568,7 +8110,7 @@ function normalizeTerminalFile(value) {
|
|
|
7568
8110
|
}
|
|
7569
8111
|
return normalized;
|
|
7570
8112
|
}
|
|
7571
|
-
if (
|
|
8113
|
+
if (isRecord7(value)) {
|
|
7572
8114
|
const normalized = {};
|
|
7573
8115
|
for (const [terminalId, entry] of Object.entries(value)) {
|
|
7574
8116
|
if (isTerminal(entry)) {
|
|
@@ -7734,12 +8276,12 @@ var init_terminal_repo = __esm({
|
|
|
7734
8276
|
});
|
|
7735
8277
|
|
|
7736
8278
|
// packages/server/src/storage/repositories/update-state-repo.ts
|
|
7737
|
-
function
|
|
8279
|
+
function isRecord8(value) {
|
|
7738
8280
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
7739
8281
|
}
|
|
7740
8282
|
function normalizeUpdateState(value, currentVersion) {
|
|
7741
8283
|
const defaults = createDefaultUpdateState(currentVersion);
|
|
7742
|
-
if (!
|
|
8284
|
+
if (!isRecord8(value)) {
|
|
7743
8285
|
return defaults;
|
|
7744
8286
|
}
|
|
7745
8287
|
return {
|
|
@@ -7802,17 +8344,17 @@ var init_update_state_repo = __esm({
|
|
|
7802
8344
|
});
|
|
7803
8345
|
|
|
7804
8346
|
// packages/server/src/storage/repositories/workspace-repo.ts
|
|
7805
|
-
function
|
|
8347
|
+
function isRecord9(value) {
|
|
7806
8348
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
7807
8349
|
}
|
|
7808
8350
|
function isWorkspace(value) {
|
|
7809
|
-
if (!
|
|
8351
|
+
if (!isRecord9(value)) {
|
|
7810
8352
|
return false;
|
|
7811
8353
|
}
|
|
7812
|
-
return typeof value.id === "string" && typeof value.path === "string" && (value.targetRuntime === "native" || value.targetRuntime === "wsl") && typeof value.openedAt === "number" && typeof value.lastActiveAt === "number" &&
|
|
8354
|
+
return typeof value.id === "string" && typeof value.path === "string" && (value.targetRuntime === "native" || value.targetRuntime === "wsl") && typeof value.openedAt === "number" && typeof value.lastActiveAt === "number" && isRecord9(value.uiState);
|
|
7813
8355
|
}
|
|
7814
8356
|
function normalizeWorkspaceFile(value) {
|
|
7815
|
-
if (
|
|
8357
|
+
if (isRecord9(value) && value.version === 1 && isRecord9(value.workspaces)) {
|
|
7816
8358
|
const normalized = {};
|
|
7817
8359
|
for (const entry of Object.values(value.workspaces)) {
|
|
7818
8360
|
if (isWorkspace(entry)) {
|
|
@@ -7830,7 +8372,7 @@ function normalizeWorkspaceFile(value) {
|
|
|
7830
8372
|
}
|
|
7831
8373
|
return normalized;
|
|
7832
8374
|
}
|
|
7833
|
-
if (
|
|
8375
|
+
if (isRecord9(value)) {
|
|
7834
8376
|
const normalized = {};
|
|
7835
8377
|
for (const [workspaceId, entry] of Object.entries(value)) {
|
|
7836
8378
|
if (isWorkspace(entry)) {
|
|
@@ -8075,13 +8617,13 @@ function ensureNodePtySpawnHelperExecutable(deps = {}) {
|
|
|
8075
8617
|
return;
|
|
8076
8618
|
}
|
|
8077
8619
|
const arch = deps.arch ?? process.arch;
|
|
8078
|
-
const
|
|
8620
|
+
const resolve6 = deps.resolve ?? ((id) => require4.resolve(id));
|
|
8079
8621
|
const fileExists = deps.existsSync ?? existsSync6;
|
|
8080
|
-
const
|
|
8622
|
+
const stat11 = deps.statSync ?? statSync;
|
|
8081
8623
|
const chmod = deps.chmodSync ?? chmodSync2;
|
|
8082
8624
|
let packageJsonPath;
|
|
8083
8625
|
try {
|
|
8084
|
-
packageJsonPath =
|
|
8626
|
+
packageJsonPath = resolve6(NODE_PTY_PKG);
|
|
8085
8627
|
} catch {
|
|
8086
8628
|
return;
|
|
8087
8629
|
}
|
|
@@ -8095,7 +8637,7 @@ function ensureNodePtySpawnHelperExecutable(deps = {}) {
|
|
|
8095
8637
|
if (!fileExists(helperPath)) {
|
|
8096
8638
|
return;
|
|
8097
8639
|
}
|
|
8098
|
-
const currentMode =
|
|
8640
|
+
const currentMode = stat11(helperPath).mode;
|
|
8099
8641
|
const executableMode = currentMode | 73;
|
|
8100
8642
|
if (executableMode === currentMode) {
|
|
8101
8643
|
return;
|
|
@@ -8146,7 +8688,7 @@ async function escalateKillWithPolling(pid, signal, options) {
|
|
|
8146
8688
|
const startTime = Date.now();
|
|
8147
8689
|
const deadline = startTime + timeoutMs;
|
|
8148
8690
|
while (Date.now() < deadline) {
|
|
8149
|
-
await new Promise((
|
|
8691
|
+
await new Promise((resolve6) => setTimeout(resolve6, pollIntervalMs));
|
|
8150
8692
|
if (!isProcessAlive(pid)) {
|
|
8151
8693
|
return true;
|
|
8152
8694
|
}
|
|
@@ -8301,13 +8843,19 @@ import { spawn as spawn3 } from "node:child_process";
|
|
|
8301
8843
|
function buildPrompt(context, mode) {
|
|
8302
8844
|
if (mode === "decompose") {
|
|
8303
8845
|
return [
|
|
8304
|
-
"You are an autonomous supervisor for
|
|
8846
|
+
"You are an autonomous planner-supervisor for this target-scoped software task.",
|
|
8847
|
+
"Your purpose is to drive the work from objective to high-quality delivery with minimal babysitting.",
|
|
8848
|
+
'Do not optimize for merely reaching "done"; optimize for a result that is correct, verified, coherent, and not obviously low-quality or rushed.',
|
|
8305
8849
|
"Your first job is to decompose the target into a supervision structure before evaluation begins.",
|
|
8306
8850
|
"",
|
|
8307
8851
|
"Return JSON only.",
|
|
8308
8852
|
"No prose before or after the JSON.",
|
|
8309
8853
|
"",
|
|
8310
8854
|
"Decomposition policy:",
|
|
8855
|
+
"- Create an execution plan, not just a task list.",
|
|
8856
|
+
"- Break the objective into the smallest reasonable set of milestones that maximize clarity, reduce uncertainty, and preserve steady forward progress.",
|
|
8857
|
+
"- Order milestones by dependency, risk reduction, and delivery leverage.",
|
|
8858
|
+
"- Prefer a plan structure that makes execution easier, verification clearer, and replanning cheaper.",
|
|
8311
8859
|
"- Do not ask the user any questions.",
|
|
8312
8860
|
"- Do not ask for clarification, confirmation, or approval.",
|
|
8313
8861
|
"- Do not propose options for the user to choose from.",
|
|
@@ -8321,6 +8869,30 @@ function buildPrompt(context, mode) {
|
|
|
8321
8869
|
"- Each item must be concrete, milestone-sized, and useful for subsequent evaluation.",
|
|
8322
8870
|
"- Do not leave the structure empty.",
|
|
8323
8871
|
"",
|
|
8872
|
+
"Decomposition principles:",
|
|
8873
|
+
"- Prefer milestones that produce a concrete artifact, observable behavior change, test result, or verification result.",
|
|
8874
|
+
"- Make dependencies explicit.",
|
|
8875
|
+
"- Separate implementation, verification, integration, and cleanup when that improves delivery reliability.",
|
|
8876
|
+
"- If a step is too vague to verify independently, split it further.",
|
|
8877
|
+
"- Prefer plans that keep the agent moving with minimal ambiguity between milestones.",
|
|
8878
|
+
"- Use stage-based planning by default unless there are clearly independent deliverables that justify subtargets.",
|
|
8879
|
+
"- Build the plan so it can recover from failed attempts: prefer decompositions that allow narrowing scope, isolating failures, checking assumptions, and restoring a working baseline when needed.",
|
|
8880
|
+
"- Keep the decomposition practical for execution, not merely neat on paper.",
|
|
8881
|
+
"",
|
|
8882
|
+
"Planning quality bar:",
|
|
8883
|
+
"- Prefer fewer, stronger milestones over many thin or vague ones.",
|
|
8884
|
+
"- Every item should imply a concrete deliverable and observable acceptance criteria.",
|
|
8885
|
+
'- Avoid vague items such as "improve", "clean up", or "refactor" unless tied to a specific delivery or verification target.',
|
|
8886
|
+
"- Include quality and verification checkpoints where they materially improve the final result.",
|
|
8887
|
+
"- Do not decompose in a way that encourages superficial completion.",
|
|
8888
|
+
"",
|
|
8889
|
+
"Planning boundary:",
|
|
8890
|
+
"- You are responsible for execution structure, sequencing, quality control, and verification structure.",
|
|
8891
|
+
"- Do not hard-code unnecessary implementation detail too early.",
|
|
8892
|
+
"- If multiple implementation paths exist, prefer a plan that keeps execution adaptable until evidence makes one path clearly better.",
|
|
8893
|
+
"- Do not hide assumptions inside the plan.",
|
|
8894
|
+
"- Do not create a brittle plan that depends on perfect execution.",
|
|
8895
|
+
"",
|
|
8324
8896
|
"Item requirements:",
|
|
8325
8897
|
'- Each item must include "id", "kind", "title", "objective", "deliverable", "acceptanceCriteria", and "status".',
|
|
8326
8898
|
'- "kind" must match the selected decompositionMode: all "stage" or all "subtarget".',
|
|
@@ -8353,8 +8925,13 @@ function buildPrompt(context, mode) {
|
|
|
8353
8925
|
].join("\n");
|
|
8354
8926
|
}
|
|
8355
8927
|
const lines = [
|
|
8356
|
-
"You are an autonomous supervisor for
|
|
8357
|
-
"Your
|
|
8928
|
+
"You are an autonomous planner-supervisor for this target-scoped software task.",
|
|
8929
|
+
"Your purpose is to drive the work from objective to high-quality delivery with minimal babysitting.",
|
|
8930
|
+
'Do not optimize for merely reaching "done"; optimize for a result that is correct, verified, coherent, and not obviously low-quality or rushed.',
|
|
8931
|
+
"Act as an autonomous execution supervisor.",
|
|
8932
|
+
"Your job is to keep the agent moving toward the objective, maintain delivery quality, detect low-yield paths early, and redirect work when needed.",
|
|
8933
|
+
"Do not passively observe progress; actively steer it toward successful, high-quality completion.",
|
|
8934
|
+
"Drive execution through the supervised agent rather than by independently performing the work yourself.",
|
|
8358
8935
|
"",
|
|
8359
8936
|
"Return JSON only.",
|
|
8360
8937
|
"No prose before or after the JSON.",
|
|
@@ -8385,7 +8962,12 @@ function buildPrompt(context, mode) {
|
|
|
8385
8962
|
'- When advancing to the next item, mark the previous item as "done" and set activeItemId to the next item explicitly.',
|
|
8386
8963
|
"- If the active item is blocked, give guidance that is most likely to unblock it.",
|
|
8387
8964
|
"- If the active item is obsolete, explain the reason briefly and move to the next useful item.",
|
|
8388
|
-
"- If the
|
|
8965
|
+
"- If the current path is low-yield, brittle, repetitive, or producing low-quality output, redirect early.",
|
|
8966
|
+
"- Diagnose stalls precisely: implementation failure, verification failure, environment failure, scope misframing, weak solution quality, or missing evidence.",
|
|
8967
|
+
"- Choose the next action that most improves objective-level progress, not merely the most local continuation.",
|
|
8968
|
+
"- Do not repeat the same tactic after failure unless new evidence justifies retrying it.",
|
|
8969
|
+
"- Maintain commitment to the objective, not blind commitment to the current tactic.",
|
|
8970
|
+
"- Replan locally when needed, but keep the overall execution coherent and objective-driven.",
|
|
8389
8971
|
"- Do not rewrite the decomposition structure during normal evaluation cycles.",
|
|
8390
8972
|
"",
|
|
8391
8973
|
"Allowed statuses:",
|
|
@@ -8410,6 +8992,21 @@ function buildPrompt(context, mode) {
|
|
|
8410
8992
|
"- If implementation is needed, point to the likely area, behavior, or file/module based on available evidence.",
|
|
8411
8993
|
"- If the agent asked a question, answer it directly in the guidance and continue with a concrete next action.",
|
|
8412
8994
|
"",
|
|
8995
|
+
"Delivery quality bar:",
|
|
8996
|
+
"- Do not accept shallow, brittle, or obviously rushed solutions.",
|
|
8997
|
+
"- Do not optimize for the smallest change if it leads to poor maintainability, weak verification, or fragile behavior.",
|
|
8998
|
+
"- Prefer solutions that are robust, coherent with the existing codebase, and likely to hold up beyond the happy path.",
|
|
8999
|
+
"- Require appropriate verification for the kind of work being done.",
|
|
9000
|
+
"- Consider edge cases, integration impact, regressions, and maintainability where relevant.",
|
|
9001
|
+
"- If a solution technically works but is low-quality, incomplete, poorly verified, or obviously a shortcut, treat the milestone as not yet complete.",
|
|
9002
|
+
"- Do not let superficial progress masquerade as real delivery.",
|
|
9003
|
+
"",
|
|
9004
|
+
"Completion standard:",
|
|
9005
|
+
"- A milestone is complete only when its deliverable and acceptanceCriteria are supported by observable evidence and the result meets a reasonable quality bar.",
|
|
9006
|
+
"- The objective is complete only when the final result is implemented, verified, and not obviously compromised in quality.",
|
|
9007
|
+
"- Do not mark work complete merely because code was changed, a command passed once, or a minimal patch exists.",
|
|
9008
|
+
"- Optimize for finished, verified, and defensible delivery.",
|
|
9009
|
+
"",
|
|
8413
9010
|
"Evaluation policy:",
|
|
8414
9011
|
"- Update progress incrementally against the existing decomposition.",
|
|
8415
9012
|
"- Use itemUpdates to reflect evidence-backed status changes only.",
|
|
@@ -8455,7 +9052,7 @@ async function runCommand(command, timeoutMs, options = {}) {
|
|
|
8455
9052
|
if (options.signal?.aborted) {
|
|
8456
9053
|
throw createSupervisorEvalAbortedError();
|
|
8457
9054
|
}
|
|
8458
|
-
return await new Promise((
|
|
9055
|
+
return await new Promise((resolve6, reject) => {
|
|
8459
9056
|
const child = spawn3(command.argv[0], command.argv.slice(1), {
|
|
8460
9057
|
cwd: command.cwd,
|
|
8461
9058
|
detached: process.platform !== "win32",
|
|
@@ -8485,7 +9082,7 @@ async function runCommand(command, timeoutMs, options = {}) {
|
|
|
8485
9082
|
}
|
|
8486
9083
|
settled = true;
|
|
8487
9084
|
cleanup();
|
|
8488
|
-
|
|
9085
|
+
resolve6(value);
|
|
8489
9086
|
};
|
|
8490
9087
|
const terminate = (error) => {
|
|
8491
9088
|
if (terminationError) {
|
|
@@ -9313,12 +9910,12 @@ var init_scheduler = __esm({
|
|
|
9313
9910
|
|
|
9314
9911
|
// packages/server/src/supervisor/manager.ts
|
|
9315
9912
|
function createDeferredCompletion() {
|
|
9316
|
-
let
|
|
9913
|
+
let resolve6 = () => {
|
|
9317
9914
|
};
|
|
9318
9915
|
const promise = new Promise((innerResolve) => {
|
|
9319
|
-
|
|
9916
|
+
resolve6 = innerResolve;
|
|
9320
9917
|
});
|
|
9321
|
-
return { promise, resolve:
|
|
9918
|
+
return { promise, resolve: resolve6 };
|
|
9322
9919
|
}
|
|
9323
9920
|
function generateSupervisorId() {
|
|
9324
9921
|
return `sup_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
|
|
@@ -10673,10 +11270,10 @@ var init_manager4 = __esm({
|
|
|
10673
11270
|
if (signal?.aborted) {
|
|
10674
11271
|
throw { code: "supervisor_eval_aborted", message: "Supervisor evaluator aborted" };
|
|
10675
11272
|
}
|
|
10676
|
-
await new Promise((
|
|
11273
|
+
await new Promise((resolve6, reject) => {
|
|
10677
11274
|
const timer = setTimeout(() => {
|
|
10678
11275
|
signal?.removeEventListener("abort", onAbort);
|
|
10679
|
-
|
|
11276
|
+
resolve6();
|
|
10680
11277
|
}, delayMs);
|
|
10681
11278
|
timer.unref?.();
|
|
10682
11279
|
const onAbort = () => {
|
|
@@ -10707,31 +11304,31 @@ __export(target_store_exports, {
|
|
|
10707
11304
|
saveTargetMemory: () => saveTargetMemory,
|
|
10708
11305
|
saveTargetMeta: () => saveTargetMeta
|
|
10709
11306
|
});
|
|
10710
|
-
import { mkdir as mkdir4, mkdtemp as mkdtemp2, readdir as readdir2, readFile as readFile3, rename, rm as
|
|
10711
|
-
import { dirname as dirname6, join as
|
|
11307
|
+
import { mkdir as mkdir4, mkdtemp as mkdtemp2, readdir as readdir2, readFile as readFile3, rename, rm as rm6, writeFile as writeFile4 } from "node:fs/promises";
|
|
11308
|
+
import { dirname as dirname6, join as join9 } from "node:path";
|
|
10712
11309
|
function targetDir(workspacePath, targetId) {
|
|
10713
|
-
return
|
|
11310
|
+
return join9(workspacePath, ".coder-studio", "supervisor", "targets", targetId);
|
|
10714
11311
|
}
|
|
10715
11312
|
function metaPath(workspacePath, targetId) {
|
|
10716
|
-
return
|
|
11313
|
+
return join9(targetDir(workspacePath, targetId), "meta.json");
|
|
10717
11314
|
}
|
|
10718
11315
|
function memoryPath(workspacePath, targetId) {
|
|
10719
|
-
return
|
|
11316
|
+
return join9(targetDir(workspacePath, targetId), "memory.json");
|
|
10720
11317
|
}
|
|
10721
11318
|
function cyclesPath(workspacePath, targetId) {
|
|
10722
|
-
return
|
|
11319
|
+
return join9(targetDir(workspacePath, targetId), "cycles.jsonl");
|
|
10723
11320
|
}
|
|
10724
11321
|
function targetsRoot(workspacePath) {
|
|
10725
|
-
return
|
|
11322
|
+
return join9(workspacePath, ".coder-studio", "supervisor", "targets");
|
|
10726
11323
|
}
|
|
10727
11324
|
function metaFilePath(dirPath) {
|
|
10728
|
-
return
|
|
11325
|
+
return join9(dirPath, "meta.json");
|
|
10729
11326
|
}
|
|
10730
11327
|
function memoryFilePath(dirPath) {
|
|
10731
|
-
return
|
|
11328
|
+
return join9(dirPath, "memory.json");
|
|
10732
11329
|
}
|
|
10733
11330
|
function cyclesFilePath(dirPath) {
|
|
10734
|
-
return
|
|
11331
|
+
return join9(dirPath, "cycles.jsonl");
|
|
10735
11332
|
}
|
|
10736
11333
|
function hasCode2(error, code) {
|
|
10737
11334
|
return Boolean(
|
|
@@ -10750,7 +11347,7 @@ function errorMessage(error, fallback) {
|
|
|
10750
11347
|
}
|
|
10751
11348
|
return fallback;
|
|
10752
11349
|
}
|
|
10753
|
-
function
|
|
11350
|
+
function isRecord10(value) {
|
|
10754
11351
|
return Boolean(value) && typeof value === "object";
|
|
10755
11352
|
}
|
|
10756
11353
|
function readNonEmptyString(value) {
|
|
@@ -10794,7 +11391,7 @@ function fallbackAcceptanceCriteria(title) {
|
|
|
10794
11391
|
return [`${title} is complete`];
|
|
10795
11392
|
}
|
|
10796
11393
|
function normalizeItem(value, fallbackKind) {
|
|
10797
|
-
if (!
|
|
11394
|
+
if (!isRecord10(value)) {
|
|
10798
11395
|
return null;
|
|
10799
11396
|
}
|
|
10800
11397
|
const id = readNonEmptyString(value.id);
|
|
@@ -10825,7 +11422,7 @@ function normalizeLegacyPlanItems(plan) {
|
|
|
10825
11422
|
}
|
|
10826
11423
|
return plan.flatMap((value) => {
|
|
10827
11424
|
const item = normalizeItem(
|
|
10828
|
-
|
|
11425
|
+
isRecord10(value) ? {
|
|
10829
11426
|
id: value.id,
|
|
10830
11427
|
kind: "stage",
|
|
10831
11428
|
title: value.title,
|
|
@@ -10849,7 +11446,7 @@ function resolveActiveItemId(items, candidate) {
|
|
|
10849
11446
|
return items.find((item) => item.status === "in_progress")?.id ?? items.find((item) => item.status === "pending")?.id ?? items[0]?.id;
|
|
10850
11447
|
}
|
|
10851
11448
|
function normalizeTargetMemory(raw, targetId) {
|
|
10852
|
-
if (!
|
|
11449
|
+
if (!isRecord10(raw)) {
|
|
10853
11450
|
return buildTargetMemory(targetId, 0);
|
|
10854
11451
|
}
|
|
10855
11452
|
const updatedAt = readTimestamp(raw.updatedAt, 0);
|
|
@@ -10876,7 +11473,7 @@ function normalizeTargetMemory(raw, targetId) {
|
|
|
10876
11473
|
};
|
|
10877
11474
|
}
|
|
10878
11475
|
function normalizePersistedSupervisor(raw, fallback) {
|
|
10879
|
-
if (!
|
|
11476
|
+
if (!isRecord10(raw)) {
|
|
10880
11477
|
return void 0;
|
|
10881
11478
|
}
|
|
10882
11479
|
const id = readNonEmptyString(raw.id) ?? fallback.targetId;
|
|
@@ -10912,7 +11509,7 @@ function normalizePersistedSupervisor(raw, fallback) {
|
|
|
10912
11509
|
};
|
|
10913
11510
|
}
|
|
10914
11511
|
function normalizeTargetMeta(raw, fallbackTargetId) {
|
|
10915
|
-
if (!
|
|
11512
|
+
if (!isRecord10(raw)) {
|
|
10916
11513
|
const targetId2 = fallbackTargetId ?? "";
|
|
10917
11514
|
return {
|
|
10918
11515
|
targetId: targetId2,
|
|
@@ -11033,7 +11630,7 @@ async function resetTargetFiles(workspacePath, input2) {
|
|
|
11033
11630
|
const parentDir = dirname6(dir);
|
|
11034
11631
|
const backupDir = `${dir}.backup-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
11035
11632
|
await mkdir4(parentDir, { recursive: true });
|
|
11036
|
-
const stagingDir = await mkdtemp2(
|
|
11633
|
+
const stagingDir = await mkdtemp2(join9(parentDir, `${input2.targetId}.reset-`));
|
|
11037
11634
|
let backupCreated = false;
|
|
11038
11635
|
let promoted = false;
|
|
11039
11636
|
let restored = false;
|
|
@@ -11069,17 +11666,17 @@ async function resetTargetFiles(workspacePath, input2) {
|
|
|
11069
11666
|
}
|
|
11070
11667
|
} catch (error) {
|
|
11071
11668
|
if (restored || !backupCreated) {
|
|
11072
|
-
await
|
|
11669
|
+
await rm6(stagingDir, { recursive: true, force: true }).catch(() => {
|
|
11073
11670
|
});
|
|
11074
11671
|
}
|
|
11075
11672
|
throw error;
|
|
11076
11673
|
}
|
|
11077
11674
|
if (backupCreated) {
|
|
11078
|
-
await
|
|
11675
|
+
await rm6(backupDir, { recursive: true, force: true }).catch(() => {
|
|
11079
11676
|
});
|
|
11080
11677
|
}
|
|
11081
11678
|
if (!promoted) {
|
|
11082
|
-
await
|
|
11679
|
+
await rm6(stagingDir, { recursive: true, force: true }).catch(() => {
|
|
11083
11680
|
});
|
|
11084
11681
|
}
|
|
11085
11682
|
}
|
|
@@ -11177,7 +11774,7 @@ async function cloneTargetFiles(workspacePath, input2) {
|
|
|
11177
11774
|
return nextCycles.filter(countsTowardCycleTotal).length;
|
|
11178
11775
|
}
|
|
11179
11776
|
async function deleteTarget(workspacePath, targetId) {
|
|
11180
|
-
await
|
|
11777
|
+
await rm6(targetDir(workspacePath, targetId), { recursive: true, force: true });
|
|
11181
11778
|
}
|
|
11182
11779
|
async function markTargetSuperseded(workspacePath, targetId, nextTargetId, updatedAt) {
|
|
11183
11780
|
const meta = await readTargetMeta(workspacePath, targetId);
|
|
@@ -11490,8 +12087,8 @@ var init_terminal_snapshot_buffer = __esm({
|
|
|
11490
12087
|
if (this.pendingWriteCount === 0) {
|
|
11491
12088
|
return Promise.resolve();
|
|
11492
12089
|
}
|
|
11493
|
-
return new Promise((
|
|
11494
|
-
this.drainResolvers.push(
|
|
12090
|
+
return new Promise((resolve6) => {
|
|
12091
|
+
this.drainResolvers.push(resolve6);
|
|
11495
12092
|
});
|
|
11496
12093
|
}
|
|
11497
12094
|
resolveDrainIfIdle() {
|
|
@@ -11500,8 +12097,8 @@ var init_terminal_snapshot_buffer = __esm({
|
|
|
11500
12097
|
}
|
|
11501
12098
|
const resolvers = this.drainResolvers;
|
|
11502
12099
|
this.drainResolvers = [];
|
|
11503
|
-
for (const
|
|
11504
|
-
|
|
12100
|
+
for (const resolve6 of resolvers) {
|
|
12101
|
+
resolve6();
|
|
11505
12102
|
}
|
|
11506
12103
|
}
|
|
11507
12104
|
requireTerminal() {
|
|
@@ -11820,10 +12417,10 @@ var init_manager5 = __esm({
|
|
|
11820
12417
|
}
|
|
11821
12418
|
return existing.promise;
|
|
11822
12419
|
}
|
|
11823
|
-
let
|
|
12420
|
+
let resolve6 = () => {
|
|
11824
12421
|
};
|
|
11825
12422
|
const promise = new Promise((innerResolve) => {
|
|
11826
|
-
|
|
12423
|
+
resolve6 = innerResolve;
|
|
11827
12424
|
});
|
|
11828
12425
|
let markKillCompleted = () => {
|
|
11829
12426
|
};
|
|
@@ -11836,7 +12433,7 @@ var init_manager5 = __esm({
|
|
|
11836
12433
|
markKillCompleted,
|
|
11837
12434
|
finalized: false,
|
|
11838
12435
|
promise,
|
|
11839
|
-
resolve:
|
|
12436
|
+
resolve: resolve6
|
|
11840
12437
|
});
|
|
11841
12438
|
void terminal.pty.kill(signal).finally(() => {
|
|
11842
12439
|
const waiter = this.explicitCloseWaiters.get(terminalId);
|
|
@@ -12312,10 +12909,10 @@ var init_update_service = __esm({
|
|
|
12312
12909
|
|
|
12313
12910
|
// packages/server/src/workspace/validator.ts
|
|
12314
12911
|
import { constants } from "fs";
|
|
12315
|
-
import { access, stat as
|
|
12912
|
+
import { access, stat as stat7 } from "fs/promises";
|
|
12316
12913
|
async function validatePath(path14) {
|
|
12317
12914
|
try {
|
|
12318
|
-
const stats = await
|
|
12915
|
+
const stats = await stat7(path14);
|
|
12319
12916
|
if (!stats.isDirectory()) {
|
|
12320
12917
|
return { valid: false, error: "Path is not a directory" };
|
|
12321
12918
|
}
|
|
@@ -12350,12 +12947,12 @@ var init_validator = __esm({
|
|
|
12350
12947
|
// packages/server/src/fs/gitignore.ts
|
|
12351
12948
|
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
|
|
12352
12949
|
import ignore from "ignore";
|
|
12353
|
-
import { join as
|
|
12950
|
+
import { join as join10, relative as relative2 } from "path";
|
|
12354
12951
|
function normalizePath(path14) {
|
|
12355
12952
|
return path14.replace(/\\/g, "/");
|
|
12356
12953
|
}
|
|
12357
12954
|
function relativeToRoot(rootPath, path14) {
|
|
12358
|
-
return normalizePath(
|
|
12955
|
+
return normalizePath(relative2(rootPath, path14));
|
|
12359
12956
|
}
|
|
12360
12957
|
function isDefaultTreeIgnored(name) {
|
|
12361
12958
|
return name.startsWith(".") || name === "node_modules" || name === ".git";
|
|
@@ -12373,7 +12970,7 @@ function isIgnoredByGitignore(ig, path14) {
|
|
|
12373
12970
|
return ig.ignores(path14) || ig.ignores(`${path14}/`);
|
|
12374
12971
|
}
|
|
12375
12972
|
function createGitignoreFilter(rootPath, dirPath) {
|
|
12376
|
-
const gitignorePath =
|
|
12973
|
+
const gitignorePath = join10(rootPath, ".gitignore");
|
|
12377
12974
|
if (!existsSync7(gitignorePath)) {
|
|
12378
12975
|
return (name) => !isDefaultTreeIgnored(name);
|
|
12379
12976
|
}
|
|
@@ -12383,7 +12980,7 @@ function createGitignoreFilter(rootPath, dirPath) {
|
|
|
12383
12980
|
if (isAlwaysTreeIgnored(name)) {
|
|
12384
12981
|
return false;
|
|
12385
12982
|
}
|
|
12386
|
-
const relativePath = relativeToRoot(rootPath,
|
|
12983
|
+
const relativePath = relativeToRoot(rootPath, join10(dirPath, name));
|
|
12387
12984
|
return !isIgnoredByGitignore(ig, relativePath);
|
|
12388
12985
|
};
|
|
12389
12986
|
}
|
|
@@ -13054,8 +13651,8 @@ var init_fencing = __esm({
|
|
|
13054
13651
|
});
|
|
13055
13652
|
|
|
13056
13653
|
// packages/server/src/commands/terminal.ts
|
|
13057
|
-
import { stat as
|
|
13058
|
-
import { basename, isAbsolute } from "node:path";
|
|
13654
|
+
import { stat as stat8 } from "node:fs/promises";
|
|
13655
|
+
import { basename, isAbsolute as isAbsolute2 } from "node:path";
|
|
13059
13656
|
import { z as z6 } from "zod";
|
|
13060
13657
|
function decodeTerminalInput(args) {
|
|
13061
13658
|
if ("bytes" in args) {
|
|
@@ -13171,7 +13768,7 @@ var init_terminal = __esm({
|
|
|
13171
13768
|
}
|
|
13172
13769
|
let cwd = workspace.path;
|
|
13173
13770
|
if (args.cwdPath && args.cwdPath !== ".") {
|
|
13174
|
-
if (
|
|
13771
|
+
if (isAbsolute2(args.cwdPath)) {
|
|
13175
13772
|
throw { code: "invalid_cwd_path", message: "cwdPath must be workspace-relative" };
|
|
13176
13773
|
}
|
|
13177
13774
|
let resolvedCwd;
|
|
@@ -13183,7 +13780,7 @@ var init_terminal = __esm({
|
|
|
13183
13780
|
}
|
|
13184
13781
|
throw error;
|
|
13185
13782
|
}
|
|
13186
|
-
const cwdStats = await
|
|
13783
|
+
const cwdStats = await stat8(resolvedCwd).catch(() => null);
|
|
13187
13784
|
if (!cwdStats) {
|
|
13188
13785
|
throw { code: "cwd_not_found", message: `Directory not found: ${args.cwdPath}` };
|
|
13189
13786
|
}
|
|
@@ -13912,7 +14509,7 @@ var init_hub = __esm({
|
|
|
13912
14509
|
}
|
|
13913
14510
|
}
|
|
13914
14511
|
awaitBinaryPayload(clientId) {
|
|
13915
|
-
return new Promise((
|
|
14512
|
+
return new Promise((resolve6, reject) => {
|
|
13916
14513
|
const timer = setTimeout(() => {
|
|
13917
14514
|
const waiters = this.pendingBinaryWaiters.get(clientId);
|
|
13918
14515
|
if (!waiters) return;
|
|
@@ -13924,7 +14521,7 @@ var init_hub = __esm({
|
|
|
13924
14521
|
}
|
|
13925
14522
|
reject(new Error("Timeout waiting for terminal input binary payload"));
|
|
13926
14523
|
}, BINARY_PAYLOAD_TIMEOUT_MS);
|
|
13927
|
-
const waiter = { resolve:
|
|
14524
|
+
const waiter = { resolve: resolve6, reject, timer };
|
|
13928
14525
|
const queue = this.pendingBinaryWaiters.get(clientId);
|
|
13929
14526
|
if (queue) {
|
|
13930
14527
|
queue.push(waiter);
|
|
@@ -14231,7 +14828,7 @@ var init_hub = __esm({
|
|
|
14231
14828
|
// packages/server/src/commands/workspace.ts
|
|
14232
14829
|
import { readdir as readdir3, realpath as realpath3 } from "node:fs/promises";
|
|
14233
14830
|
import { homedir as homedir2 } from "node:os";
|
|
14234
|
-
import { isAbsolute as
|
|
14831
|
+
import { isAbsolute as isAbsolute3, join as join11, resolve as resolve4 } from "node:path";
|
|
14235
14832
|
import { z as z7 } from "zod";
|
|
14236
14833
|
function resolveBrowsePath(path14) {
|
|
14237
14834
|
const home = homedir2();
|
|
@@ -14239,9 +14836,9 @@ function resolveBrowsePath(path14) {
|
|
|
14239
14836
|
return home;
|
|
14240
14837
|
}
|
|
14241
14838
|
if (path14.startsWith("~/")) {
|
|
14242
|
-
return
|
|
14839
|
+
return join11(home, path14.slice(2));
|
|
14243
14840
|
}
|
|
14244
|
-
return
|
|
14841
|
+
return isAbsolute3(path14) ? path14 : resolve4(home, path14);
|
|
14245
14842
|
}
|
|
14246
14843
|
async function buildRootPaths(currentPath) {
|
|
14247
14844
|
const roots = /* @__PURE__ */ new Set(["/"]);
|
|
@@ -14274,11 +14871,11 @@ var init_workspace = __esm({
|
|
|
14274
14871
|
const entries = await readdir3(basePath, { withFileTypes: true });
|
|
14275
14872
|
const directories = entries.filter((entry) => entry.isDirectory()).map((entry) => ({
|
|
14276
14873
|
name: entry.name,
|
|
14277
|
-
path:
|
|
14874
|
+
path: join11(basePath, entry.name)
|
|
14278
14875
|
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
14279
14876
|
return {
|
|
14280
14877
|
currentPath: basePath,
|
|
14281
|
-
parentPath: basePath !== "/" ?
|
|
14878
|
+
parentPath: basePath !== "/" ? join11(basePath, "..") : null,
|
|
14282
14879
|
directories,
|
|
14283
14880
|
rootPaths: await buildRootPaths(basePath)
|
|
14284
14881
|
};
|
|
@@ -14742,8 +15339,8 @@ var init_pane_layout = __esm({
|
|
|
14742
15339
|
// packages/server/src/commands/session.ts
|
|
14743
15340
|
import { z as z12 } from "zod";
|
|
14744
15341
|
function delay(ms) {
|
|
14745
|
-
return new Promise((
|
|
14746
|
-
setTimeout(
|
|
15342
|
+
return new Promise((resolve6) => {
|
|
15343
|
+
setTimeout(resolve6, ms);
|
|
14747
15344
|
});
|
|
14748
15345
|
}
|
|
14749
15346
|
function getProviderFromRegistry(providerId, registry) {
|
|
@@ -14894,20 +15491,260 @@ var init_session2 = __esm({
|
|
|
14894
15491
|
}
|
|
14895
15492
|
});
|
|
14896
15493
|
|
|
15494
|
+
// packages/server/src/fs/content-search.ts
|
|
15495
|
+
import { spawn as spawn5 } from "child_process";
|
|
15496
|
+
import { existsSync as existsSync8 } from "fs";
|
|
15497
|
+
import { readdir as readdir4, readFile as readFile4, stat as stat9 } from "fs/promises";
|
|
15498
|
+
import { basename as basename2, join as join12, relative as relative3 } from "path";
|
|
15499
|
+
import { createInterface } from "readline";
|
|
15500
|
+
async function searchFileContents(rootPath, options) {
|
|
15501
|
+
const query = options.query.trim();
|
|
15502
|
+
if (!query) {
|
|
15503
|
+
return {
|
|
15504
|
+
files: [],
|
|
15505
|
+
totalMatchCount: 0,
|
|
15506
|
+
hasMoreFiles: false,
|
|
15507
|
+
truncatedMatchFileCount: 0
|
|
15508
|
+
};
|
|
15509
|
+
}
|
|
15510
|
+
const result = await searchWithRipgrep(rootPath, query, options.maxFiles).catch(
|
|
15511
|
+
async (error) => {
|
|
15512
|
+
if (error.code === "ENOENT") {
|
|
15513
|
+
return searchWithNode(rootPath, query, options.maxFiles);
|
|
15514
|
+
}
|
|
15515
|
+
throw error;
|
|
15516
|
+
}
|
|
15517
|
+
);
|
|
15518
|
+
return finalizeResults(result, options.maxFiles, options.maxMatchesPerFile);
|
|
15519
|
+
}
|
|
15520
|
+
async function searchWithRipgrep(rootPath, query, maxFiles) {
|
|
15521
|
+
const hasGitignore = existsSync8(join12(rootPath, ".gitignore"));
|
|
15522
|
+
const args = [
|
|
15523
|
+
"--json",
|
|
15524
|
+
"--line-number",
|
|
15525
|
+
"--column",
|
|
15526
|
+
"--fixed-strings",
|
|
15527
|
+
"--sort",
|
|
15528
|
+
"path",
|
|
15529
|
+
"--with-filename",
|
|
15530
|
+
"--glob",
|
|
15531
|
+
"!**/.git/**",
|
|
15532
|
+
"--glob",
|
|
15533
|
+
"!**/node_modules/**"
|
|
15534
|
+
];
|
|
15535
|
+
if (hasGitignore) {
|
|
15536
|
+
args.push("--hidden");
|
|
15537
|
+
args.push("--no-require-git");
|
|
15538
|
+
}
|
|
15539
|
+
args.push(query, ".");
|
|
15540
|
+
return new Promise((resolve6, reject) => {
|
|
15541
|
+
const child = spawn5("rg", args, { cwd: rootPath, stdio: ["ignore", "pipe", "pipe"] });
|
|
15542
|
+
const stdout = createInterface({ input: child.stdout });
|
|
15543
|
+
const files = /* @__PURE__ */ new Map();
|
|
15544
|
+
let totalMatchCount = 0;
|
|
15545
|
+
let hasMoreFiles = false;
|
|
15546
|
+
let stderr = "";
|
|
15547
|
+
stdout.on("line", (line) => {
|
|
15548
|
+
if (!line.trim()) {
|
|
15549
|
+
return;
|
|
15550
|
+
}
|
|
15551
|
+
const event = JSON.parse(line);
|
|
15552
|
+
if (event.type !== "match") {
|
|
15553
|
+
return;
|
|
15554
|
+
}
|
|
15555
|
+
const rawPath = event.data?.path?.text;
|
|
15556
|
+
if (!rawPath) {
|
|
15557
|
+
return;
|
|
15558
|
+
}
|
|
15559
|
+
const relativePath = normalizeRelativePath(relative3(rootPath, join12(rootPath, rawPath)));
|
|
15560
|
+
const preview = (event.data?.lines?.text ?? "").replace(/\r?\n$/, "");
|
|
15561
|
+
const lineNumber = event.data?.line_number ?? 1;
|
|
15562
|
+
const submatches = event.data?.submatches ?? [];
|
|
15563
|
+
totalMatchCount += submatches.length;
|
|
15564
|
+
if (!files.has(relativePath) && files.size >= maxFiles) {
|
|
15565
|
+
hasMoreFiles = true;
|
|
15566
|
+
return;
|
|
15567
|
+
}
|
|
15568
|
+
for (const submatch of submatches) {
|
|
15569
|
+
pushMatch(files, relativePath, {
|
|
15570
|
+
line: lineNumber,
|
|
15571
|
+
column: byteOffsetToColumn(preview, submatch.start),
|
|
15572
|
+
endColumn: byteOffsetToColumn(preview, submatch.end),
|
|
15573
|
+
preview,
|
|
15574
|
+
previewColumnStart: byteOffsetToColumn(preview, submatch.start),
|
|
15575
|
+
previewColumnEnd: byteOffsetToColumn(preview, submatch.end)
|
|
15576
|
+
});
|
|
15577
|
+
}
|
|
15578
|
+
});
|
|
15579
|
+
child.stderr.on("data", (chunk) => {
|
|
15580
|
+
stderr += chunk.toString();
|
|
15581
|
+
});
|
|
15582
|
+
child.on("error", (error) => {
|
|
15583
|
+
void stdout.close();
|
|
15584
|
+
reject(error);
|
|
15585
|
+
});
|
|
15586
|
+
child.on("close", (code) => {
|
|
15587
|
+
void stdout.close();
|
|
15588
|
+
if (code === 0 || code === 1) {
|
|
15589
|
+
resolve6({
|
|
15590
|
+
files: sortAccumulators(files),
|
|
15591
|
+
totalMatchCount,
|
|
15592
|
+
hasMoreFiles
|
|
15593
|
+
});
|
|
15594
|
+
return;
|
|
15595
|
+
}
|
|
15596
|
+
reject(
|
|
15597
|
+
Object.assign(new Error(stderr || `rg exited with code ${code ?? "unknown"}`), { code })
|
|
15598
|
+
);
|
|
15599
|
+
});
|
|
15600
|
+
});
|
|
15601
|
+
}
|
|
15602
|
+
async function searchWithNode(rootPath, query, maxFiles) {
|
|
15603
|
+
const files = /* @__PURE__ */ new Map();
|
|
15604
|
+
let totalMatchCount = 0;
|
|
15605
|
+
let hasMoreFiles = false;
|
|
15606
|
+
async function walk(dirPath) {
|
|
15607
|
+
const filter = createGitignoreFilter(rootPath, dirPath);
|
|
15608
|
+
const entries = await readdir4(dirPath, { withFileTypes: true });
|
|
15609
|
+
const filteredEntries = entries.filter((entry) => filter(entry.name));
|
|
15610
|
+
filteredEntries.sort((a, b) => a.name.localeCompare(b.name));
|
|
15611
|
+
for (const entry of filteredEntries) {
|
|
15612
|
+
const fullPath = join12(dirPath, entry.name);
|
|
15613
|
+
if (entry.isDirectory()) {
|
|
15614
|
+
await walk(fullPath);
|
|
15615
|
+
continue;
|
|
15616
|
+
}
|
|
15617
|
+
if (!entry.isFile()) {
|
|
15618
|
+
continue;
|
|
15619
|
+
}
|
|
15620
|
+
const fileStat = await stat9(fullPath);
|
|
15621
|
+
if (fileStat.size > FALLBACK_MAX_FILE_BYTES) {
|
|
15622
|
+
continue;
|
|
15623
|
+
}
|
|
15624
|
+
const buffer = await readFile4(fullPath);
|
|
15625
|
+
if (isBinaryFile(buffer)) {
|
|
15626
|
+
continue;
|
|
15627
|
+
}
|
|
15628
|
+
const relativePath = normalizeRelativePath(relative3(rootPath, fullPath));
|
|
15629
|
+
const file = collectMatchesFromText(relativePath, buffer.toString("utf-8"), query);
|
|
15630
|
+
if (!file) {
|
|
15631
|
+
continue;
|
|
15632
|
+
}
|
|
15633
|
+
totalMatchCount += file.matchCount;
|
|
15634
|
+
if (files.size >= maxFiles) {
|
|
15635
|
+
hasMoreFiles = true;
|
|
15636
|
+
continue;
|
|
15637
|
+
}
|
|
15638
|
+
files.set(relativePath, file);
|
|
15639
|
+
}
|
|
15640
|
+
}
|
|
15641
|
+
await walk(rootPath);
|
|
15642
|
+
return {
|
|
15643
|
+
files: sortAccumulators(files),
|
|
15644
|
+
totalMatchCount,
|
|
15645
|
+
hasMoreFiles
|
|
15646
|
+
};
|
|
15647
|
+
}
|
|
15648
|
+
function collectMatchesFromText(relativePath, content, query) {
|
|
15649
|
+
const file = {
|
|
15650
|
+
path: relativePath,
|
|
15651
|
+
name: basename2(relativePath),
|
|
15652
|
+
matches: [],
|
|
15653
|
+
matchCount: 0
|
|
15654
|
+
};
|
|
15655
|
+
const lines = content.split(/\r?\n/);
|
|
15656
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
15657
|
+
const preview = lines[lineIndex] ?? "";
|
|
15658
|
+
if (!preview) {
|
|
15659
|
+
continue;
|
|
15660
|
+
}
|
|
15661
|
+
let fromIndex = 0;
|
|
15662
|
+
while (fromIndex <= preview.length) {
|
|
15663
|
+
const matchIndex = preview.indexOf(query, fromIndex);
|
|
15664
|
+
if (matchIndex === -1) {
|
|
15665
|
+
break;
|
|
15666
|
+
}
|
|
15667
|
+
const startColumn = matchIndex + 1;
|
|
15668
|
+
const endColumn = startColumn + query.length;
|
|
15669
|
+
file.matches.push({
|
|
15670
|
+
line: lineIndex + 1,
|
|
15671
|
+
column: startColumn,
|
|
15672
|
+
endColumn,
|
|
15673
|
+
preview,
|
|
15674
|
+
previewColumnStart: startColumn,
|
|
15675
|
+
previewColumnEnd: endColumn
|
|
15676
|
+
});
|
|
15677
|
+
file.matchCount += 1;
|
|
15678
|
+
fromIndex = matchIndex + Math.max(query.length, 1);
|
|
15679
|
+
}
|
|
15680
|
+
}
|
|
15681
|
+
return file.matchCount > 0 ? file : null;
|
|
15682
|
+
}
|
|
15683
|
+
function pushMatch(files, relativePath, match) {
|
|
15684
|
+
let file = files.get(relativePath);
|
|
15685
|
+
if (!file) {
|
|
15686
|
+
file = {
|
|
15687
|
+
path: relativePath,
|
|
15688
|
+
name: basename2(relativePath),
|
|
15689
|
+
matches: [],
|
|
15690
|
+
matchCount: 0
|
|
15691
|
+
};
|
|
15692
|
+
files.set(relativePath, file);
|
|
15693
|
+
}
|
|
15694
|
+
file.matches.push(match);
|
|
15695
|
+
file.matchCount += 1;
|
|
15696
|
+
}
|
|
15697
|
+
function sortAccumulators(files) {
|
|
15698
|
+
return Array.from(files.values()).sort((a, b) => a.path.localeCompare(b.path));
|
|
15699
|
+
}
|
|
15700
|
+
function finalizeResults(result, maxFiles, maxMatchesPerFile) {
|
|
15701
|
+
const visibleFiles = result.files.slice(0, maxFiles).map((file) => ({
|
|
15702
|
+
path: file.path,
|
|
15703
|
+
name: file.name,
|
|
15704
|
+
matchCount: file.matchCount,
|
|
15705
|
+
hasMoreMatches: file.matchCount > maxMatchesPerFile,
|
|
15706
|
+
matches: file.matches.slice(0, maxMatchesPerFile)
|
|
15707
|
+
}));
|
|
15708
|
+
return {
|
|
15709
|
+
files: visibleFiles,
|
|
15710
|
+
totalMatchCount: result.totalMatchCount,
|
|
15711
|
+
hasMoreFiles: result.hasMoreFiles || result.files.length > maxFiles,
|
|
15712
|
+
truncatedMatchFileCount: visibleFiles.filter((file) => file.hasMoreMatches).length
|
|
15713
|
+
};
|
|
15714
|
+
}
|
|
15715
|
+
function normalizeRelativePath(path14) {
|
|
15716
|
+
return path14.replace(/\\/g, "/");
|
|
15717
|
+
}
|
|
15718
|
+
function byteOffsetToColumn(preview, byteOffset) {
|
|
15719
|
+
return Buffer.from(preview, "utf8").subarray(0, byteOffset).toString("utf8").length + 1;
|
|
15720
|
+
}
|
|
15721
|
+
function isBinaryFile(buffer) {
|
|
15722
|
+
const sample = buffer.subarray(0, 8e3);
|
|
15723
|
+
return sample.includes(0);
|
|
15724
|
+
}
|
|
15725
|
+
var FALLBACK_MAX_FILE_BYTES;
|
|
15726
|
+
var init_content_search = __esm({
|
|
15727
|
+
"packages/server/src/fs/content-search.ts"() {
|
|
15728
|
+
"use strict";
|
|
15729
|
+
init_gitignore();
|
|
15730
|
+
FALLBACK_MAX_FILE_BYTES = 1e6;
|
|
15731
|
+
}
|
|
15732
|
+
});
|
|
15733
|
+
|
|
14897
15734
|
// packages/server/src/fs/tree.ts
|
|
14898
|
-
import { readdir as
|
|
14899
|
-
import { join as
|
|
15735
|
+
import { readdir as readdir5, stat as stat10 } from "fs/promises";
|
|
15736
|
+
import { join as join13, relative as relative4 } from "path";
|
|
14900
15737
|
async function readTree(rootPath, subdir) {
|
|
14901
|
-
const targetPath = subdir ?
|
|
15738
|
+
const targetPath = subdir ? join13(rootPath, subdir) : rootPath;
|
|
14902
15739
|
const filter = createTreeVisibilityFilter();
|
|
14903
|
-
const entries = await
|
|
15740
|
+
const entries = await readdir5(targetPath, { withFileTypes: true });
|
|
14904
15741
|
const nodes = [];
|
|
14905
15742
|
for (const entry of entries) {
|
|
14906
15743
|
if (!filter(entry.name)) {
|
|
14907
15744
|
continue;
|
|
14908
15745
|
}
|
|
14909
|
-
const fullPath =
|
|
14910
|
-
const relPath =
|
|
15746
|
+
const fullPath = join13(targetPath, entry.name);
|
|
15747
|
+
const relPath = relative4(rootPath, fullPath);
|
|
14911
15748
|
if (entry.isDirectory()) {
|
|
14912
15749
|
nodes.push({
|
|
14913
15750
|
name: entry.name,
|
|
@@ -14917,7 +15754,7 @@ async function readTree(rootPath, subdir) {
|
|
|
14917
15754
|
// Not loaded yet - client will request on expand
|
|
14918
15755
|
});
|
|
14919
15756
|
} else if (entry.isFile()) {
|
|
14920
|
-
const stats = await
|
|
15757
|
+
const stats = await stat10(fullPath);
|
|
14921
15758
|
nodes.push({
|
|
14922
15759
|
name: entry.name,
|
|
14923
15760
|
path: relPath,
|
|
@@ -14946,18 +15783,18 @@ async function searchFiles(rootPath, query, limit = 10) {
|
|
|
14946
15783
|
const matches = [];
|
|
14947
15784
|
async function walk(dirPath) {
|
|
14948
15785
|
const filter = createGitignoreFilter(rootPath, dirPath);
|
|
14949
|
-
const entries = await
|
|
15786
|
+
const entries = await readdir5(dirPath, { withFileTypes: true });
|
|
14950
15787
|
const filteredEntries = entries.filter((entry) => filter(entry.name));
|
|
14951
15788
|
filteredEntries.sort((a, b) => a.name.localeCompare(b.name));
|
|
14952
15789
|
for (const entry of filteredEntries) {
|
|
14953
|
-
const fullPath =
|
|
14954
|
-
const relPath =
|
|
15790
|
+
const fullPath = join13(dirPath, entry.name);
|
|
15791
|
+
const relPath = relative4(rootPath, fullPath);
|
|
14955
15792
|
if (entry.isDirectory()) {
|
|
14956
15793
|
await walk(fullPath);
|
|
14957
15794
|
continue;
|
|
14958
15795
|
}
|
|
14959
15796
|
if (entry.isFile()) {
|
|
14960
|
-
const rank =
|
|
15797
|
+
const rank = scoreFileMatch(relPath, entry.name, normalizedQuery);
|
|
14961
15798
|
if (rank === null) {
|
|
14962
15799
|
continue;
|
|
14963
15800
|
}
|
|
@@ -14986,7 +15823,7 @@ async function searchFiles(rootPath, query, limit = 10) {
|
|
|
14986
15823
|
}
|
|
14987
15824
|
return a.path.toLowerCase().localeCompare(b.path.toLowerCase());
|
|
14988
15825
|
}).slice(0, limit)) {
|
|
14989
|
-
const stats = await
|
|
15826
|
+
const stats = await stat10(match.fullPath);
|
|
14990
15827
|
files.push({
|
|
14991
15828
|
name: match.name,
|
|
14992
15829
|
path: match.path,
|
|
@@ -14997,6 +15834,13 @@ async function searchFiles(rootPath, query, limit = 10) {
|
|
|
14997
15834
|
}
|
|
14998
15835
|
return { files };
|
|
14999
15836
|
}
|
|
15837
|
+
function scoreFileMatch(path14, name, query) {
|
|
15838
|
+
const filenameRank = scoreFilenameMatch(name, query);
|
|
15839
|
+
if (filenameRank !== null) {
|
|
15840
|
+
return filenameRank;
|
|
15841
|
+
}
|
|
15842
|
+
return scorePathMatch(path14, query);
|
|
15843
|
+
}
|
|
15000
15844
|
function scoreFilenameMatch(name, query) {
|
|
15001
15845
|
const normalizedName = name.toLowerCase();
|
|
15002
15846
|
const baseName = normalizedName.replace(/\.[^.]+$/, "");
|
|
@@ -15023,6 +15867,22 @@ function scoreFilenameMatch(name, query) {
|
|
|
15023
15867
|
}
|
|
15024
15868
|
return null;
|
|
15025
15869
|
}
|
|
15870
|
+
function scorePathMatch(path14, query) {
|
|
15871
|
+
const normalizedPath = path14.toLowerCase();
|
|
15872
|
+
if (normalizedPath === query) {
|
|
15873
|
+
return 7;
|
|
15874
|
+
}
|
|
15875
|
+
if (normalizedPath.startsWith(query)) {
|
|
15876
|
+
return 8;
|
|
15877
|
+
}
|
|
15878
|
+
if (normalizedPath.includes(query)) {
|
|
15879
|
+
return 9;
|
|
15880
|
+
}
|
|
15881
|
+
if (isSubsequence(query, normalizedPath)) {
|
|
15882
|
+
return 10;
|
|
15883
|
+
}
|
|
15884
|
+
return null;
|
|
15885
|
+
}
|
|
15026
15886
|
function isSubsequence(query, candidate) {
|
|
15027
15887
|
let index = 0;
|
|
15028
15888
|
for (const char of candidate) {
|
|
@@ -15047,6 +15907,7 @@ import { z as z13 } from "zod";
|
|
|
15047
15907
|
var init_file = __esm({
|
|
15048
15908
|
"packages/server/src/commands/file.ts"() {
|
|
15049
15909
|
"use strict";
|
|
15910
|
+
init_content_search();
|
|
15050
15911
|
init_file_io();
|
|
15051
15912
|
init_tree();
|
|
15052
15913
|
init_dispatch();
|
|
@@ -15079,6 +15940,26 @@ var init_file = __esm({
|
|
|
15079
15940
|
return searchFiles(workspace.path, args.query, args.limit ?? 10);
|
|
15080
15941
|
}
|
|
15081
15942
|
);
|
|
15943
|
+
registerCommand(
|
|
15944
|
+
"file.searchContent",
|
|
15945
|
+
z13.object({
|
|
15946
|
+
workspaceId: z13.string(),
|
|
15947
|
+
query: z13.string(),
|
|
15948
|
+
maxFiles: z13.number().int().positive().max(100),
|
|
15949
|
+
maxMatchesPerFile: z13.number().int().positive().max(100)
|
|
15950
|
+
}),
|
|
15951
|
+
async (args, ctx) => {
|
|
15952
|
+
const workspace = ctx.workspaceMgr.get(args.workspaceId);
|
|
15953
|
+
if (!workspace) {
|
|
15954
|
+
throw { code: "workspace_not_found", message: `Workspace not found: ${args.workspaceId}` };
|
|
15955
|
+
}
|
|
15956
|
+
return searchFileContents(workspace.path, {
|
|
15957
|
+
query: args.query,
|
|
15958
|
+
maxFiles: args.maxFiles,
|
|
15959
|
+
maxMatchesPerFile: args.maxMatchesPerFile
|
|
15960
|
+
});
|
|
15961
|
+
}
|
|
15962
|
+
);
|
|
15082
15963
|
registerCommand(
|
|
15083
15964
|
"file.read",
|
|
15084
15965
|
z13.object({
|
|
@@ -15201,7 +16082,7 @@ var init_file = __esm({
|
|
|
15201
16082
|
});
|
|
15202
16083
|
|
|
15203
16084
|
// packages/server/src/git/diff.ts
|
|
15204
|
-
import { mkdtemp as mkdtemp3, readFile as
|
|
16085
|
+
import { mkdtemp as mkdtemp3, readFile as readFile5, rm as rm7 } from "fs/promises";
|
|
15205
16086
|
import os3 from "os";
|
|
15206
16087
|
import path11 from "path";
|
|
15207
16088
|
async function isTrackedPath(cwd, filePath) {
|
|
@@ -15236,12 +16117,12 @@ async function getUntrackedFileDiff(cwd, filePath) {
|
|
|
15236
16117
|
});
|
|
15237
16118
|
return result.stdout;
|
|
15238
16119
|
} finally {
|
|
15239
|
-
await
|
|
16120
|
+
await rm7(tempDir, { recursive: true, force: true });
|
|
15240
16121
|
}
|
|
15241
16122
|
}
|
|
15242
16123
|
async function pathExists(cwd, filePath) {
|
|
15243
16124
|
try {
|
|
15244
|
-
await
|
|
16125
|
+
await readFile5(resolveSafe(cwd, filePath));
|
|
15245
16126
|
return true;
|
|
15246
16127
|
} catch {
|
|
15247
16128
|
return false;
|
|
@@ -15249,7 +16130,7 @@ async function pathExists(cwd, filePath) {
|
|
|
15249
16130
|
}
|
|
15250
16131
|
async function readTextAtRevision(cwd, revision, filePath) {
|
|
15251
16132
|
if (revision === "WORKTREE") {
|
|
15252
|
-
return
|
|
16133
|
+
return readFile5(resolveSafe(cwd, filePath), "utf-8");
|
|
15253
16134
|
}
|
|
15254
16135
|
try {
|
|
15255
16136
|
const gitSpec = revision === "INDEX" ? `:${filePath}` : `${revision}:${filePath}`;
|
|
@@ -15667,33 +16548,33 @@ var init_git2 = __esm({
|
|
|
15667
16548
|
});
|
|
15668
16549
|
|
|
15669
16550
|
// packages/server/src/config/config-io.ts
|
|
15670
|
-
import { existsSync as
|
|
16551
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync7, renameSync as renameSync2, writeFileSync as writeFileSync5 } from "node:fs";
|
|
15671
16552
|
import { homedir as homedir3 } from "node:os";
|
|
15672
|
-
import { basename as
|
|
16553
|
+
import { basename as basename3, dirname as dirname7, join as join14 } from "node:path";
|
|
15673
16554
|
function resolveConfigPath(configType) {
|
|
15674
16555
|
if (configType === "codex") {
|
|
15675
16556
|
const testHome = process.env.CODER_STUDIO_CODEX_HOME;
|
|
15676
16557
|
if (testHome && testHome.trim()) {
|
|
15677
|
-
return
|
|
16558
|
+
return join14(testHome, "config.toml");
|
|
15678
16559
|
}
|
|
15679
16560
|
const codexHome = process.env.CODEX_HOME;
|
|
15680
16561
|
if (codexHome && codexHome.trim()) {
|
|
15681
|
-
return
|
|
16562
|
+
return join14(codexHome, "config.toml");
|
|
15682
16563
|
}
|
|
15683
|
-
return
|
|
16564
|
+
return join14(homedir3(), ".codex", "config.toml");
|
|
15684
16565
|
}
|
|
15685
16566
|
if (configType === "claude") {
|
|
15686
16567
|
const testHome = process.env.CODER_STUDIO_CLAUDE_HOME;
|
|
15687
16568
|
if (testHome && testHome.trim()) {
|
|
15688
|
-
return
|
|
16569
|
+
return join14(testHome, "settings.json");
|
|
15689
16570
|
}
|
|
15690
|
-
return
|
|
16571
|
+
return join14(homedir3(), ".claude", "settings.json");
|
|
15691
16572
|
}
|
|
15692
16573
|
throw new Error(`Unknown config type: ${configType}`);
|
|
15693
16574
|
}
|
|
15694
16575
|
function readConfigFile(configType) {
|
|
15695
16576
|
const configPath = resolveConfigPath(configType);
|
|
15696
|
-
if (!
|
|
16577
|
+
if (!existsSync9(configPath)) {
|
|
15697
16578
|
return { configPath, content: "", exists: false };
|
|
15698
16579
|
}
|
|
15699
16580
|
try {
|
|
@@ -15707,11 +16588,11 @@ function writeConfigFile(configType, content) {
|
|
|
15707
16588
|
try {
|
|
15708
16589
|
const configPath = resolveConfigPath(configType);
|
|
15709
16590
|
const parentDir = dirname7(configPath);
|
|
15710
|
-
if (!
|
|
16591
|
+
if (!existsSync9(parentDir)) {
|
|
15711
16592
|
mkdirSync7(parentDir, { recursive: true });
|
|
15712
16593
|
}
|
|
15713
16594
|
let backupPath = null;
|
|
15714
|
-
if (
|
|
16595
|
+
if (existsSync9(configPath)) {
|
|
15715
16596
|
backupPath = createBackup(configPath);
|
|
15716
16597
|
}
|
|
15717
16598
|
const tempPath = `${configPath}.tmp`;
|
|
@@ -15729,10 +16610,10 @@ function writeConfigFile(configType, content) {
|
|
|
15729
16610
|
function createBackup(filePath) {
|
|
15730
16611
|
const original = readFileSync7(filePath, "utf-8");
|
|
15731
16612
|
const ext = filePath.split(".").pop() ?? "";
|
|
15732
|
-
const base =
|
|
16613
|
+
const base = basename3(filePath, `.${ext}`);
|
|
15733
16614
|
const dir = dirname7(filePath);
|
|
15734
16615
|
const ts = formatTimestamp(/* @__PURE__ */ new Date());
|
|
15735
|
-
const backupPath =
|
|
16616
|
+
const backupPath = join14(dir, `${base}.bak.${ts}.${ext}`);
|
|
15736
16617
|
writeFileSync5(backupPath, original, "utf-8");
|
|
15737
16618
|
return backupPath;
|
|
15738
16619
|
}
|
|
@@ -15760,7 +16641,36 @@ function flattenSettings(obj, prefix = "") {
|
|
|
15760
16641
|
}
|
|
15761
16642
|
return result;
|
|
15762
16643
|
}
|
|
15763
|
-
|
|
16644
|
+
function resolveAppearancePersonalizationOverrideKeysToDelete(settings) {
|
|
16645
|
+
const appearance = settings.appearance;
|
|
16646
|
+
if (!appearance || typeof appearance !== "object" || Array.isArray(appearance)) {
|
|
16647
|
+
return [];
|
|
16648
|
+
}
|
|
16649
|
+
const personalization = appearance.personalization;
|
|
16650
|
+
if (!personalization || typeof personalization !== "object" || Array.isArray(personalization)) {
|
|
16651
|
+
return [];
|
|
16652
|
+
}
|
|
16653
|
+
if (!isFullAppearancePersonalizationSnapshot(personalization)) {
|
|
16654
|
+
return [];
|
|
16655
|
+
}
|
|
16656
|
+
const keysToDelete = [];
|
|
16657
|
+
for (const branch of PERSONALIZATION_OVERRIDE_BRANCHES) {
|
|
16658
|
+
const overrides = personalization[branch];
|
|
16659
|
+
if (!overrides || typeof overrides !== "object" || Array.isArray(overrides)) {
|
|
16660
|
+
continue;
|
|
16661
|
+
}
|
|
16662
|
+
for (const field of PERSONALIZATION_OVERRIDE_FIELDS) {
|
|
16663
|
+
if (!Object.prototype.hasOwnProperty.call(overrides, field)) {
|
|
16664
|
+
keysToDelete.push(`appearance.personalization.${branch}.${field}`);
|
|
16665
|
+
}
|
|
16666
|
+
}
|
|
16667
|
+
}
|
|
16668
|
+
return keysToDelete;
|
|
16669
|
+
}
|
|
16670
|
+
function isFullAppearancePersonalizationSnapshot(personalization) {
|
|
16671
|
+
return Object.prototype.hasOwnProperty.call(personalization, "version") && Object.prototype.hasOwnProperty.call(personalization, "common") && Object.prototype.hasOwnProperty.call(personalization, "desktop") && Object.prototype.hasOwnProperty.call(personalization, "mobile");
|
|
16672
|
+
}
|
|
16673
|
+
var PersonalizationOverridesSchema, PERSONALIZATION_OVERRIDE_BRANCHES, PERSONALIZATION_OVERRIDE_FIELDS, SettingsSchema;
|
|
15764
16674
|
var init_settings2 = __esm({
|
|
15765
16675
|
"packages/server/src/commands/settings.ts"() {
|
|
15766
16676
|
"use strict";
|
|
@@ -15769,6 +16679,23 @@ var init_settings2 = __esm({
|
|
|
15769
16679
|
init_provider_config();
|
|
15770
16680
|
init_settings();
|
|
15771
16681
|
init_dispatch();
|
|
16682
|
+
PersonalizationOverridesSchema = z15.object({
|
|
16683
|
+
backgroundAssetId: z15.string().min(1).nullable().optional(),
|
|
16684
|
+
backgroundDimness: z15.number().int().min(0).max(100).optional(),
|
|
16685
|
+
backgroundBlur: z15.number().int().min(0).max(40).optional(),
|
|
16686
|
+
glassEnabled: z15.boolean().optional(),
|
|
16687
|
+
glassIntensity: z15.number().int().min(0).max(100).optional(),
|
|
16688
|
+
surfaceOpacity: z15.number().int().min(0).max(100).optional()
|
|
16689
|
+
});
|
|
16690
|
+
PERSONALIZATION_OVERRIDE_BRANCHES = ["desktop", "mobile"];
|
|
16691
|
+
PERSONALIZATION_OVERRIDE_FIELDS = [
|
|
16692
|
+
"backgroundAssetId",
|
|
16693
|
+
"backgroundDimness",
|
|
16694
|
+
"backgroundBlur",
|
|
16695
|
+
"glassEnabled",
|
|
16696
|
+
"glassIntensity",
|
|
16697
|
+
"surfaceOpacity"
|
|
16698
|
+
];
|
|
15772
16699
|
SettingsSchema = z15.object({
|
|
15773
16700
|
defaultProviderId: z15.string().optional(),
|
|
15774
16701
|
notifications: z15.object({
|
|
@@ -15795,7 +16722,22 @@ var init_settings2 = __esm({
|
|
|
15795
16722
|
terminalFontSize: z15.number().int().min(10).max(18).optional(),
|
|
15796
16723
|
desktopTerminalFontSize: z15.number().int().min(10).max(18).optional(),
|
|
15797
16724
|
mobileTerminalFontSize: z15.number().int().min(10).max(18).optional(),
|
|
15798
|
-
locale: z15.enum(["zh", "en"]).optional()
|
|
16725
|
+
locale: z15.enum(["zh", "en"]).optional(),
|
|
16726
|
+
personalization: z15.object({
|
|
16727
|
+
version: z15.literal(1).optional(),
|
|
16728
|
+
common: z15.object({
|
|
16729
|
+
backgroundMode: z15.enum(["none", "image"]).optional(),
|
|
16730
|
+
backgroundAssetId: z15.string().min(1).nullable().optional(),
|
|
16731
|
+
backgroundFit: z15.enum(["cover", "contain"]).optional(),
|
|
16732
|
+
backgroundDimness: z15.number().int().min(0).max(100).optional(),
|
|
16733
|
+
backgroundBlur: z15.number().int().min(0).max(40).optional(),
|
|
16734
|
+
glassEnabled: z15.boolean().optional(),
|
|
16735
|
+
glassIntensity: z15.number().int().min(0).max(100).optional(),
|
|
16736
|
+
surfaceOpacity: z15.number().int().min(0).max(100).optional()
|
|
16737
|
+
}).optional(),
|
|
16738
|
+
desktop: PersonalizationOverridesSchema.optional(),
|
|
16739
|
+
mobile: PersonalizationOverridesSchema.optional()
|
|
16740
|
+
}).optional()
|
|
15799
16741
|
}).optional(),
|
|
15800
16742
|
lsp: z15.object({
|
|
15801
16743
|
mode: z15.enum(["auto", "off"]).optional()
|
|
@@ -15865,7 +16807,11 @@ var init_settings2 = __esm({
|
|
|
15865
16807
|
const nextSettings = args.settings;
|
|
15866
16808
|
const providers = nextSettings.providers && typeof nextSettings.providers === "object" && !Array.isArray(nextSettings.providers) ? nextSettings.providers : void 0;
|
|
15867
16809
|
const { providers: _providers, ...nonProviderSettings } = nextSettings;
|
|
16810
|
+
const overrideKeysToDelete = resolveAppearancePersonalizationOverrideKeysToDelete(nextSettings);
|
|
15868
16811
|
const flatSettings = flattenSettings(nonProviderSettings);
|
|
16812
|
+
for (const key of overrideKeysToDelete) {
|
|
16813
|
+
ctx.settingsRepo.delete(key);
|
|
16814
|
+
}
|
|
15869
16815
|
for (const [key, value] of Object.entries(flatSettings)) {
|
|
15870
16816
|
ctx.settingsRepo.set(key, value);
|
|
15871
16817
|
}
|
|
@@ -16511,6 +17457,7 @@ async function listWorktrees(repoPath) {
|
|
|
16511
17457
|
current.name = branch.split("/").pop() || branch;
|
|
16512
17458
|
} else if (line === "detached") {
|
|
16513
17459
|
current.branch = "detached HEAD";
|
|
17460
|
+
current.name = path12.basename(current.path ?? "") || "detached";
|
|
16514
17461
|
} else if (line === "") {
|
|
16515
17462
|
if (current.path) {
|
|
16516
17463
|
worktrees.push(current);
|
|
@@ -17092,12 +18039,12 @@ var init_commands = __esm({
|
|
|
17092
18039
|
// packages/server/src/server.ts
|
|
17093
18040
|
import { mkdtempSync, rmSync as rmSync2 } from "node:fs";
|
|
17094
18041
|
import { tmpdir } from "node:os";
|
|
17095
|
-
import { join as
|
|
18042
|
+
import { join as join15 } from "node:path";
|
|
17096
18043
|
async function createServer(configOverrides) {
|
|
17097
18044
|
const config = parseServerConfig(configOverrides);
|
|
17098
18045
|
const configuredStateDir = resolveConfiguredStateDir(config);
|
|
17099
18046
|
const shouldCleanupStateRoot = configuredStateDir === IN_MEMORY_STATE_DIR;
|
|
17100
|
-
const stateRoot = shouldCleanupStateRoot ? mkdtempSync(
|
|
18047
|
+
const stateRoot = shouldCleanupStateRoot ? mkdtempSync(join15(tmpdir(), "coder-studio-state-")) : configuredStateDir;
|
|
17101
18048
|
ensureStateDir(config);
|
|
17102
18049
|
const eventBus = new EventBus();
|
|
17103
18050
|
const activationMgr = new ActivationManager();
|
|
@@ -17107,10 +18054,10 @@ async function createServer(configOverrides) {
|
|
|
17107
18054
|
let commandContext;
|
|
17108
18055
|
let lspMgr = null;
|
|
17109
18056
|
const terminalRepo = new TerminalRepo({
|
|
17110
|
-
filePath:
|
|
18057
|
+
filePath: join15(stateRoot, "state", "terminals.json")
|
|
17111
18058
|
});
|
|
17112
18059
|
const sessionRepo = new SessionRepo({
|
|
17113
|
-
filePath:
|
|
18060
|
+
filePath: join15(stateRoot, "state", "sessions.json")
|
|
17114
18061
|
});
|
|
17115
18062
|
const terminalMgr = new TerminalManager({
|
|
17116
18063
|
ptyHost: createPtyHost(),
|
|
@@ -17118,10 +18065,10 @@ async function createServer(configOverrides) {
|
|
|
17118
18065
|
db: terminalRepo
|
|
17119
18066
|
});
|
|
17120
18067
|
const settingsRepo = new SettingsRepo({
|
|
17121
|
-
filePath:
|
|
18068
|
+
filePath: join15(stateRoot, "state", "settings.json")
|
|
17122
18069
|
});
|
|
17123
18070
|
const updateStateRepo = new UpdateStateRepo({
|
|
17124
|
-
filePath:
|
|
18071
|
+
filePath: join15(stateRoot, "state", "update-state.json"),
|
|
17125
18072
|
currentVersion: config.appVersion ?? "0.0.0"
|
|
17126
18073
|
});
|
|
17127
18074
|
const autoFetch = new AutoFetchScheduler({
|
|
@@ -17154,10 +18101,10 @@ async function createServer(configOverrides) {
|
|
|
17154
18101
|
}
|
|
17155
18102
|
});
|
|
17156
18103
|
const providerConfigRepo = new ProviderConfigRepo({
|
|
17157
|
-
filePath:
|
|
18104
|
+
filePath: join15(stateRoot, "state", "provider-configs.json")
|
|
17158
18105
|
});
|
|
17159
18106
|
const workspaceRepo = new WorkspaceRepo({
|
|
17160
|
-
filePath:
|
|
18107
|
+
filePath: join15(stateRoot, "state", "workspaces.json")
|
|
17161
18108
|
});
|
|
17162
18109
|
const sessionMgr = new SessionManager({
|
|
17163
18110
|
terminalMgr,
|
|
@@ -17192,10 +18139,13 @@ async function createServer(configOverrides) {
|
|
|
17192
18139
|
)
|
|
17193
18140
|
});
|
|
17194
18141
|
const authSessionRepo = new AuthSessionRepo({
|
|
17195
|
-
filePath:
|
|
18142
|
+
filePath: join15(stateRoot, "state", "auth-sessions.json")
|
|
17196
18143
|
});
|
|
17197
18144
|
const authLoginBlockRepo = new AuthLoginBlockRepo({
|
|
17198
|
-
filePath:
|
|
18145
|
+
filePath: join15(stateRoot, "state", "auth-login-blocks.json")
|
|
18146
|
+
});
|
|
18147
|
+
const appearanceAssetRepo = new AppearanceAssetRepo({
|
|
18148
|
+
filePath: join15(stateRoot, "state", "appearance-assets.json")
|
|
17199
18149
|
});
|
|
17200
18150
|
const app = await buildFastifyApp({
|
|
17201
18151
|
wsHub,
|
|
@@ -17204,6 +18154,7 @@ async function createServer(configOverrides) {
|
|
|
17204
18154
|
config,
|
|
17205
18155
|
authSessionRepo,
|
|
17206
18156
|
authLoginBlockRepo,
|
|
18157
|
+
appearanceAssetRepo,
|
|
17207
18158
|
logger: {
|
|
17208
18159
|
level: "info",
|
|
17209
18160
|
transport: {
|
|
@@ -17270,7 +18221,7 @@ async function createServer(configOverrides) {
|
|
|
17270
18221
|
...config.update,
|
|
17271
18222
|
currentVersion: config.appVersion ?? "0.0.0"
|
|
17272
18223
|
},
|
|
17273
|
-
updateWorkerLogFilePath:
|
|
18224
|
+
updateWorkerLogFilePath: join15(stateRoot, "logs", "update-worker.log"),
|
|
17274
18225
|
countRunningTerminals: () => terminalMgr.getAll().filter((terminal) => terminal.alive).length,
|
|
17275
18226
|
countRunningSessions: () => sessionMgr.getAll().filter((session) => session.state === "starting" || session.state === "running").length,
|
|
17276
18227
|
countActiveSupervisors: () => supervisorMgr?.countActive() ?? 0
|
|
@@ -17382,6 +18333,7 @@ var init_server = __esm({
|
|
|
17382
18333
|
init_e2e_provider_mock();
|
|
17383
18334
|
init_install_manager2();
|
|
17384
18335
|
init_manager3();
|
|
18336
|
+
init_appearance_asset_repo();
|
|
17385
18337
|
init_auth_login_block_repo();
|
|
17386
18338
|
init_auth_session_repo();
|
|
17387
18339
|
init_provider_config_repo();
|
|
@@ -17492,28 +18444,28 @@ var init_src4 = __esm({
|
|
|
17492
18444
|
});
|
|
17493
18445
|
|
|
17494
18446
|
// packages/cli/src/cli.ts
|
|
17495
|
-
import { existsSync as
|
|
17496
|
-
import { dirname as dirname10, join as
|
|
18447
|
+
import { existsSync as existsSync15 } from "fs";
|
|
18448
|
+
import { dirname as dirname10, join as join20 } from "path";
|
|
17497
18449
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
17498
18450
|
|
|
17499
18451
|
// packages/cli/src/auth-control.ts
|
|
17500
18452
|
await init_src4();
|
|
17501
|
-
import { join as
|
|
18453
|
+
import { join as join17 } from "node:path";
|
|
17502
18454
|
|
|
17503
18455
|
// packages/cli/src/config-store.ts
|
|
17504
18456
|
init_state_paths();
|
|
17505
|
-
import { existsSync as
|
|
18457
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
|
|
17506
18458
|
import { homedir as homedir4 } from "os";
|
|
17507
|
-
import { join as
|
|
18459
|
+
import { join as join16 } from "path";
|
|
17508
18460
|
function getCliConfigPath() {
|
|
17509
|
-
return
|
|
18461
|
+
return join16(homedir4(), ".coder-studio", "config.json");
|
|
17510
18462
|
}
|
|
17511
18463
|
function normalizeLegacyDataDir(input2) {
|
|
17512
18464
|
return normalizeLegacyStateDir(input2);
|
|
17513
18465
|
}
|
|
17514
18466
|
function readCliConfig() {
|
|
17515
18467
|
const path14 = getCliConfigPath();
|
|
17516
|
-
if (!
|
|
18468
|
+
if (!existsSync10(path14)) {
|
|
17517
18469
|
return null;
|
|
17518
18470
|
}
|
|
17519
18471
|
try {
|
|
@@ -17533,14 +18485,14 @@ function readCliConfig() {
|
|
|
17533
18485
|
}
|
|
17534
18486
|
function writeCliConfig(config) {
|
|
17535
18487
|
const path14 = getCliConfigPath();
|
|
17536
|
-
const dir =
|
|
18488
|
+
const dir = join16(homedir4(), ".coder-studio");
|
|
17537
18489
|
const normalizedConfig = {
|
|
17538
18490
|
...config.host !== void 0 ? { host: config.host } : {},
|
|
17539
18491
|
...config.port !== void 0 && config.port > 0 ? { port: config.port } : {},
|
|
17540
18492
|
...config.stateDir !== void 0 ? { stateDir: normalizeStateDir(config.stateDir) } : {},
|
|
17541
18493
|
...config.password !== void 0 ? { password: config.password } : {}
|
|
17542
18494
|
};
|
|
17543
|
-
if (!
|
|
18495
|
+
if (!existsSync10(dir)) {
|
|
17544
18496
|
mkdirSync8(dir, { recursive: true });
|
|
17545
18497
|
}
|
|
17546
18498
|
writeFileSync6(path14, JSON.stringify(normalizedConfig, null, 2), "utf-8");
|
|
@@ -17555,7 +18507,7 @@ function resolveStateDir() {
|
|
|
17555
18507
|
}
|
|
17556
18508
|
async function listAuthBlocks(now = Date.now()) {
|
|
17557
18509
|
const repo = new AuthLoginBlockRepo({
|
|
17558
|
-
filePath:
|
|
18510
|
+
filePath: join17(resolveStateDir(), "state", "auth-login-blocks.json")
|
|
17559
18511
|
});
|
|
17560
18512
|
return repo.listActiveBlocks(now).map((record) => ({
|
|
17561
18513
|
ip: record.ip,
|
|
@@ -17567,13 +18519,13 @@ async function listAuthBlocks(now = Date.now()) {
|
|
|
17567
18519
|
}
|
|
17568
18520
|
async function clearAuthBlockByIp(ip) {
|
|
17569
18521
|
const repo = new AuthLoginBlockRepo({
|
|
17570
|
-
filePath:
|
|
18522
|
+
filePath: join17(resolveStateDir(), "state", "auth-login-blocks.json")
|
|
17571
18523
|
});
|
|
17572
18524
|
return repo.delete(ip);
|
|
17573
18525
|
}
|
|
17574
18526
|
|
|
17575
18527
|
// packages/cli/src/browser.ts
|
|
17576
|
-
import { spawn as
|
|
18528
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
17577
18529
|
function getOpenCommand(url) {
|
|
17578
18530
|
switch (process.platform) {
|
|
17579
18531
|
case "darwin":
|
|
@@ -17586,8 +18538,8 @@ function getOpenCommand(url) {
|
|
|
17586
18538
|
}
|
|
17587
18539
|
async function openBrowser(url) {
|
|
17588
18540
|
const { command, args } = getOpenCommand(url);
|
|
17589
|
-
await new Promise((
|
|
17590
|
-
const child =
|
|
18541
|
+
await new Promise((resolve6, reject) => {
|
|
18542
|
+
const child = spawn6(command, args, {
|
|
17591
18543
|
detached: true,
|
|
17592
18544
|
stdio: "ignore",
|
|
17593
18545
|
windowsHide: true
|
|
@@ -17595,18 +18547,18 @@ async function openBrowser(url) {
|
|
|
17595
18547
|
child.once("error", reject);
|
|
17596
18548
|
child.once("spawn", () => {
|
|
17597
18549
|
child.unref();
|
|
17598
|
-
|
|
18550
|
+
resolve6();
|
|
17599
18551
|
});
|
|
17600
18552
|
});
|
|
17601
18553
|
}
|
|
17602
18554
|
|
|
17603
18555
|
// packages/cli/src/log-excerpt.ts
|
|
17604
|
-
import { closeSync, existsSync as
|
|
18556
|
+
import { closeSync, existsSync as existsSync11, openSync, readSync, statSync as statSync2 } from "fs";
|
|
17605
18557
|
var DEFAULT_MAX_LINES = 40;
|
|
17606
18558
|
var DEFAULT_MAX_CHARS = 4e3;
|
|
17607
18559
|
var DEFAULT_MAX_BYTES = 16 * 1024;
|
|
17608
18560
|
var getFileSize = (path14) => {
|
|
17609
|
-
if (!
|
|
18561
|
+
if (!existsSync11(path14)) {
|
|
17610
18562
|
return 0;
|
|
17611
18563
|
}
|
|
17612
18564
|
try {
|
|
@@ -17621,7 +18573,7 @@ var readLogExcerpt = (path14, {
|
|
|
17621
18573
|
maxLines = DEFAULT_MAX_LINES,
|
|
17622
18574
|
maxChars = DEFAULT_MAX_CHARS
|
|
17623
18575
|
} = {}) => {
|
|
17624
|
-
if (!
|
|
18576
|
+
if (!existsSync11(path14)) {
|
|
17625
18577
|
return null;
|
|
17626
18578
|
}
|
|
17627
18579
|
const fileSize = getFileSize(path14);
|
|
@@ -17700,12 +18652,12 @@ function assertSupportedNodeVersion(version = process.versions.node) {
|
|
|
17700
18652
|
}
|
|
17701
18653
|
|
|
17702
18654
|
// packages/cli/src/package-manifest.ts
|
|
17703
|
-
import { existsSync as
|
|
18655
|
+
import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
|
|
17704
18656
|
function resolveCliPackageManifestUrl(importMetaUrl) {
|
|
17705
18657
|
const manifestUrl = [
|
|
17706
18658
|
new URL("../package.json", importMetaUrl),
|
|
17707
18659
|
new URL("../../package.json", importMetaUrl)
|
|
17708
|
-
].find((candidate) =>
|
|
18660
|
+
].find((candidate) => existsSync12(candidate));
|
|
17709
18661
|
if (!manifestUrl) {
|
|
17710
18662
|
throw new Error("Unable to locate CLI package.json");
|
|
17711
18663
|
}
|
|
@@ -17928,7 +18880,7 @@ function parseArgs(argv) {
|
|
|
17928
18880
|
init_runtime();
|
|
17929
18881
|
import { mkdirSync as mkdirSync9 } from "fs";
|
|
17930
18882
|
import { homedir as homedir5 } from "os";
|
|
17931
|
-
import { join as
|
|
18883
|
+
import { join as join18 } from "path";
|
|
17932
18884
|
var MANAGED_SERVER_NAME = "coder-studio-server";
|
|
17933
18885
|
var PM2_RESTART_DELAY_MS = 2e3;
|
|
17934
18886
|
var PM2_MIN_UPTIME = "5s";
|
|
@@ -17968,29 +18920,29 @@ async function loadPm2() {
|
|
|
17968
18920
|
}
|
|
17969
18921
|
var connectPm2 = async () => {
|
|
17970
18922
|
const pm2 = await loadPm2();
|
|
17971
|
-
return new Promise((
|
|
18923
|
+
return new Promise((resolve6, reject) => {
|
|
17972
18924
|
pm2.connect((error) => {
|
|
17973
18925
|
if (error) {
|
|
17974
18926
|
reject(error);
|
|
17975
18927
|
return;
|
|
17976
18928
|
}
|
|
17977
|
-
|
|
18929
|
+
resolve6();
|
|
17978
18930
|
});
|
|
17979
18931
|
});
|
|
17980
18932
|
};
|
|
17981
|
-
var sleep = async (ms) => new Promise((
|
|
17982
|
-
setTimeout(
|
|
18933
|
+
var sleep = async (ms) => new Promise((resolve6) => {
|
|
18934
|
+
setTimeout(resolve6, ms);
|
|
17983
18935
|
});
|
|
17984
18936
|
var disconnectPm2 = async () => {
|
|
17985
18937
|
const pm2 = await loadPm2();
|
|
17986
|
-
await new Promise((
|
|
18938
|
+
await new Promise((resolve6) => {
|
|
17987
18939
|
let settled = false;
|
|
17988
18940
|
const finish = () => {
|
|
17989
18941
|
if (settled) {
|
|
17990
18942
|
return;
|
|
17991
18943
|
}
|
|
17992
18944
|
settled = true;
|
|
17993
|
-
|
|
18945
|
+
resolve6();
|
|
17994
18946
|
};
|
|
17995
18947
|
const timer = setTimeout(finish, PM2_DISCONNECT_WAIT_MS);
|
|
17996
18948
|
try {
|
|
@@ -18004,29 +18956,29 @@ var disconnectPm2 = async () => {
|
|
|
18004
18956
|
}
|
|
18005
18957
|
});
|
|
18006
18958
|
};
|
|
18007
|
-
var describeManagedServer = async (pm2) => new Promise((
|
|
18959
|
+
var describeManagedServer = async (pm2) => new Promise((resolve6, reject) => {
|
|
18008
18960
|
pm2.describe(MANAGED_SERVER_NAME, (error, result) => {
|
|
18009
18961
|
if (error) {
|
|
18010
18962
|
reject(error);
|
|
18011
18963
|
return;
|
|
18012
18964
|
}
|
|
18013
|
-
|
|
18965
|
+
resolve6(result ?? []);
|
|
18014
18966
|
});
|
|
18015
18967
|
});
|
|
18016
|
-
var removeManagedServer = async (pm2) => new Promise((
|
|
18968
|
+
var removeManagedServer = async (pm2) => new Promise((resolve6, reject) => {
|
|
18017
18969
|
pm2.delete(MANAGED_SERVER_NAME, (error) => {
|
|
18018
18970
|
if (error) {
|
|
18019
18971
|
reject(error);
|
|
18020
18972
|
return;
|
|
18021
18973
|
}
|
|
18022
|
-
|
|
18974
|
+
resolve6();
|
|
18023
18975
|
});
|
|
18024
18976
|
});
|
|
18025
18977
|
var killPm2Daemon = async () => {
|
|
18026
18978
|
const pm2 = await loadPm2();
|
|
18027
|
-
return new Promise((
|
|
18979
|
+
return new Promise((resolve6) => {
|
|
18028
18980
|
pm2.kill(() => {
|
|
18029
|
-
|
|
18981
|
+
resolve6();
|
|
18030
18982
|
});
|
|
18031
18983
|
});
|
|
18032
18984
|
};
|
|
@@ -18149,11 +19101,11 @@ var deleteManagedServerInSession = async (pm2, {
|
|
|
18149
19101
|
return true;
|
|
18150
19102
|
};
|
|
18151
19103
|
var ensureLogDirectory = () => {
|
|
18152
|
-
mkdirSync9(
|
|
19104
|
+
mkdirSync9(join18(homedir5(), ".coder-studio", "logs"), { recursive: true });
|
|
18153
19105
|
};
|
|
18154
19106
|
var getLogPaths = () => ({
|
|
18155
|
-
outFile:
|
|
18156
|
-
errFile:
|
|
19107
|
+
outFile: join18(homedir5(), ".coder-studio", "logs", "server.out.log"),
|
|
19108
|
+
errFile: join18(homedir5(), ".coder-studio", "logs", "server.err.log")
|
|
18157
19109
|
});
|
|
18158
19110
|
var captureStartupLogOffsets = () => {
|
|
18159
19111
|
const { outFile, errFile } = getLogPaths();
|
|
@@ -18202,7 +19154,7 @@ var startManagedServer = async ({
|
|
|
18202
19154
|
ensureLogDirectory();
|
|
18203
19155
|
const { outFile, errFile } = getLogPaths();
|
|
18204
19156
|
const logOffsets = captureStartupLogOffsets();
|
|
18205
|
-
await new Promise((
|
|
19157
|
+
await new Promise((resolve6, reject) => {
|
|
18206
19158
|
pm2.start(
|
|
18207
19159
|
{
|
|
18208
19160
|
name: MANAGED_SERVER_NAME,
|
|
@@ -18225,7 +19177,7 @@ var startManagedServer = async ({
|
|
|
18225
19177
|
reject(error);
|
|
18226
19178
|
return;
|
|
18227
19179
|
}
|
|
18228
|
-
|
|
19180
|
+
resolve6();
|
|
18229
19181
|
}
|
|
18230
19182
|
);
|
|
18231
19183
|
});
|
|
@@ -18281,12 +19233,12 @@ var getManagedServerStatus = async () => withPm2Connection(async (pm2) => {
|
|
|
18281
19233
|
|
|
18282
19234
|
// packages/cli/src/prompts.ts
|
|
18283
19235
|
import { stdin as input, stdout as output } from "node:process";
|
|
18284
|
-
import { createInterface } from "node:readline/promises";
|
|
19236
|
+
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
18285
19237
|
function isInteractiveSession() {
|
|
18286
19238
|
return Boolean(input.isTTY && output.isTTY);
|
|
18287
19239
|
}
|
|
18288
19240
|
async function confirmYesNo(prompt) {
|
|
18289
|
-
const rl =
|
|
19241
|
+
const rl = createInterface2({ input, output });
|
|
18290
19242
|
try {
|
|
18291
19243
|
const answer = (await rl.question(prompt)).trim().toLowerCase();
|
|
18292
19244
|
return answer === "y" || answer === "yes";
|
|
@@ -18341,30 +19293,30 @@ import { mkdirSync as mkdirSync10 } from "fs";
|
|
|
18341
19293
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
18342
19294
|
|
|
18343
19295
|
// packages/cli/src/embed.ts
|
|
18344
|
-
import { existsSync as
|
|
18345
|
-
import { dirname as dirname8, resolve as
|
|
19296
|
+
import { existsSync as existsSync13 } from "fs";
|
|
19297
|
+
import { dirname as dirname8, resolve as resolve5 } from "path";
|
|
18346
19298
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
18347
19299
|
var __filename = fileURLToPath2(import.meta.url);
|
|
18348
19300
|
var __dirname = dirname8(__filename);
|
|
18349
|
-
var WEB_ASSETS_DIR =
|
|
19301
|
+
var WEB_ASSETS_DIR = resolve5(__dirname, "../web");
|
|
18350
19302
|
function getStaticAssetsDir() {
|
|
18351
19303
|
return WEB_ASSETS_DIR;
|
|
18352
19304
|
}
|
|
18353
19305
|
function hasWebAssets() {
|
|
18354
|
-
return
|
|
19306
|
+
return existsSync13(WEB_ASSETS_DIR);
|
|
18355
19307
|
}
|
|
18356
19308
|
|
|
18357
19309
|
// packages/cli/src/update-runtime.ts
|
|
18358
|
-
import { existsSync as
|
|
18359
|
-
import { dirname as dirname9, join as
|
|
19310
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
19311
|
+
import { dirname as dirname9, join as join19 } from "node:path";
|
|
18360
19312
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
18361
19313
|
function resolveWorkerEntryPath(importMetaUrl) {
|
|
18362
19314
|
const currentDir = dirname9(fileURLToPath3(importMetaUrl));
|
|
18363
19315
|
const candidates = [
|
|
18364
|
-
|
|
18365
|
-
|
|
19316
|
+
join19(currentDir, "update-worker.mjs"),
|
|
19317
|
+
join19(currentDir, "../src/update-worker.ts")
|
|
18366
19318
|
];
|
|
18367
|
-
return candidates.find((candidate) =>
|
|
19319
|
+
return candidates.find((candidate) => existsSync14(candidate));
|
|
18368
19320
|
}
|
|
18369
19321
|
function getUpdateRuntimeInfo(importMetaUrl) {
|
|
18370
19322
|
const workerEntryPath = resolveWorkerEntryPath(importMetaUrl);
|
|
@@ -18591,11 +19543,11 @@ function resolveManagedScriptPath() {
|
|
|
18591
19543
|
const currentFile = fileURLToPath5(import.meta.url);
|
|
18592
19544
|
const currentDir = dirname10(currentFile);
|
|
18593
19545
|
const candidates = [
|
|
18594
|
-
|
|
18595
|
-
|
|
18596
|
-
|
|
19546
|
+
join20(currentDir, "server-runner.js"),
|
|
19547
|
+
join20(currentDir, "server-runner.mjs"),
|
|
19548
|
+
join20(currentDir, "../src/server-runner.ts")
|
|
18597
19549
|
];
|
|
18598
|
-
const scriptPath = candidates.find((candidate) =>
|
|
19550
|
+
const scriptPath = candidates.find((candidate) => existsSync15(candidate));
|
|
18599
19551
|
if (!scriptPath) {
|
|
18600
19552
|
throw new Error("Unable to locate the managed server entry script");
|
|
18601
19553
|
}
|