@spencer-kit/coder-studio 0.4.4 → 0.4.6
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 +1091 -438
- package/dist/esm/bin.mjs.map +4 -4
- package/dist/esm/server-runner.mjs +1087 -434
- package/dist/esm/server-runner.mjs.map +4 -4
- package/dist/web/assets/components-BGEBLvHB.css +1 -0
- package/dist/web/assets/components-BuRmXlh8.js +109 -0
- package/dist/web/assets/components-BuRmXlh8.js.map +1 -0
- package/dist/web/assets/main-DZPwC4NB.js +2 -0
- package/dist/web/assets/main-DZPwC4NB.js.map +1 -0
- package/dist/web/assets/ui-preview-BzE9924q.js +17 -0
- package/dist/web/assets/ui-preview-BzE9924q.js.map +1 -0
- package/dist/web/index.html +3 -3
- package/dist/web/ui-preview.html +3 -3
- package/package.json +3 -3
- package/dist/web/assets/components-C4SKshs2.js +0 -110
- package/dist/web/assets/components-C4SKshs2.js.map +0 -1
- package/dist/web/assets/components-CMahvybm.css +0 -1
- package/dist/web/assets/main-CZuF2VZA.js +0 -2
- package/dist/web/assets/main-CZuF2VZA.js.map +0 -1
- package/dist/web/assets/ui-preview-DCeC0YmD.js +0 -17
- package/dist/web/assets/ui-preview-DCeC0YmD.js.map +0 -1
package/dist/esm/bin.mjs
CHANGED
|
@@ -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 relative4 = path7.relative(resolvedRoot, resolvedTarget);
|
|
2558
|
-
if (!relative4) {
|
|
2559
|
-
return;
|
|
2560
|
-
}
|
|
2561
|
-
let current = resolvedRoot;
|
|
2562
|
-
for (const segment of relative4.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;
|
|
@@ -6566,7 +6892,8 @@ var init_manager3 = __esm({
|
|
|
6566
6892
|
...cmd.env,
|
|
6567
6893
|
CODER_STUDIO_SESSION_ID: sessionId
|
|
6568
6894
|
},
|
|
6569
|
-
title: req.provider.displayName
|
|
6895
|
+
title: req.provider.displayName,
|
|
6896
|
+
themeBackground: req.themeBackground
|
|
6570
6897
|
};
|
|
6571
6898
|
const terminal = this.deps.terminalMgr.create(terminalSpec);
|
|
6572
6899
|
const active = new ActiveSession({
|
|
@@ -6708,6 +7035,7 @@ var init_manager3 = __esm({
|
|
|
6708
7035
|
this.applyTerminalInputActivity(session, activity, text, { armTurnCompletion: true });
|
|
6709
7036
|
}
|
|
6710
7037
|
applyTerminalInputActivity(session, activity, text, options) {
|
|
7038
|
+
const completedSynchronously = !options.armTurnCompletion && session.state === "idle" && !session.awaitingTurnCompletion;
|
|
6711
7039
|
if (activity === "control" || activity === "typing") {
|
|
6712
7040
|
return;
|
|
6713
7041
|
}
|
|
@@ -6716,16 +7044,10 @@ var init_manager3 = __esm({
|
|
|
6716
7044
|
session.awaitingTurnCompletion = true;
|
|
6717
7045
|
session.sawOutputSinceTurnStart = false;
|
|
6718
7046
|
}
|
|
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");
|
|
7047
|
+
if (completedSynchronously) {
|
|
7048
|
+
return;
|
|
6728
7049
|
}
|
|
7050
|
+
this.transitionSessionToRunning(session);
|
|
6729
7051
|
return;
|
|
6730
7052
|
}
|
|
6731
7053
|
if (activity !== "submit") return;
|
|
@@ -6738,17 +7060,11 @@ var init_manager3 = __esm({
|
|
|
6738
7060
|
session.sawOutputSinceTurnStart = false;
|
|
6739
7061
|
}
|
|
6740
7062
|
const titleChanged = this.maybeAssignTitle(session, submittedText);
|
|
6741
|
-
const prev = session.state;
|
|
6742
7063
|
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");
|
|
7064
|
+
if (shouldResume && !completedSynchronously) {
|
|
7065
|
+
this.transitionSessionToRunning(session);
|
|
6751
7066
|
} else if (titleChanged) {
|
|
7067
|
+
const prev = session.state;
|
|
6752
7068
|
this.emitStateChanged(session, prev, session.state);
|
|
6753
7069
|
}
|
|
6754
7070
|
}
|
|
@@ -6762,9 +7078,11 @@ var init_manager3 = __esm({
|
|
|
6762
7078
|
}
|
|
6763
7079
|
const text = activity === "submit" || activity === "internal_submit" ? submittedText ?? bytes.toString("utf-8") : void 0;
|
|
6764
7080
|
const rollbackArm = this.armTurnCompletionBeforeWrite(session, activity);
|
|
7081
|
+
const rollbackRecentInput = this.recordRecentInputBeforeWrite(session, bytes, activity);
|
|
6765
7082
|
try {
|
|
6766
7083
|
this.deps.terminalMgr.write(session.terminalId, bytes);
|
|
6767
7084
|
} catch (error) {
|
|
7085
|
+
rollbackRecentInput?.();
|
|
6768
7086
|
rollbackArm?.();
|
|
6769
7087
|
throw error;
|
|
6770
7088
|
}
|
|
@@ -6826,6 +7144,19 @@ var init_manager3 = __esm({
|
|
|
6826
7144
|
this.deps.db.update(session.id, { title });
|
|
6827
7145
|
return true;
|
|
6828
7146
|
}
|
|
7147
|
+
transitionSessionToRunning(session) {
|
|
7148
|
+
if (session.state === "running") {
|
|
7149
|
+
return;
|
|
7150
|
+
}
|
|
7151
|
+
const prev = session.state;
|
|
7152
|
+
session.state = "running";
|
|
7153
|
+
session.lastActiveAt = Date.now();
|
|
7154
|
+
this.deps.db.update(session.id, {
|
|
7155
|
+
state: "running",
|
|
7156
|
+
lastActiveAt: session.lastActiveAt
|
|
7157
|
+
});
|
|
7158
|
+
this.emitStateChanged(session, prev, "running");
|
|
7159
|
+
}
|
|
6829
7160
|
/**
|
|
6830
7161
|
* Handle terminal exit event
|
|
6831
7162
|
*/
|
|
@@ -6887,6 +7218,197 @@ var init_manager3 = __esm({
|
|
|
6887
7218
|
session.sawOutputSinceTurnStart = previousSawOutputSinceTurnStart;
|
|
6888
7219
|
};
|
|
6889
7220
|
}
|
|
7221
|
+
recordRecentInputBeforeWrite(session, bytes, activity) {
|
|
7222
|
+
if (activity === "system") {
|
|
7223
|
+
return null;
|
|
7224
|
+
}
|
|
7225
|
+
const { opaque, remainingVisibleText } = classifyRecentInputEcho(bytes);
|
|
7226
|
+
if (!opaque && remainingVisibleText.length === 0) {
|
|
7227
|
+
return null;
|
|
7228
|
+
}
|
|
7229
|
+
const recentInput = {
|
|
7230
|
+
at: Date.now(),
|
|
7231
|
+
activity,
|
|
7232
|
+
byteLength: bytes.byteLength,
|
|
7233
|
+
opaque,
|
|
7234
|
+
remainingVisibleText
|
|
7235
|
+
};
|
|
7236
|
+
session.recentInputEchoes.push(recentInput);
|
|
7237
|
+
this.pruneRecentInputEchoes(session, recentInput.at);
|
|
7238
|
+
return () => {
|
|
7239
|
+
const index = session.recentInputEchoes.indexOf(recentInput);
|
|
7240
|
+
if (index !== -1) {
|
|
7241
|
+
session.recentInputEchoes.splice(index, 1);
|
|
7242
|
+
}
|
|
7243
|
+
};
|
|
7244
|
+
}
|
|
7245
|
+
pruneRecentInputEchoes(session, now = Date.now()) {
|
|
7246
|
+
session.recentInputEchoes = session.recentInputEchoes.filter(
|
|
7247
|
+
(entry) => now - entry.at <= RECENT_INPUT_ECHO_WINDOW_MS && (entry.opaque || entry.remainingVisibleText.length > 0)
|
|
7248
|
+
);
|
|
7249
|
+
let totalBytes = session.recentInputEchoes.reduce((sum, entry) => sum + entry.byteLength, 0);
|
|
7250
|
+
while (session.recentInputEchoes.length > RECENT_INPUT_ECHO_MAX_EVENTS || totalBytes > RECENT_INPUT_ECHO_MAX_BYTES) {
|
|
7251
|
+
const removed = session.recentInputEchoes.shift();
|
|
7252
|
+
if (!removed) {
|
|
7253
|
+
break;
|
|
7254
|
+
}
|
|
7255
|
+
totalBytes -= removed.byteLength;
|
|
7256
|
+
}
|
|
7257
|
+
}
|
|
7258
|
+
clearPendingResumeAggregation(session) {
|
|
7259
|
+
if (session.pendingResumeAggregation?.timer) {
|
|
7260
|
+
clearTimeout(session.pendingResumeAggregation.timer);
|
|
7261
|
+
}
|
|
7262
|
+
session.pendingResumeAggregation = null;
|
|
7263
|
+
}
|
|
7264
|
+
consumeRecentLiteralEcho(session, visibleOutput) {
|
|
7265
|
+
let offset = 0;
|
|
7266
|
+
let matchedLiteralEcho = false;
|
|
7267
|
+
for (const entry of session.recentInputEchoes) {
|
|
7268
|
+
if (offset >= visibleOutput.length) {
|
|
7269
|
+
break;
|
|
7270
|
+
}
|
|
7271
|
+
if (entry.remainingVisibleText.length === 0) {
|
|
7272
|
+
continue;
|
|
7273
|
+
}
|
|
7274
|
+
const currentVisibleText = entry.remainingVisibleText;
|
|
7275
|
+
const matchLength = commonPrefixLength(visibleOutput.slice(offset), currentVisibleText);
|
|
7276
|
+
if (matchLength === 0) {
|
|
7277
|
+
break;
|
|
7278
|
+
}
|
|
7279
|
+
entry.remainingVisibleText = currentVisibleText.slice(matchLength);
|
|
7280
|
+
offset += matchLength;
|
|
7281
|
+
matchedLiteralEcho = true;
|
|
7282
|
+
if (matchLength < currentVisibleText.length) {
|
|
7283
|
+
break;
|
|
7284
|
+
}
|
|
7285
|
+
}
|
|
7286
|
+
return {
|
|
7287
|
+
matchedLiteralEcho,
|
|
7288
|
+
leftoverVisibleOutput: visibleOutput.slice(offset)
|
|
7289
|
+
};
|
|
7290
|
+
}
|
|
7291
|
+
hasRecentNonSubmitInput(session, now) {
|
|
7292
|
+
return session.recentInputEchoes.some(
|
|
7293
|
+
(entry) => entry.activity !== "submit" && entry.activity !== "internal_submit" && now - entry.at <= RECENT_OPAQUE_INPUT_ECHO_WINDOW_MS
|
|
7294
|
+
);
|
|
7295
|
+
}
|
|
7296
|
+
hasRecentOpaqueNonSubmitInput(session, now) {
|
|
7297
|
+
return session.recentInputEchoes.some(
|
|
7298
|
+
(entry) => entry.opaque && entry.activity !== "submit" && entry.activity !== "internal_submit" && now - entry.at <= RECENT_OPAQUE_INPUT_ECHO_WINDOW_MS
|
|
7299
|
+
);
|
|
7300
|
+
}
|
|
7301
|
+
hasRecentControlInput(session, now) {
|
|
7302
|
+
return session.recentInputEchoes.some(
|
|
7303
|
+
(entry) => entry.activity === "control" && now - entry.at <= RECENT_OPAQUE_INPUT_ECHO_WINDOW_MS
|
|
7304
|
+
);
|
|
7305
|
+
}
|
|
7306
|
+
getRecentVisibleInputText(session) {
|
|
7307
|
+
return session.recentInputEchoes.filter((entry) => entry.activity !== "submit" && entry.activity !== "internal_submit").map((entry) => entry.remainingVisibleText).join("");
|
|
7308
|
+
}
|
|
7309
|
+
isLikelyPureInputRepaint(session, chunk, visibleOutput, now) {
|
|
7310
|
+
if (!this.hasRecentNonSubmitInput(session, now)) {
|
|
7311
|
+
return false;
|
|
7312
|
+
}
|
|
7313
|
+
if (!visibleOutput.trim()) {
|
|
7314
|
+
return true;
|
|
7315
|
+
}
|
|
7316
|
+
const chunkText = chunk.toString("utf8");
|
|
7317
|
+
if (chunkText.includes("\n")) {
|
|
7318
|
+
return false;
|
|
7319
|
+
}
|
|
7320
|
+
if (visibleOutputHasMultipleLines(visibleOutput)) {
|
|
7321
|
+
return false;
|
|
7322
|
+
}
|
|
7323
|
+
if (!chunkHasLineRepaintControl(chunkText)) {
|
|
7324
|
+
return false;
|
|
7325
|
+
}
|
|
7326
|
+
if (this.hasRecentOpaqueNonSubmitInput(session, now)) {
|
|
7327
|
+
return true;
|
|
7328
|
+
}
|
|
7329
|
+
const recentVisibleInputText = this.getRecentVisibleInputText(session);
|
|
7330
|
+
const trimmedVisibleOutput = visibleOutput.trim();
|
|
7331
|
+
if (!recentVisibleInputText || !trimmedVisibleOutput) {
|
|
7332
|
+
return false;
|
|
7333
|
+
}
|
|
7334
|
+
return recentVisibleInputText === trimmedVisibleOutput || recentVisibleInputText.startsWith(trimmedVisibleOutput) || trimmedVisibleOutput.endsWith(recentVisibleInputText);
|
|
7335
|
+
}
|
|
7336
|
+
assessTerminalOutput(session, chunk) {
|
|
7337
|
+
const now = Date.now();
|
|
7338
|
+
this.pruneRecentInputEchoes(session, now);
|
|
7339
|
+
if (session.recentInputEchoes.length === 0) {
|
|
7340
|
+
const hasVisibleText = extractVisibleTerminalText(chunk).trim().length > 0;
|
|
7341
|
+
return {
|
|
7342
|
+
shouldResumeRunning: hasVisibleText,
|
|
7343
|
+
countsAsTurnOutput: hasVisibleText,
|
|
7344
|
+
shouldAggregateForResume: false
|
|
7345
|
+
};
|
|
7346
|
+
}
|
|
7347
|
+
const visibleOutput = extractVisibleTerminalText(chunk);
|
|
7348
|
+
const { matchedLiteralEcho, leftoverVisibleOutput } = this.consumeRecentLiteralEcho(
|
|
7349
|
+
session,
|
|
7350
|
+
visibleOutput
|
|
7351
|
+
);
|
|
7352
|
+
const hasRecentNonSubmitInput = this.hasRecentNonSubmitInput(session, now);
|
|
7353
|
+
const hasRecentControlInput = this.hasRecentControlInput(session, now);
|
|
7354
|
+
this.pruneRecentInputEchoes(session, now);
|
|
7355
|
+
const trimmedLeftoverVisibleOutput = leftoverVisibleOutput.trim();
|
|
7356
|
+
const trimmedVisibleOutput = visibleOutput.trim();
|
|
7357
|
+
const suppressImmediateResumeAfterControl = hasRecentControlInput && !matchedLiteralEcho;
|
|
7358
|
+
const chunkText = chunk.toString("utf8");
|
|
7359
|
+
const shouldAggregateForResume = session.state === "idle" && hasRecentNonSubmitInput && !matchedLiteralEcho && trimmedVisibleOutput.length > 0 && chunkHasLineRepaintControl(chunkText) && !visibleOutputHasMultipleLines(visibleOutput);
|
|
7360
|
+
const shouldResumeRunning = suppressImmediateResumeAfterControl ? false : trimmedLeftoverVisibleOutput.length > 0 ? !this.isLikelyPureInputRepaint(session, chunk, leftoverVisibleOutput, now) : !matchedLiteralEcho && trimmedVisibleOutput.length > 0 && !this.isLikelyPureInputRepaint(session, chunk, visibleOutput, now);
|
|
7361
|
+
const countsAsTurnOutput = !suppressImmediateResumeAfterControl && (trimmedLeftoverVisibleOutput.length > 0 ? !this.isLikelyPureInputRepaint(session, chunk, leftoverVisibleOutput, now) : !matchedLiteralEcho && trimmedVisibleOutput.length > 0 && !this.isLikelyPureInputRepaint(session, chunk, visibleOutput, now));
|
|
7362
|
+
return {
|
|
7363
|
+
shouldResumeRunning,
|
|
7364
|
+
countsAsTurnOutput,
|
|
7365
|
+
shouldAggregateForResume
|
|
7366
|
+
};
|
|
7367
|
+
}
|
|
7368
|
+
flushPendingResumeAggregation(session) {
|
|
7369
|
+
const pending = session.pendingResumeAggregation;
|
|
7370
|
+
if (!pending) {
|
|
7371
|
+
return;
|
|
7372
|
+
}
|
|
7373
|
+
this.clearPendingResumeAggregation(session);
|
|
7374
|
+
if (session.state !== "idle") {
|
|
7375
|
+
return;
|
|
7376
|
+
}
|
|
7377
|
+
const combinedChunk = Buffer.concat(pending.chunks, pending.byteLength);
|
|
7378
|
+
const outputAssessment = this.assessTerminalOutput(session, combinedChunk);
|
|
7379
|
+
if (outputAssessment.countsAsTurnOutput) {
|
|
7380
|
+
session.sawOutputSinceTurnStart = true;
|
|
7381
|
+
}
|
|
7382
|
+
}
|
|
7383
|
+
schedulePendingResumeAggregation(session, chunk) {
|
|
7384
|
+
const pending = session.pendingResumeAggregation;
|
|
7385
|
+
if (!pending) {
|
|
7386
|
+
const nextPending = {
|
|
7387
|
+
startedAt: Date.now(),
|
|
7388
|
+
chunks: [chunk],
|
|
7389
|
+
byteLength: chunk.byteLength,
|
|
7390
|
+
timer: null
|
|
7391
|
+
};
|
|
7392
|
+
nextPending.timer = setTimeout(() => {
|
|
7393
|
+
const activeSession = this.sessions.get(session.id);
|
|
7394
|
+
if (!activeSession) {
|
|
7395
|
+
return;
|
|
7396
|
+
}
|
|
7397
|
+
this.flushPendingResumeAggregation(activeSession);
|
|
7398
|
+
}, RESUME_OUTPUT_AGGREGATION_WINDOW_MS);
|
|
7399
|
+
session.pendingResumeAggregation = nextPending;
|
|
7400
|
+
return;
|
|
7401
|
+
}
|
|
7402
|
+
pending.chunks.push(chunk);
|
|
7403
|
+
pending.byteLength += chunk.byteLength;
|
|
7404
|
+
const combinedChunk = Buffer.concat(pending.chunks, pending.byteLength);
|
|
7405
|
+
const visibleOutput = extractVisibleTerminalText(combinedChunk);
|
|
7406
|
+
const combinedText = combinedChunk.toString("utf8");
|
|
7407
|
+
const shouldFlushNow = pending.byteLength >= RESUME_OUTPUT_AGGREGATION_MAX_BYTES || combinedText.includes("\n") || visibleOutputHasMultipleLines(visibleOutput);
|
|
7408
|
+
if (shouldFlushNow) {
|
|
7409
|
+
this.flushPendingResumeAggregation(session);
|
|
7410
|
+
}
|
|
7411
|
+
}
|
|
6890
7412
|
flushPendingPtyIdle(session) {
|
|
6891
7413
|
const ptyState = this.comparators.get(session.id)?.snapshot().ptyState;
|
|
6892
7414
|
if (ptyState !== "idle") {
|
|
@@ -6895,6 +7417,7 @@ var init_manager3 = __esm({
|
|
|
6895
7417
|
this.transitionSessionToIdle(session);
|
|
6896
7418
|
}
|
|
6897
7419
|
transitionSessionToIdle(activeSession) {
|
|
7420
|
+
this.clearPendingResumeAggregation(activeSession);
|
|
6898
7421
|
const prev = activeSession.state;
|
|
6899
7422
|
if (prev !== "running" && prev !== "starting") {
|
|
6900
7423
|
return;
|
|
@@ -6957,7 +7480,24 @@ var init_manager3 = __esm({
|
|
|
6957
7480
|
return;
|
|
6958
7481
|
}
|
|
6959
7482
|
const activeSession = this.sessions.get(session.id);
|
|
6960
|
-
if (activeSession
|
|
7483
|
+
if (!activeSession) {
|
|
7484
|
+
return;
|
|
7485
|
+
}
|
|
7486
|
+
if (activeSession.pendingResumeAggregation && activeSession.state !== "idle") {
|
|
7487
|
+
this.clearPendingResumeAggregation(activeSession);
|
|
7488
|
+
}
|
|
7489
|
+
if (activeSession.pendingResumeAggregation) {
|
|
7490
|
+
this.schedulePendingResumeAggregation(activeSession, event.chunk);
|
|
7491
|
+
detector.feed(event.chunk);
|
|
7492
|
+
return;
|
|
7493
|
+
}
|
|
7494
|
+
const outputAssessment = this.assessTerminalOutput(activeSession, event.chunk);
|
|
7495
|
+
if (outputAssessment.shouldAggregateForResume) {
|
|
7496
|
+
this.schedulePendingResumeAggregation(activeSession, event.chunk);
|
|
7497
|
+
detector.feed(event.chunk);
|
|
7498
|
+
return;
|
|
7499
|
+
}
|
|
7500
|
+
if (outputAssessment.countsAsTurnOutput) {
|
|
6961
7501
|
activeSession.sawOutputSinceTurnStart = true;
|
|
6962
7502
|
}
|
|
6963
7503
|
detector.feed(event.chunk);
|
|
@@ -6974,6 +7514,7 @@ var init_manager3 = __esm({
|
|
|
6974
7514
|
this.comparators.delete(sessionId);
|
|
6975
7515
|
}
|
|
6976
7516
|
finishSession(session, exitCode) {
|
|
7517
|
+
this.clearPendingResumeAggregation(session);
|
|
6977
7518
|
const prev = session.state;
|
|
6978
7519
|
session.state = "ended";
|
|
6979
7520
|
session.endedAt = Date.now();
|
|
@@ -7005,6 +7546,8 @@ var init_manager3 = __esm({
|
|
|
7005
7546
|
latestSubmittedUserInput;
|
|
7006
7547
|
awaitingTurnCompletion = false;
|
|
7007
7548
|
sawOutputSinceTurnStart = false;
|
|
7549
|
+
recentInputEchoes = [];
|
|
7550
|
+
pendingResumeAggregation = null;
|
|
7008
7551
|
constructor(data) {
|
|
7009
7552
|
this.id = data.id;
|
|
7010
7553
|
this.workspaceId = data.workspaceId;
|
|
@@ -7047,17 +7590,17 @@ var init_manager3 = __esm({
|
|
|
7047
7590
|
});
|
|
7048
7591
|
|
|
7049
7592
|
// packages/server/src/storage/repositories/auth-login-block-repo.ts
|
|
7050
|
-
function
|
|
7593
|
+
function isRecord3(value) {
|
|
7051
7594
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
7052
7595
|
}
|
|
7053
7596
|
function isAuthLoginBlockRecord(value) {
|
|
7054
|
-
if (!
|
|
7597
|
+
if (!isRecord3(value)) {
|
|
7055
7598
|
return false;
|
|
7056
7599
|
}
|
|
7057
7600
|
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
7601
|
}
|
|
7059
7602
|
function normalizeFailures(value) {
|
|
7060
|
-
if (!
|
|
7603
|
+
if (!isRecord3(value)) {
|
|
7061
7604
|
return {};
|
|
7062
7605
|
}
|
|
7063
7606
|
const normalized = {};
|
|
@@ -7071,9 +7614,9 @@ function normalizeFailures(value) {
|
|
|
7071
7614
|
return normalized;
|
|
7072
7615
|
}
|
|
7073
7616
|
function normalizeState(value) {
|
|
7074
|
-
if (
|
|
7617
|
+
if (isRecord3(value) && value.version === 1) {
|
|
7075
7618
|
const blocks = {};
|
|
7076
|
-
if (
|
|
7619
|
+
if (isRecord3(value.blocks)) {
|
|
7077
7620
|
for (const entry of Object.values(value.blocks)) {
|
|
7078
7621
|
if (isAuthLoginBlockRecord(entry)) {
|
|
7079
7622
|
blocks[entry.ip] = entry;
|
|
@@ -7086,7 +7629,7 @@ function normalizeState(value) {
|
|
|
7086
7629
|
failures: normalizeFailures(value.failures)
|
|
7087
7630
|
};
|
|
7088
7631
|
}
|
|
7089
|
-
if (
|
|
7632
|
+
if (isRecord3(value)) {
|
|
7090
7633
|
const blocks = {};
|
|
7091
7634
|
for (const [ip, entry] of Object.entries(value)) {
|
|
7092
7635
|
if (isAuthLoginBlockRecord(entry)) {
|
|
@@ -7192,17 +7735,17 @@ var init_auth_login_block_repo = __esm({
|
|
|
7192
7735
|
});
|
|
7193
7736
|
|
|
7194
7737
|
// packages/server/src/storage/repositories/auth-session-repo.ts
|
|
7195
|
-
function
|
|
7738
|
+
function isRecord4(value) {
|
|
7196
7739
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
7197
7740
|
}
|
|
7198
7741
|
function isAuthSession(value) {
|
|
7199
|
-
if (!
|
|
7742
|
+
if (!isRecord4(value)) {
|
|
7200
7743
|
return false;
|
|
7201
7744
|
}
|
|
7202
7745
|
return typeof value.token === "string" && typeof value.createdAt === "number" && typeof value.lastSeenAt === "number";
|
|
7203
7746
|
}
|
|
7204
7747
|
function normalizeSessionFile2(value) {
|
|
7205
|
-
if (
|
|
7748
|
+
if (isRecord4(value) && value.version === 1 && isRecord4(value.sessions)) {
|
|
7206
7749
|
const normalized = {};
|
|
7207
7750
|
for (const entry of Object.values(value.sessions)) {
|
|
7208
7751
|
if (isAuthSession(entry)) {
|
|
@@ -7220,7 +7763,7 @@ function normalizeSessionFile2(value) {
|
|
|
7220
7763
|
}
|
|
7221
7764
|
return normalized;
|
|
7222
7765
|
}
|
|
7223
|
-
if (
|
|
7766
|
+
if (isRecord4(value)) {
|
|
7224
7767
|
const normalized = {};
|
|
7225
7768
|
for (const [token, entry] of Object.entries(value)) {
|
|
7226
7769
|
if (isAuthSession(entry)) {
|
|
@@ -7295,14 +7838,14 @@ var init_auth_session_repo = __esm({
|
|
|
7295
7838
|
});
|
|
7296
7839
|
|
|
7297
7840
|
// packages/server/src/storage/repositories/provider-config-repo.ts
|
|
7298
|
-
function
|
|
7841
|
+
function isRecord5(value) {
|
|
7299
7842
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
7300
7843
|
}
|
|
7301
7844
|
function normalizeProviderConfigFile(value) {
|
|
7302
|
-
if (
|
|
7845
|
+
if (isRecord5(value) && value.version === 1 && isRecord5(value.providers)) {
|
|
7303
7846
|
return value.providers;
|
|
7304
7847
|
}
|
|
7305
|
-
if (
|
|
7848
|
+
if (isRecord5(value)) {
|
|
7306
7849
|
return value;
|
|
7307
7850
|
}
|
|
7308
7851
|
return {};
|
|
@@ -7376,14 +7919,14 @@ var init_provider_config_repo = __esm({
|
|
|
7376
7919
|
});
|
|
7377
7920
|
|
|
7378
7921
|
// packages/server/src/storage/repositories/settings-repo.ts
|
|
7379
|
-
function
|
|
7922
|
+
function isRecord6(value) {
|
|
7380
7923
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
7381
7924
|
}
|
|
7382
7925
|
function normalizeSettingsFile(value) {
|
|
7383
|
-
if (
|
|
7926
|
+
if (isRecord6(value) && value.version === 1 && isRecord6(value.settings)) {
|
|
7384
7927
|
return { ...value.settings };
|
|
7385
7928
|
}
|
|
7386
|
-
if (
|
|
7929
|
+
if (isRecord6(value)) {
|
|
7387
7930
|
return { ...value };
|
|
7388
7931
|
}
|
|
7389
7932
|
return {};
|
|
@@ -7534,11 +8077,11 @@ var init_supervisor_repo = __esm({
|
|
|
7534
8077
|
});
|
|
7535
8078
|
|
|
7536
8079
|
// packages/server/src/storage/repositories/terminal-repo.ts
|
|
7537
|
-
function
|
|
8080
|
+
function isRecord7(value) {
|
|
7538
8081
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
7539
8082
|
}
|
|
7540
8083
|
function isTerminal(value) {
|
|
7541
|
-
if (!
|
|
8084
|
+
if (!isRecord7(value)) {
|
|
7542
8085
|
return false;
|
|
7543
8086
|
}
|
|
7544
8087
|
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 +8093,7 @@ function normalizeTerminal(value) {
|
|
|
7550
8093
|
};
|
|
7551
8094
|
}
|
|
7552
8095
|
function normalizeTerminalFile(value) {
|
|
7553
|
-
if (
|
|
8096
|
+
if (isRecord7(value) && value.version === 1 && isRecord7(value.terminals)) {
|
|
7554
8097
|
const normalized = {};
|
|
7555
8098
|
for (const entry of Object.values(value.terminals)) {
|
|
7556
8099
|
if (isTerminal(entry)) {
|
|
@@ -7568,7 +8111,7 @@ function normalizeTerminalFile(value) {
|
|
|
7568
8111
|
}
|
|
7569
8112
|
return normalized;
|
|
7570
8113
|
}
|
|
7571
|
-
if (
|
|
8114
|
+
if (isRecord7(value)) {
|
|
7572
8115
|
const normalized = {};
|
|
7573
8116
|
for (const [terminalId, entry] of Object.entries(value)) {
|
|
7574
8117
|
if (isTerminal(entry)) {
|
|
@@ -7734,12 +8277,12 @@ var init_terminal_repo = __esm({
|
|
|
7734
8277
|
});
|
|
7735
8278
|
|
|
7736
8279
|
// packages/server/src/storage/repositories/update-state-repo.ts
|
|
7737
|
-
function
|
|
8280
|
+
function isRecord8(value) {
|
|
7738
8281
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
7739
8282
|
}
|
|
7740
8283
|
function normalizeUpdateState(value, currentVersion) {
|
|
7741
8284
|
const defaults = createDefaultUpdateState(currentVersion);
|
|
7742
|
-
if (!
|
|
8285
|
+
if (!isRecord8(value)) {
|
|
7743
8286
|
return defaults;
|
|
7744
8287
|
}
|
|
7745
8288
|
return {
|
|
@@ -7802,17 +8345,17 @@ var init_update_state_repo = __esm({
|
|
|
7802
8345
|
});
|
|
7803
8346
|
|
|
7804
8347
|
// packages/server/src/storage/repositories/workspace-repo.ts
|
|
7805
|
-
function
|
|
8348
|
+
function isRecord9(value) {
|
|
7806
8349
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
7807
8350
|
}
|
|
7808
8351
|
function isWorkspace(value) {
|
|
7809
|
-
if (!
|
|
8352
|
+
if (!isRecord9(value)) {
|
|
7810
8353
|
return false;
|
|
7811
8354
|
}
|
|
7812
|
-
return typeof value.id === "string" && typeof value.path === "string" && (value.targetRuntime === "native" || value.targetRuntime === "wsl") && typeof value.openedAt === "number" && typeof value.lastActiveAt === "number" &&
|
|
8355
|
+
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
8356
|
}
|
|
7814
8357
|
function normalizeWorkspaceFile(value) {
|
|
7815
|
-
if (
|
|
8358
|
+
if (isRecord9(value) && value.version === 1 && isRecord9(value.workspaces)) {
|
|
7816
8359
|
const normalized = {};
|
|
7817
8360
|
for (const entry of Object.values(value.workspaces)) {
|
|
7818
8361
|
if (isWorkspace(entry)) {
|
|
@@ -7830,7 +8373,7 @@ function normalizeWorkspaceFile(value) {
|
|
|
7830
8373
|
}
|
|
7831
8374
|
return normalized;
|
|
7832
8375
|
}
|
|
7833
|
-
if (
|
|
8376
|
+
if (isRecord9(value)) {
|
|
7834
8377
|
const normalized = {};
|
|
7835
8378
|
for (const [workspaceId, entry] of Object.entries(value)) {
|
|
7836
8379
|
if (isWorkspace(entry)) {
|
|
@@ -8075,13 +8618,13 @@ function ensureNodePtySpawnHelperExecutable(deps = {}) {
|
|
|
8075
8618
|
return;
|
|
8076
8619
|
}
|
|
8077
8620
|
const arch = deps.arch ?? process.arch;
|
|
8078
|
-
const
|
|
8621
|
+
const resolve6 = deps.resolve ?? ((id) => require4.resolve(id));
|
|
8079
8622
|
const fileExists = deps.existsSync ?? existsSync6;
|
|
8080
|
-
const
|
|
8623
|
+
const stat11 = deps.statSync ?? statSync;
|
|
8081
8624
|
const chmod = deps.chmodSync ?? chmodSync2;
|
|
8082
8625
|
let packageJsonPath;
|
|
8083
8626
|
try {
|
|
8084
|
-
packageJsonPath =
|
|
8627
|
+
packageJsonPath = resolve6(NODE_PTY_PKG);
|
|
8085
8628
|
} catch {
|
|
8086
8629
|
return;
|
|
8087
8630
|
}
|
|
@@ -8095,7 +8638,7 @@ function ensureNodePtySpawnHelperExecutable(deps = {}) {
|
|
|
8095
8638
|
if (!fileExists(helperPath)) {
|
|
8096
8639
|
return;
|
|
8097
8640
|
}
|
|
8098
|
-
const currentMode =
|
|
8641
|
+
const currentMode = stat11(helperPath).mode;
|
|
8099
8642
|
const executableMode = currentMode | 73;
|
|
8100
8643
|
if (executableMode === currentMode) {
|
|
8101
8644
|
return;
|
|
@@ -8146,7 +8689,7 @@ async function escalateKillWithPolling(pid, signal, options) {
|
|
|
8146
8689
|
const startTime = Date.now();
|
|
8147
8690
|
const deadline = startTime + timeoutMs;
|
|
8148
8691
|
while (Date.now() < deadline) {
|
|
8149
|
-
await new Promise((
|
|
8692
|
+
await new Promise((resolve6) => setTimeout(resolve6, pollIntervalMs));
|
|
8150
8693
|
if (!isProcessAlive(pid)) {
|
|
8151
8694
|
return true;
|
|
8152
8695
|
}
|
|
@@ -8510,7 +9053,7 @@ async function runCommand(command, timeoutMs, options = {}) {
|
|
|
8510
9053
|
if (options.signal?.aborted) {
|
|
8511
9054
|
throw createSupervisorEvalAbortedError();
|
|
8512
9055
|
}
|
|
8513
|
-
return await new Promise((
|
|
9056
|
+
return await new Promise((resolve6, reject) => {
|
|
8514
9057
|
const child = spawn3(command.argv[0], command.argv.slice(1), {
|
|
8515
9058
|
cwd: command.cwd,
|
|
8516
9059
|
detached: process.platform !== "win32",
|
|
@@ -8540,7 +9083,7 @@ async function runCommand(command, timeoutMs, options = {}) {
|
|
|
8540
9083
|
}
|
|
8541
9084
|
settled = true;
|
|
8542
9085
|
cleanup();
|
|
8543
|
-
|
|
9086
|
+
resolve6(value);
|
|
8544
9087
|
};
|
|
8545
9088
|
const terminate = (error) => {
|
|
8546
9089
|
if (terminationError) {
|
|
@@ -9368,12 +9911,12 @@ var init_scheduler = __esm({
|
|
|
9368
9911
|
|
|
9369
9912
|
// packages/server/src/supervisor/manager.ts
|
|
9370
9913
|
function createDeferredCompletion() {
|
|
9371
|
-
let
|
|
9914
|
+
let resolve6 = () => {
|
|
9372
9915
|
};
|
|
9373
9916
|
const promise = new Promise((innerResolve) => {
|
|
9374
|
-
|
|
9917
|
+
resolve6 = innerResolve;
|
|
9375
9918
|
});
|
|
9376
|
-
return { promise, resolve:
|
|
9919
|
+
return { promise, resolve: resolve6 };
|
|
9377
9920
|
}
|
|
9378
9921
|
function generateSupervisorId() {
|
|
9379
9922
|
return `sup_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
|
|
@@ -10728,10 +11271,10 @@ var init_manager4 = __esm({
|
|
|
10728
11271
|
if (signal?.aborted) {
|
|
10729
11272
|
throw { code: "supervisor_eval_aborted", message: "Supervisor evaluator aborted" };
|
|
10730
11273
|
}
|
|
10731
|
-
await new Promise((
|
|
11274
|
+
await new Promise((resolve6, reject) => {
|
|
10732
11275
|
const timer = setTimeout(() => {
|
|
10733
11276
|
signal?.removeEventListener("abort", onAbort);
|
|
10734
|
-
|
|
11277
|
+
resolve6();
|
|
10735
11278
|
}, delayMs);
|
|
10736
11279
|
timer.unref?.();
|
|
10737
11280
|
const onAbort = () => {
|
|
@@ -10762,31 +11305,31 @@ __export(target_store_exports, {
|
|
|
10762
11305
|
saveTargetMemory: () => saveTargetMemory,
|
|
10763
11306
|
saveTargetMeta: () => saveTargetMeta
|
|
10764
11307
|
});
|
|
10765
|
-
import { mkdir as mkdir4, mkdtemp as mkdtemp2, readdir as readdir2, readFile as readFile3, rename, rm as
|
|
10766
|
-
import { dirname as dirname6, join as
|
|
11308
|
+
import { mkdir as mkdir4, mkdtemp as mkdtemp2, readdir as readdir2, readFile as readFile3, rename, rm as rm6, writeFile as writeFile4 } from "node:fs/promises";
|
|
11309
|
+
import { dirname as dirname6, join as join9 } from "node:path";
|
|
10767
11310
|
function targetDir(workspacePath, targetId) {
|
|
10768
|
-
return
|
|
11311
|
+
return join9(workspacePath, ".coder-studio", "supervisor", "targets", targetId);
|
|
10769
11312
|
}
|
|
10770
11313
|
function metaPath(workspacePath, targetId) {
|
|
10771
|
-
return
|
|
11314
|
+
return join9(targetDir(workspacePath, targetId), "meta.json");
|
|
10772
11315
|
}
|
|
10773
11316
|
function memoryPath(workspacePath, targetId) {
|
|
10774
|
-
return
|
|
11317
|
+
return join9(targetDir(workspacePath, targetId), "memory.json");
|
|
10775
11318
|
}
|
|
10776
11319
|
function cyclesPath(workspacePath, targetId) {
|
|
10777
|
-
return
|
|
11320
|
+
return join9(targetDir(workspacePath, targetId), "cycles.jsonl");
|
|
10778
11321
|
}
|
|
10779
11322
|
function targetsRoot(workspacePath) {
|
|
10780
|
-
return
|
|
11323
|
+
return join9(workspacePath, ".coder-studio", "supervisor", "targets");
|
|
10781
11324
|
}
|
|
10782
11325
|
function metaFilePath(dirPath) {
|
|
10783
|
-
return
|
|
11326
|
+
return join9(dirPath, "meta.json");
|
|
10784
11327
|
}
|
|
10785
11328
|
function memoryFilePath(dirPath) {
|
|
10786
|
-
return
|
|
11329
|
+
return join9(dirPath, "memory.json");
|
|
10787
11330
|
}
|
|
10788
11331
|
function cyclesFilePath(dirPath) {
|
|
10789
|
-
return
|
|
11332
|
+
return join9(dirPath, "cycles.jsonl");
|
|
10790
11333
|
}
|
|
10791
11334
|
function hasCode2(error, code) {
|
|
10792
11335
|
return Boolean(
|
|
@@ -10805,7 +11348,7 @@ function errorMessage(error, fallback) {
|
|
|
10805
11348
|
}
|
|
10806
11349
|
return fallback;
|
|
10807
11350
|
}
|
|
10808
|
-
function
|
|
11351
|
+
function isRecord10(value) {
|
|
10809
11352
|
return Boolean(value) && typeof value === "object";
|
|
10810
11353
|
}
|
|
10811
11354
|
function readNonEmptyString(value) {
|
|
@@ -10849,7 +11392,7 @@ function fallbackAcceptanceCriteria(title) {
|
|
|
10849
11392
|
return [`${title} is complete`];
|
|
10850
11393
|
}
|
|
10851
11394
|
function normalizeItem(value, fallbackKind) {
|
|
10852
|
-
if (!
|
|
11395
|
+
if (!isRecord10(value)) {
|
|
10853
11396
|
return null;
|
|
10854
11397
|
}
|
|
10855
11398
|
const id = readNonEmptyString(value.id);
|
|
@@ -10880,7 +11423,7 @@ function normalizeLegacyPlanItems(plan) {
|
|
|
10880
11423
|
}
|
|
10881
11424
|
return plan.flatMap((value) => {
|
|
10882
11425
|
const item = normalizeItem(
|
|
10883
|
-
|
|
11426
|
+
isRecord10(value) ? {
|
|
10884
11427
|
id: value.id,
|
|
10885
11428
|
kind: "stage",
|
|
10886
11429
|
title: value.title,
|
|
@@ -10904,7 +11447,7 @@ function resolveActiveItemId(items, candidate) {
|
|
|
10904
11447
|
return items.find((item) => item.status === "in_progress")?.id ?? items.find((item) => item.status === "pending")?.id ?? items[0]?.id;
|
|
10905
11448
|
}
|
|
10906
11449
|
function normalizeTargetMemory(raw, targetId) {
|
|
10907
|
-
if (!
|
|
11450
|
+
if (!isRecord10(raw)) {
|
|
10908
11451
|
return buildTargetMemory(targetId, 0);
|
|
10909
11452
|
}
|
|
10910
11453
|
const updatedAt = readTimestamp(raw.updatedAt, 0);
|
|
@@ -10931,7 +11474,7 @@ function normalizeTargetMemory(raw, targetId) {
|
|
|
10931
11474
|
};
|
|
10932
11475
|
}
|
|
10933
11476
|
function normalizePersistedSupervisor(raw, fallback) {
|
|
10934
|
-
if (!
|
|
11477
|
+
if (!isRecord10(raw)) {
|
|
10935
11478
|
return void 0;
|
|
10936
11479
|
}
|
|
10937
11480
|
const id = readNonEmptyString(raw.id) ?? fallback.targetId;
|
|
@@ -10967,7 +11510,7 @@ function normalizePersistedSupervisor(raw, fallback) {
|
|
|
10967
11510
|
};
|
|
10968
11511
|
}
|
|
10969
11512
|
function normalizeTargetMeta(raw, fallbackTargetId) {
|
|
10970
|
-
if (!
|
|
11513
|
+
if (!isRecord10(raw)) {
|
|
10971
11514
|
const targetId2 = fallbackTargetId ?? "";
|
|
10972
11515
|
return {
|
|
10973
11516
|
targetId: targetId2,
|
|
@@ -11088,7 +11631,7 @@ async function resetTargetFiles(workspacePath, input2) {
|
|
|
11088
11631
|
const parentDir = dirname6(dir);
|
|
11089
11632
|
const backupDir = `${dir}.backup-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
11090
11633
|
await mkdir4(parentDir, { recursive: true });
|
|
11091
|
-
const stagingDir = await mkdtemp2(
|
|
11634
|
+
const stagingDir = await mkdtemp2(join9(parentDir, `${input2.targetId}.reset-`));
|
|
11092
11635
|
let backupCreated = false;
|
|
11093
11636
|
let promoted = false;
|
|
11094
11637
|
let restored = false;
|
|
@@ -11124,17 +11667,17 @@ async function resetTargetFiles(workspacePath, input2) {
|
|
|
11124
11667
|
}
|
|
11125
11668
|
} catch (error) {
|
|
11126
11669
|
if (restored || !backupCreated) {
|
|
11127
|
-
await
|
|
11670
|
+
await rm6(stagingDir, { recursive: true, force: true }).catch(() => {
|
|
11128
11671
|
});
|
|
11129
11672
|
}
|
|
11130
11673
|
throw error;
|
|
11131
11674
|
}
|
|
11132
11675
|
if (backupCreated) {
|
|
11133
|
-
await
|
|
11676
|
+
await rm6(backupDir, { recursive: true, force: true }).catch(() => {
|
|
11134
11677
|
});
|
|
11135
11678
|
}
|
|
11136
11679
|
if (!promoted) {
|
|
11137
|
-
await
|
|
11680
|
+
await rm6(stagingDir, { recursive: true, force: true }).catch(() => {
|
|
11138
11681
|
});
|
|
11139
11682
|
}
|
|
11140
11683
|
}
|
|
@@ -11232,7 +11775,7 @@ async function cloneTargetFiles(workspacePath, input2) {
|
|
|
11232
11775
|
return nextCycles.filter(countsTowardCycleTotal).length;
|
|
11233
11776
|
}
|
|
11234
11777
|
async function deleteTarget(workspacePath, targetId) {
|
|
11235
|
-
await
|
|
11778
|
+
await rm6(targetDir(workspacePath, targetId), { recursive: true, force: true });
|
|
11236
11779
|
}
|
|
11237
11780
|
async function markTargetSuperseded(workspacePath, targetId, nextTargetId, updatedAt) {
|
|
11238
11781
|
const meta = await readTargetMeta(workspacePath, targetId);
|
|
@@ -11545,8 +12088,8 @@ var init_terminal_snapshot_buffer = __esm({
|
|
|
11545
12088
|
if (this.pendingWriteCount === 0) {
|
|
11546
12089
|
return Promise.resolve();
|
|
11547
12090
|
}
|
|
11548
|
-
return new Promise((
|
|
11549
|
-
this.drainResolvers.push(
|
|
12091
|
+
return new Promise((resolve6) => {
|
|
12092
|
+
this.drainResolvers.push(resolve6);
|
|
11550
12093
|
});
|
|
11551
12094
|
}
|
|
11552
12095
|
resolveDrainIfIdle() {
|
|
@@ -11555,8 +12098,8 @@ var init_terminal_snapshot_buffer = __esm({
|
|
|
11555
12098
|
}
|
|
11556
12099
|
const resolvers = this.drainResolvers;
|
|
11557
12100
|
this.drainResolvers = [];
|
|
11558
|
-
for (const
|
|
11559
|
-
|
|
12101
|
+
for (const resolve6 of resolvers) {
|
|
12102
|
+
resolve6();
|
|
11560
12103
|
}
|
|
11561
12104
|
}
|
|
11562
12105
|
requireTerminal() {
|
|
@@ -11610,6 +12153,39 @@ var init_types2 = __esm({
|
|
|
11610
12153
|
function isTerminalTraceEnabled() {
|
|
11611
12154
|
return process.env.CODER_STUDIO_TERMINAL_TRACE === "1";
|
|
11612
12155
|
}
|
|
12156
|
+
function parseHexColor(input2) {
|
|
12157
|
+
const trimmed = input2.trim();
|
|
12158
|
+
if (!trimmed.startsWith("#")) {
|
|
12159
|
+
return null;
|
|
12160
|
+
}
|
|
12161
|
+
const hex = trimmed.slice(1);
|
|
12162
|
+
let r;
|
|
12163
|
+
let g;
|
|
12164
|
+
let b;
|
|
12165
|
+
if (hex.length === 3) {
|
|
12166
|
+
r = Number.parseInt(hex[0] + hex[0], 16);
|
|
12167
|
+
g = Number.parseInt(hex[1] + hex[1], 16);
|
|
12168
|
+
b = Number.parseInt(hex[2] + hex[2], 16);
|
|
12169
|
+
} else if (hex.length === 6 || hex.length === 8) {
|
|
12170
|
+
r = Number.parseInt(hex.slice(0, 2), 16);
|
|
12171
|
+
g = Number.parseInt(hex.slice(2, 4), 16);
|
|
12172
|
+
b = Number.parseInt(hex.slice(4, 6), 16);
|
|
12173
|
+
} else {
|
|
12174
|
+
return null;
|
|
12175
|
+
}
|
|
12176
|
+
if ([r, g, b].some((v) => Number.isNaN(v))) {
|
|
12177
|
+
return null;
|
|
12178
|
+
}
|
|
12179
|
+
return { r, g, b };
|
|
12180
|
+
}
|
|
12181
|
+
function computeColorFgBg(background) {
|
|
12182
|
+
const rgb = parseHexColor(background);
|
|
12183
|
+
if (!rgb) {
|
|
12184
|
+
return void 0;
|
|
12185
|
+
}
|
|
12186
|
+
const luma = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255;
|
|
12187
|
+
return luma > 0.5 ? "0;15" : "15;0";
|
|
12188
|
+
}
|
|
11613
12189
|
function countOccurrences(text, needle) {
|
|
11614
12190
|
return text.split(needle).length - 1;
|
|
11615
12191
|
}
|
|
@@ -11666,6 +12242,7 @@ var init_manager5 = __esm({
|
|
|
11666
12242
|
*/
|
|
11667
12243
|
create(spec) {
|
|
11668
12244
|
const id = generateId();
|
|
12245
|
+
const derivedColorFgBg = spec.themeBackground ? computeColorFgBg(spec.themeBackground) : void 0;
|
|
11669
12246
|
const terminalEnv = {
|
|
11670
12247
|
...Object.fromEntries(
|
|
11671
12248
|
Object.entries(process.env).filter((e) => e[1] != null)
|
|
@@ -11673,6 +12250,7 @@ var init_manager5 = __esm({
|
|
|
11673
12250
|
TERM: "xterm-256color",
|
|
11674
12251
|
COLORTERM: "truecolor",
|
|
11675
12252
|
FORCE_COLOR: "3",
|
|
12253
|
+
...derivedColorFgBg ? { COLORFGBG: derivedColorFgBg } : {},
|
|
11676
12254
|
...spec.env
|
|
11677
12255
|
};
|
|
11678
12256
|
let pty;
|
|
@@ -11875,10 +12453,10 @@ var init_manager5 = __esm({
|
|
|
11875
12453
|
}
|
|
11876
12454
|
return existing.promise;
|
|
11877
12455
|
}
|
|
11878
|
-
let
|
|
12456
|
+
let resolve6 = () => {
|
|
11879
12457
|
};
|
|
11880
12458
|
const promise = new Promise((innerResolve) => {
|
|
11881
|
-
|
|
12459
|
+
resolve6 = innerResolve;
|
|
11882
12460
|
});
|
|
11883
12461
|
let markKillCompleted = () => {
|
|
11884
12462
|
};
|
|
@@ -11891,7 +12469,7 @@ var init_manager5 = __esm({
|
|
|
11891
12469
|
markKillCompleted,
|
|
11892
12470
|
finalized: false,
|
|
11893
12471
|
promise,
|
|
11894
|
-
resolve:
|
|
12472
|
+
resolve: resolve6
|
|
11895
12473
|
});
|
|
11896
12474
|
void terminal.pty.kill(signal).finally(() => {
|
|
11897
12475
|
const waiter = this.explicitCloseWaiters.get(terminalId);
|
|
@@ -12367,10 +12945,10 @@ var init_update_service = __esm({
|
|
|
12367
12945
|
|
|
12368
12946
|
// packages/server/src/workspace/validator.ts
|
|
12369
12947
|
import { constants } from "fs";
|
|
12370
|
-
import { access, stat as
|
|
12948
|
+
import { access, stat as stat7 } from "fs/promises";
|
|
12371
12949
|
async function validatePath(path14) {
|
|
12372
12950
|
try {
|
|
12373
|
-
const stats = await
|
|
12951
|
+
const stats = await stat7(path14);
|
|
12374
12952
|
if (!stats.isDirectory()) {
|
|
12375
12953
|
return { valid: false, error: "Path is not a directory" };
|
|
12376
12954
|
}
|
|
@@ -12405,12 +12983,12 @@ var init_validator = __esm({
|
|
|
12405
12983
|
// packages/server/src/fs/gitignore.ts
|
|
12406
12984
|
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
|
|
12407
12985
|
import ignore from "ignore";
|
|
12408
|
-
import { join as
|
|
12986
|
+
import { join as join10, relative as relative2 } from "path";
|
|
12409
12987
|
function normalizePath(path14) {
|
|
12410
12988
|
return path14.replace(/\\/g, "/");
|
|
12411
12989
|
}
|
|
12412
12990
|
function relativeToRoot(rootPath, path14) {
|
|
12413
|
-
return normalizePath(
|
|
12991
|
+
return normalizePath(relative2(rootPath, path14));
|
|
12414
12992
|
}
|
|
12415
12993
|
function isDefaultTreeIgnored(name) {
|
|
12416
12994
|
return name.startsWith(".") || name === "node_modules" || name === ".git";
|
|
@@ -12428,7 +13006,7 @@ function isIgnoredByGitignore(ig, path14) {
|
|
|
12428
13006
|
return ig.ignores(path14) || ig.ignores(`${path14}/`);
|
|
12429
13007
|
}
|
|
12430
13008
|
function createGitignoreFilter(rootPath, dirPath) {
|
|
12431
|
-
const gitignorePath =
|
|
13009
|
+
const gitignorePath = join10(rootPath, ".gitignore");
|
|
12432
13010
|
if (!existsSync7(gitignorePath)) {
|
|
12433
13011
|
return (name) => !isDefaultTreeIgnored(name);
|
|
12434
13012
|
}
|
|
@@ -12438,7 +13016,7 @@ function createGitignoreFilter(rootPath, dirPath) {
|
|
|
12438
13016
|
if (isAlwaysTreeIgnored(name)) {
|
|
12439
13017
|
return false;
|
|
12440
13018
|
}
|
|
12441
|
-
const relativePath = relativeToRoot(rootPath,
|
|
13019
|
+
const relativePath = relativeToRoot(rootPath, join10(dirPath, name));
|
|
12442
13020
|
return !isIgnoredByGitignore(ig, relativePath);
|
|
12443
13021
|
};
|
|
12444
13022
|
}
|
|
@@ -13109,8 +13687,8 @@ var init_fencing = __esm({
|
|
|
13109
13687
|
});
|
|
13110
13688
|
|
|
13111
13689
|
// packages/server/src/commands/terminal.ts
|
|
13112
|
-
import { stat as
|
|
13113
|
-
import { basename, isAbsolute } from "node:path";
|
|
13690
|
+
import { stat as stat8 } from "node:fs/promises";
|
|
13691
|
+
import { basename, isAbsolute as isAbsolute2 } from "node:path";
|
|
13114
13692
|
import { z as z6 } from "zod";
|
|
13115
13693
|
function decodeTerminalInput(args) {
|
|
13116
13694
|
if ("bytes" in args) {
|
|
@@ -13217,7 +13795,8 @@ var init_terminal = __esm({
|
|
|
13217
13795
|
workspaceId: z6.string(),
|
|
13218
13796
|
cols: z6.number().int().positive().optional(),
|
|
13219
13797
|
rows: z6.number().int().positive().optional(),
|
|
13220
|
-
cwdPath: z6.string().optional()
|
|
13798
|
+
cwdPath: z6.string().optional(),
|
|
13799
|
+
themeBackground: z6.string().regex(/^#[0-9a-fA-F]{3,8}$/).optional()
|
|
13221
13800
|
}),
|
|
13222
13801
|
async (args, ctx) => {
|
|
13223
13802
|
const workspace = ctx.workspaceMgr.get(args.workspaceId);
|
|
@@ -13226,7 +13805,7 @@ var init_terminal = __esm({
|
|
|
13226
13805
|
}
|
|
13227
13806
|
let cwd = workspace.path;
|
|
13228
13807
|
if (args.cwdPath && args.cwdPath !== ".") {
|
|
13229
|
-
if (
|
|
13808
|
+
if (isAbsolute2(args.cwdPath)) {
|
|
13230
13809
|
throw { code: "invalid_cwd_path", message: "cwdPath must be workspace-relative" };
|
|
13231
13810
|
}
|
|
13232
13811
|
let resolvedCwd;
|
|
@@ -13238,7 +13817,7 @@ var init_terminal = __esm({
|
|
|
13238
13817
|
}
|
|
13239
13818
|
throw error;
|
|
13240
13819
|
}
|
|
13241
|
-
const cwdStats = await
|
|
13820
|
+
const cwdStats = await stat8(resolvedCwd).catch(() => null);
|
|
13242
13821
|
if (!cwdStats) {
|
|
13243
13822
|
throw { code: "cwd_not_found", message: `Directory not found: ${args.cwdPath}` };
|
|
13244
13823
|
}
|
|
@@ -13255,7 +13834,8 @@ var init_terminal = __esm({
|
|
|
13255
13834
|
title: shell.title,
|
|
13256
13835
|
cwd,
|
|
13257
13836
|
cols: args.cols ?? 120,
|
|
13258
|
-
rows: args.rows ?? 30
|
|
13837
|
+
rows: args.rows ?? 30,
|
|
13838
|
+
themeBackground: args.themeBackground
|
|
13259
13839
|
});
|
|
13260
13840
|
return terminal;
|
|
13261
13841
|
}
|
|
@@ -13967,7 +14547,7 @@ var init_hub = __esm({
|
|
|
13967
14547
|
}
|
|
13968
14548
|
}
|
|
13969
14549
|
awaitBinaryPayload(clientId) {
|
|
13970
|
-
return new Promise((
|
|
14550
|
+
return new Promise((resolve6, reject) => {
|
|
13971
14551
|
const timer = setTimeout(() => {
|
|
13972
14552
|
const waiters = this.pendingBinaryWaiters.get(clientId);
|
|
13973
14553
|
if (!waiters) return;
|
|
@@ -13979,7 +14559,7 @@ var init_hub = __esm({
|
|
|
13979
14559
|
}
|
|
13980
14560
|
reject(new Error("Timeout waiting for terminal input binary payload"));
|
|
13981
14561
|
}, BINARY_PAYLOAD_TIMEOUT_MS);
|
|
13982
|
-
const waiter = { resolve:
|
|
14562
|
+
const waiter = { resolve: resolve6, reject, timer };
|
|
13983
14563
|
const queue = this.pendingBinaryWaiters.get(clientId);
|
|
13984
14564
|
if (queue) {
|
|
13985
14565
|
queue.push(waiter);
|
|
@@ -14286,7 +14866,7 @@ var init_hub = __esm({
|
|
|
14286
14866
|
// packages/server/src/commands/workspace.ts
|
|
14287
14867
|
import { readdir as readdir3, realpath as realpath3 } from "node:fs/promises";
|
|
14288
14868
|
import { homedir as homedir2 } from "node:os";
|
|
14289
|
-
import { isAbsolute as
|
|
14869
|
+
import { isAbsolute as isAbsolute3, join as join11, resolve as resolve4 } from "node:path";
|
|
14290
14870
|
import { z as z7 } from "zod";
|
|
14291
14871
|
function resolveBrowsePath(path14) {
|
|
14292
14872
|
const home = homedir2();
|
|
@@ -14294,9 +14874,9 @@ function resolveBrowsePath(path14) {
|
|
|
14294
14874
|
return home;
|
|
14295
14875
|
}
|
|
14296
14876
|
if (path14.startsWith("~/")) {
|
|
14297
|
-
return
|
|
14877
|
+
return join11(home, path14.slice(2));
|
|
14298
14878
|
}
|
|
14299
|
-
return
|
|
14879
|
+
return isAbsolute3(path14) ? path14 : resolve4(home, path14);
|
|
14300
14880
|
}
|
|
14301
14881
|
async function buildRootPaths(currentPath) {
|
|
14302
14882
|
const roots = /* @__PURE__ */ new Set(["/"]);
|
|
@@ -14329,11 +14909,11 @@ var init_workspace = __esm({
|
|
|
14329
14909
|
const entries = await readdir3(basePath, { withFileTypes: true });
|
|
14330
14910
|
const directories = entries.filter((entry) => entry.isDirectory()).map((entry) => ({
|
|
14331
14911
|
name: entry.name,
|
|
14332
|
-
path:
|
|
14912
|
+
path: join11(basePath, entry.name)
|
|
14333
14913
|
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
14334
14914
|
return {
|
|
14335
14915
|
currentPath: basePath,
|
|
14336
|
-
parentPath: basePath !== "/" ?
|
|
14916
|
+
parentPath: basePath !== "/" ? join11(basePath, "..") : null,
|
|
14337
14917
|
directories,
|
|
14338
14918
|
rootPaths: await buildRootPaths(basePath)
|
|
14339
14919
|
};
|
|
@@ -14797,8 +15377,8 @@ var init_pane_layout = __esm({
|
|
|
14797
15377
|
// packages/server/src/commands/session.ts
|
|
14798
15378
|
import { z as z12 } from "zod";
|
|
14799
15379
|
function delay(ms) {
|
|
14800
|
-
return new Promise((
|
|
14801
|
-
setTimeout(
|
|
15380
|
+
return new Promise((resolve6) => {
|
|
15381
|
+
setTimeout(resolve6, ms);
|
|
14802
15382
|
});
|
|
14803
15383
|
}
|
|
14804
15384
|
function getProviderFromRegistry(providerId, registry) {
|
|
@@ -14827,7 +15407,8 @@ var init_session2 = __esm({
|
|
|
14827
15407
|
z12.object({
|
|
14828
15408
|
workspaceId: z12.string(),
|
|
14829
15409
|
providerId: z12.string(),
|
|
14830
|
-
draft: z12.string().optional()
|
|
15410
|
+
draft: z12.string().optional(),
|
|
15411
|
+
themeBackground: z12.string().regex(/^#[0-9a-fA-F]{3,8}$/).optional()
|
|
14831
15412
|
}),
|
|
14832
15413
|
async (args, ctx) => {
|
|
14833
15414
|
const workspace = ctx.workspaceMgr.get(args.workspaceId);
|
|
@@ -14855,7 +15436,8 @@ var init_session2 = __esm({
|
|
|
14855
15436
|
workspacePath: workspace.path,
|
|
14856
15437
|
providerId: args.providerId,
|
|
14857
15438
|
provider,
|
|
14858
|
-
draft: args.draft
|
|
15439
|
+
draft: args.draft,
|
|
15440
|
+
themeBackground: args.themeBackground
|
|
14859
15441
|
});
|
|
14860
15442
|
}
|
|
14861
15443
|
);
|
|
@@ -14952,8 +15534,8 @@ var init_session2 = __esm({
|
|
|
14952
15534
|
// packages/server/src/fs/content-search.ts
|
|
14953
15535
|
import { spawn as spawn5 } from "child_process";
|
|
14954
15536
|
import { existsSync as existsSync8 } from "fs";
|
|
14955
|
-
import { readdir as readdir4, readFile as readFile4, stat as
|
|
14956
|
-
import { basename as basename2, join as
|
|
15537
|
+
import { readdir as readdir4, readFile as readFile4, stat as stat9 } from "fs/promises";
|
|
15538
|
+
import { basename as basename2, join as join12, relative as relative3 } from "path";
|
|
14957
15539
|
import { createInterface } from "readline";
|
|
14958
15540
|
async function searchFileContents(rootPath, options) {
|
|
14959
15541
|
const query = options.query.trim();
|
|
@@ -14976,7 +15558,7 @@ async function searchFileContents(rootPath, options) {
|
|
|
14976
15558
|
return finalizeResults(result, options.maxFiles, options.maxMatchesPerFile);
|
|
14977
15559
|
}
|
|
14978
15560
|
async function searchWithRipgrep(rootPath, query, maxFiles) {
|
|
14979
|
-
const hasGitignore = existsSync8(
|
|
15561
|
+
const hasGitignore = existsSync8(join12(rootPath, ".gitignore"));
|
|
14980
15562
|
const args = [
|
|
14981
15563
|
"--json",
|
|
14982
15564
|
"--line-number",
|
|
@@ -14995,7 +15577,7 @@ async function searchWithRipgrep(rootPath, query, maxFiles) {
|
|
|
14995
15577
|
args.push("--no-require-git");
|
|
14996
15578
|
}
|
|
14997
15579
|
args.push(query, ".");
|
|
14998
|
-
return new Promise((
|
|
15580
|
+
return new Promise((resolve6, reject) => {
|
|
14999
15581
|
const child = spawn5("rg", args, { cwd: rootPath, stdio: ["ignore", "pipe", "pipe"] });
|
|
15000
15582
|
const stdout = createInterface({ input: child.stdout });
|
|
15001
15583
|
const files = /* @__PURE__ */ new Map();
|
|
@@ -15014,7 +15596,7 @@ async function searchWithRipgrep(rootPath, query, maxFiles) {
|
|
|
15014
15596
|
if (!rawPath) {
|
|
15015
15597
|
return;
|
|
15016
15598
|
}
|
|
15017
|
-
const relativePath = normalizeRelativePath(
|
|
15599
|
+
const relativePath = normalizeRelativePath(relative3(rootPath, join12(rootPath, rawPath)));
|
|
15018
15600
|
const preview = (event.data?.lines?.text ?? "").replace(/\r?\n$/, "");
|
|
15019
15601
|
const lineNumber = event.data?.line_number ?? 1;
|
|
15020
15602
|
const submatches = event.data?.submatches ?? [];
|
|
@@ -15044,7 +15626,7 @@ async function searchWithRipgrep(rootPath, query, maxFiles) {
|
|
|
15044
15626
|
child.on("close", (code) => {
|
|
15045
15627
|
void stdout.close();
|
|
15046
15628
|
if (code === 0 || code === 1) {
|
|
15047
|
-
|
|
15629
|
+
resolve6({
|
|
15048
15630
|
files: sortAccumulators(files),
|
|
15049
15631
|
totalMatchCount,
|
|
15050
15632
|
hasMoreFiles
|
|
@@ -15067,7 +15649,7 @@ async function searchWithNode(rootPath, query, maxFiles) {
|
|
|
15067
15649
|
const filteredEntries = entries.filter((entry) => filter(entry.name));
|
|
15068
15650
|
filteredEntries.sort((a, b) => a.name.localeCompare(b.name));
|
|
15069
15651
|
for (const entry of filteredEntries) {
|
|
15070
|
-
const fullPath =
|
|
15652
|
+
const fullPath = join12(dirPath, entry.name);
|
|
15071
15653
|
if (entry.isDirectory()) {
|
|
15072
15654
|
await walk(fullPath);
|
|
15073
15655
|
continue;
|
|
@@ -15075,7 +15657,7 @@ async function searchWithNode(rootPath, query, maxFiles) {
|
|
|
15075
15657
|
if (!entry.isFile()) {
|
|
15076
15658
|
continue;
|
|
15077
15659
|
}
|
|
15078
|
-
const fileStat = await
|
|
15660
|
+
const fileStat = await stat9(fullPath);
|
|
15079
15661
|
if (fileStat.size > FALLBACK_MAX_FILE_BYTES) {
|
|
15080
15662
|
continue;
|
|
15081
15663
|
}
|
|
@@ -15083,7 +15665,7 @@ async function searchWithNode(rootPath, query, maxFiles) {
|
|
|
15083
15665
|
if (isBinaryFile(buffer)) {
|
|
15084
15666
|
continue;
|
|
15085
15667
|
}
|
|
15086
|
-
const relativePath = normalizeRelativePath(
|
|
15668
|
+
const relativePath = normalizeRelativePath(relative3(rootPath, fullPath));
|
|
15087
15669
|
const file = collectMatchesFromText(relativePath, buffer.toString("utf-8"), query);
|
|
15088
15670
|
if (!file) {
|
|
15089
15671
|
continue;
|
|
@@ -15190,10 +15772,10 @@ var init_content_search = __esm({
|
|
|
15190
15772
|
});
|
|
15191
15773
|
|
|
15192
15774
|
// packages/server/src/fs/tree.ts
|
|
15193
|
-
import { readdir as readdir5, stat as
|
|
15194
|
-
import { join as
|
|
15775
|
+
import { readdir as readdir5, stat as stat10 } from "fs/promises";
|
|
15776
|
+
import { join as join13, relative as relative4 } from "path";
|
|
15195
15777
|
async function readTree(rootPath, subdir) {
|
|
15196
|
-
const targetPath = subdir ?
|
|
15778
|
+
const targetPath = subdir ? join13(rootPath, subdir) : rootPath;
|
|
15197
15779
|
const filter = createTreeVisibilityFilter();
|
|
15198
15780
|
const entries = await readdir5(targetPath, { withFileTypes: true });
|
|
15199
15781
|
const nodes = [];
|
|
@@ -15201,8 +15783,8 @@ async function readTree(rootPath, subdir) {
|
|
|
15201
15783
|
if (!filter(entry.name)) {
|
|
15202
15784
|
continue;
|
|
15203
15785
|
}
|
|
15204
|
-
const fullPath =
|
|
15205
|
-
const relPath =
|
|
15786
|
+
const fullPath = join13(targetPath, entry.name);
|
|
15787
|
+
const relPath = relative4(rootPath, fullPath);
|
|
15206
15788
|
if (entry.isDirectory()) {
|
|
15207
15789
|
nodes.push({
|
|
15208
15790
|
name: entry.name,
|
|
@@ -15212,7 +15794,7 @@ async function readTree(rootPath, subdir) {
|
|
|
15212
15794
|
// Not loaded yet - client will request on expand
|
|
15213
15795
|
});
|
|
15214
15796
|
} else if (entry.isFile()) {
|
|
15215
|
-
const stats = await
|
|
15797
|
+
const stats = await stat10(fullPath);
|
|
15216
15798
|
nodes.push({
|
|
15217
15799
|
name: entry.name,
|
|
15218
15800
|
path: relPath,
|
|
@@ -15245,8 +15827,8 @@ async function searchFiles(rootPath, query, limit = 10) {
|
|
|
15245
15827
|
const filteredEntries = entries.filter((entry) => filter(entry.name));
|
|
15246
15828
|
filteredEntries.sort((a, b) => a.name.localeCompare(b.name));
|
|
15247
15829
|
for (const entry of filteredEntries) {
|
|
15248
|
-
const fullPath =
|
|
15249
|
-
const relPath =
|
|
15830
|
+
const fullPath = join13(dirPath, entry.name);
|
|
15831
|
+
const relPath = relative4(rootPath, fullPath);
|
|
15250
15832
|
if (entry.isDirectory()) {
|
|
15251
15833
|
await walk(fullPath);
|
|
15252
15834
|
continue;
|
|
@@ -15281,7 +15863,7 @@ async function searchFiles(rootPath, query, limit = 10) {
|
|
|
15281
15863
|
}
|
|
15282
15864
|
return a.path.toLowerCase().localeCompare(b.path.toLowerCase());
|
|
15283
15865
|
}).slice(0, limit)) {
|
|
15284
|
-
const stats = await
|
|
15866
|
+
const stats = await stat10(match.fullPath);
|
|
15285
15867
|
files.push({
|
|
15286
15868
|
name: match.name,
|
|
15287
15869
|
path: match.path,
|
|
@@ -15540,7 +16122,7 @@ var init_file = __esm({
|
|
|
15540
16122
|
});
|
|
15541
16123
|
|
|
15542
16124
|
// packages/server/src/git/diff.ts
|
|
15543
|
-
import { mkdtemp as mkdtemp3, readFile as readFile5, rm as
|
|
16125
|
+
import { mkdtemp as mkdtemp3, readFile as readFile5, rm as rm7 } from "fs/promises";
|
|
15544
16126
|
import os3 from "os";
|
|
15545
16127
|
import path11 from "path";
|
|
15546
16128
|
async function isTrackedPath(cwd, filePath) {
|
|
@@ -15575,7 +16157,7 @@ async function getUntrackedFileDiff(cwd, filePath) {
|
|
|
15575
16157
|
});
|
|
15576
16158
|
return result.stdout;
|
|
15577
16159
|
} finally {
|
|
15578
|
-
await
|
|
16160
|
+
await rm7(tempDir, { recursive: true, force: true });
|
|
15579
16161
|
}
|
|
15580
16162
|
}
|
|
15581
16163
|
async function pathExists(cwd, filePath) {
|
|
@@ -16008,25 +16590,25 @@ var init_git2 = __esm({
|
|
|
16008
16590
|
// packages/server/src/config/config-io.ts
|
|
16009
16591
|
import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync7, renameSync as renameSync2, writeFileSync as writeFileSync5 } from "node:fs";
|
|
16010
16592
|
import { homedir as homedir3 } from "node:os";
|
|
16011
|
-
import { basename as basename3, dirname as dirname7, join as
|
|
16593
|
+
import { basename as basename3, dirname as dirname7, join as join14 } from "node:path";
|
|
16012
16594
|
function resolveConfigPath(configType) {
|
|
16013
16595
|
if (configType === "codex") {
|
|
16014
16596
|
const testHome = process.env.CODER_STUDIO_CODEX_HOME;
|
|
16015
16597
|
if (testHome && testHome.trim()) {
|
|
16016
|
-
return
|
|
16598
|
+
return join14(testHome, "config.toml");
|
|
16017
16599
|
}
|
|
16018
16600
|
const codexHome = process.env.CODEX_HOME;
|
|
16019
16601
|
if (codexHome && codexHome.trim()) {
|
|
16020
|
-
return
|
|
16602
|
+
return join14(codexHome, "config.toml");
|
|
16021
16603
|
}
|
|
16022
|
-
return
|
|
16604
|
+
return join14(homedir3(), ".codex", "config.toml");
|
|
16023
16605
|
}
|
|
16024
16606
|
if (configType === "claude") {
|
|
16025
16607
|
const testHome = process.env.CODER_STUDIO_CLAUDE_HOME;
|
|
16026
16608
|
if (testHome && testHome.trim()) {
|
|
16027
|
-
return
|
|
16609
|
+
return join14(testHome, "settings.json");
|
|
16028
16610
|
}
|
|
16029
|
-
return
|
|
16611
|
+
return join14(homedir3(), ".claude", "settings.json");
|
|
16030
16612
|
}
|
|
16031
16613
|
throw new Error(`Unknown config type: ${configType}`);
|
|
16032
16614
|
}
|
|
@@ -16071,7 +16653,7 @@ function createBackup(filePath) {
|
|
|
16071
16653
|
const base = basename3(filePath, `.${ext}`);
|
|
16072
16654
|
const dir = dirname7(filePath);
|
|
16073
16655
|
const ts = formatTimestamp(/* @__PURE__ */ new Date());
|
|
16074
|
-
const backupPath =
|
|
16656
|
+
const backupPath = join14(dir, `${base}.bak.${ts}.${ext}`);
|
|
16075
16657
|
writeFileSync5(backupPath, original, "utf-8");
|
|
16076
16658
|
return backupPath;
|
|
16077
16659
|
}
|
|
@@ -16099,7 +16681,36 @@ function flattenSettings(obj, prefix = "") {
|
|
|
16099
16681
|
}
|
|
16100
16682
|
return result;
|
|
16101
16683
|
}
|
|
16102
|
-
|
|
16684
|
+
function resolveAppearancePersonalizationOverrideKeysToDelete(settings) {
|
|
16685
|
+
const appearance = settings.appearance;
|
|
16686
|
+
if (!appearance || typeof appearance !== "object" || Array.isArray(appearance)) {
|
|
16687
|
+
return [];
|
|
16688
|
+
}
|
|
16689
|
+
const personalization = appearance.personalization;
|
|
16690
|
+
if (!personalization || typeof personalization !== "object" || Array.isArray(personalization)) {
|
|
16691
|
+
return [];
|
|
16692
|
+
}
|
|
16693
|
+
if (!isFullAppearancePersonalizationSnapshot(personalization)) {
|
|
16694
|
+
return [];
|
|
16695
|
+
}
|
|
16696
|
+
const keysToDelete = [];
|
|
16697
|
+
for (const branch of PERSONALIZATION_OVERRIDE_BRANCHES) {
|
|
16698
|
+
const overrides = personalization[branch];
|
|
16699
|
+
if (!overrides || typeof overrides !== "object" || Array.isArray(overrides)) {
|
|
16700
|
+
continue;
|
|
16701
|
+
}
|
|
16702
|
+
for (const field of PERSONALIZATION_OVERRIDE_FIELDS) {
|
|
16703
|
+
if (!Object.prototype.hasOwnProperty.call(overrides, field)) {
|
|
16704
|
+
keysToDelete.push(`appearance.personalization.${branch}.${field}`);
|
|
16705
|
+
}
|
|
16706
|
+
}
|
|
16707
|
+
}
|
|
16708
|
+
return keysToDelete;
|
|
16709
|
+
}
|
|
16710
|
+
function isFullAppearancePersonalizationSnapshot(personalization) {
|
|
16711
|
+
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");
|
|
16712
|
+
}
|
|
16713
|
+
var PersonalizationOverridesSchema, PERSONALIZATION_OVERRIDE_BRANCHES, PERSONALIZATION_OVERRIDE_FIELDS, SettingsSchema;
|
|
16103
16714
|
var init_settings2 = __esm({
|
|
16104
16715
|
"packages/server/src/commands/settings.ts"() {
|
|
16105
16716
|
"use strict";
|
|
@@ -16108,6 +16719,23 @@ var init_settings2 = __esm({
|
|
|
16108
16719
|
init_provider_config();
|
|
16109
16720
|
init_settings();
|
|
16110
16721
|
init_dispatch();
|
|
16722
|
+
PersonalizationOverridesSchema = z15.object({
|
|
16723
|
+
backgroundAssetId: z15.string().min(1).nullable().optional(),
|
|
16724
|
+
backgroundDimness: z15.number().int().min(0).max(100).optional(),
|
|
16725
|
+
backgroundBlur: z15.number().int().min(0).max(40).optional(),
|
|
16726
|
+
glassEnabled: z15.boolean().optional(),
|
|
16727
|
+
glassIntensity: z15.number().int().min(0).max(100).optional(),
|
|
16728
|
+
surfaceOpacity: z15.number().int().min(0).max(100).optional()
|
|
16729
|
+
});
|
|
16730
|
+
PERSONALIZATION_OVERRIDE_BRANCHES = ["desktop", "mobile"];
|
|
16731
|
+
PERSONALIZATION_OVERRIDE_FIELDS = [
|
|
16732
|
+
"backgroundAssetId",
|
|
16733
|
+
"backgroundDimness",
|
|
16734
|
+
"backgroundBlur",
|
|
16735
|
+
"glassEnabled",
|
|
16736
|
+
"glassIntensity",
|
|
16737
|
+
"surfaceOpacity"
|
|
16738
|
+
];
|
|
16111
16739
|
SettingsSchema = z15.object({
|
|
16112
16740
|
defaultProviderId: z15.string().optional(),
|
|
16113
16741
|
notifications: z15.object({
|
|
@@ -16134,7 +16762,22 @@ var init_settings2 = __esm({
|
|
|
16134
16762
|
terminalFontSize: z15.number().int().min(10).max(18).optional(),
|
|
16135
16763
|
desktopTerminalFontSize: z15.number().int().min(10).max(18).optional(),
|
|
16136
16764
|
mobileTerminalFontSize: z15.number().int().min(10).max(18).optional(),
|
|
16137
|
-
locale: z15.enum(["zh", "en"]).optional()
|
|
16765
|
+
locale: z15.enum(["zh", "en"]).optional(),
|
|
16766
|
+
personalization: z15.object({
|
|
16767
|
+
version: z15.literal(1).optional(),
|
|
16768
|
+
common: z15.object({
|
|
16769
|
+
backgroundMode: z15.enum(["none", "image"]).optional(),
|
|
16770
|
+
backgroundAssetId: z15.string().min(1).nullable().optional(),
|
|
16771
|
+
backgroundFit: z15.enum(["cover", "contain"]).optional(),
|
|
16772
|
+
backgroundDimness: z15.number().int().min(0).max(100).optional(),
|
|
16773
|
+
backgroundBlur: z15.number().int().min(0).max(40).optional(),
|
|
16774
|
+
glassEnabled: z15.boolean().optional(),
|
|
16775
|
+
glassIntensity: z15.number().int().min(0).max(100).optional(),
|
|
16776
|
+
surfaceOpacity: z15.number().int().min(0).max(100).optional()
|
|
16777
|
+
}).optional(),
|
|
16778
|
+
desktop: PersonalizationOverridesSchema.optional(),
|
|
16779
|
+
mobile: PersonalizationOverridesSchema.optional()
|
|
16780
|
+
}).optional()
|
|
16138
16781
|
}).optional(),
|
|
16139
16782
|
lsp: z15.object({
|
|
16140
16783
|
mode: z15.enum(["auto", "off"]).optional()
|
|
@@ -16204,7 +16847,11 @@ var init_settings2 = __esm({
|
|
|
16204
16847
|
const nextSettings = args.settings;
|
|
16205
16848
|
const providers = nextSettings.providers && typeof nextSettings.providers === "object" && !Array.isArray(nextSettings.providers) ? nextSettings.providers : void 0;
|
|
16206
16849
|
const { providers: _providers, ...nonProviderSettings } = nextSettings;
|
|
16850
|
+
const overrideKeysToDelete = resolveAppearancePersonalizationOverrideKeysToDelete(nextSettings);
|
|
16207
16851
|
const flatSettings = flattenSettings(nonProviderSettings);
|
|
16852
|
+
for (const key of overrideKeysToDelete) {
|
|
16853
|
+
ctx.settingsRepo.delete(key);
|
|
16854
|
+
}
|
|
16208
16855
|
for (const [key, value] of Object.entries(flatSettings)) {
|
|
16209
16856
|
ctx.settingsRepo.set(key, value);
|
|
16210
16857
|
}
|
|
@@ -16850,6 +17497,7 @@ async function listWorktrees(repoPath) {
|
|
|
16850
17497
|
current.name = branch.split("/").pop() || branch;
|
|
16851
17498
|
} else if (line === "detached") {
|
|
16852
17499
|
current.branch = "detached HEAD";
|
|
17500
|
+
current.name = path12.basename(current.path ?? "") || "detached";
|
|
16853
17501
|
} else if (line === "") {
|
|
16854
17502
|
if (current.path) {
|
|
16855
17503
|
worktrees.push(current);
|
|
@@ -17431,12 +18079,12 @@ var init_commands = __esm({
|
|
|
17431
18079
|
// packages/server/src/server.ts
|
|
17432
18080
|
import { mkdtempSync, rmSync as rmSync2 } from "node:fs";
|
|
17433
18081
|
import { tmpdir } from "node:os";
|
|
17434
|
-
import { join as
|
|
18082
|
+
import { join as join15 } from "node:path";
|
|
17435
18083
|
async function createServer(configOverrides) {
|
|
17436
18084
|
const config = parseServerConfig(configOverrides);
|
|
17437
18085
|
const configuredStateDir = resolveConfiguredStateDir(config);
|
|
17438
18086
|
const shouldCleanupStateRoot = configuredStateDir === IN_MEMORY_STATE_DIR;
|
|
17439
|
-
const stateRoot = shouldCleanupStateRoot ? mkdtempSync(
|
|
18087
|
+
const stateRoot = shouldCleanupStateRoot ? mkdtempSync(join15(tmpdir(), "coder-studio-state-")) : configuredStateDir;
|
|
17440
18088
|
ensureStateDir(config);
|
|
17441
18089
|
const eventBus = new EventBus();
|
|
17442
18090
|
const activationMgr = new ActivationManager();
|
|
@@ -17446,10 +18094,10 @@ async function createServer(configOverrides) {
|
|
|
17446
18094
|
let commandContext;
|
|
17447
18095
|
let lspMgr = null;
|
|
17448
18096
|
const terminalRepo = new TerminalRepo({
|
|
17449
|
-
filePath:
|
|
18097
|
+
filePath: join15(stateRoot, "state", "terminals.json")
|
|
17450
18098
|
});
|
|
17451
18099
|
const sessionRepo = new SessionRepo({
|
|
17452
|
-
filePath:
|
|
18100
|
+
filePath: join15(stateRoot, "state", "sessions.json")
|
|
17453
18101
|
});
|
|
17454
18102
|
const terminalMgr = new TerminalManager({
|
|
17455
18103
|
ptyHost: createPtyHost(),
|
|
@@ -17457,10 +18105,10 @@ async function createServer(configOverrides) {
|
|
|
17457
18105
|
db: terminalRepo
|
|
17458
18106
|
});
|
|
17459
18107
|
const settingsRepo = new SettingsRepo({
|
|
17460
|
-
filePath:
|
|
18108
|
+
filePath: join15(stateRoot, "state", "settings.json")
|
|
17461
18109
|
});
|
|
17462
18110
|
const updateStateRepo = new UpdateStateRepo({
|
|
17463
|
-
filePath:
|
|
18111
|
+
filePath: join15(stateRoot, "state", "update-state.json"),
|
|
17464
18112
|
currentVersion: config.appVersion ?? "0.0.0"
|
|
17465
18113
|
});
|
|
17466
18114
|
const autoFetch = new AutoFetchScheduler({
|
|
@@ -17493,10 +18141,10 @@ async function createServer(configOverrides) {
|
|
|
17493
18141
|
}
|
|
17494
18142
|
});
|
|
17495
18143
|
const providerConfigRepo = new ProviderConfigRepo({
|
|
17496
|
-
filePath:
|
|
18144
|
+
filePath: join15(stateRoot, "state", "provider-configs.json")
|
|
17497
18145
|
});
|
|
17498
18146
|
const workspaceRepo = new WorkspaceRepo({
|
|
17499
|
-
filePath:
|
|
18147
|
+
filePath: join15(stateRoot, "state", "workspaces.json")
|
|
17500
18148
|
});
|
|
17501
18149
|
const sessionMgr = new SessionManager({
|
|
17502
18150
|
terminalMgr,
|
|
@@ -17531,10 +18179,13 @@ async function createServer(configOverrides) {
|
|
|
17531
18179
|
)
|
|
17532
18180
|
});
|
|
17533
18181
|
const authSessionRepo = new AuthSessionRepo({
|
|
17534
|
-
filePath:
|
|
18182
|
+
filePath: join15(stateRoot, "state", "auth-sessions.json")
|
|
17535
18183
|
});
|
|
17536
18184
|
const authLoginBlockRepo = new AuthLoginBlockRepo({
|
|
17537
|
-
filePath:
|
|
18185
|
+
filePath: join15(stateRoot, "state", "auth-login-blocks.json")
|
|
18186
|
+
});
|
|
18187
|
+
const appearanceAssetRepo = new AppearanceAssetRepo({
|
|
18188
|
+
filePath: join15(stateRoot, "state", "appearance-assets.json")
|
|
17538
18189
|
});
|
|
17539
18190
|
const app = await buildFastifyApp({
|
|
17540
18191
|
wsHub,
|
|
@@ -17543,6 +18194,7 @@ async function createServer(configOverrides) {
|
|
|
17543
18194
|
config,
|
|
17544
18195
|
authSessionRepo,
|
|
17545
18196
|
authLoginBlockRepo,
|
|
18197
|
+
appearanceAssetRepo,
|
|
17546
18198
|
logger: {
|
|
17547
18199
|
level: "info",
|
|
17548
18200
|
transport: {
|
|
@@ -17609,7 +18261,7 @@ async function createServer(configOverrides) {
|
|
|
17609
18261
|
...config.update,
|
|
17610
18262
|
currentVersion: config.appVersion ?? "0.0.0"
|
|
17611
18263
|
},
|
|
17612
|
-
updateWorkerLogFilePath:
|
|
18264
|
+
updateWorkerLogFilePath: join15(stateRoot, "logs", "update-worker.log"),
|
|
17613
18265
|
countRunningTerminals: () => terminalMgr.getAll().filter((terminal) => terminal.alive).length,
|
|
17614
18266
|
countRunningSessions: () => sessionMgr.getAll().filter((session) => session.state === "starting" || session.state === "running").length,
|
|
17615
18267
|
countActiveSupervisors: () => supervisorMgr?.countActive() ?? 0
|
|
@@ -17721,6 +18373,7 @@ var init_server = __esm({
|
|
|
17721
18373
|
init_e2e_provider_mock();
|
|
17722
18374
|
init_install_manager2();
|
|
17723
18375
|
init_manager3();
|
|
18376
|
+
init_appearance_asset_repo();
|
|
17724
18377
|
init_auth_login_block_repo();
|
|
17725
18378
|
init_auth_session_repo();
|
|
17726
18379
|
init_provider_config_repo();
|
|
@@ -17832,20 +18485,20 @@ var init_src4 = __esm({
|
|
|
17832
18485
|
|
|
17833
18486
|
// packages/cli/src/cli.ts
|
|
17834
18487
|
import { existsSync as existsSync15 } from "fs";
|
|
17835
|
-
import { dirname as dirname10, join as
|
|
18488
|
+
import { dirname as dirname10, join as join20 } from "path";
|
|
17836
18489
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
17837
18490
|
|
|
17838
18491
|
// packages/cli/src/auth-control.ts
|
|
17839
18492
|
await init_src4();
|
|
17840
|
-
import { join as
|
|
18493
|
+
import { join as join17 } from "node:path";
|
|
17841
18494
|
|
|
17842
18495
|
// packages/cli/src/config-store.ts
|
|
17843
18496
|
init_state_paths();
|
|
17844
18497
|
import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
|
|
17845
18498
|
import { homedir as homedir4 } from "os";
|
|
17846
|
-
import { join as
|
|
18499
|
+
import { join as join16 } from "path";
|
|
17847
18500
|
function getCliConfigPath() {
|
|
17848
|
-
return
|
|
18501
|
+
return join16(homedir4(), ".coder-studio", "config.json");
|
|
17849
18502
|
}
|
|
17850
18503
|
function normalizeLegacyDataDir(input2) {
|
|
17851
18504
|
return normalizeLegacyStateDir(input2);
|
|
@@ -17872,7 +18525,7 @@ function readCliConfig() {
|
|
|
17872
18525
|
}
|
|
17873
18526
|
function writeCliConfig(config) {
|
|
17874
18527
|
const path14 = getCliConfigPath();
|
|
17875
|
-
const dir =
|
|
18528
|
+
const dir = join16(homedir4(), ".coder-studio");
|
|
17876
18529
|
const normalizedConfig = {
|
|
17877
18530
|
...config.host !== void 0 ? { host: config.host } : {},
|
|
17878
18531
|
...config.port !== void 0 && config.port > 0 ? { port: config.port } : {},
|
|
@@ -17894,7 +18547,7 @@ function resolveStateDir() {
|
|
|
17894
18547
|
}
|
|
17895
18548
|
async function listAuthBlocks(now = Date.now()) {
|
|
17896
18549
|
const repo = new AuthLoginBlockRepo({
|
|
17897
|
-
filePath:
|
|
18550
|
+
filePath: join17(resolveStateDir(), "state", "auth-login-blocks.json")
|
|
17898
18551
|
});
|
|
17899
18552
|
return repo.listActiveBlocks(now).map((record) => ({
|
|
17900
18553
|
ip: record.ip,
|
|
@@ -17906,7 +18559,7 @@ async function listAuthBlocks(now = Date.now()) {
|
|
|
17906
18559
|
}
|
|
17907
18560
|
async function clearAuthBlockByIp(ip) {
|
|
17908
18561
|
const repo = new AuthLoginBlockRepo({
|
|
17909
|
-
filePath:
|
|
18562
|
+
filePath: join17(resolveStateDir(), "state", "auth-login-blocks.json")
|
|
17910
18563
|
});
|
|
17911
18564
|
return repo.delete(ip);
|
|
17912
18565
|
}
|
|
@@ -17925,7 +18578,7 @@ function getOpenCommand(url) {
|
|
|
17925
18578
|
}
|
|
17926
18579
|
async function openBrowser(url) {
|
|
17927
18580
|
const { command, args } = getOpenCommand(url);
|
|
17928
|
-
await new Promise((
|
|
18581
|
+
await new Promise((resolve6, reject) => {
|
|
17929
18582
|
const child = spawn6(command, args, {
|
|
17930
18583
|
detached: true,
|
|
17931
18584
|
stdio: "ignore",
|
|
@@ -17934,7 +18587,7 @@ async function openBrowser(url) {
|
|
|
17934
18587
|
child.once("error", reject);
|
|
17935
18588
|
child.once("spawn", () => {
|
|
17936
18589
|
child.unref();
|
|
17937
|
-
|
|
18590
|
+
resolve6();
|
|
17938
18591
|
});
|
|
17939
18592
|
});
|
|
17940
18593
|
}
|
|
@@ -18267,7 +18920,7 @@ function parseArgs(argv) {
|
|
|
18267
18920
|
init_runtime();
|
|
18268
18921
|
import { mkdirSync as mkdirSync9 } from "fs";
|
|
18269
18922
|
import { homedir as homedir5 } from "os";
|
|
18270
|
-
import { join as
|
|
18923
|
+
import { join as join18 } from "path";
|
|
18271
18924
|
var MANAGED_SERVER_NAME = "coder-studio-server";
|
|
18272
18925
|
var PM2_RESTART_DELAY_MS = 2e3;
|
|
18273
18926
|
var PM2_MIN_UPTIME = "5s";
|
|
@@ -18307,29 +18960,29 @@ async function loadPm2() {
|
|
|
18307
18960
|
}
|
|
18308
18961
|
var connectPm2 = async () => {
|
|
18309
18962
|
const pm2 = await loadPm2();
|
|
18310
|
-
return new Promise((
|
|
18963
|
+
return new Promise((resolve6, reject) => {
|
|
18311
18964
|
pm2.connect((error) => {
|
|
18312
18965
|
if (error) {
|
|
18313
18966
|
reject(error);
|
|
18314
18967
|
return;
|
|
18315
18968
|
}
|
|
18316
|
-
|
|
18969
|
+
resolve6();
|
|
18317
18970
|
});
|
|
18318
18971
|
});
|
|
18319
18972
|
};
|
|
18320
|
-
var sleep = async (ms) => new Promise((
|
|
18321
|
-
setTimeout(
|
|
18973
|
+
var sleep = async (ms) => new Promise((resolve6) => {
|
|
18974
|
+
setTimeout(resolve6, ms);
|
|
18322
18975
|
});
|
|
18323
18976
|
var disconnectPm2 = async () => {
|
|
18324
18977
|
const pm2 = await loadPm2();
|
|
18325
|
-
await new Promise((
|
|
18978
|
+
await new Promise((resolve6) => {
|
|
18326
18979
|
let settled = false;
|
|
18327
18980
|
const finish = () => {
|
|
18328
18981
|
if (settled) {
|
|
18329
18982
|
return;
|
|
18330
18983
|
}
|
|
18331
18984
|
settled = true;
|
|
18332
|
-
|
|
18985
|
+
resolve6();
|
|
18333
18986
|
};
|
|
18334
18987
|
const timer = setTimeout(finish, PM2_DISCONNECT_WAIT_MS);
|
|
18335
18988
|
try {
|
|
@@ -18343,29 +18996,29 @@ var disconnectPm2 = async () => {
|
|
|
18343
18996
|
}
|
|
18344
18997
|
});
|
|
18345
18998
|
};
|
|
18346
|
-
var describeManagedServer = async (pm2) => new Promise((
|
|
18999
|
+
var describeManagedServer = async (pm2) => new Promise((resolve6, reject) => {
|
|
18347
19000
|
pm2.describe(MANAGED_SERVER_NAME, (error, result) => {
|
|
18348
19001
|
if (error) {
|
|
18349
19002
|
reject(error);
|
|
18350
19003
|
return;
|
|
18351
19004
|
}
|
|
18352
|
-
|
|
19005
|
+
resolve6(result ?? []);
|
|
18353
19006
|
});
|
|
18354
19007
|
});
|
|
18355
|
-
var removeManagedServer = async (pm2) => new Promise((
|
|
19008
|
+
var removeManagedServer = async (pm2) => new Promise((resolve6, reject) => {
|
|
18356
19009
|
pm2.delete(MANAGED_SERVER_NAME, (error) => {
|
|
18357
19010
|
if (error) {
|
|
18358
19011
|
reject(error);
|
|
18359
19012
|
return;
|
|
18360
19013
|
}
|
|
18361
|
-
|
|
19014
|
+
resolve6();
|
|
18362
19015
|
});
|
|
18363
19016
|
});
|
|
18364
19017
|
var killPm2Daemon = async () => {
|
|
18365
19018
|
const pm2 = await loadPm2();
|
|
18366
|
-
return new Promise((
|
|
19019
|
+
return new Promise((resolve6) => {
|
|
18367
19020
|
pm2.kill(() => {
|
|
18368
|
-
|
|
19021
|
+
resolve6();
|
|
18369
19022
|
});
|
|
18370
19023
|
});
|
|
18371
19024
|
};
|
|
@@ -18488,11 +19141,11 @@ var deleteManagedServerInSession = async (pm2, {
|
|
|
18488
19141
|
return true;
|
|
18489
19142
|
};
|
|
18490
19143
|
var ensureLogDirectory = () => {
|
|
18491
|
-
mkdirSync9(
|
|
19144
|
+
mkdirSync9(join18(homedir5(), ".coder-studio", "logs"), { recursive: true });
|
|
18492
19145
|
};
|
|
18493
19146
|
var getLogPaths = () => ({
|
|
18494
|
-
outFile:
|
|
18495
|
-
errFile:
|
|
19147
|
+
outFile: join18(homedir5(), ".coder-studio", "logs", "server.out.log"),
|
|
19148
|
+
errFile: join18(homedir5(), ".coder-studio", "logs", "server.err.log")
|
|
18496
19149
|
});
|
|
18497
19150
|
var captureStartupLogOffsets = () => {
|
|
18498
19151
|
const { outFile, errFile } = getLogPaths();
|
|
@@ -18541,7 +19194,7 @@ var startManagedServer = async ({
|
|
|
18541
19194
|
ensureLogDirectory();
|
|
18542
19195
|
const { outFile, errFile } = getLogPaths();
|
|
18543
19196
|
const logOffsets = captureStartupLogOffsets();
|
|
18544
|
-
await new Promise((
|
|
19197
|
+
await new Promise((resolve6, reject) => {
|
|
18545
19198
|
pm2.start(
|
|
18546
19199
|
{
|
|
18547
19200
|
name: MANAGED_SERVER_NAME,
|
|
@@ -18564,7 +19217,7 @@ var startManagedServer = async ({
|
|
|
18564
19217
|
reject(error);
|
|
18565
19218
|
return;
|
|
18566
19219
|
}
|
|
18567
|
-
|
|
19220
|
+
resolve6();
|
|
18568
19221
|
}
|
|
18569
19222
|
);
|
|
18570
19223
|
});
|
|
@@ -18681,11 +19334,11 @@ import { fileURLToPath as fileURLToPath4 } from "url";
|
|
|
18681
19334
|
|
|
18682
19335
|
// packages/cli/src/embed.ts
|
|
18683
19336
|
import { existsSync as existsSync13 } from "fs";
|
|
18684
|
-
import { dirname as dirname8, resolve as
|
|
19337
|
+
import { dirname as dirname8, resolve as resolve5 } from "path";
|
|
18685
19338
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
18686
19339
|
var __filename = fileURLToPath2(import.meta.url);
|
|
18687
19340
|
var __dirname = dirname8(__filename);
|
|
18688
|
-
var WEB_ASSETS_DIR =
|
|
19341
|
+
var WEB_ASSETS_DIR = resolve5(__dirname, "../web");
|
|
18689
19342
|
function getStaticAssetsDir() {
|
|
18690
19343
|
return WEB_ASSETS_DIR;
|
|
18691
19344
|
}
|
|
@@ -18695,13 +19348,13 @@ function hasWebAssets() {
|
|
|
18695
19348
|
|
|
18696
19349
|
// packages/cli/src/update-runtime.ts
|
|
18697
19350
|
import { existsSync as existsSync14 } from "node:fs";
|
|
18698
|
-
import { dirname as dirname9, join as
|
|
19351
|
+
import { dirname as dirname9, join as join19 } from "node:path";
|
|
18699
19352
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
18700
19353
|
function resolveWorkerEntryPath(importMetaUrl) {
|
|
18701
19354
|
const currentDir = dirname9(fileURLToPath3(importMetaUrl));
|
|
18702
19355
|
const candidates = [
|
|
18703
|
-
|
|
18704
|
-
|
|
19356
|
+
join19(currentDir, "update-worker.mjs"),
|
|
19357
|
+
join19(currentDir, "../src/update-worker.ts")
|
|
18705
19358
|
];
|
|
18706
19359
|
return candidates.find((candidate) => existsSync14(candidate));
|
|
18707
19360
|
}
|
|
@@ -18930,9 +19583,9 @@ function resolveManagedScriptPath() {
|
|
|
18930
19583
|
const currentFile = fileURLToPath5(import.meta.url);
|
|
18931
19584
|
const currentDir = dirname10(currentFile);
|
|
18932
19585
|
const candidates = [
|
|
18933
|
-
|
|
18934
|
-
|
|
18935
|
-
|
|
19586
|
+
join20(currentDir, "server-runner.js"),
|
|
19587
|
+
join20(currentDir, "server-runner.mjs"),
|
|
19588
|
+
join20(currentDir, "../src/server-runner.ts")
|
|
18936
19589
|
];
|
|
18937
19590
|
const scriptPath = candidates.find((candidate) => existsSync15(candidate));
|
|
18938
19591
|
if (!scriptPath) {
|