@anionex/dsh-turn-rewind 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -7,6 +7,8 @@ exports.selectRewindMessage = selectRewindMessage;
7
7
  exports.apply = apply;
8
8
  exports.RewindMessagePortals = RewindMessagePortals;
9
9
  exports.RewindMessageAction = RewindMessageAction;
10
+ exports.TurnRewindSettingsCard = TurnRewindSettingsCard;
11
+ exports.formatBytes = formatBytes;
10
12
  exports.selectRewindMessageTarget = selectRewindMessageTarget;
11
13
  exports.fileRecoveryLabel = fileRecoveryLabel;
12
14
  const jsx_runtime_1 = require("react/jsx-runtime");
@@ -14,6 +16,7 @@ const react_1 = require("react");
14
16
  const react_dom_1 = require("react-dom");
15
17
  const dsh_client_ui_primitives_1 = require("@deepseek-ai/dsh-client-ui-primitives");
16
18
  const PATH = '/turn-rewind';
19
+ const MANAGE_PATH = '/turn-rewind/manage';
17
20
  const STYLE_ID = '@anionex/dsh-turn-rewind';
18
21
  const styles = `
19
22
  .dcl-rewind-tail{display:inline-flex;align-items:center;align-self:center;order:0;height:24px;margin-left:2px}
@@ -42,6 +45,36 @@ const styles = `
42
45
  .dcl-rewind-error{border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary) 30%,transparent);color:var(--dsw-alias-state-error-primary)}
43
46
  .dcl-rewind-backup{box-sizing:border-box;margin:0;padding:10px 12px;border-radius:10px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
44
47
  .dcl-rewind-retry{align-self:flex-start}
48
+ .dcl-trs-card{display:flex;flex-direction:column;gap:16px;width:100%;min-width:0}
49
+ .dcl-trs-section{display:flex;flex-direction:column;gap:10px;min-width:0}
50
+ .dcl-trs-section-title{display:flex;align-items:center;justify-content:space-between;gap:12px;min-width:0}
51
+ .dcl-trs-section-title strong{color:var(--dsw-alias-label-primary);font-size:14px}
52
+ .dcl-trs-section-title-actions{display:inline-flex;align-items:center;gap:8px}
53
+ .dcl-trs-field{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;padding:8px 0;border-bottom:1px solid var(--dsw-alias-border-l1);font-size:13px}
54
+ .dcl-trs-field:last-child{border-bottom:0}
55
+ .dcl-trs-field-label{flex:1 1 200px;min-width:0}
56
+ .dcl-trs-field-label strong{display:block;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600}
57
+ .dcl-trs-field-desc{display:block;margin-top:2px;color:var(--dsw-alias-label-tertiary);font-size:12px}
58
+ .dcl-trs-field-control{display:inline-flex;align-items:center;gap:8px;flex:none}
59
+ .dcl-trs-field-control input,.dcl-trs-field-control select{box-sizing:border-box;min-width:140px;padding:4px 8px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font-size:13px}
60
+ .dcl-trs-field-control input:disabled,.dcl-trs-field-control select:disabled{opacity:.52;cursor:not-allowed}
61
+ .dcl-trs-override{flex:none;padding:1px 8px;border-radius:999px;background:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-bg-layer-0);font-size:11px;line-height:18px}
62
+ .dcl-trs-manage-total{margin:0;color:var(--dsw-alias-label-secondary);font-size:12px;overflow-wrap:anywhere}
63
+ .dcl-trs-workspace{display:flex;flex-direction:column;gap:6px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-1)}
64
+ .dcl-trs-workspace-head{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;min-width:0}
65
+ .dcl-trs-workspace-path{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary);font-size:12px}
66
+ .dcl-trs-workspace-meta{flex:none;color:var(--dsw-alias-label-tertiary);font-size:12px}
67
+ .dcl-trs-badge{flex:none;padding:1px 8px;border-radius:999px;background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-primary);font-size:11px;line-height:18px}
68
+ .dcl-trs-points{display:flex;flex-direction:column;gap:4px;min-width:0;margin:0;padding:0;list-style:none}
69
+ .dcl-trs-point{display:flex;flex-wrap:wrap;align-items:center;gap:4px 12px;padding:6px 8px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;font-size:12px}
70
+ .dcl-trs-point time{flex:1 1 140px;min-width:0;color:var(--dsw-alias-label-secondary)}
71
+ .dcl-trs-point-kind,.dcl-trs-point-size,.dcl-trs-point-files{flex:none;color:var(--dsw-alias-label-tertiary)}
72
+ .dcl-trs-point code{flex:1 1 140px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary)}
73
+ .dcl-trs-status{margin:0;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}
74
+ .dcl-trs-notice{box-sizing:border-box;max-width:100%;margin:0;padding:8px 10px;overflow-wrap:anywhere;border-radius:8px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
75
+ .dcl-trs-notice[data-warning="true"]{background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-primary)}
76
+ .dcl-trs-error{box-sizing:border-box;max-width:100%;margin:0;padding:10px 12px;overflow-wrap:anywhere;word-break:break-word;border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary) 30%,transparent);border-radius:10px;color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}
77
+ .dcl-trs-storage{margin:0;padding:8px 10px;border-radius:8px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-tertiary);font-size:12px;overflow-wrap:anywhere}
45
78
  `;
46
79
  /** Return the rewind anchor and editable text owned by one direct user message. */
47
80
  function selectRewindMessage(node) {
@@ -76,6 +109,13 @@ function apply(ctx) {
76
109
  },
77
110
  }),
78
111
  }, RewindMessagePortals));
112
+ ctx.slots.inject('settings.plugin.item', () => ctx.slots.register({
113
+ name: 'settings.plugin.item',
114
+ key: 'turn-rewind',
115
+ inject: () => ({
116
+ scope: ctx.settingsScope?.bind({ namespace: 'turn-rewind' }),
117
+ }),
118
+ }, TurnRewindSettingsCard));
79
119
  }
80
120
  /** Session-scoped bridge that portals rewind controls into direct user-message action rows. */
81
121
  function RewindMessagePortals({ sessionId, openRestoredSession, useSession }) {
@@ -122,6 +162,7 @@ function RewindMessageAction({ matched, sessionId, openRestoredSession }) {
122
162
  const [completed, setCompleted] = (0, react_1.useState)(null);
123
163
  const loadAbort = (0, react_1.useRef)(null);
124
164
  const applyPending = (0, react_1.useRef)(false);
165
+ const modeTouched = (0, react_1.useRef)(false);
125
166
  (0, react_1.useEffect)(() => () => {
126
167
  loadAbort.current?.abort();
127
168
  loadAbort.current = null;
@@ -138,13 +179,20 @@ function RewindMessageAction({ matched, sessionId, openRestoredSession }) {
138
179
  const response = await fetch(`${PATH}?sessionId=${encodeURIComponent(sessionId)}&messageSeq=${String(matched.messageSeq)}`, {
139
180
  method: 'GET', headers: { accept: 'application/json' }, cache: 'no-store', signal: controller.signal,
140
181
  });
141
- const value = await responseJson(response);
142
- if (loadAbort.current === controller)
143
- setPreview(decodePreview(value));
182
+ const decoded = decodePreview(await responseJson(response));
183
+ if (loadAbort.current === controller) {
184
+ setPreview(decoded);
185
+ if (!modeTouched.current) {
186
+ setMode(decoded.status === 'ready' && decoded.totalChanges > 0 ? 'both' : 'messages');
187
+ }
188
+ }
144
189
  }
145
190
  catch (caught) {
146
- if (!controller.signal.aborted)
147
- setError(messageOf(caught));
191
+ if (!controller.signal.aborted) {
192
+ setPreview({ status: 'failed', error: friendlyError(caught) });
193
+ if (!modeTouched.current)
194
+ setMode('messages');
195
+ }
148
196
  }
149
197
  finally {
150
198
  if (loadAbort.current === controller) {
@@ -157,6 +205,7 @@ function RewindMessageAction({ matched, sessionId, openRestoredSession }) {
157
205
  setOpen(true);
158
206
  setPreview(null);
159
207
  setMode('both');
208
+ modeTouched.current = false;
160
209
  setStale(false);
161
210
  void load();
162
211
  };
@@ -171,6 +220,7 @@ function RewindMessageAction({ matched, sessionId, openRestoredSession }) {
171
220
  const chooseMode = (next) => {
172
221
  if (applying)
173
222
  return;
223
+ modeTouched.current = true;
174
224
  setMode(next);
175
225
  setError(null);
176
226
  setCompleted(null);
@@ -181,16 +231,14 @@ function RewindMessageAction({ matched, sessionId, openRestoredSession }) {
181
231
  const sharedBlocked = (ready?.activeSessionIds.length ?? 0) > 0;
182
232
  const planMissing = hasFileChanges && ready !== null && !sharedBlocked && !driftBlocked
183
233
  && (ready.planId === undefined || ready.confirmation === undefined);
184
- const canApply = ready !== null
234
+ const canApply = preview !== null
185
235
  && !loading
186
236
  && !applying
187
237
  && !loadingDetails
188
238
  && completed === null
189
- && hasFileChanges
190
- && !driftBlocked
191
- && !sharedBlocked
192
- && !planMissing
193
- && !stale;
239
+ && (mode === 'messages'
240
+ ? true
241
+ : ready !== null && hasFileChanges && !driftBlocked && !sharedBlocked && !planMissing && !stale);
194
242
  const loadAllChanges = async () => {
195
243
  if (ready === null || loadingDetails || !ready.truncated)
196
244
  return;
@@ -229,18 +277,20 @@ function RewindMessageAction({ matched, sessionId, openRestoredSession }) {
229
277
  }
230
278
  };
231
279
  const applyRestore = async () => {
232
- if (ready === null || !canApply || applyPending.current)
280
+ if (preview === null || !canApply || applyPending.current)
233
281
  return;
234
282
  const body = {
235
283
  mode,
236
284
  sessionId,
237
- messageSeq: ready.messageSeq,
238
- checkpointId: ready.checkpointId,
285
+ messageSeq: matched.messageSeq,
239
286
  };
240
- if (ready.planId === undefined || ready.confirmation === undefined)
241
- return;
242
- body.planId = ready.planId;
243
- body.confirmation = ready.confirmation;
287
+ if (mode !== 'messages') {
288
+ if (ready === null || ready.planId === undefined || ready.confirmation === undefined)
289
+ return;
290
+ body.checkpointId = ready.checkpointId;
291
+ body.planId = ready.planId;
292
+ body.confirmation = ready.confirmation;
293
+ }
244
294
  applyPending.current = true;
245
295
  setApplying(true);
246
296
  setError(null);
@@ -254,6 +304,18 @@ function RewindMessageAction({ matched, sessionId, openRestoredSession }) {
254
304
  const resultMode = requiredString(result.mode, 'mode');
255
305
  if (resultMode !== mode)
256
306
  throw new Error(`服务器返回了不匹配的回退模式:${resultMode}`);
307
+ if (mode === 'messages') {
308
+ const childSessionId = requiredString(result.sessionId, 'sessionId');
309
+ setCompleted('已创建从这里开始的新对话;项目文件保持不变。');
310
+ try {
311
+ await openRestoredSession(childSessionId, matched.promptText);
312
+ setOpen(false);
313
+ }
314
+ catch (navigationError) {
315
+ setError(`新对话已创建,但没能自动打开:${messageOf(navigationError)}`);
316
+ }
317
+ return;
318
+ }
257
319
  if (mode === 'code') {
258
320
  requiredString(result.rescuePointId, 'rescuePointId');
259
321
  setCompleted('项目文件已恢复;当前对话保持不变。恢复前的文件已自动备份。');
@@ -281,12 +343,217 @@ function RewindMessageAction({ matched, sessionId, openRestoredSession }) {
281
343
  setApplying(false);
282
344
  }
283
345
  };
284
- const actionLabel = mode === 'both' ? '恢复并从这里继续' : '恢复文件';
346
+ const actionLabel = mode === 'both' ? '恢复并从这里继续' : mode === 'code' ? '恢复文件' : '只回溯消息';
285
347
  const radioName = `dcl-rewind-${sessionId}-${String(matched.messageSeq)}`;
286
348
  const branchChanged = ready !== null && ready.checkpointBranch !== ready.currentBranch;
287
- return ((0, jsx_runtime_1.jsxs)("div", { className: "dcl-rewind-tail", children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Tooltip, { label: "\u6062\u590D\u5230\u53D1\u9001\u8FD9\u6761\u6D88\u606F\u4E4B\u524D", side: "bottom", children: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "dcl-rewind-trigger", onClick: show, "aria-label": "\u6062\u590D\u5230\u53D1\u9001\u8FD9\u6761\u6D88\u606F\u4E4B\u524D", children: (0, jsx_runtime_1.jsx)(RewindIcon, { size: 16 }) }) }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Modal, { open: open, onClose: close, title: "\u6062\u590D\u5230\u53D1\u9001\u8FD9\u6761\u6D88\u606F\u4E4B\u524D", closeLabel: "\u5173\u95ED", description: "\u67E5\u770B\u6062\u590D\u7684\u6587\u4EF6\uFF0C\u9009\u62E9\u9002\u5408\u4F60\u7684\u56DE\u9000\u65B9\u5F0F\u3002\u5F53\u524D\u4F1A\u8BDD\u4E0D\u53D7\u5F71\u54CD\u3002", className: "dcl-rewind-dialog", contentClassName: "dcl-rewind-content", footer: ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", onClick: close, disabled: applying, children: "\u53D6\u6D88" }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "primary", onClick: () => { void applyRestore(); }, disabled: !canApply, children: applying ? '正在恢复…' : completed === null ? actionLabel : '已完成' })] })), children: (0, jsx_runtime_1.jsxs)("div", { className: "dcl-rewind-body", children: [loading && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-status", children: "\u6B63\u5728\u68C0\u67E5\u53EF\u4EE5\u6062\u590D\u7684\u9879\u76EE\u6587\u4EF6\u2026" }), preview?.status === 'pending' && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-status", children: "\u8FD9\u6761\u6D88\u606F\u53D1\u9001\u524D\u7684\u6587\u4EF6\u8FD8\u5728\u4FDD\u5B58\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002" }), preview?.status === 'missing' && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-error", children: "\u6CA1\u6709\u4FDD\u5B58\u8FD9\u6761\u6D88\u606F\u53D1\u9001\u524D\u7684\u6587\u4EF6\u3002\u53EF\u80FD\u662F\u5F53\u65F6\u8FD8\u6CA1\u542F\u7528\u56DE\u9000\u529F\u80FD\uFF0C\u6216\u8BB0\u5F55\u5DF2\u8D85\u8FC7\u4FDD\u7559\u671F\u9650\u3002" }), preview?.status === 'skipped' && (0, jsx_runtime_1.jsxs)("p", { className: "dcl-rewind-status", children: ["\u4E3A\u907F\u514D\u963B\u585E\u6D88\u606F\u53D1\u9001\uFF0C\u672C\u8F6E\u6CA1\u6709\u81EA\u52A8\u4FDD\u5B58\u6587\u4EF6\uFF1A", preview.reason] }), preview?.status === 'failed' && (0, jsx_runtime_1.jsxs)("p", { className: "dcl-rewind-error", children: ["\u6CA1\u80FD\u4FDD\u5B58\u8FD9\u6761\u6D88\u606F\u53D1\u9001\u524D\u7684\u6587\u4EF6\uFF1A", preview.error] }), ready !== null && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "dcl-rewind-options", children: [(0, jsx_runtime_1.jsxs)("label", { className: "dcl-rewind-option", "data-selected": mode === 'both', "data-disabled": applying, children: [(0, jsx_runtime_1.jsx)("input", { type: "radio", name: radioName, checked: mode === 'both', disabled: applying, onChange: () => { chooseMode('both'); } }), (0, jsx_runtime_1.jsxs)("span", { className: "dcl-rewind-option-content", children: [(0, jsx_runtime_1.jsx)("strong", { children: "\u6062\u590D\u6587\u4EF6\u5E76\u4ECE\u8FD9\u91CC\u7EE7\u7EED" }), (0, jsx_runtime_1.jsx)("span", { className: "dcl-rewind-option-description", children: "\u521B\u5EFA\u4E00\u4E2A\u4ECE\u8FD9\u91CC\u5F00\u59CB\u7684\u65B0\u4F1A\u8BDD\uFF08\u5F53\u524D\u5BF9\u8BDD\u4F1A\u4FDD\u7559\uFF09" })] })] }), (0, jsx_runtime_1.jsxs)("label", { className: "dcl-rewind-option", "data-selected": mode === 'code', "data-disabled": applying, children: [(0, jsx_runtime_1.jsx)("input", { type: "radio", name: radioName, checked: mode === 'code', disabled: applying, onChange: () => { chooseMode('code'); } }), (0, jsx_runtime_1.jsxs)("span", { className: "dcl-rewind-option-content", children: [(0, jsx_runtime_1.jsx)("strong", { children: "\u53EA\u6062\u590D\u6587\u4EF6" }), (0, jsx_runtime_1.jsx)("span", { className: "dcl-rewind-option-description", children: "\u6062\u590D\u8FD9\u6761\u6D88\u606F\u53D1\u9001\u524D\u7684\u6587\u4EF6\uFF0C\u5F53\u524D\u5BF9\u8BDD\u4FDD\u6301\u4E0D\u53D8\u3002" })] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dcl-rewind-summary", children: [(0, jsx_runtime_1.jsxs)("strong", { children: ["\u5C06\u6062\u590D ", String(ready.totalChanges), " \u4E2A\u6587\u4EF6"] }), (0, jsx_runtime_1.jsx)("span", { children: mode === 'both' ? '恢复后在新对话里继续' : '当前对话保持不变' })] }), sharedBlocked && ((0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-error", children: "\u8FD9\u4E2A\u9879\u76EE\u76EE\u5F55\u8FD8\u6709\u522B\u7684\u5BF9\u8BDD\u6B63\u5728\u8FD0\u884C\u3002\u6062\u590D\u6587\u4EF6\u4F1A\u5F71\u54CD\u5230\u5B83\u4EEC\uFF0C\u56E0\u6B64\u672C\u6B21\u64CD\u4F5C\u5DF2\u88AB\u963B\u6B62\u3002\u8BF7\u7B49\u90A3\u4E9B\u5BF9\u8BDD\u7ED3\u675F\u6216\u505C\u6B62\u540E\uFF0C\u518D\u91CD\u65B0\u68C0\u67E5\u3002" })), ready.headChanged && !ready.operationChanged && ((0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-warning", children: branchChanged
349
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "dcl-rewind-tail", children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Tooltip, { label: "\u6062\u590D\u5230\u53D1\u9001\u8FD9\u6761\u6D88\u606F\u4E4B\u524D", side: "bottom", children: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "dcl-rewind-trigger", onClick: show, "aria-label": "\u6062\u590D\u5230\u53D1\u9001\u8FD9\u6761\u6D88\u606F\u4E4B\u524D", children: (0, jsx_runtime_1.jsx)(RewindIcon, { size: 16 }) }) }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Modal, { open: open, onClose: close, title: "\u6062\u590D\u5230\u53D1\u9001\u8FD9\u6761\u6D88\u606F\u4E4B\u524D", closeLabel: "\u5173\u95ED", description: "\u67E5\u770B\u6062\u590D\u7684\u6587\u4EF6\uFF0C\u9009\u62E9\u9002\u5408\u4F60\u7684\u56DE\u9000\u65B9\u5F0F\u3002\u5F53\u524D\u4F1A\u8BDD\u4E0D\u53D7\u5F71\u54CD\u3002", className: "dcl-rewind-dialog", contentClassName: "dcl-rewind-content", footer: ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", onClick: close, disabled: applying, children: "\u53D6\u6D88" }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "primary", onClick: () => { void applyRestore(); }, disabled: !canApply, children: applying ? '正在恢复…' : completed === null ? actionLabel : '已完成' })] })), children: (0, jsx_runtime_1.jsxs)("div", { className: "dcl-rewind-body", children: [loading && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-status", children: "\u6B63\u5728\u68C0\u67E5\u53EF\u4EE5\u6062\u590D\u7684\u9879\u76EE\u6587\u4EF6\u2026" }), preview?.status === 'pending' && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-status", children: "\u8FD9\u6761\u6D88\u606F\u53D1\u9001\u524D\u7684\u6587\u4EF6\u8FD8\u5728\u4FDD\u5B58\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002" }), preview?.status === 'missing' && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-error", children: "\u6CA1\u6709\u4FDD\u5B58\u8FD9\u6761\u6D88\u606F\u53D1\u9001\u524D\u7684\u6587\u4EF6\u3002\u53EF\u80FD\u662F\u5F53\u65F6\u8FD8\u6CA1\u542F\u7528\u56DE\u9000\u529F\u80FD\u3001\u8BB0\u5F55\u5DF2\u8D85\u8FC7\u4FDD\u7559\u671F\u9650\uFF0C\u6216\u5DF2\u5173\u95ED\u81EA\u52A8\u68C0\u67E5\u70B9\u3002\u4ECD\u53EF\u53EA\u56DE\u6EAF\u6D88\u606F\u3002" }), preview?.status === 'skipped' && (0, jsx_runtime_1.jsxs)("p", { className: "dcl-rewind-status", children: ["\u4E3A\u907F\u514D\u963B\u585E\u6D88\u606F\u53D1\u9001\uFF0C\u672C\u8F6E\u6CA1\u6709\u81EA\u52A8\u4FDD\u5B58\u6587\u4EF6\uFF1A", preview.reason, "\u4ECD\u53EF\u53EA\u56DE\u6EAF\u6D88\u606F\u3002"] }), preview?.status === 'failed' && (0, jsx_runtime_1.jsxs)("p", { className: "dcl-rewind-error", children: ["\u6CA1\u80FD\u4FDD\u5B58\u8FD9\u6761\u6D88\u606F\u53D1\u9001\u524D\u7684\u6587\u4EF6\uFF1A", preview.error, "\u4ECD\u53EF\u53EA\u56DE\u6EAF\u6D88\u606F\u3002"] }), preview !== null && ((0, jsx_runtime_1.jsxs)("div", { className: "dcl-rewind-options", children: [(0, jsx_runtime_1.jsxs)("label", { className: "dcl-rewind-option", "data-selected": mode === 'both', "data-disabled": applying || !hasFileChanges, children: [(0, jsx_runtime_1.jsx)("input", { type: "radio", name: radioName, checked: mode === 'both', disabled: applying || !hasFileChanges, onChange: () => { chooseMode('both'); } }), (0, jsx_runtime_1.jsxs)("span", { className: "dcl-rewind-option-content", children: [(0, jsx_runtime_1.jsx)("strong", { children: "\u6062\u590D\u6587\u4EF6\u5E76\u4ECE\u8FD9\u91CC\u7EE7\u7EED" }), (0, jsx_runtime_1.jsx)("span", { className: "dcl-rewind-option-description", children: "\u521B\u5EFA\u4E00\u4E2A\u4ECE\u8FD9\u91CC\u5F00\u59CB\u7684\u65B0\u4F1A\u8BDD\uFF08\u5F53\u524D\u5BF9\u8BDD\u4F1A\u4FDD\u7559\uFF09" })] })] }), (0, jsx_runtime_1.jsxs)("label", { className: "dcl-rewind-option", "data-selected": mode === 'code', "data-disabled": applying || !hasFileChanges, children: [(0, jsx_runtime_1.jsx)("input", { type: "radio", name: radioName, checked: mode === 'code', disabled: applying || !hasFileChanges, onChange: () => { chooseMode('code'); } }), (0, jsx_runtime_1.jsxs)("span", { className: "dcl-rewind-option-content", children: [(0, jsx_runtime_1.jsx)("strong", { children: "\u53EA\u6062\u590D\u6587\u4EF6" }), (0, jsx_runtime_1.jsx)("span", { className: "dcl-rewind-option-description", children: "\u6062\u590D\u8FD9\u6761\u6D88\u606F\u53D1\u9001\u524D\u7684\u6587\u4EF6\uFF0C\u5F53\u524D\u5BF9\u8BDD\u4FDD\u6301\u4E0D\u53D8\u3002" })] })] }), (0, jsx_runtime_1.jsxs)("label", { className: "dcl-rewind-option", "data-selected": mode === 'messages', "data-disabled": applying, children: [(0, jsx_runtime_1.jsx)("input", { type: "radio", name: radioName, checked: mode === 'messages', disabled: applying, onChange: () => { chooseMode('messages'); } }), (0, jsx_runtime_1.jsxs)("span", { className: "dcl-rewind-option-content", children: [(0, jsx_runtime_1.jsx)("strong", { children: "\u53EA\u56DE\u6EAF\u6D88\u606F\uFF08\u4E0D\u52A8\u6587\u4EF6\uFF09" }), (0, jsx_runtime_1.jsx)("span", { className: "dcl-rewind-option-description", children: "\u521B\u5EFA\u4E00\u4E2A\u4ECE\u8FD9\u91CC\u5F00\u59CB\u7684\u65B0\u4F1A\u8BDD\uFF0C\u9879\u76EE\u6587\u4EF6\u4FDD\u6301\u5F53\u524D\u72B6\u6001\u3002" })] })] })] })), ready !== null && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "dcl-rewind-summary", children: [mode === 'messages'
350
+ ? (0, jsx_runtime_1.jsx)("strong", { children: "\u9879\u76EE\u6587\u4EF6\u4FDD\u6301\u4E0D\u53D8" })
351
+ : (0, jsx_runtime_1.jsxs)("strong", { children: ["\u5C06\u6062\u590D ", String(ready.totalChanges), " \u4E2A\u6587\u4EF6"] }), (0, jsx_runtime_1.jsx)("span", { children: mode === 'both' ? '恢复后在新对话里继续' : mode === 'code' ? '当前对话保持不变' : '仅创建从这里继续的新对话' })] }), sharedBlocked && ((0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-error", children: "\u8FD9\u4E2A\u9879\u76EE\u76EE\u5F55\u8FD8\u6709\u522B\u7684\u5BF9\u8BDD\u6B63\u5728\u8FD0\u884C\u3002\u6062\u590D\u6587\u4EF6\u4F1A\u5F71\u54CD\u5230\u5B83\u4EEC\uFF0C\u56E0\u6B64\u672C\u6B21\u64CD\u4F5C\u5DF2\u88AB\u963B\u6B62\u3002\u8BF7\u7B49\u90A3\u4E9B\u5BF9\u8BDD\u7ED3\u675F\u6216\u505C\u6B62\u540E\uFF0C\u518D\u91CD\u65B0\u68C0\u67E5\u3002" })), ready.headChanged && !ready.operationChanged && ((0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-warning", children: branchChanged
288
352
  ? '当前所在的 Git 分支和发送这条消息时不同。恢复不会切换分支,只会把当时的文件内容恢复到当前分支。'
289
- : '这条消息之后有了新的 Git 提交。恢复只会改文件,不会撤销提交;完成后这些文件会显示为未提交修改。' })), driftBlocked && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-warning", children: "Git \u6B63\u5728\u8FDB\u884C\u5408\u5E76\u3001\u53D8\u57FA\u6216\u7C7B\u4F3C\u64CD\u4F5C\u3002\u8BF7\u5148\u5B8C\u6210\u6216\u53D6\u6D88\u8FD9\u6B21 Git \u64CD\u4F5C\uFF0C\u518D\u91CD\u65B0\u68C0\u67E5\u3002" }), planMissing && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-error", children: "\u6062\u590D\u4FE1\u606F\u5DF2\u7ECF\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5\u3002" }), stale && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-error", children: "\u9879\u76EE\u6587\u4EF6\u5728\u68C0\u67E5\u540E\u53C8\u53D1\u751F\u4E86\u53D8\u5316\u3002\u4E3A\u907F\u514D\u8986\u76D6\u65B0\u4FEE\u6539\uFF0C\u8FD9\u6B21\u6062\u590D\u5DF2\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5\u3002" }), ready.totalChanges === 0 && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-status", children: "\u9879\u76EE\u6587\u4EF6\u5DF2\u7ECF\u662F\u8FD9\u6761\u6D88\u606F\u53D1\u9001\u524D\u7684\u72B6\u6001\uFF0C\u65E0\u9700\u6062\u590D\u3002\u60F3\u91CD\u65B0\u5F00\u59CB\u65F6\uFF0C\u53EF\u4EE5\u4F7F\u7528\u201C\u5206\u652F\u65B0\u5BF9\u8BDD\u201D\u3002" }), ready.changes.length > 0 && ((0, jsx_runtime_1.jsx)("div", { className: "dcl-rewind-files", children: ready.changes.map(change => (0, jsx_runtime_1.jsxs)("div", { className: "dcl-rewind-file", children: [(0, jsx_runtime_1.jsx)("code", { children: change.path }), (0, jsx_runtime_1.jsx)("span", { className: "dcl-rewind-kind", children: fileRecoveryLabel(change.kind) })] }, change.path)) })), ready.truncated && ((0, jsx_runtime_1.jsx)("div", { className: "dcl-rewind-file-actions", children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", size: "sm", onClick: () => { void loadAllChanges(); }, disabled: loadingDetails, children: loadingDetails ? '正在读取全部文件…' : `查看全部 ${String(ready.totalChanges)} 个文件` }) }))] })), completed !== null && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-status", children: completed }), error !== null && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-error", children: error }), error !== null && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-backup", children: "\u6062\u590D\u524D\u4F1A\u81EA\u52A8\u5907\u4EFD\u5F53\u524D\u6587\u4EF6\uFF1B\u82E5\u6062\u590D\u5931\u8D25\u4F1A\u81EA\u52A8\u8FD8\u539F\uFF0C\u9879\u76EE\u4E0D\u4F1A\u505C\u7559\u5728\u53EA\u6062\u590D\u4E86\u4E00\u90E8\u5206\u7684\u72B6\u6001\u3002" }), !loading && (preview?.status !== 'ready' || stale || planMissing || sharedBlocked || driftBlocked) && (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { className: "dcl-rewind-retry", variant: "outline", size: "sm", onClick: () => { void load(); }, children: "\u91CD\u65B0\u68C0\u67E5" })] }) })] }));
353
+ : '这条消息之后有了新的 Git 提交。恢复只会改文件,不会撤销提交;完成后这些文件会显示为未提交修改。' })), driftBlocked && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-warning", children: "Git \u6B63\u5728\u8FDB\u884C\u5408\u5E76\u3001\u53D8\u57FA\u6216\u7C7B\u4F3C\u64CD\u4F5C\u3002\u8BF7\u5148\u5B8C\u6210\u6216\u53D6\u6D88\u8FD9\u6B21 Git \u64CD\u4F5C\uFF0C\u518D\u91CD\u65B0\u68C0\u67E5\u3002" }), planMissing && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-error", children: "\u6062\u590D\u4FE1\u606F\u5DF2\u7ECF\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5\u3002" }), stale && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-error", children: "\u9879\u76EE\u6587\u4EF6\u5728\u68C0\u67E5\u540E\u53C8\u53D1\u751F\u4E86\u53D8\u5316\u3002\u4E3A\u907F\u514D\u8986\u76D6\u65B0\u4FEE\u6539\uFF0C\u8FD9\u6B21\u6062\u590D\u5DF2\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5\u3002" }), ready.totalChanges === 0 && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-status", children: "\u9879\u76EE\u6587\u4EF6\u5DF2\u7ECF\u662F\u8FD9\u6761\u6D88\u606F\u53D1\u9001\u524D\u7684\u72B6\u6001\uFF0C\u65E0\u9700\u6062\u590D\u6587\u4EF6\u3002\u53EF\u9009\u62E9\u300C\u53EA\u56DE\u6EAF\u6D88\u606F\u300D\u91CD\u65B0\u5F00\u59CB\u8FD9\u6BB5\u5BF9\u8BDD\u3002" }), ready.changes.length > 0 && ((0, jsx_runtime_1.jsx)("div", { className: "dcl-rewind-files", children: ready.changes.map(change => (0, jsx_runtime_1.jsxs)("div", { className: "dcl-rewind-file", children: [(0, jsx_runtime_1.jsx)("code", { children: change.path }), (0, jsx_runtime_1.jsx)("span", { className: "dcl-rewind-kind", children: fileRecoveryLabel(change.kind) })] }, change.path)) })), ready.truncated && ((0, jsx_runtime_1.jsx)("div", { className: "dcl-rewind-file-actions", children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", size: "sm", onClick: () => { void loadAllChanges(); }, disabled: loadingDetails, children: loadingDetails ? '正在读取全部文件…' : `查看全部 ${String(ready.totalChanges)} 个文件` }) }))] })), completed !== null && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-status", children: completed }), error !== null && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-error", children: error }), error !== null && (0, jsx_runtime_1.jsx)("p", { className: "dcl-rewind-backup", children: "\u6062\u590D\u524D\u4F1A\u81EA\u52A8\u5907\u4EFD\u5F53\u524D\u6587\u4EF6\uFF1B\u82E5\u6062\u590D\u5931\u8D25\u4F1A\u81EA\u52A8\u8FD8\u539F\uFF0C\u9879\u76EE\u4E0D\u4F1A\u505C\u7559\u5728\u53EA\u6062\u590D\u4E86\u4E00\u90E8\u5206\u7684\u72B6\u6001\u3002" }), !loading && (preview?.status !== 'ready' || stale || planMissing || sharedBlocked || driftBlocked) && (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { className: "dcl-rewind-retry", variant: "outline", size: "sm", onClick: () => { void load(); }, children: "\u91CD\u65B0\u68C0\u67E5" })] }) })] }));
354
+ }
355
+ const EMPTY_SETTINGS_SNAPSHOT = {
356
+ status: 'unavailable', value: undefined, base: undefined, user: undefined, revision: undefined, writable: false, mode: 'host',
357
+ };
358
+ const NUMBER_FIELDS = [
359
+ { key: 'maxRestorePoints', label: '用户/救援恢复点上限', description: '每个工作区保留的最大手动与救援恢复点数量' },
360
+ { key: 'maxTurnCheckpointsPerSession', label: '轮次检查点上限', description: '每个会话保留的最大自动轮次检查点数量(最旧的先清理)' },
361
+ { key: 'maxFiles', label: '单恢复点文件数上限', description: '一个恢复点最多纳入的文件数量' },
362
+ { key: 'maxFileBytes', label: '单文件大小上限', description: '读取单个普通文件的最大字节数' },
363
+ { key: 'maxSnapshotBytes', label: '快照总量上限', description: '单个恢复点读取的最大字节总量' },
364
+ { key: 'planTtlMs', label: '恢复计划有效期(毫秒)', description: '回溯计划从创建到失效的时间' },
365
+ { key: 'staleLockMs', label: '锁回收时长(毫秒)', description: '锁属主消失多久后允许回收该锁' },
366
+ { key: 'turnCheckpointTimeoutMs', label: '检查点超时(毫秒)', description: '单次自动检查点最多阻塞消息发送的时间,超时记录跳过' },
367
+ { key: 'turnCheckpointMaxNewBytes', label: '检查点读取上限', description: '单次 Git 原生检查点最多读取的未缓存字节数' },
368
+ ];
369
+ const CHECKPOINT_MODE_LABELS = {
370
+ off: '关闭(不创建文件检查点)',
371
+ 'git-native': 'Git 原生(推荐大仓库)',
372
+ legacy: '完整快照(兼容模式)',
373
+ };
374
+ const TRUST_LABELS = {
375
+ fast: '快速',
376
+ strict: '严格',
377
+ };
378
+ const POINT_KIND_LABELS = {
379
+ user: '手动',
380
+ rescue: '救援',
381
+ turn: '轮次',
382
+ };
383
+ /** Settings card for the `turn-rewind` namespace: runtime options plus checkpoint management. */
384
+ function TurnRewindSettingsCard({ scope }) {
385
+ const snapshot = (0, react_1.useSyncExternalStore)((0, react_1.useCallback)((notify) => scope?.subscribe(notify) ?? (() => { }), [scope]), (0, react_1.useCallback)(() => scope?.getSnapshot() ?? EMPTY_SETTINGS_SNAPSHOT, [scope]));
386
+ const [manage, setManage] = (0, react_1.useState)(null);
387
+ const [manageLoading, setManageLoading] = (0, react_1.useState)(false);
388
+ const [manageError, setManageError] = (0, react_1.useState)(null);
389
+ const [manageNotice, setManageNotice] = (0, react_1.useState)(null);
390
+ const [formError, setFormError] = (0, react_1.useState)(null);
391
+ const [busy, setBusy] = (0, react_1.useState)(null);
392
+ const [drafts, setDrafts] = (0, react_1.useState)({});
393
+ const [confirmClearAll, setConfirmClearAll] = (0, react_1.useState)(false);
394
+ const [collapsed, setCollapsed] = (0, react_1.useState)(new Set());
395
+ const refreshManage = (0, react_1.useCallback)(async () => {
396
+ setManageLoading(true);
397
+ setManageError(null);
398
+ try {
399
+ const response = await fetch(MANAGE_PATH, { method: 'GET', headers: { accept: 'application/json' }, cache: 'no-store' });
400
+ setManage(decodeManageOverview(await responseJson(response)));
401
+ }
402
+ catch (caught) {
403
+ setManageError(messageOf(caught));
404
+ }
405
+ finally {
406
+ setManageLoading(false);
407
+ }
408
+ }, []);
409
+ (0, react_1.useEffect)(() => { void refreshManage(); }, [refreshManage]);
410
+ const userLayer = snapshot.user !== null && typeof snapshot.user === 'object' ? snapshot.user : {};
411
+ const value = snapshot.value;
412
+ const writable = snapshot.writable && snapshot.status === 'ready';
413
+ const commitEnum = (field, next) => {
414
+ if (scope === undefined || !writable)
415
+ return;
416
+ setFormError(null);
417
+ void scope.set(field, next).catch((caught) => { setFormError(messageOf(caught)); });
418
+ };
419
+ const commitNumber = (field) => {
420
+ if (scope === undefined || !writable)
421
+ return;
422
+ const draft = drafts[field];
423
+ if (draft === undefined)
424
+ return;
425
+ setDrafts((current) => { const next = { ...current }; delete next[field]; return next; });
426
+ if (draft.trim() === '') {
427
+ void scope.unset(field).catch((caught) => { setFormError(messageOf(caught)); });
428
+ return;
429
+ }
430
+ const parsed = Number.parseInt(draft, 10);
431
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) {
432
+ setFormError(`${field} 必须是正整数。`);
433
+ return;
434
+ }
435
+ if (parsed === value?.[field])
436
+ return;
437
+ setFormError(null);
438
+ void scope.set(field, parsed).catch((caught) => { setFormError(messageOf(caught)); });
439
+ };
440
+ const runManageAction = async (body, key) => {
441
+ if (busy !== null)
442
+ return;
443
+ setBusy(key);
444
+ setManageError(null);
445
+ setManageNotice(null);
446
+ setConfirmClearAll(false);
447
+ try {
448
+ const result = await responseJson(await fetch(MANAGE_PATH, {
449
+ method: 'POST',
450
+ headers: { accept: 'application/json', 'content-type': 'application/json' },
451
+ body: JSON.stringify(body),
452
+ }));
453
+ setManageNotice(decodeManageActionNotice(result));
454
+ await refreshManage();
455
+ }
456
+ catch (caught) {
457
+ setManageError(messageOf(caught));
458
+ }
459
+ finally {
460
+ setBusy(null);
461
+ }
462
+ };
463
+ const toggleWorkspace = (workspace) => {
464
+ setCollapsed((current) => {
465
+ const next = new Set(current);
466
+ if (next.has(workspace))
467
+ next.delete(workspace);
468
+ else
469
+ next.add(workspace);
470
+ return next;
471
+ });
472
+ };
473
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "dcl-trs-card", children: [(0, jsx_runtime_1.jsxs)("section", { className: "dcl-trs-section", children: [(0, jsx_runtime_1.jsx)("div", { className: "dcl-trs-section-title", children: (0, jsx_runtime_1.jsx)("strong", { children: "Turn Rewind \u56DE\u9000\u8BBE\u7F6E" }) }), snapshot.status === 'loading' && (0, jsx_runtime_1.jsx)("p", { className: "dcl-trs-status", children: "\u6B63\u5728\u52A0\u8F7D\u8BBE\u7F6E\u2026" }), snapshot.status === 'unavailable' && (0, jsx_runtime_1.jsx)("p", { className: "dcl-trs-status", children: "\u5F53\u524D\u90E8\u7F72\u672A\u63D0\u4F9B\u8BBE\u7F6E\u670D\u52A1\uFF0C\u4EE5\u4E0B\u9009\u9879\u4E0D\u53EF\u7528\u3002" }), snapshot.status === 'ready' && !snapshot.writable && (0, jsx_runtime_1.jsx)("p", { className: "dcl-trs-status", children: "\u8BBE\u7F6E\u4E3A\u53EA\u8BFB\uFF08\u5F53\u524D\u6D4F\u89C8\u5668\u8FDB\u7A0B\u5185\u4FDD\u5B58\uFF09\uFF0C\u4FEE\u6539\u4E0D\u53EF\u7528\u3002" }), value !== undefined && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "dcl-trs-field", children: [(0, jsx_runtime_1.jsxs)("span", { className: "dcl-trs-field-label", children: [(0, jsx_runtime_1.jsx)("strong", { children: "\u81EA\u52A8\u6587\u4EF6\u68C0\u67E5\u70B9" }), (0, jsx_runtime_1.jsx)("span", { className: "dcl-trs-field-desc", children: "\u5173\u95ED\u540E\u4E0D\u518D\u4E3A\u6BCF\u6761\u6D88\u606F\u4FDD\u5B58\u6587\u4EF6\u68C0\u67E5\u70B9\uFF1B\u56DE\u9000\u5F39\u7A97\u4ECD\u53EF\u53EA\u56DE\u6EAF\u6D88\u606F" })] }), (0, jsx_runtime_1.jsxs)("span", { className: "dcl-trs-field-control", children: [(0, jsx_runtime_1.jsx)("select", { value: value.turnCheckpointMode, disabled: !writable || busy !== null, onChange: (event) => { commitEnum('turnCheckpointMode', event.target.value); }, children: Object.keys(CHECKPOINT_MODE_LABELS).map((option) => ((0, jsx_runtime_1.jsx)("option", { value: option, children: CHECKPOINT_MODE_LABELS[option] }, option))) }), userLayer.turnCheckpointMode !== undefined && (0, jsx_runtime_1.jsx)("span", { className: "dcl-trs-override", children: "\u5DF2\u8986\u76D6" }), userLayer.turnCheckpointMode !== undefined && ((0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "ghost", size: "sm", disabled: !writable || busy !== null, onClick: () => { setFormError(null); void scope?.unset('turnCheckpointMode').catch((caught) => { setFormError(messageOf(caught)); }); }, children: "\u6062\u590D\u9ED8\u8BA4" }))] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dcl-trs-field", children: [(0, jsx_runtime_1.jsxs)("span", { className: "dcl-trs-field-label", children: [(0, jsx_runtime_1.jsx)("strong", { children: "\u68C0\u67E5\u70B9\u4FE1\u4EFB\u7B56\u7565" }), (0, jsx_runtime_1.jsx)("span", { className: "dcl-trs-field-desc", children: "\u5FEB\u901F\u4FE1\u4EFB Git/stat \u5143\u6570\u636E\uFF1B\u4E25\u683C\u4F1A\u9010\u4E00\u91CD\u8BFB\u6587\u4EF6\u5185\u5BB9" })] }), (0, jsx_runtime_1.jsxs)("span", { className: "dcl-trs-field-control", children: [(0, jsx_runtime_1.jsx)("select", { value: value.turnCheckpointTrust, disabled: !writable || busy !== null, onChange: (event) => { commitEnum('turnCheckpointTrust', event.target.value); }, children: Object.keys(TRUST_LABELS).map((option) => ((0, jsx_runtime_1.jsx)("option", { value: option, children: TRUST_LABELS[option] }, option))) }), userLayer.turnCheckpointTrust !== undefined && (0, jsx_runtime_1.jsx)("span", { className: "dcl-trs-override", children: "\u5DF2\u8986\u76D6" }), userLayer.turnCheckpointTrust !== undefined && ((0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "ghost", size: "sm", disabled: !writable || busy !== null, onClick: () => { setFormError(null); void scope?.unset('turnCheckpointTrust').catch((caught) => { setFormError(messageOf(caught)); }); }, children: "\u6062\u590D\u9ED8\u8BA4" }))] })] }), NUMBER_FIELDS.map((field) => ((0, jsx_runtime_1.jsxs)("div", { className: "dcl-trs-field", children: [(0, jsx_runtime_1.jsxs)("span", { className: "dcl-trs-field-label", children: [(0, jsx_runtime_1.jsx)("strong", { children: field.label }), (0, jsx_runtime_1.jsx)("span", { className: "dcl-trs-field-desc", children: field.description })] }), (0, jsx_runtime_1.jsxs)("span", { className: "dcl-trs-field-control", children: [(0, jsx_runtime_1.jsx)("input", { type: "number", min: 1, step: 1, value: drafts[field.key] ?? String(value[field.key]), disabled: !writable || busy !== null, onChange: (event) => { setDrafts((current) => ({ ...current, [field.key]: event.target.value })); }, onBlur: () => { commitNumber(field.key); }, onKeyDown: (event) => { if (event.key === 'Enter')
474
+ commitNumber(field.key); } }), userLayer[field.key] !== undefined && (0, jsx_runtime_1.jsx)("span", { className: "dcl-trs-override", children: "\u5DF2\u8986\u76D6" }), userLayer[field.key] !== undefined && ((0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "ghost", size: "sm", disabled: !writable || busy !== null, onClick: () => { setFormError(null); void scope?.unset(field.key).catch((caught) => { setFormError(messageOf(caught)); }); }, children: "\u6062\u590D\u9ED8\u8BA4" }))] })] }, field.key)))] })), formError !== null && (0, jsx_runtime_1.jsx)("p", { className: "dcl-trs-error", children: formError }), (0, jsx_runtime_1.jsxs)("p", { className: "dcl-trs-storage", children: ["\u5B58\u50A8\u76EE\u5F55\uFF08\u5728 cordis.patch.yml \u4E2D\u914D\u7F6E\uFF0C\u4E0D\u53EF\u5728\u7EBF\u4FEE\u6539\uFF09\uFF1A", manage?.storageDir ?? '…'] })] }), (0, jsx_runtime_1.jsxs)("section", { className: "dcl-trs-section", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dcl-trs-section-title", children: [(0, jsx_runtime_1.jsx)("strong", { children: "\u68C0\u67E5\u70B9\u7BA1\u7406" }), (0, jsx_runtime_1.jsxs)("span", { className: "dcl-trs-section-title-actions", children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", size: "sm", onClick: () => { void refreshManage(); }, disabled: manageLoading, children: manageLoading ? '正在刷新…' : '刷新' }), manage !== null && manage.workspaces.length > 0 && (confirmClearAll
475
+ ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", size: "sm", onClick: () => { setConfirmClearAll(false); }, disabled: busy !== null, children: "\u53D6\u6D88" }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "primary", size: "sm", onClick: () => { void runManageAction({ action: 'clear-all' }, 'clear-all'); }, disabled: busy !== null, children: busy === 'clear-all' ? '正在清理…' : '确认清空全部' })] }))
476
+ : (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", size: "sm", onClick: () => { setConfirmClearAll(true); }, disabled: busy !== null, children: "\u4E00\u952E\u6E05\u7A7A\u5168\u90E8" }))] })] }), (0, jsx_runtime_1.jsx)("p", { className: "dcl-trs-manage-total", children: manage === null
477
+ ? '正在读取检查点占用…'
478
+ : `共 ${String(manage.workspaces.length)} 个工作区,${String(manage.totalBytes >= 0 ? manage.workspaces.reduce((total, workspace) => total + workspace.restorePoints.length, 0) : 0)} 个检查点,约 ${formatBytes(manage.totalBytes)}(Git 原生检查点的实际磁盘占用以 Git 回收为准)。` }), manageNotice !== null && (0, jsx_runtime_1.jsx)("p", { className: "dcl-trs-notice", "data-warning": manageNotice.warning, children: manageNotice.message }), manageError !== null && (0, jsx_runtime_1.jsx)("p", { className: "dcl-trs-error", children: manageError }), manage?.workspaces.map((workspace) => ((0, jsx_runtime_1.jsxs)("div", { className: "dcl-trs-workspace", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dcl-trs-workspace-head", children: [(0, jsx_runtime_1.jsx)("span", { className: "dcl-trs-workspace-path", title: workspace.workspace, children: workspace.workspace }), (0, jsx_runtime_1.jsxs)("span", { className: "dcl-trs-workspace-meta", children: [String(workspace.restorePoints.length), " \u4E2A\u68C0\u67E5\u70B9 \u00B7 ", formatBytes(workspace.totalBytes)] }), workspace.recoveryCount > 0 && (0, jsx_runtime_1.jsxs)("span", { className: "dcl-trs-badge", children: [String(workspace.recoveryCount), " \u4E2A\u6062\u590D\u5F85\u5904\u7406"] }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "ghost", size: "sm", onClick: () => { toggleWorkspace(workspace.workspace); }, children: collapsed.has(workspace.workspace) ? '展开' : '收起' }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", size: "sm", onClick: () => { void runManageAction({ action: 'clear-workspace', workspace: workspace.workspace }, `clear:${workspace.workspace}`); }, disabled: busy !== null || workspace.restorePoints.length === 0, children: busy === `clear:${workspace.workspace}` ? '正在清理…' : '清空此项目' })] }), !collapsed.has(workspace.workspace) && workspace.restorePoints.length > 0 && ((0, jsx_runtime_1.jsx)("ul", { className: "dcl-trs-points", children: workspace.restorePoints.map((point) => ((0, jsx_runtime_1.jsxs)("li", { className: "dcl-trs-point", children: [(0, jsx_runtime_1.jsx)("time", { children: formatTime(point.createdAt) }), (0, jsx_runtime_1.jsx)("span", { className: "dcl-trs-point-kind", children: POINT_KIND_LABELS[point.kind] ?? point.kind }), (0, jsx_runtime_1.jsx)("span", { className: "dcl-trs-point-size", children: formatBytes(point.totalBytes) }), (0, jsx_runtime_1.jsxs)("span", { className: "dcl-trs-point-files", children: [String(point.fileCount), " \u4E2A\u6587\u4EF6"] }), point.sessionId !== undefined && (0, jsx_runtime_1.jsx)("code", { children: point.sessionId }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "ghost", size: "sm", onClick: () => { void runManageAction({ action: 'delete', workspace: workspace.workspace, restorePointId: point.id }, `delete:${point.id}`); }, disabled: busy !== null, children: busy === `delete:${point.id}` ? '正在删除…' : '删除' })] }, point.id))) }))] }, workspace.workspace))), manage !== null && manage.workspaces.length === 0 && (0, jsx_runtime_1.jsx)("p", { className: "dcl-trs-status", children: "\u8FD8\u6CA1\u6709\u4EFB\u4F55\u5DF2\u4FDD\u5B58\u7684\u68C0\u67E5\u70B9\u3002" })] })] }));
479
+ }
480
+ function decodeManageOverview(value) {
481
+ const record = recordOf(value);
482
+ const workspacesValue = record.workspaces;
483
+ if (!Array.isArray(workspacesValue))
484
+ throw new Error('管理数据缺少 workspaces');
485
+ return {
486
+ storageDir: requiredString(record.storageDir, 'storageDir'),
487
+ totalBytes: requiredInteger(record.totalBytes, 'totalBytes'),
488
+ workspaces: workspacesValue.map((entry) => {
489
+ const workspace = recordOf(entry);
490
+ const pointsValue = workspace.restorePoints;
491
+ if (!Array.isArray(pointsValue))
492
+ throw new Error('管理数据缺少 restorePoints');
493
+ return {
494
+ workspace: requiredString(workspace.workspace, 'workspace'),
495
+ totalBytes: requiredInteger(workspace.totalBytes, 'totalBytes'),
496
+ recoveryCount: requiredInteger(workspace.recoveryCount, 'recoveryCount'),
497
+ restorePoints: pointsValue.map((pointEntry) => {
498
+ const point = recordOf(pointEntry);
499
+ return {
500
+ id: requiredString(point.id, 'id'),
501
+ kind: requiredString(point.kind, 'kind'),
502
+ format: requiredInteger(point.format, 'format'),
503
+ createdAt: requiredInteger(point.createdAt, 'createdAt'),
504
+ totalBytes: requiredInteger(point.totalBytes, 'totalBytes'),
505
+ fileCount: requiredInteger(point.fileCount, 'fileCount'),
506
+ ...optionalRecordString(point, 'sessionId'),
507
+ ...optionalRecordString(point, 'label'),
508
+ };
509
+ }),
510
+ };
511
+ }),
512
+ };
513
+ }
514
+ function decodeManageActionNotice(value) {
515
+ const record = recordOf(value);
516
+ const action = requiredString(record.action, 'action');
517
+ if (action === 'clear-all') {
518
+ if (!Array.isArray(record.reports))
519
+ throw new Error('清理结果缺少 reports');
520
+ const totals = record.reports.reduce((current, entry) => {
521
+ const report = recordOf(entry);
522
+ return {
523
+ deleted: current.deleted + requiredInteger(report.deletedRestorePoints, 'deletedRestorePoints'),
524
+ retained: current.retained + requiredInteger(report.retainedRestorePoints, 'retainedRestorePoints'),
525
+ };
526
+ }, { deleted: 0, retained: 0 });
527
+ const failures = Array.isArray(record.failures) ? record.failures.length : 0;
528
+ const warning = record.status === 'partial' || totals.retained > 0 || failures > 0;
529
+ return {
530
+ warning,
531
+ message: `已删除 ${String(totals.deleted)} 个检查点${totals.retained > 0 ? `,${String(totals.retained)} 个受保护检查点未删除` : ''}${failures > 0 ? `,${String(failures)} 个工作区清理失败` : ''}。`,
532
+ };
533
+ }
534
+ const deleted = requiredInteger(record.deletedRestorePoints, 'deletedRestorePoints');
535
+ const retained = requiredInteger(record.retainedRestorePoints, 'retainedRestorePoints');
536
+ return {
537
+ warning: retained > 0,
538
+ message: `已删除 ${String(deleted)} 个检查点${retained > 0 ? `,${String(retained)} 个受保护检查点未删除` : ''}。`,
539
+ };
540
+ }
541
+ /** Format one byte count with human-friendly units. */
542
+ function formatBytes(bytes) {
543
+ if (!Number.isFinite(bytes) || bytes < 0)
544
+ return '0 B';
545
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
546
+ let value = bytes;
547
+ let unit = 0;
548
+ while (value >= 1024 && unit < units.length - 1) {
549
+ value /= 1024;
550
+ unit += 1;
551
+ }
552
+ return `${unit === 0 ? String(Math.round(value)) : value.toFixed(value >= 100 ? 0 : 1)} ${units[unit]}`;
553
+ }
554
+ function formatTime(timestamp) {
555
+ const date = new Date(timestamp);
556
+ return Number.isNaN(date.getTime()) ? String(timestamp) : date.toLocaleString();
290
557
  }
291
558
  function decodePreview(value) {
292
559
  const record = recordOf(value);
@@ -359,9 +626,12 @@ function collectPortalTargets(nodes) {
359
626
  if (target === null)
360
627
  continue;
361
628
  const row = rows.get(target.rowKey);
362
- const messageRoot = row?.querySelector('[data-time-hover-root="true"]');
363
- const actions = messageRoot?.lastElementChild;
364
- if (!(actions instanceof HTMLElement) || actions.querySelector(':scope > button') === null)
629
+ const messageRoot = row?.querySelector?.('[data-time-hover-root="true"]');
630
+ // Match the actions container by class or last child element
631
+ const actions = (typeof messageRoot?.querySelector === 'function'
632
+ ? messageRoot.querySelector('[class*="actions"]')
633
+ : undefined) ?? messageRoot?.lastElementChild;
634
+ if (!(actions instanceof HTMLElement))
365
635
  continue;
366
636
  targets.push({ container: actions, matched: target.matched });
367
637
  }
@@ -461,7 +731,7 @@ function friendlyError(error) {
461
731
  case 'REPOSITORY_CHANGED': return '这个项目目录已不属于原来的 Git 工作区,无法恢复。';
462
732
  case 'GIT_OPERATION_CHANGED': return 'Git 正在执行其他操作。请先完成或取消该操作,再重新检查。';
463
733
  case 'RESTORE_POINT_NOT_FOUND': return '没有找到对应的文件状态,可能已被清理。';
464
- case 'NO_CHANGES': return '项目文件已经是这条消息发送前的状态,无需恢复。想重新开始时,可以使用“分支新对话”。';
734
+ case 'NO_CHANGES': return '项目文件已经是这条消息发送前的状态,无需恢复文件。可选择「只回溯消息」重新开始这段对话。';
465
735
  case 'RESTORE_FAILED_ROLLED_BACK': return '恢复未能完成,项目文件已自动还原到操作前的状态。';
466
736
  case 'CONVERSATION_REWIND_FAILED': return '文件已恢复,但无法创建新对话;项目文件已自动还原。';
467
737
  default: return error.message;