@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/README.md +197 -197
- package/assets/web/index-ghcpktm7.js +183 -0
- package/assets/web/index-n305tms3.css +1 -0
- package/assets/web/index.html +23 -23
- package/cli.js +5243 -4663
- package/package.json +1 -1
- package/server.js +837 -176
- package/assets/grammars/c/highlights.scm +0 -341
- package/assets/grammars/c/tree-sitter-c.wasm +0 -0
- package/assets/grammars/cpp/highlights.scm +0 -74
- package/assets/grammars/cpp/tree-sitter-cpp.wasm +0 -0
- package/assets/grammars/css/highlights.scm +0 -109
- package/assets/grammars/css/tree-sitter-css.wasm +0 -0
- package/assets/grammars/go/highlights.scm +0 -123
- package/assets/grammars/go/tree-sitter-go.wasm +0 -0
- package/assets/grammars/html/highlights.scm +0 -7
- package/assets/grammars/html/tree-sitter-html.wasm +0 -0
- package/assets/grammars/json/highlights.scm +0 -38
- package/assets/grammars/json/tree-sitter-json.wasm +0 -0
- package/assets/grammars/python/highlights.scm +0 -443
- package/assets/grammars/python/tree-sitter-python.wasm +0 -0
- package/assets/grammars/rust/highlights.scm +0 -159
- package/assets/grammars/rust/tree-sitter-rust.wasm +0 -0
- package/assets/grammars/yaml/highlights.scm +0 -99
- package/assets/grammars/yaml/tree-sitter-yaml.wasm +0 -0
- package/assets/web/index-5zxr27sp.js +0 -171
- package/assets/web/index-7g2m4nsk.css +0 -1
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.
|
|
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
|
|
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 =
|
|
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 =
|
|
13287
|
-
id:
|
|
13288
|
-
name:
|
|
13289
|
-
contextLength:
|
|
13290
|
-
maxCompletionTokens:
|
|
13291
|
-
supportsTools:
|
|
13292
|
-
supportsReasoning:
|
|
13293
|
-
pricing:
|
|
13294
|
-
prompt:
|
|
13295
|
-
completion:
|
|
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 =
|
|
13299
|
-
fetchedAt:
|
|
13300
|
-
models:
|
|
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
|
|
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 =
|
|
14390
|
-
epoch:
|
|
14391
|
-
source:
|
|
14392
|
-
sha256:
|
|
14393
|
-
content:
|
|
14394
|
-
discoveredAt:
|
|
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 =
|
|
14397
|
-
epochs:
|
|
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
|
|
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 =
|
|
16532
|
+
selectModelRequestSchema = z42.object({
|
|
16390
16533
|
provider: lightcodeProviderSchema,
|
|
16391
|
-
model:
|
|
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
|
|
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 =
|
|
16845
|
-
name:
|
|
16987
|
+
skillParamsSchema = z43.object({
|
|
16988
|
+
name: z43.string().min(1).max(160)
|
|
16846
16989
|
});
|
|
16847
|
-
skillListQuerySchema =
|
|
16990
|
+
skillListQuerySchema = z43.object({
|
|
16848
16991
|
sessionId: sessionIdentifierSchema.optional()
|
|
16849
16992
|
});
|
|
16850
|
-
mcpServerParamsSchema =
|
|
16851
|
-
name:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
$
|
|
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
|
|
17685
|
+
import { constants as fsConstants3, promises as fs9 } from "fs";
|
|
17339
17686
|
import { homedir as homedir5 } from "os";
|
|
17340
|
-
import
|
|
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" ?
|
|
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
|
|
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
|
|
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
|
|
17760
|
+
return path26.posix.resolve(homeDirectory, configured.slice(6));
|
|
17462
17761
|
}
|
|
17463
17762
|
if (configured.startsWith("${HOME}/")) {
|
|
17464
|
-
return
|
|
17763
|
+
return path26.posix.resolve(homeDirectory, configured.slice(8));
|
|
17465
17764
|
}
|
|
17466
17765
|
if (configured.includes("$")) {
|
|
17467
17766
|
return null;
|
|
17468
17767
|
}
|
|
17469
|
-
return
|
|
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 :
|
|
17822
|
+
index === 0 ? trimmed : path26.win32.join(trimmed, "Desktop")
|
|
17524
17823
|
];
|
|
17525
17824
|
});
|
|
17526
|
-
candidates.push(
|
|
17527
|
-
return await firstUsableKnownFolder(candidates, platform4, probe) ??
|
|
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 &&
|
|
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(
|
|
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() ?
|
|
17571
|
-
|
|
17572
|
-
|
|
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) ??
|
|
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 &&
|
|
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(
|
|
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 =
|
|
17693
|
-
return relative === "" || relative !== ".." && !relative.startsWith(`..${
|
|
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
|
|
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
|
|
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
|
|
17719
|
-
const stats = await
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
18167
|
+
const candidate = path26.join(directory, name);
|
|
17851
18168
|
try {
|
|
17852
|
-
const stats = await
|
|
18169
|
+
const stats = await fs9.lstat(candidate);
|
|
17853
18170
|
if (stats.isSymbolicLink()) {
|
|
17854
18171
|
try {
|
|
17855
|
-
const target = await
|
|
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
|
|
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:
|
|
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
|
|
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({
|
|
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(
|
|
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:
|
|
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("/
|
|
18056
|
-
|
|
18057
|
-
|
|
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
|
-
|
|
18107
|
-
|
|
18108
|
-
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
18185
|
-
|
|
18186
|
-
|
|
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
|
|
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
|
|
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/
|
|
18806
|
-
|
|
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
|
-
|
|
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");
|