@voltage12/dsh-simple-worktree 0.0.0-stage → 0.1.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 ADDED
@@ -0,0 +1,752 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@voltage12/dsh-simple-worktree",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ "use strict";
7
+ var __defProp = Object.defineProperty;
8
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
9
+ var __getOwnPropNames = Object.getOwnPropertyNames;
10
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
11
+ var __export = (target, all) => {
12
+ for (var name2 in all)
13
+ __defProp(target, name2, { get: all[name2], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
24
+
25
+ // src/client/index.tsx
26
+ var index_exports = {};
27
+ __export(index_exports, {
28
+ apply: () => apply,
29
+ inject: () => inject,
30
+ name: () => name
31
+ });
32
+ module.exports = __toCommonJS(index_exports);
33
+ var import_react = require("react");
34
+ var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
35
+ var import_jsx_runtime = require("react/jsx-runtime");
36
+ function IconBranchVerticalRegular({ size = 16, className }) {
37
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
38
+ "svg",
39
+ {
40
+ width: size,
41
+ height: size,
42
+ className,
43
+ viewBox: "0 0 16 16",
44
+ fill: "none",
45
+ xmlns: "http://www.w3.org/2000/svg",
46
+ "aria-hidden": "true",
47
+ stroke: "currentColor",
48
+ strokeWidth: 1,
49
+ strokeLinecap: "round",
50
+ strokeLinejoin: "round",
51
+ children: [
52
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "4.5", cy: "3", r: "1.5" }),
53
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "4.5", cy: "13", r: "1.5" }),
54
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "5", r: "1.5" }),
55
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M4.5 4.5v7" }),
56
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M4.5 8h5.5c1.105 0 2-.895 2-1.5" })
57
+ ]
58
+ }
59
+ );
60
+ }
61
+ var name = "simple-worktree";
62
+ var inject = ["slots", "sessions", "workspaces", "uiWorkspace", "locale"];
63
+ var ROUTE_PREFIX = "/plugin/simple-worktree";
64
+ var NS = "simpleWorktree";
65
+ var zh = {
66
+ button: "Worktree",
67
+ buttonTitle: "\u65B0\u5EFA Worktree",
68
+ checking: "\u6B63\u5728\u68C0\u67E5 Git \u4ED3\u5E93\u2026",
69
+ notRepo: "\u5F53\u524D\u76EE\u5F55\u4E0D\u662F Git \u4ED3\u5E93",
70
+ gitMissing: "\u672A\u627E\u5230 git \u547D\u4EE4\uFF0C\u8BF7\u5148\u5B89\u88C5 Git \u5E76\u52A0\u5165 PATH",
71
+ createTitle: "\u65B0\u5EFA Worktree",
72
+ modeNew: "\u65B0\u5EFA\u5206\u652F",
73
+ modeExisting: "\u5DF2\u6709\u5206\u652F",
74
+ branchName: "\u5206\u652F\u540D",
75
+ branchNamePlaceholder: "\u4F8B\u5982 feat/login",
76
+ baseBranch: "\u57FA\u4E8E\u5206\u652F",
77
+ chooseBranch: "\u8BF7\u9009\u62E9\u5206\u652F",
78
+ occupied: "\u5DF2\u88AB worktree \u5360\u7528",
79
+ target: "\u5C06\u521B\u5EFA\u5230",
80
+ cancel: "\u53D6\u6D88",
81
+ create: "\u521B\u5EFA",
82
+ creating: "\u521B\u5EFA\u4E2D\u2026",
83
+ close: "\u5173\u95ED",
84
+ invalidEmpty: "\u8BF7\u8F93\u5165\u5206\u652F\u540D",
85
+ invalidName: "\u5206\u652F\u540D\u4E0D\u5408\u6CD5",
86
+ invalidExists: "\u672C\u5730\u5DF2\u5B58\u5728\u540C\u540D\u5206\u652F\uFF0C\u5982\u9700\u4E3A\u5B83\u521B\u5EFA worktree \u8BF7\u6539\u7528\u300C\u5DF2\u6709\u5206\u652F\u300D",
87
+ invalidSame: "\u8D77\u70B9\u4E0D\u80FD\u4E0E\u5206\u652F\u540D\u76F8\u540C",
88
+ loadFailed: "\u8BFB\u53D6\u4ED3\u5E93\u4FE1\u606F\u5931\u8D25",
89
+ deleteMenu: "\u5220\u9664 Worktree",
90
+ deleteCurrent: "\u5220\u9664\u5F53\u524D Worktree",
91
+ deleteTitle: "\u5220\u9664 Worktree",
92
+ deleteWarning: "\u6B64\u64CD\u4F5C\u4F1A\u5220\u9664 worktree \u76EE\u5F55\uFF0C\u672A\u63D0\u4EA4\u7684\u6539\u52A8\u5C06\u6C38\u4E45\u4E22\u5931\u3002",
93
+ willArchive: "\u8BE5\u5DE5\u4F5C\u533A\u4E0B\u7684 {count} \u4E2A\u4F1A\u8BDD\u5C06\u88AB\u5F52\u6863\uFF08\u542B\u5F53\u524D\u4F1A\u8BDD\uFF09",
94
+ willRemoveWorkspace: "\u5DE5\u4F5C\u533A\u6CE8\u518C\u5C06\u88AB\u79FB\u9664",
95
+ willRemoveDir: "worktree \u76EE\u5F55\u5C06\u88AB\u5220\u9664\uFF1A",
96
+ dirty: "{count} \u4E2A\u672A\u63D0\u4EA4\u6539\u52A8",
97
+ ahead: "{count} \u4E2A\u672A\u63A8\u9001\u63D0\u4EA4",
98
+ branchKept: "\u5206\u652F {branch} \u4F1A\u4FDD\u7559\uFF0C\u4E0D\u4F1A\u88AB\u5220\u9664",
99
+ confirmDelete: "\u786E\u8BA4\u5220\u9664",
100
+ deleting: "\u5220\u9664\u4E2D\u2026",
101
+ reading: "\u8BFB\u53D6\u4E2D\u2026",
102
+ detachedHead: "\u6E38\u79BB HEAD",
103
+ notWorktree: "\u5F53\u524D\u4F1A\u8BDD\u4E0D\u5728 worktree \u4E2D",
104
+ running: "\u8BE5\u5DE5\u4F5C\u533A\u8FD8\u6709\u4F1A\u8BDD\u6B63\u5728\u8FD0\u884C\uFF0C\u8BF7\u5148\u505C\u6B62\u540E\u518D\u5220\u9664",
105
+ workspaceMissing: "\u627E\u4E0D\u5230\u8BE5\u4F1A\u8BDD\u6240\u5C5E\u7684\u5DE5\u4F5C\u533A",
106
+ deleted: "\u5DF2\u5220\u9664 Worktree\uFF1A{path}",
107
+ workspaceFailed: "worktree \u5DF2\u521B\u5EFA\uFF0C\u4F46\u6CE8\u518C\u5DE5\u4F5C\u533A\u5931\u8D25\uFF1A",
108
+ sessionFailed: "\u5DE5\u4F5C\u533A\u5DF2\u6CE8\u518C\uFF0C\u4F46\u65B0\u5EFA\u4F1A\u8BDD\u5931\u8D25\uFF1A"
109
+ };
110
+ var en = zh;
111
+ async function request(method, path, body, query) {
112
+ const search = query === void 0 ? "" : `?${new URLSearchParams(query).toString()}`;
113
+ const response = await fetch(`${ROUTE_PREFIX}${path}${search}`, {
114
+ method,
115
+ cache: "no-store",
116
+ headers: body === void 0 ? void 0 : { "content-type": "application/json" },
117
+ body: body === void 0 ? void 0 : JSON.stringify(body)
118
+ });
119
+ const data = await response.json().catch(() => ({}));
120
+ if (!response.ok)
121
+ throw new Error(typeof data?.error === "string" ? data.error : `HTTP ${response.status}`);
122
+ return data;
123
+ }
124
+ function probeRepo(path) {
125
+ return request("GET", "/probe", void 0, { path });
126
+ }
127
+ function createOnDisk(input) {
128
+ return request("POST", "/create", input);
129
+ }
130
+ function inspectOnDisk(path) {
131
+ return request("POST", "/inspect", { path });
132
+ }
133
+ function removeOnDisk(input) {
134
+ return request("POST", "/remove", input);
135
+ }
136
+ function messageOf(error) {
137
+ return error instanceof Error ? error.message : String(error);
138
+ }
139
+ function errorText(error) {
140
+ const raw = messageOf(error);
141
+ if (/failed to fetch|networkerror|load failed|fetch failed/i.test(raw))
142
+ return "\u65E0\u6CD5\u8FDE\u63A5\u63D2\u4EF6\u670D\u52A1\uFF0C\u8BF7\u91CD\u542F\u5E94\u7528\u540E\u91CD\u8BD5";
143
+ if (/enoent|command not found/i.test(raw) && /git/i.test(raw))
144
+ return "\u672A\u627E\u5230 git \u547D\u4EE4\uFF0C\u8BF7\u5148\u5B89\u88C5 Git \u5E76\u52A0\u5165 PATH";
145
+ if (/already checked out at/i.test(raw))
146
+ return "\u8BE5\u5206\u652F\u5DF2\u88AB\u5176\u4ED6 worktree \u68C0\u51FA";
147
+ if (/not a git repository/i.test(raw))
148
+ return "\u5F53\u524D\u76EE\u5F55\u4E0D\u662F Git \u4ED3\u5E93";
149
+ if (/is a missing but already registered worktree/i.test(raw))
150
+ return "\u8BE5\u76EE\u5F55\u5DF2\u88AB git \u767B\u8BB0\u4E3A worktree\uFF0C\u8BF7\u5148\u6267\u884C git worktree prune";
151
+ if (/contains modified or untracked files/i.test(raw))
152
+ return "worktree \u5185\u6709\u672A\u63D0\u4EA4\u7684\u6539\u52A8\uFF0C\u65E0\u6CD5\u5220\u9664";
153
+ if (/is locked/i.test(raw))
154
+ return "worktree \u5DF2\u88AB\u9501\u5B9A\uFF0C\u65E0\u6CD5\u5220\u9664";
155
+ if (/invalid reference/i.test(raw))
156
+ return "\u5206\u652F\u6216\u63D0\u4EA4\u4E0D\u5B58\u5728";
157
+ return raw;
158
+ }
159
+ function branchIssue(raw, branches, base, mode) {
160
+ const name2 = raw.trim();
161
+ if (name2 === "")
162
+ return "invalidEmpty";
163
+ if (mode === "new" && name2 === base)
164
+ return "invalidSame";
165
+ if (name2 === "@" || name2 === "HEAD")
166
+ return "invalidName";
167
+ if (name2.startsWith("-") || name2.endsWith("/") || name2.endsWith(".") || name2.endsWith(".lock"))
168
+ return "invalidName";
169
+ if (/[\s~^:?*[\\\u0000-\u001f]/.test(name2))
170
+ return "invalidName";
171
+ if (name2.includes("..") || name2.includes("@{") || name2.includes("//"))
172
+ return "invalidName";
173
+ const parts = name2.split("/");
174
+ if (parts.some((part) => part === "" || part.startsWith(".") || part.endsWith(".") || part.endsWith(".lock")))
175
+ return "invalidName";
176
+ if (mode === "new" && branches.some((branch) => branch.name === name2))
177
+ return "invalidExists";
178
+ return null;
179
+ }
180
+ function flatten(name2) {
181
+ return name2.split("/").join("-");
182
+ }
183
+ var STYLE = `
184
+ .wt-dialog { width: min(460px, 100%) !important; }
185
+ .wt-dialog-body { display: flex; flex-direction: column; gap: 14px; width: 100%; min-width: 0; box-sizing: border-box; }
186
+ .wt-dialog-path { font-family: var(--dsh-font-mono, ui-monospace, monospace); overflow-wrap: anywhere; word-break: break-word; }
187
+ .wt-dialog-list { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 4px; overflow-wrap: anywhere; }
188
+ `;
189
+ function ensureStyle() {
190
+ if (typeof document === "undefined" || document.querySelector("style[data-simple-worktree-style]") !== null)
191
+ return;
192
+ const style = document.createElement("style");
193
+ style.setAttribute("data-simple-worktree-style", "");
194
+ style.textContent = STYLE;
195
+ document.head.appendChild(style);
196
+ }
197
+ var fieldStyle = {
198
+ display: "flex",
199
+ flexDirection: "column",
200
+ gap: 6,
201
+ fontSize: 13,
202
+ lineHeight: "20px"
203
+ };
204
+ var pickerStyle = {
205
+ display: "flex",
206
+ alignItems: "center",
207
+ justifyContent: "space-between",
208
+ gap: 8,
209
+ width: "100%",
210
+ minWidth: 0,
211
+ height: 34,
212
+ padding: "0 10px",
213
+ boxSizing: "border-box",
214
+ borderRadius: 8,
215
+ border: "1px solid var(--dsw-alias-line-2, rgba(128,128,128,.35))",
216
+ background: "var(--dsw-alias-bg-2, rgba(128,128,128,.08))",
217
+ color: "inherit",
218
+ fontSize: 13,
219
+ cursor: "pointer",
220
+ textAlign: "left"
221
+ };
222
+ var dangerStyle = {
223
+ color: "var(--dsw-alias-text-error, #e5484d)",
224
+ fontSize: 12,
225
+ lineHeight: "18px"
226
+ };
227
+ var mutedStyle = {
228
+ opacity: 0.65,
229
+ fontSize: 12,
230
+ lineHeight: "18px"
231
+ };
232
+ function BranchPicker(props) {
233
+ const [open, setOpen] = (0, import_react.useState)(false);
234
+ const selected = props.branches.find((branch) => branch.name === props.value);
235
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
236
+ import_dsh_client_ui_primitives.Menu,
237
+ {
238
+ open,
239
+ autoFocus: true,
240
+ dense: true,
241
+ portal: true,
242
+ align: "start",
243
+ side: "bottom",
244
+ onClose: () => setOpen(false),
245
+ anchor: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
246
+ "button",
247
+ {
248
+ type: "button",
249
+ "data-modal-autofocus": props.autoFocus === true ? "" : void 0,
250
+ style: pickerStyle,
251
+ onClick: () => setOpen((next) => !next),
252
+ children: [
253
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: selected === void 0 ? props.placeholder : selected.name }),
254
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconChevronDownOutlineRegular, { size: 12 })
255
+ ]
256
+ }
257
+ ),
258
+ items: props.branches.map((branch) => ({
259
+ id: branch.name,
260
+ label: props.disableOccupied && branch.worktreePath !== null ? `${branch.name} \xB7 ${props.t("occupied")}` : branch.name,
261
+ disabled: props.disableOccupied && branch.worktreePath !== null
262
+ })),
263
+ onSelect: (id) => {
264
+ setOpen(false);
265
+ props.onSelect(id);
266
+ }
267
+ }
268
+ );
269
+ }
270
+ function CreateWorktreeDialog(props) {
271
+ const { t } = props;
272
+ const [mode, setMode] = (0, import_react.useState)("new");
273
+ const [branch, setBranch] = (0, import_react.useState)("");
274
+ const [base, setBase] = (0, import_react.useState)(props.initial.branch ?? "HEAD");
275
+ const [existing, setExisting] = (0, import_react.useState)("");
276
+ const [info, setInfo] = (0, import_react.useState)(props.initial);
277
+ const [busy, setBusy] = (0, import_react.useState)(false);
278
+ const [error, setError] = (0, import_react.useState)("");
279
+ (0, import_react.useEffect)(() => {
280
+ let alive = true;
281
+ probeRepo(props.cwd).then((next) => {
282
+ if (!alive || next.isRepo !== true)
283
+ return;
284
+ setInfo(next);
285
+ if (next.branch !== void 0)
286
+ setBase((current) => current === "HEAD" ? next.branch : current);
287
+ }).catch(() => {
288
+ });
289
+ return () => {
290
+ alive = false;
291
+ };
292
+ }, [props.cwd]);
293
+ const branches = info.branches ?? [];
294
+ const draft = mode === "new" ? branch : existing;
295
+ const issue = draft.trim() === "" ? null : branchIssue(draft, branches, base, mode);
296
+ const valid = draft.trim() !== "" && issue === null;
297
+ const separator = info.mainRoot?.includes("\\") === true ? "\\" : "/";
298
+ const target = info.mainRoot === void 0 || draft.trim() === "" ? "" : `${info.mainRoot}${separator}.agents${separator}worktree${separator}${flatten(draft.trim())}`;
299
+ const submit = async () => {
300
+ if (!valid || busy)
301
+ return;
302
+ setBusy(true);
303
+ setError("");
304
+ try {
305
+ await props.createWorktree({
306
+ path: props.cwd,
307
+ mode,
308
+ branch: draft.trim(),
309
+ ...mode === "new" ? { base } : {}
310
+ });
311
+ props.onClose();
312
+ } catch (failure) {
313
+ setError(errorText(failure));
314
+ setBusy(false);
315
+ }
316
+ };
317
+ const close = () => {
318
+ if (!busy)
319
+ props.onClose();
320
+ };
321
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
322
+ import_dsh_client_ui_primitives.Modal,
323
+ {
324
+ open: true,
325
+ className: "wt-dialog",
326
+ title: t("createTitle"),
327
+ closeLabel: t("close"),
328
+ onClose: close,
329
+ footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
330
+ info.isLinkedWorktree === true && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
331
+ import_dsh_client_ui_primitives.Button,
332
+ {
333
+ variant: "ghost",
334
+ onClick: props.onDeleteCurrent,
335
+ disabled: busy,
336
+ style: { color: "var(--dsw-alias-text-error, #e5484d)", marginRight: "auto" },
337
+ children: t("deleteCurrent")
338
+ }
339
+ ),
340
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "ghost", onClick: close, disabled: busy, children: t("cancel") }),
341
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "primary", onClick: () => {
342
+ void submit();
343
+ }, disabled: !valid || busy, children: busy ? t("creating") : t("create") })
344
+ ] }),
345
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "wt-dialog-body", children: [
346
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
347
+ import_dsh_client_ui_primitives.Checkbox,
348
+ {
349
+ checked: mode === "new",
350
+ onChange: () => setMode("new"),
351
+ label: t("modeNew"),
352
+ disabled: busy
353
+ }
354
+ ),
355
+ mode === "new" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
356
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: fieldStyle, children: [
357
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("branchName") }),
358
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
359
+ import_dsh_client_ui_primitives.Input,
360
+ {
361
+ value: branch,
362
+ placeholder: t("branchNamePlaceholder"),
363
+ spellCheck: false,
364
+ autoComplete: "off",
365
+ disabled: busy,
366
+ "data-modal-autofocus": "",
367
+ onChange: (event) => setBranch(event.target.value),
368
+ onKeyDown: (event) => {
369
+ if (event.key === "Enter")
370
+ void submit();
371
+ }
372
+ }
373
+ )
374
+ ] }),
375
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: fieldStyle, children: [
376
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("baseBranch") }),
377
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
378
+ BranchPicker,
379
+ {
380
+ value: base,
381
+ placeholder: t("chooseBranch"),
382
+ branches,
383
+ disableOccupied: false,
384
+ t,
385
+ onSelect: setBase
386
+ }
387
+ )
388
+ ] })
389
+ ] }),
390
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
391
+ import_dsh_client_ui_primitives.Checkbox,
392
+ {
393
+ checked: mode === "existing",
394
+ onChange: () => setMode("existing"),
395
+ label: t("modeExisting"),
396
+ disabled: busy
397
+ }
398
+ ),
399
+ mode === "existing" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: fieldStyle, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
400
+ BranchPicker,
401
+ {
402
+ value: existing,
403
+ placeholder: t("chooseBranch"),
404
+ branches,
405
+ disableOccupied: true,
406
+ autoFocus: true,
407
+ t,
408
+ onSelect: setExisting
409
+ }
410
+ ) }),
411
+ target !== "" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: mutedStyle, children: [
412
+ t("target"),
413
+ ":",
414
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "wt-dialog-path", style: { marginTop: 2 }, children: target })
415
+ ] }),
416
+ issue !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: dangerStyle, children: t(issue) }),
417
+ error !== "" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: dangerStyle, children: error })
418
+ ] })
419
+ }
420
+ );
421
+ }
422
+ function WorktreeButton({ sessionId, useSessions, useWorkspaces, t, createWorktree }) {
423
+ const summary = useSessions((state) => state.byId[sessionId]);
424
+ const cwd = summary?.cwd;
425
+ const workspaceId = useWorkspaces((state) => state.items.find((item) => item.sessionIds.includes(sessionId))?.workspaceId);
426
+ const [repo, setRepo] = (0, import_react.useState)(null);
427
+ const [open, setOpen] = (0, import_react.useState)(false);
428
+ const [error, setError] = (0, import_react.useState)(null);
429
+ const busy = (0, import_react.useRef)(false);
430
+ (0, import_react.useEffect)(() => {
431
+ setOpen(false);
432
+ }, [cwd]);
433
+ const openDialog = async () => {
434
+ if (cwd === void 0 || busy.current)
435
+ return;
436
+ busy.current = true;
437
+ try {
438
+ const info = await probeRepo(cwd);
439
+ if (info.gitMissing === true) {
440
+ setError({ seq: Date.now(), text: t("gitMissing") });
441
+ return;
442
+ }
443
+ if (info.isRepo !== true) {
444
+ setError({ seq: Date.now(), text: t("notRepo") });
445
+ return;
446
+ }
447
+ setRepo(info);
448
+ setOpen(true);
449
+ } catch (failure) {
450
+ setError({ seq: Date.now(), text: errorText(failure) });
451
+ } finally {
452
+ busy.current = false;
453
+ }
454
+ };
455
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
456
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
457
+ import_dsh_client_ui_primitives.Button,
458
+ {
459
+ variant: "ghost",
460
+ size: "sm",
461
+ title: t("buttonTitle"),
462
+ "aria-label": t("buttonTitle"),
463
+ icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconBranchVerticalRegular, { size: 16 }),
464
+ style: { width: 28, minWidth: 28, padding: 0, flex: "none" },
465
+ onClick: () => {
466
+ void openDialog();
467
+ }
468
+ }
469
+ ),
470
+ open && repo !== null && cwd !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
471
+ CreateWorktreeDialog,
472
+ {
473
+ cwd,
474
+ initial: repo,
475
+ t,
476
+ createWorktree,
477
+ onClose: () => setOpen(false),
478
+ onDeleteCurrent: () => {
479
+ setOpen(false);
480
+ openDeleteRequest(sessionId, summary?.displayTitle ?? "", workspaceId);
481
+ }
482
+ }
483
+ ),
484
+ error !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Toast, { text: error.text, holdMs: 4e3, onDone: () => setError(null) }, error.seq)
485
+ ] });
486
+ }
487
+ var deleteRequest = null;
488
+ var deleteSeq = 0;
489
+ var deleteListeners = /* @__PURE__ */ new Set();
490
+ function notifyDelete() {
491
+ for (const listener of deleteListeners)
492
+ listener();
493
+ }
494
+ function subscribeDelete(listener) {
495
+ deleteListeners.add(listener);
496
+ return () => {
497
+ deleteListeners.delete(listener);
498
+ };
499
+ }
500
+ function getDeleteRequest() {
501
+ return deleteRequest;
502
+ }
503
+ function openDeleteRequest(sessionId, displayTitle, workspaceId) {
504
+ deleteSeq += 1;
505
+ deleteRequest = { sessionId, displayTitle, workspaceId, seq: deleteSeq };
506
+ notifyDelete();
507
+ }
508
+ function closeDeleteRequest() {
509
+ deleteRequest = null;
510
+ notifyDelete();
511
+ }
512
+ function DeleteWorktreeMenuItem({ sessionId, displayTitle, useMenuOpenState, useWorkspaces, t, requestDelete }) {
513
+ const workspaceId = useWorkspaces((state) => state.items.find((item) => item.sessionIds.includes(sessionId))?.workspaceId);
514
+ const [, setMenuOpen] = useMenuOpenState();
515
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
516
+ import_dsh_client_ui_primitives.MenuItemButton,
517
+ {
518
+ danger: true,
519
+ separatorBefore: true,
520
+ icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconTrashOutlineRegular, { size: 14 }),
521
+ onSelect: () => {
522
+ setMenuOpen(false);
523
+ requestDelete(sessionId, displayTitle, workspaceId);
524
+ },
525
+ children: t("deleteMenu")
526
+ }
527
+ );
528
+ }
529
+ function DeleteWorktreeOverlay({ useSessions, useWorkspaces, t, deleteWorktree }) {
530
+ const pending = (0, import_react.useSyncExternalStore)(subscribeDelete, getDeleteRequest);
531
+ const session = useSessions((state) => pending === null ? void 0 : state.byId[pending.sessionId]);
532
+ const workspace = useWorkspaces((state) => pending === null ? void 0 : state.items.find((item) => item.sessionIds.includes(pending.sessionId)));
533
+ const [toast, setToast] = (0, import_react.useState)(null);
534
+ const skip = (text) => {
535
+ closeDeleteRequest();
536
+ setToast({ seq: Date.now(), text });
537
+ };
538
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
539
+ pending !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
540
+ DeleteWorktreeGate,
541
+ {
542
+ pending,
543
+ cwd: session?.cwd,
544
+ workspaceId: pending.workspaceId ?? workspace?.workspaceId,
545
+ sessionIds: workspace?.sessionIds ?? [],
546
+ t,
547
+ deleteWorktree,
548
+ onClose: closeDeleteRequest,
549
+ onSkip: skip,
550
+ onDone: (text) => {
551
+ closeDeleteRequest();
552
+ setToast({ seq: Date.now(), text, tone: "success" });
553
+ }
554
+ },
555
+ pending.seq
556
+ ),
557
+ toast !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Toast, { text: toast.text, tone: toast.tone, holdMs: 4e3, onDone: () => setToast(null) }, toast.seq)
558
+ ] });
559
+ }
560
+ function DeleteWorktreeGate(props) {
561
+ const [ready, setReady] = (0, import_react.useState)(false);
562
+ (0, import_react.useEffect)(() => {
563
+ let alive = true;
564
+ if (props.cwd === void 0) {
565
+ props.onSkip(props.t("notWorktree"));
566
+ return () => {
567
+ alive = false;
568
+ };
569
+ }
570
+ probeRepo(props.cwd).then((info) => {
571
+ if (!alive)
572
+ return;
573
+ if (info.gitMissing === true) {
574
+ props.onSkip(props.t("gitMissing"));
575
+ return;
576
+ }
577
+ if (info.isRepo !== true || info.isLinkedWorktree !== true) {
578
+ props.onSkip(props.t("notWorktree"));
579
+ return;
580
+ }
581
+ setReady(true);
582
+ }).catch((failure) => {
583
+ if (alive)
584
+ props.onSkip(errorText(failure));
585
+ });
586
+ return () => {
587
+ alive = false;
588
+ };
589
+ }, [props.cwd]);
590
+ if (!ready)
591
+ return null;
592
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
593
+ DeleteWorktreeDialog,
594
+ {
595
+ pending: props.pending,
596
+ cwd: props.cwd,
597
+ workspaceId: props.workspaceId,
598
+ sessionIds: props.sessionIds,
599
+ t: props.t,
600
+ deleteWorktree: props.deleteWorktree,
601
+ onClose: props.onClose,
602
+ onDone: props.onDone
603
+ }
604
+ );
605
+ }
606
+ function DeleteWorktreeDialog(props) {
607
+ const { t } = props;
608
+ const [facts, setFacts] = (0, import_react.useState)(null);
609
+ const [busy, setBusy] = (0, import_react.useState)(false);
610
+ const [error, setError] = (0, import_react.useState)("");
611
+ (0, import_react.useEffect)(() => {
612
+ let alive = true;
613
+ if (props.cwd === void 0)
614
+ return () => {
615
+ alive = false;
616
+ };
617
+ inspectOnDisk(props.cwd).then((next) => {
618
+ if (alive) setFacts(next);
619
+ }).catch((failure) => {
620
+ if (alive) setError(errorText(failure));
621
+ });
622
+ return () => {
623
+ alive = false;
624
+ };
625
+ }, [props.cwd]);
626
+ const confirm = async () => {
627
+ if (facts === null || props.workspaceId === void 0 || busy)
628
+ return;
629
+ setBusy(true);
630
+ setError("");
631
+ try {
632
+ await props.deleteWorktree({
633
+ sessionId: props.pending.sessionId,
634
+ workspaceId: props.workspaceId,
635
+ path: facts.path
636
+ });
637
+ props.onDone(t("deleted", { path: facts.path }));
638
+ } catch (failure) {
639
+ const message = messageOf(failure);
640
+ setError(message === "running" || message.includes("session-active") ? t("running") : message === "workspace-missing" ? t("workspaceMissing") : errorText(failure));
641
+ setBusy(false);
642
+ }
643
+ };
644
+ const close = () => {
645
+ if (!busy)
646
+ props.onClose();
647
+ };
648
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
649
+ import_dsh_client_ui_primitives.Modal,
650
+ {
651
+ open: true,
652
+ className: "wt-dialog",
653
+ title: t("deleteTitle"),
654
+ closeLabel: t("close"),
655
+ onClose: close,
656
+ footer: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
657
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.Button, { variant: "ghost", onClick: close, disabled: busy, children: t("cancel") }),
658
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
659
+ import_dsh_client_ui_primitives.Button,
660
+ {
661
+ variant: "primary",
662
+ onClick: () => {
663
+ void confirm();
664
+ },
665
+ disabled: facts === null || props.workspaceId === void 0 || busy,
666
+ children: busy ? t("deleting") : t("confirmDelete")
667
+ }
668
+ )
669
+ ] }),
670
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "wt-dialog-body", style: { fontSize: 13, lineHeight: "20px" }, children: [
671
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6, fontWeight: 500, minWidth: 0 }, children: [
672
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconBranchVerticalRegular, { size: 14 }),
673
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: facts === null ? t("reading") : facts.branch !== "" ? facts.branch : t("detachedHead") })
674
+ ] }),
675
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { ...dangerStyle, fontSize: 13 }, children: t("deleteWarning") }),
676
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ul", { className: "wt-dialog-list", children: [
677
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: t("willArchive", { count: props.sessionIds.length }) }),
678
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: t("willRemoveWorkspace") }),
679
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
680
+ t("willRemoveDir"),
681
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "wt-dialog-path", style: { marginTop: 2 }, children: facts?.path ?? props.cwd ?? "" })
682
+ ] }),
683
+ facts !== null && facts.dirty > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { style: dangerStyle, children: t("dirty", { count: facts.dirty }) }),
684
+ facts !== null && facts.ahead !== null && facts.ahead > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: t("ahead", { count: facts.ahead }) }),
685
+ facts !== null && facts.branch !== "" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: t("branchKept", { branch: facts.branch }) })
686
+ ] }),
687
+ error !== "" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: dangerStyle, children: error })
688
+ ] })
689
+ }
690
+ );
691
+ }
692
+ function fail(prefix, failure, path) {
693
+ return new Error(`${prefix}${errorText(failure)}\uFF08\u8DEF\u5F84\uFF1A${path}\uFF09`);
694
+ }
695
+ function apply(ctx) {
696
+ ensureStyle();
697
+ ctx.effect(() => ctx.locale.register(NS, { zh, en }), "simple-worktree: dictionaries");
698
+ const createWorktree = async (input) => {
699
+ const created = await createOnDisk(input);
700
+ let workspace;
701
+ try {
702
+ workspace = await ctx.workspaces.create({ path: created.path });
703
+ } catch (failure) {
704
+ throw fail("worktree \u5DF2\u521B\u5EFA\uFF0C\u4F46\u6CE8\u518C\u5DE5\u4F5C\u533A\u5931\u8D25\uFF1A", failure, created.path);
705
+ }
706
+ try {
707
+ ctx.uiWorkspace.startSession(workspace.workspaceId);
708
+ } catch (failure) {
709
+ throw fail("\u5DE5\u4F5C\u533A\u5DF2\u6CE8\u518C\uFF0C\u4F46\u65B0\u5EFA\u4F1A\u8BDD\u5931\u8D25\uFF1A", failure, created.path);
710
+ }
711
+ return created;
712
+ };
713
+ const deleteWorktree = async (input) => {
714
+ const workspaceId = input.workspaceId;
715
+ const workspace = ctx.workspaces.list.getSnapshot().items.find((item) => item.workspaceId === workspaceId);
716
+ if (workspace !== void 0) {
717
+ const sessions = ctx.sessions.list.getSnapshot().byId;
718
+ const running = workspace.sessionIds.filter((id) => sessions[id]?.running === true);
719
+ if (running.length > 0)
720
+ throw new Error("running");
721
+ for (const id of workspace.sessionIds)
722
+ await ctx.uiWorkspace.archiveSession(id);
723
+ await ctx.workspaces.delete(workspaceId);
724
+ }
725
+ await removeOnDisk({ path: input.path, force: true });
726
+ };
727
+ ctx.slots.inject("conversation.input.left", () => ctx.slots.register({
728
+ name: "conversation.input.left",
729
+ id: "simple-worktree.create",
730
+ order: 6,
731
+ locale: NS,
732
+ inject: () => ({ createWorktree })
733
+ }, WorktreeButton));
734
+ ctx.slots.inject("sidebar.workspaces.session.menu.item", () => ctx.slots.register({
735
+ name: "sidebar.workspaces.session.menu.item",
736
+ id: "simple-worktree.delete",
737
+ order: 500,
738
+ locale: NS,
739
+ inject: () => ({ requestDelete: openDeleteRequest })
740
+ }, DeleteWorktreeMenuItem));
741
+ ctx.slots.inject("shell.overlay", () => ctx.slots.register({
742
+ name: "shell.overlay",
743
+ id: "simple-worktree.delete-dialog",
744
+ order: 0,
745
+ locale: NS,
746
+ inject: () => ({ deleteWorktree })
747
+ }, DeleteWorktreeOverlay));
748
+ }
749
+
750
+ return module.exports;
751
+ }
752
+ });