lody 0.66.0 → 0.67.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/baseline-config-BPa_f3S0.js +283 -0
- package/dist/chunks/baseline-config-CcYq1mUH.js +245 -0
- package/dist/chunks/directory-handle-Bx0hMRRw.js +1122 -0
- package/dist/chunks/index-DO0iziHq.js +27175 -0
- package/dist/chunks/{index-v46gaGAl.js → index-VoI6Ds2-.js} +2 -2
- package/dist/chunks/{review-viewer-DzrxfC5z.js → review-viewer-CXhcZgO2.js} +1 -1
- package/dist/chunks/{directory-handle-CaB_XCLI.js → schemas-muln0r74.js} +26 -991
- package/dist/claude-acp.js +66229 -0
- package/dist/codex-acp.js +7 -0
- package/dist/file-index-scan-worker.js +2 -1
- package/dist/index.js +75251 -84329
- package/package.json +19 -16
|
@@ -0,0 +1,1122 @@
|
|
|
1
|
+
import { o as object, s as string, l as literal, b as boolean, d as discriminatedUnion, n as number, _ as _enum, a as array, u as union, r as record, E as tuple, c as unknown } from "./schemas-muln0r74.js";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import path__default from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { a as countTextLines } from "./diff-line-counts-BLxwWP6r.js";
|
|
7
|
+
_enum(["host", "write", "read"]);
|
|
8
|
+
_enum([
|
|
9
|
+
"not-started",
|
|
10
|
+
"starting",
|
|
11
|
+
"online",
|
|
12
|
+
"offline",
|
|
13
|
+
"stopping",
|
|
14
|
+
"unsupported"
|
|
15
|
+
]);
|
|
16
|
+
_enum([
|
|
17
|
+
"live-collaborative",
|
|
18
|
+
"live-readonly",
|
|
19
|
+
"historical-turn",
|
|
20
|
+
"host-offline",
|
|
21
|
+
"degraded"
|
|
22
|
+
]);
|
|
23
|
+
_enum(["code-collab", "none"]);
|
|
24
|
+
_enum([
|
|
25
|
+
"focused",
|
|
26
|
+
"background",
|
|
27
|
+
"idle",
|
|
28
|
+
"disconnected"
|
|
29
|
+
]);
|
|
30
|
+
const CodeCollabFileKindSchema = _enum([
|
|
31
|
+
"text",
|
|
32
|
+
"binary",
|
|
33
|
+
"large",
|
|
34
|
+
"symlink",
|
|
35
|
+
"special",
|
|
36
|
+
"deleted"
|
|
37
|
+
]);
|
|
38
|
+
const CodeCollabSpecialKindSchema = _enum([
|
|
39
|
+
"fifo",
|
|
40
|
+
"socket",
|
|
41
|
+
"block-device",
|
|
42
|
+
"char-device",
|
|
43
|
+
"unknown"
|
|
44
|
+
]);
|
|
45
|
+
const CodeCollabTextEolSchema = _enum(["lf", "crlf", "mixed", "unknown"]);
|
|
46
|
+
const CodeCollabUnavailableReasonSchema = _enum([
|
|
47
|
+
"permission-denied",
|
|
48
|
+
"locked",
|
|
49
|
+
"transient-io",
|
|
50
|
+
"text-too-large",
|
|
51
|
+
"line-too-long",
|
|
52
|
+
"unsupported-encoding",
|
|
53
|
+
"unsupported-special",
|
|
54
|
+
"blob-too-large",
|
|
55
|
+
"path-collision",
|
|
56
|
+
"unknown"
|
|
57
|
+
]);
|
|
58
|
+
_enum([
|
|
59
|
+
"deleted",
|
|
60
|
+
"metadata-only",
|
|
61
|
+
"missing-text-frontiers",
|
|
62
|
+
"missing-blob-digest",
|
|
63
|
+
"blob-expired",
|
|
64
|
+
...CodeCollabUnavailableReasonSchema.options
|
|
65
|
+
]);
|
|
66
|
+
const CodeCollabDomainErrorCodeSchema = _enum([
|
|
67
|
+
"permission-denied",
|
|
68
|
+
"space-not-found",
|
|
69
|
+
"secret-unavailable",
|
|
70
|
+
"streams-unavailable",
|
|
71
|
+
"blob-unavailable",
|
|
72
|
+
"workspace-path-rejected",
|
|
73
|
+
"file-ignored",
|
|
74
|
+
"file-too-large",
|
|
75
|
+
"binary-unsupported",
|
|
76
|
+
"hydration-failed",
|
|
77
|
+
"save-conflict",
|
|
78
|
+
"host-offline",
|
|
79
|
+
"lsp-unavailable",
|
|
80
|
+
"external-file-unavailable",
|
|
81
|
+
"unexpected"
|
|
82
|
+
]);
|
|
83
|
+
object({
|
|
84
|
+
code: CodeCollabDomainErrorCodeSchema,
|
|
85
|
+
message: string().min(1),
|
|
86
|
+
redacted: boolean().optional()
|
|
87
|
+
}).strict();
|
|
88
|
+
const CODE_COLLAB_V2_TEXT_LIMITS = {
|
|
89
|
+
plainTextBytes: 64 * 1024,
|
|
90
|
+
maxCompressedBytes: 1024 * 1024,
|
|
91
|
+
maxRawTextBytes: 10 * 1024 * 1024
|
|
92
|
+
};
|
|
93
|
+
const CODE_COLLAB_V2_ALL_CHANGES_DIFF_LIMITS = {
|
|
94
|
+
perFileMaxCompressedBytes: 256 * 1024,
|
|
95
|
+
responseBudgetCompressedBytes: 2 * 1024 * 1024,
|
|
96
|
+
perFileMaxRawBytes: 1 * 1024 * 1024
|
|
97
|
+
};
|
|
98
|
+
const CodeCollabV2FileDigestSchema = string().regex(/^sha256:[0-9a-f]{64}$/u);
|
|
99
|
+
const CodeCollabV2TextFormatSchema = object({
|
|
100
|
+
encoding: literal("utf8"),
|
|
101
|
+
bom: boolean().optional(),
|
|
102
|
+
eol: _enum(["lf", "crlf", "mixed", "unknown"]).optional()
|
|
103
|
+
}).strict();
|
|
104
|
+
const CodeCollabV2EncodedTextPayloadSchema = discriminatedUnion("encoding", [
|
|
105
|
+
object({
|
|
106
|
+
encoding: literal("plain"),
|
|
107
|
+
text: string(),
|
|
108
|
+
rawBytes: number().int().nonnegative()
|
|
109
|
+
}).strict(),
|
|
110
|
+
object({
|
|
111
|
+
encoding: literal("gzip-base64"),
|
|
112
|
+
data: string().min(1),
|
|
113
|
+
rawBytes: number().int().nonnegative(),
|
|
114
|
+
compressedBytes: number().int().nonnegative()
|
|
115
|
+
}).strict()
|
|
116
|
+
]);
|
|
117
|
+
const CodeCollabV2ErrorCodeSchema = _enum([
|
|
118
|
+
"invalid_path",
|
|
119
|
+
"session_not_found",
|
|
120
|
+
"workspace_root_unavailable",
|
|
121
|
+
"machine_offline",
|
|
122
|
+
"file_not_found",
|
|
123
|
+
"path_conflict",
|
|
124
|
+
"unsupported_binary",
|
|
125
|
+
"unsupported_skipped",
|
|
126
|
+
"too_large",
|
|
127
|
+
"decode_error",
|
|
128
|
+
"digest_mismatch",
|
|
129
|
+
"permission_denied",
|
|
130
|
+
"transient_io",
|
|
131
|
+
"lsp_not_wired"
|
|
132
|
+
]);
|
|
133
|
+
const CodeCollabV2ErrorSchema = object({
|
|
134
|
+
status: literal("error"),
|
|
135
|
+
code: CodeCollabV2ErrorCodeSchema,
|
|
136
|
+
message: string().min(1).optional(),
|
|
137
|
+
path: string().min(1).optional(),
|
|
138
|
+
retryable: boolean().optional()
|
|
139
|
+
}).strict();
|
|
140
|
+
const CodeCollabV2OpenTextRequestSchema = object({
|
|
141
|
+
sessionId: string().trim().min(1),
|
|
142
|
+
path: string().min(1)
|
|
143
|
+
}).strict();
|
|
144
|
+
const CodeCollabV2OpenTextOkSchema = object({
|
|
145
|
+
status: literal("ok"),
|
|
146
|
+
path: string().min(1),
|
|
147
|
+
digest: CodeCollabV2FileDigestSchema,
|
|
148
|
+
text: CodeCollabV2EncodedTextPayloadSchema,
|
|
149
|
+
format: CodeCollabV2TextFormatSchema.optional(),
|
|
150
|
+
readonly: boolean().optional()
|
|
151
|
+
}).strict();
|
|
152
|
+
const CodeCollabV2RefreshTextRequestSchema = object({
|
|
153
|
+
sessionId: string().trim().min(1),
|
|
154
|
+
path: string().min(1),
|
|
155
|
+
digest: CodeCollabV2FileDigestSchema
|
|
156
|
+
}).strict();
|
|
157
|
+
const CodeCollabV2RefreshTextResponseSchema = discriminatedUnion("status", [
|
|
158
|
+
object({
|
|
159
|
+
status: literal("up_to_date"),
|
|
160
|
+
path: string().min(1),
|
|
161
|
+
digest: CodeCollabV2FileDigestSchema
|
|
162
|
+
}).strict(),
|
|
163
|
+
object({
|
|
164
|
+
status: literal("updated"),
|
|
165
|
+
path: string().min(1),
|
|
166
|
+
digest: CodeCollabV2FileDigestSchema,
|
|
167
|
+
text: CodeCollabV2EncodedTextPayloadSchema,
|
|
168
|
+
format: CodeCollabV2TextFormatSchema.optional(),
|
|
169
|
+
readonly: boolean().optional()
|
|
170
|
+
}).strict()
|
|
171
|
+
]);
|
|
172
|
+
const CodeCollabV2SaveTextRequestSchema = object({
|
|
173
|
+
sessionId: string().trim().min(1),
|
|
174
|
+
requestedByUserId: string().trim().min(1),
|
|
175
|
+
path: string().min(1),
|
|
176
|
+
baseDigest: CodeCollabV2FileDigestSchema,
|
|
177
|
+
text: CodeCollabV2EncodedTextPayloadSchema,
|
|
178
|
+
format: CodeCollabV2TextFormatSchema.optional()
|
|
179
|
+
}).strict();
|
|
180
|
+
const CodeCollabV2SaveTextOkSchema = object({
|
|
181
|
+
status: literal("ok"),
|
|
182
|
+
path: string().min(1),
|
|
183
|
+
digest: CodeCollabV2FileDigestSchema,
|
|
184
|
+
rawBytes: number().int().nonnegative()
|
|
185
|
+
}).strict();
|
|
186
|
+
const CodeCollabV2SaveTextConflictSchema = object({
|
|
187
|
+
status: literal("conflict"),
|
|
188
|
+
reason: _enum(["digest_mismatch", "path_changed", "file_deleted", "path_conflict"]),
|
|
189
|
+
path: string().min(1),
|
|
190
|
+
baseDigest: CodeCollabV2FileDigestSchema,
|
|
191
|
+
diskDigest: CodeCollabV2FileDigestSchema.optional(),
|
|
192
|
+
diskText: CodeCollabV2EncodedTextPayloadSchema.optional()
|
|
193
|
+
}).strict();
|
|
194
|
+
const CodeCollabV2SaveTextResponseSchema = discriminatedUnion("status", [
|
|
195
|
+
CodeCollabV2SaveTextOkSchema,
|
|
196
|
+
CodeCollabV2SaveTextConflictSchema
|
|
197
|
+
]);
|
|
198
|
+
const CodeCollabV2OpenCurrentDiffRequestSchema = object({
|
|
199
|
+
sessionId: string().trim().min(1),
|
|
200
|
+
path: string().min(1)
|
|
201
|
+
}).strict();
|
|
202
|
+
const CodeCollabV2DiffSnapshotSchema = discriminatedUnion("kind", [
|
|
203
|
+
object({
|
|
204
|
+
kind: literal("text"),
|
|
205
|
+
text: CodeCollabV2EncodedTextPayloadSchema,
|
|
206
|
+
format: CodeCollabV2TextFormatSchema.optional()
|
|
207
|
+
}).strict(),
|
|
208
|
+
object({ kind: literal("missing") }).strict(),
|
|
209
|
+
object({ kind: literal("binary") }).strict(),
|
|
210
|
+
object({ kind: literal("too_large") }).strict()
|
|
211
|
+
]);
|
|
212
|
+
const CodeCollabV2OpenCurrentDiffResponseSchema = discriminatedUnion("status", [
|
|
213
|
+
object({
|
|
214
|
+
status: literal("ok"),
|
|
215
|
+
path: string().min(1),
|
|
216
|
+
oldSnapshot: CodeCollabV2DiffSnapshotSchema,
|
|
217
|
+
newSnapshot: CodeCollabV2DiffSnapshotSchema,
|
|
218
|
+
add: number().int().nonnegative().optional(),
|
|
219
|
+
del: number().int().nonnegative().optional()
|
|
220
|
+
}).strict(),
|
|
221
|
+
object({
|
|
222
|
+
status: literal("unavailable"),
|
|
223
|
+
path: string().min(1),
|
|
224
|
+
reason: _enum(["base_unavailable", "not_changed", "transient_io", "unsupported_binary"]),
|
|
225
|
+
message: string().min(1).optional()
|
|
226
|
+
}).strict()
|
|
227
|
+
]);
|
|
228
|
+
const CodeCollabV2OpenAllChangesDiffRequestSchema = object({
|
|
229
|
+
sessionId: string().trim().min(1),
|
|
230
|
+
// Inline this file first (the file the user clicked) regardless of size ordering.
|
|
231
|
+
focusPath: string().min(1).optional()
|
|
232
|
+
}).strict();
|
|
233
|
+
const CodeCollabV2AllChangesDiffEntrySchema = discriminatedUnion("status", [
|
|
234
|
+
object({
|
|
235
|
+
status: literal("ok"),
|
|
236
|
+
path: string().min(1),
|
|
237
|
+
oldSnapshot: CodeCollabV2DiffSnapshotSchema,
|
|
238
|
+
newSnapshot: CodeCollabV2DiffSnapshotSchema,
|
|
239
|
+
add: number().int().nonnegative().optional(),
|
|
240
|
+
del: number().int().nonnegative().optional()
|
|
241
|
+
}).strict(),
|
|
242
|
+
// Content exists but was not inlined (over per-file cap or total budget) — the client
|
|
243
|
+
// fetches it on demand via `open-current-diff`. Line stats still ship for the list.
|
|
244
|
+
object({
|
|
245
|
+
status: literal("deferred"),
|
|
246
|
+
path: string().min(1),
|
|
247
|
+
add: number().int().nonnegative().optional(),
|
|
248
|
+
del: number().int().nonnegative().optional()
|
|
249
|
+
}).strict(),
|
|
250
|
+
// Terminal: file is in the change list but its base snapshot equals disk (no real diff
|
|
251
|
+
// vs base). Binary / too-large files are NOT unavailable — they ship as `ok` with a
|
|
252
|
+
// `binary` / `too_large` snapshot, matching single-file `open-current-diff`.
|
|
253
|
+
object({
|
|
254
|
+
status: literal("unavailable"),
|
|
255
|
+
path: string().min(1),
|
|
256
|
+
reason: _enum(["not_changed"]),
|
|
257
|
+
add: number().int().nonnegative().optional(),
|
|
258
|
+
del: number().int().nonnegative().optional()
|
|
259
|
+
}).strict()
|
|
260
|
+
]);
|
|
261
|
+
const CodeCollabV2OpenAllChangesDiffResponseSchema = discriminatedUnion("status", [
|
|
262
|
+
object({
|
|
263
|
+
status: literal("ok"),
|
|
264
|
+
// Resolved diff base for diagnostics/consistency: merge-base sha (git) or a source
|
|
265
|
+
// marker like `diff-store` (non-git).
|
|
266
|
+
base: string().min(1),
|
|
267
|
+
entries: array(CodeCollabV2AllChangesDiffEntrySchema),
|
|
268
|
+
// True when any entry is `deferred` (UI can hint "some files load on demand").
|
|
269
|
+
truncated: boolean()
|
|
270
|
+
}).strict(),
|
|
271
|
+
object({
|
|
272
|
+
status: literal("unavailable"),
|
|
273
|
+
reason: _enum(["base_unavailable", "transient_io"]),
|
|
274
|
+
message: string().min(1).optional()
|
|
275
|
+
}).strict()
|
|
276
|
+
]);
|
|
277
|
+
const CodeCollabV2OpenTurnDiffRequestSchema = object({
|
|
278
|
+
sessionId: string().trim().min(1),
|
|
279
|
+
turnId: string().trim().min(1),
|
|
280
|
+
path: string().min(1)
|
|
281
|
+
}).strict();
|
|
282
|
+
const CodeCollabV2OpenTurnDiffResponseSchema = discriminatedUnion("status", [
|
|
283
|
+
object({
|
|
284
|
+
status: literal("ok"),
|
|
285
|
+
path: string().min(1),
|
|
286
|
+
turnId: string().trim().min(1),
|
|
287
|
+
oldSnapshot: CodeCollabV2DiffSnapshotSchema,
|
|
288
|
+
newSnapshot: CodeCollabV2DiffSnapshotSchema,
|
|
289
|
+
add: number().int().nonnegative().optional(),
|
|
290
|
+
del: number().int().nonnegative().optional()
|
|
291
|
+
}).strict(),
|
|
292
|
+
object({
|
|
293
|
+
status: literal("unavailable"),
|
|
294
|
+
path: string().min(1),
|
|
295
|
+
turnId: string().trim().min(1),
|
|
296
|
+
reason: _enum(["turn_unavailable", "not_changed", "transient_io"]),
|
|
297
|
+
message: string().min(1).optional()
|
|
298
|
+
}).strict()
|
|
299
|
+
]);
|
|
300
|
+
const CodeCollabV2InitDirectoryRequestSchema = object({
|
|
301
|
+
sessionId: string().trim().min(1),
|
|
302
|
+
path: string().min(1)
|
|
303
|
+
}).strict();
|
|
304
|
+
const CodeCollabV2InitDirectoryOkSchema = object({
|
|
305
|
+
status: literal("ok"),
|
|
306
|
+
path: string().min(1),
|
|
307
|
+
publishedEntries: number().int().nonnegative().optional()
|
|
308
|
+
}).strict();
|
|
309
|
+
const CodeCollabV2LspUnsupportedSchema = object({
|
|
310
|
+
status: literal("unsupported"),
|
|
311
|
+
code: literal("lsp_not_wired")
|
|
312
|
+
}).strict();
|
|
313
|
+
const CodeCollabV2FileTreeValueSchema = union([
|
|
314
|
+
literal(true),
|
|
315
|
+
object({ kind: literal("lazy") }).strict(),
|
|
316
|
+
object({ kind: literal("binary") }).strict(),
|
|
317
|
+
object({
|
|
318
|
+
kind: literal("skipped"),
|
|
319
|
+
reason: string().min(1)
|
|
320
|
+
}).strict(),
|
|
321
|
+
object({
|
|
322
|
+
kind: literal("too_large"),
|
|
323
|
+
rawBytes: number().int().nonnegative().optional(),
|
|
324
|
+
compressedBytes: number().int().nonnegative().optional()
|
|
325
|
+
}).strict()
|
|
326
|
+
]);
|
|
327
|
+
record(
|
|
328
|
+
string().min(1),
|
|
329
|
+
CodeCollabV2FileTreeValueSchema
|
|
330
|
+
);
|
|
331
|
+
const CodeCollabV2AllChangesValueSchema = union([
|
|
332
|
+
literal(true),
|
|
333
|
+
object({
|
|
334
|
+
diff: tuple([number().int().nonnegative(), number().int().nonnegative()]).optional(),
|
|
335
|
+
del: literal(true).optional()
|
|
336
|
+
}).strict()
|
|
337
|
+
]);
|
|
338
|
+
record(
|
|
339
|
+
string().min(1),
|
|
340
|
+
CodeCollabV2AllChangesValueSchema
|
|
341
|
+
);
|
|
342
|
+
const CodeCollabV2FileIndexValueSchema = union([
|
|
343
|
+
literal(true),
|
|
344
|
+
object({
|
|
345
|
+
kind: literal("file"),
|
|
346
|
+
change: CodeCollabV2AllChangesValueSchema.optional()
|
|
347
|
+
}).strict(),
|
|
348
|
+
object({
|
|
349
|
+
kind: literal("text"),
|
|
350
|
+
change: CodeCollabV2AllChangesValueSchema.optional()
|
|
351
|
+
}).strict(),
|
|
352
|
+
object({
|
|
353
|
+
kind: literal("binary"),
|
|
354
|
+
change: CodeCollabV2AllChangesValueSchema.optional()
|
|
355
|
+
}).strict(),
|
|
356
|
+
object({
|
|
357
|
+
kind: literal("too_large"),
|
|
358
|
+
rawBytes: number().int().nonnegative().optional(),
|
|
359
|
+
compressedBytes: number().int().nonnegative().optional(),
|
|
360
|
+
change: CodeCollabV2AllChangesValueSchema.optional()
|
|
361
|
+
}).strict(),
|
|
362
|
+
object({
|
|
363
|
+
kind: literal("deleted"),
|
|
364
|
+
change: CodeCollabV2AllChangesValueSchema
|
|
365
|
+
}).strict(),
|
|
366
|
+
object({ kind: literal("lazy") }).strict(),
|
|
367
|
+
object({
|
|
368
|
+
kind: literal("skipped"),
|
|
369
|
+
reason: string().min(1)
|
|
370
|
+
}).strict()
|
|
371
|
+
]);
|
|
372
|
+
record(
|
|
373
|
+
string().min(1),
|
|
374
|
+
CodeCollabV2FileIndexValueSchema
|
|
375
|
+
);
|
|
376
|
+
const codeCollabFileIndexKeyForWorkspacePath = (workspacePath) => [workspacePath];
|
|
377
|
+
const isCodeCollabRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
378
|
+
const isCodeCollabMissing = (value) => value === void 0 || value === null;
|
|
379
|
+
const isNonNegativeInteger = (value) => typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
380
|
+
const hasOnlyCodeCollabKeys = (value, allowedKeys) => Object.keys(value).every((key) => allowedKeys.includes(key));
|
|
381
|
+
const normalizeCodeCollabAllChangesValue = (value) => {
|
|
382
|
+
if (value === true) {
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
if (!isCodeCollabRecord(value) || !hasOnlyCodeCollabKeys(value, ["diff", "del"])) {
|
|
386
|
+
return void 0;
|
|
387
|
+
}
|
|
388
|
+
const change = {};
|
|
389
|
+
if (!isCodeCollabMissing(value.diff)) {
|
|
390
|
+
if (!Array.isArray(value.diff) || value.diff.length !== 2 || !isNonNegativeInteger(value.diff[0]) || !isNonNegativeInteger(value.diff[1])) {
|
|
391
|
+
return void 0;
|
|
392
|
+
}
|
|
393
|
+
change.diff = [value.diff[0], value.diff[1]];
|
|
394
|
+
}
|
|
395
|
+
if (!isCodeCollabMissing(value.del)) {
|
|
396
|
+
if (value.del !== true) {
|
|
397
|
+
return void 0;
|
|
398
|
+
}
|
|
399
|
+
change.del = true;
|
|
400
|
+
}
|
|
401
|
+
return change;
|
|
402
|
+
};
|
|
403
|
+
const normalizeOptionalCodeCollabAllChangesValue = (value) => {
|
|
404
|
+
if (isCodeCollabMissing(value)) {
|
|
405
|
+
return { ok: true };
|
|
406
|
+
}
|
|
407
|
+
const normalized = normalizeCodeCollabAllChangesValue(value);
|
|
408
|
+
return normalized === void 0 ? { ok: false } : { ok: true, value: normalized };
|
|
409
|
+
};
|
|
410
|
+
const normalizeCodeCollabFileIndexValue = (value) => {
|
|
411
|
+
if (value === true) {
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
if (!isCodeCollabRecord(value)) {
|
|
415
|
+
return void 0;
|
|
416
|
+
}
|
|
417
|
+
switch (value.kind) {
|
|
418
|
+
case "file":
|
|
419
|
+
case "text":
|
|
420
|
+
case "binary": {
|
|
421
|
+
if (!hasOnlyCodeCollabKeys(value, ["kind", "change"])) {
|
|
422
|
+
return void 0;
|
|
423
|
+
}
|
|
424
|
+
const change = normalizeOptionalCodeCollabAllChangesValue(value.change);
|
|
425
|
+
if (!change.ok) {
|
|
426
|
+
return void 0;
|
|
427
|
+
}
|
|
428
|
+
return change.value === void 0 ? { kind: value.kind } : { kind: value.kind, change: change.value };
|
|
429
|
+
}
|
|
430
|
+
case "too_large": {
|
|
431
|
+
if (!hasOnlyCodeCollabKeys(value, ["kind", "rawBytes", "compressedBytes", "change"])) {
|
|
432
|
+
return void 0;
|
|
433
|
+
}
|
|
434
|
+
const change = normalizeOptionalCodeCollabAllChangesValue(value.change);
|
|
435
|
+
if (!change.ok) {
|
|
436
|
+
return void 0;
|
|
437
|
+
}
|
|
438
|
+
const fileIndexValue = {
|
|
439
|
+
kind: "too_large"
|
|
440
|
+
};
|
|
441
|
+
if (!isCodeCollabMissing(value.rawBytes)) {
|
|
442
|
+
if (!isNonNegativeInteger(value.rawBytes)) {
|
|
443
|
+
return void 0;
|
|
444
|
+
}
|
|
445
|
+
fileIndexValue.rawBytes = value.rawBytes;
|
|
446
|
+
}
|
|
447
|
+
if (!isCodeCollabMissing(value.compressedBytes)) {
|
|
448
|
+
if (!isNonNegativeInteger(value.compressedBytes)) {
|
|
449
|
+
return void 0;
|
|
450
|
+
}
|
|
451
|
+
fileIndexValue.compressedBytes = value.compressedBytes;
|
|
452
|
+
}
|
|
453
|
+
if (change.value !== void 0) {
|
|
454
|
+
fileIndexValue.change = change.value;
|
|
455
|
+
}
|
|
456
|
+
return fileIndexValue;
|
|
457
|
+
}
|
|
458
|
+
case "deleted": {
|
|
459
|
+
if (!hasOnlyCodeCollabKeys(value, ["kind", "change"]) || isCodeCollabMissing(value.change)) {
|
|
460
|
+
return void 0;
|
|
461
|
+
}
|
|
462
|
+
const change = normalizeCodeCollabAllChangesValue(value.change);
|
|
463
|
+
return change === void 0 ? void 0 : { kind: "deleted", change };
|
|
464
|
+
}
|
|
465
|
+
case "lazy":
|
|
466
|
+
return hasOnlyCodeCollabKeys(value, ["kind"]) ? { kind: "lazy" } : void 0;
|
|
467
|
+
case "skipped":
|
|
468
|
+
return hasOnlyCodeCollabKeys(value, ["kind", "reason"]) && typeof value.reason === "string" && value.reason.length > 0 ? { kind: "skipped", reason: value.reason } : void 0;
|
|
469
|
+
default:
|
|
470
|
+
return void 0;
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
function readCodeCollabFileIndexFromFlock(flock) {
|
|
474
|
+
const fileIndex = {};
|
|
475
|
+
for (const row of flock.scan()) {
|
|
476
|
+
if (row.value === void 0) {
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
const [workspacePath, ...extra] = row.key;
|
|
480
|
+
if (typeof workspacePath !== "string" || workspacePath.length === 0 || extra.length > 0) {
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
const parsed = normalizeCodeCollabFileIndexValue(row.value);
|
|
484
|
+
if (!parsed) {
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
fileIndex[workspacePath] = parsed;
|
|
488
|
+
}
|
|
489
|
+
return fileIndex;
|
|
490
|
+
}
|
|
491
|
+
function writeCodeCollabFileIndexToFlock(flock, next, nowMs) {
|
|
492
|
+
const previous = readCodeCollabFileIndexFromFlock(flock);
|
|
493
|
+
let changed = false;
|
|
494
|
+
for (const workspacePath of Object.keys(previous)) {
|
|
495
|
+
if (Object.prototype.hasOwnProperty.call(next, workspacePath)) {
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
flock.delete(codeCollabFileIndexKeyForWorkspacePath(workspacePath), nowMs);
|
|
499
|
+
changed = true;
|
|
500
|
+
}
|
|
501
|
+
for (const [workspacePath, value] of Object.entries(next)) {
|
|
502
|
+
const normalized = normalizeCodeCollabFileIndexValue(value);
|
|
503
|
+
if (!normalized || codeCollabFileIndexValuesEqual(previous[workspacePath], normalized)) {
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
flock.set(codeCollabFileIndexKeyForWorkspacePath(workspacePath), normalized, nowMs);
|
|
507
|
+
changed = true;
|
|
508
|
+
}
|
|
509
|
+
if (changed) {
|
|
510
|
+
flock.commit();
|
|
511
|
+
}
|
|
512
|
+
return changed;
|
|
513
|
+
}
|
|
514
|
+
function codeCollabAllChangesValuesEqual(left, right) {
|
|
515
|
+
if (left === right) return true;
|
|
516
|
+
if (left === void 0 || right === void 0) return false;
|
|
517
|
+
if (left === true || right === true) return false;
|
|
518
|
+
const leftDiff = left.diff;
|
|
519
|
+
const rightDiff = right.diff;
|
|
520
|
+
return left.del === right.del && (leftDiff === void 0 && rightDiff === void 0 || leftDiff !== void 0 && rightDiff !== void 0 && leftDiff[0] === rightDiff[0] && leftDiff[1] === rightDiff[1]);
|
|
521
|
+
}
|
|
522
|
+
function codeCollabFileIndexValuesEqual(left, right) {
|
|
523
|
+
if (left === right) return true;
|
|
524
|
+
if (left === void 0 || right === void 0) return false;
|
|
525
|
+
if (left === true || right === true) return false;
|
|
526
|
+
switch (left.kind) {
|
|
527
|
+
case "file":
|
|
528
|
+
return right.kind === "file" && codeCollabAllChangesValuesEqual(left.change, right.change);
|
|
529
|
+
case "text":
|
|
530
|
+
return right.kind === "text" && codeCollabAllChangesValuesEqual(left.change, right.change);
|
|
531
|
+
case "binary":
|
|
532
|
+
return right.kind === "binary" && codeCollabAllChangesValuesEqual(left.change, right.change);
|
|
533
|
+
case "too_large":
|
|
534
|
+
return right.kind === "too_large" && left.rawBytes === right.rawBytes && left.compressedBytes === right.compressedBytes && codeCollabAllChangesValuesEqual(left.change, right.change);
|
|
535
|
+
case "deleted":
|
|
536
|
+
return right.kind === "deleted" && codeCollabAllChangesValuesEqual(left.change, right.change);
|
|
537
|
+
case "lazy":
|
|
538
|
+
return right.kind === "lazy";
|
|
539
|
+
case "skipped":
|
|
540
|
+
return right.kind === "skipped" && left.reason === right.reason;
|
|
541
|
+
}
|
|
542
|
+
return assertNever(left);
|
|
543
|
+
}
|
|
544
|
+
function codeCollabFileIndexStatesEqual(left, right) {
|
|
545
|
+
const leftPaths = Object.keys(left);
|
|
546
|
+
const rightPaths = Object.keys(right);
|
|
547
|
+
if (leftPaths.length !== rightPaths.length) return false;
|
|
548
|
+
for (const workspacePath of leftPaths) {
|
|
549
|
+
if (!Object.prototype.hasOwnProperty.call(right, workspacePath)) return false;
|
|
550
|
+
if (!codeCollabFileIndexValuesEqual(left[workspacePath], right[workspacePath])) return false;
|
|
551
|
+
}
|
|
552
|
+
return true;
|
|
553
|
+
}
|
|
554
|
+
function codeCollabFileTreeValueToFileIndexValue(value, change) {
|
|
555
|
+
if (value === true) {
|
|
556
|
+
return change === void 0 ? true : { kind: "file", change };
|
|
557
|
+
}
|
|
558
|
+
switch (value.kind) {
|
|
559
|
+
case "lazy":
|
|
560
|
+
return { kind: "lazy" };
|
|
561
|
+
case "binary":
|
|
562
|
+
case "too_large":
|
|
563
|
+
return change === void 0 ? true : { kind: "file", change };
|
|
564
|
+
case "skipped":
|
|
565
|
+
return { kind: "skipped", reason: value.reason };
|
|
566
|
+
}
|
|
567
|
+
return assertNever(value);
|
|
568
|
+
}
|
|
569
|
+
function buildCodeCollabFileIndexState(fileTree, allChanges) {
|
|
570
|
+
const fileIndex = {};
|
|
571
|
+
for (const [workspacePath, value] of Object.entries(fileTree)) {
|
|
572
|
+
fileIndex[workspacePath] = codeCollabFileTreeValueToFileIndexValue(
|
|
573
|
+
value,
|
|
574
|
+
allChanges[workspacePath]
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
for (const [workspacePath, change] of Object.entries(allChanges)) {
|
|
578
|
+
if (fileIndex[workspacePath] !== void 0) {
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
fileIndex[workspacePath] = { kind: "deleted", change };
|
|
582
|
+
}
|
|
583
|
+
return fileIndex;
|
|
584
|
+
}
|
|
585
|
+
const CODE_COLLAB_V2_RPC_CONTENT_ENVELOPE_TYPE = "code-collab-v2-content-envelope";
|
|
586
|
+
const CODE_COLLAB_V2_RPC_CONTENT_KEY_VERSION = 1;
|
|
587
|
+
const CODE_COLLAB_V2_RPC_CONTENT_KEY_ALGORITHM = "AES-256-GCM";
|
|
588
|
+
const CodeCollabV2RpcContentEnvelopeSchema = object({
|
|
589
|
+
type: literal(CODE_COLLAB_V2_RPC_CONTENT_ENVELOPE_TYPE),
|
|
590
|
+
keyVersion: literal(CODE_COLLAB_V2_RPC_CONTENT_KEY_VERSION),
|
|
591
|
+
algorithm: literal(CODE_COLLAB_V2_RPC_CONTENT_KEY_ALGORITHM),
|
|
592
|
+
ownerSessionId: string().trim().min(1),
|
|
593
|
+
keyId: string().trim().min(1),
|
|
594
|
+
iv: string().trim().min(1),
|
|
595
|
+
ciphertext: string().trim().min(1)
|
|
596
|
+
}).strict();
|
|
597
|
+
union([
|
|
598
|
+
CodeCollabV2OpenTextOkSchema,
|
|
599
|
+
CodeCollabV2RefreshTextResponseSchema,
|
|
600
|
+
CodeCollabV2SaveTextResponseSchema,
|
|
601
|
+
CodeCollabV2OpenCurrentDiffResponseSchema,
|
|
602
|
+
CodeCollabV2OpenAllChangesDiffResponseSchema,
|
|
603
|
+
CodeCollabV2OpenTurnDiffResponseSchema,
|
|
604
|
+
CodeCollabV2InitDirectoryOkSchema,
|
|
605
|
+
CodeCollabV2LspUnsupportedSchema
|
|
606
|
+
]);
|
|
607
|
+
const CODE_COLLAB_V2_CONTENT_KEY_DERIVATION_LABEL = "lody-code-collab-v2-bootstrap-content-key-v1";
|
|
608
|
+
const CODE_COLLAB_V2_CONTENT_KEY_ID_DERIVATION_LABEL = "lody-code-collab-v2-bootstrap-content-key-id-v1";
|
|
609
|
+
const CODE_COLLAB_V2_BOOTSTRAP_CONTENT_KEY_SALT = "lody-code-collab-v2-bootstrap-salt-v1";
|
|
610
|
+
function deriveCodeCollabV2ContentKeyBytes(ownerSessionId) {
|
|
611
|
+
return sha256Bytes(
|
|
612
|
+
`${CODE_COLLAB_V2_CONTENT_KEY_DERIVATION_LABEL}\0${CODE_COLLAB_V2_BOOTSTRAP_CONTENT_KEY_SALT}\0${ownerSessionId}`
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
function deriveCodeCollabV2ContentKeyId(ownerSessionId) {
|
|
616
|
+
const digest = sha256Bytes(
|
|
617
|
+
`${CODE_COLLAB_V2_CONTENT_KEY_ID_DERIVATION_LABEL}\0${CODE_COLLAB_V2_BOOTSTRAP_CONTENT_KEY_SALT}\0${ownerSessionId}`
|
|
618
|
+
);
|
|
619
|
+
return `ccv2:${bytesToHex(digest).slice(0, 24)}`;
|
|
620
|
+
}
|
|
621
|
+
object({
|
|
622
|
+
fileId: string().min(1),
|
|
623
|
+
path: string().min(1),
|
|
624
|
+
kind: CodeCollabFileKindSchema,
|
|
625
|
+
specialKind: CodeCollabSpecialKindSchema.optional(),
|
|
626
|
+
textEol: CodeCollabTextEolSchema.optional(),
|
|
627
|
+
hasBom: boolean().optional(),
|
|
628
|
+
mode: number().int().nonnegative().optional(),
|
|
629
|
+
executable: boolean().optional(),
|
|
630
|
+
sizeBytes: number().int().nonnegative().optional(),
|
|
631
|
+
mimeType: string().optional(),
|
|
632
|
+
contentDigest: string().optional(),
|
|
633
|
+
linkTarget: string().optional(),
|
|
634
|
+
unavailableReason: CodeCollabUnavailableReasonSchema.optional(),
|
|
635
|
+
updatedAt: string().optional(),
|
|
636
|
+
deletedAt: string().optional()
|
|
637
|
+
}).strict();
|
|
638
|
+
_enum([
|
|
639
|
+
"regular-file",
|
|
640
|
+
"directory",
|
|
641
|
+
"symlink",
|
|
642
|
+
"fifo",
|
|
643
|
+
"socket",
|
|
644
|
+
"block-device",
|
|
645
|
+
"char-device",
|
|
646
|
+
"unknown"
|
|
647
|
+
]);
|
|
648
|
+
object({
|
|
649
|
+
exists: boolean(),
|
|
650
|
+
path: string().optional(),
|
|
651
|
+
kind: _enum(["text", "binary", "large"]).optional(),
|
|
652
|
+
textDocId: string().optional(),
|
|
653
|
+
textFrontiers: unknown().optional(),
|
|
654
|
+
contentDigest: string().optional(),
|
|
655
|
+
blobDigest: string().optional(),
|
|
656
|
+
mode: number().int().nonnegative().optional(),
|
|
657
|
+
executable: boolean().optional(),
|
|
658
|
+
sizeBytes: number().int().nonnegative().optional()
|
|
659
|
+
}).strict();
|
|
660
|
+
const SHA256_INITIAL_HASH = [
|
|
661
|
+
1779033703,
|
|
662
|
+
3144134277,
|
|
663
|
+
1013904242,
|
|
664
|
+
2773480762,
|
|
665
|
+
1359893119,
|
|
666
|
+
2600822924,
|
|
667
|
+
528734635,
|
|
668
|
+
1541459225
|
|
669
|
+
];
|
|
670
|
+
const SHA256_K = [
|
|
671
|
+
1116352408,
|
|
672
|
+
1899447441,
|
|
673
|
+
3049323471,
|
|
674
|
+
3921009573,
|
|
675
|
+
961987163,
|
|
676
|
+
1508970993,
|
|
677
|
+
2453635748,
|
|
678
|
+
2870763221,
|
|
679
|
+
3624381080,
|
|
680
|
+
310598401,
|
|
681
|
+
607225278,
|
|
682
|
+
1426881987,
|
|
683
|
+
1925078388,
|
|
684
|
+
2162078206,
|
|
685
|
+
2614888103,
|
|
686
|
+
3248222580,
|
|
687
|
+
3835390401,
|
|
688
|
+
4022224774,
|
|
689
|
+
264347078,
|
|
690
|
+
604807628,
|
|
691
|
+
770255983,
|
|
692
|
+
1249150122,
|
|
693
|
+
1555081692,
|
|
694
|
+
1996064986,
|
|
695
|
+
2554220882,
|
|
696
|
+
2821834349,
|
|
697
|
+
2952996808,
|
|
698
|
+
3210313671,
|
|
699
|
+
3336571891,
|
|
700
|
+
3584528711,
|
|
701
|
+
113926993,
|
|
702
|
+
338241895,
|
|
703
|
+
666307205,
|
|
704
|
+
773529912,
|
|
705
|
+
1294757372,
|
|
706
|
+
1396182291,
|
|
707
|
+
1695183700,
|
|
708
|
+
1986661051,
|
|
709
|
+
2177026350,
|
|
710
|
+
2456956037,
|
|
711
|
+
2730485921,
|
|
712
|
+
2820302411,
|
|
713
|
+
3259730800,
|
|
714
|
+
3345764771,
|
|
715
|
+
3516065817,
|
|
716
|
+
3600352804,
|
|
717
|
+
4094571909,
|
|
718
|
+
275423344,
|
|
719
|
+
430227734,
|
|
720
|
+
506948616,
|
|
721
|
+
659060556,
|
|
722
|
+
883997877,
|
|
723
|
+
958139571,
|
|
724
|
+
1322822218,
|
|
725
|
+
1537002063,
|
|
726
|
+
1747873779,
|
|
727
|
+
1955562222,
|
|
728
|
+
2024104815,
|
|
729
|
+
2227730452,
|
|
730
|
+
2361852424,
|
|
731
|
+
2428436474,
|
|
732
|
+
2756734187,
|
|
733
|
+
3204031479,
|
|
734
|
+
3329325298
|
|
735
|
+
];
|
|
736
|
+
const rightRotate = (value, bits) => value >>> bits | value << 32 - bits;
|
|
737
|
+
function sha256Bytes(value) {
|
|
738
|
+
const bytes = new TextEncoder().encode(value);
|
|
739
|
+
const bitLength = bytes.length * 8;
|
|
740
|
+
const paddingLength = (64 - (bytes.length + 1 + 8) % 64) % 64;
|
|
741
|
+
const data = new Uint8Array(bytes.length + 1 + paddingLength + 8);
|
|
742
|
+
data.set(bytes);
|
|
743
|
+
data[bytes.length] = 128;
|
|
744
|
+
const view = new DataView(data.buffer);
|
|
745
|
+
view.setUint32(data.length - 8, Math.floor(bitLength / 4294967296));
|
|
746
|
+
view.setUint32(data.length - 4, bitLength >>> 0);
|
|
747
|
+
const hash = [...SHA256_INITIAL_HASH];
|
|
748
|
+
const words = new Array(64).fill(0);
|
|
749
|
+
for (let offset = 0; offset < data.length; offset += 64) {
|
|
750
|
+
for (let i = 0; i < 16; i += 1) {
|
|
751
|
+
words[i] = view.getUint32(offset + i * 4);
|
|
752
|
+
}
|
|
753
|
+
for (let i = 16; i < 64; i += 1) {
|
|
754
|
+
const s0 = rightRotate(words[i - 15] ?? 0, 7) ^ rightRotate(words[i - 15] ?? 0, 18) ^ (words[i - 15] ?? 0) >>> 3;
|
|
755
|
+
const s1 = rightRotate(words[i - 2] ?? 0, 17) ^ rightRotate(words[i - 2] ?? 0, 19) ^ (words[i - 2] ?? 0) >>> 10;
|
|
756
|
+
words[i] = (words[i - 16] ?? 0) + s0 + (words[i - 7] ?? 0) + s1 >>> 0 >>> 0;
|
|
757
|
+
}
|
|
758
|
+
let [a, b, c, d, e, f, g, h] = hash;
|
|
759
|
+
for (let i = 0; i < 64; i += 1) {
|
|
760
|
+
const s1 = rightRotate(e ?? 0, 6) ^ rightRotate(e ?? 0, 11) ^ rightRotate(e ?? 0, 25);
|
|
761
|
+
const ch = (e ?? 0) & (f ?? 0) ^ ~(e ?? 0) & (g ?? 0);
|
|
762
|
+
const temp1 = (h ?? 0) + s1 + ch + SHA256_K[i] + words[i] >>> 0 >>> 0;
|
|
763
|
+
const s0 = rightRotate(a ?? 0, 2) ^ rightRotate(a ?? 0, 13) ^ rightRotate(a ?? 0, 22);
|
|
764
|
+
const maj = (a ?? 0) & (b ?? 0) ^ (a ?? 0) & (c ?? 0) ^ (b ?? 0) & (c ?? 0);
|
|
765
|
+
const temp2 = s0 + maj >>> 0;
|
|
766
|
+
h = g;
|
|
767
|
+
g = f;
|
|
768
|
+
f = e;
|
|
769
|
+
e = (d ?? 0) + temp1 >>> 0;
|
|
770
|
+
d = c;
|
|
771
|
+
c = b;
|
|
772
|
+
b = a;
|
|
773
|
+
a = temp1 + temp2 >>> 0;
|
|
774
|
+
}
|
|
775
|
+
hash[0] = (hash[0] ?? 0) + (a ?? 0) >>> 0;
|
|
776
|
+
hash[1] = (hash[1] ?? 0) + (b ?? 0) >>> 0;
|
|
777
|
+
hash[2] = (hash[2] ?? 0) + (c ?? 0) >>> 0;
|
|
778
|
+
hash[3] = (hash[3] ?? 0) + (d ?? 0) >>> 0;
|
|
779
|
+
hash[4] = (hash[4] ?? 0) + (e ?? 0) >>> 0;
|
|
780
|
+
hash[5] = (hash[5] ?? 0) + (f ?? 0) >>> 0;
|
|
781
|
+
hash[6] = (hash[6] ?? 0) + (g ?? 0) >>> 0;
|
|
782
|
+
hash[7] = (hash[7] ?? 0) + (h ?? 0) >>> 0;
|
|
783
|
+
}
|
|
784
|
+
const output = new Uint8Array(32);
|
|
785
|
+
const outputView = new DataView(output.buffer);
|
|
786
|
+
for (let i = 0; i < hash.length; i += 1) {
|
|
787
|
+
outputView.setUint32(i * 4, hash[i] ?? 0);
|
|
788
|
+
}
|
|
789
|
+
return output;
|
|
790
|
+
}
|
|
791
|
+
function bytesToHex(bytes) {
|
|
792
|
+
return Array.from(bytes).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
793
|
+
}
|
|
794
|
+
function assertNever(value) {
|
|
795
|
+
throw new Error(`Unhandled Code Collab value: ${String(value)}`);
|
|
796
|
+
}
|
|
797
|
+
const execFileAsync = promisify(execFile);
|
|
798
|
+
const GIT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
|
|
799
|
+
async function isInsideGitWorktree(workspaceRoot) {
|
|
800
|
+
const inside = await runGit(workspaceRoot, ["rev-parse", "--is-inside-work-tree"]);
|
|
801
|
+
return inside.ok && inside.stdout.trim() === "true";
|
|
802
|
+
}
|
|
803
|
+
async function scanGitDirectoryEntries(options) {
|
|
804
|
+
if (!options.recursive) {
|
|
805
|
+
return null;
|
|
806
|
+
}
|
|
807
|
+
const result = await runGitLsFiles(options.directoryAbsolutePath);
|
|
808
|
+
if (!result.ok) {
|
|
809
|
+
return null;
|
|
810
|
+
}
|
|
811
|
+
return buildEntriesFromGitFilePaths({
|
|
812
|
+
directoryWorkspacePath: options.directoryWorkspacePath,
|
|
813
|
+
entryBudget: options.entryBudget,
|
|
814
|
+
recursive: options.recursive,
|
|
815
|
+
relativeFilePaths: result.paths
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
async function runGitLsFiles(cwd) {
|
|
819
|
+
try {
|
|
820
|
+
const [{ stdout }, deleted] = await Promise.all([
|
|
821
|
+
execFileAsync(
|
|
822
|
+
"git",
|
|
823
|
+
[
|
|
824
|
+
"-C",
|
|
825
|
+
cwd,
|
|
826
|
+
"ls-files",
|
|
827
|
+
"-z",
|
|
828
|
+
"--cached",
|
|
829
|
+
"--others",
|
|
830
|
+
"--exclude-standard",
|
|
831
|
+
"--deduplicate",
|
|
832
|
+
"--",
|
|
833
|
+
"."
|
|
834
|
+
],
|
|
835
|
+
{ maxBuffer: GIT_MAX_BUFFER_BYTES }
|
|
836
|
+
),
|
|
837
|
+
runGit(cwd, ["ls-files", "--deleted", "-z", "--", "."])
|
|
838
|
+
]);
|
|
839
|
+
const deletedPaths = deleted.ok ? new Set(deleted.stdout.split("\0").map(normalizeGitPath).filter(isValidRelativeGitPath)) : /* @__PURE__ */ new Set();
|
|
840
|
+
return {
|
|
841
|
+
ok: true,
|
|
842
|
+
paths: stdout.split("\0").map(normalizeGitPath).filter((filePath) => isValidRelativeGitPath(filePath) && !deletedPaths.has(filePath))
|
|
843
|
+
};
|
|
844
|
+
} catch {
|
|
845
|
+
return { ok: false };
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
async function computeAllChanges(workspaceRoot, options = {}) {
|
|
849
|
+
const diffBase = await resolveAllChangesDiffBase(workspaceRoot, options.preferredBaseBranch);
|
|
850
|
+
const diffTarget = diffBase ?? "HEAD";
|
|
851
|
+
const [numstat, nameStatus, untracked] = await Promise.all([
|
|
852
|
+
// `--numstat` cannot use `-z`, so disable `core.quotePath` to keep non-ASCII paths
|
|
853
|
+
// raw (matching the `-z` outputs below); otherwise they are mis-keyed and lose their
|
|
854
|
+
// All Changes diff counts.
|
|
855
|
+
runGit(workspaceRoot, [
|
|
856
|
+
"-c",
|
|
857
|
+
"core.quotePath=false",
|
|
858
|
+
"diff",
|
|
859
|
+
"--numstat",
|
|
860
|
+
"--no-renames",
|
|
861
|
+
"--relative",
|
|
862
|
+
diffTarget,
|
|
863
|
+
"--"
|
|
864
|
+
]),
|
|
865
|
+
runGit(workspaceRoot, [
|
|
866
|
+
"diff",
|
|
867
|
+
"--name-status",
|
|
868
|
+
"--no-renames",
|
|
869
|
+
"-z",
|
|
870
|
+
"--relative",
|
|
871
|
+
diffTarget,
|
|
872
|
+
"--"
|
|
873
|
+
]),
|
|
874
|
+
runGit(workspaceRoot, ["ls-files", "--others", "--exclude-standard", "-z"])
|
|
875
|
+
]);
|
|
876
|
+
if (!numstat.ok && !nameStatus.ok && !untracked.ok) {
|
|
877
|
+
return {};
|
|
878
|
+
}
|
|
879
|
+
const deletedPaths = nameStatus.ok ? parseDeletedPathsFromNameStatus(nameStatus.stdout) : /* @__PURE__ */ new Set();
|
|
880
|
+
const changes = {};
|
|
881
|
+
if (numstat.ok) {
|
|
882
|
+
for (const line of numstat.stdout.split(/\r?\n/u)) {
|
|
883
|
+
const trimmed = line.trim();
|
|
884
|
+
if (!trimmed) continue;
|
|
885
|
+
const [addRaw, delRaw, ...pathParts] = trimmed.split(" ");
|
|
886
|
+
const filePath = pathParts.join(" ").trim();
|
|
887
|
+
if (!filePath) continue;
|
|
888
|
+
const normalizedPath = normalizeGitPath(filePath);
|
|
889
|
+
if (addRaw === "-" || delRaw === "-") {
|
|
890
|
+
changes[normalizedPath] = true;
|
|
891
|
+
continue;
|
|
892
|
+
}
|
|
893
|
+
const add = Number(addRaw);
|
|
894
|
+
const del = Number(delRaw);
|
|
895
|
+
const diff = [
|
|
896
|
+
Number.isFinite(add) && add >= 0 ? Math.trunc(add) : 0,
|
|
897
|
+
Number.isFinite(del) && del >= 0 ? Math.trunc(del) : 0
|
|
898
|
+
];
|
|
899
|
+
changes[normalizedPath] = deletedPaths.has(normalizedPath) ? { diff: [0, diff[1]], del: true } : { diff };
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
if (untracked.ok) {
|
|
903
|
+
for (const filePath of untracked.stdout.split("\0")) {
|
|
904
|
+
if (!filePath) continue;
|
|
905
|
+
const normalizedPath = normalizeGitPath(filePath);
|
|
906
|
+
if (!(normalizedPath in changes)) {
|
|
907
|
+
changes[normalizedPath] = await lineStatsForUntrackedFile(workspaceRoot, normalizedPath);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
return changes;
|
|
912
|
+
}
|
|
913
|
+
async function resolveAllChangesDiffBase(workspaceRoot, preferredBaseBranch) {
|
|
914
|
+
if (!await isInsideGitWorktree(workspaceRoot)) {
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
const baseRef = await resolveAllChangesBaseRef(workspaceRoot, preferredBaseBranch);
|
|
918
|
+
if (baseRef) {
|
|
919
|
+
const mergeBase = await runGit(workspaceRoot, ["merge-base", baseRef, "HEAD"]);
|
|
920
|
+
const trimmed = mergeBase.ok ? mergeBase.stdout.trim() : "";
|
|
921
|
+
if (trimmed) {
|
|
922
|
+
return trimmed;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
const head = await runGit(workspaceRoot, ["rev-parse", "--verify", "HEAD^{commit}"]);
|
|
926
|
+
return head.ok && head.stdout.trim() ? "HEAD" : null;
|
|
927
|
+
}
|
|
928
|
+
async function resolveAllChangesBaseRef(workspaceRoot, preferredBaseBranch) {
|
|
929
|
+
const candidates = [
|
|
930
|
+
...preferredBaseBranch ? [`origin/${preferredBaseBranch}`, preferredBaseBranch] : [],
|
|
931
|
+
"origin/main",
|
|
932
|
+
"main",
|
|
933
|
+
"origin/master",
|
|
934
|
+
"master",
|
|
935
|
+
"origin/HEAD"
|
|
936
|
+
];
|
|
937
|
+
for (const candidate of candidates) {
|
|
938
|
+
const exists = await runGit(workspaceRoot, ["rev-parse", "--verify", `${candidate}^{commit}`]);
|
|
939
|
+
if (exists.ok) {
|
|
940
|
+
return candidate;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
return null;
|
|
944
|
+
}
|
|
945
|
+
async function lineStatsForUntrackedFile(workspaceRoot, workspacePath) {
|
|
946
|
+
try {
|
|
947
|
+
const absolutePath = path__default.resolve(workspaceRoot, workspacePath);
|
|
948
|
+
const bytes = await readFile(absolutePath);
|
|
949
|
+
if (hasBinaryNul(bytes)) return true;
|
|
950
|
+
const text = decodeUtf8(bytes);
|
|
951
|
+
return { diff: [countTextLines(text), 0] };
|
|
952
|
+
} catch {
|
|
953
|
+
return true;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
function parseDeletedPathsFromNameStatus(stdout) {
|
|
957
|
+
const deleted = /* @__PURE__ */ new Set();
|
|
958
|
+
const tokens = stdout.split("\0").filter(Boolean);
|
|
959
|
+
for (let index = 0; index < tokens.length; ) {
|
|
960
|
+
const status = tokens[index] ?? "";
|
|
961
|
+
index += 1;
|
|
962
|
+
if (status.startsWith("R") || status.startsWith("C")) {
|
|
963
|
+
const oldPath = tokens[index];
|
|
964
|
+
const newPath = tokens[index + 1];
|
|
965
|
+
index += 2;
|
|
966
|
+
if (oldPath) deleted.add(normalizeGitPath(oldPath));
|
|
967
|
+
if (newPath) deleted.delete(normalizeGitPath(newPath));
|
|
968
|
+
continue;
|
|
969
|
+
}
|
|
970
|
+
const filePath = tokens[index];
|
|
971
|
+
index += 1;
|
|
972
|
+
if (status.startsWith("D") && filePath) {
|
|
973
|
+
deleted.add(normalizeGitPath(filePath));
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
return deleted;
|
|
977
|
+
}
|
|
978
|
+
async function runGit(cwd, args) {
|
|
979
|
+
try {
|
|
980
|
+
const { stdout } = await execFileAsync("git", ["-C", cwd, ...args], {
|
|
981
|
+
maxBuffer: GIT_MAX_BUFFER_BYTES
|
|
982
|
+
});
|
|
983
|
+
return { ok: true, stdout };
|
|
984
|
+
} catch {
|
|
985
|
+
return { ok: false };
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
function buildEntriesFromGitFilePaths(options) {
|
|
989
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
990
|
+
const conflicts = buildGitPathConflictIndex(options.relativeFilePaths);
|
|
991
|
+
for (const relativeFilePath of options.relativeFilePaths) {
|
|
992
|
+
const segments = relativeFilePath.split("/").filter((segment) => segment.length > 0);
|
|
993
|
+
const maxSegmentIndex = options.recursive ? segments.length - 1 : 0;
|
|
994
|
+
for (let index = 0; index <= maxSegmentIndex; index += 1) {
|
|
995
|
+
const segment = segments[index];
|
|
996
|
+
if (!segment) {
|
|
997
|
+
break;
|
|
998
|
+
}
|
|
999
|
+
const relativePath = segments.slice(0, index + 1).join("/");
|
|
1000
|
+
const workspacePath = joinWorkspacePath(options.directoryWorkspacePath, relativePath);
|
|
1001
|
+
if (conflicts.has(relativePath)) {
|
|
1002
|
+
candidates.set(workspacePath, { kind: "skipped", reason: "path_conflict" });
|
|
1003
|
+
break;
|
|
1004
|
+
}
|
|
1005
|
+
if (index < segments.length - 1) {
|
|
1006
|
+
if (!candidates.has(workspacePath)) {
|
|
1007
|
+
candidates.set(workspacePath, { kind: "lazy" });
|
|
1008
|
+
}
|
|
1009
|
+
} else {
|
|
1010
|
+
candidates.set(workspacePath, true);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
const sortedCandidates = [...candidates.entries()].sort(([leftPath], [rightPath]) => {
|
|
1015
|
+
const depthOrder = pathDepth(leftPath) - pathDepth(rightPath);
|
|
1016
|
+
return depthOrder === 0 ? leftPath.localeCompare(rightPath) : depthOrder;
|
|
1017
|
+
});
|
|
1018
|
+
return new Map(sortedCandidates.slice(0, Math.max(0, options.entryBudget)));
|
|
1019
|
+
}
|
|
1020
|
+
function buildGitPathConflictIndex(relativeFilePaths) {
|
|
1021
|
+
const namesByDirectoryAndKey = /* @__PURE__ */ new Map();
|
|
1022
|
+
for (const relativeFilePath of relativeFilePaths) {
|
|
1023
|
+
const segments = relativeFilePath.split("/").filter((segment) => segment.length > 0);
|
|
1024
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
1025
|
+
const segment = segments[index];
|
|
1026
|
+
if (!segment) {
|
|
1027
|
+
continue;
|
|
1028
|
+
}
|
|
1029
|
+
const parent = segments.slice(0, index).join("/");
|
|
1030
|
+
const comparisonKey = pathSegmentComparisonKey(segment);
|
|
1031
|
+
let namesByKey = namesByDirectoryAndKey.get(parent);
|
|
1032
|
+
if (!namesByKey) {
|
|
1033
|
+
namesByKey = /* @__PURE__ */ new Map();
|
|
1034
|
+
namesByDirectoryAndKey.set(parent, namesByKey);
|
|
1035
|
+
}
|
|
1036
|
+
let names = namesByKey.get(comparisonKey);
|
|
1037
|
+
if (!names) {
|
|
1038
|
+
names = /* @__PURE__ */ new Set();
|
|
1039
|
+
namesByKey.set(comparisonKey, names);
|
|
1040
|
+
}
|
|
1041
|
+
names.add(segment);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
const conflicts = /* @__PURE__ */ new Set();
|
|
1045
|
+
for (const [parent, namesByKey] of namesByDirectoryAndKey) {
|
|
1046
|
+
for (const names of namesByKey.values()) {
|
|
1047
|
+
if (names.size <= 1) {
|
|
1048
|
+
continue;
|
|
1049
|
+
}
|
|
1050
|
+
for (const name of names) {
|
|
1051
|
+
conflicts.add(parent ? `${parent}/${name}` : name);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
return conflicts;
|
|
1056
|
+
}
|
|
1057
|
+
function normalizeGitPath(filePath) {
|
|
1058
|
+
return filePath.replace(/\\/gu, "/").normalize("NFC");
|
|
1059
|
+
}
|
|
1060
|
+
function isValidRelativeGitPath(filePath) {
|
|
1061
|
+
return filePath.length > 0 && filePath !== "." && !path__default.isAbsolute(filePath) && !filePath.split("/").some((segment) => segment.length === 0 || segment === "..");
|
|
1062
|
+
}
|
|
1063
|
+
function joinWorkspacePath(parent, child) {
|
|
1064
|
+
return (parent ? `${parent}/${child}` : child).normalize("NFC");
|
|
1065
|
+
}
|
|
1066
|
+
function pathSegmentComparisonKey(segment) {
|
|
1067
|
+
return segment.normalize("NFC").toLocaleLowerCase("en-US");
|
|
1068
|
+
}
|
|
1069
|
+
function pathDepth(workspacePath) {
|
|
1070
|
+
return workspacePath ? workspacePath.split("/").length : 0;
|
|
1071
|
+
}
|
|
1072
|
+
function hasBinaryNul(bytes) {
|
|
1073
|
+
const limit = Math.min(bytes.byteLength, 8 * 1024);
|
|
1074
|
+
for (let index = 0; index < limit; index += 1) {
|
|
1075
|
+
if (bytes[index] === 0) {
|
|
1076
|
+
return true;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
return false;
|
|
1080
|
+
}
|
|
1081
|
+
function decodeUtf8(bytes) {
|
|
1082
|
+
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
1083
|
+
}
|
|
1084
|
+
async function closeDirectoryQuietly(directory) {
|
|
1085
|
+
try {
|
|
1086
|
+
await directory.close();
|
|
1087
|
+
} catch {
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
export {
|
|
1091
|
+
writeCodeCollabFileIndexToFlock as A,
|
|
1092
|
+
joinWorkspacePath as B,
|
|
1093
|
+
CodeCollabV2OpenTextRequestSchema as C,
|
|
1094
|
+
isInsideGitWorktree as D,
|
|
1095
|
+
pathSegmentComparisonKey as E,
|
|
1096
|
+
CodeCollabV2RefreshTextRequestSchema as a,
|
|
1097
|
+
CodeCollabV2SaveTextRequestSchema as b,
|
|
1098
|
+
CodeCollabV2OpenCurrentDiffRequestSchema as c,
|
|
1099
|
+
CodeCollabV2OpenAllChangesDiffRequestSchema as d,
|
|
1100
|
+
CodeCollabV2OpenTurnDiffRequestSchema as e,
|
|
1101
|
+
CodeCollabV2InitDirectoryRequestSchema as f,
|
|
1102
|
+
CodeCollabV2OpenTextOkSchema as g,
|
|
1103
|
+
CodeCollabV2RefreshTextResponseSchema as h,
|
|
1104
|
+
CodeCollabV2SaveTextResponseSchema as i,
|
|
1105
|
+
CodeCollabV2OpenCurrentDiffResponseSchema as j,
|
|
1106
|
+
CodeCollabV2OpenAllChangesDiffResponseSchema as k,
|
|
1107
|
+
CodeCollabV2OpenTurnDiffResponseSchema as l,
|
|
1108
|
+
CodeCollabV2InitDirectoryOkSchema as m,
|
|
1109
|
+
CodeCollabV2LspUnsupportedSchema as n,
|
|
1110
|
+
CodeCollabV2ErrorSchema as o,
|
|
1111
|
+
CodeCollabV2RpcContentEnvelopeSchema as p,
|
|
1112
|
+
deriveCodeCollabV2ContentKeyId as q,
|
|
1113
|
+
deriveCodeCollabV2ContentKeyBytes as r,
|
|
1114
|
+
CodeCollabV2ErrorCodeSchema as s,
|
|
1115
|
+
CODE_COLLAB_V2_TEXT_LIMITS as t,
|
|
1116
|
+
CODE_COLLAB_V2_ALL_CHANGES_DIFF_LIMITS as u,
|
|
1117
|
+
computeAllChanges as v,
|
|
1118
|
+
buildCodeCollabFileIndexState as w,
|
|
1119
|
+
codeCollabFileIndexStatesEqual as x,
|
|
1120
|
+
scanGitDirectoryEntries as y,
|
|
1121
|
+
closeDirectoryQuietly as z
|
|
1122
|
+
};
|