@kmugalkhod/lightcode 0.17.3 → 0.18.1

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,31 +17444,139 @@ 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
- $dialog = New-Object System.Windows.Forms.FolderBrowserDialog
17295
- $dialog.Description = 'Choose a project folder'
17296
- $dialog.ShowNewFolderButton = $true
17297
- $dialog.AutoUpgradeEnabled = $true
17450
+ $modernPicker = $false
17451
+ try {
17452
+ Add-Type -Language CSharp -TypeDefinition @'
17453
+ using System;
17454
+ using System.Runtime.InteropServices;
17455
+
17456
+ namespace Lightcode {
17457
+ [ComImport, Guid("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7")]
17458
+ internal class FileOpenDialogCoClass {}
17459
+
17460
+ [ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
17461
+ internal interface IShellItem {
17462
+ void BindToHandler(IntPtr pbc, ref Guid bhid, ref Guid riid, out IntPtr ppv);
17463
+ void GetParent(out IShellItem ppsi);
17464
+ void GetDisplayName(uint sigdnName, out IntPtr ppszName);
17465
+ void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs);
17466
+ void Compare(IShellItem psi, uint hint, out int piOrder);
17467
+ }
17468
+
17469
+ // IFileOpenDialog; only the IModalWindow/IFileDialog prefix of the vtable
17470
+ // is declared, in order.
17471
+ [ComImport, Guid("d57c7288-d4ad-4768-be02-9d969532d960"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
17472
+ internal interface IFileOpenDialog {
17473
+ [PreserveSig] int Show(IntPtr parent);
17474
+ void SetFileTypes(uint cFileTypes, IntPtr rgFilterSpec);
17475
+ void SetFileTypeIndex(uint iFileType);
17476
+ void GetFileTypeIndex(out uint piFileType);
17477
+ void Advise(IntPtr pfde, out uint pdwCookie);
17478
+ void Unadvise(uint dwCookie);
17479
+ void SetOptions(uint fos);
17480
+ void GetOptions(out uint pfos);
17481
+ void SetDefaultFolder(IShellItem psi);
17482
+ void SetFolder(IShellItem psi);
17483
+ void GetFolder(out IShellItem ppsi);
17484
+ void GetCurrentSelection(out IShellItem ppsi);
17485
+ void SetFileName([MarshalAs(UnmanagedType.LPWStr)] string pszName);
17486
+ void GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName);
17487
+ void SetTitle([MarshalAs(UnmanagedType.LPWStr)] string pszTitle);
17488
+ void SetOkButtonLabel([MarshalAs(UnmanagedType.LPWStr)] string pszText);
17489
+ void SetFileNameLabel([MarshalAs(UnmanagedType.LPWStr)] string pszLabel);
17490
+ void GetResult(out IShellItem ppsi);
17491
+ }
17492
+
17493
+ public static class FolderPicker {
17494
+ const uint PickFolders = 0x20, ForceFileSystem = 0x40, PathMustExist = 0x800, NoChangeDir = 0x8;
17495
+ const uint FileSystemPath = 0x80058000;
17496
+ const int Cancelled = unchecked((int)0x800704C7);
17497
+
17498
+ // Returns the chosen folder, or null when the user cancels.
17499
+ public static string Pick(IntPtr owner, string title) {
17500
+ var dialog = (IFileOpenDialog)new FileOpenDialogCoClass();
17501
+ try {
17502
+ uint options;
17503
+ dialog.GetOptions(out options);
17504
+ dialog.SetOptions(options | PickFolders | ForceFileSystem | PathMustExist | NoChangeDir);
17505
+ dialog.SetTitle(title);
17506
+ dialog.SetOkButtonLabel("Open project");
17507
+ int hr = dialog.Show(owner);
17508
+ if (hr == Cancelled) return null;
17509
+ Marshal.ThrowExceptionForHR(hr);
17510
+ IShellItem item;
17511
+ dialog.GetResult(out item);
17512
+ IntPtr name;
17513
+ item.GetDisplayName(FileSystemPath, out name);
17514
+ try {
17515
+ return Marshal.PtrToStringUni(name);
17516
+ } finally {
17517
+ Marshal.FreeCoTaskMem(name);
17518
+ }
17519
+ } finally {
17520
+ Marshal.ReleaseComObject(dialog);
17521
+ }
17522
+ }
17523
+ }
17524
+ }
17525
+ '@
17526
+ $modernPicker = $true
17527
+ } catch {
17528
+ $modernPicker = $false
17529
+ }
17298
17530
  $owner = New-Object System.Windows.Forms.Form
17531
+ $owner.Text = 'Lightcode - choose a project folder'
17299
17532
  $owner.TopMost = $true
17300
- $owner.ShowInTaskbar = $false
17533
+ # A taskbar button keeps the dialog findable when Windows refuses to bring it
17534
+ # in front of the browser.
17535
+ $owner.ShowInTaskbar = $true
17301
17536
  $owner.StartPosition = [System.Windows.Forms.FormStartPosition]::Manual
17302
- $owner.Location = New-Object System.Drawing.Point(-32000, -32000)
17537
+ $workArea = [System.Windows.Forms.Screen]::PrimaryScreen.WorkingArea
17538
+ $owner.Location = New-Object System.Drawing.Point(
17539
+ [int]($workArea.Left + $workArea.Width / 2),
17540
+ [int]($workArea.Top + $workArea.Height / 2))
17303
17541
  $owner.Size = New-Object System.Drawing.Size(1, 1)
17304
17542
  $owner.Opacity = 0
17543
+ # lightcode:show-dialog
17305
17544
  try {
17545
+ # The spawner's windowsHide turns the process's first show into SW_HIDE.
17546
+ # Showing again makes the owner, and its taskbar button, actually visible.
17547
+ $owner.Show()
17548
+ $owner.Hide()
17306
17549
  $owner.Show()
17307
17550
  $owner.Activate()
17308
- $result = $dialog.ShowDialog($owner)
17309
- if ($result -eq [System.Windows.Forms.DialogResult]::OK) {
17310
- $bytes = [Text.Encoding]::UTF8.GetBytes($dialog.SelectedPath)
17551
+ $selected = $null
17552
+ $shown = $false
17553
+ if ($modernPicker) {
17554
+ try {
17555
+ $selected = [Lightcode.FolderPicker]::Pick($owner.Handle, 'Choose a project folder')
17556
+ $shown = $true
17557
+ } catch {
17558
+ # The dialog failed to open; fall back to the classic picker below.
17559
+ }
17560
+ }
17561
+ if (-not $shown) {
17562
+ $dialog = New-Object System.Windows.Forms.FolderBrowserDialog
17563
+ $dialog.Description = 'Choose a project folder'
17564
+ $dialog.ShowNewFolderButton = $true
17565
+ try {
17566
+ if ($dialog.ShowDialog($owner) -eq [System.Windows.Forms.DialogResult]::OK) {
17567
+ $selected = $dialog.SelectedPath
17568
+ }
17569
+ } finally {
17570
+ $dialog.Dispose()
17571
+ }
17572
+ }
17573
+ if ($selected) {
17574
+ $bytes = [Text.Encoding]::UTF8.GetBytes($selected)
17311
17575
  [Console]::Out.Write('selected:' + [Convert]::ToBase64String($bytes))
17312
17576
  } else {
17313
17577
  [Console]::Out.Write('cancelled')
17314
17578
  }
17315
17579
  } finally {
17316
- $dialog.Dispose()
17317
17580
  $owner.Dispose()
17318
17581
  }
17319
17582
  `;
@@ -17327,6 +17590,187 @@ end try
17327
17590
  `;
17328
17591
  });
17329
17592
 
17593
+ // apps/server/src/lib/windows-known-folders.ts
17594
+ import path25 from "path";
17595
+ import { z as z44 } from "zod";
17596
+ function parseWindowsKnownFoldersOutput(output) {
17597
+ if (Buffer.byteLength(output) > 16384)
17598
+ return null;
17599
+ try {
17600
+ const parsed = knownFoldersOutputSchema.safeParse(JSON.parse(output));
17601
+ return parsed.success ? parsed.data : null;
17602
+ } catch {
17603
+ return null;
17604
+ }
17605
+ }
17606
+ async function runKnownFoldersResolver(environment) {
17607
+ try {
17608
+ const systemRootCandidate = environment.SystemRoot?.trim() || environment.WINDIR?.trim() || "C:\\Windows";
17609
+ if (!path25.win32.isAbsolute(systemRootCandidate))
17610
+ return null;
17611
+ const windowsRoot = path25.win32.normalize(systemRootCandidate);
17612
+ if (windowsRoot === path25.win32.parse(windowsRoot).root)
17613
+ return null;
17614
+ const systemDirectory = path25.win32.join(windowsRoot, "System32");
17615
+ const executable = path25.win32.join(systemDirectory, "WindowsPowerShell", "v1.0", "powershell.exe");
17616
+ const child = Bun.spawn([
17617
+ executable,
17618
+ "-NoLogo",
17619
+ "-NoProfile",
17620
+ "-NonInteractive",
17621
+ "-Command",
17622
+ knownFoldersScript
17623
+ ], {
17624
+ cwd: systemDirectory,
17625
+ env: {
17626
+ SystemRoot: windowsRoot,
17627
+ WINDIR: windowsRoot,
17628
+ ...Object.fromEntries(["USERPROFILE", "APPDATA", "LOCALAPPDATA", "TEMP", "TMP"].flatMap((name) => environment[name] ? [[name, environment[name]]] : []))
17629
+ },
17630
+ stdout: "pipe",
17631
+ stderr: "ignore",
17632
+ stdin: "ignore",
17633
+ windowsHide: true
17634
+ });
17635
+ const timeout = setTimeout(() => child.kill(), resolverKillMs);
17636
+ timeout.unref();
17637
+ try {
17638
+ const [output, exitCode] = await Promise.all([
17639
+ new Response(child.stdout).text(),
17640
+ child.exited
17641
+ ]);
17642
+ return exitCode === 0 ? parseWindowsKnownFoldersOutput(output) : null;
17643
+ } finally {
17644
+ clearTimeout(timeout);
17645
+ }
17646
+ } catch {
17647
+ return null;
17648
+ }
17649
+ }
17650
+ function cacheKey(environment) {
17651
+ return JSON.stringify(["SystemRoot", "WINDIR", "USERPROFILE", "APPDATA", "LOCALAPPDATA"].map((name) => environment[name] ?? null));
17652
+ }
17653
+ function resolveWindowsKnownFolders(environment, run = runKnownFoldersResolver) {
17654
+ const key = cacheKey(environment);
17655
+ const cached = resolverCache.get(key);
17656
+ if (cached)
17657
+ return cached;
17658
+ const pending = run(environment).then((folders) => {
17659
+ if (!folders)
17660
+ resolverCache.delete(key);
17661
+ return folders;
17662
+ });
17663
+ resolverCache.set(key, pending);
17664
+ return pending;
17665
+ }
17666
+ async function resolveWindowsKnownFolder(environment, folder, {
17667
+ budgetMs = defaultWaitBudgetMs,
17668
+ run
17669
+ } = {}) {
17670
+ let timer;
17671
+ const budget = new Promise((resolve2) => {
17672
+ timer = setTimeout(() => resolve2(null), budgetMs);
17673
+ });
17674
+ try {
17675
+ const folders = await Promise.race([
17676
+ resolveWindowsKnownFolders(environment, run),
17677
+ budget
17678
+ ]);
17679
+ return folders?.[folder] ?? null;
17680
+ } finally {
17681
+ clearTimeout(timer);
17682
+ }
17683
+ }
17684
+ var resolverKillMs = 20000, defaultWaitBudgetMs = 2000, downloadsFolderGuid = "{374DE290-123F-4565-9164-39C4925E467B}", knownFoldersScript, knownFolderValueSchema, knownFoldersOutputSchema, resolverCache;
17685
+ var init_windows_known_folders = __esm(() => {
17686
+ knownFoldersScript = [
17687
+ "[Console]::OutputEncoding=[Text.UTF8Encoding]::new($false)",
17688
+ "$desktop=[Environment]::GetFolderPath([Environment+SpecialFolder]::DesktopDirectory)",
17689
+ "$documents=[Environment]::GetFolderPath([Environment+SpecialFolder]::MyDocuments)",
17690
+ `$downloads=try{[Environment]::ExpandEnvironmentVariables([string](Get-ItemProperty -LiteralPath 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders' -Name '${downloadsFolderGuid}' -ErrorAction Stop).'${downloadsFolderGuid}')}catch{''}`,
17691
+ "[Console]::Write((@{desktop=$desktop;documents=$documents;downloads=$downloads}|ConvertTo-Json -Compress))"
17692
+ ].join(";");
17693
+ knownFolderValueSchema = z44.string().max(4096).transform((value) => value.trim()).transform((value) => value && !/[\r\n\0]/.test(value) && path25.win32.isAbsolute(value) ? value : null);
17694
+ knownFoldersOutputSchema = z44.object({
17695
+ desktop: knownFolderValueSchema,
17696
+ documents: knownFolderValueSchema,
17697
+ downloads: knownFolderValueSchema
17698
+ });
17699
+ resolverCache = new Map;
17700
+ });
17701
+
17702
+ // apps/server/src/lib/workspace-entry-filter.ts
17703
+ function isHiddenBrowserEntry(entry, platform4 = process.platform) {
17704
+ if (entry.name.startsWith("."))
17705
+ return true;
17706
+ if (platform4 !== "win32")
17707
+ return false;
17708
+ if (windowsSystemEntryPattern.test(entry.name))
17709
+ return true;
17710
+ return entry.isSymbolicLink() && windowsLegacyJunctionNames.has(entry.name.toLowerCase());
17711
+ }
17712
+ function isOmittedFromFolderListing(entry) {
17713
+ return entry.isFile();
17714
+ }
17715
+ var windowsSystemEntryPattern, windowsLegacyJunctionNames;
17716
+ var init_workspace_entry_filter = __esm(() => {
17717
+ 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;
17718
+ windowsLegacyJunctionNames = new Set([
17719
+ "application data",
17720
+ "cookies",
17721
+ "local settings",
17722
+ "my documents",
17723
+ "nethood",
17724
+ "printhood",
17725
+ "recent",
17726
+ "sendto",
17727
+ "start menu",
17728
+ "templates",
17729
+ "my music",
17730
+ "my pictures",
17731
+ "my videos",
17732
+ "documents and settings"
17733
+ ]);
17734
+ });
17735
+
17736
+ // apps/server/src/lib/workspace-drives.ts
17737
+ import { promises as fs8 } from "fs";
17738
+ async function probeDriveRoot(root) {
17739
+ let timer;
17740
+ const timeout = new Promise((resolve2) => {
17741
+ timer = setTimeout(() => resolve2("unresponsive"), driveProbeTimeoutMs);
17742
+ });
17743
+ try {
17744
+ return await Promise.race([
17745
+ fs8.stat(root).then((stats) => stats.isDirectory() ? "present" : "absent").catch((error) => {
17746
+ const code = typeof error === "object" && error !== null ? Reflect.get(error, "code") : undefined;
17747
+ return code === "EPERM" || code === "EACCES" || code === "EBUSY" ? "unresponsive" : "absent";
17748
+ }),
17749
+ timeout
17750
+ ]);
17751
+ } finally {
17752
+ clearTimeout(timer);
17753
+ }
17754
+ }
17755
+ async function listWindowsDrives(probe = probeDriveRoot) {
17756
+ const letters = Array.from({ length: 24 }, (_2, index) => String.fromCharCode(67 + index));
17757
+ const results = await Promise.all(letters.map(async (letter) => ({
17758
+ token: `${letter}:`,
17759
+ state: await probe(`${letter}:\\`)
17760
+ })));
17761
+ return results.filter((result) => result.state !== "absent").map((result) => ({
17762
+ token: result.token,
17763
+ readable: result.state === "present"
17764
+ }));
17765
+ }
17766
+ function windowsDriveRoot(token) {
17767
+ return `${token}\\`;
17768
+ }
17769
+ var windowsDriveTokenPattern, driveProbeTimeoutMs = 1500;
17770
+ var init_workspace_drives = __esm(() => {
17771
+ windowsDriveTokenPattern = /^[A-Z]:$/;
17772
+ });
17773
+
17330
17774
  // apps/server/src/routes/workspace-routes.ts
17331
17775
  import { zValidator as zValidator3 } from "@hono/zod-validator";
17332
17776
  import {
@@ -17335,12 +17779,17 @@ import {
17335
17779
  randomBytes,
17336
17780
  timingSafeEqual as timingSafeEqual2
17337
17781
  } from "crypto";
17338
- import { constants as fsConstants3, promises as fs8 } from "fs";
17782
+ import { constants as fsConstants3, promises as fs9 } from "fs";
17339
17783
  import { homedir as homedir5 } from "os";
17340
- import path25 from "path";
17784
+ import path26 from "path";
17341
17785
  import { Hono as Hono7 } from "hono";
17786
+ import { z as z45 } from "zod";
17787
+ function parseLaunchFolderPath(value) {
17788
+ const parsed = launchFolderEnvironmentSchema.safeParse(value);
17789
+ return parsed.success ? normalizeKnownFolderCandidate(parsed.data, process.platform) : null;
17790
+ }
17342
17791
  function pathImplementation(platform4) {
17343
- return platform4 === "win32" ? path25.win32 : path25.posix;
17792
+ return platform4 === "win32" ? path26.win32 : path26.posix;
17344
17793
  }
17345
17794
  function isFilesystemRootPath(candidate, platform4 = process.platform) {
17346
17795
  const pathApi = pathImplementation(platform4);
@@ -17363,7 +17812,7 @@ function normalizeKnownFolderCandidate(candidate, platform4) {
17363
17812
  }
17364
17813
  async function probeDirectory(candidate) {
17365
17814
  try {
17366
- const metadata = await fs8.stat(candidate);
17815
+ const metadata = await fs9.stat(candidate);
17367
17816
  return metadata.isDirectory() ? "available" : "missing";
17368
17817
  } catch (error) {
17369
17818
  const code = filesystemErrorCode(error);
@@ -17377,7 +17826,7 @@ async function readSmallUserDirsConfiguration(filePath) {
17377
17826
  const noFollowFlag = typeof fsConstants3.O_NOFOLLOW === "number" ? fsConstants3.O_NOFOLLOW : 0;
17378
17827
  let handle;
17379
17828
  try {
17380
- handle = await fs8.open(filePath, fsConstants3.O_RDONLY | noFollowFlag);
17829
+ handle = await fs9.open(filePath, fsConstants3.O_RDONLY | noFollowFlag);
17381
17830
  const metadata = await handle.stat();
17382
17831
  if (!metadata.isFile() || metadata.size <= 0 || metadata.size > maximumUserDirsConfigurationBytes) {
17383
17832
  return null;
@@ -17391,59 +17840,6 @@ async function readSmallUserDirsConfiguration(filePath) {
17391
17840
  });
17392
17841
  }
17393
17842
  }
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
17843
  function parseXdgUserDirectory(contents, homeDirectory, variableName) {
17448
17844
  for (const line of contents.split(/\r?\n/)) {
17449
17845
  const match = line.match(/^\s*(XDG_(?:DESKTOP|DOCUMENTS|DOWNLOAD)_DIR)="([^"\r\n]*)"\s*$/);
@@ -17458,15 +17854,15 @@ function parseXdgUserDirectory(contents, homeDirectory, variableName) {
17458
17854
  return null;
17459
17855
  }
17460
17856
  if (configured.startsWith("$HOME/")) {
17461
- return path25.posix.resolve(homeDirectory, configured.slice(6));
17857
+ return path26.posix.resolve(homeDirectory, configured.slice(6));
17462
17858
  }
17463
17859
  if (configured.startsWith("${HOME}/")) {
17464
- return path25.posix.resolve(homeDirectory, configured.slice(8));
17860
+ return path26.posix.resolve(homeDirectory, configured.slice(8));
17465
17861
  }
17466
17862
  if (configured.includes("$")) {
17467
17863
  return null;
17468
17864
  }
17469
- return path25.posix.isAbsolute(configured) ? path25.posix.normalize(configured) : null;
17865
+ return path26.posix.isAbsolute(configured) ? path26.posix.normalize(configured) : null;
17470
17866
  }
17471
17867
  return null;
17472
17868
  }
@@ -17520,17 +17916,17 @@ async function resolveDesktopDirectoryPath(options = {}) {
17520
17916
  return [];
17521
17917
  }
17522
17918
  return [
17523
- index === 0 ? trimmed : path25.win32.join(trimmed, "Desktop")
17919
+ index === 0 ? trimmed : path26.win32.join(trimmed, "Desktop")
17524
17920
  ];
17525
17921
  });
17526
- candidates.push(path25.win32.join(userProfile, "Desktop"));
17527
- return await firstUsableKnownFolder(candidates, platform4, probe) ?? path25.win32.join(homeDirectory, "Desktop");
17922
+ candidates.push(path26.win32.join(userProfile, "Desktop"));
17923
+ return await firstUsableKnownFolder(candidates, platform4, probe) ?? path26.win32.join(homeDirectory, "Desktop");
17528
17924
  }
17529
17925
  if (platform4 === "linux") {
17530
17926
  const configHome = environment.XDG_CONFIG_HOME?.trim();
17531
- const safeConfigHome = configHome && path25.posix.isAbsolute(configHome) ? path25.posix.normalize(configHome) : path25.posix.join(homeDirectory, ".config");
17927
+ const safeConfigHome = configHome && path26.posix.isAbsolute(configHome) ? path26.posix.normalize(configHome) : path26.posix.join(homeDirectory, ".config");
17532
17928
  const readConfiguration = options.readUserDirsConfiguration ?? readSmallUserDirsConfiguration;
17533
- const contents = await readConfiguration(path25.posix.join(safeConfigHome, "user-dirs.dirs"));
17929
+ const contents = await readConfiguration(path26.posix.join(safeConfigHome, "user-dirs.dirs"));
17534
17930
  const configured = contents ? parseXdgUserDirectory(contents, homeDirectory, "XDG_DESKTOP_DIR") : null;
17535
17931
  if (configured) {
17536
17932
  const selected = await firstUsableKnownFolder([configured], platform4, probe);
@@ -17567,17 +17963,17 @@ async function resolveUserDirectoryPath(folder, options) {
17567
17963
  ];
17568
17964
  const candidates = [
17569
17965
  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)
17966
+ ...cloudRoots.map((root) => root?.trim() ? path26.win32.join(root.trim(), displayName) : null),
17967
+ path26.win32.join(userProfile, displayName),
17968
+ path26.win32.join(homeDirectory, displayName)
17573
17969
  ];
17574
- return await firstUsableKnownFolder(candidates, platform4, probe) ?? path25.win32.join(homeDirectory, displayName);
17970
+ return await firstUsableKnownFolder(candidates, platform4, probe) ?? path26.win32.join(homeDirectory, displayName);
17575
17971
  }
17576
17972
  if (platform4 === "linux") {
17577
17973
  const configHome = environment.XDG_CONFIG_HOME?.trim();
17578
- const safeConfigHome = configHome && path25.posix.isAbsolute(configHome) ? path25.posix.normalize(configHome) : path25.posix.join(homeDirectory, ".config");
17974
+ const safeConfigHome = configHome && path26.posix.isAbsolute(configHome) ? path26.posix.normalize(configHome) : path26.posix.join(homeDirectory, ".config");
17579
17975
  const readConfiguration = options.readUserDirsConfiguration ?? readSmallUserDirsConfiguration;
17580
- const contents = await readConfiguration(path25.posix.join(safeConfigHome, "user-dirs.dirs"));
17976
+ const contents = await readConfiguration(path26.posix.join(safeConfigHome, "user-dirs.dirs"));
17581
17977
  const configured = contents ? parseXdgUserDirectory(contents, homeDirectory, folder === "documents" ? "XDG_DOCUMENTS_DIR" : "XDG_DOWNLOAD_DIR") : null;
17582
17978
  const selected = await firstUsableKnownFolder([configured], platform4, probe);
17583
17979
  if (selected) {
@@ -17648,6 +18044,17 @@ function mapFilesystemError(error) {
17648
18044
  return new WorkspaceRouteError("workspace_unavailable", "Unable to access the selected workspace.", 500, true);
17649
18045
  }
17650
18046
  function mapNativePickerError(error) {
18047
+ if (error instanceof NativeDirectoryPickerError) {
18048
+ logger13.warn("native_picker_error", {
18049
+ code: error.code,
18050
+ ...error.diagnostic ? { diagnostic: error.diagnostic } : {}
18051
+ });
18052
+ } else {
18053
+ logger13.warn("native_picker_error", {
18054
+ code: "native_picker_failed",
18055
+ error: getErrorMessage(error)
18056
+ });
18057
+ }
17651
18058
  if (!(error instanceof NativeDirectoryPickerError)) {
17652
18059
  return new WorkspaceRouteError("native_picker_failed", "The system folder picker could not be opened.", 500, true);
17653
18060
  }
@@ -17688,17 +18095,21 @@ function rootIdentity(stats) {
17688
18095
  function identitiesMatch(left, right) {
17689
18096
  return left.device === right.device && left.inode === right.inode;
17690
18097
  }
18098
+ async function realpathPreservingDriveRoot(candidate) {
18099
+ const resolved = await fs9.realpath(candidate);
18100
+ return /^[A-Za-z]:$/.test(resolved) ? `${resolved}${path26.win32.sep}` : resolved;
18101
+ }
17691
18102
  function isContainedPath2(root, candidate) {
17692
- const relative = path25.relative(root, candidate);
17693
- return relative === "" || relative !== ".." && !relative.startsWith(`..${path25.sep}`) && !path25.isAbsolute(relative);
18103
+ const relative = path26.relative(root, candidate);
18104
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path26.sep}`) && !path26.isAbsolute(relative);
17694
18105
  }
17695
18106
  async function resolveKnownDirectory(directory) {
17696
18107
  try {
17697
- const root = await fs8.realpath(path25.resolve(directory));
18108
+ const root = await realpathPreservingDriveRoot(path26.resolve(directory));
17698
18109
  if (isFilesystemRootPath(root)) {
17699
18110
  throw new WorkspaceRouteError("workspace_unavailable", "A filesystem root cannot be opened as a workspace location.", 500, false);
17700
18111
  }
17701
- const stats = await fs8.stat(root, { bigint: true });
18112
+ const stats = await fs9.stat(root, { bigint: true });
17702
18113
  if (!stats.isDirectory()) {
17703
18114
  throw new WorkspaceRouteError("workspace_not_directory", "The selected path is not a directory.", 422, false);
17704
18115
  }
@@ -17715,8 +18126,8 @@ async function assertRootIdentity({
17715
18126
  identity
17716
18127
  }) {
17717
18128
  try {
17718
- const canonical = await fs8.realpath(root);
17719
- const stats = await fs8.stat(canonical, { bigint: true });
18129
+ const canonical = await realpathPreservingDriveRoot(root);
18130
+ const stats = await fs9.stat(canonical, { bigint: true });
17720
18131
  if (canonical !== root || !stats.isDirectory() || !identitiesMatch(identity, rootIdentity(stats))) {
17721
18132
  throw new WorkspaceRouteError("workspace_replaced", "The selected workspace changed on disk. Select it again before continuing.", 409, true);
17722
18133
  }
@@ -17729,7 +18140,7 @@ async function assertRootIdentity({
17729
18140
  }
17730
18141
  async function symlinkFailure(root, candidate, locationName) {
17731
18142
  try {
17732
- const target = await fs8.realpath(candidate);
18143
+ const target = await realpathPreservingDriveRoot(candidate);
17733
18144
  if (!isContainedPath2(root, target)) {
17734
18145
  return new WorkspaceRouteError("workspace_symlink_escape", `This symbolic link points outside the allowed ${locationName} location.`, 403, false);
17735
18146
  }
@@ -17744,13 +18155,13 @@ async function symlinkFailure(root, candidate, locationName) {
17744
18155
  async function resolveDirectoryFromSegments(root, segments, locationName) {
17745
18156
  let current = root;
17746
18157
  for (const segment of segments) {
17747
- const candidate = path25.join(current, segment);
18158
+ const candidate = path26.join(current, segment);
17748
18159
  if (!isContainedPath2(root, candidate)) {
17749
18160
  throw new WorkspaceRouteError("workspace_symlink_escape", `The requested path is outside the allowed ${locationName} location.`, 403, false);
17750
18161
  }
17751
18162
  let stats;
17752
18163
  try {
17753
- stats = await fs8.lstat(candidate);
18164
+ stats = await fs9.lstat(candidate);
17754
18165
  } catch (error) {
17755
18166
  throw mapFilesystemError(error);
17756
18167
  }
@@ -17762,13 +18173,13 @@ async function resolveDirectoryFromSegments(root, segments, locationName) {
17762
18173
  }
17763
18174
  current = candidate;
17764
18175
  }
17765
- const canonical = await fs8.realpath(current).catch((error) => {
18176
+ const canonical = await realpathPreservingDriveRoot(current).catch((error) => {
17766
18177
  throw mapFilesystemError(error);
17767
18178
  });
17768
18179
  if (!isContainedPath2(root, canonical)) {
17769
18180
  throw new WorkspaceRouteError("workspace_symlink_escape", `The requested path is outside the allowed ${locationName} location.`, 403, false);
17770
18181
  }
17771
- const metadata = await fs8.lstat(canonical, { bigint: true });
18182
+ const metadata = await fs9.lstat(canonical, { bigint: true });
17772
18183
  if (metadata.isSymbolicLink() || !metadata.isDirectory()) {
17773
18184
  throw new WorkspaceRouteError("workspace_replaced", "The selected folder changed while it was being opened.", 409, true);
17774
18185
  }
@@ -17776,11 +18187,11 @@ async function resolveDirectoryFromSegments(root, segments, locationName) {
17776
18187
  }
17777
18188
  async function assertDirectoryIdentity(root, directory) {
17778
18189
  try {
17779
- const lexical = await fs8.lstat(directory.path, { bigint: true });
18190
+ const lexical = await fs9.lstat(directory.path, { bigint: true });
17780
18191
  if (lexical.isSymbolicLink() || !lexical.isDirectory()) {
17781
18192
  throw new WorkspaceRouteError("workspace_replaced", "The selected folder changed while it was being read.", 409, true);
17782
18193
  }
17783
- const canonical = await fs8.realpath(directory.path);
18194
+ const canonical = await realpathPreservingDriveRoot(directory.path);
17784
18195
  const currentIdentity = rootIdentity(lexical);
17785
18196
  if (canonical !== directory.path || !isContainedPath2(root, canonical) || !identitiesMatch(directory.identity, currentIdentity)) {
17786
18197
  throw new WorkspaceRouteError("workspace_replaced", "The selected folder changed while it was being read.", 409, true);
@@ -17793,6 +18204,9 @@ async function assertDirectoryIdentity(root, directory) {
17793
18204
  throw mapFilesystemError(error);
17794
18205
  }
17795
18206
  }
18207
+ function cursorViewFlags(binding) {
18208
+ return (binding.includeHidden ? 1 : 0) | (binding.directoriesOnly ? 2 : 0);
18209
+ }
17796
18210
  function cursorPathDigest(segments) {
17797
18211
  return createHash6("sha256").update(JSON.stringify(segments)).digest("base64url").slice(0, 22);
17798
18212
  }
@@ -17801,7 +18215,7 @@ function encodeCursor(secret, binding, offset) {
17801
18215
  1,
17802
18216
  binding.browserId,
17803
18217
  cursorPathDigest(binding.segments),
17804
- binding.includeHidden ? 1 : 0,
18218
+ cursorViewFlags(binding),
17805
18219
  offset,
17806
18220
  binding.directory.identity.device,
17807
18221
  binding.directory.identity.inode,
@@ -17831,7 +18245,7 @@ function decodeCursor(secret, cursor, binding) {
17831
18245
  mtimeNs,
17832
18246
  signature
17833
18247
  ] = 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") {
18248
+ 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
18249
  throw new Error("cursor binding mismatch");
17836
18250
  }
17837
18251
  const serialized = JSON.stringify(parsed.slice(0, 8));
@@ -17847,12 +18261,12 @@ function decodeCursor(secret, cursor, binding) {
17847
18261
  }
17848
18262
  }
17849
18263
  async function describeEntry(root, directory, name) {
17850
- const candidate = path25.join(directory, name);
18264
+ const candidate = path26.join(directory, name);
17851
18265
  try {
17852
- const stats = await fs8.lstat(candidate);
18266
+ const stats = await fs9.lstat(candidate);
17853
18267
  if (stats.isSymbolicLink()) {
17854
18268
  try {
17855
- const target = await fs8.realpath(candidate);
18269
+ const target = await realpathPreservingDriveRoot(candidate);
17856
18270
  return {
17857
18271
  name,
17858
18272
  kind: "symlink",
@@ -17872,7 +18286,7 @@ async function describeEntry(root, directory, name) {
17872
18286
  }
17873
18287
  const kind = stats.isDirectory() ? "directory" : stats.isFile() ? "file" : "other";
17874
18288
  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);
18289
+ const readable = await fs9.access(candidate, requiredAccess).then(() => true).catch(() => false);
17876
18290
  return {
17877
18291
  name,
17878
18292
  kind,
@@ -17911,7 +18325,7 @@ function knownLocation(definition, state) {
17911
18325
  return {
17912
18326
  id: definition.id,
17913
18327
  name: definition.name,
17914
- kind: "known-folder",
18328
+ kind: definition.kind,
17915
18329
  state,
17916
18330
  pathLabel: definition.pathLabel
17917
18331
  };
@@ -17933,6 +18347,7 @@ function createWorkspaceRoutes(options = {}) {
17933
18347
  const definitions = [
17934
18348
  {
17935
18349
  id: "desktop",
18350
+ kind: "known-folder",
17936
18351
  name: "Desktop",
17937
18352
  rootName: "Desktop",
17938
18353
  pathLabel: options.locationPathLabels?.desktop ?? desktopPathLabel,
@@ -17940,6 +18355,7 @@ function createWorkspaceRoutes(options = {}) {
17940
18355
  },
17941
18356
  {
17942
18357
  id: "home",
18358
+ kind: "known-folder",
17943
18359
  name: "Home (broad access)",
17944
18360
  rootName: "Home",
17945
18361
  pathLabel: options.locationPathLabels?.home ?? "Home",
@@ -17947,6 +18363,7 @@ function createWorkspaceRoutes(options = {}) {
17947
18363
  },
17948
18364
  {
17949
18365
  id: "documents",
18366
+ kind: "known-folder",
17950
18367
  name: "Documents",
17951
18368
  rootName: "Documents",
17952
18369
  pathLabel: options.locationPathLabels?.documents ?? "Documents",
@@ -17954,6 +18371,7 @@ function createWorkspaceRoutes(options = {}) {
17954
18371
  },
17955
18372
  {
17956
18373
  id: "downloads",
18374
+ kind: "known-folder",
17957
18375
  name: "Downloads",
17958
18376
  rootName: "Downloads",
17959
18377
  pathLabel: options.locationPathLabels?.downloads ?? "Downloads",
@@ -17961,16 +18379,45 @@ function createWorkspaceRoutes(options = {}) {
17961
18379
  },
17962
18380
  {
17963
18381
  id: "projects",
18382
+ kind: "known-folder",
17964
18383
  name: "Projects",
17965
18384
  rootName: "Projects",
17966
18385
  pathLabel: options.locationPathLabels?.projects ?? "Projects",
17967
18386
  resolvePath: configuredKnownLocationResolver(options, "projects", () => resolveProjectsDirectoryPath())
17968
18387
  }
17969
18388
  ];
18389
+ if (options.enableDrives ?? process.platform === "win32") {
18390
+ definitions.push({
18391
+ id: "drives",
18392
+ kind: "drives",
18393
+ name: "This PC",
18394
+ rootName: "This PC",
18395
+ pathLabel: "This PC",
18396
+ resolvePath: async () => {
18397
+ throw new Error("The Drives location has no single root.");
18398
+ }
18399
+ });
18400
+ }
18401
+ const listDrives = options.listDrives ?? (() => listWindowsDrives());
18402
+ const driveRoot = options.driveRoot ?? windowsDriveRoot;
18403
+ const usesDefaultKnownFolders = !options.locationPaths && !options.resolveLocationPaths && !options.resolveDesktopPath && !options.desktopPath;
18404
+ const prewarmKnownFolders = options.prewarmKnownFolders ?? (process.platform === "win32" && usesDefaultKnownFolders ? () => void resolveWindowsKnownFolders(Bun.env) : () => {
18405
+ return;
18406
+ });
18407
+ const listRecentWorkspaces2 = options.listRecentWorkspaces ?? listRecentWorkspaces;
18408
+ const recentKeySecret = randomBytes(32);
18409
+ const recentWorkspaceKey = (cwd) => createHmac("sha256", recentKeySecret).update(process.platform === "win32" ? cwd.toLowerCase() : cwd).digest("base64url").slice(0, 22);
17970
18410
  const definitionsById = new Map(definitions.map((definition) => [definition.id, definition]));
17971
18411
  const createSession = options.createSession ?? createChatSession;
17972
18412
  const nativeDirectoryPicker = options.nativeDirectoryPicker ?? createNativeDirectoryPicker();
17973
18413
  const authorizeNativePickerRequest = options.authorizeNativePickerRequest ?? isAuthenticatedWebRequest;
18414
+ const launchFolderPath = parseLaunchFolderPath(options.launchFolderPath === undefined ? Bun.env.LIGHTCODE_WEB_LAUNCH_CWD : options.launchFolderPath);
18415
+ const resolveLaunchFolder = async () => {
18416
+ if (!launchFolderPath) {
18417
+ throw new WorkspaceRouteError("workspace_unavailable", "Lightcode was not started from a project folder.", 404, false);
18418
+ }
18419
+ return resolveKnownDirectory(launchFolderPath);
18420
+ };
17974
18421
  const cursorSecret = randomBytes(32);
17975
18422
  const browserCapabilities = new Map;
17976
18423
  const workspaceGrants = new Map;
@@ -17993,9 +18440,10 @@ function createWorkspaceRoutes(options = {}) {
17993
18440
  root,
17994
18441
  identity,
17995
18442
  name,
17996
- pathLabel
18443
+ pathLabel,
18444
+ request
17997
18445
  }) => {
17998
- await fs8.access(root, fsConstants3.R_OK | fsConstants3.X_OK);
18446
+ await fs9.access(root, fsConstants3.R_OK | fsConstants3.X_OK);
17999
18447
  const createdAt = new Date(now()).toISOString();
18000
18448
  const id = crypto.randomUUID();
18001
18449
  const grant = {
@@ -18014,7 +18462,27 @@ function createWorkspaceRoutes(options = {}) {
18014
18462
  workspaceGrants.delete(id);
18015
18463
  throw error;
18016
18464
  }
18017
- return workspaceGrantSchema.parse({ id, name, pathLabel, createdAt });
18465
+ return workspaceGrantSchema.parse({
18466
+ id,
18467
+ name,
18468
+ pathLabel,
18469
+ ...authorizeNativePickerRequest(request) ? { hostPath: root } : {},
18470
+ createdAt
18471
+ });
18472
+ };
18473
+ const resolveBrowseScope = async (capability2, segments) => {
18474
+ if (capability2.kind === "folder") {
18475
+ await assertRootIdentity(capability2);
18476
+ return { root: capability2.root, segments, rootName: capability2.rootName };
18477
+ }
18478
+ const [token, ...rest] = segments;
18479
+ if (!token || !windowsDriveTokenPattern.test(token) || !capability2.driveTokens?.has(token)) {
18480
+ throw new WorkspaceRouteError("location_not_found", "That drive is not available. Open This PC again.", 404, true);
18481
+ }
18482
+ const root = await realpathPreservingDriveRoot(driveRoot(token)).catch((error) => {
18483
+ throw mapFilesystemError(error);
18484
+ });
18485
+ return { root, segments: rest, rootName: token };
18018
18486
  };
18019
18487
  return new Hono7().post("/picker/open", zValidator3("json", workspaceNativePickerRequestSchema, (result, c) => {
18020
18488
  if (!result.success) {
@@ -18030,7 +18498,10 @@ function createWorkspaceRoutes(options = {}) {
18030
18498
  try {
18031
18499
  let picked;
18032
18500
  try {
18033
- picked = await nativeDirectoryPicker.pick(c.req.raw.signal);
18501
+ picked = await nativeDirectoryPicker.pick(AbortSignal.any([
18502
+ c.req.raw.signal,
18503
+ AbortSignal.timeout(nativePickerMaximumMs)
18504
+ ]));
18034
18505
  } catch (error) {
18035
18506
  throw mapNativePickerError(error);
18036
18507
  }
@@ -18041,8 +18512,9 @@ function createWorkspaceRoutes(options = {}) {
18041
18512
  }
18042
18513
  const directory = await resolveKnownDirectory(picked.directory);
18043
18514
  const workspace = await storeWorkspaceGrant({
18515
+ request: c.req.raw,
18044
18516
  ...directory,
18045
- name: path25.basename(directory.root),
18517
+ name: path26.basename(directory.root),
18046
18518
  pathLabel: directory.root
18047
18519
  });
18048
18520
  return c.json(workspaceNativePickerResponseSchema.parse({
@@ -18052,9 +18524,97 @@ function createWorkspaceRoutes(options = {}) {
18052
18524
  } catch (error) {
18053
18525
  return workspaceErrorResponse(c, error);
18054
18526
  }
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) => {
18527
+ }).get("/launch-folder", async (c) => {
18528
+ try {
18529
+ const { root } = await resolveLaunchFolder();
18530
+ const home = await realpathPreservingDriveRoot(homedir5()).catch(() => homedir5());
18531
+ return c.json(workspaceLaunchFolderResponseSchema.parse({
18532
+ available: true,
18533
+ name: path26.basename(root),
18534
+ pathLabel: root,
18535
+ broad: isContainedPath2(root, home)
18536
+ }));
18537
+ } catch {
18538
+ return c.json(workspaceLaunchFolderResponseSchema.parse({ available: false }));
18539
+ }
18540
+ }).post("/launch-folder/grant", zValidator3("json", workspaceLaunchFolderGrantRequestSchema, (result, c) => {
18541
+ if (!result.success) {
18542
+ return invalidRequestResponse(c);
18543
+ }
18544
+ }), async (c) => {
18545
+ if (!authorizeNativePickerRequest(c.req.raw)) {
18546
+ return c.json({
18547
+ error: "The launch folder can be opened only from an authenticated Lightcode web session.",
18548
+ code: "web_browser_required"
18549
+ }, 403);
18550
+ }
18551
+ try {
18552
+ const directory = await resolveLaunchFolder();
18553
+ const workspace = await storeWorkspaceGrant({
18554
+ request: c.req.raw,
18555
+ ...directory,
18556
+ name: path26.basename(directory.root),
18557
+ pathLabel: directory.root
18558
+ });
18559
+ return c.json(workspaceBrowserSelectResponseSchema.parse({ workspace }), 201);
18560
+ } catch (error) {
18561
+ return workspaceErrorResponse(c, error);
18562
+ }
18563
+ }).get("/locations", (c) => {
18564
+ prewarmKnownFolders();
18565
+ return c.json(workspaceLocationsResponseSchema.parse({
18566
+ locations: definitions.map((definition) => knownLocation(definition, "unprobed"))
18567
+ }));
18568
+ }).get("/recent", async (c) => {
18569
+ try {
18570
+ const recent = (await listRecentWorkspaces2(12)).filter((workspace) => !isFilesystemRootPath(workspace.cwd)).slice(0, 8);
18571
+ const projects = await Promise.all(recent.map(async (workspace) => ({
18572
+ key: recentWorkspaceKey(workspace.cwd),
18573
+ name: path26.basename(workspace.cwd) || workspace.cwd,
18574
+ pathLabel: workspace.cwd,
18575
+ lastUsedAt: workspace.lastUsedAt.toISOString(),
18576
+ available: await fs9.stat(workspace.cwd).then((stats) => stats.isDirectory()).catch(() => false)
18577
+ })));
18578
+ return c.json(workspaceRecentProjectsResponseSchema.parse({ projects }));
18579
+ } catch (error) {
18580
+ return workspaceErrorResponse(c, error);
18581
+ }
18582
+ }).post("/recent/:key/grant", zValidator3("param", workspaceRecentPathParamsSchema, (result, c) => {
18583
+ if (!result.success) {
18584
+ return invalidRequestResponse(c);
18585
+ }
18586
+ }), zValidator3("json", workspaceRecentGrantRequestSchema, (result, c) => {
18587
+ if (!result.success) {
18588
+ return invalidRequestResponse(c);
18589
+ }
18590
+ }), async (c) => {
18591
+ if (!authorizeNativePickerRequest(c.req.raw)) {
18592
+ return c.json({
18593
+ error: "Recent projects can be opened only from an authenticated Lightcode web session.",
18594
+ code: "web_browser_required"
18595
+ }, 403);
18596
+ }
18597
+ try {
18598
+ const { key } = c.req.valid("param");
18599
+ const recent = (await listRecentWorkspaces2(50)).find((workspace2) => recentWorkspaceKey(workspace2.cwd) === key);
18600
+ if (!recent) {
18601
+ throw new WorkspaceRouteError("location_not_found", "That recent project is no longer listed. Refresh and try again.", 404, false);
18602
+ }
18603
+ const directory = await resolveKnownDirectory(recent.cwd);
18604
+ if (recent.identity && recent.identity.inode !== "unsupported" && directory.identity.inode !== "unsupported" && !identitiesMatch(recent.identity, directory.identity)) {
18605
+ throw new WorkspaceRouteError("workspace_replaced", "This folder was replaced since it was last used. Choose it again with the folder picker.", 409, false);
18606
+ }
18607
+ const workspace = await storeWorkspaceGrant({
18608
+ request: c.req.raw,
18609
+ ...directory,
18610
+ name: path26.basename(directory.root),
18611
+ pathLabel: directory.root
18612
+ });
18613
+ return c.json(workspaceBrowserSelectResponseSchema.parse({ workspace }), 201);
18614
+ } catch (error) {
18615
+ return workspaceErrorResponse(c, error);
18616
+ }
18617
+ }).post("/browser/open", zValidator3("json", workspaceBrowserOpenRequestSchema, (result, c) => {
18058
18618
  if (!result.success) {
18059
18619
  return invalidRequestResponse(c);
18060
18620
  }
@@ -18065,17 +18625,43 @@ function createWorkspaceRoutes(options = {}) {
18065
18625
  if (!definition) {
18066
18626
  throw new WorkspaceRouteError("location_not_found", "Workspace location not found.", 404, false);
18067
18627
  }
18628
+ if (definition.kind === "drives") {
18629
+ const browserId2 = crypto.randomUUID();
18630
+ const expiresAtMs2 = now() + capabilityTtlMs;
18631
+ const location2 = knownLocation(definition, "available");
18632
+ browserCapabilities.set(browserId2, {
18633
+ id: browserId2,
18634
+ kind: "drives",
18635
+ root: "",
18636
+ identity: { device: "drives", inode: "drives" },
18637
+ location: location2,
18638
+ rootName: definition.rootName,
18639
+ expiresAtMs: expiresAtMs2
18640
+ });
18641
+ trimOldestEntries(browserCapabilities, maximumBrowserCapabilities);
18642
+ return c.json(workspaceBrowserOpenResponseSchema.parse({
18643
+ browserId: browserId2,
18644
+ location: location2,
18645
+ expiresAt: new Date(expiresAtMs2).toISOString()
18646
+ }), 201);
18647
+ }
18068
18648
  const discoveredPath = await definition.resolvePath();
18069
18649
  const safePath = normalizeKnownFolderCandidate(discoveredPath, process.platform);
18070
18650
  if (!safePath) {
18071
18651
  throw new WorkspaceRouteError("workspace_unavailable", `The resolved ${definition.rootName} location is not a safe absolute folder path.`, 500, false);
18072
18652
  }
18073
- const resolved = await resolveKnownDirectory(safePath);
18653
+ const resolved = await resolveKnownDirectory(safePath).catch((error) => {
18654
+ if (error instanceof WorkspaceRouteError && error.code === "workspace_missing") {
18655
+ 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);
18656
+ }
18657
+ throw error;
18658
+ });
18074
18659
  const browserId = crypto.randomUUID();
18075
18660
  const expiresAtMs = now() + capabilityTtlMs;
18076
18661
  const location = knownLocation(definition, "available");
18077
18662
  browserCapabilities.set(browserId, {
18078
18663
  id: browserId,
18664
+ kind: "folder",
18079
18665
  ...resolved,
18080
18666
  location,
18081
18667
  rootName: definition.rootName,
@@ -18103,13 +18689,31 @@ function createWorkspaceRoutes(options = {}) {
18103
18689
  const { browserId } = c.req.valid("param");
18104
18690
  const input = c.req.valid("json");
18105
18691
  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);
18692
+ if (capability2.kind === "drives" && input.segments.length === 0) {
18693
+ const drives = await listDrives();
18694
+ capability2.driveTokens = new Set(drives.map((drive) => drive.token));
18695
+ return c.json(workspaceBrowserEntriesResponseSchema.parse({
18696
+ browserId,
18697
+ segments: [],
18698
+ entries: drives.map((drive) => ({
18699
+ name: drive.token,
18700
+ kind: "directory",
18701
+ size: null,
18702
+ readable: drive.readable,
18703
+ symlinkState: null
18704
+ })),
18705
+ nextCursor: null,
18706
+ truncated: false
18707
+ }));
18708
+ }
18709
+ const scope = await resolveBrowseScope(capability2, input.segments);
18710
+ const directory = await resolveDirectoryFromSegments(scope.root, scope.segments, scope.rootName);
18711
+ const beforeRead = await assertDirectoryIdentity(scope.root, directory);
18109
18712
  const cursorBinding = {
18110
18713
  browserId,
18111
18714
  segments: input.segments,
18112
18715
  includeHidden: input.includeHidden,
18716
+ directoriesOnly: input.directoriesOnly,
18113
18717
  directory,
18114
18718
  mtimeNs: beforeRead.mtimeNs
18115
18719
  };
@@ -18121,14 +18725,17 @@ function createWorkspaceRoutes(options = {}) {
18121
18725
  const describedEntries = [];
18122
18726
  let visibleOffset = 0;
18123
18727
  let truncated = false;
18124
- const directoryHandle = await fs8.opendir(directory.path);
18728
+ const directoryHandle = await fs9.opendir(directory.path);
18125
18729
  try {
18126
18730
  while (true) {
18127
18731
  const entry = await directoryHandle.read();
18128
18732
  if (!entry) {
18129
18733
  break;
18130
18734
  }
18131
- if (!input.includeHidden && entry.name.startsWith(".")) {
18735
+ if (!input.includeHidden && isHiddenBrowserEntry(entry)) {
18736
+ continue;
18737
+ }
18738
+ if (input.directoriesOnly && isOmittedFromFolderListing(entry)) {
18132
18739
  continue;
18133
18740
  }
18134
18741
  if (visibleOffset < offset) {
@@ -18140,7 +18747,7 @@ function createWorkspaceRoutes(options = {}) {
18140
18747
  break;
18141
18748
  }
18142
18749
  visibleOffset += 1;
18143
- const described = await describeEntry(capability2.root, directory.path, entry.name);
18750
+ const described = await describeEntry(scope.root, directory.path, entry.name);
18144
18751
  if (described) {
18145
18752
  describedEntries.push(described);
18146
18753
  }
@@ -18154,7 +18761,7 @@ function createWorkspaceRoutes(options = {}) {
18154
18761
  }
18155
18762
  }
18156
18763
  }
18157
- const afterRead = await assertDirectoryIdentity(capability2.root, directory);
18764
+ const afterRead = await assertDirectoryIdentity(scope.root, directory);
18158
18765
  if (afterRead.mtimeNs !== beforeRead.mtimeNs) {
18159
18766
  throw new WorkspaceRouteError("invalid_cursor", "The folder changed while it was being listed. Refresh and try again.", 409, true);
18160
18767
  }
@@ -18181,19 +18788,20 @@ function createWorkspaceRoutes(options = {}) {
18181
18788
  const { browserId } = c.req.valid("param");
18182
18789
  const { segments } = c.req.valid("json");
18183
18790
  const capability2 = getBrowserCapability(browserId);
18184
- await assertRootIdentity(capability2);
18185
- const directory = await resolveDirectoryFromSegments(capability2.root, segments, capability2.rootName);
18186
- await assertDirectoryIdentity(capability2.root, directory);
18791
+ if (capability2.kind === "drives" && segments.length < 2) {
18792
+ 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);
18793
+ }
18794
+ const scope = await resolveBrowseScope(capability2, segments);
18795
+ const directory = await resolveDirectoryFromSegments(scope.root, scope.segments, scope.rootName);
18796
+ await assertDirectoryIdentity(scope.root, directory);
18187
18797
  await options.beforeDirectoryOperation?.({
18188
18798
  operation: "select",
18189
18799
  directory: directory.path
18190
18800
  });
18191
18801
  const name = segments.at(-1) ?? capability2.rootName;
18192
- const pathLabel = [
18193
- capability2.location.pathLabel,
18194
- ...segments
18195
- ].join("/");
18802
+ const pathLabel = capability2.kind === "drives" ? directory.path : [capability2.location.pathLabel, ...segments].join("/");
18196
18803
  const workspace = await storeWorkspaceGrant({
18804
+ request: c.req.raw,
18197
18805
  root: directory.path,
18198
18806
  identity: directory.identity,
18199
18807
  name,
@@ -18232,7 +18840,7 @@ function createWorkspaceRoutes(options = {}) {
18232
18840
  }
18233
18841
  });
18234
18842
  }
18235
- var logger13, defaultBrowserCapabilityTtlMs, maximumBrowserCapabilities = 64, maximumWorkspaceGrants = 256, maximumUserDirsConfigurationBytes, WorkspaceRouteError, workspaceRoutes;
18843
+ var logger13, defaultBrowserCapabilityTtlMs, maximumBrowserCapabilities = 64, maximumWorkspaceGrants = 256, maximumUserDirsConfigurationBytes, nativePickerMaximumMs, launchFolderEnvironmentSchema, WorkspaceRouteError, workspaceRoutes;
18236
18844
  var init_workspace_routes = __esm(() => {
18237
18845
  init_src2();
18238
18846
  init_src();
@@ -18240,9 +18848,14 @@ var init_workspace_routes = __esm(() => {
18240
18848
  init_runtime_config();
18241
18849
  init_native_directory_picker();
18242
18850
  init_web_auth();
18851
+ init_windows_known_folders();
18852
+ init_workspace_entry_filter();
18853
+ init_workspace_drives();
18243
18854
  logger13 = createLogger("workspace-routes");
18244
18855
  defaultBrowserCapabilityTtlMs = 10 * 60 * 1000;
18245
18856
  maximumUserDirsConfigurationBytes = 16 * 1024;
18857
+ nativePickerMaximumMs = 10 * 60 * 1000;
18858
+ launchFolderEnvironmentSchema = z45.string().min(1).max(4096).refine((value) => !value.includes("\x00")).refine((value) => path26.isAbsolute(value));
18246
18859
  WorkspaceRouteError = class WorkspaceRouteError extends Error {
18247
18860
  code;
18248
18861
  status;
@@ -18258,12 +18871,151 @@ var init_workspace_routes = __esm(() => {
18258
18871
  workspaceRoutes = createWorkspaceRoutes();
18259
18872
  });
18260
18873
 
18874
+ // apps/server/src/routes/session-changes-routes.ts
18875
+ import { zValidator as zValidator4 } from "@hono/zod-validator";
18876
+ import { createHmac as createHmac2, randomBytes as randomBytes2 } from "crypto";
18877
+ import { promises as fs10 } from "fs";
18878
+ import path27 from "path";
18879
+ import { Hono as Hono8 } from "hono";
18880
+ async function defaultResolveSessionWorkspace(id) {
18881
+ const sessionId = await resolveChatSessionIdentifier(id);
18882
+ return (await assertSessionWorkspaceIdentity(sessionId)).cwd;
18883
+ }
18884
+ function isContained(root, candidate) {
18885
+ const relative = path27.relative(root, candidate);
18886
+ return relative !== "" && relative !== ".." && !relative.startsWith(`..${path27.sep}`) && !path27.isAbsolute(relative);
18887
+ }
18888
+ async function untrackedFileDiff(workspace, relativePath) {
18889
+ const candidate = path27.resolve(workspace, relativePath);
18890
+ const stats = await fs10.lstat(candidate).catch(() => null);
18891
+ if (!stats || !stats.isFile() || !isContained(workspace, candidate)) {
18892
+ return { diff: "", truncated: false, binary: false };
18893
+ }
18894
+ const handle = await fs10.open(candidate, "r");
18895
+ try {
18896
+ const length = Math.min(stats.size, maximumUntrackedBytes);
18897
+ const buffer = Buffer.alloc(length);
18898
+ await handle.read(buffer, 0, length, 0);
18899
+ if (buffer.includes(0)) {
18900
+ return { diff: "", truncated: false, binary: true };
18901
+ }
18902
+ const lines = buffer.toString("utf8").replace(/\r\n/g, `
18903
+ `).split(`
18904
+ `);
18905
+ if (lines.at(-1) === "")
18906
+ lines.pop();
18907
+ const diff = [
18908
+ "--- /dev/null",
18909
+ `+++ b/${relativePath}`,
18910
+ `@@ -0,0 +1,${lines.length} @@`,
18911
+ ...lines.map((line) => `+${line}`)
18912
+ ].join(`
18913
+ `);
18914
+ return { diff, truncated: stats.size > maximumUntrackedBytes, binary: false };
18915
+ } finally {
18916
+ await handle.close();
18917
+ }
18918
+ }
18919
+ function errorResponse(c, error, event) {
18920
+ if (error instanceof SessionNotFoundError) {
18921
+ return c.json({ error: error.message }, 404);
18922
+ }
18923
+ if (error instanceof SessionWorkspaceIdentityError) {
18924
+ return c.json({ error: error.message, code: error.code }, error.code === "workspace_unavailable" ? 404 : 409);
18925
+ }
18926
+ return internalErrorResponse(c, {
18927
+ event,
18928
+ message: "Unable to read the session's changes.",
18929
+ error
18930
+ });
18931
+ }
18932
+ function createSessionChangesRoutes(options = {}) {
18933
+ const resolveSessionWorkspace = options.resolveSessionWorkspace ?? defaultResolveSessionWorkspace;
18934
+ const fileIdSecret = randomBytes2(32);
18935
+ const fileId = (workspace, entryPath) => createHmac2("sha256", fileIdSecret).update(`${workspace}\x00${entryPath}`).digest("base64url").slice(0, 22);
18936
+ return new Hono8().get("/:id/changes", zValidator4("param", sessionPathParamsSchema), async (c) => {
18937
+ try {
18938
+ const workspace = await resolveSessionWorkspace(c.req.valid("param").id);
18939
+ const context2 = createWorkspaceContext(workspace);
18940
+ const status = await executeGitStatus({}, context2);
18941
+ if (!status.ok) {
18942
+ return c.json(sessionChangesResponseSchema.parse({
18943
+ isRepo: false,
18944
+ branch: null,
18945
+ files: [],
18946
+ truncated: false
18947
+ }));
18948
+ }
18949
+ const counts = await executeGitNumstat(context2);
18950
+ const files = status.entries.slice(0, maximumChangedFiles).map((entry) => {
18951
+ const lineCounts = counts.get(entry.path);
18952
+ return {
18953
+ fileId: fileId(workspace, entry.path),
18954
+ path: entry.path,
18955
+ originalPath: entry.originalPath,
18956
+ changeKind: changeKindFromGitStatus(entry.indexStatus, entry.workingTreeStatus),
18957
+ added: lineCounts ? lineCounts.added : null,
18958
+ removed: lineCounts ? lineCounts.removed : null
18959
+ };
18960
+ });
18961
+ return c.json(sessionChangesResponseSchema.parse({
18962
+ isRepo: true,
18963
+ branch: status.branch,
18964
+ files,
18965
+ truncated: status.truncated || status.entries.length > maximumChangedFiles
18966
+ }));
18967
+ } catch (error) {
18968
+ return errorResponse(c, error, "session_changes_failed");
18969
+ }
18970
+ }).get("/:id/changes/:fileId/diff", zValidator4("param", sessionChangeDiffParamsSchema), async (c) => {
18971
+ try {
18972
+ const params = c.req.valid("param");
18973
+ const workspace = await resolveSessionWorkspace(params.id);
18974
+ const context2 = createWorkspaceContext(workspace);
18975
+ const status = await executeGitStatus({}, context2);
18976
+ const entry = status.ok ? status.entries.find((candidate) => fileId(workspace, candidate.path) === params.fileId) : undefined;
18977
+ if (!entry) {
18978
+ return c.json({ error: "That file is no longer changed. Refresh the list." }, 404);
18979
+ }
18980
+ if (changeKindFromGitStatus(entry.indexStatus, entry.workingTreeStatus) === "untracked") {
18981
+ return c.json(sessionChangeDiffResponseSchema.parse({
18982
+ path: entry.path,
18983
+ ...await untrackedFileDiff(workspace, entry.path)
18984
+ }));
18985
+ }
18986
+ let result = await executeGitDiff({ path: entry.path }, context2);
18987
+ if (result.ok && !result.diff.trim()) {
18988
+ const staged = await executeGitDiff({ path: entry.path, staged: true }, context2);
18989
+ if (staged.ok && staged.diff.trim())
18990
+ result = staged;
18991
+ }
18992
+ return c.json(sessionChangeDiffResponseSchema.parse({
18993
+ path: entry.path,
18994
+ diff: result.ok ? result.diff : "",
18995
+ truncated: result.ok ? result.truncated : false,
18996
+ binary: result.ok && /^Binary files /m.test(result.diff)
18997
+ }));
18998
+ } catch (error) {
18999
+ return errorResponse(c, error, "session_change_diff_failed");
19000
+ }
19001
+ });
19002
+ }
19003
+ var maximumUntrackedBytes, maximumChangedFiles = 500, sessionChangesRoutes;
19004
+ var init_session_changes_routes = __esm(() => {
19005
+ init_src2();
19006
+ init_runtime17();
19007
+ init_chat_store();
19008
+ init_route_helpers();
19009
+ maximumUntrackedBytes = 256 * 1024;
19010
+ sessionChangesRoutes = createSessionChangesRoutes();
19011
+ });
19012
+
18261
19013
  // apps/server/src/app.ts
18262
19014
  var exports_app = {};
18263
19015
  __export(exports_app, {
18264
19016
  app: () => app
18265
19017
  });
18266
- import { Hono as Hono8 } from "hono";
19018
+ import { Hono as Hono9 } from "hono";
18267
19019
  import { bodyLimit } from "hono/body-limit";
18268
19020
  var maxRequestBodyBytes, app;
18269
19021
  var init_app = __esm(() => {
@@ -18275,11 +19027,12 @@ var init_app = __esm(() => {
18275
19027
  init_root_routes();
18276
19028
  init_web_routes();
18277
19029
  init_workspace_routes();
19030
+ init_session_changes_routes();
18278
19031
  maxRequestBodyBytes = 32 * 1024 * 1024;
18279
- app = new Hono8().use("*", createWebSecurityMiddleware()).use(bodyLimit({
19032
+ app = new Hono9().use("*", createWebSecurityMiddleware()).use(bodyLimit({
18280
19033
  maxSize: maxRequestBodyBytes,
18281
19034
  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);
19035
+ })).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
19036
  });
18284
19037
 
18285
19038
  // apps/server/src/lib/headroom-proxy.ts
@@ -18802,17 +19555,27 @@ function isLoopbackBindHost(hostname) {
18802
19555
  return isIP(normalized) === 6 && normalized === "::1";
18803
19556
  }
18804
19557
 
18805
- // apps/server/src/index.ts
18806
- var logger15 = createLogger("server");
18807
- var httpIdleTimeoutSeconds = 30;
18808
- var shutdownForceExitMs = 5000;
18809
- function isChatStreamingRoute(request) {
19558
+ // apps/server/src/lib/long-running-routes.ts
19559
+ function isLongRunningRoute(request) {
18810
19560
  if (request.method !== "POST") {
18811
19561
  return false;
18812
19562
  }
18813
19563
  const pathname = new URL(request.url).pathname;
18814
- return /^\/sessions\/[^/]+\/(?:chat|turns)$/.test(pathname);
19564
+ return /^\/sessions\/[^/]+\/(?:chat|turns)$/.test(pathname) || pathname === "/workspaces/picker/open";
18815
19565
  }
19566
+ function createServerFetch(app) {
19567
+ return (request, server) => {
19568
+ if (isLongRunningRoute(request)) {
19569
+ server.timeout(request, 0);
19570
+ }
19571
+ return app.fetch(request);
19572
+ };
19573
+ }
19574
+
19575
+ // apps/server/src/index.ts
19576
+ var logger15 = createLogger("server");
19577
+ var httpIdleTimeoutSeconds = 30;
19578
+ var shutdownForceExitMs = 5000;
18816
19579
  function serverLockFailureMessage(error) {
18817
19580
  if (error instanceof ServerInstanceLockError && error.code === "server_already_running") {
18818
19581
  return [
@@ -18942,12 +19705,7 @@ async function startServer() {
18942
19705
  idleTimeout: httpIdleTimeoutSeconds,
18943
19706
  port,
18944
19707
  hostname,
18945
- async fetch(request, bunServer) {
18946
- if (isChatStreamingRoute(request)) {
18947
- bunServer.timeout(request, 0);
18948
- }
18949
- return app2.fetch(request);
18950
- }
19708
+ fetch: createServerFetch(app2)
18951
19709
  });
18952
19710
  } catch (error) {
18953
19711
  await releaseServerLock(instanceLock, "bind-failure");