dsh-git-worktree 0.7.5 → 0.9.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/CHANGELOG.md +51 -0
- package/README.en.md +11 -5
- package/README.md +11 -5
- package/docs/i18n.md +63 -0
- package/lib/adapters/files.d.ts +0 -7
- package/lib/adapters/files.js +4 -3
- package/lib/adapters/files.js.map +1 -1
- package/lib/adapters/git.d.ts +0 -10
- package/lib/adapters/git.js +92 -18
- package/lib/adapters/git.js.map +1 -1
- package/lib/adapters/initial-commit.d.ts +16 -0
- package/lib/adapters/initial-commit.js +112 -0
- package/lib/adapters/initial-commit.js.map +1 -0
- package/lib/adapters/lookup.js +21 -0
- package/lib/adapters/lookup.js.map +1 -1
- package/lib/adapters/registry.d.ts +0 -7
- package/lib/adapters/registry.js +2 -1
- package/lib/adapters/registry.js.map +1 -1
- package/lib/adapters/safe-file.d.ts +0 -7
- package/lib/adapters/safe-file.js +5 -4
- package/lib/adapters/safe-file.js.map +1 -1
- package/lib/client/WorktreeCreateRow.js +5 -2
- package/lib/client/WorktreeCreateRow.js.map +1 -1
- package/lib/client/WorktreeReviewRow.d.ts +1 -1
- package/lib/client/WorktreeReviewRow.js +11 -9
- package/lib/client/WorktreeReviewRow.js.map +1 -1
- package/lib/client/actions.d.ts +4 -7
- package/lib/client/actions.js +17 -13
- package/lib/client/actions.js.map +1 -1
- package/lib/client/console-remote/adapter.d.ts +2 -1
- package/lib/client/console-remote/adapter.js +37 -32
- package/lib/client/console-remote/adapter.js.map +1 -1
- package/lib/client/console-remote/index.js +6 -2
- package/lib/client/console-remote/index.js.map +1 -1
- package/lib/client/i18n.d.ts +25 -0
- package/lib/client/i18n.js +43 -0
- package/lib/client/i18n.js.map +1 -0
- package/lib/client/index.d.ts +1 -1
- package/lib/client/index.js +17 -4
- package/lib/client/index.js.map +1 -1
- package/lib/client/model.d.ts +3 -2
- package/lib/client/model.js +8 -8
- package/lib/client/model.js.map +1 -1
- package/lib/client/pre-session/PreSessionWorktreeToggle.d.ts +2 -2
- package/lib/client/pre-session/PreSessionWorktreeToggle.js +95 -80
- package/lib/client/pre-session/PreSessionWorktreeToggle.js.map +1 -1
- package/lib/client/pre-session/controller.d.ts +3 -0
- package/lib/client/pre-session/controller.js +27 -13
- package/lib/client/pre-session/controller.js.map +1 -1
- package/lib/client/pre-session/harness-input.d.ts +13 -0
- package/lib/client/pre-session/harness-input.js +18 -0
- package/lib/client/pre-session/harness-input.js.map +1 -0
- package/lib/client/pre-session/index.d.ts +13 -2
- package/lib/client/pre-session/index.js +11 -2
- package/lib/client/pre-session/index.js.map +1 -1
- package/lib/client/review-console/DeliveryProof.js +15 -12
- package/lib/client/review-console/DeliveryProof.js.map +1 -1
- package/lib/client/review-console/PreflightStatus.js +15 -8
- package/lib/client/review-console/PreflightStatus.js.map +1 -1
- package/lib/client/review-console/ReviewActions.d.ts +4 -1
- package/lib/client/review-console/ReviewActions.js +139 -114
- package/lib/client/review-console/ReviewActions.js.map +1 -1
- package/lib/client/review-console/ReviewDetailsModal.d.ts +12 -0
- package/lib/client/review-console/ReviewDetailsModal.js +56 -0
- package/lib/client/review-console/ReviewDetailsModal.js.map +1 -0
- package/lib/client/review-console/ReviewIcon.d.ts +35 -0
- package/lib/client/review-console/ReviewIcon.js +14 -0
- package/lib/client/review-console/ReviewIcon.js.map +1 -0
- package/lib/client/review-console/WorktreeReviewPanel.d.ts +3 -7
- package/lib/client/review-console/WorktreeReviewPanel.js +20 -82
- package/lib/client/review-console/WorktreeReviewPanel.js.map +1 -1
- package/lib/client/review-console/recovery-continuation.d.ts +2 -1
- package/lib/client/review-console/recovery-continuation.js +12 -8
- package/lib/client/review-console/recovery-continuation.js.map +1 -1
- package/lib/client/review-console/review-console.styles.js +101 -23
- package/lib/client/review-console/review-console.styles.js.map +1 -1
- package/lib/client/styles.js +13 -10
- package/lib/client/styles.js.map +1 -1
- package/lib/client/target-console/TargetStatusAction.js +87 -65
- package/lib/client/target-console/TargetStatusAction.js.map +1 -1
- package/lib/client/target-console/WorktreeConsoleView.d.ts +2 -1
- package/lib/client/target-console/WorktreeConsoleView.js +42 -37
- package/lib/client/target-console/WorktreeConsoleView.js.map +1 -1
- package/lib/client/target-console/WorktreeManagerModal.js +3 -1
- package/lib/client/target-console/WorktreeManagerModal.js.map +1 -1
- package/lib/client/target-console/WorktreeReviewStatus.js +45 -88
- package/lib/client/target-console/WorktreeReviewStatus.js.map +1 -1
- package/lib/client/target-console/current-polling.d.ts +11 -0
- package/lib/client/target-console/current-polling.js +104 -0
- package/lib/client/target-console/current-polling.js.map +1 -0
- package/lib/client/target-console/target-console.styles.js +8 -0
- package/lib/client/target-console/target-console.styles.js.map +1 -1
- package/lib/client/workspace-sidebar/index.js +8 -3
- package/lib/client/workspace-sidebar/index.js.map +1 -1
- package/lib/client/workspace-sidebar/model.d.ts +5 -1
- package/lib/client/workspace-sidebar/model.js +97 -23
- package/lib/client/workspace-sidebar/model.js.map +1 -1
- package/lib/client.js +12228 -8928
- package/lib/commands.js +32 -35
- package/lib/commands.js.map +1 -1
- package/lib/console-contract.d.ts +20 -0
- package/lib/console-contract.js.map +1 -1
- package/lib/console-host/control-plane.d.ts +3 -1
- package/lib/console-host/control-plane.js +186 -95
- package/lib/console-host/control-plane.js.map +1 -1
- package/lib/console-host/errors.js +2 -1
- package/lib/console-host/errors.js.map +1 -1
- package/lib/console-host/projection.js +3 -1
- package/lib/console-host/projection.js.map +1 -1
- package/lib/console-host/review-diff.js +6 -5
- package/lib/console-host/review-diff.js.map +1 -1
- package/lib/console-host/service.d.ts +27 -23
- package/lib/console-host/service.js +66 -51
- package/lib/console-host/service.js.map +1 -1
- package/lib/console-remote/descriptors.js +4 -2
- package/lib/console-remote/descriptors.js.map +1 -1
- package/lib/console-remote/remote.d.ts +25 -22
- package/lib/console-remote/remote.js.map +1 -1
- package/lib/console-remote/schemas.d.ts +14 -2
- package/lib/console-remote/schemas.js +8 -0
- package/lib/console-remote/schemas.js.map +1 -1
- package/lib/i18n/client-messages.d.ts +1671 -0
- package/lib/i18n/client-messages.js +1468 -0
- package/lib/i18n/client-messages.js.map +1 -0
- package/lib/i18n/core.d.ts +12 -0
- package/lib/i18n/core.js +10 -0
- package/lib/i18n/core.js.map +1 -0
- package/lib/i18n/host-messages.d.ts +1495 -0
- package/lib/i18n/host-messages.js +1485 -0
- package/lib/i18n/host-messages.js.map +1 -0
- package/lib/i18n/host.d.ts +9 -0
- package/lib/i18n/host.js +34 -0
- package/lib/i18n/host.js.map +1 -0
- package/lib/i18n/transport-messages.d.ts +10 -0
- package/lib/i18n/transport-messages.js +11 -0
- package/lib/i18n/transport-messages.js.map +1 -0
- package/lib/ports.d.ts +15 -0
- package/lib/session-checkout-apply.js +93 -92
- package/lib/session-checkout-apply.js.map +1 -1
- package/lib/session-checkout-module.js +234 -229
- package/lib/session-checkout-module.js.map +1 -1
- package/lib/session-target-context.js +2 -1
- package/lib/session-target-context.js.map +1 -1
- package/lib/tools.d.ts +0 -8
- package/lib/tools.js +108 -96
- package/lib/tools.js.map +1 -1
- package/package.json +3 -1
- package/scripts/check-publish.mjs +1 -1
- package/scripts/dev-dsh-lib.mjs +1 -1
- package/scripts/workspace-sidebar-upstream.mjs +1 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { hostMessages, hostPersistedCleanupMessage } from './i18n/host-messages.js';
|
|
2
|
+
import { hostMessage } from './i18n/host.js';
|
|
1
3
|
import { createHash } from 'node:crypto';
|
|
2
4
|
import { realpathSync } from 'node:fs';
|
|
3
5
|
import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
@@ -8,8 +10,8 @@ const UNVERSIONED_OID = 'unversioned';
|
|
|
8
10
|
const UNVERSIONED_REF = 'WORKING_TREE';
|
|
9
11
|
const RETENTION_24H_MS = 24 * 60 * 60 * 1000;
|
|
10
12
|
const RETENTION_3D_MS = 3 * RETENTION_24H_MS;
|
|
11
|
-
const CLEANUP_IDENTITY_CHANGED_MESSAGE =
|
|
12
|
-
const CLEANUP_RESIDUE_MESSAGE =
|
|
13
|
+
const CLEANUP_IDENTITY_CHANGED_MESSAGE = hostMessages.cleanupIdentityChanged.zh;
|
|
14
|
+
const CLEANUP_RESIDUE_MESSAGE = hostMessages.cleanupResidue.zh;
|
|
13
15
|
const TRANSIENT_CLEANUP_RETRY_DELAYS_MS = [100, 300, 800];
|
|
14
16
|
/**
|
|
15
17
|
* 单个 checkout 清理/维护操作的启动收敛超时。
|
|
@@ -24,7 +26,7 @@ async function withCleanupTimeout(checkoutId, operation) {
|
|
|
24
26
|
operation(),
|
|
25
27
|
new Promise((resolve) => {
|
|
26
28
|
timeoutHandle = setTimeout(() => {
|
|
27
|
-
console.warn(
|
|
29
|
+
console.warn(hostMessage('cleanupTimeout', { p0: checkoutId.slice(0, 8), p1: CHECKOUT_CLEANUP_TIMEOUT_MS }));
|
|
28
30
|
resolve(null);
|
|
29
31
|
}, CHECKOUT_CLEANUP_TIMEOUT_MS);
|
|
30
32
|
}),
|
|
@@ -103,18 +105,18 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
103
105
|
function requireSession(sessionId) {
|
|
104
106
|
const session = dependencies.lookup.getSession(sessionId);
|
|
105
107
|
if (!session)
|
|
106
|
-
throw new SessionCheckoutError('session_not_found',
|
|
108
|
+
throw new SessionCheckoutError('session_not_found', hostMessage('sessionNotFound', { p0: sessionId }));
|
|
107
109
|
return session;
|
|
108
110
|
}
|
|
109
111
|
async function resolveSessionProject(sessionId) {
|
|
110
112
|
const session = requireSession(sessionId);
|
|
111
113
|
if (!session.projectId)
|
|
112
|
-
throw new SessionCheckoutError('project_not_found', '
|
|
114
|
+
throw new SessionCheckoutError('project_not_found', hostMessage('theSessionIsNotAssociatedWithAProject'));
|
|
113
115
|
const project = dependencies.lookup.getProject(session.projectId);
|
|
114
116
|
if (!project)
|
|
115
|
-
throw new SessionCheckoutError('project_not_found',
|
|
117
|
+
throw new SessionCheckoutError('project_not_found', hostMessage('projectNotFound', { p0: session.projectId }));
|
|
116
118
|
if (!dependencies.files.exists(project.root)) {
|
|
117
|
-
throw new SessionCheckoutError('project_root_missing',
|
|
119
|
+
throw new SessionCheckoutError('project_root_missing', hostMessage('projectRootDirectoryNotFound', { p0: project.name }));
|
|
118
120
|
}
|
|
119
121
|
return { session, project };
|
|
120
122
|
}
|
|
@@ -132,7 +134,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
132
134
|
checkout: {
|
|
133
135
|
id: `local:${binding.projectId}`,
|
|
134
136
|
kind: 'local',
|
|
135
|
-
label: '
|
|
137
|
+
label: hostMessage('localCheckout'),
|
|
136
138
|
phase: 'ready',
|
|
137
139
|
},
|
|
138
140
|
source: { ref: binding.sourceRef, oid: binding.sourceOid },
|
|
@@ -202,7 +204,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
202
204
|
commitOid: delivery.commitOid,
|
|
203
205
|
...(projectProof(delivery.proof) ? { proof: projectProof(delivery.proof) } : {}),
|
|
204
206
|
cleanup: delivery.cleanup,
|
|
205
|
-
...(delivery.cleanupMessage ? { cleanupMessage: delivery.cleanupMessage } : {}),
|
|
207
|
+
...(delivery.cleanupMessage ? { cleanupMessage: hostPersistedCleanupMessage(delivery.cleanupMessage, hostMessage) } : {}),
|
|
206
208
|
};
|
|
207
209
|
}
|
|
208
210
|
if (delivery.state === 'retained') {
|
|
@@ -215,7 +217,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
215
217
|
retainedAt: delivery.retainedAt,
|
|
216
218
|
expiresAt: delivery.expiresAt,
|
|
217
219
|
cleanup: delivery.cleanup,
|
|
218
|
-
...(delivery.cleanupMessage ? { cleanupMessage: delivery.cleanupMessage } : {}),
|
|
220
|
+
...(delivery.cleanupMessage ? { cleanupMessage: hostPersistedCleanupMessage(delivery.cleanupMessage, hostMessage) } : {}),
|
|
219
221
|
};
|
|
220
222
|
}
|
|
221
223
|
if (delivery.state === 'delivered') {
|
|
@@ -252,7 +254,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
252
254
|
checkout: {
|
|
253
255
|
id: record?.checkoutId ?? (binding.target.kind === 'isolated' ? binding.target.checkoutId : `local:${binding.projectId}`),
|
|
254
256
|
kind: binding.target.kind === 'isolated' ? 'isolated' : 'local',
|
|
255
|
-
label: binding.target.kind === 'isolated' ? '
|
|
257
|
+
label: binding.target.kind === 'isolated' ? hostMessage('isolatedCheckout') : hostMessage('localCheckout'),
|
|
256
258
|
phase,
|
|
257
259
|
},
|
|
258
260
|
source: { ref: binding.sourceRef, oid: binding.sourceOid },
|
|
@@ -315,7 +317,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
315
317
|
checkout: {
|
|
316
318
|
id: record.checkoutId,
|
|
317
319
|
kind: 'isolated',
|
|
318
|
-
label: '
|
|
320
|
+
label: hostMessage('isolatedCheckout'),
|
|
319
321
|
// Commit 已是权威交付事实;残余环境异常只能影响 cleanup,不能降级整个会话。
|
|
320
322
|
phase: delivery.state === 'retained' ? 'retained' : delivery.state === 'delivered' ? 'discarded' : 'finalized',
|
|
321
323
|
},
|
|
@@ -557,7 +559,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
557
559
|
checkout: {
|
|
558
560
|
id: record.checkoutId,
|
|
559
561
|
kind: 'isolated',
|
|
560
|
-
label: '
|
|
562
|
+
label: hostMessage('isolatedCheckout'),
|
|
561
563
|
phase: 'discarded',
|
|
562
564
|
},
|
|
563
565
|
};
|
|
@@ -624,7 +626,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
624
626
|
checkout: {
|
|
625
627
|
id: record.checkoutId,
|
|
626
628
|
kind: 'isolated',
|
|
627
|
-
label: '
|
|
629
|
+
label: hostMessage('isolatedCheckout'),
|
|
628
630
|
phase: record.phase,
|
|
629
631
|
},
|
|
630
632
|
source: { ref: record.sourceRef, oid: record.baseOid },
|
|
@@ -759,12 +761,12 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
759
761
|
}
|
|
760
762
|
}
|
|
761
763
|
if (!isolatedWorkspaceMatches) {
|
|
762
|
-
throw new SessionCheckoutError('project_mismatch', '
|
|
764
|
+
throw new SessionCheckoutError('project_mismatch', hostMessage('theSessionWorkspaceNoLongerMatchesItsBoundSession'));
|
|
763
765
|
}
|
|
764
766
|
}
|
|
765
767
|
return persisted;
|
|
766
768
|
}
|
|
767
|
-
throw new SessionCheckoutError('target_unselected', '
|
|
769
|
+
throw new SessionCheckoutError('target_unselected', hostMessage('theSessionHasNotSelectedASessionTarget'));
|
|
768
770
|
}
|
|
769
771
|
let bindingQueue = Promise.resolve();
|
|
770
772
|
let activeBindingOperation = null;
|
|
@@ -948,7 +950,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
948
950
|
await dependencies.git.releaseInternalArtifacts(current.localRoot, current.checkoutId, `checkpoints/${journal.checkpointId}`);
|
|
949
951
|
}
|
|
950
952
|
catch {
|
|
951
|
-
console.warn('
|
|
953
|
+
console.warn(hostMessage('checkpointRefCleanupFailed'));
|
|
952
954
|
}
|
|
953
955
|
record = { ...current, phase: 'ready', journal: null, revision: current.revision + 1 };
|
|
954
956
|
}
|
|
@@ -1110,22 +1112,22 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1110
1112
|
async function markReadyForReviewTarget(sessionId, input) {
|
|
1111
1113
|
const binding = await resolveBinding(sessionId);
|
|
1112
1114
|
if (binding.ownerSessionId !== sessionId || binding.target.kind !== 'isolated') {
|
|
1113
|
-
throw new SessionCheckoutError('not_owner', '
|
|
1115
|
+
throw new SessionCheckoutError('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanPrepareAReview'));
|
|
1114
1116
|
}
|
|
1115
1117
|
const summary = input.summary.trim();
|
|
1116
1118
|
const suggestedCommitMessage = input.suggestedCommitMessage.trim();
|
|
1117
1119
|
if (!summary || !suggestedCommitMessage || input.tests.length > 20) {
|
|
1118
|
-
throw new SessionCheckoutError('invalid_input', '
|
|
1120
|
+
throw new SessionCheckoutError('invalid_input', hostMessage('theReviewSummaryCommitMessageOrValidationItemsAre'));
|
|
1119
1121
|
}
|
|
1120
1122
|
const record = dependencies.registry.read().managedCheckouts[binding.target.checkoutId];
|
|
1121
1123
|
if (!record)
|
|
1122
|
-
throw new SessionCheckoutError('checkout_missing', '
|
|
1124
|
+
throw new SessionCheckoutError('checkout_missing', hostMessage('theIsolatedCheckoutRecordDoesNotExist'));
|
|
1123
1125
|
if (record.phase !== 'ready' || record.delivery.state === 'preview_active' || record.delivery.state === 'preview_detached') {
|
|
1124
|
-
throw new SessionCheckoutError('operation_not_allowed',
|
|
1126
|
+
throw new SessionCheckoutError('operation_not_allowed', hostMessage('cannotPrepareAReviewInTheCurrentState', { p0: record.phase, p1: record.delivery.state }));
|
|
1125
1127
|
}
|
|
1126
1128
|
const inspected = await inspectIsolated(binding);
|
|
1127
1129
|
if (inspected.checkout.phase !== 'ready') {
|
|
1128
|
-
throw new SessionCheckoutError('recovery_required', '
|
|
1130
|
+
throw new SessionCheckoutError('recovery_required', hostMessage('theIsolatedCheckoutIdentityCannotBeVerifiedRecoveryIs'));
|
|
1129
1131
|
}
|
|
1130
1132
|
const snapshot = await dependencies.applyEngine.inspectReview({
|
|
1131
1133
|
baseOid: record.applyBaseOid ?? record.baseOid,
|
|
@@ -1175,7 +1177,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1175
1177
|
}
|
|
1176
1178
|
async function operateCheckpoint(input, binding) {
|
|
1177
1179
|
if (binding.ownerSessionId !== input.sessionId || binding.target.kind !== 'isolated') {
|
|
1178
|
-
return operationError('not_owner', '
|
|
1180
|
+
return operationError('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanSaveACheckpoint'));
|
|
1179
1181
|
}
|
|
1180
1182
|
const commitMessage = input.commitMessage.trim();
|
|
1181
1183
|
if (!commitMessage
|
|
@@ -1185,10 +1187,10 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1185
1187
|
|| !input.requestId.trim()
|
|
1186
1188
|
|| input.requestId.length > 200
|
|
1187
1189
|
|| /[\0\r\n]/u.test(input.requestId))
|
|
1188
|
-
return operationError('invalid_input', '
|
|
1190
|
+
return operationError('invalid_input', hostMessage('theCheckpointRequestOrCommitMessageIsInvalid'));
|
|
1189
1191
|
let record = dependencies.registry.read().managedCheckouts[binding.target.checkoutId];
|
|
1190
1192
|
if (!record)
|
|
1191
|
-
return operationError('checkout_missing', '
|
|
1193
|
+
return operationError('checkout_missing', hostMessage('theIsolatedCheckoutRecordDoesNotExist'));
|
|
1192
1194
|
const previousRequest = record.checkpoints?.find(checkpoint => checkpoint.requestId === input.requestId);
|
|
1193
1195
|
if (previousRequest) {
|
|
1194
1196
|
const exactReplay = record.delivery.state === 'working'
|
|
@@ -1198,7 +1200,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1198
1200
|
&& previousRequest.generation === input.expectedGeneration
|
|
1199
1201
|
&& previousRequest.commitMessage === commitMessage;
|
|
1200
1202
|
if (!exactReplay)
|
|
1201
|
-
return operationError('stale_target', '
|
|
1203
|
+
return operationError('stale_target', hostMessage('theCheckpointRequestIdIsAlreadyUsedByAnotherState'));
|
|
1202
1204
|
return {
|
|
1203
1205
|
status: 'checkpointed',
|
|
1204
1206
|
target: await inspectIsolated(binding),
|
|
@@ -1207,21 +1209,21 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1207
1209
|
};
|
|
1208
1210
|
}
|
|
1209
1211
|
if (record.revision !== input.expectedRevision) {
|
|
1210
|
-
return operationError('stale_target', '
|
|
1212
|
+
return operationError('stale_target', hostMessage('theSessionTargetHasChangedRefreshAndRetry'), await inspectIsolated(binding));
|
|
1211
1213
|
}
|
|
1212
1214
|
if (record.phase !== 'ready' || (record.delivery.state !== 'ready_for_review' && record.delivery.state !== 'preview_active')) {
|
|
1213
|
-
return operationError('operation_not_allowed',
|
|
1215
|
+
return operationError('operation_not_allowed', hostMessage('cannotSaveACheckpointInTheCurrentState', { p0: record.phase, p1: record.delivery.state }), await inspectIsolated(binding));
|
|
1214
1216
|
}
|
|
1215
1217
|
if (record.delivery.review.reviewId !== input.expectedReviewId || checkpointGenerationForRecord(record) !== input.expectedGeneration) {
|
|
1216
|
-
return operationError('stale_target', '
|
|
1218
|
+
return operationError('stale_target', hostMessage('theCheckpointReviewOrGenerationHasChangedRefreshAnd'), await inspectIsolated(binding));
|
|
1217
1219
|
}
|
|
1218
1220
|
const holder = findProjectAcceptanceHolder(record);
|
|
1219
1221
|
if (holder) {
|
|
1220
|
-
return operationError('project_acceptance_busy', '
|
|
1222
|
+
return operationError('project_acceptance_busy', hostMessage('anotherTaskIsOccupyingThisProjectSLocalAcceptance'), await inspectIsolated(binding));
|
|
1221
1223
|
}
|
|
1222
1224
|
const inspected = await inspectIsolated(binding);
|
|
1223
1225
|
if (inspected.checkout.phase !== 'ready') {
|
|
1224
|
-
return operationError('recovery_required', '
|
|
1226
|
+
return operationError('recovery_required', hostMessage('theIsolatedCheckoutIdentityCannotBeVerifiedRecoveryIs'), inspected);
|
|
1225
1227
|
}
|
|
1226
1228
|
record = dependencies.registry.read().managedCheckouts[record.checkoutId];
|
|
1227
1229
|
if (!record
|
|
@@ -1230,7 +1232,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1230
1232
|
|| (record.delivery.state !== 'ready_for_review' && record.delivery.state !== 'preview_active')
|
|
1231
1233
|
|| record.delivery.review.reviewId !== input.expectedReviewId
|
|
1232
1234
|
|| checkpointGenerationForRecord(record) !== input.expectedGeneration)
|
|
1233
|
-
return operationError('stale_target', '
|
|
1235
|
+
return operationError('stale_target', hostMessage('theCheckpointStateChangedBeforeTheHostCASRefresh'));
|
|
1234
1236
|
if (record.delivery.state === 'preview_active') {
|
|
1235
1237
|
const { preview, review } = record.delivery;
|
|
1236
1238
|
const rollbackOperationId = dependencies.createCheckoutId();
|
|
@@ -1283,12 +1285,12 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1283
1285
|
revision: current.revision + 1,
|
|
1284
1286
|
}));
|
|
1285
1287
|
if (!restored)
|
|
1286
|
-
return operationError('checkout_missing', '
|
|
1288
|
+
return operationError('checkout_missing', hostMessage('previewWasRolledBackButTheCheckoutRecordIs'));
|
|
1287
1289
|
await releasePreviewArtifactsBestEffort(record, preview.previewId);
|
|
1288
1290
|
record = restored;
|
|
1289
1291
|
}
|
|
1290
1292
|
if (record.delivery.state !== 'ready_for_review') {
|
|
1291
|
-
return operationError('operation_not_allowed', '
|
|
1293
|
+
return operationError('operation_not_allowed', hostMessage('thereIsNoReviewCheckpointToSave'), await inspectIsolated(binding));
|
|
1292
1294
|
}
|
|
1293
1295
|
const review = record.delivery.review;
|
|
1294
1296
|
const checkpointId = dependencies.createCheckoutId();
|
|
@@ -1316,7 +1318,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1316
1318
|
revision: current.revision + 1,
|
|
1317
1319
|
}));
|
|
1318
1320
|
if (!mutating)
|
|
1319
|
-
return operationError('checkout_missing', '
|
|
1321
|
+
return operationError('checkout_missing', hostMessage('theIsolatedCheckoutRecordDoesNotExist'));
|
|
1320
1322
|
const result = await dependencies.applyEngine.checkpoint({
|
|
1321
1323
|
isolatedPath: mutating.managedRoot,
|
|
1322
1324
|
expectedFingerprint: review.isolatedFingerprint,
|
|
@@ -1338,7 +1340,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1338
1340
|
revision: current.revision + 1,
|
|
1339
1341
|
}));
|
|
1340
1342
|
if (!journaled?.journal || journaled.journal.operation !== 'checkpoint' || journaled.journal.commitOid !== prepared.commitOid) {
|
|
1341
|
-
throw new SessionCheckoutError('stale_target', '
|
|
1343
|
+
throw new SessionCheckoutError('stale_target', hostMessage('theCheckpointJournalChangedBeforeRetainingTheInternalRef'));
|
|
1342
1344
|
}
|
|
1343
1345
|
await dependencies.git.retainInternalArtifact(mutating.localRoot, mutating.checkoutId, `checkpoints/${checkpointId}`, prepared.commitOid);
|
|
1344
1346
|
},
|
|
@@ -1392,7 +1394,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1392
1394
|
};
|
|
1393
1395
|
});
|
|
1394
1396
|
if (!completed)
|
|
1395
|
-
return operationError('checkout_missing', '
|
|
1397
|
+
return operationError('checkout_missing', hostMessage('checkpointWasCreatedButTheCheckoutRecordIsMissing'));
|
|
1396
1398
|
return {
|
|
1397
1399
|
status: 'checkpointed',
|
|
1398
1400
|
target: await inspectIsolated(binding),
|
|
@@ -1403,23 +1405,23 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1403
1405
|
async function resumeRevisionTarget(sessionId, expectedRevision, expectedReviewId, recovery) {
|
|
1404
1406
|
const binding = await resolveBinding(sessionId);
|
|
1405
1407
|
if (binding.ownerSessionId !== sessionId || binding.target.kind !== 'isolated') {
|
|
1406
|
-
throw new SessionCheckoutError('not_owner', '
|
|
1408
|
+
throw new SessionCheckoutError('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanResumeEditing'));
|
|
1407
1409
|
}
|
|
1408
1410
|
let record = dependencies.registry.read().managedCheckouts[binding.target.checkoutId];
|
|
1409
1411
|
if (!record)
|
|
1410
|
-
throw new SessionCheckoutError('checkout_missing', '
|
|
1412
|
+
throw new SessionCheckoutError('checkout_missing', hostMessage('theIsolatedCheckoutRecordDoesNotExist'));
|
|
1411
1413
|
if (record.revision !== expectedRevision) {
|
|
1412
|
-
throw new SessionCheckoutError('stale_target', '
|
|
1414
|
+
throw new SessionCheckoutError('stale_target', hostMessage('theSessionTargetHasChangedRefreshAndRetry'));
|
|
1413
1415
|
}
|
|
1414
1416
|
if (record.phase !== 'ready' || record.delivery.state !== 'ready_for_review') {
|
|
1415
|
-
throw new SessionCheckoutError('operation_not_allowed', '
|
|
1417
|
+
throw new SessionCheckoutError('operation_not_allowed', hostMessage('theCurrentWorktreeHasNoUnsyncedReviewToResume'));
|
|
1416
1418
|
}
|
|
1417
1419
|
if (record.delivery.review.reviewId !== expectedReviewId) {
|
|
1418
|
-
throw new SessionCheckoutError('stale_target', '
|
|
1420
|
+
throw new SessionCheckoutError('stale_target', hostMessage('thisReviewCardIsNoLongerTheCurrentReview'));
|
|
1419
1421
|
}
|
|
1420
1422
|
const inspected = await inspectIsolated(binding);
|
|
1421
1423
|
if (inspected.checkout.phase !== 'ready') {
|
|
1422
|
-
throw new SessionCheckoutError('recovery_required', '
|
|
1424
|
+
throw new SessionCheckoutError('recovery_required', hostMessage('theIsolatedCheckoutIdentityCannotBeVerifiedRecoveryIs'));
|
|
1423
1425
|
}
|
|
1424
1426
|
record = dependencies.registry.read().managedCheckouts[record.checkoutId];
|
|
1425
1427
|
if (!record
|
|
@@ -1427,7 +1429,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1427
1429
|
|| record.phase !== 'ready'
|
|
1428
1430
|
|| record.delivery.state !== 'ready_for_review'
|
|
1429
1431
|
|| record.delivery.review.reviewId !== expectedReviewId) {
|
|
1430
|
-
throw new SessionCheckoutError('stale_target', '
|
|
1432
|
+
throw new SessionCheckoutError('stale_target', hostMessage('theAcceptanceStateChangedBeforeResumingEditingRefreshAnd'));
|
|
1431
1433
|
}
|
|
1432
1434
|
if (recovery) {
|
|
1433
1435
|
const preflight = await preflightTarget(sessionId, expectedRevision);
|
|
@@ -1440,13 +1442,13 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1440
1442
|
|| preflight.status !== 'conflict'
|
|
1441
1443
|
|| preflight.localHeadOid !== recovery.localHeadOid
|
|
1442
1444
|
|| !sameFiles)
|
|
1443
|
-
throw new SessionCheckoutError('stale_target', '
|
|
1445
|
+
throw new SessionCheckoutError('stale_target', hostMessage('theConflictRecoveryIdentityChangedBeforeHostCASRun'));
|
|
1444
1446
|
record = dependencies.registry.read().managedCheckouts[record.checkoutId];
|
|
1445
1447
|
if (!record
|
|
1446
1448
|
|| record.revision !== expectedRevision
|
|
1447
1449
|
|| record.delivery.state !== 'ready_for_review'
|
|
1448
1450
|
|| record.delivery.review.reviewId !== expectedReviewId)
|
|
1449
|
-
throw new SessionCheckoutError('stale_target', '
|
|
1451
|
+
throw new SessionCheckoutError('stale_target', hostMessage('theAcceptanceStateChangedBeforeConflictRecoveryRefreshAnd'));
|
|
1450
1452
|
}
|
|
1451
1453
|
const reviewBeforeResume = record.delivery.review;
|
|
1452
1454
|
const iteration = reviewBeforeResume.iteration;
|
|
@@ -1467,7 +1469,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1467
1469
|
async function prepareReviewRegenerationTarget(sessionId, expectedRevision, expectedReviewId, requestId) {
|
|
1468
1470
|
const binding = await resolveBinding(sessionId);
|
|
1469
1471
|
if (binding.ownerSessionId !== sessionId || binding.target.kind !== 'isolated') {
|
|
1470
|
-
throw new SessionCheckoutError('not_owner', '
|
|
1472
|
+
throw new SessionCheckoutError('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanRequestReviewRegeneration'));
|
|
1471
1473
|
}
|
|
1472
1474
|
const preflight = await preflightTarget(sessionId, expectedRevision);
|
|
1473
1475
|
if (preflight.status !== 'blocked'
|
|
@@ -1475,7 +1477,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1475
1477
|
|| preflight.checkoutId !== binding.target.checkoutId
|
|
1476
1478
|
|| preflight.reviewId !== expectedReviewId
|
|
1477
1479
|
|| preflight.revision !== expectedRevision)
|
|
1478
|
-
throw new SessionCheckoutError('stale_target', '
|
|
1480
|
+
throw new SessionCheckoutError('stale_target', hostMessage('theReadOnlyReviewRegenerationIdentityChangedBeforeHost'));
|
|
1479
1481
|
const continuation = {
|
|
1480
1482
|
kind: 'worktree_review_regeneration', requestId, reviewId: expectedReviewId, revision: expectedRevision,
|
|
1481
1483
|
};
|
|
@@ -1483,17 +1485,17 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1483
1485
|
if (current.revision !== expectedRevision
|
|
1484
1486
|
|| current.delivery.state !== 'ready_for_review'
|
|
1485
1487
|
|| current.delivery.review.reviewId !== expectedReviewId)
|
|
1486
|
-
throw new SessionCheckoutError('stale_target', '
|
|
1488
|
+
throw new SessionCheckoutError('stale_target', hostMessage('theReadyReviewChangedBeforeReadOnlyAuthorization'));
|
|
1487
1489
|
return { ...current, recoveryContinuation: continuation };
|
|
1488
1490
|
});
|
|
1489
1491
|
if (!updated)
|
|
1490
|
-
throw new SessionCheckoutError('checkout_missing', '
|
|
1492
|
+
throw new SessionCheckoutError('checkout_missing', hostMessage('theIsolatedCheckoutRecordDoesNotExist'));
|
|
1491
1493
|
return continuation;
|
|
1492
1494
|
}
|
|
1493
1495
|
async function preparePreviewRecoveryAnalysisTarget(sessionId, proof, requestId) {
|
|
1494
1496
|
const recovery = await preflightPreviewRecoveryTarget(sessionId, proof.revision, proof.reviewId, proof.previewId);
|
|
1495
1497
|
if (recovery.status !== 'assessed' || !recoveryProofMatches(proof, recovery.proof)) {
|
|
1496
|
-
throw new SessionCheckoutError('stale_target', '
|
|
1498
|
+
throw new SessionCheckoutError('stale_target', hostMessage('theDetachedPreviewRecoveryProofHasChangedCheckAgain'));
|
|
1497
1499
|
}
|
|
1498
1500
|
const continuation = {
|
|
1499
1501
|
kind: 'worktree_preview_recovery_analysis',
|
|
@@ -1509,31 +1511,31 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1509
1511
|
|| current.delivery.state !== 'preview_detached'
|
|
1510
1512
|
|| current.delivery.review.reviewId !== proof.reviewId
|
|
1511
1513
|
|| current.delivery.preview.previewId !== proof.previewId)
|
|
1512
|
-
throw new SessionCheckoutError('stale_target', '
|
|
1514
|
+
throw new SessionCheckoutError('stale_target', hostMessage('theDetachedPreviewChangedBeforeAnalysisAuthorization'));
|
|
1513
1515
|
return { ...current, recoveryContinuation: continuation };
|
|
1514
1516
|
});
|
|
1515
1517
|
if (!updated)
|
|
1516
|
-
throw new SessionCheckoutError('checkout_missing', '
|
|
1518
|
+
throw new SessionCheckoutError('checkout_missing', hostMessage('theIsolatedCheckoutRecordDoesNotExist'));
|
|
1517
1519
|
return continuation;
|
|
1518
1520
|
}
|
|
1519
1521
|
async function createPreviewRecoveryHandoffTarget(sessionId, proof, targetSessionId, requestId) {
|
|
1520
1522
|
if (!targetSessionId || targetSessionId === sessionId) {
|
|
1521
|
-
throw new SessionCheckoutError('invalid_input', '
|
|
1523
|
+
throw new SessionCheckoutError('invalid_input', hostMessage('recoveryHandoffRequiresANewPreallocatedSessionID'));
|
|
1522
1524
|
}
|
|
1523
1525
|
if (dependencies.lookup.getSession(targetSessionId) || getPersistedBinding(targetSessionId)) {
|
|
1524
|
-
throw new SessionCheckoutError('target_already_bound', '
|
|
1526
|
+
throw new SessionCheckoutError('target_already_bound', hostMessage('theRecoveryHandoffSessionIDIsAlreadyInUse'));
|
|
1525
1527
|
}
|
|
1526
1528
|
const recovery = await preflightPreviewRecoveryTarget(sessionId, proof.revision, proof.reviewId, proof.previewId);
|
|
1527
1529
|
if (recovery.status !== 'assessed' || !recoveryProofMatches(proof, recovery.proof)) {
|
|
1528
|
-
throw new SessionCheckoutError('stale_target', '
|
|
1530
|
+
throw new SessionCheckoutError('stale_target', hostMessage('theDetachedPreviewRecoveryProofHasChangedCheckAgain'));
|
|
1529
1531
|
}
|
|
1530
1532
|
const source = dependencies.registry.read().managedCheckouts[proof.checkoutId];
|
|
1531
1533
|
if (!source || source.ownerSessionId !== sessionId || source.delivery.state !== 'preview_detached') {
|
|
1532
|
-
throw new SessionCheckoutError('stale_target', '
|
|
1534
|
+
throw new SessionCheckoutError('stale_target', hostMessage('thePreviousDetachedPreviewIdentityHasChanged'));
|
|
1533
1535
|
}
|
|
1534
1536
|
const originSessionId = source.sourceSessionId ?? source.ownerSessionId;
|
|
1535
1537
|
if (!dependencies.lookup.getSession(originSessionId)) {
|
|
1536
|
-
throw new SessionCheckoutError('session_not_found', '
|
|
1538
|
+
throw new SessionCheckoutError('session_not_found', hostMessage('theOriginalSourceSessionIsUnavailableNoHandoffWorktree'));
|
|
1537
1539
|
}
|
|
1538
1540
|
const target = await bindTarget(targetSessionId, { kind: 'isolated' }, 0, Date.now(), originSessionId);
|
|
1539
1541
|
const binding = getPersistedBinding(targetSessionId);
|
|
@@ -1541,7 +1543,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1541
1543
|
? dependencies.registry.read().managedCheckouts[binding.target.checkoutId]
|
|
1542
1544
|
: undefined;
|
|
1543
1545
|
if (!created)
|
|
1544
|
-
throw new SessionCheckoutError('checkout_missing', '
|
|
1546
|
+
throw new SessionCheckoutError('checkout_missing', hostMessage('theRecoveryHandoffWorktreeRecordIsMissingAfterCreation'));
|
|
1545
1547
|
const continuation = {
|
|
1546
1548
|
kind: 'worktree_preview_recovery_handoff',
|
|
1547
1549
|
requestId,
|
|
@@ -1556,31 +1558,31 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1556
1558
|
}
|
|
1557
1559
|
async function operateApply(input, binding) {
|
|
1558
1560
|
if (binding.ownerSessionId !== input.sessionId) {
|
|
1559
|
-
return operationError('not_owner', '
|
|
1561
|
+
return operationError('not_owner', hostMessage('aSessionThatInheritedItsSessionTargetCannotPerform'));
|
|
1560
1562
|
}
|
|
1561
1563
|
if (binding.target.kind !== 'isolated') {
|
|
1562
|
-
return operationError('operation_not_allowed', '
|
|
1564
|
+
return operationError('operation_not_allowed', hostMessage('localCheckoutDoesNotSupportApply'));
|
|
1563
1565
|
}
|
|
1564
1566
|
let record = dependencies.registry.read().managedCheckouts[binding.target.checkoutId];
|
|
1565
1567
|
if (!record)
|
|
1566
|
-
return operationError('checkout_missing', '
|
|
1568
|
+
return operationError('checkout_missing', hostMessage('theIsolatedCheckoutRecordDoesNotExist'));
|
|
1567
1569
|
if (record.revision !== input.expectedRevision) {
|
|
1568
|
-
return operationError('stale_target', '
|
|
1570
|
+
return operationError('stale_target', hostMessage('theSessionTargetHasChangedRefreshAndRetry'), await inspectIsolated(binding));
|
|
1569
1571
|
}
|
|
1570
1572
|
if (record.phase !== 'ready') {
|
|
1571
|
-
return operationError('operation_not_allowed',
|
|
1573
|
+
return operationError('operation_not_allowed', hostMessage('cannotApplyInTheCurrentState', { p0: record.phase, p1: record.delivery.state }), await inspectIsolated(binding));
|
|
1572
1574
|
}
|
|
1573
1575
|
const holder = findProjectAcceptanceHolder(record);
|
|
1574
1576
|
if (holder) {
|
|
1575
|
-
return operationError('project_acceptance_busy',
|
|
1577
|
+
return operationError('project_acceptance_busy', hostMessage('anotherAcceptanceTaskInTheSameProjectIsOccupying', { p0: holder.projectName }), await inspectIsolated(binding));
|
|
1576
1578
|
}
|
|
1577
1579
|
const inspected = await inspectIsolated(binding);
|
|
1578
1580
|
if (inspected.checkout.phase !== 'ready') {
|
|
1579
|
-
return operationError('recovery_required', '
|
|
1581
|
+
return operationError('recovery_required', hostMessage('theIsolatedCheckoutIdentityCannotBeVerifiedRecoveryIs'), inspected);
|
|
1580
1582
|
}
|
|
1581
1583
|
record = dependencies.registry.read().managedCheckouts[record.checkoutId];
|
|
1582
1584
|
if (!record || record.phase !== 'ready') {
|
|
1583
|
-
return operationError('recovery_required', '
|
|
1585
|
+
return operationError('recovery_required', hostMessage('theIsolatedCheckoutStateHasChangedRecoveryIsRequired'));
|
|
1584
1586
|
}
|
|
1585
1587
|
const startedAt = Date.now();
|
|
1586
1588
|
const operationId = dependencies.createCheckoutId();
|
|
@@ -1592,7 +1594,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1592
1594
|
revision: current.revision + 1,
|
|
1593
1595
|
}));
|
|
1594
1596
|
if (!applying)
|
|
1595
|
-
return operationError('checkout_missing', '
|
|
1597
|
+
return operationError('checkout_missing', hostMessage('theIsolatedCheckoutRecordDoesNotExist'));
|
|
1596
1598
|
const planResult = await dependencies.applyEngine.plan({
|
|
1597
1599
|
baseOid: applyBaseOid,
|
|
1598
1600
|
isolatedPath: applying.managedRoot,
|
|
@@ -1626,7 +1628,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1626
1628
|
journal: null,
|
|
1627
1629
|
revision: current.revision + 1,
|
|
1628
1630
|
}));
|
|
1629
|
-
return operationError('stale_isolated', '
|
|
1631
|
+
return operationError('stale_isolated', hostMessage('worktreeChangedAfterBeingMarkedReadyForReviewPrepare'), await inspectIsolated(binding));
|
|
1630
1632
|
}
|
|
1631
1633
|
if (planResult.plan.changedFiles.length === 0) {
|
|
1632
1634
|
updateManagedCheckout(applying.checkoutId, (current) => ({ ...current, phase: 'ready', journal: null, revision: current.revision + 1 }));
|
|
@@ -1643,11 +1645,11 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1643
1645
|
reviewId: operationId,
|
|
1644
1646
|
iteration,
|
|
1645
1647
|
preparedAt: startedAt,
|
|
1646
|
-
summary: '
|
|
1648
|
+
summary: hostMessage('worktreeChangesWereWrittenToLocalPreviewThroughApplyWorktree'),
|
|
1647
1649
|
validationStatus: 'not_run',
|
|
1648
1650
|
tests: [],
|
|
1649
1651
|
changedFiles: [...planResult.plan.changedFiles],
|
|
1650
|
-
suggestedCommitMessage: '
|
|
1652
|
+
suggestedCommitMessage: hostMessage('choreCommitWorktreeChanges'),
|
|
1651
1653
|
isolatedFingerprint: planResult.plan.isolatedFingerprint,
|
|
1652
1654
|
isolatedHeadOid: planResult.plan.isolatedHeadOid,
|
|
1653
1655
|
};
|
|
@@ -1715,7 +1717,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1715
1717
|
await dependencies.git.releaseInternalArtifacts(record.localRoot, record.checkoutId, `previews/${previewId}`);
|
|
1716
1718
|
}
|
|
1717
1719
|
catch {
|
|
1718
|
-
console.warn('
|
|
1720
|
+
console.warn(hostMessage('previewRefsCleanupFailed'));
|
|
1719
1721
|
}
|
|
1720
1722
|
}
|
|
1721
1723
|
async function previewArtifactsMatch(record, receipt) {
|
|
@@ -1766,28 +1768,28 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1766
1768
|
async function preflightPreviewRecoveryTarget(sessionId, expectedRevision, expectedReviewId, expectedPreviewId) {
|
|
1767
1769
|
const binding = await resolveBinding(sessionId);
|
|
1768
1770
|
if (binding.ownerSessionId !== sessionId || binding.target.kind !== 'isolated') {
|
|
1769
|
-
return blockedPreviewRecovery(undefined, 'not_owner', '
|
|
1771
|
+
return blockedPreviewRecovery(undefined, 'not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanCheckPreviewRecovery'));
|
|
1770
1772
|
}
|
|
1771
1773
|
const record = dependencies.registry.read().managedCheckouts[binding.target.checkoutId];
|
|
1772
1774
|
if (!record)
|
|
1773
|
-
return blockedPreviewRecovery(undefined, 'checkout_unavailable', '
|
|
1775
|
+
return blockedPreviewRecovery(undefined, 'checkout_unavailable', hostMessage('theIsolatedCheckoutRecordDoesNotExist'));
|
|
1774
1776
|
if (record.revision !== expectedRevision) {
|
|
1775
|
-
return blockedPreviewRecovery(record, 'stale_target', '
|
|
1777
|
+
return blockedPreviewRecovery(record, 'stale_target', hostMessage('theSessionTargetHasChangedRefreshBeforeCheckingAgain'));
|
|
1776
1778
|
}
|
|
1777
1779
|
if (record.phase !== 'ready'
|
|
1778
1780
|
|| record.delivery.state !== 'preview_detached'
|
|
1779
1781
|
|| record.delivery.review.reviewId !== expectedReviewId
|
|
1780
1782
|
|| record.delivery.preview.previewId !== expectedPreviewId) {
|
|
1781
|
-
return blockedPreviewRecovery(record, 'not_preview_detached', '
|
|
1783
|
+
return blockedPreviewRecovery(record, 'not_preview_detached', hostMessage('theCurrentStateIsNotTheSpecifiedDetachedPreview'));
|
|
1782
1784
|
}
|
|
1783
1785
|
const validated = await validateManagedCheckoutDetailed(binding, record, false);
|
|
1784
1786
|
if (validated.status !== 'valid') {
|
|
1785
|
-
return blockedPreviewRecovery(record, 'checkout_unavailable', '
|
|
1787
|
+
return blockedPreviewRecovery(record, 'checkout_unavailable', hostMessage('theWorktreeIdentityPathOrGitStateCannotCurrently'));
|
|
1786
1788
|
}
|
|
1787
1789
|
const { review, preview } = record.delivery;
|
|
1788
1790
|
try {
|
|
1789
1791
|
if (!await previewArtifactsMatch(record, preview)) {
|
|
1790
|
-
return blockedPreviewRecovery(record, 'artifacts_missing', '
|
|
1792
|
+
return blockedPreviewRecovery(record, 'artifacts_missing', hostMessage('previewRetainedArtifactsAreMissingOrDoNotMatch'));
|
|
1791
1793
|
}
|
|
1792
1794
|
const assessment = await dependencies.applyEngine.assessPreviewRecovery({
|
|
1793
1795
|
localPath: record.localRoot,
|
|
@@ -1801,10 +1803,10 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1801
1803
|
? { checkoutId: holder.checkoutId, ownerSessionId: holder.ownerSessionId, revision: holder.revision, state: holder.delivery.state }
|
|
1802
1804
|
: undefined;
|
|
1803
1805
|
const rollback = holder
|
|
1804
|
-
? { status: 'blocked', code: 'project_acceptance_busy', message: '
|
|
1806
|
+
? { status: 'blocked', code: 'project_acceptance_busy', message: hostMessage('anotherTaskIsOccupyingThisProjectSLocalAcceptance') }
|
|
1805
1807
|
: assessment.rollback;
|
|
1806
1808
|
const finalize = holder
|
|
1807
|
-
? { status: 'blocked', code: 'project_acceptance_busy', message: '
|
|
1809
|
+
? { status: 'blocked', code: 'project_acceptance_busy', message: hostMessage('anotherTaskIsOccupyingThisProjectSLocalAcceptance') }
|
|
1808
1810
|
: assessment.finalize;
|
|
1809
1811
|
const facts = {
|
|
1810
1812
|
sessionId,
|
|
@@ -1830,16 +1832,16 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1830
1832
|
|| current.delivery.state !== 'preview_detached'
|
|
1831
1833
|
|| current.delivery.review.reviewId !== review.reviewId
|
|
1832
1834
|
|| current.delivery.preview.previewId !== preview.previewId) {
|
|
1833
|
-
return blockedPreviewRecovery(current, 'stale_target', '
|
|
1835
|
+
return blockedPreviewRecovery(current, 'stale_target', hostMessage('thePreviewRecoveryStateChangedDuringTheCheck'));
|
|
1834
1836
|
}
|
|
1835
1837
|
if (!await previewArtifactsMatch(current, current.delivery.preview)) {
|
|
1836
|
-
return blockedPreviewRecovery(current, 'artifacts_missing', '
|
|
1838
|
+
return blockedPreviewRecovery(current, 'artifacts_missing', hostMessage('previewRetainedArtifactsChangedDuringTheCheck'));
|
|
1837
1839
|
}
|
|
1838
1840
|
const proof = { ...facts, generation: recoveryGeneration(facts) };
|
|
1839
1841
|
return { status: 'assessed', localModified: false, proof };
|
|
1840
1842
|
}
|
|
1841
1843
|
catch (error) {
|
|
1842
|
-
const message = error instanceof Error ? error.message : '
|
|
1844
|
+
const message = error instanceof Error ? error.message : hostMessage('previewRecoveryCheckFailed');
|
|
1843
1845
|
return blockedPreviewRecovery(record, 'git_error', message);
|
|
1844
1846
|
}
|
|
1845
1847
|
}
|
|
@@ -1871,24 +1873,24 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1871
1873
|
async function preflightTarget(sessionId, expectedRevision) {
|
|
1872
1874
|
const binding = await resolveBinding(sessionId);
|
|
1873
1875
|
if (binding.ownerSessionId !== sessionId || binding.target.kind !== 'isolated') {
|
|
1874
|
-
return blockedPreflight(undefined, 'not_owner', '
|
|
1876
|
+
return blockedPreflight(undefined, 'not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanRunSyncPreflight'));
|
|
1875
1877
|
}
|
|
1876
1878
|
const record = dependencies.registry.read().managedCheckouts[binding.target.checkoutId];
|
|
1877
1879
|
if (!record)
|
|
1878
|
-
return blockedPreflight(undefined, 'checkout_unavailable', '
|
|
1880
|
+
return blockedPreflight(undefined, 'checkout_unavailable', hostMessage('theIsolatedCheckoutRecordDoesNotExist'));
|
|
1879
1881
|
if (record.revision !== expectedRevision) {
|
|
1880
|
-
return blockedPreflight(record, 'stale_target', '
|
|
1882
|
+
return blockedPreflight(record, 'stale_target', hostMessage('theSessionTargetHasChangedRefreshBeforeRunningPreflight'));
|
|
1881
1883
|
}
|
|
1882
1884
|
if (record.phase !== 'ready' || record.delivery.state !== 'ready_for_review') {
|
|
1883
|
-
return blockedPreflight(record, 'not_ready_for_review', '
|
|
1885
|
+
return blockedPreflight(record, 'not_ready_for_review', hostMessage('theCurrentWorktreeIsNotReadyForReview'));
|
|
1884
1886
|
}
|
|
1885
1887
|
const holder = findProjectAcceptanceHolder(record);
|
|
1886
1888
|
if (holder) {
|
|
1887
|
-
return blockedPreflight(record, 'project_acceptance_busy', '
|
|
1889
|
+
return blockedPreflight(record, 'project_acceptance_busy', hostMessage('anotherTaskIsOccupyingThisProjectSLocalAcceptance'), holder);
|
|
1888
1890
|
}
|
|
1889
1891
|
const validated = await validateManagedCheckoutDetailed(binding, record, false);
|
|
1890
1892
|
if (validated.status !== 'valid') {
|
|
1891
|
-
return blockedPreflight(record, 'checkout_unavailable', '
|
|
1893
|
+
return blockedPreflight(record, 'checkout_unavailable', hostMessage('theWorktreeIdentityPathOrGitStateCannotCurrently'));
|
|
1892
1894
|
}
|
|
1893
1895
|
const review = record.delivery.review;
|
|
1894
1896
|
const result = await dependencies.applyEngine.preflight({
|
|
@@ -1901,7 +1903,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1901
1903
|
|| current.revision !== expectedRevision
|
|
1902
1904
|
|| current.delivery.state !== 'ready_for_review'
|
|
1903
1905
|
|| current.delivery.review.reviewId !== review.reviewId)
|
|
1904
|
-
return blockedPreflight(current, 'stale_target', '
|
|
1906
|
+
return blockedPreflight(current, 'stale_target', hostMessage('theSessionTargetChangedDuringPreflightRefreshAndRetry'));
|
|
1905
1907
|
if (result.status === 'error') {
|
|
1906
1908
|
const reason = result.error.code === 'stale_isolated'
|
|
1907
1909
|
? 'stale_isolated'
|
|
@@ -1914,7 +1916,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1914
1916
|
? result.plan.isolatedFingerprint
|
|
1915
1917
|
: result.isolatedFingerprint;
|
|
1916
1918
|
if (isolatedFingerprint !== review.isolatedFingerprint) {
|
|
1917
|
-
return blockedPreflight(current, 'stale_isolated', '
|
|
1919
|
+
return blockedPreflight(current, 'stale_isolated', hostMessage('worktreeChangedAfterTheReviewWasPreparedRegenerateThe'));
|
|
1918
1920
|
}
|
|
1919
1921
|
const localBranch = result.status === 'ready' && result.plan.localHeadRef?.startsWith('refs/heads/')
|
|
1920
1922
|
? result.plan.localHeadRef.slice('refs/heads/'.length)
|
|
@@ -1944,28 +1946,28 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1944
1946
|
}
|
|
1945
1947
|
async function operatePreview(input, binding) {
|
|
1946
1948
|
if (binding.ownerSessionId !== input.sessionId || binding.target.kind !== 'isolated') {
|
|
1947
|
-
return operationError('not_owner', '
|
|
1949
|
+
return operationError('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanSyncAReview'));
|
|
1948
1950
|
}
|
|
1949
1951
|
let record = dependencies.registry.read().managedCheckouts[binding.target.checkoutId];
|
|
1950
1952
|
if (!record)
|
|
1951
|
-
return operationError('checkout_missing', '
|
|
1953
|
+
return operationError('checkout_missing', hostMessage('theIsolatedCheckoutRecordDoesNotExist'));
|
|
1952
1954
|
if (record.revision !== input.expectedRevision) {
|
|
1953
|
-
return operationError('stale_target', '
|
|
1955
|
+
return operationError('stale_target', hostMessage('theSessionTargetHasChangedRefreshAndRetry'), await inspectIsolated(binding));
|
|
1954
1956
|
}
|
|
1955
1957
|
if (record.phase !== 'ready' || record.delivery.state !== 'ready_for_review') {
|
|
1956
|
-
return operationError('operation_not_allowed', '
|
|
1958
|
+
return operationError('operation_not_allowed', hostMessage('theCurrentWorktreeIsNotReadyForReview'), await inspectIsolated(binding));
|
|
1957
1959
|
}
|
|
1958
1960
|
const holder = findProjectAcceptanceHolder(record);
|
|
1959
1961
|
if (holder) {
|
|
1960
|
-
return operationError('project_acceptance_busy',
|
|
1962
|
+
return operationError('project_acceptance_busy', hostMessage('anotherAcceptanceTaskInTheSameProjectIsOccupying', { p0: holder.projectName }), await inspectIsolated(binding));
|
|
1961
1963
|
}
|
|
1962
1964
|
const inspected = await inspectIsolated(binding);
|
|
1963
1965
|
if (inspected.checkout.phase !== 'ready') {
|
|
1964
|
-
return operationError('recovery_required', '
|
|
1966
|
+
return operationError('recovery_required', hostMessage('theIsolatedCheckoutIdentityCannotBeVerifiedRecoveryIs'), inspected);
|
|
1965
1967
|
}
|
|
1966
1968
|
record = dependencies.registry.read().managedCheckouts[record.checkoutId];
|
|
1967
1969
|
if (!record || record.delivery.state !== 'ready_for_review') {
|
|
1968
|
-
return operationError('stale_target', '
|
|
1970
|
+
return operationError('stale_target', hostMessage('theAcceptanceStateHasChangedRefreshAndRetry'));
|
|
1969
1971
|
}
|
|
1970
1972
|
const review = record.delivery.review;
|
|
1971
1973
|
const operationId = dependencies.createCheckoutId();
|
|
@@ -1987,7 +1989,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
1987
1989
|
revision: current.revision + 1,
|
|
1988
1990
|
}));
|
|
1989
1991
|
if (!mutating)
|
|
1990
|
-
return operationError('checkout_missing', '
|
|
1992
|
+
return operationError('checkout_missing', hostMessage('theIsolatedCheckoutRecordDoesNotExist'));
|
|
1991
1993
|
const planResult = await dependencies.applyEngine.plan({
|
|
1992
1994
|
baseOid: applyBaseOid,
|
|
1993
1995
|
isolatedPath: mutating.managedRoot,
|
|
@@ -2020,7 +2022,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
2020
2022
|
journal: null,
|
|
2021
2023
|
revision: current.revision + 1,
|
|
2022
2024
|
}));
|
|
2023
|
-
return operationError('stale_isolated', '
|
|
2025
|
+
return operationError('stale_isolated', hostMessage('worktreeChangedAfterBeingMarkedReadyForReviewPrepare'), await inspectIsolated(binding));
|
|
2024
2026
|
}
|
|
2025
2027
|
updateManagedCheckout(mutating.checkoutId, (current) => ({
|
|
2026
2028
|
...current,
|
|
@@ -2085,7 +2087,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
2085
2087
|
}
|
|
2086
2088
|
async function detachPreviewAfterLocalDrift(record, binding, reason, attemptedAction) {
|
|
2087
2089
|
if (record.delivery.state !== 'preview_active') {
|
|
2088
|
-
return operationError('preview_not_active', '
|
|
2090
|
+
return operationError('preview_not_active', hostMessage('thereIsNoLocalPreviewToRelease'), await inspectIsolated(binding));
|
|
2089
2091
|
}
|
|
2090
2092
|
const { review, preview } = record.delivery;
|
|
2091
2093
|
const detached = updateManagedCheckout(record.checkoutId, (current) => {
|
|
@@ -2107,7 +2109,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
2107
2109
|
};
|
|
2108
2110
|
});
|
|
2109
2111
|
if (!detached || detached.delivery.state !== 'preview_detached') {
|
|
2110
|
-
return operationError('stale_target', '
|
|
2112
|
+
return operationError('stale_target', hostMessage('thePreviewStateHasChangedRefreshAndRetry'), await inspectIsolated(binding));
|
|
2111
2113
|
}
|
|
2112
2114
|
return {
|
|
2113
2115
|
status: 'preview_detached',
|
|
@@ -2119,20 +2121,20 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
2119
2121
|
}
|
|
2120
2122
|
async function operateRollbackPreview(input, binding) {
|
|
2121
2123
|
if (binding.ownerSessionId !== input.sessionId || binding.target.kind !== 'isolated') {
|
|
2122
|
-
return operationError('not_owner', '
|
|
2124
|
+
return operationError('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanRollBackAcceptance'));
|
|
2123
2125
|
}
|
|
2124
2126
|
const record = dependencies.registry.read().managedCheckouts[binding.target.checkoutId];
|
|
2125
2127
|
if (!record)
|
|
2126
|
-
return operationError('checkout_missing', '
|
|
2128
|
+
return operationError('checkout_missing', hostMessage('theIsolatedCheckoutRecordDoesNotExist'));
|
|
2127
2129
|
if (record.revision !== input.expectedRevision) {
|
|
2128
|
-
return operationError('stale_target', '
|
|
2130
|
+
return operationError('stale_target', hostMessage('theSessionTargetHasChangedRefreshAndRetry'), await inspectIsolated(binding));
|
|
2129
2131
|
}
|
|
2130
2132
|
const retryingRecovery = record.phase === 'recovery_required'
|
|
2131
2133
|
&& record.delivery.state === 'preview_active'
|
|
2132
2134
|
&& record.journal?.operation === 'rollback_preview';
|
|
2133
2135
|
if ((record.phase !== 'ready' && !retryingRecovery)
|
|
2134
2136
|
|| (record.delivery.state !== 'preview_active' && record.delivery.state !== 'preview_detached')) {
|
|
2135
|
-
return operationError('preview_not_active', '
|
|
2137
|
+
return operationError('preview_not_active', hostMessage('thereIsNoLocalPreviewToRollBack'), await inspectIsolated(binding));
|
|
2136
2138
|
}
|
|
2137
2139
|
const retryingDetached = record.delivery.state === 'preview_detached';
|
|
2138
2140
|
if (retryingDetached) {
|
|
@@ -2141,7 +2143,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
2141
2143
|
return operationError('stale_target', recovery.message, await inspectIsolated(binding));
|
|
2142
2144
|
}
|
|
2143
2145
|
if (!recoveryProofMatches(input.recoveryProof, recovery.proof)) {
|
|
2144
|
-
return operationError('stale_target', '
|
|
2146
|
+
return operationError('stale_target', hostMessage('thePreviewRecoveryProofHasExpiredOrDoesNot'), await inspectIsolated(binding));
|
|
2145
2147
|
}
|
|
2146
2148
|
if (recovery.proof.rollback.status !== 'safe') {
|
|
2147
2149
|
return operationError(recovery.proof.rollback.code, recovery.proof.rollback.message, await inspectIsolated(binding));
|
|
@@ -2262,10 +2264,10 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
2262
2264
|
}
|
|
2263
2265
|
return current;
|
|
2264
2266
|
});
|
|
2265
|
-
return { cleaned: false, message, reason };
|
|
2267
|
+
return { cleaned: false, message: hostPersistedCleanupMessage(message, hostMessage), reason };
|
|
2266
2268
|
};
|
|
2267
2269
|
if (record.delivery.state !== 'finalized' && record.delivery.state !== 'retained') {
|
|
2268
|
-
return block(
|
|
2270
|
+
return block(hostMessages.cleanupIncompleteDelivery.zh);
|
|
2269
2271
|
}
|
|
2270
2272
|
if ((record.delivery.state === 'finalized' || record.delivery.state === 'retained')
|
|
2271
2273
|
&& record.delivery.cleanup === 'blocked'
|
|
@@ -2305,7 +2307,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
2305
2307
|
const quarantineAndRemove = async (currentRecord, residue) => {
|
|
2306
2308
|
const journal = currentRecord.journal?.operation === 'cleanup' ? currentRecord.journal : undefined;
|
|
2307
2309
|
if (!journal?.managedDirectoryIdentity)
|
|
2308
|
-
throw new SessionCheckoutError('checkout_mismatch',
|
|
2310
|
+
throw new SessionCheckoutError('checkout_mismatch', hostMessages.cleanupIncompleteReceipt.zh);
|
|
2309
2311
|
const quarantinePath = journal.cleanupQuarantinePath ?? join(dirname(currentRecord.managedGitRoot), `.dsh-wt-cleanup--${currentRecord.checkoutId.replace(/[^a-zA-Z0-9]/g, '').slice(0, 8)}--${journal.operationId}`);
|
|
2310
2312
|
const quarantining = journal.cleanupQuarantinePath
|
|
2311
2313
|
? currentRecord
|
|
@@ -2317,13 +2319,13 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
2317
2319
|
revision: current.revision + 1,
|
|
2318
2320
|
}));
|
|
2319
2321
|
if (!quarantining)
|
|
2320
|
-
throw new SessionCheckoutError('checkout_missing',
|
|
2322
|
+
throw new SessionCheckoutError('checkout_missing', hostMessages.cleanupMissingBeforeQuarantine.zh);
|
|
2321
2323
|
if (!dependencies.files.exists(quarantinePath)) {
|
|
2322
2324
|
await retryTransientCleanup(() => dependencies.files.quarantineDirectoryTree(residue.canonicalManagedGitRoot, residue.directoryIdentity, quarantinePath));
|
|
2323
2325
|
}
|
|
2324
2326
|
const validatedQuarantine = await validateCleanupQuarantine(quarantining);
|
|
2325
2327
|
if (!validatedQuarantine)
|
|
2326
|
-
throw new SessionCheckoutError('checkout_mismatch',
|
|
2328
|
+
throw new SessionCheckoutError('checkout_mismatch', hostMessages.cleanupQuarantineIdentity.zh);
|
|
2327
2329
|
await retryTransientCleanup(() => dependencies.files.removeDirectoryTree(validatedQuarantine));
|
|
2328
2330
|
};
|
|
2329
2331
|
try {
|
|
@@ -2345,14 +2347,14 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
2345
2347
|
localPath: record.localRoot,
|
|
2346
2348
|
});
|
|
2347
2349
|
if (snapshot.status !== 'ready' || snapshot.isolatedFingerprint !== record.delivery.isolatedFingerprint) {
|
|
2348
|
-
return block(
|
|
2350
|
+
return block(hostMessages.cleanupNewChanges.zh);
|
|
2349
2351
|
}
|
|
2350
2352
|
const directoryIdentity = await dependencies.files.inspectDirectoryIdentity(validated.canonicalManagedGitRoot);
|
|
2351
2353
|
if (!directoryIdentity)
|
|
2352
2354
|
return block(CLEANUP_IDENTITY_CHANGED_MESSAGE);
|
|
2353
2355
|
const removing = beginRemoval(record, directoryIdentity);
|
|
2354
2356
|
if (!removing)
|
|
2355
|
-
return block(
|
|
2357
|
+
return block(hostMessages.cleanupMissingBeforeRemoval.zh);
|
|
2356
2358
|
await retryTransientCleanup(() => dependencies.git.removeWorktree(removing.localRoot, removing.managedGitRoot));
|
|
2357
2359
|
if (dependencies.files.exists(removing.managedGitRoot)) {
|
|
2358
2360
|
const residue = await validateDetachedCleanupResidue(removing);
|
|
@@ -2367,7 +2369,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
2367
2369
|
return block(CLEANUP_IDENTITY_CHANGED_MESSAGE);
|
|
2368
2370
|
const removing = beginRemoval(record, residue.directoryIdentity);
|
|
2369
2371
|
if (!removing)
|
|
2370
|
-
return block(
|
|
2372
|
+
return block(hostMessages.cleanupMissingBeforeRemoval.zh);
|
|
2371
2373
|
const revalidatedResidue = await validateDetachedCleanupResidue(removing);
|
|
2372
2374
|
if (!revalidatedResidue)
|
|
2373
2375
|
return block(CLEANUP_IDENTITY_CHANGED_MESSAGE);
|
|
@@ -2400,8 +2402,8 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
2400
2402
|
const quarantineBusy = failureRecord.journal?.operation === 'cleanup' && Boolean(failureRecord.journal.cleanupQuarantinePath);
|
|
2401
2403
|
const reason = quarantineBusy ? 'quarantine_busy' : 'directory_busy';
|
|
2402
2404
|
const message = quarantineBusy
|
|
2403
|
-
?
|
|
2404
|
-
:
|
|
2405
|
+
? hostMessages.cleanupQuarantineBusy.zh
|
|
2406
|
+
: hostMessages.cleanupDirectoryBusy.zh;
|
|
2405
2407
|
updateManagedCheckout(record.checkoutId, (current) => {
|
|
2406
2408
|
const journal = current.journal?.operation === 'cleanup' ? current.journal : null;
|
|
2407
2409
|
if (current.delivery.state === 'finalized') {
|
|
@@ -2417,17 +2419,17 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
2417
2419
|
}
|
|
2418
2420
|
async function operateFinalizePreview(input, binding) {
|
|
2419
2421
|
if (binding.ownerSessionId !== input.sessionId || binding.target.kind !== 'isolated') {
|
|
2420
|
-
return operationError('not_owner', '
|
|
2422
|
+
return operationError('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanFinalizeTheAcceptance'));
|
|
2421
2423
|
}
|
|
2422
2424
|
const record = dependencies.registry.read().managedCheckouts[binding.target.checkoutId];
|
|
2423
2425
|
if (!record)
|
|
2424
|
-
return operationError('checkout_missing', '
|
|
2426
|
+
return operationError('checkout_missing', hostMessage('theIsolatedCheckoutRecordDoesNotExist'));
|
|
2425
2427
|
if (record.revision !== input.expectedRevision) {
|
|
2426
|
-
return operationError('stale_target', '
|
|
2428
|
+
return operationError('stale_target', hostMessage('theSessionTargetHasChangedRefreshAndRetry'), await inspectIsolated(binding));
|
|
2427
2429
|
}
|
|
2428
2430
|
if (record.phase !== 'ready'
|
|
2429
2431
|
|| (record.delivery.state !== 'preview_active' && record.delivery.state !== 'preview_detached')) {
|
|
2430
|
-
return operationError('preview_not_active', '
|
|
2432
|
+
return operationError('preview_not_active', hostMessage('thereIsNoLocalPreviewAwaitingAcceptance'), await inspectIsolated(binding));
|
|
2431
2433
|
}
|
|
2432
2434
|
const retryingDetached = record.delivery.state === 'preview_detached';
|
|
2433
2435
|
if (retryingDetached) {
|
|
@@ -2436,7 +2438,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
2436
2438
|
return operationError('stale_target', recovery.message, await inspectIsolated(binding));
|
|
2437
2439
|
}
|
|
2438
2440
|
if (!recoveryProofMatches(input.recoveryProof, recovery.proof)) {
|
|
2439
|
-
return operationError('stale_target', '
|
|
2441
|
+
return operationError('stale_target', hostMessage('thePreviewRecoveryProofHasExpiredOrDoesNot'), await inspectIsolated(binding));
|
|
2440
2442
|
}
|
|
2441
2443
|
if (recovery.proof.finalize.status !== 'safe') {
|
|
2442
2444
|
return operationError(recovery.proof.finalize.code, recovery.proof.finalize.message, await inspectIsolated(binding));
|
|
@@ -2522,12 +2524,12 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
2522
2524
|
revision: current.revision + 1,
|
|
2523
2525
|
}));
|
|
2524
2526
|
if (!finalized)
|
|
2525
|
-
return operationError('checkout_missing', '
|
|
2527
|
+
return operationError('checkout_missing', hostMessage('theCommitWasCreatedButTheCheckoutRecordIs'));
|
|
2526
2528
|
const retention = input.retention ?? 'cleanup';
|
|
2527
2529
|
if (retention !== 'cleanup') {
|
|
2528
2530
|
const retained = retainFinalized(finalized, retention);
|
|
2529
2531
|
if (!retained)
|
|
2530
|
-
return operationError('checkout_missing', '
|
|
2532
|
+
return operationError('checkout_missing', hostMessage('theCommitWasCreatedButSavingTheRetainedWorktree'));
|
|
2531
2533
|
return {
|
|
2532
2534
|
status: 'finished',
|
|
2533
2535
|
target: await inspectIsolated(binding),
|
|
@@ -2549,16 +2551,16 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
2549
2551
|
}
|
|
2550
2552
|
async function operateRetryCleanup(input, binding) {
|
|
2551
2553
|
if (binding.ownerSessionId !== input.sessionId || binding.target.kind !== 'isolated') {
|
|
2552
|
-
return operationError('not_owner', '
|
|
2554
|
+
return operationError('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanRetryCleanup'));
|
|
2553
2555
|
}
|
|
2554
2556
|
const record = dependencies.registry.read().managedCheckouts[binding.target.checkoutId];
|
|
2555
2557
|
if (!record)
|
|
2556
|
-
return operationError('checkout_missing', '
|
|
2558
|
+
return operationError('checkout_missing', hostMessage('theIsolatedCheckoutRecordDoesNotExist'));
|
|
2557
2559
|
if (record.revision !== input.expectedRevision) {
|
|
2558
|
-
return operationError('stale_target', '
|
|
2560
|
+
return operationError('stale_target', hostMessage('theSessionTargetHasChangedRefreshAndRetry'), await inspectIsolated(binding));
|
|
2559
2561
|
}
|
|
2560
2562
|
if (record.delivery.state !== 'finalized' && record.delivery.state !== 'retained') {
|
|
2561
|
-
return operationError('operation_not_allowed', '
|
|
2563
|
+
return operationError('operation_not_allowed', hostMessage('thereIsNoWorktreeCleanupToRetry'), await inspectIsolated(binding));
|
|
2562
2564
|
}
|
|
2563
2565
|
const cleanup = await cleanupFinalized(record, { allowLegacyResidue: true });
|
|
2564
2566
|
return {
|
|
@@ -2573,38 +2575,38 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
2573
2575
|
}
|
|
2574
2576
|
async function operateFinish(input, binding) {
|
|
2575
2577
|
if (binding.ownerSessionId !== input.sessionId) {
|
|
2576
|
-
return operationError('not_owner', '
|
|
2578
|
+
return operationError('not_owner', hostMessage('aSessionThatInheritedItsSessionTargetCannotPerform162'));
|
|
2577
2579
|
}
|
|
2578
2580
|
if (binding.target.kind !== 'isolated') {
|
|
2579
|
-
return operationError('operation_not_allowed', '
|
|
2581
|
+
return operationError('operation_not_allowed', hostMessage('localCheckoutDoesNotSupportFinish'));
|
|
2580
2582
|
}
|
|
2581
2583
|
let record = dependencies.registry.read().managedCheckouts[binding.target.checkoutId];
|
|
2582
2584
|
if (!record)
|
|
2583
|
-
return operationError('checkout_missing', '
|
|
2585
|
+
return operationError('checkout_missing', hostMessage('theIsolatedCheckoutRecordDoesNotExist'));
|
|
2584
2586
|
if (record.revision !== input.expectedRevision) {
|
|
2585
|
-
return operationError('stale_target', '
|
|
2587
|
+
return operationError('stale_target', hostMessage('theSessionTargetHasChangedRefreshAndRetry'), await inspectIsolated(binding));
|
|
2586
2588
|
}
|
|
2587
2589
|
if (input.expectedReviewId !== undefined && (record.delivery.state !== 'ready_for_review'
|
|
2588
2590
|
|| record.delivery.review.reviewId !== input.expectedReviewId)) {
|
|
2589
|
-
return operationError('stale_target', '
|
|
2591
|
+
return operationError('stale_target', hostMessage('thisReviewCardIsNoLongerTheCurrentReview166'), await inspectIsolated(binding));
|
|
2590
2592
|
}
|
|
2591
2593
|
if (record.applyBaseOid && (record.delivery.state === 'working' || record.delivery.state === 'ready_for_review')) {
|
|
2592
|
-
return operationError('operation_not_allowed', '
|
|
2594
|
+
return operationError('operation_not_allowed', hostMessage('thisLegacyWorktreeWasWrittenToLocalThroughAn'), await inspectIsolated(binding));
|
|
2593
2595
|
}
|
|
2594
2596
|
if (record.phase !== 'ready' || record.delivery.state === 'preview_active' || record.delivery.state === 'preview_detached') {
|
|
2595
|
-
return operationError('operation_not_allowed',
|
|
2597
|
+
return operationError('operation_not_allowed', hostMessage('cannotFinishDirectlyInTheCurrentState', { p0: record.phase, p1: record.delivery.state }), await inspectIsolated(binding));
|
|
2596
2598
|
}
|
|
2597
2599
|
const holder = findProjectAcceptanceHolder(record);
|
|
2598
2600
|
if (holder) {
|
|
2599
|
-
return operationError('project_acceptance_busy',
|
|
2601
|
+
return operationError('project_acceptance_busy', hostMessage('anotherAcceptanceTaskInTheSameProjectIsOccupying', { p0: holder.projectName }), await inspectIsolated(binding));
|
|
2600
2602
|
}
|
|
2601
2603
|
const inspected = await inspectIsolated(binding);
|
|
2602
2604
|
if (inspected.checkout.phase !== 'ready') {
|
|
2603
|
-
return operationError('recovery_required', '
|
|
2605
|
+
return operationError('recovery_required', hostMessage('theIsolatedCheckoutIdentityCannotBeVerifiedRecoveryIs'), inspected);
|
|
2604
2606
|
}
|
|
2605
2607
|
record = dependencies.registry.read().managedCheckouts[record.checkoutId];
|
|
2606
2608
|
if (!record || record.phase !== 'ready') {
|
|
2607
|
-
return operationError('recovery_required', '
|
|
2609
|
+
return operationError('recovery_required', hostMessage('theIsolatedCheckoutStateHasChangedRecoveryIsRequired'));
|
|
2608
2610
|
}
|
|
2609
2611
|
const startedAt = Date.now();
|
|
2610
2612
|
const operationId = dependencies.createCheckoutId();
|
|
@@ -2622,7 +2624,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
2622
2624
|
revision: current.revision + 1,
|
|
2623
2625
|
}));
|
|
2624
2626
|
if (!applying)
|
|
2625
|
-
return operationError('checkout_missing', '
|
|
2627
|
+
return operationError('checkout_missing', hostMessage('theIsolatedCheckoutRecordDoesNotExist'));
|
|
2626
2628
|
const planResult = await dependencies.applyEngine.plan({
|
|
2627
2629
|
baseOid: applyBaseOid,
|
|
2628
2630
|
isolatedPath: applying.managedRoot,
|
|
@@ -2670,7 +2672,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
2670
2672
|
journal: null,
|
|
2671
2673
|
revision: current.revision + 1,
|
|
2672
2674
|
}));
|
|
2673
|
-
return operationError('stale_isolated', '
|
|
2675
|
+
return operationError('stale_isolated', hostMessage('worktreeChangedAfterReviewPrepareTheReviewAgain'), await inspectIsolated(binding));
|
|
2674
2676
|
}
|
|
2675
2677
|
const iteration = applying.delivery.state === 'working'
|
|
2676
2678
|
? applying.delivery.iteration
|
|
@@ -2683,7 +2685,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
2683
2685
|
reviewId: operationId,
|
|
2684
2686
|
iteration,
|
|
2685
2687
|
preparedAt: startedAt,
|
|
2686
|
-
summary: '
|
|
2688
|
+
summary: hostMessage('skipLocalAcceptanceAndCommitDirectly'),
|
|
2687
2689
|
validationStatus: 'not_run',
|
|
2688
2690
|
tests: [],
|
|
2689
2691
|
changedFiles: [...planResult.plan.changedFiles],
|
|
@@ -2804,13 +2806,13 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
2804
2806
|
revision: current.revision + 1,
|
|
2805
2807
|
}));
|
|
2806
2808
|
if (!finalized) {
|
|
2807
|
-
return operationError('checkout_missing', '
|
|
2809
|
+
return operationError('checkout_missing', hostMessage('theTaskCommitWasCreatedButTheCheckoutRecord'));
|
|
2808
2810
|
}
|
|
2809
2811
|
const retention = input.retention ?? 'cleanup';
|
|
2810
2812
|
if (retention !== 'cleanup') {
|
|
2811
2813
|
const retained = retainFinalized(finalized, retention);
|
|
2812
2814
|
if (!retained)
|
|
2813
|
-
return operationError('checkout_missing', '
|
|
2815
|
+
return operationError('checkout_missing', hostMessage('theTaskCommitWasCreatedButSavingTheRetained'));
|
|
2814
2816
|
return {
|
|
2815
2817
|
status: 'finished',
|
|
2816
2818
|
target: await inspectIsolated(binding),
|
|
@@ -2837,31 +2839,31 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
2837
2839
|
}
|
|
2838
2840
|
catch {
|
|
2839
2841
|
// checkout 删除优先;内部无 ref artifact 的清理失败不能阻止 owner 明确收口。
|
|
2840
|
-
console.warn('
|
|
2842
|
+
console.warn(hostMessage('internalRefsCleanupFailed'));
|
|
2841
2843
|
}
|
|
2842
2844
|
}
|
|
2843
2845
|
async function operateDiscard(input, binding) {
|
|
2844
2846
|
if (binding.ownerSessionId !== input.sessionId) {
|
|
2845
|
-
return operationError('not_owner', '
|
|
2847
|
+
return operationError('not_owner', hostMessage('aSessionThatInheritedItsSessionTargetCannotPerform178'));
|
|
2846
2848
|
}
|
|
2847
2849
|
if (binding.target.kind !== 'isolated') {
|
|
2848
|
-
return operationError('operation_not_allowed', '
|
|
2850
|
+
return operationError('operation_not_allowed', hostMessage('localCheckoutDoesNotSupportDiscard'));
|
|
2849
2851
|
}
|
|
2850
2852
|
let record = dependencies.registry.read().managedCheckouts[binding.target.checkoutId];
|
|
2851
2853
|
if (!record)
|
|
2852
|
-
return operationError('checkout_missing', '
|
|
2854
|
+
return operationError('checkout_missing', hostMessage('theIsolatedCheckoutRecordDoesNotExist'));
|
|
2853
2855
|
if (record.revision !== input.expectedRevision) {
|
|
2854
|
-
return operationError('stale_target', '
|
|
2856
|
+
return operationError('stale_target', hostMessage('theSessionTargetHasChangedRefreshAndRetry'), await inspectIsolated(binding));
|
|
2855
2857
|
}
|
|
2856
2858
|
if (record.delivery.state === 'preview_detached') {
|
|
2857
|
-
return operationError('preview_modified', '
|
|
2859
|
+
return operationError('preview_modified', hostMessage('localPreviewDriftedIntoDetachedRecoveryToPreserveRecovery'), await inspectIsolated(binding));
|
|
2858
2860
|
}
|
|
2859
2861
|
if (record.applyBaseOid && (record.delivery.state === 'working' || record.delivery.state === 'ready_for_review')) {
|
|
2860
|
-
return operationError('operation_not_allowed', '
|
|
2862
|
+
return operationError('operation_not_allowed', hostMessage('thisLegacyWorktreeWasWrittenToLocalThroughAn183'), await inspectIsolated(binding));
|
|
2861
2863
|
}
|
|
2862
2864
|
if (record.delivery.state === 'preview_active') {
|
|
2863
2865
|
if (!input.rollbackPreview) {
|
|
2864
|
-
return operationError('preview_not_active', '
|
|
2866
|
+
return operationError('preview_not_active', hostMessage('thisTaskIsPreviewingInLocalSafelyRollBack'), await inspectIsolated(binding));
|
|
2865
2867
|
}
|
|
2866
2868
|
const rollback = await dependencies.applyEngine.rollback({
|
|
2867
2869
|
localPath: record.localRoot,
|
|
@@ -2882,7 +2884,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
2882
2884
|
revision: current.revision + 1,
|
|
2883
2885
|
}));
|
|
2884
2886
|
if (!rolledBack)
|
|
2885
|
-
return operationError('checkout_missing', '
|
|
2887
|
+
return operationError('checkout_missing', hostMessage('theIsolatedCheckoutRecordDoesNotExist'));
|
|
2886
2888
|
await releasePreviewArtifactsBestEffort(record, previewId);
|
|
2887
2889
|
record = rolledBack;
|
|
2888
2890
|
}
|
|
@@ -2898,18 +2900,18 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
2898
2900
|
return { status: 'discarded', target: await inspectIsolated(binding) };
|
|
2899
2901
|
}
|
|
2900
2902
|
if (record.phase !== 'ready' && record.phase !== 'recovery_required') {
|
|
2901
|
-
return operationError('operation_not_allowed',
|
|
2903
|
+
return operationError('operation_not_allowed', hostMessage('cannotDiscardInTheCurrentState', { p0: record.phase }), await inspectIsolated(binding));
|
|
2902
2904
|
}
|
|
2903
2905
|
let inspected;
|
|
2904
2906
|
let dirty = true;
|
|
2905
2907
|
if (record.phase === 'ready') {
|
|
2906
2908
|
inspected = await inspectIsolated(binding);
|
|
2907
2909
|
if (inspected.checkout.phase !== 'ready') {
|
|
2908
|
-
return operationError('recovery_required', '
|
|
2910
|
+
return operationError('recovery_required', hostMessage('theIsolatedCheckoutIdentityCannotBeVerifiedRecoveryIs'), inspected);
|
|
2909
2911
|
}
|
|
2910
2912
|
record = dependencies.registry.read().managedCheckouts[record.checkoutId];
|
|
2911
2913
|
if (!record || record.phase !== 'ready') {
|
|
2912
|
-
return operationError('recovery_required', '
|
|
2914
|
+
return operationError('recovery_required', hostMessage('theIsolatedCheckoutStateHasChangedRecoveryIsRequired'));
|
|
2913
2915
|
}
|
|
2914
2916
|
dirty = (await dependencies.git.status(record.managedRoot)).dirty || (record.checkpoints?.length ?? 0) > 0;
|
|
2915
2917
|
}
|
|
@@ -2924,15 +2926,15 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
2924
2926
|
}
|
|
2925
2927
|
if (dirty && !input.confirmDirty) {
|
|
2926
2928
|
const checkpointDetail = (record.checkpoints?.length ?? 0) > 0
|
|
2927
|
-
?
|
|
2929
|
+
? hostMessage('confirmationWillPermanentlyDeleteCheckpointsNotYetDeliveredTo', { p0: record.checkpoints.length })
|
|
2928
2930
|
: '';
|
|
2929
|
-
return operationError('dirty_confirmation_required',
|
|
2931
|
+
return operationError('dirty_confirmation_required', hostMessage('theIsolatedCheckoutHasUncommittedChangesUndeliveredCheckpointsOr', { p0: checkpointDetail }), inspected);
|
|
2930
2932
|
}
|
|
2931
2933
|
try {
|
|
2932
2934
|
await dependencies.git.removeWorktree(record.localRoot, record.managedGitRoot);
|
|
2933
2935
|
}
|
|
2934
2936
|
catch {
|
|
2935
|
-
return operationError('git_operation_failed', '
|
|
2937
|
+
return operationError('git_operation_failed', hostMessage('failedToDeleteTheManagedCheckout'), await inspectIsolated(binding));
|
|
2936
2938
|
}
|
|
2937
2939
|
await releaseApplyBaseBestEffort(record);
|
|
2938
2940
|
updateManagedCheckout(record.checkoutId, (current) => ({
|
|
@@ -2945,27 +2947,27 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
2945
2947
|
}
|
|
2946
2948
|
async function operateRecover(input, binding) {
|
|
2947
2949
|
if (binding.ownerSessionId !== input.sessionId) {
|
|
2948
|
-
return operationError('not_owner', '
|
|
2950
|
+
return operationError('not_owner', hostMessage('aSessionThatInheritedItsSessionTargetCannotPerform192'));
|
|
2949
2951
|
}
|
|
2950
2952
|
if (binding.target.kind !== 'isolated') {
|
|
2951
|
-
return operationError('operation_not_allowed', '
|
|
2953
|
+
return operationError('operation_not_allowed', hostMessage('localCheckoutDoesNotSupportRecover'));
|
|
2952
2954
|
}
|
|
2953
2955
|
const record = dependencies.registry.read().managedCheckouts[binding.target.checkoutId];
|
|
2954
2956
|
if (!record)
|
|
2955
|
-
return operationError('checkout_missing', '
|
|
2957
|
+
return operationError('checkout_missing', hostMessage('theIsolatedCheckoutRecordDoesNotExist'));
|
|
2956
2958
|
if (record.revision !== input.expectedRevision) {
|
|
2957
|
-
return operationError('stale_target', '
|
|
2959
|
+
return operationError('stale_target', hostMessage('theSessionTargetHasChangedRefreshAndRetry'), await inspectIsolated(binding));
|
|
2958
2960
|
}
|
|
2959
2961
|
const recoverCreate = record.journal?.operation === 'create';
|
|
2960
2962
|
if ((record.journal !== null && !recoverCreate) || record.phase === 'mutating') {
|
|
2961
|
-
return operationError('recovery_unsafe', '
|
|
2963
|
+
return operationError('recovery_unsafe', hostMessage('cannotSafelyDetermineWhetherApplyModifiedLocalItWill'), await inspectIsolated(binding));
|
|
2962
2964
|
}
|
|
2963
2965
|
if (!dependencies.files.exists(record.managedRoot)) {
|
|
2964
|
-
return operationError('recovery_required', '
|
|
2966
|
+
return operationError('recovery_required', hostMessage('theIsolatedCheckoutIsMissingOnlyAnExplicitDiscard'));
|
|
2965
2967
|
}
|
|
2966
2968
|
const validated = await validateManagedCheckout(binding, record, recoverCreate);
|
|
2967
2969
|
if (!validated) {
|
|
2968
|
-
return operationError('recovery_unsafe', '
|
|
2970
|
+
return operationError('recovery_unsafe', hostMessage('theIsolatedCheckoutPathGitIdentityProjectHEADOr'));
|
|
2969
2971
|
}
|
|
2970
2972
|
updateManagedCheckout(record.checkoutId, (current) => ({
|
|
2971
2973
|
...current,
|
|
@@ -3104,7 +3106,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
3104
3106
|
checkoutId: record.checkoutId,
|
|
3105
3107
|
revision: record.revision,
|
|
3106
3108
|
ownerSessionId: record.ownerSessionId,
|
|
3107
|
-
ownerSessionTitle: dependencies.lookup.getSession(record.ownerSessionId)?.title?.trim() || '
|
|
3109
|
+
ownerSessionTitle: dependencies.lookup.getSession(record.ownerSessionId)?.title?.trim() || hostMessage('deletedAgentSession'),
|
|
3108
3110
|
project: { id: record.projectId, name: record.projectName },
|
|
3109
3111
|
iteration: managedIteration(record),
|
|
3110
3112
|
state,
|
|
@@ -3117,7 +3119,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
3117
3119
|
retainedAt: delivery.retainedAt,
|
|
3118
3120
|
expiresAt: delivery.expiresAt,
|
|
3119
3121
|
} : {}),
|
|
3120
|
-
...(cleanupMessage ? { cleanupMessage } : {}),
|
|
3122
|
+
...(cleanupMessage ? { cleanupMessage: hostPersistedCleanupMessage(cleanupMessage, hostMessage) } : {}),
|
|
3121
3123
|
...(cleanupReason ? { cleanupReason } : {}),
|
|
3122
3124
|
approximateBytes,
|
|
3123
3125
|
updatedAt: managedUpdatedAt(record),
|
|
@@ -3129,38 +3131,38 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
3129
3131
|
}
|
|
3130
3132
|
async function inspectCleanupForRecord(record) {
|
|
3131
3133
|
if (record.delivery.state === 'working')
|
|
3132
|
-
return cleanupBlocked('working', '
|
|
3134
|
+
return cleanupBlocked('working', hostMessage('theCurrentIterationIsStillBeingEditedAndHas'), record.revision);
|
|
3133
3135
|
if (record.delivery.state === 'ready_for_review')
|
|
3134
|
-
return cleanupBlocked('review_pending', '
|
|
3136
|
+
return cleanupBlocked('review_pending', hostMessage('theCurrentIterationIsAwaitingAcceptanceAndCannotBe'), record.revision);
|
|
3135
3137
|
if (record.delivery.state === 'preview_active' || record.delivery.state === 'preview_detached') {
|
|
3136
|
-
return cleanupBlocked('preview_active', '
|
|
3138
|
+
return cleanupBlocked('preview_active', hostMessage('localPreviewHasNotBeenSafelyResolvedAndCannot'), record.revision);
|
|
3137
3139
|
}
|
|
3138
3140
|
if (record.delivery.state === 'delivered' || record.phase === 'discarded') {
|
|
3139
|
-
return cleanupBlocked('unknown', '
|
|
3141
|
+
return cleanupBlocked('unknown', hostMessage('worktreeHasBeenDeliveredAndReleasedFromManagementNo'), record.revision);
|
|
3140
3142
|
}
|
|
3141
3143
|
if (record.delivery.commitOid && record.delivery.proof) {
|
|
3142
3144
|
const local = await validateCommittedLocalCheckout(bindingForManagedRecord(record), record);
|
|
3143
3145
|
if (!local)
|
|
3144
|
-
return cleanupBlocked('identity_mismatch', '
|
|
3146
|
+
return cleanupBlocked('identity_mismatch', hostMessage('theLocalCheckoutIdentityCannotBeVerifiedThisIteration'), record.revision);
|
|
3145
3147
|
try {
|
|
3146
3148
|
const delivered = await dependencies.git.isAncestor(local.canonicalLocalRoot, record.delivery.commitOid, local.snapshot.headOid);
|
|
3147
3149
|
if (!delivered)
|
|
3148
|
-
return cleanupBlocked('identity_mismatch', '
|
|
3150
|
+
return cleanupBlocked('identity_mismatch', hostMessage('thisIterationSDeliveryCommitIsNoLongerIn'), record.revision);
|
|
3149
3151
|
}
|
|
3150
3152
|
catch {
|
|
3151
|
-
return cleanupBlocked('unknown', '
|
|
3153
|
+
return cleanupBlocked('unknown', hostMessage('cannotVerifyWhetherThisIterationSDeliveryCommitIs'), record.revision);
|
|
3152
3154
|
}
|
|
3153
3155
|
}
|
|
3154
3156
|
const quarantine = await validateCleanupQuarantine(record);
|
|
3155
3157
|
const residue = quarantine ? undefined : await validateDetachedCleanupResidue(record, true);
|
|
3156
3158
|
if (record.journal?.operation === 'cleanup' && record.journal.cleanupQuarantinePath && !quarantine) {
|
|
3157
|
-
return cleanupBlocked('identity_mismatch', '
|
|
3159
|
+
return cleanupBlocked('identity_mismatch', hostMessage('theCleanupDirectoryIdentityCannotBeReverifiedTheEnvironment'), record.revision);
|
|
3158
3160
|
}
|
|
3159
3161
|
if (!quarantine && !residue && dependencies.files.exists(record.managedRoot)) {
|
|
3160
3162
|
const binding = bindingForManagedRecord(record);
|
|
3161
3163
|
const validated = await validateManagedCheckout(binding, record, false);
|
|
3162
3164
|
if (!validated)
|
|
3163
|
-
return cleanupBlocked('identity_mismatch', '
|
|
3165
|
+
return cleanupBlocked('identity_mismatch', hostMessage('theWorktreeCheckoutIdentityCannotBeVerifiedTheEnvironment'), record.revision);
|
|
3164
3166
|
try {
|
|
3165
3167
|
const snapshot = await dependencies.applyEngine.inspectReview({
|
|
3166
3168
|
baseOid: record.applyBaseOid ?? record.baseOid,
|
|
@@ -3168,25 +3170,26 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
3168
3170
|
localPath: record.localRoot,
|
|
3169
3171
|
});
|
|
3170
3172
|
if (snapshot.status !== 'ready' || snapshot.isolatedFingerprint !== record.delivery.isolatedFingerprint) {
|
|
3171
|
-
return cleanupBlocked('uncommitted_changes', '
|
|
3173
|
+
return cleanupBlocked('uncommitted_changes', hostMessage('newChangesWereDetectedAfterCommitOrRetentionBulk'), record.revision);
|
|
3172
3174
|
}
|
|
3173
3175
|
}
|
|
3174
3176
|
catch {
|
|
3175
|
-
return cleanupBlocked('unknown', '
|
|
3177
|
+
return cleanupBlocked('unknown', hostMessage('cannotProveTheCurrentWorktreeStateIsSafeThe'), record.revision);
|
|
3176
3178
|
}
|
|
3177
3179
|
}
|
|
3178
3180
|
if (record.delivery.cleanup === 'blocked' || record.delivery.cleanup === 'pending') {
|
|
3179
|
-
const
|
|
3180
|
-
|
|
3181
|
+
const cleanupReason = cleanupReasonForMessage(record.delivery.cleanupMessage ?? '');
|
|
3182
|
+
const cleanupMessage = record.delivery.cleanupMessage ? hostPersistedCleanupMessage(record.delivery.cleanupMessage, hostMessage) : hostMessage('thePreviousCleanupDidNotFinishRevalidateAndRetry');
|
|
3183
|
+
if (cleanupReason === 'identity_changed')
|
|
3181
3184
|
return cleanupBlocked('identity_mismatch', cleanupMessage, record.revision);
|
|
3182
|
-
if (
|
|
3185
|
+
if (cleanupReason === 'modified_after_finalize')
|
|
3183
3186
|
return cleanupBlocked('uncommitted_changes', cleanupMessage, record.revision);
|
|
3184
3187
|
}
|
|
3185
3188
|
if (record.delivery.state === 'retained'
|
|
3186
3189
|
&& (record.delivery.retention === 'retain_manual' || record.delivery.expiresAt === null || record.delivery.expiresAt > Date.now())) {
|
|
3187
|
-
return { eligibility: 'retained', reason: 'retention_active', message: record.delivery.retention === 'retain_manual' ? '
|
|
3190
|
+
return { eligibility: 'retained', reason: 'retention_active', message: record.delivery.retention === 'retain_manual' ? hostMessage('retainedManuallyAtTheUserSRequest') : hostMessage('theRetentionPeriodHasNotExpired'), inspectedRevision: record.revision };
|
|
3188
3191
|
}
|
|
3189
|
-
return { eligibility: 'safe', reason: 'cleanup_failed', message: record.delivery.cleanupMessage
|
|
3192
|
+
return { eligibility: 'safe', reason: 'cleanup_failed', message: record.delivery.cleanupMessage ? hostPersistedCleanupMessage(record.delivery.cleanupMessage, hostMessage) : hostMessage('readOnlySafetyInspectionPassedCleanupIsAvailable'), inspectedRevision: record.revision };
|
|
3190
3193
|
}
|
|
3191
3194
|
async function inspectManagedWorktreeCleanup(input = {}) {
|
|
3192
3195
|
const records = Object.values(dependencies.registry.read().managedCheckouts)
|
|
@@ -3214,7 +3217,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
3214
3217
|
retained.push({
|
|
3215
3218
|
checkoutId: record.checkoutId,
|
|
3216
3219
|
iteration: managedIteration(record),
|
|
3217
|
-
cleanup: cleanupBlocked('unknown', '
|
|
3220
|
+
cleanup: cleanupBlocked('unknown', hostMessage('theWorktreeRevisionHasChangedCleanupWasNotPerformed'), record.revision),
|
|
3218
3221
|
});
|
|
3219
3222
|
continue;
|
|
3220
3223
|
}
|
|
@@ -3228,7 +3231,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
3228
3231
|
retained.push({
|
|
3229
3232
|
checkoutId: record.checkoutId,
|
|
3230
3233
|
iteration: managedIteration(record),
|
|
3231
|
-
cleanup: cleanupBlocked('unknown', '
|
|
3234
|
+
cleanup: cleanupBlocked('unknown', hostMessage('worktreeChangedBeforeCleanupCleanupWasNotPerformed'), latest?.revision ?? record.revision),
|
|
3232
3235
|
});
|
|
3233
3236
|
continue;
|
|
3234
3237
|
}
|
|
@@ -3266,7 +3269,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
3266
3269
|
const persisted = getPersistedBinding(sessionId);
|
|
3267
3270
|
const projectId = persisted?.projectId ?? session.projectId;
|
|
3268
3271
|
if (!projectId)
|
|
3269
|
-
throw new SessionCheckoutError('project_not_found', '
|
|
3272
|
+
throw new SessionCheckoutError('project_not_found', hostMessage('theSessionIsNotAssociatedWithAProject'));
|
|
3270
3273
|
const allowedCheckoutIds = new Set(Object.values(dependencies.registry.read().managedCheckouts)
|
|
3271
3274
|
.filter((record) => record.projectId === projectId)
|
|
3272
3275
|
.filter((record) => record.ownerSessionId === sessionId || record.sourceSessionId === sessionId)
|
|
@@ -3278,26 +3281,26 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
3278
3281
|
requireSession(sessionId);
|
|
3279
3282
|
const record = dependencies.registry.read().managedCheckouts[input.checkoutId];
|
|
3280
3283
|
if (!record)
|
|
3281
|
-
throw new SessionCheckoutError('checkout_missing', '
|
|
3284
|
+
throw new SessionCheckoutError('checkout_missing', hostMessage('theWorktreeRecordDoesNotExist'));
|
|
3282
3285
|
if (record.ownerSessionId !== sessionId && record.sourceSessionId !== sessionId) {
|
|
3283
|
-
throw new SessionCheckoutError('not_owner', '
|
|
3286
|
+
throw new SessionCheckoutError('not_owner', hostMessage('theCurrentSessionIsNotAllowedToManageThis'));
|
|
3284
3287
|
}
|
|
3285
3288
|
if (record.ownerSessionId !== sessionId && dependencies.lookup.getSession(record.ownerSessionId)) {
|
|
3286
|
-
throw new SessionCheckoutError('not_owner', '
|
|
3289
|
+
throw new SessionCheckoutError('not_owner', hostMessage('theOwnerSessionHasTakenOverThisWorktreeOnly'));
|
|
3287
3290
|
}
|
|
3288
3291
|
const persisted = getPersistedBinding(sessionId);
|
|
3289
3292
|
const callerProjectId = persisted?.projectId ?? dependencies.lookup.getSession(sessionId)?.projectId;
|
|
3290
3293
|
if (callerProjectId !== record.projectId) {
|
|
3291
|
-
throw new SessionCheckoutError('project_mismatch', '
|
|
3294
|
+
throw new SessionCheckoutError('project_mismatch', hostMessage('theCurrentSessionAndWorktreeDoNotBelongTo'));
|
|
3292
3295
|
}
|
|
3293
3296
|
return manageManagedWorktree(input);
|
|
3294
3297
|
}
|
|
3295
3298
|
async function manageManagedWorktree(input) {
|
|
3296
3299
|
const record = dependencies.registry.read().managedCheckouts[input.checkoutId];
|
|
3297
3300
|
if (!record)
|
|
3298
|
-
throw new SessionCheckoutError('checkout_missing', '
|
|
3301
|
+
throw new SessionCheckoutError('checkout_missing', hostMessage('theWorktreeRecordDoesNotExist'));
|
|
3299
3302
|
if (record.revision !== input.expectedRevision)
|
|
3300
|
-
throw new SessionCheckoutError('stale_target', '
|
|
3303
|
+
throw new SessionCheckoutError('stale_target', hostMessage('theWorktreeStateHasChangedRefreshAndRetry'));
|
|
3301
3304
|
if (input.action === 'discard') {
|
|
3302
3305
|
// Caller authorization is performed by the scoped wrapper (or a trusted Host manager).
|
|
3303
3306
|
// The reserved owner Session may not exist yet, so do not re-resolve it through lookup.
|
|
@@ -3312,12 +3315,12 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
3312
3315
|
throw new SessionCheckoutError(result.code, result.message);
|
|
3313
3316
|
const updated = dependencies.registry.read().managedCheckouts[record.checkoutId];
|
|
3314
3317
|
if (!updated)
|
|
3315
|
-
throw new SessionCheckoutError('checkout_missing', '
|
|
3318
|
+
throw new SessionCheckoutError('checkout_missing', hostMessage('theWorktreeRecordDoesNotExist'));
|
|
3316
3319
|
return summarizeManagedWorktree(updated);
|
|
3317
3320
|
}
|
|
3318
3321
|
if (input.action === 'set_retention') {
|
|
3319
3322
|
if (record.phase !== 'retained' || record.delivery.state !== 'retained' || !input.retention) {
|
|
3320
|
-
throw new SessionCheckoutError('operation_not_allowed', '
|
|
3323
|
+
throw new SessionCheckoutError('operation_not_allowed', hostMessage('onlyRetainedFrozenWorktreesCanHaveTheirRetentionPeriod'));
|
|
3321
3324
|
}
|
|
3322
3325
|
const retainedAt = Date.now();
|
|
3323
3326
|
const updated = updateManagedCheckout(record.checkoutId, (current) => {
|
|
@@ -3337,25 +3340,25 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
3337
3340
|
};
|
|
3338
3341
|
});
|
|
3339
3342
|
if (!updated)
|
|
3340
|
-
throw new SessionCheckoutError('checkout_missing', '
|
|
3343
|
+
throw new SessionCheckoutError('checkout_missing', hostMessage('theWorktreeRecordDoesNotExist'));
|
|
3341
3344
|
return summarizeManagedWorktree(updated);
|
|
3342
3345
|
}
|
|
3343
3346
|
if (record.delivery.state !== 'retained' && record.delivery.state !== 'finalized') {
|
|
3344
|
-
throw new SessionCheckoutError('operation_not_allowed', '
|
|
3347
|
+
throw new SessionCheckoutError('operation_not_allowed', hostMessage('theCurrentWorktreeIsNotInACleanableState'));
|
|
3345
3348
|
}
|
|
3346
3349
|
await cleanupFinalized(record, { allowLegacyResidue: true });
|
|
3347
3350
|
const updated = dependencies.registry.read().managedCheckouts[record.checkoutId];
|
|
3348
3351
|
if (!updated)
|
|
3349
|
-
throw new SessionCheckoutError('checkout_missing', '
|
|
3352
|
+
throw new SessionCheckoutError('checkout_missing', hostMessage('theWorktreeRecordDoesNotExist'));
|
|
3350
3353
|
return summarizeManagedWorktree(updated);
|
|
3351
3354
|
}
|
|
3352
3355
|
async function resolveManagedRoot(checkoutId) {
|
|
3353
3356
|
const record = dependencies.registry.read().managedCheckouts[checkoutId];
|
|
3354
3357
|
if (!record || record.phase === 'discarded')
|
|
3355
|
-
throw new SessionCheckoutError('checkout_missing', '
|
|
3358
|
+
throw new SessionCheckoutError('checkout_missing', hostMessage('theWorktreeDirectoryNoLongerExists'));
|
|
3356
3359
|
const validated = await validateManagedCheckout(bindingForManagedRecord(record), record, false);
|
|
3357
3360
|
if (!validated)
|
|
3358
|
-
throw new SessionCheckoutError('checkout_mismatch', '
|
|
3361
|
+
throw new SessionCheckoutError('checkout_mismatch', hostMessage('theWorktreeDirectoryIdentityCannotBeVerified'));
|
|
3359
3362
|
return validated.canonicalManagedRoot;
|
|
3360
3363
|
}
|
|
3361
3364
|
async function cleanupExpiredRetained(now = Date.now()) {
|
|
@@ -3383,58 +3386,58 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
3383
3386
|
const registry = dependencies.registry.read();
|
|
3384
3387
|
const previousBinding = registry.sessionBindings[sessionId];
|
|
3385
3388
|
if (!previousBinding || previousBinding.target.kind !== 'isolated') {
|
|
3386
|
-
throw new SessionCheckoutError('operation_not_allowed', '
|
|
3389
|
+
throw new SessionCheckoutError('operation_not_allowed', hostMessage('onlyADeliveredIsolatedSessionCanBeginTheNext'));
|
|
3387
3390
|
}
|
|
3388
3391
|
const predecessor = registry.managedCheckouts[previousBinding.target.checkoutId];
|
|
3389
3392
|
if (!predecessor)
|
|
3390
|
-
throw new SessionCheckoutError('checkout_missing', '
|
|
3393
|
+
throw new SessionCheckoutError('checkout_missing', hostMessage('thePreviousIterationSWorktreeRecordDoesNotExist'));
|
|
3391
3394
|
if (predecessor.ownerSessionId !== sessionId || previousBinding.ownerSessionId !== sessionId) {
|
|
3392
|
-
throw new SessionCheckoutError('not_owner', '
|
|
3395
|
+
throw new SessionCheckoutError('not_owner', hostMessage('onlyTheOwnerSessionCanBeginTheNextIteration'));
|
|
3393
3396
|
}
|
|
3394
3397
|
if (predecessor.revision !== expectedRevision) {
|
|
3395
|
-
throw new SessionCheckoutError('stale_target', '
|
|
3398
|
+
throw new SessionCheckoutError('stale_target', hostMessage('theWorktreeStateHasChangedRefreshBeforeBeginningThe'));
|
|
3396
3399
|
}
|
|
3397
3400
|
if (predecessor.phase !== 'discarded' || predecessor.delivery.state !== 'delivered') {
|
|
3398
|
-
throw new SessionCheckoutError('operation_not_allowed', '
|
|
3401
|
+
throw new SessionCheckoutError('operation_not_allowed', hostMessage('onlyADeliveredStateWithSuccessfulCleanupCanBegin'));
|
|
3399
3402
|
}
|
|
3400
3403
|
if (!session.projectId)
|
|
3401
|
-
throw new SessionCheckoutError('project_not_found', '
|
|
3404
|
+
throw new SessionCheckoutError('project_not_found', hostMessage('theCurrentSessionIsNotAssociatedWithAWorkspace'));
|
|
3402
3405
|
const ownerWorkspace = dependencies.lookup.getProject(session.projectId);
|
|
3403
3406
|
if (!ownerWorkspace || !resolvedPathsEqual(ownerWorkspace.root, predecessor.managedRoot)) {
|
|
3404
|
-
throw new SessionCheckoutError('project_mismatch', '
|
|
3407
|
+
throw new SessionCheckoutError('project_mismatch', hostMessage('theCurrentSessionSImmutableCwdDoesNotMatch'));
|
|
3405
3408
|
}
|
|
3406
3409
|
if (dependencies.files.exists(predecessor.managedGitRoot) || dependencies.files.exists(predecessor.managedRoot)) {
|
|
3407
|
-
throw new SessionCheckoutError('checkout_mismatch', '
|
|
3410
|
+
throw new SessionCheckoutError('checkout_mismatch', hostMessage('thePreviousIterationSWorktreePathHasReappearedUnknown'));
|
|
3408
3411
|
}
|
|
3409
3412
|
const localProject = dependencies.lookup.getProject(predecessor.projectId);
|
|
3410
3413
|
if (!localProject || !dependencies.files.exists(localProject.root) || !dependencies.files.exists(predecessor.localRoot)) {
|
|
3411
|
-
throw new SessionCheckoutError('project_root_missing', '
|
|
3414
|
+
throw new SessionCheckoutError('project_root_missing', hostMessage('theOriginalLocalProjectIsUnavailableTheNextIteration'));
|
|
3412
3415
|
}
|
|
3413
3416
|
const canonicalProjectRoot = await dependencies.files.canonicalize(localProject.root);
|
|
3414
3417
|
const canonicalLocalRoot = await dependencies.files.canonicalize(predecessor.localRoot);
|
|
3415
3418
|
if (!pathsEqual(canonicalProjectRoot, canonicalLocalRoot)) {
|
|
3416
|
-
throw new SessionCheckoutError('project_mismatch', '
|
|
3419
|
+
throw new SessionCheckoutError('project_mismatch', hostMessage('theOriginalLocalProjectIdentityHasChanged'));
|
|
3417
3420
|
}
|
|
3418
3421
|
const snapshot = await dependencies.git.inspect(canonicalLocalRoot);
|
|
3419
3422
|
if (!snapshot || !pathsEqual(snapshot.commonDir, predecessor.gitCommonDir)) {
|
|
3420
|
-
throw new SessionCheckoutError('checkout_mismatch', '
|
|
3423
|
+
throw new SessionCheckoutError('checkout_mismatch', hostMessage('theOriginalLocalGitIdentityHasChanged'));
|
|
3421
3424
|
}
|
|
3422
3425
|
const projectRelativePath = relative(snapshot.root, canonicalLocalRoot);
|
|
3423
3426
|
if (projectRelativePath.startsWith('..') || isAbsolute(projectRelativePath)) {
|
|
3424
|
-
throw new SessionCheckoutError('checkout_mismatch', '
|
|
3427
|
+
throw new SessionCheckoutError('checkout_mismatch', hostMessage('theProjectRootIsNotInsideItsGitCheckout'));
|
|
3425
3428
|
}
|
|
3426
3429
|
if (!resolvedPathsEqual(resolve(predecessor.managedGitRoot, projectRelativePath), predecessor.managedRoot)) {
|
|
3427
|
-
throw new SessionCheckoutError('checkout_mismatch', '
|
|
3430
|
+
throw new SessionCheckoutError('checkout_mismatch', hostMessage('thePreviousIterationSManagedProjectPathCannotBe'));
|
|
3428
3431
|
}
|
|
3429
3432
|
const managedContainer = dirname(predecessor.managedGitRoot);
|
|
3430
3433
|
if (!dependencies.files.exists(managedContainer))
|
|
3431
3434
|
dependencies.files.ensureDirectory(managedContainer);
|
|
3432
3435
|
const containerIdentity = await dependencies.files.inspectDirectoryIdentity(managedContainer);
|
|
3433
3436
|
if (!containerIdentity)
|
|
3434
|
-
throw new SessionCheckoutError('checkout_mismatch', '
|
|
3437
|
+
throw new SessionCheckoutError('checkout_mismatch', hostMessage('theWorktreeContainerIsNotATrustedDirectory'));
|
|
3435
3438
|
const canonicalContainer = await dependencies.files.canonicalize(managedContainer);
|
|
3436
3439
|
if (!resolvedPathsEqual(canonicalContainer, managedContainer)) {
|
|
3437
|
-
throw new SessionCheckoutError('checkout_mismatch', '
|
|
3440
|
+
throw new SessionCheckoutError('checkout_mismatch', hostMessage('theWorktreeContainerPathHasBeenRedirected'));
|
|
3438
3441
|
}
|
|
3439
3442
|
const checkoutId = dependencies.createCheckoutId();
|
|
3440
3443
|
const iteration = predecessor.delivery.iteration + 1;
|
|
@@ -3490,12 +3493,12 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
3490
3493
|
|| !pathsEqual(created.root, canonicalManagedGitRoot)
|
|
3491
3494
|
|| !pathsEqual(created.commonDir, snapshot.commonDir)
|
|
3492
3495
|
|| created.headOid !== snapshot.headOid) {
|
|
3493
|
-
throw new SessionCheckoutError('checkout_mismatch', '
|
|
3496
|
+
throw new SessionCheckoutError('checkout_mismatch', hostMessage('theNextIterationSCheckoutGitIdentityDoesNot'));
|
|
3494
3497
|
}
|
|
3495
3498
|
const readyRegistry = dependencies.registry.read();
|
|
3496
3499
|
const current = readyRegistry.managedCheckouts[checkoutId];
|
|
3497
3500
|
if (!current || current.phase !== 'preparing') {
|
|
3498
|
-
throw new SessionCheckoutError('stale_target', '
|
|
3501
|
+
throw new SessionCheckoutError('stale_target', hostMessage('theStateChangedWhileCreatingTheNextIterationS'));
|
|
3499
3502
|
}
|
|
3500
3503
|
readyRegistry.managedCheckouts[checkoutId] = {
|
|
3501
3504
|
...current,
|
|
@@ -3537,7 +3540,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
3537
3540
|
const current = dependencies.registry.read().managedCheckouts[checkoutId];
|
|
3538
3541
|
if (current)
|
|
3539
3542
|
markRecoveryRequired(current);
|
|
3540
|
-
throw new SessionCheckoutError('recovery_required', '
|
|
3543
|
+
throw new SessionCheckoutError('recovery_required', hostMessage('creatingTheNextIterationSWorktreeFailedAndIts'));
|
|
3541
3544
|
}
|
|
3542
3545
|
const failedRegistry = dependencies.registry.read();
|
|
3543
3546
|
delete failedRegistry.managedCheckouts[checkoutId];
|
|
@@ -3582,7 +3585,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
3582
3585
|
}
|
|
3583
3586
|
}
|
|
3584
3587
|
if (!(choice.kind === 'isolated' && existing.target.kind === 'isolated' && nextIteration > 1)) {
|
|
3585
|
-
throw new SessionCheckoutError('target_already_bound', '
|
|
3588
|
+
throw new SessionCheckoutError('target_already_bound', hostMessage('theSessionIsAlreadyBoundToASessionTarget'));
|
|
3586
3589
|
}
|
|
3587
3590
|
}
|
|
3588
3591
|
const { project } = await resolveSessionProject(sourceSessionId);
|
|
@@ -3605,8 +3608,10 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
3605
3608
|
return inspectLocal(binding);
|
|
3606
3609
|
}
|
|
3607
3610
|
if (!snapshot) {
|
|
3608
|
-
throw new SessionCheckoutError('not_git_repository', '
|
|
3611
|
+
throw new SessionCheckoutError('not_git_repository', hostMessage('aNonGitProjectCannotCreateAnIsolatedCheckout'));
|
|
3609
3612
|
}
|
|
3613
|
+
if (snapshot.headOid === 'unborn')
|
|
3614
|
+
throw new SessionCheckoutError('operation_not_allowed', hostMessage('initialConfirmationRequired'));
|
|
3610
3615
|
// managed Worktree 不再使用全局数量硬上限;生命周期通过交付后清理收口。
|
|
3611
3616
|
const checkoutId = dependencies.createCheckoutId();
|
|
3612
3617
|
const localRoot = await dependencies.files.canonicalize(project.root);
|
|
@@ -3669,7 +3674,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
3669
3674
|
if (usingSibling) {
|
|
3670
3675
|
return bindTarget(sessionId, choice, createAttempt + 1, requestStartedAt, sourceSessionId);
|
|
3671
3676
|
}
|
|
3672
|
-
throw new SessionCheckoutError('checkout_mismatch', '
|
|
3677
|
+
throw new SessionCheckoutError('checkout_mismatch', hostMessage('theFallbackWorktreeContainerIsNotATrustedDirectory'));
|
|
3673
3678
|
}
|
|
3674
3679
|
const managedGitRoot = pathCandidates
|
|
3675
3680
|
.map(candidate => usingSibling ? candidate.siblingRoot : candidate.fallbackRoot)
|
|
@@ -3678,11 +3683,11 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
3678
3683
|
if (usingSibling) {
|
|
3679
3684
|
return bindTarget(sessionId, choice, createAttempt + 1, requestStartedAt, sourceSessionId);
|
|
3680
3685
|
}
|
|
3681
|
-
throw new SessionCheckoutError('checkout_mismatch', '
|
|
3686
|
+
throw new SessionCheckoutError('checkout_mismatch', hostMessage('allWorktreeCheckoutIdentityPathsAlreadyExistUnknownDirectories'));
|
|
3682
3687
|
}
|
|
3683
3688
|
const projectRelativePath = relative(localGitRoot, localRoot);
|
|
3684
3689
|
if (projectRelativePath.startsWith('..') || isAbsolute(projectRelativePath)) {
|
|
3685
|
-
throw new SessionCheckoutError('checkout_mismatch', '
|
|
3690
|
+
throw new SessionCheckoutError('checkout_mismatch', hostMessage('theProjectRootIsNotInsideItsGitCheckout'));
|
|
3686
3691
|
}
|
|
3687
3692
|
const managedRoot = join(managedGitRoot, projectRelativePath);
|
|
3688
3693
|
const record = {
|
|
@@ -3731,7 +3736,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
3731
3736
|
if (!created
|
|
3732
3737
|
|| !pathsEqual(created.root, canonicalManagedGitRoot)
|
|
3733
3738
|
|| !pathsEqual(created.commonDir, snapshot.commonDir)) {
|
|
3734
|
-
throw new SessionCheckoutError('checkout_mismatch', '
|
|
3739
|
+
throw new SessionCheckoutError('checkout_mismatch', hostMessage('theNewCheckoutSGitCommonDirectoryDoesNot'));
|
|
3735
3740
|
}
|
|
3736
3741
|
const readyRegistry = dependencies.registry.read();
|
|
3737
3742
|
const readyRecord = {
|
|
@@ -3773,7 +3778,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
3773
3778
|
}
|
|
3774
3779
|
if (!residueRemoved) {
|
|
3775
3780
|
markRecoveryRequired(record);
|
|
3776
|
-
throw new SessionCheckoutError('recovery_required', '
|
|
3781
|
+
throw new SessionCheckoutError('recovery_required', hostMessage('worktreeCreationFailedAndTheRemainingDirectoryContainsUnknown'));
|
|
3777
3782
|
}
|
|
3778
3783
|
const failedRegistry = dependencies.registry.read();
|
|
3779
3784
|
delete failedRegistry.managedCheckouts[checkoutId];
|
|
@@ -3791,7 +3796,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
3791
3796
|
if (createAttempt < 1) {
|
|
3792
3797
|
return bindTarget(sessionId, choice, createAttempt + 1, requestStartedAt, sourceSessionId);
|
|
3793
3798
|
}
|
|
3794
|
-
throw new SessionCheckoutError('git_operation_failed', '
|
|
3799
|
+
throw new SessionCheckoutError('git_operation_failed', hostMessage('worktreeCreationFailedTheRemainingDirectoryWasSafelyCleaned'));
|
|
3795
3800
|
}
|
|
3796
3801
|
}
|
|
3797
3802
|
return {
|
|
@@ -3806,17 +3811,17 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
3806
3811
|
},
|
|
3807
3812
|
createIsolatedTarget: (sourceSessionId, targetSessionId) => withBindingLock(async () => {
|
|
3808
3813
|
if (!targetSessionId || targetSessionId === sourceSessionId) {
|
|
3809
|
-
throw new SessionCheckoutError('invalid_input', '
|
|
3814
|
+
throw new SessionCheckoutError('invalid_input', hostMessage('anIsolatedTargetRequiresASeparatePreallocatedSessionID'));
|
|
3810
3815
|
}
|
|
3811
3816
|
if (dependencies.lookup.getSession(targetSessionId) && !getPersistedBinding(targetSessionId)) {
|
|
3812
|
-
throw new SessionCheckoutError('target_already_bound', '
|
|
3817
|
+
throw new SessionCheckoutError('target_already_bound', hostMessage('theTargetSessionIDIsAlreadyUsedByAnother'));
|
|
3813
3818
|
}
|
|
3814
3819
|
const sourceBinding = getPersistedBinding(sourceSessionId);
|
|
3815
3820
|
if (!sourceBinding) {
|
|
3816
3821
|
await bindTarget(sourceSessionId, { kind: 'local' });
|
|
3817
3822
|
}
|
|
3818
3823
|
else if (sourceBinding.target.kind !== 'local') {
|
|
3819
|
-
throw new SessionCheckoutError('operation_not_allowed', '
|
|
3824
|
+
throw new SessionCheckoutError('operation_not_allowed', hostMessage('aNewIsolatedTargetCanOnlyBeCreatedFrom'));
|
|
3820
3825
|
}
|
|
3821
3826
|
const target = await bindTarget(targetSessionId, { kind: 'isolated' }, 0, Date.now(), sourceSessionId);
|
|
3822
3827
|
const binding = getPersistedBinding(targetSessionId);
|
|
@@ -3824,7 +3829,7 @@ export function createSessionCheckoutModule(dependencies) {
|
|
|
3824
3829
|
? dependencies.registry.read().managedCheckouts[binding.target.checkoutId]
|
|
3825
3830
|
: undefined;
|
|
3826
3831
|
if (!record)
|
|
3827
|
-
throw new SessionCheckoutError('checkout_missing', '
|
|
3832
|
+
throw new SessionCheckoutError('checkout_missing', hostMessage('theIsolatedTargetRecordIsMissingAfterCreation'));
|
|
3828
3833
|
return { targetSessionId, managedRoot: record.managedRoot, target };
|
|
3829
3834
|
}),
|
|
3830
3835
|
beginNextIteration: (sessionId, expectedRevision) => withBindingLock(() => beginNextIterationTarget(sessionId, expectedRevision)),
|