@ricsam/r5d-worker 0.0.99 → 0.0.100
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 +37 -5
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/workspace-git-sync.cjs +54 -38
- package/dist/mjs/main.mjs +37 -5
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/workspace-git-sync.mjs +54 -38
- package/dist/types/main.d.ts +181 -0
- package/dist/types/workspace-git-sync.d.ts +25 -0
- package/package.json +1 -1
package/dist/cjs/main.cjs
CHANGED
|
@@ -97,9 +97,35 @@ function workerServerHeartbeatAction(input) {
|
|
|
97
97
|
if (input.watchdogSweepDelayed || input.probeElapsedMs === void 0) return "start_probe";
|
|
98
98
|
return input.probeElapsedMs >= input.timeoutMs ? "terminate" : "wait";
|
|
99
99
|
}
|
|
100
|
+
const WORKSPACE_HYDRATION_LEASE_OPERATION_PREFIX = "hydration:";
|
|
101
|
+
function createWorkspaceHydrationLeaseHooks(send, options = {}) {
|
|
102
|
+
const yieldToEventLoop = options.yieldToEventLoop ?? (() => new Promise((resolve) => setImmediate(resolve)));
|
|
103
|
+
const log = options.log ?? ((line) => process.stdout.write(`${line}
|
|
104
|
+
`));
|
|
105
|
+
const now = options.now ?? (() => performance.now());
|
|
106
|
+
const operationId = (context) => `${WORKSPACE_HYDRATION_LEASE_OPERATION_PREFIX}${context.transactionId}`;
|
|
107
|
+
const startedAtMs = /* @__PURE__ */ new Map();
|
|
108
|
+
return {
|
|
109
|
+
async beforeTransaction(context) {
|
|
110
|
+
send({ type: "heartbeat_lease", operationId: operationId(context), kind: "hydration", active: true });
|
|
111
|
+
startedAtMs.set(context.transactionId, now());
|
|
112
|
+
log(`[r5d-worker] hydrating ${context.mountIds.length} workspace mount(s) under hydration lease ${context.transactionId}`);
|
|
113
|
+
await yieldToEventLoop();
|
|
114
|
+
},
|
|
115
|
+
afterTransaction(context) {
|
|
116
|
+
const startedAt = startedAtMs.get(context.transactionId);
|
|
117
|
+
startedAtMs.delete(context.transactionId);
|
|
118
|
+
send({ type: "heartbeat_lease", operationId: operationId(context), kind: "hydration", active: false });
|
|
119
|
+
log(
|
|
120
|
+
`[r5d-worker] released hydration lease ${context.transactionId} after ${startedAt === void 0 ? "?" : Math.round(now() - startedAt)}ms`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
100
125
|
const workerLifecycleTestHarness = {
|
|
101
126
|
boundedWorkerLifecycleProgress,
|
|
102
|
-
workerServerHeartbeatAction
|
|
127
|
+
workerServerHeartbeatAction,
|
|
128
|
+
createWorkspaceHydrationLeaseHooks
|
|
103
129
|
};
|
|
104
130
|
class ProjectWorkspaceConfigurationDeferredError extends Error {
|
|
105
131
|
}
|
|
@@ -4370,6 +4396,7 @@ async function startWorker(options) {
|
|
|
4370
4396
|
error: error instanceof Error ? error.message : String(error)
|
|
4371
4397
|
};
|
|
4372
4398
|
};
|
|
4399
|
+
const workspaceHydrationTransactionHooks = createWorkspaceHydrationLeaseHooks((message) => sendWorkerMessageFromCurrentSource(ws, message));
|
|
4373
4400
|
const performWorkspaceSync = async (input) => {
|
|
4374
4401
|
if (!workspaceRemoteUrl || !workspaceCredentialHelper || !workspaceCredentialUsername || !workspaceGitIdentity) {
|
|
4375
4402
|
throw new Error("Worker workspace configuration has not been received");
|
|
@@ -4392,7 +4419,8 @@ async function startWorker(options) {
|
|
|
4392
4419
|
credentialHelper: workspaceCredentialHelper,
|
|
4393
4420
|
credentialUsername: workspaceCredentialUsername,
|
|
4394
4421
|
gitIdentity: workspaceGitIdentity,
|
|
4395
|
-
mounts: configuredMounts
|
|
4422
|
+
mounts: configuredMounts,
|
|
4423
|
+
hydrationHooks: workspaceHydrationTransactionHooks
|
|
4396
4424
|
});
|
|
4397
4425
|
await applyObservedProjectHeadsAfterInboundWorkspace(observedProjectHeads2, reset.activeMountIds, true);
|
|
4398
4426
|
return {
|
|
@@ -4446,7 +4474,8 @@ async function startWorker(options) {
|
|
|
4446
4474
|
assertStillAdmitted: input.assertStillAdmitted,
|
|
4447
4475
|
afterWorkspacePublished: outerRemediation ? void 0 : async ({ activeMountIds, publishedHead }) => {
|
|
4448
4476
|
await pushChangedProjectHeads(activeMountIds, publishedHead, publicationHeads, expectedMirrorHeads, inboundMoveMountIds);
|
|
4449
|
-
}
|
|
4477
|
+
},
|
|
4478
|
+
hydrationHooks: workspaceHydrationTransactionHooks
|
|
4450
4479
|
});
|
|
4451
4480
|
input.assertStillAdmitted?.();
|
|
4452
4481
|
if (result.initialPublicationMountIds && result.initialPublicationMountIds.length > 0) {
|
|
@@ -5040,7 +5069,10 @@ async function startWorker(options) {
|
|
|
5040
5069
|
sourcePath: recoverySourceOverrides.get(mount.id) ?? mount.sourcePath,
|
|
5041
5070
|
busy: mount.busyForRecovery ?? mount.busy
|
|
5042
5071
|
}));
|
|
5043
|
-
await (0, import_workspace_git_sync.recoverWorkspaceGitHydration)(workspaceShadowRoot, recoveryMounts, {
|
|
5072
|
+
await (0, import_workspace_git_sync.recoverWorkspaceGitHydration)(workspaceShadowRoot, recoveryMounts, {
|
|
5073
|
+
preserveStaleBases: true,
|
|
5074
|
+
hydrationHooks: workspaceHydrationTransactionHooks
|
|
5075
|
+
});
|
|
5044
5076
|
for (const project of message.projects) {
|
|
5045
5077
|
for (const { branchName } of project.preserveOnlyBranches) {
|
|
5046
5078
|
if (stillPendingCreatedBranches.has((0, import_workspace_automatic_sync_policy.pendingCreatedBranchKey)(project.projectId, branchName))) {
|
|
@@ -5084,7 +5116,7 @@ async function startWorker(options) {
|
|
|
5084
5116
|
// layouts can be marked ready. No process can resolve an unready
|
|
5085
5117
|
// target, and preserving the fetched outer tree here prevents clean
|
|
5086
5118
|
// checkout initialization from erasing synchronized dirt.
|
|
5087
|
-
{ ignoreBusy: true }
|
|
5119
|
+
{ ignoreBusy: true, hydrationHooks: workspaceHydrationTransactionHooks }
|
|
5088
5120
|
);
|
|
5089
5121
|
await ensureConfiguredProjects((progress) => {
|
|
5090
5122
|
sendLifecycleProgress({ phase: "configuring", operationId: configurationOperationId, ...progress });
|
package/dist/cjs/package.json
CHANGED
|
@@ -1601,56 +1601,67 @@ async function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
|
1601
1601
|
const busyMountIds = new Set(input.ignoreBusy ? [] : candidateMounts.filter((mount) => mount.busy?.()).map(({ id }) => id));
|
|
1602
1602
|
const requestedIds = new Set(input.mounts.map(({ id }) => id));
|
|
1603
1603
|
const requiredIds = new Set(requiredMounts.map(({ id }) => id));
|
|
1604
|
-
const
|
|
1605
|
-
const
|
|
1604
|
+
const hydrationMounts = input.mounts.filter(({ id }) => !busyMountIds.has(id));
|
|
1605
|
+
const advancedDurabilityMounts = durabilityMounts.filter(
|
|
1606
1606
|
({ id }) => !busyMountIds.has(id) && (requestedIds.has(id) || !requiredIds.has(id))
|
|
1607
1607
|
);
|
|
1608
1608
|
const unavailableRequiredIds = requiredMounts.filter(({ id }) => busyMountIds.has(id) || !requestedIds.has(id)).map(({ id }) => id);
|
|
1609
1609
|
const carriedMounts = carryMounts.filter(({ id }) => !busyMountIds.has(id));
|
|
1610
1610
|
return {
|
|
1611
1611
|
busyMountIds,
|
|
1612
|
-
hydrationMounts
|
|
1613
|
-
advancedDurabilityMounts
|
|
1612
|
+
hydrationMounts,
|
|
1613
|
+
advancedDurabilityMounts,
|
|
1614
1614
|
skippedMountIds: [.../* @__PURE__ */ new Set([...busyMountIds, ...unavailableRequiredIds])].sort(),
|
|
1615
|
-
targetReceipt: targetHead ? receiptWithMountsAtHead(receiptBeforeTransaction, targetHead, [...
|
|
1615
|
+
targetReceipt: targetHead ? receiptWithMountsAtHead(receiptBeforeTransaction, targetHead, [...advancedDurabilityMounts, ...carriedMounts], receiptMounts) : null
|
|
1616
1616
|
};
|
|
1617
1617
|
};
|
|
1618
1618
|
const noopCandidate = computeSelection();
|
|
1619
1619
|
if (noopCandidate.hydrationMounts.length === 0 && noopCandidate.advancedDurabilityMounts.length === 0 && noopCandidate.targetReceipt && lstatIfExists(hydrationTransactionPath(workspacePath)) === null && workspaceHydrationReceiptsEqual(receiptBeforeTransaction, noopCandidate.targetReceipt) && hydratedWorkspaceReceiptFileIsV3(workspacePath) && await workspaceBasisRefsAreCurrentAsync(workspacePath, noopCandidate.targetReceipt)) {
|
|
1620
1620
|
return { hydratedMountIds: [], skippedMountIds: noopCandidate.skippedMountIds };
|
|
1621
1621
|
}
|
|
1622
|
-
const
|
|
1623
|
-
const
|
|
1624
|
-
|
|
1622
|
+
const hooks = input.hydrationHooks;
|
|
1623
|
+
const context = {
|
|
1624
|
+
transactionId: crypto.randomUUID(),
|
|
1625
|
+
workspacePath,
|
|
1626
|
+
mountIds: [...new Set([...noopCandidate.hydrationMounts, ...noopCandidate.advancedDurabilityMounts].map(({ id }) => id))].sort()
|
|
1627
|
+
};
|
|
1628
|
+
if (hooks) await hooks.beforeTransaction(context);
|
|
1625
1629
|
try {
|
|
1626
|
-
const
|
|
1627
|
-
const
|
|
1628
|
-
|
|
1629
|
-
throw new Error("Workspace hydration did not include every required mount");
|
|
1630
|
-
}
|
|
1631
|
-
fsyncHydratedWorkspaceMounts(advancedDurabilityMounts, durabilityScopes);
|
|
1632
|
-
markHydrationTransactionDurable(workspacePath);
|
|
1633
|
-
if (targetReceipt) updateHydratedWorkspaceReceipt(workspacePath, targetReceipt);
|
|
1634
|
-
removeHydrationTransaction(workspacePath);
|
|
1635
|
-
return {
|
|
1636
|
-
hydratedMountIds: hydration.hydratedMountIds,
|
|
1637
|
-
skippedMountIds: selection.skippedMountIds
|
|
1638
|
-
};
|
|
1639
|
-
} catch (error) {
|
|
1640
|
-
const transactionPath = hydrationTransactionPath(workspacePath);
|
|
1641
|
-
if (lstatIfExists(transactionPath) && hydrationTransactionCommitted(transactionPath)) {
|
|
1642
|
-
throw error;
|
|
1643
|
-
}
|
|
1630
|
+
const selection = computeSelection();
|
|
1631
|
+
const { hydrationMounts, advancedDurabilityMounts, targetReceipt } = selection;
|
|
1632
|
+
const { manifest, durabilityScopes } = beginHydrationTransaction(workspacePath, hydrationMounts, targetReceipt);
|
|
1644
1633
|
try {
|
|
1645
|
-
|
|
1634
|
+
const hydration = hydrateWorkspaceGitMountsRaw(workspacePath, hydrationMounts, { ignoreBusy: true });
|
|
1635
|
+
const expectedHydratedIds = hydrationMounts.map(({ id }) => id).sort();
|
|
1636
|
+
if (hydration.skippedMountIds.length > 0 || hydration.hydratedMountIds.length !== expectedHydratedIds.length || hydration.hydratedMountIds.some((id, index) => id !== expectedHydratedIds[index])) {
|
|
1637
|
+
throw new Error("Workspace hydration did not include every required mount");
|
|
1638
|
+
}
|
|
1639
|
+
fsyncHydratedWorkspaceMounts(advancedDurabilityMounts, durabilityScopes);
|
|
1640
|
+
markHydrationTransactionDurable(workspacePath);
|
|
1641
|
+
if (targetReceipt) updateHydratedWorkspaceReceipt(workspacePath, targetReceipt);
|
|
1646
1642
|
removeHydrationTransaction(workspacePath);
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1643
|
+
return {
|
|
1644
|
+
hydratedMountIds: hydration.hydratedMountIds,
|
|
1645
|
+
skippedMountIds: selection.skippedMountIds
|
|
1646
|
+
};
|
|
1647
|
+
} catch (error) {
|
|
1648
|
+
const transactionPath = hydrationTransactionPath(workspacePath);
|
|
1649
|
+
if (lstatIfExists(transactionPath) && hydrationTransactionCommitted(transactionPath)) {
|
|
1650
|
+
throw error;
|
|
1651
|
+
}
|
|
1652
|
+
try {
|
|
1653
|
+
restoreHydrationTransaction(workspacePath, manifest);
|
|
1654
|
+
removeHydrationTransaction(workspacePath);
|
|
1655
|
+
} catch (restoreError) {
|
|
1656
|
+
throw new AggregateError(
|
|
1657
|
+
[error, restoreError],
|
|
1658
|
+
`Workspace hydration failed and its durable snapshot could not be restored: ${error instanceof Error ? error.message : String(error)}`
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
throw error;
|
|
1652
1662
|
}
|
|
1653
|
-
|
|
1663
|
+
} finally {
|
|
1664
|
+
hooks?.afterTransaction(context);
|
|
1654
1665
|
}
|
|
1655
1666
|
}
|
|
1656
1667
|
async function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
|
|
@@ -1662,7 +1673,8 @@ async function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
|
|
|
1662
1673
|
return hydrateWorkspaceGitMountsTransactionally({
|
|
1663
1674
|
workspacePath,
|
|
1664
1675
|
mounts,
|
|
1665
|
-
ignoreBusy: options.ignoreBusy
|
|
1676
|
+
ignoreBusy: options.ignoreBusy,
|
|
1677
|
+
hydrationHooks: options.hydrationHooks
|
|
1666
1678
|
});
|
|
1667
1679
|
}
|
|
1668
1680
|
async function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
|
|
@@ -1702,7 +1714,8 @@ async function recoverWorkspaceGitHydration(workspacePath, mounts, options = {})
|
|
|
1702
1714
|
receiptMounts: configuredBasisMounts,
|
|
1703
1715
|
requiredMounts: hydrationTargets,
|
|
1704
1716
|
ignoreBusy: options.ignoreBusy,
|
|
1705
|
-
recordCurrentHead: true
|
|
1717
|
+
recordCurrentHead: true,
|
|
1718
|
+
hydrationHooks: options.hydrationHooks
|
|
1706
1719
|
});
|
|
1707
1720
|
void hydration;
|
|
1708
1721
|
}
|
|
@@ -1753,7 +1766,8 @@ async function resetWorkspaceGit(input) {
|
|
|
1753
1766
|
durabilityMounts: [...requiredLiveMounts, ...selected.tombstones, ...durablyAbsentMounts],
|
|
1754
1767
|
receiptMounts: input.mounts,
|
|
1755
1768
|
requiredMounts: requiredLiveMounts,
|
|
1756
|
-
recordCurrentHead: true
|
|
1769
|
+
recordCurrentHead: true,
|
|
1770
|
+
hydrationHooks: input.hydrationHooks
|
|
1757
1771
|
});
|
|
1758
1772
|
const requiredLiveIds = requiredLiveMounts.map(({ id }) => id).sort();
|
|
1759
1773
|
const resetHead = revParse(workspacePath, "HEAD");
|
|
@@ -2008,7 +2022,8 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
2008
2022
|
await recoverWorkspaceGitHydration(workspacePath, input.mounts, {
|
|
2009
2023
|
...deferredMountIds.size > 0 ? { deferMountIds: deferredMountIds } : {},
|
|
2010
2024
|
preserveResolutionInProgress,
|
|
2011
|
-
preserveStaleBases: true
|
|
2025
|
+
preserveStaleBases: true,
|
|
2026
|
+
hydrationHooks: input.hydrationHooks
|
|
2012
2027
|
});
|
|
2013
2028
|
const selected = activeMounts(input.mounts);
|
|
2014
2029
|
const projectionMutationTokens = new Map(
|
|
@@ -2134,7 +2149,8 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
2134
2149
|
carryMounts: uncoveredCompletionMounts.filter(({ id }) => unchangedMountIds.has(id)),
|
|
2135
2150
|
receiptMounts: completionReceiptMounts,
|
|
2136
2151
|
requiredMounts: uncoveredRequiredLiveMounts.filter(({ id }) => !unchangedMountIds.has(id)),
|
|
2137
|
-
recordCurrentHead: true
|
|
2152
|
+
recordCurrentHead: true,
|
|
2153
|
+
hydrationHooks: input.hydrationHooks
|
|
2138
2154
|
});
|
|
2139
2155
|
for (const id of hydration.skippedMountIds) cycleSkippedMountIds.add(id);
|
|
2140
2156
|
refreshCycleSkippedMounts(completionReceiptMounts);
|
package/dist/mjs/main.mjs
CHANGED
|
@@ -104,9 +104,35 @@ function workerServerHeartbeatAction(input) {
|
|
|
104
104
|
if (input.watchdogSweepDelayed || input.probeElapsedMs === void 0) return "start_probe";
|
|
105
105
|
return input.probeElapsedMs >= input.timeoutMs ? "terminate" : "wait";
|
|
106
106
|
}
|
|
107
|
+
const WORKSPACE_HYDRATION_LEASE_OPERATION_PREFIX = "hydration:";
|
|
108
|
+
function createWorkspaceHydrationLeaseHooks(send, options = {}) {
|
|
109
|
+
const yieldToEventLoop = options.yieldToEventLoop ?? (() => new Promise((resolve) => setImmediate(resolve)));
|
|
110
|
+
const log = options.log ?? ((line) => process.stdout.write(`${line}
|
|
111
|
+
`));
|
|
112
|
+
const now = options.now ?? (() => performance.now());
|
|
113
|
+
const operationId = (context) => `${WORKSPACE_HYDRATION_LEASE_OPERATION_PREFIX}${context.transactionId}`;
|
|
114
|
+
const startedAtMs = /* @__PURE__ */ new Map();
|
|
115
|
+
return {
|
|
116
|
+
async beforeTransaction(context) {
|
|
117
|
+
send({ type: "heartbeat_lease", operationId: operationId(context), kind: "hydration", active: true });
|
|
118
|
+
startedAtMs.set(context.transactionId, now());
|
|
119
|
+
log(`[r5d-worker] hydrating ${context.mountIds.length} workspace mount(s) under hydration lease ${context.transactionId}`);
|
|
120
|
+
await yieldToEventLoop();
|
|
121
|
+
},
|
|
122
|
+
afterTransaction(context) {
|
|
123
|
+
const startedAt = startedAtMs.get(context.transactionId);
|
|
124
|
+
startedAtMs.delete(context.transactionId);
|
|
125
|
+
send({ type: "heartbeat_lease", operationId: operationId(context), kind: "hydration", active: false });
|
|
126
|
+
log(
|
|
127
|
+
`[r5d-worker] released hydration lease ${context.transactionId} after ${startedAt === void 0 ? "?" : Math.round(now() - startedAt)}ms`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
107
132
|
const workerLifecycleTestHarness = {
|
|
108
133
|
boundedWorkerLifecycleProgress,
|
|
109
|
-
workerServerHeartbeatAction
|
|
134
|
+
workerServerHeartbeatAction,
|
|
135
|
+
createWorkspaceHydrationLeaseHooks
|
|
110
136
|
};
|
|
111
137
|
class ProjectWorkspaceConfigurationDeferredError extends Error {
|
|
112
138
|
}
|
|
@@ -4377,6 +4403,7 @@ async function startWorker(options) {
|
|
|
4377
4403
|
error: error instanceof Error ? error.message : String(error)
|
|
4378
4404
|
};
|
|
4379
4405
|
};
|
|
4406
|
+
const workspaceHydrationTransactionHooks = createWorkspaceHydrationLeaseHooks((message) => sendWorkerMessageFromCurrentSource(ws, message));
|
|
4380
4407
|
const performWorkspaceSync = async (input) => {
|
|
4381
4408
|
if (!workspaceRemoteUrl || !workspaceCredentialHelper || !workspaceCredentialUsername || !workspaceGitIdentity) {
|
|
4382
4409
|
throw new Error("Worker workspace configuration has not been received");
|
|
@@ -4399,7 +4426,8 @@ async function startWorker(options) {
|
|
|
4399
4426
|
credentialHelper: workspaceCredentialHelper,
|
|
4400
4427
|
credentialUsername: workspaceCredentialUsername,
|
|
4401
4428
|
gitIdentity: workspaceGitIdentity,
|
|
4402
|
-
mounts: configuredMounts
|
|
4429
|
+
mounts: configuredMounts,
|
|
4430
|
+
hydrationHooks: workspaceHydrationTransactionHooks
|
|
4403
4431
|
});
|
|
4404
4432
|
await applyObservedProjectHeadsAfterInboundWorkspace(observedProjectHeads2, reset.activeMountIds, true);
|
|
4405
4433
|
return {
|
|
@@ -4453,7 +4481,8 @@ async function startWorker(options) {
|
|
|
4453
4481
|
assertStillAdmitted: input.assertStillAdmitted,
|
|
4454
4482
|
afterWorkspacePublished: outerRemediation ? void 0 : async ({ activeMountIds, publishedHead }) => {
|
|
4455
4483
|
await pushChangedProjectHeads(activeMountIds, publishedHead, publicationHeads, expectedMirrorHeads, inboundMoveMountIds);
|
|
4456
|
-
}
|
|
4484
|
+
},
|
|
4485
|
+
hydrationHooks: workspaceHydrationTransactionHooks
|
|
4457
4486
|
});
|
|
4458
4487
|
input.assertStillAdmitted?.();
|
|
4459
4488
|
if (result.initialPublicationMountIds && result.initialPublicationMountIds.length > 0) {
|
|
@@ -5047,7 +5076,10 @@ async function startWorker(options) {
|
|
|
5047
5076
|
sourcePath: recoverySourceOverrides.get(mount.id) ?? mount.sourcePath,
|
|
5048
5077
|
busy: mount.busyForRecovery ?? mount.busy
|
|
5049
5078
|
}));
|
|
5050
|
-
await recoverWorkspaceGitHydration(workspaceShadowRoot, recoveryMounts, {
|
|
5079
|
+
await recoverWorkspaceGitHydration(workspaceShadowRoot, recoveryMounts, {
|
|
5080
|
+
preserveStaleBases: true,
|
|
5081
|
+
hydrationHooks: workspaceHydrationTransactionHooks
|
|
5082
|
+
});
|
|
5051
5083
|
for (const project of message.projects) {
|
|
5052
5084
|
for (const { branchName } of project.preserveOnlyBranches) {
|
|
5053
5085
|
if (stillPendingCreatedBranches.has(pendingCreatedBranchKey(project.projectId, branchName))) {
|
|
@@ -5091,7 +5123,7 @@ async function startWorker(options) {
|
|
|
5091
5123
|
// layouts can be marked ready. No process can resolve an unready
|
|
5092
5124
|
// target, and preserving the fetched outer tree here prevents clean
|
|
5093
5125
|
// checkout initialization from erasing synchronized dirt.
|
|
5094
|
-
{ ignoreBusy: true }
|
|
5126
|
+
{ ignoreBusy: true, hydrationHooks: workspaceHydrationTransactionHooks }
|
|
5095
5127
|
);
|
|
5096
5128
|
await ensureConfiguredProjects((progress) => {
|
|
5097
5129
|
sendLifecycleProgress({ phase: "configuring", operationId: configurationOperationId, ...progress });
|
package/dist/mjs/package.json
CHANGED
|
@@ -1564,56 +1564,67 @@ async function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
|
1564
1564
|
const busyMountIds = new Set(input.ignoreBusy ? [] : candidateMounts.filter((mount) => mount.busy?.()).map(({ id }) => id));
|
|
1565
1565
|
const requestedIds = new Set(input.mounts.map(({ id }) => id));
|
|
1566
1566
|
const requiredIds = new Set(requiredMounts.map(({ id }) => id));
|
|
1567
|
-
const
|
|
1568
|
-
const
|
|
1567
|
+
const hydrationMounts = input.mounts.filter(({ id }) => !busyMountIds.has(id));
|
|
1568
|
+
const advancedDurabilityMounts = durabilityMounts.filter(
|
|
1569
1569
|
({ id }) => !busyMountIds.has(id) && (requestedIds.has(id) || !requiredIds.has(id))
|
|
1570
1570
|
);
|
|
1571
1571
|
const unavailableRequiredIds = requiredMounts.filter(({ id }) => busyMountIds.has(id) || !requestedIds.has(id)).map(({ id }) => id);
|
|
1572
1572
|
const carriedMounts = carryMounts.filter(({ id }) => !busyMountIds.has(id));
|
|
1573
1573
|
return {
|
|
1574
1574
|
busyMountIds,
|
|
1575
|
-
hydrationMounts
|
|
1576
|
-
advancedDurabilityMounts
|
|
1575
|
+
hydrationMounts,
|
|
1576
|
+
advancedDurabilityMounts,
|
|
1577
1577
|
skippedMountIds: [.../* @__PURE__ */ new Set([...busyMountIds, ...unavailableRequiredIds])].sort(),
|
|
1578
|
-
targetReceipt: targetHead ? receiptWithMountsAtHead(receiptBeforeTransaction, targetHead, [...
|
|
1578
|
+
targetReceipt: targetHead ? receiptWithMountsAtHead(receiptBeforeTransaction, targetHead, [...advancedDurabilityMounts, ...carriedMounts], receiptMounts) : null
|
|
1579
1579
|
};
|
|
1580
1580
|
};
|
|
1581
1581
|
const noopCandidate = computeSelection();
|
|
1582
1582
|
if (noopCandidate.hydrationMounts.length === 0 && noopCandidate.advancedDurabilityMounts.length === 0 && noopCandidate.targetReceipt && lstatIfExists(hydrationTransactionPath(workspacePath)) === null && workspaceHydrationReceiptsEqual(receiptBeforeTransaction, noopCandidate.targetReceipt) && hydratedWorkspaceReceiptFileIsV3(workspacePath) && await workspaceBasisRefsAreCurrentAsync(workspacePath, noopCandidate.targetReceipt)) {
|
|
1583
1583
|
return { hydratedMountIds: [], skippedMountIds: noopCandidate.skippedMountIds };
|
|
1584
1584
|
}
|
|
1585
|
-
const
|
|
1586
|
-
const
|
|
1587
|
-
|
|
1585
|
+
const hooks = input.hydrationHooks;
|
|
1586
|
+
const context = {
|
|
1587
|
+
transactionId: crypto.randomUUID(),
|
|
1588
|
+
workspacePath,
|
|
1589
|
+
mountIds: [...new Set([...noopCandidate.hydrationMounts, ...noopCandidate.advancedDurabilityMounts].map(({ id }) => id))].sort()
|
|
1590
|
+
};
|
|
1591
|
+
if (hooks) await hooks.beforeTransaction(context);
|
|
1588
1592
|
try {
|
|
1589
|
-
const
|
|
1590
|
-
const
|
|
1591
|
-
|
|
1592
|
-
throw new Error("Workspace hydration did not include every required mount");
|
|
1593
|
-
}
|
|
1594
|
-
fsyncHydratedWorkspaceMounts(advancedDurabilityMounts, durabilityScopes);
|
|
1595
|
-
markHydrationTransactionDurable(workspacePath);
|
|
1596
|
-
if (targetReceipt) updateHydratedWorkspaceReceipt(workspacePath, targetReceipt);
|
|
1597
|
-
removeHydrationTransaction(workspacePath);
|
|
1598
|
-
return {
|
|
1599
|
-
hydratedMountIds: hydration.hydratedMountIds,
|
|
1600
|
-
skippedMountIds: selection.skippedMountIds
|
|
1601
|
-
};
|
|
1602
|
-
} catch (error) {
|
|
1603
|
-
const transactionPath = hydrationTransactionPath(workspacePath);
|
|
1604
|
-
if (lstatIfExists(transactionPath) && hydrationTransactionCommitted(transactionPath)) {
|
|
1605
|
-
throw error;
|
|
1606
|
-
}
|
|
1593
|
+
const selection = computeSelection();
|
|
1594
|
+
const { hydrationMounts, advancedDurabilityMounts, targetReceipt } = selection;
|
|
1595
|
+
const { manifest, durabilityScopes } = beginHydrationTransaction(workspacePath, hydrationMounts, targetReceipt);
|
|
1607
1596
|
try {
|
|
1608
|
-
|
|
1597
|
+
const hydration = hydrateWorkspaceGitMountsRaw(workspacePath, hydrationMounts, { ignoreBusy: true });
|
|
1598
|
+
const expectedHydratedIds = hydrationMounts.map(({ id }) => id).sort();
|
|
1599
|
+
if (hydration.skippedMountIds.length > 0 || hydration.hydratedMountIds.length !== expectedHydratedIds.length || hydration.hydratedMountIds.some((id, index) => id !== expectedHydratedIds[index])) {
|
|
1600
|
+
throw new Error("Workspace hydration did not include every required mount");
|
|
1601
|
+
}
|
|
1602
|
+
fsyncHydratedWorkspaceMounts(advancedDurabilityMounts, durabilityScopes);
|
|
1603
|
+
markHydrationTransactionDurable(workspacePath);
|
|
1604
|
+
if (targetReceipt) updateHydratedWorkspaceReceipt(workspacePath, targetReceipt);
|
|
1609
1605
|
removeHydrationTransaction(workspacePath);
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1606
|
+
return {
|
|
1607
|
+
hydratedMountIds: hydration.hydratedMountIds,
|
|
1608
|
+
skippedMountIds: selection.skippedMountIds
|
|
1609
|
+
};
|
|
1610
|
+
} catch (error) {
|
|
1611
|
+
const transactionPath = hydrationTransactionPath(workspacePath);
|
|
1612
|
+
if (lstatIfExists(transactionPath) && hydrationTransactionCommitted(transactionPath)) {
|
|
1613
|
+
throw error;
|
|
1614
|
+
}
|
|
1615
|
+
try {
|
|
1616
|
+
restoreHydrationTransaction(workspacePath, manifest);
|
|
1617
|
+
removeHydrationTransaction(workspacePath);
|
|
1618
|
+
} catch (restoreError) {
|
|
1619
|
+
throw new AggregateError(
|
|
1620
|
+
[error, restoreError],
|
|
1621
|
+
`Workspace hydration failed and its durable snapshot could not be restored: ${error instanceof Error ? error.message : String(error)}`
|
|
1622
|
+
);
|
|
1623
|
+
}
|
|
1624
|
+
throw error;
|
|
1615
1625
|
}
|
|
1616
|
-
|
|
1626
|
+
} finally {
|
|
1627
|
+
hooks?.afterTransaction(context);
|
|
1617
1628
|
}
|
|
1618
1629
|
}
|
|
1619
1630
|
async function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
|
|
@@ -1625,7 +1636,8 @@ async function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
|
|
|
1625
1636
|
return hydrateWorkspaceGitMountsTransactionally({
|
|
1626
1637
|
workspacePath,
|
|
1627
1638
|
mounts,
|
|
1628
|
-
ignoreBusy: options.ignoreBusy
|
|
1639
|
+
ignoreBusy: options.ignoreBusy,
|
|
1640
|
+
hydrationHooks: options.hydrationHooks
|
|
1629
1641
|
});
|
|
1630
1642
|
}
|
|
1631
1643
|
async function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
|
|
@@ -1665,7 +1677,8 @@ async function recoverWorkspaceGitHydration(workspacePath, mounts, options = {})
|
|
|
1665
1677
|
receiptMounts: configuredBasisMounts,
|
|
1666
1678
|
requiredMounts: hydrationTargets,
|
|
1667
1679
|
ignoreBusy: options.ignoreBusy,
|
|
1668
|
-
recordCurrentHead: true
|
|
1680
|
+
recordCurrentHead: true,
|
|
1681
|
+
hydrationHooks: options.hydrationHooks
|
|
1669
1682
|
});
|
|
1670
1683
|
void hydration;
|
|
1671
1684
|
}
|
|
@@ -1716,7 +1729,8 @@ async function resetWorkspaceGit(input) {
|
|
|
1716
1729
|
durabilityMounts: [...requiredLiveMounts, ...selected.tombstones, ...durablyAbsentMounts],
|
|
1717
1730
|
receiptMounts: input.mounts,
|
|
1718
1731
|
requiredMounts: requiredLiveMounts,
|
|
1719
|
-
recordCurrentHead: true
|
|
1732
|
+
recordCurrentHead: true,
|
|
1733
|
+
hydrationHooks: input.hydrationHooks
|
|
1720
1734
|
});
|
|
1721
1735
|
const requiredLiveIds = requiredLiveMounts.map(({ id }) => id).sort();
|
|
1722
1736
|
const resetHead = revParse(workspacePath, "HEAD");
|
|
@@ -1971,7 +1985,8 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1971
1985
|
await recoverWorkspaceGitHydration(workspacePath, input.mounts, {
|
|
1972
1986
|
...deferredMountIds.size > 0 ? { deferMountIds: deferredMountIds } : {},
|
|
1973
1987
|
preserveResolutionInProgress,
|
|
1974
|
-
preserveStaleBases: true
|
|
1988
|
+
preserveStaleBases: true,
|
|
1989
|
+
hydrationHooks: input.hydrationHooks
|
|
1975
1990
|
});
|
|
1976
1991
|
const selected = activeMounts(input.mounts);
|
|
1977
1992
|
const projectionMutationTokens = new Map(
|
|
@@ -2097,7 +2112,8 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
2097
2112
|
carryMounts: uncoveredCompletionMounts.filter(({ id }) => unchangedMountIds.has(id)),
|
|
2098
2113
|
receiptMounts: completionReceiptMounts,
|
|
2099
2114
|
requiredMounts: uncoveredRequiredLiveMounts.filter(({ id }) => !unchangedMountIds.has(id)),
|
|
2100
|
-
recordCurrentHead: true
|
|
2115
|
+
recordCurrentHead: true,
|
|
2116
|
+
hydrationHooks: input.hydrationHooks
|
|
2101
2117
|
});
|
|
2102
2118
|
for (const id of hydration.skippedMountIds) cycleSkippedMountIds.add(id);
|
|
2103
2119
|
refreshCycleSkippedMounts(completionReceiptMounts);
|
package/dist/types/main.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Database } from "bun:sqlite";
|
|
3
3
|
import type { WorkerGitIdentity } from "./git-identity";
|
|
4
4
|
import { configureGitHubRegistryAuthFiles, type PreparedPrivateAuthFileGeneration } from "./registry-auth";
|
|
5
|
+
import { type WorkspaceHydrationTransactionHooks } from "./workspace-git-sync";
|
|
5
6
|
type WorkerProjectConfig = {
|
|
6
7
|
projectId: string;
|
|
7
8
|
checkoutPathSegments: [namespace: string, project: string];
|
|
@@ -23,6 +24,11 @@ type WorkerProjectConfig = {
|
|
|
23
24
|
baseCommitHash: string;
|
|
24
25
|
}>;
|
|
25
26
|
};
|
|
27
|
+
type WorkerAheadOfOriginBranch = {
|
|
28
|
+
projectId: string;
|
|
29
|
+
branchName: string;
|
|
30
|
+
ahead: number;
|
|
31
|
+
};
|
|
26
32
|
type WorkerLifecycleProgress = {
|
|
27
33
|
phase: "starting" | "recovering" | "configuring" | "syncing" | "ready" | "failed";
|
|
28
34
|
operationId: string;
|
|
@@ -39,9 +45,28 @@ declare function workerServerHeartbeatAction(input: {
|
|
|
39
45
|
timeoutMs: number;
|
|
40
46
|
watchdogSweepDelayed: boolean;
|
|
41
47
|
}): "wait" | "start_probe" | "terminate";
|
|
48
|
+
/**
|
|
49
|
+
* A hydration transaction blocks this event loop until it ends, so no pong,
|
|
50
|
+
* progress, or result can leave the process while it runs; on a slow disk
|
|
51
|
+
* that is hours. Declare a hydration heartbeat lease first: the server keeps
|
|
52
|
+
* the control socket open and defers the affected request deadlines while the
|
|
53
|
+
* lease is active instead of reading the silence as a dead worker, which is
|
|
54
|
+
* what turned one slow hydration into a mandatory canonical reset (incident
|
|
55
|
+
* 3bccab03). Bun writes the frame to the socket synchronously; the extra
|
|
56
|
+
* event-loop turn lets control messages that were already queued run before
|
|
57
|
+
* the busy observation that precedes the transaction body.
|
|
58
|
+
*/
|
|
59
|
+
declare function createWorkspaceHydrationLeaseHooks(send: (message: Extract<WorkerClientMessage, {
|
|
60
|
+
type: "heartbeat_lease";
|
|
61
|
+
}>) => void, options?: {
|
|
62
|
+
yieldToEventLoop?: () => Promise<void>;
|
|
63
|
+
log?: (line: string) => void;
|
|
64
|
+
now?: () => number;
|
|
65
|
+
}): WorkspaceHydrationTransactionHooks;
|
|
42
66
|
export declare const workerLifecycleTestHarness: {
|
|
43
67
|
boundedWorkerLifecycleProgress: typeof boundedWorkerLifecycleProgress;
|
|
44
68
|
workerServerHeartbeatAction: typeof workerServerHeartbeatAction;
|
|
69
|
+
createWorkspaceHydrationLeaseHooks: typeof createWorkspaceHydrationLeaseHooks;
|
|
45
70
|
};
|
|
46
71
|
type WorkspaceSyncTrigger = {
|
|
47
72
|
type: "connect" | "inbound_head" | "write" | "edit" | "shell_inline" | "process_terminal" | "process_cancel" | "periodic" | "manual" | "remediation" | "remediation_confirm" | "remediation_reset";
|
|
@@ -103,6 +128,144 @@ export type WorkerSessionTarget = {
|
|
|
103
128
|
ownerUserId: string;
|
|
104
129
|
rootProfile: "visible_projects" | "canonical_sync";
|
|
105
130
|
};
|
|
131
|
+
type WorkerClientMessage = {
|
|
132
|
+
type: "hello";
|
|
133
|
+
hostInfo?: Record<string, unknown>;
|
|
134
|
+
startupReady?: boolean;
|
|
135
|
+
} | {
|
|
136
|
+
type: "worker_progress";
|
|
137
|
+
progress: WorkerLifecycleProgress;
|
|
138
|
+
} | {
|
|
139
|
+
type: "heartbeat_lease";
|
|
140
|
+
operationId: string;
|
|
141
|
+
active: boolean;
|
|
142
|
+
/** A hydration transaction may block the worker for hours; the server bounds it separately from preparation leases. */
|
|
143
|
+
kind?: "hydration";
|
|
144
|
+
} | {
|
|
145
|
+
type: "startup_ready";
|
|
146
|
+
} | {
|
|
147
|
+
type: "active_processes";
|
|
148
|
+
processes: Array<{
|
|
149
|
+
runId: string;
|
|
150
|
+
target: WorkerSessionTarget;
|
|
151
|
+
sessionId: string;
|
|
152
|
+
platform: string;
|
|
153
|
+
arch: string;
|
|
154
|
+
mode: "foreground" | "detached";
|
|
155
|
+
pid?: number;
|
|
156
|
+
processGroupId?: number;
|
|
157
|
+
credentialId?: string;
|
|
158
|
+
argv: string[];
|
|
159
|
+
command: string;
|
|
160
|
+
cwd?: string;
|
|
161
|
+
startedAt: string;
|
|
162
|
+
interactive?: boolean;
|
|
163
|
+
workspaceEffect?: "none";
|
|
164
|
+
}>;
|
|
165
|
+
} | {
|
|
166
|
+
type: "pty_opened";
|
|
167
|
+
requestId: string;
|
|
168
|
+
ptyId: string;
|
|
169
|
+
} | {
|
|
170
|
+
type: "pty_output";
|
|
171
|
+
ptyId: string;
|
|
172
|
+
data: string;
|
|
173
|
+
} | {
|
|
174
|
+
type: "pty_exit";
|
|
175
|
+
ptyId: string;
|
|
176
|
+
exitCode: number;
|
|
177
|
+
signal?: number | string;
|
|
178
|
+
} | {
|
|
179
|
+
type: "pty_error";
|
|
180
|
+
ptyId: string;
|
|
181
|
+
requestId?: string;
|
|
182
|
+
error: string;
|
|
183
|
+
} | {
|
|
184
|
+
type: "operation_result";
|
|
185
|
+
requestId: string;
|
|
186
|
+
result?: WorkerOperationResult;
|
|
187
|
+
error?: string;
|
|
188
|
+
} | {
|
|
189
|
+
type: "exec_result";
|
|
190
|
+
requestId: string;
|
|
191
|
+
stdout: string;
|
|
192
|
+
stderr: string;
|
|
193
|
+
exitCode: number;
|
|
194
|
+
durationMs?: number;
|
|
195
|
+
error?: string;
|
|
196
|
+
} | {
|
|
197
|
+
type: "exec_accepted";
|
|
198
|
+
requestId: string;
|
|
199
|
+
runId: string;
|
|
200
|
+
} | {
|
|
201
|
+
type: "exec_started";
|
|
202
|
+
requestId: string;
|
|
203
|
+
runId: string;
|
|
204
|
+
cwd: string;
|
|
205
|
+
pid?: number;
|
|
206
|
+
processGroupId?: number;
|
|
207
|
+
} | {
|
|
208
|
+
type: "exec_start_error";
|
|
209
|
+
requestId: string;
|
|
210
|
+
runId: string;
|
|
211
|
+
error: string;
|
|
212
|
+
} | {
|
|
213
|
+
type: "exec_output";
|
|
214
|
+
runId: string;
|
|
215
|
+
stream: "stdout" | "stderr";
|
|
216
|
+
data: string;
|
|
217
|
+
} | {
|
|
218
|
+
type: "exec_exit";
|
|
219
|
+
runId: string;
|
|
220
|
+
exitCode: number;
|
|
221
|
+
durationMs?: number;
|
|
222
|
+
timedOut?: boolean;
|
|
223
|
+
workspaceEffect?: "none";
|
|
224
|
+
canonicalWorkspaceHead?: string;
|
|
225
|
+
} | {
|
|
226
|
+
type: "exec_error";
|
|
227
|
+
runId: string;
|
|
228
|
+
error: string;
|
|
229
|
+
durationMs?: number;
|
|
230
|
+
workspaceEffect?: "none";
|
|
231
|
+
canonicalWorkspaceHead?: string;
|
|
232
|
+
} | {
|
|
233
|
+
type: "cancel_result";
|
|
234
|
+
requestId: string;
|
|
235
|
+
runId: string;
|
|
236
|
+
cancelled: boolean;
|
|
237
|
+
message?: string;
|
|
238
|
+
} | {
|
|
239
|
+
type: "pong";
|
|
240
|
+
} | {
|
|
241
|
+
type: "workspace_configured";
|
|
242
|
+
requestId: string;
|
|
243
|
+
result: WorkspaceSyncResult;
|
|
244
|
+
pendingCheckouts: Array<{
|
|
245
|
+
projectId: string;
|
|
246
|
+
branchName: string;
|
|
247
|
+
}>;
|
|
248
|
+
aheadOfOriginBranches: WorkerAheadOfOriginBranch[];
|
|
249
|
+
deferredWorkspaceSyncForIncidentId?: string;
|
|
250
|
+
} | {
|
|
251
|
+
type: "workspace_sync_started";
|
|
252
|
+
attemptId: string;
|
|
253
|
+
} | {
|
|
254
|
+
type: "workspace_sync_result";
|
|
255
|
+
requestId?: string;
|
|
256
|
+
result: WorkspaceSyncResult;
|
|
257
|
+
} | {
|
|
258
|
+
type: "session_artifacts_sync_result";
|
|
259
|
+
requestId: string;
|
|
260
|
+
sessionId: string;
|
|
261
|
+
error?: string;
|
|
262
|
+
} | {
|
|
263
|
+
type: "update_clis_result";
|
|
264
|
+
requestId: string;
|
|
265
|
+
installedWorkerVersion?: string;
|
|
266
|
+
installedR5dctlVersion?: string;
|
|
267
|
+
error?: string;
|
|
268
|
+
};
|
|
106
269
|
type WorkerServerMessage = {
|
|
107
270
|
type: "connected";
|
|
108
271
|
label: string;
|
|
@@ -371,6 +534,23 @@ type WorkerViewFileBytesResult = {
|
|
|
371
534
|
width: number;
|
|
372
535
|
height: number;
|
|
373
536
|
};
|
|
537
|
+
type WorkerExecStdinAck = {
|
|
538
|
+
type: "exec_stdin";
|
|
539
|
+
runId: string;
|
|
540
|
+
bytesWritten: number;
|
|
541
|
+
eof: boolean;
|
|
542
|
+
};
|
|
543
|
+
type WorkerCreateProjectBranchResult = {
|
|
544
|
+
type: "create_project_branch";
|
|
545
|
+
branchId: string;
|
|
546
|
+
branchName: string;
|
|
547
|
+
baseCommitHash: string;
|
|
548
|
+
};
|
|
549
|
+
type WorkerDeleteProjectBranchResult = {
|
|
550
|
+
type: "delete_project_branch";
|
|
551
|
+
branchId: string;
|
|
552
|
+
branchName: string;
|
|
553
|
+
};
|
|
374
554
|
type WorkerCodeEntry = {
|
|
375
555
|
name: string;
|
|
376
556
|
type: "file" | "directory";
|
|
@@ -389,6 +569,7 @@ type WorkerCodeReadResult = {
|
|
|
389
569
|
mtime: string;
|
|
390
570
|
base64: string;
|
|
391
571
|
};
|
|
572
|
+
type WorkerOperationResult = WorkerReadFileResult | WorkerWriteFileResult | WorkerEditFileResult | WorkerGrepResult | WorkerFindResult | WorkerLsResult | WorkerViewFileBytesResult | WorkerExecStdinAck | WorkerCreateProjectBranchResult | WorkerDeleteProjectBranchResult | WorkerCodeListResult | WorkerCodeReadResult;
|
|
392
573
|
declare class WorkspaceSyncAdmissionChangedError extends Error {
|
|
393
574
|
readonly name = "WorkspaceSyncAdmissionChangedError";
|
|
394
575
|
}
|
|
@@ -49,6 +49,26 @@ export type WorkspaceGitMount = {
|
|
|
49
49
|
* unready state for concurrent visible work.
|
|
50
50
|
*/
|
|
51
51
|
export declare function workspaceGitMountsForRemediation(mounts: readonly WorkspaceGitMount[]): WorkspaceGitMount[];
|
|
52
|
+
export type WorkspaceHydrationTransactionContext = {
|
|
53
|
+
/** Unique per transaction; pairs the begin and end observations. */
|
|
54
|
+
transactionId: string;
|
|
55
|
+
workspacePath: string;
|
|
56
|
+
/** Mounts the transaction may hydrate or carry across the durability barrier. */
|
|
57
|
+
mountIds: string[];
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Observes the synchronous body of a hydration transaction (snapshot,
|
|
61
|
+
* hydration pass, durability barrier, receipt). That body deliberately never
|
|
62
|
+
* yields, so the worker cannot answer control-socket messages while it runs,
|
|
63
|
+
* and on a slow disk it can run for hours. `beforeTransaction` is awaited
|
|
64
|
+
* after the last asynchronous precheck and before the final busy observation,
|
|
65
|
+
* so it may announce the operation and yield once; `afterTransaction` runs
|
|
66
|
+
* after the body committed or rolled back.
|
|
67
|
+
*/
|
|
68
|
+
export type WorkspaceHydrationTransactionHooks = {
|
|
69
|
+
beforeTransaction: (context: WorkspaceHydrationTransactionContext) => Promise<void> | void;
|
|
70
|
+
afterTransaction: (context: WorkspaceHydrationTransactionContext) => void;
|
|
71
|
+
};
|
|
52
72
|
export type WorkspaceGitSyncOutcome = "no_change" | "updated" | "pushed" | "large_diff_blocked" | "conflict_blocked";
|
|
53
73
|
export type WorkspaceGitSyncResult = {
|
|
54
74
|
outcome: WorkspaceGitSyncOutcome;
|
|
@@ -137,6 +157,7 @@ export declare function ensureWorkspaceGitClone(input: {
|
|
|
137
157
|
declare function mirrorMountsToWorkspace(workspacePath: string, mounts: readonly WorkspaceGitMount[]): void;
|
|
138
158
|
export declare function hydrateWorkspaceGitMounts(workspacePath: string, mounts: readonly WorkspaceGitMount[], options?: {
|
|
139
159
|
ignoreBusy?: boolean;
|
|
160
|
+
hydrationHooks?: WorkspaceHydrationTransactionHooks;
|
|
140
161
|
}): Promise<{
|
|
141
162
|
hydratedMountIds: string[];
|
|
142
163
|
skippedMountIds: string[];
|
|
@@ -146,6 +167,7 @@ export declare function recoverWorkspaceGitHydration(workspacePath: string, moun
|
|
|
146
167
|
deferMountIds?: ReadonlySet<string>;
|
|
147
168
|
preserveResolutionInProgress?: boolean;
|
|
148
169
|
preserveStaleBases?: boolean;
|
|
170
|
+
hydrationHooks?: WorkspaceHydrationTransactionHooks;
|
|
149
171
|
}): Promise<void>;
|
|
150
172
|
export declare function resetWorkspaceGit(input: {
|
|
151
173
|
workspacePath: string;
|
|
@@ -157,6 +179,7 @@ export declare function resetWorkspaceGit(input: {
|
|
|
157
179
|
email: string;
|
|
158
180
|
};
|
|
159
181
|
mounts: readonly WorkspaceGitMount[];
|
|
182
|
+
hydrationHooks?: WorkspaceHydrationTransactionHooks;
|
|
160
183
|
}): Promise<{
|
|
161
184
|
startingHead: string | null;
|
|
162
185
|
localHead: string | null;
|
|
@@ -194,6 +217,8 @@ export declare function synchronizeWorkspaceGit(input: {
|
|
|
194
217
|
publishedHead: string;
|
|
195
218
|
activeMountIds: string[];
|
|
196
219
|
}) => void | Promise<void>;
|
|
220
|
+
/** Announces each synchronous hydration transaction of this cycle; see WorkspaceHydrationTransactionHooks. */
|
|
221
|
+
hydrationHooks?: WorkspaceHydrationTransactionHooks;
|
|
197
222
|
}): Promise<WorkspaceGitSyncResult>;
|
|
198
223
|
export declare const workspaceGitSyncTestHarness: {
|
|
199
224
|
commandArgs: typeof gitCommandArgs;
|