@ariobarin/glossa 0.1.1 → 0.2.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +938 -365
- package/package.json +1 -1
package/dist/app.js
CHANGED
|
@@ -39921,6 +39921,9 @@ var MAX_TEXT_BYTES = 1024 * 1024;
|
|
|
39921
39921
|
var MAX_EDIT_DIFF_BYTES = 128 * 1024;
|
|
39922
39922
|
var MAX_EDIT_OPERATIONS = 100;
|
|
39923
39923
|
var MAX_COMMAND_OUTPUT_BYTES = 12 * 1024;
|
|
39924
|
+
var MAX_COMMAND_RETAINED_STREAM_BYTES = 1024 * 1024;
|
|
39925
|
+
var DEFAULT_COMMAND_OUTPUT_RANGE_BYTES = 32 * 1024;
|
|
39926
|
+
var MAX_COMMAND_OUTPUT_RANGE_BYTES = 64 * 1024;
|
|
39924
39927
|
var DEFAULT_COMMAND_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
39925
39928
|
var MAX_COMMAND_TIMEOUT_MS = 60 * 60 * 1e3;
|
|
39926
39929
|
var DEFAULT_COMMAND_FAST_WAIT_MS = 750;
|
|
@@ -39976,12 +39979,16 @@ var listFilesJobSchema = listFilesRequestSchema.extend({
|
|
|
39976
39979
|
requestId: external_exports.string().uuid(),
|
|
39977
39980
|
timeoutMs: structuredReadTimeoutSchema
|
|
39978
39981
|
});
|
|
39982
|
+
var searchGlobSchema = external_exports.string().min(1).max(256).refine((value) => !/[\r\n\u0000]/.test(value), "Search glob must fit on one line").describe("Root-relative glob pattern using forward slashes, for example src/**/*.ts.");
|
|
39979
39983
|
var searchTextRequestSchema = external_exports.object({
|
|
39980
|
-
query: external_exports.string().min(1).max(256).refine((value) => !/[\r\n\u0000]/.test(value), "Search text must fit on one line").describe("
|
|
39984
|
+
query: external_exports.string().min(1).max(256).refine((value) => !/[\r\n\u0000]/.test(value), "Search text must fit on one line").describe("Single-line UTF-8 search expression. Interpreted literally by default or as a JavaScript regular expression when matchMode is regex."),
|
|
39981
39985
|
path: relativePathSchema.optional().describe("File or directory relative to the exposed root. Defaults to the root."),
|
|
39986
|
+
matchMode: external_exports.enum(["literal", "regex"]).optional().describe("How to interpret query. Defaults to literal; regex uses JavaScript regular-expression syntax."),
|
|
39982
39987
|
caseSensitive: external_exports.boolean().optional().describe("Whether matching is case-sensitive. Defaults to false."),
|
|
39983
39988
|
maxResults: external_exports.number().int().min(1).max(MAX_SEARCH_TEXT_RESULTS).optional().describe("Maximum matching lines to return, from 1 through 100. Defaults to 50."),
|
|
39984
|
-
extensions: external_exports.array(external_exports.string().regex(/^\.[A-Za-z0-9][A-Za-z0-9._-]{0,19}$/).describe("Filename suffix including the leading dot, such as .ts or .d.ts.")).min(1).max(20).optional().describe("Optional filename extensions to search.")
|
|
39989
|
+
extensions: external_exports.array(external_exports.string().regex(/^\.[A-Za-z0-9][A-Za-z0-9._-]{0,19}$/).describe("Filename suffix including the leading dot, such as .ts or .d.ts.")).min(1).max(20).optional().describe("Optional filename extensions to search."),
|
|
39990
|
+
includeGlobs: external_exports.array(searchGlobSchema).min(1).max(20).optional().describe("Optional root-relative glob patterns. A file must match at least one include pattern before its contents are scanned."),
|
|
39991
|
+
excludeGlobs: external_exports.array(searchGlobSchema).min(1).max(20).optional().describe("Optional root-relative glob patterns. Matching files are skipped before their contents are scanned.")
|
|
39985
39992
|
}).strict();
|
|
39986
39993
|
var searchTextJobSchema = searchTextRequestSchema.extend({
|
|
39987
39994
|
type: external_exports.literal("search_text"),
|
|
@@ -40000,8 +40007,8 @@ var readFileRangeJobSchema = readFileRangeRequestSchema.extend({
|
|
|
40000
40007
|
});
|
|
40001
40008
|
var writeFileRequestSchema = external_exports.object({
|
|
40002
40009
|
path: relativePathSchema,
|
|
40003
|
-
content: boundedTextSchema.describe("Complete UTF-8 text content
|
|
40004
|
-
expectedSha256: external_exports.string().regex(/^[a-f0-9]{64}$/).optional().describe("Full-file SHA-256 returned by read_file or read_file_range.
|
|
40010
|
+
content: boundedTextSchema.describe("Complete UTF-8 text content for the new file or replacement revision."),
|
|
40011
|
+
expectedSha256: external_exports.string().regex(/^[a-f0-9]{64}$/).optional().describe("Full-file SHA-256 returned by read_file or read_file_range. Omit only when creating a new path; when provided, write_file replaces exactly that existing revision and fails if it is missing or changed.")
|
|
40005
40012
|
}).strict();
|
|
40006
40013
|
var writeFileJobSchema = writeFileRequestSchema.extend({
|
|
40007
40014
|
type: external_exports.literal("write_file"),
|
|
@@ -40029,6 +40036,30 @@ var editFileJobSchema = editFileRequestSchema.safeExtend({
|
|
|
40029
40036
|
type: external_exports.literal("edit_file"),
|
|
40030
40037
|
requestId: external_exports.string().uuid()
|
|
40031
40038
|
});
|
|
40039
|
+
var makeDirectoryRequestSchema = external_exports.object({
|
|
40040
|
+
path: relativePathSchema,
|
|
40041
|
+
recursive: external_exports.boolean().optional().describe("Whether to create missing parent directories. Defaults to false.")
|
|
40042
|
+
}).strict();
|
|
40043
|
+
var makeDirectoryJobSchema = makeDirectoryRequestSchema.extend({
|
|
40044
|
+
type: external_exports.literal("make_directory"),
|
|
40045
|
+
requestId: external_exports.string().uuid()
|
|
40046
|
+
});
|
|
40047
|
+
var deletePathRequestSchema = external_exports.object({
|
|
40048
|
+
path: relativePathSchema,
|
|
40049
|
+
recursive: external_exports.boolean().optional().describe("Whether to delete a non-empty directory tree. Defaults to false.")
|
|
40050
|
+
}).strict();
|
|
40051
|
+
var deletePathJobSchema = deletePathRequestSchema.extend({
|
|
40052
|
+
type: external_exports.literal("delete_path"),
|
|
40053
|
+
requestId: external_exports.string().uuid()
|
|
40054
|
+
});
|
|
40055
|
+
var movePathRequestSchema = external_exports.object({
|
|
40056
|
+
source: relativePathSchema.describe("Existing file or directory to move."),
|
|
40057
|
+
destination: relativePathSchema.describe("New path inside the exposed root. The destination must not already exist.")
|
|
40058
|
+
}).strict();
|
|
40059
|
+
var movePathJobSchema = movePathRequestSchema.extend({
|
|
40060
|
+
type: external_exports.literal("move_path"),
|
|
40061
|
+
requestId: external_exports.string().uuid()
|
|
40062
|
+
});
|
|
40032
40063
|
function requireOneCommand(value, context) {
|
|
40033
40064
|
if ((value.argv ? 1 : 0) + (value.shellCommand ? 1 : 0) !== 1) {
|
|
40034
40065
|
context.addIssue({
|
|
@@ -40058,6 +40089,16 @@ var getCommandJobSchema = getCommandRequestSchema.extend({
|
|
|
40058
40089
|
type: external_exports.literal("get_command"),
|
|
40059
40090
|
requestId: external_exports.string().uuid()
|
|
40060
40091
|
});
|
|
40092
|
+
var readCommandOutputRequestSchema = external_exports.object({
|
|
40093
|
+
commandId: external_exports.string().uuid().describe("Command identifier returned by run_command."),
|
|
40094
|
+
stream: external_exports.enum(["stdout", "stderr"]).describe("Command output stream to read independently."),
|
|
40095
|
+
offset: external_exports.number().int().min(0).optional().describe("Zero-based retained byte offset. Defaults to 0."),
|
|
40096
|
+
maxBytes: external_exports.number().int().min(4).max(MAX_COMMAND_OUTPUT_RANGE_BYTES).optional().describe("Maximum retained source bytes to inspect, from 4 through 65536. Defaults to 32768.")
|
|
40097
|
+
}).strict();
|
|
40098
|
+
var readCommandOutputJobSchema = readCommandOutputRequestSchema.extend({
|
|
40099
|
+
type: external_exports.literal("read_command_output"),
|
|
40100
|
+
requestId: external_exports.string().uuid()
|
|
40101
|
+
});
|
|
40061
40102
|
var cancelCommandRequestSchema = external_exports.object({
|
|
40062
40103
|
commandId: external_exports.string().uuid().describe("Command identifier returned by run_command.")
|
|
40063
40104
|
}).strict();
|
|
@@ -40072,8 +40113,12 @@ var workerJobSchema = external_exports.discriminatedUnion("type", [
|
|
|
40072
40113
|
readFileRangeJobSchema,
|
|
40073
40114
|
writeFileJobSchema,
|
|
40074
40115
|
editFileJobSchema,
|
|
40116
|
+
makeDirectoryJobSchema,
|
|
40117
|
+
deletePathJobSchema,
|
|
40118
|
+
movePathJobSchema,
|
|
40075
40119
|
runCommandJobSchema,
|
|
40076
40120
|
getCommandJobSchema,
|
|
40121
|
+
readCommandOutputJobSchema,
|
|
40077
40122
|
cancelCommandJobSchema
|
|
40078
40123
|
]);
|
|
40079
40124
|
var workerResultSchema = external_exports.object({
|
|
@@ -49820,6 +49865,8 @@ var WorkerError = class extends Error {
|
|
|
49820
49865
|
var STREAM_HEAD_BYTES = Math.floor(MAX_COMMAND_OUTPUT_BYTES / 3);
|
|
49821
49866
|
var STREAM_TAIL_BYTES = MAX_COMMAND_OUTPUT_BYTES - STREAM_HEAD_BYTES;
|
|
49822
49867
|
var RESTRICTED_SCAN_TAIL_BYTES = 1024;
|
|
49868
|
+
var COMMAND_RECORD_RETENTION_MS = 5 * 60 * 1e3;
|
|
49869
|
+
var MAX_RETAINED_COMMAND_RECORDS = 8;
|
|
49823
49870
|
function restrictedDataError() {
|
|
49824
49871
|
return new WorkerError(
|
|
49825
49872
|
RESTRICTED_DATA_ERROR_CODE,
|
|
@@ -49836,20 +49883,26 @@ function scanOutputChunk(previousTail, chunk) {
|
|
|
49836
49883
|
tail
|
|
49837
49884
|
};
|
|
49838
49885
|
}
|
|
49886
|
+
function markRestrictedData(record2) {
|
|
49887
|
+
if (record2.restrictedDataDetected) return;
|
|
49888
|
+
record2.restrictedDataDetected = true;
|
|
49889
|
+
record2.stdout = emptyCapture();
|
|
49890
|
+
record2.stderr = emptyCapture();
|
|
49891
|
+
record2.stdoutScanTail = Buffer.alloc(0);
|
|
49892
|
+
record2.stderrScanTail = Buffer.alloc(0);
|
|
49893
|
+
if (record2.status === "running") {
|
|
49894
|
+
record2.requestedTerminal = "canceled";
|
|
49895
|
+
void terminateProcessTree(record2.child).catch(() => void 0);
|
|
49896
|
+
}
|
|
49897
|
+
markChanged(record2);
|
|
49898
|
+
}
|
|
49839
49899
|
function recordCommandOutput(record2, streamName, chunk) {
|
|
49840
49900
|
if (record2.restrictedDataDetected || chunk.byteLength === 0) return;
|
|
49841
49901
|
const tailName = streamName === "stdout" ? "stdoutScanTail" : "stderrScanTail";
|
|
49842
49902
|
const scan = scanOutputChunk(record2[tailName], chunk);
|
|
49843
49903
|
record2[tailName] = scan.tail;
|
|
49844
49904
|
if (scan.detected) {
|
|
49845
|
-
record2
|
|
49846
|
-
record2.stdout = emptyCapture();
|
|
49847
|
-
record2.stderr = emptyCapture();
|
|
49848
|
-
if (record2.status === "running") {
|
|
49849
|
-
record2.requestedTerminal = "canceled";
|
|
49850
|
-
void terminateProcessTree(record2.child).catch(() => void 0);
|
|
49851
|
-
}
|
|
49852
|
-
markChanged(record2);
|
|
49905
|
+
markRestrictedData(record2);
|
|
49853
49906
|
return;
|
|
49854
49907
|
}
|
|
49855
49908
|
if (capture(record2, record2[streamName], chunk)) markChanged(record2);
|
|
@@ -49864,6 +49917,17 @@ function appendTail(existing, chunk) {
|
|
|
49864
49917
|
function capture(_record2, stream, chunk) {
|
|
49865
49918
|
if (chunk.byteLength === 0) return false;
|
|
49866
49919
|
stream.totalBytes += chunk.byteLength;
|
|
49920
|
+
const retentionBudget = MAX_COMMAND_RETAINED_STREAM_BYTES - stream.retainedBytes;
|
|
49921
|
+
if (retentionBudget > 0) {
|
|
49922
|
+
const retained = chunk.subarray(0, Math.min(chunk.byteLength, retentionBudget));
|
|
49923
|
+
if (retained.byteLength > 0) {
|
|
49924
|
+
stream.retained.push(Buffer.from(retained));
|
|
49925
|
+
stream.retainedBytes += retained.byteLength;
|
|
49926
|
+
}
|
|
49927
|
+
}
|
|
49928
|
+
if (chunk.byteLength > Math.max(0, retentionBudget)) {
|
|
49929
|
+
stream.retentionTruncated = true;
|
|
49930
|
+
}
|
|
49867
49931
|
let offset = 0;
|
|
49868
49932
|
if (stream.headBytes < STREAM_HEAD_BYTES) {
|
|
49869
49933
|
const accepted = chunk.subarray(
|
|
@@ -49912,7 +49976,10 @@ function emptyCapture() {
|
|
|
49912
49976
|
head: [],
|
|
49913
49977
|
headBytes: 0,
|
|
49914
49978
|
tail: Buffer.alloc(0),
|
|
49915
|
-
|
|
49979
|
+
retained: [],
|
|
49980
|
+
retainedBytes: 0,
|
|
49981
|
+
totalBytes: 0,
|
|
49982
|
+
retentionTruncated: false
|
|
49916
49983
|
};
|
|
49917
49984
|
}
|
|
49918
49985
|
function retainedBytes(stream, complete) {
|
|
@@ -49954,6 +50021,44 @@ function utf8SuffixWithinBudget(value, budget) {
|
|
|
49954
50021
|
}
|
|
49955
50022
|
return characters.slice(start).join("");
|
|
49956
50023
|
}
|
|
50024
|
+
function isUtf8Continuation(byte) {
|
|
50025
|
+
return byte !== void 0 && (byte & 192) === 128;
|
|
50026
|
+
}
|
|
50027
|
+
function utf8SequenceBytes(byte) {
|
|
50028
|
+
if ((byte & 128) === 0) return 1;
|
|
50029
|
+
if ((byte & 224) === 192) return 2;
|
|
50030
|
+
if ((byte & 240) === 224) return 3;
|
|
50031
|
+
if ((byte & 248) === 240) return 4;
|
|
50032
|
+
return 1;
|
|
50033
|
+
}
|
|
50034
|
+
function retainedRange(stream, requestedOffset, maxBytes) {
|
|
50035
|
+
const retained = Buffer.concat(stream.retained, stream.retainedBytes);
|
|
50036
|
+
let offset = requestedOffset;
|
|
50037
|
+
while (offset < retained.byteLength && isUtf8Continuation(retained[offset])) {
|
|
50038
|
+
offset += 1;
|
|
50039
|
+
}
|
|
50040
|
+
if (offset >= retained.byteLength) {
|
|
50041
|
+
return { offset, content: "" };
|
|
50042
|
+
}
|
|
50043
|
+
let end = Math.min(retained.byteLength, offset + maxBytes);
|
|
50044
|
+
if (end < retained.byteLength) {
|
|
50045
|
+
while (end > offset && isUtf8Continuation(retained[end])) end -= 1;
|
|
50046
|
+
}
|
|
50047
|
+
if (end > offset) {
|
|
50048
|
+
let lead = end - 1;
|
|
50049
|
+
while (lead > offset && isUtf8Continuation(retained[lead])) lead -= 1;
|
|
50050
|
+
const expected = utf8SequenceBytes(retained[lead]);
|
|
50051
|
+
if (expected > 1 && end - lead < expected) end = lead;
|
|
50052
|
+
}
|
|
50053
|
+
if (end <= offset) {
|
|
50054
|
+
end = Math.min(retained.byteLength, offset + maxBytes);
|
|
50055
|
+
}
|
|
50056
|
+
return {
|
|
50057
|
+
offset,
|
|
50058
|
+
content: retained.subarray(offset, end).toString("utf8"),
|
|
50059
|
+
...end < retained.byteLength ? { nextOffset: end } : {}
|
|
50060
|
+
};
|
|
50061
|
+
}
|
|
49957
50062
|
function renderStream(stream, budget, complete) {
|
|
49958
50063
|
if (budget <= 0 || stream.totalBytes === 0) {
|
|
49959
50064
|
return { content: "", truncated: stream.totalBytes > 0 };
|
|
@@ -50046,6 +50151,21 @@ var CommandService = class {
|
|
|
50046
50151
|
policy;
|
|
50047
50152
|
#commands = /* @__PURE__ */ new Map();
|
|
50048
50153
|
#activeCommandId = null;
|
|
50154
|
+
#pruneRetainedCommands() {
|
|
50155
|
+
while (this.#commands.size >= MAX_RETAINED_COMMAND_RECORDS) {
|
|
50156
|
+
const oldestTerminal = [...this.#commands].find(
|
|
50157
|
+
([, record2]) => record2.status !== "running"
|
|
50158
|
+
);
|
|
50159
|
+
if (!oldestTerminal) return;
|
|
50160
|
+
this.#commands.delete(oldestTerminal[0]);
|
|
50161
|
+
}
|
|
50162
|
+
}
|
|
50163
|
+
#scheduleDeletion(commandId) {
|
|
50164
|
+
setTimeout(
|
|
50165
|
+
() => this.#commands.delete(commandId),
|
|
50166
|
+
COMMAND_RECORD_RETENTION_MS
|
|
50167
|
+
).unref();
|
|
50168
|
+
}
|
|
50049
50169
|
async start(options) {
|
|
50050
50170
|
if (this.#activeCommandId) {
|
|
50051
50171
|
const active = this.#commands.get(this.#activeCommandId);
|
|
@@ -50130,6 +50250,7 @@ var CommandService = class {
|
|
|
50130
50250
|
void terminateProcessTree(child);
|
|
50131
50251
|
}, timeoutMs);
|
|
50132
50252
|
record2.timeout.unref();
|
|
50253
|
+
this.#pruneRetainedCommands();
|
|
50133
50254
|
this.#commands.set(id, record2);
|
|
50134
50255
|
this.#activeCommandId = id;
|
|
50135
50256
|
child.stdout.on("data", (chunk) => {
|
|
@@ -50147,6 +50268,7 @@ var CommandService = class {
|
|
|
50147
50268
|
this.#activeCommandId = null;
|
|
50148
50269
|
markChanged(record2);
|
|
50149
50270
|
record2.complete();
|
|
50271
|
+
this.#scheduleDeletion(id);
|
|
50150
50272
|
});
|
|
50151
50273
|
child.once("close", (exitCode, signal) => {
|
|
50152
50274
|
if (record2.status !== "running") return;
|
|
@@ -50158,7 +50280,7 @@ var CommandService = class {
|
|
|
50158
50280
|
this.#activeCommandId = null;
|
|
50159
50281
|
markChanged(record2);
|
|
50160
50282
|
record2.complete();
|
|
50161
|
-
|
|
50283
|
+
this.#scheduleDeletion(id);
|
|
50162
50284
|
});
|
|
50163
50285
|
if (options.stdin !== void 0) child.stdin.end(options.stdin);
|
|
50164
50286
|
else child.stdin.end();
|
|
@@ -50215,6 +50337,53 @@ var CommandService = class {
|
|
|
50215
50337
|
}
|
|
50216
50338
|
return this.snapshot(record2);
|
|
50217
50339
|
}
|
|
50340
|
+
async readOutput(commandId, streamName, offset = 0, maxBytes = DEFAULT_COMMAND_OUTPUT_RANGE_BYTES) {
|
|
50341
|
+
const record2 = this.#commands.get(commandId);
|
|
50342
|
+
if (!record2) throw new WorkerError("command_not_found", "The command was not found.");
|
|
50343
|
+
if (streamName !== "stdout" && streamName !== "stderr") {
|
|
50344
|
+
throw new WorkerError(
|
|
50345
|
+
"invalid_output_stream",
|
|
50346
|
+
"Command output stream must be stdout or stderr."
|
|
50347
|
+
);
|
|
50348
|
+
}
|
|
50349
|
+
if (!Number.isInteger(offset) || offset < 0) {
|
|
50350
|
+
throw new WorkerError(
|
|
50351
|
+
"invalid_output_offset",
|
|
50352
|
+
"Command output offset must be a non-negative integer."
|
|
50353
|
+
);
|
|
50354
|
+
}
|
|
50355
|
+
if (!Number.isInteger(maxBytes) || maxBytes < 4 || maxBytes > MAX_COMMAND_OUTPUT_RANGE_BYTES) {
|
|
50356
|
+
throw new WorkerError(
|
|
50357
|
+
"invalid_output_range",
|
|
50358
|
+
"Command output range must be between 4 and 65536 source bytes."
|
|
50359
|
+
);
|
|
50360
|
+
}
|
|
50361
|
+
if (record2.restrictedDataDetected) throw restrictedDataError();
|
|
50362
|
+
const stream = record2[streamName];
|
|
50363
|
+
if (offset > stream.retainedBytes) {
|
|
50364
|
+
throw new WorkerError(
|
|
50365
|
+
"output_offset_out_of_range",
|
|
50366
|
+
"The command output offset exceeds the retained stream length."
|
|
50367
|
+
);
|
|
50368
|
+
}
|
|
50369
|
+
const range = retainedRange(stream, offset, maxBytes);
|
|
50370
|
+
if (containsRestrictedAuthenticationData(range.content)) {
|
|
50371
|
+
markRestrictedData(record2);
|
|
50372
|
+
throw restrictedDataError();
|
|
50373
|
+
}
|
|
50374
|
+
return {
|
|
50375
|
+
commandId,
|
|
50376
|
+
stream: streamName,
|
|
50377
|
+
status: record2.status,
|
|
50378
|
+
offset: range.offset,
|
|
50379
|
+
content: range.content,
|
|
50380
|
+
...range.nextOffset === void 0 ? {} : { nextOffset: range.nextOffset },
|
|
50381
|
+
retainedBytes: stream.retainedBytes,
|
|
50382
|
+
totalBytes: stream.totalBytes,
|
|
50383
|
+
retentionTruncated: stream.retentionTruncated,
|
|
50384
|
+
complete: record2.status !== "running"
|
|
50385
|
+
};
|
|
50386
|
+
}
|
|
50218
50387
|
async cancel(commandId) {
|
|
50219
50388
|
const record2 = this.#commands.get(commandId);
|
|
50220
50389
|
if (!record2) throw new WorkerError("command_not_found", "The command was not found.");
|
|
@@ -50262,124 +50431,414 @@ var CommandService = class {
|
|
|
50262
50431
|
import { createHash, randomUUID as randomUUID2 } from "node:crypto";
|
|
50263
50432
|
import {
|
|
50264
50433
|
chmod as chmod2,
|
|
50265
|
-
|
|
50434
|
+
link as link2,
|
|
50435
|
+
lstat as lstat2,
|
|
50436
|
+
mkdir as mkdir3,
|
|
50266
50437
|
opendir,
|
|
50267
50438
|
open,
|
|
50268
50439
|
readFile as readFile3,
|
|
50269
50440
|
rename,
|
|
50270
50441
|
rm as rm2,
|
|
50271
|
-
|
|
50442
|
+
rmdir,
|
|
50443
|
+
stat as stat2,
|
|
50272
50444
|
writeFile as writeFile3
|
|
50273
50445
|
} from "node:fs/promises";
|
|
50274
|
-
import
|
|
50446
|
+
import path6 from "node:path";
|
|
50275
50447
|
import { performance as performance2 } from "node:perf_hooks";
|
|
50276
50448
|
import { StringDecoder as StringDecoder2 } from "node:string_decoder";
|
|
50277
|
-
|
|
50278
|
-
|
|
50449
|
+
|
|
50450
|
+
// src/worker/path-policy.ts
|
|
50451
|
+
import { lstat, realpath, stat } from "node:fs/promises";
|
|
50452
|
+
import os5 from "node:os";
|
|
50453
|
+
import path5 from "node:path";
|
|
50454
|
+
function samePath(left, right) {
|
|
50455
|
+
return process.platform === "win32" ? left.toLowerCase() === right.toLowerCase() : left === right;
|
|
50279
50456
|
}
|
|
50280
|
-
|
|
50281
|
-
async function withFileWriteLock(target, operation) {
|
|
50282
|
-
const normalized = path5.normalize(target);
|
|
50283
|
-
const key = process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
50284
|
-
const predecessor = fileWriteTails.get(key);
|
|
50285
|
-
let release;
|
|
50286
|
-
const tail = new Promise((resolve) => {
|
|
50287
|
-
release = resolve;
|
|
50288
|
-
});
|
|
50289
|
-
fileWriteTails.set(key, tail);
|
|
50290
|
-
if (predecessor) await predecessor;
|
|
50457
|
+
function accountHomeDirectory() {
|
|
50291
50458
|
try {
|
|
50292
|
-
return
|
|
50293
|
-
}
|
|
50294
|
-
|
|
50295
|
-
if (fileWriteTails.get(key) === tail) fileWriteTails.delete(key);
|
|
50459
|
+
return os5.userInfo().homedir;
|
|
50460
|
+
} catch {
|
|
50461
|
+
return os5.homedir();
|
|
50296
50462
|
}
|
|
50297
50463
|
}
|
|
50298
|
-
|
|
50299
|
-
|
|
50300
|
-
|
|
50301
|
-
|
|
50302
|
-
|
|
50303
|
-
|
|
50464
|
+
function isWithin(root, candidate) {
|
|
50465
|
+
const relative = path5.relative(root, candidate);
|
|
50466
|
+
return relative === "" || !relative.startsWith(`..${path5.sep}`) && relative !== ".." && !path5.isAbsolute(relative);
|
|
50467
|
+
}
|
|
50468
|
+
function validateRelativePath(value) {
|
|
50469
|
+
if (value.includes("\0")) {
|
|
50470
|
+
throw new WorkerError("invalid_path", "Paths cannot contain null bytes.");
|
|
50304
50471
|
}
|
|
50305
|
-
|
|
50306
|
-
|
|
50472
|
+
const explicitNativePosixPath = process.platform !== "win32" && value.startsWith("./");
|
|
50473
|
+
if (path5.isAbsolute(value) || path5.posix.isAbsolute(value) || !explicitNativePosixPath && path5.win32.isAbsolute(value)) {
|
|
50474
|
+
throw new WorkerError("absolute_path", "Absolute paths are not allowed.");
|
|
50307
50475
|
}
|
|
50476
|
+
const segments = explicitNativePosixPath ? value.split(/\/+/).filter(Boolean) : value.split(/[\\/]+/);
|
|
50477
|
+
if (segments.includes("..")) {
|
|
50478
|
+
throw new WorkerError("path_traversal", "Parent path traversal is not allowed.");
|
|
50479
|
+
}
|
|
50480
|
+
return value === "" ? "." : value;
|
|
50308
50481
|
}
|
|
50309
|
-
|
|
50310
|
-
|
|
50311
|
-
|
|
50312
|
-
|
|
50313
|
-
|
|
50314
|
-
|
|
50315
|
-
|
|
50316
|
-
|
|
50317
|
-
|
|
50318
|
-
|
|
50319
|
-
|
|
50320
|
-
|
|
50321
|
-
|
|
50322
|
-
|
|
50323
|
-
|
|
50324
|
-
|
|
50325
|
-
|
|
50326
|
-
|
|
50327
|
-
|
|
50328
|
-
|
|
50329
|
-
|
|
50330
|
-
|
|
50331
|
-
|
|
50332
|
-
|
|
50333
|
-
}
|
|
50334
|
-
|
|
50335
|
-
const code = error46?.code;
|
|
50336
|
-
return code === "ENOENT" || code === "EACCES" || code === "EPERM" || code === "ENOTDIR";
|
|
50337
|
-
}
|
|
50338
|
-
function isUnavailableDiscoveredPathError(error46) {
|
|
50339
|
-
return isUnavailableFileError(error46) || error46 instanceof WorkerError && error46.code === "path_not_found";
|
|
50340
|
-
}
|
|
50341
|
-
function isLinkedPathError(error46) {
|
|
50342
|
-
return error46 instanceof WorkerError && error46.code === "linked_path";
|
|
50482
|
+
async function canonicalizeRoot(candidate) {
|
|
50483
|
+
const root = await realpath(path5.resolve(candidate)).catch((error46) => {
|
|
50484
|
+
if (error46.code === "ENOENT") {
|
|
50485
|
+
throw new WorkerError("root_not_found", "The workspace directory does not exist.");
|
|
50486
|
+
}
|
|
50487
|
+
throw error46;
|
|
50488
|
+
});
|
|
50489
|
+
const rootStat = await stat(root);
|
|
50490
|
+
if (!rootStat.isDirectory()) {
|
|
50491
|
+
throw new WorkerError("root_not_directory", "The exposed root must be a directory.");
|
|
50492
|
+
}
|
|
50493
|
+
const filesystemRoot = path5.parse(root).root;
|
|
50494
|
+
const homes = await Promise.all(
|
|
50495
|
+
[os5.homedir(), accountHomeDirectory()].map(
|
|
50496
|
+
async (home) => await realpath(home).catch(() => path5.resolve(home))
|
|
50497
|
+
)
|
|
50498
|
+
);
|
|
50499
|
+
const isHomeDirectory = homes.some((home) => samePath(root, home));
|
|
50500
|
+
if (samePath(root, filesystemRoot) || isHomeDirectory) {
|
|
50501
|
+
const kind = isHomeDirectory ? "your home directory" : "a filesystem root";
|
|
50502
|
+
throw new WorkerError(
|
|
50503
|
+
"broad_root_refused",
|
|
50504
|
+
`The selected root is ${kind}, which Glossa will not expose. Choose a project directory instead.`
|
|
50505
|
+
);
|
|
50506
|
+
}
|
|
50507
|
+
return root;
|
|
50343
50508
|
}
|
|
50344
|
-
|
|
50345
|
-
|
|
50346
|
-
|
|
50347
|
-
|
|
50348
|
-
|
|
50349
|
-
|
|
50509
|
+
var PathPolicy = class _PathPolicy {
|
|
50510
|
+
constructor(root) {
|
|
50511
|
+
this.root = root;
|
|
50512
|
+
}
|
|
50513
|
+
root;
|
|
50514
|
+
static async create(candidate) {
|
|
50515
|
+
return new _PathPolicy(await canonicalizeRoot(candidate));
|
|
50516
|
+
}
|
|
50517
|
+
async resolveExisting(relativePath) {
|
|
50518
|
+
const lexical = this.resolveLexical(relativePath);
|
|
50519
|
+
await this.rejectLinkedComponents(lexical);
|
|
50520
|
+
const canonical = await realpath(lexical).catch((error46) => {
|
|
50521
|
+
if (error46.code === "ENOENT") {
|
|
50522
|
+
throw new WorkerError("path_not_found", "The requested path does not exist.");
|
|
50523
|
+
}
|
|
50524
|
+
throw error46;
|
|
50525
|
+
});
|
|
50526
|
+
if (!isWithin(this.root, canonical)) {
|
|
50527
|
+
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
50350
50528
|
}
|
|
50351
|
-
|
|
50352
|
-
|
|
50529
|
+
return canonical;
|
|
50530
|
+
}
|
|
50531
|
+
async resolveDiscoveredExisting(candidate) {
|
|
50532
|
+
const lexical = path5.resolve(candidate);
|
|
50533
|
+
if (!isWithin(this.root, lexical)) {
|
|
50534
|
+
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
50353
50535
|
}
|
|
50354
|
-
|
|
50355
|
-
|
|
50536
|
+
await this.rejectLinkedComponents(lexical);
|
|
50537
|
+
const canonical = await realpath(lexical).catch((error46) => {
|
|
50538
|
+
if (error46.code === "ENOENT") {
|
|
50539
|
+
throw new WorkerError("path_not_found", "The requested path does not exist.");
|
|
50540
|
+
}
|
|
50541
|
+
throw error46;
|
|
50542
|
+
});
|
|
50543
|
+
if (!isWithin(this.root, canonical)) {
|
|
50544
|
+
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
50356
50545
|
}
|
|
50357
|
-
|
|
50358
|
-
|
|
50359
|
-
|
|
50360
|
-
|
|
50361
|
-
|
|
50362
|
-
|
|
50363
|
-
|
|
50364
|
-
|
|
50365
|
-
|
|
50366
|
-
|
|
50367
|
-
|
|
50546
|
+
return canonical;
|
|
50547
|
+
}
|
|
50548
|
+
async resolveWritableFile(relativePath) {
|
|
50549
|
+
const lexical = this.resolveLexical(relativePath);
|
|
50550
|
+
const parent = path5.dirname(lexical);
|
|
50551
|
+
await this.rejectLinkedComponents(parent);
|
|
50552
|
+
const canonicalParent = await realpath(parent).catch((error46) => {
|
|
50553
|
+
if (error46.code === "ENOENT") {
|
|
50554
|
+
throw new WorkerError("parent_not_found", "The destination directory does not exist.");
|
|
50555
|
+
}
|
|
50556
|
+
throw error46;
|
|
50557
|
+
});
|
|
50558
|
+
if (!isWithin(this.root, canonicalParent)) {
|
|
50559
|
+
throw new WorkerError("path_escape", "The destination escapes the exposed root.");
|
|
50368
50560
|
}
|
|
50369
|
-
|
|
50370
|
-
|
|
50371
|
-
throw new WorkerError("file_changed", "The file changed while it was being read.");
|
|
50561
|
+
if (!(await stat(canonicalParent)).isDirectory()) {
|
|
50562
|
+
throw new WorkerError("not_directory", "The destination parent is not a directory.");
|
|
50372
50563
|
}
|
|
50373
|
-
|
|
50374
|
-
|
|
50375
|
-
|
|
50564
|
+
try {
|
|
50565
|
+
const targetStat = await lstat(lexical);
|
|
50566
|
+
if (targetStat.isSymbolicLink()) {
|
|
50567
|
+
throw new WorkerError("linked_path", "Writes through links are not allowed.");
|
|
50568
|
+
}
|
|
50569
|
+
if (targetStat.isDirectory()) {
|
|
50570
|
+
throw new WorkerError("not_file", "The destination is a directory.");
|
|
50571
|
+
}
|
|
50572
|
+
const canonicalTarget = await realpath(lexical);
|
|
50573
|
+
if (!isWithin(this.root, canonicalTarget)) {
|
|
50574
|
+
throw new WorkerError("path_escape", "The destination escapes the exposed root.");
|
|
50575
|
+
}
|
|
50576
|
+
return canonicalTarget;
|
|
50577
|
+
} catch (error46) {
|
|
50578
|
+
if (error46 instanceof WorkerError) throw error46;
|
|
50579
|
+
if (error46.code !== "ENOENT") throw error46;
|
|
50580
|
+
}
|
|
50581
|
+
return path5.join(canonicalParent, path5.basename(lexical));
|
|
50376
50582
|
}
|
|
50377
|
-
|
|
50378
|
-
|
|
50379
|
-
|
|
50380
|
-
|
|
50381
|
-
|
|
50382
|
-
|
|
50583
|
+
async resolveWritableDirectory(relativePath, recursive) {
|
|
50584
|
+
const lexical = this.resolveLexical(relativePath);
|
|
50585
|
+
await this.rejectLinkedComponents(lexical);
|
|
50586
|
+
try {
|
|
50587
|
+
const targetStat = await lstat(lexical);
|
|
50588
|
+
if (targetStat.isSymbolicLink()) {
|
|
50589
|
+
throw new WorkerError("linked_path", "Directory creation through links is not allowed.");
|
|
50590
|
+
}
|
|
50591
|
+
if (!targetStat.isDirectory()) {
|
|
50592
|
+
throw new WorkerError("not_directory", "The destination is not a directory.");
|
|
50593
|
+
}
|
|
50594
|
+
const canonicalTarget = await realpath(lexical);
|
|
50595
|
+
if (!isWithin(this.root, canonicalTarget)) {
|
|
50596
|
+
throw new WorkerError("path_escape", "The destination escapes the exposed root.");
|
|
50597
|
+
}
|
|
50598
|
+
return { target: canonicalTarget, exists: true };
|
|
50599
|
+
} catch (error46) {
|
|
50600
|
+
if (error46 instanceof WorkerError) throw error46;
|
|
50601
|
+
if (error46.code !== "ENOENT") throw error46;
|
|
50602
|
+
}
|
|
50603
|
+
if (!recursive) {
|
|
50604
|
+
const parent = path5.dirname(lexical);
|
|
50605
|
+
await this.rejectLinkedComponents(parent);
|
|
50606
|
+
const canonicalParent = await realpath(parent).catch(
|
|
50607
|
+
(error46) => {
|
|
50608
|
+
if (error46.code === "ENOENT") {
|
|
50609
|
+
throw new WorkerError(
|
|
50610
|
+
"parent_not_found",
|
|
50611
|
+
"The destination directory does not exist."
|
|
50612
|
+
);
|
|
50613
|
+
}
|
|
50614
|
+
throw error46;
|
|
50615
|
+
}
|
|
50616
|
+
);
|
|
50617
|
+
if (!isWithin(this.root, canonicalParent)) {
|
|
50618
|
+
throw new WorkerError("path_escape", "The destination escapes the exposed root.");
|
|
50619
|
+
}
|
|
50620
|
+
if (!(await stat(canonicalParent)).isDirectory()) {
|
|
50621
|
+
throw new WorkerError("not_directory", "The destination parent is not a directory.");
|
|
50622
|
+
}
|
|
50623
|
+
return {
|
|
50624
|
+
target: path5.join(canonicalParent, path5.basename(lexical)),
|
|
50625
|
+
exists: false
|
|
50626
|
+
};
|
|
50627
|
+
}
|
|
50628
|
+
let existingAncestor = path5.dirname(lexical);
|
|
50629
|
+
while (!samePath(existingAncestor, this.root)) {
|
|
50630
|
+
try {
|
|
50631
|
+
await this.rejectLinkedComponents(existingAncestor);
|
|
50632
|
+
const canonicalAncestor = await realpath(existingAncestor);
|
|
50633
|
+
if (!isWithin(this.root, canonicalAncestor)) {
|
|
50634
|
+
throw new WorkerError("path_escape", "The destination escapes the exposed root.");
|
|
50635
|
+
}
|
|
50636
|
+
if (!(await stat(canonicalAncestor)).isDirectory()) {
|
|
50637
|
+
throw new WorkerError("not_directory", "The destination parent is not a directory.");
|
|
50638
|
+
}
|
|
50639
|
+
return {
|
|
50640
|
+
target: path5.join(
|
|
50641
|
+
canonicalAncestor,
|
|
50642
|
+
path5.relative(existingAncestor, lexical)
|
|
50643
|
+
),
|
|
50644
|
+
exists: false
|
|
50645
|
+
};
|
|
50646
|
+
} catch (error46) {
|
|
50647
|
+
if (error46 instanceof WorkerError) throw error46;
|
|
50648
|
+
if (error46.code !== "ENOENT") throw error46;
|
|
50649
|
+
}
|
|
50650
|
+
existingAncestor = path5.dirname(existingAncestor);
|
|
50651
|
+
}
|
|
50652
|
+
return { target: lexical, exists: false };
|
|
50653
|
+
}
|
|
50654
|
+
async resolveVacantPath(relativePath) {
|
|
50655
|
+
const lexical = this.resolveLexical(relativePath);
|
|
50656
|
+
const parent = path5.dirname(lexical);
|
|
50657
|
+
await this.rejectLinkedComponents(parent);
|
|
50658
|
+
const canonicalParent = await realpath(parent).catch(
|
|
50659
|
+
(error46) => {
|
|
50660
|
+
if (error46.code === "ENOENT") {
|
|
50661
|
+
throw new WorkerError(
|
|
50662
|
+
"parent_not_found",
|
|
50663
|
+
"The destination directory does not exist."
|
|
50664
|
+
);
|
|
50665
|
+
}
|
|
50666
|
+
throw error46;
|
|
50667
|
+
}
|
|
50668
|
+
);
|
|
50669
|
+
if (!isWithin(this.root, canonicalParent)) {
|
|
50670
|
+
throw new WorkerError("path_escape", "The destination escapes the exposed root.");
|
|
50671
|
+
}
|
|
50672
|
+
if (!(await stat(canonicalParent)).isDirectory()) {
|
|
50673
|
+
throw new WorkerError("not_directory", "The destination parent is not a directory.");
|
|
50674
|
+
}
|
|
50675
|
+
try {
|
|
50676
|
+
await lstat(lexical);
|
|
50677
|
+
throw new WorkerError(
|
|
50678
|
+
"destination_exists",
|
|
50679
|
+
"The destination already exists."
|
|
50680
|
+
);
|
|
50681
|
+
} catch (error46) {
|
|
50682
|
+
if (error46 instanceof WorkerError) throw error46;
|
|
50683
|
+
if (error46.code !== "ENOENT") throw error46;
|
|
50684
|
+
}
|
|
50685
|
+
return path5.join(canonicalParent, path5.basename(lexical));
|
|
50686
|
+
}
|
|
50687
|
+
resolveLexical(relativePath) {
|
|
50688
|
+
const validated = validateRelativePath(relativePath);
|
|
50689
|
+
const candidate = path5.resolve(this.root, validated);
|
|
50690
|
+
if (!isWithin(this.root, candidate)) {
|
|
50691
|
+
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
50692
|
+
}
|
|
50693
|
+
return candidate;
|
|
50694
|
+
}
|
|
50695
|
+
async rejectLinkedComponents(candidate) {
|
|
50696
|
+
if (!isWithin(this.root, candidate)) {
|
|
50697
|
+
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
50698
|
+
}
|
|
50699
|
+
const relative = path5.relative(this.root, candidate);
|
|
50700
|
+
if (!relative) return;
|
|
50701
|
+
let current = this.root;
|
|
50702
|
+
for (const segment of relative.split(path5.sep)) {
|
|
50703
|
+
current = path5.join(current, segment);
|
|
50704
|
+
try {
|
|
50705
|
+
const currentStat = await lstat(current);
|
|
50706
|
+
if (currentStat.isSymbolicLink()) {
|
|
50707
|
+
throw new WorkerError(
|
|
50708
|
+
"linked_path",
|
|
50709
|
+
"Symlink and junction paths are not allowed."
|
|
50710
|
+
);
|
|
50711
|
+
}
|
|
50712
|
+
} catch (error46) {
|
|
50713
|
+
if (error46 instanceof WorkerError) throw error46;
|
|
50714
|
+
if (error46.code === "ENOENT") return;
|
|
50715
|
+
throw error46;
|
|
50716
|
+
}
|
|
50717
|
+
}
|
|
50718
|
+
}
|
|
50719
|
+
};
|
|
50720
|
+
|
|
50721
|
+
// src/worker/file-service.ts
|
|
50722
|
+
function sha256(content) {
|
|
50723
|
+
return createHash("sha256").update(content).digest("hex");
|
|
50724
|
+
}
|
|
50725
|
+
function isPathWithin(root, candidate) {
|
|
50726
|
+
const relative = path6.relative(root, candidate);
|
|
50727
|
+
return relative === "" || !relative.startsWith(`..${path6.sep}`) && relative !== ".." && !path6.isAbsolute(relative);
|
|
50728
|
+
}
|
|
50729
|
+
var fileWriteTails = /* @__PURE__ */ new Map();
|
|
50730
|
+
async function withFileWriteLock(target, operation) {
|
|
50731
|
+
const normalized = path6.normalize(target);
|
|
50732
|
+
const key = process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
50733
|
+
const predecessor = fileWriteTails.get(key);
|
|
50734
|
+
let release;
|
|
50735
|
+
const tail = new Promise((resolve) => {
|
|
50736
|
+
release = resolve;
|
|
50737
|
+
});
|
|
50738
|
+
fileWriteTails.set(key, tail);
|
|
50739
|
+
if (predecessor) await predecessor;
|
|
50740
|
+
try {
|
|
50741
|
+
return await operation();
|
|
50742
|
+
} finally {
|
|
50743
|
+
release();
|
|
50744
|
+
if (fileWriteTails.get(key) === tail) fileWriteTails.delete(key);
|
|
50745
|
+
}
|
|
50746
|
+
}
|
|
50747
|
+
async function withFileWriteLocks(targets, operation) {
|
|
50748
|
+
const unique = [...new Set(targets.map((target) => path6.normalize(target)))].sort(
|
|
50749
|
+
(left, right) => left.localeCompare(right)
|
|
50750
|
+
);
|
|
50751
|
+
const run = async (index) => {
|
|
50752
|
+
const target = unique[index];
|
|
50753
|
+
return target === void 0 ? await operation() : await withFileWriteLock(target, async () => await run(index + 1));
|
|
50754
|
+
};
|
|
50755
|
+
return await run(0);
|
|
50756
|
+
}
|
|
50757
|
+
async function requireRevision(target, expectedSha256) {
|
|
50758
|
+
let actual = null;
|
|
50759
|
+
try {
|
|
50760
|
+
actual = sha256(await readFile3(target));
|
|
50761
|
+
} catch (error46) {
|
|
50762
|
+
if (error46.code !== "ENOENT") throw error46;
|
|
50763
|
+
}
|
|
50764
|
+
if (actual !== expectedSha256) {
|
|
50765
|
+
throw new WorkerError("stale_revision", "The file revision has changed.");
|
|
50766
|
+
}
|
|
50767
|
+
}
|
|
50768
|
+
var DEFAULT_LIST_FILES_LIMIT = 100;
|
|
50769
|
+
var DEFAULT_SEARCH_TEXT_RESULTS = 50;
|
|
50770
|
+
var DEFAULT_READ_RANGE_LINES = 200;
|
|
50771
|
+
var MAX_REPOSITORY_SCAN_ENTRIES = 2e4;
|
|
50772
|
+
var MAX_SEARCH_FILES = 5e3;
|
|
50773
|
+
var MAX_SEARCH_BYTES = 32 * 1024 * 1024;
|
|
50774
|
+
var SKIPPED_RECURSIVE_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
50775
|
+
".git",
|
|
50776
|
+
".hg",
|
|
50777
|
+
".svn",
|
|
50778
|
+
"node_modules"
|
|
50779
|
+
]);
|
|
50780
|
+
function compareNames(left, right) {
|
|
50781
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
50782
|
+
}
|
|
50783
|
+
function relativeSortKey(root, target) {
|
|
50784
|
+
const relative = path6.relative(root, target);
|
|
50785
|
+
if (!relative) return ".";
|
|
50786
|
+
return process.platform === "win32" ? relative.replaceAll("\\", "/") : relative;
|
|
50787
|
+
}
|
|
50788
|
+
function displayPath(root, target) {
|
|
50789
|
+
const relative = relativeSortKey(root, target);
|
|
50790
|
+
if (process.platform === "win32") return relative;
|
|
50791
|
+
return relative.includes("\\") ? "./" + relative : relative;
|
|
50792
|
+
}
|
|
50793
|
+
function isUnavailableFileError(error46) {
|
|
50794
|
+
const code = error46?.code;
|
|
50795
|
+
return code === "ENOENT" || code === "EACCES" || code === "EPERM" || code === "ENOTDIR";
|
|
50796
|
+
}
|
|
50797
|
+
function isUnavailableDiscoveredPathError(error46) {
|
|
50798
|
+
return isUnavailableFileError(error46) || error46 instanceof WorkerError && error46.code === "path_not_found";
|
|
50799
|
+
}
|
|
50800
|
+
function isLinkedPathError(error46) {
|
|
50801
|
+
return error46 instanceof WorkerError && error46.code === "linked_path";
|
|
50802
|
+
}
|
|
50803
|
+
async function readBoundedFile(target, maximumBytes, expectedBytes) {
|
|
50804
|
+
const handle = await open(target, "r");
|
|
50805
|
+
try {
|
|
50806
|
+
const openedStat = await handle.stat();
|
|
50807
|
+
if (!openedStat.isFile()) {
|
|
50808
|
+
throw new WorkerError("not_file", "The requested path is not a file.");
|
|
50809
|
+
}
|
|
50810
|
+
if (openedStat.size !== expectedBytes) {
|
|
50811
|
+
throw new WorkerError("file_changed", "The file changed while it was being read.");
|
|
50812
|
+
}
|
|
50813
|
+
if (openedStat.size > maximumBytes) {
|
|
50814
|
+
throw new WorkerError("search_byte_limit", "The search byte limit was reached.");
|
|
50815
|
+
}
|
|
50816
|
+
const buffer = Buffer.allocUnsafe(openedStat.size);
|
|
50817
|
+
let offset = 0;
|
|
50818
|
+
while (offset < buffer.byteLength) {
|
|
50819
|
+
const { bytesRead } = await handle.read(
|
|
50820
|
+
buffer,
|
|
50821
|
+
offset,
|
|
50822
|
+
buffer.byteLength - offset,
|
|
50823
|
+
offset
|
|
50824
|
+
);
|
|
50825
|
+
if (bytesRead === 0) break;
|
|
50826
|
+
offset += bytesRead;
|
|
50827
|
+
}
|
|
50828
|
+
const completedStat = await handle.stat();
|
|
50829
|
+
if (completedStat.size !== openedStat.size || offset !== openedStat.size) {
|
|
50830
|
+
throw new WorkerError("file_changed", "The file changed while it was being read.");
|
|
50831
|
+
}
|
|
50832
|
+
return Buffer.from(buffer);
|
|
50833
|
+
} finally {
|
|
50834
|
+
await handle.close();
|
|
50835
|
+
}
|
|
50836
|
+
}
|
|
50837
|
+
function normalizedLines(content) {
|
|
50838
|
+
if (content.length === 0) return [];
|
|
50839
|
+
const normalized = content.replace(/\r\n?/g, "\n");
|
|
50840
|
+
const lines = normalized.split("\n");
|
|
50841
|
+
if (normalized.endsWith("\n")) lines.pop();
|
|
50383
50842
|
return lines;
|
|
50384
50843
|
}
|
|
50385
50844
|
function escapeRegExp(value) {
|
|
@@ -50563,7 +51022,7 @@ var FileService = class {
|
|
|
50563
51022
|
this.#beforeDeadline = dependencies.beforeDeadline ?? defaultBeforeDeadline;
|
|
50564
51023
|
this.#openDirectory = dependencies.openDirectory ?? ((directory) => opendir(directory));
|
|
50565
51024
|
this.#readFileBytes = dependencies.readFileBytes ?? readBoundedFile;
|
|
50566
|
-
this.#lstatPath = dependencies.lstatPath ?? ((target) =>
|
|
51025
|
+
this.#lstatPath = dependencies.lstatPath ?? ((target) => lstat2(target));
|
|
50567
51026
|
this.#trustDirectoryEntryTypes = dependencies.trustDirectoryEntryTypes ?? dependencies.lstatPath === void 0;
|
|
50568
51027
|
this.#maxRepositoryScanEntries = dependencies.maxRepositoryScanEntries ?? MAX_REPOSITORY_SCAN_ENTRIES;
|
|
50569
51028
|
this.#maxSearchBytes = dependencies.maxSearchBytes ?? MAX_SEARCH_BYTES;
|
|
@@ -50634,7 +51093,7 @@ var FileService = class {
|
|
|
50634
51093
|
}
|
|
50635
51094
|
async #readResolvedText(target, maximumBytes = MAX_TEXT_BYTES) {
|
|
50636
51095
|
const boundedMaximum = Math.min(maximumBytes, MAX_TEXT_BYTES);
|
|
50637
|
-
const targetStat = await
|
|
51096
|
+
const targetStat = await stat2(target);
|
|
50638
51097
|
if (!targetStat.isFile()) {
|
|
50639
51098
|
throw new WorkerError("not_file", "The requested path is not a file.");
|
|
50640
51099
|
}
|
|
@@ -50677,7 +51136,7 @@ var FileService = class {
|
|
|
50677
51136
|
this.policy.resolveExisting(startPath),
|
|
50678
51137
|
deadlineAt
|
|
50679
51138
|
);
|
|
50680
|
-
const startStat = await this.#withinDeadline(
|
|
51139
|
+
const startStat = await this.#withinDeadline(stat2(start), deadlineAt);
|
|
50681
51140
|
if (!startStat.isDirectory()) {
|
|
50682
51141
|
throw new WorkerError("not_directory", "The requested path is not a directory.");
|
|
50683
51142
|
}
|
|
@@ -50706,7 +51165,7 @@ var FileService = class {
|
|
|
50706
51165
|
if (batch.overflow) throw scanLimitError();
|
|
50707
51166
|
scannedEntries += batch.children.length;
|
|
50708
51167
|
for (const child of batch.children) {
|
|
50709
|
-
const target =
|
|
51168
|
+
const target = path6.join(directory, child.name);
|
|
50710
51169
|
heapPush(frontier, {
|
|
50711
51170
|
target,
|
|
50712
51171
|
sortKey: relativeSortKey(this.policy.root, target),
|
|
@@ -50730,7 +51189,7 @@ var FileService = class {
|
|
|
50730
51189
|
throw scanLimitError();
|
|
50731
51190
|
}
|
|
50732
51191
|
scannedEntries += 1;
|
|
50733
|
-
const target =
|
|
51192
|
+
const target = path6.join(directory, child.name);
|
|
50734
51193
|
let childStat;
|
|
50735
51194
|
try {
|
|
50736
51195
|
childStat = await this.#withinDeadline(
|
|
@@ -50876,15 +51335,31 @@ var FileService = class {
|
|
|
50876
51335
|
);
|
|
50877
51336
|
const matchLimit = maxResults + 1;
|
|
50878
51337
|
const extensions = options.extensions?.map((extension) => extension.toLowerCase());
|
|
50879
|
-
const
|
|
50880
|
-
|
|
50881
|
-
|
|
50882
|
-
|
|
51338
|
+
const includeGlobs = options.includeGlobs;
|
|
51339
|
+
const excludeGlobs = options.excludeGlobs;
|
|
51340
|
+
const globMatches = (relativePath, patterns) => {
|
|
51341
|
+
if (!patterns) return false;
|
|
51342
|
+
const normalized = relativePath.replaceAll("\\", "/");
|
|
51343
|
+
try {
|
|
51344
|
+
return patterns.some((pattern) => path6.posix.matchesGlob(normalized, pattern));
|
|
51345
|
+
} catch {
|
|
51346
|
+
throw new WorkerError("invalid_search", "Search glob pattern is invalid.");
|
|
51347
|
+
}
|
|
51348
|
+
};
|
|
51349
|
+
let matcher;
|
|
51350
|
+
try {
|
|
51351
|
+
matcher = new RegExp(
|
|
51352
|
+
options.matchMode === "regex" ? options.query : escapeRegExp(options.query),
|
|
51353
|
+
options.caseSensitive === true ? "u" : "iu"
|
|
51354
|
+
);
|
|
51355
|
+
} catch {
|
|
51356
|
+
throw new WorkerError("invalid_search", "Search regular expression is invalid.");
|
|
51357
|
+
}
|
|
50883
51358
|
const start = await this.#withinDeadline(
|
|
50884
51359
|
this.policy.resolveExisting(options.path ?? "."),
|
|
50885
51360
|
deadlineAt
|
|
50886
51361
|
);
|
|
50887
|
-
const startStat = await this.#withinDeadline(
|
|
51362
|
+
const startStat = await this.#withinDeadline(stat2(start), deadlineAt);
|
|
50888
51363
|
const matches = [];
|
|
50889
51364
|
let scannedEntries = 0;
|
|
50890
51365
|
let scannedFiles = 0;
|
|
@@ -50895,10 +51370,12 @@ var FileService = class {
|
|
|
50895
51370
|
const searchFile = async (target) => {
|
|
50896
51371
|
const relative = displayPath(this.policy.root, target);
|
|
50897
51372
|
if (extensions && !extensions.some(
|
|
50898
|
-
(extension) =>
|
|
51373
|
+
(extension) => path6.basename(target).toLowerCase().endsWith(extension)
|
|
50899
51374
|
)) {
|
|
50900
51375
|
return false;
|
|
50901
51376
|
}
|
|
51377
|
+
if (includeGlobs && !globMatches(relative, includeGlobs)) return false;
|
|
51378
|
+
if (globMatches(relative, excludeGlobs)) return false;
|
|
50902
51379
|
if (scannedFiles >= MAX_SEARCH_FILES) {
|
|
50903
51380
|
scanTruncated = true;
|
|
50904
51381
|
return true;
|
|
@@ -50988,7 +51465,7 @@ var FileService = class {
|
|
|
50988
51465
|
scannedEntries += batch.children.length;
|
|
50989
51466
|
for (const child of batch.children) {
|
|
50990
51467
|
this.#assertBeforeDeadline(deadlineAt);
|
|
50991
|
-
const target =
|
|
51468
|
+
const target = path6.join(directory, child.name);
|
|
50992
51469
|
let targetType;
|
|
50993
51470
|
if (this.#trustDirectoryEntryTypes) {
|
|
50994
51471
|
if (child.isSymbolicLink()) {
|
|
@@ -51123,6 +51600,118 @@ var FileService = class {
|
|
|
51123
51600
|
...endLine < lines.length ? { nextLine: endLine + 1 } : {}
|
|
51124
51601
|
};
|
|
51125
51602
|
}
|
|
51603
|
+
async makeDirectory(relativePath, recursive = false) {
|
|
51604
|
+
const initial = await this.policy.resolveWritableDirectory(
|
|
51605
|
+
relativePath,
|
|
51606
|
+
recursive
|
|
51607
|
+
);
|
|
51608
|
+
if (initial.exists) return { created: false };
|
|
51609
|
+
return await withFileWriteLock(initial.target, async () => {
|
|
51610
|
+
const current = await this.policy.resolveWritableDirectory(
|
|
51611
|
+
relativePath,
|
|
51612
|
+
recursive
|
|
51613
|
+
);
|
|
51614
|
+
if (current.exists) return { created: false };
|
|
51615
|
+
try {
|
|
51616
|
+
await mkdir3(current.target, { recursive });
|
|
51617
|
+
} catch (error46) {
|
|
51618
|
+
if (error46.code !== "EEXIST") throw error46;
|
|
51619
|
+
}
|
|
51620
|
+
const resolved = await this.policy.resolveExisting(relativePath);
|
|
51621
|
+
if (!(await stat2(resolved)).isDirectory()) {
|
|
51622
|
+
throw new WorkerError("not_directory", "The destination is not a directory.");
|
|
51623
|
+
}
|
|
51624
|
+
return { created: true };
|
|
51625
|
+
});
|
|
51626
|
+
}
|
|
51627
|
+
async deletePath(relativePath, recursive = false) {
|
|
51628
|
+
const initial = await this.policy.resolveExisting(relativePath);
|
|
51629
|
+
if (samePath(initial, this.policy.root)) {
|
|
51630
|
+
throw new WorkerError(
|
|
51631
|
+
"root_operation_refused",
|
|
51632
|
+
"The exposed workspace root cannot be deleted."
|
|
51633
|
+
);
|
|
51634
|
+
}
|
|
51635
|
+
return await withFileWriteLock(initial, async () => {
|
|
51636
|
+
const target = await this.policy.resolveExisting(relativePath);
|
|
51637
|
+
if (samePath(target, this.policy.root)) {
|
|
51638
|
+
throw new WorkerError(
|
|
51639
|
+
"root_operation_refused",
|
|
51640
|
+
"The exposed workspace root cannot be deleted."
|
|
51641
|
+
);
|
|
51642
|
+
}
|
|
51643
|
+
const targetStat = await lstat2(target);
|
|
51644
|
+
if (!targetStat.isFile() && !targetStat.isDirectory()) {
|
|
51645
|
+
throw new WorkerError(
|
|
51646
|
+
"unsupported_path_type",
|
|
51647
|
+
"Only regular files and directories can be deleted."
|
|
51648
|
+
);
|
|
51649
|
+
}
|
|
51650
|
+
try {
|
|
51651
|
+
if (targetStat.isDirectory()) {
|
|
51652
|
+
if (recursive) {
|
|
51653
|
+
await rm2(target, { recursive: true, force: false });
|
|
51654
|
+
} else {
|
|
51655
|
+
await rmdir(target);
|
|
51656
|
+
}
|
|
51657
|
+
} else {
|
|
51658
|
+
await rm2(target, { force: false });
|
|
51659
|
+
}
|
|
51660
|
+
} catch (error46) {
|
|
51661
|
+
const code = error46.code;
|
|
51662
|
+
if (targetStat.isDirectory() && !recursive && (code === "ENOTEMPTY" || code === "EEXIST" || code === "EPERM")) {
|
|
51663
|
+
throw new WorkerError(
|
|
51664
|
+
"directory_not_empty",
|
|
51665
|
+
"The directory is not empty. Set recursive to true to delete its contents."
|
|
51666
|
+
);
|
|
51667
|
+
}
|
|
51668
|
+
throw error46;
|
|
51669
|
+
}
|
|
51670
|
+
return {
|
|
51671
|
+
deletedType: targetStat.isDirectory() ? "directory" : "file"
|
|
51672
|
+
};
|
|
51673
|
+
});
|
|
51674
|
+
}
|
|
51675
|
+
async movePath(sourcePath, destinationPath) {
|
|
51676
|
+
const initialSource = await this.policy.resolveExisting(sourcePath);
|
|
51677
|
+
if (samePath(initialSource, this.policy.root)) {
|
|
51678
|
+
throw new WorkerError(
|
|
51679
|
+
"root_operation_refused",
|
|
51680
|
+
"The exposed workspace root cannot be moved."
|
|
51681
|
+
);
|
|
51682
|
+
}
|
|
51683
|
+
const initialDestination = await this.policy.resolveVacantPath(destinationPath);
|
|
51684
|
+
return await withFileWriteLocks(
|
|
51685
|
+
[initialSource, initialDestination],
|
|
51686
|
+
async () => {
|
|
51687
|
+
const source = await this.policy.resolveExisting(sourcePath);
|
|
51688
|
+
if (samePath(source, this.policy.root)) {
|
|
51689
|
+
throw new WorkerError(
|
|
51690
|
+
"root_operation_refused",
|
|
51691
|
+
"The exposed workspace root cannot be moved."
|
|
51692
|
+
);
|
|
51693
|
+
}
|
|
51694
|
+
const destination = await this.policy.resolveVacantPath(destinationPath);
|
|
51695
|
+
const sourceStat = await lstat2(source);
|
|
51696
|
+
if (!sourceStat.isFile() && !sourceStat.isDirectory()) {
|
|
51697
|
+
throw new WorkerError(
|
|
51698
|
+
"unsupported_path_type",
|
|
51699
|
+
"Only regular files and directories can be moved."
|
|
51700
|
+
);
|
|
51701
|
+
}
|
|
51702
|
+
if (sourceStat.isDirectory() && isPathWithin(source, destination)) {
|
|
51703
|
+
throw new WorkerError(
|
|
51704
|
+
"invalid_destination",
|
|
51705
|
+
"A directory cannot be moved inside itself."
|
|
51706
|
+
);
|
|
51707
|
+
}
|
|
51708
|
+
await rename(source, destination);
|
|
51709
|
+
return {
|
|
51710
|
+
movedType: sourceStat.isDirectory() ? "directory" : "file"
|
|
51711
|
+
};
|
|
51712
|
+
}
|
|
51713
|
+
);
|
|
51714
|
+
}
|
|
51126
51715
|
async editText(relativePath, edits, expectedSha256) {
|
|
51127
51716
|
const original = await this.readText(relativePath);
|
|
51128
51717
|
if (expectedSha256 && original.sha256 !== expectedSha256) {
|
|
@@ -51174,25 +51763,45 @@ var FileService = class {
|
|
|
51174
51763
|
return await withFileWriteLock(target, async () => {
|
|
51175
51764
|
let existingMode;
|
|
51176
51765
|
try {
|
|
51177
|
-
const existingStat = await
|
|
51766
|
+
const existingStat = await stat2(target);
|
|
51178
51767
|
if (existingStat.isFile()) existingMode = existingStat.mode & 4095;
|
|
51179
51768
|
} catch (error46) {
|
|
51180
51769
|
if (error46.code !== "ENOENT") throw error46;
|
|
51181
51770
|
}
|
|
51771
|
+
if (existingMode !== void 0 && expectedSha256 === void 0) {
|
|
51772
|
+
throw new WorkerError(
|
|
51773
|
+
"path_exists",
|
|
51774
|
+
"The file already exists. Read it first and pass expectedSha256 to replace that revision."
|
|
51775
|
+
);
|
|
51776
|
+
}
|
|
51182
51777
|
if (expectedSha256) await requireRevision(target, expectedSha256);
|
|
51183
|
-
const temporary =
|
|
51778
|
+
const temporary = path6.join(path6.dirname(target), `.glossa-${randomUUID2()}.tmp`);
|
|
51184
51779
|
try {
|
|
51185
51780
|
await writeFile3(temporary, bytes, { flag: "wx", mode: 384 });
|
|
51186
51781
|
target = await this.policy.resolveWritableFile(relativePath);
|
|
51187
|
-
const tempStat = await
|
|
51782
|
+
const tempStat = await lstat2(temporary);
|
|
51188
51783
|
if (!tempStat.isFile() || tempStat.isSymbolicLink()) {
|
|
51189
51784
|
throw new WorkerError("unsafe_temporary_file", "The atomic write temporary file changed.");
|
|
51190
51785
|
}
|
|
51191
51786
|
if (existingMode !== void 0 && process.platform !== "win32") {
|
|
51192
51787
|
await chmod2(temporary, existingMode);
|
|
51193
51788
|
}
|
|
51194
|
-
if (expectedSha256)
|
|
51195
|
-
|
|
51789
|
+
if (expectedSha256) {
|
|
51790
|
+
await requireRevision(target, expectedSha256);
|
|
51791
|
+
await rename(temporary, target);
|
|
51792
|
+
} else {
|
|
51793
|
+
try {
|
|
51794
|
+
await link2(temporary, target);
|
|
51795
|
+
} catch (error46) {
|
|
51796
|
+
if (error46.code === "EEXIST") {
|
|
51797
|
+
throw new WorkerError(
|
|
51798
|
+
"path_exists",
|
|
51799
|
+
"The file already exists. Read it first and pass expectedSha256 to replace that revision."
|
|
51800
|
+
);
|
|
51801
|
+
}
|
|
51802
|
+
throw error46;
|
|
51803
|
+
}
|
|
51804
|
+
}
|
|
51196
51805
|
} finally {
|
|
51197
51806
|
await rm2(temporary, { force: true });
|
|
51198
51807
|
}
|
|
@@ -51201,173 +51810,6 @@ var FileService = class {
|
|
|
51201
51810
|
}
|
|
51202
51811
|
};
|
|
51203
51812
|
|
|
51204
|
-
// src/worker/path-policy.ts
|
|
51205
|
-
import { lstat as lstat2, realpath, stat as stat2 } from "node:fs/promises";
|
|
51206
|
-
import os5 from "node:os";
|
|
51207
|
-
import path6 from "node:path";
|
|
51208
|
-
function samePath(left, right) {
|
|
51209
|
-
return process.platform === "win32" ? left.toLowerCase() === right.toLowerCase() : left === right;
|
|
51210
|
-
}
|
|
51211
|
-
function accountHomeDirectory() {
|
|
51212
|
-
try {
|
|
51213
|
-
return os5.userInfo().homedir;
|
|
51214
|
-
} catch {
|
|
51215
|
-
return os5.homedir();
|
|
51216
|
-
}
|
|
51217
|
-
}
|
|
51218
|
-
function isWithin(root, candidate) {
|
|
51219
|
-
const relative = path6.relative(root, candidate);
|
|
51220
|
-
return relative === "" || !relative.startsWith(`..${path6.sep}`) && relative !== ".." && !path6.isAbsolute(relative);
|
|
51221
|
-
}
|
|
51222
|
-
function validateRelativePath(value) {
|
|
51223
|
-
if (value.includes("\0")) {
|
|
51224
|
-
throw new WorkerError("invalid_path", "Paths cannot contain null bytes.");
|
|
51225
|
-
}
|
|
51226
|
-
const explicitNativePosixPath = process.platform !== "win32" && value.startsWith("./");
|
|
51227
|
-
if (path6.isAbsolute(value) || path6.posix.isAbsolute(value) || !explicitNativePosixPath && path6.win32.isAbsolute(value)) {
|
|
51228
|
-
throw new WorkerError("absolute_path", "Absolute paths are not allowed.");
|
|
51229
|
-
}
|
|
51230
|
-
const segments = explicitNativePosixPath ? value.split(/\/+/).filter(Boolean) : value.split(/[\\/]+/);
|
|
51231
|
-
if (segments.includes("..")) {
|
|
51232
|
-
throw new WorkerError("path_traversal", "Parent path traversal is not allowed.");
|
|
51233
|
-
}
|
|
51234
|
-
return value === "" ? "." : value;
|
|
51235
|
-
}
|
|
51236
|
-
async function canonicalizeRoot(candidate) {
|
|
51237
|
-
const root = await realpath(path6.resolve(candidate)).catch((error46) => {
|
|
51238
|
-
if (error46.code === "ENOENT") {
|
|
51239
|
-
throw new WorkerError("root_not_found", "The workspace directory does not exist.");
|
|
51240
|
-
}
|
|
51241
|
-
throw error46;
|
|
51242
|
-
});
|
|
51243
|
-
const rootStat = await stat2(root);
|
|
51244
|
-
if (!rootStat.isDirectory()) {
|
|
51245
|
-
throw new WorkerError("root_not_directory", "The exposed root must be a directory.");
|
|
51246
|
-
}
|
|
51247
|
-
const filesystemRoot = path6.parse(root).root;
|
|
51248
|
-
const homes = await Promise.all(
|
|
51249
|
-
[os5.homedir(), accountHomeDirectory()].map(
|
|
51250
|
-
async (home) => await realpath(home).catch(() => path6.resolve(home))
|
|
51251
|
-
)
|
|
51252
|
-
);
|
|
51253
|
-
const isHomeDirectory = homes.some((home) => samePath(root, home));
|
|
51254
|
-
if (samePath(root, filesystemRoot) || isHomeDirectory) {
|
|
51255
|
-
const kind = isHomeDirectory ? "your home directory" : "a filesystem root";
|
|
51256
|
-
throw new WorkerError(
|
|
51257
|
-
"broad_root_refused",
|
|
51258
|
-
`The selected root is ${kind}, which Glossa will not expose. Choose a project directory instead.`
|
|
51259
|
-
);
|
|
51260
|
-
}
|
|
51261
|
-
return root;
|
|
51262
|
-
}
|
|
51263
|
-
var PathPolicy = class _PathPolicy {
|
|
51264
|
-
constructor(root) {
|
|
51265
|
-
this.root = root;
|
|
51266
|
-
}
|
|
51267
|
-
root;
|
|
51268
|
-
static async create(candidate) {
|
|
51269
|
-
return new _PathPolicy(await canonicalizeRoot(candidate));
|
|
51270
|
-
}
|
|
51271
|
-
async resolveExisting(relativePath) {
|
|
51272
|
-
const lexical = this.resolveLexical(relativePath);
|
|
51273
|
-
await this.rejectLinkedComponents(lexical);
|
|
51274
|
-
const canonical = await realpath(lexical).catch((error46) => {
|
|
51275
|
-
if (error46.code === "ENOENT") {
|
|
51276
|
-
throw new WorkerError("path_not_found", "The requested path does not exist.");
|
|
51277
|
-
}
|
|
51278
|
-
throw error46;
|
|
51279
|
-
});
|
|
51280
|
-
if (!isWithin(this.root, canonical)) {
|
|
51281
|
-
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
51282
|
-
}
|
|
51283
|
-
return canonical;
|
|
51284
|
-
}
|
|
51285
|
-
async resolveDiscoveredExisting(candidate) {
|
|
51286
|
-
const lexical = path6.resolve(candidate);
|
|
51287
|
-
if (!isWithin(this.root, lexical)) {
|
|
51288
|
-
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
51289
|
-
}
|
|
51290
|
-
await this.rejectLinkedComponents(lexical);
|
|
51291
|
-
const canonical = await realpath(lexical).catch((error46) => {
|
|
51292
|
-
if (error46.code === "ENOENT") {
|
|
51293
|
-
throw new WorkerError("path_not_found", "The requested path does not exist.");
|
|
51294
|
-
}
|
|
51295
|
-
throw error46;
|
|
51296
|
-
});
|
|
51297
|
-
if (!isWithin(this.root, canonical)) {
|
|
51298
|
-
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
51299
|
-
}
|
|
51300
|
-
return canonical;
|
|
51301
|
-
}
|
|
51302
|
-
async resolveWritableFile(relativePath) {
|
|
51303
|
-
const lexical = this.resolveLexical(relativePath);
|
|
51304
|
-
const parent = path6.dirname(lexical);
|
|
51305
|
-
await this.rejectLinkedComponents(parent);
|
|
51306
|
-
const canonicalParent = await realpath(parent).catch((error46) => {
|
|
51307
|
-
if (error46.code === "ENOENT") {
|
|
51308
|
-
throw new WorkerError("parent_not_found", "The destination directory does not exist.");
|
|
51309
|
-
}
|
|
51310
|
-
throw error46;
|
|
51311
|
-
});
|
|
51312
|
-
if (!isWithin(this.root, canonicalParent)) {
|
|
51313
|
-
throw new WorkerError("path_escape", "The destination escapes the exposed root.");
|
|
51314
|
-
}
|
|
51315
|
-
if (!(await stat2(canonicalParent)).isDirectory()) {
|
|
51316
|
-
throw new WorkerError("not_directory", "The destination parent is not a directory.");
|
|
51317
|
-
}
|
|
51318
|
-
try {
|
|
51319
|
-
const targetStat = await lstat2(lexical);
|
|
51320
|
-
if (targetStat.isSymbolicLink()) {
|
|
51321
|
-
throw new WorkerError("linked_path", "Writes through links are not allowed.");
|
|
51322
|
-
}
|
|
51323
|
-
if (targetStat.isDirectory()) {
|
|
51324
|
-
throw new WorkerError("not_file", "The destination is a directory.");
|
|
51325
|
-
}
|
|
51326
|
-
const canonicalTarget = await realpath(lexical);
|
|
51327
|
-
if (!isWithin(this.root, canonicalTarget)) {
|
|
51328
|
-
throw new WorkerError("path_escape", "The destination escapes the exposed root.");
|
|
51329
|
-
}
|
|
51330
|
-
return canonicalTarget;
|
|
51331
|
-
} catch (error46) {
|
|
51332
|
-
if (error46 instanceof WorkerError) throw error46;
|
|
51333
|
-
if (error46.code !== "ENOENT") throw error46;
|
|
51334
|
-
}
|
|
51335
|
-
return path6.join(canonicalParent, path6.basename(lexical));
|
|
51336
|
-
}
|
|
51337
|
-
resolveLexical(relativePath) {
|
|
51338
|
-
const validated = validateRelativePath(relativePath);
|
|
51339
|
-
const candidate = path6.resolve(this.root, validated);
|
|
51340
|
-
if (!isWithin(this.root, candidate)) {
|
|
51341
|
-
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
51342
|
-
}
|
|
51343
|
-
return candidate;
|
|
51344
|
-
}
|
|
51345
|
-
async rejectLinkedComponents(candidate) {
|
|
51346
|
-
if (!isWithin(this.root, candidate)) {
|
|
51347
|
-
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
51348
|
-
}
|
|
51349
|
-
const relative = path6.relative(this.root, candidate);
|
|
51350
|
-
if (!relative) return;
|
|
51351
|
-
let current = this.root;
|
|
51352
|
-
for (const segment of relative.split(path6.sep)) {
|
|
51353
|
-
current = path6.join(current, segment);
|
|
51354
|
-
try {
|
|
51355
|
-
const currentStat = await lstat2(current);
|
|
51356
|
-
if (currentStat.isSymbolicLink()) {
|
|
51357
|
-
throw new WorkerError(
|
|
51358
|
-
"linked_path",
|
|
51359
|
-
"Symlink and junction paths are not allowed."
|
|
51360
|
-
);
|
|
51361
|
-
}
|
|
51362
|
-
} catch (error46) {
|
|
51363
|
-
if (error46 instanceof WorkerError) throw error46;
|
|
51364
|
-
if (error46.code === "ENOENT") return;
|
|
51365
|
-
throw error46;
|
|
51366
|
-
}
|
|
51367
|
-
}
|
|
51368
|
-
}
|
|
51369
|
-
};
|
|
51370
|
-
|
|
51371
51813
|
// src/worker/local-worker.ts
|
|
51372
51814
|
function restrictedDataError2() {
|
|
51373
51815
|
return new WorkerError(
|
|
@@ -51402,6 +51844,14 @@ function jobInputContainsRestrictedData(job) {
|
|
|
51402
51844
|
path: job.path,
|
|
51403
51845
|
edits: job.edits
|
|
51404
51846
|
});
|
|
51847
|
+
case "make_directory":
|
|
51848
|
+
case "delete_path":
|
|
51849
|
+
return containsRestrictedAuthenticationData(job.path);
|
|
51850
|
+
case "move_path":
|
|
51851
|
+
return containsRestrictedAuthenticationData({
|
|
51852
|
+
source: job.source,
|
|
51853
|
+
destination: job.destination
|
|
51854
|
+
});
|
|
51405
51855
|
case "run_command":
|
|
51406
51856
|
return containsRestrictedAuthenticationData({
|
|
51407
51857
|
argv: job.argv,
|
|
@@ -51409,12 +51859,13 @@ function jobInputContainsRestrictedData(job) {
|
|
|
51409
51859
|
stdin: job.stdin
|
|
51410
51860
|
});
|
|
51411
51861
|
case "get_command":
|
|
51862
|
+
case "read_command_output":
|
|
51412
51863
|
case "cancel_command":
|
|
51413
51864
|
return false;
|
|
51414
51865
|
}
|
|
51415
51866
|
}
|
|
51416
51867
|
function resultMayContainRestrictedData(job) {
|
|
51417
|
-
return job.type === "read_file" || job.type === "list_files" || job.type === "search_text" || job.type === "read_file_range" || job.type === "edit_file" || job.type === "run_command" || job.type === "get_command" || job.type === "cancel_command";
|
|
51868
|
+
return job.type === "read_file" || job.type === "list_files" || job.type === "search_text" || job.type === "read_file_range" || job.type === "edit_file" || job.type === "run_command" || job.type === "get_command" || job.type === "read_command_output" || job.type === "cancel_command";
|
|
51418
51869
|
}
|
|
51419
51870
|
var LocalWorker = class _LocalWorker {
|
|
51420
51871
|
constructor(accessProfile, policy, files, commands) {
|
|
@@ -51439,13 +51890,13 @@ var LocalWorker = class _LocalWorker {
|
|
|
51439
51890
|
async handle(job) {
|
|
51440
51891
|
try {
|
|
51441
51892
|
const permissions = workerPermissions(this.accessProfile);
|
|
51442
|
-
if ((job.type === "write_file" || job.type === "edit_file") && !permissions.writeFiles) {
|
|
51893
|
+
if ((job.type === "write_file" || job.type === "edit_file" || job.type === "make_directory" || job.type === "delete_path" || job.type === "move_path") && !permissions.writeFiles) {
|
|
51443
51894
|
throw new WorkerError(
|
|
51444
51895
|
"write_access_disabled",
|
|
51445
51896
|
"This worker was started without file-write access."
|
|
51446
51897
|
);
|
|
51447
51898
|
}
|
|
51448
|
-
if ((job.type === "run_command" || job.type === "get_command" || job.type === "cancel_command") && !permissions.runCommands) {
|
|
51899
|
+
if ((job.type === "run_command" || job.type === "get_command" || job.type === "read_command_output" || job.type === "cancel_command") && !permissions.runCommands) {
|
|
51449
51900
|
throw new WorkerError(
|
|
51450
51901
|
"command_access_disabled",
|
|
51451
51902
|
"This worker was started without system-command access."
|
|
@@ -51470,9 +51921,12 @@ var LocalWorker = class _LocalWorker {
|
|
|
51470
51921
|
value = await this.files.searchText({
|
|
51471
51922
|
query: job.query,
|
|
51472
51923
|
...job.path ? { path: job.path } : {},
|
|
51924
|
+
...job.matchMode === void 0 ? {} : { matchMode: job.matchMode },
|
|
51473
51925
|
...job.caseSensitive === void 0 ? {} : { caseSensitive: job.caseSensitive },
|
|
51474
51926
|
...job.maxResults === void 0 ? {} : { maxResults: job.maxResults },
|
|
51475
51927
|
...job.extensions ? { extensions: job.extensions } : {},
|
|
51928
|
+
...job.includeGlobs ? { includeGlobs: job.includeGlobs } : {},
|
|
51929
|
+
...job.excludeGlobs ? { excludeGlobs: job.excludeGlobs } : {},
|
|
51476
51930
|
timeoutMs: job.timeoutMs
|
|
51477
51931
|
});
|
|
51478
51932
|
break;
|
|
@@ -51503,6 +51957,15 @@ var LocalWorker = class _LocalWorker {
|
|
|
51503
51957
|
);
|
|
51504
51958
|
break;
|
|
51505
51959
|
}
|
|
51960
|
+
case "make_directory":
|
|
51961
|
+
value = await this.files.makeDirectory(job.path, job.recursive);
|
|
51962
|
+
break;
|
|
51963
|
+
case "delete_path":
|
|
51964
|
+
value = await this.files.deletePath(job.path, job.recursive);
|
|
51965
|
+
break;
|
|
51966
|
+
case "move_path":
|
|
51967
|
+
value = await this.files.movePath(job.source, job.destination);
|
|
51968
|
+
break;
|
|
51506
51969
|
case "run_command":
|
|
51507
51970
|
value = await this.commands.start({
|
|
51508
51971
|
...job.argv ? { argv: job.argv } : {},
|
|
@@ -51519,6 +51982,14 @@ var LocalWorker = class _LocalWorker {
|
|
|
51519
51982
|
job.afterSequence
|
|
51520
51983
|
);
|
|
51521
51984
|
break;
|
|
51985
|
+
case "read_command_output":
|
|
51986
|
+
value = await this.commands.readOutput(
|
|
51987
|
+
job.commandId,
|
|
51988
|
+
job.stream,
|
|
51989
|
+
job.offset,
|
|
51990
|
+
job.maxBytes
|
|
51991
|
+
);
|
|
51992
|
+
break;
|
|
51522
51993
|
case "cancel_command":
|
|
51523
51994
|
value = await this.commands.cancel(job.commandId);
|
|
51524
51995
|
break;
|
|
@@ -51556,6 +52027,14 @@ var DeviceRejectedError = class extends Error {
|
|
|
51556
52027
|
this.name = "DeviceRejectedError";
|
|
51557
52028
|
}
|
|
51558
52029
|
};
|
|
52030
|
+
var RelayAccessProfileUnsupportedError = class extends Error {
|
|
52031
|
+
constructor(profile) {
|
|
52032
|
+
super(
|
|
52033
|
+
`The relay needs an update before it can represent ${profile} access. Update the relay before reconnecting this workspace.`
|
|
52034
|
+
);
|
|
52035
|
+
this.name = "RelayAccessProfileUnsupportedError";
|
|
52036
|
+
}
|
|
52037
|
+
};
|
|
51559
52038
|
var RelayResponseError = class extends Error {
|
|
51560
52039
|
constructor(status) {
|
|
51561
52040
|
super(`The relay returned HTTP ${status}.`);
|
|
@@ -51581,6 +52060,7 @@ function supportsCapability(value, capability) {
|
|
|
51581
52060
|
function jobLane(job) {
|
|
51582
52061
|
switch (job.type) {
|
|
51583
52062
|
case "get_command":
|
|
52063
|
+
case "read_command_output":
|
|
51584
52064
|
return "status";
|
|
51585
52065
|
case "cancel_command":
|
|
51586
52066
|
return "cancel";
|
|
@@ -51591,14 +52071,20 @@ function jobLane(job) {
|
|
|
51591
52071
|
return "read";
|
|
51592
52072
|
case "write_file":
|
|
51593
52073
|
case "edit_file":
|
|
52074
|
+
case "make_directory":
|
|
52075
|
+
case "delete_path":
|
|
52076
|
+
case "move_path":
|
|
51594
52077
|
case "run_command":
|
|
51595
52078
|
return "mutation";
|
|
51596
52079
|
}
|
|
51597
52080
|
}
|
|
51598
|
-
function acceptedJobTypes(counts, total, structuredReads) {
|
|
52081
|
+
function acceptedJobTypes(counts, total, structuredReads, structuredMutations, commandOutputRanges) {
|
|
51599
52082
|
if (total >= MAX_CONCURRENT_JOBS) return [];
|
|
51600
52083
|
const accepted = [];
|
|
51601
|
-
if (counts.status < 1)
|
|
52084
|
+
if (counts.status < 1) {
|
|
52085
|
+
accepted.push("get_command");
|
|
52086
|
+
if (commandOutputRanges) accepted.push("read_command_output");
|
|
52087
|
+
}
|
|
51602
52088
|
if (counts.cancel < 1) accepted.push("cancel_command");
|
|
51603
52089
|
if (counts.read < 2) {
|
|
51604
52090
|
accepted.push("read_file");
|
|
@@ -51608,6 +52094,9 @@ function acceptedJobTypes(counts, total, structuredReads) {
|
|
|
51608
52094
|
}
|
|
51609
52095
|
if (counts.mutation < 1) {
|
|
51610
52096
|
accepted.push("write_file", "edit_file", "run_command");
|
|
52097
|
+
if (structuredMutations) {
|
|
52098
|
+
accepted.push("make_directory", "delete_path", "move_path");
|
|
52099
|
+
}
|
|
51611
52100
|
}
|
|
51612
52101
|
return accepted;
|
|
51613
52102
|
}
|
|
@@ -51685,7 +52174,9 @@ var RemoteWorker = class {
|
|
|
51685
52174
|
await this.#pollGeneration(session);
|
|
51686
52175
|
} catch (error46) {
|
|
51687
52176
|
if (this.#signal.aborted) return;
|
|
51688
|
-
if (error46 instanceof DeviceRejectedError
|
|
52177
|
+
if (error46 instanceof DeviceRejectedError || error46 instanceof RelayAccessProfileUnsupportedError) {
|
|
52178
|
+
throw error46;
|
|
52179
|
+
}
|
|
51689
52180
|
const delay4 = reconnectDelayMs(
|
|
51690
52181
|
failures,
|
|
51691
52182
|
this.#random,
|
|
@@ -51712,6 +52203,25 @@ var RemoteWorker = class {
|
|
|
51712
52203
|
}
|
|
51713
52204
|
}
|
|
51714
52205
|
async #register() {
|
|
52206
|
+
const currentBody = {
|
|
52207
|
+
workerId: this.#workerId,
|
|
52208
|
+
capabilities: {
|
|
52209
|
+
commandProgress: true,
|
|
52210
|
+
concurrentJobs: true,
|
|
52211
|
+
structuredReads: true,
|
|
52212
|
+
structuredMutations: true,
|
|
52213
|
+
commandOutputRanges: true
|
|
52214
|
+
}
|
|
52215
|
+
};
|
|
52216
|
+
const mutationBody = {
|
|
52217
|
+
workerId: this.#workerId,
|
|
52218
|
+
capabilities: {
|
|
52219
|
+
commandProgress: true,
|
|
52220
|
+
concurrentJobs: true,
|
|
52221
|
+
structuredReads: true,
|
|
52222
|
+
structuredMutations: true
|
|
52223
|
+
}
|
|
52224
|
+
};
|
|
51715
52225
|
const structuredBody = {
|
|
51716
52226
|
workerId: this.#workerId,
|
|
51717
52227
|
capabilities: {
|
|
@@ -51724,60 +52234,74 @@ var RemoteWorker = class {
|
|
|
51724
52234
|
workerId: this.#workerId,
|
|
51725
52235
|
capabilities: { commandProgress: true, concurrentJobs: true }
|
|
51726
52236
|
};
|
|
51727
|
-
const
|
|
51728
|
-
|
|
51729
|
-
|
|
51730
|
-
|
|
51731
|
-
|
|
51732
|
-
} : void 0;
|
|
51733
|
-
const attempts = [
|
|
51734
|
-
...preferredProfileBody ? [{ body: preferredProfileBody, legacyRelay: false }] : [],
|
|
51735
|
-
...versionedStructuredBody && this.#workspaceLabel ? [{
|
|
51736
|
-
body: {
|
|
51737
|
-
...versionedStructuredBody,
|
|
51738
|
-
workspaceLabel: this.#workspaceLabel
|
|
51739
|
-
},
|
|
51740
|
-
legacyRelay: false
|
|
51741
|
-
}] : [],
|
|
51742
|
-
...this.#workspaceLabel ? [{
|
|
51743
|
-
body: {
|
|
51744
|
-
...structuredBody,
|
|
51745
|
-
workspaceLabel: this.#workspaceLabel
|
|
51746
|
-
},
|
|
51747
|
-
legacyRelay: false
|
|
51748
|
-
}] : [],
|
|
51749
|
-
...versionedStructuredBody ? [{ body: versionedStructuredBody, legacyRelay: false }] : [],
|
|
51750
|
-
{
|
|
51751
|
-
body: structuredBody,
|
|
51752
|
-
legacyRelay: false
|
|
51753
|
-
},
|
|
51754
|
-
...this.#workspaceLabel ? [{
|
|
51755
|
-
body: {
|
|
51756
|
-
...concurrentBody,
|
|
51757
|
-
workspaceLabel: this.#workspaceLabel
|
|
51758
|
-
},
|
|
51759
|
-
legacyRelay: false
|
|
51760
|
-
}] : [],
|
|
52237
|
+
const capabilityBodies = [
|
|
52238
|
+
currentBody,
|
|
52239
|
+
mutationBody,
|
|
52240
|
+
structuredBody,
|
|
52241
|
+
concurrentBody,
|
|
51761
52242
|
{
|
|
51762
|
-
|
|
51763
|
-
|
|
52243
|
+
workerId: this.#workerId,
|
|
52244
|
+
capabilities: { commandProgress: true }
|
|
51764
52245
|
},
|
|
52246
|
+
{ workerId: this.#workerId }
|
|
52247
|
+
];
|
|
52248
|
+
const metadataPhases = [
|
|
51765
52249
|
{
|
|
51766
|
-
|
|
51767
|
-
|
|
51768
|
-
capabilities: { commandProgress: true }
|
|
51769
|
-
},
|
|
51770
|
-
legacyRelay: false
|
|
52250
|
+
includeVersion: Boolean(this.#workerVersion),
|
|
52251
|
+
includeLabel: Boolean(this.#workspaceLabel)
|
|
51771
52252
|
},
|
|
51772
|
-
|
|
51773
|
-
|
|
52253
|
+
...this.#workspaceLabel ? [{
|
|
52254
|
+
includeVersion: Boolean(this.#workerVersion),
|
|
52255
|
+
includeLabel: false
|
|
52256
|
+
}] : [],
|
|
52257
|
+
...this.#workerVersion && this.#workspaceLabel ? [{ includeVersion: false, includeLabel: true }] : [],
|
|
52258
|
+
{ includeVersion: false, includeLabel: false }
|
|
51774
52259
|
];
|
|
51775
|
-
|
|
52260
|
+
const attempts = [];
|
|
52261
|
+
const seenAttempts = /* @__PURE__ */ new Set();
|
|
52262
|
+
const pushAttempt = (base, includeProfile, includeVersion, includeLabel, legacyRelay = false) => {
|
|
52263
|
+
const body = {
|
|
52264
|
+
...base,
|
|
52265
|
+
...includeProfile && this.#accessProfile ? { accessProfile: this.#accessProfile } : {},
|
|
52266
|
+
...includeVersion && this.#workerVersion ? { workerVersion: this.#workerVersion } : {},
|
|
52267
|
+
...includeLabel && this.#workspaceLabel ? { workspaceLabel: this.#workspaceLabel } : {}
|
|
52268
|
+
};
|
|
52269
|
+
const key = JSON.stringify(body);
|
|
52270
|
+
if (seenAttempts.has(key)) return;
|
|
52271
|
+
seenAttempts.add(key);
|
|
52272
|
+
attempts.push({ body, legacyRelay, profileIncluded: includeProfile });
|
|
52273
|
+
};
|
|
52274
|
+
if (this.#accessProfile) {
|
|
52275
|
+
for (const metadata of metadataPhases) {
|
|
52276
|
+
for (const body of capabilityBodies) {
|
|
52277
|
+
pushAttempt(
|
|
52278
|
+
body,
|
|
52279
|
+
true,
|
|
52280
|
+
metadata.includeVersion,
|
|
52281
|
+
metadata.includeLabel
|
|
52282
|
+
);
|
|
52283
|
+
}
|
|
52284
|
+
}
|
|
52285
|
+
}
|
|
52286
|
+
if (this.#accessProfile === void 0 || this.#accessProfile === "system") {
|
|
52287
|
+
for (const metadata of metadataPhases) {
|
|
52288
|
+
for (const body of capabilityBodies) {
|
|
52289
|
+
pushAttempt(
|
|
52290
|
+
body,
|
|
52291
|
+
false,
|
|
52292
|
+
metadata.includeVersion,
|
|
52293
|
+
metadata.includeLabel
|
|
52294
|
+
);
|
|
52295
|
+
}
|
|
52296
|
+
}
|
|
52297
|
+
attempts.push({ body: {}, legacyRelay: true, profileIncluded: false });
|
|
52298
|
+
}
|
|
52299
|
+
for (const attempt of attempts) {
|
|
51776
52300
|
let response;
|
|
51777
52301
|
try {
|
|
51778
52302
|
response = await this.#post("/device/register", attempt.body);
|
|
51779
52303
|
} catch (error46) {
|
|
51780
|
-
if (error46 instanceof RelayResponseError && error46.status === 400
|
|
52304
|
+
if (error46 instanceof RelayResponseError && error46.status === 400) {
|
|
51781
52305
|
continue;
|
|
51782
52306
|
}
|
|
51783
52307
|
throw error46;
|
|
@@ -51795,11 +52319,16 @@ var RemoteWorker = class {
|
|
|
51795
52319
|
legacyRelay: attempt.legacyRelay,
|
|
51796
52320
|
concurrentJobs: !attempt.legacyRelay && supportsCapability(value, "concurrentJobs"),
|
|
51797
52321
|
structuredReads: !attempt.legacyRelay && supportsCapability(value, "structuredReads"),
|
|
51798
|
-
|
|
52322
|
+
structuredMutations: !attempt.legacyRelay && supportsCapability(value, "structuredMutations"),
|
|
52323
|
+
commandOutputRanges: !attempt.legacyRelay && supportsCapability(value, "commandOutputRanges"),
|
|
52324
|
+
accessProfileAccepted: this.#accessProfile === void 0 || "accessProfile" in value && value.accessProfile === this.#accessProfile || this.#accessProfile === "system" && !attempt.profileIncluded,
|
|
51799
52325
|
workspaceLabelAccepted: this.#workspaceLabel === void 0 || "workspaceLabel" in value && value.workspaceLabel === this.#workspaceLabel,
|
|
51800
52326
|
...workerToken ? { workerToken } : {}
|
|
51801
52327
|
};
|
|
51802
52328
|
}
|
|
52329
|
+
if (this.#accessProfile && this.#accessProfile !== "system") {
|
|
52330
|
+
throw new RelayAccessProfileUnsupportedError(this.#accessProfile);
|
|
52331
|
+
}
|
|
51803
52332
|
throw new Error("The relay rejected every supported registration shape.");
|
|
51804
52333
|
}
|
|
51805
52334
|
async #pollGeneration(session) {
|
|
@@ -51889,7 +52418,9 @@ var RemoteWorker = class {
|
|
|
51889
52418
|
const acceptedTypes = acceptedJobTypes(
|
|
51890
52419
|
counts,
|
|
51891
52420
|
inFlight.size,
|
|
51892
|
-
session.structuredReads
|
|
52421
|
+
session.structuredReads,
|
|
52422
|
+
session.structuredMutations,
|
|
52423
|
+
session.commandOutputRanges
|
|
51893
52424
|
);
|
|
51894
52425
|
if (acceptedTypes.length === 0) {
|
|
51895
52426
|
await Promise.race(inFlight);
|
|
@@ -51919,7 +52450,9 @@ var RemoteWorker = class {
|
|
|
51919
52450
|
const refreshedTypes = acceptedJobTypes(
|
|
51920
52451
|
counts,
|
|
51921
52452
|
inFlight.size,
|
|
51922
|
-
session.structuredReads
|
|
52453
|
+
session.structuredReads,
|
|
52454
|
+
session.structuredMutations,
|
|
52455
|
+
session.commandOutputRanges
|
|
51923
52456
|
);
|
|
51924
52457
|
const newlyAcceptedTypes = refreshedTypes.filter(
|
|
51925
52458
|
(type) => !acceptedTypes.includes(type)
|
|
@@ -52099,6 +52632,15 @@ function activitySafeJob(job) {
|
|
|
52099
52632
|
newText: ""
|
|
52100
52633
|
}]
|
|
52101
52634
|
};
|
|
52635
|
+
case "make_directory":
|
|
52636
|
+
case "delete_path":
|
|
52637
|
+
return { ...job, path: "[restricted input blocked]" };
|
|
52638
|
+
case "move_path":
|
|
52639
|
+
return {
|
|
52640
|
+
...job,
|
|
52641
|
+
source: "[restricted input blocked]",
|
|
52642
|
+
destination: "[restricted input blocked]"
|
|
52643
|
+
};
|
|
52102
52644
|
case "run_command":
|
|
52103
52645
|
return {
|
|
52104
52646
|
type: "run_command",
|
|
@@ -52108,6 +52650,7 @@ function activitySafeJob(job) {
|
|
|
52108
52650
|
...job.waitMs === void 0 ? {} : { waitMs: job.waitMs }
|
|
52109
52651
|
};
|
|
52110
52652
|
case "get_command":
|
|
52653
|
+
case "read_command_output":
|
|
52111
52654
|
case "cancel_command":
|
|
52112
52655
|
return job;
|
|
52113
52656
|
}
|
|
@@ -52541,6 +53084,16 @@ function summarizeJob(job) {
|
|
|
52541
53084
|
`${job.edits.length} ${job.edits.length === 1 ? "edit" : "edits"}`,
|
|
52542
53085
|
...job.expectedSha256 ? ["guarded"] : []
|
|
52543
53086
|
]);
|
|
53087
|
+
case "make_directory":
|
|
53088
|
+
return pathSummary(job.path, job.recursive ? ["recursive"] : []);
|
|
53089
|
+
case "delete_path":
|
|
53090
|
+
return pathSummary(job.path, job.recursive ? ["recursive"] : []);
|
|
53091
|
+
case "move_path":
|
|
53092
|
+
return {
|
|
53093
|
+
target: `${quoteActivityInput(job.source)} \u2192 ${quoteActivityInput(job.destination)}`,
|
|
53094
|
+
details: [],
|
|
53095
|
+
truncation: "middle"
|
|
53096
|
+
};
|
|
52544
53097
|
case "run_command":
|
|
52545
53098
|
return {
|
|
52546
53099
|
target: job.argv ? `argv [${job.argv.map(quoteActivityInput).join(", ")}]` : `shell ${quoteActivityInput(job.shellCommand ?? "")}`,
|
|
@@ -52560,6 +53113,15 @@ function summarizeJob(job) {
|
|
|
52560
53113
|
],
|
|
52561
53114
|
truncation: "middle"
|
|
52562
53115
|
};
|
|
53116
|
+
case "read_command_output":
|
|
53117
|
+
return {
|
|
53118
|
+
target: `command ${job.commandId} ${job.stream}`,
|
|
53119
|
+
details: [
|
|
53120
|
+
...job.offset === void 0 ? [] : [`offset ${job.offset}`],
|
|
53121
|
+
...job.maxBytes === void 0 ? [] : [`max ${job.maxBytes} bytes`]
|
|
53122
|
+
],
|
|
53123
|
+
truncation: "middle"
|
|
53124
|
+
};
|
|
52563
53125
|
case "cancel_command":
|
|
52564
53126
|
return {
|
|
52565
53127
|
target: `command ${job.commandId}`,
|
|
@@ -53504,7 +54066,7 @@ import path8 from "node:path";
|
|
|
53504
54066
|
|
|
53505
54067
|
// src/update-lock.ts
|
|
53506
54068
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
53507
|
-
import { mkdir as
|
|
54069
|
+
import { mkdir as mkdir4, readdir, rm as rm3, writeFile as writeFile4 } from "node:fs/promises";
|
|
53508
54070
|
import path7 from "node:path";
|
|
53509
54071
|
var UPDATE_SUFFIX = ".update";
|
|
53510
54072
|
var SESSION_SUFFIX = ".session";
|
|
@@ -53552,7 +54114,7 @@ async function activeSessionFiles(directory) {
|
|
|
53552
54114
|
return await activeLeaseFiles(directory, SESSION_SUFFIX);
|
|
53553
54115
|
}
|
|
53554
54116
|
async function createUpdateLock(directory) {
|
|
53555
|
-
await
|
|
54117
|
+
await mkdir4(directory, { recursive: true, mode: 448 });
|
|
53556
54118
|
const lockFile = path7.join(
|
|
53557
54119
|
directory,
|
|
53558
54120
|
`${process.pid}-${randomUUID4()}${UPDATE_SUFFIX}`
|
|
@@ -53587,7 +54149,7 @@ async function withUpdateLease(action, directory = updateRuntimeDirectory()) {
|
|
|
53587
54149
|
}
|
|
53588
54150
|
}
|
|
53589
54151
|
async function withWorkspaceLease(action, directory = updateRuntimeDirectory()) {
|
|
53590
|
-
await
|
|
54152
|
+
await mkdir4(directory, { recursive: true, mode: 448 });
|
|
53591
54153
|
if (await updateLockIsActive(directory)) {
|
|
53592
54154
|
throw new Error("Glossa is updating. Run this workspace again after the update finishes.");
|
|
53593
54155
|
}
|
|
@@ -53725,14 +54287,25 @@ async function fetchText(url2, fetchImpl, version2) {
|
|
|
53725
54287
|
if (!response.ok) throw new Error(`Glossa could not download ${url2} (HTTP ${response.status}).`);
|
|
53726
54288
|
return await response.text();
|
|
53727
54289
|
}
|
|
54290
|
+
function npmInstallInvocation(version2, platform2 = process.platform, commandShell = process.env.ComSpec ?? "cmd.exe") {
|
|
54291
|
+
const normalizedVersion = import_semver.default.valid(version2);
|
|
54292
|
+
if (!normalizedVersion) throw new Error(`Glossa version ${version2} is invalid.`);
|
|
54293
|
+
const packageSpec = `${PACKAGE_NAME}@${normalizedVersion}`;
|
|
54294
|
+
if (platform2 === "win32") {
|
|
54295
|
+
return {
|
|
54296
|
+
command: commandShell,
|
|
54297
|
+
args: ["/d", "/s", "/c", `npm.cmd install --global ${packageSpec}`]
|
|
54298
|
+
};
|
|
54299
|
+
}
|
|
54300
|
+
return {
|
|
54301
|
+
command: "npm",
|
|
54302
|
+
args: ["install", "--global", packageSpec]
|
|
54303
|
+
};
|
|
54304
|
+
}
|
|
53728
54305
|
async function runNpmInstall(version2) {
|
|
53729
|
-
const command
|
|
54306
|
+
const { command, args } = npmInstallInvocation(version2);
|
|
53730
54307
|
await new Promise((resolve, reject) => {
|
|
53731
|
-
const child = spawn3(
|
|
53732
|
-
command,
|
|
53733
|
-
["install", "--global", `${PACKAGE_NAME}@${version2}`],
|
|
53734
|
-
{ stdio: "inherit" }
|
|
53735
|
-
);
|
|
54308
|
+
const child = spawn3(command, args, { stdio: "inherit" });
|
|
53736
54309
|
child.once("error", reject);
|
|
53737
54310
|
child.once("close", (status) => {
|
|
53738
54311
|
if (status === 0) resolve();
|
|
@@ -53795,7 +54368,7 @@ async function installUpdate(info, distribution, options = {}) {
|
|
|
53795
54368
|
}
|
|
53796
54369
|
|
|
53797
54370
|
// src/update-state.ts
|
|
53798
|
-
import { chmod as chmod4, mkdir as
|
|
54371
|
+
import { chmod as chmod4, mkdir as mkdir5, readFile as readFile4, rename as rename3, rm as rm5, writeFile as writeFile6 } from "node:fs/promises";
|
|
53799
54372
|
import path9 from "node:path";
|
|
53800
54373
|
var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
53801
54374
|
function defaultUpdateChannel(version2) {
|
|
@@ -53838,7 +54411,7 @@ async function loadUpdateState(currentVersion, file2 = updateStateFile()) {
|
|
|
53838
54411
|
async function saveUpdateState(state, file2 = updateStateFile()) {
|
|
53839
54412
|
const directory = path9.dirname(file2);
|
|
53840
54413
|
const temporary = `${file2}.${process.pid}.tmp`;
|
|
53841
|
-
await
|
|
54414
|
+
await mkdir5(directory, { recursive: true, mode: 448 });
|
|
53842
54415
|
try {
|
|
53843
54416
|
await writeFile6(temporary, `${JSON.stringify(state, null, 2)}
|
|
53844
54417
|
`, {
|
|
@@ -53876,14 +54449,14 @@ function isUpdateCheckDue(lastCheckedAt, now = Date.now()) {
|
|
|
53876
54449
|
}
|
|
53877
54450
|
|
|
53878
54451
|
// src/usage-store.ts
|
|
53879
|
-
import { appendFile, chmod as chmod5, mkdir as
|
|
54452
|
+
import { appendFile, chmod as chmod5, mkdir as mkdir6, readFile as readFile5 } from "node:fs/promises";
|
|
53880
54453
|
import path10 from "node:path";
|
|
53881
54454
|
function usageFile() {
|
|
53882
54455
|
return path10.join(configDirectory(), "usage.jsonl");
|
|
53883
54456
|
}
|
|
53884
54457
|
async function recordUsageEvent(event, file2 = usageFile()) {
|
|
53885
54458
|
const directory = path10.dirname(file2);
|
|
53886
|
-
await
|
|
54459
|
+
await mkdir6(directory, { recursive: true, mode: 448 });
|
|
53887
54460
|
await appendFile(file2, `${JSON.stringify(event)}
|
|
53888
54461
|
`, {
|
|
53889
54462
|
encoding: "utf8",
|
|
@@ -53927,7 +54500,7 @@ async function selectExposureRoot(explicitPath, cwd2 = process.cwd()) {
|
|
|
53927
54500
|
|
|
53928
54501
|
// src/worker/workspace-lease.ts
|
|
53929
54502
|
import { createHash as createHash3 } from "node:crypto";
|
|
53930
|
-
import { lstat as lstat3, mkdir as
|
|
54503
|
+
import { lstat as lstat3, mkdir as mkdir7, rm as rm6, stat as stat3 } from "node:fs/promises";
|
|
53931
54504
|
import net from "node:net";
|
|
53932
54505
|
import path12 from "node:path";
|
|
53933
54506
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
@@ -53958,7 +54531,7 @@ function defaultLeaseDirectory() {
|
|
|
53958
54531
|
return path12.join(accountHomeDirectory(), ".glossa-workspace-leases");
|
|
53959
54532
|
}
|
|
53960
54533
|
async function ensureLeaseDirectory(directory) {
|
|
53961
|
-
await
|
|
54534
|
+
await mkdir7(directory, { recursive: true, mode: 448 });
|
|
53962
54535
|
const directoryStat = await lstat3(directory);
|
|
53963
54536
|
if (directoryStat.isSymbolicLink() || !directoryStat.isDirectory()) {
|
|
53964
54537
|
throw new Error("The Glossa workspace lease location is not a private directory.");
|
|
@@ -53982,7 +54555,7 @@ async function acquireGuard(directory, identity) {
|
|
|
53982
54555
|
const deadline = Date.now() + GUARD_TIMEOUT_MS;
|
|
53983
54556
|
while (true) {
|
|
53984
54557
|
try {
|
|
53985
|
-
await
|
|
54558
|
+
await mkdir7(guard, { mode: 448 });
|
|
53986
54559
|
return async () => await rm6(guard, { recursive: true, force: true });
|
|
53987
54560
|
} catch (error46) {
|
|
53988
54561
|
if (error46.code !== "EEXIST") throw error46;
|
|
@@ -54077,10 +54650,10 @@ async function acquireWorkspaceLease(root, options = {}) {
|
|
|
54077
54650
|
};
|
|
54078
54651
|
} catch (error46) {
|
|
54079
54652
|
await closeServer(server);
|
|
54080
|
-
if (error46.code !== "EADDRINUSE") throw error46;
|
|
54081
54653
|
if (await endpointIsActive(endpoint3)) {
|
|
54082
54654
|
throw new WorkspaceAlreadyActiveError();
|
|
54083
54655
|
}
|
|
54656
|
+
if (error46.code !== "EADDRINUSE") throw error46;
|
|
54084
54657
|
if (process.platform !== "win32") await rm6(endpoint3, { force: true });
|
|
54085
54658
|
}
|
|
54086
54659
|
}
|
|
@@ -54091,7 +54664,7 @@ async function acquireWorkspaceLease(root, options = {}) {
|
|
|
54091
54664
|
}
|
|
54092
54665
|
|
|
54093
54666
|
// src/main.ts
|
|
54094
|
-
var VERSION = "0.
|
|
54667
|
+
var VERSION = "0.2.0-beta.2";
|
|
54095
54668
|
var DISTRIBUTION = "npm";
|
|
54096
54669
|
var HELP = `Glossa ${VERSION}
|
|
54097
54670
|
|