@dungsil/dsh-plugin-ui-worktree 0.3.0 → 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/README.md +1 -1
- package/dist/client.js +289 -184
- package/dist/index.js +105 -30
- package/package.json +4 -5
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ DSH 입력창의 권한 선택 옆에 **Worktree 선택 상자**를 추가합니
|
|
|
8
8
|
2. 입력창의 선택 상자에서 현재 체크아웃과 기존 worktree의 이름을 확인합니다. 접두사와 경로는 표시하지 않습니다.
|
|
9
9
|
3. **새 워크트리**를 선택합니다. 별도 입력 창 없이 **새 워크트리**로 표시하며, 이때는 브랜치나 경로를 만들지 않습니다. 다음 일반 프롬프트를 전송하면 해당 프롬프트로 이름을 정하고 생성·전환합니다. 대화 영역에서 단계별 진행 상태와 소요 시간을 확인할 수 있으며, 생성과 전환이 끝난 뒤에 실제 브랜치 이름을 표시합니다.
|
|
10
10
|
4. 기존 worktree를 선택하면 같은 저장소에 등록된 경로인지 확인한 뒤 즉시 전환합니다. 생성 예약이 있다면 취소하며, 현재 체크아웃을 다시 선택해도 예약을 취소할 수 있습니다.
|
|
11
|
-
5. 오류가 발생하면 **조회 실패** 또는 **변경 확인
|
|
11
|
+
5. 오류가 발생하면 선택 상자 안에 **조회 실패** 또는 **변경 확인 필요**를 표시하며, 선택 상자를 누르면 상세 창을 엽니다. 선택 상자 옆에는 별도 오류 라벨을 표시하지 않습니다. **워크트리 새로고침**으로 실제 상태를 확인한 뒤 기존 worktree를 선택하거나 다시 예약합니다. 생성에 실패하면 모델 작업을 시작하지 않으므로 프롬프트를 다시 전송해야 합니다. 다음 프롬프트나 새로고침만으로 생성을 자동 재시도하지 않습니다.
|
|
12
12
|
|
|
13
13
|
## 동작 범위
|
|
14
14
|
|
package/dist/client.js
CHANGED
|
@@ -3,6 +3,147 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
3
3
|
let react = require("react");
|
|
4
4
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
5
5
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
6
|
+
// ../../tools/ui/src/client.ts
|
|
7
|
+
function useLocaleSnapshot(locale) {
|
|
8
|
+
const subscribe = (0, react.useCallback)((listener) => locale.subscribe(listener), [locale]);
|
|
9
|
+
const getSnapshot = (0, react.useCallback)(() => locale.getSnapshot(), [locale]);
|
|
10
|
+
return (0, react.useSyncExternalStore)(subscribe, getSnapshot);
|
|
11
|
+
}
|
|
12
|
+
/** Each effect owns its sheet, even when another plugin installs the same defaults. */
|
|
13
|
+
function installStyles(css) {
|
|
14
|
+
const style = document.createElement("style");
|
|
15
|
+
style.textContent = css;
|
|
16
|
+
document.head.append(style);
|
|
17
|
+
return () => style.remove();
|
|
18
|
+
}
|
|
19
|
+
// ../../tools/ui/src/icons/git-branch.svg
|
|
20
|
+
var git_branch_default = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"6\" cy=\"5\" r=\"3\"/>\n <circle cx=\"6\" cy=\"19\" r=\"3\"/>\n <circle cx=\"18\" cy=\"6\" r=\"3\"/>\n <path d=\"M6 8v8M6 14h7a5 5 0 0 0 5-5\"/>\n</svg>\n";
|
|
21
|
+
// ../../tools/ui/src/composer.tsx
|
|
22
|
+
const composerStyles = `
|
|
23
|
+
:where(.dsh-plugin-composer-trigger) { display: flex; align-items: center; gap: 4px; min-width: 0; height: 28px; padding: 0 4px 0 8px; border: 0; border-radius: var(--dsw-radius-sm); background: transparent; color: var(--dsw-alias-label-secondary); font: inherit; font-size: 13px; line-height: 20px; cursor: pointer; }
|
|
24
|
+
:where(.dsh-plugin-composer-trigger):disabled { color: var(--dsw-alias-label-dimmed); cursor: default; }
|
|
25
|
+
.dsh-plugin-composer-icon, .dsh-plugin-composer-chevron { display: inline-flex; flex: none; }
|
|
26
|
+
.dsh-plugin-composer-label, .dsh-plugin-composer-detail { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
27
|
+
.dsh-plugin-composer-chevron, .dsh-plugin-composer-detail { color: var(--dsw-alias-label-caption); }
|
|
28
|
+
.dsh-plugin-composer-trigger[aria-expanded=true] .dsh-plugin-composer-chevron { transform: rotate(180deg); }
|
|
29
|
+
`;
|
|
30
|
+
const ComposerTrigger = (0, react.forwardRef)(function ComposerTrigger({ className = "", icon, label, detail, ...props }, ref) {
|
|
31
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
32
|
+
...props,
|
|
33
|
+
ref,
|
|
34
|
+
type: "button",
|
|
35
|
+
className: `dsh-plugin-composer-trigger ${className}`,
|
|
36
|
+
children: [
|
|
37
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
38
|
+
className: "dsh-plugin-composer-icon",
|
|
39
|
+
"aria-hidden": "true",
|
|
40
|
+
children: icon
|
|
41
|
+
}),
|
|
42
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
43
|
+
className: "dsh-plugin-composer-label",
|
|
44
|
+
children: label
|
|
45
|
+
}),
|
|
46
|
+
detail && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
47
|
+
className: "dsh-plugin-composer-detail",
|
|
48
|
+
children: detail
|
|
49
|
+
}),
|
|
50
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
51
|
+
className: "dsh-plugin-composer-chevron",
|
|
52
|
+
"aria-hidden": "true",
|
|
53
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {})
|
|
54
|
+
})
|
|
55
|
+
]
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
// ../../tools/ui/src/settings.tsx
|
|
59
|
+
/** The rc.5 settings shell has no icon slot, so decorate only the matching navigation rows. */
|
|
60
|
+
function installSettingsIcon(id, labels, svg) {
|
|
61
|
+
const attribute = "data-dsh-settings-icon";
|
|
62
|
+
const selector = `[${attribute}="${id}"]`;
|
|
63
|
+
const removeStyles = installStyles(`
|
|
64
|
+
${selector} > svg { display: none; }
|
|
65
|
+
${selector}::before { content: ''; display: block; flex: none; width: 16px; height: 16px; background: currentColor; mask: url("data:image/svg+xml,${encodeURIComponent(svg)}") center / contain no-repeat; }
|
|
66
|
+
`);
|
|
67
|
+
const marked = /* @__PURE__ */ new Set();
|
|
68
|
+
const refresh = () => {
|
|
69
|
+
const rows = new Set(document.querySelectorAll("[data-shortcut-modal=\"settings\"] nav button"));
|
|
70
|
+
for (const row of marked) if (!rows.has(row) || !labels.includes(row.textContent?.trim() ?? "")) {
|
|
71
|
+
row.removeAttribute(attribute);
|
|
72
|
+
marked.delete(row);
|
|
73
|
+
}
|
|
74
|
+
for (const row of rows) if (labels.includes(row.textContent?.trim() ?? "")) {
|
|
75
|
+
row.setAttribute(attribute, id);
|
|
76
|
+
marked.add(row);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const observer = new MutationObserver(refresh);
|
|
80
|
+
observer.observe(document.body, {
|
|
81
|
+
childList: true,
|
|
82
|
+
subtree: true,
|
|
83
|
+
characterData: true
|
|
84
|
+
});
|
|
85
|
+
refresh();
|
|
86
|
+
return () => {
|
|
87
|
+
observer.disconnect();
|
|
88
|
+
for (const row of marked) row.removeAttribute(attribute);
|
|
89
|
+
removeStyles();
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const settingsStyles = `
|
|
93
|
+
.dsh-plugin-settings-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 16px 0; border-bottom: .5px solid var(--dsw-alias-border-l2); }
|
|
94
|
+
.dsh-plugin-settings-text { flex: 1; min-width: 0; }
|
|
95
|
+
.dsh-plugin-settings-picker { min-width: 0; }
|
|
96
|
+
.dsh-plugin-settings-selector { box-sizing: border-box; display: inline-flex; align-items: center; gap: 12px; max-width: 100%; height: 36px; padding: 0 14px; border: none; border-radius: var(--dsw-radius-md); background: var(--dsw-alias-bg-module-platform); color: inherit; font: inherit; cursor: pointer; }
|
|
97
|
+
.dsh-plugin-settings-value { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
98
|
+
.dsh-plugin-settings-selector svg { flex: none; }
|
|
99
|
+
.dsh-plugin-settings-selector:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
|
|
100
|
+
.dsh-plugin-settings-selector:disabled { opacity: .5; cursor: default; }
|
|
101
|
+
.dsh-plugin-settings-selector:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary-new-colorprimary-new-color); outline-offset: 2px; }
|
|
102
|
+
`;
|
|
103
|
+
function SettingsRow({ label, children }) {
|
|
104
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
105
|
+
className: "dsh-plugin-settings-row",
|
|
106
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
107
|
+
className: "dsh-plugin-settings-text",
|
|
108
|
+
children: label
|
|
109
|
+
}), children]
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function SettingsPicker({ className = "", value, selectedId, items, onSelect, disabled = false, ...buttonProps }) {
|
|
113
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
114
|
+
(0, react.useEffect)(() => {
|
|
115
|
+
if (disabled) setOpen(false);
|
|
116
|
+
}, [disabled]);
|
|
117
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
118
|
+
className: `dsh-plugin-settings-picker ${className}`,
|
|
119
|
+
open: open && !disabled,
|
|
120
|
+
onClose: () => setOpen(false),
|
|
121
|
+
items,
|
|
122
|
+
selectedId,
|
|
123
|
+
onSelect: (id) => {
|
|
124
|
+
setOpen(false);
|
|
125
|
+
if (!disabled) onSelect(id);
|
|
126
|
+
},
|
|
127
|
+
align: "end",
|
|
128
|
+
portal: true,
|
|
129
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
130
|
+
...buttonProps,
|
|
131
|
+
type: "button",
|
|
132
|
+
className: "dsh-plugin-settings-selector",
|
|
133
|
+
"aria-haspopup": "menu",
|
|
134
|
+
"aria-expanded": open && !disabled,
|
|
135
|
+
disabled,
|
|
136
|
+
onClick: () => setOpen((current) => !current),
|
|
137
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
138
|
+
className: "dsh-plugin-settings-value",
|
|
139
|
+
children: value
|
|
140
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {
|
|
141
|
+
size: 16,
|
|
142
|
+
"aria-hidden": "true"
|
|
143
|
+
})]
|
|
144
|
+
})
|
|
145
|
+
});
|
|
146
|
+
}
|
|
6
147
|
// src/protocol.ts
|
|
7
148
|
const channel = "/api";
|
|
8
149
|
const endpointPrefix = "dungsil-worktree/";
|
|
@@ -192,6 +333,9 @@ const copy$1 = {
|
|
|
192
333
|
const copy = {
|
|
193
334
|
ko: {
|
|
194
335
|
nav: "워크트리",
|
|
336
|
+
defaultSelection: "기본 선택",
|
|
337
|
+
current: "현재 체크아웃",
|
|
338
|
+
new: "새 워크트리",
|
|
195
339
|
directory: "워크트리 풀 디렉터리",
|
|
196
340
|
remote: "추적 중인 원격 브랜치 사용",
|
|
197
341
|
naming: "자동 브랜치 이름 형식",
|
|
@@ -209,6 +353,9 @@ const copy = {
|
|
|
209
353
|
},
|
|
210
354
|
zh: {
|
|
211
355
|
nav: "工作树",
|
|
356
|
+
defaultSelection: "默认选择",
|
|
357
|
+
current: "当前检出",
|
|
358
|
+
new: "新工作树",
|
|
212
359
|
directory: "工作树池目录",
|
|
213
360
|
remote: "使用跟踪的远程分支",
|
|
214
361
|
naming: "自动分支命名格式",
|
|
@@ -226,6 +373,9 @@ const copy = {
|
|
|
226
373
|
},
|
|
227
374
|
en: {
|
|
228
375
|
nav: "Worktrees",
|
|
376
|
+
defaultSelection: "Default selection",
|
|
377
|
+
current: "Current checkout",
|
|
378
|
+
new: "New worktree",
|
|
229
379
|
directory: "Worktree pool directory",
|
|
230
380
|
remote: "Use tracked remote branch",
|
|
231
381
|
naming: "Automatic branch naming",
|
|
@@ -249,14 +399,13 @@ const validPrefix = (value) => value.length <= 100 && (value === "" || /^[a-zA-Z
|
|
|
249
399
|
const validSettings = (settings) => validDirectory(settings.directory) && validPrefix(settings.prefix) && (settings.naming !== "custom" || !!settings.prefix);
|
|
250
400
|
const sameSettings = (left, right) => JSON.stringify(left) === JSON.stringify(right);
|
|
251
401
|
function WorktreeSettingsSection({ ctx }) {
|
|
252
|
-
const
|
|
402
|
+
const { active: language } = useLocaleSnapshot(ctx.locale);
|
|
253
403
|
const text = copy[languageKey(language)];
|
|
254
404
|
const id = (0, react.useId)();
|
|
255
405
|
const connection = ctx.get("connection");
|
|
256
406
|
const [draft, setDraft] = (0, react.useState)();
|
|
257
407
|
const [operation, setOperation] = (0, react.useState)("loading");
|
|
258
408
|
const [notice, setNotice] = (0, react.useState)();
|
|
259
|
-
const [namingOpen, setNamingOpen] = (0, react.useState)(false);
|
|
260
409
|
const saved = (0, react.useRef)(void 0);
|
|
261
410
|
const latest = (0, react.useRef)(void 0);
|
|
262
411
|
const pending = (0, react.useRef)(false);
|
|
@@ -277,7 +426,6 @@ function WorktreeSettingsSection({ ctx }) {
|
|
|
277
426
|
if (!controller || controller.signal.aborted || pending.current) return;
|
|
278
427
|
pending.current = true;
|
|
279
428
|
blocked.current = true;
|
|
280
|
-
setNamingOpen(false);
|
|
281
429
|
setOperation("loading");
|
|
282
430
|
try {
|
|
283
431
|
await read(controller);
|
|
@@ -327,7 +475,6 @@ function WorktreeSettingsSection({ ctx }) {
|
|
|
327
475
|
} finally {
|
|
328
476
|
if (!controller.signal.aborted) {
|
|
329
477
|
if (blocked.current) {
|
|
330
|
-
setNamingOpen(false);
|
|
331
478
|
setOperation("loading");
|
|
332
479
|
try {
|
|
333
480
|
await read(controller);
|
|
@@ -383,19 +530,37 @@ function WorktreeSettingsSection({ ctx }) {
|
|
|
383
530
|
draft && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
|
|
384
531
|
disabled,
|
|
385
532
|
children: [
|
|
386
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
className: "dsh-worktree-settings-
|
|
390
|
-
children:
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
533
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsRow, {
|
|
534
|
+
label: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
535
|
+
id: `${id}-default-selection`,
|
|
536
|
+
className: "dsh-worktree-settings-title",
|
|
537
|
+
children: text.defaultSelection
|
|
538
|
+
}),
|
|
539
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsPicker, {
|
|
540
|
+
className: "dsh-worktree-settings-picker",
|
|
541
|
+
disabled,
|
|
542
|
+
"aria-labelledby": `${id}-default-selection`,
|
|
543
|
+
value: text[draft.defaultSelection],
|
|
544
|
+
selectedId: draft.defaultSelection,
|
|
545
|
+
items: ["current", "new"].map((id) => ({
|
|
546
|
+
id,
|
|
547
|
+
label: text[id]
|
|
548
|
+
})),
|
|
549
|
+
onSelect: (defaultSelection) => {
|
|
550
|
+
change({ defaultSelection });
|
|
551
|
+
}
|
|
552
|
+
})
|
|
553
|
+
}),
|
|
554
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsRow, {
|
|
555
|
+
label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
556
|
+
htmlFor: `${id}-directory`,
|
|
557
|
+
children: text.directory
|
|
558
|
+
}), invalidDirectory && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
559
|
+
id: `${id}-directory-error`,
|
|
560
|
+
role: "alert",
|
|
561
|
+
children: text.invalidDirectory
|
|
562
|
+
})] }),
|
|
563
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
399
564
|
className: "dsh-worktree-settings-input",
|
|
400
565
|
id: `${id}-directory`,
|
|
401
566
|
type: "text",
|
|
@@ -407,36 +572,31 @@ function WorktreeSettingsSection({ ctx }) {
|
|
|
407
572
|
"aria-invalid": invalidDirectory || void 0,
|
|
408
573
|
"aria-describedby": invalidDirectory ? `${id}-directory-error` : void 0,
|
|
409
574
|
onChange: (event) => change({ directory: event.target.value })
|
|
410
|
-
})
|
|
575
|
+
})
|
|
411
576
|
}),
|
|
412
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
children: text.remote
|
|
419
|
-
})
|
|
420
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
|
|
577
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsRow, {
|
|
578
|
+
label: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
579
|
+
className: "dsh-worktree-settings-title",
|
|
580
|
+
children: text.remote
|
|
581
|
+
}),
|
|
582
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
|
|
421
583
|
label: text.remote,
|
|
422
584
|
checked: draft.remote,
|
|
423
585
|
disabled,
|
|
424
586
|
onChange: (remote) => change({ remote })
|
|
425
|
-
})
|
|
587
|
+
})
|
|
426
588
|
}),
|
|
427
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
className: "dsh-worktree-settings-
|
|
431
|
-
children:
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
align: "end",
|
|
439
|
-
portal: true,
|
|
589
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsRow, {
|
|
590
|
+
label: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
591
|
+
id: `${id}-naming`,
|
|
592
|
+
className: "dsh-worktree-settings-title",
|
|
593
|
+
children: text.naming
|
|
594
|
+
}),
|
|
595
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsPicker, {
|
|
596
|
+
className: "dsh-worktree-settings-picker",
|
|
597
|
+
disabled,
|
|
598
|
+
"aria-labelledby": `${id}-naming`,
|
|
599
|
+
value: text[draft.naming],
|
|
440
600
|
selectedId: draft.naming,
|
|
441
601
|
items: [
|
|
442
602
|
"semantic",
|
|
@@ -446,39 +606,21 @@ function WorktreeSettingsSection({ ctx }) {
|
|
|
446
606
|
id,
|
|
447
607
|
label: text[id]
|
|
448
608
|
})),
|
|
449
|
-
onClose: () => setNamingOpen(false),
|
|
450
609
|
onSelect: (naming) => {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
455
|
-
type: "button",
|
|
456
|
-
className: "dsh-worktree-settings-selector",
|
|
457
|
-
disabled,
|
|
458
|
-
"aria-labelledby": `${id}-naming`,
|
|
459
|
-
"aria-haspopup": "menu",
|
|
460
|
-
"aria-expanded": namingOpen,
|
|
461
|
-
onClick: () => setNamingOpen((open) => !open),
|
|
462
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text[draft.naming] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {
|
|
463
|
-
size: 16,
|
|
464
|
-
"aria-hidden": "true"
|
|
465
|
-
})]
|
|
466
|
-
})
|
|
467
|
-
})]
|
|
610
|
+
change({ naming });
|
|
611
|
+
}
|
|
612
|
+
})
|
|
468
613
|
}),
|
|
469
|
-
draft.naming === "custom" && /* @__PURE__ */ (0, react_jsx_runtime.
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
children: text.invalidPrefix
|
|
480
|
-
})]
|
|
481
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
614
|
+
draft.naming === "custom" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsRow, {
|
|
615
|
+
label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
616
|
+
htmlFor: `${id}-prefix`,
|
|
617
|
+
children: text.prefix
|
|
618
|
+
}), invalidPrefix && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
619
|
+
id: `${id}-prefix-error`,
|
|
620
|
+
role: "alert",
|
|
621
|
+
children: text.invalidPrefix
|
|
622
|
+
})] }),
|
|
623
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
482
624
|
className: "dsh-worktree-settings-input",
|
|
483
625
|
id: `${id}-prefix`,
|
|
484
626
|
type: "text",
|
|
@@ -491,7 +633,7 @@ function WorktreeSettingsSection({ ctx }) {
|
|
|
491
633
|
autoComplete: "off",
|
|
492
634
|
spellCheck: false,
|
|
493
635
|
onChange: (event) => change({ prefix: event.target.value })
|
|
494
|
-
})
|
|
636
|
+
})
|
|
495
637
|
})
|
|
496
638
|
]
|
|
497
639
|
}),
|
|
@@ -508,30 +650,22 @@ function WorktreeSettingsSection({ ctx }) {
|
|
|
508
650
|
});
|
|
509
651
|
}
|
|
510
652
|
// src/styles.ts
|
|
511
|
-
const styles =
|
|
653
|
+
const styles = `${settingsStyles}${composerStyles}
|
|
512
654
|
.dsh-worktree-sidebar-name { flex: 0 1 auto; min-width: 0; max-width: 88px; margin-inline: 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-caption); font-size: 10px; line-height: 16px; }
|
|
513
655
|
.dsh-worktree { display: inline-flex; flex-wrap: wrap; align-items: center; min-width: 0; max-width: 100%; color: var(--dsw-alias-label-secondary); font: inherit; }
|
|
514
|
-
.dsh-worktree-controls { display: inline-flex; flex-wrap: nowrap; align-items: center; min-width: 0; max-width: 100%; }
|
|
515
656
|
.dsh-worktree-picker { display: inline-flex; min-width: 0; max-width: 100%; }
|
|
516
|
-
.dsh-worktree-trigger { display: inline-flex;
|
|
517
|
-
.dsh-worktree-
|
|
518
|
-
.dsh-worktree-icon svg { width: 14px; height: 14px; }
|
|
519
|
-
.dsh-worktree-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
520
|
-
.dsh-worktree-chevron { color: var(--dsw-alias-label-caption); }
|
|
521
|
-
.dsh-worktree-trigger[aria-expanded=true] .dsh-worktree-chevron { transform: rotate(180deg); }
|
|
522
|
-
.dsh-worktree-trigger:disabled { color: var(--dsw-alias-label-dimmed); cursor: default; }
|
|
657
|
+
.dsh-worktree-trigger { display: inline-flex; max-width: 220px; font-weight: 500; }
|
|
658
|
+
.dsh-worktree-trigger .dsh-plugin-composer-icon svg { width: 14px; height: 14px; }
|
|
523
659
|
.dsh-worktree-trigger:focus-visible { outline: 2px solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); outline-offset: 0; }
|
|
524
660
|
@container (width <= 460px) {
|
|
525
|
-
.dsh-worktree-
|
|
661
|
+
.dsh-worktree-trigger .dsh-plugin-composer-label { display: none; }
|
|
526
662
|
}
|
|
527
663
|
@media (prefers-reduced-motion: no-preference) {
|
|
528
|
-
.dsh-worktree-chevron { transition: transform .12s; }
|
|
664
|
+
.dsh-worktree-trigger .dsh-plugin-composer-chevron { transition: transform .12s; }
|
|
529
665
|
}
|
|
530
666
|
@media (hover: hover) {
|
|
531
|
-
.dsh-worktree-trigger:hover:not(:disabled), .dsh-worktree-
|
|
667
|
+
.dsh-worktree-trigger:hover:not(:disabled), .dsh-worktree-dialog button:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
|
|
532
668
|
}
|
|
533
|
-
.dsh-worktree-error-trigger { flex: 0 0 auto; min-height: 28px; padding: 2px 6px; border: 0; border-radius: var(--dsw-radius-sm); background: transparent; color: var(--dsw-alias-label-secondary); font: inherit; font-size: 12px; line-height: 20px; white-space: nowrap; text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
|
|
534
|
-
.dsh-worktree-error-trigger:focus-visible { outline: 2px solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); outline-offset: 0; }
|
|
535
669
|
.dsh-worktree-dialog.dsh-worktree-error-dialog { inset: 0; margin: auto; }
|
|
536
670
|
.dsh-worktree-error-dialog dl { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 8px 12px; }
|
|
537
671
|
.dsh-worktree-error-dialog dt { color: var(--dsw-alias-label-secondary); }
|
|
@@ -547,40 +681,35 @@ const styles = `
|
|
|
547
681
|
.dsh-worktree-dialog button:disabled { opacity: .45; cursor: not-allowed; }
|
|
548
682
|
.dsh-worktree-dialog button:focus-visible { outline: 2px solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); outline-offset: 2px; }
|
|
549
683
|
.dsh-worktree-sr { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
|
|
550
|
-
.dsh-worktree-progress { margin: 12px 0 20px; color: var(--dsw-alias-label-secondary); font: 13px
|
|
684
|
+
.dsh-worktree-progress { margin: 12px 0 20px; color: var(--dsw-alias-label-secondary); font: inherit; font-size: var(--dsh-content-font-size-secondary, 13px); line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px)); }
|
|
551
685
|
.dsh-worktree-progress > p { margin: 0 0 8px; overflow-wrap: anywhere; }
|
|
552
686
|
.dsh-worktree-progress ol { list-style: none; margin: 0; padding: 8px 0 0; border-top: 1px solid var(--dsw-alias-border-l2); }
|
|
553
687
|
.dsh-worktree-progress li { display: flex; align-items: center; gap: 10px; padding: 3px 4px; }
|
|
554
688
|
.dsh-worktree-progress li[data-state=pending] { opacity: .4; }
|
|
555
|
-
.dsh-worktree-progress-mark {
|
|
556
|
-
.dsh-worktree-progress
|
|
689
|
+
.dsh-worktree-progress-mark { display: inline-flex; flex: none; align-items: center; justify-content: center; width: calc(16px + var(--dsh-content-font-delta, 0px)); height: calc(16px + var(--dsh-content-font-delta, 0px)); }
|
|
690
|
+
.dsh-worktree-progress-mark svg { width: calc(14px + var(--dsh-content-font-delta, 0px)); height: calc(14px + var(--dsh-content-font-delta, 0px)); }
|
|
557
691
|
.dsh-worktree-progress li[data-state=failed], .dsh-worktree-progress [role=alert] { color: var(--dsw-alias-state-error-primary); }
|
|
558
692
|
.dsh-worktree-progress [role=alert] { margin-top: 10px; font: inherit; }
|
|
559
|
-
.dsh-worktree-progress-time { margin-inline-start: auto; padding-inline-start: 8px; font-size:
|
|
693
|
+
.dsh-worktree-progress-time { margin-inline-start: auto; padding-inline-start: 8px; font-size: calc(var(--dsh-content-font-size-secondary, 13px) - 1px); font-variant-numeric: tabular-nums; }
|
|
560
694
|
@keyframes dsh-worktree-spin { to { transform: rotate(360deg); } }
|
|
561
695
|
@media (prefers-reduced-motion: no-preference) {
|
|
562
696
|
.dsh-worktree-progress li[data-state=running] .dsh-worktree-progress-mark { animation: dsh-worktree-spin .8s linear infinite; }
|
|
563
697
|
}
|
|
564
698
|
.dsh-worktree-settings { width: 100%; color: var(--dsw-alias-label-primary); font: inherit; }
|
|
565
699
|
.dsh-worktree-settings fieldset { min-width: 0; padding: 0; margin: 0; border: 0; }
|
|
566
|
-
.dsh-worktree-settings-row {
|
|
567
|
-
.dsh-worktree-settings-row:last-child { border-bottom: 0; }
|
|
568
|
-
.dsh-worktree-settings-text { flex: 1; min-width: 0; }
|
|
700
|
+
.dsh-worktree-settings .dsh-plugin-settings-row { gap: 24px; }
|
|
701
|
+
.dsh-worktree-settings .dsh-plugin-settings-row:last-child { border-bottom: 0; }
|
|
569
702
|
.dsh-worktree-settings label, .dsh-worktree-settings-title { display: block; font-size: 14px; font-weight: 400; line-height: 20px; }
|
|
570
|
-
.dsh-worktree-settings-text p { margin: 4px 0 0; color: var(--dsw-alias-label-secondary); font-size: 12px; line-height: 18px; overflow-wrap: anywhere; }
|
|
703
|
+
.dsh-worktree-settings .dsh-plugin-settings-text p { margin: 4px 0 0; color: var(--dsw-alias-label-secondary); font-size: 12px; line-height: 18px; overflow-wrap: anywhere; }
|
|
571
704
|
.dsh-worktree-settings-input { box-sizing: border-box; width: 220px; max-width: 100%; flex: 0 1 220px; min-width: 0; }
|
|
572
|
-
.dsh-worktree-settings-
|
|
573
|
-
.dsh-worktree-settings-selector
|
|
574
|
-
.dsh-worktree-settings-selector svg { flex: none; }
|
|
575
|
-
.dsh-worktree-settings-selector:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }
|
|
576
|
-
.dsh-worktree-settings-selector:disabled { opacity: .5; cursor: default; }
|
|
577
|
-
.dsh-worktree-settings-selector:focus-visible { outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); outline-offset: 2px; }
|
|
705
|
+
.dsh-worktree-settings-picker .dsh-plugin-settings-selector { max-width: 220px; color: var(--dsw-alias-label-primary); font-size: 14px; line-height: 22px; }
|
|
706
|
+
.dsh-worktree-settings-picker .dsh-plugin-settings-selector:focus-visible { outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); }
|
|
578
707
|
.dsh-worktree-settings [role=status], .dsh-worktree-settings [role=alert] { margin: 12px 0; font-size: 12px; line-height: 18px; }
|
|
579
708
|
.dsh-worktree-settings [role=status]:empty { margin: 0; }
|
|
580
709
|
.dsh-worktree-settings [role=alert] { color: var(--dsw-alias-state-error-primary); }
|
|
581
710
|
@media (max-width: 600px) {
|
|
582
|
-
.dsh-worktree-settings-row { flex-wrap: wrap; gap: 12px; }
|
|
583
|
-
.dsh-worktree-settings-text { flex-basis: 160px; }
|
|
711
|
+
.dsh-worktree-settings .dsh-plugin-settings-row { flex-wrap: wrap; gap: 12px; }
|
|
712
|
+
.dsh-worktree-settings .dsh-plugin-settings-text { flex-basis: 160px; }
|
|
584
713
|
.dsh-worktree-settings-input { flex-grow: 1; }
|
|
585
714
|
}
|
|
586
715
|
`;
|
|
@@ -699,7 +828,7 @@ const progressDefinition = {
|
|
|
699
828
|
anchorSeq: context.state.anchorSeq,
|
|
700
829
|
location: context.start.location,
|
|
701
830
|
visibility: "visible",
|
|
702
|
-
groupTree: "turn",
|
|
831
|
+
groupTree: context.state.progress.status === "completed" ? void 0 : "turn",
|
|
703
832
|
data: context.state.progress
|
|
704
833
|
};
|
|
705
834
|
}
|
|
@@ -711,7 +840,7 @@ function installProgress(ctx) {
|
|
|
711
840
|
key: "dungsil-worktree-progress",
|
|
712
841
|
inject: () => ({})
|
|
713
842
|
}, function Progress({ node }) {
|
|
714
|
-
const
|
|
843
|
+
const { active: language } = useLocaleSnapshot(ctx.locale);
|
|
715
844
|
const text = labels[language.startsWith("ko") ? "ko" : language.startsWith("zh") ? "zh" : "en"];
|
|
716
845
|
const progress = node.data;
|
|
717
846
|
const ended = node.location.kind === "turn" || node.location.kind === "step" ? node.location.turn.status === "closed" : false;
|
|
@@ -741,7 +870,7 @@ function installProgress(ctx) {
|
|
|
741
870
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
742
871
|
className: "dsh-worktree-progress-mark",
|
|
743
872
|
"aria-hidden": "true",
|
|
744
|
-
children: state === "completed" ?
|
|
873
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(state === "completed" ? _deepseek_ai_dsh_client_ui_primitives.IconCheckCircleOutlineRegular : state === "failed" ? _deepseek_ai_dsh_client_ui_primitives.IconCloseOutlineRegular : state === "cancelled" ? _deepseek_ai_dsh_client_ui_primitives.IconStopFillRegular : state === "running" ? _deepseek_ai_dsh_client_ui_primitives.IconLoadingOutlineRegular : _deepseek_ai_dsh_client_ui_primitives.IconClockOutlineRegular, { size: 14 })
|
|
745
874
|
}),
|
|
746
875
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }),
|
|
747
876
|
state !== "pending" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -768,11 +897,15 @@ const inject = [
|
|
|
768
897
|
"uiConversation"
|
|
769
898
|
];
|
|
770
899
|
function apply(ctx) {
|
|
900
|
+
ctx.effect(() => installSettingsIcon(name, [
|
|
901
|
+
"en",
|
|
902
|
+
"ko",
|
|
903
|
+
"zh"
|
|
904
|
+
].map(settingsLabel), git_branch_default));
|
|
771
905
|
const connection = ctx.get("connection");
|
|
772
|
-
const subscribeLocale = (listener) => ctx.locale.subscribe(listener);
|
|
773
906
|
installProgress(ctx);
|
|
774
907
|
function Picker({ sessionId, useSession }) {
|
|
775
|
-
const language = (
|
|
908
|
+
const { active: language } = useLocaleSnapshot(ctx.locale);
|
|
776
909
|
const text = copy$1[language.startsWith("ko") ? "ko" : language.startsWith("zh") ? "zh" : "en"];
|
|
777
910
|
const unavailable = useSession((s) => s.running || s.removed || s.openState !== "open" || s.pendingSubmissions.length > 0);
|
|
778
911
|
const running = useSession((s) => s.running);
|
|
@@ -788,7 +921,6 @@ function apply(ctx) {
|
|
|
788
921
|
const lifetime = (0, react.useRef)(null);
|
|
789
922
|
const trigger = (0, react.useRef)(null);
|
|
790
923
|
const errorDialog = (0, react.useRef)(null);
|
|
791
|
-
const errorTrigger = (0, react.useRef)(null);
|
|
792
924
|
const errorTitle = (0, react.useId)();
|
|
793
925
|
const errorDescription = (0, react.useId)();
|
|
794
926
|
const status = (0, react.useId)();
|
|
@@ -904,7 +1036,7 @@ function apply(ctx) {
|
|
|
904
1036
|
if (!detailError && errorDialog.current?.open) errorDialog.current.close();
|
|
905
1037
|
}, [error, creation?.failed]);
|
|
906
1038
|
const restoreErrorFocus = () => {
|
|
907
|
-
|
|
1039
|
+
trigger.current?.focus();
|
|
908
1040
|
};
|
|
909
1041
|
const current = currentWorktree(view);
|
|
910
1042
|
(0, react.useEffect)(() => {
|
|
@@ -916,79 +1048,57 @@ function apply(ctx) {
|
|
|
916
1048
|
view
|
|
917
1049
|
]);
|
|
918
1050
|
const branch = creation ? creation.failed ? text.mutationError : text.create : current ? current.branch ?? text.detached : text.unknown;
|
|
1051
|
+
const label = detailError ? errorLabel : view ? branch : loading ? text.loading : text.title;
|
|
919
1052
|
const notice = loading ? text.loading : busy ? text.busy : error ? errorLabel : unavailable ? text.wait : creation ? creation.failed ? text.creationError : text.reservationHint : changed ? text.changed : view?.worktrees.length === 0 ? text.empty : "";
|
|
920
1053
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
921
1054
|
className: "dsh-worktree",
|
|
922
1055
|
children: [
|
|
923
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
924
|
-
className: "dsh-worktree-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
if (
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1056
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
1057
|
+
className: "dsh-worktree-picker",
|
|
1058
|
+
open,
|
|
1059
|
+
side: "top",
|
|
1060
|
+
portal: true,
|
|
1061
|
+
selectedId: creation ? "create" : current ? `path:${current.path}` : void 0,
|
|
1062
|
+
items: [{
|
|
1063
|
+
id: "create",
|
|
1064
|
+
label: text.create,
|
|
1065
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutlineRegular, {})
|
|
1066
|
+
}, ...view?.worktrees.map((item) => ({
|
|
1067
|
+
id: `path:${item.path}`,
|
|
1068
|
+
label: item.branch ?? text.detached,
|
|
1069
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutlineRegular, {})
|
|
1070
|
+
})) ?? []],
|
|
1071
|
+
onClose: () => setOpen(false),
|
|
1072
|
+
onSelect: (id) => {
|
|
1073
|
+
setOpen(false);
|
|
1074
|
+
if (disabled || pending.current) return;
|
|
1075
|
+
if (id === "create") {
|
|
1076
|
+
if (!creation || creation.failed) change("create");
|
|
1077
|
+
} else if (id.startsWith("path:") && (creation || id !== `path:${current?.path}`)) change("switch", id.slice(5));
|
|
1078
|
+
},
|
|
1079
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ComposerTrigger, {
|
|
1080
|
+
ref: trigger,
|
|
1081
|
+
className: "dsh-worktree-trigger",
|
|
1082
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutlineRegular, {}),
|
|
1083
|
+
label,
|
|
1084
|
+
disabled: detailError ? loading || busy : disabled,
|
|
1085
|
+
"aria-label": `${text.title}: ${label}`,
|
|
1086
|
+
"aria-haspopup": detailError ? "dialog" : "menu",
|
|
1087
|
+
"aria-expanded": detailError ? void 0 : open,
|
|
1088
|
+
"aria-describedby": status,
|
|
1089
|
+
"aria-busy": loading || busy,
|
|
1090
|
+
title: label,
|
|
1091
|
+
onClick: () => {
|
|
1092
|
+
if (detailError) errorDialog.current?.showModal();
|
|
1093
|
+
else setOpen((value) => !value);
|
|
947
1094
|
},
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
"aria-expanded": open,
|
|
956
|
-
"aria-describedby": status,
|
|
957
|
-
"aria-busy": loading || busy,
|
|
958
|
-
title: view ? branch : text.title,
|
|
959
|
-
onClick: () => setOpen((value) => !value),
|
|
960
|
-
onKeyDown: (event) => {
|
|
961
|
-
if (!open && (event.key === "ArrowDown" || event.key === "ArrowUp")) {
|
|
962
|
-
event.preventDefault();
|
|
963
|
-
setOpen(true);
|
|
964
|
-
}
|
|
965
|
-
},
|
|
966
|
-
children: [
|
|
967
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
968
|
-
className: "dsh-worktree-icon",
|
|
969
|
-
"aria-hidden": "true",
|
|
970
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutlineRegular, { className: "dsh-worktree-branch" })
|
|
971
|
-
}),
|
|
972
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
973
|
-
className: "dsh-worktree-name",
|
|
974
|
-
children: view ? branch : loading ? text.loading : text.title
|
|
975
|
-
}),
|
|
976
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
977
|
-
className: "dsh-worktree-chevron",
|
|
978
|
-
"aria-hidden": "true",
|
|
979
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {})
|
|
980
|
-
})
|
|
981
|
-
]
|
|
982
|
-
})
|
|
983
|
-
}), detailError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
984
|
-
ref: errorTrigger,
|
|
985
|
-
type: "button",
|
|
986
|
-
className: "dsh-worktree-error-trigger",
|
|
987
|
-
"aria-haspopup": "dialog",
|
|
988
|
-
"aria-label": `${errorLabel}: ${text.errorTitle}`,
|
|
989
|
-
onClick: () => errorDialog.current?.showModal(),
|
|
990
|
-
children: errorLabel
|
|
991
|
-
})]
|
|
1095
|
+
onKeyDown: (event) => {
|
|
1096
|
+
if (!detailError && !disabled && !open && (event.key === "ArrowDown" || event.key === "ArrowUp")) {
|
|
1097
|
+
event.preventDefault();
|
|
1098
|
+
setOpen(true);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
})
|
|
992
1102
|
}),
|
|
993
1103
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
994
1104
|
id: status,
|
|
@@ -1046,12 +1156,7 @@ function apply(ctx) {
|
|
|
1046
1156
|
label: () => settingsLabel(ctx.locale.getSnapshot().active),
|
|
1047
1157
|
inject: () => ({ ctx })
|
|
1048
1158
|
}, WorktreeSettingsSection));
|
|
1049
|
-
ctx.effect(() =>
|
|
1050
|
-
const style = document.createElement("style");
|
|
1051
|
-
style.textContent = styles;
|
|
1052
|
-
document.head.append(style);
|
|
1053
|
-
return () => style.remove();
|
|
1054
|
-
}, "ui-worktree: composer styles");
|
|
1159
|
+
ctx.effect(() => installStyles(styles), "ui-worktree: composer styles");
|
|
1055
1160
|
}
|
|
1056
1161
|
exports.apply = apply;
|
|
1057
1162
|
exports.inject = inject;
|
package/dist/index.js
CHANGED
|
@@ -2,11 +2,12 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import { clientRequestSchema } from "@deepseek-ai/dsh-client-connection";
|
|
4
4
|
import { BlockAssembler, ReasoningEffortId, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
5
|
-
import { SessionId, appendPluginRecord } from "@deepseek-ai/dsh-session";
|
|
5
|
+
import { SessionId, appendPluginRecord, pluginRecordOf } from "@deepseek-ai/dsh-session";
|
|
6
6
|
// src/protocol.ts
|
|
7
7
|
const channel = "/api";
|
|
8
8
|
const endpointPrefix = "dungsil-worktree/";
|
|
9
9
|
const progressRecord = "plugin:dungsil/ui-worktree/progress";
|
|
10
|
+
const selectionRecord = "plugin:dungsil/ui-worktree/selection";
|
|
10
11
|
// src/index.ts
|
|
11
12
|
const name = "dungsil-ui-worktree";
|
|
12
13
|
const inject = [
|
|
@@ -22,6 +23,7 @@ const inject = [
|
|
|
22
23
|
"sandboxPolicy"
|
|
23
24
|
];
|
|
24
25
|
const Config = z.object({
|
|
26
|
+
defaultSelection: z.union(["current", "new"]).default("current"),
|
|
25
27
|
directory: z.string().default(".agents/worktrees"),
|
|
26
28
|
remote: z.boolean().default(false),
|
|
27
29
|
naming: z.union([
|
|
@@ -37,7 +39,7 @@ const Config = z.object({
|
|
|
37
39
|
function validSettings(value) {
|
|
38
40
|
if (!value || typeof value !== "object") return false;
|
|
39
41
|
const input = value;
|
|
40
|
-
return typeof input.directory === "string" && input.directory.trim().length > 0 && input.directory.length <= 4096 && !/[\0\r\n]/.test(input.directory) && typeof input.remote === "boolean" && [
|
|
42
|
+
return ["current", "new"].includes(input.defaultSelection) && typeof input.directory === "string" && input.directory.trim().length > 0 && input.directory.length <= 4096 && !/[\0\r\n]/.test(input.directory) && typeof input.remote === "boolean" && [
|
|
41
43
|
"semantic",
|
|
42
44
|
"dsh",
|
|
43
45
|
"custom"
|
|
@@ -213,13 +215,22 @@ function apply(ctx, config) {
|
|
|
213
215
|
for (const record of output.split("\0\0")) {
|
|
214
216
|
const fields = record.split("\0");
|
|
215
217
|
const path = fields.find((field) => field.startsWith("worktree "))?.slice(9);
|
|
216
|
-
if (!path || fields.includes("bare")) continue;
|
|
218
|
+
if (!path || fields.includes("bare") || fields.some((field) => /^prunable(?: |$)/.test(field))) continue;
|
|
217
219
|
const branch = fields.find((field) => field.startsWith("branch refs/heads/"))?.slice(18) ?? null;
|
|
218
220
|
worktrees.push({
|
|
219
221
|
path,
|
|
220
222
|
branch
|
|
221
223
|
});
|
|
222
224
|
}
|
|
225
|
+
if (!agent.session.snapshotEvents().some((event) => pluginRecordOf(event)?.type === "plugin:dungsil/ui-worktree/selection")) {
|
|
226
|
+
if (config.defaultSelection === "new" && !creations.has(agent) && !agent.session.deriveMessages().some((message) => ["user", "user-rpc"].includes(message.source.kind))) creations.set(agent, {
|
|
227
|
+
cwd,
|
|
228
|
+
name: "",
|
|
229
|
+
failed: false
|
|
230
|
+
});
|
|
231
|
+
appendPluginRecord(agent.session, selectionRecord, { initialized: true });
|
|
232
|
+
await ctx.sessions.flush(agent.session);
|
|
233
|
+
}
|
|
223
234
|
const creation = creations.get(agent);
|
|
224
235
|
return {
|
|
225
236
|
cwd,
|
|
@@ -240,6 +251,7 @@ function apply(ctx, config) {
|
|
|
240
251
|
return {
|
|
241
252
|
revision: createHash("sha256").update(JSON.stringify(current ?? {})).digest("hex"),
|
|
242
253
|
settings: {
|
|
254
|
+
defaultSelection: current?.defaultSelection ?? "current",
|
|
243
255
|
directory: current?.directory ?? ".agents/worktrees",
|
|
244
256
|
remote: current?.remote ?? false,
|
|
245
257
|
naming: current?.naming ?? "semantic",
|
|
@@ -274,33 +286,67 @@ function apply(ctx, config) {
|
|
|
274
286
|
"--format=%(refname)",
|
|
275
287
|
`refs/heads/${branch}`
|
|
276
288
|
])) throw new Error("Branch already exists");
|
|
277
|
-
const
|
|
278
|
-
cwd: root,
|
|
279
|
-
signal
|
|
280
|
-
}));
|
|
281
|
-
const path = ctx.fs.processPath(await ctx.fs.resolve(branch, {
|
|
282
|
-
cwd: pool,
|
|
283
|
-
signal
|
|
284
|
-
}));
|
|
285
|
-
await run(agent, cwd, [
|
|
289
|
+
const allocate = async (checkOnly = false) => JSON.parse(await run(agent, cwd, [
|
|
286
290
|
"--input-type=module",
|
|
287
291
|
"-e",
|
|
288
292
|
`
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
293
|
+
import fs from 'node:fs/promises';
|
|
294
|
+
import path from 'node:path';
|
|
295
|
+
const [source, configured, branch, checkOnly] = process.argv.slice(1);
|
|
296
|
+
let pool = path.resolve(source, configured);
|
|
297
|
+
const fromSource = path.relative(source, pool);
|
|
298
|
+
// An explicitly configured external pool may use owner-selected symlinks.
|
|
299
|
+
// Repository-relative pools must be inspected before following links.
|
|
300
|
+
if (fromSource === '..' || fromSource.startsWith('..' + path.sep) || path.isAbsolute(fromSource)) {
|
|
301
|
+
async function canonicalParent(target) {
|
|
302
|
+
try { return await fs.realpath(target); }
|
|
303
|
+
catch (error) {
|
|
304
|
+
if (error.code !== 'ENOENT') throw error;
|
|
305
|
+
const parent = path.dirname(target);
|
|
306
|
+
if (parent === target) throw error;
|
|
307
|
+
return path.join(await canonicalParent(parent), path.basename(target));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
pool = await canonicalParent(pool);
|
|
311
|
+
}
|
|
312
|
+
const destination = path.resolve(pool, branch);
|
|
313
|
+
const relative = path.relative(pool, destination);
|
|
314
|
+
if (!relative || relative.startsWith('..' + path.sep) || path.isAbsolute(relative))
|
|
315
|
+
throw new Error('Invalid destination');
|
|
316
|
+
async function walk(target, create) {
|
|
317
|
+
let current = path.parse(target).root;
|
|
318
|
+
for (const component of target.slice(current.length).split(path.sep).filter(Boolean)) {
|
|
319
|
+
current = path.join(current, component);
|
|
320
|
+
if (create) {
|
|
321
|
+
try { await fs.mkdir(current); }
|
|
322
|
+
catch (error) { if (error.code !== 'EEXIST') throw error; }
|
|
323
|
+
}
|
|
324
|
+
const info = await fs.lstat(current);
|
|
325
|
+
if (!info.isDirectory() || info.isSymbolicLink()) throw new Error('Unsafe directory');
|
|
326
|
+
}
|
|
327
|
+
if (await fs.realpath(target) !== target) throw new Error('Directory changed');
|
|
328
|
+
}
|
|
329
|
+
await walk(path.dirname(pool), !checkOnly);
|
|
330
|
+
if (!checkOnly) {
|
|
331
|
+
try {
|
|
332
|
+
await fs.mkdir(pool);
|
|
333
|
+
await fs.writeFile(path.join(pool, '.gitignore'), '*\\n', { flag: 'wx' });
|
|
334
|
+
} catch (error) { if (error.code !== 'EEXIST') throw error; }
|
|
335
|
+
}
|
|
336
|
+
await walk(pool, false);
|
|
337
|
+
await walk(path.dirname(destination), !checkOnly);
|
|
338
|
+
if (!checkOnly) await fs.mkdir(destination);
|
|
339
|
+
await walk(destination, false);
|
|
340
|
+
process.stdout.write(JSON.stringify(destination));
|
|
341
|
+
`,
|
|
342
|
+
root,
|
|
343
|
+
config.directory.trim(),
|
|
344
|
+
branch,
|
|
345
|
+
checkOnly ? "check" : ""
|
|
346
|
+
], signal, "node"));
|
|
347
|
+
const path = await allocate();
|
|
303
348
|
report(2);
|
|
349
|
+
await allocate(true);
|
|
304
350
|
await git([
|
|
305
351
|
"worktree",
|
|
306
352
|
"add",
|
|
@@ -311,13 +357,15 @@ function apply(ctx, config) {
|
|
|
311
357
|
path,
|
|
312
358
|
commit
|
|
313
359
|
]);
|
|
314
|
-
const filters = await
|
|
360
|
+
const filters = await run(agent, path, [
|
|
361
|
+
"--work-tree",
|
|
362
|
+
path,
|
|
315
363
|
"config",
|
|
316
364
|
"--null",
|
|
317
365
|
"--name-only",
|
|
318
366
|
"--get-regexp",
|
|
319
367
|
"^filter\\."
|
|
320
|
-
],
|
|
368
|
+
], signal, "git", {}, [0, 1]);
|
|
321
369
|
const drivers = new Set(filters.split("\0").flatMap((key) => {
|
|
322
370
|
const separator = key.lastIndexOf(".");
|
|
323
371
|
return separator > 7 && [
|
|
@@ -338,6 +386,7 @@ function apply(ctx, config) {
|
|
|
338
386
|
env[`GIT_CONFIG_KEY_${index}`] = `filter.${driver}.${attribute}`;
|
|
339
387
|
env[`GIT_CONFIG_VALUE_${index++}`] = attribute === "required" ? "false" : "";
|
|
340
388
|
}
|
|
389
|
+
await allocate(true);
|
|
341
390
|
await run(agent, path, [
|
|
342
391
|
"--work-tree",
|
|
343
392
|
path,
|
|
@@ -371,9 +420,10 @@ function apply(ctx, config) {
|
|
|
371
420
|
signal.throwIfAborted();
|
|
372
421
|
await ctx.configEditor.edit(entry(), (current) => {
|
|
373
422
|
if (snapshot().revision !== input.revision) throw new Error("conflict");
|
|
374
|
-
const { directory, remote, naming, prefix } = input.settings;
|
|
423
|
+
const { defaultSelection, directory, remote, naming, prefix } = input.settings;
|
|
375
424
|
return {
|
|
376
425
|
...current,
|
|
426
|
+
defaultSelection,
|
|
377
427
|
directory,
|
|
378
428
|
remote,
|
|
379
429
|
naming,
|
|
@@ -431,7 +481,32 @@ function apply(ctx, config) {
|
|
|
431
481
|
});
|
|
432
482
|
else {
|
|
433
483
|
if (!(await describe(agent, operationSignal)).worktrees.some((entry) => entry.path === input.path)) return failure("invalid");
|
|
434
|
-
|
|
484
|
+
const targetPath = input.path;
|
|
485
|
+
const resolve = (path, cwd) => ctx.fs.resolve(path, {
|
|
486
|
+
cwd,
|
|
487
|
+
signal: operationSignal
|
|
488
|
+
});
|
|
489
|
+
const sourceCwd = await ctx.workingDirectory.ensure(agent, operationSignal);
|
|
490
|
+
const rev = async (cwd, option) => (await run(agent, cwd, [
|
|
491
|
+
"rev-parse",
|
|
492
|
+
"--path-format=absolute",
|
|
493
|
+
option
|
|
494
|
+
], operationSignal)).replace(/\r?\n$/, "");
|
|
495
|
+
const target = await resolve(targetPath, sourceCwd);
|
|
496
|
+
const top = await resolve(await rev(targetPath, "--show-toplevel"), targetPath);
|
|
497
|
+
const common = await resolve(await rev(sourceCwd, "--git-common-dir"), sourceCwd);
|
|
498
|
+
const targetCommon = await resolve(await rev(targetPath, "--git-common-dir"), targetPath);
|
|
499
|
+
if (target.targetKey !== top.targetKey || common.targetKey !== targetCommon.targetKey) return failure("invalid");
|
|
500
|
+
const admin = await resolve(await rev(targetPath, "--absolute-git-dir"), targetPath);
|
|
501
|
+
const dotGitInfo = await ctx.fs.lstat(".git", { cwd: targetPath }, operationSignal);
|
|
502
|
+
if (!dotGitInfo || !["file", "directory"].includes(dotGitInfo.type)) return failure("invalid");
|
|
503
|
+
const dotGit = await resolve(".git", targetPath);
|
|
504
|
+
if (admin.targetKey !== common.targetKey) {
|
|
505
|
+
const backlink = await resolve("gitdir", ctx.fs.processPath(admin));
|
|
506
|
+
const bytes = await ctx.fs.readBytes(backlink, operationSignal, 8192);
|
|
507
|
+
if ((await resolve(new TextDecoder().decode(bytes).replace(/\r?\n$/, ""), ctx.fs.processPath(admin))).targetKey !== dotGit.targetKey) return failure("invalid");
|
|
508
|
+
}
|
|
509
|
+
await ctx.workingDirectory.set(agent, ctx.fs.processPath(target), operationSignal);
|
|
435
510
|
creations.delete(agent);
|
|
436
511
|
}
|
|
437
512
|
await ctx.sessions.flush(agent.session);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dungsil/dsh-plugin-ui-worktree",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Automatically name, create, and enter Git worktrees after sending a task, with setup progress in the conversation.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -33,7 +33,6 @@
|
|
|
33
33
|
"@deepseek-ai/dsh-client-locale": "0.2.1-rc.5",
|
|
34
34
|
"@deepseek-ai/dsh-client-ui-chat": "0.2.1-rc.5",
|
|
35
35
|
"@deepseek-ai/dsh-client-ui-conversation": "0.2.1-rc.5",
|
|
36
|
-
"@deepseek-ai/dsh-client-ui-primitives": "0.2.1-rc.5",
|
|
37
36
|
"@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.5",
|
|
38
37
|
"@deepseek-ai/dsh-client-ui-session": "0.2.1-rc.5",
|
|
39
38
|
"@deepseek-ai/dsh-client-ui-settings": "0.2.1-rc.5",
|
|
@@ -50,12 +49,12 @@
|
|
|
50
49
|
"@deepseek-ai/dsh-subprocess-local": "0.2.1-rc.5",
|
|
51
50
|
"@deepseek-ai/dsh-working-directory": "0.2.1-rc.5",
|
|
52
51
|
"@deepseek-ai/schemastery": "~3.18.5",
|
|
53
|
-
"@dungsil/dsh-plugin-model-settings": "0.
|
|
52
|
+
"@dungsil/dsh-plugin-model-settings": "0.4.0",
|
|
54
53
|
"@dungsil/dsh-tsconfig": "0.0.0",
|
|
54
|
+
"@dungsil/dsh-tsdown": "0.0.0",
|
|
55
|
+
"@dungsil/dsh-ui": "0.0.0",
|
|
55
56
|
"@types/react": "18.3.31",
|
|
56
|
-
"happy-dom": "21.0.4",
|
|
57
57
|
"react": "18.3.1",
|
|
58
|
-
"react-dom": "18.3.1",
|
|
59
58
|
"tsdown": "0.23.4"
|
|
60
59
|
},
|
|
61
60
|
"peerDependencies": {
|