@kmugalkhod/lightcode 0.17.2 → 0.18.0

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/server.js CHANGED
@@ -4903,6 +4903,32 @@ async function executeGitStatus(input, workspaceContext = getDefaultWorkspaceCon
4903
4903
  error: null
4904
4904
  });
4905
4905
  }
4906
+ async function executeGitNumstat(workspaceContext = getDefaultWorkspaceContext()) {
4907
+ const counts = new Map;
4908
+ for (const staged of [false, true]) {
4909
+ const args = ["diff", "--no-ext-diff", "--no-renames", "--numstat"];
4910
+ if (staged)
4911
+ args.push("--cached");
4912
+ args.push("--", ".");
4913
+ const result = await runGit(args, workspaceContext);
4914
+ if (!result.ok)
4915
+ continue;
4916
+ for (const line of result.stdout.split(`
4917
+ `)) {
4918
+ const match = /^(\d+|-)\t(\d+|-)\t(.+)$/.exec(line.trimEnd());
4919
+ if (!match)
4920
+ continue;
4921
+ const [, addedText, removedText, path9] = match;
4922
+ const binary = addedText === "-" || removedText === "-";
4923
+ const previous = counts.get(path9);
4924
+ counts.set(path9, {
4925
+ added: binary || previous?.added === null ? null : (previous?.added ?? 0) + Number(addedText),
4926
+ removed: binary || previous?.removed === null ? null : (previous?.removed ?? 0) + Number(removedText)
4927
+ });
4928
+ }
4929
+ }
4930
+ return counts;
4931
+ }
4906
4932
  async function executeGitDiff(input, workspaceContext = getDefaultWorkspaceContext()) {
4907
4933
  const parsedInput = gitDiffInputSchema.parse(input);
4908
4934
  const relativePath = resolveOptionalGitPath(parsedInput.path, workspaceContext);
@@ -9688,7 +9714,7 @@ var init_runtime16 = __esm(() => {
9688
9714
 
9689
9715
  // packages/ai/src/workspace-browser.ts
9690
9716
  import { z as z38 } from "zod";
9691
- var workspaceLocationIdSchema, workspaceLocationStateSchema, workspaceLocationSchema, workspaceLocationsResponseSchema, workspaceBrowserOpenRequestSchema, workspaceBrowserIdSchema, workspaceBrowserOpenResponseSchema, workspacePathSegmentSchema, workspacePathSegmentsSchema, workspaceBrowserCursorSchema, workspaceBrowserEntriesRequestSchema, workspaceBrowserEntryKindSchema, workspaceBrowserSymlinkStateSchema, workspaceBrowserEntrySchema, workspaceBrowserEntriesResponseSchema, workspaceBrowserSelectRequestSchema, workspaceGrantIdSchema, workspaceGrantSchema, workspaceNativePickerRequestSchema, workspaceNativePickerResponseSchema, workspaceBrowserSelectResponseSchema, workspaceSessionPathParamsSchema, workspaceSessionCreateRequestSchema, workspaceBrowserPathParamsSchema, workspaceApiErrorCodeSchema, workspaceApiErrorResponseSchema;
9717
+ var workspaceLocationIdSchema, workspaceLocationStateSchema, workspaceLocationSchema, workspaceLocationsResponseSchema, workspaceBrowserOpenRequestSchema, workspaceBrowserIdSchema, workspaceBrowserOpenResponseSchema, workspacePathSegmentSchema, workspacePathSegmentsSchema, workspaceBrowserCursorSchema, workspaceBrowserEntriesRequestSchema, workspaceBrowserEntryKindSchema, workspaceBrowserSymlinkStateSchema, workspaceBrowserEntrySchema, workspaceBrowserEntriesResponseSchema, workspaceBrowserSelectRequestSchema, workspaceGrantIdSchema, workspaceGrantSchema, workspaceRecentKeySchema, workspaceRecentProjectSchema, workspaceRecentProjectsResponseSchema, workspaceRecentPathParamsSchema, workspaceRecentGrantRequestSchema, workspaceNativePickerRequestSchema, workspaceNativePickerResponseSchema, workspaceLaunchFolderResponseSchema, workspaceLaunchFolderGrantRequestSchema, workspaceBrowserSelectResponseSchema, workspaceSessionPathParamsSchema, workspaceSessionCreateRequestSchema, workspaceBrowserPathParamsSchema, workspaceApiErrorCodeSchema, workspaceApiErrorResponseSchema;
9692
9718
  var init_workspace_browser = __esm(() => {
9693
9719
  init_coding_agent_modes();
9694
9720
  init_permissions();
@@ -9697,7 +9723,8 @@ var init_workspace_browser = __esm(() => {
9697
9723
  "home",
9698
9724
  "documents",
9699
9725
  "downloads",
9700
- "projects"
9726
+ "projects",
9727
+ "drives"
9701
9728
  ]);
9702
9729
  workspaceLocationStateSchema = z38.enum([
9703
9730
  "unprobed",
@@ -9706,7 +9733,7 @@ var init_workspace_browser = __esm(() => {
9706
9733
  workspaceLocationSchema = z38.object({
9707
9734
  id: workspaceLocationIdSchema,
9708
9735
  name: z38.string().min(1).max(120),
9709
- kind: z38.literal("known-folder"),
9736
+ kind: z38.enum(["known-folder", "drives"]),
9710
9737
  state: workspaceLocationStateSchema,
9711
9738
  pathLabel: z38.string().min(1).max(4096)
9712
9739
  }).strict();
@@ -9733,7 +9760,8 @@ var init_workspace_browser = __esm(() => {
9733
9760
  segments: workspacePathSegmentsSchema.default([]),
9734
9761
  cursor: workspaceBrowserCursorSchema.optional(),
9735
9762
  limit: z38.number().int().min(1).max(200).default(80),
9736
- includeHidden: z38.boolean().default(false)
9763
+ includeHidden: z38.boolean().default(false),
9764
+ directoriesOnly: z38.boolean().default(false)
9737
9765
  }).strict();
9738
9766
  workspaceBrowserEntryKindSchema = z38.enum([
9739
9767
  "file",
@@ -9768,8 +9796,24 @@ var init_workspace_browser = __esm(() => {
9768
9796
  id: workspaceGrantIdSchema,
9769
9797
  name: z38.string().min(1).max(255),
9770
9798
  pathLabel: z38.string().min(1).max(4096),
9799
+ hostPath: z38.string().min(1).max(4096).optional(),
9771
9800
  createdAt: z38.string().min(1)
9772
9801
  }).strict();
9802
+ workspaceRecentKeySchema = z38.string().regex(/^[A-Za-z0-9_-]{22}$/);
9803
+ workspaceRecentProjectSchema = z38.object({
9804
+ key: workspaceRecentKeySchema,
9805
+ name: z38.string().min(1).max(255),
9806
+ pathLabel: z38.string().min(1).max(4096),
9807
+ lastUsedAt: z38.string().min(1),
9808
+ available: z38.boolean()
9809
+ }).strict();
9810
+ workspaceRecentProjectsResponseSchema = z38.object({
9811
+ projects: z38.array(workspaceRecentProjectSchema)
9812
+ }).strict();
9813
+ workspaceRecentPathParamsSchema = z38.object({
9814
+ key: workspaceRecentKeySchema
9815
+ }).strict();
9816
+ workspaceRecentGrantRequestSchema = z38.object({}).strict();
9773
9817
  workspaceNativePickerRequestSchema = z38.object({}).strict();
9774
9818
  workspaceNativePickerResponseSchema = z38.discriminatedUnion("outcome", [
9775
9819
  z38.object({
@@ -9780,6 +9824,16 @@ var init_workspace_browser = __esm(() => {
9780
9824
  outcome: z38.literal("cancelled")
9781
9825
  }).strict()
9782
9826
  ]);
9827
+ workspaceLaunchFolderResponseSchema = z38.discriminatedUnion("available", [
9828
+ z38.object({
9829
+ available: z38.literal(true),
9830
+ name: z38.string().min(1).max(255),
9831
+ pathLabel: z38.string().min(1).max(4096),
9832
+ broad: z38.boolean()
9833
+ }).strict(),
9834
+ z38.object({ available: z38.literal(false) }).strict()
9835
+ ]);
9836
+ workspaceLaunchFolderGrantRequestSchema = z38.object({}).strict();
9783
9837
  workspaceBrowserSelectResponseSchema = z38.object({
9784
9838
  workspace: workspaceGrantSchema
9785
9839
  }).strict();
@@ -9797,6 +9851,8 @@ var init_workspace_browser = __esm(() => {
9797
9851
  workspaceApiErrorCodeSchema = z38.enum([
9798
9852
  "invalid_request",
9799
9853
  "location_not_found",
9854
+ "location_missing",
9855
+ "drive_root_not_selectable",
9800
9856
  "os_permission_denied",
9801
9857
  "workspace_missing",
9802
9858
  "workspace_replaced",
@@ -9819,6 +9875,61 @@ var init_workspace_browser = __esm(() => {
9819
9875
  }).strict();
9820
9876
  });
9821
9877
 
9878
+ // packages/ai/src/session-changes-schemas.ts
9879
+ import { z as z39 } from "zod";
9880
+ function changeKindFromGitStatus(indexStatus, workingTreeStatus) {
9881
+ if (indexStatus === "?" || workingTreeStatus === "?") {
9882
+ return "untracked";
9883
+ }
9884
+ const code = (workingTreeStatus.trim() || indexStatus.trim()).toUpperCase();
9885
+ switch (code) {
9886
+ case "A":
9887
+ return "created";
9888
+ case "D":
9889
+ return "deleted";
9890
+ case "R":
9891
+ return "renamed";
9892
+ default:
9893
+ return "modified";
9894
+ }
9895
+ }
9896
+ var sessionChangeFileIdSchema, sessionChangeKindSchema, sessionChangedFileSchema, sessionChangesResponseSchema, sessionChangeDiffParamsSchema, sessionChangeDiffResponseSchema;
9897
+ var init_session_changes_schemas = __esm(() => {
9898
+ init_chat_schemas();
9899
+ sessionChangeFileIdSchema = z39.string().regex(/^[A-Za-z0-9_-]{22}$/);
9900
+ sessionChangeKindSchema = z39.enum([
9901
+ "created",
9902
+ "modified",
9903
+ "deleted",
9904
+ "renamed",
9905
+ "untracked"
9906
+ ]);
9907
+ sessionChangedFileSchema = z39.object({
9908
+ fileId: sessionChangeFileIdSchema,
9909
+ path: z39.string().min(1).max(4096),
9910
+ originalPath: z39.string().max(4096).nullable(),
9911
+ changeKind: sessionChangeKindSchema,
9912
+ added: z39.number().int().nonnegative().nullable(),
9913
+ removed: z39.number().int().nonnegative().nullable()
9914
+ }).strict();
9915
+ sessionChangesResponseSchema = z39.object({
9916
+ isRepo: z39.boolean(),
9917
+ branch: z39.string().nullable(),
9918
+ files: z39.array(sessionChangedFileSchema),
9919
+ truncated: z39.boolean()
9920
+ }).strict();
9921
+ sessionChangeDiffParamsSchema = z39.object({
9922
+ id: sessionIdentifierSchema,
9923
+ fileId: sessionChangeFileIdSchema
9924
+ }).strict();
9925
+ sessionChangeDiffResponseSchema = z39.object({
9926
+ path: z39.string().min(1),
9927
+ diff: z39.string(),
9928
+ truncated: z39.boolean(),
9929
+ binary: z39.boolean()
9930
+ }).strict();
9931
+ });
9932
+
9822
9933
  // packages/ai/src/index.ts
9823
9934
  var init_src2 = __esm(() => {
9824
9935
  init_agent_tools();
@@ -9855,6 +9966,7 @@ var init_src2 = __esm(() => {
9855
9966
  init_tool_input();
9856
9967
  init_config2();
9857
9968
  init_workspace_browser();
9969
+ init_session_changes_schemas();
9858
9970
  });
9859
9971
 
9860
9972
  // apps/server/src/lib/chat-interaction-store.ts
@@ -11547,6 +11659,37 @@ async function mergeSessionTurnDelta(sessionIdentifier, input) {
11547
11659
  appended: true
11548
11660
  };
11549
11661
  }
11662
+ async function listRecentWorkspaces(limit = 8) {
11663
+ const sessions = await prisma.chatSession.findMany({
11664
+ where: { cwd: { not: null } },
11665
+ orderBy: [{ updatedAt: "desc" }, { createdAt: "desc" }],
11666
+ take: 200,
11667
+ select: {
11668
+ cwd: true,
11669
+ workspaceDevice: true,
11670
+ workspaceInode: true,
11671
+ updatedAt: true
11672
+ }
11673
+ });
11674
+ const seen = new Set;
11675
+ const recent = [];
11676
+ for (const session of sessions) {
11677
+ if (!session.cwd)
11678
+ continue;
11679
+ const key = process.platform === "win32" ? session.cwd.toLowerCase() : session.cwd;
11680
+ if (seen.has(key))
11681
+ continue;
11682
+ seen.add(key);
11683
+ recent.push({
11684
+ cwd: session.cwd,
11685
+ identity: session.workspaceDevice && session.workspaceInode ? { device: session.workspaceDevice, inode: session.workspaceInode } : null,
11686
+ lastUsedAt: session.updatedAt
11687
+ });
11688
+ if (recent.length >= limit)
11689
+ break;
11690
+ }
11691
+ return recent;
11692
+ }
11550
11693
  async function listChatSessions(limit = 50) {
11551
11694
  const safeLimit = Math.min(Math.max(limit, 1), 100);
11552
11695
  const sessions = await prisma.chatSession.findMany({
@@ -13155,7 +13298,7 @@ var init_sse_heartbeat = __esm(() => {
13155
13298
  // apps/server/src/lib/openrouter-models.ts
13156
13299
  import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
13157
13300
  import path20 from "path";
13158
- import { z as z39 } from "zod";
13301
+ import { z as z40 } from "zod";
13159
13302
  function getCacheFilePath() {
13160
13303
  return path20.join(getLightcodeDataDir(), "cache", "openrouter-models.json");
13161
13304
  }
@@ -13218,7 +13361,7 @@ async function fetchModelsFromApi() {
13218
13361
  if (!response.ok) {
13219
13362
  throw new Error(`OpenRouter models API returned HTTP ${response.status}.`);
13220
13363
  }
13221
- const payload = z39.object({ data: z39.array(z39.unknown()) }).parse(await response.json());
13364
+ const payload = z40.object({ data: z40.array(z40.unknown()) }).parse(await response.json());
13222
13365
  const models = payload.data.map(summarizeRawModel).filter((model) => model !== null);
13223
13366
  if (models.length === 0) {
13224
13367
  throw new Error("OpenRouter models API returned no models.");
@@ -13283,21 +13426,21 @@ var init_openrouter_models = __esm(() => {
13283
13426
  init_network_fetch();
13284
13427
  logger5 = createLogger("openrouter-models");
13285
13428
  cacheTtlMs = 24 * 60 * 60 * 1000;
13286
- openRouterModelSummarySchema = z39.object({
13287
- id: z39.string(),
13288
- name: z39.string(),
13289
- contextLength: z39.number().int().positive().nullable(),
13290
- maxCompletionTokens: z39.number().int().positive().nullable().default(null),
13291
- supportsTools: z39.boolean(),
13292
- supportsReasoning: z39.boolean(),
13293
- pricing: z39.object({
13294
- prompt: z39.string().nullable(),
13295
- completion: z39.string().nullable()
13429
+ openRouterModelSummarySchema = z40.object({
13430
+ id: z40.string(),
13431
+ name: z40.string(),
13432
+ contextLength: z40.number().int().positive().nullable(),
13433
+ maxCompletionTokens: z40.number().int().positive().nullable().default(null),
13434
+ supportsTools: z40.boolean(),
13435
+ supportsReasoning: z40.boolean(),
13436
+ pricing: z40.object({
13437
+ prompt: z40.string().nullable(),
13438
+ completion: z40.string().nullable()
13296
13439
  }).nullable()
13297
13440
  });
13298
- modelsCacheSchema = z39.object({
13299
- fetchedAt: z39.number(),
13300
- models: z39.array(openRouterModelSummarySchema)
13441
+ modelsCacheSchema = z40.object({
13442
+ fetchedAt: z40.number(),
13443
+ models: z40.array(openRouterModelSummarySchema)
13301
13444
  });
13302
13445
  });
13303
13446
 
@@ -13929,7 +14072,7 @@ import {
13929
14072
  } from "fs/promises";
13930
14073
  import { platform as platform3 } from "os";
13931
14074
  import path21 from "path";
13932
- import { z as z40 } from "zod";
14075
+ import { z as z41 } from "zod";
13933
14076
  function isContainedPath(root, candidate) {
13934
14077
  const relative = path21.relative(root, candidate);
13935
14078
  return relative === "" || !relative.startsWith("..") && !path21.isAbsolute(relative);
@@ -14386,15 +14529,15 @@ var init_workspace_context = __esm(() => {
14386
14529
  init_src();
14387
14530
  logger7 = createLogger("workspace-context");
14388
14531
  projectInstructionNames = ["AGENTS.md", "CLAUDE.md"];
14389
- instructionEpochSchema = z40.object({
14390
- epoch: z40.number().int().positive(),
14391
- source: z40.string().min(1),
14392
- sha256: z40.string().regex(/^[a-f0-9]{64}$/),
14393
- content: z40.string(),
14394
- discoveredAt: z40.string().min(1)
14532
+ instructionEpochSchema = z41.object({
14533
+ epoch: z41.number().int().positive(),
14534
+ source: z41.string().min(1),
14535
+ sha256: z41.string().regex(/^[a-f0-9]{64}$/),
14536
+ content: z41.string(),
14537
+ discoveredAt: z41.string().min(1)
14395
14538
  });
14396
- instructionEpochStateSchema = z40.object({
14397
- epochs: z40.array(instructionEpochSchema)
14539
+ instructionEpochStateSchema = z41.object({
14540
+ epochs: z41.array(instructionEpochSchema)
14398
14541
  });
14399
14542
  gitSummaryCache = new Map;
14400
14543
  });
@@ -16379,16 +16522,16 @@ var init_chat_routes = __esm(() => {
16379
16522
 
16380
16523
  // apps/server/src/routes/config-routes.ts
16381
16524
  import { Hono as Hono2 } from "hono";
16382
- import { z as z41 } from "zod";
16525
+ import { z as z42 } from "zod";
16383
16526
  var selectModelRequestSchema, configRoutes;
16384
16527
  var init_config_routes = __esm(() => {
16385
16528
  init_src2();
16386
16529
  init_src();
16387
16530
  init_openrouter_models();
16388
16531
  init_runtime_config();
16389
- selectModelRequestSchema = z41.object({
16532
+ selectModelRequestSchema = z42.object({
16390
16533
  provider: lightcodeProviderSchema,
16391
- model: z41.string().min(1).max(240)
16534
+ model: z42.string().min(1).max(240)
16392
16535
  });
16393
16536
  configRoutes = new Hono2().get("/status", (c) => c.json(configStatus)).post("/reload", (c) => c.json({ ok: true, configStatus: reloadRuntimeConfig() })).get("/models", async (c) => {
16394
16537
  const provider = c.req.query("provider") ?? "openrouter";
@@ -16835,20 +16978,20 @@ var init_diagnostics_routes = __esm(() => {
16835
16978
  // apps/server/src/routes/extension-routes.ts
16836
16979
  import { zValidator as zValidator2 } from "@hono/zod-validator";
16837
16980
  import { Hono as Hono4 } from "hono";
16838
- import { z as z42 } from "zod";
16981
+ import { z as z43 } from "zod";
16839
16982
  var skillParamsSchema, skillListQuerySchema, mcpServerParamsSchema, extensionRoutes;
16840
16983
  var init_extension_routes = __esm(() => {
16841
16984
  init_src2();
16842
16985
  init_extension_runtime();
16843
16986
  init_chat_store();
16844
- skillParamsSchema = z42.object({
16845
- name: z42.string().min(1).max(160)
16987
+ skillParamsSchema = z43.object({
16988
+ name: z43.string().min(1).max(160)
16846
16989
  });
16847
- skillListQuerySchema = z42.object({
16990
+ skillListQuerySchema = z43.object({
16848
16991
  sessionId: sessionIdentifierSchema.optional()
16849
16992
  });
16850
- mcpServerParamsSchema = z42.object({
16851
- name: z42.string().min(1).max(120)
16993
+ mcpServerParamsSchema = z43.object({
16994
+ name: z43.string().min(1).max(120)
16852
16995
  });
16853
16996
  extensionRoutes = new Hono4().get("/skills", zValidator2("query", skillListQuerySchema), async (c) => {
16854
16997
  const { sessionId } = c.req.valid("query");
@@ -17017,6 +17160,16 @@ var init_web_routes = __esm(() => {
17017
17160
  import path24 from "path";
17018
17161
  import { accessSync, constants as fsConstants2 } from "fs";
17019
17162
  import { homedir as homedir4 } from "os";
17163
+ function unwrapClixml(stderr) {
17164
+ if (!stderr.startsWith("#< CLIXML"))
17165
+ return stderr;
17166
+ const errors = [...stderr.matchAll(/<S S="Error">([^<]*)<\/S>/g)].map((match) => match[1] ?? "");
17167
+ return errors.join("").replace(/_x000D_/g, "").replace(/_x000A_/g, " ");
17168
+ }
17169
+ function pickerDiagnostic(stderr) {
17170
+ const cleaned = unwrapClixml(stderr).replace(/[\u0000-\u001F\u007F]/g, " ").replace(/ {2,}/g, " ").trim().slice(0, maximumDiagnosticLength);
17171
+ return cleaned || undefined;
17172
+ }
17020
17173
  async function readBoundedStream(stream, maximumBytes) {
17021
17174
  const reader = stream.getReader();
17022
17175
  const chunks = [];
@@ -17059,7 +17212,7 @@ function assertPickerPath(value, pathApi) {
17059
17212
  function parseWindowsResult(result) {
17060
17213
  const output = stripToolLineEnding(result.stdout);
17061
17214
  if (result.exitCode !== 0) {
17062
- throw new NativeDirectoryPickerError("native_picker_failed", "The Windows folder picker could not be opened.");
17215
+ throw new NativeDirectoryPickerError("native_picker_failed", "The Windows folder picker could not be opened.", pickerDiagnostic(result.stderr));
17063
17216
  }
17064
17217
  if (output === "cancelled")
17065
17218
  return { outcome: "cancelled" };
@@ -17090,7 +17243,7 @@ function parseMacosResult(result) {
17090
17243
  if (/\(-128\)|user canceled/i.test(result.stderr)) {
17091
17244
  return { outcome: "cancelled" };
17092
17245
  }
17093
- throw new NativeDirectoryPickerError("native_picker_failed", "The macOS folder picker could not be opened.");
17246
+ throw new NativeDirectoryPickerError("native_picker_failed", "The macOS folder picker could not be opened.", pickerDiagnostic(result.stderr));
17094
17247
  }
17095
17248
  return {
17096
17249
  outcome: "selected",
@@ -17101,7 +17254,7 @@ function parseLinuxResult(result) {
17101
17254
  if (result.exitCode === 1)
17102
17255
  return { outcome: "cancelled" };
17103
17256
  if (result.exitCode !== 0) {
17104
- throw new NativeDirectoryPickerError("native_picker_failed", "The Linux folder picker could not be opened.");
17257
+ throw new NativeDirectoryPickerError("native_picker_failed", "The Linux folder picker could not be opened.", pickerDiagnostic(result.stderr));
17105
17258
  }
17106
17259
  return {
17107
17260
  outcome: "selected",
@@ -17229,7 +17382,7 @@ function createNativeDirectoryPicker(options = {}) {
17229
17382
  }
17230
17383
  };
17231
17384
  }
17232
- var maximumPickerOutputBytes, NativeDirectoryPickerError, trustedLinuxExecutableDirectories, PickerOutputTooLargeError, runNativePickerProcess = async (command, signal) => {
17385
+ var maximumPickerOutputBytes, NativeDirectoryPickerError, maximumDiagnosticLength = 500, trustedLinuxExecutableDirectories, PickerOutputTooLargeError, runNativePickerProcess = async (command, signal) => {
17233
17386
  if (signal?.aborted) {
17234
17387
  return { exitCode: 1, stdout: "", stderr: "" };
17235
17388
  }
@@ -17274,9 +17427,11 @@ var init_native_directory_picker = __esm(() => {
17274
17427
  maximumPickerOutputBytes = 16 * 1024;
17275
17428
  NativeDirectoryPickerError = class NativeDirectoryPickerError extends Error {
17276
17429
  code;
17277
- constructor(code, message) {
17430
+ diagnostic;
17431
+ constructor(code, message, diagnostic) {
17278
17432
  super(message);
17279
17433
  this.code = code;
17434
+ this.diagnostic = diagnostic;
17280
17435
  this.name = "NativeDirectoryPickerError";
17281
17436
  }
17282
17437
  };
@@ -17289,20 +17444,31 @@ var init_native_directory_picker = __esm(() => {
17289
17444
  };
17290
17445
  windowsPickerScript = String.raw`
17291
17446
  $ErrorActionPreference = 'Stop'
17447
+ $ProgressPreference = 'SilentlyContinue'
17292
17448
  [Console]::OutputEncoding = [Text.UTF8Encoding]::new($false)
17293
17449
  Add-Type -AssemblyName System.Windows.Forms
17294
17450
  $dialog = New-Object System.Windows.Forms.FolderBrowserDialog
17295
17451
  $dialog.Description = 'Choose a project folder'
17296
17452
  $dialog.ShowNewFolderButton = $true
17297
- $dialog.AutoUpgradeEnabled = $true
17298
17453
  $owner = New-Object System.Windows.Forms.Form
17454
+ $owner.Text = 'Lightcode - choose a project folder'
17299
17455
  $owner.TopMost = $true
17300
- $owner.ShowInTaskbar = $false
17456
+ # A taskbar button keeps the dialog findable when Windows refuses to bring it
17457
+ # in front of the browser.
17458
+ $owner.ShowInTaskbar = $true
17301
17459
  $owner.StartPosition = [System.Windows.Forms.FormStartPosition]::Manual
17302
- $owner.Location = New-Object System.Drawing.Point(-32000, -32000)
17460
+ $workArea = [System.Windows.Forms.Screen]::PrimaryScreen.WorkingArea
17461
+ $owner.Location = New-Object System.Drawing.Point(
17462
+ [int]($workArea.Left + $workArea.Width / 2),
17463
+ [int]($workArea.Top + $workArea.Height / 2))
17303
17464
  $owner.Size = New-Object System.Drawing.Size(1, 1)
17304
17465
  $owner.Opacity = 0
17466
+ # lightcode:show-dialog
17305
17467
  try {
17468
+ # The spawner's windowsHide turns the process's first show into SW_HIDE.
17469
+ # Showing again makes the owner, and its taskbar button, actually visible.
17470
+ $owner.Show()
17471
+ $owner.Hide()
17306
17472
  $owner.Show()
17307
17473
  $owner.Activate()
17308
17474
  $result = $dialog.ShowDialog($owner)
@@ -17327,6 +17493,187 @@ end try
17327
17493
  `;
17328
17494
  });
17329
17495
 
17496
+ // apps/server/src/lib/windows-known-folders.ts
17497
+ import path25 from "path";
17498
+ import { z as z44 } from "zod";
17499
+ function parseWindowsKnownFoldersOutput(output) {
17500
+ if (Buffer.byteLength(output) > 16384)
17501
+ return null;
17502
+ try {
17503
+ const parsed = knownFoldersOutputSchema.safeParse(JSON.parse(output));
17504
+ return parsed.success ? parsed.data : null;
17505
+ } catch {
17506
+ return null;
17507
+ }
17508
+ }
17509
+ async function runKnownFoldersResolver(environment) {
17510
+ try {
17511
+ const systemRootCandidate = environment.SystemRoot?.trim() || environment.WINDIR?.trim() || "C:\\Windows";
17512
+ if (!path25.win32.isAbsolute(systemRootCandidate))
17513
+ return null;
17514
+ const windowsRoot = path25.win32.normalize(systemRootCandidate);
17515
+ if (windowsRoot === path25.win32.parse(windowsRoot).root)
17516
+ return null;
17517
+ const systemDirectory = path25.win32.join(windowsRoot, "System32");
17518
+ const executable = path25.win32.join(systemDirectory, "WindowsPowerShell", "v1.0", "powershell.exe");
17519
+ const child = Bun.spawn([
17520
+ executable,
17521
+ "-NoLogo",
17522
+ "-NoProfile",
17523
+ "-NonInteractive",
17524
+ "-Command",
17525
+ knownFoldersScript
17526
+ ], {
17527
+ cwd: systemDirectory,
17528
+ env: {
17529
+ SystemRoot: windowsRoot,
17530
+ WINDIR: windowsRoot,
17531
+ ...Object.fromEntries(["USERPROFILE", "APPDATA", "LOCALAPPDATA", "TEMP", "TMP"].flatMap((name) => environment[name] ? [[name, environment[name]]] : []))
17532
+ },
17533
+ stdout: "pipe",
17534
+ stderr: "ignore",
17535
+ stdin: "ignore",
17536
+ windowsHide: true
17537
+ });
17538
+ const timeout = setTimeout(() => child.kill(), resolverKillMs);
17539
+ timeout.unref();
17540
+ try {
17541
+ const [output, exitCode] = await Promise.all([
17542
+ new Response(child.stdout).text(),
17543
+ child.exited
17544
+ ]);
17545
+ return exitCode === 0 ? parseWindowsKnownFoldersOutput(output) : null;
17546
+ } finally {
17547
+ clearTimeout(timeout);
17548
+ }
17549
+ } catch {
17550
+ return null;
17551
+ }
17552
+ }
17553
+ function cacheKey(environment) {
17554
+ return JSON.stringify(["SystemRoot", "WINDIR", "USERPROFILE", "APPDATA", "LOCALAPPDATA"].map((name) => environment[name] ?? null));
17555
+ }
17556
+ function resolveWindowsKnownFolders(environment, run = runKnownFoldersResolver) {
17557
+ const key = cacheKey(environment);
17558
+ const cached = resolverCache.get(key);
17559
+ if (cached)
17560
+ return cached;
17561
+ const pending = run(environment).then((folders) => {
17562
+ if (!folders)
17563
+ resolverCache.delete(key);
17564
+ return folders;
17565
+ });
17566
+ resolverCache.set(key, pending);
17567
+ return pending;
17568
+ }
17569
+ async function resolveWindowsKnownFolder(environment, folder, {
17570
+ budgetMs = defaultWaitBudgetMs,
17571
+ run
17572
+ } = {}) {
17573
+ let timer;
17574
+ const budget = new Promise((resolve2) => {
17575
+ timer = setTimeout(() => resolve2(null), budgetMs);
17576
+ });
17577
+ try {
17578
+ const folders = await Promise.race([
17579
+ resolveWindowsKnownFolders(environment, run),
17580
+ budget
17581
+ ]);
17582
+ return folders?.[folder] ?? null;
17583
+ } finally {
17584
+ clearTimeout(timer);
17585
+ }
17586
+ }
17587
+ var resolverKillMs = 20000, defaultWaitBudgetMs = 2000, downloadsFolderGuid = "{374DE290-123F-4565-9164-39C4925E467B}", knownFoldersScript, knownFolderValueSchema, knownFoldersOutputSchema, resolverCache;
17588
+ var init_windows_known_folders = __esm(() => {
17589
+ knownFoldersScript = [
17590
+ "[Console]::OutputEncoding=[Text.UTF8Encoding]::new($false)",
17591
+ "$desktop=[Environment]::GetFolderPath([Environment+SpecialFolder]::DesktopDirectory)",
17592
+ "$documents=[Environment]::GetFolderPath([Environment+SpecialFolder]::MyDocuments)",
17593
+ `$downloads=try{[Environment]::ExpandEnvironmentVariables([string](Get-ItemProperty -LiteralPath 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders' -Name '${downloadsFolderGuid}' -ErrorAction Stop).'${downloadsFolderGuid}')}catch{''}`,
17594
+ "[Console]::Write((@{desktop=$desktop;documents=$documents;downloads=$downloads}|ConvertTo-Json -Compress))"
17595
+ ].join(";");
17596
+ knownFolderValueSchema = z44.string().max(4096).transform((value) => value.trim()).transform((value) => value && !/[\r\n\0]/.test(value) && path25.win32.isAbsolute(value) ? value : null);
17597
+ knownFoldersOutputSchema = z44.object({
17598
+ desktop: knownFolderValueSchema,
17599
+ documents: knownFolderValueSchema,
17600
+ downloads: knownFolderValueSchema
17601
+ });
17602
+ resolverCache = new Map;
17603
+ });
17604
+
17605
+ // apps/server/src/lib/workspace-entry-filter.ts
17606
+ function isHiddenBrowserEntry(entry, platform4 = process.platform) {
17607
+ if (entry.name.startsWith("."))
17608
+ return true;
17609
+ if (platform4 !== "win32")
17610
+ return false;
17611
+ if (windowsSystemEntryPattern.test(entry.name))
17612
+ return true;
17613
+ return entry.isSymbolicLink() && windowsLegacyJunctionNames.has(entry.name.toLowerCase());
17614
+ }
17615
+ function isOmittedFromFolderListing(entry) {
17616
+ return entry.isFile();
17617
+ }
17618
+ var windowsSystemEntryPattern, windowsLegacyJunctionNames;
17619
+ var init_workspace_entry_filter = __esm(() => {
17620
+ windowsSystemEntryPattern = /^(?:ntuser\.(?:dat|ini|pol)(?:[.{].*)?|desktop\.ini|thumbs\.db|\$recycle\.bin|system volume information|pagefile\.sys|hiberfil\.sys|swapfile\.sys|dumpstack\.log(?:\.tmp)?|config\.msi|\$windows\.~(?:bt|ws)|\$winreagent|\$sysreset)$/i;
17621
+ windowsLegacyJunctionNames = new Set([
17622
+ "application data",
17623
+ "cookies",
17624
+ "local settings",
17625
+ "my documents",
17626
+ "nethood",
17627
+ "printhood",
17628
+ "recent",
17629
+ "sendto",
17630
+ "start menu",
17631
+ "templates",
17632
+ "my music",
17633
+ "my pictures",
17634
+ "my videos",
17635
+ "documents and settings"
17636
+ ]);
17637
+ });
17638
+
17639
+ // apps/server/src/lib/workspace-drives.ts
17640
+ import { promises as fs8 } from "fs";
17641
+ async function probeDriveRoot(root) {
17642
+ let timer;
17643
+ const timeout = new Promise((resolve2) => {
17644
+ timer = setTimeout(() => resolve2("unresponsive"), driveProbeTimeoutMs);
17645
+ });
17646
+ try {
17647
+ return await Promise.race([
17648
+ fs8.stat(root).then((stats) => stats.isDirectory() ? "present" : "absent").catch((error) => {
17649
+ const code = typeof error === "object" && error !== null ? Reflect.get(error, "code") : undefined;
17650
+ return code === "EPERM" || code === "EACCES" || code === "EBUSY" ? "unresponsive" : "absent";
17651
+ }),
17652
+ timeout
17653
+ ]);
17654
+ } finally {
17655
+ clearTimeout(timer);
17656
+ }
17657
+ }
17658
+ async function listWindowsDrives(probe = probeDriveRoot) {
17659
+ const letters = Array.from({ length: 24 }, (_2, index) => String.fromCharCode(67 + index));
17660
+ const results = await Promise.all(letters.map(async (letter) => ({
17661
+ token: `${letter}:`,
17662
+ state: await probe(`${letter}:\\`)
17663
+ })));
17664
+ return results.filter((result) => result.state !== "absent").map((result) => ({
17665
+ token: result.token,
17666
+ readable: result.state === "present"
17667
+ }));
17668
+ }
17669
+ function windowsDriveRoot(token) {
17670
+ return `${token}\\`;
17671
+ }
17672
+ var windowsDriveTokenPattern, driveProbeTimeoutMs = 1500;
17673
+ var init_workspace_drives = __esm(() => {
17674
+ windowsDriveTokenPattern = /^[A-Z]:$/;
17675
+ });
17676
+
17330
17677
  // apps/server/src/routes/workspace-routes.ts
17331
17678
  import { zValidator as zValidator3 } from "@hono/zod-validator";
17332
17679
  import {
@@ -17335,12 +17682,17 @@ import {
17335
17682
  randomBytes,
17336
17683
  timingSafeEqual as timingSafeEqual2
17337
17684
  } from "crypto";
17338
- import { constants as fsConstants3, promises as fs8 } from "fs";
17685
+ import { constants as fsConstants3, promises as fs9 } from "fs";
17339
17686
  import { homedir as homedir5 } from "os";
17340
- import path25 from "path";
17687
+ import path26 from "path";
17341
17688
  import { Hono as Hono7 } from "hono";
17689
+ import { z as z45 } from "zod";
17690
+ function parseLaunchFolderPath(value) {
17691
+ const parsed = launchFolderEnvironmentSchema.safeParse(value);
17692
+ return parsed.success ? normalizeKnownFolderCandidate(parsed.data, process.platform) : null;
17693
+ }
17342
17694
  function pathImplementation(platform4) {
17343
- return platform4 === "win32" ? path25.win32 : path25.posix;
17695
+ return platform4 === "win32" ? path26.win32 : path26.posix;
17344
17696
  }
17345
17697
  function isFilesystemRootPath(candidate, platform4 = process.platform) {
17346
17698
  const pathApi = pathImplementation(platform4);
@@ -17363,7 +17715,7 @@ function normalizeKnownFolderCandidate(candidate, platform4) {
17363
17715
  }
17364
17716
  async function probeDirectory(candidate) {
17365
17717
  try {
17366
- const metadata = await fs8.stat(candidate);
17718
+ const metadata = await fs9.stat(candidate);
17367
17719
  return metadata.isDirectory() ? "available" : "missing";
17368
17720
  } catch (error) {
17369
17721
  const code = filesystemErrorCode(error);
@@ -17377,7 +17729,7 @@ async function readSmallUserDirsConfiguration(filePath) {
17377
17729
  const noFollowFlag = typeof fsConstants3.O_NOFOLLOW === "number" ? fsConstants3.O_NOFOLLOW : 0;
17378
17730
  let handle;
17379
17731
  try {
17380
- handle = await fs8.open(filePath, fsConstants3.O_RDONLY | noFollowFlag);
17732
+ handle = await fs9.open(filePath, fsConstants3.O_RDONLY | noFollowFlag);
17381
17733
  const metadata = await handle.stat();
17382
17734
  if (!metadata.isFile() || metadata.size <= 0 || metadata.size > maximumUserDirsConfigurationBytes) {
17383
17735
  return null;
@@ -17391,59 +17743,6 @@ async function readSmallUserDirsConfiguration(filePath) {
17391
17743
  });
17392
17744
  }
17393
17745
  }
17394
- async function resolveWindowsKnownFolder(environment, folder) {
17395
- try {
17396
- const systemRootCandidate = environment.SystemRoot?.trim() || environment.WINDIR?.trim() || "C:\\Windows";
17397
- const windowsRoot = normalizeKnownFolderCandidate(systemRootCandidate, "win32");
17398
- if (!windowsRoot) {
17399
- return null;
17400
- }
17401
- const systemDirectory = path25.win32.join(windowsRoot, "System32");
17402
- const executable = path25.win32.join(systemDirectory, "WindowsPowerShell", "v1.0", "powershell.exe");
17403
- const folderExpression = folder === "desktop" ? "[Environment]::GetFolderPath([Environment+SpecialFolder]::DesktopDirectory)" : folder === "documents" ? "[Environment]::GetFolderPath([Environment+SpecialFolder]::MyDocuments)" : "[Environment]::ExpandEnvironmentVariables([string](Get-ItemProperty -LiteralPath 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders' -Name '{374DE290-123F-4565-9164-39C4925E467B}' -ErrorAction Stop).'{374DE290-123F-4565-9164-39C4925E467B}')";
17404
- const child = Bun.spawn([
17405
- executable,
17406
- "-NoLogo",
17407
- "-NoProfile",
17408
- "-NonInteractive",
17409
- "-Command",
17410
- `[Console]::OutputEncoding=[Text.UTF8Encoding]::new($false);[Console]::Write(${folderExpression})`
17411
- ], {
17412
- cwd: systemDirectory,
17413
- env: {
17414
- SystemRoot: windowsRoot,
17415
- WINDIR: windowsRoot,
17416
- ...Object.fromEntries([
17417
- "USERPROFILE",
17418
- "APPDATA",
17419
- "LOCALAPPDATA",
17420
- "TEMP",
17421
- "TMP"
17422
- ].flatMap((name) => environment[name] ? [[name, environment[name]]] : []))
17423
- },
17424
- stdout: "pipe",
17425
- stderr: "ignore",
17426
- stdin: "ignore"
17427
- });
17428
- const timeout = setTimeout(() => child.kill(), 2000);
17429
- timeout.unref();
17430
- try {
17431
- const [output, exitCode] = await Promise.all([
17432
- new Response(child.stdout).text(),
17433
- child.exited
17434
- ]);
17435
- if (exitCode !== 0 || Buffer.byteLength(output) > 4096) {
17436
- return null;
17437
- }
17438
- const candidate = output.trim();
17439
- return candidate && !/[\r\n]/.test(candidate) ? candidate : null;
17440
- } finally {
17441
- clearTimeout(timeout);
17442
- }
17443
- } catch {
17444
- return null;
17445
- }
17446
- }
17447
17746
  function parseXdgUserDirectory(contents, homeDirectory, variableName) {
17448
17747
  for (const line of contents.split(/\r?\n/)) {
17449
17748
  const match = line.match(/^\s*(XDG_(?:DESKTOP|DOCUMENTS|DOWNLOAD)_DIR)="([^"\r\n]*)"\s*$/);
@@ -17458,15 +17757,15 @@ function parseXdgUserDirectory(contents, homeDirectory, variableName) {
17458
17757
  return null;
17459
17758
  }
17460
17759
  if (configured.startsWith("$HOME/")) {
17461
- return path25.posix.resolve(homeDirectory, configured.slice(6));
17760
+ return path26.posix.resolve(homeDirectory, configured.slice(6));
17462
17761
  }
17463
17762
  if (configured.startsWith("${HOME}/")) {
17464
- return path25.posix.resolve(homeDirectory, configured.slice(8));
17763
+ return path26.posix.resolve(homeDirectory, configured.slice(8));
17465
17764
  }
17466
17765
  if (configured.includes("$")) {
17467
17766
  return null;
17468
17767
  }
17469
- return path25.posix.isAbsolute(configured) ? path25.posix.normalize(configured) : null;
17768
+ return path26.posix.isAbsolute(configured) ? path26.posix.normalize(configured) : null;
17470
17769
  }
17471
17770
  return null;
17472
17771
  }
@@ -17520,17 +17819,17 @@ async function resolveDesktopDirectoryPath(options = {}) {
17520
17819
  return [];
17521
17820
  }
17522
17821
  return [
17523
- index === 0 ? trimmed : path25.win32.join(trimmed, "Desktop")
17822
+ index === 0 ? trimmed : path26.win32.join(trimmed, "Desktop")
17524
17823
  ];
17525
17824
  });
17526
- candidates.push(path25.win32.join(userProfile, "Desktop"));
17527
- return await firstUsableKnownFolder(candidates, platform4, probe) ?? path25.win32.join(homeDirectory, "Desktop");
17825
+ candidates.push(path26.win32.join(userProfile, "Desktop"));
17826
+ return await firstUsableKnownFolder(candidates, platform4, probe) ?? path26.win32.join(homeDirectory, "Desktop");
17528
17827
  }
17529
17828
  if (platform4 === "linux") {
17530
17829
  const configHome = environment.XDG_CONFIG_HOME?.trim();
17531
- const safeConfigHome = configHome && path25.posix.isAbsolute(configHome) ? path25.posix.normalize(configHome) : path25.posix.join(homeDirectory, ".config");
17830
+ const safeConfigHome = configHome && path26.posix.isAbsolute(configHome) ? path26.posix.normalize(configHome) : path26.posix.join(homeDirectory, ".config");
17532
17831
  const readConfiguration = options.readUserDirsConfiguration ?? readSmallUserDirsConfiguration;
17533
- const contents = await readConfiguration(path25.posix.join(safeConfigHome, "user-dirs.dirs"));
17832
+ const contents = await readConfiguration(path26.posix.join(safeConfigHome, "user-dirs.dirs"));
17534
17833
  const configured = contents ? parseXdgUserDirectory(contents, homeDirectory, "XDG_DESKTOP_DIR") : null;
17535
17834
  if (configured) {
17536
17835
  const selected = await firstUsableKnownFolder([configured], platform4, probe);
@@ -17567,17 +17866,17 @@ async function resolveUserDirectoryPath(folder, options) {
17567
17866
  ];
17568
17867
  const candidates = [
17569
17868
  knownFolder,
17570
- ...cloudRoots.map((root) => root?.trim() ? path25.win32.join(root.trim(), displayName) : null),
17571
- path25.win32.join(userProfile, displayName),
17572
- path25.win32.join(homeDirectory, displayName)
17869
+ ...cloudRoots.map((root) => root?.trim() ? path26.win32.join(root.trim(), displayName) : null),
17870
+ path26.win32.join(userProfile, displayName),
17871
+ path26.win32.join(homeDirectory, displayName)
17573
17872
  ];
17574
- return await firstUsableKnownFolder(candidates, platform4, probe) ?? path25.win32.join(homeDirectory, displayName);
17873
+ return await firstUsableKnownFolder(candidates, platform4, probe) ?? path26.win32.join(homeDirectory, displayName);
17575
17874
  }
17576
17875
  if (platform4 === "linux") {
17577
17876
  const configHome = environment.XDG_CONFIG_HOME?.trim();
17578
- const safeConfigHome = configHome && path25.posix.isAbsolute(configHome) ? path25.posix.normalize(configHome) : path25.posix.join(homeDirectory, ".config");
17877
+ const safeConfigHome = configHome && path26.posix.isAbsolute(configHome) ? path26.posix.normalize(configHome) : path26.posix.join(homeDirectory, ".config");
17579
17878
  const readConfiguration = options.readUserDirsConfiguration ?? readSmallUserDirsConfiguration;
17580
- const contents = await readConfiguration(path25.posix.join(safeConfigHome, "user-dirs.dirs"));
17879
+ const contents = await readConfiguration(path26.posix.join(safeConfigHome, "user-dirs.dirs"));
17581
17880
  const configured = contents ? parseXdgUserDirectory(contents, homeDirectory, folder === "documents" ? "XDG_DOCUMENTS_DIR" : "XDG_DOWNLOAD_DIR") : null;
17582
17881
  const selected = await firstUsableKnownFolder([configured], platform4, probe);
17583
17882
  if (selected) {
@@ -17648,6 +17947,17 @@ function mapFilesystemError(error) {
17648
17947
  return new WorkspaceRouteError("workspace_unavailable", "Unable to access the selected workspace.", 500, true);
17649
17948
  }
17650
17949
  function mapNativePickerError(error) {
17950
+ if (error instanceof NativeDirectoryPickerError) {
17951
+ logger13.warn("native_picker_error", {
17952
+ code: error.code,
17953
+ ...error.diagnostic ? { diagnostic: error.diagnostic } : {}
17954
+ });
17955
+ } else {
17956
+ logger13.warn("native_picker_error", {
17957
+ code: "native_picker_failed",
17958
+ error: getErrorMessage(error)
17959
+ });
17960
+ }
17651
17961
  if (!(error instanceof NativeDirectoryPickerError)) {
17652
17962
  return new WorkspaceRouteError("native_picker_failed", "The system folder picker could not be opened.", 500, true);
17653
17963
  }
@@ -17688,17 +17998,21 @@ function rootIdentity(stats) {
17688
17998
  function identitiesMatch(left, right) {
17689
17999
  return left.device === right.device && left.inode === right.inode;
17690
18000
  }
18001
+ async function realpathPreservingDriveRoot(candidate) {
18002
+ const resolved = await fs9.realpath(candidate);
18003
+ return /^[A-Za-z]:$/.test(resolved) ? `${resolved}${path26.win32.sep}` : resolved;
18004
+ }
17691
18005
  function isContainedPath2(root, candidate) {
17692
- const relative = path25.relative(root, candidate);
17693
- return relative === "" || relative !== ".." && !relative.startsWith(`..${path25.sep}`) && !path25.isAbsolute(relative);
18006
+ const relative = path26.relative(root, candidate);
18007
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path26.sep}`) && !path26.isAbsolute(relative);
17694
18008
  }
17695
18009
  async function resolveKnownDirectory(directory) {
17696
18010
  try {
17697
- const root = await fs8.realpath(path25.resolve(directory));
18011
+ const root = await realpathPreservingDriveRoot(path26.resolve(directory));
17698
18012
  if (isFilesystemRootPath(root)) {
17699
18013
  throw new WorkspaceRouteError("workspace_unavailable", "A filesystem root cannot be opened as a workspace location.", 500, false);
17700
18014
  }
17701
- const stats = await fs8.stat(root, { bigint: true });
18015
+ const stats = await fs9.stat(root, { bigint: true });
17702
18016
  if (!stats.isDirectory()) {
17703
18017
  throw new WorkspaceRouteError("workspace_not_directory", "The selected path is not a directory.", 422, false);
17704
18018
  }
@@ -17715,8 +18029,8 @@ async function assertRootIdentity({
17715
18029
  identity
17716
18030
  }) {
17717
18031
  try {
17718
- const canonical = await fs8.realpath(root);
17719
- const stats = await fs8.stat(canonical, { bigint: true });
18032
+ const canonical = await realpathPreservingDriveRoot(root);
18033
+ const stats = await fs9.stat(canonical, { bigint: true });
17720
18034
  if (canonical !== root || !stats.isDirectory() || !identitiesMatch(identity, rootIdentity(stats))) {
17721
18035
  throw new WorkspaceRouteError("workspace_replaced", "The selected workspace changed on disk. Select it again before continuing.", 409, true);
17722
18036
  }
@@ -17729,7 +18043,7 @@ async function assertRootIdentity({
17729
18043
  }
17730
18044
  async function symlinkFailure(root, candidate, locationName) {
17731
18045
  try {
17732
- const target = await fs8.realpath(candidate);
18046
+ const target = await realpathPreservingDriveRoot(candidate);
17733
18047
  if (!isContainedPath2(root, target)) {
17734
18048
  return new WorkspaceRouteError("workspace_symlink_escape", `This symbolic link points outside the allowed ${locationName} location.`, 403, false);
17735
18049
  }
@@ -17744,13 +18058,13 @@ async function symlinkFailure(root, candidate, locationName) {
17744
18058
  async function resolveDirectoryFromSegments(root, segments, locationName) {
17745
18059
  let current = root;
17746
18060
  for (const segment of segments) {
17747
- const candidate = path25.join(current, segment);
18061
+ const candidate = path26.join(current, segment);
17748
18062
  if (!isContainedPath2(root, candidate)) {
17749
18063
  throw new WorkspaceRouteError("workspace_symlink_escape", `The requested path is outside the allowed ${locationName} location.`, 403, false);
17750
18064
  }
17751
18065
  let stats;
17752
18066
  try {
17753
- stats = await fs8.lstat(candidate);
18067
+ stats = await fs9.lstat(candidate);
17754
18068
  } catch (error) {
17755
18069
  throw mapFilesystemError(error);
17756
18070
  }
@@ -17762,13 +18076,13 @@ async function resolveDirectoryFromSegments(root, segments, locationName) {
17762
18076
  }
17763
18077
  current = candidate;
17764
18078
  }
17765
- const canonical = await fs8.realpath(current).catch((error) => {
18079
+ const canonical = await realpathPreservingDriveRoot(current).catch((error) => {
17766
18080
  throw mapFilesystemError(error);
17767
18081
  });
17768
18082
  if (!isContainedPath2(root, canonical)) {
17769
18083
  throw new WorkspaceRouteError("workspace_symlink_escape", `The requested path is outside the allowed ${locationName} location.`, 403, false);
17770
18084
  }
17771
- const metadata = await fs8.lstat(canonical, { bigint: true });
18085
+ const metadata = await fs9.lstat(canonical, { bigint: true });
17772
18086
  if (metadata.isSymbolicLink() || !metadata.isDirectory()) {
17773
18087
  throw new WorkspaceRouteError("workspace_replaced", "The selected folder changed while it was being opened.", 409, true);
17774
18088
  }
@@ -17776,11 +18090,11 @@ async function resolveDirectoryFromSegments(root, segments, locationName) {
17776
18090
  }
17777
18091
  async function assertDirectoryIdentity(root, directory) {
17778
18092
  try {
17779
- const lexical = await fs8.lstat(directory.path, { bigint: true });
18093
+ const lexical = await fs9.lstat(directory.path, { bigint: true });
17780
18094
  if (lexical.isSymbolicLink() || !lexical.isDirectory()) {
17781
18095
  throw new WorkspaceRouteError("workspace_replaced", "The selected folder changed while it was being read.", 409, true);
17782
18096
  }
17783
- const canonical = await fs8.realpath(directory.path);
18097
+ const canonical = await realpathPreservingDriveRoot(directory.path);
17784
18098
  const currentIdentity = rootIdentity(lexical);
17785
18099
  if (canonical !== directory.path || !isContainedPath2(root, canonical) || !identitiesMatch(directory.identity, currentIdentity)) {
17786
18100
  throw new WorkspaceRouteError("workspace_replaced", "The selected folder changed while it was being read.", 409, true);
@@ -17793,6 +18107,9 @@ async function assertDirectoryIdentity(root, directory) {
17793
18107
  throw mapFilesystemError(error);
17794
18108
  }
17795
18109
  }
18110
+ function cursorViewFlags(binding) {
18111
+ return (binding.includeHidden ? 1 : 0) | (binding.directoriesOnly ? 2 : 0);
18112
+ }
17796
18113
  function cursorPathDigest(segments) {
17797
18114
  return createHash6("sha256").update(JSON.stringify(segments)).digest("base64url").slice(0, 22);
17798
18115
  }
@@ -17801,7 +18118,7 @@ function encodeCursor(secret, binding, offset) {
17801
18118
  1,
17802
18119
  binding.browserId,
17803
18120
  cursorPathDigest(binding.segments),
17804
- binding.includeHidden ? 1 : 0,
18121
+ cursorViewFlags(binding),
17805
18122
  offset,
17806
18123
  binding.directory.identity.device,
17807
18124
  binding.directory.identity.inode,
@@ -17831,7 +18148,7 @@ function decodeCursor(secret, cursor, binding) {
17831
18148
  mtimeNs,
17832
18149
  signature
17833
18150
  ] = parsed;
17834
- if (version2 !== 1 || browserId !== binding.browserId || pathDigest !== cursorPathDigest(binding.segments) || hidden !== (binding.includeHidden ? 1 : 0) || !Number.isSafeInteger(offset) || offset < 0 || device !== binding.directory.identity.device || inode !== binding.directory.identity.inode || mtimeNs !== binding.mtimeNs || typeof signature !== "string") {
18151
+ if (version2 !== 1 || browserId !== binding.browserId || pathDigest !== cursorPathDigest(binding.segments) || hidden !== cursorViewFlags(binding) || !Number.isSafeInteger(offset) || offset < 0 || device !== binding.directory.identity.device || inode !== binding.directory.identity.inode || mtimeNs !== binding.mtimeNs || typeof signature !== "string") {
17835
18152
  throw new Error("cursor binding mismatch");
17836
18153
  }
17837
18154
  const serialized = JSON.stringify(parsed.slice(0, 8));
@@ -17847,12 +18164,12 @@ function decodeCursor(secret, cursor, binding) {
17847
18164
  }
17848
18165
  }
17849
18166
  async function describeEntry(root, directory, name) {
17850
- const candidate = path25.join(directory, name);
18167
+ const candidate = path26.join(directory, name);
17851
18168
  try {
17852
- const stats = await fs8.lstat(candidate);
18169
+ const stats = await fs9.lstat(candidate);
17853
18170
  if (stats.isSymbolicLink()) {
17854
18171
  try {
17855
- const target = await fs8.realpath(candidate);
18172
+ const target = await realpathPreservingDriveRoot(candidate);
17856
18173
  return {
17857
18174
  name,
17858
18175
  kind: "symlink",
@@ -17872,7 +18189,7 @@ async function describeEntry(root, directory, name) {
17872
18189
  }
17873
18190
  const kind = stats.isDirectory() ? "directory" : stats.isFile() ? "file" : "other";
17874
18191
  const requiredAccess = stats.isDirectory() ? fsConstants3.R_OK | fsConstants3.X_OK : fsConstants3.R_OK;
17875
- const readable = await fs8.access(candidate, requiredAccess).then(() => true).catch(() => false);
18192
+ const readable = await fs9.access(candidate, requiredAccess).then(() => true).catch(() => false);
17876
18193
  return {
17877
18194
  name,
17878
18195
  kind,
@@ -17911,7 +18228,7 @@ function knownLocation(definition, state) {
17911
18228
  return {
17912
18229
  id: definition.id,
17913
18230
  name: definition.name,
17914
- kind: "known-folder",
18231
+ kind: definition.kind,
17915
18232
  state,
17916
18233
  pathLabel: definition.pathLabel
17917
18234
  };
@@ -17933,6 +18250,7 @@ function createWorkspaceRoutes(options = {}) {
17933
18250
  const definitions = [
17934
18251
  {
17935
18252
  id: "desktop",
18253
+ kind: "known-folder",
17936
18254
  name: "Desktop",
17937
18255
  rootName: "Desktop",
17938
18256
  pathLabel: options.locationPathLabels?.desktop ?? desktopPathLabel,
@@ -17940,6 +18258,7 @@ function createWorkspaceRoutes(options = {}) {
17940
18258
  },
17941
18259
  {
17942
18260
  id: "home",
18261
+ kind: "known-folder",
17943
18262
  name: "Home (broad access)",
17944
18263
  rootName: "Home",
17945
18264
  pathLabel: options.locationPathLabels?.home ?? "Home",
@@ -17947,6 +18266,7 @@ function createWorkspaceRoutes(options = {}) {
17947
18266
  },
17948
18267
  {
17949
18268
  id: "documents",
18269
+ kind: "known-folder",
17950
18270
  name: "Documents",
17951
18271
  rootName: "Documents",
17952
18272
  pathLabel: options.locationPathLabels?.documents ?? "Documents",
@@ -17954,6 +18274,7 @@ function createWorkspaceRoutes(options = {}) {
17954
18274
  },
17955
18275
  {
17956
18276
  id: "downloads",
18277
+ kind: "known-folder",
17957
18278
  name: "Downloads",
17958
18279
  rootName: "Downloads",
17959
18280
  pathLabel: options.locationPathLabels?.downloads ?? "Downloads",
@@ -17961,16 +18282,45 @@ function createWorkspaceRoutes(options = {}) {
17961
18282
  },
17962
18283
  {
17963
18284
  id: "projects",
18285
+ kind: "known-folder",
17964
18286
  name: "Projects",
17965
18287
  rootName: "Projects",
17966
18288
  pathLabel: options.locationPathLabels?.projects ?? "Projects",
17967
18289
  resolvePath: configuredKnownLocationResolver(options, "projects", () => resolveProjectsDirectoryPath())
17968
18290
  }
17969
18291
  ];
18292
+ if (options.enableDrives ?? process.platform === "win32") {
18293
+ definitions.push({
18294
+ id: "drives",
18295
+ kind: "drives",
18296
+ name: "This PC",
18297
+ rootName: "This PC",
18298
+ pathLabel: "This PC",
18299
+ resolvePath: async () => {
18300
+ throw new Error("The Drives location has no single root.");
18301
+ }
18302
+ });
18303
+ }
18304
+ const listDrives = options.listDrives ?? (() => listWindowsDrives());
18305
+ const driveRoot = options.driveRoot ?? windowsDriveRoot;
18306
+ const usesDefaultKnownFolders = !options.locationPaths && !options.resolveLocationPaths && !options.resolveDesktopPath && !options.desktopPath;
18307
+ const prewarmKnownFolders = options.prewarmKnownFolders ?? (process.platform === "win32" && usesDefaultKnownFolders ? () => void resolveWindowsKnownFolders(Bun.env) : () => {
18308
+ return;
18309
+ });
18310
+ const listRecentWorkspaces2 = options.listRecentWorkspaces ?? listRecentWorkspaces;
18311
+ const recentKeySecret = randomBytes(32);
18312
+ const recentWorkspaceKey = (cwd) => createHmac("sha256", recentKeySecret).update(process.platform === "win32" ? cwd.toLowerCase() : cwd).digest("base64url").slice(0, 22);
17970
18313
  const definitionsById = new Map(definitions.map((definition) => [definition.id, definition]));
17971
18314
  const createSession = options.createSession ?? createChatSession;
17972
18315
  const nativeDirectoryPicker = options.nativeDirectoryPicker ?? createNativeDirectoryPicker();
17973
18316
  const authorizeNativePickerRequest = options.authorizeNativePickerRequest ?? isAuthenticatedWebRequest;
18317
+ const launchFolderPath = parseLaunchFolderPath(options.launchFolderPath === undefined ? Bun.env.LIGHTCODE_WEB_LAUNCH_CWD : options.launchFolderPath);
18318
+ const resolveLaunchFolder = async () => {
18319
+ if (!launchFolderPath) {
18320
+ throw new WorkspaceRouteError("workspace_unavailable", "Lightcode was not started from a project folder.", 404, false);
18321
+ }
18322
+ return resolveKnownDirectory(launchFolderPath);
18323
+ };
17974
18324
  const cursorSecret = randomBytes(32);
17975
18325
  const browserCapabilities = new Map;
17976
18326
  const workspaceGrants = new Map;
@@ -17993,9 +18343,10 @@ function createWorkspaceRoutes(options = {}) {
17993
18343
  root,
17994
18344
  identity,
17995
18345
  name,
17996
- pathLabel
18346
+ pathLabel,
18347
+ request
17997
18348
  }) => {
17998
- await fs8.access(root, fsConstants3.R_OK | fsConstants3.X_OK);
18349
+ await fs9.access(root, fsConstants3.R_OK | fsConstants3.X_OK);
17999
18350
  const createdAt = new Date(now()).toISOString();
18000
18351
  const id = crypto.randomUUID();
18001
18352
  const grant = {
@@ -18014,7 +18365,27 @@ function createWorkspaceRoutes(options = {}) {
18014
18365
  workspaceGrants.delete(id);
18015
18366
  throw error;
18016
18367
  }
18017
- return workspaceGrantSchema.parse({ id, name, pathLabel, createdAt });
18368
+ return workspaceGrantSchema.parse({
18369
+ id,
18370
+ name,
18371
+ pathLabel,
18372
+ ...authorizeNativePickerRequest(request) ? { hostPath: root } : {},
18373
+ createdAt
18374
+ });
18375
+ };
18376
+ const resolveBrowseScope = async (capability2, segments) => {
18377
+ if (capability2.kind === "folder") {
18378
+ await assertRootIdentity(capability2);
18379
+ return { root: capability2.root, segments, rootName: capability2.rootName };
18380
+ }
18381
+ const [token, ...rest] = segments;
18382
+ if (!token || !windowsDriveTokenPattern.test(token) || !capability2.driveTokens?.has(token)) {
18383
+ throw new WorkspaceRouteError("location_not_found", "That drive is not available. Open This PC again.", 404, true);
18384
+ }
18385
+ const root = await realpathPreservingDriveRoot(driveRoot(token)).catch((error) => {
18386
+ throw mapFilesystemError(error);
18387
+ });
18388
+ return { root, segments: rest, rootName: token };
18018
18389
  };
18019
18390
  return new Hono7().post("/picker/open", zValidator3("json", workspaceNativePickerRequestSchema, (result, c) => {
18020
18391
  if (!result.success) {
@@ -18030,7 +18401,10 @@ function createWorkspaceRoutes(options = {}) {
18030
18401
  try {
18031
18402
  let picked;
18032
18403
  try {
18033
- picked = await nativeDirectoryPicker.pick(c.req.raw.signal);
18404
+ picked = await nativeDirectoryPicker.pick(AbortSignal.any([
18405
+ c.req.raw.signal,
18406
+ AbortSignal.timeout(nativePickerMaximumMs)
18407
+ ]));
18034
18408
  } catch (error) {
18035
18409
  throw mapNativePickerError(error);
18036
18410
  }
@@ -18041,8 +18415,9 @@ function createWorkspaceRoutes(options = {}) {
18041
18415
  }
18042
18416
  const directory = await resolveKnownDirectory(picked.directory);
18043
18417
  const workspace = await storeWorkspaceGrant({
18418
+ request: c.req.raw,
18044
18419
  ...directory,
18045
- name: path25.basename(directory.root),
18420
+ name: path26.basename(directory.root),
18046
18421
  pathLabel: directory.root
18047
18422
  });
18048
18423
  return c.json(workspaceNativePickerResponseSchema.parse({
@@ -18052,9 +18427,97 @@ function createWorkspaceRoutes(options = {}) {
18052
18427
  } catch (error) {
18053
18428
  return workspaceErrorResponse(c, error);
18054
18429
  }
18055
- }).get("/locations", (c) => c.json(workspaceLocationsResponseSchema.parse({
18056
- locations: definitions.map((definition) => knownLocation(definition, "unprobed"))
18057
- }))).post("/browser/open", zValidator3("json", workspaceBrowserOpenRequestSchema, (result, c) => {
18430
+ }).get("/launch-folder", async (c) => {
18431
+ try {
18432
+ const { root } = await resolveLaunchFolder();
18433
+ const home = await realpathPreservingDriveRoot(homedir5()).catch(() => homedir5());
18434
+ return c.json(workspaceLaunchFolderResponseSchema.parse({
18435
+ available: true,
18436
+ name: path26.basename(root),
18437
+ pathLabel: root,
18438
+ broad: isContainedPath2(root, home)
18439
+ }));
18440
+ } catch {
18441
+ return c.json(workspaceLaunchFolderResponseSchema.parse({ available: false }));
18442
+ }
18443
+ }).post("/launch-folder/grant", zValidator3("json", workspaceLaunchFolderGrantRequestSchema, (result, c) => {
18444
+ if (!result.success) {
18445
+ return invalidRequestResponse(c);
18446
+ }
18447
+ }), async (c) => {
18448
+ if (!authorizeNativePickerRequest(c.req.raw)) {
18449
+ return c.json({
18450
+ error: "The launch folder can be opened only from an authenticated Lightcode web session.",
18451
+ code: "web_browser_required"
18452
+ }, 403);
18453
+ }
18454
+ try {
18455
+ const directory = await resolveLaunchFolder();
18456
+ const workspace = await storeWorkspaceGrant({
18457
+ request: c.req.raw,
18458
+ ...directory,
18459
+ name: path26.basename(directory.root),
18460
+ pathLabel: directory.root
18461
+ });
18462
+ return c.json(workspaceBrowserSelectResponseSchema.parse({ workspace }), 201);
18463
+ } catch (error) {
18464
+ return workspaceErrorResponse(c, error);
18465
+ }
18466
+ }).get("/locations", (c) => {
18467
+ prewarmKnownFolders();
18468
+ return c.json(workspaceLocationsResponseSchema.parse({
18469
+ locations: definitions.map((definition) => knownLocation(definition, "unprobed"))
18470
+ }));
18471
+ }).get("/recent", async (c) => {
18472
+ try {
18473
+ const recent = (await listRecentWorkspaces2(12)).filter((workspace) => !isFilesystemRootPath(workspace.cwd)).slice(0, 8);
18474
+ const projects = await Promise.all(recent.map(async (workspace) => ({
18475
+ key: recentWorkspaceKey(workspace.cwd),
18476
+ name: path26.basename(workspace.cwd) || workspace.cwd,
18477
+ pathLabel: workspace.cwd,
18478
+ lastUsedAt: workspace.lastUsedAt.toISOString(),
18479
+ available: await fs9.stat(workspace.cwd).then((stats) => stats.isDirectory()).catch(() => false)
18480
+ })));
18481
+ return c.json(workspaceRecentProjectsResponseSchema.parse({ projects }));
18482
+ } catch (error) {
18483
+ return workspaceErrorResponse(c, error);
18484
+ }
18485
+ }).post("/recent/:key/grant", zValidator3("param", workspaceRecentPathParamsSchema, (result, c) => {
18486
+ if (!result.success) {
18487
+ return invalidRequestResponse(c);
18488
+ }
18489
+ }), zValidator3("json", workspaceRecentGrantRequestSchema, (result, c) => {
18490
+ if (!result.success) {
18491
+ return invalidRequestResponse(c);
18492
+ }
18493
+ }), async (c) => {
18494
+ if (!authorizeNativePickerRequest(c.req.raw)) {
18495
+ return c.json({
18496
+ error: "Recent projects can be opened only from an authenticated Lightcode web session.",
18497
+ code: "web_browser_required"
18498
+ }, 403);
18499
+ }
18500
+ try {
18501
+ const { key } = c.req.valid("param");
18502
+ const recent = (await listRecentWorkspaces2(50)).find((workspace2) => recentWorkspaceKey(workspace2.cwd) === key);
18503
+ if (!recent) {
18504
+ throw new WorkspaceRouteError("location_not_found", "That recent project is no longer listed. Refresh and try again.", 404, false);
18505
+ }
18506
+ const directory = await resolveKnownDirectory(recent.cwd);
18507
+ if (recent.identity && recent.identity.inode !== "unsupported" && directory.identity.inode !== "unsupported" && !identitiesMatch(recent.identity, directory.identity)) {
18508
+ throw new WorkspaceRouteError("workspace_replaced", "This folder was replaced since it was last used. Choose it again with the folder picker.", 409, false);
18509
+ }
18510
+ const workspace = await storeWorkspaceGrant({
18511
+ request: c.req.raw,
18512
+ ...directory,
18513
+ name: path26.basename(directory.root),
18514
+ pathLabel: directory.root
18515
+ });
18516
+ return c.json(workspaceBrowserSelectResponseSchema.parse({ workspace }), 201);
18517
+ } catch (error) {
18518
+ return workspaceErrorResponse(c, error);
18519
+ }
18520
+ }).post("/browser/open", zValidator3("json", workspaceBrowserOpenRequestSchema, (result, c) => {
18058
18521
  if (!result.success) {
18059
18522
  return invalidRequestResponse(c);
18060
18523
  }
@@ -18065,17 +18528,43 @@ function createWorkspaceRoutes(options = {}) {
18065
18528
  if (!definition) {
18066
18529
  throw new WorkspaceRouteError("location_not_found", "Workspace location not found.", 404, false);
18067
18530
  }
18531
+ if (definition.kind === "drives") {
18532
+ const browserId2 = crypto.randomUUID();
18533
+ const expiresAtMs2 = now() + capabilityTtlMs;
18534
+ const location2 = knownLocation(definition, "available");
18535
+ browserCapabilities.set(browserId2, {
18536
+ id: browserId2,
18537
+ kind: "drives",
18538
+ root: "",
18539
+ identity: { device: "drives", inode: "drives" },
18540
+ location: location2,
18541
+ rootName: definition.rootName,
18542
+ expiresAtMs: expiresAtMs2
18543
+ });
18544
+ trimOldestEntries(browserCapabilities, maximumBrowserCapabilities);
18545
+ return c.json(workspaceBrowserOpenResponseSchema.parse({
18546
+ browserId: browserId2,
18547
+ location: location2,
18548
+ expiresAt: new Date(expiresAtMs2).toISOString()
18549
+ }), 201);
18550
+ }
18068
18551
  const discoveredPath = await definition.resolvePath();
18069
18552
  const safePath = normalizeKnownFolderCandidate(discoveredPath, process.platform);
18070
18553
  if (!safePath) {
18071
18554
  throw new WorkspaceRouteError("workspace_unavailable", `The resolved ${definition.rootName} location is not a safe absolute folder path.`, 500, false);
18072
18555
  }
18073
- const resolved = await resolveKnownDirectory(safePath);
18556
+ const resolved = await resolveKnownDirectory(safePath).catch((error) => {
18557
+ if (error instanceof WorkspaceRouteError && error.code === "workspace_missing") {
18558
+ throw new WorkspaceRouteError("location_missing", definition.id === "projects" ? `No Projects folder at ${safePath}. Create it, or set LIGHTCODE_PROJECTS_PATH to the folder that holds your projects.` : `No ${definition.rootName} folder at ${safePath}.`, 404, false);
18559
+ }
18560
+ throw error;
18561
+ });
18074
18562
  const browserId = crypto.randomUUID();
18075
18563
  const expiresAtMs = now() + capabilityTtlMs;
18076
18564
  const location = knownLocation(definition, "available");
18077
18565
  browserCapabilities.set(browserId, {
18078
18566
  id: browserId,
18567
+ kind: "folder",
18079
18568
  ...resolved,
18080
18569
  location,
18081
18570
  rootName: definition.rootName,
@@ -18103,13 +18592,31 @@ function createWorkspaceRoutes(options = {}) {
18103
18592
  const { browserId } = c.req.valid("param");
18104
18593
  const input = c.req.valid("json");
18105
18594
  const capability2 = getBrowserCapability(browserId);
18106
- await assertRootIdentity(capability2);
18107
- const directory = await resolveDirectoryFromSegments(capability2.root, input.segments, capability2.rootName);
18108
- const beforeRead = await assertDirectoryIdentity(capability2.root, directory);
18595
+ if (capability2.kind === "drives" && input.segments.length === 0) {
18596
+ const drives = await listDrives();
18597
+ capability2.driveTokens = new Set(drives.map((drive) => drive.token));
18598
+ return c.json(workspaceBrowserEntriesResponseSchema.parse({
18599
+ browserId,
18600
+ segments: [],
18601
+ entries: drives.map((drive) => ({
18602
+ name: drive.token,
18603
+ kind: "directory",
18604
+ size: null,
18605
+ readable: drive.readable,
18606
+ symlinkState: null
18607
+ })),
18608
+ nextCursor: null,
18609
+ truncated: false
18610
+ }));
18611
+ }
18612
+ const scope = await resolveBrowseScope(capability2, input.segments);
18613
+ const directory = await resolveDirectoryFromSegments(scope.root, scope.segments, scope.rootName);
18614
+ const beforeRead = await assertDirectoryIdentity(scope.root, directory);
18109
18615
  const cursorBinding = {
18110
18616
  browserId,
18111
18617
  segments: input.segments,
18112
18618
  includeHidden: input.includeHidden,
18619
+ directoriesOnly: input.directoriesOnly,
18113
18620
  directory,
18114
18621
  mtimeNs: beforeRead.mtimeNs
18115
18622
  };
@@ -18121,14 +18628,17 @@ function createWorkspaceRoutes(options = {}) {
18121
18628
  const describedEntries = [];
18122
18629
  let visibleOffset = 0;
18123
18630
  let truncated = false;
18124
- const directoryHandle = await fs8.opendir(directory.path);
18631
+ const directoryHandle = await fs9.opendir(directory.path);
18125
18632
  try {
18126
18633
  while (true) {
18127
18634
  const entry = await directoryHandle.read();
18128
18635
  if (!entry) {
18129
18636
  break;
18130
18637
  }
18131
- if (!input.includeHidden && entry.name.startsWith(".")) {
18638
+ if (!input.includeHidden && isHiddenBrowserEntry(entry)) {
18639
+ continue;
18640
+ }
18641
+ if (input.directoriesOnly && isOmittedFromFolderListing(entry)) {
18132
18642
  continue;
18133
18643
  }
18134
18644
  if (visibleOffset < offset) {
@@ -18140,7 +18650,7 @@ function createWorkspaceRoutes(options = {}) {
18140
18650
  break;
18141
18651
  }
18142
18652
  visibleOffset += 1;
18143
- const described = await describeEntry(capability2.root, directory.path, entry.name);
18653
+ const described = await describeEntry(scope.root, directory.path, entry.name);
18144
18654
  if (described) {
18145
18655
  describedEntries.push(described);
18146
18656
  }
@@ -18154,7 +18664,7 @@ function createWorkspaceRoutes(options = {}) {
18154
18664
  }
18155
18665
  }
18156
18666
  }
18157
- const afterRead = await assertDirectoryIdentity(capability2.root, directory);
18667
+ const afterRead = await assertDirectoryIdentity(scope.root, directory);
18158
18668
  if (afterRead.mtimeNs !== beforeRead.mtimeNs) {
18159
18669
  throw new WorkspaceRouteError("invalid_cursor", "The folder changed while it was being listed. Refresh and try again.", 409, true);
18160
18670
  }
@@ -18181,19 +18691,20 @@ function createWorkspaceRoutes(options = {}) {
18181
18691
  const { browserId } = c.req.valid("param");
18182
18692
  const { segments } = c.req.valid("json");
18183
18693
  const capability2 = getBrowserCapability(browserId);
18184
- await assertRootIdentity(capability2);
18185
- const directory = await resolveDirectoryFromSegments(capability2.root, segments, capability2.rootName);
18186
- await assertDirectoryIdentity(capability2.root, directory);
18694
+ if (capability2.kind === "drives" && segments.length < 2) {
18695
+ throw new WorkspaceRouteError("drive_root_not_selectable", "A whole drive can't be a project. Open a folder inside it and choose that.", 422, false);
18696
+ }
18697
+ const scope = await resolveBrowseScope(capability2, segments);
18698
+ const directory = await resolveDirectoryFromSegments(scope.root, scope.segments, scope.rootName);
18699
+ await assertDirectoryIdentity(scope.root, directory);
18187
18700
  await options.beforeDirectoryOperation?.({
18188
18701
  operation: "select",
18189
18702
  directory: directory.path
18190
18703
  });
18191
18704
  const name = segments.at(-1) ?? capability2.rootName;
18192
- const pathLabel = [
18193
- capability2.location.pathLabel,
18194
- ...segments
18195
- ].join("/");
18705
+ const pathLabel = capability2.kind === "drives" ? directory.path : [capability2.location.pathLabel, ...segments].join("/");
18196
18706
  const workspace = await storeWorkspaceGrant({
18707
+ request: c.req.raw,
18197
18708
  root: directory.path,
18198
18709
  identity: directory.identity,
18199
18710
  name,
@@ -18232,7 +18743,7 @@ function createWorkspaceRoutes(options = {}) {
18232
18743
  }
18233
18744
  });
18234
18745
  }
18235
- var logger13, defaultBrowserCapabilityTtlMs, maximumBrowserCapabilities = 64, maximumWorkspaceGrants = 256, maximumUserDirsConfigurationBytes, WorkspaceRouteError, workspaceRoutes;
18746
+ var logger13, defaultBrowserCapabilityTtlMs, maximumBrowserCapabilities = 64, maximumWorkspaceGrants = 256, maximumUserDirsConfigurationBytes, nativePickerMaximumMs, launchFolderEnvironmentSchema, WorkspaceRouteError, workspaceRoutes;
18236
18747
  var init_workspace_routes = __esm(() => {
18237
18748
  init_src2();
18238
18749
  init_src();
@@ -18240,9 +18751,14 @@ var init_workspace_routes = __esm(() => {
18240
18751
  init_runtime_config();
18241
18752
  init_native_directory_picker();
18242
18753
  init_web_auth();
18754
+ init_windows_known_folders();
18755
+ init_workspace_entry_filter();
18756
+ init_workspace_drives();
18243
18757
  logger13 = createLogger("workspace-routes");
18244
18758
  defaultBrowserCapabilityTtlMs = 10 * 60 * 1000;
18245
18759
  maximumUserDirsConfigurationBytes = 16 * 1024;
18760
+ nativePickerMaximumMs = 10 * 60 * 1000;
18761
+ launchFolderEnvironmentSchema = z45.string().min(1).max(4096).refine((value) => !value.includes("\x00")).refine((value) => path26.isAbsolute(value));
18246
18762
  WorkspaceRouteError = class WorkspaceRouteError extends Error {
18247
18763
  code;
18248
18764
  status;
@@ -18258,12 +18774,151 @@ var init_workspace_routes = __esm(() => {
18258
18774
  workspaceRoutes = createWorkspaceRoutes();
18259
18775
  });
18260
18776
 
18777
+ // apps/server/src/routes/session-changes-routes.ts
18778
+ import { zValidator as zValidator4 } from "@hono/zod-validator";
18779
+ import { createHmac as createHmac2, randomBytes as randomBytes2 } from "crypto";
18780
+ import { promises as fs10 } from "fs";
18781
+ import path27 from "path";
18782
+ import { Hono as Hono8 } from "hono";
18783
+ async function defaultResolveSessionWorkspace(id) {
18784
+ const sessionId = await resolveChatSessionIdentifier(id);
18785
+ return (await assertSessionWorkspaceIdentity(sessionId)).cwd;
18786
+ }
18787
+ function isContained(root, candidate) {
18788
+ const relative = path27.relative(root, candidate);
18789
+ return relative !== "" && relative !== ".." && !relative.startsWith(`..${path27.sep}`) && !path27.isAbsolute(relative);
18790
+ }
18791
+ async function untrackedFileDiff(workspace, relativePath) {
18792
+ const candidate = path27.resolve(workspace, relativePath);
18793
+ const stats = await fs10.lstat(candidate).catch(() => null);
18794
+ if (!stats || !stats.isFile() || !isContained(workspace, candidate)) {
18795
+ return { diff: "", truncated: false, binary: false };
18796
+ }
18797
+ const handle = await fs10.open(candidate, "r");
18798
+ try {
18799
+ const length = Math.min(stats.size, maximumUntrackedBytes);
18800
+ const buffer = Buffer.alloc(length);
18801
+ await handle.read(buffer, 0, length, 0);
18802
+ if (buffer.includes(0)) {
18803
+ return { diff: "", truncated: false, binary: true };
18804
+ }
18805
+ const lines = buffer.toString("utf8").replace(/\r\n/g, `
18806
+ `).split(`
18807
+ `);
18808
+ if (lines.at(-1) === "")
18809
+ lines.pop();
18810
+ const diff = [
18811
+ "--- /dev/null",
18812
+ `+++ b/${relativePath}`,
18813
+ `@@ -0,0 +1,${lines.length} @@`,
18814
+ ...lines.map((line) => `+${line}`)
18815
+ ].join(`
18816
+ `);
18817
+ return { diff, truncated: stats.size > maximumUntrackedBytes, binary: false };
18818
+ } finally {
18819
+ await handle.close();
18820
+ }
18821
+ }
18822
+ function errorResponse(c, error, event) {
18823
+ if (error instanceof SessionNotFoundError) {
18824
+ return c.json({ error: error.message }, 404);
18825
+ }
18826
+ if (error instanceof SessionWorkspaceIdentityError) {
18827
+ return c.json({ error: error.message, code: error.code }, error.code === "workspace_unavailable" ? 404 : 409);
18828
+ }
18829
+ return internalErrorResponse(c, {
18830
+ event,
18831
+ message: "Unable to read the session's changes.",
18832
+ error
18833
+ });
18834
+ }
18835
+ function createSessionChangesRoutes(options = {}) {
18836
+ const resolveSessionWorkspace = options.resolveSessionWorkspace ?? defaultResolveSessionWorkspace;
18837
+ const fileIdSecret = randomBytes2(32);
18838
+ const fileId = (workspace, entryPath) => createHmac2("sha256", fileIdSecret).update(`${workspace}\x00${entryPath}`).digest("base64url").slice(0, 22);
18839
+ return new Hono8().get("/:id/changes", zValidator4("param", sessionPathParamsSchema), async (c) => {
18840
+ try {
18841
+ const workspace = await resolveSessionWorkspace(c.req.valid("param").id);
18842
+ const context2 = createWorkspaceContext(workspace);
18843
+ const status = await executeGitStatus({}, context2);
18844
+ if (!status.ok) {
18845
+ return c.json(sessionChangesResponseSchema.parse({
18846
+ isRepo: false,
18847
+ branch: null,
18848
+ files: [],
18849
+ truncated: false
18850
+ }));
18851
+ }
18852
+ const counts = await executeGitNumstat(context2);
18853
+ const files = status.entries.slice(0, maximumChangedFiles).map((entry) => {
18854
+ const lineCounts = counts.get(entry.path);
18855
+ return {
18856
+ fileId: fileId(workspace, entry.path),
18857
+ path: entry.path,
18858
+ originalPath: entry.originalPath,
18859
+ changeKind: changeKindFromGitStatus(entry.indexStatus, entry.workingTreeStatus),
18860
+ added: lineCounts ? lineCounts.added : null,
18861
+ removed: lineCounts ? lineCounts.removed : null
18862
+ };
18863
+ });
18864
+ return c.json(sessionChangesResponseSchema.parse({
18865
+ isRepo: true,
18866
+ branch: status.branch,
18867
+ files,
18868
+ truncated: status.truncated || status.entries.length > maximumChangedFiles
18869
+ }));
18870
+ } catch (error) {
18871
+ return errorResponse(c, error, "session_changes_failed");
18872
+ }
18873
+ }).get("/:id/changes/:fileId/diff", zValidator4("param", sessionChangeDiffParamsSchema), async (c) => {
18874
+ try {
18875
+ const params = c.req.valid("param");
18876
+ const workspace = await resolveSessionWorkspace(params.id);
18877
+ const context2 = createWorkspaceContext(workspace);
18878
+ const status = await executeGitStatus({}, context2);
18879
+ const entry = status.ok ? status.entries.find((candidate) => fileId(workspace, candidate.path) === params.fileId) : undefined;
18880
+ if (!entry) {
18881
+ return c.json({ error: "That file is no longer changed. Refresh the list." }, 404);
18882
+ }
18883
+ if (changeKindFromGitStatus(entry.indexStatus, entry.workingTreeStatus) === "untracked") {
18884
+ return c.json(sessionChangeDiffResponseSchema.parse({
18885
+ path: entry.path,
18886
+ ...await untrackedFileDiff(workspace, entry.path)
18887
+ }));
18888
+ }
18889
+ let result = await executeGitDiff({ path: entry.path }, context2);
18890
+ if (result.ok && !result.diff.trim()) {
18891
+ const staged = await executeGitDiff({ path: entry.path, staged: true }, context2);
18892
+ if (staged.ok && staged.diff.trim())
18893
+ result = staged;
18894
+ }
18895
+ return c.json(sessionChangeDiffResponseSchema.parse({
18896
+ path: entry.path,
18897
+ diff: result.ok ? result.diff : "",
18898
+ truncated: result.ok ? result.truncated : false,
18899
+ binary: result.ok && /^Binary files /m.test(result.diff)
18900
+ }));
18901
+ } catch (error) {
18902
+ return errorResponse(c, error, "session_change_diff_failed");
18903
+ }
18904
+ });
18905
+ }
18906
+ var maximumUntrackedBytes, maximumChangedFiles = 500, sessionChangesRoutes;
18907
+ var init_session_changes_routes = __esm(() => {
18908
+ init_src2();
18909
+ init_runtime17();
18910
+ init_chat_store();
18911
+ init_route_helpers();
18912
+ maximumUntrackedBytes = 256 * 1024;
18913
+ sessionChangesRoutes = createSessionChangesRoutes();
18914
+ });
18915
+
18261
18916
  // apps/server/src/app.ts
18262
18917
  var exports_app = {};
18263
18918
  __export(exports_app, {
18264
18919
  app: () => app
18265
18920
  });
18266
- import { Hono as Hono8 } from "hono";
18921
+ import { Hono as Hono9 } from "hono";
18267
18922
  import { bodyLimit } from "hono/body-limit";
18268
18923
  var maxRequestBodyBytes, app;
18269
18924
  var init_app = __esm(() => {
@@ -18275,11 +18930,12 @@ var init_app = __esm(() => {
18275
18930
  init_root_routes();
18276
18931
  init_web_routes();
18277
18932
  init_workspace_routes();
18933
+ init_session_changes_routes();
18278
18934
  maxRequestBodyBytes = 32 * 1024 * 1024;
18279
- app = new Hono8().use("*", createWebSecurityMiddleware()).use(bodyLimit({
18935
+ app = new Hono9().use("*", createWebSecurityMiddleware()).use(bodyLimit({
18280
18936
  maxSize: maxRequestBodyBytes,
18281
18937
  onError: (c) => c.json({ error: "Request body too large." }, 413)
18282
- })).get("/healthz", (c) => c.json({ ok: true, service: "lightcode", protocolVersion: 1 })).route("/", webRoutes).route("/", rootRoutes).route("/config", configRoutes).route("/diagnostics", diagnosticsRoutes).route("/extensions", extensionRoutes).route("/workspaces", workspaceRoutes).route("/sessions", sessionRoutes);
18938
+ })).get("/healthz", (c) => c.json({ ok: true, service: "lightcode", protocolVersion: 1 })).route("/", webRoutes).route("/", rootRoutes).route("/config", configRoutes).route("/diagnostics", diagnosticsRoutes).route("/extensions", extensionRoutes).route("/workspaces", workspaceRoutes).route("/sessions", sessionRoutes).route("/sessions", sessionChangesRoutes);
18283
18939
  });
18284
18940
 
18285
18941
  // apps/server/src/lib/headroom-proxy.ts
@@ -18802,17 +19458,27 @@ function isLoopbackBindHost(hostname) {
18802
19458
  return isIP(normalized) === 6 && normalized === "::1";
18803
19459
  }
18804
19460
 
18805
- // apps/server/src/index.ts
18806
- var logger15 = createLogger("server");
18807
- var httpIdleTimeoutSeconds = 30;
18808
- var shutdownForceExitMs = 5000;
18809
- function isChatStreamingRoute(request) {
19461
+ // apps/server/src/lib/long-running-routes.ts
19462
+ function isLongRunningRoute(request) {
18810
19463
  if (request.method !== "POST") {
18811
19464
  return false;
18812
19465
  }
18813
19466
  const pathname = new URL(request.url).pathname;
18814
- return /^\/sessions\/[^/]+\/(?:chat|turns)$/.test(pathname);
19467
+ return /^\/sessions\/[^/]+\/(?:chat|turns)$/.test(pathname) || pathname === "/workspaces/picker/open";
19468
+ }
19469
+ function createServerFetch(app) {
19470
+ return (request, server) => {
19471
+ if (isLongRunningRoute(request)) {
19472
+ server.timeout(request, 0);
19473
+ }
19474
+ return app.fetch(request);
19475
+ };
18815
19476
  }
19477
+
19478
+ // apps/server/src/index.ts
19479
+ var logger15 = createLogger("server");
19480
+ var httpIdleTimeoutSeconds = 30;
19481
+ var shutdownForceExitMs = 5000;
18816
19482
  function serverLockFailureMessage(error) {
18817
19483
  if (error instanceof ServerInstanceLockError && error.code === "server_already_running") {
18818
19484
  return [
@@ -18942,12 +19608,7 @@ async function startServer() {
18942
19608
  idleTimeout: httpIdleTimeoutSeconds,
18943
19609
  port,
18944
19610
  hostname,
18945
- async fetch(request, bunServer) {
18946
- if (isChatStreamingRoute(request)) {
18947
- bunServer.timeout(request, 0);
18948
- }
18949
- return app2.fetch(request);
18950
- }
19611
+ fetch: createServerFetch(app2)
18951
19612
  });
18952
19613
  } catch (error) {
18953
19614
  await releaseServerLock(instanceLock, "bind-failure");