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.
Files changed (95) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/NOTICE +14 -0
  3. package/README.en.md +74 -266
  4. package/README.md +78 -286
  5. package/cordis.patch.yml +7 -3
  6. package/lib/adapters/git.js +8 -0
  7. package/lib/adapters/git.js.map +1 -1
  8. package/lib/adapters/registry.js +100 -1
  9. package/lib/adapters/registry.js.map +1 -1
  10. package/lib/checkpoint.d.ts +3 -0
  11. package/lib/checkpoint.js +29 -0
  12. package/lib/checkpoint.js.map +1 -0
  13. package/lib/client/WorktreeReviewRow.d.ts +1 -1
  14. package/lib/client/WorktreeReviewRow.js +32 -4
  15. package/lib/client/WorktreeReviewRow.js.map +1 -1
  16. package/lib/client/actions.d.ts +25 -1
  17. package/lib/client/actions.js.map +1 -1
  18. package/lib/client/console-remote/adapter.js +11 -3
  19. package/lib/client/console-remote/adapter.js.map +1 -1
  20. package/lib/client/index.d.ts +1 -1
  21. package/lib/client/index.js +3 -1
  22. package/lib/client/index.js.map +1 -1
  23. package/lib/client/pre-session/controller.js +9 -0
  24. package/lib/client/pre-session/controller.js.map +1 -1
  25. package/lib/client/review-console/PreflightStatus.d.ts +3 -2
  26. package/lib/client/review-console/PreflightStatus.js +3 -3
  27. package/lib/client/review-console/PreflightStatus.js.map +1 -1
  28. package/lib/client/review-console/ReviewActions.js +566 -27
  29. package/lib/client/review-console/ReviewActions.js.map +1 -1
  30. package/lib/client/review-console/WorktreeReviewPanel.js +18 -3
  31. package/lib/client/review-console/WorktreeReviewPanel.js.map +1 -1
  32. package/lib/client/review-console/preflight-cache.d.ts +2 -0
  33. package/lib/client/review-console/preflight-cache.js +18 -5
  34. package/lib/client/review-console/preflight-cache.js.map +1 -1
  35. package/lib/client/review-console/preview-recovery-cache.d.ts +20 -0
  36. package/lib/client/review-console/preview-recovery-cache.js +103 -0
  37. package/lib/client/review-console/preview-recovery-cache.js.map +1 -0
  38. package/lib/client/review-console/recovery-continuation.d.ts +53 -0
  39. package/lib/client/review-console/recovery-continuation.js +483 -0
  40. package/lib/client/review-console/recovery-continuation.js.map +1 -0
  41. package/lib/client/review-console/review-console.styles.js +9 -1
  42. package/lib/client/review-console/review-console.styles.js.map +1 -1
  43. package/lib/client/styles.js +61 -0
  44. package/lib/client/styles.js.map +1 -1
  45. package/lib/client/target-console/TargetStatusAction.js +2 -2
  46. package/lib/client/target-console/TargetStatusAction.js.map +1 -1
  47. package/lib/client/target-console/WorktreeConsoleView.js +1 -1
  48. package/lib/client/target-console/WorktreeConsoleView.js.map +1 -1
  49. package/lib/client/target-console/WorktreeReviewStatus.js +19 -6
  50. package/lib/client/target-console/WorktreeReviewStatus.js.map +1 -1
  51. package/lib/client/workspace-sidebar/index.d.ts +33 -0
  52. package/lib/client/workspace-sidebar/index.js +127 -0
  53. package/lib/client/workspace-sidebar/index.js.map +1 -0
  54. package/lib/client/workspace-sidebar/model.d.ts +37 -0
  55. package/lib/client/workspace-sidebar/model.js +88 -0
  56. package/lib/client/workspace-sidebar/model.js.map +1 -0
  57. package/lib/client.js +4511 -106
  58. package/lib/console-contract.d.ts +124 -1
  59. package/lib/console-contract.js.map +1 -1
  60. package/lib/console-host/control-plane.d.ts +7 -1
  61. package/lib/console-host/control-plane.js +264 -9
  62. package/lib/console-host/control-plane.js.map +1 -1
  63. package/lib/console-host/errors.d.ts +2 -2
  64. package/lib/console-host/errors.js +21 -5
  65. package/lib/console-host/errors.js.map +1 -1
  66. package/lib/console-host/projection.js +60 -1
  67. package/lib/console-host/projection.js.map +1 -1
  68. package/lib/console-host/service.d.ts +11 -5
  69. package/lib/console-host/service.js +54 -6
  70. package/lib/console-host/service.js.map +1 -1
  71. package/lib/console-remote/descriptors.js +40 -3
  72. package/lib/console-remote/descriptors.js.map +1 -1
  73. package/lib/console-remote/remote.d.ts +11 -5
  74. package/lib/console-remote/remote.js.map +1 -1
  75. package/lib/console-remote/schemas.d.ts +104 -1
  76. package/lib/console-remote/schemas.js +161 -5
  77. package/lib/console-remote/schemas.js.map +1 -1
  78. package/lib/index.d.ts +13 -2
  79. package/lib/index.js.map +1 -1
  80. package/lib/ports.d.ts +76 -1
  81. package/lib/session-checkout-apply.d.ts +76 -0
  82. package/lib/session-checkout-apply.js +608 -143
  83. package/lib/session-checkout-apply.js.map +1 -1
  84. package/lib/session-checkout-module.js +759 -124
  85. package/lib/session-checkout-module.js.map +1 -1
  86. package/lib/tools.js +1 -1
  87. package/lib/tools.js.map +1 -1
  88. package/lib/types.d.ts +93 -2
  89. package/lib/types.js.map +1 -1
  90. package/package.json +22 -14
  91. package/scripts/check-publish.mjs +17 -2
  92. package/scripts/check-workspace-sidebar-version.mjs +14 -0
  93. package/scripts/dev-dsh-lib.mjs +1 -1
  94. package/scripts/workspace-sidebar-upstream.d.mts +8 -0
  95. 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":"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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, 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' || record.delivery.state !== 'preview_active') {
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 可以同步到 Local 验收');
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 target = await options.module.resumeRevision(request.sessionId, request.expectedRevision, request.expectedReviewId);
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
- return mutationResponse(request.sessionId, request.checkoutId);
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);