@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,196 @@
1
+ import { createCapabilityProfile, } from './types.js';
2
+ export class DshCapabilityRegistryError extends Error {
3
+ code;
4
+ capability;
5
+ constructor(code, message, capability = null, options) {
6
+ super(message, options);
7
+ this.name = 'DshCapabilityRegistryError';
8
+ this.code = code;
9
+ this.capability = capability;
10
+ }
11
+ }
12
+ /** 集中解析 DSH 能力路由;Profile 生成后不可变,避免运行期替换半初始化服务。 */
13
+ export class DshCapabilityRegistry {
14
+ dshVersion;
15
+ runtime;
16
+ routes = new Map();
17
+ profile;
18
+ constructor(dshVersion, runtime) {
19
+ this.dshVersion = dshVersion;
20
+ this.runtime = runtime;
21
+ }
22
+ register(route) {
23
+ validateRoute(route);
24
+ if (route.runtime !== this.runtime)
25
+ return;
26
+ const list = this.routes.get(route.capability) ?? [];
27
+ if (list.some((entry) => entry.id === route.id)) {
28
+ throw new DshCapabilityRegistryError('CAPABILITY_INVALID_ROUTE', `重复的能力路由: ${route.id}`, route.capability);
29
+ }
30
+ list.push(route);
31
+ this.routes.set(route.capability, list);
32
+ this.profile = undefined;
33
+ }
34
+ registerMany(routes) {
35
+ for (const route of routes)
36
+ this.register(route);
37
+ }
38
+ resolve(context) {
39
+ if (this.profile !== undefined)
40
+ return this.profile;
41
+ const resolutions = new Map();
42
+ const diagnostics = [];
43
+ for (const [capability, routes] of this.routes) {
44
+ const candidates = routes
45
+ .filter((route) => isVersionInRange(this.dshVersion, route.supportedDsh))
46
+ .sort((left, right) => routeRank(right) - routeRank(left));
47
+ if (candidates.length === 0) {
48
+ const reason = `DSH ${this.dshVersion} 没有能力 ${capability} 的适配路由`;
49
+ resolutions.set(capability, unavailable(capability, this.dshVersion, reason));
50
+ diagnostics.push({ code: 'CAPABILITY_VERSION_UNSUPPORTED', capability, dshVersion: this.dshVersion, message: reason });
51
+ continue;
52
+ }
53
+ const topPriority = routeRank(candidates[0]);
54
+ const top = candidates.filter((route) => routeRank(route) === topPriority);
55
+ if (top.length > 1) {
56
+ throw new DshCapabilityRegistryError('CAPABILITY_ROUTE_CONFLICT', `能力 ${capability} 存在同优先级路由: ${top.map((route) => route.id).join(', ')}`, capability);
57
+ }
58
+ let selected;
59
+ for (const route of candidates) {
60
+ try {
61
+ if (route.detect(context)) {
62
+ selected = route;
63
+ break;
64
+ }
65
+ }
66
+ catch (error) {
67
+ diagnostics.push({
68
+ code: 'CAPABILITY_DETECT_FAILED',
69
+ capability,
70
+ routeId: route.id,
71
+ dshVersion: this.dshVersion,
72
+ message: `${route.id} 探测失败: ${errorMessage(error)}`,
73
+ ...(route.replacement === undefined ? {} : { replacement: route.replacement }),
74
+ });
75
+ }
76
+ }
77
+ if (selected === undefined) {
78
+ const reason = `DSH ${this.dshVersion} 的能力 ${capability} 没有可用运行时实现`;
79
+ resolutions.set(capability, unavailable(capability, this.dshVersion, reason));
80
+ diagnostics.push({ code: 'CAPABILITY_UNAVAILABLE', capability, dshVersion: this.dshVersion, message: reason });
81
+ continue;
82
+ }
83
+ try {
84
+ const value = selected.create(context);
85
+ const resolution = {
86
+ capability,
87
+ routeId: selected.id,
88
+ dshVersion: this.dshVersion,
89
+ status: selected.status === 'deprecated' ? 'degraded' : 'ready',
90
+ value,
91
+ ...(selected.replacement === undefined ? {} : { replacement: selected.replacement }),
92
+ };
93
+ resolutions.set(capability, resolution);
94
+ if (selected.status === 'deprecated') {
95
+ diagnostics.push({
96
+ code: 'CAPABILITY_DEPRECATED',
97
+ capability,
98
+ routeId: selected.id,
99
+ dshVersion: this.dshVersion,
100
+ message: `能力 ${capability} 使用已弃用路由 ${selected.id}`,
101
+ ...(selected.replacement === undefined ? {} : { replacement: selected.replacement }),
102
+ });
103
+ }
104
+ }
105
+ catch (error) {
106
+ const reason = `${selected.id} 创建失败: ${errorMessage(error)}`;
107
+ resolutions.set(capability, unavailable(capability, this.dshVersion, reason));
108
+ diagnostics.push({ code: 'CAPABILITY_CREATE_FAILED', capability, routeId: selected.id, dshVersion: this.dshVersion, message: reason });
109
+ }
110
+ }
111
+ this.profile = createCapabilityProfile(this.dshVersion, this.runtime, resolutions, diagnostics);
112
+ return this.profile;
113
+ }
114
+ getProfile(context) {
115
+ return this.resolve(context);
116
+ }
117
+ require(profile, capability) {
118
+ const resolution = profile.capabilities.get(capability);
119
+ if (resolution?.value === undefined || resolution.status === 'unavailable') {
120
+ throw new DshCapabilityRegistryError('CAPABILITY_UNAVAILABLE', resolution?.reason ?? `能力不可用: ${capability}`, capability);
121
+ }
122
+ return resolution.value;
123
+ }
124
+ explain(profile, capability) {
125
+ if (capability === undefined)
126
+ return profile.diagnostics;
127
+ return profile.diagnostics.filter((item) => item.capability === capability);
128
+ }
129
+ }
130
+ function validateRoute(route) {
131
+ if (route.id.trim() === '' || route.capability === undefined || route.supportedDsh.trim() === '') {
132
+ throw new DshCapabilityRegistryError('CAPABILITY_INVALID_ROUTE', '能力路由必须包含 id、capability 和 supportedDsh');
133
+ }
134
+ if (!Number.isFinite(route.priority))
135
+ throw new DshCapabilityRegistryError('CAPABILITY_INVALID_ROUTE', `能力路由优先级非法: ${route.id}`, route.capability);
136
+ if (typeof route.detect !== 'function' || typeof route.create !== 'function')
137
+ throw new DshCapabilityRegistryError('CAPABILITY_INVALID_ROUTE', `能力路由缺少探测或工厂: ${route.id}`, route.capability);
138
+ }
139
+ function routeRank(route) {
140
+ return route.priority + (route.status === 'supported' ? 1_000_000 : 0);
141
+ }
142
+ function unavailable(capability, dshVersion, reason) {
143
+ return { capability, dshVersion, status: 'unavailable', reason };
144
+ }
145
+ function isVersionInRange(version, range) {
146
+ const match = /^>=([^ ]+) <=([^ ]+)$/u.exec(range);
147
+ if (match === null)
148
+ return false;
149
+ const actual = parseVersion(version);
150
+ const minimum = parseVersion(match[1]);
151
+ const maximum = parseVersion(match[2]);
152
+ if (actual === undefined || minimum === undefined || maximum === undefined)
153
+ return false;
154
+ return compareVersions(actual, minimum) >= 0 && compareVersions(actual, maximum) <= 0;
155
+ }
156
+ function parseVersion(value) {
157
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/u.exec(value);
158
+ if (match === null)
159
+ return undefined;
160
+ return {
161
+ major: Number(match[1]),
162
+ minor: Number(match[2]),
163
+ patch: Number(match[3]),
164
+ pre: match[4] === undefined ? [] : match[4].split('.').map((part) => /^\d+$/u.test(part) ? Number(part) : part),
165
+ };
166
+ }
167
+ function compareVersions(left, right) {
168
+ for (const key of ['major', 'minor', 'patch']) {
169
+ if (left[key] !== right[key])
170
+ return left[key] - right[key];
171
+ }
172
+ if (left.pre.length === 0 && right.pre.length > 0)
173
+ return 1;
174
+ if (left.pre.length > 0 && right.pre.length === 0)
175
+ return -1;
176
+ for (let index = 0; index < Math.max(left.pre.length, right.pre.length); index += 1) {
177
+ const a = left.pre[index];
178
+ const b = right.pre[index];
179
+ if (a === undefined)
180
+ return -1;
181
+ if (b === undefined)
182
+ return 1;
183
+ if (a === b)
184
+ continue;
185
+ if (typeof a === 'number' && typeof b === 'string')
186
+ return -1;
187
+ if (typeof a === 'string' && typeof b === 'number')
188
+ return 1;
189
+ return a < b ? -1 : 1;
190
+ }
191
+ return 0;
192
+ }
193
+ function errorMessage(error) {
194
+ return error instanceof Error ? error.message : String(error);
195
+ }
196
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1,10 @@
1
+ import { DshCapabilityRegistry } from './registry.js';
2
+ import type { DshCapabilityRuntime } from './types.js';
3
+ /**
4
+ * 注册 Codingns4DSH 当前实际消费的 DSH 服务能力。
5
+ *
6
+ * 探测只检查结构,不读取版本字符串;版本范围由 Registry 统一处理,避免
7
+ * 业务模块在运行时继续堆叠 if (version >= ...)。
8
+ */
9
+ export declare function createDshCapabilityRegistry(dshVersion: string, runtime: DshCapabilityRuntime, context: unknown): DshCapabilityRegistry;
10
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1,34 @@
1
+ import { DshCapabilityRegistry } from './registry.js';
2
+ /**
3
+ * 注册 Codingns4DSH 当前实际消费的 DSH 服务能力。
4
+ *
5
+ * 探测只检查结构,不读取版本字符串;版本范围由 Registry 统一处理,避免
6
+ * 业务模块在运行时继续堆叠 if (version >= ...)。
7
+ */
8
+ export function createDshCapabilityRegistry(dshVersion, runtime, context) {
9
+ const registry = new DshCapabilityRegistry(dshVersion, runtime);
10
+ const value = context;
11
+ const rangeLegacy = '>=0.1.5-rc.3 <=0.1.6';
12
+ const rangeModern = '>=0.1.7-rc.2 <=0.1.7-rc.2';
13
+ const add = (route) => registry.register(route);
14
+ if (runtime === 'host') {
15
+ add({ id: 'settings-scope', capability: 'settings.store', supportedDsh: rangeLegacy, runtime, priority: 10, status: 'supported', introducedIn: '0.1.5-rc.3', detect: (ctx) => typeof ctx.settings?.register === 'function', create: (ctx) => ctx.settings });
16
+ add({ id: 'config-settings', capability: 'settings.store', supportedDsh: rangeModern, runtime, priority: 20, status: 'supported', introducedIn: '0.1.7-rc.2', detect: (ctx) => typeof ctx.settings?.describe === 'function' && typeof ctx.settings?.mutate === 'function', create: (ctx) => ctx.settings });
17
+ add({ id: 'connection-rpc', capability: 'connection.rpc', supportedDsh: rangeLegacy, runtime, priority: 10, status: 'supported', introducedIn: '0.1.5-rc.3', detect: (ctx) => ctx.connection !== undefined, create: (ctx) => ctx.connection });
18
+ add({ id: 'connection-rpc-peer-aware', capability: 'connection.rpc', supportedDsh: rangeModern, runtime, priority: 20, status: 'supported', introducedIn: '0.1.7-rc.2', detect: (ctx) => ctx.connection !== undefined, create: (ctx) => ctx.connection });
19
+ add({ id: 'connection-peer', capability: 'connection.peer', supportedDsh: rangeModern, runtime, priority: 20, status: 'supported', introducedIn: '0.1.7-rc.2', detect: (ctx) => ctx.connection?.peer !== undefined, create: (ctx) => ctx.connection.peer });
20
+ add({ id: 'remote-result', capability: 'typert.remote', supportedDsh: '>=0.1.5-rc.3 <=0.1.7-rc.2', runtime, priority: 10, status: 'supported', introducedIn: '0.1.5-rc.3', detect: (ctx) => ctx.remote !== undefined, create: (ctx) => ctx.remote });
21
+ }
22
+ else {
23
+ add({ id: 'settings-scope', capability: 'settings.store', supportedDsh: rangeLegacy, runtime, priority: 10, status: 'supported', introducedIn: '0.1.5-rc.3', detect: (ctx) => typeof ctx.settingsScope?.bind === 'function', create: (ctx) => ctx.settingsScope });
24
+ add({ id: 'config-form', capability: 'settings.store', supportedDsh: rangeModern, runtime, priority: 20, status: 'supported', introducedIn: '0.1.7-rc.2', detect: (ctx) => typeof ctx.configForms?.get === 'function', create: (ctx) => ctx.configForms });
25
+ add({ id: 'icon-primitives', capability: 'ui.icon.plus', supportedDsh: '>=0.1.5-rc.3 <=0.1.7-rc.2', runtime, priority: 10, status: 'supported', introducedIn: '0.1.5-rc.3', detect: () => value.primitives !== undefined, create: () => value.primitives });
26
+ add({ id: 'locale-runtime', capability: 'locale.runtime', supportedDsh: '>=0.1.5-rc.3 <=0.1.7-rc.2', runtime, priority: 10, status: 'supported', introducedIn: '0.1.5-rc.3', detect: (ctx) => ctx.locale !== undefined, create: (ctx) => ctx.locale });
27
+ add({ id: 'theme-runtime', capability: 'theme.runtime', supportedDsh: '>=0.1.5-rc.3 <=0.1.7-rc.2', runtime, priority: 10, status: 'supported', introducedIn: '0.1.5-rc.3', detect: (ctx) => ctx.theme !== undefined, create: (ctx) => ctx.theme });
28
+ add({ id: 'conversation-events', capability: 'conversation.tool-call', supportedDsh: '>=0.1.5-rc.3 <=0.1.7-rc.2', runtime, priority: 10, status: 'supported', introducedIn: '0.1.5-rc.3', detect: (ctx) => ctx.uiConversation !== undefined, create: (ctx) => ctx.uiConversation });
29
+ add({ id: 'sidebar-right', capability: 'sidebar.right', supportedDsh: '>=0.1.5-rc.3 <=0.1.7-rc.2', runtime, priority: 10, status: 'supported', introducedIn: '0.1.5-rc.3', detect: (ctx) => ctx.sidebarRight !== undefined, create: (ctx) => ctx.sidebarRight });
30
+ add({ id: 'remote-result', capability: 'typert.remote', supportedDsh: '>=0.1.5-rc.3 <=0.1.7-rc.2', runtime, priority: 10, status: 'supported', introducedIn: '0.1.5-rc.3', detect: (ctx) => ctx.remote !== undefined, create: (ctx) => ctx.remote });
31
+ }
32
+ return registry;
33
+ }
34
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Codingns4DSH 内部设置存储契约。
3
+ *
4
+ * 业务模块只依赖这个最小接口,不直接依赖 DSH 的 SettingsScope 或未来的
5
+ * ConfigForm。不同 DSH 版本的读写、revision 和订阅语义都在边界适配器中收敛。
6
+ */
7
+ export interface CodingNsSettingsSnapshot<T> {
8
+ readonly value: T | undefined;
9
+ readonly revision: number | undefined;
10
+ readonly writable: boolean;
11
+ readonly status: 'loading' | 'ready' | 'unavailable';
12
+ }
13
+ export interface CodingNsSettingsOperation {
14
+ readonly op: 'set' | 'unset';
15
+ readonly path: readonly string[];
16
+ readonly value?: unknown;
17
+ }
18
+ export interface CodingNsSettingsStore<T> {
19
+ getSnapshot(): CodingNsSettingsSnapshot<T>;
20
+ subscribe(listener: () => void): () => void;
21
+ mutate(operations: readonly CodingNsSettingsOperation[], expectedRevision?: number): Promise<boolean>;
22
+ set(field: string, value: unknown): Promise<boolean>;
23
+ unset(field: string): Promise<boolean>;
24
+ load?(): Promise<void>;
25
+ dispose?(): void | Promise<void>;
26
+ }
27
+ /** 将 DSH 两代写入结果统一成内部成功布尔值。 */
28
+ export declare function accepted(write: Promise<void | boolean>): Promise<boolean>;
29
+ //# sourceMappingURL=settings-store.d.ts.map
@@ -0,0 +1,5 @@
1
+ /** 将 DSH 两代写入结果统一成内部成功布尔值。 */
2
+ export function accepted(write) {
3
+ return write.then((result) => result !== false);
4
+ }
5
+ //# sourceMappingURL=settings-store.js.map
@@ -0,0 +1,50 @@
1
+ /** DSH 宿主能力的稳定标识。业务模块只依赖这些标识,不直接依赖 DSH 版本。 */
2
+ export type DshCapabilityId = 'settings.store' | 'connection.rpc' | 'connection.peer' | 'ui.icon.plus' | 'ui.icon.chevron' | 'locale.runtime' | 'theme.runtime' | 'conversation.tool-call' | 'sidebar.right' | 'typert.remote';
3
+ export type DshCapabilityRuntime = 'host' | 'client';
4
+ export type DshCapabilityRouteStatus = 'supported' | 'deprecated';
5
+ export type DshCapabilityResolutionStatus = 'ready' | 'degraded' | 'unavailable';
6
+ export interface DshCapabilityDiagnostic {
7
+ readonly code: string;
8
+ readonly capability: DshCapabilityId;
9
+ readonly routeId?: string;
10
+ readonly dshVersion: string;
11
+ readonly message: string;
12
+ readonly replacement?: DshCapabilityId;
13
+ readonly featureNames?: readonly string[];
14
+ }
15
+ export interface DshCapabilityRoute<T> {
16
+ readonly id: string;
17
+ readonly capability: DshCapabilityId;
18
+ readonly supportedDsh: string;
19
+ readonly runtime: DshCapabilityRuntime;
20
+ readonly priority: number;
21
+ readonly detect: (context: unknown) => boolean;
22
+ readonly create: (context: unknown) => T;
23
+ readonly status: DshCapabilityRouteStatus;
24
+ readonly introducedIn: string;
25
+ readonly removableAfter?: string;
26
+ readonly replacement?: DshCapabilityId;
27
+ }
28
+ export interface DshCapabilityResolution<T = unknown> {
29
+ readonly capability: DshCapabilityId;
30
+ readonly routeId?: string;
31
+ readonly dshVersion: string;
32
+ readonly status: DshCapabilityResolutionStatus;
33
+ readonly value?: T;
34
+ readonly reason?: string;
35
+ readonly replacement?: DshCapabilityId;
36
+ }
37
+ export interface DshCapabilityProfile {
38
+ readonly dshVersion: string;
39
+ readonly runtime: DshCapabilityRuntime;
40
+ readonly capabilities: ReadonlyMap<DshCapabilityId, DshCapabilityResolution>;
41
+ readonly diagnostics: readonly DshCapabilityDiagnostic[];
42
+ readonly frozenAt: number;
43
+ }
44
+ export interface FeatureCapabilityRequirement {
45
+ readonly capability: DshCapabilityId;
46
+ readonly required: boolean;
47
+ readonly fallback?: 'disable' | 'degrade' | 'error';
48
+ }
49
+ export declare function createCapabilityProfile(dshVersion: string, runtime: DshCapabilityRuntime, capabilities: ReadonlyMap<DshCapabilityId, DshCapabilityResolution>, diagnostics: readonly DshCapabilityDiagnostic[]): DshCapabilityProfile;
50
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,23 @@
1
+ export function createCapabilityProfile(dshVersion, runtime, capabilities, diagnostics) {
2
+ return Object.freeze({
3
+ dshVersion,
4
+ runtime,
5
+ capabilities: freezeMap(capabilities),
6
+ diagnostics: Object.freeze([...diagnostics]),
7
+ frozenAt: Date.now(),
8
+ });
9
+ }
10
+ /** Map 的只读视图,避免 Object.freeze(Map) 仍可通过 set() 修改。 */
11
+ function freezeMap(source) {
12
+ const map = new Map(source);
13
+ const view = new Proxy(map, {
14
+ get(target, property, receiver) {
15
+ if (property === 'set' || property === 'delete' || property === 'clear')
16
+ return undefined;
17
+ const value = Reflect.get(target, property, target);
18
+ return typeof value === 'function' ? value.bind(target) : value;
19
+ },
20
+ });
21
+ return Object.freeze(view);
22
+ }
23
+ //# sourceMappingURL=types.js.map
@@ -1,5 +1,6 @@
1
1
  import type { FeatureDescriptor, FeatureDisposer, FeatureModule, FeatureResourceScope, FeatureState } from '../shared/contracts/feature.js';
2
- export type FeatureRegistryErrorCode = 'FEATURE_INVALID_DESCRIPTOR' | 'FEATURE_ALREADY_REGISTERED' | 'FEATURE_NOT_FOUND' | 'FEATURE_DEPENDENCY_MISSING' | 'FEATURE_DEPENDENCY_CYCLE' | 'FEATURE_START_FAILED' | 'FEATURE_DISPOSE_FAILED' | 'FEATURE_STATE_INVALID';
2
+ import type { DshCapabilityDiagnostic, DshCapabilityProfile } from '../dsh-capabilities/types.js';
3
+ export type FeatureRegistryErrorCode = 'FEATURE_INVALID_DESCRIPTOR' | 'FEATURE_ALREADY_REGISTERED' | 'FEATURE_NOT_FOUND' | 'FEATURE_DEPENDENCY_MISSING' | 'FEATURE_DEPENDENCY_CYCLE' | 'FEATURE_START_FAILED' | 'FEATURE_DISPOSE_FAILED' | 'FEATURE_STATE_INVALID' | 'FEATURE_CAPABILITY_MISSING';
3
4
  export declare class FeatureRegistryError extends Error {
4
5
  readonly code: FeatureRegistryErrorCode;
5
6
  readonly featureName: string | null;
@@ -12,6 +13,7 @@ export interface FeatureSnapshot {
12
13
  runtime: FeatureDescriptor['runtime'];
13
14
  dependencies: readonly string[];
14
15
  reason: string | null;
16
+ capabilities: readonly DshCapabilityDiagnostic[];
15
17
  }
16
18
  /**
17
19
  * 管理功能模块的依赖、状态和资源所有权。
@@ -35,10 +37,12 @@ export declare class FeatureResourceScopeImpl implements FeatureResourceScope {
35
37
  */
36
38
  export declare class FeatureRegistry<S = unknown, M extends FeatureModule<S> = FeatureModule<S>> {
37
39
  private readonly services;
40
+ private readonly capabilityProfile?;
38
41
  private readonly records;
39
42
  private readonly operations;
43
+ private reconcileOperation;
40
44
  /** @param services - 每个模块在 start 时通过 context.services 取用的服务集合。 */
41
- constructor(services: S);
45
+ constructor(services: S, capabilityProfile?: DshCapabilityProfile | undefined);
42
46
  register(module: M): void;
43
47
  registerMany(modules: readonly M[]): void;
44
48
  has(name: string): boolean;
@@ -61,12 +65,14 @@ export declare class FeatureRegistry<S = unknown, M extends FeatureModule<S> = F
61
65
  * 并发调用按模块串行队列排队,最终状态由最后一次调用决定。
62
66
  */
63
67
  reconcile(enabledNames: readonly string[]): Promise<void>;
68
+ private reconcileInternal;
64
69
  drain(name: string): Promise<void>;
65
70
  dispose(name: string): Promise<void>;
66
71
  disable(name: string): Promise<void>;
67
72
  /** 把期望集合补齐为含依赖的闭包,避免依赖在停用阶段被误停。 */
68
73
  private resolveDesired;
69
74
  private startInternal;
75
+ private checkCapabilities;
70
76
  private activeDependents;
71
77
  private getRecord;
72
78
  private enqueue;
@@ -1,3 +1,4 @@
1
+ import { debugInfo } from '../shared/debug.js';
1
2
  export class FeatureRegistryError extends Error {
2
3
  code;
3
4
  featureName;
@@ -57,11 +58,14 @@ export class FeatureResourceScopeImpl {
57
58
  */
58
59
  export class FeatureRegistry {
59
60
  services;
61
+ capabilityProfile;
60
62
  records = new Map();
61
63
  operations = new Map();
64
+ reconcileOperation = Promise.resolve();
62
65
  /** @param services - 每个模块在 start 时通过 context.services 取用的服务集合。 */
63
- constructor(services) {
66
+ constructor(services, capabilityProfile) {
64
67
  this.services = services;
68
+ this.capabilityProfile = capabilityProfile;
65
69
  }
66
70
  register(module) {
67
71
  validateDescriptor(module?.descriptor);
@@ -76,6 +80,13 @@ export class FeatureRegistry {
76
80
  reason: null,
77
81
  resources,
78
82
  context: { descriptor: module.descriptor, resources, services: this.services },
83
+ capabilities: [],
84
+ });
85
+ traceFeature('registered', {
86
+ feature: name,
87
+ runtime: module.descriptor.runtime,
88
+ dependencies: module.descriptor.dependencies,
89
+ enabledByDefault: module.descriptor.enabledByDefault,
79
90
  });
80
91
  }
81
92
  registerMany(modules) {
@@ -130,6 +141,7 @@ export class FeatureRegistry {
130
141
  };
131
142
  for (const name of this.records.keys())
132
143
  visit(name);
144
+ traceFeature('validated', { features: [...this.records.keys()] });
133
145
  }
134
146
  async start(name) {
135
147
  return this.enqueue(name, async () => {
@@ -145,8 +157,18 @@ export class FeatureRegistry {
145
157
  * 并发调用按模块串行队列排队,最终状态由最后一次调用决定。
146
158
  */
147
159
  async reconcile(enabledNames) {
160
+ const operation = this.reconcileOperation.then(() => this.reconcileInternal(enabledNames), () => this.reconcileInternal(enabledNames));
161
+ this.reconcileOperation = operation.catch(() => undefined);
162
+ return operation;
163
+ }
164
+ async reconcileInternal(enabledNames) {
148
165
  this.validate();
149
166
  const desired = this.resolveDesired(enabledNames);
167
+ traceFeature('reconcile', {
168
+ requested: enabledNames,
169
+ desired: [...desired],
170
+ states: this.list().map((item) => ({ name: item.name, state: item.state, reason: item.reason })),
171
+ });
150
172
  for (const name of this.records.keys()) {
151
173
  if (desired.has(name))
152
174
  await this.start(name);
@@ -243,6 +265,7 @@ export class FeatureRegistry {
243
265
  const record = this.getRecord(name);
244
266
  if (record.state === 'enabled')
245
267
  return;
268
+ traceFeature('start.begin', { feature: name, state: record.state, dependencies: record.module.descriptor.dependencies });
246
269
  if (record.state === 'enabling') {
247
270
  throw new FeatureRegistryError('FEATURE_STATE_INVALID', `Feature is already enabling: ${name}`, name);
248
271
  }
@@ -255,6 +278,13 @@ export class FeatureRegistry {
255
278
  starting.add(name);
256
279
  for (const dependency of record.module.descriptor.dependencies) {
257
280
  await this.enqueue(dependency, () => this.startInternal(dependency, starting));
281
+ const dependencyRecord = this.getRecord(dependency);
282
+ if (dependencyRecord.state !== 'enabled') {
283
+ record.state = 'disabled';
284
+ record.reason = `依赖模块 ${dependency} 未启用`;
285
+ traceFeature('start.blocked', { feature: name, dependency, dependencyState: dependencyRecord.state });
286
+ return;
287
+ }
258
288
  }
259
289
  starting.delete(name);
260
290
  if (record.resources.disposed) {
@@ -263,10 +293,33 @@ export class FeatureRegistry {
263
293
  }
264
294
  record.state = 'enabling';
265
295
  record.reason = null;
296
+ const capabilityCheck = this.checkCapabilities(record);
297
+ record.capabilities = capabilityCheck.diagnostics;
298
+ traceFeature('start.capabilities', {
299
+ feature: name,
300
+ action: capabilityCheck.action,
301
+ reason: capabilityCheck.reason ?? null,
302
+ diagnostics: capabilityCheck.diagnostics,
303
+ });
304
+ if (capabilityCheck.action === 'disable') {
305
+ record.state = 'disabled';
306
+ record.reason = capabilityCheck.reason ?? null;
307
+ traceFeature('start.disabled', { feature: name, reason: record.reason });
308
+ return;
309
+ }
310
+ if (capabilityCheck.action === 'error') {
311
+ record.state = 'failed';
312
+ record.reason = capabilityCheck.reason ?? '能力不可用';
313
+ traceFeature('start.failed', { feature: name, reason: record.reason });
314
+ throw new FeatureRegistryError('FEATURE_CAPABILITY_MISSING', record.reason, name);
315
+ }
316
+ record.context = { ...record.context, capabilityDiagnostics: record.capabilities };
266
317
  try {
318
+ traceFeature('start.module', { feature: name });
267
319
  const returned = await record.module.start(record.context);
268
320
  addReturnedDisposer(record.resources, returned);
269
321
  record.state = 'enabled';
322
+ traceFeature('start.enabled', { feature: name });
270
323
  }
271
324
  catch (error) {
272
325
  try {
@@ -277,9 +330,33 @@ export class FeatureRegistry {
277
330
  }
278
331
  record.state = 'failed';
279
332
  record.reason = errorMessage(error);
333
+ traceFeature('start.failed', { feature: name, reason: record.reason, error: serializeError(error) });
280
334
  throw new FeatureRegistryError('FEATURE_START_FAILED', `Failed to start feature ${name}`, name, { cause: error });
281
335
  }
282
336
  }
337
+ checkCapabilities(record) {
338
+ const requirements = record.module.descriptor.requires ?? [];
339
+ if (requirements.length === 0 || this.capabilityProfile === undefined)
340
+ return { action: 'start', diagnostics: [] };
341
+ const diagnostics = [];
342
+ for (const requirement of requirements) {
343
+ const resolution = this.capabilityProfile.capabilities.get(requirement.capability);
344
+ if (resolution?.status !== 'unavailable') {
345
+ if (resolution?.status === 'degraded')
346
+ diagnostics.push(...this.capabilityProfile.diagnostics.filter((item) => item.capability === requirement.capability));
347
+ continue;
348
+ }
349
+ const diagnostic = this.capabilityProfile.diagnostics.find((item) => item.capability === requirement.capability)
350
+ ?? { code: 'CAPABILITY_UNAVAILABLE', capability: requirement.capability, dshVersion: this.capabilityProfile.dshVersion, message: resolution?.reason ?? `能力不可用: ${requirement.capability}` };
351
+ diagnostics.push(diagnostic);
352
+ const fallback = requirement.fallback ?? (requirement.required ? 'error' : 'disable');
353
+ if (requirement.required || fallback === 'error')
354
+ return { action: 'error', reason: `${record.module.descriptor.name} 缺少能力 ${requirement.capability}: ${diagnostic.message}`, diagnostics };
355
+ if (fallback === 'disable')
356
+ return { action: 'disable', reason: `能力 ${requirement.capability} 不可用,模块已禁用`, diagnostics };
357
+ }
358
+ return { action: 'start', diagnostics };
359
+ }
283
360
  activeDependents(name) {
284
361
  const result = [];
285
362
  for (const record of this.records.values()) {
@@ -308,6 +385,15 @@ export class FeatureRegistry {
308
385
  return next;
309
386
  }
310
387
  }
388
+ function traceFeature(event, details) {
389
+ debugInfo(`codingns4dsh: feature.${event}`, details);
390
+ }
391
+ function serializeError(error) {
392
+ if (error instanceof Error) {
393
+ return { name: error.name, message: error.message, ...(error.stack === undefined ? {} : { stack: error.stack }) };
394
+ }
395
+ return { name: typeof error, message: String(error) };
396
+ }
311
397
  function validateDescriptor(descriptor) {
312
398
  if (!descriptor || typeof descriptor.name !== 'string' || descriptor.name.trim() === '') {
313
399
  throw new FeatureRegistryError('FEATURE_INVALID_DESCRIPTOR', 'Feature descriptor requires a non-empty name');
@@ -328,6 +414,22 @@ function validateDescriptor(descriptor) {
328
414
  && (typeof descriptor.minimumDshVersion !== 'string' || descriptor.minimumDshVersion.trim() === '')) {
329
415
  throw new FeatureRegistryError('FEATURE_INVALID_DESCRIPTOR', `Feature ${descriptor.name} has invalid minimumDshVersion`, descriptor.name);
330
416
  }
417
+ if (descriptor.requires !== undefined) {
418
+ if (!Array.isArray(descriptor.requires))
419
+ throw new FeatureRegistryError('FEATURE_INVALID_DESCRIPTOR', `Feature ${descriptor.name} has invalid requires`, descriptor.name);
420
+ const seen = new Set();
421
+ for (const requirement of descriptor.requires) {
422
+ if (requirement === undefined || typeof requirement.capability !== 'string' || requirement.capability.trim() === '' || typeof requirement.required !== 'boolean') {
423
+ throw new FeatureRegistryError('FEATURE_INVALID_DESCRIPTOR', `Feature ${descriptor.name} has invalid capability requirement`, descriptor.name);
424
+ }
425
+ if (seen.has(requirement.capability))
426
+ throw new FeatureRegistryError('FEATURE_INVALID_DESCRIPTOR', `Feature ${descriptor.name} has duplicate capability requirement`, descriptor.name);
427
+ seen.add(requirement.capability);
428
+ if (requirement.fallback !== undefined && !['disable', 'degrade', 'error'].includes(requirement.fallback)) {
429
+ throw new FeatureRegistryError('FEATURE_INVALID_DESCRIPTOR', `Feature ${descriptor.name} has invalid capability fallback`, descriptor.name);
430
+ }
431
+ }
432
+ }
331
433
  if (!Array.isArray(descriptor.dependencies) || descriptor.dependencies.some((dependency) => typeof dependency !== 'string' || dependency.trim() === '')) {
332
434
  throw new FeatureRegistryError('FEATURE_INVALID_DESCRIPTOR', `Feature ${descriptor.name} has invalid dependencies`, descriptor.name);
333
435
  }
@@ -374,6 +476,7 @@ function snapshot(record) {
374
476
  runtime: record.module.descriptor.runtime,
375
477
  dependencies: [...record.module.descriptor.dependencies],
376
478
  reason: record.reason,
479
+ capabilities: [...record.capabilities],
377
480
  };
378
481
  }
379
482
  function errorMessage(error) {
@@ -1,5 +1,5 @@
1
1
  import type { AuthDeviceManagementSnapshotDto, CodingNsAuthLoginResult, CodingNsAuthSessionSnapshot, HostBindRequest, LoginByEmailRequest, TunnelBindingSummary } from '../shared/contracts/auth.js';
2
- import type { CodingNsControlApiClient } from './control-api-client.js';
2
+ import { type CodingNsControlApiClient } from './control-api-client.js';
3
3
  import type { RelaySignalingTicketResponse } from '../shared/contracts/signaling.js';
4
4
  import type { CodingNsCredentialStore } from './credential-store.js';
5
5
  /**
@@ -13,6 +13,7 @@ export declare class CodingNsAuthSession {
13
13
  private state;
14
14
  private accessToken;
15
15
  private credential;
16
+ private refreshPromise;
16
17
  constructor(client: CodingNsControlApiClient, credentials: CodingNsCredentialStore, controlBaseUrl: string);
17
18
  snapshot(): CodingNsAuthSessionSnapshot;
18
19
  /**
@@ -22,10 +23,16 @@ export declare class CodingNsAuthSession {
22
23
  getAccessToken(): string | null;
23
24
  /** 仅供 Host 内部设备运行时调用,Client 不会拿到控制面客户端。 */
24
25
  getControlClient(): CodingNsControlApiClient;
26
+ /**
27
+ * 执行需要 access token 的请求;服务端拒绝旧 token 时只续期一次并重试。
28
+ * refresh token 仍然只留在 Host,调用方无需复制认证恢复逻辑。
29
+ */
30
+ withAccessToken<T>(operation: (accessToken: string) => Promise<T>): Promise<T>;
25
31
  /** 使用保存的 refresh token 恢复会话;没有凭据时保持 logged_out。 */
26
32
  restore(): Promise<CodingNsAuthSessionSnapshot>;
27
33
  login(request: LoginByEmailRequest): Promise<CodingNsAuthLoginResult>;
28
34
  refresh(): Promise<CodingNsAuthSessionSnapshot>;
35
+ private refreshInternal;
29
36
  logout(): Promise<void>;
30
37
  getDevices(): Promise<AuthDeviceManagementSnapshotDto>;
31
38
  listHostBindings(): Promise<TunnelBindingSummary[]>;