@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/README.md +197 -197
- package/assets/web/index-e40p9zj3.css +1 -0
- package/assets/web/index-h22g370d.js +183 -0
- package/assets/web/index.html +23 -23
- package/cli.js +5243 -4663
- package/package.json +1 -1
- package/server.js +941 -183
- 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-7ve7dt7b.css +0 -1
- package/assets/web/index-n2g1090y.js +0 -171
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,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
|
-
$
|
|
17295
|
-
|
|
17296
|
-
|
|
17297
|
-
|
|
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
|
-
|
|
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
|
-
$
|
|
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
|
-
$
|
|
17309
|
-
|
|
17310
|
-
|
|
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
|
|
17782
|
+
import { constants as fsConstants3, promises as fs9 } from "fs";
|
|
17339
17783
|
import { homedir as homedir5 } from "os";
|
|
17340
|
-
import
|
|
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" ?
|
|
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
|
|
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
|
|
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
|
|
17857
|
+
return path26.posix.resolve(homeDirectory, configured.slice(6));
|
|
17462
17858
|
}
|
|
17463
17859
|
if (configured.startsWith("${HOME}/")) {
|
|
17464
|
-
return
|
|
17860
|
+
return path26.posix.resolve(homeDirectory, configured.slice(8));
|
|
17465
17861
|
}
|
|
17466
17862
|
if (configured.includes("$")) {
|
|
17467
17863
|
return null;
|
|
17468
17864
|
}
|
|
17469
|
-
return
|
|
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 :
|
|
17919
|
+
index === 0 ? trimmed : path26.win32.join(trimmed, "Desktop")
|
|
17524
17920
|
];
|
|
17525
17921
|
});
|
|
17526
|
-
candidates.push(
|
|
17527
|
-
return await firstUsableKnownFolder(candidates, platform4, probe) ??
|
|
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 &&
|
|
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(
|
|
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() ?
|
|
17571
|
-
|
|
17572
|
-
|
|
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) ??
|
|
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 &&
|
|
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(
|
|
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 =
|
|
17693
|
-
return relative === "" || relative !== ".." && !relative.startsWith(`..${
|
|
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
|
|
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
|
|
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
|
|
17719
|
-
const stats = await
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
18264
|
+
const candidate = path26.join(directory, name);
|
|
17851
18265
|
try {
|
|
17852
|
-
const stats = await
|
|
18266
|
+
const stats = await fs9.lstat(candidate);
|
|
17853
18267
|
if (stats.isSymbolicLink()) {
|
|
17854
18268
|
try {
|
|
17855
|
-
const target = await
|
|
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
|
|
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:
|
|
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
|
|
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({
|
|
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(
|
|
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:
|
|
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("/
|
|
18056
|
-
|
|
18057
|
-
|
|
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
|
-
|
|
18107
|
-
|
|
18108
|
-
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
18185
|
-
|
|
18186
|
-
|
|
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
|
|
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
|
|
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/
|
|
18806
|
-
|
|
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
|
-
|
|
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");
|