dsh-git-worktree 0.4.0 → 0.7.1
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 +98 -0
- package/NOTICE +14 -0
- package/README.en.md +74 -266
- package/README.md +78 -286
- package/cordis.patch.yml +7 -3
- package/lib/adapters/git.js +8 -0
- package/lib/adapters/git.js.map +1 -1
- package/lib/adapters/registry.js +100 -1
- package/lib/adapters/registry.js.map +1 -1
- package/lib/checkpoint.d.ts +3 -0
- package/lib/checkpoint.js +29 -0
- package/lib/checkpoint.js.map +1 -0
- package/lib/client/WorktreeReviewRow.d.ts +1 -1
- package/lib/client/WorktreeReviewRow.js +32 -4
- package/lib/client/WorktreeReviewRow.js.map +1 -1
- package/lib/client/actions.d.ts +25 -1
- package/lib/client/actions.js.map +1 -1
- package/lib/client/console-remote/adapter.js +11 -3
- package/lib/client/console-remote/adapter.js.map +1 -1
- package/lib/client/index.d.ts +1 -1
- package/lib/client/index.js +3 -1
- package/lib/client/index.js.map +1 -1
- package/lib/client/pre-session/controller.js +9 -0
- package/lib/client/pre-session/controller.js.map +1 -1
- package/lib/client/review-console/PreflightStatus.d.ts +3 -2
- package/lib/client/review-console/PreflightStatus.js +3 -3
- package/lib/client/review-console/PreflightStatus.js.map +1 -1
- package/lib/client/review-console/ReviewActions.js +566 -27
- package/lib/client/review-console/ReviewActions.js.map +1 -1
- package/lib/client/review-console/WorktreeReviewPanel.js +18 -3
- package/lib/client/review-console/WorktreeReviewPanel.js.map +1 -1
- package/lib/client/review-console/preflight-cache.d.ts +2 -0
- package/lib/client/review-console/preflight-cache.js +18 -5
- package/lib/client/review-console/preflight-cache.js.map +1 -1
- package/lib/client/review-console/preview-recovery-cache.d.ts +20 -0
- package/lib/client/review-console/preview-recovery-cache.js +103 -0
- package/lib/client/review-console/preview-recovery-cache.js.map +1 -0
- package/lib/client/review-console/recovery-continuation.d.ts +53 -0
- package/lib/client/review-console/recovery-continuation.js +483 -0
- package/lib/client/review-console/recovery-continuation.js.map +1 -0
- package/lib/client/review-console/review-console.styles.js +9 -1
- package/lib/client/review-console/review-console.styles.js.map +1 -1
- package/lib/client/styles.js +61 -0
- package/lib/client/styles.js.map +1 -1
- package/lib/client/target-console/TargetStatusAction.js +2 -2
- package/lib/client/target-console/TargetStatusAction.js.map +1 -1
- package/lib/client/target-console/WorktreeConsoleView.js +1 -1
- package/lib/client/target-console/WorktreeConsoleView.js.map +1 -1
- package/lib/client/target-console/WorktreeReviewStatus.js +19 -6
- package/lib/client/target-console/WorktreeReviewStatus.js.map +1 -1
- package/lib/client/workspace-sidebar/index.d.ts +33 -0
- package/lib/client/workspace-sidebar/index.js +127 -0
- package/lib/client/workspace-sidebar/index.js.map +1 -0
- package/lib/client/workspace-sidebar/model.d.ts +37 -0
- package/lib/client/workspace-sidebar/model.js +88 -0
- package/lib/client/workspace-sidebar/model.js.map +1 -0
- package/lib/client.js +4511 -106
- package/lib/console-contract.d.ts +124 -1
- package/lib/console-contract.js.map +1 -1
- package/lib/console-host/control-plane.d.ts +7 -1
- package/lib/console-host/control-plane.js +264 -9
- package/lib/console-host/control-plane.js.map +1 -1
- package/lib/console-host/errors.d.ts +2 -2
- package/lib/console-host/errors.js +21 -5
- package/lib/console-host/errors.js.map +1 -1
- package/lib/console-host/projection.js +60 -1
- package/lib/console-host/projection.js.map +1 -1
- package/lib/console-host/service.d.ts +11 -5
- package/lib/console-host/service.js +54 -6
- package/lib/console-host/service.js.map +1 -1
- package/lib/console-remote/descriptors.js +40 -3
- package/lib/console-remote/descriptors.js.map +1 -1
- package/lib/console-remote/remote.d.ts +11 -5
- package/lib/console-remote/remote.js.map +1 -1
- package/lib/console-remote/schemas.d.ts +104 -1
- package/lib/console-remote/schemas.js +161 -5
- package/lib/console-remote/schemas.js.map +1 -1
- package/lib/index.d.ts +13 -2
- package/lib/index.js.map +1 -1
- package/lib/ports.d.ts +76 -1
- package/lib/session-checkout-apply.d.ts +76 -0
- package/lib/session-checkout-apply.js +608 -143
- package/lib/session-checkout-apply.js.map +1 -1
- package/lib/session-checkout-module.js +759 -124
- package/lib/session-checkout-module.js.map +1 -1
- package/lib/tools.js +1 -1
- package/lib/tools.js.map +1 -1
- package/lib/types.d.ts +93 -2
- package/lib/types.js.map +1 -1
- package/package.json +22 -14
- package/scripts/check-publish.mjs +17 -2
- package/scripts/check-workspace-sidebar-version.mjs +14 -0
- package/scripts/dev-dsh-lib.mjs +1 -1
- package/scripts/workspace-sidebar-upstream.d.mts +8 -0
- package/scripts/workspace-sidebar-upstream.mjs +160 -0
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* re-validates project, owner, cwd, revision and review identity at execution.
|
|
8
8
|
* @module dsh-git-worktree/console-contract
|
|
9
9
|
*/
|
|
10
|
-
import { type SessionCheckoutErrorCode, type SessionCheckoutPhase, type WorktreeRetentionMode, type WorktreeApplyPreflightView, type WorktreeAcceptanceBlockerView, type WorktreeDeliveryProofView, type WorktreeValidationItem, type WorktreeValidationStatus } from './types.js';
|
|
10
|
+
import { type SessionCheckoutErrorCode, type SessionCheckoutPhase, type WorktreeRetentionMode, type WorktreeApplyPreflightView, type WorktreePreviewRecoveryPreflightView, type WorktreePreviewRecoveryProof, type WorktreeAcceptanceBlockerView, type WorktreeDeliveryProofView, type WorktreeValidationItem, type WorktreeValidationStatus, type WorktreeCheckpointView } from './types.js';
|
|
11
11
|
export declare const WORKTREE_CONSOLE_TARGET_STATES: readonly ["local", "creating", "working", "ready_for_review", "preview_active", "preview_detached", "retained", "cleanup_pending", "recovery_required", "delivered"];
|
|
12
12
|
export type WorktreeConsoleTargetState = typeof WORKTREE_CONSOLE_TARGET_STATES[number];
|
|
13
13
|
export type WorktreeConsoleDeliveryState = 'working' | 'ready_for_review' | 'preview_active' | 'preview_detached' | 'finalized' | 'retained' | 'delivered';
|
|
@@ -30,6 +30,8 @@ export interface WorktreeConsoleCapabilities {
|
|
|
30
30
|
discard: boolean;
|
|
31
31
|
preflight: boolean;
|
|
32
32
|
preview: boolean;
|
|
33
|
+
/** Save the exact reviewed snapshot as a managed-Worktree-only stage and continue editing. */
|
|
34
|
+
checkpoint: boolean;
|
|
33
35
|
/** Invalidate an unsynced review and resume the same iteration without touching Local. */
|
|
34
36
|
resumeRevision: boolean;
|
|
35
37
|
rollbackPreview: boolean;
|
|
@@ -42,6 +44,8 @@ export interface WorktreeConsoleCapabilities {
|
|
|
42
44
|
beginNextIteration: boolean;
|
|
43
45
|
}
|
|
44
46
|
export interface WorktreeConsolePreviewRecovery {
|
|
47
|
+
previewId: string;
|
|
48
|
+
detachedAt: number;
|
|
45
49
|
reason: 'stale_local' | 'preview_modified';
|
|
46
50
|
attemptedAction: 'rollback_preview' | 'finalize_preview' | 'discard';
|
|
47
51
|
}
|
|
@@ -78,6 +82,8 @@ export interface WorktreeConsoleTargetSummary {
|
|
|
78
82
|
cleanupMessage?: string;
|
|
79
83
|
deliveryProof?: WorktreeConsoleDeliveryProof;
|
|
80
84
|
review?: WorktreeConsoleReviewSummary;
|
|
85
|
+
checkpoints?: WorktreeCheckpointView[];
|
|
86
|
+
checkpointGeneration?: string;
|
|
81
87
|
reviewSlot?: 'available' | 'waiting';
|
|
82
88
|
reviewSlotOwnerSessionId?: string;
|
|
83
89
|
reviewSlotHolder?: WorktreeAcceptanceBlockerView;
|
|
@@ -91,6 +97,8 @@ export interface WorktreeConsoleTargetDetails extends WorktreeConsoleTargetSumma
|
|
|
91
97
|
sourceRoot: string | null;
|
|
92
98
|
sourceOid: string;
|
|
93
99
|
currentBranch: string | null;
|
|
100
|
+
/** Owner-only Host proof for one explicitly authorized recovery continuation. */
|
|
101
|
+
recoveryContinuation?: WorktreeRecoveryProof;
|
|
94
102
|
}
|
|
95
103
|
export interface WorktreeConsoleCurrentRequest {
|
|
96
104
|
sessionId: string;
|
|
@@ -107,6 +115,24 @@ export interface WorktreeConsoleListResponse {
|
|
|
107
115
|
project: WorktreeConsoleProject;
|
|
108
116
|
worktrees: WorktreeConsoleTargetSummary[];
|
|
109
117
|
}
|
|
118
|
+
export type WorktreeSidebarTaskState = 'working' | 'ready_for_review' | 'preview_active' | 'preview_detached' | 'recovery_required' | 'finalized' | 'discarded';
|
|
119
|
+
/** Path-free, read-only task identity used only to aggregate the Workspace sidebar. */
|
|
120
|
+
export interface WorktreeSidebarTask {
|
|
121
|
+
checkoutId: string;
|
|
122
|
+
ownerSessionId: string;
|
|
123
|
+
sourceSessionId: string;
|
|
124
|
+
iteration: number;
|
|
125
|
+
revision: number;
|
|
126
|
+
phase: SessionCheckoutPhase;
|
|
127
|
+
state: WorktreeSidebarTaskState;
|
|
128
|
+
}
|
|
129
|
+
export interface WorktreeSidebarProject {
|
|
130
|
+
project: WorktreeConsoleProject;
|
|
131
|
+
tasks: WorktreeSidebarTask[];
|
|
132
|
+
}
|
|
133
|
+
export interface WorktreeSidebarTopologyResponse {
|
|
134
|
+
projects: WorktreeSidebarProject[];
|
|
135
|
+
}
|
|
110
136
|
/** targetSessionId is allocated on the Host; the browser never chooses ownership identity. */
|
|
111
137
|
export interface WorktreeConsoleCreateRequest {
|
|
112
138
|
sourceSessionId: string;
|
|
@@ -145,17 +171,48 @@ export interface WorktreeConsolePreviewRequest {
|
|
|
145
171
|
expectedRevision: number;
|
|
146
172
|
expectedReviewId: string;
|
|
147
173
|
}
|
|
174
|
+
export interface WorktreeConsoleCheckpointRequest {
|
|
175
|
+
sessionId: string;
|
|
176
|
+
checkoutId: string;
|
|
177
|
+
expectedRevision: number;
|
|
178
|
+
expectedReviewId: string;
|
|
179
|
+
expectedGeneration: string;
|
|
180
|
+
requestId: string;
|
|
181
|
+
commitMessage: string;
|
|
182
|
+
}
|
|
148
183
|
export interface WorktreeConsoleResumeRevisionRequest {
|
|
149
184
|
sessionId: string;
|
|
150
185
|
checkoutId: string;
|
|
151
186
|
expectedRevision: number;
|
|
152
187
|
expectedReviewId: string;
|
|
188
|
+
/** Present only for the explicit conflict-recovery action; Host re-runs conflict preflight under CAS. */
|
|
189
|
+
conflictContinuation?: WorktreeApplyConflictContinuation;
|
|
190
|
+
}
|
|
191
|
+
export interface WorktreeConsolePreviewRecoveryPreflightRequest {
|
|
192
|
+
sessionId: string;
|
|
193
|
+
checkoutId: string;
|
|
194
|
+
expectedRevision: number;
|
|
195
|
+
expectedReviewId: string;
|
|
196
|
+
expectedPreviewId: string;
|
|
197
|
+
}
|
|
198
|
+
export interface WorktreeConsolePreviewRecoveryPreflightResponse {
|
|
199
|
+
preflight: WorktreePreviewRecoveryPreflightView;
|
|
200
|
+
}
|
|
201
|
+
export interface WorktreeConsolePreparePreviewRecoveryAnalysisRequest extends WorktreeConsolePreviewRecoveryPreflightRequest {
|
|
202
|
+
recoveryProof: WorktreePreviewRecoveryProof;
|
|
203
|
+
}
|
|
204
|
+
export interface WorktreeConsoleCreatePreviewRecoveryHandoffRequest extends WorktreeConsolePreviewRecoveryPreflightRequest {
|
|
205
|
+
recoveryProof: WorktreePreviewRecoveryProof;
|
|
206
|
+
}
|
|
207
|
+
export interface WorktreeConsoleCreatePreviewRecoveryHandoffResponse extends WorktreeConsoleCreateResponse {
|
|
208
|
+
recoveryContinuation: WorktreePreviewRecoveryHandoffProof;
|
|
153
209
|
}
|
|
154
210
|
export interface WorktreeConsoleRollbackPreviewRequest {
|
|
155
211
|
sessionId: string;
|
|
156
212
|
checkoutId: string;
|
|
157
213
|
expectedRevision: number;
|
|
158
214
|
resumeRevision?: boolean;
|
|
215
|
+
recoveryProof?: WorktreePreviewRecoveryProof;
|
|
159
216
|
}
|
|
160
217
|
export interface WorktreeConsoleFinalizeRequest {
|
|
161
218
|
sessionId: string;
|
|
@@ -173,6 +230,7 @@ export interface WorktreeConsoleFinalizePreviewRequest {
|
|
|
173
230
|
expectedReviewId: string;
|
|
174
231
|
commitMessage: string;
|
|
175
232
|
retention: WorktreeRetentionMode;
|
|
233
|
+
recoveryProof?: WorktreePreviewRecoveryProof;
|
|
176
234
|
}
|
|
177
235
|
export interface WorktreeConsoleSetRetentionRequest {
|
|
178
236
|
sessionId: string;
|
|
@@ -194,6 +252,14 @@ export interface WorktreeConsoleMutationResponse {
|
|
|
194
252
|
target: WorktreeConsoleTargetSummary;
|
|
195
253
|
changedFiles?: string[];
|
|
196
254
|
commitOid?: string | null;
|
|
255
|
+
checkpoint?: WorktreeCheckpointView;
|
|
256
|
+
recoveryContinuation?: WorktreeRecoveryProof;
|
|
257
|
+
}
|
|
258
|
+
export interface WorktreeConsolePrepareRegenerationRequest {
|
|
259
|
+
sessionId: string;
|
|
260
|
+
checkoutId: string;
|
|
261
|
+
expectedRevision: number;
|
|
262
|
+
expectedReviewId: string;
|
|
197
263
|
}
|
|
198
264
|
export type WorktreeConsoleFileStatus = 'added' | 'modified' | 'deleted' | 'renamed' | 'binary';
|
|
199
265
|
export interface WorktreeConsoleDiffFile {
|
|
@@ -220,10 +286,61 @@ export declare const WORKTREE_CONSOLE_ERROR_CODES: readonly ["session_not_found"
|
|
|
220
286
|
export type WorktreeConsoleErrorCode = SessionCheckoutErrorCode | 'transport_unavailable' | 'malformed_response';
|
|
221
287
|
export type WorktreeConsoleErrorCategory = 'permission' | 'stale' | 'confirmation' | 'recovery' | 'conflict' | 'unavailable' | 'invalid' | 'internal';
|
|
222
288
|
export type WorktreeConsoleRecovery = 'none' | 'refresh' | 'confirm_dirty' | 'open_recovery' | 'retry';
|
|
289
|
+
export interface WorktreeApplyConflictContinuation {
|
|
290
|
+
kind: 'worktree_apply_conflict';
|
|
291
|
+
/** Stable identity for Client single-flight and retry within this renderer lifetime. */
|
|
292
|
+
requestId: string;
|
|
293
|
+
checkoutId: string;
|
|
294
|
+
/** Review that remains Ready until the user explicitly resumes it for conflict resolution. */
|
|
295
|
+
reviewId: string;
|
|
296
|
+
/** Authoritative revision after the failed real-time write attempt returned to Ready. */
|
|
297
|
+
revision: number;
|
|
298
|
+
localHeadOid: string;
|
|
299
|
+
conflictingFiles: string[];
|
|
300
|
+
}
|
|
301
|
+
export interface WorktreeApplyConflictRecoveryProof {
|
|
302
|
+
kind: 'worktree_apply_conflict';
|
|
303
|
+
requestId: string;
|
|
304
|
+
checkoutId: string;
|
|
305
|
+
reviewId: string;
|
|
306
|
+
/** Exact Working revision produced by the Host conflict-resume CAS. */
|
|
307
|
+
revision: number;
|
|
308
|
+
localHeadOid: string;
|
|
309
|
+
conflictingFiles: string[];
|
|
310
|
+
}
|
|
311
|
+
export interface WorktreeReviewRegenerationProof {
|
|
312
|
+
kind: 'worktree_review_regeneration';
|
|
313
|
+
requestId: string;
|
|
314
|
+
checkoutId: string;
|
|
315
|
+
reviewId: string;
|
|
316
|
+
revision: number;
|
|
317
|
+
}
|
|
318
|
+
export interface WorktreePreviewRecoveryAnalysisProof {
|
|
319
|
+
kind: 'worktree_preview_recovery_analysis';
|
|
320
|
+
requestId: string;
|
|
321
|
+
checkoutId: string;
|
|
322
|
+
reviewId: string;
|
|
323
|
+
previewId: string;
|
|
324
|
+
revision: number;
|
|
325
|
+
generation: string;
|
|
326
|
+
}
|
|
327
|
+
export interface WorktreePreviewRecoveryHandoffProof {
|
|
328
|
+
kind: 'worktree_preview_recovery_handoff';
|
|
329
|
+
requestId: string;
|
|
330
|
+
checkoutId: string;
|
|
331
|
+
sourceCheckoutId: string;
|
|
332
|
+
reviewId: string;
|
|
333
|
+
previewId: string;
|
|
334
|
+
revision: number;
|
|
335
|
+
generation: string;
|
|
336
|
+
}
|
|
337
|
+
export type WorktreeRecoveryProof = WorktreeApplyConflictRecoveryProof | WorktreeReviewRegenerationProof | WorktreePreviewRecoveryAnalysisProof | WorktreePreviewRecoveryHandoffProof;
|
|
338
|
+
export type WorktreeConsoleContinuation = WorktreeApplyConflictContinuation;
|
|
223
339
|
export interface WorktreeConsoleError {
|
|
224
340
|
code: WorktreeConsoleErrorCode;
|
|
225
341
|
message: string;
|
|
226
342
|
details?: Record<string, string | number | boolean | null>;
|
|
343
|
+
continuation?: WorktreeConsoleContinuation;
|
|
227
344
|
}
|
|
228
345
|
export type WorktreeConsoleOutcome<T> = {
|
|
229
346
|
ok: true;
|
|
@@ -240,14 +357,20 @@ export interface WorktreeConsoleErrorMeta {
|
|
|
240
357
|
export declare function worktreeConsoleErrorMeta(code: WorktreeConsoleErrorCode): WorktreeConsoleErrorMeta;
|
|
241
358
|
/** Normalized Client seam; Remote transport details stay behind one adapter. */
|
|
242
359
|
export interface WorktreeConsoleAdapter {
|
|
360
|
+
sidebarTopology(): Promise<WorktreeConsoleOutcome<WorktreeSidebarTopologyResponse>>;
|
|
243
361
|
current(request: WorktreeConsoleCurrentRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleCurrentResponse>>;
|
|
244
362
|
list(request: WorktreeConsoleListRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleListResponse>>;
|
|
245
363
|
create(request: WorktreeConsoleCreateRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleCreateResponse>>;
|
|
246
364
|
inspect(request: WorktreeConsoleInspectRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleInspectResponse>>;
|
|
247
365
|
reviewDiff(request: WorktreeConsoleReviewDiffRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleReviewDiffResponse>>;
|
|
248
366
|
preflight(request: WorktreeConsolePreflightRequest): Promise<WorktreeConsoleOutcome<WorktreeConsolePreflightResponse>>;
|
|
367
|
+
previewRecoveryPreflight(request: WorktreeConsolePreviewRecoveryPreflightRequest): Promise<WorktreeConsoleOutcome<WorktreeConsolePreviewRecoveryPreflightResponse>>;
|
|
368
|
+
preparePreviewRecoveryAnalysis(request: WorktreeConsolePreparePreviewRecoveryAnalysisRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
|
|
369
|
+
createPreviewRecoveryHandoff(request: WorktreeConsoleCreatePreviewRecoveryHandoffRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleCreatePreviewRecoveryHandoffResponse>>;
|
|
249
370
|
preview(request: WorktreeConsolePreviewRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
|
|
371
|
+
checkpoint(request: WorktreeConsoleCheckpointRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
|
|
250
372
|
resumeRevision(request: WorktreeConsoleResumeRevisionRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
|
|
373
|
+
prepareReviewRegeneration(request: WorktreeConsolePrepareRegenerationRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
|
|
251
374
|
rollbackPreview(request: WorktreeConsoleRollbackPreviewRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
|
|
252
375
|
discard(request: WorktreeConsoleDiscardRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
|
|
253
376
|
finalize(request: WorktreeConsoleFinalizeRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"console-contract.js","sourceRoot":"","sources":["../src/console-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,4BAA4B,
|
|
1
|
+
{"version":3,"file":"console-contract.js","sourceRoot":"","sources":["../src/console-contract.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WorktreeConsoleBeginNextIterationRequest, WorktreeConsoleCreateResponse, WorktreeConsoleCurrentResponse, WorktreeConsoleDiscardRequest, WorktreeConsoleFinalizePreviewRequest, WorktreeConsoleFinalizeRequest, WorktreeConsoleInspectResponse, WorktreeConsoleListRequest, WorktreeConsoleListResponse, WorktreeConsoleMutationResponse, WorktreeConsoleOutcome, WorktreeConsolePreflightRequest, WorktreeConsolePreflightResponse, WorktreeConsolePreviewRequest, WorktreeConsoleResumeRevisionRequest, WorktreeConsoleRollbackPreviewRequest, WorktreeConsoleRetryCleanupRequest, WorktreeConsoleReviewDiffRequest, WorktreeConsoleReviewDiffResponse, WorktreeConsoleSetRetentionRequest } from '../console-contract.js';
|
|
1
|
+
import type { WorktreeConsoleBeginNextIterationRequest, WorktreeConsoleCreatePreviewRecoveryHandoffRequest, WorktreeConsoleCreatePreviewRecoveryHandoffResponse, WorktreeConsoleCreateResponse, WorktreeConsoleCheckpointRequest, WorktreeConsoleCurrentResponse, WorktreeConsoleDiscardRequest, WorktreeConsoleFinalizePreviewRequest, WorktreeConsoleFinalizeRequest, WorktreeConsoleInspectResponse, WorktreeConsoleListRequest, WorktreeConsoleListResponse, WorktreeConsoleMutationResponse, WorktreeConsoleOutcome, WorktreeConsolePreflightRequest, WorktreeConsolePreflightResponse, WorktreeConsolePreparePreviewRecoveryAnalysisRequest, WorktreeConsolePreviewRecoveryPreflightRequest, WorktreeConsolePreviewRecoveryPreflightResponse, WorktreeConsolePrepareRegenerationRequest, WorktreeConsolePreviewRequest, WorktreeConsoleResumeRevisionRequest, WorktreeConsoleRollbackPreviewRequest, WorktreeConsoleRetryCleanupRequest, WorktreeConsoleReviewDiffRequest, WorktreeConsoleReviewDiffResponse, WorktreeConsoleSetRetentionRequest, WorktreeSidebarTopologyResponse } from '../console-contract.js';
|
|
2
2
|
import type { SessionCheckoutFilesPort, SessionCheckoutGitPort, SessionCheckoutLookupPort, SessionCheckoutRegistryPort } from '../ports.js';
|
|
3
3
|
import type { SessionCheckoutModule } from '../index.js';
|
|
4
4
|
import { type WorktreeReviewDiffReader } from './review-diff.js';
|
|
@@ -12,14 +12,20 @@ export interface WorktreeConsoleControlPlaneOptions {
|
|
|
12
12
|
createTargetSessionId?: () => string;
|
|
13
13
|
}
|
|
14
14
|
export interface WorktreeConsoleControlPlane {
|
|
15
|
+
sidebarTopology(): Promise<WorktreeConsoleOutcome<WorktreeSidebarTopologyResponse>>;
|
|
15
16
|
current(sessionId: string): Promise<WorktreeConsoleOutcome<WorktreeConsoleCurrentResponse>>;
|
|
16
17
|
list(request: WorktreeConsoleListRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleListResponse>>;
|
|
17
18
|
create(sourceSessionId: string): Promise<WorktreeConsoleOutcome<WorktreeConsoleCreateResponse>>;
|
|
18
19
|
inspect(sessionId: string, checkoutId: string): Promise<WorktreeConsoleOutcome<WorktreeConsoleInspectResponse>>;
|
|
19
20
|
reviewDiff(request: WorktreeConsoleReviewDiffRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleReviewDiffResponse>>;
|
|
20
21
|
preflight(request: WorktreeConsolePreflightRequest): Promise<WorktreeConsoleOutcome<WorktreeConsolePreflightResponse>>;
|
|
22
|
+
previewRecoveryPreflight(request: WorktreeConsolePreviewRecoveryPreflightRequest): Promise<WorktreeConsoleOutcome<WorktreeConsolePreviewRecoveryPreflightResponse>>;
|
|
23
|
+
preparePreviewRecoveryAnalysis(request: WorktreeConsolePreparePreviewRecoveryAnalysisRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
|
|
24
|
+
createPreviewRecoveryHandoff(request: WorktreeConsoleCreatePreviewRecoveryHandoffRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleCreatePreviewRecoveryHandoffResponse>>;
|
|
21
25
|
preview(request: WorktreeConsolePreviewRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
|
|
26
|
+
checkpoint(request: WorktreeConsoleCheckpointRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
|
|
22
27
|
resumeRevision(request: WorktreeConsoleResumeRevisionRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
|
|
28
|
+
prepareReviewRegeneration(request: WorktreeConsolePrepareRegenerationRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
|
|
23
29
|
rollbackPreview(request: WorktreeConsoleRollbackPreviewRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
|
|
24
30
|
discard(request: WorktreeConsoleDiscardRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
|
|
25
31
|
finalize(request: WorktreeConsoleFinalizeRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
|
|
@@ -9,6 +9,25 @@ function recordOf(registry, checkoutId) {
|
|
|
9
9
|
throw domainError('checkout_missing', 'Worktree 记录不存在');
|
|
10
10
|
return record;
|
|
11
11
|
}
|
|
12
|
+
function sidebarTaskState(record) {
|
|
13
|
+
if (record.phase === 'discarded')
|
|
14
|
+
return 'discarded';
|
|
15
|
+
if (record.phase === 'finalized'
|
|
16
|
+
|| record.phase === 'retained'
|
|
17
|
+
|| record.delivery.state === 'finalized'
|
|
18
|
+
|| record.delivery.state === 'retained'
|
|
19
|
+
|| record.delivery.state === 'delivered')
|
|
20
|
+
return 'finalized';
|
|
21
|
+
if (record.phase === 'recovery_required')
|
|
22
|
+
return 'recovery_required';
|
|
23
|
+
if (record.delivery.state === 'preview_detached')
|
|
24
|
+
return 'preview_detached';
|
|
25
|
+
if (record.delivery.state === 'preview_active')
|
|
26
|
+
return 'preview_active';
|
|
27
|
+
if (record.delivery.state === 'ready_for_review')
|
|
28
|
+
return 'ready_for_review';
|
|
29
|
+
return 'working';
|
|
30
|
+
}
|
|
12
31
|
function readyReview(record) {
|
|
13
32
|
if (record.phase !== 'ready' || record.delivery.state !== 'ready_for_review') {
|
|
14
33
|
throw domainError('operation_not_allowed', '当前 Worktree 尚未处于可验收状态');
|
|
@@ -16,11 +35,43 @@ function readyReview(record) {
|
|
|
16
35
|
return record.delivery.review;
|
|
17
36
|
}
|
|
18
37
|
function previewReview(record) {
|
|
19
|
-
if (record.phase !== 'ready'
|
|
38
|
+
if (record.phase !== 'ready'
|
|
39
|
+
|| (record.delivery.state !== 'preview_active' && record.delivery.state !== 'preview_detached')) {
|
|
20
40
|
throw domainError('preview_not_active', '当前没有等待验收的 Local Preview');
|
|
21
41
|
}
|
|
22
42
|
return record.delivery.review;
|
|
23
43
|
}
|
|
44
|
+
function safeRecoveryRequestId(value) {
|
|
45
|
+
return value.length > 0 && value.length <= 500 && !/[\0\r\n]/u.test(value);
|
|
46
|
+
}
|
|
47
|
+
function safeConflictFile(file) {
|
|
48
|
+
if (!file || file.length > 1000 || /[\0-\x1f\x7f]/u.test(file))
|
|
49
|
+
return false;
|
|
50
|
+
if (/^(?:[A-Za-z]:[\\/]|[\\/])/u.test(file))
|
|
51
|
+
return false;
|
|
52
|
+
return !file.split(/[\\/]/u).some(segment => segment === '' || segment === '.' || segment === '..');
|
|
53
|
+
}
|
|
54
|
+
function applyConflictContinuation(result, reviewId) {
|
|
55
|
+
const checkoutId = result.target.checkout.id;
|
|
56
|
+
const revision = result.target.revision;
|
|
57
|
+
if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/iu.test(result.localHeadOid)) {
|
|
58
|
+
throw domainError('git_error', '冲突恢复缺少有效的 Local HEAD 身份');
|
|
59
|
+
}
|
|
60
|
+
const localHeadOid = result.localHeadOid;
|
|
61
|
+
if (result.conflictingFiles.length > 500 || !result.conflictingFiles.every(safeConflictFile)) {
|
|
62
|
+
throw domainError('git_error', '冲突恢复包含不安全或越界的文件身份');
|
|
63
|
+
}
|
|
64
|
+
const conflictingFiles = [...result.conflictingFiles];
|
|
65
|
+
return {
|
|
66
|
+
kind: 'worktree_apply_conflict',
|
|
67
|
+
requestId: `apply-conflict:${checkoutId}:${reviewId}:${revision}:${localHeadOid}`,
|
|
68
|
+
checkoutId,
|
|
69
|
+
reviewId,
|
|
70
|
+
revision,
|
|
71
|
+
localHeadOid,
|
|
72
|
+
conflictingFiles,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
24
75
|
function preflightFailure(view) {
|
|
25
76
|
if (view === undefined)
|
|
26
77
|
return failure('git_error', '当前 SessionCheckoutModule 不支持验收预检');
|
|
@@ -202,9 +253,10 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
202
253
|
reviewSlotHolder: {
|
|
203
254
|
checkoutId: holder.checkoutId,
|
|
204
255
|
ownerSessionId: holder.ownerSessionId,
|
|
256
|
+
revision: holder.revision,
|
|
205
257
|
state: holder.delivery.state,
|
|
206
258
|
},
|
|
207
|
-
capabilities: { ...target.capabilities, preview: false, finalize: false },
|
|
259
|
+
capabilities: { ...target.capabilities, preview: false, checkpoint: false, finalize: false },
|
|
208
260
|
} : {}),
|
|
209
261
|
};
|
|
210
262
|
}
|
|
@@ -259,6 +311,39 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
259
311
|
};
|
|
260
312
|
}
|
|
261
313
|
return {
|
|
314
|
+
sidebarTopology: () => outcome(async () => {
|
|
315
|
+
const projects = new Map();
|
|
316
|
+
for (const record of Object.values(options.registry.read().managedCheckouts)) {
|
|
317
|
+
const project = projects.get(record.projectId) ?? {
|
|
318
|
+
project: { id: record.projectId, name: record.projectName },
|
|
319
|
+
tasksByOwner: new Map(),
|
|
320
|
+
};
|
|
321
|
+
const task = {
|
|
322
|
+
checkoutId: record.checkoutId,
|
|
323
|
+
ownerSessionId: record.ownerSessionId,
|
|
324
|
+
sourceSessionId: record.sourceSessionId ?? record.ownerSessionId,
|
|
325
|
+
iteration: record.delivery.state === 'working' || record.delivery.state === 'delivered'
|
|
326
|
+
? record.delivery.iteration
|
|
327
|
+
: record.delivery.review.iteration,
|
|
328
|
+
revision: record.revision,
|
|
329
|
+
phase: record.phase,
|
|
330
|
+
state: sidebarTaskState(record),
|
|
331
|
+
};
|
|
332
|
+
const previous = project.tasksByOwner.get(record.ownerSessionId);
|
|
333
|
+
if (!previous
|
|
334
|
+
|| task.iteration > previous.iteration
|
|
335
|
+
|| (task.iteration === previous.iteration && task.revision > previous.revision)) {
|
|
336
|
+
project.tasksByOwner.set(record.ownerSessionId, task);
|
|
337
|
+
}
|
|
338
|
+
projects.set(record.projectId, project);
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
projects: [...projects.values()].map(project => ({
|
|
342
|
+
project: project.project,
|
|
343
|
+
tasks: [...project.tasksByOwner.values()],
|
|
344
|
+
})),
|
|
345
|
+
};
|
|
346
|
+
}),
|
|
262
347
|
current: sessionId => outcome(async () => {
|
|
263
348
|
const target = await callerTarget(sessionId);
|
|
264
349
|
if (target.checkout.kind === 'local')
|
|
@@ -384,10 +469,114 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
384
469
|
throw domainError('git_error', '当前 SessionCheckoutModule 不支持验收预检');
|
|
385
470
|
return { preflight };
|
|
386
471
|
}),
|
|
472
|
+
previewRecoveryPreflight: request => outcome(async () => {
|
|
473
|
+
const record = await authorize(request.sessionId, request.checkoutId);
|
|
474
|
+
if (record.ownerSessionId !== request.sessionId)
|
|
475
|
+
throw domainError('not_owner', '只有 owner Isolated Session 可以检查 Preview 恢复');
|
|
476
|
+
if (record.revision !== request.expectedRevision)
|
|
477
|
+
throw domainError('stale_target', 'Session Target 已变化,请刷新');
|
|
478
|
+
if (record.phase !== 'ready'
|
|
479
|
+
|| record.delivery.state !== 'preview_detached'
|
|
480
|
+
|| record.delivery.review.reviewId !== request.expectedReviewId
|
|
481
|
+
|| record.delivery.preview.previewId !== request.expectedPreviewId)
|
|
482
|
+
throw domainError('stale_target', 'Detached Preview 身份已变化,请刷新');
|
|
483
|
+
const preflight = await options.module.preflightPreviewRecovery?.(request.sessionId, request.expectedRevision, request.expectedReviewId, request.expectedPreviewId);
|
|
484
|
+
if (preflight === undefined)
|
|
485
|
+
throw domainError('git_error', '当前 SessionCheckoutModule 不支持 Preview Recovery 预检');
|
|
486
|
+
return { preflight };
|
|
487
|
+
}),
|
|
488
|
+
preparePreviewRecoveryAnalysis: request => outcome(async () => {
|
|
489
|
+
const record = await authorize(request.sessionId, request.checkoutId);
|
|
490
|
+
if (record.ownerSessionId !== request.sessionId
|
|
491
|
+
|| request.recoveryProof.sessionId !== request.sessionId
|
|
492
|
+
|| request.recoveryProof.checkoutId !== request.checkoutId
|
|
493
|
+
|| request.recoveryProof.revision !== request.expectedRevision
|
|
494
|
+
|| request.recoveryProof.reviewId !== request.expectedReviewId
|
|
495
|
+
|| request.recoveryProof.previewId !== request.expectedPreviewId)
|
|
496
|
+
throw domainError('stale_target', 'Preview Recovery 分析身份不匹配');
|
|
497
|
+
const prepared = await options.module.preparePreviewRecoveryAnalysis(request.sessionId, request.recoveryProof, `preview-recovery-analysis:${randomUUID()}`);
|
|
498
|
+
const response = await mutationResponse(request.sessionId, request.checkoutId);
|
|
499
|
+
return {
|
|
500
|
+
...response,
|
|
501
|
+
recoveryContinuation: {
|
|
502
|
+
kind: prepared.kind,
|
|
503
|
+
requestId: prepared.requestId,
|
|
504
|
+
checkoutId: request.checkoutId,
|
|
505
|
+
reviewId: prepared.reviewId,
|
|
506
|
+
previewId: prepared.previewId,
|
|
507
|
+
revision: prepared.revision,
|
|
508
|
+
generation: prepared.generation,
|
|
509
|
+
},
|
|
510
|
+
};
|
|
511
|
+
}),
|
|
512
|
+
createPreviewRecoveryHandoff: request => outcome(async () => {
|
|
513
|
+
const record = await authorize(request.sessionId, request.checkoutId);
|
|
514
|
+
if (record.ownerSessionId !== request.sessionId
|
|
515
|
+
|| request.recoveryProof.sessionId !== request.sessionId
|
|
516
|
+
|| request.recoveryProof.checkoutId !== request.checkoutId
|
|
517
|
+
|| request.recoveryProof.revision !== request.expectedRevision
|
|
518
|
+
|| request.recoveryProof.reviewId !== request.expectedReviewId
|
|
519
|
+
|| request.recoveryProof.previewId !== request.expectedPreviewId)
|
|
520
|
+
throw domainError('stale_target', 'Preview Recovery handoff 身份不匹配');
|
|
521
|
+
const targetSessionId = createTargetSessionId();
|
|
522
|
+
const launch = await options.module.createPreviewRecoveryHandoff(request.sessionId, request.recoveryProof, targetSessionId, `preview-recovery-handoff:${randomUUID()}`);
|
|
523
|
+
const created = recordOf(options.registry, launch.target.checkout.id);
|
|
524
|
+
const observed = await observe(created);
|
|
525
|
+
if (created.ownerSessionId !== targetSessionId
|
|
526
|
+
|| created.recoveryContinuation?.kind !== 'worktree_preview_recovery_handoff'
|
|
527
|
+
|| created.recoveryContinuation.requestId !== launch.continuation.requestId
|
|
528
|
+
|| observed.managedRoot !== launch.managedRoot)
|
|
529
|
+
throw domainError('checkout_mismatch', 'Recovery handoff Worktree 的 Host 身份校验失败');
|
|
530
|
+
return {
|
|
531
|
+
target: projectDetails(created, targetSessionId, observed.managedRoot, observed.snapshot, observed.dirty, false),
|
|
532
|
+
targetSessionId,
|
|
533
|
+
managedRoot: launch.managedRoot,
|
|
534
|
+
recoveryContinuation: {
|
|
535
|
+
kind: launch.continuation.kind,
|
|
536
|
+
requestId: launch.continuation.requestId,
|
|
537
|
+
checkoutId: created.checkoutId,
|
|
538
|
+
sourceCheckoutId: launch.continuation.sourceCheckoutId,
|
|
539
|
+
reviewId: launch.continuation.reviewId,
|
|
540
|
+
previewId: launch.continuation.previewId,
|
|
541
|
+
revision: launch.continuation.sourceRevision,
|
|
542
|
+
generation: launch.continuation.generation,
|
|
543
|
+
},
|
|
544
|
+
};
|
|
545
|
+
}),
|
|
546
|
+
checkpoint: request => outcome(async () => {
|
|
547
|
+
const record = await authorize(request.sessionId, request.checkoutId);
|
|
548
|
+
if (record.ownerSessionId !== request.sessionId)
|
|
549
|
+
throw domainError('not_owner', '只有 owner Isolated Session 可以保存阶段');
|
|
550
|
+
const commitMessage = request.commitMessage.trim();
|
|
551
|
+
if (!commitMessage || commitMessage.length > 500 || !safeRecoveryRequestId(request.requestId)) {
|
|
552
|
+
throw domainError('invalid_input', 'Checkpoint Commit Message 或 requestId 无效');
|
|
553
|
+
}
|
|
554
|
+
const result = await options.module.operate({
|
|
555
|
+
action: 'checkpoint',
|
|
556
|
+
sessionId: request.sessionId,
|
|
557
|
+
expectedRevision: request.expectedRevision,
|
|
558
|
+
expectedReviewId: request.expectedReviewId,
|
|
559
|
+
expectedGeneration: request.expectedGeneration,
|
|
560
|
+
requestId: request.requestId,
|
|
561
|
+
commitMessage,
|
|
562
|
+
});
|
|
563
|
+
if (result.status === 'error')
|
|
564
|
+
throw domainError(result.code, result.message);
|
|
565
|
+
if (result.status === 'preview_detached') {
|
|
566
|
+
return { ...(await mutationResponse(request.sessionId, request.checkoutId)), changedFiles: [...result.changedFiles] };
|
|
567
|
+
}
|
|
568
|
+
if (result.status !== 'checkpointed')
|
|
569
|
+
throw domainError('operation_not_allowed', 'Checkpoint 返回了非预期状态');
|
|
570
|
+
const response = await mutationResponse(request.sessionId, request.checkoutId);
|
|
571
|
+
if (response.target.state !== 'working' || response.target.revision !== result.target.revision) {
|
|
572
|
+
throw domainError('checkout_mismatch', 'Checkpoint 后 Worktree 状态未收敛到 Working');
|
|
573
|
+
}
|
|
574
|
+
return { ...response, checkpoint: result.checkpoint, changedFiles: [...result.changedFiles] };
|
|
575
|
+
}),
|
|
387
576
|
preview: request => outcome(async () => {
|
|
388
577
|
const record = await authorize(request.sessionId, request.checkoutId);
|
|
389
578
|
if (record.ownerSessionId !== request.sessionId)
|
|
390
|
-
throw domainError('not_owner', '只有 owner Isolated Session
|
|
579
|
+
throw domainError('not_owner', '只有 owner Isolated Session 可以预览修改');
|
|
391
580
|
if (record.revision !== request.expectedRevision)
|
|
392
581
|
throw domainError('stale_target', 'Session Target 已变化,请刷新');
|
|
393
582
|
const review = readyReview(record);
|
|
@@ -398,8 +587,9 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
398
587
|
});
|
|
399
588
|
if (result.status === 'error')
|
|
400
589
|
throw domainError(result.code, result.message);
|
|
401
|
-
if (result.status === 'conflict')
|
|
402
|
-
throw domainError('apply_conflict', 'Local Preview 预检发现内容冲突');
|
|
590
|
+
if (result.status === 'conflict') {
|
|
591
|
+
throw domainError('apply_conflict', 'Local Preview 预检发现内容冲突', applyConflictContinuation(result, review.reviewId));
|
|
592
|
+
}
|
|
403
593
|
if (result.status !== 'previewed')
|
|
404
594
|
throw domainError('operation_not_allowed', 'Preview 返回了非预期状态');
|
|
405
595
|
return {
|
|
@@ -416,11 +606,73 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
416
606
|
const review = readyReview(record);
|
|
417
607
|
if (review.reviewId !== request.expectedReviewId)
|
|
418
608
|
throw domainError('stale_target', 'Review 身份已变化,请刷新');
|
|
419
|
-
const
|
|
609
|
+
const requestedRecovery = request.conflictContinuation;
|
|
610
|
+
if (requestedRecovery && (requestedRecovery.kind !== 'worktree_apply_conflict'
|
|
611
|
+
|| !safeRecoveryRequestId(requestedRecovery.requestId)
|
|
612
|
+
|| requestedRecovery.checkoutId !== request.checkoutId
|
|
613
|
+
|| requestedRecovery.reviewId !== request.expectedReviewId
|
|
614
|
+
|| requestedRecovery.revision !== request.expectedRevision
|
|
615
|
+
|| !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/iu.test(requestedRecovery.localHeadOid)
|
|
616
|
+
|| requestedRecovery.conflictingFiles.length > 500
|
|
617
|
+
|| !requestedRecovery.conflictingFiles.every(safeConflictFile)))
|
|
618
|
+
throw domainError('invalid_input', '冲突恢复请求身份无效');
|
|
619
|
+
const recovery = requestedRecovery ? {
|
|
620
|
+
kind: 'worktree_apply_conflict',
|
|
621
|
+
requestId: `conflict-recovery:${randomUUID()}`,
|
|
622
|
+
reviewId: request.expectedReviewId,
|
|
623
|
+
readyRevision: request.expectedRevision,
|
|
624
|
+
localHeadOid: requestedRecovery.localHeadOid,
|
|
625
|
+
conflictingFiles: [...requestedRecovery.conflictingFiles],
|
|
626
|
+
} : undefined;
|
|
627
|
+
const target = recovery
|
|
628
|
+
? await options.module.resumeRevision(request.sessionId, request.expectedRevision, request.expectedReviewId, recovery)
|
|
629
|
+
: await options.module.resumeRevision(request.sessionId, request.expectedRevision, request.expectedReviewId);
|
|
420
630
|
if (target.checkout.id !== request.checkoutId || target.delivery?.state !== 'working') {
|
|
421
631
|
throw domainError('checkout_mismatch', '恢复编辑后 Worktree 身份或状态不一致');
|
|
422
632
|
}
|
|
423
|
-
|
|
633
|
+
const response = await mutationResponse(request.sessionId, request.checkoutId);
|
|
634
|
+
if (!requestedRecovery)
|
|
635
|
+
return response;
|
|
636
|
+
const current = recordOf(options.registry, request.checkoutId).recoveryContinuation;
|
|
637
|
+
if (!current
|
|
638
|
+
|| current.kind !== 'worktree_apply_conflict'
|
|
639
|
+
|| current.requestId !== recovery.requestId
|
|
640
|
+
|| current.workingRevision !== response.target.revision)
|
|
641
|
+
throw domainError('checkout_mismatch', 'Host 未能持久化精确冲突恢复凭证');
|
|
642
|
+
return {
|
|
643
|
+
...response,
|
|
644
|
+
recoveryContinuation: {
|
|
645
|
+
kind: current.kind,
|
|
646
|
+
requestId: current.requestId,
|
|
647
|
+
checkoutId: request.checkoutId,
|
|
648
|
+
reviewId: current.reviewId,
|
|
649
|
+
revision: current.workingRevision,
|
|
650
|
+
localHeadOid: current.localHeadOid,
|
|
651
|
+
conflictingFiles: [...current.conflictingFiles],
|
|
652
|
+
},
|
|
653
|
+
};
|
|
654
|
+
}),
|
|
655
|
+
prepareReviewRegeneration: request => outcome(async () => {
|
|
656
|
+
const record = await authorize(request.sessionId, request.checkoutId);
|
|
657
|
+
if (record.ownerSessionId !== request.sessionId)
|
|
658
|
+
throw domainError('not_owner', '只有 owner Isolated Session 可以请求重新生成验收结果');
|
|
659
|
+
if (record.revision !== request.expectedRevision)
|
|
660
|
+
throw domainError('stale_target', 'Session Target 已变化,请刷新');
|
|
661
|
+
const review = readyReview(record);
|
|
662
|
+
if (review.reviewId !== request.expectedReviewId)
|
|
663
|
+
throw domainError('stale_target', 'Review 身份已变化,请刷新');
|
|
664
|
+
const prepared = await options.module.prepareReviewRegeneration(request.sessionId, request.expectedRevision, request.expectedReviewId, `review-regeneration:${randomUUID()}`);
|
|
665
|
+
const response = await mutationResponse(request.sessionId, request.checkoutId);
|
|
666
|
+
return {
|
|
667
|
+
...response,
|
|
668
|
+
recoveryContinuation: {
|
|
669
|
+
kind: prepared.kind,
|
|
670
|
+
requestId: prepared.requestId,
|
|
671
|
+
checkoutId: request.checkoutId,
|
|
672
|
+
reviewId: prepared.reviewId,
|
|
673
|
+
revision: prepared.revision,
|
|
674
|
+
},
|
|
675
|
+
};
|
|
424
676
|
}),
|
|
425
677
|
rollbackPreview: request => outcome(async () => {
|
|
426
678
|
const record = await authorize(request.sessionId, request.checkoutId);
|
|
@@ -431,6 +683,7 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
431
683
|
sessionId: request.sessionId,
|
|
432
684
|
expectedRevision: request.expectedRevision,
|
|
433
685
|
...(request.resumeRevision === undefined ? {} : { resumeRevision: request.resumeRevision }),
|
|
686
|
+
...(request.recoveryProof === undefined ? {} : { recoveryProof: request.recoveryProof }),
|
|
434
687
|
});
|
|
435
688
|
if (result.status === 'error')
|
|
436
689
|
throw domainError(result.code, result.message);
|
|
@@ -496,8 +749,9 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
496
749
|
});
|
|
497
750
|
if (result.status === 'error')
|
|
498
751
|
return failure(result.code, result.message);
|
|
499
|
-
if (result.status === 'conflict')
|
|
500
|
-
return failure('apply_conflict', 'Local 应用发生冲突');
|
|
752
|
+
if (result.status === 'conflict') {
|
|
753
|
+
return failure('apply_conflict', 'Local 应用发生冲突', undefined, applyConflictContinuation(result, review.reviewId));
|
|
754
|
+
}
|
|
501
755
|
if (result.status !== 'finished')
|
|
502
756
|
return failure('operation_not_allowed', 'Finalize 返回了非预期状态');
|
|
503
757
|
return {
|
|
@@ -532,6 +786,7 @@ export function createWorktreeConsoleControlPlane(options) {
|
|
|
532
786
|
expectedRevision: request.expectedRevision,
|
|
533
787
|
commitMessage,
|
|
534
788
|
retention: request.retention,
|
|
789
|
+
...(request.recoveryProof === undefined ? {} : { recoveryProof: request.recoveryProof }),
|
|
535
790
|
});
|
|
536
791
|
if (result.status === 'error')
|
|
537
792
|
return failure(result.code, result.message);
|