@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
@@ -1,5 +1,6 @@
1
- import { homedir } from 'node:os';
1
+ import { homedir, hostname } from 'node:os';
2
2
  import { join } from 'node:path';
3
+ import { DSH_VERSION } from '../shared/contracts/version.js';
3
4
  import { FileDshDeviceCredentialStore, } from './credential-store.js';
4
5
  import { ensureHostDtlsIdentity, FileHostDtlsIdentityStore, startHostRelayRuntime, } from './relay-tunnel-runtime.js';
5
6
  /**
@@ -15,20 +16,26 @@ export async function startDshHostDeviceRuntime(options) {
15
16
  throw new Error('DSH Host accessToken 已失效');
16
17
  return token;
17
18
  };
19
+ const withAccessToken = (operation) => options.withAccessToken === undefined ? operation(getAccessToken()) : options.withAccessToken(operation);
18
20
  const credentialStore = options.credentialStore ?? new FileDshDeviceCredentialStore(defaultDshCredentialPath());
19
21
  const dtlsStore = options.dtlsStore ?? new FileHostDtlsIdentityStore(defaultDtlsPath());
20
22
  const identity = await ensureHostDtlsIdentity(dtlsStore);
23
+ const dshVersion = options.dshVersion?.trim() || DSH_VERSION;
24
+ const computerName = options.computerName?.trim() || hostname().trim() || 'unknown';
25
+ const heartbeatDetails = { dshVersion, computerName };
21
26
  let credential = await credentialStore.read();
22
27
  let device;
23
28
  if (credential === null || credential.dtlsFingerprint !== identity.fingerprint) {
24
29
  const request = {
25
30
  displayName: options.displayName?.trim() || 'DSH Host',
31
+ dshVersion,
32
+ computerName,
26
33
  devicePublicKey: identity.certPem,
27
34
  dtlsFingerprint: identity.fingerprint,
28
35
  protocolVersion: options.protocolVersion ?? 'dsh-envelope-v1',
29
36
  capabilities: [...(options.capabilities ?? ['rpc', 'pty', 'file', 'web'])],
30
37
  };
31
- const registered = await options.controlClient.registerDshDevice(getAccessToken(), request);
38
+ const registered = await withAccessToken((accessToken) => options.controlClient.registerDshDevice(accessToken, request));
32
39
  credential = {
33
40
  deviceId: registered.device.dshDeviceId ?? registered.device.deviceId ?? (() => { throw new Error('DSH 注册响应缺少设备标识'); })(),
34
41
  deviceCredential: registered.deviceCredential,
@@ -36,17 +43,21 @@ export async function startDshHostDeviceRuntime(options) {
36
43
  dtlsFingerprint: identity.fingerprint,
37
44
  tunnelDomain: registered.device.tunnelDomain ?? null,
38
45
  displayName: registered.device.displayName,
46
+ dshVersion: registered.device.dshVersion ?? dshVersion,
47
+ computerName: registered.device.computerName ?? computerName,
39
48
  savedAt: new Date().toISOString(),
40
49
  };
41
50
  await credentialStore.write(credential);
42
51
  device = registered.device;
43
52
  }
44
53
  else {
45
- const listed = await options.controlClient.listDshDevices(getAccessToken());
54
+ const listed = await withAccessToken((accessToken) => options.controlClient.listDshDevices(accessToken));
46
55
  device = listed.devices.find((candidate) => (candidate.dshDeviceId ?? candidate.deviceId) === credential.deviceId) ?? {
47
56
  dshDeviceId: credential.deviceId,
48
57
  deviceId: credential.deviceId,
49
58
  displayName: credential.displayName,
59
+ dshVersion: credential.dshVersion ?? dshVersion,
60
+ computerName: credential.computerName ?? computerName,
50
61
  protocolVersion: options.protocolVersion ?? 'dsh-envelope-v1',
51
62
  capabilities: [...(options.capabilities ?? [])],
52
63
  dtlsFingerprint: credential.dtlsFingerprint,
@@ -61,10 +72,10 @@ export async function startDshHostDeviceRuntime(options) {
61
72
  if (credential === null)
62
73
  throw new Error('DSH 设备凭据初始化失败');
63
74
  const savedCredential = credential;
64
- await options.controlClient.heartbeatDshDevice(getAccessToken(), savedCredential.deviceId, savedCredential.deviceCredential);
75
+ await withAccessToken((accessToken) => options.controlClient.heartbeatDshDevice(accessToken, savedCredential.deviceId, savedCredential.deviceCredential, heartbeatDetails));
65
76
  const runtimeOptions = {
66
77
  controlClient: { createSignalingTicket: async () => { throw new Error('DSH runtime 必须使用 DSH ticket'); } },
67
- createTicket: ({ identity: material, credentialVersion }) => requestDshTicket(options.controlClient, getAccessToken(), savedCredential, material.fingerprint, credentialVersion),
78
+ createTicket: ({ identity: material, credentialVersion }) => requestDshTicket(withAccessToken, options.controlClient, savedCredential, material.fingerprint, credentialVersion),
68
79
  accessToken: options.accessToken,
69
80
  bindingId: savedCredential.deviceId,
70
81
  credentialVersion: savedCredential.credentialVersion,
@@ -83,7 +94,7 @@ export async function startDshHostDeviceRuntime(options) {
83
94
  if (stopped)
84
95
  return;
85
96
  void Promise.resolve()
86
- .then(() => options.controlClient.heartbeatDshDevice(getAccessToken(), savedCredential.deviceId, savedCredential.deviceCredential))
97
+ .then(() => withAccessToken((accessToken) => options.controlClient.heartbeatDshDevice(accessToken, savedCredential.deviceId, savedCredential.deviceCredential, heartbeatDetails)))
87
98
  .catch(() => undefined);
88
99
  }, options.heartbeatIntervalMs ?? 30_000);
89
100
  const stop = async () => {
@@ -98,14 +109,14 @@ export async function startDshHostDeviceRuntime(options) {
98
109
  await options.onRuntime?.(runtime);
99
110
  return { credential: savedCredential, device, runtime, stop };
100
111
  }
101
- async function requestDshTicket(client, accessToken, credential, fingerprint, credentialVersion) {
102
- const response = await client.createDshRelayTicket(accessToken, {
112
+ async function requestDshTicket(withAccessToken, client, credential, fingerprint, credentialVersion) {
113
+ const response = await withAccessToken((accessToken) => client.createDshRelayTicket(accessToken, {
103
114
  dshDeviceId: credential.deviceId,
104
115
  deviceCredential: credential.deviceCredential,
105
116
  hostDtlsFingerprint: fingerprint,
106
117
  credentialVersion: credentialVersion ?? credential.credentialVersion,
107
118
  role: 'host',
108
- });
119
+ }));
109
120
  return {
110
121
  ...response,
111
122
  bindingId: response.dshDeviceId,
@@ -3,8 +3,9 @@
3
3
  *
4
4
  * DSH 0.1.6 尚未把启动器版本作为 Cordis 服务暴露给插件,因此不能从
5
5
  * 插件自身的 DSH_VERSION 常量推断宿主版本。优先沿当前 dsh 可执行入口
6
- * 向上查找 @deepseek-ai/dsh/package.json,只有开发环境无法提供入口时才
7
- * 回退到 PATH 中的 `dsh --version`。
6
+ * 向上查找 @deepseek-ai/dsh/package.json。Desktop Host 额外传入 Runtime
7
+ * 根目录,需要直接从该目录的 node_modules 读取实际加载的包版本;只有
8
+ * 这些路径都无法提供版本时才回退到 PATH 中的 `dsh --version`。
8
9
  */
9
10
  export declare function detectRuntimeDshVersion(): string;
10
11
  /** Host 启动后供 Web Client 复用的版本注入名称。 */
@@ -1,4 +1,5 @@
1
1
  import { readFileSync } from 'node:fs';
2
+ import { createRequire } from 'node:module';
2
3
  import { dirname, join } from 'node:path';
3
4
  import { spawnSync } from 'node:child_process';
4
5
  import { fileURLToPath } from 'node:url';
@@ -9,13 +10,17 @@ const VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u;
9
10
  *
10
11
  * DSH 0.1.6 尚未把启动器版本作为 Cordis 服务暴露给插件,因此不能从
11
12
  * 插件自身的 DSH_VERSION 常量推断宿主版本。优先沿当前 dsh 可执行入口
12
- * 向上查找 @deepseek-ai/dsh/package.json,只有开发环境无法提供入口时才
13
- * 回退到 PATH 中的 `dsh --version`。
13
+ * 向上查找 @deepseek-ai/dsh/package.json。Desktop Host 额外传入 Runtime
14
+ * 根目录,需要直接从该目录的 node_modules 读取实际加载的包版本;只有
15
+ * 这些路径都无法提供版本时才回退到 PATH 中的 `dsh --version`。
14
16
  */
15
17
  export function detectRuntimeDshVersion() {
16
18
  const candidates = [
17
19
  process.env.DSH_RUNTIME_VERSION,
18
20
  process.env.DSH_VERSION,
21
+ ...process.argv.slice(1).map(readDshVersionFromRuntimeRoot),
22
+ ...process.argv.slice(1).map(readDshVersionFromModule),
23
+ ...readDshVersionsFromDesktopResources(),
19
24
  readDshPackageVersion(process.argv[1]),
20
25
  readDshPackageVersion(fileURLToPath(import.meta.url)),
21
26
  readDshVersionFromCommand(),
@@ -50,6 +55,70 @@ function readDshPackageVersion(entryPath) {
50
55
  }
51
56
  return undefined;
52
57
  }
58
+ /**
59
+ * Desktop 启动器把 Runtime 根目录作为 Host 参数传入,Host 入口本身与
60
+ * @deepseek-ai/dsh 是兄弟目录,无法通过普通的父目录遍历找到它。
61
+ */
62
+ function readDshVersionFromRuntimeRoot(runtimeRoot) {
63
+ if (runtimeRoot === undefined || runtimeRoot.trim() === '')
64
+ return undefined;
65
+ const packageVersion = readDshPackageManifestVersion(join(runtimeRoot, 'node_modules', '@deepseek-ai', 'dsh', 'package.json'));
66
+ if (packageVersion !== undefined)
67
+ return packageVersion;
68
+ // 某些打包方式只保留 Runtime 根 package.json 的依赖声明。
69
+ try {
70
+ const manifest = JSON.parse(readFileSync(join(runtimeRoot, 'package.json'), 'utf8'));
71
+ const declaredVersion = manifest.dependencies?.['@deepseek-ai/dsh'] ?? manifest.devDependencies?.['@deepseek-ai/dsh'];
72
+ return typeof declaredVersion === 'string' && VERSION_PATTERN.test(declaredVersion) ? declaredVersion : undefined;
73
+ }
74
+ catch {
75
+ return undefined;
76
+ }
77
+ }
78
+ /**
79
+ * 让 Node/Electron 按 Desktop Host 自身的依赖边界解析 DSH。asar 内部路径
80
+ * 由 Electron 的模块加载器处理,不能假设普通文件 API 能独立遍历所有目录。
81
+ */
82
+ function readDshVersionFromModule(entryPath) {
83
+ if (entryPath === undefined || entryPath.trim() === '' || entryPath.startsWith('-'))
84
+ return undefined;
85
+ try {
86
+ const require = createRequire(entryPath);
87
+ const manifestPath = require.resolve('@deepseek-ai/dsh/package.json');
88
+ return readDshPackageManifestVersion(manifestPath);
89
+ }
90
+ catch {
91
+ return undefined;
92
+ }
93
+ }
94
+ /** Electron 可能消费部分 argv;此时从自身 Resources 目录定位 Desktop Runtime。 */
95
+ function readDshVersionsFromDesktopResources() {
96
+ const processWithResources = process;
97
+ const resourcesRoots = [
98
+ processWithResources.resourcesPath,
99
+ join(dirname(dirname(process.execPath)), 'Resources'),
100
+ ].filter((value) => typeof value === 'string' && value.trim() !== '');
101
+ const runtimeRoots = resourcesRoots.flatMap((resourcesRoot) => [
102
+ join(resourcesRoot, 'app.asar', 'dsh'),
103
+ join(resourcesRoot, 'app.asar.unpacked', 'dsh'),
104
+ join(resourcesRoot, 'dsh'),
105
+ ]);
106
+ return runtimeRoots.flatMap((runtimeRoot) => [
107
+ readDshVersionFromRuntimeRoot(runtimeRoot),
108
+ readDshVersionFromModule(join(runtimeRoot, 'node_modules', '@deepseek-ai', 'dsh-desktop-host', 'lib', 'index.js')),
109
+ ]);
110
+ }
111
+ function readDshPackageManifestVersion(manifestPath) {
112
+ try {
113
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
114
+ if (manifest.name === '@deepseek-ai/dsh' && typeof manifest.version === 'string')
115
+ return manifest.version;
116
+ }
117
+ catch {
118
+ return undefined;
119
+ }
120
+ return undefined;
121
+ }
53
122
  function readDshVersionFromCommand() {
54
123
  try {
55
124
  const result = spawnSync(process.platform === 'win32' ? 'dsh.cmd' : 'dsh', ['--version'], {
@@ -29,7 +29,7 @@ export function createAuthFeature() {
29
29
  return {
30
30
  descriptor: {
31
31
  name: 'auth',
32
- version: '0.1.0',
32
+ version: '0.1.1',
33
33
  enabledByDefault: true,
34
34
  dependencies: [],
35
35
  runtime: 'host',
@@ -89,6 +89,7 @@ export function createAuthFeature() {
89
89
  accessToken,
90
90
  credentialStore: dshCredentials,
91
91
  accessTokenProvider: () => target.getAccessToken(),
92
+ withAccessToken: (operation) => target.withAccessToken(operation),
92
93
  ...(context.services.dshVersion === undefined ? {} : { dshVersion: context.services.dshVersion }),
93
94
  resources: context.resources,
94
95
  gatewayFeatures,
@@ -131,7 +132,10 @@ export function createAuthFeature() {
131
132
  bind: (payload) => requireSession().bindHost(parseHostBind(payload)),
132
133
  unbind: (payload) => requireSession().unbindHost(parseStringField(payload, 'bindingId')),
133
134
  signalingTicket: (payload) => requireSession().createClientSignalingTicket(parseOptionalStringField(payload, 'tunnelDomain')),
134
- 'dsh/device/list': () => requireSession().getControlClient().listDshDevices(requireSession().getAccessToken() ?? ''),
135
+ 'dsh/device/list': () => {
136
+ const target = requireSession();
137
+ return target.withAccessToken((accessToken) => target.getControlClient().listDshDevices(accessToken));
138
+ },
135
139
  'dsh/device/start': async () => { await startDsh(requireSession()); return dshRuntime?.device ?? null; },
136
140
  'dsh/device/stop': async () => { await dshRuntime?.stop(); dshRuntime = null; return { stopped: true }; },
137
141
  'dsh/device/status': () => dshRuntime ? { device: dshRuntime.device, online: true } : { device: null, online: false },
@@ -148,8 +152,7 @@ export function createAuthFeature() {
148
152
  const input = isRecord(payload) && typeof payload.dshDeviceId === 'string' ? payload.dshDeviceId.trim() : '';
149
153
  if (!input || input !== dshRuntime.credential.deviceId)
150
154
  throw new CodingNsRpcError('DSH_DEVICE_NOT_FOUND', '请求的 DSH 设备不是当前 Host');
151
- const accessToken = target.getAccessToken();
152
- if (!accessToken)
155
+ if (!target.getAccessToken())
153
156
  throw new CodingNsRpcError('CODINGNS_RPC_UNAUTHENTICATED', 'Codingns4DSH 尚未登录');
154
157
  const request = {
155
158
  dshDeviceId: dshRuntime.credential.deviceId,
@@ -158,7 +161,7 @@ export function createAuthFeature() {
158
161
  credentialVersion: dshRuntime.credential.credentialVersion,
159
162
  role: 'client',
160
163
  };
161
- return target.getControlClient().createDshRelayTicket(accessToken, request);
164
+ return target.withAccessToken((accessToken) => target.getControlClient().createDshRelayTicket(accessToken, request));
162
165
  },
163
166
  };
164
167
  context.resources.add(context.services.rpc.register('auth', (action, payload) => {
@@ -4,7 +4,7 @@ export function createDebugFeature() {
4
4
  return {
5
5
  descriptor: {
6
6
  name: 'debug',
7
- version: '0.1.0',
7
+ version: '0.1.1',
8
8
  enabledByDefault: true,
9
9
  dependencies: ['terminalProcess'],
10
10
  runtime: 'host',
@@ -0,0 +1,5 @@
1
+ import type { FeatureModule } from '../../shared/contracts/feature.js';
2
+ import type { CodingNsHostServices } from './types.js';
3
+ /** Host 侧 Git 工作区服务;所有路径都由 resolveWorkspaceRoot 权威解析。 */
4
+ export declare function createGitManagementFeature(): FeatureModule<CodingNsHostServices>;
5
+ //# sourceMappingURL=git-management.d.ts.map
@@ -0,0 +1,290 @@
1
+ import { execFile as execFileCallback } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ import { isAbsolute, relative, resolve } from 'node:path';
4
+ import { CodingNsRpcError } from '../rpc-table.js';
5
+ const execFile = promisify(execFileCallback);
6
+ const MAX_OUTPUT_BYTES = 4 * 1024 * 1024;
7
+ const MAX_DIFF_BYTES = 60_000;
8
+ /** Host 侧 Git 工作区服务;所有路径都由 resolveWorkspaceRoot 权威解析。 */
9
+ export function createGitManagementFeature() {
10
+ return {
11
+ descriptor: {
12
+ name: 'gitManagement',
13
+ version: '0.1.0',
14
+ enabledByDefault: true,
15
+ dependencies: [],
16
+ runtime: 'host',
17
+ },
18
+ start(context) {
19
+ context.resources.add(context.services.rpc.register('git', async (action, payload) => {
20
+ const input = record(payload);
21
+ const workspaceId = requiredString(input.workspaceId, 'workspaceId');
22
+ const root = context.services.resolveWorkspaceRoot?.(workspaceId);
23
+ if (root === null || root === undefined)
24
+ throw new CodingNsRpcError('GIT_WORKSPACE_UNAVAILABLE', '当前 Workspace 没有可用的本地目录');
25
+ switch (action) {
26
+ case 'status': return readStatus(workspaceId, root);
27
+ case 'init': return runGit(root, ['init']).then(() => readStatus(workspaceId, root));
28
+ case 'diff': return readDiff(workspaceId, root, requiredString(input.path, 'path'), input.staged === true);
29
+ case 'stage': return mutateTargets(workspaceId, root, input.targets, 'add');
30
+ case 'unstage': return mutateTargets(workspaceId, root, input.targets, 'reset');
31
+ case 'discard': return discardTargets(workspaceId, root, input.targets);
32
+ case 'commit': return commit(workspaceId, root, requiredString(input.subject, 'subject'));
33
+ case 'commit-diff': return readCommitDiff(root, requiredString(input.commitHash, 'commitHash'));
34
+ case 'history': return readHistory(workspaceId, root, input.limit, input.offset);
35
+ case 'branches': return readBranches(root);
36
+ case 'switch': return switchBranch(root, requiredString(input.branchName, 'branchName'), input.create === true);
37
+ case 'fetch': return syncRemote(workspaceId, root, ['fetch', '--all', '--prune']);
38
+ case 'pull': return syncRemote(workspaceId, root, ['pull', '--ff-only']);
39
+ case 'push': return syncRemote(workspaceId, root, ['push']);
40
+ case 'undo': return undoLastCommit(workspaceId, root);
41
+ default: throw new CodingNsRpcError('CODINGNS_RPC_NOT_FOUND', `未知 Git RPC: git/${action}`);
42
+ }
43
+ }));
44
+ },
45
+ };
46
+ }
47
+ async function readStatus(workspaceId, root) {
48
+ let result;
49
+ try {
50
+ result = await runGit(root, ['status', '--porcelain=v1', '-z', '--branch']);
51
+ }
52
+ catch (error) {
53
+ if (!isNotGitRepositoryError(error))
54
+ throw error;
55
+ return {
56
+ snapshot: { workspaceId, repoRoot: root, enabled: false, branch: 'HEAD', ahead: 0, behind: 0, hasRemote: false, isDirty: false, lastFetchedAt: null },
57
+ changes: [],
58
+ };
59
+ }
60
+ const tokens = result.stdout.split('\0');
61
+ const changes = [];
62
+ let branch = 'HEAD';
63
+ let ahead = 0;
64
+ let behind = 0;
65
+ let hasRemote = false;
66
+ for (let index = 0; index < tokens.length; index += 1) {
67
+ const token = tokens[index];
68
+ if (!token)
69
+ continue;
70
+ if (token.startsWith('## ')) {
71
+ const tracking = parseBranchLine(token.slice(3));
72
+ branch = tracking.branch;
73
+ ahead = tracking.ahead;
74
+ behind = tracking.behind;
75
+ hasRemote = tracking.hasRemote;
76
+ continue;
77
+ }
78
+ if (token.length < 3)
79
+ continue;
80
+ const stagedStatus = token[0] === ' ' || token[0] === '?' || token[0] === '!' ? null : token[0] ?? null;
81
+ const worktreeStatus = token[1] === ' ' || token[1] === '?' || token[1] === '!' ? null : token[1] ?? null;
82
+ const status = `${token[0] ?? ' '}${token[1] ?? ' '}`.trim() || '?';
83
+ const path = normalizeGitPath(token.slice(3));
84
+ let oldPath = null;
85
+ if ((token[0] === 'R' || token[0] === 'C' || token[1] === 'R' || token[1] === 'C') && tokens[index + 1]) {
86
+ oldPath = normalizeGitPath(tokens[index + 1] ?? '');
87
+ index += 1;
88
+ }
89
+ changes.push({ path, status, staged: stagedStatus !== null, oldPath, binary: false, stagedStatus, worktreeStatus });
90
+ }
91
+ return {
92
+ snapshot: { workspaceId, repoRoot: root, enabled: true, branch, ahead, behind, hasRemote, isDirty: changes.length > 0, lastFetchedAt: null },
93
+ changes,
94
+ };
95
+ }
96
+ function parseBranchLine(value) {
97
+ const [rawBranch, rawTracking] = value.split('...');
98
+ const branch = (rawBranch ?? 'HEAD').replace(/^No commits yet on /u, '').trim() || 'HEAD';
99
+ const tracking = rawTracking ?? '';
100
+ const ahead = Number(/\[ahead (\d+)/u.exec(tracking)?.[1] ?? 0);
101
+ const behind = Number(/\[behind (\d+)/u.exec(tracking)?.[1] ?? 0);
102
+ return { branch, ahead, behind, hasRemote: rawTracking !== undefined };
103
+ }
104
+ async function readDiff(workspaceId, root, target, staged) {
105
+ const path = safeTarget(root, target);
106
+ try {
107
+ const result = await runGit(root, [...(staged ? ['diff', '--cached'] : ['diff']), '--binary', '--', path]);
108
+ const content = result.stdout.slice(0, MAX_DIFF_BYTES);
109
+ return { workspaceId, path: normalizeGitPath(target), staged, binary: /Binary files /u.test(result.stdout), truncated: result.stdout.length > content.length, content };
110
+ }
111
+ catch (error) {
112
+ if (error instanceof GitCommandError && error.stderr.includes('unknown revision'))
113
+ throw error;
114
+ return { workspaceId, path: normalizeGitPath(target), staged, binary: false, truncated: false, content: '' };
115
+ }
116
+ }
117
+ async function mutateTargets(workspaceId, root, rawTargets, action) {
118
+ const targets = safeTargets(root, rawTargets);
119
+ if (targets.length > 0)
120
+ await runGit(root, action === 'add' ? ['add', '--', ...targets] : ['reset', '--', ...targets]);
121
+ return readStatus(workspaceId, root);
122
+ }
123
+ async function discardTargets(workspaceId, root, rawTargets) {
124
+ const targets = safeTargets(root, rawTargets);
125
+ if (targets.length > 0) {
126
+ await runGit(root, ['restore', '--worktree', '--staged', '--', ...targets]).catch(() => undefined);
127
+ await runGit(root, ['clean', '-f', '--', ...targets]).catch(() => undefined);
128
+ }
129
+ return readStatus(workspaceId, root);
130
+ }
131
+ async function commit(workspaceId, root, subject) {
132
+ if (subject.length > 200)
133
+ throw new TypeError('subject 不能超过 200 个字符');
134
+ const result = await runGit(root, ['commit', '-m', subject]);
135
+ const hash = (await runGit(root, ['rev-parse', 'HEAD'])).stdout.trim();
136
+ return { commitHash: hash, summary: result.stdout.trim() || `已提交 ${hash.slice(0, 8)}`, status: await readStatus(workspaceId, root) };
137
+ }
138
+ async function readCommitDiff(root, rawCommitHash) {
139
+ const commitHash = rawCommitHash.trim();
140
+ if (!/^[0-9a-f]{7,40}$/iu.test(commitHash))
141
+ throw new TypeError('commitHash 无效');
142
+ const [result, filesResult] = await Promise.all([
143
+ runGit(root, ['-c', 'core.quotePath=false', 'show', '--format=', '--binary', '--no-ext-diff', '-M', commitHash, '--']),
144
+ runGit(root, ['-c', 'core.quotePath=false', 'show', '--format=', '--name-status', '-M', commitHash, '--']),
145
+ ]);
146
+ const content = result.stdout.slice(0, MAX_DIFF_BYTES);
147
+ return { commitHash, files: parseCommitChangedFiles(filesResult.stdout, result.stdout), content, truncated: result.stdout.length > content.length };
148
+ }
149
+ function parseCommitChangedFiles(value, diffContent) {
150
+ const binaryPaths = new Set();
151
+ for (const line of diffContent.split(/\r?\n/u)) {
152
+ const match = /^Binary files .* and .* differ$/u.exec(line);
153
+ if (match !== null) {
154
+ const paths = line.match(/(?:a|b)\/([^\s]+?)(?: differ)?$/u);
155
+ if (paths?.[1] !== undefined)
156
+ binaryPaths.add(paths[1]);
157
+ }
158
+ }
159
+ return value.split(/\r?\n/u).flatMap((line) => {
160
+ const fields = line.split('\t');
161
+ const statusToken = fields[0]?.trim() ?? '';
162
+ if (statusToken === '' || fields.length < 2)
163
+ return [];
164
+ const status = statusToken[0] ?? '?';
165
+ const renamed = status === 'R' || status === 'C';
166
+ const oldPath = renamed ? fields[1] ?? null : null;
167
+ const path = (renamed ? fields[2] : fields[1])?.trim() ?? '';
168
+ if (path === '')
169
+ return [];
170
+ return [{ path, oldPath, status, binary: binaryPaths.has(path) }];
171
+ });
172
+ }
173
+ async function syncRemote(workspaceId, root, args) {
174
+ await runGit(root, args);
175
+ return readStatus(workspaceId, root);
176
+ }
177
+ async function undoLastCommit(workspaceId, root) {
178
+ await runGit(root, ['rev-parse', '--verify', 'HEAD']);
179
+ await runGit(root, ['reset', '--soft', 'HEAD~1']);
180
+ return readStatus(workspaceId, root);
181
+ }
182
+ async function readHistory(_workspaceId, root, rawLimit, rawOffset) {
183
+ const limit = Math.max(1, Math.min(100, Number.isSafeInteger(rawLimit) ? Number(rawLimit) : 50));
184
+ const offset = Math.max(0, Math.min(1_000_000, Number.isSafeInteger(rawOffset) ? Number(rawOffset) : 0));
185
+ let result;
186
+ try {
187
+ result = await runGit(root, ['log', `--skip=${String(offset)}`, `--max-count=${String(limit)}`, '--format=%H%x1f%an%x1f%aI%x1f%s%x1f%b%x1e']);
188
+ }
189
+ catch (error) {
190
+ if (isEmptyRepositoryError(error))
191
+ return { items: [], cursor: String(offset), nextCursor: null, totalCount: 0 };
192
+ throw error;
193
+ }
194
+ const items = [];
195
+ for (const record of result.stdout.split('\x1e')) {
196
+ const fields = record.trim().split('\x1f');
197
+ if (fields.length < 5 || !fields[0])
198
+ continue;
199
+ items.push({ commitHash: fields[0], authorName: fields[1] ?? '', authoredAt: fields[2] ?? '', subject: fields[3] ?? '', body: fields[4] ?? '', refs: [] });
200
+ }
201
+ let total = 0;
202
+ try {
203
+ total = Number((await runGit(root, ['rev-list', '--count', 'HEAD'])).stdout.trim() || 0);
204
+ }
205
+ catch (error) {
206
+ if (!isEmptyRepositoryError(error))
207
+ throw error;
208
+ }
209
+ return { items, cursor: String(offset), nextCursor: offset + items.length < total ? String(offset + items.length) : null, totalCount: total };
210
+ }
211
+ async function readBranches(root) {
212
+ const currentBranch = (await runGit(root, ['branch', '--show-current'])).stdout.trim() || 'HEAD';
213
+ // for-each-ref 不展开 %xNN;使用 Git 原生支持的 NUL 字段分隔符,避免分支名被污染。
214
+ const result = await runGit(root, ['for-each-ref', '--format=%(refname)%00%(refname:short)%00%(HEAD)%00%(upstream:short)', 'refs/heads', 'refs/remotes']);
215
+ const local = [];
216
+ const remote = [];
217
+ const fields = result.stdout.replace(/\r?\n$/u, '').split('\0');
218
+ for (let index = 0; index + 3 < fields.length; index += 4) {
219
+ const fullName = fields[index] ?? '';
220
+ const name = fields[index + 1] ?? '';
221
+ const head = fields[index + 2] ?? '';
222
+ const upstream = fields[index + 3] ?? '';
223
+ if (!name)
224
+ continue;
225
+ const item = { name, current: head === '*', upstream: upstream || null, remote: fullName.startsWith('refs/remotes/') };
226
+ (item.remote ? remote : local).push(item);
227
+ }
228
+ return { currentBranch, local, remote };
229
+ }
230
+ async function switchBranch(root, branchName, create) {
231
+ if (!/^[A-Za-z0-9._/-]+$/u.test(branchName) || branchName.startsWith('-'))
232
+ throw new TypeError('branchName 无效');
233
+ await runGit(root, create ? ['switch', '-c', branchName] : ['switch', branchName]);
234
+ return readBranches(root);
235
+ }
236
+ async function runGit(cwd, args) {
237
+ try {
238
+ return await execFile('git', [...args], { cwd, encoding: 'utf8', maxBuffer: MAX_OUTPUT_BYTES, timeout: 30_000 });
239
+ }
240
+ catch (error) {
241
+ const detail = error;
242
+ throw new GitCommandError(`git ${args.join(' ')} 执行失败`, detail.stderr ?? detail.stdout ?? String(error), detail.code);
243
+ }
244
+ }
245
+ class GitCommandError extends Error {
246
+ stderr;
247
+ commandCode;
248
+ constructor(message, stderr, commandCode) {
249
+ super(message);
250
+ this.stderr = stderr;
251
+ this.commandCode = commandCode;
252
+ }
253
+ }
254
+ function safeTargets(root, value) {
255
+ if (!Array.isArray(value) || value.some((item) => typeof item !== 'string'))
256
+ throw new TypeError('targets 必须是字符串数组');
257
+ return value.map((item) => safeTarget(root, item)).filter((item, index, list) => list.indexOf(item) === index);
258
+ }
259
+ function safeTarget(root, value) {
260
+ const target = normalizeGitPath(value);
261
+ if (!target || target.includes('\0') || isAbsolute(target) || target === '.' || target.split('/').includes('..'))
262
+ throw new TypeError('Git 路径无效');
263
+ const absolute = resolve(root, target);
264
+ const escaped = relative(root, absolute);
265
+ if (escaped.startsWith('..') || isAbsolute(escaped))
266
+ throw new TypeError('Git 路径超出 Workspace');
267
+ return target;
268
+ }
269
+ function normalizeGitPath(value) { return value.replaceAll('\\', '/').replace(/^\.\//u, '').trim(); }
270
+ function isNotGitRepositoryError(error) {
271
+ if (!(error instanceof GitCommandError))
272
+ return false;
273
+ return error.commandCode === 128 && /not a git repository|not a git repository/u.test(error.stderr);
274
+ }
275
+ function isEmptyRepositoryError(error) {
276
+ if (!(error instanceof GitCommandError))
277
+ return false;
278
+ return error.commandCode === 128 && /does not have any commits yet|your current branch/u.test(error.stderr);
279
+ }
280
+ function record(value) {
281
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
282
+ throw new TypeError('Git RPC 参数必须是对象');
283
+ return value;
284
+ }
285
+ function requiredString(value, field) {
286
+ if (typeof value !== 'string' || value.trim() === '')
287
+ throw new TypeError(`${field} 必须是非空字符串`);
288
+ return value.trim();
289
+ }
290
+ //# sourceMappingURL=git-management.js.map
@@ -5,7 +5,7 @@ export function createHostStatusFeature() {
5
5
  return {
6
6
  descriptor: {
7
7
  name: 'hostStatus',
8
- version: '0.1.0',
8
+ version: '0.1.1',
9
9
  enabledByDefault: true,
10
10
  dependencies: [],
11
11
  runtime: 'host',
@@ -15,5 +15,6 @@ export { createCliAdaptersFeature } from '../cli-adapters/feature.js';
15
15
  export { createTerminalProcessFeature } from './terminal-process.js';
16
16
  export { createDebugFeature } from './debug.js';
17
17
  export { createHostStatusFeature } from './host-status.js';
18
+ export { createGitManagementFeature } from './git-management.js';
18
19
  export type { CodingNsHostServices } from './types.js';
19
20
  //# sourceMappingURL=index.d.ts.map
@@ -5,6 +5,7 @@ import { createCliAdaptersFeature } from '../cli-adapters/feature.js';
5
5
  import { createTerminalProcessFeature } from './terminal-process.js';
6
6
  import { createDebugFeature } from './debug.js';
7
7
  import { createHostStatusFeature } from './host-status.js';
8
+ import { createGitManagementFeature } from './git-management.js';
8
9
  /** 生产启动可把实际 controller 模式注入状态模块,避免向设置页报告默认占位值。 */
9
10
  export function createHostFeatures(options = {}) {
10
11
  return [
@@ -15,6 +16,7 @@ export function createHostFeatures(options = {}) {
15
16
  createTerminalProcessFeature(),
16
17
  createDebugFeature(),
17
18
  createHostStatusFeature(),
19
+ createGitManagementFeature(),
18
20
  ];
19
21
  }
20
22
  /** Host 侧功能模块清单:新增模块在这里登记一行,不需要改动入口或 RPC 分发。 */
@@ -26,4 +28,5 @@ export { createCliAdaptersFeature } from '../cli-adapters/feature.js';
26
28
  export { createTerminalProcessFeature } from './terminal-process.js';
27
29
  export { createDebugFeature } from './debug.js';
28
30
  export { createHostStatusFeature } from './host-status.js';
31
+ export { createGitManagementFeature } from './git-management.js';
29
32
  //# sourceMappingURL=index.js.map
@@ -4,7 +4,7 @@ export function createTerminalProcessFeature() {
4
4
  return {
5
5
  descriptor: {
6
6
  name: 'terminalProcess',
7
- version: '0.1.0',
7
+ version: '0.1.1',
8
8
  enabledByDefault: true,
9
9
  dependencies: [],
10
10
  runtime: 'host',
@@ -1,11 +1,13 @@
1
1
  import type { FeatureModule } from '../../shared/contracts/feature.js';
2
- import type { CodingNsTerminalStatus } from '../../shared/contracts/terminal.js';
2
+ import type { CodingNsTerminalRuntimeType, CodingNsTerminalStatus } from '../../shared/contracts/terminal.js';
3
3
  import { type DetectedTerminalShell } from '../terminal/shell-detection.js';
4
4
  import type { CodingNsHostServices } from './types.js';
5
5
  export interface TerminalStatusFeatureOptions {
6
6
  readonly platform?: string;
7
7
  readonly controllerMode?: CodingNsTerminalStatus['controllerMode'];
8
8
  readonly effectiveEnabled?: boolean;
9
+ readonly runtimeTypes?: readonly CodingNsTerminalRuntimeType[];
10
+ readonly runtimeWarning?: string;
9
11
  readonly detectShells?: () => readonly DetectedTerminalShell[];
10
12
  }
11
13
  /**
@@ -14,7 +14,7 @@ export function createTerminalStatusFeature(options = {}) {
14
14
  return {
15
15
  descriptor: {
16
16
  name: 'terminalStatus',
17
- version: '0.1.0',
17
+ version: '0.1.1',
18
18
  enabledByDefault: true,
19
19
  dependencies: [],
20
20
  runtime: 'host',
@@ -38,6 +38,8 @@ export function createTerminalStatusFeature(options = {}) {
38
38
  platform: normalizePlatform(platform),
39
39
  controllerMode,
40
40
  effectiveEnabled,
41
+ ...(options.runtimeTypes === undefined ? {} : { runtimeTypes: options.runtimeTypes }),
42
+ ...(options.runtimeWarning === undefined ? {} : { runtimeWarning: options.runtimeWarning }),
41
43
  profiles: available.map((shell) => ({
42
44
  profileId: shell.profileId,
43
45
  name: shell.displayName,