dsh-git-worktree 0.7.5 → 0.8.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 (117) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.en.md +7 -3
  3. package/README.md +7 -3
  4. package/docs/i18n.md +63 -0
  5. package/lib/adapters/files.d.ts +0 -7
  6. package/lib/adapters/files.js +4 -3
  7. package/lib/adapters/files.js.map +1 -1
  8. package/lib/adapters/git.d.ts +0 -10
  9. package/lib/adapters/git.js +6 -5
  10. package/lib/adapters/git.js.map +1 -1
  11. package/lib/adapters/registry.d.ts +0 -7
  12. package/lib/adapters/registry.js +2 -1
  13. package/lib/adapters/registry.js.map +1 -1
  14. package/lib/adapters/safe-file.d.ts +0 -7
  15. package/lib/adapters/safe-file.js +5 -4
  16. package/lib/adapters/safe-file.js.map +1 -1
  17. package/lib/client/WorktreeCreateRow.js +4 -2
  18. package/lib/client/WorktreeCreateRow.js.map +1 -1
  19. package/lib/client/WorktreeReviewRow.js +10 -8
  20. package/lib/client/WorktreeReviewRow.js.map +1 -1
  21. package/lib/client/actions.d.ts +3 -2
  22. package/lib/client/actions.js +17 -13
  23. package/lib/client/actions.js.map +1 -1
  24. package/lib/client/console-remote/adapter.d.ts +2 -1
  25. package/lib/client/console-remote/adapter.js +35 -32
  26. package/lib/client/console-remote/adapter.js.map +1 -1
  27. package/lib/client/console-remote/index.js +6 -2
  28. package/lib/client/console-remote/index.js.map +1 -1
  29. package/lib/client/i18n.d.ts +25 -0
  30. package/lib/client/i18n.js +43 -0
  31. package/lib/client/i18n.js.map +1 -0
  32. package/lib/client/index.d.ts +1 -1
  33. package/lib/client/index.js +17 -4
  34. package/lib/client/index.js.map +1 -1
  35. package/lib/client/model.d.ts +3 -2
  36. package/lib/client/model.js +8 -8
  37. package/lib/client/model.js.map +1 -1
  38. package/lib/client/pre-session/PreSessionWorktreeToggle.js +14 -12
  39. package/lib/client/pre-session/PreSessionWorktreeToggle.js.map +1 -1
  40. package/lib/client/pre-session/controller.js +11 -9
  41. package/lib/client/pre-session/controller.js.map +1 -1
  42. package/lib/client/pre-session/index.d.ts +12 -1
  43. package/lib/client/pre-session/index.js +9 -1
  44. package/lib/client/pre-session/index.js.map +1 -1
  45. package/lib/client/review-console/DeliveryProof.js +15 -12
  46. package/lib/client/review-console/DeliveryProof.js.map +1 -1
  47. package/lib/client/review-console/PreflightStatus.js +14 -8
  48. package/lib/client/review-console/PreflightStatus.js.map +1 -1
  49. package/lib/client/review-console/ReviewActions.js +97 -92
  50. package/lib/client/review-console/ReviewActions.js.map +1 -1
  51. package/lib/client/review-console/WorktreeReviewPanel.d.ts +1 -1
  52. package/lib/client/review-console/WorktreeReviewPanel.js +19 -16
  53. package/lib/client/review-console/WorktreeReviewPanel.js.map +1 -1
  54. package/lib/client/review-console/recovery-continuation.d.ts +2 -1
  55. package/lib/client/review-console/recovery-continuation.js +12 -8
  56. package/lib/client/review-console/recovery-continuation.js.map +1 -1
  57. package/lib/client/target-console/TargetStatusAction.js +28 -25
  58. package/lib/client/target-console/TargetStatusAction.js.map +1 -1
  59. package/lib/client/target-console/WorktreeConsoleView.js +40 -36
  60. package/lib/client/target-console/WorktreeConsoleView.js.map +1 -1
  61. package/lib/client/target-console/WorktreeManagerModal.js +3 -1
  62. package/lib/client/target-console/WorktreeManagerModal.js.map +1 -1
  63. package/lib/client/target-console/WorktreeReviewStatus.js +14 -12
  64. package/lib/client/target-console/WorktreeReviewStatus.js.map +1 -1
  65. package/lib/client/workspace-sidebar/index.js +4 -2
  66. package/lib/client/workspace-sidebar/index.js.map +1 -1
  67. package/lib/client/workspace-sidebar/model.d.ts +2 -1
  68. package/lib/client/workspace-sidebar/model.js +14 -13
  69. package/lib/client/workspace-sidebar/model.js.map +1 -1
  70. package/lib/client.js +2173 -477
  71. package/lib/commands.js +32 -35
  72. package/lib/commands.js.map +1 -1
  73. package/lib/console-host/control-plane.js +82 -81
  74. package/lib/console-host/control-plane.js.map +1 -1
  75. package/lib/console-host/errors.js +2 -1
  76. package/lib/console-host/errors.js.map +1 -1
  77. package/lib/console-host/projection.js +3 -1
  78. package/lib/console-host/projection.js.map +1 -1
  79. package/lib/console-host/review-diff.js +6 -5
  80. package/lib/console-host/review-diff.js.map +1 -1
  81. package/lib/console-host/service.d.ts +24 -22
  82. package/lib/console-host/service.js +54 -51
  83. package/lib/console-host/service.js.map +1 -1
  84. package/lib/console-remote/descriptors.js +2 -2
  85. package/lib/console-remote/descriptors.js.map +1 -1
  86. package/lib/console-remote/remote.d.ts +22 -21
  87. package/lib/console-remote/remote.js.map +1 -1
  88. package/lib/console-remote/schemas.d.ts +4 -0
  89. package/lib/console-remote/schemas.js +1 -0
  90. package/lib/console-remote/schemas.js.map +1 -1
  91. package/lib/i18n/client-messages.d.ts +1523 -0
  92. package/lib/i18n/client-messages.js +1431 -0
  93. package/lib/i18n/client-messages.js.map +1 -0
  94. package/lib/i18n/core.d.ts +12 -0
  95. package/lib/i18n/core.js +10 -0
  96. package/lib/i18n/core.js.map +1 -0
  97. package/lib/i18n/host-messages.d.ts +1463 -0
  98. package/lib/i18n/host-messages.js +1474 -0
  99. package/lib/i18n/host-messages.js.map +1 -0
  100. package/lib/i18n/host.d.ts +9 -0
  101. package/lib/i18n/host.js +34 -0
  102. package/lib/i18n/host.js.map +1 -0
  103. package/lib/i18n/transport-messages.d.ts +10 -0
  104. package/lib/i18n/transport-messages.js +11 -0
  105. package/lib/i18n/transport-messages.js.map +1 -0
  106. package/lib/session-checkout-apply.js +93 -92
  107. package/lib/session-checkout-apply.js.map +1 -1
  108. package/lib/session-checkout-module.js +232 -229
  109. package/lib/session-checkout-module.js.map +1 -1
  110. package/lib/session-target-context.js +2 -1
  111. package/lib/session-target-context.js.map +1 -1
  112. package/lib/tools.d.ts +0 -8
  113. package/lib/tools.js +108 -96
  114. package/lib/tools.js.map +1 -1
  115. package/package.json +2 -1
  116. package/scripts/dev-dsh-lib.mjs +1 -1
  117. package/scripts/workspace-sidebar-upstream.mjs +1 -1
@@ -1,3 +1,4 @@
1
+ import { hostMessage } from '../i18n/host.js';
1
2
  import { randomUUID } from 'node:crypto';
2
3
  import { resolve } from 'node:path';
3
4
  import { consoleFailure, domainError, failure, outcome } from './errors.js';
@@ -6,7 +7,7 @@ import { ReviewDiffStaleError } from './review-diff.js';
6
7
  function recordOf(registry, checkoutId) {
7
8
  const record = registry.read().managedCheckouts[checkoutId];
8
9
  if (record === undefined)
9
- throw domainError('checkout_missing', 'Worktree 记录不存在');
10
+ throw domainError('checkout_missing', hostMessage('theWorktreeRecordDoesNotExist'));
10
11
  return record;
11
12
  }
12
13
  function sidebarTaskState(record) {
@@ -30,14 +31,14 @@ function sidebarTaskState(record) {
30
31
  }
31
32
  function readyReview(record) {
32
33
  if (record.phase !== 'ready' || record.delivery.state !== 'ready_for_review') {
33
- throw domainError('operation_not_allowed', '当前 Worktree 尚未处于可验收状态');
34
+ throw domainError('operation_not_allowed', hostMessage('theCurrentWorktreeIsNotReadyForReview'));
34
35
  }
35
36
  return record.delivery.review;
36
37
  }
37
38
  function previewReview(record) {
38
39
  if (record.phase !== 'ready'
39
40
  || (record.delivery.state !== 'preview_active' && record.delivery.state !== 'preview_detached')) {
40
- throw domainError('preview_not_active', '当前没有等待验收的 Local Preview');
41
+ throw domainError('preview_not_active', hostMessage('thereIsNoLocalPreviewAwaitingAcceptance'));
41
42
  }
42
43
  return record.delivery.review;
43
44
  }
@@ -55,11 +56,11 @@ function applyConflictContinuation(result, reviewId) {
55
56
  const checkoutId = result.target.checkout.id;
56
57
  const revision = result.target.revision;
57
58
  if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/iu.test(result.localHeadOid)) {
58
- throw domainError('git_error', '冲突恢复缺少有效的 Local HEAD 身份');
59
+ throw domainError('git_error', hostMessage('conflictRecoveryIsMissingAValidLocalHEADIdentity'));
59
60
  }
60
61
  const localHeadOid = result.localHeadOid;
61
62
  if (result.conflictingFiles.length > 500 || !result.conflictingFiles.every(safeConflictFile)) {
62
- throw domainError('git_error', '冲突恢复包含不安全或越界的文件身份');
63
+ throw domainError('git_error', hostMessage('conflictRecoveryContainsUnsafeOrOutOfBoundsFile'));
63
64
  }
64
65
  const conflictingFiles = [...result.conflictingFiles];
65
66
  return {
@@ -74,7 +75,7 @@ function applyConflictContinuation(result, reviewId) {
74
75
  }
75
76
  function preflightFailure(view) {
76
77
  if (view === undefined)
77
- return failure('git_error', '当前 SessionCheckoutModule 不支持验收预检');
78
+ return failure('git_error', hostMessage('theCurrentSessionCheckoutModuleDoesNotSupportAcceptancePreflight'));
78
79
  if (view.status !== 'blocked')
79
80
  return undefined;
80
81
  const code = view.reason === 'stale_isolated' ? 'stale_isolated'
@@ -92,17 +93,17 @@ export function createWorktreeConsoleControlPlane(options) {
92
93
  async function unboundLocalTarget(sessionId) {
93
94
  const session = options.lookup.getSession(sessionId);
94
95
  if (session === undefined)
95
- throw domainError('session_not_found', '当前 Session 不存在');
96
+ throw domainError('session_not_found', hostMessage('theCurrentSessionDoesNotExist'));
96
97
  if (session.projectId === undefined)
97
- throw domainError('project_not_found', '当前 Session 尚未关联项目');
98
+ throw domainError('project_not_found', hostMessage('theCurrentSessionIsNotAssociatedWithAProject'));
98
99
  const project = options.lookup.getProject(session.projectId);
99
100
  if (project === undefined)
100
- throw domainError('project_not_found', '当前 Session 项目不存在');
101
+ throw domainError('project_not_found', hostMessage('theCurrentSessionSProjectDoesNotExist'));
101
102
  if (!options.files.exists(project.root))
102
- throw domainError('project_root_missing', '当前 Session 项目目录不存在');
103
+ throw domainError('project_root_missing', hostMessage('theCurrentSessionSProjectDirectoryDoesNotExist'));
103
104
  const snapshot = await options.git.inspect(project.root);
104
105
  if (snapshot === null)
105
- throw domainError('not_git_repository', '当前 Session 项目不是可用的 Git Worktree');
106
+ throw domainError('not_git_repository', hostMessage('theCurrentSessionSProjectIsNotAnAvailable'));
106
107
  const status = await options.git.status(project.root);
107
108
  return {
108
109
  project: { id: project.id, name: project.name },
@@ -129,7 +130,7 @@ export function createWorktreeConsoleControlPlane(options) {
129
130
  const session = options.lookup.getSession(sessionId);
130
131
  const project = session?.projectId === undefined ? undefined : options.lookup.getProject(session.projectId);
131
132
  if (project === undefined) {
132
- throw domainError('project_mismatch', '当前 Session Workspace 无法证明属于该 Worktree 的原始项目');
133
+ throw domainError('project_mismatch', hostMessage('theCurrentSessionWorkspaceCannotBeProvenToBelong'));
133
134
  }
134
135
  const cleanedOwnerCwd = record.ownerSessionId === sessionId
135
136
  && record.phase === 'discarded'
@@ -139,28 +140,28 @@ export function createWorktreeConsoleControlPlane(options) {
139
140
  if (cleanedOwnerCwd)
140
141
  return;
141
142
  if (!options.files.exists(project.root)) {
142
- throw domainError('project_mismatch', '当前 Session Workspace 无法证明属于该 Worktree 的原始项目');
143
+ throw domainError('project_mismatch', hostMessage('theCurrentSessionWorkspaceCannotBeProvenToBelong'));
143
144
  }
144
145
  const workspaceRoot = await options.files.canonicalize(project.root);
145
146
  if (!sameLocalRoot(workspaceRoot, expectedRoot)) {
146
- throw domainError('project_mismatch', '当前 Session cwd 与 Worktree 授权边界不一致');
147
+ throw domainError('project_mismatch', hostMessage('theCurrentSessionCwdDoesNotMatchTheWorktree'));
147
148
  }
148
149
  }
149
150
  async function authorize(sessionId, checkoutId) {
150
151
  const caller = await options.module.inspect(sessionId);
151
152
  const record = recordOf(options.registry, checkoutId);
152
153
  if (record.ownerSessionId !== sessionId && record.sourceSessionId !== sessionId) {
153
- throw domainError('not_owner', '当前 Session 无权访问该 Worktree');
154
+ throw domainError('not_owner', hostMessage('theCurrentSessionIsNotAllowedToAccessThis'));
154
155
  }
155
156
  if (record.projectId !== caller.project.id) {
156
- throw domainError('project_mismatch', 'Worktree 与当前 Session 项目不一致');
157
+ throw domainError('project_mismatch', hostMessage('worktreeDoesNotMatchTheCurrentSessionSProject'));
157
158
  }
158
159
  const expectedRoot = record.ownerSessionId === sessionId ? record.managedRoot : record.localRoot;
159
160
  await verifyCallerRoot(sessionId, record, expectedRoot);
160
161
  if (record.phase !== 'discarded') {
161
162
  const visible = await options.module.listManagedWorktreesForSession(sessionId, { checkoutId });
162
163
  if (!visible.some(item => item.checkoutId === checkoutId)) {
163
- throw domainError('not_owner', '当前 Session 无权访问该 Worktree');
164
+ throw domainError('not_owner', hostMessage('theCurrentSessionIsNotAllowedToAccessThis'));
164
165
  }
165
166
  }
166
167
  return record;
@@ -172,7 +173,7 @@ export function createWorktreeConsoleControlPlane(options) {
172
173
  }
173
174
  const caller = await callerTarget(sessionId);
174
175
  if (caller.checkout.kind !== 'isolated') {
175
- throw domainError('not_owner', '当前 Session 无权访问该 Worktree');
176
+ throw domainError('not_owner', hostMessage('theCurrentSessionIsNotAllowedToAccessThis'));
176
177
  }
177
178
  const anchor = await authorize(sessionId, caller.checkout.id);
178
179
  const sourceSessionId = anchor.sourceSessionId ?? anchor.ownerSessionId;
@@ -189,10 +190,10 @@ export function createWorktreeConsoleControlPlane(options) {
189
190
  && requested.projectId === anchor.projectId
190
191
  && sameLocalRoot(requested.localRoot, anchor.localRoot);
191
192
  if (!sameLinkedGroup && !exactAcceptanceHolder) {
192
- throw domainError('not_owner', '当前 Session 无权访问该 Worktree');
193
+ throw domainError('not_owner', hostMessage('theCurrentSessionIsNotAllowedToAccessThis'));
193
194
  }
194
195
  if (!ownerSessionAvailable(requested)) {
195
- throw domainError('checkout_missing', '关联 Worktree 的 owner Session 不可用');
196
+ throw domainError('checkout_missing', hostMessage('theAssociatedWorktreeSOwnerSessionIsUnavailable'));
196
197
  }
197
198
  await verifyCallerRoot(requested.ownerSessionId, requested, requested.managedRoot);
198
199
  if (exactAcceptanceHolder) {
@@ -206,7 +207,7 @@ export function createWorktreeConsoleControlPlane(options) {
206
207
  if (currentHolder?.checkoutId !== currentRequested.checkoutId
207
208
  || currentRequested.ownerSessionId !== requested.ownerSessionId
208
209
  || !sameLocalRoot(currentRequested.localRoot, currentAnchor.localRoot))
209
- throw domainError('not_owner', '验收槽位占用者已变化,请重新检查');
210
+ throw domainError('not_owner', hostMessage('theAcceptanceSlotHolderHasChangedCheckAgain'));
210
211
  return {
211
212
  record: currentRequested,
212
213
  linkedRead: true,
@@ -221,7 +222,7 @@ export function createWorktreeConsoleControlPlane(options) {
221
222
  const managedRoot = await options.module.resolveManagedRoot(record.checkoutId);
222
223
  const snapshot = await options.git.inspect(managedRoot);
223
224
  if (snapshot === null)
224
- throw domainError('checkout_mismatch', 'Worktree Git 身份无法验证');
225
+ throw domainError('checkout_mismatch', hostMessage('theWorktreeGitIdentityCannotBeVerified'));
225
226
  const status = await options.git.status(managedRoot);
226
227
  return { managedRoot, snapshot, dirty: status.dirty };
227
228
  }
@@ -286,7 +287,7 @@ export function createWorktreeConsoleControlPlane(options) {
286
287
  || !sameLocalRoot(currentRequested.localRoot, currentAnchor.localRoot)
287
288
  || observed.managedRoot === null
288
289
  || !sameLocalRoot(observed.managedRoot, currentRequested.managedRoot))
289
- throw domainError('not_owner', '验收槽位占用者已变化,请重新检查');
290
+ throw domainError('not_owner', hostMessage('theAcceptanceSlotHolderHasChangedCheckAgain'));
290
291
  projectedRecord = currentRequested;
291
292
  }
292
293
  return projectReviewSlot(projectedRecord, projectDetails(projectedRecord, sessionId, observed.managedRoot, observed.snapshot, observed.dirty, ownerSessionAvailable(access.record), access.linkedRead, observedDeliveryProof));
@@ -365,7 +366,7 @@ export function createWorktreeConsoleControlPlane(options) {
365
366
  const session = options.lookup.getSession(request.sessionId);
366
367
  const project = session?.projectId === undefined ? undefined : options.lookup.getProject(session.projectId);
367
368
  if (project === undefined || project.id !== caller.project.id || !options.files.exists(project.root)) {
368
- throw domainError('project_mismatch', '当前 Session Workspace 无法证明关联 Worktree 项目');
369
+ throw domainError('project_mismatch', hostMessage('theCurrentSessionWorkspaceCannotBeProvenToBe'));
369
370
  }
370
371
  localRoot = await options.files.canonicalize(project.root);
371
372
  }
@@ -403,7 +404,7 @@ export function createWorktreeConsoleControlPlane(options) {
403
404
  if (record.sourceSessionId !== sourceSessionId
404
405
  || record.ownerSessionId !== targetSessionId
405
406
  || observed.managedRoot !== launch.managedRoot)
406
- throw domainError('checkout_mismatch', '新建 Worktree 的 Host 身份校验失败');
407
+ throw domainError('checkout_mismatch', hostMessage('hostIdentityVerificationFailedForTheNewlyCreatedWorktree'));
407
408
  return {
408
409
  target: projectDetails(record, sourceSessionId, observed.managedRoot, observed.snapshot, observed.dirty, ownerSessionAvailable(record)),
409
410
  targetSessionId,
@@ -415,17 +416,17 @@ export function createWorktreeConsoleControlPlane(options) {
415
416
  try {
416
417
  const record = await authorize(request.sessionId, request.checkoutId);
417
418
  if (record.ownerSessionId !== request.sessionId)
418
- return failure('not_owner', '只有 owner Isolated Session 可以读取验收 Diff');
419
+ return failure('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanReadTheReview'));
419
420
  if (record.revision !== request.expectedRevision)
420
- return failure('stale_target', 'Session Target 已变化,请刷新');
421
+ return failure('stale_target', hostMessage('theSessionTargetHasChangedRefresh'));
421
422
  const review = readyReview(record);
422
423
  if (review.reviewId !== request.expectedReviewId)
423
- return failure('stale_target', 'Review 身份已变化,请刷新');
424
+ return failure('stale_target', hostMessage('theReviewIdentityHasChangedRefresh'));
424
425
  const before = await options.module.preflight?.(request.sessionId, request.expectedRevision);
425
426
  if (before === undefined || before.status === 'blocked')
426
427
  return preflightFailure(before);
427
428
  if (before.reviewId !== review.reviewId || before.isolatedHeadOid !== review.isolatedHeadOid) {
428
- return failure('stale_isolated', 'Ready 后 Isolated HEAD 已变化');
429
+ return failure('stale_isolated', hostMessage('isolatedHEADChangedAfterReady'));
429
430
  }
430
431
  const observed = await observe(record);
431
432
  const diff = await options.reviewDiff.read({
@@ -437,7 +438,7 @@ export function createWorktreeConsoleControlPlane(options) {
437
438
  });
438
439
  const after = await options.module.preflight?.(request.sessionId, request.expectedRevision);
439
440
  if (after === undefined || after.status === 'blocked') {
440
- return failure('stale_isolated', 'Ready 后 Isolated 内容已变化,Diff bytes 已丢弃');
441
+ return failure('stale_isolated', hostMessage('isolatedContentChangedAfterReadyDiffBytesWereDiscarded'));
441
442
  }
442
443
  const current = recordOf(options.registry, request.checkoutId);
443
444
  if (after.reviewId !== review.reviewId
@@ -446,7 +447,7 @@ export function createWorktreeConsoleControlPlane(options) {
446
447
  || current.delivery.state !== 'ready_for_review'
447
448
  || current.delivery.review.reviewId !== review.reviewId
448
449
  || current.delivery.review.isolatedFingerprint !== review.isolatedFingerprint)
449
- return failure('stale_isolated', 'Ready 后 Isolated 内容已变化,Diff bytes 已丢弃');
450
+ return failure('stale_isolated', hostMessage('isolatedContentChangedAfterReadyDiffBytesWereDiscarded'));
450
451
  return { ok: true, value: diff };
451
452
  }
452
453
  catch (error) {
@@ -458,31 +459,31 @@ export function createWorktreeConsoleControlPlane(options) {
458
459
  preflight: request => outcome(async () => {
459
460
  const record = await authorize(request.sessionId, request.checkoutId);
460
461
  if (record.ownerSessionId !== request.sessionId)
461
- throw domainError('not_owner', '只有 owner Isolated Session 可以执行同步预检');
462
+ throw domainError('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanRunSyncPreflight436'));
462
463
  if (record.revision !== request.expectedRevision)
463
- throw domainError('stale_target', 'Session Target 已变化,请刷新');
464
+ throw domainError('stale_target', hostMessage('theSessionTargetHasChangedRefresh'));
464
465
  const review = readyReview(record);
465
466
  if (review.reviewId !== request.expectedReviewId)
466
- throw domainError('stale_target', 'Review 身份已变化,请刷新');
467
+ throw domainError('stale_target', hostMessage('theReviewIdentityHasChangedRefresh'));
467
468
  const preflight = await options.module.preflight?.(request.sessionId, request.expectedRevision);
468
469
  if (preflight === undefined)
469
- throw domainError('git_error', '当前 SessionCheckoutModule 不支持验收预检');
470
+ throw domainError('git_error', hostMessage('theCurrentSessionCheckoutModuleDoesNotSupportAcceptancePreflight'));
470
471
  return { preflight };
471
472
  }),
472
473
  previewRecoveryPreflight: request => outcome(async () => {
473
474
  const record = await authorize(request.sessionId, request.checkoutId);
474
475
  if (record.ownerSessionId !== request.sessionId)
475
- throw domainError('not_owner', '只有 owner Isolated Session 可以检查 Preview 恢复');
476
+ throw domainError('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanCheckPreviewRecovery440'));
476
477
  if (record.revision !== request.expectedRevision)
477
- throw domainError('stale_target', 'Session Target 已变化,请刷新');
478
+ throw domainError('stale_target', hostMessage('theSessionTargetHasChangedRefresh'));
478
479
  if (record.phase !== 'ready'
479
480
  || record.delivery.state !== 'preview_detached'
480
481
  || record.delivery.review.reviewId !== request.expectedReviewId
481
482
  || record.delivery.preview.previewId !== request.expectedPreviewId)
482
- throw domainError('stale_target', 'Detached Preview 身份已变化,请刷新');
483
+ throw domainError('stale_target', hostMessage('theDetachedPreviewIdentityHasChangedRefresh'));
483
484
  const preflight = await options.module.preflightPreviewRecovery?.(request.sessionId, request.expectedRevision, request.expectedReviewId, request.expectedPreviewId);
484
485
  if (preflight === undefined)
485
- throw domainError('git_error', '当前 SessionCheckoutModule 不支持 Preview Recovery 预检');
486
+ throw domainError('git_error', hostMessage('theCurrentSessionCheckoutModuleDoesNotSupportPreviewRecoveryPreflight'));
486
487
  return { preflight };
487
488
  }),
488
489
  preparePreviewRecoveryAnalysis: request => outcome(async () => {
@@ -493,7 +494,7 @@ export function createWorktreeConsoleControlPlane(options) {
493
494
  || request.recoveryProof.revision !== request.expectedRevision
494
495
  || request.recoveryProof.reviewId !== request.expectedReviewId
495
496
  || request.recoveryProof.previewId !== request.expectedPreviewId)
496
- throw domainError('stale_target', 'Preview Recovery 分析身份不匹配');
497
+ throw domainError('stale_target', hostMessage('thePreviewRecoveryAnalysisIdentityDoesNotMatch'));
497
498
  const prepared = await options.module.preparePreviewRecoveryAnalysis(request.sessionId, request.recoveryProof, `preview-recovery-analysis:${randomUUID()}`);
498
499
  const response = await mutationResponse(request.sessionId, request.checkoutId);
499
500
  return {
@@ -517,7 +518,7 @@ export function createWorktreeConsoleControlPlane(options) {
517
518
  || request.recoveryProof.revision !== request.expectedRevision
518
519
  || request.recoveryProof.reviewId !== request.expectedReviewId
519
520
  || request.recoveryProof.previewId !== request.expectedPreviewId)
520
- throw domainError('stale_target', 'Preview Recovery handoff 身份不匹配');
521
+ throw domainError('stale_target', hostMessage('thePreviewRecoveryHandoffIdentityDoesNotMatch'));
521
522
  const targetSessionId = createTargetSessionId();
522
523
  const launch = await options.module.createPreviewRecoveryHandoff(request.sessionId, request.recoveryProof, targetSessionId, `preview-recovery-handoff:${randomUUID()}`);
523
524
  const created = recordOf(options.registry, launch.target.checkout.id);
@@ -526,7 +527,7 @@ export function createWorktreeConsoleControlPlane(options) {
526
527
  || created.recoveryContinuation?.kind !== 'worktree_preview_recovery_handoff'
527
528
  || created.recoveryContinuation.requestId !== launch.continuation.requestId
528
529
  || observed.managedRoot !== launch.managedRoot)
529
- throw domainError('checkout_mismatch', 'Recovery handoff Worktree 的 Host 身份校验失败');
530
+ throw domainError('checkout_mismatch', hostMessage('hostIdentityVerificationFailedForTheRecoveryHandoffWorktree'));
530
531
  return {
531
532
  target: projectDetails(created, targetSessionId, observed.managedRoot, observed.snapshot, observed.dirty, false),
532
533
  targetSessionId,
@@ -546,10 +547,10 @@ export function createWorktreeConsoleControlPlane(options) {
546
547
  checkpoint: request => outcome(async () => {
547
548
  const record = await authorize(request.sessionId, request.checkoutId);
548
549
  if (record.ownerSessionId !== request.sessionId)
549
- throw domainError('not_owner', '只有 owner Isolated Session 可以保存阶段');
550
+ throw domainError('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanSaveACheckpoint447'));
550
551
  const commitMessage = request.commitMessage.trim();
551
552
  if (!commitMessage || commitMessage.length > 500 || !safeRecoveryRequestId(request.requestId)) {
552
- throw domainError('invalid_input', 'Checkpoint Commit Message 或 requestId 无效');
553
+ throw domainError('invalid_input', hostMessage('theCheckpointCommitMessageOrRequestIdIsInvalid'));
553
554
  }
554
555
  const result = await options.module.operate({
555
556
  action: 'checkpoint',
@@ -566,32 +567,32 @@ export function createWorktreeConsoleControlPlane(options) {
566
567
  return { ...(await mutationResponse(request.sessionId, request.checkoutId)), changedFiles: [...result.changedFiles] };
567
568
  }
568
569
  if (result.status !== 'checkpointed')
569
- throw domainError('operation_not_allowed', 'Checkpoint 返回了非预期状态');
570
+ throw domainError('operation_not_allowed', hostMessage('checkpointReturnedAnUnexpectedState'));
570
571
  const response = await mutationResponse(request.sessionId, request.checkoutId);
571
572
  if (response.target.state !== 'working' || response.target.revision !== result.target.revision) {
572
- throw domainError('checkout_mismatch', 'Checkpoint 后 Worktree 状态未收敛到 Working');
573
+ throw domainError('checkout_mismatch', hostMessage('worktreeDidNotReturnToWorkingAfterCheckpoint'));
573
574
  }
574
575
  return { ...response, checkpoint: result.checkpoint, changedFiles: [...result.changedFiles] };
575
576
  }),
576
577
  preview: request => outcome(async () => {
577
578
  const record = await authorize(request.sessionId, request.checkoutId);
578
579
  if (record.ownerSessionId !== request.sessionId)
579
- throw domainError('not_owner', '只有 owner Isolated Session 可以预览修改');
580
+ throw domainError('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanPreviewChanges'));
580
581
  if (record.revision !== request.expectedRevision)
581
- throw domainError('stale_target', 'Session Target 已变化,请刷新');
582
+ throw domainError('stale_target', hostMessage('theSessionTargetHasChangedRefresh'));
582
583
  const review = readyReview(record);
583
584
  if (review.reviewId !== request.expectedReviewId)
584
- throw domainError('stale_target', 'Review 身份已变化,请刷新');
585
+ throw domainError('stale_target', hostMessage('theReviewIdentityHasChangedRefresh'));
585
586
  const result = await options.module.operate({
586
587
  action: 'preview', sessionId: request.sessionId, expectedRevision: request.expectedRevision,
587
588
  });
588
589
  if (result.status === 'error')
589
590
  throw domainError(result.code, result.message);
590
591
  if (result.status === 'conflict') {
591
- throw domainError('apply_conflict', 'Local Preview 预检发现内容冲突', applyConflictContinuation(result, review.reviewId));
592
+ throw domainError('apply_conflict', hostMessage('localPreviewPreflightFoundContentConflicts'), applyConflictContinuation(result, review.reviewId));
592
593
  }
593
594
  if (result.status !== 'previewed')
594
- throw domainError('operation_not_allowed', 'Preview 返回了非预期状态');
595
+ throw domainError('operation_not_allowed', hostMessage('previewReturnedAnUnexpectedState'));
595
596
  return {
596
597
  ...(await mutationResponse(request.sessionId, request.checkoutId)),
597
598
  changedFiles: [...result.changedFiles],
@@ -600,12 +601,12 @@ export function createWorktreeConsoleControlPlane(options) {
600
601
  resumeRevision: request => outcome(async () => {
601
602
  const record = await authorize(request.sessionId, request.checkoutId);
602
603
  if (record.ownerSessionId !== request.sessionId)
603
- throw domainError('not_owner', '只有 owner Isolated Session 可以继续修改');
604
+ throw domainError('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanResumeEditing456'));
604
605
  if (record.revision !== request.expectedRevision)
605
- throw domainError('stale_target', 'Session Target 已变化,请刷新');
606
+ throw domainError('stale_target', hostMessage('theSessionTargetHasChangedRefresh'));
606
607
  const review = readyReview(record);
607
608
  if (review.reviewId !== request.expectedReviewId)
608
- throw domainError('stale_target', 'Review 身份已变化,请刷新');
609
+ throw domainError('stale_target', hostMessage('theReviewIdentityHasChangedRefresh'));
609
610
  const requestedRecovery = request.conflictContinuation;
610
611
  if (requestedRecovery && (requestedRecovery.kind !== 'worktree_apply_conflict'
611
612
  || !safeRecoveryRequestId(requestedRecovery.requestId)
@@ -615,7 +616,7 @@ export function createWorktreeConsoleControlPlane(options) {
615
616
  || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/iu.test(requestedRecovery.localHeadOid)
616
617
  || requestedRecovery.conflictingFiles.length > 500
617
618
  || !requestedRecovery.conflictingFiles.every(safeConflictFile)))
618
- throw domainError('invalid_input', '冲突恢复请求身份无效');
619
+ throw domainError('invalid_input', hostMessage('invalidConflictRecoveryRequestIdentity'));
619
620
  const recovery = requestedRecovery ? {
620
621
  kind: 'worktree_apply_conflict',
621
622
  requestId: `conflict-recovery:${randomUUID()}`,
@@ -628,7 +629,7 @@ export function createWorktreeConsoleControlPlane(options) {
628
629
  ? await options.module.resumeRevision(request.sessionId, request.expectedRevision, request.expectedReviewId, recovery)
629
630
  : await options.module.resumeRevision(request.sessionId, request.expectedRevision, request.expectedReviewId);
630
631
  if (target.checkout.id !== request.checkoutId || target.delivery?.state !== 'working') {
631
- throw domainError('checkout_mismatch', '恢复编辑后 Worktree 身份或状态不一致');
632
+ throw domainError('checkout_mismatch', hostMessage('theWorktreeIdentityOrStateDoesNotMatchAfter'));
632
633
  }
633
634
  const response = await mutationResponse(request.sessionId, request.checkoutId);
634
635
  if (!requestedRecovery)
@@ -638,7 +639,7 @@ export function createWorktreeConsoleControlPlane(options) {
638
639
  || current.kind !== 'worktree_apply_conflict'
639
640
  || current.requestId !== recovery.requestId
640
641
  || current.workingRevision !== response.target.revision)
641
- throw domainError('checkout_mismatch', 'Host 未能持久化精确冲突恢复凭证');
642
+ throw domainError('checkout_mismatch', hostMessage('theHostCouldNotPersistTheExactConflictRecovery'));
642
643
  return {
643
644
  ...response,
644
645
  recoveryContinuation: {
@@ -655,12 +656,12 @@ export function createWorktreeConsoleControlPlane(options) {
655
656
  prepareReviewRegeneration: request => outcome(async () => {
656
657
  const record = await authorize(request.sessionId, request.checkoutId);
657
658
  if (record.ownerSessionId !== request.sessionId)
658
- throw domainError('not_owner', '只有 owner Isolated Session 可以请求重新生成验收结果');
659
+ throw domainError('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanRequestReviewRegeneration462'));
659
660
  if (record.revision !== request.expectedRevision)
660
- throw domainError('stale_target', 'Session Target 已变化,请刷新');
661
+ throw domainError('stale_target', hostMessage('theSessionTargetHasChangedRefresh'));
661
662
  const review = readyReview(record);
662
663
  if (review.reviewId !== request.expectedReviewId)
663
- throw domainError('stale_target', 'Review 身份已变化,请刷新');
664
+ throw domainError('stale_target', hostMessage('theReviewIdentityHasChangedRefresh'));
664
665
  const prepared = await options.module.prepareReviewRegeneration(request.sessionId, request.expectedRevision, request.expectedReviewId, `review-regeneration:${randomUUID()}`);
665
666
  const response = await mutationResponse(request.sessionId, request.checkoutId);
666
667
  return {
@@ -677,7 +678,7 @@ export function createWorktreeConsoleControlPlane(options) {
677
678
  rollbackPreview: request => outcome(async () => {
678
679
  const record = await authorize(request.sessionId, request.checkoutId);
679
680
  if (record.ownerSessionId !== request.sessionId)
680
- throw domainError('not_owner', '只有 owner Isolated Session 可以撤回 Local Preview');
681
+ throw domainError('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanRollBackLocal'));
681
682
  const result = await options.module.operate({
682
683
  action: 'rollback_preview',
683
684
  sessionId: request.sessionId,
@@ -688,7 +689,7 @@ export function createWorktreeConsoleControlPlane(options) {
688
689
  if (result.status === 'error')
689
690
  throw domainError(result.code, result.message);
690
691
  if (result.status !== 'preview_rolled_back' && result.status !== 'preview_detached') {
691
- throw domainError('operation_not_allowed', 'Rollback Preview 返回了非预期状态');
692
+ throw domainError('operation_not_allowed', hostMessage('rollbackPreviewReturnedAnUnexpectedState'));
692
693
  }
693
694
  return {
694
695
  ...(await mutationResponse(request.sessionId, request.checkoutId)),
@@ -698,7 +699,7 @@ export function createWorktreeConsoleControlPlane(options) {
698
699
  discard: request => outcome(async () => {
699
700
  const record = await authorize(request.sessionId, request.checkoutId);
700
701
  if (record.ownerSessionId !== request.sessionId && ownerSessionAvailable(record)) {
701
- throw domainError('not_owner', 'Owner Session 已接管该 Worktree,只有 owner 可以 Discard');
702
+ throw domainError('not_owner', hostMessage('theOwnerSessionHasTakenOverThisWorktreeOnly467'));
702
703
  }
703
704
  if (record.ownerSessionId === request.sessionId) {
704
705
  const result = await options.module.operate({
@@ -714,7 +715,7 @@ export function createWorktreeConsoleControlPlane(options) {
714
715
  return { ...(await mutationResponse(request.sessionId, request.checkoutId)), changedFiles: [...result.changedFiles] };
715
716
  }
716
717
  if (result.status !== 'discarded')
717
- throw domainError('operation_not_allowed', 'Discard 返回了非预期状态');
718
+ throw domainError('operation_not_allowed', hostMessage('discardReturnedAnUnexpectedState'));
718
719
  }
719
720
  else {
720
721
  await options.module.manageManagedWorktreeForSession(request.sessionId, {
@@ -730,15 +731,15 @@ export function createWorktreeConsoleControlPlane(options) {
730
731
  try {
731
732
  const record = await authorize(request.sessionId, request.checkoutId);
732
733
  if (record.ownerSessionId !== request.sessionId)
733
- return failure('not_owner', '只有 owner Isolated Session 可以提交验收');
734
+ return failure('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanCommitAcceptance'));
734
735
  if (record.revision !== request.expectedRevision)
735
- return failure('stale_target', 'Session Target 已变化,请刷新');
736
+ return failure('stale_target', hostMessage('theSessionTargetHasChangedRefresh'));
736
737
  const review = readyReview(record);
737
738
  if (review.reviewId !== request.expectedReviewId)
738
- return failure('stale_target', 'Review 身份已变化,请刷新');
739
+ return failure('stale_target', hostMessage('theReviewIdentityHasChangedRefresh'));
739
740
  const commitMessage = request.commitMessage.trim();
740
741
  if (!commitMessage || commitMessage.length > 500)
741
- return failure('invalid_input', 'Commit Message 必须为 1–500 个字符');
742
+ return failure('invalid_input', hostMessage('theCommitMessageMustContain1500Characters'));
742
743
  const result = await options.module.operate({
743
744
  sessionId: request.sessionId,
744
745
  expectedRevision: request.expectedRevision,
@@ -750,10 +751,10 @@ export function createWorktreeConsoleControlPlane(options) {
750
751
  if (result.status === 'error')
751
752
  return failure(result.code, result.message);
752
753
  if (result.status === 'conflict') {
753
- return failure('apply_conflict', 'Local 应用发生冲突', undefined, applyConflictContinuation(result, review.reviewId));
754
+ return failure('apply_conflict', hostMessage('applyingToLocalCausedConflicts'), undefined, applyConflictContinuation(result, review.reviewId));
754
755
  }
755
756
  if (result.status !== 'finished')
756
- return failure('operation_not_allowed', 'Finalize 返回了非预期状态');
757
+ return failure('operation_not_allowed', hostMessage('finalizeReturnedAnUnexpectedState'));
757
758
  return {
758
759
  ok: true,
759
760
  value: {
@@ -771,15 +772,15 @@ export function createWorktreeConsoleControlPlane(options) {
771
772
  try {
772
773
  const record = await authorize(request.sessionId, request.checkoutId);
773
774
  if (record.ownerSessionId !== request.sessionId)
774
- return failure('not_owner', '只有 owner Isolated Session 可以完成 Local Preview 验收');
775
+ return failure('not_owner', hostMessage('onlyTheOwnerIsolatedSessionCanFinalizeLocalPreview'));
775
776
  if (record.revision !== request.expectedRevision)
776
- return failure('stale_target', 'Session Target 已变化,请刷新');
777
+ return failure('stale_target', hostMessage('theSessionTargetHasChangedRefresh'));
777
778
  const review = previewReview(record);
778
779
  if (review.reviewId !== request.expectedReviewId)
779
- return failure('stale_target', 'Review 身份已变化,请刷新');
780
+ return failure('stale_target', hostMessage('theReviewIdentityHasChangedRefresh'));
780
781
  const commitMessage = request.commitMessage.trim();
781
782
  if (!commitMessage || commitMessage.length > 500)
782
- return failure('invalid_input', 'Commit Message 必须为 1–500 个字符');
783
+ return failure('invalid_input', hostMessage('theCommitMessageMustContain1500Characters'));
783
784
  const result = await options.module.operate({
784
785
  action: 'finalize_preview',
785
786
  sessionId: request.sessionId,
@@ -800,7 +801,7 @@ export function createWorktreeConsoleControlPlane(options) {
800
801
  };
801
802
  }
802
803
  if (result.status !== 'finished')
803
- return failure('operation_not_allowed', 'Finalize Preview 返回了非预期状态');
804
+ return failure('operation_not_allowed', hostMessage('finalizePreviewReturnedAnUnexpectedState'));
804
805
  return {
805
806
  ok: true,
806
807
  value: {
@@ -836,28 +837,28 @@ export function createWorktreeConsoleControlPlane(options) {
836
837
  beginNextIteration: request => outcome(async () => {
837
838
  const predecessor = recordOf(options.registry, request.checkoutId);
838
839
  if (predecessor.ownerSessionId !== request.sessionId) {
839
- throw domainError('not_owner', '只有 owner Session 可以开始下一轮');
840
+ throw domainError('not_owner', hostMessage('onlyTheOwnerSessionCanBeginTheNextIteration'));
840
841
  }
841
842
  if (predecessor.revision !== request.expectedRevision) {
842
- throw domainError('stale_target', 'Worktree 状态已变化,请刷新后再开始下一轮');
843
+ throw domainError('stale_target', hostMessage('theWorktreeStateHasChangedRefreshBeforeBeginningThe'));
843
844
  }
844
845
  if (predecessor.phase !== 'discarded' || predecessor.delivery.state !== 'delivered') {
845
- throw domainError('operation_not_allowed', '只有已成功清理的交付状态可以开始下一轮');
846
+ throw domainError('operation_not_allowed', hostMessage('onlyADeliveredStateWithSuccessfulCleanupCanBegin'));
846
847
  }
847
848
  const session = options.lookup.getSession(request.sessionId);
848
849
  const workspace = session?.projectId === undefined ? undefined : options.lookup.getProject(session.projectId);
849
850
  if (!workspace || !sameLocalRoot(resolve(workspace.root), resolve(predecessor.managedRoot))) {
850
- throw domainError('project_mismatch', '当前 Session 的 immutable cwd 与上一轮 Worktree 不一致');
851
+ throw domainError('project_mismatch', hostMessage('theCurrentSessionSImmutableCwdDoesNotMatch'));
851
852
  }
852
853
  const target = await options.module.beginNextIteration(request.sessionId, request.expectedRevision);
853
854
  if (target.checkout.kind !== 'isolated' || target.delivery?.state !== 'working') {
854
- throw domainError('checkout_mismatch', '下一轮 Worktree 未进入 working 状态');
855
+ throw domainError('checkout_mismatch', hostMessage('theNextWorktreeIterationDidNotEnterWorkingState484'));
855
856
  }
856
857
  const record = recordOf(options.registry, target.checkout.id);
857
858
  if (record.predecessorCheckoutId !== predecessor.checkoutId
858
859
  || record.ownerSessionId !== request.sessionId
859
860
  || !sameLocalRoot(record.managedRoot, predecessor.managedRoot)) {
860
- throw domainError('checkout_mismatch', '下一轮 Worktree 的 lineage 或 cwd 身份不一致');
861
+ throw domainError('checkout_mismatch', hostMessage('theNextIterationSWorktreeLineageOrCwdIdentity'));
861
862
  }
862
863
  const observed = await observe(record);
863
864
  return {