dsh-git-worktree 0.3.2 → 0.4.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 (62) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.en.md +21 -11
  3. package/README.md +21 -11
  4. package/lib/adapters/registry.js +7 -1
  5. package/lib/adapters/registry.js.map +1 -1
  6. package/lib/client/WorktreeReviewRow.d.ts +1 -1
  7. package/lib/client/WorktreeReviewRow.js +2 -2
  8. package/lib/client/WorktreeReviewRow.js.map +1 -1
  9. package/lib/client/actions.d.ts +25 -4
  10. package/lib/client/actions.js +104 -0
  11. package/lib/client/actions.js.map +1 -1
  12. package/lib/client/review-console/DeliveryProof.d.ts +5 -0
  13. package/lib/client/review-console/DeliveryProof.js +32 -0
  14. package/lib/client/review-console/DeliveryProof.js.map +1 -0
  15. package/lib/client/review-console/PreflightStatus.d.ts +11 -0
  16. package/lib/client/review-console/PreflightStatus.js +31 -0
  17. package/lib/client/review-console/PreflightStatus.js.map +1 -0
  18. package/lib/client/review-console/ReviewActions.d.ts +4 -1
  19. package/lib/client/review-console/ReviewActions.js +103 -14
  20. package/lib/client/review-console/ReviewActions.js.map +1 -1
  21. package/lib/client/review-console/WorktreeReviewPanel.d.ts +3 -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 +19 -0
  25. package/lib/client/review-console/preflight-cache.js +85 -0
  26. package/lib/client/review-console/preflight-cache.js.map +1 -0
  27. package/lib/client/review-console/review-console.styles.js +27 -3
  28. package/lib/client/review-console/review-console.styles.js.map +1 -1
  29. package/lib/client/target-console/TargetStatusAction.d.ts +4 -2
  30. package/lib/client/target-console/TargetStatusAction.js +196 -23
  31. package/lib/client/target-console/TargetStatusAction.js.map +1 -1
  32. package/lib/client/target-console/WorktreeConsoleView.d.ts +4 -2
  33. package/lib/client/target-console/WorktreeConsoleView.js +33 -73
  34. package/lib/client/target-console/WorktreeConsoleView.js.map +1 -1
  35. package/lib/client/target-console/WorktreeManagerModal.d.ts +13 -0
  36. package/lib/client/target-console/WorktreeManagerModal.js +8 -0
  37. package/lib/client/target-console/WorktreeManagerModal.js.map +1 -0
  38. package/lib/client/target-console/WorktreeReviewStatus.d.ts +3 -1
  39. package/lib/client/target-console/WorktreeReviewStatus.js +22 -15
  40. package/lib/client/target-console/WorktreeReviewStatus.js.map +1 -1
  41. package/lib/client/target-console/index.d.ts +2 -1
  42. package/lib/client/target-console/index.js +4 -3
  43. package/lib/client/target-console/index.js.map +1 -1
  44. package/lib/client/target-console/target-console.styles.js +22 -3
  45. package/lib/client/target-console/target-console.styles.js.map +1 -1
  46. package/lib/client.js +1297 -77
  47. package/lib/console-contract.d.ts +6 -1
  48. package/lib/console-contract.js.map +1 -1
  49. package/lib/console-host/control-plane.js +153 -37
  50. package/lib/console-host/control-plane.js.map +1 -1
  51. package/lib/console-host/projection.d.ts +5 -3
  52. package/lib/console-host/projection.js +43 -10
  53. package/lib/console-host/projection.js.map +1 -1
  54. package/lib/console-remote/schemas.js +19 -1
  55. package/lib/console-remote/schemas.js.map +1 -1
  56. package/lib/ports.d.ts +3 -0
  57. package/lib/session-checkout-module.js +31 -4
  58. package/lib/session-checkout-module.js.map +1 -1
  59. package/lib/types.d.ts +11 -1
  60. package/lib/types.js.map +1 -1
  61. package/package.json +13 -13
  62. package/scripts/check-publish.mjs +1 -1
package/lib/client.js CHANGED
@@ -4696,6 +4696,33 @@ const capabilitiesSchema = strict({
4696
4696
  retryCleanup: boolean(),
4697
4697
  beginNextIteration: boolean()
4698
4698
  });
4699
+ const acceptanceBlockerSchema = strict({
4700
+ checkoutId: checkoutIdSchema,
4701
+ ownerSessionId: sessionIdSchema,
4702
+ state: _enum([
4703
+ "preview_active",
4704
+ "preview_detached",
4705
+ "finalized",
4706
+ "retained",
4707
+ "working",
4708
+ "ready_for_review",
4709
+ "delivered"
4710
+ ])
4711
+ });
4712
+ const deliveryProofSchema = strict({
4713
+ localBranch: string().nullable(),
4714
+ localHeadBefore: oidSchema,
4715
+ localHeadAfter: oidSchema,
4716
+ changedFiles: array(string()),
4717
+ validationStatus: _enum([
4718
+ "passed",
4719
+ "failed",
4720
+ "partial",
4721
+ "not_run"
4722
+ ]).optional(),
4723
+ validationSummary: string().optional(),
4724
+ commitInLocalHistory: boolean().nullable()
4725
+ });
4699
4726
  const targetSummarySchema = strict({
4700
4727
  project: projectSchema,
4701
4728
  checkoutId: checkoutIdSchema.nullable(),
@@ -4723,9 +4750,11 @@ const targetSummarySchema = strict({
4723
4750
  retainedAt: number().finite().optional(),
4724
4751
  expiresAt: number().finite().nullable().optional(),
4725
4752
  cleanupMessage: string().optional(),
4753
+ deliveryProof: deliveryProofSchema.optional(),
4726
4754
  review: reviewSchema.optional(),
4727
4755
  reviewSlot: _enum(["available", "waiting"]).optional(),
4728
4756
  reviewSlotOwnerSessionId: sessionIdSchema.optional(),
4757
+ reviewSlotHolder: acceptanceBlockerSchema.optional(),
4729
4758
  previewRecovery: strict({
4730
4759
  reason: _enum(["stale_local", "preview_modified"]),
4731
4760
  attemptedAction: _enum([
@@ -4738,6 +4767,7 @@ const targetSummarySchema = strict({
4738
4767
  });
4739
4768
  const targetDetailsSchema = targetSummarySchema.extend({
4740
4769
  managedRoot: string().min(1).nullable(),
4770
+ sourceRoot: string().min(1).nullable(),
4741
4771
  sourceOid: oidSchema,
4742
4772
  currentBranch: string().nullable()
4743
4773
  }).strict();
@@ -4830,12 +4860,14 @@ const preflightSchema = union([
4830
4860
  "not_owner",
4831
4861
  "not_ready_for_review",
4832
4862
  "stale_target",
4863
+ "stale_local",
4833
4864
  "stale_isolated",
4834
4865
  "project_acceptance_busy",
4835
4866
  "checkout_unavailable",
4836
4867
  "git_error"
4837
4868
  ]),
4838
- message: string()
4869
+ message: string(),
4870
+ blocker: acceptanceBlockerSchema.optional()
4839
4871
  })
4840
4872
  ]);
4841
4873
  const preflightResponseSchema = strict({ preflight: preflightSchema });
@@ -4958,8 +4990,61 @@ var remote_default = WORKTREE_CONSOLE_REMOTE;
4958
4990
 
4959
4991
  //#endregion
4960
4992
  //#region src/client/actions.ts
4993
+ function samePath(left, right) {
4994
+ const normalize = (value) => value.replace(/[\\/]+$/u, "").replaceAll("\\", "/");
4995
+ const normalizedLeft = normalize(left);
4996
+ const normalizedRight = normalize(right);
4997
+ return /^[A-Za-z]:\//u.test(normalizedLeft) || /^[A-Za-z]:\//u.test(normalizedRight) ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight;
4998
+ }
4999
+ /** Open a Session only when the Harness list proves both its identity and cwd. */
5000
+ function openExistingSession(services, sessionId, expectedCwd) {
5001
+ const summary = services.sessions.list.getSnapshot().byId[sessionId];
5002
+ if (summary?.cwd === void 0 || !samePath(summary.cwd, expectedCwd)) return false;
5003
+ services.sessions.open(sessionId);
5004
+ return true;
5005
+ }
5006
+ async function waitForProjectedSessionPath(services, sessionId, expectedCwd, isActive) {
5007
+ return new Promise((resolvePromise, rejectPromise) => {
5008
+ let settled = false;
5009
+ let unsubscribe = () => {};
5010
+ const settle = (result, error) => {
5011
+ if (settled) return;
5012
+ settled = true;
5013
+ globalThis.clearTimeout(timer);
5014
+ unsubscribe();
5015
+ if (error) rejectPromise(error);
5016
+ else resolvePromise(result);
5017
+ };
5018
+ const inspect = () => {
5019
+ if (!isActive()) {
5020
+ settle(false);
5021
+ return;
5022
+ }
5023
+ const summary = services.sessions.list.getSnapshot().byId[sessionId];
5024
+ if (summary?.cwd === void 0) return;
5025
+ if (!samePath(summary.cwd, expectedCwd)) {
5026
+ settle(false, /* @__PURE__ */ new Error(`Harness 新建 Session ${sessionId} 的 cwd 与 Host 记录不一致。`));
5027
+ return;
5028
+ }
5029
+ settle(true);
5030
+ };
5031
+ const timer = globalThis.setTimeout(() => {
5032
+ settle(false, /* @__PURE__ */ new Error(`Harness 未投影新建 Session ${sessionId} 的可信 cwd。`));
5033
+ }, 2e3);
5034
+ const dispose = services.sessions.list.subscribe(inspect);
5035
+ unsubscribe = dispose;
5036
+ if (settled) dispose();
5037
+ else inspect();
5038
+ });
5039
+ }
4961
5040
  /** Idempotently register the managed root, create the preallocated Session, and navigate. */
4962
5041
  async function openIsolatedTarget(services, payload, isActive = () => true) {
5042
+ if (!isActive()) return;
5043
+ if (services.sessions.list.getSnapshot().byId[payload.targetSessionId] !== void 0) {
5044
+ if (!isActive()) return;
5045
+ if (!openExistingSession(services, payload.targetSessionId, payload.managedRoot)) throw new Error(`Harness 现有 Session ${payload.targetSessionId} 的 cwd 与 Host 记录不一致。`);
5046
+ return;
5047
+ }
4963
5048
  const workspace = await services.workspaces.create({ path: payload.managedRoot });
4964
5049
  if (!isActive()) return;
4965
5050
  if (workspace.path !== payload.managedRoot) throw new Error(`Harness 注册的工作目录与 Host 记录不一致:${workspace.path}`);
@@ -4968,9 +5053,38 @@ async function openIsolatedTarget(services, payload, isActive = () => true) {
4968
5053
  sessionId: payload.targetSessionId
4969
5054
  });
4970
5055
  if (sessionId !== payload.targetSessionId) throw new Error(`Harness 创建了非预期 Session ${sessionId};应为 ${payload.targetSessionId}`);
5056
+ if (!await waitForProjectedSessionPath(services, sessionId, payload.managedRoot, isActive)) return;
4971
5057
  if (!isActive()) return;
4972
5058
  services.sessions.open(sessionId);
4973
5059
  }
5060
+ /**
5061
+ * Inspect a path-free Host authorization, verify exact checkout/owner identity,
5062
+ * then reuse the cwd-validated Session navigation path.
5063
+ */
5064
+ async function openAuthorizedWorktreeTarget(adapter, services, callerSessionId, expected, isActive = () => true) {
5065
+ const outcome = await adapter.inspect({
5066
+ sessionId: callerSessionId,
5067
+ checkoutId: expected.checkoutId
5068
+ });
5069
+ if (!isActive()) return;
5070
+ if (!outcome.ok) throw new Error(`${outcome.error.code}: ${outcome.error.message}`);
5071
+ const target = outcome.value.target;
5072
+ if (!target.capabilities.inspect || !target.capabilities.open) throw new Error("最新 Host 状态已不允许打开该 Worktree。");
5073
+ if (target.checkoutId !== expected.checkoutId || target.ownerSessionId !== expected.ownerSessionId || target.targetSessionId !== expected.ownerSessionId) throw new Error("Host 检查结果中的 Worktree 身份不一致。");
5074
+ if (target.managedRoot === null) throw new Error("Host 未提供可验证的 Worktree 路径。");
5075
+ if (!isActive()) return;
5076
+ await openIsolatedTarget(services, {
5077
+ managedRoot: target.managedRoot,
5078
+ targetSessionId: expected.ownerSessionId
5079
+ }, isActive);
5080
+ }
5081
+ /** Prefill a visible recovery request. This never invokes command/send/followup. */
5082
+ function prefillSessionDraft(services, sessionId, text) {
5083
+ const binding = services.sessions.binding(sessionId);
5084
+ if (binding?.ctx === void 0 || services.conversation === void 0) return false;
5085
+ services.conversation.input.for(binding.ctx).setDraft(text);
5086
+ return true;
5087
+ }
4974
5088
 
4975
5089
  //#endregion
4976
5090
  //#region src/client/model.ts
@@ -5209,6 +5323,256 @@ function requestWorktreeReviewRefresh(sessionId) {
5209
5323
  window.dispatchEvent(new CustomEvent(WORKTREE_REVIEW_REFRESH_EVENT, { detail: { sessionId } }));
5210
5324
  }
5211
5325
 
5326
+ //#endregion
5327
+ //#region src/client/review-console/DeliveryProof.tsx
5328
+ function shortOid$2(value) {
5329
+ return value.slice(0, 8);
5330
+ }
5331
+ function validationLabel$1(status) {
5332
+ if (status === "passed") return "验证通过";
5333
+ if (status === "failed") return "验证失败";
5334
+ if (status === "partial") return "部分验证";
5335
+ if (status === "not_run") return "未运行验证";
5336
+ return "无验证摘要";
5337
+ }
5338
+ function DeliveryProof({ target, compact = false }) {
5339
+ const proof = target.deliveryProof;
5340
+ if (!proof) return null;
5341
+ const lifecycle$1 = target.state === "delivered" ? "环境已清理" : target.state === "retained" ? `环境已保留${target.expiresAt ? `至 ${new Date(target.expiresAt).toLocaleString()}` : ""}` : target.state === "cleanup_pending" ? "Commit 已创建,环境清理待完成" : "交付证据已记录";
5342
+ if (compact) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5343
+ className: "dsh-wt-delivery-proof dsh-wt-delivery-proof-compact",
5344
+ children: [
5345
+ "Commit ",
5346
+ target.commitOid ? shortOid$2(target.commitOid) : "无新增 Commit",
5347
+ " · ",
5348
+ proof.localBranch ?? "detached",
5349
+ "@",
5350
+ shortOid$2(proof.localHeadAfter),
5351
+ " · ",
5352
+ proof.changedFiles.length,
5353
+ " 个文件 · ",
5354
+ lifecycle$1
5355
+ ]
5356
+ });
5357
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
5358
+ className: "dsh-wt-delivery-proof",
5359
+ "aria-label": "交付证明",
5360
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "Delivery Proof" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: lifecycle$1 })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", { children: [
5361
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: "Commit" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: target.commitOid ?? "无新增 Commit" }) })] }),
5362
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: "Local" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dd", { children: [
5363
+ proof.localBranch ?? "detached",
5364
+ " · ",
5365
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: shortOid$2(proof.localHeadBefore) }),
5366
+ " → ",
5367
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: shortOid$2(proof.localHeadAfter) })
5368
+ ] })] }),
5369
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: "文件" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dd", { children: [proof.changedFiles.length, " 个"] })] }),
5370
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: "验证" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dd", { children: [validationLabel$1(proof.validationStatus), proof.validationSummary ? ` · ${proof.validationSummary}` : ""] })] }),
5371
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: "Local 历史" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: proof.commitInLocalHistory === true ? "Commit 仍在 Local 历史中" : proof.commitInLocalHistory === false ? "Commit 已不在当前 Local 历史中" : "当前无法确认" })] })
5372
+ ] })]
5373
+ });
5374
+ }
5375
+
5376
+ //#endregion
5377
+ //#region src/client/review-console/PreflightStatus.tsx
5378
+ function shortOid$1(value) {
5379
+ return value.slice(0, 8);
5380
+ }
5381
+ function statusLabel(preflight) {
5382
+ switch (preflight.status) {
5383
+ case "ready": return "同步条件已确认";
5384
+ case "local_advanced": return "Local 已前进,可安全合并";
5385
+ case "already_in_local": return "本轮内容已在 Local";
5386
+ case "conflict": return `发现 ${preflight.conflictingFiles.length} 个冲突文件`;
5387
+ case "blocked": return preflight.message;
5388
+ }
5389
+ }
5390
+ function PreflightStatus({ snapshot, target, compact = false, onRefresh, onResume, onOpenHolder, busy }) {
5391
+ if (snapshot.status === "idle") return null;
5392
+ if (snapshot.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5393
+ className: "dsh-wt-preflight",
5394
+ "data-preflight": "loading",
5395
+ children: "正在执行只读同步预检… Local 不会被修改。"
5396
+ });
5397
+ if (snapshot.status === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5398
+ className: "dsh-wt-preflight",
5399
+ "data-preflight": "error",
5400
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["预检失败:", snapshot.error.message] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5401
+ type: "button",
5402
+ className: "dsh-wt-inline-action",
5403
+ disabled: busy,
5404
+ onClick: onRefresh,
5405
+ children: "重新检查"
5406
+ })]
5407
+ });
5408
+ const preflight = snapshot.preflight;
5409
+ const blocked = preflight.status === "blocked";
5410
+ const stale = blocked && (preflight.reason === "stale_isolated" || preflight.reason === "stale_target");
5411
+ const holder = blocked && preflight.reason === "project_acceptance_busy" ? preflight.blocker ?? target?.reviewSlotHolder : void 0;
5412
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5413
+ className: "dsh-wt-preflight",
5414
+ "data-preflight": preflight.status,
5415
+ children: [
5416
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5417
+ className: "dsh-wt-preflight-head",
5418
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: statusLabel(preflight) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "只读检查 · Local 未修改" })]
5419
+ }),
5420
+ !compact && preflight.status !== "blocked" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
5421
+ className: "dsh-wt-preflight-facts",
5422
+ children: [
5423
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: "Local" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dd", { children: [
5424
+ preflight.localBranch ?? "detached",
5425
+ " · ",
5426
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: shortOid$1(preflight.localHeadOid) })
5427
+ ] })] }),
5428
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: "Worktree" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: shortOid$1(preflight.isolatedHeadOid) }) })] }),
5429
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: "Effective base" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: shortOid$1(preflight.effectiveBaseOid) }) })] }),
5430
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: "变更" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dd", { children: [preflight.changedFiles.length, " 个文件"] })] })
5431
+ ]
5432
+ }) : null,
5433
+ !compact && preflight.status === "conflict" && preflight.conflictingFiles.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
5434
+ className: "dsh-wt-conflict-list",
5435
+ "aria-label": "冲突文件",
5436
+ children: preflight.conflictingFiles.map((path) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: path }) }, path))
5437
+ }) : null,
5438
+ holder ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
5439
+ "占用任务:",
5440
+ holder.checkoutId.slice(0, 8),
5441
+ " · Session ",
5442
+ holder.ownerSessionId.slice(0, 8),
5443
+ " · ",
5444
+ holder.state
5445
+ ] }) : null,
5446
+ preflight.status === "conflict" || blocked ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5447
+ className: "dsh-wt-recovery-actions",
5448
+ children: [
5449
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5450
+ type: "button",
5451
+ className: "dsh-wt-inline-action",
5452
+ disabled: busy,
5453
+ onClick: onRefresh,
5454
+ children: "重新检查"
5455
+ }),
5456
+ stale || preflight.status === "conflict" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5457
+ type: "button",
5458
+ className: "dsh-wt-inline-action",
5459
+ disabled: busy || !target?.capabilities.resumeRevision,
5460
+ onClick: onResume,
5461
+ children: "返回 Worktree 重新生成验收稿"
5462
+ }) : null,
5463
+ holder ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5464
+ type: "button",
5465
+ className: "dsh-wt-inline-action",
5466
+ disabled: busy,
5467
+ onClick: onOpenHolder,
5468
+ children: "打开占用任务"
5469
+ }) : null
5470
+ ]
5471
+ }) : null
5472
+ ]
5473
+ });
5474
+ }
5475
+
5476
+ //#endregion
5477
+ //#region src/client/review-console/preflight-cache.ts
5478
+ const IDLE = { status: "idle" };
5479
+ const caches = /* @__PURE__ */ new WeakMap();
5480
+ function identityKey(identity) {
5481
+ return [
5482
+ identity.sessionId,
5483
+ identity.checkoutId,
5484
+ identity.expectedRevision,
5485
+ identity.expectedReviewId
5486
+ ].join("\0");
5487
+ }
5488
+ function cacheFor(adapter) {
5489
+ let cache = caches.get(adapter);
5490
+ if (!cache) {
5491
+ cache = /* @__PURE__ */ new Map();
5492
+ caches.set(adapter, cache);
5493
+ }
5494
+ return cache;
5495
+ }
5496
+ function entryFor(adapter, identity) {
5497
+ const cache = cacheFor(adapter);
5498
+ const key = identityKey(identity);
5499
+ let entry = cache.get(key);
5500
+ if (!entry) {
5501
+ entry = {
5502
+ snapshot: IDLE,
5503
+ listeners: /* @__PURE__ */ new Set()
5504
+ };
5505
+ cache.set(key, entry);
5506
+ }
5507
+ return entry;
5508
+ }
5509
+ function publish(entry, snapshot) {
5510
+ entry.snapshot = snapshot;
5511
+ for (const listener of entry.listeners) listener();
5512
+ }
5513
+ /** Shared read-only request. `force` bypasses cached success and is mandatory before writes. */
5514
+ async function readReviewPreflight(adapter, identity, force = false) {
5515
+ const entry = entryFor(adapter, identity);
5516
+ if (entry.promise) return entry.promise;
5517
+ if (!force && entry.snapshot.status === "success") return entry.snapshot;
5518
+ publish(entry, { status: "loading" });
5519
+ const promise = adapter.preflight(identity).then((outcome) => {
5520
+ const snapshot = outcome.ok ? {
5521
+ status: "success",
5522
+ preflight: outcome.value.preflight
5523
+ } : {
5524
+ status: "error",
5525
+ error: outcome.error
5526
+ };
5527
+ publish(entry, snapshot);
5528
+ return snapshot;
5529
+ }, (reason) => {
5530
+ const snapshot = {
5531
+ status: "error",
5532
+ error: {
5533
+ code: "transport_unavailable",
5534
+ message: reason instanceof Error ? reason.message : String(reason)
5535
+ }
5536
+ };
5537
+ publish(entry, snapshot);
5538
+ return snapshot;
5539
+ }).finally(() => {
5540
+ entry.promise = void 0;
5541
+ });
5542
+ entry.promise = promise;
5543
+ return promise;
5544
+ }
5545
+ function useReviewPreflight(adapter, identity, enabled) {
5546
+ const key = identity ? identityKey(identity) : "";
5547
+ const subscribe = (0, react.useCallback)((listener) => {
5548
+ if (!adapter || !identity) return () => void 0;
5549
+ const entry = entryFor(adapter, identity);
5550
+ entry.listeners.add(listener);
5551
+ return () => {
5552
+ entry.listeners.delete(listener);
5553
+ };
5554
+ }, [adapter, key]);
5555
+ const getSnapshot = (0, react.useCallback)(() => {
5556
+ if (!adapter || !identity) return IDLE;
5557
+ return entryFor(adapter, identity).snapshot;
5558
+ }, [adapter, key]);
5559
+ const snapshot = (0, react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
5560
+ (0, react.useEffect)(() => {
5561
+ if (adapter && identity && enabled) readReviewPreflight(adapter, identity);
5562
+ }, [
5563
+ adapter,
5564
+ enabled,
5565
+ key
5566
+ ]);
5567
+ return {
5568
+ snapshot,
5569
+ refresh: (0, react.useCallback)(async () => {
5570
+ if (!adapter || !identity) return IDLE;
5571
+ return readReviewPreflight(adapter, identity, true);
5572
+ }, [adapter, key])
5573
+ };
5574
+ }
5575
+
5212
5576
  //#endregion
5213
5577
  //#region src/client/review-console/ReviewActions.tsx
5214
5578
  function isStale(error) {
@@ -5222,7 +5586,7 @@ function preflightMessage(preflight) {
5222
5586
  if (preflight.status === "blocked") return `同步暂时阻塞:${preflight.message}`;
5223
5587
  return "同步预检完成;Local 未修改。";
5224
5588
  }
5225
- function ReviewActions({ review, adapter, identity, target, disabled, unavailableMessage, focusReview, onStale, onTargetChange }) {
5589
+ function ReviewActions({ review, adapter, services, identity, target, disabled, unavailableMessage, focusReview, isActive = () => true, onStale, onTargetChange }) {
5226
5590
  const formId = (0, react.useId)();
5227
5591
  const [submitting, setSubmitting] = (0, react.useState)(null);
5228
5592
  const [commitMode, setCommitMode] = (0, react.useState)(null);
@@ -5232,14 +5596,14 @@ function ReviewActions({ review, adapter, identity, target, disabled, unavailabl
5232
5596
  const [retention, setRetention] = (0, react.useState)("retain_24h");
5233
5597
  const [message, setMessage] = (0, react.useState)(null);
5234
5598
  const [error, setError] = (0, react.useState)(null);
5235
- const [preflight, setPreflight] = (0, react.useState)(null);
5236
5599
  const mutationLock = (0, react.useRef)(false);
5237
5600
  const observedTargetRevision = (0, react.useRef)(target?.revision);
5601
+ const { snapshot: preflightSnapshot, refresh: refreshPreflight } = useReviewPreflight(adapter, identity, Boolean(adapter && identity && !disabled && target?.state === "ready_for_review" && target.capabilities.preflight));
5602
+ const safeReadyPreflight = preflightSnapshot.status === "success" && preflightSnapshot.preflight.status !== "blocked" && preflightSnapshot.preflight.status !== "conflict";
5238
5603
  (0, react.useEffect)(() => {
5239
5604
  setCommitMessage(review.suggestedCommitMessage);
5240
5605
  setRetainEnvironment(false);
5241
5606
  setRetention("retain_24h");
5242
- setPreflight(null);
5243
5607
  }, [review.reviewId, review.suggestedCommitMessage]);
5244
5608
  (0, react.useEffect)(() => {
5245
5609
  const revision = target?.revision;
@@ -5248,7 +5612,6 @@ function ReviewActions({ review, adapter, identity, target, disabled, unavailabl
5248
5612
  if (submitting !== null) return;
5249
5613
  setMessage(null);
5250
5614
  setError(null);
5251
- setPreflight(null);
5252
5615
  }, [submitting, target?.revision]);
5253
5616
  const begin = (mutation) => {
5254
5617
  if (disabled || !adapter || !identity || !target || mutationLock.current) return false;
@@ -5264,28 +5627,44 @@ function ReviewActions({ review, adapter, identity, target, disabled, unavailabl
5264
5627
  };
5265
5628
  const finishError = (nextError) => {
5266
5629
  setError(nextError);
5267
- if (isStale(nextError)) onStale(nextError);
5630
+ if (nextError.code === "stale_target") onStale(nextError);
5631
+ if (nextError.code === "stale_local" || nextError.code === "stale_isolated") {
5632
+ setMessage("状态在写入前发生变化;已停止操作,请按最新只读预检恢复。");
5633
+ refreshPreflight();
5634
+ }
5268
5635
  finish();
5269
5636
  };
5270
5637
  const applyTarget = (nextTarget) => {
5638
+ if (!isActive()) return;
5271
5639
  observedTargetRevision.current = nextTarget.revision;
5272
5640
  onTargetChange(nextTarget);
5273
5641
  if (identity) requestWorktreeReviewRefresh(identity.sessionId);
5274
5642
  };
5275
5643
  const previewLocal = async () => {
5276
5644
  if (!begin("preview") || !adapter || !identity) return;
5277
- const inspected = await adapter.preflight(identity);
5278
- if (!inspected.ok) {
5645
+ const inspected = await readReviewPreflight(adapter, identity, true);
5646
+ if (!isActive()) {
5647
+ finish();
5648
+ return;
5649
+ }
5650
+ if (inspected.status === "error") {
5279
5651
  finishError(inspected.error);
5280
5652
  return;
5281
5653
  }
5282
- setPreflight(inspected.value.preflight);
5283
- setMessage(preflightMessage(inspected.value.preflight));
5284
- if (inspected.value.preflight.status === "conflict" || inspected.value.preflight.status === "blocked") {
5654
+ if (inspected.status !== "success") {
5655
+ finish();
5656
+ return;
5657
+ }
5658
+ setMessage(preflightMessage(inspected.preflight));
5659
+ if (inspected.preflight.status === "conflict" || inspected.preflight.status === "blocked") {
5285
5660
  finish();
5286
5661
  return;
5287
5662
  }
5288
5663
  const outcome = await adapter.preview(identity);
5664
+ if (!isActive()) {
5665
+ finish();
5666
+ return;
5667
+ }
5289
5668
  if (!outcome.ok) {
5290
5669
  finishError(outcome.error);
5291
5670
  return;
@@ -5297,12 +5676,16 @@ function ReviewActions({ review, adapter, identity, target, disabled, unavailabl
5297
5676
  const resumeRevision = async () => {
5298
5677
  if (!begin("resume_revision") || !adapter || !identity) return;
5299
5678
  const outcome = await adapter.resumeRevision(identity);
5679
+ if (!isActive()) {
5680
+ finish();
5681
+ return;
5682
+ }
5300
5683
  if (!outcome.ok) {
5301
5684
  finishError(outcome.error);
5302
5685
  return;
5303
5686
  }
5304
5687
  applyTarget(outcome.value.target);
5305
- setMessage("已恢复编辑;继续发送修改要求即可。Local 未受影响。");
5688
+ setMessage((services ? prefillSessionDraft(services, identity.sessionId, "请重新检查当前 Worktree 的修改,重新运行必要验证,并重新生成验收稿。") : false) ? "已恢复编辑并预填重新验收请求;Local 未受影响。" : "已恢复编辑;请重新检查、验证并生成新的验收稿。Local 未受影响。");
5306
5689
  finish();
5307
5690
  };
5308
5691
  const rollbackPreview = async (resumeRevision$1 = false) => {
@@ -5311,6 +5694,10 @@ function ReviewActions({ review, adapter, identity, target, disabled, unavailabl
5311
5694
  ...identity,
5312
5695
  resumeRevision: resumeRevision$1
5313
5696
  });
5697
+ if (!isActive()) {
5698
+ finish();
5699
+ return;
5700
+ }
5314
5701
  if (!outcome.ok) {
5315
5702
  finishError(outcome.error);
5316
5703
  return;
@@ -5329,7 +5716,27 @@ function ReviewActions({ review, adapter, identity, target, disabled, unavailabl
5329
5716
  commitMessage: value,
5330
5717
  retention: selectedRetention
5331
5718
  };
5719
+ if (mode === "finish") {
5720
+ const inspected = await readReviewPreflight(adapter, identity, true);
5721
+ if (!isActive()) {
5722
+ finish();
5723
+ return;
5724
+ }
5725
+ if (inspected.status === "error") {
5726
+ finishError(inspected.error);
5727
+ return;
5728
+ }
5729
+ if (inspected.status !== "success" || inspected.preflight.status === "blocked" || inspected.preflight.status === "conflict") {
5730
+ setMessage(inspected.status === "success" ? preflightMessage(inspected.preflight) : "同步预检未完成。");
5731
+ finish();
5732
+ return;
5733
+ }
5734
+ }
5332
5735
  const outcome = mode === "finalize_preview" ? await adapter.finalizePreview(request) : await adapter.finalize(request);
5736
+ if (!isActive()) {
5737
+ finish();
5738
+ return;
5739
+ }
5333
5740
  if (!outcome.ok) {
5334
5741
  finishError(outcome.error);
5335
5742
  return;
@@ -5349,6 +5756,10 @@ function ReviewActions({ review, adapter, identity, target, disabled, unavailabl
5349
5756
  confirmDirty: true,
5350
5757
  ...target.state === "preview_active" || target.capabilities.rollbackPreview ? { rollbackPreview: true } : {}
5351
5758
  });
5759
+ if (!isActive()) {
5760
+ finish();
5761
+ return;
5762
+ }
5352
5763
  if (!outcome.ok) {
5353
5764
  finishError(outcome.error);
5354
5765
  return;
@@ -5365,6 +5776,10 @@ function ReviewActions({ review, adapter, identity, target, disabled, unavailabl
5365
5776
  checkoutId: identity.checkoutId,
5366
5777
  expectedRevision: identity.expectedRevision
5367
5778
  });
5779
+ if (!isActive()) {
5780
+ finish();
5781
+ return;
5782
+ }
5368
5783
  if (!outcome.ok) {
5369
5784
  finishError(outcome.error);
5370
5785
  return;
@@ -5373,6 +5788,23 @@ function ReviewActions({ review, adapter, identity, target, disabled, unavailabl
5373
5788
  setMessage(outcome.value.target.state === "delivered" ? "Worktree 环境已清理。" : "清理仍未完成,已保留恢复信息。");
5374
5789
  finish();
5375
5790
  };
5791
+ const openHolder = async () => {
5792
+ const holder = preflightSnapshot.status === "success" && preflightSnapshot.preflight.status === "blocked" && preflightSnapshot.preflight.reason === "project_acceptance_busy" ? preflightSnapshot.preflight.blocker ?? target?.reviewSlotHolder : target?.reviewSlotHolder;
5793
+ if (!holder || !adapter || !identity || !services || mutationLock.current) return;
5794
+ mutationLock.current = true;
5795
+ setSubmitting("open_holder");
5796
+ setError(null);
5797
+ try {
5798
+ await openAuthorizedWorktreeTarget(adapter, services, identity.sessionId, holder, isActive);
5799
+ } catch (reason) {
5800
+ setError({
5801
+ code: "checkout_mismatch",
5802
+ message: reason instanceof Error ? reason.message : String(reason)
5803
+ });
5804
+ } finally {
5805
+ finish();
5806
+ }
5807
+ };
5376
5808
  const live = Boolean(adapter && identity && target);
5377
5809
  const allDisabled = disabled || submitting !== null || !live;
5378
5810
  const ready = target?.state === "ready_for_review";
@@ -5391,7 +5823,7 @@ function ReviewActions({ review, adapter, identity, target, disabled, unavailabl
5391
5823
  const primary = ready ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5392
5824
  type: "button",
5393
5825
  className: "dsh-wt-button dsh-wt-primary",
5394
- disabled: allDisabled || !target.capabilities.preview,
5826
+ disabled: allDisabled || !target.capabilities.preview || !safeReadyPreflight,
5395
5827
  onClick: () => {
5396
5828
  previewLocal();
5397
5829
  },
@@ -5466,7 +5898,7 @@ function ReviewActions({ review, adapter, identity, target, disabled, unavailabl
5466
5898
  type: "button",
5467
5899
  role: "menuitem",
5468
5900
  className: "dsh-wt-more-item",
5469
- disabled: allDisabled || !target.capabilities.finalize,
5901
+ disabled: allDisabled || !target.capabilities.finalize || !safeReadyPreflight,
5470
5902
  onClick: (event) => {
5471
5903
  setCommitMode("finish");
5472
5904
  event.currentTarget.closest("details")?.removeAttribute("open");
@@ -5600,11 +6032,21 @@ function ReviewActions({ review, adapter, identity, target, disabled, unavailabl
5600
6032
  })]
5601
6033
  })
5602
6034
  }),
5603
- preflight ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5604
- className: "dsh-wt-preflight",
5605
- "data-preflight": preflight.status,
5606
- children: preflightMessage(preflight)
6035
+ ready ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PreflightStatus, {
6036
+ snapshot: preflightSnapshot,
6037
+ target,
6038
+ onRefresh: () => {
6039
+ refreshPreflight();
6040
+ },
6041
+ onResume: () => {
6042
+ resumeRevision();
6043
+ },
6044
+ onOpenHolder: () => {
6045
+ openHolder();
6046
+ },
6047
+ busy: submitting !== null
5607
6048
  }) : null,
6049
+ target && (target.state === "cleanup_pending" || terminal) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeliveryProof, { target }) : null,
5608
6050
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5609
6051
  className: "dsh-wt-action-status",
5610
6052
  "aria-live": "polite",
@@ -5653,8 +6095,16 @@ function reviewIsStale(target, identity, review) {
5653
6095
  const expectedRevision = identity?.expectedRevision ?? review.revision;
5654
6096
  return !target.review || target.revision !== expectedRevision || target.review.reviewId !== review.reviewId;
5655
6097
  }
5656
- function WorktreeReviewPanel({ review, adapter, identity, target, onRefresh, onTargetChange, unavailableMessage = "实时 Worktree Console 未连接;连接后即可执行验收操作。" }) {
6098
+ function WorktreeReviewPanel({ review, adapter, services, identity, target, onRefresh, onTargetChange, unavailableMessage = "实时 Worktree Console 未连接;连接后即可执行验收操作。" }) {
5657
6099
  const panelId = (0, react.useId)();
6100
+ const actionScope = identity ? [
6101
+ identity.sessionId,
6102
+ identity.checkoutId,
6103
+ identity.expectedRevision,
6104
+ identity.expectedReviewId
6105
+ ].join("\0") : "";
6106
+ const activeActionScope = (0, react.useRef)(actionScope);
6107
+ activeActionScope.current = actionScope;
5658
6108
  const targetKey = target ? [
5659
6109
  target.checkoutId,
5660
6110
  target.revision,
@@ -5663,6 +6113,16 @@ function WorktreeReviewPanel({ review, adapter, identity, target, onRefresh, onT
5663
6113
  target.commitOid,
5664
6114
  target.retention,
5665
6115
  target.cleanupMessage,
6116
+ target.deliveryProof?.localHeadAfter,
6117
+ target.deliveryProof?.commitInLocalHistory,
6118
+ target.deliveryProof?.validationStatus,
6119
+ target.deliveryProof?.validationSummary,
6120
+ target.reviewSlot,
6121
+ target.reviewSlotHolder?.checkoutId,
6122
+ target.reviewSlotHolder?.ownerSessionId,
6123
+ target.reviewSlotHolder?.state,
6124
+ target.capabilities.preflight,
6125
+ target.capabilities.preview,
5666
6126
  target.capabilities.finalize,
5667
6127
  target.capabilities.discard
5668
6128
  ].join("\0") : "";
@@ -5698,7 +6158,8 @@ function WorktreeReviewPanel({ review, adapter, identity, target, onRefresh, onT
5698
6158
  "preview_detached",
5699
6159
  "cleanup_pending",
5700
6160
  "recovery_required",
5701
- "retained"
6161
+ "retained",
6162
+ "delivered"
5702
6163
  ].includes(currentTarget.state) && !invalidReason);
5703
6164
  const hasValidationDetails = Boolean(review.validationSummary || review.tests.length > 0);
5704
6165
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
@@ -5791,10 +6252,12 @@ function WorktreeReviewPanel({ review, adapter, identity, target, onRefresh, onT
5791
6252
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewActions, {
5792
6253
  review,
5793
6254
  adapter,
6255
+ services,
5794
6256
  identity,
5795
6257
  target: currentTarget,
5796
6258
  disabled: !liveReady,
5797
6259
  unavailableMessage,
6260
+ isActive: () => activeActionScope.current === actionScope,
5798
6261
  onStale: handleStale,
5799
6262
  onTargetChange: handleTargetChange
5800
6263
  })
@@ -5804,7 +6267,7 @@ function WorktreeReviewPanel({ review, adapter, identity, target, onRefresh, onT
5804
6267
 
5805
6268
  //#endregion
5806
6269
  //#region src/client/WorktreeReviewRow.tsx
5807
- function WorktreeReviewRow({ block, sessionId, adapter }) {
6270
+ function WorktreeReviewRow({ block, sessionId, adapter, services }) {
5808
6271
  const model = parseReviewTool(block);
5809
6272
  const payload = model.payload;
5810
6273
  const args = model.args;
@@ -5869,6 +6332,7 @@ function WorktreeReviewRow({ block, sessionId, adapter }) {
5869
6332
  children: [review ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorktreeReviewPanel, {
5870
6333
  review,
5871
6334
  adapter,
6335
+ services,
5872
6336
  identity,
5873
6337
  target: liveTarget,
5874
6338
  unavailableMessage,
@@ -5960,16 +6424,40 @@ const REVIEW_CONSOLE_STYLES = String.raw`
5960
6424
  .dsh-wt-retention-check { display:flex; align-items:center; gap:8px; border:1px solid var(--wt-line); border-radius:8px; padding:8px 9px; font-size:12px; }
5961
6425
  .dsh-wt-retention-select { display:grid; gap:5px; padding:9px; border-radius:8px; background:var(--wt-panel); font-size:11px; }
5962
6426
  .dsh-wt-retention-select select { min-height:32px; border:1px solid var(--wt-line); border-radius:7px; padding:0 8px; background:Canvas; color:inherit; }
5963
- .dsh-wt-preflight { margin-top:7px; padding:7px 9px; border-radius:8px; background:var(--wt-panel); color:var(--wt-muted); font-size:11px; }
6427
+ .dsh-wt-preflight { display:grid; gap:7px; margin-top:7px; padding:8px 9px; border-radius:8px; background:var(--wt-panel); color:var(--wt-muted); font-size:11px; }
5964
6428
  .dsh-wt-preflight[data-preflight="conflict"],
5965
- .dsh-wt-preflight[data-preflight="blocked"] { color:var(--wt-amber); }
6429
+ .dsh-wt-preflight[data-preflight="blocked"],
6430
+ .dsh-wt-preflight[data-preflight="error"] { color:var(--wt-amber); }
6431
+ .dsh-wt-preflight-head { display:flex; align-items:center; justify-content:space-between; gap:8px; }
6432
+ .dsh-wt-preflight-head strong { color:var(--wt-ink); }
6433
+ .dsh-wt-preflight-facts,
6434
+ .dsh-wt-delivery-proof dl { display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:6px 12px; margin:0; }
6435
+ .dsh-wt-preflight-facts > div,
6436
+ .dsh-wt-delivery-proof dl > div { display:grid; grid-template-columns:auto minmax(0, 1fr); gap:5px; min-width:0; }
6437
+ .dsh-wt-preflight-facts dt,
6438
+ .dsh-wt-delivery-proof dt { color:var(--wt-muted); }
6439
+ .dsh-wt-preflight-facts dd,
6440
+ .dsh-wt-delivery-proof dd { min-width:0; margin:0; overflow-wrap:anywhere; }
6441
+ .dsh-wt-conflict-list { display:grid; gap:3px; max-height:116px; margin:0; padding-left:18px; overflow:auto; }
6442
+ .dsh-wt-recovery-actions { display:flex; flex-wrap:wrap; gap:6px; }
6443
+ .dsh-wt-inline-action { border:0; padding:0; background:transparent; color:inherit; font:inherit; text-decoration:underline; cursor:pointer; }
6444
+ .dsh-wt-inline-action:disabled { opacity:.5; cursor:not-allowed; }
6445
+ .dsh-wt-delivery-proof { display:grid; gap:7px; margin-top:8px; padding:8px 9px; border:1px solid color-mix(in srgb, var(--wt-green) 26%, transparent); border-radius:8px; background:color-mix(in srgb, var(--wt-green) 6%, transparent); font-size:11px; }
6446
+ .dsh-wt-delivery-proof header { display:flex; align-items:center; justify-content:space-between; gap:8px; }
6447
+ .dsh-wt-delivery-proof header span { color:var(--wt-muted); }
6448
+ .dsh-wt-delivery-proof-compact { display:block; margin:1px 0 0; padding:0; border:0; background:transparent; color:var(--wt-muted); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
5966
6449
  .dsh-wt-action-status { min-height:1.35em; margin-top:7px; color:var(--wt-muted); font-size:11px; }
5967
6450
  .dsh-wt-review-dock { box-sizing:border-box; display:flex; flex:none; align-items:center; gap:9px; width:calc(100% - 2 * var(--dsh-composer-side-clearance, 16px) - 2 * var(--dsh-composer-dock-inset, 0px)); max-width:calc(var(--dsh-composer-card-max-width, 780px) - 2 * var(--dsh-composer-dock-inset, 0px)); margin:0 auto 8px; padding:7px 9px; border:1px solid color-mix(in srgb, #3275db 22%, transparent); border-radius:9px; background:color-mix(in srgb, #3275db 6%, transparent); color:var(--wt-ink); font-size:12px; }
5968
6451
  .dsh-wt-review-dock-copy { display:grid; min-width:0; flex:1; gap:1px; }
5969
6452
  .dsh-wt-review-dock-copy strong { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
5970
6453
  .dsh-wt-review-dock-copy span { color:var(--wt-muted); font-size:10px; }
5971
6454
  .dsh-wt-review-dock .dsh-wt-review-actions { padding:0; }
5972
- .dsh-wt-review-dock .dsh-wt-action-status { display:none; }
6455
+ .dsh-wt-review-dock .dsh-wt-action-status,
6456
+ .dsh-wt-review-dock .dsh-wt-preflight-facts,
6457
+ .dsh-wt-review-dock .dsh-wt-conflict-list,
6458
+ .dsh-wt-review-dock .dsh-wt-delivery-proof:not(.dsh-wt-delivery-proof-compact) { display:none; }
6459
+ .dsh-wt-review-dock .dsh-wt-preflight { margin:0; padding:4px 6px; }
6460
+ .dsh-wt-review-dock .dsh-wt-preflight-head > span { display:none; }
5973
6461
  .dsh-wt-review-dock .dsh-wt-button { padding:5px 9px; font-size:11px; }
5974
6462
  .dsh-wt-review-dock .dsh-wt-more-trigger { width:29px; height:27px; }
5975
6463
  @media (max-width: 620px) {
@@ -6363,11 +6851,11 @@ function registerPreSessionWorktree(ctx, adapter, services) {
6363
6851
  }
6364
6852
 
6365
6853
  //#endregion
6366
- //#region src/client/target-console/TargetStatusAction.tsx
6367
- const STATE_LABELS = {
6854
+ //#region src/client/target-console/WorktreeConsoleView.tsx
6855
+ const STATE_LABELS$1 = {
6368
6856
  local: "Local",
6369
6857
  creating: "创建中…",
6370
- working: "Worktree",
6858
+ working: "修改中",
6371
6859
  ready_for_review: "待验收",
6372
6860
  preview_active: "Local 验收中",
6373
6861
  preview_detached: "预览待恢复",
@@ -6376,51 +6864,747 @@ const STATE_LABELS = {
6376
6864
  recovery_required: "需要恢复",
6377
6865
  delivered: "已交付"
6378
6866
  };
6379
- /** Read-only Session Target capsule. The Harness header slot exposes no public view-switch action. */
6380
- function TargetStatusAction({ sessionId, adapter }) {
6381
- const [state, setState] = (0, react.useState)("loading");
6382
- const [target, setTarget] = (0, react.useState)(null);
6867
+ function errorText(error) {
6868
+ const next = {
6869
+ refresh: "请刷新以读取最新 Host 状态。",
6870
+ confirm_dirty: "请明确确认脏 Worktree 后重试。",
6871
+ open_recovery: "再次操作前请先查看恢复信息。",
6872
+ retry: "临时故障消失后可以重试。",
6873
+ none: "当前 Session 无权完成此操作。"
6874
+ }[worktreeConsoleErrorMeta(error.code).recovery];
6875
+ return `${error.code}: ${error.message} ${next}`;
6876
+ }
6877
+ function retentionLabel(retention) {
6878
+ if (retention === "retain_24h") return "保留 24 小时";
6879
+ if (retention === "retain_3d") return "保留 3 天";
6880
+ return "手动清理";
6881
+ }
6882
+ function TargetState({ target }) {
6883
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6884
+ className: "dsh-wtc-state",
6885
+ "data-target-state": target.state,
6886
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6887
+ className: "dsh-wtc-state-dot",
6888
+ "aria-hidden": true
6889
+ }), STATE_LABELS$1[target.state]]
6890
+ });
6891
+ }
6892
+ const ATTENTION_RANK = {
6893
+ recovery_required: 0,
6894
+ preview_detached: 1,
6895
+ cleanup_pending: 2,
6896
+ ready_for_review: 3,
6897
+ preview_active: 4,
6898
+ retained: 5,
6899
+ working: 6,
6900
+ creating: 7,
6901
+ delivered: 8,
6902
+ local: 9
6903
+ };
6904
+ /** Source-linked Worktree management surface backed only by WorktreeConsoleAdapter. */
6905
+ function WorktreeConsoleView({ sessionId, adapter, services, focusCheckoutId, onTargetChange }) {
6906
+ const mounted = (0, react.useRef)(true);
6907
+ const request = (0, react.useRef)(0);
6908
+ const sessionGeneration = (0, react.useRef)(0);
6909
+ const [snapshot, setSnapshot] = (0, react.useState)(null);
6910
+ const [loading, setLoading] = (0, react.useState)(true);
6911
+ const [error, setError] = (0, react.useState)(null);
6912
+ const [pendingAction, setPendingAction] = (0, react.useState)(null);
6913
+ const [confirmTarget, setConfirmTarget] = (0, react.useState)(null);
6914
+ const confirmButton = (0, react.useRef)(null);
6915
+ const visibleSnapshot = snapshot?.sessionId === sessionId ? snapshot : null;
6383
6916
  (0, react.useLayoutEffect)(() => {
6384
- let active = true;
6385
- setState("loading");
6386
- setTarget(null);
6387
- adapter.current({ sessionId }).then((outcome) => {
6388
- if (!active) return;
6389
- if (outcome.ok) {
6390
- setTarget(outcome.value.target);
6391
- setState(outcome.value.target.state);
6392
- } else setState("error");
6393
- }, () => {
6394
- if (active) setState("error");
6395
- });
6917
+ mounted.current = true;
6396
6918
  return () => {
6397
- active = false;
6919
+ mounted.current = false;
6398
6920
  };
6921
+ }, []);
6922
+ (0, react.useLayoutEffect)(() => {
6923
+ sessionGeneration.current += 1;
6924
+ request.current += 1;
6925
+ setSnapshot(null);
6926
+ setConfirmTarget(null);
6927
+ setPendingAction(null);
6928
+ setError(null);
6929
+ setLoading(true);
6930
+ }, [sessionId]);
6931
+ const isActive = (generation) => mounted.current && sessionGeneration.current === generation;
6932
+ const refresh = (0, react.useCallback)(async (clearError = true) => {
6933
+ const generation = sessionGeneration.current;
6934
+ const token = ++request.current;
6935
+ setLoading(true);
6936
+ if (clearError) setError(null);
6937
+ try {
6938
+ const [current, list] = await Promise.all([adapter.current({ sessionId }), adapter.list({ sessionId })]);
6939
+ if (!isActive(generation) || token !== request.current) return;
6940
+ if (!current.ok) {
6941
+ setError(errorText(current.error));
6942
+ return;
6943
+ }
6944
+ if (!list.ok) {
6945
+ setError(errorText(list.error));
6946
+ return;
6947
+ }
6948
+ setSnapshot({
6949
+ sessionId,
6950
+ current: current.value.target,
6951
+ list: list.value
6952
+ });
6953
+ } catch (reason) {
6954
+ if (isActive(generation) && token === request.current) setError(reason instanceof Error ? reason.message : String(reason));
6955
+ } finally {
6956
+ if (isActive(generation) && token === request.current) setLoading(false);
6957
+ }
6399
6958
  }, [adapter, sessionId]);
6400
- const label = state === "loading" ? "加载中…" : state === "error" ? "不可用" : STATE_LABELS[state];
6401
- const expiry = state === "retained" && target?.expiresAt ? new Date(target.expiresAt).toLocaleDateString() : null;
6402
- const accessibleLabel = expiry === null ? `Session Target:${label}` : `Session Target:${label},到期 ${expiry}`;
6403
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6404
- className: "dsh-wtc-target-chip",
6405
- "data-target-state": state,
6959
+ (0, react.useEffect)(() => {
6960
+ refresh();
6961
+ }, [refresh]);
6962
+ (0, react.useEffect)(() => {
6963
+ if (confirmTarget !== null) confirmButton.current?.focus();
6964
+ }, [confirmTarget]);
6965
+ const applyMutation = (response, generation) => {
6966
+ if (!isActive(generation)) return;
6967
+ setSnapshot((current) => {
6968
+ if (current === null || current.sessionId !== sessionId) return current;
6969
+ const target = response.target;
6970
+ return {
6971
+ sessionId,
6972
+ current: current.current.checkoutId === target.checkoutId ? {
6973
+ ...current.current,
6974
+ ...target
6975
+ } : current.current,
6976
+ list: {
6977
+ ...current.list,
6978
+ worktrees: current.list.worktrees.map((row) => row.checkoutId === target.checkoutId ? target : row)
6979
+ }
6980
+ };
6981
+ });
6982
+ onTargetChange?.();
6983
+ };
6984
+ const mutationError = (code, message, generation) => {
6985
+ if (!isActive(generation)) return;
6986
+ setError(errorText({
6987
+ code,
6988
+ message
6989
+ }));
6990
+ if (code === "stale_target" || code === "stale_local" || code === "stale_isolated") refresh(false);
6991
+ };
6992
+ const createTarget = async () => {
6993
+ if (pendingAction !== null || visibleSnapshot?.current.capabilities.create !== true) return;
6994
+ const generation = sessionGeneration.current;
6995
+ const sourceProjectId = visibleSnapshot.current.project.id;
6996
+ setPendingAction("create");
6997
+ setError(null);
6998
+ try {
6999
+ const outcome = await adapter.create({ sourceSessionId: sessionId });
7000
+ if (!isActive(generation)) return;
7001
+ if (!outcome.ok) {
7002
+ setError(errorText(outcome.error));
7003
+ return;
7004
+ }
7005
+ const { target, targetSessionId, managedRoot } = outcome.value;
7006
+ if (target.sourceSessionId !== sessionId) throw new Error("Host 返回了属于其他 source Session 的目标。");
7007
+ if (targetSessionId === sessionId) throw new Error("Host 错误地把 source Session 作为 isolated target Session 返回。");
7008
+ if (target.checkoutId === null || target.targetSessionId !== targetSessionId || target.ownerSessionId !== targetSessionId || target.managedRoot !== managedRoot || target.project.id !== sourceProjectId || !target.capabilities.open) throw new Error("Host 返回的 target Session 身份不一致。");
7009
+ await openIsolatedTarget(services, {
7010
+ targetSessionId,
7011
+ managedRoot
7012
+ }, () => isActive(generation));
7013
+ if (!isActive(generation)) return;
7014
+ setSnapshot((current) => current === null || current.sessionId !== sessionId ? current : {
7015
+ sessionId,
7016
+ current: target,
7017
+ list: {
7018
+ ...current.list,
7019
+ worktrees: [...current.list.worktrees.filter((row) => row.checkoutId !== target.checkoutId), (() => {
7020
+ const { managedRoot: _root, sourceRoot: _sourceRoot, sourceOid: _source, currentBranch: _branch, ...summary } = target;
7021
+ return summary;
7022
+ })()]
7023
+ }
7024
+ });
7025
+ onTargetChange?.();
7026
+ refresh();
7027
+ } catch (reason) {
7028
+ if (isActive(generation)) {
7029
+ setError(reason instanceof Error ? reason.message : String(reason));
7030
+ refresh(false);
7031
+ }
7032
+ } finally {
7033
+ if (isActive(generation)) setPendingAction(null);
7034
+ }
7035
+ };
7036
+ const discardTarget = async (target, confirmDirty) => {
7037
+ if (pendingAction !== null || target.checkoutId === null || !target.capabilities.discard) return;
7038
+ const generation = sessionGeneration.current;
7039
+ setConfirmTarget(null);
7040
+ setPendingAction(`discard:${target.checkoutId}`);
7041
+ setError(null);
7042
+ try {
7043
+ const outcome = await adapter.discard({
7044
+ sessionId,
7045
+ checkoutId: target.checkoutId,
7046
+ expectedRevision: target.revision,
7047
+ confirmDirty,
7048
+ ...target.state === "preview_active" || target.capabilities.rollbackPreview ? { rollbackPreview: true } : {}
7049
+ });
7050
+ if (!isActive(generation)) return;
7051
+ if (!outcome.ok) {
7052
+ mutationError(outcome.error.code, outcome.error.message, generation);
7053
+ return;
7054
+ }
7055
+ applyMutation(outcome.value, generation);
7056
+ refresh();
7057
+ } catch (reason) {
7058
+ if (isActive(generation)) setError(reason instanceof Error ? reason.message : String(reason));
7059
+ } finally {
7060
+ if (isActive(generation)) setPendingAction(null);
7061
+ }
7062
+ };
7063
+ const retryCleanup = async (target) => {
7064
+ if (pendingAction !== null || target.checkoutId === null || !target.capabilities.retryCleanup) return;
7065
+ const generation = sessionGeneration.current;
7066
+ setPendingAction(`cleanup:${target.checkoutId}`);
7067
+ setError(null);
7068
+ try {
7069
+ const outcome = await adapter.retryCleanup({
7070
+ sessionId,
7071
+ checkoutId: target.checkoutId,
7072
+ expectedRevision: target.revision
7073
+ });
7074
+ if (!isActive(generation)) return;
7075
+ if (!outcome.ok) {
7076
+ mutationError(outcome.error.code, outcome.error.message, generation);
7077
+ return;
7078
+ }
7079
+ applyMutation(outcome.value, generation);
7080
+ refresh();
7081
+ } catch (reason) {
7082
+ if (isActive(generation)) setError(reason instanceof Error ? reason.message : String(reason));
7083
+ } finally {
7084
+ if (isActive(generation)) setPendingAction(null);
7085
+ }
7086
+ };
7087
+ const openListedTarget = async (target) => {
7088
+ if (pendingAction !== null || target.checkoutId === null || !target.capabilities.open || !target.capabilities.inspect) return;
7089
+ const generation = sessionGeneration.current;
7090
+ setPendingAction(`open:${target.checkoutId}`);
7091
+ setError(null);
7092
+ try {
7093
+ await openAuthorizedWorktreeTarget(adapter, services, sessionId, {
7094
+ checkoutId: target.checkoutId,
7095
+ ownerSessionId: target.ownerSessionId
7096
+ }, () => isActive(generation));
7097
+ } catch (reason) {
7098
+ if (isActive(generation)) {
7099
+ setError(reason instanceof Error ? reason.message : String(reason));
7100
+ refresh(false);
7101
+ }
7102
+ } finally {
7103
+ if (isActive(generation)) setPendingAction(null);
7104
+ }
7105
+ };
7106
+ const orderedWorktrees = visibleSnapshot === null ? [] : [...visibleSnapshot.list.worktrees].sort((left, right) => {
7107
+ const focusedLeft = left.checkoutId === focusCheckoutId ? 0 : 1;
7108
+ const focusedRight = right.checkoutId === focusCheckoutId ? 0 : 1;
7109
+ if (focusedLeft !== focusedRight) return focusedLeft - focusedRight;
7110
+ const rank = ATTENTION_RANK[left.state] - ATTENTION_RANK[right.state];
7111
+ return rank !== 0 ? rank : right.iteration - left.iteration;
7112
+ });
7113
+ if (loading && visibleSnapshot === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7114
+ className: "dsh-wtc-loading",
6406
7115
  role: "status",
6407
7116
  "aria-live": "polite",
6408
- "aria-label": accessibleLabel,
6409
- title: "Session Target 状态",
7117
+ children: "正在加载 Worktree 控制台…"
7118
+ });
7119
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
7120
+ className: "dsh-wtc-console",
7121
+ "aria-label": "Worktree 控制台",
6410
7122
  children: [
6411
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6412
- className: "dsh-wtc-target-dot",
6413
- "aria-hidden": true
7123
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
7124
+ className: "dsh-wtc-console-head",
7125
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7126
+ className: "dsh-wtc-kicker",
7127
+ children: "SESSION TARGET"
7128
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", { children: "关联 Worktrees" })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7129
+ type: "button",
7130
+ className: "dsh-wtc-button",
7131
+ disabled: loading,
7132
+ onClick: () => {
7133
+ refresh();
7134
+ },
7135
+ children: loading ? "刷新中…" : "刷新"
7136
+ })]
6414
7137
  }),
6415
- label,
6416
- expiry ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6417
- className: "dsh-wtc-target-expiry",
6418
- children: ["· ", expiry]
7138
+ error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7139
+ className: "dsh-wtc-error",
7140
+ role: "alert",
7141
+ children: error
7142
+ }) : null,
7143
+ visibleSnapshot ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
7144
+ className: "dsh-wtc-current",
7145
+ "aria-label": "当前目标",
7146
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7147
+ className: "dsh-wtc-label",
7148
+ children: "当前目标"
7149
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: visibleSnapshot.current.project.name })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7150
+ className: "dsh-wtc-current-actions",
7151
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TargetState, { target: visibleSnapshot.current }), visibleSnapshot.current.state !== "recovery_required" && visibleSnapshot.current.capabilities.create ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7152
+ type: "button",
7153
+ className: "dsh-wtc-button dsh-wtc-primary",
7154
+ disabled: pendingAction !== null,
7155
+ onClick: () => {
7156
+ createTarget();
7157
+ },
7158
+ children: pendingAction === "create" ? "创建中…" : "创建 Worktree"
7159
+ }) : null]
7160
+ })]
7161
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
7162
+ className: "dsh-wtc-list-section",
7163
+ "aria-label": "关联 Worktrees",
7164
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7165
+ className: "dsh-wtc-section-head",
7166
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7167
+ className: "dsh-wtc-label",
7168
+ children: "逻辑关联"
7169
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: visibleSnapshot.list.project.name })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7170
+ className: "dsh-wtc-count",
7171
+ children: visibleSnapshot.list.worktrees.length
7172
+ })]
7173
+ }), visibleSnapshot.list.worktrees.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7174
+ className: "dsh-wtc-empty",
7175
+ children: "这个项目还没有受管 Worktree。"
7176
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
7177
+ className: "dsh-wtc-list",
7178
+ children: orderedWorktrees.map((target) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
7179
+ className: "dsh-wtc-row",
7180
+ "data-current-target": target.checkoutId === visibleSnapshot.current.checkoutId || void 0,
7181
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7182
+ className: "dsh-wtc-row-main",
7183
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7184
+ className: "dsh-wtc-row-title",
7185
+ children: [
7186
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TargetState, { target }),
7187
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7188
+ className: "dsh-wtc-row-id",
7189
+ children: target.checkoutId ?? "Local source"
7190
+ }),
7191
+ target.checkoutId === visibleSnapshot.current.checkoutId ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7192
+ className: "dsh-wtc-relation",
7193
+ children: "当前"
7194
+ }) : null,
7195
+ sessionId === target.sourceSessionId && sessionId !== target.ownerSessionId ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7196
+ className: "dsh-wtc-relation",
7197
+ children: "来源"
7198
+ }) : null,
7199
+ sessionId !== target.sourceSessionId && sessionId !== target.ownerSessionId ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7200
+ className: "dsh-wtc-relation",
7201
+ children: "关联任务"
7202
+ }) : null
7203
+ ]
7204
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7205
+ className: "dsh-wtc-facts",
7206
+ children: [
7207
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
7208
+ "第 ",
7209
+ target.iteration,
7210
+ " 轮"
7211
+ ] }),
7212
+ target.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "有未提交修改" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "干净" }),
7213
+ target.retention ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["保留方式:", retentionLabel(target.retention)] }) : null,
7214
+ target.expiresAt ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["到期时间:", new Date(target.expiresAt).toLocaleString()] }) : null,
7215
+ target.cleanupMessage ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7216
+ className: "dsh-wtc-recovery-message",
7217
+ children: target.cleanupMessage
7218
+ }) : null
7219
+ ]
7220
+ })]
7221
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7222
+ className: "dsh-wtc-row-actions",
7223
+ children: [
7224
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7225
+ className: "dsh-wtc-revision",
7226
+ children: ["r", target.revision]
7227
+ }),
7228
+ target.capabilities.open && target.capabilities.inspect && target.checkoutId !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7229
+ type: "button",
7230
+ className: "dsh-wtc-button",
7231
+ "aria-label": `打开 ${target.checkoutId}`,
7232
+ disabled: pendingAction !== null,
7233
+ onClick: () => {
7234
+ openListedTarget(target);
7235
+ },
7236
+ children: pendingAction === `open:${target.checkoutId}` ? "打开中…" : "打开"
7237
+ }) : null,
7238
+ target.capabilities.discard && target.checkoutId !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7239
+ type: "button",
7240
+ className: "dsh-wtc-button dsh-wtc-danger",
7241
+ "aria-label": `放弃 ${target.checkoutId}`,
7242
+ disabled: pendingAction !== null,
7243
+ onClick: () => {
7244
+ if (target.dirty) setConfirmTarget(target);
7245
+ else discardTarget(target, false);
7246
+ },
7247
+ children: pendingAction === `discard:${target.checkoutId}` ? "放弃中…" : "放弃"
7248
+ }) : null,
7249
+ target.capabilities.retryCleanup && target.checkoutId !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7250
+ type: "button",
7251
+ className: "dsh-wtc-button",
7252
+ "aria-label": `重试清理 ${target.checkoutId}`,
7253
+ disabled: pendingAction !== null,
7254
+ onClick: () => {
7255
+ retryCleanup(target);
7256
+ },
7257
+ children: pendingAction === `cleanup:${target.checkoutId}` ? "重试中…" : "重试清理"
7258
+ }) : null
7259
+ ]
7260
+ })]
7261
+ }, target.checkoutId ?? `local:${target.sourceSessionId}`))
7262
+ })]
7263
+ })] }) : null,
7264
+ confirmTarget !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7265
+ className: "dsh-wtc-confirm",
7266
+ role: "alertdialog",
7267
+ "aria-modal": "true",
7268
+ "aria-label": "放弃有修改的 Worktree?",
7269
+ onKeyDown: (event) => {
7270
+ if (event.key !== "Escape") return;
7271
+ event.preventDefault();
7272
+ setConfirmTarget(null);
7273
+ },
7274
+ children: [
7275
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "放弃有修改的 Worktree?" }),
7276
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: sessionId === confirmTarget.sourceSessionId && sessionId !== confirmTarget.ownerSessionId ? `Local source 将放弃预留目标 ${confirmTarget.checkoutId} 及其全部未提交修改。` : confirmTarget.state === "preview_active" || confirmTarget.capabilities.rollbackPreview ? `将先安全撤回 ${confirmTarget.checkoutId} 的 Local Preview;只有撤回成功后才会删除 Worktree。` : `当前 Session 将放弃 Worktree ${confirmTarget.checkoutId} 及其全部未提交修改。` }),
7277
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7278
+ className: "dsh-wtc-confirm-actions",
7279
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7280
+ type: "button",
7281
+ className: "dsh-wtc-button",
7282
+ onClick: () => {
7283
+ setConfirmTarget(null);
7284
+ },
7285
+ children: "取消"
7286
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7287
+ ref: confirmButton,
7288
+ type: "button",
7289
+ className: "dsh-wtc-button dsh-wtc-danger",
7290
+ onClick: () => {
7291
+ discardTarget(confirmTarget, true);
7292
+ },
7293
+ children: "确认放弃修改"
7294
+ })]
7295
+ })
7296
+ ]
6419
7297
  }) : null
6420
7298
  ]
6421
7299
  });
6422
7300
  }
6423
7301
 
7302
+ //#endregion
7303
+ //#region src/client/target-console/WorktreeManagerModal.tsx
7304
+ /** Session-local entry point for the source-linked Worktree management surface. */
7305
+ function WorktreeManagerModal({ open, sessionId, adapter, services, focusCheckoutId, onClose, onTargetChange }) {
7306
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Modal, {
7307
+ open,
7308
+ onClose,
7309
+ title: "关联 Worktrees",
7310
+ closeLabel: "关闭关联 Worktrees",
7311
+ description: "由插件 registry 维护的 source/target 逻辑关联;DSH 仍按各自 cwd 显示独立 Workspace。",
7312
+ className: "dsh-wtc-manager-dialog",
7313
+ contentClassName: "dsh-wtc-manager-content",
7314
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorktreeConsoleView, {
7315
+ sessionId,
7316
+ adapter,
7317
+ services,
7318
+ focusCheckoutId,
7319
+ onTargetChange
7320
+ })
7321
+ });
7322
+ }
7323
+
7324
+ //#endregion
7325
+ //#region src/client/target-console/TargetStatusAction.tsx
7326
+ const STATE_LABELS = {
7327
+ local: "Local",
7328
+ creating: "创建中…",
7329
+ working: "修改中",
7330
+ ready_for_review: "待验收",
7331
+ preview_active: "Local 验收中",
7332
+ preview_detached: "预览待恢复",
7333
+ retained: "已保留",
7334
+ cleanup_pending: "清理中",
7335
+ recovery_required: "需要恢复",
7336
+ delivered: "已交付"
7337
+ };
7338
+ function shortOid(value) {
7339
+ return value === "unversioned" ? value : value.slice(0, 7);
7340
+ }
7341
+ /** Interactive Session Target capsule with source-linked management actions. */
7342
+ function TargetStatusAction({ sessionId, adapter, services }) {
7343
+ const [target, setTarget] = (0, react.useState)(null);
7344
+ const [loading, setLoading] = (0, react.useState)(true);
7345
+ const [error, setError] = (0, react.useState)(null);
7346
+ const [menuOpen, setMenuOpen] = (0, react.useState)(false);
7347
+ const [managerOpen, setManagerOpen] = (0, react.useState)(false);
7348
+ const [cleanupConfirmOpen, setCleanupConfirmOpen] = (0, react.useState)(false);
7349
+ const [pendingAction, setPendingAction] = (0, react.useState)(null);
7350
+ const mounted = (0, react.useRef)(true);
7351
+ const request = (0, react.useRef)(0);
7352
+ (0, react.useEffect)(() => {
7353
+ mounted.current = true;
7354
+ return () => {
7355
+ mounted.current = false;
7356
+ };
7357
+ }, []);
7358
+ const refresh = (0, react.useCallback)(async () => {
7359
+ const token = ++request.current;
7360
+ try {
7361
+ const outcome = await adapter.current({ sessionId });
7362
+ if (!mounted.current || token !== request.current) return;
7363
+ if (outcome.ok) {
7364
+ setTarget(outcome.value.target);
7365
+ setError(null);
7366
+ } else setError(outcome.error.message);
7367
+ } catch (reason) {
7368
+ if (mounted.current && token === request.current) setError(reason instanceof Error ? reason.message : String(reason));
7369
+ } finally {
7370
+ if (mounted.current && token === request.current) setLoading(false);
7371
+ }
7372
+ }, [adapter, sessionId]);
7373
+ (0, react.useEffect)(() => {
7374
+ request.current += 1;
7375
+ setTarget(null);
7376
+ setLoading(true);
7377
+ setMenuOpen(false);
7378
+ setManagerOpen(false);
7379
+ setCleanupConfirmOpen(false);
7380
+ setPendingAction(null);
7381
+ refresh();
7382
+ const listener = (event) => {
7383
+ if (event.detail?.sessionId === sessionId) refresh();
7384
+ };
7385
+ const timer = window.setInterval(() => {
7386
+ refresh();
7387
+ }, 5e3);
7388
+ window.addEventListener(WORKTREE_REVIEW_REFRESH_EVENT, listener);
7389
+ return () => {
7390
+ window.clearInterval(timer);
7391
+ window.removeEventListener(WORKTREE_REVIEW_REFRESH_EVENT, listener);
7392
+ };
7393
+ }, [refresh, sessionId]);
7394
+ const state = target?.state ?? (loading ? "loading" : "error");
7395
+ const stateLabel = state === "loading" ? "加载中…" : state === "error" ? "不可用" : STATE_LABELS[state];
7396
+ const triggerLabel = target?.state === "local" ? "Local" : target ? `Worktree · ${stateLabel}` : stateLabel;
7397
+ const expiry = target?.state === "retained" && target.expiresAt ? new Date(target.expiresAt).toLocaleDateString() : null;
7398
+ const accessibleLabel = expiry === null ? `Session Target:${triggerLabel}` : `Session Target:${triggerLabel},到期 ${expiry}`;
7399
+ const reveal = () => {
7400
+ if (!target?.managedRoot) return;
7401
+ setMenuOpen(false);
7402
+ services.workspaces.openPath(target.managedRoot).catch((reason) => {
7403
+ if (mounted.current) setError(reason instanceof Error ? reason.message : String(reason));
7404
+ });
7405
+ };
7406
+ const openSource = () => {
7407
+ if (!target || target.sourceSessionId === sessionId) return;
7408
+ setMenuOpen(false);
7409
+ if (target.sourceRoot === null || !openExistingSession(services, target.sourceSessionId, target.sourceRoot)) setError("来源 Session 的 cwd 无法与 Host 记录的 Local root 匹配。");
7410
+ };
7411
+ const retryCleanup = async () => {
7412
+ if (!target?.checkoutId || !target.capabilities.retryCleanup || pendingAction !== null) return;
7413
+ setMenuOpen(false);
7414
+ setPendingAction("retry_cleanup");
7415
+ setError(null);
7416
+ try {
7417
+ const outcome = await adapter.retryCleanup({
7418
+ sessionId,
7419
+ checkoutId: target.checkoutId,
7420
+ expectedRevision: target.revision
7421
+ });
7422
+ if (!mounted.current) return;
7423
+ if (!outcome.ok) {
7424
+ setError(outcome.error.message);
7425
+ if (outcome.error.code === "stale_target") refresh();
7426
+ return;
7427
+ }
7428
+ setTarget((current) => current ? {
7429
+ ...current,
7430
+ ...outcome.value.target,
7431
+ ...outcome.value.target.phase === "discarded" ? { managedRoot: null } : {}
7432
+ } : current);
7433
+ requestWorktreeReviewRefresh(sessionId);
7434
+ } catch (reason) {
7435
+ if (mounted.current) setError(reason instanceof Error ? reason.message : String(reason));
7436
+ } finally {
7437
+ if (mounted.current) setPendingAction(null);
7438
+ }
7439
+ };
7440
+ const discard = async () => {
7441
+ if (!target?.checkoutId || !target.capabilities.discard || pendingAction !== null) return;
7442
+ setCleanupConfirmOpen(false);
7443
+ setPendingAction("discard");
7444
+ setError(null);
7445
+ try {
7446
+ const outcome = await adapter.discard({
7447
+ sessionId,
7448
+ checkoutId: target.checkoutId,
7449
+ expectedRevision: target.revision,
7450
+ confirmDirty: true,
7451
+ ...target.state === "preview_active" || target.capabilities.rollbackPreview ? { rollbackPreview: true } : {}
7452
+ });
7453
+ if (!mounted.current) return;
7454
+ if (!outcome.ok) {
7455
+ setError(outcome.error.message);
7456
+ if (outcome.error.code === "stale_target") refresh();
7457
+ return;
7458
+ }
7459
+ setTarget((current) => current ? {
7460
+ ...current,
7461
+ ...outcome.value.target,
7462
+ ...outcome.value.target.phase === "discarded" ? { managedRoot: null } : {}
7463
+ } : current);
7464
+ requestWorktreeReviewRefresh(sessionId);
7465
+ } catch (reason) {
7466
+ if (mounted.current) setError(reason instanceof Error ? reason.message : String(reason));
7467
+ } finally {
7468
+ if (mounted.current) setPendingAction(null);
7469
+ }
7470
+ };
7471
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
7472
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Menu, {
7473
+ open: menuOpen,
7474
+ align: "end",
7475
+ portal: true,
7476
+ compact: true,
7477
+ onClose: () => setMenuOpen(false),
7478
+ items: [
7479
+ {
7480
+ id: "summary",
7481
+ label: target ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7482
+ className: "dsh-wtc-menu-summary",
7483
+ children: [
7484
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: triggerLabel }),
7485
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: target.project.name }),
7486
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
7487
+ "来源 ",
7488
+ shortOid(target.sourceOid),
7489
+ " · 当前 ",
7490
+ shortOid(target.currentOid),
7491
+ " · Iteration ",
7492
+ target.iteration
7493
+ ] }),
7494
+ expiry ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["保留至 ", expiry] }) : null,
7495
+ error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7496
+ className: "dsh-wtc-menu-error",
7497
+ children: error
7498
+ }) : null
7499
+ ]
7500
+ }) : error ?? triggerLabel,
7501
+ disabled: true
7502
+ },
7503
+ {
7504
+ type: "separator",
7505
+ id: "summary-separator"
7506
+ },
7507
+ ...target?.managedRoot && target.capabilities.open ? [{
7508
+ id: "reveal",
7509
+ label: "打开当前工作位置"
7510
+ }] : [],
7511
+ ...target && target.sourceSessionId !== sessionId ? [{
7512
+ id: "source",
7513
+ label: "返回来源 Session"
7514
+ }] : [],
7515
+ ...target ? [{
7516
+ id: "manager",
7517
+ label: "管理关联 Worktrees"
7518
+ }] : []
7519
+ ],
7520
+ footer: target?.capabilities.retryCleanup ? [{
7521
+ id: "retry_cleanup",
7522
+ label: pendingAction === "retry_cleanup" ? "处理中…" : "重试清理环境",
7523
+ disabled: pendingAction !== null
7524
+ }] : target?.capabilities.discard ? [{
7525
+ id: "discard",
7526
+ label: pendingAction === "discard" ? "处理中…" : target.state === "preview_active" ? "撤回验收并清理 Worktree" : "放弃任务并清理 Worktree",
7527
+ danger: true,
7528
+ disabled: pendingAction !== null
7529
+ }] : [],
7530
+ onSelect: (id) => {
7531
+ if (id === "reveal") reveal();
7532
+ if (id === "source") openSource();
7533
+ if (id === "manager") {
7534
+ setMenuOpen(false);
7535
+ setManagerOpen(true);
7536
+ }
7537
+ if (id === "retry_cleanup") retryCleanup();
7538
+ if (id === "discard") {
7539
+ setMenuOpen(false);
7540
+ setCleanupConfirmOpen(true);
7541
+ }
7542
+ },
7543
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7544
+ type: "button",
7545
+ className: "dsh-wtc-target-chip",
7546
+ "data-target-state": state,
7547
+ "aria-expanded": menuOpen,
7548
+ "aria-haspopup": "menu",
7549
+ "aria-label": accessibleLabel,
7550
+ title: "Session Target 与关联 Worktrees",
7551
+ onClick: () => setMenuOpen((current) => !current),
7552
+ children: [
7553
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7554
+ className: "dsh-wtc-target-dot",
7555
+ "aria-hidden": true
7556
+ }),
7557
+ target?.state === "local" || !target ? triggerLabel : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: ["Worktree · ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: stateLabel })] }),
7558
+ expiry ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7559
+ className: "dsh-wtc-target-expiry",
7560
+ children: ["· ", expiry]
7561
+ }) : null,
7562
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7563
+ className: "dsh-wtc-target-chevron",
7564
+ "aria-hidden": true
7565
+ })
7566
+ ]
7567
+ })
7568
+ }),
7569
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorktreeManagerModal, {
7570
+ open: managerOpen,
7571
+ sessionId,
7572
+ adapter,
7573
+ services,
7574
+ focusCheckoutId: target?.checkoutId,
7575
+ onClose: () => setManagerOpen(false),
7576
+ onTargetChange: () => {
7577
+ requestWorktreeReviewRefresh(sessionId);
7578
+ refresh();
7579
+ }
7580
+ }),
7581
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__deepseek_ai_dsh_client_ui_primitives.Modal, {
7582
+ open: cleanupConfirmOpen,
7583
+ onClose: () => setCleanupConfirmOpen(false),
7584
+ title: target?.state === "preview_active" ? "撤回验收并清理 Worktree?" : "放弃任务并清理 Worktree?",
7585
+ closeLabel: "取消清理 Worktree",
7586
+ description: target?.state === "preview_active" ? "Host 会先安全撤回 Local Preview;无法证明可无损撤回时会停止并保留恢复现场。" : "Worktree 中尚未交付的修改会永久丢弃;Local Checkout 不会被静默覆盖。",
7587
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7588
+ className: "dsh-wtc-confirm-actions",
7589
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7590
+ type: "button",
7591
+ className: "dsh-wtc-button",
7592
+ onClick: () => setCleanupConfirmOpen(false),
7593
+ children: "取消"
7594
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7595
+ type: "button",
7596
+ className: "dsh-wtc-button dsh-wtc-danger",
7597
+ disabled: pendingAction !== null,
7598
+ onClick: () => {
7599
+ discard();
7600
+ },
7601
+ children: "确认清理 Worktree"
7602
+ })]
7603
+ })
7604
+ })
7605
+ ] });
7606
+ }
7607
+
6424
7608
  //#endregion
6425
7609
  //#region src/client/review-console/index.ts
6426
7610
  /** Project one persisted review into the shared compact card/dock evidence shape. */
@@ -6445,13 +7629,16 @@ function reviewIdentityFromTarget(sessionId, target) {
6445
7629
  //#endregion
6446
7630
  //#region src/client/target-console/WorktreeReviewStatus.tsx
6447
7631
  /** Domi-style compact delivery status above the native Harness composer. */
6448
- function WorktreeReviewStatus({ session, adapter }) {
7632
+ function WorktreeReviewStatus({ session, adapter, services }) {
6449
7633
  const sessionId = session.sessionId;
6450
7634
  const [target, setTarget] = (0, react.useState)(null);
6451
7635
  const [error, setError] = (0, react.useState)(null);
6452
7636
  const [startingIteration, setStartingIteration] = (0, react.useState)(false);
6453
7637
  const mounted = (0, react.useRef)(true);
6454
- const refreshing = (0, react.useRef)(false);
7638
+ const requestToken = (0, react.useRef)(0);
7639
+ const sessionGeneration = (0, react.useRef)(0);
7640
+ const currentSessionId = (0, react.useRef)(sessionId);
7641
+ currentSessionId.current = sessionId;
6455
7642
  (0, react.useEffect)(() => {
6456
7643
  mounted.current = true;
6457
7644
  return () => {
@@ -6459,22 +7646,23 @@ function WorktreeReviewStatus({ session, adapter }) {
6459
7646
  };
6460
7647
  }, []);
6461
7648
  const refresh = (0, react.useCallback)(async () => {
6462
- if (refreshing.current) return;
6463
- refreshing.current = true;
7649
+ const token = ++requestToken.current;
6464
7650
  try {
6465
7651
  const outcome = await adapter.current({ sessionId });
6466
- if (!mounted.current) return;
7652
+ if (!mounted.current || currentSessionId.current !== sessionId || token !== requestToken.current) return;
6467
7653
  if (outcome.ok) {
6468
7654
  setTarget(outcome.value.target);
6469
7655
  setError(null);
6470
7656
  } else setError(outcome.error.message);
6471
7657
  } catch (reason) {
6472
- if (mounted.current) setError(reason instanceof Error ? reason.message : String(reason));
6473
- } finally {
6474
- refreshing.current = false;
7658
+ if (mounted.current && currentSessionId.current === sessionId && token === requestToken.current) setError(reason instanceof Error ? reason.message : String(reason));
6475
7659
  }
6476
7660
  }, [adapter, sessionId]);
6477
7661
  (0, react.useEffect)(() => {
7662
+ sessionGeneration.current += 1;
7663
+ requestToken.current += 1;
7664
+ setTarget(null);
7665
+ setError(null);
6478
7666
  refresh();
6479
7667
  const listener = (event) => {
6480
7668
  if (event.detail?.sessionId === sessionId) refresh();
@@ -6491,6 +7679,7 @@ function WorktreeReviewStatus({ session, adapter }) {
6491
7679
  if (target?.state === "delivered" && target.checkoutId !== null && target.capabilities.beginNextIteration) {
6492
7680
  const beginNextIteration = async () => {
6493
7681
  if (startingIteration) return;
7682
+ const generation = sessionGeneration.current;
6494
7683
  setStartingIteration(true);
6495
7684
  setError(null);
6496
7685
  try {
@@ -6499,7 +7688,7 @@ function WorktreeReviewStatus({ session, adapter }) {
6499
7688
  checkoutId: target.checkoutId,
6500
7689
  expectedRevision: target.revision
6501
7690
  });
6502
- if (!mounted.current) return;
7691
+ if (!mounted.current || currentSessionId.current !== sessionId || generation !== sessionGeneration.current) return;
6503
7692
  if (!outcome.ok) {
6504
7693
  setError(outcome.error.message);
6505
7694
  if (outcome.error.code === "stale_target") refresh();
@@ -6510,9 +7699,9 @@ function WorktreeReviewStatus({ session, adapter }) {
6510
7699
  ...outcome.value.target
6511
7700
  } : current);
6512
7701
  } catch (reason) {
6513
- if (mounted.current) setError(reason instanceof Error ? reason.message : String(reason));
7702
+ if (mounted.current && currentSessionId.current === sessionId && generation === sessionGeneration.current) setError(reason instanceof Error ? reason.message : String(reason));
6514
7703
  } finally {
6515
- if (mounted.current) setStartingIteration(false);
7704
+ if (mounted.current && currentSessionId.current === sessionId && generation === sessionGeneration.current) setStartingIteration(false);
6516
7705
  }
6517
7706
  };
6518
7707
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
@@ -6527,7 +7716,14 @@ function WorktreeReviewStatus({ session, adapter }) {
6527
7716
  }),
6528
7717
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6529
7718
  className: "dsh-wt-review-dock-copy",
6530
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "本轮已交付,可在原会话继续下一轮修改" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "将安全重建已清理的 Worktree 路径,并保留当前对话。" })]
7719
+ children: [
7720
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "本轮已交付,可在原会话继续下一轮修改" }),
7721
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "将安全重建已清理的 Worktree 路径,并保留当前对话。" }),
7722
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeliveryProof, {
7723
+ target,
7724
+ compact: true
7725
+ })
7726
+ ]
6531
7727
  }),
6532
7728
  error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6533
7729
  className: "dsh-wt-error",
@@ -6556,6 +7752,7 @@ function WorktreeReviewStatus({ session, adapter }) {
6556
7752
  const review = reviewEvidenceFromTarget(target);
6557
7753
  const identity = reviewIdentityFromTarget(sessionId, target);
6558
7754
  if (!review || !identity) return null;
7755
+ const actionGeneration = sessionGeneration.current;
6559
7756
  const focusReview = () => {
6560
7757
  document.querySelector(`[data-worktree-review-id="${CSS.escape(review.reviewId)}"]`)?.scrollIntoView({
6561
7758
  behavior: "smooth",
@@ -6592,11 +7789,13 @@ function WorktreeReviewStatus({ session, adapter }) {
6592
7789
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewActions, {
6593
7790
  review,
6594
7791
  adapter,
7792
+ services,
6595
7793
  identity,
6596
7794
  target,
6597
7795
  disabled: false,
6598
7796
  unavailableMessage: "实时 Worktree Console 未连接。",
6599
7797
  focusReview,
7798
+ isActive: () => mounted.current && currentSessionId.current === sessionId && sessionGeneration.current === actionGeneration,
6600
7799
  onStale: () => {
6601
7800
  refresh();
6602
7801
  },
@@ -6609,14 +7808,16 @@ function WorktreeReviewStatus({ session, adapter }) {
6609
7808
  //#endregion
6610
7809
  //#region src/client/target-console/index.ts
6611
7810
  /** Register the Harness-native Session Target contributions against an injected adapter seam. */
6612
- function registerTargetConsole(ctx, adapter, _services) {
7811
+ function registerTargetConsole(ctx, adapter, services) {
6613
7812
  const HeaderAction = ({ sessionId }) => (0, react.createElement)(TargetStatusAction, {
6614
7813
  sessionId,
6615
- adapter
7814
+ adapter,
7815
+ services
6616
7816
  });
6617
7817
  const ReviewStatus = ({ session }) => (0, react.createElement)(WorktreeReviewStatus, {
6618
7818
  session,
6619
- adapter
7819
+ adapter,
7820
+ services
6620
7821
  });
6621
7822
  ctx.slots.inject("conversation.session.header.actions", () => ctx.slots.register({
6622
7823
  name: "conversation.session.header.actions",
@@ -6649,7 +7850,24 @@ const TARGET_CONSOLE_STYLES = String.raw`
6649
7850
  font-weight: 650;
6650
7851
  line-height: 1;
6651
7852
  white-space: nowrap;
7853
+ cursor: pointer;
6652
7854
  }
7855
+ .dsh-wtc-target-chip:hover { background: color-mix(in srgb, var(--wtc-state) 15%, transparent); }
7856
+ .dsh-wtc-target-chip:focus-visible { outline: 2px solid #5b8fe8; outline-offset: 2px; }
7857
+ .dsh-wtc-target-chevron {
7858
+ width: 6px;
7859
+ height: 6px;
7860
+ margin: -3px 1px 0 2px;
7861
+ flex: 0 0 auto;
7862
+ border-right: 1.5px solid currentColor;
7863
+ border-bottom: 1.5px solid currentColor;
7864
+ opacity: .58;
7865
+ transform: rotate(45deg);
7866
+ }
7867
+ .dsh-wtc-menu-summary { display: grid; gap: 3px; min-width: 250px; text-align: left; white-space: normal; }
7868
+ .dsh-wtc-menu-summary strong { font-size: 12px; color: inherit; }
7869
+ .dsh-wtc-menu-summary span { color: color-mix(in srgb, currentColor 64%, transparent); font-size: 11px; line-height: 1.35; }
7870
+ .dsh-wtc-menu-summary .dsh-wtc-menu-error { color: #c44747; }
6653
7871
  .dsh-wtc-target-dot, .dsh-wtc-state-dot {
6654
7872
  width: 7px;
6655
7873
  height: 7px;
@@ -6719,9 +7937,11 @@ const TARGET_CONSOLE_STYLES = String.raw`
6719
7937
  .dsh-wtc-list { list-style: none; margin: 0; padding: 0; }
6720
7938
  .dsh-wtc-row { position: relative; gap: 14px; min-width: 0; padding: 12px 14px; border-bottom: 1px solid var(--wtc-line); }
6721
7939
  .dsh-wtc-row:last-child { border-bottom: 0; }
7940
+ .dsh-wtc-row[data-current-target="true"] { background: color-mix(in srgb, var(--wtc-blue) 6%, transparent); }
6722
7941
  .dsh-wtc-row-main { min-width: 0; display: grid; gap: 6px; }
6723
7942
  .dsh-wtc-row-title { min-width: 0; gap: 10px; }
6724
7943
  .dsh-wtc-row-id { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: 600 12px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
7944
+ .dsh-wtc-relation { flex: 0 0 auto; padding: 2px 6px; border-radius: 999px; background: var(--wtc-panel); color: var(--wtc-muted); font-size: 10px; font-weight: 650; }
6725
7945
  .dsh-wtc-state { --wtc-state: #728096; display: inline-flex; align-items: center; gap: 6px; flex: 0 0 auto; font-size: 12px; font-weight: 680; }
6726
7946
  .dsh-wtc-state[data-target-state="creating"], .dsh-wtc-state[data-target-state="cleanup_pending"] { --wtc-state: #c47a1c; }
6727
7947
  .dsh-wtc-state[data-target-state="working"] { --wtc-state: #2676dc; }
@@ -6751,15 +7971,15 @@ const TARGET_CONSOLE_STYLES = String.raw`
6751
7971
  .dsh-wtc-danger { color: var(--wtc-red); }
6752
7972
  .dsh-wtc-empty, .dsh-wtc-loading { padding: 24px 14px; color: var(--wtc-muted); text-align: center; }
6753
7973
  .dsh-wtc-error { padding: 10px 12px; border: 1px solid color-mix(in srgb, var(--wtc-red) 35%, transparent); border-radius: 10px; background: color-mix(in srgb, var(--wtc-red) 8%, transparent); color: var(--wtc-red); overflow-wrap: anywhere; }
6754
- .dsh-wtc-inspect { flex: 1 0 100%; display: grid; grid-template-columns: minmax(110px, .25fr) minmax(0, 1fr); gap: 5px 12px; min-width: 0; padding: 9px 10px; border-radius: 9px; background: var(--wtc-panel); }
6755
- .dsh-wtc-inspect code { min-width: 0; overflow-wrap: anywhere; font-size: 11px; }
6756
7974
  .dsh-wtc-confirm { position: absolute; z-index: 2; inset: 12px; align-self: start; display: grid; gap: 10px; max-width: 520px; margin: 64px auto 0; padding: 18px; border: 1px solid color-mix(in srgb, var(--wtc-red) 35%, var(--wtc-line)); border-radius: 14px; background: Canvas; color: CanvasText; box-shadow: 0 20px 60px rgba(0,0,0,.24); }
6757
7975
  .dsh-wtc-confirm p { margin: 0; color: color-mix(in srgb, currentColor 68%, transparent); line-height: 1.5; }
6758
7976
  .dsh-wtc-confirm-actions { display: flex; justify-content: flex-end; gap: 8px; flex-wrap: wrap; }
7977
+ .dsh-wtc-manager-dialog { width: min(960px, calc(100vw - 32px)); max-width: 960px; max-height: min(86vh, 900px); }
7978
+ .dsh-wtc-manager-content { overflow: auto; min-height: 0; }
7979
+ .dsh-wtc-manager-content .dsh-wtc-console { padding: 4px 0 8px; }
6759
7980
  @media (max-width: 620px) {
6760
7981
  .dsh-wtc-current, .dsh-wtc-row { align-items: flex-start; flex-direction: column; }
6761
7982
  .dsh-wtc-current-actions, .dsh-wtc-row-actions { width: 100%; justify-content: flex-start; }
6762
- .dsh-wtc-inspect { grid-template-columns: 1fr; }
6763
7983
  }
6764
7984
  `;
6765
7985