@ricsam/r5d-worker 0.0.135 → 0.0.137
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/cjs/main.cjs +7 -1
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/workspace-git-sync.cjs +241 -96
- package/dist/cjs/workspace-hydration-ledger.cjs +28 -11
- package/dist/cjs/workspace-hydration-merge.cjs +17 -2
- package/dist/cjs/workspace-merge-projection.cjs +190 -29
- package/dist/mjs/main.mjs +7 -1
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/workspace-git-sync.mjs +243 -97
- package/dist/mjs/workspace-hydration-ledger.mjs +27 -11
- package/dist/mjs/workspace-hydration-merge.mjs +17 -2
- package/dist/mjs/workspace-merge-projection.mjs +190 -29
- package/dist/types/runtime/adapter.d.ts +1 -0
- package/dist/types/runtime/client.d.ts +15 -0
- package/dist/types/runtime/daemon.d.ts +8 -0
- package/dist/types/runtime/executor.d.ts +30 -0
- package/dist/types/runtime/index.d.ts +5 -0
- package/dist/types/runtime/protocol.d.ts +372 -0
- package/dist/types/runtime/pty.d.ts +29 -0
- package/dist/types/runtime/storage.d.ts +21 -0
- package/dist/types/runtime/test-harness.d.ts +53 -0
- package/dist/types/workspace-filesystem-job-types.d.ts +19 -5
- package/dist/types/workspace-git-sync.d.ts +25 -6
- package/dist/types/workspace-hydration-ledger.d.ts +51 -18
- package/dist/types/workspace-hydration-merge.d.ts +8 -0
- package/dist/types/workspace-merge-projection.d.ts +10 -1
- package/package.json +2 -2
|
@@ -1,28 +1,42 @@
|
|
|
1
|
-
class
|
|
1
|
+
class WorkspaceHydrationLedger {
|
|
2
2
|
mounts = /* @__PURE__ */ new Map();
|
|
3
3
|
static key(mount) {
|
|
4
4
|
return `${mount.hydrationIncarnationKey}\0${mount.id}`;
|
|
5
5
|
}
|
|
6
|
-
/**
|
|
6
|
+
/** Entries by mount-relative path for the mount, or an empty map. */
|
|
7
|
+
entries(mount) {
|
|
8
|
+
return this.mounts.get(WorkspaceHydrationLedger.key(mount)) ?? /* @__PURE__ */ new Map();
|
|
9
|
+
}
|
|
10
|
+
/** Blob ids by mount-relative path for the mount (the §3.3 detector's input), or an empty map. */
|
|
7
11
|
preBlobs(mount) {
|
|
8
|
-
return this.
|
|
12
|
+
return new Map([...this.entries(mount)].map(([relativePath, entry]) => [relativePath, entry.preBlob]));
|
|
9
13
|
}
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Record what a merge hydration replaced; earlier entries for other paths
|
|
16
|
+
* are kept. A path that already carries a live entry keeps its `preBlob`
|
|
17
|
+
* while the new hydration also ran across a live process: the process's
|
|
18
|
+
* read predates both hydrations, so what it read is still the first blob.
|
|
19
|
+
*/
|
|
20
|
+
record(mount, entries, options = {}) {
|
|
21
|
+
const incoming = entries instanceof Map ? [...entries] : Object.entries(entries);
|
|
22
|
+
if (incoming.length === 0) return;
|
|
23
|
+
const replace = new Set(options.replacePreBlobFor ?? []);
|
|
24
|
+
const key = WorkspaceHydrationLedger.key(mount);
|
|
15
25
|
const current = this.mounts.get(key) ?? /* @__PURE__ */ new Map();
|
|
16
|
-
for (const [relativePath,
|
|
26
|
+
for (const [relativePath, entry] of incoming) {
|
|
27
|
+
const previous = current.get(relativePath);
|
|
28
|
+
const preBlob = previous && previous.processLiveAcrossHydration && entry.processLiveAcrossHydration && !replace.has(relativePath) ? previous.preBlob : entry.preBlob;
|
|
29
|
+
current.set(relativePath, { ...entry, preBlob });
|
|
30
|
+
}
|
|
17
31
|
this.mounts.set(key, current);
|
|
18
32
|
}
|
|
19
33
|
/** Forget the mount: its checkout was rewritten by something this ledger did not observe. */
|
|
20
34
|
clearMount(mount) {
|
|
21
|
-
this.mounts.delete(
|
|
35
|
+
this.mounts.delete(WorkspaceHydrationLedger.key(mount));
|
|
22
36
|
}
|
|
23
37
|
/** Forget paths whose checkout content moved on from the hydrated bytes: a later return to the old bytes is a deliberate edit. */
|
|
24
38
|
forget(mount, paths) {
|
|
25
|
-
const key =
|
|
39
|
+
const key = WorkspaceHydrationLedger.key(mount);
|
|
26
40
|
const current = this.mounts.get(key);
|
|
27
41
|
if (!current) return;
|
|
28
42
|
for (const relativePath of paths) current.delete(relativePath);
|
|
@@ -37,6 +51,8 @@ class WorkspaceHydrationPreBlobLedger {
|
|
|
37
51
|
return total;
|
|
38
52
|
}
|
|
39
53
|
}
|
|
54
|
+
const WorkspaceHydrationPreBlobLedger = WorkspaceHydrationLedger;
|
|
40
55
|
export {
|
|
56
|
+
WorkspaceHydrationLedger,
|
|
41
57
|
WorkspaceHydrationPreBlobLedger
|
|
42
58
|
};
|
|
@@ -87,6 +87,8 @@ function decideWorkspaceHydrationMerge(input) {
|
|
|
87
87
|
const overrides = /* @__PURE__ */ new Map();
|
|
88
88
|
const expectations = /* @__PURE__ */ new Map();
|
|
89
89
|
const preBlobs = /* @__PURE__ */ new Map();
|
|
90
|
+
const postBlobs = /* @__PURE__ */ new Map();
|
|
91
|
+
const blobsToStore = /* @__PURE__ */ new Map();
|
|
90
92
|
const mergedPaths = [];
|
|
91
93
|
const keptPaths = /* @__PURE__ */ new Set();
|
|
92
94
|
const desiredForIndex = new Map(desired);
|
|
@@ -139,13 +141,17 @@ function decideWorkspaceHydrationMerge(input) {
|
|
|
139
141
|
remove.add(relativePath);
|
|
140
142
|
expectations.set(relativePath, oursStat);
|
|
141
143
|
}
|
|
142
|
-
if (change.base && isBlobMode(change.base.mode))
|
|
144
|
+
if (change.base && isBlobMode(change.base.mode)) {
|
|
145
|
+
preBlobs.set(relativePath, change.base.objectId);
|
|
146
|
+
postBlobs.set(relativePath, null);
|
|
147
|
+
}
|
|
143
148
|
continue;
|
|
144
149
|
}
|
|
145
150
|
write.add(relativePath);
|
|
146
151
|
expectations.set(relativePath, oursStat ?? currentTargetStat(targetRoot, relativePath));
|
|
147
152
|
if (change.base && isBlobMode(change.base.mode) && change.base.objectId !== change.theirs.objectId) {
|
|
148
153
|
preBlobs.set(relativePath, change.base.objectId);
|
|
154
|
+
postBlobs.set(relativePath, isBlobMode(change.theirs.mode) ? change.theirs.objectId : null);
|
|
149
155
|
}
|
|
150
156
|
continue;
|
|
151
157
|
}
|
|
@@ -259,7 +265,14 @@ function decideWorkspaceHydrationMerge(input) {
|
|
|
259
265
|
overrides.set(relativePath, { content: merged.content, mode });
|
|
260
266
|
write.add(relativePath);
|
|
261
267
|
expectations.set(relativePath, candidate.oursStat);
|
|
262
|
-
if (!contentUnchanged)
|
|
268
|
+
if (!contentUnchanged) {
|
|
269
|
+
const preBlob = hashOf(oursBytes);
|
|
270
|
+
const postBlob = hashOf(merged.content);
|
|
271
|
+
preBlobs.set(relativePath, preBlob);
|
|
272
|
+
postBlobs.set(relativePath, postBlob);
|
|
273
|
+
blobsToStore.set(preBlob, oursBytes);
|
|
274
|
+
blobsToStore.set(postBlob, merged.content);
|
|
275
|
+
}
|
|
263
276
|
mergedPaths.push(relativePath);
|
|
264
277
|
}
|
|
265
278
|
}
|
|
@@ -333,6 +346,8 @@ function decideWorkspaceHydrationMerge(input) {
|
|
|
333
346
|
overrides,
|
|
334
347
|
expectations,
|
|
335
348
|
preBlobs,
|
|
349
|
+
postBlobs,
|
|
350
|
+
blobsToStore,
|
|
336
351
|
mergedPaths: mergedPaths.sort(),
|
|
337
352
|
keptPaths: [...keptPaths].sort()
|
|
338
353
|
};
|
|
@@ -218,9 +218,12 @@ function synthesizeMountTree(input) {
|
|
|
218
218
|
removeTemporaryIndex(indexPath);
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
|
-
function
|
|
222
|
-
|
|
223
|
-
|
|
221
|
+
function ledgerStatMatches(recorded, projected) {
|
|
222
|
+
return recorded !== null && projected?.kind === "file" && projected.ino === recorded.ino && projected.size === recorded.size && projected.mode === recorded.mode && projected.mtimeMs === recorded.mtimeMs && projected.ctimeMs === recorded.ctimeMs;
|
|
223
|
+
}
|
|
224
|
+
function classifyHydrationLedger(input) {
|
|
225
|
+
const entries = Object.entries(input.hydrationLedger ?? {});
|
|
226
|
+
if (entries.length === 0) return { stale: [], merge: [], cleared: [] };
|
|
224
227
|
const probed = gitText(input.workspacePath, ["cat-file", "--batch-check"], "resolve workspace projection basis subtree", {
|
|
225
228
|
stdin: Buffer.from(`${input.basisHead}:${input.workspaceRelativePath}
|
|
226
229
|
`)
|
|
@@ -241,21 +244,116 @@ function staleRewritePaths(input) {
|
|
|
241
244
|
for (let index = 0; index + 1 < records.length; index += 2) changed.add(records[index + 1].toString());
|
|
242
245
|
}
|
|
243
246
|
const stale = [];
|
|
247
|
+
const merge = [];
|
|
244
248
|
const cleared = [];
|
|
245
|
-
for (const [relativePath,
|
|
249
|
+
for (const [relativePath, entry] of entries) {
|
|
246
250
|
const synthesized = input.blobs.get(relativePath);
|
|
247
251
|
if (synthesized === void 0) {
|
|
252
|
+
if (entry.postBlob === null) continue;
|
|
253
|
+
cleared.push(relativePath);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (ledgerStatMatches(entry.stat, input.projectedFiles[relativePath]) || synthesized === entry.postBlob) continue;
|
|
257
|
+
if (!changed.has(relativePath)) {
|
|
248
258
|
cleared.push(relativePath);
|
|
249
259
|
continue;
|
|
250
260
|
}
|
|
251
|
-
if (
|
|
252
|
-
|
|
253
|
-
else cleared.push(relativePath);
|
|
261
|
+
if (entry.processLiveAcrossHydration) {
|
|
262
|
+
merge.push({ relativePath, preBlob: entry.preBlob, postBlob: entry.postBlob });
|
|
254
263
|
continue;
|
|
255
264
|
}
|
|
256
|
-
if (
|
|
265
|
+
if (synthesized === entry.preBlob) {
|
|
266
|
+
stale.push(relativePath);
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
cleared.push(relativePath);
|
|
270
|
+
}
|
|
271
|
+
if (merge.length > 0) {
|
|
272
|
+
const ids = [...new Set(merge.flatMap(({ preBlob, postBlob }) => postBlob ? [preBlob, postBlob] : [preBlob]))];
|
|
273
|
+
const checked = gitText(input.workspacePath, ["cat-file", "--batch-check"], "verify hydration ledger blobs", {
|
|
274
|
+
stdin: Buffer.from(`${ids.join("\n")}
|
|
275
|
+
`)
|
|
276
|
+
});
|
|
277
|
+
const missing = new Set(
|
|
278
|
+
checked.split("\n").filter((line) => / missing$/u.test(line)).map((line) => line.split(" ")[0])
|
|
279
|
+
);
|
|
280
|
+
if (missing.size > 0) {
|
|
281
|
+
for (let index = merge.length - 1; index >= 0; index -= 1) {
|
|
282
|
+
const { relativePath, preBlob, postBlob } = merge[index];
|
|
283
|
+
if (missing.has(preBlob) || postBlob !== null && missing.has(postBlob)) {
|
|
284
|
+
merge.splice(index, 1);
|
|
285
|
+
cleared.push(relativePath);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return { stale: stale.sort(), merge: merge.sort((left, right) => left.relativePath.localeCompare(right.relativePath)), cleared: cleared.sort() };
|
|
291
|
+
}
|
|
292
|
+
function hydrationLedgerMergeBase(input) {
|
|
293
|
+
if (input.merges.length === 0) return input.basisHead;
|
|
294
|
+
return commitWithLedgerPaths({
|
|
295
|
+
workspacePath: input.workspacePath,
|
|
296
|
+
workspaceRelativePath: input.workspaceRelativePath,
|
|
297
|
+
parent: input.basisHead,
|
|
298
|
+
paths: input.merges.map(({ relativePath, preBlob }) => ({ relativePath, blob: preBlob })),
|
|
299
|
+
message: JSON.stringify({
|
|
300
|
+
type: "workspace_hydration_ledger_basis",
|
|
301
|
+
mountId: input.mountId,
|
|
302
|
+
attemptId: input.attemptId,
|
|
303
|
+
paths: input.merges.map(({ relativePath }) => relativePath)
|
|
304
|
+
}),
|
|
305
|
+
action: "hydration ledger merge base"
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
function hydrationLedgerTheirs(input) {
|
|
309
|
+
const paths = input.merges.flatMap(({ relativePath, postBlob }) => postBlob ? [{ relativePath, blob: postBlob }] : []);
|
|
310
|
+
if (paths.length === 0) return input.currentHead;
|
|
311
|
+
return commitWithLedgerPaths({
|
|
312
|
+
workspacePath: input.workspacePath,
|
|
313
|
+
workspaceRelativePath: input.workspaceRelativePath,
|
|
314
|
+
parent: input.currentHead,
|
|
315
|
+
paths,
|
|
316
|
+
message: JSON.stringify({
|
|
317
|
+
type: "workspace_hydration_ledger_theirs",
|
|
318
|
+
mountId: input.mountId,
|
|
319
|
+
attemptId: input.attemptId,
|
|
320
|
+
paths: paths.map(({ relativePath }) => relativePath)
|
|
321
|
+
}),
|
|
322
|
+
action: "hydration ledger theirs"
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
function commitWithLedgerPaths(input) {
|
|
326
|
+
const indexPath = temporaryIndexPath(input.workspacePath);
|
|
327
|
+
const environment = temporaryIndexEnvironment(indexPath);
|
|
328
|
+
try {
|
|
329
|
+
git(input.workspacePath, ["read-tree", input.parent], `read workspace tree for the ${input.action}`, { environment });
|
|
330
|
+
const paths = input.paths.map(({ relativePath }) => `${input.workspaceRelativePath}/${relativePath}`);
|
|
331
|
+
const listed = git(
|
|
332
|
+
input.workspacePath,
|
|
333
|
+
["ls-files", "-z", "--stage", "--", ...paths.map((entry) => `:(literal)${entry}`)],
|
|
334
|
+
`inspect modes for the ${input.action}`,
|
|
335
|
+
{ environment }
|
|
336
|
+
);
|
|
337
|
+
const modes = /* @__PURE__ */ new Map();
|
|
338
|
+
for (const record of nulRecords(listed)) {
|
|
339
|
+
const match = /^([0-7]{6}) [0-9a-f]{40,64} [0-3]\t(.+)$/su.exec(record.toString());
|
|
340
|
+
if (match) modes.set(match[2], match[1]);
|
|
341
|
+
}
|
|
342
|
+
const records = input.paths.map(
|
|
343
|
+
({ relativePath, blob }, index) => indexInfoRecord(modes.get(paths[index]) ?? "100644", requireObjectId(blob, `hydration ledger blob for ${relativePath}`), paths[index])
|
|
344
|
+
);
|
|
345
|
+
git(input.workspacePath, ["update-index", "-z", "--index-info"], `rewrite paths for the ${input.action}`, {
|
|
346
|
+
environment,
|
|
347
|
+
stdin: Buffer.concat(records)
|
|
348
|
+
});
|
|
349
|
+
const tree = requireObjectId(gitText(input.workspacePath, ["write-tree"], `write the ${input.action}`, { environment }), `${input.action} tree`);
|
|
350
|
+
return requireObjectId(
|
|
351
|
+
gitText(input.workspacePath, ["commit-tree", tree, "-p", input.parent, "-m", input.message], `commit the ${input.action}`),
|
|
352
|
+
`${input.action} commit`
|
|
353
|
+
);
|
|
354
|
+
} finally {
|
|
355
|
+
removeTemporaryIndex(indexPath);
|
|
257
356
|
}
|
|
258
|
-
return { stale: stale.sort(), cleared: cleared.sort() };
|
|
259
357
|
}
|
|
260
358
|
function nulRecords(content) {
|
|
261
359
|
const records = [];
|
|
@@ -289,12 +387,14 @@ function graftMountTree(input) {
|
|
|
289
387
|
stdin: removedPaths
|
|
290
388
|
});
|
|
291
389
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
390
|
+
if (input.mountTree !== null) {
|
|
391
|
+
git(
|
|
392
|
+
input.workspacePath,
|
|
393
|
+
["read-tree", "-i", `--prefix=${input.workspaceRelativePath}/`, input.mountTree],
|
|
394
|
+
"graft workspace projection mount tree",
|
|
395
|
+
{ environment }
|
|
396
|
+
);
|
|
397
|
+
}
|
|
298
398
|
return requireObjectId(
|
|
299
399
|
gitText(input.workspacePath, ["write-tree"], "write grafted workspace projection tree", { environment }),
|
|
300
400
|
"Grafted workspace projection tree"
|
|
@@ -303,25 +403,41 @@ function graftMountTree(input) {
|
|
|
303
403
|
removeTemporaryIndex(indexPath);
|
|
304
404
|
}
|
|
305
405
|
}
|
|
406
|
+
function mountSubtreeObjectId(workspacePath, revision, workspaceRelativePath, label) {
|
|
407
|
+
const spec = `${revision}:${workspaceRelativePath}`;
|
|
408
|
+
if (gitResult(workspacePath, ["cat-file", "-e", spec]).exitCode !== 0) return null;
|
|
409
|
+
const type = gitText(workspacePath, ["cat-file", "-t", spec], `inspect ${label}`);
|
|
410
|
+
if (type !== "tree") throw new Error(`${label} is not a directory at ${workspaceRelativePath}`);
|
|
411
|
+
return requireObjectId(gitText(workspacePath, ["rev-parse", spec], `resolve ${label}`), label);
|
|
412
|
+
}
|
|
306
413
|
function synthesizeOursCommit(input) {
|
|
307
414
|
const synthesized = synthesizeMountTree({
|
|
308
415
|
workspacePath: input.workspacePath,
|
|
309
416
|
sourcePath: input.mount.sourcePath,
|
|
310
417
|
sourceMode: input.mount.sourceMode
|
|
311
418
|
});
|
|
312
|
-
const
|
|
419
|
+
const classified = classifyHydrationLedger({
|
|
313
420
|
workspacePath: input.workspacePath,
|
|
314
421
|
workspaceRelativePath: input.workspaceRelativePath,
|
|
315
422
|
basisHead: input.basisHead,
|
|
316
423
|
mountTree: synthesized.tree,
|
|
317
424
|
blobs: synthesized.blobs,
|
|
318
|
-
|
|
425
|
+
projectedFiles: synthesized.projectedFiles,
|
|
426
|
+
hydrationLedger: input.hydrationLedger
|
|
319
427
|
});
|
|
320
|
-
if (
|
|
321
|
-
const
|
|
428
|
+
if (classified.stale.length > 0) return { staleRewritePaths: classified.stale, staleRewriteCleared: classified.cleared };
|
|
429
|
+
const mergeBase = hydrationLedgerMergeBase({
|
|
322
430
|
workspacePath: input.workspacePath,
|
|
323
431
|
workspaceRelativePath: input.workspaceRelativePath,
|
|
324
432
|
basisHead: input.basisHead,
|
|
433
|
+
merges: classified.merge,
|
|
434
|
+
mountId: input.mount.id,
|
|
435
|
+
attemptId: input.attemptId
|
|
436
|
+
});
|
|
437
|
+
const rootTree = graftMountTree({
|
|
438
|
+
workspacePath: input.workspacePath,
|
|
439
|
+
workspaceRelativePath: input.workspaceRelativePath,
|
|
440
|
+
basisHead: mergeBase,
|
|
325
441
|
mountTree: synthesized.tree
|
|
326
442
|
});
|
|
327
443
|
const message = JSON.stringify({
|
|
@@ -332,12 +448,18 @@ function synthesizeOursCommit(input) {
|
|
|
332
448
|
const oursCommit = requireObjectId(
|
|
333
449
|
gitText(
|
|
334
450
|
input.workspacePath,
|
|
335
|
-
["commit-tree", rootTree, "-p",
|
|
451
|
+
["commit-tree", rootTree, "-p", mergeBase, "-m", message],
|
|
336
452
|
`commit synthesized workspace projection for mount ${input.mount.id}`
|
|
337
453
|
),
|
|
338
454
|
`Synthesized workspace projection commit for mount ${input.mount.id}`
|
|
339
455
|
);
|
|
340
|
-
return {
|
|
456
|
+
return {
|
|
457
|
+
oursCommit,
|
|
458
|
+
mergeBase,
|
|
459
|
+
projectedFiles: synthesized.projectedFiles,
|
|
460
|
+
staleRewriteCleared: classified.cleared,
|
|
461
|
+
ledgerMerges: classified.merge
|
|
462
|
+
};
|
|
341
463
|
}
|
|
342
464
|
function mergeWorkspaceProjectionMount(input) {
|
|
343
465
|
const support = workspaceMergeProjectionSupport();
|
|
@@ -352,31 +474,69 @@ function mergeWorkspaceProjectionMount(input) {
|
|
|
352
474
|
workspaceRelativePath,
|
|
353
475
|
basisHead,
|
|
354
476
|
attemptId: input.attemptId,
|
|
355
|
-
...input.
|
|
477
|
+
...input.hydrationLedger ? { hydrationLedger: input.hydrationLedger } : {}
|
|
356
478
|
});
|
|
357
479
|
if ("staleRewritePaths" in synthesized) {
|
|
358
480
|
return { kind: "stale_rewrite", paths: synthesized.staleRewritePaths, staleRewriteCleared: synthesized.staleRewriteCleared };
|
|
359
481
|
}
|
|
360
|
-
const { oursCommit, projectedFiles, staleRewriteCleared } = synthesized;
|
|
482
|
+
const { oursCommit, mergeBase, projectedFiles, staleRewriteCleared, ledgerMerges } = synthesized;
|
|
483
|
+
const ledgerMergedPaths = ledgerMerges.map(({ relativePath }) => relativePath);
|
|
484
|
+
const theirsHead = hydrationLedgerTheirs({
|
|
485
|
+
workspacePath,
|
|
486
|
+
workspaceRelativePath,
|
|
487
|
+
currentHead,
|
|
488
|
+
merges: ledgerMerges,
|
|
489
|
+
mountId: input.mount.id,
|
|
490
|
+
attemptId: input.attemptId
|
|
491
|
+
});
|
|
361
492
|
const readAtMs = Date.now();
|
|
493
|
+
const inboundMountTree = mountSubtreeObjectId(
|
|
494
|
+
workspacePath,
|
|
495
|
+
theirsHead,
|
|
496
|
+
workspaceRelativePath,
|
|
497
|
+
`current workspace head subtree for mount ${input.mount.id}`
|
|
498
|
+
);
|
|
499
|
+
const inboundCommit = requireObjectId(
|
|
500
|
+
gitText(
|
|
501
|
+
workspacePath,
|
|
502
|
+
[
|
|
503
|
+
"commit-tree",
|
|
504
|
+
graftMountTree({ workspacePath, workspaceRelativePath, basisHead: mergeBase, mountTree: inboundMountTree }),
|
|
505
|
+
"-p",
|
|
506
|
+
mergeBase,
|
|
507
|
+
"-m",
|
|
508
|
+
JSON.stringify({ type: "workspace_projection_inbound", mountId: input.mount.id, attemptId: input.attemptId })
|
|
509
|
+
],
|
|
510
|
+
`commit inbound workspace projection for mount ${input.mount.id}`
|
|
511
|
+
),
|
|
512
|
+
`Inbound workspace projection commit for mount ${input.mount.id}`
|
|
513
|
+
);
|
|
362
514
|
const merged = gitResult(workspacePath, [
|
|
363
515
|
"merge-tree",
|
|
364
516
|
"--write-tree",
|
|
365
|
-
`--merge-base=${
|
|
517
|
+
`--merge-base=${mergeBase}`,
|
|
366
518
|
"--name-only",
|
|
367
519
|
"-z",
|
|
368
520
|
"--no-messages",
|
|
369
521
|
oursCommit,
|
|
370
|
-
|
|
522
|
+
inboundCommit
|
|
371
523
|
]);
|
|
372
524
|
if (merged.exitCode !== 0 && merged.exitCode !== 1) {
|
|
373
525
|
const detail = merged.stderr.toString().trim() || merged.stdout.toString().trim() || `git exited ${merged.exitCode}`;
|
|
374
526
|
throw new Error(`Merge workspace projection for mount ${input.mount.id}: ${detail}`);
|
|
375
527
|
}
|
|
376
528
|
const records = nulRecords(merged.stdout);
|
|
377
|
-
const
|
|
378
|
-
requireObjectId(
|
|
379
|
-
if (merged.exitCode === 0)
|
|
529
|
+
const mergedTree = records.shift()?.toString() ?? "";
|
|
530
|
+
requireObjectId(mergedTree, `Merged workspace projection tree for mount ${input.mount.id}`);
|
|
531
|
+
if (merged.exitCode === 0) {
|
|
532
|
+
const resultTree = graftMountTree({
|
|
533
|
+
workspacePath,
|
|
534
|
+
workspaceRelativePath,
|
|
535
|
+
basisHead: currentHead,
|
|
536
|
+
mountTree: mountSubtreeObjectId(workspacePath, mergedTree, workspaceRelativePath, `merged subtree for mount ${input.mount.id}`)
|
|
537
|
+
});
|
|
538
|
+
return { kind: "clean", resultTree, oursCommit, projectedFiles, readAtMs, staleRewriteCleared, ledgerMergedPaths };
|
|
539
|
+
}
|
|
380
540
|
const conflictPaths = records.map((record) => record.toString()).sort();
|
|
381
541
|
return {
|
|
382
542
|
kind: "conflict",
|
|
@@ -385,7 +545,8 @@ function mergeWorkspaceProjectionMount(input) {
|
|
|
385
545
|
error: `Workspace projection for mount ${input.mount.id} conflicted with the current workspace head`,
|
|
386
546
|
projectedFiles,
|
|
387
547
|
readAtMs,
|
|
388
|
-
staleRewriteCleared
|
|
548
|
+
staleRewriteCleared,
|
|
549
|
+
ledgerMergedPaths
|
|
389
550
|
};
|
|
390
551
|
}
|
|
391
552
|
function materializeWorkspaceProjectionTree(input) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { OperationEnvelope, OwnershipFence } from "@ricsam/r5d-api/runtime-protocol";
|
|
2
|
+
import { AdapterConfig, type ActionReceipt, type ExecutorCommand, type PollResult, type RunIdentity, type RunReceipt } from "./protocol";
|
|
3
|
+
/** Stateless one-request connections: disconnect/timeout never implies the operation did not happen. */
|
|
4
|
+
export declare class HostExecutorClient {
|
|
5
|
+
readonly config: ReturnType<typeof AdapterConfig.parse>;
|
|
6
|
+
constructor(config: AdapterConfig);
|
|
7
|
+
request<T = unknown>(command: ExecutorCommand): Promise<T>;
|
|
8
|
+
status(): Promise<unknown>;
|
|
9
|
+
setAuthority(fence: OwnershipFence, validUntil: number): Promise<unknown>;
|
|
10
|
+
start(operation: OperationEnvelope): Promise<RunReceipt>;
|
|
11
|
+
poll(run: RunIdentity, stdoutOffset?: number, stderrOffset?: number, maxBytes?: number): Promise<PollResult>;
|
|
12
|
+
write(run: RunIdentity, actionId: string, data: string, eof?: boolean): Promise<ActionReceipt>;
|
|
13
|
+
resize(run: RunIdentity, actionId: string, cols: number, rows: number): Promise<ActionReceipt>;
|
|
14
|
+
cancel(run: RunIdentity, actionId: string): Promise<ActionReceipt>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { HostExecutor } from "./executor";
|
|
2
|
+
import { type ExecutorConfig as Config } from "./protocol";
|
|
3
|
+
/** Start only from an independently supervised daemon entrypoint, never an adapter. */
|
|
4
|
+
export declare function startHostExecutor(input: Config): Promise<{
|
|
5
|
+
executor: HostExecutor;
|
|
6
|
+
socketPath: string;
|
|
7
|
+
close(): Promise<void>;
|
|
8
|
+
}>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type RuntimeHello } from "@ricsam/r5d-api/runtime-protocol";
|
|
2
|
+
import { ExecutorConfig, type ResolvedConfig } from "./protocol";
|
|
3
|
+
import { ExecutorStore } from "./storage";
|
|
4
|
+
type PtyModule = typeof import("node-pty");
|
|
5
|
+
export declare const tokenHash: (token: string) => string;
|
|
6
|
+
/** Resource owner, never instantiated in a replaceable adapter. No legacy worker singleton imports. */
|
|
7
|
+
export declare class HostExecutor {
|
|
8
|
+
private readonly nativePty?;
|
|
9
|
+
readonly config: ResolvedConfig;
|
|
10
|
+
readonly store: ExecutorStore;
|
|
11
|
+
readonly hello: RuntimeHello;
|
|
12
|
+
private readonly active;
|
|
13
|
+
private readonly leaseTimer;
|
|
14
|
+
private closing;
|
|
15
|
+
private poisoned;
|
|
16
|
+
private serial;
|
|
17
|
+
constructor(config: ExecutorConfig, nativePty?: PtyModule | undefined);
|
|
18
|
+
/** Single serialized command boundary, including controller promotions and dedupe checks. */
|
|
19
|
+
handle(input: unknown): Promise<unknown>;
|
|
20
|
+
private authenticate;
|
|
21
|
+
private authority;
|
|
22
|
+
private workerFence;
|
|
23
|
+
private authorize;
|
|
24
|
+
private receipt;
|
|
25
|
+
private dispatch;
|
|
26
|
+
private output;
|
|
27
|
+
private finish;
|
|
28
|
+
close(): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { startHostExecutor } from "./daemon";
|
|
2
|
+
export { HostExecutorClient } from "./client";
|
|
3
|
+
export { tokenHash } from "./executor";
|
|
4
|
+
export { AdapterConfig, ExecutorConfig, ExecutorCommand, ExecutorError, EXECUTOR_CAPABILITY, ShellPayload } from "./protocol";
|
|
5
|
+
export type { ActionReceipt, PollResult, RunIdentity, RunReceipt } from "./protocol";
|