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,4 @@
|
|
|
1
|
+
import { hostMessage } from '../i18n/host.js';
|
|
1
2
|
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { resolve } from 'node:path';
|
|
3
4
|
import { consoleFailure, domainError, failure, outcome } from './errors.js';
|
|
@@ -6,7 +7,7 @@ import { ReviewDiffStaleError } from './review-diff.js';
|
|
|
6
7
|
function recordOf(registry, checkoutId) {
|
|
7
8
|
const record = registry.read().managedCheckouts[checkoutId];
|
|
8
9
|
if (record === undefined)
|
|
9
|
-
throw domainError('checkout_missing', '
|
|
10
|
+
throw domainError('checkout_missing', hostMessage('theWorktreeRecordDoesNotExist'));
|
|
10
11
|
return record;
|
|
11
12
|
}
|
|
12
13
|
function sidebarTaskState(record) {
|
|
@@ -30,14 +31,14 @@ function sidebarTaskState(record) {
|
|
|
30
31
|
}
|
|
31
32
|
function readyReview(record) {
|
|
32
33
|
if (record.phase !== 'ready' || record.delivery.state !== 'ready_for_review') {
|
|
33
|
-
throw domainError('operation_not_allowed', '
|
|
34
|
+
throw domainError('operation_not_allowed', hostMessage('theCurrentWorktreeIsNotReadyForReview'));
|
|
34
35
|
}
|
|
35
36
|
return record.delivery.review;
|
|
36
37
|
}
|
|
37
38
|
function previewReview(record) {
|
|
38
39
|
if (record.phase !== 'ready'
|
|
39
40
|
|| (record.delivery.state !== 'preview_active' && record.delivery.state !== 'preview_detached')) {
|
|
40
|
-
throw domainError('preview_not_active', '
|
|
41
|
+
throw domainError('preview_not_active', hostMessage('thereIsNoLocalPreviewAwaitingAcceptance'));
|
|
41
42
|
}
|
|
42
43
|
return record.delivery.review;
|
|
43
44
|
}
|
|
@@ -55,11 +56,11 @@ function applyConflictContinuation(result, reviewId) {
|
|
|
55
56
|
const checkoutId = result.target.checkout.id;
|
|
56
57
|
const revision = result.target.revision;
|
|
57
58
|
if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/iu.test(result.localHeadOid)) {
|
|
58
|
-
throw domainError('git_error', '
|
|
59
|
+
throw domainError('git_error', hostMessage('conflictRecoveryIsMissingAValidLocalHEADIdentity'));
|
|
59
60
|
}
|
|
60
61
|
const localHeadOid = result.localHeadOid;
|
|
61
62
|
if (result.conflictingFiles.length > 500 || !result.conflictingFiles.every(safeConflictFile)) {
|
|
62
|
-
throw domainError('git_error', '
|
|
63
|
+
throw domainError('git_error', hostMessage('conflictRecoveryContainsUnsafeOrOutOfBoundsFile'));
|
|
63
64
|
}
|
|
64
65
|
const conflictingFiles = [...result.conflictingFiles];
|
|
65
66
|
return {
|
|
@@ -74,7 +75,7 @@ function applyConflictContinuation(result, reviewId) {
|
|
|
74
75
|
}
|
|
75
76
|
function preflightFailure(view) {
|
|
76
77
|
if (view === undefined)
|
|
77
|
-
return failure('git_error', '
|
|
78
|
+
return failure('git_error', hostMessage('theCurrentSessionCheckoutModuleDoesNotSupportAcceptancePreflight'));
|
|
78
79
|
if (view.status !== 'blocked')
|
|
79
80
|
return undefined;
|
|
80
81
|
const code = view.reason === 'stale_isolated' ? 'stale_isolated'
|
|
@@ -92,17 +93,17 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
92
93
|
async function unboundLocalTarget(sessionId) {
|
|
93
94
|
const session = options.lookup.getSession(sessionId);
|
|
94
95
|
if (session === undefined)
|
|
95
|
-
throw domainError('session_not_found', '
|
|
96
|
+
throw domainError('session_not_found', hostMessage('theCurrentSessionDoesNotExist'));
|
|
96
97
|
if (session.projectId === undefined)
|
|
97
|
-
throw domainError('project_not_found', '
|
|
98
|
+
throw domainError('project_not_found', hostMessage('theCurrentSessionIsNotAssociatedWithAProject'));
|
|
98
99
|
const project = options.lookup.getProject(session.projectId);
|
|
99
100
|
if (project === undefined)
|
|
100
|
-
throw domainError('project_not_found', '
|
|
101
|
+
throw domainError('project_not_found', hostMessage('theCurrentSessionSProjectDoesNotExist'));
|
|
101
102
|
if (!options.files.exists(project.root))
|
|
102
|
-
throw domainError('project_root_missing', '
|
|
103
|
+
throw domainError('project_root_missing', hostMessage('theCurrentSessionSProjectDirectoryDoesNotExist'));
|
|
103
104
|
const snapshot = await options.git.inspect(project.root);
|
|
104
105
|
if (snapshot === null)
|
|
105
|
-
throw domainError('not_git_repository', '
|
|
106
|
+
throw domainError('not_git_repository', hostMessage('theCurrentSessionSProjectIsNotAnAvailable'));
|
|
106
107
|
const status = await options.git.status(project.root);
|
|
107
108
|
return {
|
|
108
109
|
project: { id: project.id, name: project.name },
|
|
@@ -129,7 +130,7 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
129
130
|
const session = options.lookup.getSession(sessionId);
|
|
130
131
|
const project = session?.projectId === undefined ? undefined : options.lookup.getProject(session.projectId);
|
|
131
132
|
if (project === undefined) {
|
|
132
|
-
throw domainError('project_mismatch', '
|
|
133
|
+
throw domainError('project_mismatch', hostMessage('theCurrentSessionWorkspaceCannotBeProvenToBelong'));
|
|
133
134
|
}
|
|
134
135
|
const cleanedOwnerCwd = record.ownerSessionId === sessionId
|
|
135
136
|
&& record.phase === 'discarded'
|
|
@@ -139,28 +140,28 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
139
140
|
if (cleanedOwnerCwd)
|
|
140
141
|
return;
|
|
141
142
|
if (!options.files.exists(project.root)) {
|
|
142
|
-
throw domainError('project_mismatch', '
|
|
143
|
+
throw domainError('project_mismatch', hostMessage('theCurrentSessionWorkspaceCannotBeProvenToBelong'));
|
|
143
144
|
}
|
|
144
145
|
const workspaceRoot = await options.files.canonicalize(project.root);
|
|
145
146
|
if (!sameLocalRoot(workspaceRoot, expectedRoot)) {
|
|
146
|
-
throw domainError('project_mismatch', '
|
|
147
|
+
throw domainError('project_mismatch', hostMessage('theCurrentSessionCwdDoesNotMatchTheWorktree'));
|
|
147
148
|
}
|
|
148
149
|
}
|
|
149
150
|
async function authorize(sessionId, checkoutId) {
|
|
150
151
|
const caller = await options.module.inspect(sessionId);
|
|
151
152
|
const record = recordOf(options.registry, checkoutId);
|
|
152
153
|
if (record.ownerSessionId !== sessionId && record.sourceSessionId !== sessionId) {
|
|
153
|
-
throw domainError('not_owner', '
|
|
154
|
+
throw domainError('not_owner', hostMessage('theCurrentSessionIsNotAllowedToAccessThis'));
|
|
154
155
|
}
|
|
155
156
|
if (record.projectId !== caller.project.id) {
|
|
156
|
-
throw domainError('project_mismatch', '
|
|
157
|
+
throw domainError('project_mismatch', hostMessage('worktreeDoesNotMatchTheCurrentSessionSProject'));
|
|
157
158
|
}
|
|
158
159
|
const expectedRoot = record.ownerSessionId === sessionId ? record.managedRoot : record.localRoot;
|
|
159
160
|
await verifyCallerRoot(sessionId, record, expectedRoot);
|
|
160
161
|
if (record.phase !== 'discarded') {
|
|
161
162
|
const visible = await options.module.listManagedWorktreesForSession(sessionId, { checkoutId });
|
|
162
163
|
if (!visible.some(item => item.checkoutId === checkoutId)) {
|
|
163
|
-
throw domainError('not_owner', '
|
|
164
|
+
throw domainError('not_owner', hostMessage('theCurrentSessionIsNotAllowedToAccessThis'));
|
|
164
165
|
}
|
|
165
166
|
}
|
|
166
167
|
return record;
|
|
@@ -172,7 +173,7 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
172
173
|
}
|
|
173
174
|
const caller = await callerTarget(sessionId);
|
|
174
175
|
if (caller.checkout.kind !== 'isolated') {
|
|
175
|
-
throw domainError('not_owner', '
|
|
176
|
+
throw domainError('not_owner', hostMessage('theCurrentSessionIsNotAllowedToAccessThis'));
|
|
176
177
|
}
|
|
177
178
|
const anchor = await authorize(sessionId, caller.checkout.id);
|
|
178
179
|
const sourceSessionId = anchor.sourceSessionId ?? anchor.ownerSessionId;
|
|
@@ -189,10 +190,10 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
189
190
|
&& requested.projectId === anchor.projectId
|
|
190
191
|
&& sameLocalRoot(requested.localRoot, anchor.localRoot);
|
|
191
192
|
if (!sameLinkedGroup && !exactAcceptanceHolder) {
|
|
192
|
-
throw domainError('not_owner', '
|
|
193
|
+
throw domainError('not_owner', hostMessage('theCurrentSessionIsNotAllowedToAccessThis'));
|
|
193
194
|
}
|
|
194
195
|
if (!ownerSessionAvailable(requested)) {
|
|
195
|
-
throw domainError('checkout_missing', '
|
|
196
|
+
throw domainError('checkout_missing', hostMessage('theAssociatedWorktreeSOwnerSessionIsUnavailable'));
|
|
196
197
|
}
|
|
197
198
|
await verifyCallerRoot(requested.ownerSessionId, requested, requested.managedRoot);
|
|
198
199
|
if (exactAcceptanceHolder) {
|
|
@@ -206,7 +207,7 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
206
207
|
if (currentHolder?.checkoutId !== currentRequested.checkoutId
|
|
207
208
|
|| currentRequested.ownerSessionId !== requested.ownerSessionId
|
|
208
209
|
|| !sameLocalRoot(currentRequested.localRoot, currentAnchor.localRoot))
|
|
209
|
-
throw domainError('not_owner', '
|
|
210
|
+
throw domainError('not_owner', hostMessage('theAcceptanceSlotHolderHasChangedCheckAgain'));
|
|
210
211
|
return {
|
|
211
212
|
record: currentRequested,
|
|
212
213
|
linkedRead: true,
|
|
@@ -221,7 +222,7 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
221
222
|
const managedRoot = await options.module.resolveManagedRoot(record.checkoutId);
|
|
222
223
|
const snapshot = await options.git.inspect(managedRoot);
|
|
223
224
|
if (snapshot === null)
|
|
224
|
-
throw domainError('checkout_mismatch', '
|
|
225
|
+
throw domainError('checkout_mismatch', hostMessage('theWorktreeGitIdentityCannotBeVerified'));
|
|
225
226
|
const status = await options.git.status(managedRoot);
|
|
226
227
|
return { managedRoot, snapshot, dirty: status.dirty };
|
|
227
228
|
}
|
|
@@ -286,7 +287,7 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
286
287
|
|| !sameLocalRoot(currentRequested.localRoot, currentAnchor.localRoot)
|
|
287
288
|
|| observed.managedRoot === null
|
|
288
289
|
|| !sameLocalRoot(observed.managedRoot, currentRequested.managedRoot))
|
|
289
|
-
throw domainError('not_owner', '
|
|
290
|
+
throw domainError('not_owner', hostMessage('theAcceptanceSlotHolderHasChangedCheckAgain'));
|
|
290
291
|
projectedRecord = currentRequested;
|
|
291
292
|
}
|
|
292
293
|
return projectReviewSlot(projectedRecord, projectDetails(projectedRecord, sessionId, observed.managedRoot, observed.snapshot, observed.dirty, ownerSessionAvailable(access.record), access.linkedRead, observedDeliveryProof));
|
|
@@ -310,14 +311,74 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
310
311
|
})),
|
|
311
312
|
};
|
|
312
313
|
}
|
|
314
|
+
const confirmations = new Map();
|
|
315
|
+
const creating = new Set();
|
|
316
|
+
async function localProject(sessionId) {
|
|
317
|
+
const target = await callerTarget(sessionId);
|
|
318
|
+
const session = options.lookup.getSession(sessionId);
|
|
319
|
+
const project = session?.projectId ? options.lookup.getProject(session.projectId) : undefined;
|
|
320
|
+
if (target.checkout.kind !== 'local' || !project || project.id !== target.project.id || !options.files.exists(project.root)) {
|
|
321
|
+
throw domainError('operation_not_allowed', hostMessage('initialStateChanged'));
|
|
322
|
+
}
|
|
323
|
+
return project;
|
|
324
|
+
}
|
|
325
|
+
async function withCreation(sessionId, action) {
|
|
326
|
+
const sessionKey = `session:${sessionId}`;
|
|
327
|
+
if (creating.has(sessionKey))
|
|
328
|
+
throw domainError('operation_not_allowed', hostMessage('initialRepositoryBusy'));
|
|
329
|
+
creating.add(sessionKey);
|
|
330
|
+
let rootKey;
|
|
331
|
+
try {
|
|
332
|
+
const project = await localProject(sessionId);
|
|
333
|
+
const canonical = await options.files.canonicalize(project.root);
|
|
334
|
+
const key = `root:${process.platform === 'win32' ? canonical.toLowerCase() : canonical}`;
|
|
335
|
+
if (creating.has(key))
|
|
336
|
+
throw domainError('operation_not_allowed', hostMessage('initialRepositoryBusy'));
|
|
337
|
+
rootKey = key;
|
|
338
|
+
creating.add(key);
|
|
339
|
+
return await action(project);
|
|
340
|
+
}
|
|
341
|
+
finally {
|
|
342
|
+
creating.delete(sessionKey);
|
|
343
|
+
if (rootKey)
|
|
344
|
+
creating.delete(rootKey);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
async function createTarget(sourceSessionId) {
|
|
348
|
+
const targetSessionId = createTargetSessionId();
|
|
349
|
+
const launch = await options.module.createIsolatedTarget(sourceSessionId, targetSessionId);
|
|
350
|
+
const record = recordOf(options.registry, launch.target.checkout.id);
|
|
351
|
+
const observed = await observe(record);
|
|
352
|
+
if (record.sourceSessionId !== sourceSessionId
|
|
353
|
+
|| record.ownerSessionId !== targetSessionId
|
|
354
|
+
|| observed.managedRoot !== launch.managedRoot)
|
|
355
|
+
throw domainError('checkout_mismatch', hostMessage('hostIdentityVerificationFailedForTheNewlyCreatedWorktree'));
|
|
356
|
+
return {
|
|
357
|
+
target: projectDetails(record, sourceSessionId, observed.managedRoot, observed.snapshot, observed.dirty, ownerSessionAvailable(record)),
|
|
358
|
+
targetSessionId,
|
|
359
|
+
managedRoot: launch.managedRoot,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
313
362
|
return {
|
|
314
363
|
sidebarTopology: () => outcome(async () => {
|
|
364
|
+
const records = Object.values(options.registry.read().managedCheckouts);
|
|
365
|
+
const memberships = await options.lookup.getSidebarMemberships?.(records.map(record => record.managedRoot));
|
|
366
|
+
const membershipByRoot = new Map(memberships?.map(item => [item.managedRoot, item]));
|
|
315
367
|
const projects = new Map();
|
|
316
|
-
for (const record of
|
|
368
|
+
for (const record of records) {
|
|
317
369
|
const project = projects.get(record.projectId) ?? {
|
|
318
370
|
project: { id: record.projectId, name: record.projectName },
|
|
319
371
|
tasksByOwner: new Map(),
|
|
372
|
+
workspaceIds: new Set(),
|
|
373
|
+
sessionIds: new Set(),
|
|
320
374
|
};
|
|
375
|
+
// Preserve directory identities from all iterations, not just the
|
|
376
|
+
// newest owner's badge. Cleanup may already have removed its members.
|
|
377
|
+
const membership = membershipByRoot.get(record.managedRoot);
|
|
378
|
+
for (const id of membership?.workspaceIds ?? [])
|
|
379
|
+
project.workspaceIds.add(id);
|
|
380
|
+
for (const id of membership?.sessionIds ?? [])
|
|
381
|
+
project.sessionIds.add(id);
|
|
321
382
|
const task = {
|
|
322
383
|
checkoutId: record.checkoutId,
|
|
323
384
|
ownerSessionId: record.ownerSessionId,
|
|
@@ -341,6 +402,12 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
341
402
|
projects: [...projects.values()].map(project => ({
|
|
342
403
|
project: project.project,
|
|
343
404
|
tasks: [...project.tasksByOwner.values()],
|
|
405
|
+
...(memberships === undefined ? {} : {
|
|
406
|
+
memberships: {
|
|
407
|
+
workspaceIds: [...project.workspaceIds],
|
|
408
|
+
sessionIds: [...project.sessionIds],
|
|
409
|
+
},
|
|
410
|
+
}),
|
|
344
411
|
})),
|
|
345
412
|
};
|
|
346
413
|
}),
|
|
@@ -365,7 +432,7 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
365
432
|
const session = options.lookup.getSession(request.sessionId);
|
|
366
433
|
const project = session?.projectId === undefined ? undefined : options.lookup.getProject(session.projectId);
|
|
367
434
|
if (project === undefined || project.id !== caller.project.id || !options.files.exists(project.root)) {
|
|
368
|
-
throw domainError('project_mismatch', '
|
|
435
|
+
throw domainError('project_mismatch', hostMessage('theCurrentSessionWorkspaceCannotBeProvenToBe'));
|
|
369
436
|
}
|
|
370
437
|
localRoot = await options.files.canonicalize(project.root);
|
|
371
438
|
}
|
|
@@ -395,37 +462,61 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
395
462
|
}
|
|
396
463
|
return { project: { ...caller.project }, worktrees };
|
|
397
464
|
}),
|
|
398
|
-
|
|
399
|
-
const
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
if (
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
465
|
+
preflightCreate: sourceSessionId => outcome(async () => {
|
|
466
|
+
const project = await localProject(sourceSessionId);
|
|
467
|
+
const check = await options.git.preflightInitialCommit?.(project.root);
|
|
468
|
+
if (!check || check.kind === 'ready')
|
|
469
|
+
return { kind: 'ready' };
|
|
470
|
+
if (check.kind === 'files') {
|
|
471
|
+
confirmations.delete(sourceSessionId);
|
|
472
|
+
return { kind: 'files' };
|
|
473
|
+
}
|
|
474
|
+
for (const [id, value] of confirmations)
|
|
475
|
+
if (value.expires <= Date.now())
|
|
476
|
+
confirmations.delete(id);
|
|
477
|
+
if (confirmations.size >= 500)
|
|
478
|
+
confirmations.delete(confirmations.keys().next().value);
|
|
479
|
+
const token = randomUUID();
|
|
480
|
+
confirmations.set(sourceSessionId, { token, root: project.root, projectId: project.id, fingerprint: check.fingerprint, expires: Date.now() + 300_000 });
|
|
481
|
+
return { kind: 'empty', confirmationToken: token };
|
|
412
482
|
}),
|
|
483
|
+
createWithInitialCommit: (sourceSessionId, confirmationToken) => outcome(() => withCreation(sourceSessionId, async (project) => {
|
|
484
|
+
const confirmation = confirmations.get(sourceSessionId);
|
|
485
|
+
confirmations.delete(sourceSessionId);
|
|
486
|
+
if (!confirmation || confirmation.token !== confirmationToken || confirmation.expires <= Date.now()
|
|
487
|
+
|| confirmation.root !== project.root || confirmation.projectId !== project.id || !options.git.initializeEmptyRepository) {
|
|
488
|
+
throw domainError('stale_local', hostMessage('initialStateChanged'));
|
|
489
|
+
}
|
|
490
|
+
await options.git.initializeEmptyRepository(project.root, confirmation.fingerprint, async () => {
|
|
491
|
+
const current = await localProject(sourceSessionId);
|
|
492
|
+
if (current.id !== confirmation.projectId || current.root !== confirmation.root) {
|
|
493
|
+
throw domainError('stale_local', hostMessage('initialStateChanged'));
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
try {
|
|
497
|
+
return await createTarget(sourceSessionId);
|
|
498
|
+
}
|
|
499
|
+
catch {
|
|
500
|
+
throw domainError('git_operation_failed', hostMessage('initialCreatedWorktreeFailed'));
|
|
501
|
+
}
|
|
502
|
+
})),
|
|
503
|
+
create: sourceSessionId => outcome(() => withCreation(sourceSessionId, () => createTarget(sourceSessionId))),
|
|
413
504
|
inspect: (sessionId, checkoutId) => outcome(async () => ({ target: await details(sessionId, checkoutId) })),
|
|
414
505
|
reviewDiff: async (request) => {
|
|
415
506
|
try {
|
|
416
507
|
const record = await authorize(request.sessionId, request.checkoutId);
|
|
417
508
|
if (record.ownerSessionId !== request.sessionId)
|
|
418
|
-
return failure('not_owner', '
|
|
509
|
+
return failure('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanReadTheReview'));
|
|
419
510
|
if (record.revision !== request.expectedRevision)
|
|
420
|
-
return failure('stale_target', '
|
|
511
|
+
return failure('stale_target', hostMessage('theSessionTargetHasChangedRefresh'));
|
|
421
512
|
const review = readyReview(record);
|
|
422
513
|
if (review.reviewId !== request.expectedReviewId)
|
|
423
|
-
return failure('stale_target', '
|
|
514
|
+
return failure('stale_target', hostMessage('theReviewIdentityHasChangedRefresh'));
|
|
424
515
|
const before = await options.module.preflight?.(request.sessionId, request.expectedRevision);
|
|
425
516
|
if (before === undefined || before.status === 'blocked')
|
|
426
517
|
return preflightFailure(before);
|
|
427
518
|
if (before.reviewId !== review.reviewId || before.isolatedHeadOid !== review.isolatedHeadOid) {
|
|
428
|
-
return failure('stale_isolated', '
|
|
519
|
+
return failure('stale_isolated', hostMessage('isolatedHEADChangedAfterReady'));
|
|
429
520
|
}
|
|
430
521
|
const observed = await observe(record);
|
|
431
522
|
const diff = await options.reviewDiff.read({
|
|
@@ -437,7 +528,7 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
437
528
|
});
|
|
438
529
|
const after = await options.module.preflight?.(request.sessionId, request.expectedRevision);
|
|
439
530
|
if (after === undefined || after.status === 'blocked') {
|
|
440
|
-
return failure('stale_isolated', '
|
|
531
|
+
return failure('stale_isolated', hostMessage('isolatedContentChangedAfterReadyDiffBytesWereDiscarded'));
|
|
441
532
|
}
|
|
442
533
|
const current = recordOf(options.registry, request.checkoutId);
|
|
443
534
|
if (after.reviewId !== review.reviewId
|
|
@@ -446,7 +537,7 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
446
537
|
|| current.delivery.state !== 'ready_for_review'
|
|
447
538
|
|| current.delivery.review.reviewId !== review.reviewId
|
|
448
539
|
|| current.delivery.review.isolatedFingerprint !== review.isolatedFingerprint)
|
|
449
|
-
return failure('stale_isolated', '
|
|
540
|
+
return failure('stale_isolated', hostMessage('isolatedContentChangedAfterReadyDiffBytesWereDiscarded'));
|
|
450
541
|
return { ok: true, value: diff };
|
|
451
542
|
}
|
|
452
543
|
catch (error) {
|
|
@@ -458,31 +549,31 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
458
549
|
preflight: request => outcome(async () => {
|
|
459
550
|
const record = await authorize(request.sessionId, request.checkoutId);
|
|
460
551
|
if (record.ownerSessionId !== request.sessionId)
|
|
461
|
-
throw domainError('not_owner', '
|
|
552
|
+
throw domainError('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanRunSyncPreflight436'));
|
|
462
553
|
if (record.revision !== request.expectedRevision)
|
|
463
|
-
throw domainError('stale_target', '
|
|
554
|
+
throw domainError('stale_target', hostMessage('theSessionTargetHasChangedRefresh'));
|
|
464
555
|
const review = readyReview(record);
|
|
465
556
|
if (review.reviewId !== request.expectedReviewId)
|
|
466
|
-
throw domainError('stale_target', '
|
|
557
|
+
throw domainError('stale_target', hostMessage('theReviewIdentityHasChangedRefresh'));
|
|
467
558
|
const preflight = await options.module.preflight?.(request.sessionId, request.expectedRevision);
|
|
468
559
|
if (preflight === undefined)
|
|
469
|
-
throw domainError('git_error', '
|
|
560
|
+
throw domainError('git_error', hostMessage('theCurrentSessionCheckoutModuleDoesNotSupportAcceptancePreflight'));
|
|
470
561
|
return { preflight };
|
|
471
562
|
}),
|
|
472
563
|
previewRecoveryPreflight: request => outcome(async () => {
|
|
473
564
|
const record = await authorize(request.sessionId, request.checkoutId);
|
|
474
565
|
if (record.ownerSessionId !== request.sessionId)
|
|
475
|
-
throw domainError('not_owner', '
|
|
566
|
+
throw domainError('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanCheckPreviewRecovery440'));
|
|
476
567
|
if (record.revision !== request.expectedRevision)
|
|
477
|
-
throw domainError('stale_target', '
|
|
568
|
+
throw domainError('stale_target', hostMessage('theSessionTargetHasChangedRefresh'));
|
|
478
569
|
if (record.phase !== 'ready'
|
|
479
570
|
|| record.delivery.state !== 'preview_detached'
|
|
480
571
|
|| record.delivery.review.reviewId !== request.expectedReviewId
|
|
481
572
|
|| record.delivery.preview.previewId !== request.expectedPreviewId)
|
|
482
|
-
throw domainError('stale_target', '
|
|
573
|
+
throw domainError('stale_target', hostMessage('theDetachedPreviewIdentityHasChangedRefresh'));
|
|
483
574
|
const preflight = await options.module.preflightPreviewRecovery?.(request.sessionId, request.expectedRevision, request.expectedReviewId, request.expectedPreviewId);
|
|
484
575
|
if (preflight === undefined)
|
|
485
|
-
throw domainError('git_error', '
|
|
576
|
+
throw domainError('git_error', hostMessage('theCurrentSessionCheckoutModuleDoesNotSupportPreviewRecoveryPreflight'));
|
|
486
577
|
return { preflight };
|
|
487
578
|
}),
|
|
488
579
|
preparePreviewRecoveryAnalysis: request => outcome(async () => {
|
|
@@ -493,7 +584,7 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
493
584
|
|| request.recoveryProof.revision !== request.expectedRevision
|
|
494
585
|
|| request.recoveryProof.reviewId !== request.expectedReviewId
|
|
495
586
|
|| request.recoveryProof.previewId !== request.expectedPreviewId)
|
|
496
|
-
throw domainError('stale_target', '
|
|
587
|
+
throw domainError('stale_target', hostMessage('thePreviewRecoveryAnalysisIdentityDoesNotMatch'));
|
|
497
588
|
const prepared = await options.module.preparePreviewRecoveryAnalysis(request.sessionId, request.recoveryProof, `preview-recovery-analysis:${randomUUID()}`);
|
|
498
589
|
const response = await mutationResponse(request.sessionId, request.checkoutId);
|
|
499
590
|
return {
|
|
@@ -517,7 +608,7 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
517
608
|
|| request.recoveryProof.revision !== request.expectedRevision
|
|
518
609
|
|| request.recoveryProof.reviewId !== request.expectedReviewId
|
|
519
610
|
|| request.recoveryProof.previewId !== request.expectedPreviewId)
|
|
520
|
-
throw domainError('stale_target', '
|
|
611
|
+
throw domainError('stale_target', hostMessage('thePreviewRecoveryHandoffIdentityDoesNotMatch'));
|
|
521
612
|
const targetSessionId = createTargetSessionId();
|
|
522
613
|
const launch = await options.module.createPreviewRecoveryHandoff(request.sessionId, request.recoveryProof, targetSessionId, `preview-recovery-handoff:${randomUUID()}`);
|
|
523
614
|
const created = recordOf(options.registry, launch.target.checkout.id);
|
|
@@ -526,7 +617,7 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
526
617
|
|| created.recoveryContinuation?.kind !== 'worktree_preview_recovery_handoff'
|
|
527
618
|
|| created.recoveryContinuation.requestId !== launch.continuation.requestId
|
|
528
619
|
|| observed.managedRoot !== launch.managedRoot)
|
|
529
|
-
throw domainError('checkout_mismatch', '
|
|
620
|
+
throw domainError('checkout_mismatch', hostMessage('hostIdentityVerificationFailedForTheRecoveryHandoffWorktree'));
|
|
530
621
|
return {
|
|
531
622
|
target: projectDetails(created, targetSessionId, observed.managedRoot, observed.snapshot, observed.dirty, false),
|
|
532
623
|
targetSessionId,
|
|
@@ -546,10 +637,10 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
546
637
|
checkpoint: request => outcome(async () => {
|
|
547
638
|
const record = await authorize(request.sessionId, request.checkoutId);
|
|
548
639
|
if (record.ownerSessionId !== request.sessionId)
|
|
549
|
-
throw domainError('not_owner', '
|
|
640
|
+
throw domainError('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanSaveACheckpoint447'));
|
|
550
641
|
const commitMessage = request.commitMessage.trim();
|
|
551
642
|
if (!commitMessage || commitMessage.length > 500 || !safeRecoveryRequestId(request.requestId)) {
|
|
552
|
-
throw domainError('invalid_input', '
|
|
643
|
+
throw domainError('invalid_input', hostMessage('theCheckpointCommitMessageOrRequestIdIsInvalid'));
|
|
553
644
|
}
|
|
554
645
|
const result = await options.module.operate({
|
|
555
646
|
action: 'checkpoint',
|
|
@@ -566,32 +657,32 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
566
657
|
return { ...(await mutationResponse(request.sessionId, request.checkoutId)), changedFiles: [...result.changedFiles] };
|
|
567
658
|
}
|
|
568
659
|
if (result.status !== 'checkpointed')
|
|
569
|
-
throw domainError('operation_not_allowed', '
|
|
660
|
+
throw domainError('operation_not_allowed', hostMessage('checkpointReturnedAnUnexpectedState'));
|
|
570
661
|
const response = await mutationResponse(request.sessionId, request.checkoutId);
|
|
571
662
|
if (response.target.state !== 'working' || response.target.revision !== result.target.revision) {
|
|
572
|
-
throw domainError('checkout_mismatch', '
|
|
663
|
+
throw domainError('checkout_mismatch', hostMessage('worktreeDidNotReturnToWorkingAfterCheckpoint'));
|
|
573
664
|
}
|
|
574
665
|
return { ...response, checkpoint: result.checkpoint, changedFiles: [...result.changedFiles] };
|
|
575
666
|
}),
|
|
576
667
|
preview: request => outcome(async () => {
|
|
577
668
|
const record = await authorize(request.sessionId, request.checkoutId);
|
|
578
669
|
if (record.ownerSessionId !== request.sessionId)
|
|
579
|
-
throw domainError('not_owner', '
|
|
670
|
+
throw domainError('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanPreviewChanges'));
|
|
580
671
|
if (record.revision !== request.expectedRevision)
|
|
581
|
-
throw domainError('stale_target', '
|
|
672
|
+
throw domainError('stale_target', hostMessage('theSessionTargetHasChangedRefresh'));
|
|
582
673
|
const review = readyReview(record);
|
|
583
674
|
if (review.reviewId !== request.expectedReviewId)
|
|
584
|
-
throw domainError('stale_target', '
|
|
675
|
+
throw domainError('stale_target', hostMessage('theReviewIdentityHasChangedRefresh'));
|
|
585
676
|
const result = await options.module.operate({
|
|
586
677
|
action: 'preview', sessionId: request.sessionId, expectedRevision: request.expectedRevision,
|
|
587
678
|
});
|
|
588
679
|
if (result.status === 'error')
|
|
589
680
|
throw domainError(result.code, result.message);
|
|
590
681
|
if (result.status === 'conflict') {
|
|
591
|
-
throw domainError('apply_conflict', '
|
|
682
|
+
throw domainError('apply_conflict', hostMessage('localPreviewPreflightFoundContentConflicts'), applyConflictContinuation(result, review.reviewId));
|
|
592
683
|
}
|
|
593
684
|
if (result.status !== 'previewed')
|
|
594
|
-
throw domainError('operation_not_allowed', '
|
|
685
|
+
throw domainError('operation_not_allowed', hostMessage('previewReturnedAnUnexpectedState'));
|
|
595
686
|
return {
|
|
596
687
|
...(await mutationResponse(request.sessionId, request.checkoutId)),
|
|
597
688
|
changedFiles: [...result.changedFiles],
|
|
@@ -600,12 +691,12 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
600
691
|
resumeRevision: request => outcome(async () => {
|
|
601
692
|
const record = await authorize(request.sessionId, request.checkoutId);
|
|
602
693
|
if (record.ownerSessionId !== request.sessionId)
|
|
603
|
-
throw domainError('not_owner', '
|
|
694
|
+
throw domainError('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanResumeEditing456'));
|
|
604
695
|
if (record.revision !== request.expectedRevision)
|
|
605
|
-
throw domainError('stale_target', '
|
|
696
|
+
throw domainError('stale_target', hostMessage('theSessionTargetHasChangedRefresh'));
|
|
606
697
|
const review = readyReview(record);
|
|
607
698
|
if (review.reviewId !== request.expectedReviewId)
|
|
608
|
-
throw domainError('stale_target', '
|
|
699
|
+
throw domainError('stale_target', hostMessage('theReviewIdentityHasChangedRefresh'));
|
|
609
700
|
const requestedRecovery = request.conflictContinuation;
|
|
610
701
|
if (requestedRecovery && (requestedRecovery.kind !== 'worktree_apply_conflict'
|
|
611
702
|
|| !safeRecoveryRequestId(requestedRecovery.requestId)
|
|
@@ -615,7 +706,7 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
615
706
|
|| !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/iu.test(requestedRecovery.localHeadOid)
|
|
616
707
|
|| requestedRecovery.conflictingFiles.length > 500
|
|
617
708
|
|| !requestedRecovery.conflictingFiles.every(safeConflictFile)))
|
|
618
|
-
throw domainError('invalid_input', '
|
|
709
|
+
throw domainError('invalid_input', hostMessage('invalidConflictRecoveryRequestIdentity'));
|
|
619
710
|
const recovery = requestedRecovery ? {
|
|
620
711
|
kind: 'worktree_apply_conflict',
|
|
621
712
|
requestId: `conflict-recovery:${randomUUID()}`,
|
|
@@ -628,7 +719,7 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
628
719
|
? await options.module.resumeRevision(request.sessionId, request.expectedRevision, request.expectedReviewId, recovery)
|
|
629
720
|
: await options.module.resumeRevision(request.sessionId, request.expectedRevision, request.expectedReviewId);
|
|
630
721
|
if (target.checkout.id !== request.checkoutId || target.delivery?.state !== 'working') {
|
|
631
|
-
throw domainError('checkout_mismatch', '
|
|
722
|
+
throw domainError('checkout_mismatch', hostMessage('theWorktreeIdentityOrStateDoesNotMatchAfter'));
|
|
632
723
|
}
|
|
633
724
|
const response = await mutationResponse(request.sessionId, request.checkoutId);
|
|
634
725
|
if (!requestedRecovery)
|
|
@@ -638,7 +729,7 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
638
729
|
|| current.kind !== 'worktree_apply_conflict'
|
|
639
730
|
|| current.requestId !== recovery.requestId
|
|
640
731
|
|| current.workingRevision !== response.target.revision)
|
|
641
|
-
throw domainError('checkout_mismatch', '
|
|
732
|
+
throw domainError('checkout_mismatch', hostMessage('theHostCouldNotPersistTheExactConflictRecovery'));
|
|
642
733
|
return {
|
|
643
734
|
...response,
|
|
644
735
|
recoveryContinuation: {
|
|
@@ -655,12 +746,12 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
655
746
|
prepareReviewRegeneration: request => outcome(async () => {
|
|
656
747
|
const record = await authorize(request.sessionId, request.checkoutId);
|
|
657
748
|
if (record.ownerSessionId !== request.sessionId)
|
|
658
|
-
throw domainError('not_owner', '
|
|
749
|
+
throw domainError('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanRequestReviewRegeneration462'));
|
|
659
750
|
if (record.revision !== request.expectedRevision)
|
|
660
|
-
throw domainError('stale_target', '
|
|
751
|
+
throw domainError('stale_target', hostMessage('theSessionTargetHasChangedRefresh'));
|
|
661
752
|
const review = readyReview(record);
|
|
662
753
|
if (review.reviewId !== request.expectedReviewId)
|
|
663
|
-
throw domainError('stale_target', '
|
|
754
|
+
throw domainError('stale_target', hostMessage('theReviewIdentityHasChangedRefresh'));
|
|
664
755
|
const prepared = await options.module.prepareReviewRegeneration(request.sessionId, request.expectedRevision, request.expectedReviewId, `review-regeneration:${randomUUID()}`);
|
|
665
756
|
const response = await mutationResponse(request.sessionId, request.checkoutId);
|
|
666
757
|
return {
|
|
@@ -677,7 +768,7 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
677
768
|
rollbackPreview: request => outcome(async () => {
|
|
678
769
|
const record = await authorize(request.sessionId, request.checkoutId);
|
|
679
770
|
if (record.ownerSessionId !== request.sessionId)
|
|
680
|
-
throw domainError('not_owner', '
|
|
771
|
+
throw domainError('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanRollBackLocal'));
|
|
681
772
|
const result = await options.module.operate({
|
|
682
773
|
action: 'rollback_preview',
|
|
683
774
|
sessionId: request.sessionId,
|
|
@@ -688,7 +779,7 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
688
779
|
if (result.status === 'error')
|
|
689
780
|
throw domainError(result.code, result.message);
|
|
690
781
|
if (result.status !== 'preview_rolled_back' && result.status !== 'preview_detached') {
|
|
691
|
-
throw domainError('operation_not_allowed', '
|
|
782
|
+
throw domainError('operation_not_allowed', hostMessage('rollbackPreviewReturnedAnUnexpectedState'));
|
|
692
783
|
}
|
|
693
784
|
return {
|
|
694
785
|
...(await mutationResponse(request.sessionId, request.checkoutId)),
|
|
@@ -698,7 +789,7 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
698
789
|
discard: request => outcome(async () => {
|
|
699
790
|
const record = await authorize(request.sessionId, request.checkoutId);
|
|
700
791
|
if (record.ownerSessionId !== request.sessionId && ownerSessionAvailable(record)) {
|
|
701
|
-
throw domainError('not_owner', '
|
|
792
|
+
throw domainError('not_owner', hostMessage('theOwnerSessionHasTakenOverThisWorktreeOnly467'));
|
|
702
793
|
}
|
|
703
794
|
if (record.ownerSessionId === request.sessionId) {
|
|
704
795
|
const result = await options.module.operate({
|
|
@@ -714,7 +805,7 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
714
805
|
return { ...(await mutationResponse(request.sessionId, request.checkoutId)), changedFiles: [...result.changedFiles] };
|
|
715
806
|
}
|
|
716
807
|
if (result.status !== 'discarded')
|
|
717
|
-
throw domainError('operation_not_allowed', '
|
|
808
|
+
throw domainError('operation_not_allowed', hostMessage('discardReturnedAnUnexpectedState'));
|
|
718
809
|
}
|
|
719
810
|
else {
|
|
720
811
|
await options.module.manageManagedWorktreeForSession(request.sessionId, {
|
|
@@ -730,15 +821,15 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
730
821
|
try {
|
|
731
822
|
const record = await authorize(request.sessionId, request.checkoutId);
|
|
732
823
|
if (record.ownerSessionId !== request.sessionId)
|
|
733
|
-
return failure('not_owner', '
|
|
824
|
+
return failure('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanCommitAcceptance'));
|
|
734
825
|
if (record.revision !== request.expectedRevision)
|
|
735
|
-
return failure('stale_target', '
|
|
826
|
+
return failure('stale_target', hostMessage('theSessionTargetHasChangedRefresh'));
|
|
736
827
|
const review = readyReview(record);
|
|
737
828
|
if (review.reviewId !== request.expectedReviewId)
|
|
738
|
-
return failure('stale_target', '
|
|
829
|
+
return failure('stale_target', hostMessage('theReviewIdentityHasChangedRefresh'));
|
|
739
830
|
const commitMessage = request.commitMessage.trim();
|
|
740
831
|
if (!commitMessage || commitMessage.length > 500)
|
|
741
|
-
return failure('invalid_input', '
|
|
832
|
+
return failure('invalid_input', hostMessage('theCommitMessageMustContain1500Characters'));
|
|
742
833
|
const result = await options.module.operate({
|
|
743
834
|
sessionId: request.sessionId,
|
|
744
835
|
expectedRevision: request.expectedRevision,
|
|
@@ -750,10 +841,10 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
750
841
|
if (result.status === 'error')
|
|
751
842
|
return failure(result.code, result.message);
|
|
752
843
|
if (result.status === 'conflict') {
|
|
753
|
-
return failure('apply_conflict', '
|
|
844
|
+
return failure('apply_conflict', hostMessage('applyingToLocalCausedConflicts'), undefined, applyConflictContinuation(result, review.reviewId));
|
|
754
845
|
}
|
|
755
846
|
if (result.status !== 'finished')
|
|
756
|
-
return failure('operation_not_allowed', '
|
|
847
|
+
return failure('operation_not_allowed', hostMessage('finalizeReturnedAnUnexpectedState'));
|
|
757
848
|
return {
|
|
758
849
|
ok: true,
|
|
759
850
|
value: {
|
|
@@ -771,15 +862,15 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
771
862
|
try {
|
|
772
863
|
const record = await authorize(request.sessionId, request.checkoutId);
|
|
773
864
|
if (record.ownerSessionId !== request.sessionId)
|
|
774
|
-
return failure('not_owner', '
|
|
865
|
+
return failure('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanFinalizeLocalPreview'));
|
|
775
866
|
if (record.revision !== request.expectedRevision)
|
|
776
|
-
return failure('stale_target', '
|
|
867
|
+
return failure('stale_target', hostMessage('theSessionTargetHasChangedRefresh'));
|
|
777
868
|
const review = previewReview(record);
|
|
778
869
|
if (review.reviewId !== request.expectedReviewId)
|
|
779
|
-
return failure('stale_target', '
|
|
870
|
+
return failure('stale_target', hostMessage('theReviewIdentityHasChangedRefresh'));
|
|
780
871
|
const commitMessage = request.commitMessage.trim();
|
|
781
872
|
if (!commitMessage || commitMessage.length > 500)
|
|
782
|
-
return failure('invalid_input', '
|
|
873
|
+
return failure('invalid_input', hostMessage('theCommitMessageMustContain1500Characters'));
|
|
783
874
|
const result = await options.module.operate({
|
|
784
875
|
action: 'finalize_preview',
|
|
785
876
|
sessionId: request.sessionId,
|
|
@@ -800,7 +891,7 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
800
891
|
};
|
|
801
892
|
}
|
|
802
893
|
if (result.status !== 'finished')
|
|
803
|
-
return failure('operation_not_allowed', '
|
|
894
|
+
return failure('operation_not_allowed', hostMessage('finalizePreviewReturnedAnUnexpectedState'));
|
|
804
895
|
return {
|
|
805
896
|
ok: true,
|
|
806
897
|
value: {
|
|
@@ -836,28 +927,28 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
836
927
|
beginNextIteration: request => outcome(async () => {
|
|
837
928
|
const predecessor = recordOf(options.registry, request.checkoutId);
|
|
838
929
|
if (predecessor.ownerSessionId !== request.sessionId) {
|
|
839
|
-
throw domainError('not_owner', '
|
|
930
|
+
throw domainError('not_owner', hostMessage('onlyTheOwnerSessionCanBeginTheNextIteration'));
|
|
840
931
|
}
|
|
841
932
|
if (predecessor.revision !== request.expectedRevision) {
|
|
842
|
-
throw domainError('stale_target', '
|
|
933
|
+
throw domainError('stale_target', hostMessage('theWorktreeStateHasChangedRefreshBeforeBeginningThe'));
|
|
843
934
|
}
|
|
844
935
|
if (predecessor.phase !== 'discarded' || predecessor.delivery.state !== 'delivered') {
|
|
845
|
-
throw domainError('operation_not_allowed', '
|
|
936
|
+
throw domainError('operation_not_allowed', hostMessage('onlyADeliveredStateWithSuccessfulCleanupCanBegin'));
|
|
846
937
|
}
|
|
847
938
|
const session = options.lookup.getSession(request.sessionId);
|
|
848
939
|
const workspace = session?.projectId === undefined ? undefined : options.lookup.getProject(session.projectId);
|
|
849
940
|
if (!workspace || !sameLocalRoot(resolve(workspace.root), resolve(predecessor.managedRoot))) {
|
|
850
|
-
throw domainError('project_mismatch', '
|
|
941
|
+
throw domainError('project_mismatch', hostMessage('theCurrentSessionSImmutableCwdDoesNotMatch'));
|
|
851
942
|
}
|
|
852
943
|
const target = await options.module.beginNextIteration(request.sessionId, request.expectedRevision);
|
|
853
944
|
if (target.checkout.kind !== 'isolated' || target.delivery?.state !== 'working') {
|
|
854
|
-
throw domainError('checkout_mismatch', '
|
|
945
|
+
throw domainError('checkout_mismatch', hostMessage('theNextWorktreeIterationDidNotEnterWorkingState484'));
|
|
855
946
|
}
|
|
856
947
|
const record = recordOf(options.registry, target.checkout.id);
|
|
857
948
|
if (record.predecessorCheckoutId !== predecessor.checkoutId
|
|
858
949
|
|| record.ownerSessionId !== request.sessionId
|
|
859
950
|
|| !sameLocalRoot(record.managedRoot, predecessor.managedRoot)) {
|
|
860
|
-
throw domainError('checkout_mismatch', '
|
|
951
|
+
throw domainError('checkout_mismatch', hostMessage('theNextIterationSWorktreeLineageOrCwdIdentity'));
|
|
861
952
|
}
|
|
862
953
|
const observed = await observe(record);
|
|
863
954
|
return {
|