@spencer-kit/coder-studio 0.4.4 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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 path4 from "node:path";
1147
- function isPathInsideRoot(rootPath, targetPath, pathApi = path4) {
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 path5 from "node:path";
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 stat(path14);
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 mkdir(path5.dirname(abs), { recursive: true });
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 mkdir(abs, { recursive: true });
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 rm(abs, { recursive: true });
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 = path5.dirname(fromAbs);
1206
- const toParent = path5.dirname(toAbs);
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 = path5) {
1496
+ function resolveSafe(root, relPath, pathApi = path6) {
1222
1497
  const absRoot = pathApi.resolve(root);
1223
1498
  const abs = pathApi.resolve(absRoot, relPath);
1224
- if (!isPathInsideRoot(absRoot, abs, pathApi)) {
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 mkdir(path5.dirname(abs), { recursive: true });
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 mkdir2, mkdtemp, rm as rm2, writeFile as writeFile2 } from "fs/promises";
1711
+ import { mkdir as mkdir3, mkdtemp, rm as rm3, writeFile as writeFile2 } from "fs/promises";
1437
1712
  import os2 from "os";
1438
- import path6 from "path";
1713
+ import path7 from "path";
1439
1714
  async function runGit(cwd, args, options = {}) {
1440
- return new Promise((resolve4, reject) => {
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
- resolve4({ stdout, stderr });
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(path6.join(os2.tmpdir(), "coder-studio-git-auth-"));
1927
- const hooksDir = path6.join(tempDir, "hooks");
1928
- const askPassPath = path6.join(tempDir, "askpass.sh");
1929
- await mkdir2(hooksDir, { recursive: true, mode: 448 });
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 rm2(tempDir, { recursive: true, force: true });
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 stat2 } from "fs/promises";
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 (!isPathInsideRoot(realWorkspacePath, realAssetPath)) {
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 stat2(absPath);
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(createReadStream(absPath));
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 stat3 } from "node:fs/promises";
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 (!isPathInsideRoot(realWorkspacePath, realAssetPath)) {
2631
+ if (!isPathInsideRoot2(realWorkspacePath, realAssetPath)) {
2357
2632
  throw new Error("path_escape");
2358
2633
  }
2359
- const [bytes, stats] = await Promise.all([readFile2(realAssetPath), stat3(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 rm3, rmdir, stat as stat4, unlink } from "node:fs/promises";
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 stat4(childPath);
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 rm3(bucket, { recursive: true, force: true });
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 rm4, stat as stat5, writeFile as writeFile3 } from "node:fs/promises";
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) => rm4(file.path, { force: true })));
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 rejectAndCleanup(reply, written, statusCode, error) {
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 rejectAndCleanup(reply, written, 404, "workspace_not_found");
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 rejectAndCleanup(reply, written, 400, "workspace_mismatch");
2988
+ return rejectAndCleanup2(reply, written, 400, "workspace_mismatch");
2824
2989
  }
2825
2990
  workspaceId = lockedWorkspaceId;
2826
2991
  if (!getActiveWorkspace(deps, workspaceId)) {
2827
- return rejectAndCleanup(reply, written, 404, "workspace_not_found");
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 rejectAndCleanup(reply, written, 400, "workspace_required");
2999
+ return rejectAndCleanup2(reply, written, 400, "workspace_required");
2835
3000
  }
2836
3001
  fileCount += 1;
2837
3002
  if (fileCount > MAX_FILES_PER_BATCH) {
2838
- return rejectAndCleanup(reply, written, 400, "too_many_files");
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 rm4(target2.absolutePath, { force: true });
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 stat5(target2.absolutePath);
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 rm4(target2.absolutePath, { force: true });
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 rejectAndCleanup(reply, written, 400, "workspace_required");
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 rejectAndCleanup(reply, written, 400, "too_many_files");
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,68 +3066,166 @@ function registerUploadsRoute(app, deps) {
2901
3066
  }
2902
3067
  try {
2903
3068
  await ensureSafeUploadDir(deps.uploadsDir, target.dir);
2904
- await pipeline(part.file, createWriteStream(target.absolutePath));
3069
+ await pipeline2(part.file, createWriteStream2(target.absolutePath));
2905
3070
  } catch (error) {
2906
- await rm4(target.absolutePath, { force: true });
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 rm4(target.absolutePath, { force: true });
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 stat5(target.absolutePath);
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 rm4(target.absolutePath, { force: true });
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" });
2928
3093
  }
2929
3094
  }
2930
- } catch (error) {
2931
- await cleanupWrittenFiles(written);
2932
- if (error.code === "FST_REQ_FILE_TOO_LARGE") {
2933
- return reply.status(413).send({ ok: false, error: "file_too_large" });
3095
+ } catch (error) {
3096
+ await cleanupWrittenFiles(written);
3097
+ if (error.code === "FST_REQ_FILE_TOO_LARGE") {
3098
+ return reply.status(413).send({ ok: false, error: "file_too_large" });
3099
+ }
3100
+ logger?.warn({ err: error }, "upload parse failed");
3101
+ return reply.status(400).send({ ok: false, error: "parse_failed" });
3102
+ }
3103
+ if (!workspaceId) {
3104
+ return rejectAndCleanup2(reply, written, 400, "workspace_required");
3105
+ }
3106
+ if (!workspaceValidated) {
3107
+ return rejectAndCleanup2(reply, written, 404, "workspace_not_found");
3108
+ }
3109
+ if (written.length === 0) {
3110
+ return rejectAndCleanup2(reply, written, 400, "no_files");
3111
+ }
3112
+ if (!await ensureWorkspaceStillActive(deps, workspaceId, reply, written)) {
3113
+ return;
3114
+ }
3115
+ void enforceBucketCap(deps.uploadsDir, workspaceId, UPLOAD_BUCKET_MAX_BYTES, logger).catch(
3116
+ (error) => logger?.warn({ err: error }, "bucket cap enforcement failed")
3117
+ );
3118
+ return reply.send({ ok: true, files: written });
3119
+ });
3120
+ }
3121
+ var init_uploads = __esm({
3122
+ "packages/server/src/routes/uploads.ts"() {
3123
+ "use strict";
3124
+ init_cleanup();
3125
+ init_constants();
3126
+ init_paths();
3127
+ }
3128
+ });
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());
2934
3222
  }
2935
- logger?.warn({ err: error }, "upload parse failed");
2936
- return reply.status(400).send({ ok: false, error: "parse_failed" });
2937
- }
2938
- if (!workspaceId) {
2939
- return rejectAndCleanup(reply, written, 400, "workspace_required");
2940
- }
2941
- if (!workspaceValidated) {
2942
- return rejectAndCleanup(reply, written, 404, "workspace_not_found");
2943
- }
2944
- if (written.length === 0) {
2945
- return rejectAndCleanup(reply, written, 400, "no_files");
2946
- }
2947
- if (!await ensureWorkspaceStillActive(deps, workspaceId, reply, written)) {
2948
- return;
2949
- }
2950
- void enforceBucketCap(deps.uploadsDir, workspaceId, UPLOAD_BUCKET_MAX_BYTES, logger).catch(
2951
- (error) => logger?.warn({ err: error }, "bucket cap enforcement failed")
2952
- );
2953
- return reply.send({ ok: true, files: written });
2954
- });
2955
- }
2956
- var init_uploads = __esm({
2957
- "packages/server/src/routes/uploads.ts"() {
2958
- "use strict";
2959
- init_cleanup();
2960
- init_constants();
2961
- init_paths();
3223
+ };
2962
3224
  }
2963
3225
  });
2964
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((resolve4) => {
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
- resolve4(() => {
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((resolve4, reject) => {
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
- resolve4({ stdout, stderr });
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 randomUUID3 } from "node:crypto";
4504
- import { mkdirSync as mkdirSync2 } from "node:fs";
4505
- import { dirname as dirname3, join as join2 } from "node:path";
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 = randomUUID3();
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 = join2(this.deps.manifestStore.getRoot(), input2.serverKind, managed.version);
4739
- const executablePath = input2.serverKind === "python" ? join2(
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" ? join2(installRoot, "bin", platform === "win32" ? "gopls.exe" : "gopls") : join2(installRoot, "bin", platform === "win32" ? "rust-analyzer.exe" : "rust-analyzer");
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 = join2(this.deps.manifestStore.getRoot(), serverKind, managed.version);
4772
- const executablePath = serverKind === "python" ? join2(
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" ? join2(installRoot, "bin", platform === "win32" ? "gopls.exe" : "gopls") : join2(installRoot, "bin", platform === "win32" ? "rust-analyzer.exe" : "rust-analyzer");
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
- mkdirSync2(dirname3(executablePath), { recursive: true });
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 = join2(input2.installRoot, "venv");
4848
- const binDir = join2(venvRoot, input2.platform === "win32" ? "Scripts" : "bin");
4849
- const pipPath = join2(binDir, input2.platform === "win32" ? "pip.exe" : "pip");
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: join2(input2.installRoot, "bin")
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 join3 } from "node:path";
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 = join3(packageJsonPath, "..");
5081
- const entryPath = join3(packageRoot, definition.bundled.entry);
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 mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
5134
- import { join as join4 } from "node:path";
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(readFileSync3(path14, "utf8"));
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
- mkdirSync3(join4(this.root, serverKind), { recursive: true });
5161
- writeFileSync2(path14, JSON.stringify(manifest, null, 2));
5434
+ mkdirSync4(join6(this.root, serverKind), { recursive: true });
5435
+ writeFileSync3(path14, JSON.stringify(manifest, null, 2));
5162
5436
  }
5163
5437
  pathFor(serverKind) {
5164
- return join4(this.root, serverKind, "manifest.json");
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 mkdirSync4 } from "node:fs";
5172
- import { join as join5 } from "node:path";
5445
+ import { mkdirSync as mkdirSync5 } from "node:fs";
5446
+ import { join as join7 } from "node:path";
5173
5447
  function resolveLspToolRoot(stateDir) {
5174
- const root = join5(stateDir, "lsp-tools");
5175
- mkdirSync4(root, { recursive: true });
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 mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
5186
- import { dirname as dirname4, join as join6 } from "node:path";
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 = readFileSync4(statePath, "utf8");
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
- mkdirSync5(dirname4(statePath), { recursive: true });
5287
- writeFileSync3(statePath, JSON.stringify(nextState, null, 2));
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
- mkdirSync5(binDir, { recursive: true });
5292
- const scriptPath = join6(binDir, providerId);
5293
- writeFileSync3(scriptPath, PROVIDER_COMMAND_SCRIPTS[providerId], "utf8");
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
- mkdirSync5(dirname4(path14), { recursive: true });
5301
- writeFileSync3(path14, `${line}
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 randomUUID4 } from "node:crypto";
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: randomUUID4(),
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 = randomUUID4();
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 isRecord(value) {
6421
+ function isRecord2(value) {
6183
6422
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
6184
6423
  }
6185
6424
  function isSession(value) {
6186
- if (!isRecord(value)) {
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 (isRecord(value) && value.version === 1 && isRecord(value.sessions)) {
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 (isRecord(value)) {
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
- var NOOP_SESSION_LOGGER, SessionManager, ActiveSession;
6761
+ function readTerminalEscapeSequence(text, start) {
6762
+ const match = text.slice(start).match(TERMINAL_ESCAPE_SEQUENCE_PATTERN);
6763
+ return match?.[0] ?? null;
6764
+ }
6765
+ function classifyRecentInputEcho(bytes) {
6766
+ const text = bytes.toString("utf8");
6767
+ let opaque = false;
6768
+ let remainingVisibleText = "";
6769
+ for (let index = 0; index < text.length; index += 1) {
6770
+ const char = text[index];
6771
+ if (char === "\x1B") {
6772
+ const escape = readTerminalEscapeSequence(text, index);
6773
+ if (escape) {
6774
+ opaque = true;
6775
+ index += escape.length - 1;
6776
+ continue;
6777
+ }
6778
+ }
6779
+ if (char === "\r" || char === "\n" || char === " ") {
6780
+ opaque = true;
6781
+ continue;
6782
+ }
6783
+ if (char === "\x7F" || char === "\b") {
6784
+ opaque = true;
6785
+ continue;
6786
+ }
6787
+ if (char < " ") {
6788
+ opaque = true;
6789
+ continue;
6790
+ }
6791
+ remainingVisibleText += char;
6792
+ }
6793
+ return { opaque, remainingVisibleText };
6794
+ }
6795
+ function extractVisibleTerminalText(bytes) {
6796
+ const text = bytes.toString("utf8");
6797
+ let visibleText = "";
6798
+ for (let index = 0; index < text.length; index += 1) {
6799
+ const char = text[index];
6800
+ if (char === "\x1B") {
6801
+ const escape = readTerminalEscapeSequence(text, index);
6802
+ if (escape) {
6803
+ index += escape.length - 1;
6804
+ continue;
6805
+ }
6806
+ continue;
6807
+ }
6808
+ if (char === "\x7F" || char === "\b") {
6809
+ visibleText = visibleText.slice(0, -1);
6810
+ continue;
6811
+ }
6812
+ if (char === "\r" || char === "\n") {
6813
+ visibleText += "\n";
6814
+ continue;
6815
+ }
6816
+ if (char === " ") {
6817
+ visibleText += " ";
6818
+ continue;
6819
+ }
6820
+ if (char < " ") {
6821
+ continue;
6822
+ }
6823
+ visibleText += char;
6824
+ }
6825
+ return visibleText;
6826
+ }
6827
+ function commonPrefixLength(left, right) {
6828
+ const maxLength = Math.min(left.length, right.length);
6829
+ let index = 0;
6830
+ while (index < maxLength && left[index] === right[index]) {
6831
+ index += 1;
6832
+ }
6833
+ return index;
6834
+ }
6835
+ function chunkHasLineRepaintControl(text) {
6836
+ return text.includes("\r") || /\x1b\[[0-9;?]*K/.test(text) || /\x1b\[[0-9;?]*[ABCDGHF]/.test(text);
6837
+ }
6838
+ function visibleOutputHasMultipleLines(visibleOutput) {
6839
+ return visibleOutput.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).length > 1;
6840
+ }
6841
+ var NOOP_SESSION_LOGGER, RECENT_INPUT_ECHO_WINDOW_MS, RECENT_INPUT_ECHO_MAX_EVENTS, RECENT_INPUT_ECHO_MAX_BYTES, RECENT_OPAQUE_INPUT_ECHO_WINDOW_MS, RESUME_OUTPUT_AGGREGATION_WINDOW_MS, RESUME_OUTPUT_AGGREGATION_MAX_BYTES, TERMINAL_ESCAPE_SEQUENCE_PATTERN, SessionManager, ActiveSession;
6523
6842
  var init_manager3 = __esm({
6524
6843
  "packages/server/src/session/manager.ts"() {
6525
6844
  "use strict";
@@ -6532,6 +6851,13 @@ var init_manager3 = __esm({
6532
6851
  warn: () => {
6533
6852
  }
6534
6853
  };
6854
+ RECENT_INPUT_ECHO_WINDOW_MS = 3e3;
6855
+ RECENT_INPUT_ECHO_MAX_EVENTS = 12;
6856
+ RECENT_INPUT_ECHO_MAX_BYTES = 8192;
6857
+ RECENT_OPAQUE_INPUT_ECHO_WINDOW_MS = 200;
6858
+ RESUME_OUTPUT_AGGREGATION_WINDOW_MS = 75;
6859
+ RESUME_OUTPUT_AGGREGATION_MAX_BYTES = 4096;
6860
+ TERMINAL_ESCAPE_SEQUENCE_PATTERN = /^\x1b(?:\[[0-9;?<>]*[ -/]*[@-~]|\][^\x07\x1b]*(?:\x07|\x1b\\)|P[\s\S]*?\x1b\\|[@-_])/;
6535
6861
  SessionManager = class {
6536
6862
  constructor(deps) {
6537
6863
  this.deps = deps;
@@ -6708,6 +7034,7 @@ var init_manager3 = __esm({
6708
7034
  this.applyTerminalInputActivity(session, activity, text, { armTurnCompletion: true });
6709
7035
  }
6710
7036
  applyTerminalInputActivity(session, activity, text, options) {
7037
+ const completedSynchronously = !options.armTurnCompletion && session.state === "idle" && !session.awaitingTurnCompletion;
6711
7038
  if (activity === "control" || activity === "typing") {
6712
7039
  return;
6713
7040
  }
@@ -6716,16 +7043,10 @@ var init_manager3 = __esm({
6716
7043
  session.awaitingTurnCompletion = true;
6717
7044
  session.sawOutputSinceTurnStart = false;
6718
7045
  }
6719
- const prev2 = session.state;
6720
- if (session.state !== "running") {
6721
- session.state = "running";
6722
- session.lastActiveAt = Date.now();
6723
- this.deps.db.update(session.id, {
6724
- state: "running",
6725
- lastActiveAt: session.lastActiveAt
6726
- });
6727
- this.emitStateChanged(session, prev2, "running");
7046
+ if (completedSynchronously) {
7047
+ return;
6728
7048
  }
7049
+ this.transitionSessionToRunning(session);
6729
7050
  return;
6730
7051
  }
6731
7052
  if (activity !== "submit") return;
@@ -6738,17 +7059,11 @@ var init_manager3 = __esm({
6738
7059
  session.sawOutputSinceTurnStart = false;
6739
7060
  }
6740
7061
  const titleChanged = this.maybeAssignTitle(session, submittedText);
6741
- const prev = session.state;
6742
7062
  const shouldResume = session.state === "idle" || session.state === "starting";
6743
- if (shouldResume) {
6744
- session.state = "running";
6745
- session.lastActiveAt = Date.now();
6746
- this.deps.db.update(session.id, {
6747
- state: "running",
6748
- lastActiveAt: session.lastActiveAt
6749
- });
6750
- this.emitStateChanged(session, prev, "running");
7063
+ if (shouldResume && !completedSynchronously) {
7064
+ this.transitionSessionToRunning(session);
6751
7065
  } else if (titleChanged) {
7066
+ const prev = session.state;
6752
7067
  this.emitStateChanged(session, prev, session.state);
6753
7068
  }
6754
7069
  }
@@ -6762,9 +7077,11 @@ var init_manager3 = __esm({
6762
7077
  }
6763
7078
  const text = activity === "submit" || activity === "internal_submit" ? submittedText ?? bytes.toString("utf-8") : void 0;
6764
7079
  const rollbackArm = this.armTurnCompletionBeforeWrite(session, activity);
7080
+ const rollbackRecentInput = this.recordRecentInputBeforeWrite(session, bytes, activity);
6765
7081
  try {
6766
7082
  this.deps.terminalMgr.write(session.terminalId, bytes);
6767
7083
  } catch (error) {
7084
+ rollbackRecentInput?.();
6768
7085
  rollbackArm?.();
6769
7086
  throw error;
6770
7087
  }
@@ -6826,6 +7143,19 @@ var init_manager3 = __esm({
6826
7143
  this.deps.db.update(session.id, { title });
6827
7144
  return true;
6828
7145
  }
7146
+ transitionSessionToRunning(session) {
7147
+ if (session.state === "running") {
7148
+ return;
7149
+ }
7150
+ const prev = session.state;
7151
+ session.state = "running";
7152
+ session.lastActiveAt = Date.now();
7153
+ this.deps.db.update(session.id, {
7154
+ state: "running",
7155
+ lastActiveAt: session.lastActiveAt
7156
+ });
7157
+ this.emitStateChanged(session, prev, "running");
7158
+ }
6829
7159
  /**
6830
7160
  * Handle terminal exit event
6831
7161
  */
@@ -6887,6 +7217,197 @@ var init_manager3 = __esm({
6887
7217
  session.sawOutputSinceTurnStart = previousSawOutputSinceTurnStart;
6888
7218
  };
6889
7219
  }
7220
+ recordRecentInputBeforeWrite(session, bytes, activity) {
7221
+ if (activity === "system") {
7222
+ return null;
7223
+ }
7224
+ const { opaque, remainingVisibleText } = classifyRecentInputEcho(bytes);
7225
+ if (!opaque && remainingVisibleText.length === 0) {
7226
+ return null;
7227
+ }
7228
+ const recentInput = {
7229
+ at: Date.now(),
7230
+ activity,
7231
+ byteLength: bytes.byteLength,
7232
+ opaque,
7233
+ remainingVisibleText
7234
+ };
7235
+ session.recentInputEchoes.push(recentInput);
7236
+ this.pruneRecentInputEchoes(session, recentInput.at);
7237
+ return () => {
7238
+ const index = session.recentInputEchoes.indexOf(recentInput);
7239
+ if (index !== -1) {
7240
+ session.recentInputEchoes.splice(index, 1);
7241
+ }
7242
+ };
7243
+ }
7244
+ pruneRecentInputEchoes(session, now = Date.now()) {
7245
+ session.recentInputEchoes = session.recentInputEchoes.filter(
7246
+ (entry) => now - entry.at <= RECENT_INPUT_ECHO_WINDOW_MS && (entry.opaque || entry.remainingVisibleText.length > 0)
7247
+ );
7248
+ let totalBytes = session.recentInputEchoes.reduce((sum, entry) => sum + entry.byteLength, 0);
7249
+ while (session.recentInputEchoes.length > RECENT_INPUT_ECHO_MAX_EVENTS || totalBytes > RECENT_INPUT_ECHO_MAX_BYTES) {
7250
+ const removed = session.recentInputEchoes.shift();
7251
+ if (!removed) {
7252
+ break;
7253
+ }
7254
+ totalBytes -= removed.byteLength;
7255
+ }
7256
+ }
7257
+ clearPendingResumeAggregation(session) {
7258
+ if (session.pendingResumeAggregation?.timer) {
7259
+ clearTimeout(session.pendingResumeAggregation.timer);
7260
+ }
7261
+ session.pendingResumeAggregation = null;
7262
+ }
7263
+ consumeRecentLiteralEcho(session, visibleOutput) {
7264
+ let offset = 0;
7265
+ let matchedLiteralEcho = false;
7266
+ for (const entry of session.recentInputEchoes) {
7267
+ if (offset >= visibleOutput.length) {
7268
+ break;
7269
+ }
7270
+ if (entry.remainingVisibleText.length === 0) {
7271
+ continue;
7272
+ }
7273
+ const currentVisibleText = entry.remainingVisibleText;
7274
+ const matchLength = commonPrefixLength(visibleOutput.slice(offset), currentVisibleText);
7275
+ if (matchLength === 0) {
7276
+ break;
7277
+ }
7278
+ entry.remainingVisibleText = currentVisibleText.slice(matchLength);
7279
+ offset += matchLength;
7280
+ matchedLiteralEcho = true;
7281
+ if (matchLength < currentVisibleText.length) {
7282
+ break;
7283
+ }
7284
+ }
7285
+ return {
7286
+ matchedLiteralEcho,
7287
+ leftoverVisibleOutput: visibleOutput.slice(offset)
7288
+ };
7289
+ }
7290
+ hasRecentNonSubmitInput(session, now) {
7291
+ return session.recentInputEchoes.some(
7292
+ (entry) => entry.activity !== "submit" && entry.activity !== "internal_submit" && now - entry.at <= RECENT_OPAQUE_INPUT_ECHO_WINDOW_MS
7293
+ );
7294
+ }
7295
+ hasRecentOpaqueNonSubmitInput(session, now) {
7296
+ return session.recentInputEchoes.some(
7297
+ (entry) => entry.opaque && entry.activity !== "submit" && entry.activity !== "internal_submit" && now - entry.at <= RECENT_OPAQUE_INPUT_ECHO_WINDOW_MS
7298
+ );
7299
+ }
7300
+ hasRecentControlInput(session, now) {
7301
+ return session.recentInputEchoes.some(
7302
+ (entry) => entry.activity === "control" && now - entry.at <= RECENT_OPAQUE_INPUT_ECHO_WINDOW_MS
7303
+ );
7304
+ }
7305
+ getRecentVisibleInputText(session) {
7306
+ return session.recentInputEchoes.filter((entry) => entry.activity !== "submit" && entry.activity !== "internal_submit").map((entry) => entry.remainingVisibleText).join("");
7307
+ }
7308
+ isLikelyPureInputRepaint(session, chunk, visibleOutput, now) {
7309
+ if (!this.hasRecentNonSubmitInput(session, now)) {
7310
+ return false;
7311
+ }
7312
+ if (!visibleOutput.trim()) {
7313
+ return true;
7314
+ }
7315
+ const chunkText = chunk.toString("utf8");
7316
+ if (chunkText.includes("\n")) {
7317
+ return false;
7318
+ }
7319
+ if (visibleOutputHasMultipleLines(visibleOutput)) {
7320
+ return false;
7321
+ }
7322
+ if (!chunkHasLineRepaintControl(chunkText)) {
7323
+ return false;
7324
+ }
7325
+ if (this.hasRecentOpaqueNonSubmitInput(session, now)) {
7326
+ return true;
7327
+ }
7328
+ const recentVisibleInputText = this.getRecentVisibleInputText(session);
7329
+ const trimmedVisibleOutput = visibleOutput.trim();
7330
+ if (!recentVisibleInputText || !trimmedVisibleOutput) {
7331
+ return false;
7332
+ }
7333
+ return recentVisibleInputText === trimmedVisibleOutput || recentVisibleInputText.startsWith(trimmedVisibleOutput) || trimmedVisibleOutput.endsWith(recentVisibleInputText);
7334
+ }
7335
+ assessTerminalOutput(session, chunk) {
7336
+ const now = Date.now();
7337
+ this.pruneRecentInputEchoes(session, now);
7338
+ if (session.recentInputEchoes.length === 0) {
7339
+ const hasVisibleText = extractVisibleTerminalText(chunk).trim().length > 0;
7340
+ return {
7341
+ shouldResumeRunning: hasVisibleText,
7342
+ countsAsTurnOutput: hasVisibleText,
7343
+ shouldAggregateForResume: false
7344
+ };
7345
+ }
7346
+ const visibleOutput = extractVisibleTerminalText(chunk);
7347
+ const { matchedLiteralEcho, leftoverVisibleOutput } = this.consumeRecentLiteralEcho(
7348
+ session,
7349
+ visibleOutput
7350
+ );
7351
+ const hasRecentNonSubmitInput = this.hasRecentNonSubmitInput(session, now);
7352
+ const hasRecentControlInput = this.hasRecentControlInput(session, now);
7353
+ this.pruneRecentInputEchoes(session, now);
7354
+ const trimmedLeftoverVisibleOutput = leftoverVisibleOutput.trim();
7355
+ const trimmedVisibleOutput = visibleOutput.trim();
7356
+ const suppressImmediateResumeAfterControl = hasRecentControlInput && !matchedLiteralEcho;
7357
+ const chunkText = chunk.toString("utf8");
7358
+ const shouldAggregateForResume = session.state === "idle" && hasRecentNonSubmitInput && !matchedLiteralEcho && trimmedVisibleOutput.length > 0 && chunkHasLineRepaintControl(chunkText) && !visibleOutputHasMultipleLines(visibleOutput);
7359
+ const shouldResumeRunning = suppressImmediateResumeAfterControl ? false : trimmedLeftoverVisibleOutput.length > 0 ? !this.isLikelyPureInputRepaint(session, chunk, leftoverVisibleOutput, now) : !matchedLiteralEcho && trimmedVisibleOutput.length > 0 && !this.isLikelyPureInputRepaint(session, chunk, visibleOutput, now);
7360
+ const countsAsTurnOutput = !suppressImmediateResumeAfterControl && (trimmedLeftoverVisibleOutput.length > 0 ? !this.isLikelyPureInputRepaint(session, chunk, leftoverVisibleOutput, now) : !matchedLiteralEcho && trimmedVisibleOutput.length > 0 && !this.isLikelyPureInputRepaint(session, chunk, visibleOutput, now));
7361
+ return {
7362
+ shouldResumeRunning,
7363
+ countsAsTurnOutput,
7364
+ shouldAggregateForResume
7365
+ };
7366
+ }
7367
+ flushPendingResumeAggregation(session) {
7368
+ const pending = session.pendingResumeAggregation;
7369
+ if (!pending) {
7370
+ return;
7371
+ }
7372
+ this.clearPendingResumeAggregation(session);
7373
+ if (session.state !== "idle") {
7374
+ return;
7375
+ }
7376
+ const combinedChunk = Buffer.concat(pending.chunks, pending.byteLength);
7377
+ const outputAssessment = this.assessTerminalOutput(session, combinedChunk);
7378
+ if (outputAssessment.countsAsTurnOutput) {
7379
+ session.sawOutputSinceTurnStart = true;
7380
+ }
7381
+ }
7382
+ schedulePendingResumeAggregation(session, chunk) {
7383
+ const pending = session.pendingResumeAggregation;
7384
+ if (!pending) {
7385
+ const nextPending = {
7386
+ startedAt: Date.now(),
7387
+ chunks: [chunk],
7388
+ byteLength: chunk.byteLength,
7389
+ timer: null
7390
+ };
7391
+ nextPending.timer = setTimeout(() => {
7392
+ const activeSession = this.sessions.get(session.id);
7393
+ if (!activeSession) {
7394
+ return;
7395
+ }
7396
+ this.flushPendingResumeAggregation(activeSession);
7397
+ }, RESUME_OUTPUT_AGGREGATION_WINDOW_MS);
7398
+ session.pendingResumeAggregation = nextPending;
7399
+ return;
7400
+ }
7401
+ pending.chunks.push(chunk);
7402
+ pending.byteLength += chunk.byteLength;
7403
+ const combinedChunk = Buffer.concat(pending.chunks, pending.byteLength);
7404
+ const visibleOutput = extractVisibleTerminalText(combinedChunk);
7405
+ const combinedText = combinedChunk.toString("utf8");
7406
+ const shouldFlushNow = pending.byteLength >= RESUME_OUTPUT_AGGREGATION_MAX_BYTES || combinedText.includes("\n") || visibleOutputHasMultipleLines(visibleOutput);
7407
+ if (shouldFlushNow) {
7408
+ this.flushPendingResumeAggregation(session);
7409
+ }
7410
+ }
6890
7411
  flushPendingPtyIdle(session) {
6891
7412
  const ptyState = this.comparators.get(session.id)?.snapshot().ptyState;
6892
7413
  if (ptyState !== "idle") {
@@ -6895,6 +7416,7 @@ var init_manager3 = __esm({
6895
7416
  this.transitionSessionToIdle(session);
6896
7417
  }
6897
7418
  transitionSessionToIdle(activeSession) {
7419
+ this.clearPendingResumeAggregation(activeSession);
6898
7420
  const prev = activeSession.state;
6899
7421
  if (prev !== "running" && prev !== "starting") {
6900
7422
  return;
@@ -6957,7 +7479,24 @@ var init_manager3 = __esm({
6957
7479
  return;
6958
7480
  }
6959
7481
  const activeSession = this.sessions.get(session.id);
6960
- if (activeSession?.awaitingTurnCompletion) {
7482
+ if (!activeSession) {
7483
+ return;
7484
+ }
7485
+ if (activeSession.pendingResumeAggregation && activeSession.state !== "idle") {
7486
+ this.clearPendingResumeAggregation(activeSession);
7487
+ }
7488
+ if (activeSession.pendingResumeAggregation) {
7489
+ this.schedulePendingResumeAggregation(activeSession, event.chunk);
7490
+ detector.feed(event.chunk);
7491
+ return;
7492
+ }
7493
+ const outputAssessment = this.assessTerminalOutput(activeSession, event.chunk);
7494
+ if (outputAssessment.shouldAggregateForResume) {
7495
+ this.schedulePendingResumeAggregation(activeSession, event.chunk);
7496
+ detector.feed(event.chunk);
7497
+ return;
7498
+ }
7499
+ if (outputAssessment.countsAsTurnOutput) {
6961
7500
  activeSession.sawOutputSinceTurnStart = true;
6962
7501
  }
6963
7502
  detector.feed(event.chunk);
@@ -6974,6 +7513,7 @@ var init_manager3 = __esm({
6974
7513
  this.comparators.delete(sessionId);
6975
7514
  }
6976
7515
  finishSession(session, exitCode) {
7516
+ this.clearPendingResumeAggregation(session);
6977
7517
  const prev = session.state;
6978
7518
  session.state = "ended";
6979
7519
  session.endedAt = Date.now();
@@ -7005,6 +7545,8 @@ var init_manager3 = __esm({
7005
7545
  latestSubmittedUserInput;
7006
7546
  awaitingTurnCompletion = false;
7007
7547
  sawOutputSinceTurnStart = false;
7548
+ recentInputEchoes = [];
7549
+ pendingResumeAggregation = null;
7008
7550
  constructor(data) {
7009
7551
  this.id = data.id;
7010
7552
  this.workspaceId = data.workspaceId;
@@ -7047,17 +7589,17 @@ var init_manager3 = __esm({
7047
7589
  });
7048
7590
 
7049
7591
  // packages/server/src/storage/repositories/auth-login-block-repo.ts
7050
- function isRecord2(value) {
7592
+ function isRecord3(value) {
7051
7593
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
7052
7594
  }
7053
7595
  function isAuthLoginBlockRecord(value) {
7054
- if (!isRecord2(value)) {
7596
+ if (!isRecord3(value)) {
7055
7597
  return false;
7056
7598
  }
7057
7599
  return typeof value.ip === "string" && typeof value.failedCount === "number" && typeof value.firstFailedAt === "number" && typeof value.lastFailedAt === "number" && (typeof value.blockedUntil === "number" || value.blockedUntil === null);
7058
7600
  }
7059
7601
  function normalizeFailures(value) {
7060
- if (!isRecord2(value)) {
7602
+ if (!isRecord3(value)) {
7061
7603
  return {};
7062
7604
  }
7063
7605
  const normalized = {};
@@ -7071,9 +7613,9 @@ function normalizeFailures(value) {
7071
7613
  return normalized;
7072
7614
  }
7073
7615
  function normalizeState(value) {
7074
- if (isRecord2(value) && value.version === 1) {
7616
+ if (isRecord3(value) && value.version === 1) {
7075
7617
  const blocks = {};
7076
- if (isRecord2(value.blocks)) {
7618
+ if (isRecord3(value.blocks)) {
7077
7619
  for (const entry of Object.values(value.blocks)) {
7078
7620
  if (isAuthLoginBlockRecord(entry)) {
7079
7621
  blocks[entry.ip] = entry;
@@ -7086,7 +7628,7 @@ function normalizeState(value) {
7086
7628
  failures: normalizeFailures(value.failures)
7087
7629
  };
7088
7630
  }
7089
- if (isRecord2(value)) {
7631
+ if (isRecord3(value)) {
7090
7632
  const blocks = {};
7091
7633
  for (const [ip, entry] of Object.entries(value)) {
7092
7634
  if (isAuthLoginBlockRecord(entry)) {
@@ -7192,17 +7734,17 @@ var init_auth_login_block_repo = __esm({
7192
7734
  });
7193
7735
 
7194
7736
  // packages/server/src/storage/repositories/auth-session-repo.ts
7195
- function isRecord3(value) {
7737
+ function isRecord4(value) {
7196
7738
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
7197
7739
  }
7198
7740
  function isAuthSession(value) {
7199
- if (!isRecord3(value)) {
7741
+ if (!isRecord4(value)) {
7200
7742
  return false;
7201
7743
  }
7202
7744
  return typeof value.token === "string" && typeof value.createdAt === "number" && typeof value.lastSeenAt === "number";
7203
7745
  }
7204
7746
  function normalizeSessionFile2(value) {
7205
- if (isRecord3(value) && value.version === 1 && isRecord3(value.sessions)) {
7747
+ if (isRecord4(value) && value.version === 1 && isRecord4(value.sessions)) {
7206
7748
  const normalized = {};
7207
7749
  for (const entry of Object.values(value.sessions)) {
7208
7750
  if (isAuthSession(entry)) {
@@ -7220,7 +7762,7 @@ function normalizeSessionFile2(value) {
7220
7762
  }
7221
7763
  return normalized;
7222
7764
  }
7223
- if (isRecord3(value)) {
7765
+ if (isRecord4(value)) {
7224
7766
  const normalized = {};
7225
7767
  for (const [token, entry] of Object.entries(value)) {
7226
7768
  if (isAuthSession(entry)) {
@@ -7295,14 +7837,14 @@ var init_auth_session_repo = __esm({
7295
7837
  });
7296
7838
 
7297
7839
  // packages/server/src/storage/repositories/provider-config-repo.ts
7298
- function isRecord4(value) {
7840
+ function isRecord5(value) {
7299
7841
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
7300
7842
  }
7301
7843
  function normalizeProviderConfigFile(value) {
7302
- if (isRecord4(value) && value.version === 1 && isRecord4(value.providers)) {
7844
+ if (isRecord5(value) && value.version === 1 && isRecord5(value.providers)) {
7303
7845
  return value.providers;
7304
7846
  }
7305
- if (isRecord4(value)) {
7847
+ if (isRecord5(value)) {
7306
7848
  return value;
7307
7849
  }
7308
7850
  return {};
@@ -7376,14 +7918,14 @@ var init_provider_config_repo = __esm({
7376
7918
  });
7377
7919
 
7378
7920
  // packages/server/src/storage/repositories/settings-repo.ts
7379
- function isRecord5(value) {
7921
+ function isRecord6(value) {
7380
7922
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
7381
7923
  }
7382
7924
  function normalizeSettingsFile(value) {
7383
- if (isRecord5(value) && value.version === 1 && isRecord5(value.settings)) {
7925
+ if (isRecord6(value) && value.version === 1 && isRecord6(value.settings)) {
7384
7926
  return { ...value.settings };
7385
7927
  }
7386
- if (isRecord5(value)) {
7928
+ if (isRecord6(value)) {
7387
7929
  return { ...value };
7388
7930
  }
7389
7931
  return {};
@@ -7534,11 +8076,11 @@ var init_supervisor_repo = __esm({
7534
8076
  });
7535
8077
 
7536
8078
  // packages/server/src/storage/repositories/terminal-repo.ts
7537
- function isRecord6(value) {
8079
+ function isRecord7(value) {
7538
8080
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
7539
8081
  }
7540
8082
  function isTerminal(value) {
7541
- if (!isRecord6(value)) {
8083
+ if (!isRecord7(value)) {
7542
8084
  return false;
7543
8085
  }
7544
8086
  return typeof value.id === "string" && typeof value.workspaceId === "string" && (value.kind === "agent" || value.kind === "shell") && typeof value.cwd === "string" && Array.isArray(value.argv) && typeof value.cols === "number" && typeof value.rows === "number" && typeof value.alive === "boolean" && typeof value.createdAt === "number";
@@ -7550,7 +8092,7 @@ function normalizeTerminal(value) {
7550
8092
  };
7551
8093
  }
7552
8094
  function normalizeTerminalFile(value) {
7553
- if (isRecord6(value) && value.version === 1 && isRecord6(value.terminals)) {
8095
+ if (isRecord7(value) && value.version === 1 && isRecord7(value.terminals)) {
7554
8096
  const normalized = {};
7555
8097
  for (const entry of Object.values(value.terminals)) {
7556
8098
  if (isTerminal(entry)) {
@@ -7568,7 +8110,7 @@ function normalizeTerminalFile(value) {
7568
8110
  }
7569
8111
  return normalized;
7570
8112
  }
7571
- if (isRecord6(value)) {
8113
+ if (isRecord7(value)) {
7572
8114
  const normalized = {};
7573
8115
  for (const [terminalId, entry] of Object.entries(value)) {
7574
8116
  if (isTerminal(entry)) {
@@ -7734,12 +8276,12 @@ var init_terminal_repo = __esm({
7734
8276
  });
7735
8277
 
7736
8278
  // packages/server/src/storage/repositories/update-state-repo.ts
7737
- function isRecord7(value) {
8279
+ function isRecord8(value) {
7738
8280
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
7739
8281
  }
7740
8282
  function normalizeUpdateState(value, currentVersion) {
7741
8283
  const defaults = createDefaultUpdateState(currentVersion);
7742
- if (!isRecord7(value)) {
8284
+ if (!isRecord8(value)) {
7743
8285
  return defaults;
7744
8286
  }
7745
8287
  return {
@@ -7802,17 +8344,17 @@ var init_update_state_repo = __esm({
7802
8344
  });
7803
8345
 
7804
8346
  // packages/server/src/storage/repositories/workspace-repo.ts
7805
- function isRecord8(value) {
8347
+ function isRecord9(value) {
7806
8348
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
7807
8349
  }
7808
8350
  function isWorkspace(value) {
7809
- if (!isRecord8(value)) {
8351
+ if (!isRecord9(value)) {
7810
8352
  return false;
7811
8353
  }
7812
- return typeof value.id === "string" && typeof value.path === "string" && (value.targetRuntime === "native" || value.targetRuntime === "wsl") && typeof value.openedAt === "number" && typeof value.lastActiveAt === "number" && isRecord8(value.uiState);
8354
+ return typeof value.id === "string" && typeof value.path === "string" && (value.targetRuntime === "native" || value.targetRuntime === "wsl") && typeof value.openedAt === "number" && typeof value.lastActiveAt === "number" && isRecord9(value.uiState);
7813
8355
  }
7814
8356
  function normalizeWorkspaceFile(value) {
7815
- if (isRecord8(value) && value.version === 1 && isRecord8(value.workspaces)) {
8357
+ if (isRecord9(value) && value.version === 1 && isRecord9(value.workspaces)) {
7816
8358
  const normalized = {};
7817
8359
  for (const entry of Object.values(value.workspaces)) {
7818
8360
  if (isWorkspace(entry)) {
@@ -7830,7 +8372,7 @@ function normalizeWorkspaceFile(value) {
7830
8372
  }
7831
8373
  return normalized;
7832
8374
  }
7833
- if (isRecord8(value)) {
8375
+ if (isRecord9(value)) {
7834
8376
  const normalized = {};
7835
8377
  for (const [workspaceId, entry] of Object.entries(value)) {
7836
8378
  if (isWorkspace(entry)) {
@@ -8075,13 +8617,13 @@ function ensureNodePtySpawnHelperExecutable(deps = {}) {
8075
8617
  return;
8076
8618
  }
8077
8619
  const arch = deps.arch ?? process.arch;
8078
- const resolve4 = deps.resolve ?? ((id) => require4.resolve(id));
8620
+ const resolve6 = deps.resolve ?? ((id) => require4.resolve(id));
8079
8621
  const fileExists = deps.existsSync ?? existsSync6;
8080
- const stat10 = deps.statSync ?? statSync;
8622
+ const stat11 = deps.statSync ?? statSync;
8081
8623
  const chmod = deps.chmodSync ?? chmodSync2;
8082
8624
  let packageJsonPath;
8083
8625
  try {
8084
- packageJsonPath = resolve4(NODE_PTY_PKG);
8626
+ packageJsonPath = resolve6(NODE_PTY_PKG);
8085
8627
  } catch {
8086
8628
  return;
8087
8629
  }
@@ -8095,7 +8637,7 @@ function ensureNodePtySpawnHelperExecutable(deps = {}) {
8095
8637
  if (!fileExists(helperPath)) {
8096
8638
  return;
8097
8639
  }
8098
- const currentMode = stat10(helperPath).mode;
8640
+ const currentMode = stat11(helperPath).mode;
8099
8641
  const executableMode = currentMode | 73;
8100
8642
  if (executableMode === currentMode) {
8101
8643
  return;
@@ -8146,7 +8688,7 @@ async function escalateKillWithPolling(pid, signal, options) {
8146
8688
  const startTime = Date.now();
8147
8689
  const deadline = startTime + timeoutMs;
8148
8690
  while (Date.now() < deadline) {
8149
- await new Promise((resolve4) => setTimeout(resolve4, pollIntervalMs));
8691
+ await new Promise((resolve6) => setTimeout(resolve6, pollIntervalMs));
8150
8692
  if (!isProcessAlive(pid)) {
8151
8693
  return true;
8152
8694
  }
@@ -8510,7 +9052,7 @@ async function runCommand(command, timeoutMs, options = {}) {
8510
9052
  if (options.signal?.aborted) {
8511
9053
  throw createSupervisorEvalAbortedError();
8512
9054
  }
8513
- return await new Promise((resolve4, reject) => {
9055
+ return await new Promise((resolve6, reject) => {
8514
9056
  const child = spawn3(command.argv[0], command.argv.slice(1), {
8515
9057
  cwd: command.cwd,
8516
9058
  detached: process.platform !== "win32",
@@ -8540,7 +9082,7 @@ async function runCommand(command, timeoutMs, options = {}) {
8540
9082
  }
8541
9083
  settled = true;
8542
9084
  cleanup();
8543
- resolve4(value);
9085
+ resolve6(value);
8544
9086
  };
8545
9087
  const terminate = (error) => {
8546
9088
  if (terminationError) {
@@ -9368,12 +9910,12 @@ var init_scheduler = __esm({
9368
9910
 
9369
9911
  // packages/server/src/supervisor/manager.ts
9370
9912
  function createDeferredCompletion() {
9371
- let resolve4 = () => {
9913
+ let resolve6 = () => {
9372
9914
  };
9373
9915
  const promise = new Promise((innerResolve) => {
9374
- resolve4 = innerResolve;
9916
+ resolve6 = innerResolve;
9375
9917
  });
9376
- return { promise, resolve: resolve4 };
9918
+ return { promise, resolve: resolve6 };
9377
9919
  }
9378
9920
  function generateSupervisorId() {
9379
9921
  return `sup_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
@@ -10728,10 +11270,10 @@ var init_manager4 = __esm({
10728
11270
  if (signal?.aborted) {
10729
11271
  throw { code: "supervisor_eval_aborted", message: "Supervisor evaluator aborted" };
10730
11272
  }
10731
- await new Promise((resolve4, reject) => {
11273
+ await new Promise((resolve6, reject) => {
10732
11274
  const timer = setTimeout(() => {
10733
11275
  signal?.removeEventListener("abort", onAbort);
10734
- resolve4();
11276
+ resolve6();
10735
11277
  }, delayMs);
10736
11278
  timer.unref?.();
10737
11279
  const onAbort = () => {
@@ -10762,31 +11304,31 @@ __export(target_store_exports, {
10762
11304
  saveTargetMemory: () => saveTargetMemory,
10763
11305
  saveTargetMeta: () => saveTargetMeta
10764
11306
  });
10765
- import { mkdir as mkdir4, mkdtemp as mkdtemp2, readdir as readdir2, readFile as readFile3, rename, rm as rm5, writeFile as writeFile4 } from "node:fs/promises";
10766
- import { dirname as dirname6, join as join7 } from "node:path";
11307
+ import { mkdir as mkdir4, mkdtemp as mkdtemp2, readdir as readdir2, readFile as readFile3, rename, rm as rm6, writeFile as writeFile4 } from "node:fs/promises";
11308
+ import { dirname as dirname6, join as join9 } from "node:path";
10767
11309
  function targetDir(workspacePath, targetId) {
10768
- return join7(workspacePath, ".coder-studio", "supervisor", "targets", targetId);
11310
+ return join9(workspacePath, ".coder-studio", "supervisor", "targets", targetId);
10769
11311
  }
10770
11312
  function metaPath(workspacePath, targetId) {
10771
- return join7(targetDir(workspacePath, targetId), "meta.json");
11313
+ return join9(targetDir(workspacePath, targetId), "meta.json");
10772
11314
  }
10773
11315
  function memoryPath(workspacePath, targetId) {
10774
- return join7(targetDir(workspacePath, targetId), "memory.json");
11316
+ return join9(targetDir(workspacePath, targetId), "memory.json");
10775
11317
  }
10776
11318
  function cyclesPath(workspacePath, targetId) {
10777
- return join7(targetDir(workspacePath, targetId), "cycles.jsonl");
11319
+ return join9(targetDir(workspacePath, targetId), "cycles.jsonl");
10778
11320
  }
10779
11321
  function targetsRoot(workspacePath) {
10780
- return join7(workspacePath, ".coder-studio", "supervisor", "targets");
11322
+ return join9(workspacePath, ".coder-studio", "supervisor", "targets");
10781
11323
  }
10782
11324
  function metaFilePath(dirPath) {
10783
- return join7(dirPath, "meta.json");
11325
+ return join9(dirPath, "meta.json");
10784
11326
  }
10785
11327
  function memoryFilePath(dirPath) {
10786
- return join7(dirPath, "memory.json");
11328
+ return join9(dirPath, "memory.json");
10787
11329
  }
10788
11330
  function cyclesFilePath(dirPath) {
10789
- return join7(dirPath, "cycles.jsonl");
11331
+ return join9(dirPath, "cycles.jsonl");
10790
11332
  }
10791
11333
  function hasCode2(error, code) {
10792
11334
  return Boolean(
@@ -10805,7 +11347,7 @@ function errorMessage(error, fallback) {
10805
11347
  }
10806
11348
  return fallback;
10807
11349
  }
10808
- function isRecord9(value) {
11350
+ function isRecord10(value) {
10809
11351
  return Boolean(value) && typeof value === "object";
10810
11352
  }
10811
11353
  function readNonEmptyString(value) {
@@ -10849,7 +11391,7 @@ function fallbackAcceptanceCriteria(title) {
10849
11391
  return [`${title} is complete`];
10850
11392
  }
10851
11393
  function normalizeItem(value, fallbackKind) {
10852
- if (!isRecord9(value)) {
11394
+ if (!isRecord10(value)) {
10853
11395
  return null;
10854
11396
  }
10855
11397
  const id = readNonEmptyString(value.id);
@@ -10880,7 +11422,7 @@ function normalizeLegacyPlanItems(plan) {
10880
11422
  }
10881
11423
  return plan.flatMap((value) => {
10882
11424
  const item = normalizeItem(
10883
- isRecord9(value) ? {
11425
+ isRecord10(value) ? {
10884
11426
  id: value.id,
10885
11427
  kind: "stage",
10886
11428
  title: value.title,
@@ -10904,7 +11446,7 @@ function resolveActiveItemId(items, candidate) {
10904
11446
  return items.find((item) => item.status === "in_progress")?.id ?? items.find((item) => item.status === "pending")?.id ?? items[0]?.id;
10905
11447
  }
10906
11448
  function normalizeTargetMemory(raw, targetId) {
10907
- if (!isRecord9(raw)) {
11449
+ if (!isRecord10(raw)) {
10908
11450
  return buildTargetMemory(targetId, 0);
10909
11451
  }
10910
11452
  const updatedAt = readTimestamp(raw.updatedAt, 0);
@@ -10931,7 +11473,7 @@ function normalizeTargetMemory(raw, targetId) {
10931
11473
  };
10932
11474
  }
10933
11475
  function normalizePersistedSupervisor(raw, fallback) {
10934
- if (!isRecord9(raw)) {
11476
+ if (!isRecord10(raw)) {
10935
11477
  return void 0;
10936
11478
  }
10937
11479
  const id = readNonEmptyString(raw.id) ?? fallback.targetId;
@@ -10967,7 +11509,7 @@ function normalizePersistedSupervisor(raw, fallback) {
10967
11509
  };
10968
11510
  }
10969
11511
  function normalizeTargetMeta(raw, fallbackTargetId) {
10970
- if (!isRecord9(raw)) {
11512
+ if (!isRecord10(raw)) {
10971
11513
  const targetId2 = fallbackTargetId ?? "";
10972
11514
  return {
10973
11515
  targetId: targetId2,
@@ -11088,7 +11630,7 @@ async function resetTargetFiles(workspacePath, input2) {
11088
11630
  const parentDir = dirname6(dir);
11089
11631
  const backupDir = `${dir}.backup-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
11090
11632
  await mkdir4(parentDir, { recursive: true });
11091
- const stagingDir = await mkdtemp2(join7(parentDir, `${input2.targetId}.reset-`));
11633
+ const stagingDir = await mkdtemp2(join9(parentDir, `${input2.targetId}.reset-`));
11092
11634
  let backupCreated = false;
11093
11635
  let promoted = false;
11094
11636
  let restored = false;
@@ -11124,17 +11666,17 @@ async function resetTargetFiles(workspacePath, input2) {
11124
11666
  }
11125
11667
  } catch (error) {
11126
11668
  if (restored || !backupCreated) {
11127
- await rm5(stagingDir, { recursive: true, force: true }).catch(() => {
11669
+ await rm6(stagingDir, { recursive: true, force: true }).catch(() => {
11128
11670
  });
11129
11671
  }
11130
11672
  throw error;
11131
11673
  }
11132
11674
  if (backupCreated) {
11133
- await rm5(backupDir, { recursive: true, force: true }).catch(() => {
11675
+ await rm6(backupDir, { recursive: true, force: true }).catch(() => {
11134
11676
  });
11135
11677
  }
11136
11678
  if (!promoted) {
11137
- await rm5(stagingDir, { recursive: true, force: true }).catch(() => {
11679
+ await rm6(stagingDir, { recursive: true, force: true }).catch(() => {
11138
11680
  });
11139
11681
  }
11140
11682
  }
@@ -11232,7 +11774,7 @@ async function cloneTargetFiles(workspacePath, input2) {
11232
11774
  return nextCycles.filter(countsTowardCycleTotal).length;
11233
11775
  }
11234
11776
  async function deleteTarget(workspacePath, targetId) {
11235
- await rm5(targetDir(workspacePath, targetId), { recursive: true, force: true });
11777
+ await rm6(targetDir(workspacePath, targetId), { recursive: true, force: true });
11236
11778
  }
11237
11779
  async function markTargetSuperseded(workspacePath, targetId, nextTargetId, updatedAt) {
11238
11780
  const meta = await readTargetMeta(workspacePath, targetId);
@@ -11545,8 +12087,8 @@ var init_terminal_snapshot_buffer = __esm({
11545
12087
  if (this.pendingWriteCount === 0) {
11546
12088
  return Promise.resolve();
11547
12089
  }
11548
- return new Promise((resolve4) => {
11549
- this.drainResolvers.push(resolve4);
12090
+ return new Promise((resolve6) => {
12091
+ this.drainResolvers.push(resolve6);
11550
12092
  });
11551
12093
  }
11552
12094
  resolveDrainIfIdle() {
@@ -11555,8 +12097,8 @@ var init_terminal_snapshot_buffer = __esm({
11555
12097
  }
11556
12098
  const resolvers = this.drainResolvers;
11557
12099
  this.drainResolvers = [];
11558
- for (const resolve4 of resolvers) {
11559
- resolve4();
12100
+ for (const resolve6 of resolvers) {
12101
+ resolve6();
11560
12102
  }
11561
12103
  }
11562
12104
  requireTerminal() {
@@ -11875,10 +12417,10 @@ var init_manager5 = __esm({
11875
12417
  }
11876
12418
  return existing.promise;
11877
12419
  }
11878
- let resolve4 = () => {
12420
+ let resolve6 = () => {
11879
12421
  };
11880
12422
  const promise = new Promise((innerResolve) => {
11881
- resolve4 = innerResolve;
12423
+ resolve6 = innerResolve;
11882
12424
  });
11883
12425
  let markKillCompleted = () => {
11884
12426
  };
@@ -11891,7 +12433,7 @@ var init_manager5 = __esm({
11891
12433
  markKillCompleted,
11892
12434
  finalized: false,
11893
12435
  promise,
11894
- resolve: resolve4
12436
+ resolve: resolve6
11895
12437
  });
11896
12438
  void terminal.pty.kill(signal).finally(() => {
11897
12439
  const waiter = this.explicitCloseWaiters.get(terminalId);
@@ -12367,10 +12909,10 @@ var init_update_service = __esm({
12367
12909
 
12368
12910
  // packages/server/src/workspace/validator.ts
12369
12911
  import { constants } from "fs";
12370
- import { access, stat as stat6 } from "fs/promises";
12912
+ import { access, stat as stat7 } from "fs/promises";
12371
12913
  async function validatePath(path14) {
12372
12914
  try {
12373
- const stats = await stat6(path14);
12915
+ const stats = await stat7(path14);
12374
12916
  if (!stats.isDirectory()) {
12375
12917
  return { valid: false, error: "Path is not a directory" };
12376
12918
  }
@@ -12405,12 +12947,12 @@ var init_validator = __esm({
12405
12947
  // packages/server/src/fs/gitignore.ts
12406
12948
  import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
12407
12949
  import ignore from "ignore";
12408
- import { join as join8, relative } from "path";
12950
+ import { join as join10, relative as relative2 } from "path";
12409
12951
  function normalizePath(path14) {
12410
12952
  return path14.replace(/\\/g, "/");
12411
12953
  }
12412
12954
  function relativeToRoot(rootPath, path14) {
12413
- return normalizePath(relative(rootPath, path14));
12955
+ return normalizePath(relative2(rootPath, path14));
12414
12956
  }
12415
12957
  function isDefaultTreeIgnored(name) {
12416
12958
  return name.startsWith(".") || name === "node_modules" || name === ".git";
@@ -12428,7 +12970,7 @@ function isIgnoredByGitignore(ig, path14) {
12428
12970
  return ig.ignores(path14) || ig.ignores(`${path14}/`);
12429
12971
  }
12430
12972
  function createGitignoreFilter(rootPath, dirPath) {
12431
- const gitignorePath = join8(rootPath, ".gitignore");
12973
+ const gitignorePath = join10(rootPath, ".gitignore");
12432
12974
  if (!existsSync7(gitignorePath)) {
12433
12975
  return (name) => !isDefaultTreeIgnored(name);
12434
12976
  }
@@ -12438,7 +12980,7 @@ function createGitignoreFilter(rootPath, dirPath) {
12438
12980
  if (isAlwaysTreeIgnored(name)) {
12439
12981
  return false;
12440
12982
  }
12441
- const relativePath = relativeToRoot(rootPath, join8(dirPath, name));
12983
+ const relativePath = relativeToRoot(rootPath, join10(dirPath, name));
12442
12984
  return !isIgnoredByGitignore(ig, relativePath);
12443
12985
  };
12444
12986
  }
@@ -13109,8 +13651,8 @@ var init_fencing = __esm({
13109
13651
  });
13110
13652
 
13111
13653
  // packages/server/src/commands/terminal.ts
13112
- import { stat as stat7 } from "node:fs/promises";
13113
- import { basename, isAbsolute } from "node:path";
13654
+ import { stat as stat8 } from "node:fs/promises";
13655
+ import { basename, isAbsolute as isAbsolute2 } from "node:path";
13114
13656
  import { z as z6 } from "zod";
13115
13657
  function decodeTerminalInput(args) {
13116
13658
  if ("bytes" in args) {
@@ -13226,7 +13768,7 @@ var init_terminal = __esm({
13226
13768
  }
13227
13769
  let cwd = workspace.path;
13228
13770
  if (args.cwdPath && args.cwdPath !== ".") {
13229
- if (isAbsolute(args.cwdPath)) {
13771
+ if (isAbsolute2(args.cwdPath)) {
13230
13772
  throw { code: "invalid_cwd_path", message: "cwdPath must be workspace-relative" };
13231
13773
  }
13232
13774
  let resolvedCwd;
@@ -13238,7 +13780,7 @@ var init_terminal = __esm({
13238
13780
  }
13239
13781
  throw error;
13240
13782
  }
13241
- const cwdStats = await stat7(resolvedCwd).catch(() => null);
13783
+ const cwdStats = await stat8(resolvedCwd).catch(() => null);
13242
13784
  if (!cwdStats) {
13243
13785
  throw { code: "cwd_not_found", message: `Directory not found: ${args.cwdPath}` };
13244
13786
  }
@@ -13967,7 +14509,7 @@ var init_hub = __esm({
13967
14509
  }
13968
14510
  }
13969
14511
  awaitBinaryPayload(clientId) {
13970
- return new Promise((resolve4, reject) => {
14512
+ return new Promise((resolve6, reject) => {
13971
14513
  const timer = setTimeout(() => {
13972
14514
  const waiters = this.pendingBinaryWaiters.get(clientId);
13973
14515
  if (!waiters) return;
@@ -13979,7 +14521,7 @@ var init_hub = __esm({
13979
14521
  }
13980
14522
  reject(new Error("Timeout waiting for terminal input binary payload"));
13981
14523
  }, BINARY_PAYLOAD_TIMEOUT_MS);
13982
- const waiter = { resolve: resolve4, reject, timer };
14524
+ const waiter = { resolve: resolve6, reject, timer };
13983
14525
  const queue = this.pendingBinaryWaiters.get(clientId);
13984
14526
  if (queue) {
13985
14527
  queue.push(waiter);
@@ -14286,7 +14828,7 @@ var init_hub = __esm({
14286
14828
  // packages/server/src/commands/workspace.ts
14287
14829
  import { readdir as readdir3, realpath as realpath3 } from "node:fs/promises";
14288
14830
  import { homedir as homedir2 } from "node:os";
14289
- import { isAbsolute as isAbsolute2, join as join9, resolve as resolve2 } from "node:path";
14831
+ import { isAbsolute as isAbsolute3, join as join11, resolve as resolve4 } from "node:path";
14290
14832
  import { z as z7 } from "zod";
14291
14833
  function resolveBrowsePath(path14) {
14292
14834
  const home = homedir2();
@@ -14294,9 +14836,9 @@ function resolveBrowsePath(path14) {
14294
14836
  return home;
14295
14837
  }
14296
14838
  if (path14.startsWith("~/")) {
14297
- return join9(home, path14.slice(2));
14839
+ return join11(home, path14.slice(2));
14298
14840
  }
14299
- return isAbsolute2(path14) ? path14 : resolve2(home, path14);
14841
+ return isAbsolute3(path14) ? path14 : resolve4(home, path14);
14300
14842
  }
14301
14843
  async function buildRootPaths(currentPath) {
14302
14844
  const roots = /* @__PURE__ */ new Set(["/"]);
@@ -14329,11 +14871,11 @@ var init_workspace = __esm({
14329
14871
  const entries = await readdir3(basePath, { withFileTypes: true });
14330
14872
  const directories = entries.filter((entry) => entry.isDirectory()).map((entry) => ({
14331
14873
  name: entry.name,
14332
- path: join9(basePath, entry.name)
14874
+ path: join11(basePath, entry.name)
14333
14875
  })).sort((a, b) => a.name.localeCompare(b.name));
14334
14876
  return {
14335
14877
  currentPath: basePath,
14336
- parentPath: basePath !== "/" ? join9(basePath, "..") : null,
14878
+ parentPath: basePath !== "/" ? join11(basePath, "..") : null,
14337
14879
  directories,
14338
14880
  rootPaths: await buildRootPaths(basePath)
14339
14881
  };
@@ -14797,8 +15339,8 @@ var init_pane_layout = __esm({
14797
15339
  // packages/server/src/commands/session.ts
14798
15340
  import { z as z12 } from "zod";
14799
15341
  function delay(ms) {
14800
- return new Promise((resolve4) => {
14801
- setTimeout(resolve4, ms);
15342
+ return new Promise((resolve6) => {
15343
+ setTimeout(resolve6, ms);
14802
15344
  });
14803
15345
  }
14804
15346
  function getProviderFromRegistry(providerId, registry) {
@@ -14952,8 +15494,8 @@ var init_session2 = __esm({
14952
15494
  // packages/server/src/fs/content-search.ts
14953
15495
  import { spawn as spawn5 } from "child_process";
14954
15496
  import { existsSync as existsSync8 } from "fs";
14955
- import { readdir as readdir4, readFile as readFile4, stat as stat8 } from "fs/promises";
14956
- import { basename as basename2, join as join10, relative as relative2 } from "path";
15497
+ import { readdir as readdir4, readFile as readFile4, stat as stat9 } from "fs/promises";
15498
+ import { basename as basename2, join as join12, relative as relative3 } from "path";
14957
15499
  import { createInterface } from "readline";
14958
15500
  async function searchFileContents(rootPath, options) {
14959
15501
  const query = options.query.trim();
@@ -14976,7 +15518,7 @@ async function searchFileContents(rootPath, options) {
14976
15518
  return finalizeResults(result, options.maxFiles, options.maxMatchesPerFile);
14977
15519
  }
14978
15520
  async function searchWithRipgrep(rootPath, query, maxFiles) {
14979
- const hasGitignore = existsSync8(join10(rootPath, ".gitignore"));
15521
+ const hasGitignore = existsSync8(join12(rootPath, ".gitignore"));
14980
15522
  const args = [
14981
15523
  "--json",
14982
15524
  "--line-number",
@@ -14995,7 +15537,7 @@ async function searchWithRipgrep(rootPath, query, maxFiles) {
14995
15537
  args.push("--no-require-git");
14996
15538
  }
14997
15539
  args.push(query, ".");
14998
- return new Promise((resolve4, reject) => {
15540
+ return new Promise((resolve6, reject) => {
14999
15541
  const child = spawn5("rg", args, { cwd: rootPath, stdio: ["ignore", "pipe", "pipe"] });
15000
15542
  const stdout = createInterface({ input: child.stdout });
15001
15543
  const files = /* @__PURE__ */ new Map();
@@ -15014,7 +15556,7 @@ async function searchWithRipgrep(rootPath, query, maxFiles) {
15014
15556
  if (!rawPath) {
15015
15557
  return;
15016
15558
  }
15017
- const relativePath = normalizeRelativePath(relative2(rootPath, join10(rootPath, rawPath)));
15559
+ const relativePath = normalizeRelativePath(relative3(rootPath, join12(rootPath, rawPath)));
15018
15560
  const preview = (event.data?.lines?.text ?? "").replace(/\r?\n$/, "");
15019
15561
  const lineNumber = event.data?.line_number ?? 1;
15020
15562
  const submatches = event.data?.submatches ?? [];
@@ -15044,7 +15586,7 @@ async function searchWithRipgrep(rootPath, query, maxFiles) {
15044
15586
  child.on("close", (code) => {
15045
15587
  void stdout.close();
15046
15588
  if (code === 0 || code === 1) {
15047
- resolve4({
15589
+ resolve6({
15048
15590
  files: sortAccumulators(files),
15049
15591
  totalMatchCount,
15050
15592
  hasMoreFiles
@@ -15067,7 +15609,7 @@ async function searchWithNode(rootPath, query, maxFiles) {
15067
15609
  const filteredEntries = entries.filter((entry) => filter(entry.name));
15068
15610
  filteredEntries.sort((a, b) => a.name.localeCompare(b.name));
15069
15611
  for (const entry of filteredEntries) {
15070
- const fullPath = join10(dirPath, entry.name);
15612
+ const fullPath = join12(dirPath, entry.name);
15071
15613
  if (entry.isDirectory()) {
15072
15614
  await walk(fullPath);
15073
15615
  continue;
@@ -15075,7 +15617,7 @@ async function searchWithNode(rootPath, query, maxFiles) {
15075
15617
  if (!entry.isFile()) {
15076
15618
  continue;
15077
15619
  }
15078
- const fileStat = await stat8(fullPath);
15620
+ const fileStat = await stat9(fullPath);
15079
15621
  if (fileStat.size > FALLBACK_MAX_FILE_BYTES) {
15080
15622
  continue;
15081
15623
  }
@@ -15083,7 +15625,7 @@ async function searchWithNode(rootPath, query, maxFiles) {
15083
15625
  if (isBinaryFile(buffer)) {
15084
15626
  continue;
15085
15627
  }
15086
- const relativePath = normalizeRelativePath(relative2(rootPath, fullPath));
15628
+ const relativePath = normalizeRelativePath(relative3(rootPath, fullPath));
15087
15629
  const file = collectMatchesFromText(relativePath, buffer.toString("utf-8"), query);
15088
15630
  if (!file) {
15089
15631
  continue;
@@ -15190,10 +15732,10 @@ var init_content_search = __esm({
15190
15732
  });
15191
15733
 
15192
15734
  // packages/server/src/fs/tree.ts
15193
- import { readdir as readdir5, stat as stat9 } from "fs/promises";
15194
- import { join as join11, relative as relative3 } from "path";
15735
+ import { readdir as readdir5, stat as stat10 } from "fs/promises";
15736
+ import { join as join13, relative as relative4 } from "path";
15195
15737
  async function readTree(rootPath, subdir) {
15196
- const targetPath = subdir ? join11(rootPath, subdir) : rootPath;
15738
+ const targetPath = subdir ? join13(rootPath, subdir) : rootPath;
15197
15739
  const filter = createTreeVisibilityFilter();
15198
15740
  const entries = await readdir5(targetPath, { withFileTypes: true });
15199
15741
  const nodes = [];
@@ -15201,8 +15743,8 @@ async function readTree(rootPath, subdir) {
15201
15743
  if (!filter(entry.name)) {
15202
15744
  continue;
15203
15745
  }
15204
- const fullPath = join11(targetPath, entry.name);
15205
- const relPath = relative3(rootPath, fullPath);
15746
+ const fullPath = join13(targetPath, entry.name);
15747
+ const relPath = relative4(rootPath, fullPath);
15206
15748
  if (entry.isDirectory()) {
15207
15749
  nodes.push({
15208
15750
  name: entry.name,
@@ -15212,7 +15754,7 @@ async function readTree(rootPath, subdir) {
15212
15754
  // Not loaded yet - client will request on expand
15213
15755
  });
15214
15756
  } else if (entry.isFile()) {
15215
- const stats = await stat9(fullPath);
15757
+ const stats = await stat10(fullPath);
15216
15758
  nodes.push({
15217
15759
  name: entry.name,
15218
15760
  path: relPath,
@@ -15245,8 +15787,8 @@ async function searchFiles(rootPath, query, limit = 10) {
15245
15787
  const filteredEntries = entries.filter((entry) => filter(entry.name));
15246
15788
  filteredEntries.sort((a, b) => a.name.localeCompare(b.name));
15247
15789
  for (const entry of filteredEntries) {
15248
- const fullPath = join11(dirPath, entry.name);
15249
- const relPath = relative3(rootPath, fullPath);
15790
+ const fullPath = join13(dirPath, entry.name);
15791
+ const relPath = relative4(rootPath, fullPath);
15250
15792
  if (entry.isDirectory()) {
15251
15793
  await walk(fullPath);
15252
15794
  continue;
@@ -15281,7 +15823,7 @@ async function searchFiles(rootPath, query, limit = 10) {
15281
15823
  }
15282
15824
  return a.path.toLowerCase().localeCompare(b.path.toLowerCase());
15283
15825
  }).slice(0, limit)) {
15284
- const stats = await stat9(match.fullPath);
15826
+ const stats = await stat10(match.fullPath);
15285
15827
  files.push({
15286
15828
  name: match.name,
15287
15829
  path: match.path,
@@ -15540,7 +16082,7 @@ var init_file = __esm({
15540
16082
  });
15541
16083
 
15542
16084
  // packages/server/src/git/diff.ts
15543
- import { mkdtemp as mkdtemp3, readFile as readFile5, rm as rm6 } from "fs/promises";
16085
+ import { mkdtemp as mkdtemp3, readFile as readFile5, rm as rm7 } from "fs/promises";
15544
16086
  import os3 from "os";
15545
16087
  import path11 from "path";
15546
16088
  async function isTrackedPath(cwd, filePath) {
@@ -15575,7 +16117,7 @@ async function getUntrackedFileDiff(cwd, filePath) {
15575
16117
  });
15576
16118
  return result.stdout;
15577
16119
  } finally {
15578
- await rm6(tempDir, { recursive: true, force: true });
16120
+ await rm7(tempDir, { recursive: true, force: true });
15579
16121
  }
15580
16122
  }
15581
16123
  async function pathExists(cwd, filePath) {
@@ -16008,25 +16550,25 @@ var init_git2 = __esm({
16008
16550
  // packages/server/src/config/config-io.ts
16009
16551
  import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync7, renameSync as renameSync2, writeFileSync as writeFileSync5 } from "node:fs";
16010
16552
  import { homedir as homedir3 } from "node:os";
16011
- import { basename as basename3, dirname as dirname7, join as join12 } from "node:path";
16553
+ import { basename as basename3, dirname as dirname7, join as join14 } from "node:path";
16012
16554
  function resolveConfigPath(configType) {
16013
16555
  if (configType === "codex") {
16014
16556
  const testHome = process.env.CODER_STUDIO_CODEX_HOME;
16015
16557
  if (testHome && testHome.trim()) {
16016
- return join12(testHome, "config.toml");
16558
+ return join14(testHome, "config.toml");
16017
16559
  }
16018
16560
  const codexHome = process.env.CODEX_HOME;
16019
16561
  if (codexHome && codexHome.trim()) {
16020
- return join12(codexHome, "config.toml");
16562
+ return join14(codexHome, "config.toml");
16021
16563
  }
16022
- return join12(homedir3(), ".codex", "config.toml");
16564
+ return join14(homedir3(), ".codex", "config.toml");
16023
16565
  }
16024
16566
  if (configType === "claude") {
16025
16567
  const testHome = process.env.CODER_STUDIO_CLAUDE_HOME;
16026
16568
  if (testHome && testHome.trim()) {
16027
- return join12(testHome, "settings.json");
16569
+ return join14(testHome, "settings.json");
16028
16570
  }
16029
- return join12(homedir3(), ".claude", "settings.json");
16571
+ return join14(homedir3(), ".claude", "settings.json");
16030
16572
  }
16031
16573
  throw new Error(`Unknown config type: ${configType}`);
16032
16574
  }
@@ -16071,7 +16613,7 @@ function createBackup(filePath) {
16071
16613
  const base = basename3(filePath, `.${ext}`);
16072
16614
  const dir = dirname7(filePath);
16073
16615
  const ts = formatTimestamp(/* @__PURE__ */ new Date());
16074
- const backupPath = join12(dir, `${base}.bak.${ts}.${ext}`);
16616
+ const backupPath = join14(dir, `${base}.bak.${ts}.${ext}`);
16075
16617
  writeFileSync5(backupPath, original, "utf-8");
16076
16618
  return backupPath;
16077
16619
  }
@@ -16099,7 +16641,36 @@ function flattenSettings(obj, prefix = "") {
16099
16641
  }
16100
16642
  return result;
16101
16643
  }
16102
- var SettingsSchema;
16644
+ function resolveAppearancePersonalizationOverrideKeysToDelete(settings) {
16645
+ const appearance = settings.appearance;
16646
+ if (!appearance || typeof appearance !== "object" || Array.isArray(appearance)) {
16647
+ return [];
16648
+ }
16649
+ const personalization = appearance.personalization;
16650
+ if (!personalization || typeof personalization !== "object" || Array.isArray(personalization)) {
16651
+ return [];
16652
+ }
16653
+ if (!isFullAppearancePersonalizationSnapshot(personalization)) {
16654
+ return [];
16655
+ }
16656
+ const keysToDelete = [];
16657
+ for (const branch of PERSONALIZATION_OVERRIDE_BRANCHES) {
16658
+ const overrides = personalization[branch];
16659
+ if (!overrides || typeof overrides !== "object" || Array.isArray(overrides)) {
16660
+ continue;
16661
+ }
16662
+ for (const field of PERSONALIZATION_OVERRIDE_FIELDS) {
16663
+ if (!Object.prototype.hasOwnProperty.call(overrides, field)) {
16664
+ keysToDelete.push(`appearance.personalization.${branch}.${field}`);
16665
+ }
16666
+ }
16667
+ }
16668
+ return keysToDelete;
16669
+ }
16670
+ function isFullAppearancePersonalizationSnapshot(personalization) {
16671
+ return Object.prototype.hasOwnProperty.call(personalization, "version") && Object.prototype.hasOwnProperty.call(personalization, "common") && Object.prototype.hasOwnProperty.call(personalization, "desktop") && Object.prototype.hasOwnProperty.call(personalization, "mobile");
16672
+ }
16673
+ var PersonalizationOverridesSchema, PERSONALIZATION_OVERRIDE_BRANCHES, PERSONALIZATION_OVERRIDE_FIELDS, SettingsSchema;
16103
16674
  var init_settings2 = __esm({
16104
16675
  "packages/server/src/commands/settings.ts"() {
16105
16676
  "use strict";
@@ -16108,6 +16679,23 @@ var init_settings2 = __esm({
16108
16679
  init_provider_config();
16109
16680
  init_settings();
16110
16681
  init_dispatch();
16682
+ PersonalizationOverridesSchema = z15.object({
16683
+ backgroundAssetId: z15.string().min(1).nullable().optional(),
16684
+ backgroundDimness: z15.number().int().min(0).max(100).optional(),
16685
+ backgroundBlur: z15.number().int().min(0).max(40).optional(),
16686
+ glassEnabled: z15.boolean().optional(),
16687
+ glassIntensity: z15.number().int().min(0).max(100).optional(),
16688
+ surfaceOpacity: z15.number().int().min(0).max(100).optional()
16689
+ });
16690
+ PERSONALIZATION_OVERRIDE_BRANCHES = ["desktop", "mobile"];
16691
+ PERSONALIZATION_OVERRIDE_FIELDS = [
16692
+ "backgroundAssetId",
16693
+ "backgroundDimness",
16694
+ "backgroundBlur",
16695
+ "glassEnabled",
16696
+ "glassIntensity",
16697
+ "surfaceOpacity"
16698
+ ];
16111
16699
  SettingsSchema = z15.object({
16112
16700
  defaultProviderId: z15.string().optional(),
16113
16701
  notifications: z15.object({
@@ -16134,7 +16722,22 @@ var init_settings2 = __esm({
16134
16722
  terminalFontSize: z15.number().int().min(10).max(18).optional(),
16135
16723
  desktopTerminalFontSize: z15.number().int().min(10).max(18).optional(),
16136
16724
  mobileTerminalFontSize: z15.number().int().min(10).max(18).optional(),
16137
- locale: z15.enum(["zh", "en"]).optional()
16725
+ locale: z15.enum(["zh", "en"]).optional(),
16726
+ personalization: z15.object({
16727
+ version: z15.literal(1).optional(),
16728
+ common: z15.object({
16729
+ backgroundMode: z15.enum(["none", "image"]).optional(),
16730
+ backgroundAssetId: z15.string().min(1).nullable().optional(),
16731
+ backgroundFit: z15.enum(["cover", "contain"]).optional(),
16732
+ backgroundDimness: z15.number().int().min(0).max(100).optional(),
16733
+ backgroundBlur: z15.number().int().min(0).max(40).optional(),
16734
+ glassEnabled: z15.boolean().optional(),
16735
+ glassIntensity: z15.number().int().min(0).max(100).optional(),
16736
+ surfaceOpacity: z15.number().int().min(0).max(100).optional()
16737
+ }).optional(),
16738
+ desktop: PersonalizationOverridesSchema.optional(),
16739
+ mobile: PersonalizationOverridesSchema.optional()
16740
+ }).optional()
16138
16741
  }).optional(),
16139
16742
  lsp: z15.object({
16140
16743
  mode: z15.enum(["auto", "off"]).optional()
@@ -16204,7 +16807,11 @@ var init_settings2 = __esm({
16204
16807
  const nextSettings = args.settings;
16205
16808
  const providers = nextSettings.providers && typeof nextSettings.providers === "object" && !Array.isArray(nextSettings.providers) ? nextSettings.providers : void 0;
16206
16809
  const { providers: _providers, ...nonProviderSettings } = nextSettings;
16810
+ const overrideKeysToDelete = resolveAppearancePersonalizationOverrideKeysToDelete(nextSettings);
16207
16811
  const flatSettings = flattenSettings(nonProviderSettings);
16812
+ for (const key of overrideKeysToDelete) {
16813
+ ctx.settingsRepo.delete(key);
16814
+ }
16208
16815
  for (const [key, value] of Object.entries(flatSettings)) {
16209
16816
  ctx.settingsRepo.set(key, value);
16210
16817
  }
@@ -16850,6 +17457,7 @@ async function listWorktrees(repoPath) {
16850
17457
  current.name = branch.split("/").pop() || branch;
16851
17458
  } else if (line === "detached") {
16852
17459
  current.branch = "detached HEAD";
17460
+ current.name = path12.basename(current.path ?? "") || "detached";
16853
17461
  } else if (line === "") {
16854
17462
  if (current.path) {
16855
17463
  worktrees.push(current);
@@ -17431,12 +18039,12 @@ var init_commands = __esm({
17431
18039
  // packages/server/src/server.ts
17432
18040
  import { mkdtempSync, rmSync as rmSync2 } from "node:fs";
17433
18041
  import { tmpdir } from "node:os";
17434
- import { join as join13 } from "node:path";
18042
+ import { join as join15 } from "node:path";
17435
18043
  async function createServer(configOverrides) {
17436
18044
  const config = parseServerConfig(configOverrides);
17437
18045
  const configuredStateDir = resolveConfiguredStateDir(config);
17438
18046
  const shouldCleanupStateRoot = configuredStateDir === IN_MEMORY_STATE_DIR;
17439
- const stateRoot = shouldCleanupStateRoot ? mkdtempSync(join13(tmpdir(), "coder-studio-state-")) : configuredStateDir;
18047
+ const stateRoot = shouldCleanupStateRoot ? mkdtempSync(join15(tmpdir(), "coder-studio-state-")) : configuredStateDir;
17440
18048
  ensureStateDir(config);
17441
18049
  const eventBus = new EventBus();
17442
18050
  const activationMgr = new ActivationManager();
@@ -17446,10 +18054,10 @@ async function createServer(configOverrides) {
17446
18054
  let commandContext;
17447
18055
  let lspMgr = null;
17448
18056
  const terminalRepo = new TerminalRepo({
17449
- filePath: join13(stateRoot, "state", "terminals.json")
18057
+ filePath: join15(stateRoot, "state", "terminals.json")
17450
18058
  });
17451
18059
  const sessionRepo = new SessionRepo({
17452
- filePath: join13(stateRoot, "state", "sessions.json")
18060
+ filePath: join15(stateRoot, "state", "sessions.json")
17453
18061
  });
17454
18062
  const terminalMgr = new TerminalManager({
17455
18063
  ptyHost: createPtyHost(),
@@ -17457,10 +18065,10 @@ async function createServer(configOverrides) {
17457
18065
  db: terminalRepo
17458
18066
  });
17459
18067
  const settingsRepo = new SettingsRepo({
17460
- filePath: join13(stateRoot, "state", "settings.json")
18068
+ filePath: join15(stateRoot, "state", "settings.json")
17461
18069
  });
17462
18070
  const updateStateRepo = new UpdateStateRepo({
17463
- filePath: join13(stateRoot, "state", "update-state.json"),
18071
+ filePath: join15(stateRoot, "state", "update-state.json"),
17464
18072
  currentVersion: config.appVersion ?? "0.0.0"
17465
18073
  });
17466
18074
  const autoFetch = new AutoFetchScheduler({
@@ -17493,10 +18101,10 @@ async function createServer(configOverrides) {
17493
18101
  }
17494
18102
  });
17495
18103
  const providerConfigRepo = new ProviderConfigRepo({
17496
- filePath: join13(stateRoot, "state", "provider-configs.json")
18104
+ filePath: join15(stateRoot, "state", "provider-configs.json")
17497
18105
  });
17498
18106
  const workspaceRepo = new WorkspaceRepo({
17499
- filePath: join13(stateRoot, "state", "workspaces.json")
18107
+ filePath: join15(stateRoot, "state", "workspaces.json")
17500
18108
  });
17501
18109
  const sessionMgr = new SessionManager({
17502
18110
  terminalMgr,
@@ -17531,10 +18139,13 @@ async function createServer(configOverrides) {
17531
18139
  )
17532
18140
  });
17533
18141
  const authSessionRepo = new AuthSessionRepo({
17534
- filePath: join13(stateRoot, "state", "auth-sessions.json")
18142
+ filePath: join15(stateRoot, "state", "auth-sessions.json")
17535
18143
  });
17536
18144
  const authLoginBlockRepo = new AuthLoginBlockRepo({
17537
- filePath: join13(stateRoot, "state", "auth-login-blocks.json")
18145
+ filePath: join15(stateRoot, "state", "auth-login-blocks.json")
18146
+ });
18147
+ const appearanceAssetRepo = new AppearanceAssetRepo({
18148
+ filePath: join15(stateRoot, "state", "appearance-assets.json")
17538
18149
  });
17539
18150
  const app = await buildFastifyApp({
17540
18151
  wsHub,
@@ -17543,6 +18154,7 @@ async function createServer(configOverrides) {
17543
18154
  config,
17544
18155
  authSessionRepo,
17545
18156
  authLoginBlockRepo,
18157
+ appearanceAssetRepo,
17546
18158
  logger: {
17547
18159
  level: "info",
17548
18160
  transport: {
@@ -17609,7 +18221,7 @@ async function createServer(configOverrides) {
17609
18221
  ...config.update,
17610
18222
  currentVersion: config.appVersion ?? "0.0.0"
17611
18223
  },
17612
- updateWorkerLogFilePath: join13(stateRoot, "logs", "update-worker.log"),
18224
+ updateWorkerLogFilePath: join15(stateRoot, "logs", "update-worker.log"),
17613
18225
  countRunningTerminals: () => terminalMgr.getAll().filter((terminal) => terminal.alive).length,
17614
18226
  countRunningSessions: () => sessionMgr.getAll().filter((session) => session.state === "starting" || session.state === "running").length,
17615
18227
  countActiveSupervisors: () => supervisorMgr?.countActive() ?? 0
@@ -17721,6 +18333,7 @@ var init_server = __esm({
17721
18333
  init_e2e_provider_mock();
17722
18334
  init_install_manager2();
17723
18335
  init_manager3();
18336
+ init_appearance_asset_repo();
17724
18337
  init_auth_login_block_repo();
17725
18338
  init_auth_session_repo();
17726
18339
  init_provider_config_repo();
@@ -17832,20 +18445,20 @@ var init_src4 = __esm({
17832
18445
 
17833
18446
  // packages/cli/src/cli.ts
17834
18447
  import { existsSync as existsSync15 } from "fs";
17835
- import { dirname as dirname10, join as join18 } from "path";
18448
+ import { dirname as dirname10, join as join20 } from "path";
17836
18449
  import { fileURLToPath as fileURLToPath5 } from "url";
17837
18450
 
17838
18451
  // packages/cli/src/auth-control.ts
17839
18452
  await init_src4();
17840
- import { join as join15 } from "node:path";
18453
+ import { join as join17 } from "node:path";
17841
18454
 
17842
18455
  // packages/cli/src/config-store.ts
17843
18456
  init_state_paths();
17844
18457
  import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
17845
18458
  import { homedir as homedir4 } from "os";
17846
- import { join as join14 } from "path";
18459
+ import { join as join16 } from "path";
17847
18460
  function getCliConfigPath() {
17848
- return join14(homedir4(), ".coder-studio", "config.json");
18461
+ return join16(homedir4(), ".coder-studio", "config.json");
17849
18462
  }
17850
18463
  function normalizeLegacyDataDir(input2) {
17851
18464
  return normalizeLegacyStateDir(input2);
@@ -17872,7 +18485,7 @@ function readCliConfig() {
17872
18485
  }
17873
18486
  function writeCliConfig(config) {
17874
18487
  const path14 = getCliConfigPath();
17875
- const dir = join14(homedir4(), ".coder-studio");
18488
+ const dir = join16(homedir4(), ".coder-studio");
17876
18489
  const normalizedConfig = {
17877
18490
  ...config.host !== void 0 ? { host: config.host } : {},
17878
18491
  ...config.port !== void 0 && config.port > 0 ? { port: config.port } : {},
@@ -17894,7 +18507,7 @@ function resolveStateDir() {
17894
18507
  }
17895
18508
  async function listAuthBlocks(now = Date.now()) {
17896
18509
  const repo = new AuthLoginBlockRepo({
17897
- filePath: join15(resolveStateDir(), "state", "auth-login-blocks.json")
18510
+ filePath: join17(resolveStateDir(), "state", "auth-login-blocks.json")
17898
18511
  });
17899
18512
  return repo.listActiveBlocks(now).map((record) => ({
17900
18513
  ip: record.ip,
@@ -17906,7 +18519,7 @@ async function listAuthBlocks(now = Date.now()) {
17906
18519
  }
17907
18520
  async function clearAuthBlockByIp(ip) {
17908
18521
  const repo = new AuthLoginBlockRepo({
17909
- filePath: join15(resolveStateDir(), "state", "auth-login-blocks.json")
18522
+ filePath: join17(resolveStateDir(), "state", "auth-login-blocks.json")
17910
18523
  });
17911
18524
  return repo.delete(ip);
17912
18525
  }
@@ -17925,7 +18538,7 @@ function getOpenCommand(url) {
17925
18538
  }
17926
18539
  async function openBrowser(url) {
17927
18540
  const { command, args } = getOpenCommand(url);
17928
- await new Promise((resolve4, reject) => {
18541
+ await new Promise((resolve6, reject) => {
17929
18542
  const child = spawn6(command, args, {
17930
18543
  detached: true,
17931
18544
  stdio: "ignore",
@@ -17934,7 +18547,7 @@ async function openBrowser(url) {
17934
18547
  child.once("error", reject);
17935
18548
  child.once("spawn", () => {
17936
18549
  child.unref();
17937
- resolve4();
18550
+ resolve6();
17938
18551
  });
17939
18552
  });
17940
18553
  }
@@ -18267,7 +18880,7 @@ function parseArgs(argv) {
18267
18880
  init_runtime();
18268
18881
  import { mkdirSync as mkdirSync9 } from "fs";
18269
18882
  import { homedir as homedir5 } from "os";
18270
- import { join as join16 } from "path";
18883
+ import { join as join18 } from "path";
18271
18884
  var MANAGED_SERVER_NAME = "coder-studio-server";
18272
18885
  var PM2_RESTART_DELAY_MS = 2e3;
18273
18886
  var PM2_MIN_UPTIME = "5s";
@@ -18307,29 +18920,29 @@ async function loadPm2() {
18307
18920
  }
18308
18921
  var connectPm2 = async () => {
18309
18922
  const pm2 = await loadPm2();
18310
- return new Promise((resolve4, reject) => {
18923
+ return new Promise((resolve6, reject) => {
18311
18924
  pm2.connect((error) => {
18312
18925
  if (error) {
18313
18926
  reject(error);
18314
18927
  return;
18315
18928
  }
18316
- resolve4();
18929
+ resolve6();
18317
18930
  });
18318
18931
  });
18319
18932
  };
18320
- var sleep = async (ms) => new Promise((resolve4) => {
18321
- setTimeout(resolve4, ms);
18933
+ var sleep = async (ms) => new Promise((resolve6) => {
18934
+ setTimeout(resolve6, ms);
18322
18935
  });
18323
18936
  var disconnectPm2 = async () => {
18324
18937
  const pm2 = await loadPm2();
18325
- await new Promise((resolve4) => {
18938
+ await new Promise((resolve6) => {
18326
18939
  let settled = false;
18327
18940
  const finish = () => {
18328
18941
  if (settled) {
18329
18942
  return;
18330
18943
  }
18331
18944
  settled = true;
18332
- resolve4();
18945
+ resolve6();
18333
18946
  };
18334
18947
  const timer = setTimeout(finish, PM2_DISCONNECT_WAIT_MS);
18335
18948
  try {
@@ -18343,29 +18956,29 @@ var disconnectPm2 = async () => {
18343
18956
  }
18344
18957
  });
18345
18958
  };
18346
- var describeManagedServer = async (pm2) => new Promise((resolve4, reject) => {
18959
+ var describeManagedServer = async (pm2) => new Promise((resolve6, reject) => {
18347
18960
  pm2.describe(MANAGED_SERVER_NAME, (error, result) => {
18348
18961
  if (error) {
18349
18962
  reject(error);
18350
18963
  return;
18351
18964
  }
18352
- resolve4(result ?? []);
18965
+ resolve6(result ?? []);
18353
18966
  });
18354
18967
  });
18355
- var removeManagedServer = async (pm2) => new Promise((resolve4, reject) => {
18968
+ var removeManagedServer = async (pm2) => new Promise((resolve6, reject) => {
18356
18969
  pm2.delete(MANAGED_SERVER_NAME, (error) => {
18357
18970
  if (error) {
18358
18971
  reject(error);
18359
18972
  return;
18360
18973
  }
18361
- resolve4();
18974
+ resolve6();
18362
18975
  });
18363
18976
  });
18364
18977
  var killPm2Daemon = async () => {
18365
18978
  const pm2 = await loadPm2();
18366
- return new Promise((resolve4) => {
18979
+ return new Promise((resolve6) => {
18367
18980
  pm2.kill(() => {
18368
- resolve4();
18981
+ resolve6();
18369
18982
  });
18370
18983
  });
18371
18984
  };
@@ -18488,11 +19101,11 @@ var deleteManagedServerInSession = async (pm2, {
18488
19101
  return true;
18489
19102
  };
18490
19103
  var ensureLogDirectory = () => {
18491
- mkdirSync9(join16(homedir5(), ".coder-studio", "logs"), { recursive: true });
19104
+ mkdirSync9(join18(homedir5(), ".coder-studio", "logs"), { recursive: true });
18492
19105
  };
18493
19106
  var getLogPaths = () => ({
18494
- outFile: join16(homedir5(), ".coder-studio", "logs", "server.out.log"),
18495
- errFile: join16(homedir5(), ".coder-studio", "logs", "server.err.log")
19107
+ outFile: join18(homedir5(), ".coder-studio", "logs", "server.out.log"),
19108
+ errFile: join18(homedir5(), ".coder-studio", "logs", "server.err.log")
18496
19109
  });
18497
19110
  var captureStartupLogOffsets = () => {
18498
19111
  const { outFile, errFile } = getLogPaths();
@@ -18541,7 +19154,7 @@ var startManagedServer = async ({
18541
19154
  ensureLogDirectory();
18542
19155
  const { outFile, errFile } = getLogPaths();
18543
19156
  const logOffsets = captureStartupLogOffsets();
18544
- await new Promise((resolve4, reject) => {
19157
+ await new Promise((resolve6, reject) => {
18545
19158
  pm2.start(
18546
19159
  {
18547
19160
  name: MANAGED_SERVER_NAME,
@@ -18564,7 +19177,7 @@ var startManagedServer = async ({
18564
19177
  reject(error);
18565
19178
  return;
18566
19179
  }
18567
- resolve4();
19180
+ resolve6();
18568
19181
  }
18569
19182
  );
18570
19183
  });
@@ -18681,11 +19294,11 @@ import { fileURLToPath as fileURLToPath4 } from "url";
18681
19294
 
18682
19295
  // packages/cli/src/embed.ts
18683
19296
  import { existsSync as existsSync13 } from "fs";
18684
- import { dirname as dirname8, resolve as resolve3 } from "path";
19297
+ import { dirname as dirname8, resolve as resolve5 } from "path";
18685
19298
  import { fileURLToPath as fileURLToPath2 } from "url";
18686
19299
  var __filename = fileURLToPath2(import.meta.url);
18687
19300
  var __dirname = dirname8(__filename);
18688
- var WEB_ASSETS_DIR = resolve3(__dirname, "../web");
19301
+ var WEB_ASSETS_DIR = resolve5(__dirname, "../web");
18689
19302
  function getStaticAssetsDir() {
18690
19303
  return WEB_ASSETS_DIR;
18691
19304
  }
@@ -18695,13 +19308,13 @@ function hasWebAssets() {
18695
19308
 
18696
19309
  // packages/cli/src/update-runtime.ts
18697
19310
  import { existsSync as existsSync14 } from "node:fs";
18698
- import { dirname as dirname9, join as join17 } from "node:path";
19311
+ import { dirname as dirname9, join as join19 } from "node:path";
18699
19312
  import { fileURLToPath as fileURLToPath3 } from "node:url";
18700
19313
  function resolveWorkerEntryPath(importMetaUrl) {
18701
19314
  const currentDir = dirname9(fileURLToPath3(importMetaUrl));
18702
19315
  const candidates = [
18703
- join17(currentDir, "update-worker.mjs"),
18704
- join17(currentDir, "../src/update-worker.ts")
19316
+ join19(currentDir, "update-worker.mjs"),
19317
+ join19(currentDir, "../src/update-worker.ts")
18705
19318
  ];
18706
19319
  return candidates.find((candidate) => existsSync14(candidate));
18707
19320
  }
@@ -18930,9 +19543,9 @@ function resolveManagedScriptPath() {
18930
19543
  const currentFile = fileURLToPath5(import.meta.url);
18931
19544
  const currentDir = dirname10(currentFile);
18932
19545
  const candidates = [
18933
- join18(currentDir, "server-runner.js"),
18934
- join18(currentDir, "server-runner.mjs"),
18935
- join18(currentDir, "../src/server-runner.ts")
19546
+ join20(currentDir, "server-runner.js"),
19547
+ join20(currentDir, "server-runner.mjs"),
19548
+ join20(currentDir, "../src/server-runner.ts")
18936
19549
  ];
18937
19550
  const scriptPath = candidates.find((candidate) => existsSync15(candidate));
18938
19551
  if (!scriptPath) {