dsh-git-worktree 0.4.0 → 0.6.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.
Files changed (71) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.en.md +33 -13
  3. package/README.md +34 -13
  4. package/lib/adapters/git.js +8 -0
  5. package/lib/adapters/git.js.map +1 -1
  6. package/lib/adapters/registry.js +100 -1
  7. package/lib/adapters/registry.js.map +1 -1
  8. package/lib/checkpoint.d.ts +3 -0
  9. package/lib/checkpoint.js +29 -0
  10. package/lib/checkpoint.js.map +1 -0
  11. package/lib/client/WorktreeReviewRow.js +24 -4
  12. package/lib/client/WorktreeReviewRow.js.map +1 -1
  13. package/lib/client/actions.d.ts +25 -1
  14. package/lib/client/actions.js.map +1 -1
  15. package/lib/client/console-remote/adapter.js +10 -3
  16. package/lib/client/console-remote/adapter.js.map +1 -1
  17. package/lib/client/review-console/PreflightStatus.d.ts +3 -2
  18. package/lib/client/review-console/PreflightStatus.js +3 -3
  19. package/lib/client/review-console/PreflightStatus.js.map +1 -1
  20. package/lib/client/review-console/ReviewActions.js +560 -23
  21. package/lib/client/review-console/ReviewActions.js.map +1 -1
  22. package/lib/client/review-console/WorktreeReviewPanel.js +18 -3
  23. package/lib/client/review-console/WorktreeReviewPanel.js.map +1 -1
  24. package/lib/client/review-console/preflight-cache.d.ts +2 -0
  25. package/lib/client/review-console/preflight-cache.js +18 -5
  26. package/lib/client/review-console/preflight-cache.js.map +1 -1
  27. package/lib/client/review-console/preview-recovery-cache.d.ts +20 -0
  28. package/lib/client/review-console/preview-recovery-cache.js +103 -0
  29. package/lib/client/review-console/preview-recovery-cache.js.map +1 -0
  30. package/lib/client/review-console/recovery-continuation.d.ts +53 -0
  31. package/lib/client/review-console/recovery-continuation.js +483 -0
  32. package/lib/client/review-console/recovery-continuation.js.map +1 -0
  33. package/lib/client/review-console/review-console.styles.js +9 -1
  34. package/lib/client/review-console/review-console.styles.js.map +1 -1
  35. package/lib/client/target-console/WorktreeConsoleView.js +1 -1
  36. package/lib/client/target-console/WorktreeConsoleView.js.map +1 -1
  37. package/lib/client/target-console/WorktreeReviewStatus.js +13 -0
  38. package/lib/client/target-console/WorktreeReviewStatus.js.map +1 -1
  39. package/lib/client.js +1628 -83
  40. package/lib/console-contract.d.ts +105 -1
  41. package/lib/console-contract.js.map +1 -1
  42. package/lib/console-host/control-plane.d.ts +6 -1
  43. package/lib/console-host/control-plane.js +211 -8
  44. package/lib/console-host/control-plane.js.map +1 -1
  45. package/lib/console-host/errors.d.ts +2 -2
  46. package/lib/console-host/errors.js +21 -5
  47. package/lib/console-host/errors.js.map +1 -1
  48. package/lib/console-host/projection.js +60 -1
  49. package/lib/console-host/projection.js.map +1 -1
  50. package/lib/console-host/service.d.ts +10 -5
  51. package/lib/console-host/service.js +48 -6
  52. package/lib/console-host/service.js.map +1 -1
  53. package/lib/console-remote/descriptors.js +37 -1
  54. package/lib/console-remote/descriptors.js.map +1 -1
  55. package/lib/console-remote/remote.d.ts +10 -5
  56. package/lib/console-remote/remote.js.map +1 -1
  57. package/lib/console-remote/schemas.d.ts +103 -1
  58. package/lib/console-remote/schemas.js +147 -5
  59. package/lib/console-remote/schemas.js.map +1 -1
  60. package/lib/index.d.ts +13 -2
  61. package/lib/index.js.map +1 -1
  62. package/lib/ports.d.ts +76 -1
  63. package/lib/session-checkout-apply.d.ts +76 -0
  64. package/lib/session-checkout-apply.js +608 -143
  65. package/lib/session-checkout-apply.js.map +1 -1
  66. package/lib/session-checkout-module.js +752 -121
  67. package/lib/session-checkout-module.js.map +1 -1
  68. package/lib/types.d.ts +93 -2
  69. package/lib/types.js.map +1 -1
  70. package/package.json +13 -13
  71. package/scripts/check-publish.mjs +1 -1
@@ -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;
@@ -145,17 +153,48 @@ export interface WorktreeConsolePreviewRequest {
145
153
  expectedRevision: number;
146
154
  expectedReviewId: string;
147
155
  }
156
+ export interface WorktreeConsoleCheckpointRequest {
157
+ sessionId: string;
158
+ checkoutId: string;
159
+ expectedRevision: number;
160
+ expectedReviewId: string;
161
+ expectedGeneration: string;
162
+ requestId: string;
163
+ commitMessage: string;
164
+ }
148
165
  export interface WorktreeConsoleResumeRevisionRequest {
149
166
  sessionId: string;
150
167
  checkoutId: string;
151
168
  expectedRevision: number;
152
169
  expectedReviewId: string;
170
+ /** Present only for the explicit conflict-recovery action; Host re-runs conflict preflight under CAS. */
171
+ conflictContinuation?: WorktreeApplyConflictContinuation;
172
+ }
173
+ export interface WorktreeConsolePreviewRecoveryPreflightRequest {
174
+ sessionId: string;
175
+ checkoutId: string;
176
+ expectedRevision: number;
177
+ expectedReviewId: string;
178
+ expectedPreviewId: string;
179
+ }
180
+ export interface WorktreeConsolePreviewRecoveryPreflightResponse {
181
+ preflight: WorktreePreviewRecoveryPreflightView;
182
+ }
183
+ export interface WorktreeConsolePreparePreviewRecoveryAnalysisRequest extends WorktreeConsolePreviewRecoveryPreflightRequest {
184
+ recoveryProof: WorktreePreviewRecoveryProof;
185
+ }
186
+ export interface WorktreeConsoleCreatePreviewRecoveryHandoffRequest extends WorktreeConsolePreviewRecoveryPreflightRequest {
187
+ recoveryProof: WorktreePreviewRecoveryProof;
188
+ }
189
+ export interface WorktreeConsoleCreatePreviewRecoveryHandoffResponse extends WorktreeConsoleCreateResponse {
190
+ recoveryContinuation: WorktreePreviewRecoveryHandoffProof;
153
191
  }
154
192
  export interface WorktreeConsoleRollbackPreviewRequest {
155
193
  sessionId: string;
156
194
  checkoutId: string;
157
195
  expectedRevision: number;
158
196
  resumeRevision?: boolean;
197
+ recoveryProof?: WorktreePreviewRecoveryProof;
159
198
  }
160
199
  export interface WorktreeConsoleFinalizeRequest {
161
200
  sessionId: string;
@@ -173,6 +212,7 @@ export interface WorktreeConsoleFinalizePreviewRequest {
173
212
  expectedReviewId: string;
174
213
  commitMessage: string;
175
214
  retention: WorktreeRetentionMode;
215
+ recoveryProof?: WorktreePreviewRecoveryProof;
176
216
  }
177
217
  export interface WorktreeConsoleSetRetentionRequest {
178
218
  sessionId: string;
@@ -194,6 +234,14 @@ export interface WorktreeConsoleMutationResponse {
194
234
  target: WorktreeConsoleTargetSummary;
195
235
  changedFiles?: string[];
196
236
  commitOid?: string | null;
237
+ checkpoint?: WorktreeCheckpointView;
238
+ recoveryContinuation?: WorktreeRecoveryProof;
239
+ }
240
+ export interface WorktreeConsolePrepareRegenerationRequest {
241
+ sessionId: string;
242
+ checkoutId: string;
243
+ expectedRevision: number;
244
+ expectedReviewId: string;
197
245
  }
198
246
  export type WorktreeConsoleFileStatus = 'added' | 'modified' | 'deleted' | 'renamed' | 'binary';
199
247
  export interface WorktreeConsoleDiffFile {
@@ -220,10 +268,61 @@ export declare const WORKTREE_CONSOLE_ERROR_CODES: readonly ["session_not_found"
220
268
  export type WorktreeConsoleErrorCode = SessionCheckoutErrorCode | 'transport_unavailable' | 'malformed_response';
221
269
  export type WorktreeConsoleErrorCategory = 'permission' | 'stale' | 'confirmation' | 'recovery' | 'conflict' | 'unavailable' | 'invalid' | 'internal';
222
270
  export type WorktreeConsoleRecovery = 'none' | 'refresh' | 'confirm_dirty' | 'open_recovery' | 'retry';
271
+ export interface WorktreeApplyConflictContinuation {
272
+ kind: 'worktree_apply_conflict';
273
+ /** Stable identity for Client single-flight and retry within this renderer lifetime. */
274
+ requestId: string;
275
+ checkoutId: string;
276
+ /** Review that remains Ready until the user explicitly resumes it for conflict resolution. */
277
+ reviewId: string;
278
+ /** Authoritative revision after the failed real-time write attempt returned to Ready. */
279
+ revision: number;
280
+ localHeadOid: string;
281
+ conflictingFiles: string[];
282
+ }
283
+ export interface WorktreeApplyConflictRecoveryProof {
284
+ kind: 'worktree_apply_conflict';
285
+ requestId: string;
286
+ checkoutId: string;
287
+ reviewId: string;
288
+ /** Exact Working revision produced by the Host conflict-resume CAS. */
289
+ revision: number;
290
+ localHeadOid: string;
291
+ conflictingFiles: string[];
292
+ }
293
+ export interface WorktreeReviewRegenerationProof {
294
+ kind: 'worktree_review_regeneration';
295
+ requestId: string;
296
+ checkoutId: string;
297
+ reviewId: string;
298
+ revision: number;
299
+ }
300
+ export interface WorktreePreviewRecoveryAnalysisProof {
301
+ kind: 'worktree_preview_recovery_analysis';
302
+ requestId: string;
303
+ checkoutId: string;
304
+ reviewId: string;
305
+ previewId: string;
306
+ revision: number;
307
+ generation: string;
308
+ }
309
+ export interface WorktreePreviewRecoveryHandoffProof {
310
+ kind: 'worktree_preview_recovery_handoff';
311
+ requestId: string;
312
+ checkoutId: string;
313
+ sourceCheckoutId: string;
314
+ reviewId: string;
315
+ previewId: string;
316
+ revision: number;
317
+ generation: string;
318
+ }
319
+ export type WorktreeRecoveryProof = WorktreeApplyConflictRecoveryProof | WorktreeReviewRegenerationProof | WorktreePreviewRecoveryAnalysisProof | WorktreePreviewRecoveryHandoffProof;
320
+ export type WorktreeConsoleContinuation = WorktreeApplyConflictContinuation;
223
321
  export interface WorktreeConsoleError {
224
322
  code: WorktreeConsoleErrorCode;
225
323
  message: string;
226
324
  details?: Record<string, string | number | boolean | null>;
325
+ continuation?: WorktreeConsoleContinuation;
227
326
  }
228
327
  export type WorktreeConsoleOutcome<T> = {
229
328
  ok: true;
@@ -246,8 +345,13 @@ export interface WorktreeConsoleAdapter {
246
345
  inspect(request: WorktreeConsoleInspectRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleInspectResponse>>;
247
346
  reviewDiff(request: WorktreeConsoleReviewDiffRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleReviewDiffResponse>>;
248
347
  preflight(request: WorktreeConsolePreflightRequest): Promise<WorktreeConsoleOutcome<WorktreeConsolePreflightResponse>>;
348
+ previewRecoveryPreflight(request: WorktreeConsolePreviewRecoveryPreflightRequest): Promise<WorktreeConsoleOutcome<WorktreeConsolePreviewRecoveryPreflightResponse>>;
349
+ preparePreviewRecoveryAnalysis(request: WorktreeConsolePreparePreviewRecoveryAnalysisRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
350
+ createPreviewRecoveryHandoff(request: WorktreeConsoleCreatePreviewRecoveryHandoffRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleCreatePreviewRecoveryHandoffResponse>>;
249
351
  preview(request: WorktreeConsolePreviewRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
352
+ checkpoint(request: WorktreeConsoleCheckpointRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
250
353
  resumeRevision(request: WorktreeConsoleResumeRevisionRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
354
+ prepareReviewRegeneration(request: WorktreeConsolePrepareRegenerationRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
251
355
  rollbackPreview(request: WorktreeConsoleRollbackPreviewRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
252
356
  discard(request: WorktreeConsoleDiscardRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
253
357
  finalize(request: WorktreeConsoleFinalizeRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
@@ -1 +1 @@
1
- {"version":3,"file":"console-contract.js","sourceRoot":"","sources":["../src/console-contract.ts"],"names":[],"mappings":"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"}
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 } 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';
@@ -18,8 +18,13 @@ export interface WorktreeConsoleControlPlane {
18
18
  inspect(sessionId: string, checkoutId: string): Promise<WorktreeConsoleOutcome<WorktreeConsoleInspectResponse>>;
19
19
  reviewDiff(request: WorktreeConsoleReviewDiffRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleReviewDiffResponse>>;
20
20
  preflight(request: WorktreeConsolePreflightRequest): Promise<WorktreeConsoleOutcome<WorktreeConsolePreflightResponse>>;
21
+ previewRecoveryPreflight(request: WorktreeConsolePreviewRecoveryPreflightRequest): Promise<WorktreeConsoleOutcome<WorktreeConsolePreviewRecoveryPreflightResponse>>;
22
+ preparePreviewRecoveryAnalysis(request: WorktreeConsolePreparePreviewRecoveryAnalysisRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
23
+ createPreviewRecoveryHandoff(request: WorktreeConsoleCreatePreviewRecoveryHandoffRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleCreatePreviewRecoveryHandoffResponse>>;
21
24
  preview(request: WorktreeConsolePreviewRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
25
+ checkpoint(request: WorktreeConsoleCheckpointRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
22
26
  resumeRevision(request: WorktreeConsoleResumeRevisionRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
27
+ prepareReviewRegeneration(request: WorktreeConsolePrepareRegenerationRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
23
28
  rollbackPreview(request: WorktreeConsoleRollbackPreviewRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
24
29
  discard(request: WorktreeConsoleDiscardRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
25
30
  finalize(request: WorktreeConsoleFinalizeRequest): Promise<WorktreeConsoleOutcome<WorktreeConsoleMutationResponse>>;
@@ -16,11 +16,43 @@ function readyReview(record) {
16
16
  return record.delivery.review;
17
17
  }
18
18
  function previewReview(record) {
19
- if (record.phase !== 'ready' || record.delivery.state !== 'preview_active') {
19
+ if (record.phase !== 'ready'
20
+ || (record.delivery.state !== 'preview_active' && record.delivery.state !== 'preview_detached')) {
20
21
  throw domainError('preview_not_active', '当前没有等待验收的 Local Preview');
21
22
  }
22
23
  return record.delivery.review;
23
24
  }
25
+ function safeRecoveryRequestId(value) {
26
+ return value.length > 0 && value.length <= 500 && !/[\0\r\n]/u.test(value);
27
+ }
28
+ function safeConflictFile(file) {
29
+ if (!file || file.length > 1000 || /[\0-\x1f\x7f]/u.test(file))
30
+ return false;
31
+ if (/^(?:[A-Za-z]:[\\/]|[\\/])/u.test(file))
32
+ return false;
33
+ return !file.split(/[\\/]/u).some(segment => segment === '' || segment === '.' || segment === '..');
34
+ }
35
+ function applyConflictContinuation(result, reviewId) {
36
+ const checkoutId = result.target.checkout.id;
37
+ const revision = result.target.revision;
38
+ if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/iu.test(result.localHeadOid)) {
39
+ throw domainError('git_error', '冲突恢复缺少有效的 Local HEAD 身份');
40
+ }
41
+ const localHeadOid = result.localHeadOid;
42
+ if (result.conflictingFiles.length > 500 || !result.conflictingFiles.every(safeConflictFile)) {
43
+ throw domainError('git_error', '冲突恢复包含不安全或越界的文件身份');
44
+ }
45
+ const conflictingFiles = [...result.conflictingFiles];
46
+ return {
47
+ kind: 'worktree_apply_conflict',
48
+ requestId: `apply-conflict:${checkoutId}:${reviewId}:${revision}:${localHeadOid}`,
49
+ checkoutId,
50
+ reviewId,
51
+ revision,
52
+ localHeadOid,
53
+ conflictingFiles,
54
+ };
55
+ }
24
56
  function preflightFailure(view) {
25
57
  if (view === undefined)
26
58
  return failure('git_error', '当前 SessionCheckoutModule 不支持验收预检');
@@ -202,9 +234,10 @@ export function createWorktreeConsoleControlPlane(options) {
202
234
  reviewSlotHolder: {
203
235
  checkoutId: holder.checkoutId,
204
236
  ownerSessionId: holder.ownerSessionId,
237
+ revision: holder.revision,
205
238
  state: holder.delivery.state,
206
239
  },
207
- capabilities: { ...target.capabilities, preview: false, finalize: false },
240
+ capabilities: { ...target.capabilities, preview: false, checkpoint: false, finalize: false },
208
241
  } : {}),
209
242
  };
210
243
  }
@@ -384,6 +417,110 @@ export function createWorktreeConsoleControlPlane(options) {
384
417
  throw domainError('git_error', '当前 SessionCheckoutModule 不支持验收预检');
385
418
  return { preflight };
386
419
  }),
420
+ previewRecoveryPreflight: request => outcome(async () => {
421
+ const record = await authorize(request.sessionId, request.checkoutId);
422
+ if (record.ownerSessionId !== request.sessionId)
423
+ throw domainError('not_owner', '只有 owner Isolated Session 可以检查 Preview 恢复');
424
+ if (record.revision !== request.expectedRevision)
425
+ throw domainError('stale_target', 'Session Target 已变化,请刷新');
426
+ if (record.phase !== 'ready'
427
+ || record.delivery.state !== 'preview_detached'
428
+ || record.delivery.review.reviewId !== request.expectedReviewId
429
+ || record.delivery.preview.previewId !== request.expectedPreviewId)
430
+ throw domainError('stale_target', 'Detached Preview 身份已变化,请刷新');
431
+ const preflight = await options.module.preflightPreviewRecovery?.(request.sessionId, request.expectedRevision, request.expectedReviewId, request.expectedPreviewId);
432
+ if (preflight === undefined)
433
+ throw domainError('git_error', '当前 SessionCheckoutModule 不支持 Preview Recovery 预检');
434
+ return { preflight };
435
+ }),
436
+ preparePreviewRecoveryAnalysis: request => outcome(async () => {
437
+ const record = await authorize(request.sessionId, request.checkoutId);
438
+ if (record.ownerSessionId !== request.sessionId
439
+ || request.recoveryProof.sessionId !== request.sessionId
440
+ || request.recoveryProof.checkoutId !== request.checkoutId
441
+ || request.recoveryProof.revision !== request.expectedRevision
442
+ || request.recoveryProof.reviewId !== request.expectedReviewId
443
+ || request.recoveryProof.previewId !== request.expectedPreviewId)
444
+ throw domainError('stale_target', 'Preview Recovery 分析身份不匹配');
445
+ const prepared = await options.module.preparePreviewRecoveryAnalysis(request.sessionId, request.recoveryProof, `preview-recovery-analysis:${randomUUID()}`);
446
+ const response = await mutationResponse(request.sessionId, request.checkoutId);
447
+ return {
448
+ ...response,
449
+ recoveryContinuation: {
450
+ kind: prepared.kind,
451
+ requestId: prepared.requestId,
452
+ checkoutId: request.checkoutId,
453
+ reviewId: prepared.reviewId,
454
+ previewId: prepared.previewId,
455
+ revision: prepared.revision,
456
+ generation: prepared.generation,
457
+ },
458
+ };
459
+ }),
460
+ createPreviewRecoveryHandoff: request => outcome(async () => {
461
+ const record = await authorize(request.sessionId, request.checkoutId);
462
+ if (record.ownerSessionId !== request.sessionId
463
+ || request.recoveryProof.sessionId !== request.sessionId
464
+ || request.recoveryProof.checkoutId !== request.checkoutId
465
+ || request.recoveryProof.revision !== request.expectedRevision
466
+ || request.recoveryProof.reviewId !== request.expectedReviewId
467
+ || request.recoveryProof.previewId !== request.expectedPreviewId)
468
+ throw domainError('stale_target', 'Preview Recovery handoff 身份不匹配');
469
+ const targetSessionId = createTargetSessionId();
470
+ const launch = await options.module.createPreviewRecoveryHandoff(request.sessionId, request.recoveryProof, targetSessionId, `preview-recovery-handoff:${randomUUID()}`);
471
+ const created = recordOf(options.registry, launch.target.checkout.id);
472
+ const observed = await observe(created);
473
+ if (created.ownerSessionId !== targetSessionId
474
+ || created.recoveryContinuation?.kind !== 'worktree_preview_recovery_handoff'
475
+ || created.recoveryContinuation.requestId !== launch.continuation.requestId
476
+ || observed.managedRoot !== launch.managedRoot)
477
+ throw domainError('checkout_mismatch', 'Recovery handoff Worktree 的 Host 身份校验失败');
478
+ return {
479
+ target: projectDetails(created, targetSessionId, observed.managedRoot, observed.snapshot, observed.dirty, false),
480
+ targetSessionId,
481
+ managedRoot: launch.managedRoot,
482
+ recoveryContinuation: {
483
+ kind: launch.continuation.kind,
484
+ requestId: launch.continuation.requestId,
485
+ checkoutId: created.checkoutId,
486
+ sourceCheckoutId: launch.continuation.sourceCheckoutId,
487
+ reviewId: launch.continuation.reviewId,
488
+ previewId: launch.continuation.previewId,
489
+ revision: launch.continuation.sourceRevision,
490
+ generation: launch.continuation.generation,
491
+ },
492
+ };
493
+ }),
494
+ checkpoint: request => outcome(async () => {
495
+ const record = await authorize(request.sessionId, request.checkoutId);
496
+ if (record.ownerSessionId !== request.sessionId)
497
+ throw domainError('not_owner', '只有 owner Isolated Session 可以保存阶段');
498
+ const commitMessage = request.commitMessage.trim();
499
+ if (!commitMessage || commitMessage.length > 500 || !safeRecoveryRequestId(request.requestId)) {
500
+ throw domainError('invalid_input', 'Checkpoint Commit Message 或 requestId 无效');
501
+ }
502
+ const result = await options.module.operate({
503
+ action: 'checkpoint',
504
+ sessionId: request.sessionId,
505
+ expectedRevision: request.expectedRevision,
506
+ expectedReviewId: request.expectedReviewId,
507
+ expectedGeneration: request.expectedGeneration,
508
+ requestId: request.requestId,
509
+ commitMessage,
510
+ });
511
+ if (result.status === 'error')
512
+ throw domainError(result.code, result.message);
513
+ if (result.status === 'preview_detached') {
514
+ return { ...(await mutationResponse(request.sessionId, request.checkoutId)), changedFiles: [...result.changedFiles] };
515
+ }
516
+ if (result.status !== 'checkpointed')
517
+ throw domainError('operation_not_allowed', 'Checkpoint 返回了非预期状态');
518
+ const response = await mutationResponse(request.sessionId, request.checkoutId);
519
+ if (response.target.state !== 'working' || response.target.revision !== result.target.revision) {
520
+ throw domainError('checkout_mismatch', 'Checkpoint 后 Worktree 状态未收敛到 Working');
521
+ }
522
+ return { ...response, checkpoint: result.checkpoint, changedFiles: [...result.changedFiles] };
523
+ }),
387
524
  preview: request => outcome(async () => {
388
525
  const record = await authorize(request.sessionId, request.checkoutId);
389
526
  if (record.ownerSessionId !== request.sessionId)
@@ -398,8 +535,9 @@ export function createWorktreeConsoleControlPlane(options) {
398
535
  });
399
536
  if (result.status === 'error')
400
537
  throw domainError(result.code, result.message);
401
- if (result.status === 'conflict')
402
- throw domainError('apply_conflict', 'Local Preview 预检发现内容冲突');
538
+ if (result.status === 'conflict') {
539
+ throw domainError('apply_conflict', 'Local Preview 预检发现内容冲突', applyConflictContinuation(result, review.reviewId));
540
+ }
403
541
  if (result.status !== 'previewed')
404
542
  throw domainError('operation_not_allowed', 'Preview 返回了非预期状态');
405
543
  return {
@@ -416,11 +554,73 @@ export function createWorktreeConsoleControlPlane(options) {
416
554
  const review = readyReview(record);
417
555
  if (review.reviewId !== request.expectedReviewId)
418
556
  throw domainError('stale_target', 'Review 身份已变化,请刷新');
419
- const target = await options.module.resumeRevision(request.sessionId, request.expectedRevision, request.expectedReviewId);
557
+ const requestedRecovery = request.conflictContinuation;
558
+ if (requestedRecovery && (requestedRecovery.kind !== 'worktree_apply_conflict'
559
+ || !safeRecoveryRequestId(requestedRecovery.requestId)
560
+ || requestedRecovery.checkoutId !== request.checkoutId
561
+ || requestedRecovery.reviewId !== request.expectedReviewId
562
+ || requestedRecovery.revision !== request.expectedRevision
563
+ || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/iu.test(requestedRecovery.localHeadOid)
564
+ || requestedRecovery.conflictingFiles.length > 500
565
+ || !requestedRecovery.conflictingFiles.every(safeConflictFile)))
566
+ throw domainError('invalid_input', '冲突恢复请求身份无效');
567
+ const recovery = requestedRecovery ? {
568
+ kind: 'worktree_apply_conflict',
569
+ requestId: `conflict-recovery:${randomUUID()}`,
570
+ reviewId: request.expectedReviewId,
571
+ readyRevision: request.expectedRevision,
572
+ localHeadOid: requestedRecovery.localHeadOid,
573
+ conflictingFiles: [...requestedRecovery.conflictingFiles],
574
+ } : undefined;
575
+ const target = recovery
576
+ ? await options.module.resumeRevision(request.sessionId, request.expectedRevision, request.expectedReviewId, recovery)
577
+ : await options.module.resumeRevision(request.sessionId, request.expectedRevision, request.expectedReviewId);
420
578
  if (target.checkout.id !== request.checkoutId || target.delivery?.state !== 'working') {
421
579
  throw domainError('checkout_mismatch', '恢复编辑后 Worktree 身份或状态不一致');
422
580
  }
423
- return mutationResponse(request.sessionId, request.checkoutId);
581
+ const response = await mutationResponse(request.sessionId, request.checkoutId);
582
+ if (!requestedRecovery)
583
+ return response;
584
+ const current = recordOf(options.registry, request.checkoutId).recoveryContinuation;
585
+ if (!current
586
+ || current.kind !== 'worktree_apply_conflict'
587
+ || current.requestId !== recovery.requestId
588
+ || current.workingRevision !== response.target.revision)
589
+ throw domainError('checkout_mismatch', 'Host 未能持久化精确冲突恢复凭证');
590
+ return {
591
+ ...response,
592
+ recoveryContinuation: {
593
+ kind: current.kind,
594
+ requestId: current.requestId,
595
+ checkoutId: request.checkoutId,
596
+ reviewId: current.reviewId,
597
+ revision: current.workingRevision,
598
+ localHeadOid: current.localHeadOid,
599
+ conflictingFiles: [...current.conflictingFiles],
600
+ },
601
+ };
602
+ }),
603
+ prepareReviewRegeneration: request => outcome(async () => {
604
+ const record = await authorize(request.sessionId, request.checkoutId);
605
+ if (record.ownerSessionId !== request.sessionId)
606
+ throw domainError('not_owner', '只有 owner Isolated Session 可以请求重新生成验收结果');
607
+ if (record.revision !== request.expectedRevision)
608
+ throw domainError('stale_target', 'Session Target 已变化,请刷新');
609
+ const review = readyReview(record);
610
+ if (review.reviewId !== request.expectedReviewId)
611
+ throw domainError('stale_target', 'Review 身份已变化,请刷新');
612
+ const prepared = await options.module.prepareReviewRegeneration(request.sessionId, request.expectedRevision, request.expectedReviewId, `review-regeneration:${randomUUID()}`);
613
+ const response = await mutationResponse(request.sessionId, request.checkoutId);
614
+ return {
615
+ ...response,
616
+ recoveryContinuation: {
617
+ kind: prepared.kind,
618
+ requestId: prepared.requestId,
619
+ checkoutId: request.checkoutId,
620
+ reviewId: prepared.reviewId,
621
+ revision: prepared.revision,
622
+ },
623
+ };
424
624
  }),
425
625
  rollbackPreview: request => outcome(async () => {
426
626
  const record = await authorize(request.sessionId, request.checkoutId);
@@ -431,6 +631,7 @@ export function createWorktreeConsoleControlPlane(options) {
431
631
  sessionId: request.sessionId,
432
632
  expectedRevision: request.expectedRevision,
433
633
  ...(request.resumeRevision === undefined ? {} : { resumeRevision: request.resumeRevision }),
634
+ ...(request.recoveryProof === undefined ? {} : { recoveryProof: request.recoveryProof }),
434
635
  });
435
636
  if (result.status === 'error')
436
637
  throw domainError(result.code, result.message);
@@ -496,8 +697,9 @@ export function createWorktreeConsoleControlPlane(options) {
496
697
  });
497
698
  if (result.status === 'error')
498
699
  return failure(result.code, result.message);
499
- if (result.status === 'conflict')
500
- return failure('apply_conflict', 'Local 应用发生冲突');
700
+ if (result.status === 'conflict') {
701
+ return failure('apply_conflict', 'Local 应用发生冲突', undefined, applyConflictContinuation(result, review.reviewId));
702
+ }
501
703
  if (result.status !== 'finished')
502
704
  return failure('operation_not_allowed', 'Finalize 返回了非预期状态');
503
705
  return {
@@ -532,6 +734,7 @@ export function createWorktreeConsoleControlPlane(options) {
532
734
  expectedRevision: request.expectedRevision,
533
735
  commitMessage,
534
736
  retention: request.retention,
737
+ ...(request.recoveryProof === undefined ? {} : { recoveryProof: request.recoveryProof }),
535
738
  });
536
739
  if (result.status === 'error')
537
740
  return failure(result.code, result.message);