@jingyi0605/codingns4dsh 0.1.0 → 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.
Files changed (216) hide show
  1. package/README.en.md +306 -0
  2. package/README.md +190 -993
  3. package/data/build/dist/client/account-bar.d.ts +2 -2
  4. package/data/build/dist/client/account-bar.js +96 -39
  5. package/data/build/dist/client/bundle.js +13722 -3611
  6. package/data/build/dist/client/cli-catalog.js +20 -3
  7. package/data/build/dist/client/cli-slots.d.ts +5 -0
  8. package/data/build/dist/client/cli-slots.js +57 -7
  9. package/data/build/dist/client/debug/ui.d.ts +1 -1
  10. package/data/build/dist/client/debug/ui.js +62 -32
  11. package/data/build/dist/client/dsh-runtime-version.d.ts +9 -2
  12. package/data/build/dist/client/dsh-runtime-version.js +24 -2
  13. package/data/build/dist/client/features/cli-adapters.d.ts +1 -1
  14. package/data/build/dist/client/features/cli-adapters.js +19 -16
  15. package/data/build/dist/client/features/debug.js +2 -2
  16. package/data/build/dist/client/features/index.d.ts +2 -1
  17. package/data/build/dist/client/features/index.js +3 -1
  18. package/data/build/dist/client/features/lan-access-panel.d.ts +1 -1
  19. package/data/build/dist/client/features/lan-access-panel.js +8 -10
  20. package/data/build/dist/client/features/lan-access.js +1 -1
  21. package/data/build/dist/client/features/login-protection-panel.d.ts +1 -1
  22. package/data/build/dist/client/features/login-protection-panel.js +7 -9
  23. package/data/build/dist/client/features/reverse-proxy-panel.d.ts +1 -1
  24. package/data/build/dist/client/features/reverse-proxy-panel.js +99 -11
  25. package/data/build/dist/client/features/reverse-proxy.d.ts +1 -1
  26. package/data/build/dist/client/features/reverse-proxy.js +19 -18
  27. package/data/build/dist/client/features/terminal-enhancement-panel.d.ts +1 -1
  28. package/data/build/dist/client/features/terminal-enhancement-panel.js +62 -55
  29. package/data/build/dist/client/features/terminal-enhancement.d.ts +1 -1
  30. package/data/build/dist/client/features/terminal-enhancement.js +3 -3
  31. package/data/build/dist/client/features/types.d.ts +14 -3
  32. package/data/build/dist/client/features/workspace-session-enhancement-panel.d.ts +1 -1
  33. package/data/build/dist/client/features/workspace-session-enhancement-panel.js +31 -9
  34. package/data/build/dist/client/features/workspace-session-enhancement.js +72 -11
  35. package/data/build/dist/client/git-management.d.ts +16 -0
  36. package/data/build/dist/client/git-management.js +757 -0
  37. package/data/build/dist/client/index.d.ts +3 -2
  38. package/data/build/dist/client/index.js +152 -27
  39. package/data/build/dist/client/locale.js +91 -29
  40. package/data/build/dist/client/quick-phrase-layout.d.ts +49 -0
  41. package/data/build/dist/client/quick-phrase-layout.js +95 -0
  42. package/data/build/dist/client/quick-phrase-slot.d.ts +7 -0
  43. package/data/build/dist/client/quick-phrase-slot.js +370 -0
  44. package/data/build/dist/client/remote-web-context.js +1 -1
  45. package/data/build/dist/client/settings-bridge.d.ts +9 -6
  46. package/data/build/dist/client/settings-bridge.js +51 -27
  47. package/data/build/dist/client/settings-section.d.ts +3 -2
  48. package/data/build/dist/client/settings-section.js +33 -19
  49. package/data/build/dist/client/subscription-slot.d.ts +3 -0
  50. package/data/build/dist/client/subscription-slot.js +108 -16
  51. package/data/build/dist/client/terminal/model.d.ts +14 -2
  52. package/data/build/dist/client/terminal/model.js +109 -21
  53. package/data/build/dist/client/terminal/recovery.d.ts +50 -0
  54. package/data/build/dist/client/terminal/recovery.js +101 -0
  55. package/data/build/dist/client/terminal/ui.d.ts +2 -2
  56. package/data/build/dist/client/terminal/ui.js +49 -11
  57. package/data/build/dist/client/terminal/xterm-view.d.ts +2 -2
  58. package/data/build/dist/client/terminal/xterm-view.js +2 -1
  59. package/data/build/dist/client/theme.d.ts +6 -1
  60. package/data/build/dist/client/theme.js +32 -1
  61. package/data/build/dist/client/workspace-session-archive-dom.d.ts +12 -0
  62. package/data/build/dist/client/workspace-session-archive-dom.js +27 -5
  63. package/data/build/dist/client/workspace-session-visibility-dom.d.ts +30 -0
  64. package/data/build/dist/client/workspace-session-visibility-dom.js +604 -0
  65. package/data/build/dist/dsh-capabilities/client/config-forms-adapter.d.ts +50 -0
  66. package/data/build/dist/dsh-capabilities/client/config-forms-adapter.js +139 -0
  67. package/data/build/dist/dsh-capabilities/client/conversation-adapter.d.ts +7 -0
  68. package/data/build/dist/dsh-capabilities/client/conversation-adapter.js +6 -0
  69. package/data/build/dist/dsh-capabilities/client/locale-adapter.d.ts +9 -0
  70. package/data/build/dist/dsh-capabilities/client/locale-adapter.js +4 -0
  71. package/data/build/dist/dsh-capabilities/client/primitives-adapter.d.ts +10 -0
  72. package/data/build/dist/dsh-capabilities/client/primitives-adapter.js +14 -0
  73. package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.d.ts +8 -0
  74. package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.js +16 -0
  75. package/data/build/dist/dsh-capabilities/client/sidebar-adapter.d.ts +7 -0
  76. package/data/build/dist/dsh-capabilities/client/sidebar-adapter.js +5 -0
  77. package/data/build/dist/dsh-capabilities/client/theme-adapter.d.ts +6 -0
  78. package/data/build/dist/dsh-capabilities/client/theme-adapter.js +4 -0
  79. package/data/build/dist/dsh-capabilities/client/typert-adapter.d.ts +5 -0
  80. package/data/build/dist/dsh-capabilities/client/typert-adapter.js +4 -0
  81. package/data/build/dist/dsh-capabilities/host/config-forms-adapter.d.ts +19 -0
  82. package/data/build/dist/dsh-capabilities/host/config-forms-adapter.js +45 -0
  83. package/data/build/dist/dsh-capabilities/host/connection-rpc-adapter.d.ts +19 -0
  84. package/data/build/dist/dsh-capabilities/host/connection-rpc-adapter.js +13 -0
  85. package/data/build/dist/dsh-capabilities/host/settings-scope-adapter.d.ts +8 -0
  86. package/data/build/dist/dsh-capabilities/host/settings-scope-adapter.js +47 -0
  87. package/data/build/dist/dsh-capabilities/host/typert-adapter.d.ts +6 -0
  88. package/data/build/dist/dsh-capabilities/host/typert-adapter.js +4 -0
  89. package/data/build/dist/dsh-capabilities/index.d.ts +6 -0
  90. package/data/build/dist/dsh-capabilities/index.js +6 -0
  91. package/data/build/dist/dsh-capabilities/matrix.d.ts +15 -0
  92. package/data/build/dist/dsh-capabilities/matrix.js +32 -0
  93. package/data/build/dist/dsh-capabilities/registry.d.ts +22 -0
  94. package/data/build/dist/dsh-capabilities/registry.js +196 -0
  95. package/data/build/dist/dsh-capabilities/routes.d.ts +10 -0
  96. package/data/build/dist/dsh-capabilities/routes.js +34 -0
  97. package/data/build/dist/dsh-capabilities/settings-store.d.ts +29 -0
  98. package/data/build/dist/dsh-capabilities/settings-store.js +5 -0
  99. package/data/build/dist/dsh-capabilities/types.d.ts +50 -0
  100. package/data/build/dist/dsh-capabilities/types.js +23 -0
  101. package/data/build/dist/features/registry.d.ts +8 -2
  102. package/data/build/dist/features/registry.js +104 -1
  103. package/data/build/dist/host/auth-session.d.ts +8 -1
  104. package/data/build/dist/host/auth-session.js +28 -6
  105. package/data/build/dist/host/cli-adapters/claude-driver.d.ts +2 -0
  106. package/data/build/dist/host/cli-adapters/claude-driver.js +19 -4
  107. package/data/build/dist/host/cli-adapters/claude-model-options.d.ts +14 -0
  108. package/data/build/dist/host/cli-adapters/claude-model-options.js +157 -0
  109. package/data/build/dist/host/cli-adapters/codex-driver.d.ts +11 -0
  110. package/data/build/dist/host/cli-adapters/codex-driver.js +166 -14
  111. package/data/build/dist/host/cli-adapters/driver.d.ts +2 -0
  112. package/data/build/dist/host/cli-adapters/dsh-message-projector.d.ts +15 -0
  113. package/data/build/dist/host/cli-adapters/dsh-message-projector.js +120 -29
  114. package/data/build/dist/host/cli-adapters/dsh-tool-history.d.ts +4 -1
  115. package/data/build/dist/host/cli-adapters/dsh-tool-history.js +25 -7
  116. package/data/build/dist/host/cli-adapters/feature.js +74 -8
  117. package/data/build/dist/host/cli-adapters/gemini-driver.js +2 -2
  118. package/data/build/dist/host/cli-adapters/grok-driver.js +2 -2
  119. package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts +4 -2
  120. package/data/build/dist/host/cli-adapters/json-rpc-process.js +8 -9
  121. package/data/build/dist/host/cli-adapters/kimi-driver.js +1 -1
  122. package/data/build/dist/host/cli-adapters/legacy-session-adapter.d.ts +17 -0
  123. package/data/build/dist/host/cli-adapters/legacy-session-adapter.js +88 -0
  124. package/data/build/dist/host/cli-adapters/legacy-session-settings.d.ts +24 -0
  125. package/data/build/dist/host/cli-adapters/legacy-session-settings.js +162 -0
  126. package/data/build/dist/host/cli-adapters/official-provider-subscription.d.ts +29 -0
  127. package/data/build/dist/host/cli-adapters/official-provider-subscription.js +361 -0
  128. package/data/build/dist/host/cli-adapters/opencode-driver.d.ts +2 -0
  129. package/data/build/dist/host/cli-adapters/opencode-driver.js +97 -12
  130. package/data/build/dist/host/cli-adapters/pi-driver.js +69 -23
  131. package/data/build/dist/host/cli-adapters/provider-registry.d.ts +33 -0
  132. package/data/build/dist/host/cli-adapters/provider-registry.js +83 -0
  133. package/data/build/dist/host/cli-adapters/provider-subscription.d.ts +25 -3
  134. package/data/build/dist/host/cli-adapters/provider-subscription.js +346 -30
  135. package/data/build/dist/host/cli-adapters/registry.d.ts +6 -0
  136. package/data/build/dist/host/cli-adapters/registry.js +170 -13
  137. package/data/build/dist/host/cli-adapters/rpc-driver-utils.js +10 -0
  138. package/data/build/dist/host/cli-adapters/session-store.d.ts +7 -0
  139. package/data/build/dist/host/cli-adapters/session-store.js +39 -1
  140. package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts +3 -0
  141. package/data/build/dist/host/cli-adapters/stream-normalizer.d.ts +3 -0
  142. package/data/build/dist/host/cli-adapters/stream-normalizer.js +22 -2
  143. package/data/build/dist/host/control-api-client.d.ts +3 -3
  144. package/data/build/dist/host/control-api-client.js +2 -1
  145. package/data/build/dist/host/credential-store.js +11 -1
  146. package/data/build/dist/host/dsh-device-runtime.d.ts +4 -0
  147. package/data/build/dist/host/dsh-device-runtime.js +20 -9
  148. package/data/build/dist/host/dsh-runtime-version.d.ts +3 -2
  149. package/data/build/dist/host/dsh-runtime-version.js +71 -2
  150. package/data/build/dist/host/features/auth.js +8 -5
  151. package/data/build/dist/host/features/debug.js +1 -1
  152. package/data/build/dist/host/features/git-management.d.ts +5 -0
  153. package/data/build/dist/host/features/git-management.js +290 -0
  154. package/data/build/dist/host/features/host-status.js +1 -1
  155. package/data/build/dist/host/features/index.d.ts +1 -0
  156. package/data/build/dist/host/features/index.js +3 -0
  157. package/data/build/dist/host/features/terminal-process.js +1 -1
  158. package/data/build/dist/host/features/terminal-status.d.ts +3 -1
  159. package/data/build/dist/host/features/terminal-status.js +3 -1
  160. package/data/build/dist/host/index.d.ts +7 -3
  161. package/data/build/dist/host/index.js +79 -15
  162. package/data/build/dist/host/lan-access-dsh.js +18 -5
  163. package/data/build/dist/host/native-session-bridge.d.ts +23 -1
  164. package/data/build/dist/host/native-session-bridge.js +157 -10
  165. package/data/build/dist/host/rpc-table.d.ts +1 -1
  166. package/data/build/dist/host/rpc.js +60 -10
  167. package/data/build/dist/host/session-migration-repair.d.ts +19 -0
  168. package/data/build/dist/host/session-migration-repair.js +395 -0
  169. package/data/build/dist/host/settings.d.ts +8 -1
  170. package/data/build/dist/host/settings.js +94 -3
  171. package/data/build/dist/host/terminal/backends/tmux-backend.d.ts +1 -0
  172. package/data/build/dist/host/terminal/backends/tmux-backend.js +4 -2
  173. package/data/build/dist/host/terminal/controller-factory.d.ts +4 -0
  174. package/data/build/dist/host/terminal/controller-factory.js +26 -7
  175. package/data/build/dist/host/terminal/runtime-manager.d.ts +2 -1
  176. package/data/build/dist/host/terminal/runtime-manager.js +3 -0
  177. package/data/build/dist/host/terminal/startup.d.ts +2 -0
  178. package/data/build/dist/host/terminal/startup.js +2 -1
  179. package/data/build/dist/host/terminal/terminal-controller.d.ts +10 -3
  180. package/data/build/dist/host/terminal/terminal-controller.js +101 -15
  181. package/data/build/dist/host/terminal/terminal-process-service.js +3 -1
  182. package/data/build/dist/host/terminal/terminal-service.d.ts +2 -0
  183. package/data/build/dist/host/terminal/terminal-service.js +10 -0
  184. package/data/build/dist/index.d.ts +6 -2
  185. package/data/build/dist/index.js +8 -2
  186. package/data/build/dist/shared/contracts/cli-adapter.d.ts +14 -0
  187. package/data/build/dist/shared/contracts/config.d.ts +31 -1
  188. package/data/build/dist/shared/contracts/config.js +25 -0
  189. package/data/build/dist/shared/contracts/dsh-device.d.ts +13 -1
  190. package/data/build/dist/shared/contracts/errors.d.ts +1 -1
  191. package/data/build/dist/shared/contracts/feature.d.ts +6 -0
  192. package/data/build/dist/shared/contracts/git.d.ts +82 -0
  193. package/data/build/dist/shared/contracts/git.js +3 -0
  194. package/data/build/dist/shared/contracts/subscription.d.ts +46 -0
  195. package/data/build/dist/shared/contracts/terminal.d.ts +4 -1
  196. package/data/build/dist/shared/contracts/version.d.ts +2 -2
  197. package/data/build/dist/shared/contracts/version.js +4 -6
  198. package/data/build/dist/shared/debug.d.ts +14 -0
  199. package/data/build/dist/shared/debug.js +53 -0
  200. package/data/build/dist/shared/index.d.ts +7 -4
  201. package/data/build/dist/shared/index.js +2 -1
  202. package/data/build/dist/transport/debug.d.ts +1 -1
  203. package/data/build/dist/transport/debug.js +3 -30
  204. package/data/build/dist/transport/dsh-session.js +3 -3
  205. package/data/build/dist/typert.host.d.ts +1 -1
  206. package/data/build/dist/typert.host.js +6 -3
  207. package/data/build/dist/typert.remote-client.d.ts +11 -0
  208. package/data/build/dist/typert.remote-client.js +7 -0
  209. package/dsh.bundle.patch +1 -1
  210. package/package.json +13 -5
  211. package/profile/README.md +4 -4
  212. package/profile/package.json +6 -4
  213. package/profile/scripts/check-dsh-install.mjs +2 -2
  214. package/profile/version.json +2 -2
  215. package/scripts/check-dsh-install.mjs +2 -2
  216. package/version.json +2 -2
@@ -0,0 +1,757 @@
1
+ import { createElement, useEffect, useRef, useState, useSyncExternalStore } from 'react';
2
+ import { CODINGNS_RPC_CHANNEL } from '../shared/contracts/transport.js';
3
+ import { debugWarn } from '../shared/debug.js';
4
+ import { dshSettingsToastStyle, dshThemeColor } from './theme.js';
5
+ export const GIT_PROVIDER_ID = 'codingns4dsh/git';
6
+ export const GIT_KIND = 'git';
7
+ const INITIAL_HISTORY_LIMIT = 50;
8
+ const HISTORY_PAGE_SIZE = 100;
9
+ /** 兼容早期调用方使用的面板标识;实际注册已迁移到右侧 Sidebar。 */
10
+ export const GIT_PANEL_ID = GIT_PROVIDER_ID;
11
+ /** 注册 DSH 右侧 Sidebar 的 Git 标签类型,数据按 Workspace 复用。 */
12
+ export function registerGitManagementUi(ctx, services) {
13
+ const disposers = [];
14
+ const sidebarRight = ctx.sidebarRight;
15
+ try {
16
+ disposers.push(ctx.sidebarRightTabs.register({
17
+ id: GIT_PROVIDER_ID,
18
+ kind: GIT_KIND,
19
+ multiple: false,
20
+ priority: 'extension',
21
+ title: () => 'Git',
22
+ guide: [{ id: 'git', order: 40, title: () => 'Git', description: () => '查看改动、提交和版本历史', icon: GitPanelIcon }],
23
+ }));
24
+ disposers.push(ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register({
25
+ name: 'sidebar.right.pane.tab', key: GIT_PROVIDER_ID,
26
+ inject: () => ({ rpc: services.rpc, remote: services.remote }),
27
+ }, GitPanel)));
28
+ disposers.push(ctx.slots.inject('sidebar.right.pane.tab.title', () => ctx.slots.register({
29
+ name: 'sidebar.right.pane.tab.title', key: GIT_PROVIDER_ID,
30
+ }, GitTabTitle)));
31
+ disposers.push(ctx.slots.inject('shell.overlay', () => ctx.slots.register({
32
+ name: 'shell.overlay', id: 'codingns4dsh-git-workspace-recovery', order: 990,
33
+ inject: () => ({ remote: services.remote, sidebarRight }),
34
+ }, GitWorkspaceRecovery)));
35
+ if (typeof sidebarRight.registerCloseHandler === 'function') {
36
+ disposers.push(sidebarRight.registerCloseHandler(GIT_KIND, (sessionId, tab) => closeGitWorkspaceTabs(sidebarRight, services.remote, sessionId, tab)));
37
+ }
38
+ }
39
+ catch (error) {
40
+ for (const dispose of disposers.reverse())
41
+ dispose();
42
+ if (isDuplicateGitRegistration(error)) {
43
+ debugWarn(`codingns4dsh: Git Sidebar 已注册,跳过重复注册: ${GIT_PROVIDER_ID}`);
44
+ return () => { };
45
+ }
46
+ throw error;
47
+ }
48
+ return () => { for (const dispose of disposers.reverse())
49
+ dispose(); };
50
+ }
51
+ function isDuplicateGitRegistration(error) {
52
+ return error instanceof Error && /sidebarRight: (?:tab type id|tab kind) .* already registered/u.test(error.message);
53
+ }
54
+ function GitPanelIcon({ size = 16 }) {
55
+ return createElement('span', {
56
+ title: 'Git 仓库管理', 'aria-label': 'Git 仓库管理', style: { display: 'inline-flex', width: size, height: size, alignItems: 'center', justifyContent: 'center', color: dshThemeColor.labelSecondary, fontSize: Math.max(12, size - 2), fontWeight: 700 },
57
+ }, '⑂');
58
+ }
59
+ function GitTabTitle({ useTabInfo }) {
60
+ useTabInfo();
61
+ return createElement('span', { title: 'Git 仓库管理', 'aria-label': 'Git 仓库管理', style: tabTitleStyle }, 'Git');
62
+ }
63
+ function GitPanel(props) {
64
+ const sessionId = String(props.sessionId);
65
+ const tabInfo = props.useTabInfo();
66
+ const [workspaceId, setWorkspaceId] = useState();
67
+ const [status, setStatus] = useState(null);
68
+ const [history, setHistory] = useState([]);
69
+ const [branches, setBranches] = useState(null);
70
+ const [subject, setSubject] = useState('');
71
+ const [busy, setBusy] = useState(false);
72
+ const [toast, setToast] = useState(null);
73
+ const [diffView, setDiffView] = useState(null);
74
+ const [historyTotalCount, setHistoryTotalCount] = useState(0);
75
+ const [historyLoadingMore, setHistoryLoadingMore] = useState(false);
76
+ const historyExpanded = useRef(false);
77
+ useEffect(() => {
78
+ if (toast === null)
79
+ return;
80
+ const timer = globalThis.setTimeout(() => setToast(null), 3200);
81
+ return () => globalThis.clearTimeout(timer);
82
+ }, [toast]);
83
+ const notify = (kind, message) => setToast({ kind, message });
84
+ useEffect(() => {
85
+ let disposed = false;
86
+ let cleanupTimer;
87
+ setToast(null);
88
+ setWorkspaceId(undefined);
89
+ setStatus(null);
90
+ setHistory([]);
91
+ setHistoryTotalCount(0);
92
+ historyExpanded.current = false;
93
+ setBranches(null);
94
+ setDiffView(null);
95
+ const load = async (resolvedWorkspaceId) => {
96
+ const cached = readCache(resolvedWorkspaceId);
97
+ // 已经手动展开历史后,定时刷新只能更新状态和分支,不能用首屏缓存覆盖已加载的分页。
98
+ if (cached !== null && !historyExpanded.current) {
99
+ setStatus(cached.status);
100
+ setHistory(cached.history);
101
+ setHistoryTotalCount(cached.historyTotalCount);
102
+ setBranches(cached.branches);
103
+ }
104
+ try {
105
+ const nextStatus = await call(props.rpc, 'git/status', { workspaceId: resolvedWorkspaceId });
106
+ if (disposed)
107
+ return;
108
+ if (nextStatus.snapshot.enabled === false) {
109
+ setStatus(nextStatus);
110
+ setHistory([]);
111
+ setHistoryTotalCount(0);
112
+ setBranches(null);
113
+ writeCache(resolvedWorkspaceId, { status: nextStatus, history: [], historyTotalCount: 0, branches: null });
114
+ setToast(null);
115
+ return;
116
+ }
117
+ const [nextHistory, nextBranches] = await Promise.all([
118
+ historyExpanded.current ? Promise.resolve(null) : call(props.rpc, 'git/history', { workspaceId: resolvedWorkspaceId, limit: INITIAL_HISTORY_LIMIT, offset: 0 }),
119
+ call(props.rpc, 'git/branches', { workspaceId: resolvedWorkspaceId }),
120
+ ]);
121
+ if (disposed)
122
+ return;
123
+ const normalizedBranches = normalizeBranchSnapshot(nextBranches);
124
+ if (nextHistory !== null) {
125
+ setHistory(nextHistory.items);
126
+ setHistoryTotalCount(nextHistory.totalCount);
127
+ writeCache(resolvedWorkspaceId, { status: nextStatus, history: nextHistory.items, historyTotalCount: nextHistory.totalCount, branches: normalizedBranches });
128
+ }
129
+ setStatus(nextStatus);
130
+ setBranches(normalizedBranches);
131
+ setToast(null);
132
+ }
133
+ catch (error) {
134
+ if (!disposed)
135
+ notify('error', error instanceof Error ? error.message : String(error));
136
+ }
137
+ };
138
+ void resolveGitWorkspaceId(props.remote, sessionId).then((resolvedWorkspaceId) => {
139
+ if (disposed)
140
+ return;
141
+ if (resolvedWorkspaceId === undefined) {
142
+ notify('error', '当前没有可用的工作区');
143
+ return;
144
+ }
145
+ rememberGitWorkspaceSession(sessionId, resolvedWorkspaceId);
146
+ writeGitWorkspaceOpen(resolvedWorkspaceId, true);
147
+ setWorkspaceId(resolvedWorkspaceId);
148
+ void load(resolvedWorkspaceId);
149
+ const timer = globalThis.setInterval(() => { void load(resolvedWorkspaceId); }, 5_000);
150
+ cleanupTimer = () => globalThis.clearInterval(timer);
151
+ }).catch((error) => { if (!disposed)
152
+ notify('error', error instanceof Error ? error.message : String(error)); });
153
+ return () => { disposed = true; cleanupTimer?.(); };
154
+ }, [props.remote, props.rpc, sessionId]);
155
+ useEffect(() => {
156
+ const close = () => {
157
+ if (!tabInfo.tab.signal.aborted)
158
+ return;
159
+ const resolvedWorkspaceId = workspaceBySession.get(sessionId);
160
+ if (resolvedWorkspaceId !== undefined)
161
+ writeGitWorkspaceOpen(resolvedWorkspaceId, false);
162
+ };
163
+ if (tabInfo.tab.signal.aborted)
164
+ close();
165
+ else
166
+ tabInfo.tab.signal.addEventListener('abort', close, { once: true });
167
+ return () => tabInfo.tab.signal.removeEventListener('abort', close);
168
+ }, [sessionId, tabInfo.tab.signal]);
169
+ const run = async (action, payload, onSuccess) => {
170
+ setBusy(true);
171
+ setToast(null);
172
+ if (workspaceId === undefined) {
173
+ notify('error', '当前没有可用的工作区');
174
+ setBusy(false);
175
+ return;
176
+ }
177
+ const targetWorkspaceId = workspaceId;
178
+ const preserveExpandedHistory = action === 'git/status' && historyExpanded.current;
179
+ if (!preserveExpandedHistory)
180
+ historyExpanded.current = false;
181
+ try {
182
+ const value = await call(props.rpc, action, { workspaceId: targetWorkspaceId, ...payload });
183
+ onSuccess?.(value);
184
+ const nextStatus = await call(props.rpc, 'git/status', { workspaceId: targetWorkspaceId });
185
+ setStatus(nextStatus);
186
+ if (nextStatus.snapshot.enabled !== false) {
187
+ const [nextHistory, nextBranches] = await Promise.all([
188
+ preserveExpandedHistory ? Promise.resolve(null) : call(props.rpc, 'git/history', { workspaceId: targetWorkspaceId, limit: INITIAL_HISTORY_LIMIT, offset: 0 }),
189
+ call(props.rpc, 'git/branches', { workspaceId: targetWorkspaceId }),
190
+ ]);
191
+ const normalizedBranches = normalizeBranchSnapshot(nextBranches);
192
+ if (nextHistory !== null) {
193
+ setHistory(nextHistory.items);
194
+ setHistoryTotalCount(nextHistory.totalCount);
195
+ writeCache(targetWorkspaceId, { status: nextStatus, history: nextHistory.items, historyTotalCount: nextHistory.totalCount, branches: normalizedBranches });
196
+ }
197
+ setBranches(normalizedBranches);
198
+ }
199
+ else {
200
+ setHistory([]);
201
+ setHistoryTotalCount(0);
202
+ setBranches(null);
203
+ writeCache(targetWorkspaceId, { status: nextStatus, history: [], historyTotalCount: 0, branches: null });
204
+ }
205
+ notify('success', '操作已完成');
206
+ }
207
+ catch (error) {
208
+ notify('error', error instanceof Error ? error.message : String(error));
209
+ }
210
+ finally {
211
+ setBusy(false);
212
+ }
213
+ };
214
+ const commit = () => {
215
+ const value = subject.trim();
216
+ if (!value) {
217
+ notify('error', '请输入提交说明');
218
+ return;
219
+ }
220
+ void run('git/commit', { subject: value }, () => setSubject(''));
221
+ };
222
+ const copyCommitHash = (commitHash) => {
223
+ void copyText(commitHash).then((copied) => notify(copied ? 'success' : 'error', copied ? 'Git 版本号已复制' : '当前环境不支持复制'));
224
+ };
225
+ const copyCommitMessage = (value) => {
226
+ void copyText(value).then((copied) => notify(copied ? 'success' : 'error', copied ? '提交信息已复制' : '当前环境不支持复制'));
227
+ };
228
+ const openCommitDiff = (commitHash) => {
229
+ if (workspaceId === undefined)
230
+ return;
231
+ setBusy(true);
232
+ notify('info', '正在读取提交 Diff…');
233
+ void call(props.rpc, 'git/commit-diff', { workspaceId, commitHash }).then((value) => {
234
+ setDiffView(value);
235
+ setToast(null);
236
+ }).catch((error) => notify('error', error instanceof Error ? error.message : String(error))).finally(() => setBusy(false));
237
+ };
238
+ const loadMoreHistory = () => {
239
+ if (workspaceId === undefined || historyLoadingMore || history.length >= historyTotalCount)
240
+ return;
241
+ setHistoryLoadingMore(true);
242
+ historyExpanded.current = true;
243
+ void call(props.rpc, 'git/history', { workspaceId, limit: HISTORY_PAGE_SIZE, offset: history.length }).then((page) => {
244
+ setHistory((current) => [...current, ...page.items.filter((item) => !current.some((existing) => existing.commitHash === item.commitHash))]);
245
+ setHistoryTotalCount(page.totalCount);
246
+ }).catch((error) => { historyExpanded.current = history.length > INITIAL_HISTORY_LIMIT; notify('error', error instanceof Error ? error.message : String(error)); }).finally(() => setHistoryLoadingMore(false));
247
+ };
248
+ const changes = status?.changes ?? [];
249
+ const staged = changes.filter((item) => hasStagedChanges(item));
250
+ const unstaged = changes.filter((item) => hasUnstagedChanges(item));
251
+ const runGitOperation = (action) => {
252
+ if (action === 'refresh') {
253
+ void run('git/status', {}, (value) => setStatus(value));
254
+ return;
255
+ }
256
+ void run(`git/${action}`, {});
257
+ };
258
+ const stageAll = () => { void run('git/stage', { targets: unstaged.map((item) => item.path) }); };
259
+ const discardAll = () => { void run('git/discard', { targets: changes.map((item) => item.path) }); };
260
+ return createElement('section', { style: panelStyle, 'data-git-management-panel': 'true' }, createElement('header', { style: headerStyle }, createElement('div', { style: { minWidth: 0 } }, createElement('h1', { style: titleStyle }, 'Git'), createElement('div', { style: branchStyle, title: status?.snapshot.repoRoot }, status?.snapshot.branch ?? '读取中')), createElement('div', { style: headerActionsStyle }, createElement(GitOperationsMenu, { busy, hasRemote: Boolean(status?.snapshot.hasRemote || branches?.remote.length), canUndo: history.length > 0, hasMoreVersions: history.length < historyTotalCount, stagedCount: staged.length, unstagedCount: unstaged.length, onStageAll: stageAll, onDiscardAll: discardAll, onLoadMore: loadMoreHistory, onOperation: runGitOperation }))), toast === null ? null : createElement('div', { role: toast.kind === 'error' ? 'alert' : 'status', 'aria-live': 'polite', style: { ...dshSettingsToastStyle, position: 'absolute', top: 8, right: 'auto', left: '50%', transform: 'translateX(-50%)', width: 'min(300px, calc(100% - 24px))', pointerEvents: 'none', borderColor: toast.kind === 'error' ? dshThemeColor.error : toast.kind === 'success' ? dshThemeColor.success : dshThemeColor.border } }, toast.message), status === null ? createElement('div', { style: emptyStyle }, '正在读取 Git 状态…') : null, status !== null && status.snapshot.enabled === false ? createElement('section', { style: sectionStyle }, createElement('strong', undefined, '当前目录还没有 Git 仓库'), createElement('div', { style: mutedStyle }, '初始化后即可查看改动、提交和版本历史。'), createElement('button', { type: 'button', disabled: busy, onClick: () => void run('git/init', {}), style: primaryButtonStyle }, '初始化 Git')) : null, status !== null ? createElement('div', { style: summaryStyle }, `${staged.length} 个已暂存 · ${unstaged.length} 个未暂存 · ${historyTotalCount} 条提交`) : null, diffView !== null ? createElement(DiffViewer, { diff: diffView, onClose: () => setDiffView(null) }) : null, status !== null && status.snapshot.enabled !== false ? createElement('div', { style: contentGridStyle }, createElement('div', { style: columnStyle }, createElement('section', { style: commitSectionStyle }, createElement('div', { style: commitEditorRowStyle }, createElement('textarea', { value: subject, disabled: busy || workspaceId === undefined, onChange: (event) => setSubject(event.currentTarget.value), onKeyDown: (event) => { if (event.key === 'Enter')
261
+ event.preventDefault(); }, placeholder: '在这里输入提交信息', rows: 1, style: commitSubjectStyle }), createElement('button', { type: 'button', disabled: busy || workspaceId === undefined, onClick: () => notify('info', '生成提交信息功能暂未开放'), style: draftButtonStyle, title: '生成提交信息', 'aria-label': '生成提交信息' }, '✦')), createElement('div', { style: commitActionsStyle }, createElement('button', { type: 'button', disabled: busy || workspaceId === undefined, onClick: () => void run('git/status', {}, (value) => setStatus(value)), style: refreshActionStyle }, '刷新'), createElement('button', { type: 'button', disabled: busy || workspaceId === undefined || staged.length === 0 || subject.trim().length === 0, onClick: commit, style: submitActionStyle }, '提交'))), staged.length > 0 ? createElement(ChangeSection, { title: '暂存文件', items: staged, busy, onAction: (action, path) => void run(`git/${action}`, { targets: [path] }), onBatchAction: (action, paths) => void run(`git/${action}`, { targets: paths }), onBulkAction: () => void run('git/unstage', { targets: staged.map((item) => item.path) }) }) : null, unstaged.length > 0 ? createElement(ChangeSection, { title: '未提交文件', items: unstaged, busy, onAction: (action, path) => void run(`git/${action}`, { targets: [path] }), onBatchAction: (action, paths) => void run(`git/${action}`, { targets: paths }), onBulkAction: () => void run('git/stage', { targets: unstaged.map((item) => item.path) }) }) : null), createElement('div', { style: columnStyle }, createElement(HistorySection, { history, totalCount: historyTotalCount, hasMore: history.length < historyTotalCount, loadingMore: historyLoadingMore, onLoadMore: loadMoreHistory, branches, busy, onSwitch: (branchName) => void run('git/switch', { branchName, create: false }, (value) => setBranches(normalizeBranchSnapshot(value))), onCopy: copyCommitHash, onCopyMessage: copyCommitMessage, onViewDiff: openCommitDiff, onUndo: () => runGitOperation('undo') }))) : null);
262
+ }
263
+ function ChangeSection({ title, items, busy, onAction, onBatchAction, onBulkAction }) {
264
+ const [hoveredPath, setHoveredPath] = useState(null);
265
+ const nodes = buildChangeTree(items);
266
+ const staged = title === '暂存文件';
267
+ const renderNode = (node, depth) => {
268
+ if (node.kind === 'directory') {
269
+ const directoryKey = `dir:${node.path}`;
270
+ const directoryTargets = collectTreeTargets(node);
271
+ const isHovered = hoveredPath === directoryKey;
272
+ return createElement('details', { key: `dir:${node.path}`, open: true, style: treeDirectoryStyle }, createElement('summary', { style: { ...treeRowStyle, paddingLeft: 8 + depth * 14 }, onMouseEnter: () => setHoveredPath(directoryKey), onMouseLeave: () => setHoveredPath(null) }, createElement('span', { style: treeChevronStyle }, '⌄'), createElement('span', { style: folderIconStyle }, '▰'), createElement('span', { style: fileNameStyle, title: node.path }, node.name), createElement('span', { style: mutedStyle }, countTreeFiles(node)), isHovered ? createElement('div', { style: rowActionsStyle }, createElement('button', { type: 'button', disabled: busy, onClick: (event) => { event.preventDefault(); event.stopPropagation(); onBatchAction(staged ? 'unstage' : 'stage', directoryTargets); }, style: iconButtonStyle, title: staged ? '撤销目录暂存' : '将目录添加到暂存区', 'aria-label': staged ? '撤销目录暂存' : '将目录添加到暂存区' }, staged ? '↶' : '+'), !staged ? createElement('button', { type: 'button', disabled: busy, onClick: (event) => { event.preventDefault(); event.stopPropagation(); onBatchAction('discard', directoryTargets); }, style: dangerIconButtonStyle, title: '撤销目录变更', 'aria-label': '撤销目录变更' }, '×') : null) : null), createElement('div', undefined, ...node.children.map((child) => renderNode(child, depth + 1))));
273
+ }
274
+ const isHovered = hoveredPath === node.path;
275
+ return createElement('div', { key: `file:${node.path}`, style: { ...treeRowStyle, paddingLeft: 28 + depth * 14 }, onMouseEnter: () => setHoveredPath(node.path), onMouseLeave: () => setHoveredPath(null) }, createElement('span', { style: fileIconStyle }, fileIcon(node.name)), createElement('span', { title: node.path, style: fileNameStyle }, node.name), createElement('span', { style: fileStatusStyle }, changeStatus(node.item, staged)), isHovered ? createElement('div', { style: rowActionsStyle }, createElement('button', { type: 'button', disabled: busy, onClick: () => onAction(staged ? 'unstage' : 'stage', node.path), style: iconButtonStyle, title: staged ? '撤销暂存' : '添加到暂存区', 'aria-label': staged ? '撤销暂存' : '添加到暂存区' }, staged ? '↶' : '+'), !staged ? createElement('button', { type: 'button', disabled: busy, onClick: () => onAction('discard', node.path), style: dangerIconButtonStyle, title: '撤销变更', 'aria-label': '撤销变更' }, '×') : null) : null);
276
+ };
277
+ return createElement('section', { style: sectionStyle }, createElement('div', { style: sectionHeaderStyle }, createElement('strong', undefined, `${title} (${items.length})`), createElement('button', { type: 'button', disabled: busy, onClick: onBulkAction, style: iconButtonStyle, title: staged ? '取消全部暂存' : '全部添加到暂存区', 'aria-label': staged ? '取消全部暂存' : '全部添加到暂存区' }, staged ? '↶' : '+')), createElement('div', { style: treeStyle }, ...nodes.map((node) => renderNode(node, 0))));
278
+ }
279
+ function GitOperationsMenu({ busy, hasRemote, canUndo, hasMoreVersions, stagedCount, unstagedCount, onStageAll, onDiscardAll, onLoadMore, onOperation }) {
280
+ return createElement('details', { style: menuStyle }, createElement('summary', { style: menuSummaryStyle, title: 'Git 操作菜单', 'aria-label': 'Git 操作菜单' }, '⋯'), createElement('div', { style: menuPopupStyle }, createElement('button', { type: 'button', disabled: busy || unstagedCount === 0, onClick: onStageAll, style: menuButtonStyle }, '暂存全部'), createElement('button', { type: 'button', disabled: busy || stagedCount + unstagedCount === 0, onClick: onDiscardAll, style: dangerMenuButtonStyle }, '放弃全部改动'), createElement('button', { type: 'button', disabled: busy || !hasRemote, onClick: () => onOperation('fetch'), style: menuButtonStyle }, 'Fetch'), createElement('button', { type: 'button', disabled: busy || !hasRemote, onClick: () => onOperation('pull'), style: menuButtonStyle }, 'Pull'), createElement('button', { type: 'button', disabled: busy || !hasRemote || stagedCount > 0 || unstagedCount > 0, onClick: () => onOperation('push'), style: menuButtonStyle }, 'Push'), createElement('button', { type: 'button', disabled: busy || !hasMoreVersions, onClick: onLoadMore, style: menuButtonStyle, title: '查看所有版本' }, '查看更多版本(每次 100 条)'), createElement('button', { type: 'button', disabled: busy || !canUndo, onClick: () => onOperation('undo'), style: menuButtonStyle }, '撤销上次提交'), createElement('button', { type: 'button', disabled: busy, onClick: () => onOperation('refresh'), style: menuButtonStyle }, '刷新')));
281
+ }
282
+ function DiffViewer({ diff, onClose }) {
283
+ const providedFiles = diff.files ?? [];
284
+ const files = providedFiles.length > 0 ? providedFiles : parseDiffFiles(diff.content);
285
+ const lines = parseDiffLines(diff.content);
286
+ return createElement('div', { style: diffOverlayStyle }, createElement('section', { role: 'dialog', 'aria-modal': true, 'aria-label': '提交 Diff', style: diffStyle }, createElement('div', { style: sectionHeaderStyle }, createElement('strong', undefined, `提交 Diff · ${diff.commitHash.slice(0, 8)}`), createElement('button', { type: 'button', onClick: onClose, style: iconButtonStyle, title: '关闭 Diff', 'aria-label': '关闭 Diff' }, '×')), createElement('div', { style: diffBodyStyle }, createElement('section', { style: diffFilesSectionStyle }, createElement('div', { style: diffSectionHeaderStyle }, createElement('strong', undefined, '变更文件'), createElement('span', { style: diffCountStyle }, String(files.length))), files.length === 0 ? createElement('div', { style: diffEmptyStyle }, '没有检测到文件变更。') : createElement('div', { style: diffFileListStyle }, ...files.map((file) => createElement('div', { key: `${file.status}:${file.oldPath ?? ''}:${file.path}`, style: diffFileRowStyle }, createElement('span', { style: diffFileStatusStyle, 'data-status': file.status }, diffFileStatusLabel(file.status)), createElement('div', { style: diffFileNameStyle }, createElement('strong', undefined, file.path), file.oldPath ? createElement('span', { style: diffFileOldPathStyle }, `原路径:${file.oldPath}`) : null), file.binary ? createElement('span', { style: diffBinaryStyle }, '二进制') : null)))), createElement('section', { style: diffDiffSectionStyle }, createElement('div', { style: diffSectionHeaderStyle }, createElement('strong', undefined, 'Diff'), diff.truncated ? createElement('span', { style: diffTruncatedStyle }, '内容已截断') : null), lines.length === 0 ? createElement('div', { style: diffEmptyStyle }, '当前没有可显示的文本差异。') : createElement('div', { style: diffLinesStyle }, ...lines.map((line, index) => createElement('div', { key: `${index}:${line.kind}:${line.text}`, style: diffLineStyle(line.kind) }, createElement('span', { style: diffLineNumberStyle }, line.oldLineNo === null ? '' : String(line.oldLineNo)), createElement('span', { style: diffLineNumberStyle }, line.newLineNo === null ? '' : String(line.newLineNo)), createElement('code', { style: diffCodeStyle }, `${diffLinePrefix(line.kind)}${line.text}`))))))));
287
+ }
288
+ function parseDiffLines(content) {
289
+ const lines = [];
290
+ let oldLine = 0;
291
+ let newLine = 0;
292
+ for (const rawLine of content.replace(/\r\n/gu, '\n').split('\n')) {
293
+ if (rawLine.startsWith('diff --git') || rawLine.startsWith('index ') || rawLine.startsWith('--- ') || rawLine.startsWith('+++ ')) {
294
+ lines.push({ kind: 'meta', text: rawLine, oldLineNo: null, newLineNo: null });
295
+ continue;
296
+ }
297
+ const hunk = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/u.exec(rawLine);
298
+ if (hunk !== null) {
299
+ oldLine = Number(hunk[1]);
300
+ newLine = Number(hunk[2]);
301
+ lines.push({ kind: 'hunk', text: rawLine, oldLineNo: null, newLineNo: null });
302
+ continue;
303
+ }
304
+ if (rawLine.startsWith(' ') || rawLine === '') {
305
+ lines.push({ kind: 'context', text: rawLine.slice(1), oldLineNo: oldLine, newLineNo: newLine });
306
+ oldLine += 1;
307
+ newLine += 1;
308
+ continue;
309
+ }
310
+ if (rawLine.startsWith('+')) {
311
+ lines.push({ kind: 'add', text: rawLine.slice(1), oldLineNo: null, newLineNo: newLine });
312
+ newLine += 1;
313
+ continue;
314
+ }
315
+ if (rawLine.startsWith('-')) {
316
+ lines.push({ kind: 'remove', text: rawLine.slice(1), oldLineNo: oldLine, newLineNo: null });
317
+ oldLine += 1;
318
+ continue;
319
+ }
320
+ lines.push({ kind: 'meta', text: rawLine, oldLineNo: null, newLineNo: null });
321
+ }
322
+ return lines;
323
+ }
324
+ function parseDiffFiles(content) {
325
+ const result = [];
326
+ for (const line of content.split(/\r?\n/u)) {
327
+ const match = /^diff --git a\/(.+) b\/(.+)$/u.exec(line);
328
+ if (match === null)
329
+ continue;
330
+ const path = match[2] ?? match[1] ?? '';
331
+ if (path === '' || result.some((file) => file.path === path))
332
+ continue;
333
+ result.push({ path, oldPath: match[1] === path ? null : match[1] ?? null, status: 'M', binary: false });
334
+ }
335
+ return result;
336
+ }
337
+ function diffFileStatusLabel(status) { return status === 'A' ? '新增' : status === 'D' ? '删除' : status === 'R' ? '重命名' : status === 'C' ? '复制' : '修改'; }
338
+ function diffLinePrefix(kind) { return kind === 'add' ? '+' : kind === 'remove' ? '-' : kind === 'context' ? ' ' : ''; }
339
+ function diffLineStyle(kind) {
340
+ if (kind === 'add')
341
+ return { ...diffLineBaseStyle, color: '#1f9d55', background: 'color-mix(in srgb, #22c55e 13%, transparent)' };
342
+ if (kind === 'remove')
343
+ return { ...diffLineBaseStyle, color: '#e05252', background: 'color-mix(in srgb, #ef4444 13%, transparent)' };
344
+ if (kind === 'hunk')
345
+ return { ...diffLineBaseStyle, color: dshThemeColor.accent, background: 'color-mix(in srgb, currentColor 10%, transparent)', fontWeight: 600 };
346
+ if (kind === 'meta')
347
+ return { ...diffLineBaseStyle, color: dshThemeColor.labelTertiary };
348
+ return diffLineBaseStyle;
349
+ }
350
+ function HistorySection({ history, totalCount, hasMore, loadingMore, onLoadMore, branches, busy, onSwitch, onCopy, onCopyMessage, onViewDiff, onUndo }) {
351
+ const rows = history.map((item, index) => createElement('div', { key: item.commitHash, style: historyRowStyle }, createElement('code', { style: hashStyle }, item.commitHash.slice(0, 8)), createElement('span', { style: fileNameStyle, title: item.subject }, item.subject), createElement('time', { style: mutedStyle }, formatDate(item.authoredAt)), createElement('details', { style: menuStyle }, createElement('summary', { style: menuSummaryStyle, title: '版本操作菜单', 'aria-label': '版本操作菜单' }, '⋯'), createElement('div', { style: menuPopupStyle }, createElement('button', { type: 'button', onClick: () => onViewDiff(item.commitHash), style: menuButtonStyle }, '查看更改文件与 Diff'), createElement('button', { type: 'button', onClick: () => onCopy(item.commitHash), style: menuButtonStyle }, '复制 Commit Hash'), createElement('button', { type: 'button', onClick: () => onCopyMessage(buildCommitMessageText(item.subject, item.body)), style: menuButtonStyle }, '复制提交信息'), createElement('button', { type: 'button', onClick: () => onCopy(item.commitHash), style: menuButtonStyle }, '复制 Git 版本号'), index === 0 ? createElement('button', { type: 'button', disabled: busy, onClick: onUndo, style: dangerMenuButtonStyle }, '撤销上次提交') : null))));
352
+ return createElement('section', { style: sectionStyle }, createElement('div', { style: sectionHeaderStyle }, createElement('strong', undefined, `Git 版本 (${totalCount})`), branches === null ? null : createElement('select', { value: branches.currentBranch, disabled: busy, onChange: (event) => onSwitch(event.currentTarget.value), style: branchSelectStyle }, ...branches.local.map((branch) => createElement('option', { key: branch.name, value: branch.name }, branch.name)))), history.length === 0 ? createElement('div', { style: mutedStyle }, '暂无提交') : rows, hasMore ? createElement('button', { type: 'button', disabled: busy || loadingMore, onClick: onLoadMore, style: loadMoreButtonStyle }, loadingMore ? '正在加载…' : '查看更多版本(每次 100 条)') : null);
353
+ }
354
+ function buildChangeTree(items) {
355
+ const root = new Map();
356
+ for (const item of items) {
357
+ const parts = item.path.split('/').filter(Boolean);
358
+ if (parts.length === 0)
359
+ continue;
360
+ let current = root;
361
+ let parentPath = '';
362
+ for (let index = 0; index < parts.length - 1; index += 1) {
363
+ const name = parts[index];
364
+ parentPath = parentPath ? `${parentPath}/${name}` : name;
365
+ const existing = current.get(name);
366
+ if (existing?.kind === 'directory')
367
+ current = existing.children;
368
+ else {
369
+ const directory = { kind: 'directory', name, path: parentPath, children: new Map() };
370
+ current.set(name, directory);
371
+ current = directory.children;
372
+ }
373
+ }
374
+ const fileName = parts.at(-1);
375
+ current.set(fileName, { kind: 'file', name: fileName, path: item.path, item });
376
+ }
377
+ return finalizeChangeTree(root);
378
+ }
379
+ function finalizeChangeTree(nodes) {
380
+ return [...nodes.values()].map((node) => node.kind === 'directory' ? { kind: 'directory', name: node.name, path: node.path, children: finalizeChangeTree(node.children) } : node).sort((left, right) => left.kind === right.kind ? left.name.localeCompare(right.name) : left.kind === 'directory' ? -1 : 1);
381
+ }
382
+ function collectTreeTargets(node) {
383
+ if (node.kind === 'file')
384
+ return [node.path];
385
+ return node.children.flatMap((child) => collectTreeTargets(child));
386
+ }
387
+ function countTreeFiles(node) { return node.kind === 'file' ? 1 : node.children.reduce((total, child) => total + countTreeFiles(child), 0); }
388
+ function hasStagedChanges(item) { return item.staged || item.stagedStatus !== null; }
389
+ function hasUnstagedChanges(item) { return !item.staged || item.worktreeStatus !== null; }
390
+ function changeStatus(item, staged) { return staged ? item.stagedStatus ?? item.status : item.worktreeStatus ?? item.status; }
391
+ function fileIcon(name) { return name.endsWith('/') ? '▰' : name.includes('.') ? '·' : '□'; }
392
+ const GIT_WORKSPACE_OPEN_PREFIX = 'codingns4dsh.git.open.';
393
+ const GIT_WORKSPACE_STATE_EVENT = 'codingns4dsh-git-workspace-state';
394
+ const workspaceBySession = new Map();
395
+ const closingWorkspaces = new Set();
396
+ function GitWorkspaceRecovery({ useSessions, remote, sidebarRight }) {
397
+ const sessionsSnapshot = useSessions((value) => value);
398
+ const [workspaceRevision, setWorkspaceRevision] = useState(0);
399
+ const openTabs = readGitOpenTabs(sidebarRight);
400
+ const openTabSnapshot = useSyncExternalStore(openTabs?.subscribe ?? noSubscribe, openTabs?.getSnapshot ?? noOpenTabs, openTabs?.getSnapshot ?? noOpenTabs);
401
+ const sessionIds = [...new Set([...readSessionIds(sessionsSnapshot), ...openTabSnapshot.map((tab) => String(tab.sessionId))].filter(Boolean))];
402
+ useEffect(() => {
403
+ if (typeof window === 'undefined')
404
+ return;
405
+ const refresh = () => setWorkspaceRevision((value) => value + 1);
406
+ window.addEventListener(GIT_WORKSPACE_STATE_EVENT, refresh);
407
+ window.addEventListener('storage', refresh);
408
+ return () => { window.removeEventListener(GIT_WORKSPACE_STATE_EVENT, refresh); window.removeEventListener('storage', refresh); };
409
+ }, []);
410
+ useEffect(() => {
411
+ if (typeof sidebarRight.openTabIn !== 'function')
412
+ return;
413
+ let disposed = false;
414
+ const recover = async () => {
415
+ const workspaceItems = await readWorkspaceItems(remote?.workspace);
416
+ const allSessionIds = [...new Set([...sessionIds, ...workspaceItems.flatMap((item) => item.sessionIds)])];
417
+ for (const sessionId of allSessionIds) {
418
+ const workspaceId = await resolveGitWorkspaceId(remote, sessionId).catch(() => undefined);
419
+ if (disposed || workspaceId === undefined)
420
+ continue;
421
+ rememberGitWorkspaceSession(sessionId, workspaceId);
422
+ const existing = readGitTabs(sidebarRight, sessionId, openTabSnapshot);
423
+ const openState = readGitWorkspaceOpen(workspaceId);
424
+ if (openState === false) {
425
+ for (const tab of existing)
426
+ if (tab.kind === GIT_KIND)
427
+ sidebarRight.closeIn?.(sessionId, tab.id);
428
+ continue;
429
+ }
430
+ if (openState !== true)
431
+ continue;
432
+ if (existing.some((tab) => tab.kind === GIT_KIND))
433
+ continue;
434
+ sidebarRight.openTabIn?.(sessionId, GIT_KIND);
435
+ }
436
+ };
437
+ void recover();
438
+ return () => { disposed = true; };
439
+ }, [openTabSnapshot, remote, sessionIds.join('|'), sidebarRight, workspaceRevision]);
440
+ return null;
441
+ }
442
+ function closeGitWorkspaceTabs(sidebar, remote, sessionId, tab) {
443
+ const knownWorkspaceId = workspaceBySession.get(sessionId);
444
+ if (knownWorkspaceId !== undefined) {
445
+ closeKnownGitWorkspaceTabs(sidebar, knownWorkspaceId, sessionId, tab.id);
446
+ return;
447
+ }
448
+ void resolveGitWorkspaceId(remote, sessionId).then((workspaceId) => {
449
+ if (workspaceId === undefined)
450
+ return;
451
+ rememberGitWorkspaceSession(sessionId, workspaceId);
452
+ closeKnownGitWorkspaceTabs(sidebar, workspaceId, sessionId, tab.id);
453
+ }).catch(() => undefined);
454
+ }
455
+ function closeKnownGitWorkspaceTabs(sidebar, workspaceId, currentSessionId, currentTabId) {
456
+ writeGitWorkspaceOpen(workspaceId, false);
457
+ if (closingWorkspaces.has(workspaceId))
458
+ return;
459
+ closingWorkspaces.add(workspaceId);
460
+ try {
461
+ for (const entry of readGitOpenTabs(sidebar)?.getSnapshot() ?? []) {
462
+ if (entry.kind !== GIT_KIND || entry.sessionId === currentSessionId && entry.tabId === currentTabId)
463
+ continue;
464
+ if (workspaceBySession.get(String(entry.sessionId)) !== workspaceId)
465
+ continue;
466
+ sidebar.closeIn?.(String(entry.sessionId), String(entry.tabId));
467
+ }
468
+ }
469
+ finally {
470
+ closingWorkspaces.delete(workspaceId);
471
+ }
472
+ }
473
+ function rememberGitWorkspaceSession(sessionId, workspaceId) {
474
+ const normalizedSessionId = sessionId.trim();
475
+ const normalizedWorkspaceId = workspaceId.trim();
476
+ if (normalizedSessionId !== '' && normalizedWorkspaceId !== '')
477
+ workspaceBySession.set(normalizedSessionId, normalizedWorkspaceId);
478
+ }
479
+ function gitWorkspaceOpenKey(workspaceId) { return `${GIT_WORKSPACE_OPEN_PREFIX}${workspaceId}`; }
480
+ function readGitWorkspaceOpen(workspaceId) {
481
+ try {
482
+ const value = localStorage.getItem(gitWorkspaceOpenKey(workspaceId));
483
+ return value === null ? undefined : value === '1';
484
+ }
485
+ catch {
486
+ return undefined;
487
+ }
488
+ }
489
+ function writeGitWorkspaceOpen(workspaceId, open) {
490
+ try {
491
+ localStorage.setItem(gitWorkspaceOpenKey(workspaceId), open ? '1' : '0');
492
+ if (typeof window !== 'undefined')
493
+ window.dispatchEvent(new Event(GIT_WORKSPACE_STATE_EVENT));
494
+ }
495
+ catch { /* 浏览器禁用存储时仅失去跨会话恢复。 */ }
496
+ }
497
+ function readGitOpenTabs(sidebar) {
498
+ try {
499
+ return sidebar.openTabs;
500
+ }
501
+ catch {
502
+ return undefined;
503
+ }
504
+ }
505
+ function readGitTabs(sidebar, sessionId, snapshot) {
506
+ try {
507
+ if (typeof sidebar.tabsIn === 'function')
508
+ return sidebar.tabsIn(sessionId);
509
+ }
510
+ catch { /* 旧版没有已装配的 Session store,回退到全局索引。 */ }
511
+ return snapshot.filter((tab) => String(tab.sessionId) === sessionId).map((tab) => ({ id: String(tab.tabId), kind: tab.kind }));
512
+ }
513
+ function readSessionIds(value) {
514
+ const record = asRecord(value);
515
+ if (record === undefined)
516
+ return [];
517
+ const result = new Set();
518
+ const byId = asRecord(record.byId);
519
+ if (byId !== undefined)
520
+ for (const sessionId of Object.keys(byId))
521
+ if (sessionId.trim() !== '')
522
+ result.add(sessionId);
523
+ for (const key of ['sessionId', 'currentSessionId', 'selectedSessionId']) {
524
+ const candidate = record[key];
525
+ if (typeof candidate === 'string' && candidate.trim() !== '')
526
+ result.add(candidate);
527
+ }
528
+ for (const key of ['items', 'sessions']) {
529
+ const items = record[key];
530
+ if (!Array.isArray(items))
531
+ continue;
532
+ for (const item of items) {
533
+ const itemRecord = asRecord(item);
534
+ const candidate = itemRecord?.sessionId ?? itemRecord?.id;
535
+ if (typeof candidate === 'string' && candidate.trim() !== '')
536
+ result.add(candidate);
537
+ }
538
+ }
539
+ return [...result];
540
+ }
541
+ function noSubscribe() { return () => undefined; }
542
+ const EMPTY_GIT_OPEN_TABS = [];
543
+ function noOpenTabs() { return EMPTY_GIT_OPEN_TABS; }
544
+ export async function resolveGitWorkspaceId(remote, sessionId) {
545
+ const api = remote;
546
+ const workspaces = await readWorkspaceItems(api?.workspace);
547
+ const direct = workspaces.find((item) => item.sessionIds.includes(sessionId));
548
+ if (direct !== undefined)
549
+ return direct.workspaceId;
550
+ const session = await readCurrentSession(api?.session, sessionId);
551
+ if (session?.workspaceId !== undefined)
552
+ return session.workspaceId;
553
+ const sessionCwd = session?.cwd;
554
+ if (sessionCwd !== undefined) {
555
+ const byPath = workspaces
556
+ .filter((item) => item.path !== undefined && isPathWithin(sessionCwd, item.path))
557
+ .sort((left, right) => (right.path?.length ?? 0) - (left.path?.length ?? 0))[0];
558
+ if (byPath !== undefined)
559
+ return byPath.workspaceId;
560
+ }
561
+ if (workspaces.length === 1)
562
+ return workspaces[0]?.workspaceId;
563
+ return undefined;
564
+ }
565
+ async function readWorkspaceItems(api) {
566
+ if (api?.follow === undefined)
567
+ return [];
568
+ const source = await api.follow();
569
+ const iterator = source[Symbol.asyncIterator]();
570
+ const first = await iterator.next();
571
+ await iterator.return?.();
572
+ const unwrapped = asRecord(unwrapRemoteValue(first.value));
573
+ const value = Array.isArray(unwrapped?.items) ? unwrapped : asRecord(unwrapped?.value);
574
+ const items = Array.isArray(value?.items) ? value.items : [];
575
+ return items.flatMap((item) => {
576
+ const record = asRecord(item);
577
+ const workspaceId = record?.workspaceId;
578
+ if (typeof workspaceId !== 'string' || workspaceId.trim() === '')
579
+ return [];
580
+ const rawSessionIds = record?.sessionIds;
581
+ const sessionIds = Array.isArray(rawSessionIds)
582
+ ? rawSessionIds.filter((sessionId) => typeof sessionId === 'string')
583
+ : [];
584
+ const path = typeof record?.path === 'string' && record.path.trim() !== '' ? record.path : undefined;
585
+ return [{ workspaceId, sessionIds, ...(path === undefined ? {} : { path }) }];
586
+ });
587
+ }
588
+ async function readCurrentSession(api, sessionId) {
589
+ if (api?.list === undefined)
590
+ return undefined;
591
+ const result = asRecord(unwrapRemoteValue(await api.list({})));
592
+ const items = Array.isArray(result?.items) ? result.items : [];
593
+ const session = asRecord(items.find((item) => asRecord(item)?.sessionId === sessionId));
594
+ if (session === undefined)
595
+ return undefined;
596
+ const workspaceId = typeof session.workspaceId === 'string' && session.workspaceId.trim() !== '' ? session.workspaceId : undefined;
597
+ const cwd = typeof session.cwd === 'string' && session.cwd.trim() !== '' ? session.cwd : undefined;
598
+ return workspaceId === undefined && cwd === undefined ? {} : { ...(workspaceId === undefined ? {} : { workspaceId }), ...(cwd === undefined ? {} : { cwd }) };
599
+ }
600
+ function isPathWithin(candidate, parent) {
601
+ const normalizedCandidate = normalizePath(candidate);
602
+ const normalizedParent = normalizePath(parent);
603
+ return normalizedCandidate === normalizedParent || normalizedCandidate.startsWith(`${normalizedParent}/`);
604
+ }
605
+ function normalizePath(value) {
606
+ const normalized = value.replaceAll('\\', '/').replace(/\/+/u, '/');
607
+ const withoutTrailingSlash = normalized.replace(/\/+$/u, '');
608
+ const result = withoutTrailingSlash || '/';
609
+ return /^[A-Za-z]:\//u.test(result) ? result.toLowerCase() : result;
610
+ }
611
+ function asRecord(value) { return typeof value === 'object' && value !== null ? value : undefined; }
612
+ function unwrapRemoteValue(value) {
613
+ const record = asRecord(value);
614
+ if (record === undefined || typeof record.ok !== 'boolean')
615
+ return value;
616
+ return record.ok ? record.value : undefined;
617
+ }
618
+ function cacheKey(workspaceId) { return `codingns4dsh.git.${workspaceId}`; }
619
+ function readCache(workspaceId) { try {
620
+ const value = JSON.parse(localStorage.getItem(cacheKey(workspaceId)) ?? 'null');
621
+ if (!value?.status || typeof value.historyTotalCount !== 'number')
622
+ return null;
623
+ return { status: value.status, history: value.history ?? [], historyTotalCount: value.historyTotalCount, branches: normalizeBranchSnapshot(value.branches ?? null) };
624
+ }
625
+ catch {
626
+ return null;
627
+ } }
628
+ function writeCache(workspaceId, value) { try {
629
+ localStorage.setItem(cacheKey(workspaceId), JSON.stringify(value));
630
+ }
631
+ catch { /* 浏览器禁用存储时仅失去缓存 */ } }
632
+ function normalizeBranchSnapshot(value) {
633
+ if (value === null)
634
+ return null;
635
+ const normalize = (item, fallbackRemote) => {
636
+ const rawName = typeof item.name === 'string' ? item.name.trim() : '';
637
+ if (rawName === '')
638
+ return null;
639
+ const fields = rawName.split(/%x1f/iu);
640
+ const name = (fields[0] ?? '').trim();
641
+ if (name === '')
642
+ return null;
643
+ const upstream = (fields[2] ?? item.upstream ?? '').trim();
644
+ return { name, current: item.current || fields[1] === '*', upstream: upstream || null, remote: item.remote || fallbackRemote || name.startsWith('refs/remotes/') };
645
+ };
646
+ const local = value.local.flatMap((item) => {
647
+ const normalized = normalize(item, false);
648
+ return normalized?.remote === true ? [] : normalized === null ? [] : [normalized];
649
+ });
650
+ const remote = value.remote.flatMap((item) => {
651
+ const normalized = normalize(item, true);
652
+ return normalized === null ? [] : [normalized];
653
+ });
654
+ const currentFields = value.currentBranch.split(/%x1f/iu);
655
+ const currentBranch = (currentFields[0] ?? value.currentBranch).trim() || 'HEAD';
656
+ return { currentBranch, local, remote };
657
+ }
658
+ async function call(rpc, endpoint, payload) { let result; try {
659
+ result = await rpc.call(CODINGNS_RPC_CHANNEL, endpoint, payload);
660
+ }
661
+ catch (error) {
662
+ if (!/HTTP (?:404|405)\b/u.test(error instanceof Error ? error.message : String(error)))
663
+ throw error;
664
+ result = await rpc.call('/api', `codingns/${endpoint}`, payload);
665
+ } if (!result.ok)
666
+ throw new Error(result.error.message); return result.value; }
667
+ async function copyText(value) { try {
668
+ if (typeof navigator === 'undefined' || typeof navigator.clipboard?.writeText !== 'function')
669
+ return false;
670
+ await navigator.clipboard.writeText(value);
671
+ return true;
672
+ }
673
+ catch {
674
+ return false;
675
+ } }
676
+ function formatDate(value) {
677
+ const timestamp = Date.parse(value);
678
+ if (Number.isNaN(timestamp))
679
+ return value;
680
+ const parts = new Intl.DateTimeFormat('zh-CN', { month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit', hourCycle: 'h23' }).formatToParts(timestamp);
681
+ const get = (type) => parts.find((part) => part.type === type)?.value ?? '';
682
+ return `${get('month')}月${get('day')}日 ${get('hour')}:${get('minute')}`;
683
+ }
684
+ function buildCommitMessageText(subject, body) { const normalizedSubject = subject.trim(); const normalizedBody = body.trim(); return normalizedBody ? `${normalizedSubject}\n\n${normalizedBody}` : normalizedSubject; }
685
+ const panelStyle = { position: 'relative', boxSizing: 'border-box', display: 'flex', flexDirection: 'column', gap: 12, minHeight: '100%', padding: '16px 18px 24px', overflow: 'auto', background: dshThemeColor.pageBackground, color: dshThemeColor.labelPrimary };
686
+ const headerStyle = { display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 12, paddingBottom: 10, borderBottom: `1px solid ${dshThemeColor.border}` };
687
+ const titleStyle = { margin: 0, fontSize: 20, lineHeight: 1.2 };
688
+ const tabTitleStyle = { display: 'inline-flex', alignItems: 'center', minWidth: 0, color: dshThemeColor.labelPrimary, fontSize: 12 };
689
+ const headerActionsStyle = { display: 'inline-flex', alignItems: 'center', gap: 4 };
690
+ const branchStyle = { marginTop: 4, overflow: 'hidden', color: dshThemeColor.labelSecondary, fontSize: 12, textOverflow: 'ellipsis', whiteSpace: 'nowrap' };
691
+ const summaryStyle = { color: dshThemeColor.labelSecondary, fontSize: 12 };
692
+ const contentGridStyle = { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', alignItems: 'start', gap: 12 };
693
+ const columnStyle = { display: 'flex', flexDirection: 'column', gap: 12, minWidth: 0 };
694
+ const sectionStyle = { display: 'flex', flexDirection: 'column', gap: 8, padding: 10, border: `1px solid ${dshThemeColor.border}`, borderRadius: 6, background: dshThemeColor.menuBackground };
695
+ const sectionHeaderStyle = { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, fontSize: 12 };
696
+ const treeStyle = { display: 'flex', flexDirection: 'column', gap: 1 };
697
+ const treeDirectoryStyle = { minWidth: 0 };
698
+ const treeRowStyle = { display: 'flex', alignItems: 'center', gap: 7, minHeight: 28, fontSize: 12 };
699
+ const treeChevronStyle = { width: 12, color: dshThemeColor.labelTertiary, fontSize: 12 };
700
+ const folderIconStyle = { color: dshThemeColor.accent, fontSize: 11 };
701
+ const fileIconStyle = { width: 12, color: dshThemeColor.labelTertiary, fontSize: 12, textAlign: 'center' };
702
+ const fileNameStyle = { minWidth: 0, flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' };
703
+ const fileStatusStyle = { color: dshThemeColor.labelTertiary, fontFamily: 'monospace', fontSize: 11 };
704
+ const historyRowStyle = { display: 'flex', alignItems: 'center', gap: 8, minHeight: 28, fontSize: 12 };
705
+ const hashStyle = { color: dshThemeColor.labelTertiary, fontSize: 11 };
706
+ const rowActionsStyle = { display: 'inline-flex', alignItems: 'center', gap: 2, flex: '0 0 auto' };
707
+ const iconButtonStyle = { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 24, height: 24, border: 0, borderRadius: 4, padding: 0, color: dshThemeColor.labelSecondary, background: 'transparent', cursor: 'pointer', fontSize: 16 };
708
+ const dangerIconButtonStyle = { ...iconButtonStyle, color: dshThemeColor.error };
709
+ const mutedStyle = { color: dshThemeColor.labelTertiary, fontSize: 11 };
710
+ const emptyStyle = { padding: 16, color: dshThemeColor.labelSecondary, fontSize: 12 };
711
+ const diffOverlayStyle = { position: 'fixed', inset: 0, zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20, boxSizing: 'border-box', background: 'color-mix(in srgb, #000 28%, transparent)' };
712
+ const diffStyle = { ...sectionStyle, width: 'min(1000px, 100%)', maxHeight: 'min(88vh, 760px)', overflow: 'hidden', boxShadow: dshThemeColor.subtleShadow };
713
+ const diffBodyStyle = { display: 'flex', flexDirection: 'column', gap: 12, minHeight: 0, overflow: 'auto', paddingRight: 2 };
714
+ const diffFilesSectionStyle = { ...sectionStyle, gap: 6, padding: 10, background: dshThemeColor.pageBackground };
715
+ const diffDiffSectionStyle = { ...sectionStyle, gap: 6, padding: 10, background: dshThemeColor.pageBackground };
716
+ const diffSectionHeaderStyle = { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, minHeight: 24, fontSize: 12 };
717
+ const diffCountStyle = { minWidth: 20, padding: '2px 6px', borderRadius: 10, color: dshThemeColor.labelSecondary, background: dshThemeColor.menuBackground, fontSize: 11, textAlign: 'center' };
718
+ const diffFileListStyle = { display: 'flex', flexDirection: 'column', gap: 2 };
719
+ const diffFileRowStyle = { display: 'flex', alignItems: 'center', gap: 8, minHeight: 30, padding: '4px 6px', borderRadius: 4, background: dshThemeColor.menuBackground, fontSize: 12 };
720
+ const diffFileStatusStyle = { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 42, flex: '0 0 auto', color: dshThemeColor.accent, fontSize: 11, fontWeight: 700 };
721
+ const diffFileNameStyle = { display: 'flex', flexDirection: 'column', minWidth: 0, flex: 1, overflow: 'hidden' };
722
+ const diffFileOldPathStyle = { overflow: 'hidden', color: dshThemeColor.labelTertiary, textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontSize: 11 };
723
+ const diffBinaryStyle = { color: dshThemeColor.labelTertiary, fontSize: 11 };
724
+ const diffTruncatedStyle = { color: dshThemeColor.labelTertiary, fontSize: 11 };
725
+ const diffEmptyStyle = { padding: 10, color: dshThemeColor.labelTertiary, fontSize: 12 };
726
+ const diffLinesStyle = { overflow: 'auto', border: `1px solid ${dshThemeColor.border}`, borderRadius: 4, background: dshThemeColor.pageBackground, fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace', fontSize: 12 };
727
+ const diffLineBaseStyle = { display: 'grid', gridTemplateColumns: '42px 42px minmax(0, 1fr)', minHeight: 21, alignItems: 'stretch', padding: '0 8px', whiteSpace: 'pre', overflowWrap: 'normal', lineHeight: 1.5 };
728
+ const diffLineNumberStyle = { paddingRight: 8, color: dshThemeColor.labelTertiary, borderRight: `1px solid ${dshThemeColor.border}`, textAlign: 'right', userSelect: 'none' };
729
+ const diffCodeStyle = { minWidth: 0, paddingLeft: 10, color: 'inherit', font: 'inherit', overflow: 'visible' };
730
+ const commitSectionStyle = { ...sectionStyle, gap: 14, padding: '16px 14px 12px' };
731
+ const commitEditorRowStyle = { display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 };
732
+ const commitSubjectStyle = { width: '100%', minHeight: 30, boxSizing: 'border-box', resize: 'none', border: 0, outline: 'none', padding: '3px 0', color: dshThemeColor.labelPrimary, background: 'transparent', font: 'inherit', fontSize: 16 };
733
+ const draftButtonStyle = { ...iconButtonStyle, width: 30, height: 30, color: dshThemeColor.labelSecondary, fontSize: 21 };
734
+ const commitActionsStyle = { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 };
735
+ const refreshActionStyle = { minHeight: 30, border: 0, borderRadius: 5, background: 'transparent', color: dshThemeColor.labelSecondary, cursor: 'pointer', fontSize: 13 };
736
+ const submitActionStyle = { ...refreshActionStyle, color: dshThemeColor.accent, fontWeight: 600 };
737
+ const primaryButtonStyle = { minHeight: 28, border: 0, borderRadius: 5, padding: '4px 9px', color: '#fff', background: dshThemeColor.accent, cursor: 'pointer', fontSize: 12 };
738
+ const branchSelectStyle = { maxWidth: 150, border: `1px solid ${dshThemeColor.border}`, borderRadius: 4, padding: '3px 5px', color: dshThemeColor.labelSecondary, background: dshThemeColor.pageBackground, fontSize: 11 };
739
+ const loadMoreButtonStyle = { minHeight: 30, border: `1px solid ${dshThemeColor.border}`, borderRadius: 5, padding: '4px 9px', color: dshThemeColor.labelSecondary, background: 'transparent', cursor: 'pointer', fontSize: 12 };
740
+ const menuStyle = { position: 'relative', flex: '0 0 auto' };
741
+ const menuSummaryStyle = { listStyle: 'none', cursor: 'pointer', padding: '0 4px', color: dshThemeColor.labelSecondary, fontSize: 16 };
742
+ const menuPopupStyle = { position: 'absolute', right: 0, zIndex: 2, display: 'flex', flexDirection: 'column', gap: 2, minWidth: 100, padding: 4, border: `1px solid ${dshThemeColor.border}`, borderRadius: 5, background: dshThemeColor.menuBackground, boxShadow: dshThemeColor.subtleShadow };
743
+ const menuButtonStyle = { border: 0, padding: '5px 7px', color: dshThemeColor.labelPrimary, background: 'transparent', textAlign: 'left', cursor: 'pointer', fontSize: 11 };
744
+ const dangerMenuButtonStyle = { ...menuButtonStyle, color: dshThemeColor.error };
745
+ export const gitManagementFeature = {
746
+ descriptor: {
747
+ name: 'gitManagement', version: '0.1.0', enabledByDefault: true, dependencies: [], runtime: 'client',
748
+ ui: { label: 'Git 仓库管理', description: '在右侧 Sidebar 标签页查看提交、暂存文件、未提交文件和 Git 版本历史。', order: 40, defaultOpen: false },
749
+ },
750
+ start(context) {
751
+ const uiContext = context.services.uiContext;
752
+ if (uiContext === undefined)
753
+ throw new Error('Git 管理模块缺少 DSH UI 上下文');
754
+ context.resources.add(registerGitManagementUi(uiContext, { rpc: context.services.rpc, remote: context.services.remote }));
755
+ },
756
+ };
757
+ //# sourceMappingURL=git-management.js.map