@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.
- package/README.en.md +306 -0
- package/README.md +190 -993
- package/data/build/dist/client/account-bar.d.ts +2 -2
- package/data/build/dist/client/account-bar.js +96 -39
- package/data/build/dist/client/bundle.js +13722 -3611
- package/data/build/dist/client/cli-catalog.js +20 -3
- package/data/build/dist/client/cli-slots.d.ts +5 -0
- package/data/build/dist/client/cli-slots.js +57 -7
- package/data/build/dist/client/debug/ui.d.ts +1 -1
- package/data/build/dist/client/debug/ui.js +62 -32
- package/data/build/dist/client/dsh-runtime-version.d.ts +9 -2
- package/data/build/dist/client/dsh-runtime-version.js +24 -2
- package/data/build/dist/client/features/cli-adapters.d.ts +1 -1
- package/data/build/dist/client/features/cli-adapters.js +19 -16
- package/data/build/dist/client/features/debug.js +2 -2
- package/data/build/dist/client/features/index.d.ts +2 -1
- package/data/build/dist/client/features/index.js +3 -1
- package/data/build/dist/client/features/lan-access-panel.d.ts +1 -1
- package/data/build/dist/client/features/lan-access-panel.js +8 -10
- package/data/build/dist/client/features/lan-access.js +1 -1
- package/data/build/dist/client/features/login-protection-panel.d.ts +1 -1
- package/data/build/dist/client/features/login-protection-panel.js +7 -9
- package/data/build/dist/client/features/reverse-proxy-panel.d.ts +1 -1
- package/data/build/dist/client/features/reverse-proxy-panel.js +99 -11
- package/data/build/dist/client/features/reverse-proxy.d.ts +1 -1
- package/data/build/dist/client/features/reverse-proxy.js +19 -18
- package/data/build/dist/client/features/terminal-enhancement-panel.d.ts +1 -1
- package/data/build/dist/client/features/terminal-enhancement-panel.js +62 -55
- package/data/build/dist/client/features/terminal-enhancement.d.ts +1 -1
- package/data/build/dist/client/features/terminal-enhancement.js +3 -3
- package/data/build/dist/client/features/types.d.ts +14 -3
- package/data/build/dist/client/features/workspace-session-enhancement-panel.d.ts +1 -1
- package/data/build/dist/client/features/workspace-session-enhancement-panel.js +31 -9
- package/data/build/dist/client/features/workspace-session-enhancement.js +72 -11
- package/data/build/dist/client/git-management.d.ts +16 -0
- package/data/build/dist/client/git-management.js +757 -0
- package/data/build/dist/client/index.d.ts +3 -2
- package/data/build/dist/client/index.js +152 -27
- package/data/build/dist/client/locale.js +91 -29
- package/data/build/dist/client/quick-phrase-layout.d.ts +49 -0
- package/data/build/dist/client/quick-phrase-layout.js +95 -0
- package/data/build/dist/client/quick-phrase-slot.d.ts +7 -0
- package/data/build/dist/client/quick-phrase-slot.js +370 -0
- package/data/build/dist/client/remote-web-context.js +1 -1
- package/data/build/dist/client/settings-bridge.d.ts +9 -6
- package/data/build/dist/client/settings-bridge.js +51 -27
- package/data/build/dist/client/settings-section.d.ts +3 -2
- package/data/build/dist/client/settings-section.js +33 -19
- package/data/build/dist/client/subscription-slot.d.ts +3 -0
- package/data/build/dist/client/subscription-slot.js +108 -16
- package/data/build/dist/client/terminal/model.d.ts +14 -2
- package/data/build/dist/client/terminal/model.js +109 -21
- package/data/build/dist/client/terminal/recovery.d.ts +50 -0
- package/data/build/dist/client/terminal/recovery.js +101 -0
- package/data/build/dist/client/terminal/ui.d.ts +2 -2
- package/data/build/dist/client/terminal/ui.js +49 -11
- package/data/build/dist/client/terminal/xterm-view.d.ts +2 -2
- package/data/build/dist/client/terminal/xterm-view.js +2 -1
- package/data/build/dist/client/theme.d.ts +6 -1
- package/data/build/dist/client/theme.js +32 -1
- package/data/build/dist/client/workspace-session-archive-dom.d.ts +12 -0
- package/data/build/dist/client/workspace-session-archive-dom.js +27 -5
- package/data/build/dist/client/workspace-session-visibility-dom.d.ts +30 -0
- package/data/build/dist/client/workspace-session-visibility-dom.js +604 -0
- package/data/build/dist/dsh-capabilities/client/config-forms-adapter.d.ts +50 -0
- package/data/build/dist/dsh-capabilities/client/config-forms-adapter.js +139 -0
- package/data/build/dist/dsh-capabilities/client/conversation-adapter.d.ts +7 -0
- package/data/build/dist/dsh-capabilities/client/conversation-adapter.js +6 -0
- package/data/build/dist/dsh-capabilities/client/locale-adapter.d.ts +9 -0
- package/data/build/dist/dsh-capabilities/client/locale-adapter.js +4 -0
- package/data/build/dist/dsh-capabilities/client/primitives-adapter.d.ts +10 -0
- package/data/build/dist/dsh-capabilities/client/primitives-adapter.js +14 -0
- package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.d.ts +8 -0
- package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.js +16 -0
- package/data/build/dist/dsh-capabilities/client/sidebar-adapter.d.ts +7 -0
- package/data/build/dist/dsh-capabilities/client/sidebar-adapter.js +5 -0
- package/data/build/dist/dsh-capabilities/client/theme-adapter.d.ts +6 -0
- package/data/build/dist/dsh-capabilities/client/theme-adapter.js +4 -0
- package/data/build/dist/dsh-capabilities/client/typert-adapter.d.ts +5 -0
- package/data/build/dist/dsh-capabilities/client/typert-adapter.js +4 -0
- package/data/build/dist/dsh-capabilities/host/config-forms-adapter.d.ts +19 -0
- package/data/build/dist/dsh-capabilities/host/config-forms-adapter.js +45 -0
- package/data/build/dist/dsh-capabilities/host/connection-rpc-adapter.d.ts +19 -0
- package/data/build/dist/dsh-capabilities/host/connection-rpc-adapter.js +13 -0
- package/data/build/dist/dsh-capabilities/host/settings-scope-adapter.d.ts +8 -0
- package/data/build/dist/dsh-capabilities/host/settings-scope-adapter.js +47 -0
- package/data/build/dist/dsh-capabilities/host/typert-adapter.d.ts +6 -0
- package/data/build/dist/dsh-capabilities/host/typert-adapter.js +4 -0
- package/data/build/dist/dsh-capabilities/index.d.ts +6 -0
- package/data/build/dist/dsh-capabilities/index.js +6 -0
- package/data/build/dist/dsh-capabilities/matrix.d.ts +15 -0
- package/data/build/dist/dsh-capabilities/matrix.js +32 -0
- package/data/build/dist/dsh-capabilities/registry.d.ts +22 -0
- package/data/build/dist/dsh-capabilities/registry.js +196 -0
- package/data/build/dist/dsh-capabilities/routes.d.ts +10 -0
- package/data/build/dist/dsh-capabilities/routes.js +34 -0
- package/data/build/dist/dsh-capabilities/settings-store.d.ts +29 -0
- package/data/build/dist/dsh-capabilities/settings-store.js +5 -0
- package/data/build/dist/dsh-capabilities/types.d.ts +50 -0
- package/data/build/dist/dsh-capabilities/types.js +23 -0
- package/data/build/dist/features/registry.d.ts +8 -2
- package/data/build/dist/features/registry.js +104 -1
- package/data/build/dist/host/auth-session.d.ts +8 -1
- package/data/build/dist/host/auth-session.js +28 -6
- package/data/build/dist/host/cli-adapters/claude-driver.d.ts +2 -0
- package/data/build/dist/host/cli-adapters/claude-driver.js +19 -4
- package/data/build/dist/host/cli-adapters/claude-model-options.d.ts +14 -0
- package/data/build/dist/host/cli-adapters/claude-model-options.js +157 -0
- package/data/build/dist/host/cli-adapters/codex-driver.d.ts +11 -0
- package/data/build/dist/host/cli-adapters/codex-driver.js +166 -14
- package/data/build/dist/host/cli-adapters/driver.d.ts +2 -0
- package/data/build/dist/host/cli-adapters/dsh-message-projector.d.ts +15 -0
- package/data/build/dist/host/cli-adapters/dsh-message-projector.js +120 -29
- package/data/build/dist/host/cli-adapters/dsh-tool-history.d.ts +4 -1
- package/data/build/dist/host/cli-adapters/dsh-tool-history.js +25 -7
- package/data/build/dist/host/cli-adapters/feature.js +74 -8
- package/data/build/dist/host/cli-adapters/gemini-driver.js +2 -2
- package/data/build/dist/host/cli-adapters/grok-driver.js +2 -2
- package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts +4 -2
- package/data/build/dist/host/cli-adapters/json-rpc-process.js +8 -9
- package/data/build/dist/host/cli-adapters/kimi-driver.js +1 -1
- package/data/build/dist/host/cli-adapters/legacy-session-adapter.d.ts +17 -0
- package/data/build/dist/host/cli-adapters/legacy-session-adapter.js +88 -0
- package/data/build/dist/host/cli-adapters/legacy-session-settings.d.ts +24 -0
- package/data/build/dist/host/cli-adapters/legacy-session-settings.js +162 -0
- package/data/build/dist/host/cli-adapters/official-provider-subscription.d.ts +29 -0
- package/data/build/dist/host/cli-adapters/official-provider-subscription.js +361 -0
- package/data/build/dist/host/cli-adapters/opencode-driver.d.ts +2 -0
- package/data/build/dist/host/cli-adapters/opencode-driver.js +97 -12
- package/data/build/dist/host/cli-adapters/pi-driver.js +69 -23
- package/data/build/dist/host/cli-adapters/provider-registry.d.ts +33 -0
- package/data/build/dist/host/cli-adapters/provider-registry.js +83 -0
- package/data/build/dist/host/cli-adapters/provider-subscription.d.ts +25 -3
- package/data/build/dist/host/cli-adapters/provider-subscription.js +346 -30
- package/data/build/dist/host/cli-adapters/registry.d.ts +6 -0
- package/data/build/dist/host/cli-adapters/registry.js +170 -13
- package/data/build/dist/host/cli-adapters/rpc-driver-utils.js +10 -0
- package/data/build/dist/host/cli-adapters/session-store.d.ts +7 -0
- package/data/build/dist/host/cli-adapters/session-store.js +39 -1
- package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts +3 -0
- package/data/build/dist/host/cli-adapters/stream-normalizer.d.ts +3 -0
- package/data/build/dist/host/cli-adapters/stream-normalizer.js +22 -2
- package/data/build/dist/host/control-api-client.d.ts +3 -3
- package/data/build/dist/host/control-api-client.js +2 -1
- package/data/build/dist/host/credential-store.js +11 -1
- package/data/build/dist/host/dsh-device-runtime.d.ts +4 -0
- package/data/build/dist/host/dsh-device-runtime.js +20 -9
- package/data/build/dist/host/dsh-runtime-version.d.ts +3 -2
- package/data/build/dist/host/dsh-runtime-version.js +71 -2
- package/data/build/dist/host/features/auth.js +8 -5
- package/data/build/dist/host/features/debug.js +1 -1
- package/data/build/dist/host/features/git-management.d.ts +5 -0
- package/data/build/dist/host/features/git-management.js +290 -0
- package/data/build/dist/host/features/host-status.js +1 -1
- package/data/build/dist/host/features/index.d.ts +1 -0
- package/data/build/dist/host/features/index.js +3 -0
- package/data/build/dist/host/features/terminal-process.js +1 -1
- package/data/build/dist/host/features/terminal-status.d.ts +3 -1
- package/data/build/dist/host/features/terminal-status.js +3 -1
- package/data/build/dist/host/index.d.ts +7 -3
- package/data/build/dist/host/index.js +79 -15
- package/data/build/dist/host/lan-access-dsh.js +18 -5
- package/data/build/dist/host/native-session-bridge.d.ts +23 -1
- package/data/build/dist/host/native-session-bridge.js +157 -10
- package/data/build/dist/host/rpc-table.d.ts +1 -1
- package/data/build/dist/host/rpc.js +60 -10
- package/data/build/dist/host/session-migration-repair.d.ts +19 -0
- package/data/build/dist/host/session-migration-repair.js +395 -0
- package/data/build/dist/host/settings.d.ts +8 -1
- package/data/build/dist/host/settings.js +94 -3
- package/data/build/dist/host/terminal/backends/tmux-backend.d.ts +1 -0
- package/data/build/dist/host/terminal/backends/tmux-backend.js +4 -2
- package/data/build/dist/host/terminal/controller-factory.d.ts +4 -0
- package/data/build/dist/host/terminal/controller-factory.js +26 -7
- package/data/build/dist/host/terminal/runtime-manager.d.ts +2 -1
- package/data/build/dist/host/terminal/runtime-manager.js +3 -0
- package/data/build/dist/host/terminal/startup.d.ts +2 -0
- package/data/build/dist/host/terminal/startup.js +2 -1
- package/data/build/dist/host/terminal/terminal-controller.d.ts +10 -3
- package/data/build/dist/host/terminal/terminal-controller.js +101 -15
- package/data/build/dist/host/terminal/terminal-process-service.js +3 -1
- package/data/build/dist/host/terminal/terminal-service.d.ts +2 -0
- package/data/build/dist/host/terminal/terminal-service.js +10 -0
- package/data/build/dist/index.d.ts +6 -2
- package/data/build/dist/index.js +8 -2
- package/data/build/dist/shared/contracts/cli-adapter.d.ts +14 -0
- package/data/build/dist/shared/contracts/config.d.ts +31 -1
- package/data/build/dist/shared/contracts/config.js +25 -0
- package/data/build/dist/shared/contracts/dsh-device.d.ts +13 -1
- package/data/build/dist/shared/contracts/errors.d.ts +1 -1
- package/data/build/dist/shared/contracts/feature.d.ts +6 -0
- package/data/build/dist/shared/contracts/git.d.ts +82 -0
- package/data/build/dist/shared/contracts/git.js +3 -0
- package/data/build/dist/shared/contracts/subscription.d.ts +46 -0
- package/data/build/dist/shared/contracts/terminal.d.ts +4 -1
- package/data/build/dist/shared/contracts/version.d.ts +2 -2
- package/data/build/dist/shared/contracts/version.js +4 -6
- package/data/build/dist/shared/debug.d.ts +14 -0
- package/data/build/dist/shared/debug.js +53 -0
- package/data/build/dist/shared/index.d.ts +7 -4
- package/data/build/dist/shared/index.js +2 -1
- package/data/build/dist/transport/debug.d.ts +1 -1
- package/data/build/dist/transport/debug.js +3 -30
- package/data/build/dist/transport/dsh-session.js +3 -3
- package/data/build/dist/typert.host.d.ts +1 -1
- package/data/build/dist/typert.host.js +6 -3
- package/data/build/dist/typert.remote-client.d.ts +11 -0
- package/data/build/dist/typert.remote-client.js +7 -0
- package/dsh.bundle.patch +1 -1
- package/package.json +13 -5
- package/profile/README.md +4 -4
- package/profile/package.json +6 -4
- package/profile/scripts/check-dsh-install.mjs +2 -2
- package/profile/version.json +2 -2
- package/scripts/check-dsh-install.mjs +2 -2
- package/version.json +2 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Service } from '@deepseek-ai/cordis';
|
|
2
|
+
import { debugInfo, debugWarn } from '../../shared/debug.js';
|
|
2
3
|
class ObservableValue {
|
|
3
4
|
value;
|
|
4
5
|
listeners = new Set();
|
|
@@ -19,12 +20,12 @@ class ObservableValue {
|
|
|
19
20
|
/** 一个 Sidebar 标签对应的终端模型;DOM 卸载只 detach,显式 close 才结束 Host 进程。 */
|
|
20
21
|
export class CodingNsTerminalView {
|
|
21
22
|
sessionId;
|
|
22
|
-
id;
|
|
23
23
|
remote;
|
|
24
24
|
createWhenMissing;
|
|
25
25
|
shellPath;
|
|
26
26
|
onWorkspaceResolved;
|
|
27
27
|
state;
|
|
28
|
+
id;
|
|
28
29
|
store = new ObservableValue({ phase: 'idle', title: '终端', writable: false });
|
|
29
30
|
lifetime = new AbortController();
|
|
30
31
|
followController;
|
|
@@ -38,11 +39,11 @@ export class CodingNsTerminalView {
|
|
|
38
39
|
lastResize;
|
|
39
40
|
constructor(sessionId, id, remote, createWhenMissing, shellPath, onWorkspaceResolved) {
|
|
40
41
|
this.sessionId = sessionId;
|
|
41
|
-
this.id = id;
|
|
42
42
|
this.remote = remote;
|
|
43
43
|
this.createWhenMissing = createWhenMissing;
|
|
44
44
|
this.shellPath = shellPath;
|
|
45
45
|
this.onWorkspaceResolved = onWorkspaceResolved;
|
|
46
|
+
this.id = id;
|
|
46
47
|
this.state = this.store;
|
|
47
48
|
}
|
|
48
49
|
mount() {
|
|
@@ -122,9 +123,11 @@ export class CodingNsTerminalView {
|
|
|
122
123
|
async close() {
|
|
123
124
|
if (this.store.getSnapshot().phase === 'closed')
|
|
124
125
|
return;
|
|
126
|
+
debugInfo('codingns4dsh: client terminal close entered', { sessionId: this.sessionId, terminalId: this.id });
|
|
125
127
|
this.patch({ phase: 'closing', writable: false });
|
|
126
128
|
this.detach();
|
|
127
129
|
unwrap(await resolveRemote(this.remote).close(this.sessionId, this.id));
|
|
130
|
+
debugInfo('codingns4dsh: client terminal close success', { sessionId: this.sessionId, terminalId: this.id });
|
|
128
131
|
this.patch({ phase: 'closed', writable: false });
|
|
129
132
|
}
|
|
130
133
|
async dispose() {
|
|
@@ -137,11 +140,22 @@ export class CodingNsTerminalView {
|
|
|
137
140
|
try {
|
|
138
141
|
const remote = resolveRemote(this.remote);
|
|
139
142
|
const environment = unwrap(await remote.environment(this.sessionId, this.lifetime.signal));
|
|
140
|
-
|
|
141
|
-
|
|
143
|
+
debugInfo('codingns4dsh: client terminal environment', { sessionId: this.sessionId, workspaceId: environment.workspaceId, cwd: environment.cwd });
|
|
144
|
+
let createWhenMissing = this.createWhenMissing;
|
|
145
|
+
if (environment.workspaceId !== undefined) {
|
|
146
|
+
const binding = this.onWorkspaceResolved?.(environment.workspaceId, this.id);
|
|
147
|
+
debugInfo('codingns4dsh: client terminal workspace binding', { sessionId: this.sessionId, workspaceId: environment.workspaceId, requestedId: this.id, binding: binding ?? null });
|
|
148
|
+
if (binding !== undefined) {
|
|
149
|
+
if (binding.id !== this.id)
|
|
150
|
+
this.id = binding.id;
|
|
151
|
+
// 工作区已有绑定时,恢复必须失败闭合,不能用新 ID 偷建替代终端。
|
|
152
|
+
createWhenMissing = !binding.existing;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
142
155
|
const listed = unwrap(await remote.list(this.sessionId));
|
|
156
|
+
debugInfo('codingns4dsh: client terminal list', { sessionId: this.sessionId, workspaceId: environment.workspaceId, terminalIds: listed.map((entry) => entry.id), requestedId: this.id, createWhenMissing });
|
|
143
157
|
let info = listed.find((entry) => entry.id === this.id);
|
|
144
|
-
if (info === undefined &&
|
|
158
|
+
if (info === undefined && createWhenMissing) {
|
|
145
159
|
this.patch({ phase: 'creating', environment });
|
|
146
160
|
info = unwrap(await remote.create(this.sessionId, {
|
|
147
161
|
id: this.id,
|
|
@@ -149,6 +163,7 @@ export class CodingNsTerminalView {
|
|
|
149
163
|
cols: Math.min(80, environment.maxCols),
|
|
150
164
|
rows: Math.min(24, environment.maxRows),
|
|
151
165
|
}, this.lifetime.signal));
|
|
166
|
+
debugInfo('codingns4dsh: client terminal created', { sessionId: this.sessionId, workspaceId: environment.workspaceId, terminalId: info.id });
|
|
152
167
|
}
|
|
153
168
|
if (info === undefined)
|
|
154
169
|
throw new Error('Host 中不存在该终端,且恢复流程禁止自动创建替代进程');
|
|
@@ -238,14 +253,17 @@ export class CodingNsWebTerminals extends Service {
|
|
|
238
253
|
recoveries = new Map();
|
|
239
254
|
closeFailureStore = new ObservableValue([]);
|
|
240
255
|
closeFailures = this.closeFailureStore;
|
|
256
|
+
remoteReadyStore = new ObservableValue(false);
|
|
257
|
+
remoteReadyState = this.remoteReadyStore;
|
|
241
258
|
closeRequests = new Map();
|
|
259
|
+
/** Remote 注入前不能执行关闭请求;就绪后统一冲刷,避免把启动竞态显示成永久错误。 */
|
|
260
|
+
cleanupQueued = false;
|
|
242
261
|
constructor(ctx, remote) {
|
|
243
262
|
super(ctx, 'webTerminals');
|
|
244
263
|
this.remote = remote;
|
|
245
264
|
for (const request of readCloseRequests())
|
|
246
265
|
this.closeRequests.set(String(request.id), request);
|
|
247
|
-
|
|
248
|
-
void this.cleanup(request);
|
|
266
|
+
void this.flushCleanup();
|
|
249
267
|
}
|
|
250
268
|
view(sessionId, key, contentId, terminalId, shellPath) {
|
|
251
269
|
const mapKey = JSON.stringify([sessionId, key]);
|
|
@@ -254,19 +272,20 @@ export class CodingNsWebTerminals extends Service {
|
|
|
254
272
|
return existing.view;
|
|
255
273
|
const workspaceId = this.workspaceIds.get(sessionId);
|
|
256
274
|
const saved = terminalId
|
|
257
|
-
?? (workspaceId === undefined ? undefined :
|
|
275
|
+
?? (workspaceId === undefined ? undefined : readWorkspaceBindingWithMigration(workspaceId, contentId))
|
|
258
276
|
?? readBinding(sessionId, contentId);
|
|
259
277
|
const id = saved ?? crypto.randomUUID();
|
|
278
|
+
debugInfo('codingns4dsh: client terminal view', { sessionId, key, contentId, workspaceId: workspaceId ?? null, savedId: saved ?? null, terminalId: id, bindingSource: terminalId !== undefined ? 'argument' : workspaceId !== undefined && readWorkspaceBindingWithMigration(workspaceId, contentId) !== undefined ? 'workspace-storage' : readBinding(sessionId, contentId) !== undefined ? 'session-storage' : 'new' });
|
|
260
279
|
if (saved === undefined)
|
|
261
280
|
writeBinding(sessionId, contentId, id);
|
|
262
|
-
const view = new CodingNsTerminalView(sessionId, id, this.remote, saved === undefined, shellPath, (resolvedWorkspaceId) => this.rememberWorkspace(sessionId, contentId,
|
|
281
|
+
const view = new CodingNsTerminalView(sessionId, id, this.remote, saved === undefined, shellPath, (resolvedWorkspaceId, currentId) => this.rememberWorkspace(sessionId, contentId, currentId, resolvedWorkspaceId));
|
|
263
282
|
this.views.set(mapKey, { contentId, view });
|
|
264
283
|
return view;
|
|
265
284
|
}
|
|
266
285
|
/** 返回某个 Sidebar 内容已经保存的 Host 终端身份,用于恢复时去重。 */
|
|
267
286
|
boundTerminalId(sessionId, contentId) {
|
|
268
287
|
const workspaceId = this.workspaceIds.get(sessionId);
|
|
269
|
-
return (workspaceId === undefined ? undefined :
|
|
288
|
+
return (workspaceId === undefined ? undefined : readWorkspaceBindingWithMigration(workspaceId, contentId))
|
|
270
289
|
?? readBinding(sessionId, contentId);
|
|
271
290
|
}
|
|
272
291
|
async launchShells(sessionId, signal) {
|
|
@@ -284,13 +303,16 @@ export class CodingNsWebTerminals extends Service {
|
|
|
284
303
|
const id = terminalId ?? record?.view.id ?? this.boundTerminalId(sessionId, contentId);
|
|
285
304
|
if (id === undefined)
|
|
286
305
|
return;
|
|
306
|
+
debugInfo('codingns4dsh: client terminal close request', { sessionId, key, contentId, workspaceId: this.workspaceIds.get(sessionId) ?? null, terminalId: id, hasView: record !== undefined });
|
|
287
307
|
const request = { sessionId, id, title: record?.view.state.getSnapshot().title ?? '终端' };
|
|
288
308
|
this.closeRequests.set(String(id), request);
|
|
289
309
|
persistCloseRequests(this.closeRequests.values());
|
|
290
310
|
deleteBinding(sessionId, contentId);
|
|
291
311
|
const workspaceId = this.workspaceIds.get(sessionId);
|
|
292
|
-
if (workspaceId !== undefined)
|
|
293
|
-
deleteWorkspaceBinding(workspaceId
|
|
312
|
+
if (workspaceId !== undefined) {
|
|
313
|
+
deleteWorkspaceBinding(workspaceId);
|
|
314
|
+
deleteLegacyWorkspaceBinding(workspaceId, contentId);
|
|
315
|
+
}
|
|
294
316
|
this.views.delete(mapKey);
|
|
295
317
|
void this.cleanup(request, record?.view);
|
|
296
318
|
}
|
|
@@ -303,7 +325,9 @@ export class CodingNsWebTerminals extends Service {
|
|
|
303
325
|
const environment = unwrap(await resolveRemote(this.remote).environment(sessionId));
|
|
304
326
|
if (environment.workspaceId !== undefined)
|
|
305
327
|
this.workspaceIds.set(sessionId, environment.workspaceId);
|
|
306
|
-
|
|
328
|
+
const result = unwrap(await resolveRemote(this.remote).list(sessionId));
|
|
329
|
+
debugInfo('codingns4dsh: client terminal recover', { sessionId, workspaceId: environment.workspaceId, terminalIds: result.map((entry) => entry.id) });
|
|
330
|
+
return result;
|
|
307
331
|
})().finally(() => this.recoveries.delete(sessionId));
|
|
308
332
|
this.recoveries.set(sessionId, recovery);
|
|
309
333
|
return recovery;
|
|
@@ -313,6 +337,12 @@ export class CodingNsWebTerminals extends Service {
|
|
|
313
337
|
if (request !== undefined)
|
|
314
338
|
void this.cleanup(request);
|
|
315
339
|
}
|
|
340
|
+
/** 由 Client 的 Remote 注入回调调用,完成启动阶段延迟的关闭请求。 */
|
|
341
|
+
remoteReady() {
|
|
342
|
+
debugInfo('codingns4dsh: client terminal remote ready notification');
|
|
343
|
+
this.remoteReadyStore.set(true);
|
|
344
|
+
void this.flushCleanup();
|
|
345
|
+
}
|
|
316
346
|
async dispose() {
|
|
317
347
|
await Promise.all([...this.views.values()].map((record) => record.view.dispose()));
|
|
318
348
|
this.views.clear();
|
|
@@ -321,23 +351,59 @@ export class CodingNsWebTerminals extends Service {
|
|
|
321
351
|
}
|
|
322
352
|
rememberWorkspace(sessionId, contentId, id, workspaceId) {
|
|
323
353
|
this.workspaceIds.set(sessionId, workspaceId);
|
|
324
|
-
|
|
354
|
+
const existing = readWorkspaceBindingWithMigration(workspaceId, contentId);
|
|
355
|
+
const resolvedId = existing ?? id;
|
|
356
|
+
// 修正首次渲染时已经写入的会话键,避免第二个会话继续携带临时 ID。
|
|
357
|
+
writeBinding(sessionId, contentId, resolvedId);
|
|
358
|
+
// 旧版本的工作区键带 contentId;统一重写成只含 Workspace ID 的新键。
|
|
359
|
+
writeWorkspaceBinding(workspaceId, contentId, resolvedId);
|
|
360
|
+
deleteLegacyWorkspaceBinding(workspaceId, contentId);
|
|
361
|
+
return { id: resolvedId, existing: existing !== undefined };
|
|
325
362
|
}
|
|
326
363
|
async cleanup(request, view) {
|
|
327
364
|
try {
|
|
365
|
+
debugInfo('codingns4dsh: client terminal cleanup begin', { sessionId: request.sessionId, terminalId: request.id, hasView: view !== undefined });
|
|
328
366
|
if (view === undefined)
|
|
329
367
|
unwrap(await resolveRemote(this.remote).close(request.sessionId, request.id));
|
|
330
368
|
else
|
|
331
369
|
await view.close();
|
|
370
|
+
debugInfo('codingns4dsh: client terminal cleanup success', { sessionId: request.sessionId, terminalId: request.id });
|
|
332
371
|
this.closeRequests.delete(String(request.id));
|
|
333
372
|
persistCloseRequests(this.closeRequests.values());
|
|
334
373
|
this.closeFailureStore.set(this.closeFailureStore.getSnapshot().filter((item) => item.id !== request.id));
|
|
335
374
|
}
|
|
336
375
|
catch (error) {
|
|
376
|
+
if (isTerminalRemoteUnavailable(error)) {
|
|
377
|
+
debugInfo('codingns4dsh: client terminal cleanup deferred', { sessionId: request.sessionId, terminalId: request.id });
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
debugWarn('codingns4dsh: client terminal cleanup failed', { sessionId: request.sessionId, terminalId: request.id, error: errorMessage(error) });
|
|
337
381
|
const failure = { ...request, message: errorMessage(error) };
|
|
338
382
|
this.closeFailureStore.set([...this.closeFailureStore.getSnapshot().filter((item) => item.id !== request.id), failure]);
|
|
339
383
|
}
|
|
340
384
|
}
|
|
385
|
+
async flushCleanup() {
|
|
386
|
+
if (this.cleanupQueued || this.closeRequests.size === 0)
|
|
387
|
+
return;
|
|
388
|
+
try {
|
|
389
|
+
resolveRemote(this.remote);
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
if (isTerminalRemoteUnavailable(error)) {
|
|
393
|
+
debugInfo('codingns4dsh: client terminal cleanup waiting for remote');
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
throw error;
|
|
397
|
+
}
|
|
398
|
+
this.cleanupQueued = true;
|
|
399
|
+
try {
|
|
400
|
+
for (const request of [...this.closeRequests.values()])
|
|
401
|
+
await this.cleanup(request);
|
|
402
|
+
}
|
|
403
|
+
finally {
|
|
404
|
+
this.cleanupQueued = false;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
341
407
|
}
|
|
342
408
|
function unwrap(result) {
|
|
343
409
|
if (typeof result !== 'object' || result === null || !('ok' in result)) {
|
|
@@ -359,10 +425,19 @@ function sameResize(left, right) {
|
|
|
359
425
|
function errorMessage(error) {
|
|
360
426
|
return error instanceof Error ? error.message : String(error);
|
|
361
427
|
}
|
|
428
|
+
function isTerminalRemoteUnavailable(error) {
|
|
429
|
+
return errorMessage(error) === '终端服务尚未就绪,请稍后重试';
|
|
430
|
+
}
|
|
362
431
|
function bindingKey(sessionId, contentId) {
|
|
363
432
|
return `${BINDING_PREFIX}${JSON.stringify([sessionId, contentId])}`;
|
|
364
433
|
}
|
|
365
|
-
function workspaceBindingKey(workspaceId
|
|
434
|
+
function workspaceBindingKey(workspaceId) {
|
|
435
|
+
// 工作区模式的终端身份不能带 session、tab 或 contentId;这些值在每个
|
|
436
|
+
// DSH 会话中都会重新生成,带进去就会把所谓的工作区绑定重新拆回会话绑定。
|
|
437
|
+
return `${BINDING_PREFIX}${JSON.stringify(['workspace', workspaceId])}`;
|
|
438
|
+
}
|
|
439
|
+
/** 0.1.7 早期构建把 contentId 写进了工作区键,读取它只用于一次迁移。 */
|
|
440
|
+
function legacyWorkspaceBindingKey(workspaceId, contentId) {
|
|
366
441
|
return `${BINDING_PREFIX}${JSON.stringify(['workspace', workspaceId, contentId])}`;
|
|
367
442
|
}
|
|
368
443
|
function readBinding(sessionId, contentId) {
|
|
@@ -372,16 +447,29 @@ function readBinding(sessionId, contentId) {
|
|
|
372
447
|
function writeBinding(sessionId, contentId, id) {
|
|
373
448
|
writeString(bindingKey(sessionId, contentId), String(id));
|
|
374
449
|
}
|
|
375
|
-
function readWorkspaceBinding(workspaceId,
|
|
376
|
-
const value = readString(workspaceBindingKey(workspaceId
|
|
450
|
+
function readWorkspaceBinding(workspaceId, _contentId) {
|
|
451
|
+
const value = readString(workspaceBindingKey(workspaceId));
|
|
377
452
|
return value !== null && /^[\w-]{1,128}$/u.test(value) ? value : undefined;
|
|
378
453
|
}
|
|
379
|
-
function
|
|
380
|
-
|
|
454
|
+
function readWorkspaceBindingWithMigration(workspaceId, contentId) {
|
|
455
|
+
const current = readWorkspaceBinding(workspaceId);
|
|
456
|
+
if (current !== undefined)
|
|
457
|
+
return current;
|
|
458
|
+
const legacy = readString(legacyWorkspaceBindingKey(workspaceId, contentId));
|
|
459
|
+
return legacy !== null && /^[\w-]{1,128}$/u.test(legacy) ? legacy : undefined;
|
|
460
|
+
}
|
|
461
|
+
function writeWorkspaceBinding(workspaceId, _contentId, id) {
|
|
462
|
+
writeString(workspaceBindingKey(workspaceId), String(id));
|
|
463
|
+
}
|
|
464
|
+
function deleteWorkspaceBinding(workspaceId, _contentId) {
|
|
465
|
+
try {
|
|
466
|
+
localStorage.removeItem(workspaceBindingKey(workspaceId));
|
|
467
|
+
}
|
|
468
|
+
catch { /* 浏览器禁用存储时仅失去跨刷新绑定。 */ }
|
|
381
469
|
}
|
|
382
|
-
function
|
|
470
|
+
function deleteLegacyWorkspaceBinding(workspaceId, contentId) {
|
|
383
471
|
try {
|
|
384
|
-
localStorage.removeItem(
|
|
472
|
+
localStorage.removeItem(legacyWorkspaceBindingKey(workspaceId, contentId));
|
|
385
473
|
}
|
|
386
474
|
catch { /* 浏览器禁用存储时仅失去跨刷新绑定。 */ }
|
|
387
475
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { CodingNsWebTerminals, WebTerminalInfo } from './model.js';
|
|
2
|
+
/** Sidebar 记录的最小结构,避免恢复逻辑绑定某个 DSH 版本的类型包。 */
|
|
3
|
+
export interface TerminalSidebarTab {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly kind: string;
|
|
6
|
+
}
|
|
7
|
+
interface TerminalNavigationSnapshot {
|
|
8
|
+
readonly params?: unknown;
|
|
9
|
+
}
|
|
10
|
+
interface TerminalOccurrence {
|
|
11
|
+
readonly navigation: {
|
|
12
|
+
getSnapshot(): TerminalNavigationSnapshot;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export interface TerminalSidebarMountedSource {
|
|
16
|
+
readonly getSnapshot: () => string | undefined;
|
|
17
|
+
readonly subscribe: (listener: () => void) => () => void;
|
|
18
|
+
}
|
|
19
|
+
export interface TerminalSidebarRecoveryPort {
|
|
20
|
+
readonly tabsIn?: (sessionId: string) => readonly TerminalSidebarTab[];
|
|
21
|
+
readonly openTabs?: {
|
|
22
|
+
getSnapshot(): readonly {
|
|
23
|
+
readonly sessionId: string;
|
|
24
|
+
readonly tabId: string;
|
|
25
|
+
readonly kind: string;
|
|
26
|
+
}[];
|
|
27
|
+
subscribe?: (listener: () => void) => () => void;
|
|
28
|
+
};
|
|
29
|
+
readonly mounted?: TerminalSidebarMountedSource;
|
|
30
|
+
readonly openTabIn?: (sessionId: string, kind: string, options?: {
|
|
31
|
+
readonly params?: unknown;
|
|
32
|
+
}) => void;
|
|
33
|
+
readonly tabDomain?: {
|
|
34
|
+
occurrence(sessionId: string, tab: {
|
|
35
|
+
readonly id: string;
|
|
36
|
+
}): TerminalOccurrence;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export interface TerminalSessionRecovery {
|
|
40
|
+
ensure(sessionId: string): Promise<readonly WebTerminalInfo[]>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 将 Host 已存在的终端补回当前 DSH 会话的 Sidebar。
|
|
44
|
+
*
|
|
45
|
+
* DSH 的布局和标签身份以 sessionId 为存储边界,所以工作区共享只解决
|
|
46
|
+
* Host 终端身份,不能替新会话创建 Sidebar 标签。这里把两个生命周期接起来。
|
|
47
|
+
*/
|
|
48
|
+
export declare function createTerminalSessionRecovery(webTerminals: Pick<CodingNsWebTerminals, 'recover'>, sidebar: TerminalSidebarRecoveryPort, terminalKind: string): TerminalSessionRecovery;
|
|
49
|
+
export {};
|
|
50
|
+
//# sourceMappingURL=recovery.d.ts.map
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { debugInfo, debugWarn } from '../../shared/debug.js';
|
|
2
|
+
/**
|
|
3
|
+
* 将 Host 已存在的终端补回当前 DSH 会话的 Sidebar。
|
|
4
|
+
*
|
|
5
|
+
* DSH 的布局和标签身份以 sessionId 为存储边界,所以工作区共享只解决
|
|
6
|
+
* Host 终端身份,不能替新会话创建 Sidebar 标签。这里把两个生命周期接起来。
|
|
7
|
+
*/
|
|
8
|
+
export function createTerminalSessionRecovery(webTerminals, sidebar, terminalKind) {
|
|
9
|
+
const pending = new Map();
|
|
10
|
+
const ensure = (sessionId) => {
|
|
11
|
+
const normalized = sessionId.trim();
|
|
12
|
+
if (normalized === '')
|
|
13
|
+
return Promise.resolve([]);
|
|
14
|
+
const current = pending.get(normalized);
|
|
15
|
+
if (current !== undefined)
|
|
16
|
+
return current;
|
|
17
|
+
debugInfo('codingns4dsh: client terminal sidebar recovery begin', { sessionId: normalized });
|
|
18
|
+
const task = recoverAndOpen(normalized)
|
|
19
|
+
.catch((cause) => {
|
|
20
|
+
debugWarn('codingns4dsh: client terminal sidebar recovery failed', { sessionId: normalized, error: messageOf(cause) });
|
|
21
|
+
throw cause;
|
|
22
|
+
})
|
|
23
|
+
.finally(() => pending.delete(normalized));
|
|
24
|
+
pending.set(normalized, task);
|
|
25
|
+
return task;
|
|
26
|
+
};
|
|
27
|
+
const recoverAndOpen = async (sessionId) => {
|
|
28
|
+
const terminals = await webTerminals.recover(sessionId);
|
|
29
|
+
const existing = readTabs(sidebar, sessionId);
|
|
30
|
+
debugInfo('codingns4dsh: client terminal sidebar recovery listed', {
|
|
31
|
+
sessionId,
|
|
32
|
+
terminalIds: terminals.map((terminal) => terminal.id),
|
|
33
|
+
existingTabs: existing.map((tab) => ({ id: tab.id, kind: tab.kind })),
|
|
34
|
+
});
|
|
35
|
+
const opened = new Set();
|
|
36
|
+
for (const tab of existing) {
|
|
37
|
+
if (tab.kind !== terminalKind)
|
|
38
|
+
continue;
|
|
39
|
+
const id = terminalIdOf(sidebar, sessionId, tab);
|
|
40
|
+
if (id === undefined) {
|
|
41
|
+
// 旧版标签没有导航参数时仍代表一个有效终端,避免恢复时重复创建。
|
|
42
|
+
for (const terminal of terminals)
|
|
43
|
+
opened.add(terminal.id);
|
|
44
|
+
debugInfo('codingns4dsh: client terminal sidebar recovery legacy tab', { sessionId, tabId: tab.id });
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
opened.add(id);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
for (const terminal of terminals) {
|
|
51
|
+
if (opened.has(terminal.id)) {
|
|
52
|
+
debugInfo('codingns4dsh: client terminal sidebar recovery skip existing', { sessionId, terminalId: terminal.id });
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (typeof sidebar.openTabIn !== 'function') {
|
|
56
|
+
debugWarn('codingns4dsh: client terminal sidebar recovery unavailable', { sessionId, terminalId: terminal.id, reason: 'openTabIn-undefined' });
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
sidebar.openTabIn(sessionId, terminalKind, { params: { terminalId: terminal.id } });
|
|
60
|
+
debugInfo('codingns4dsh: client terminal sidebar recovery opened', { sessionId, terminalId: terminal.id });
|
|
61
|
+
opened.add(terminal.id);
|
|
62
|
+
}
|
|
63
|
+
return terminals;
|
|
64
|
+
};
|
|
65
|
+
return { ensure };
|
|
66
|
+
}
|
|
67
|
+
function readTabs(sidebar, sessionId) {
|
|
68
|
+
if (typeof sidebar.tabsIn === 'function') {
|
|
69
|
+
try {
|
|
70
|
+
return sidebar.tabsIn(sessionId);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return readOpenTabs(sidebar)
|
|
77
|
+
.filter((tab) => tab.sessionId === sessionId)
|
|
78
|
+
.map((tab) => ({ id: tab.tabId, kind: tab.kind }));
|
|
79
|
+
}
|
|
80
|
+
function readOpenTabs(sidebar) {
|
|
81
|
+
try {
|
|
82
|
+
return sidebar.openTabs?.getSnapshot() ?? [];
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function terminalIdOf(sidebar, sessionId, tab) {
|
|
89
|
+
try {
|
|
90
|
+
const params = sidebar.tabDomain?.occurrence(sessionId, tab).navigation.getSnapshot().params;
|
|
91
|
+
if (typeof params !== 'object' || params === null)
|
|
92
|
+
return undefined;
|
|
93
|
+
const terminalId = params.terminalId;
|
|
94
|
+
return typeof terminalId === 'string' && terminalId.trim() !== '' ? terminalId : undefined;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function messageOf(value) { return value instanceof Error ? value.message : String(value); }
|
|
101
|
+
//# sourceMappingURL=recovery.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
-
import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client';
|
|
3
2
|
import type { CodingNsSettings } from '../../shared/contracts/config.js';
|
|
4
3
|
import { CodingNsWebTerminals, type WebTerminalId } from './model.js';
|
|
4
|
+
import type { CodingNsSettingsStore } from '../../dsh-capabilities/settings-store.js';
|
|
5
5
|
export declare const TERMINAL_PROVIDER_ID = "codingns4dsh/terminal";
|
|
6
6
|
export declare const TERMINAL_KIND = "terminal";
|
|
7
7
|
interface TerminalParams {
|
|
@@ -9,7 +9,7 @@ interface TerminalParams {
|
|
|
9
9
|
readonly shellPath?: string;
|
|
10
10
|
}
|
|
11
11
|
/** 注册终端类型及其所有公开 Sidebar Slot。 */
|
|
12
|
-
export declare function registerCodingNsTerminalUi(ctx: Context, webTerminals: CodingNsWebTerminals, settings:
|
|
12
|
+
export declare function registerCodingNsTerminalUi(ctx: Context, webTerminals: CodingNsWebTerminals, settings: CodingNsSettingsStore<CodingNsSettings>): () => void;
|
|
13
13
|
declare module '@deepseek-ai/dsh-client-ui-sidebar-right/client' {
|
|
14
14
|
interface SidebarRightTabParamsMap {
|
|
15
15
|
terminal: TerminalParams;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createElement, Fragment, useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore, } from 'react';
|
|
2
2
|
import { Button, IconChevronDownOutline14, Menu, } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
3
|
+
import { resolveChevronDownIcon } from '../../dsh-capabilities/client/primitives-adapter.js';
|
|
3
4
|
import { CodingNsWebTerminals } from './model.js';
|
|
5
|
+
import { createTerminalSessionRecovery } from './recovery.js';
|
|
4
6
|
import { installTerminalStyles, terminalClass } from './styles.js';
|
|
5
7
|
import { CodingNsXtermView } from './xterm-view.js';
|
|
6
8
|
import { codingNsTranslator, useCodingNsTranslator } from '../locale.js';
|
|
@@ -18,8 +20,9 @@ export function registerCodingNsTerminalUi(ctx, webTerminals, settings) {
|
|
|
18
20
|
const registerCloseHandler = sidebarRight.registerCloseHandler;
|
|
19
21
|
// alpha2 提供多 Tab 元数据和关闭 API;rc3 没有这些字段,不能直接调用。
|
|
20
22
|
const legacyCloseFallback = typeof registerCloseHandler !== 'function';
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
+
const recoverySidebar = sidebarRight;
|
|
24
|
+
const supportsMultiple = readSidebarOpenTabs(recoverySidebar) !== undefined;
|
|
25
|
+
const recovery = createTerminalSessionRecovery(webTerminals, recoverySidebar, TERMINAL_KIND);
|
|
23
26
|
disposers.push(installTerminalStyles());
|
|
24
27
|
const tabDefinition = {
|
|
25
28
|
id: TERMINAL_PROVIDER_ID,
|
|
@@ -44,14 +47,13 @@ export function registerCodingNsTerminalUi(ctx, webTerminals, settings) {
|
|
|
44
47
|
name: 'sidebar.right.pane.tab.title', key: TERMINAL_PROVIDER_ID,
|
|
45
48
|
inject: () => ({ webTerminals, locale: ctx.locale }),
|
|
46
49
|
}, TerminalTitle)));
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}, TerminalGuide)));
|
|
50
|
+
disposers.push(ctx.slots.inject('sidebar.right.tab.guide.entry', () => ctx.slots.register({
|
|
51
|
+
name: 'sidebar.right.tab.guide.entry', key: TERMINAL_PROVIDER_ID,
|
|
52
|
+
inject: () => ({ webTerminals, recoverSession: recovery.ensure, locale: ctx.locale }),
|
|
53
|
+
}, TerminalGuide)));
|
|
52
54
|
disposers.push(ctx.slots.inject('shell.overlay', () => ctx.slots.register({
|
|
53
55
|
name: 'shell.overlay', id: 'codingns4dsh-terminal-cleanup', order: 1000,
|
|
54
|
-
inject: () => ({ webTerminals, locale: ctx.locale }),
|
|
56
|
+
inject: () => ({ webTerminals, locale: ctx.locale, sidebarRight: recoverySidebar, recoverSession: recovery.ensure }),
|
|
55
57
|
}, TerminalCleanup)));
|
|
56
58
|
if (typeof registerCloseHandler === 'function')
|
|
57
59
|
disposers.push(registerCloseHandler.call(ctx.sidebarRight, TERMINAL_KIND, (sessionId, tab) => {
|
|
@@ -141,12 +143,13 @@ function TerminalTitle({ sessionId, useTabInfo, webTerminals, locale }) {
|
|
|
141
143
|
title: t('terminal.rename'),
|
|
142
144
|
}, state.title));
|
|
143
145
|
}
|
|
144
|
-
function TerminalGuide({ sessionId, useTabInfo, webTerminals, title, description, locale }) {
|
|
146
|
+
function TerminalGuide({ sessionId, useTabInfo, webTerminals, recoverSession, title, description, locale }) {
|
|
145
147
|
const t = useCodingNsTranslator(locale);
|
|
146
148
|
const info = useTabInfo();
|
|
147
149
|
const [open, setOpen] = useState(false);
|
|
148
150
|
const [attempt, setAttempt] = useState(0);
|
|
149
151
|
const [state, setState] = useState({ phase: 'loading' });
|
|
152
|
+
useEffect(() => { void recoverSession(String(sessionId)).catch(() => undefined); }, [recoverSession, sessionId]);
|
|
150
153
|
useEffect(() => {
|
|
151
154
|
if (!open)
|
|
152
155
|
return;
|
|
@@ -200,7 +203,7 @@ function TerminalGuide({ sessionId, useTabInfo, webTerminals, title, description
|
|
|
200
203
|
'aria-haspopup': 'menu',
|
|
201
204
|
'aria-expanded': open,
|
|
202
205
|
onClick: () => { setState({ phase: 'loading' }); setOpen((value) => !value); },
|
|
203
|
-
}, createElement(
|
|
206
|
+
}, createElement(resolveChevronDownIcon())),
|
|
204
207
|
}));
|
|
205
208
|
}
|
|
206
209
|
function shellMenuItems(state, t) {
|
|
@@ -215,8 +218,23 @@ function shellMenuItems(state, t) {
|
|
|
215
218
|
return [{ id: 'empty', label: t('terminal.noShell'), disabled: true }];
|
|
216
219
|
return state.shells.map((shell) => ({ id: shell.path, label: shell.name }));
|
|
217
220
|
}
|
|
218
|
-
function TerminalCleanup({ webTerminals, locale }) {
|
|
221
|
+
function TerminalCleanup({ webTerminals, locale, sidebarRight, recoverSession }) {
|
|
219
222
|
const t = useCodingNsTranslator(locale);
|
|
223
|
+
const openTabs = readSidebarOpenTabs(sidebarRight);
|
|
224
|
+
const openTabSnapshot = useSyncExternalStore(openTabs?.subscribe ?? noSubscribe, openTabs?.getSnapshot ?? noOpenTabs, openTabs?.getSnapshot ?? noOpenTabs);
|
|
225
|
+
const remoteReady = useSyncExternalStore(webTerminals.remoteReadyState.subscribe.bind(webTerminals.remoteReadyState), webTerminals.remoteReadyState.getSnapshot.bind(webTerminals.remoteReadyState), webTerminals.remoteReadyState.getSnapshot.bind(webTerminals.remoteReadyState));
|
|
226
|
+
const mounted = readSidebarMounted(sidebarRight);
|
|
227
|
+
const mountedSessionId = useSyncExternalStore(mounted?.subscribe ?? noSubscribe, mounted?.getSnapshot ?? noMountedSession, mounted?.getSnapshot ?? noMountedSession);
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
// 0.1.7 才提供 mounted 会话 observable;旧版本保留原有 Guide 挂载路径。
|
|
230
|
+
if (!remoteReady || mounted === undefined)
|
|
231
|
+
return;
|
|
232
|
+
const sessionIds = new Set(openTabSnapshot.map((tab) => String(tab.sessionId).trim()).filter(Boolean));
|
|
233
|
+
if (mountedSessionId !== undefined)
|
|
234
|
+
sessionIds.add(String(mountedSessionId).trim());
|
|
235
|
+
for (const sessionId of sessionIds)
|
|
236
|
+
void recoverSession(sessionId).catch(() => undefined);
|
|
237
|
+
}, [openTabSnapshot, recoverSession, remoteReady, mountedSessionId]);
|
|
220
238
|
const failures = useSyncExternalStore(webTerminals.closeFailures.subscribe.bind(webTerminals.closeFailures), webTerminals.closeFailures.getSnapshot.bind(webTerminals.closeFailures));
|
|
221
239
|
if (failures.length === 0)
|
|
222
240
|
return null;
|
|
@@ -226,6 +244,26 @@ function TerminalCleanup({ webTerminals, locale }) {
|
|
|
226
244
|
role: 'alert',
|
|
227
245
|
}, createElement('span', undefined, t('terminal.cleanupFailed', { title: failure.title, message: failure.message })), createElement('button', { type: 'button', onClick: () => webTerminals.retryClose(failure.id) }, t('terminal.retry')))));
|
|
228
246
|
}
|
|
247
|
+
function readSidebarOpenTabs(sidebarRight) {
|
|
248
|
+
try {
|
|
249
|
+
return sidebarRight.openTabs;
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function readSidebarMounted(sidebarRight) {
|
|
256
|
+
try {
|
|
257
|
+
return sidebarRight.mounted;
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function noMountedSession() { return undefined; }
|
|
264
|
+
const EMPTY_OPEN_TABS = [];
|
|
265
|
+
function noOpenTabs() { return EMPTY_OPEN_TABS; }
|
|
266
|
+
function noSubscribe() { return () => undefined; }
|
|
229
267
|
function terminalParams(info) {
|
|
230
268
|
const params = info.tab.navigation.params;
|
|
231
269
|
return typeof params === 'object' && params !== null ? params : {};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { ReactElement } from 'react';
|
|
2
|
-
import type {
|
|
2
|
+
import type { CodingNsSettingsStore } from '../../dsh-capabilities/settings-store.js';
|
|
3
3
|
import { type CodingNsSettings } from '../../shared/contracts/config.js';
|
|
4
4
|
import type { CodingNsTerminalView } from './model.js';
|
|
5
5
|
export interface CodingNsXtermViewProps {
|
|
6
6
|
readonly view: CodingNsTerminalView;
|
|
7
|
-
readonly settings:
|
|
7
|
+
readonly settings: CodingNsSettingsStore<CodingNsSettings>;
|
|
8
8
|
readonly themeRevision: number;
|
|
9
9
|
readonly onNewTerminal: () => void;
|
|
10
10
|
}
|
|
@@ -3,6 +3,7 @@ import { Button, IconPlusOutline16 } from '@deepseek-ai/dsh-client-ui-primitives
|
|
|
3
3
|
import { FitAddon } from '@xterm/addon-fit';
|
|
4
4
|
import { Terminal } from '@xterm/xterm';
|
|
5
5
|
import xtermCss from '@xterm/xterm/css/xterm.css';
|
|
6
|
+
import { resolvePlusIcon } from '../../dsh-capabilities/client/primitives-adapter.js';
|
|
6
7
|
import { DEFAULT_TERMINAL_ENHANCEMENT_SETTINGS, } from '../../shared/contracts/config.js';
|
|
7
8
|
import { terminalClass } from './styles.js';
|
|
8
9
|
/** 使用与 DSH 内置终端相同的布局、状态条和 xterm 默认参数。 */
|
|
@@ -116,7 +117,7 @@ function TerminalStatus({ state, view, onNewTerminal, }) {
|
|
|
116
117
|
}, state.phase === 'disconnected' ? '重新连接' : '重试') : null, ended ? createElement(Button, {
|
|
117
118
|
variant: 'primary',
|
|
118
119
|
size: 'sm',
|
|
119
|
-
icon: createElement(
|
|
120
|
+
icon: createElement(resolvePlusIcon()),
|
|
120
121
|
onClick: onNewTerminal,
|
|
121
122
|
}, '新建终端') : null);
|
|
122
123
|
}
|
|
@@ -14,7 +14,7 @@ export declare const dshThemeColor: {
|
|
|
14
14
|
readonly inputBackground: "var(--dsw-specific-input-major, Canvas)";
|
|
15
15
|
readonly surfaceSubtle: "var(--dsw-alias-bg-secondary, rgba(127, 127, 127, 0.06))";
|
|
16
16
|
readonly buttonBackground: "var(--dsw-alias-button-elevated-fill, transparent)";
|
|
17
|
-
readonly menuBackground: "var(--dsw-specific-menu, Canvas)";
|
|
17
|
+
readonly menuBackground: "var(--dsw-alias-bg-layer-3, var(--dsw-alias-bg-l1, var(--dsw-specific-menu, Canvas)))";
|
|
18
18
|
readonly pageBackground: "var(--dsw-alias-bg-primary, Canvas)";
|
|
19
19
|
readonly overlay: "var(--dsw-alias-bg-mask-1, rgba(0, 0, 0, 0.45))";
|
|
20
20
|
readonly accent: "var(--dsw-alias-button-info-fill, #1677ff)";
|
|
@@ -51,6 +51,11 @@ export declare const dshSettingsRowStyle: CSSProperties;
|
|
|
51
51
|
export declare const dshSettingsListRowStyle: CSSProperties;
|
|
52
52
|
export declare const dshSettingsHelpStyle: CSSProperties;
|
|
53
53
|
export declare const dshSettingsNoteStyle: CSSProperties;
|
|
54
|
+
/** 终端外观设置的紧凑双列布局。 */
|
|
55
|
+
export declare const dshSettingsGridStyle: CSSProperties;
|
|
56
|
+
export declare const dshSettingsSectionHeaderStyle: CSSProperties;
|
|
57
|
+
/** 设置写入结果的顶部浮动提示。 */
|
|
58
|
+
export declare const dshSettingsToastStyle: CSSProperties;
|
|
54
59
|
/** 模态框和弹出菜单必须成对设置前景色与背景色。 */
|
|
55
60
|
export declare const dshPopupSurfaceStyle: CSSProperties;
|
|
56
61
|
//# sourceMappingURL=theme.d.ts.map
|