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.
- package/CHANGELOG.md +28 -0
- package/README.en.md +21 -11
- package/README.md +21 -11
- package/lib/adapters/registry.js +7 -1
- package/lib/adapters/registry.js.map +1 -1
- package/lib/client/WorktreeReviewRow.d.ts +1 -1
- package/lib/client/WorktreeReviewRow.js +2 -2
- package/lib/client/WorktreeReviewRow.js.map +1 -1
- package/lib/client/actions.d.ts +25 -4
- package/lib/client/actions.js +104 -0
- package/lib/client/actions.js.map +1 -1
- package/lib/client/review-console/DeliveryProof.d.ts +5 -0
- package/lib/client/review-console/DeliveryProof.js +32 -0
- package/lib/client/review-console/DeliveryProof.js.map +1 -0
- package/lib/client/review-console/PreflightStatus.d.ts +11 -0
- package/lib/client/review-console/PreflightStatus.js +31 -0
- package/lib/client/review-console/PreflightStatus.js.map +1 -0
- package/lib/client/review-console/ReviewActions.d.ts +4 -1
- package/lib/client/review-console/ReviewActions.js +103 -14
- package/lib/client/review-console/ReviewActions.js.map +1 -1
- package/lib/client/review-console/WorktreeReviewPanel.d.ts +3 -1
- package/lib/client/review-console/WorktreeReviewPanel.js +18 -3
- package/lib/client/review-console/WorktreeReviewPanel.js.map +1 -1
- package/lib/client/review-console/preflight-cache.d.ts +19 -0
- package/lib/client/review-console/preflight-cache.js +85 -0
- package/lib/client/review-console/preflight-cache.js.map +1 -0
- package/lib/client/review-console/review-console.styles.js +27 -3
- package/lib/client/review-console/review-console.styles.js.map +1 -1
- package/lib/client/target-console/TargetStatusAction.d.ts +4 -2
- package/lib/client/target-console/TargetStatusAction.js +196 -23
- package/lib/client/target-console/TargetStatusAction.js.map +1 -1
- package/lib/client/target-console/WorktreeConsoleView.d.ts +4 -2
- package/lib/client/target-console/WorktreeConsoleView.js +33 -73
- package/lib/client/target-console/WorktreeConsoleView.js.map +1 -1
- package/lib/client/target-console/WorktreeManagerModal.d.ts +13 -0
- package/lib/client/target-console/WorktreeManagerModal.js +8 -0
- package/lib/client/target-console/WorktreeManagerModal.js.map +1 -0
- package/lib/client/target-console/WorktreeReviewStatus.d.ts +3 -1
- package/lib/client/target-console/WorktreeReviewStatus.js +22 -15
- package/lib/client/target-console/WorktreeReviewStatus.js.map +1 -1
- package/lib/client/target-console/index.d.ts +2 -1
- package/lib/client/target-console/index.js +4 -3
- package/lib/client/target-console/index.js.map +1 -1
- package/lib/client/target-console/target-console.styles.js +22 -3
- package/lib/client/target-console/target-console.styles.js.map +1 -1
- package/lib/client.js +1297 -77
- package/lib/console-contract.d.ts +6 -1
- package/lib/console-contract.js.map +1 -1
- package/lib/console-host/control-plane.js +153 -37
- package/lib/console-host/control-plane.js.map +1 -1
- package/lib/console-host/projection.d.ts +5 -3
- package/lib/console-host/projection.js +43 -10
- package/lib/console-host/projection.js.map +1 -1
- package/lib/console-remote/schemas.js +19 -1
- package/lib/console-remote/schemas.js.map +1 -1
- package/lib/ports.d.ts +3 -0
- package/lib/session-checkout-module.js +31 -4
- package/lib/session-checkout-module.js.map +1 -1
- package/lib/types.d.ts +11 -1
- package/lib/types.js.map +1 -1
- package/package.json +13 -13
- 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 (
|
|
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
|
|
5278
|
-
if (!
|
|
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
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
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("
|
|
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
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
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:
|
|
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"]
|
|
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
|
|
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/
|
|
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: "
|
|
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
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
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
|
-
"
|
|
6409
|
-
|
|
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.
|
|
6412
|
-
className: "dsh-wtc-
|
|
6413
|
-
"
|
|
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
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
children:
|
|
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
|
|
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
|
-
|
|
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: [
|
|
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,
|
|
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
|
|