@jingyi0605/codingns4dsh 0.1.1 → 0.1.7-rc.2
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 +4 -4
- package/README.md +14 -5
- package/data/build/dist/client/account-bar.js +57 -3
- package/data/build/dist/client/bundle.js +36245 -267
- package/data/build/dist/client/cli-slots.js +60 -18
- package/data/build/dist/client/dsh-h5-bootstrap.d.ts +2 -0
- package/data/build/dist/client/dsh-h5-bootstrap.js +79 -13
- package/data/build/dist/client/features/file-management-panel.d.ts +5 -0
- package/data/build/dist/client/features/file-management-panel.js +48 -0
- package/data/build/dist/client/features/file-management.d.ts +4 -0
- package/data/build/dist/client/features/file-management.js +50 -0
- package/data/build/dist/client/features/index.d.ts +10 -1
- package/data/build/dist/client/features/index.js +12 -1
- package/data/build/dist/client/features/login-protection-panel.js +1 -1
- package/data/build/dist/client/features/login-protection-session.d.ts +2 -0
- package/data/build/dist/client/features/login-protection-session.js +18 -0
- package/data/build/dist/client/features/peer-host.d.ts +4 -0
- package/data/build/dist/client/features/peer-host.js +89 -0
- package/data/build/dist/client/features/reverse-proxy-panel.js +23 -7
- package/data/build/dist/client/features/reverse-proxy.d.ts +1 -2
- package/data/build/dist/client/features/reverse-proxy.js +75 -5
- package/data/build/dist/client/features/types.d.ts +9 -0
- package/data/build/dist/client/features/workspace-session-enhancement-panel.js +10 -0
- package/data/build/dist/client/features/workspace-session-enhancement.js +18 -1
- package/data/build/dist/client/file-management-dom.d.ts +12 -0
- package/data/build/dist/client/file-management-dom.js +527 -0
- package/data/build/dist/client/git-management.js +24 -5
- package/data/build/dist/client/host-navigation.d.ts +29 -0
- package/data/build/dist/client/host-navigation.js +29 -0
- package/data/build/dist/client/host-router.d.ts +3 -0
- package/data/build/dist/client/host-router.js +2 -0
- package/data/build/dist/client/index.d.ts +6 -1
- package/data/build/dist/client/index.js +30 -10
- package/data/build/dist/client/locale.js +36 -7
- package/data/build/dist/client/peer-host-connection-button.d.ts +13 -0
- package/data/build/dist/client/peer-host-connection-button.js +69 -0
- package/data/build/dist/client/peer-host-management-api.d.ts +40 -0
- package/data/build/dist/client/peer-host-management-api.js +33 -0
- package/data/build/dist/client/peer-host-management-panel.d.ts +13 -0
- package/data/build/dist/client/peer-host-management-panel.js +181 -0
- package/data/build/dist/client/peer-host-native-session-ui.d.ts +60 -0
- package/data/build/dist/client/peer-host-native-session-ui.js +372 -0
- package/data/build/dist/client/peer-host-scoped-client.d.ts +67 -0
- package/data/build/dist/client/peer-host-scoped-client.js +316 -0
- package/data/build/dist/client/peer-host-session-controller.d.ts +30 -0
- package/data/build/dist/client/peer-host-session-controller.js +95 -0
- package/data/build/dist/client/quick-phrase-slot.js +14 -0
- package/data/build/dist/client/remote-web-context.js +26 -4
- package/data/build/dist/client/session-changed-files-view.d.ts +15 -0
- package/data/build/dist/client/session-changed-files-view.js +337 -0
- package/data/build/dist/client/settings-bridge.d.ts +14 -4
- package/data/build/dist/client/settings-bridge.js +59 -12
- package/data/build/dist/client/settings-section.js +17 -4
- package/data/build/dist/client/subscription-slot.js +21 -8
- package/data/build/dist/client/workspace-session-rightbar-dom.d.ts +28 -0
- package/data/build/dist/client/workspace-session-rightbar-dom.js +291 -0
- package/data/build/dist/dsh-capabilities/client/config-forms-adapter.js +25 -33
- package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.d.ts +7 -1
- package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.js +34 -11
- package/data/build/dist/dsh-capabilities/matrix.js +9 -0
- package/data/build/dist/dsh-capabilities/routes.js +35 -0
- package/data/build/dist/dsh-capabilities/settings-store.d.ts +12 -0
- package/data/build/dist/dsh-capabilities/settings-store.js +34 -0
- package/data/build/dist/dsh-capabilities/types.d.ts +1 -1
- package/data/build/dist/features/host-router.d.ts +26 -0
- package/data/build/dist/features/host-router.js +110 -0
- package/data/build/dist/features/registry.js +3 -0
- package/data/build/dist/host/cli-adapters/codex-driver.d.ts +13 -1
- package/data/build/dist/host/cli-adapters/codex-driver.js +528 -72
- package/data/build/dist/host/cli-adapters/command-code-driver.d.ts +21 -2
- package/data/build/dist/host/cli-adapters/command-code-driver.js +408 -105
- package/data/build/dist/host/cli-adapters/driver.d.ts +2 -0
- package/data/build/dist/host/cli-adapters/dsh-message-projector.d.ts +2 -0
- package/data/build/dist/host/cli-adapters/dsh-message-projector.js +47 -28
- package/data/build/dist/host/cli-adapters/dsh-tool-history.js +37 -0
- package/data/build/dist/host/cli-adapters/feature.js +94 -10
- package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts +6 -0
- package/data/build/dist/host/cli-adapters/json-rpc-process.js +14 -3
- package/data/build/dist/host/cli-adapters/kimi-driver.js +3 -3
- package/data/build/dist/host/cli-adapters/model-catalog.d.ts +6 -0
- package/data/build/dist/host/cli-adapters/model-catalog.js +8 -0
- package/data/build/dist/host/cli-adapters/opencode-driver.js +14 -2
- package/data/build/dist/host/cli-adapters/pi-driver.js +2 -2
- package/data/build/dist/host/cli-adapters/process-utils.d.ts +8 -0
- package/data/build/dist/host/cli-adapters/process-utils.js +48 -0
- package/data/build/dist/host/cli-adapters/registry.d.ts +6 -1
- package/data/build/dist/host/cli-adapters/registry.js +41 -7
- package/data/build/dist/host/cli-adapters/rpc-driver-utils.js +23 -4
- package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts +3 -0
- package/data/build/dist/host/cli-adapters/standard-stream-driver.js +36 -15
- package/data/build/dist/host/dsh-device-runtime.d.ts +2 -0
- package/data/build/dist/host/dsh-device-runtime.js +55 -29
- package/data/build/dist/host/features/auth.js +10 -2
- package/data/build/dist/host/features/file-management.d.ts +5 -0
- package/data/build/dist/host/features/file-management.js +267 -0
- package/data/build/dist/host/features/git-management.js +24 -2
- package/data/build/dist/host/features/index.d.ts +2 -0
- package/data/build/dist/host/features/index.js +6 -0
- package/data/build/dist/host/features/peer-host.d.ts +22 -0
- package/data/build/dist/host/features/peer-host.js +322 -0
- package/data/build/dist/host/features/types.d.ts +4 -0
- package/data/build/dist/host/host-router.d.ts +3 -0
- package/data/build/dist/host/host-router.js +2 -0
- package/data/build/dist/host/index-injection.d.ts +17 -0
- package/data/build/dist/host/index-injection.js +24 -0
- package/data/build/dist/host/index.d.ts +4 -1
- package/data/build/dist/host/index.js +41 -6
- package/data/build/dist/host/lan-access-dsh.d.ts +34 -1
- package/data/build/dist/host/lan-access-dsh.js +145 -26
- package/data/build/dist/host/modules/peer-host/host-api-proxy-service.d.ts +52 -0
- package/data/build/dist/host/modules/peer-host/host-api-proxy-service.js +166 -0
- package/data/build/dist/host/modules/peer-host/host-ws-connector.d.ts +31 -0
- package/data/build/dist/host/modules/peer-host/host-ws-connector.js +108 -0
- package/data/build/dist/host/modules/peer-host/host-ws-proxy-service.d.ts +26 -0
- package/data/build/dist/host/modules/peer-host/host-ws-proxy-service.js +159 -0
- package/data/build/dist/host/modules/peer-host/peer-host-aggregate-service.d.ts +50 -0
- package/data/build/dist/host/modules/peer-host/peer-host-aggregate-service.js +97 -0
- package/data/build/dist/host/modules/peer-host/peer-host-diagnostics.d.ts +19 -0
- package/data/build/dist/host/modules/peer-host/peer-host-diagnostics.js +54 -0
- package/data/build/dist/host/modules/peer-host/peer-host-handshake.d.ts +44 -0
- package/data/build/dist/host/modules/peer-host/peer-host-handshake.js +133 -0
- package/data/build/dist/host/modules/peer-host/peer-host-relay.d.ts +65 -0
- package/data/build/dist/host/modules/peer-host/peer-host-relay.js +196 -0
- package/data/build/dist/host/modules/peer-host/peer-host-session.d.ts +42 -0
- package/data/build/dist/host/modules/peer-host/peer-host-session.js +152 -0
- package/data/build/dist/host/modules/peer-host/peer-host-store.d.ts +92 -0
- package/data/build/dist/host/modules/peer-host/peer-host-store.js +323 -0
- package/data/build/dist/host/modules/peer-host/peer-host-ws-gateway.d.ts +34 -0
- package/data/build/dist/host/modules/peer-host/peer-host-ws-gateway.js +156 -0
- package/data/build/dist/host/native-session-bridge.d.ts +18 -2
- package/data/build/dist/host/native-session-bridge.js +232 -8
- package/data/build/dist/host/remote-web-runtime.js +26 -1
- package/data/build/dist/host/rpc.js +8 -3
- package/data/build/dist/host/session-changes.d.ts +5 -0
- package/data/build/dist/host/session-changes.js +163 -0
- package/data/build/dist/host/settings.js +6 -0
- package/data/build/dist/index.d.ts +1 -1
- package/data/build/dist/index.js +1 -1
- package/data/build/dist/shared/contracts/cli-adapter.d.ts +23 -2
- package/data/build/dist/shared/contracts/config.d.ts +15 -0
- package/data/build/dist/shared/contracts/config.js +10 -0
- package/data/build/dist/shared/contracts/dsh-device.d.ts +2 -0
- package/data/build/dist/shared/contracts/errors.d.ts +14 -0
- package/data/build/dist/shared/contracts/errors.js +15 -0
- package/data/build/dist/shared/contracts/feature.d.ts +2 -0
- package/data/build/dist/shared/contracts/file-management.d.ts +6 -0
- package/data/build/dist/shared/contracts/file-management.js +3 -0
- package/data/build/dist/shared/contracts/peer-host.d.ts +116 -15
- package/data/build/dist/shared/contracts/peer-host.js +19 -0
- package/data/build/dist/shared/contracts/version.d.ts +1 -1
- package/data/build/dist/shared/contracts/version.js +1 -1
- package/data/build/dist/shared/index.d.ts +4 -3
- package/data/build/dist/shared/index.js +2 -2
- package/data/build/dist/transport/debug.d.ts +5 -0
- package/data/build/dist/transport/debug.js +23 -1
- package/data/build/dist/transport/dsh-transport.d.ts +2 -0
- package/data/build/dist/transport/dsh-transport.js +11 -0
- package/data/build/dist/transport/webrtc-client.d.ts +2 -0
- package/data/build/dist/transport/webrtc-client.js +2 -1
- package/data/build/dist/transport/webrtc-host.d.ts +2 -0
- package/data/build/dist/transport/webrtc-host.js +6 -3
- package/dsh.bundle.patch +10 -0
- package/package.json +23 -11
- package/profile/README.md +2 -2
- package/profile/package.json +2 -2
- package/profile/version.json +1 -1
- package/version.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CodingNsRpcError } from '../rpc-table.js';
|
|
2
2
|
import { CodingNsCliSessionStore } from './session-store.js';
|
|
3
3
|
import { readLegacyImportedAdapterPreferences } from './legacy-session-settings.js';
|
|
4
|
+
import { knownCodexContextWindow } from './model-catalog.js';
|
|
4
5
|
const DEFAULT_INSTALLED_CACHE_TTL_MS = 5 * 60_000;
|
|
5
6
|
const DEFAULT_UNINSTALLED_CACHE_TTL_MS = 30_000;
|
|
6
7
|
const DEFAULT_MODEL_CACHE_TTL_MS = 10 * 60_000;
|
|
@@ -29,7 +30,12 @@ export class CodingNsCliAdapterRegistry {
|
|
|
29
30
|
modelTimers = new Map();
|
|
30
31
|
modelGenerations = new Map();
|
|
31
32
|
requestedModelCatalogs = new Set();
|
|
32
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* 已切到下一个 DSH step、但仍在继续产出 Provider 流的运行。
|
|
35
|
+
*
|
|
36
|
+
* 通用切分由 Registry 持有活迭代器;驱动自行分段的适配器(Codex、Command Code)
|
|
37
|
+
* 把进程和段状态留在驱动内,这里只登记续段意图,用 null 迭代器区分两者。
|
|
38
|
+
*/
|
|
33
39
|
segmentedTurns = new Map();
|
|
34
40
|
cacheGeneration = 0;
|
|
35
41
|
disposed = false;
|
|
@@ -264,6 +270,11 @@ export class CodingNsCliAdapterRegistry {
|
|
|
264
270
|
}
|
|
265
271
|
const suspended = this.segmentedTurns.get(input.sessionId);
|
|
266
272
|
const resumingSegmentedTurn = suspended?.adapterId === input.adapterId;
|
|
273
|
+
// 驱动自行分段时,续段必须由 Host 显式声明;驱动不能靠猜输入形状决定是否
|
|
274
|
+
// 复用旧进程,否则注入失败后的新用户消息会被拼进上一次运行。
|
|
275
|
+
const resumingDriverTurn = resumingSegmentedTurn && suspended !== undefined && suspended.iterator === null;
|
|
276
|
+
if (resumingDriverTurn)
|
|
277
|
+
this.segmentedTurns.delete(input.sessionId);
|
|
267
278
|
this.sessions.set(input.sessionId, current);
|
|
268
279
|
this.sessionStore?.upsert(input.sessionId, {
|
|
269
280
|
...current,
|
|
@@ -281,19 +292,32 @@ export class CodingNsCliAdapterRegistry {
|
|
|
281
292
|
// 每轮都写入稳定的适配器身份。旧日志只有通用 codingns-external 标记,
|
|
282
293
|
// 这条 request/context 是升级后自动迁移时唯一可靠的回填依据。
|
|
283
294
|
try {
|
|
295
|
+
const contextWindow = input.adapterId === 'codex' ? knownCodexContextWindow(input.modelId) : undefined;
|
|
284
296
|
this.nativeSessions?.appendRequestContext?.(input.sessionId, {
|
|
285
297
|
provider: input.adapterId,
|
|
286
298
|
model: input.modelId ?? input.adapterId,
|
|
299
|
+
...(contextWindow === undefined ? {} : { contextWindow, confirmed: true, source: 'catalog' }),
|
|
287
300
|
});
|
|
288
301
|
}
|
|
289
302
|
catch { /* 原生历史写入失败不应阻断外部 Agent */ }
|
|
290
303
|
if (suspended !== undefined && suspended.adapterId !== input.adapterId) {
|
|
291
304
|
this.segmentedTurns.delete(input.sessionId);
|
|
292
|
-
|
|
305
|
+
if (suspended.iterator !== null)
|
|
306
|
+
await closeAgentIterator(suspended.iterator);
|
|
307
|
+
try {
|
|
308
|
+
this.drivers.get(suspended.adapterId)?.discardSegmentedTurn?.(input.sessionId);
|
|
309
|
+
}
|
|
310
|
+
catch { /* 切换适配器不能被旧驱动清理失败阻断 */ }
|
|
311
|
+
}
|
|
312
|
+
// 通用切分要在复用时立刻撤销登记,否则耗尽后的旧迭代器会被下一条用户消息再次消费。
|
|
313
|
+
let iterator;
|
|
314
|
+
if (resumingSegmentedTurn && suspended !== undefined && suspended.iterator !== null) {
|
|
315
|
+
this.segmentedTurns.delete(input.sessionId);
|
|
316
|
+
iterator = suspended.iterator;
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
iterator = driver.executeTurn({ ...input, ...(resumingDriverTurn ? { resumeSegmentedTurn: true } : {}) })[Symbol.asyncIterator]();
|
|
293
320
|
}
|
|
294
|
-
const iterator = suspended?.adapterId === input.adapterId
|
|
295
|
-
? (this.segmentedTurns.delete(input.sessionId), suspended.iterator)
|
|
296
|
-
: driver.executeTurn(input)[Symbol.asyncIterator]();
|
|
297
321
|
while (true) {
|
|
298
322
|
const next = await iterator.next();
|
|
299
323
|
if (next.done)
|
|
@@ -328,6 +352,10 @@ export class CodingNsCliAdapterRegistry {
|
|
|
328
352
|
// 直接交给 Feature,不能再次按工具完成事件切一遍。
|
|
329
353
|
if (event.type === 'step-boundary') {
|
|
330
354
|
await closeAgentIterator(iterator);
|
|
355
|
+
// 驱动自己持有 Provider 进程与段状态:这里只登记续段意图,下一次
|
|
356
|
+
// llm/stream 再显式要求驱动恢复;注入失败时 discardSegmentedTurn
|
|
357
|
+
// 会同时丢弃 Registry 意图和驱动内的进程。
|
|
358
|
+
this.segmentedTurns.set(input.sessionId, { adapterId: input.adapterId, iterator: null });
|
|
331
359
|
yield event;
|
|
332
360
|
return;
|
|
333
361
|
}
|
|
@@ -433,7 +461,13 @@ export class CodingNsCliAdapterRegistry {
|
|
|
433
461
|
if (suspended === undefined)
|
|
434
462
|
return;
|
|
435
463
|
this.segmentedTurns.delete(sessionId);
|
|
436
|
-
|
|
464
|
+
if (suspended.iterator !== null)
|
|
465
|
+
void closeAgentIterator(suspended.iterator);
|
|
466
|
+
// 驱动自行分段时进程留在驱动内,必须显式通知它结束悬挂的 Provider 运行。
|
|
467
|
+
try {
|
|
468
|
+
this.drivers.get(suspended.adapterId)?.discardSegmentedTurn?.(sessionId);
|
|
469
|
+
}
|
|
470
|
+
catch { /* 取消流程不能被清理失败阻断 */ }
|
|
437
471
|
}
|
|
438
472
|
async dispose() {
|
|
439
473
|
if (this.disposed)
|
|
@@ -447,7 +481,7 @@ export class CodingNsCliAdapterRegistry {
|
|
|
447
481
|
this.modelFailures.clear();
|
|
448
482
|
this.modelGenerations.clear();
|
|
449
483
|
this.requestedModelCatalogs.clear();
|
|
450
|
-
await Promise.all([...this.segmentedTurns.values()].map(({ iterator }) => closeAgentIterator(iterator)));
|
|
484
|
+
await Promise.all([...this.segmentedTurns.values()].map(({ iterator }) => iterator === null ? Promise.resolve() : closeAgentIterator(iterator)));
|
|
451
485
|
this.segmentedTurns.clear();
|
|
452
486
|
await Promise.all([...this.drivers.values()].map((driver) => driver.dispose?.()));
|
|
453
487
|
}
|
|
@@ -1,19 +1,38 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
2
|
import { JsonRpcProcess } from './json-rpc-process.js';
|
|
3
|
-
import { WINDOWS } from './process-utils.js';
|
|
3
|
+
import { commandEnvironment, resolveCommandPath, WINDOWS } from './process-utils.js';
|
|
4
4
|
export async function detectBinary(options) {
|
|
5
5
|
const run = options.spawnSync ?? spawnSync;
|
|
6
|
+
let lookupAfterDetectionFailure = false;
|
|
6
7
|
for (const command of options.binaries) {
|
|
8
|
+
const direct = detectCommand(command);
|
|
9
|
+
if (direct !== null)
|
|
10
|
+
return direct;
|
|
11
|
+
if (!lookupAfterDetectionFailure)
|
|
12
|
+
continue;
|
|
13
|
+
const resolved = resolveCommandPath(command, run);
|
|
14
|
+
if (resolved === null)
|
|
15
|
+
continue;
|
|
16
|
+
const fallback = detectCommand(resolved);
|
|
17
|
+
if (fallback !== null)
|
|
18
|
+
return fallback;
|
|
19
|
+
}
|
|
20
|
+
return { installed: false, version: null, command: null };
|
|
21
|
+
function detectCommand(command) {
|
|
22
|
+
lookupAfterDetectionFailure = false;
|
|
7
23
|
try {
|
|
8
|
-
const result = run(command, ['--version'], { encoding: 'utf8', timeout: 3_000, windowsHide: true, shell: WINDOWS });
|
|
24
|
+
const result = run(command, ['--version'], { encoding: 'utf8', timeout: 3_000, windowsHide: true, shell: WINDOWS, env: commandEnvironment(command) });
|
|
9
25
|
const output = `${result.stdout ?? ''}${result.stderr ?? ''}`;
|
|
10
26
|
const version = output.match(/\d+\.\d+(?:\.\d+)?/u)?.[0] ?? null;
|
|
11
27
|
if (result.status === 0 && version !== null)
|
|
12
28
|
return { installed: true, version, command };
|
|
29
|
+
lookupAfterDetectionFailure = result.status === null;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
lookupAfterDetectionFailure = true; /* PATH 中没有该命令 */
|
|
13
33
|
}
|
|
14
|
-
|
|
34
|
+
return null;
|
|
15
35
|
}
|
|
16
|
-
return { installed: false, version: null, command: null };
|
|
17
36
|
}
|
|
18
37
|
/** 把一个 JSON-RPC 请求期间收到的通知排成异步流,同时等待最终响应。 */
|
|
19
38
|
export async function* streamRpcRequest(process, method, params, signal, options = {}) {
|
|
@@ -25,6 +25,7 @@ export declare abstract class StandardStreamDriver implements CodingNsCliDriver
|
|
|
25
25
|
private readonly versionArgs;
|
|
26
26
|
private readonly modelArgs;
|
|
27
27
|
private cachedBinary;
|
|
28
|
+
private cachedEnvironment;
|
|
28
29
|
private readonly processes;
|
|
29
30
|
protected constructor(descriptor: Omit<CodingNsCliAdapterDescriptor, 'installed' | 'enabled' | 'version' | 'command'>, defaults: {
|
|
30
31
|
binaries: readonly string[];
|
|
@@ -36,6 +37,8 @@ export declare abstract class StandardStreamDriver implements CodingNsCliDriver
|
|
|
36
37
|
version: string | null;
|
|
37
38
|
command: string | null;
|
|
38
39
|
}>;
|
|
40
|
+
private lookupAfterDetectionFailure;
|
|
41
|
+
private detectCommand;
|
|
39
42
|
listModels(): Promise<CodingNsCliModelCatalog>;
|
|
40
43
|
executeTurn(input: CodingNsCliTurnInput): AsyncIterable<CodingNsAgentEvent>;
|
|
41
44
|
dispose(): void;
|
|
@@ -2,7 +2,7 @@ import { spawn, spawnSync } from 'node:child_process';
|
|
|
2
2
|
import readline from 'node:readline';
|
|
3
3
|
import { firstToolText, isToolRecord, normalizeToolStatus, serializeToolValue } from './tool-observation.js';
|
|
4
4
|
import { usageChunk } from './rpc-driver-utils.js';
|
|
5
|
-
import { terminateChildProcess } from './process-utils.js';
|
|
5
|
+
import { commandEnvironment, resolveCommandPath, terminateChildProcess } from './process-utils.js';
|
|
6
6
|
const WINDOWS = process.platform === 'win32';
|
|
7
7
|
/**
|
|
8
8
|
* 把采用 JSONL/stream-json 的 CLI 统一成 Codingns4DSH 的最小驱动契约。
|
|
@@ -16,6 +16,7 @@ export class StandardStreamDriver {
|
|
|
16
16
|
versionArgs;
|
|
17
17
|
modelArgs;
|
|
18
18
|
cachedBinary = null;
|
|
19
|
+
cachedEnvironment;
|
|
19
20
|
processes = new Set();
|
|
20
21
|
constructor(descriptor, defaults, options = {}) {
|
|
21
22
|
this.descriptor = descriptor;
|
|
@@ -27,27 +28,46 @@ export class StandardStreamDriver {
|
|
|
27
28
|
}
|
|
28
29
|
async detect() {
|
|
29
30
|
for (const command of this.binaries) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
31
|
+
const direct = await this.detectCommand(command);
|
|
32
|
+
if (direct !== null)
|
|
33
|
+
return direct;
|
|
34
|
+
if (!this.lookupAfterDetectionFailure)
|
|
35
|
+
continue;
|
|
36
|
+
const resolved = resolveCommandPath(command, this.runSpawnSync);
|
|
37
|
+
if (resolved === null)
|
|
38
|
+
continue;
|
|
39
|
+
const fallback = await this.detectCommand(resolved, commandEnvironment(resolved));
|
|
40
|
+
if (fallback !== null)
|
|
41
|
+
return fallback;
|
|
42
42
|
}
|
|
43
43
|
return { installed: false, version: null, command: null };
|
|
44
44
|
}
|
|
45
|
+
lookupAfterDetectionFailure = false;
|
|
46
|
+
async detectCommand(command, env) {
|
|
47
|
+
this.lookupAfterDetectionFailure = false;
|
|
48
|
+
try {
|
|
49
|
+
const result = this.runSpawnSync(command, this.versionArgs, { encoding: 'utf8', timeout: 5_000, windowsHide: true, shell: WINDOWS, ...(env === undefined ? {} : { env }) });
|
|
50
|
+
const output = `${result.stdout ?? ''}\n${result.stderr ?? ''}`;
|
|
51
|
+
const version = this.parseVersion(output);
|
|
52
|
+
if (result.status === 0 && version !== null) {
|
|
53
|
+
this.cachedBinary = command;
|
|
54
|
+
this.cachedEnvironment = env ?? commandEnvironment(command);
|
|
55
|
+
return { installed: true, version, command };
|
|
56
|
+
}
|
|
57
|
+
this.lookupAfterDetectionFailure = result.status === null;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// 候选命令不存在时继续尝试下一个名称。
|
|
61
|
+
this.lookupAfterDetectionFailure = true;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
45
65
|
async listModels() {
|
|
46
66
|
const command = this.cachedBinary ?? (await this.detect()).command;
|
|
47
67
|
if (command === null)
|
|
48
68
|
return emptyCatalog();
|
|
49
69
|
try {
|
|
50
|
-
const result = this.runSpawnSync(command, this.modelArgs, { encoding: 'utf8', timeout: 12_000, windowsHide: true, shell: WINDOWS });
|
|
70
|
+
const result = this.runSpawnSync(command, this.modelArgs, { encoding: 'utf8', timeout: 12_000, windowsHide: true, shell: WINDOWS, ...(this.cachedEnvironment === undefined ? {} : { env: this.cachedEnvironment }) });
|
|
51
71
|
if (result.status !== 0)
|
|
52
72
|
return emptyCatalog();
|
|
53
73
|
return this.parseModels(`${result.stdout ?? ''}\n${result.stderr ?? ''}`);
|
|
@@ -61,7 +81,7 @@ export class StandardStreamDriver {
|
|
|
61
81
|
if (command === null)
|
|
62
82
|
throw new Error(`${this.descriptor.name} 未安装`);
|
|
63
83
|
const child = this.runSpawn(command, this.buildArgs(input), {
|
|
64
|
-
cwd: input.cwd ?? process.cwd(), env: { ...process.env }, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true, shell: WINDOWS,
|
|
84
|
+
cwd: input.cwd ?? process.cwd(), env: this.cachedEnvironment ?? { ...process.env }, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true, shell: WINDOWS,
|
|
65
85
|
});
|
|
66
86
|
this.processes.add(child);
|
|
67
87
|
let emittedFinish = false;
|
|
@@ -119,6 +139,7 @@ export class StandardStreamDriver {
|
|
|
119
139
|
terminateChildProcess(child);
|
|
120
140
|
this.processes.clear();
|
|
121
141
|
this.cachedBinary = null;
|
|
142
|
+
this.cachedEnvironment = undefined;
|
|
122
143
|
}
|
|
123
144
|
parseVersion(output) { return output.match(/\b\d+\.\d+(?:\.\d+)?(?:[-+][0-9A-Za-z.-]+)?\b/u)?.[0] ?? null; }
|
|
124
145
|
parseEvent(value, input) {
|
|
@@ -20,6 +20,8 @@ export interface DshHostDeviceRuntimeOptions {
|
|
|
20
20
|
readonly dshVersion?: string;
|
|
21
21
|
/** 覆盖自动获取的计算机名,主要用于测试和受控运行环境。 */
|
|
22
22
|
readonly computerName?: string;
|
|
23
|
+
/** 当前 DSH 配置文件名称,用于在远端 Host 列表中区分同机实例。 */
|
|
24
|
+
readonly profileName?: string;
|
|
23
25
|
readonly capabilities?: readonly string[];
|
|
24
26
|
readonly resources?: Pick<FeatureResourceScope, 'add'>;
|
|
25
27
|
readonly signalingSocketFactory?: Parameters<typeof startHostRelayRuntime>[0]['signalingSocketFactory'];
|
|
@@ -21,14 +21,13 @@ export async function startDshHostDeviceRuntime(options) {
|
|
|
21
21
|
const dtlsStore = options.dtlsStore ?? new FileHostDtlsIdentityStore(defaultDtlsPath());
|
|
22
22
|
const identity = await ensureHostDtlsIdentity(dtlsStore);
|
|
23
23
|
const dshVersion = options.dshVersion?.trim() || DSH_VERSION;
|
|
24
|
+
const reportedDshVersion = formatDshVersion(dshVersion, options.profileName ?? process.env.CODINGNS4DSH_PROFILE_NAME);
|
|
24
25
|
const computerName = options.computerName?.trim() || hostname().trim() || 'unknown';
|
|
25
|
-
const heartbeatDetails = { dshVersion, computerName };
|
|
26
|
-
|
|
27
|
-
let device;
|
|
28
|
-
if (credential === null || credential.dtlsFingerprint !== identity.fingerprint) {
|
|
26
|
+
const heartbeatDetails = { dshVersion: reportedDshVersion, computerName, dtlsFingerprint: identity.fingerprint };
|
|
27
|
+
const registerDevice = async (previous) => {
|
|
29
28
|
const request = {
|
|
30
|
-
displayName: options.displayName?.trim() || 'DSH Host',
|
|
31
|
-
dshVersion,
|
|
29
|
+
displayName: options.displayName?.trim() || previous?.displayName || 'DSH Host',
|
|
30
|
+
dshVersion: reportedDshVersion,
|
|
32
31
|
computerName,
|
|
33
32
|
devicePublicKey: identity.certPem,
|
|
34
33
|
dtlsFingerprint: identity.fingerprint,
|
|
@@ -36,43 +35,56 @@ export async function startDshHostDeviceRuntime(options) {
|
|
|
36
35
|
capabilities: [...(options.capabilities ?? ['rpc', 'pty', 'file', 'web'])],
|
|
37
36
|
};
|
|
38
37
|
const registered = await withAccessToken((accessToken) => options.controlClient.registerDshDevice(accessToken, request));
|
|
39
|
-
|
|
38
|
+
const nextCredential = {
|
|
40
39
|
deviceId: registered.device.dshDeviceId ?? registered.device.deviceId ?? (() => { throw new Error('DSH 注册响应缺少设备标识'); })(),
|
|
41
40
|
deviceCredential: registered.deviceCredential,
|
|
42
41
|
credentialVersion: registered.credentialVersion,
|
|
43
42
|
dtlsFingerprint: identity.fingerprint,
|
|
44
43
|
tunnelDomain: registered.device.tunnelDomain ?? null,
|
|
45
44
|
displayName: registered.device.displayName,
|
|
46
|
-
dshVersion: registered.device.dshVersion ??
|
|
45
|
+
dshVersion: registered.device.dshVersion ?? reportedDshVersion,
|
|
47
46
|
computerName: registered.device.computerName ?? computerName,
|
|
48
47
|
savedAt: new Date().toISOString(),
|
|
49
48
|
};
|
|
50
|
-
await credentialStore.write(
|
|
49
|
+
await credentialStore.write(nextCredential);
|
|
50
|
+
return { credential: nextCredential, device: registered.device };
|
|
51
|
+
};
|
|
52
|
+
let credential = await credentialStore.read();
|
|
53
|
+
let device;
|
|
54
|
+
if (credential === null || credential.dtlsFingerprint !== identity.fingerprint) {
|
|
55
|
+
const registered = await registerDevice(credential);
|
|
56
|
+
credential = registered.credential;
|
|
51
57
|
device = registered.device;
|
|
52
58
|
}
|
|
53
59
|
else {
|
|
54
60
|
const listed = await withAccessToken((accessToken) => options.controlClient.listDshDevices(accessToken));
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
status: 'active',
|
|
66
|
-
online: false,
|
|
67
|
-
lastHeartbeatAt: null,
|
|
68
|
-
createdAt: credential.savedAt,
|
|
69
|
-
updatedAt: credential.savedAt,
|
|
70
|
-
};
|
|
61
|
+
const listedDevice = listed.devices.find((candidate) => (candidate.dshDeviceId ?? candidate.deviceId) === credential.deviceId);
|
|
62
|
+
if (listedDevice) {
|
|
63
|
+
device = listedDevice;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
// 控制台删除了服务端记录时,旧凭据已经失去归属,必须重新注册当前 DTLS 身份。
|
|
67
|
+
const registered = await registerDevice(credential);
|
|
68
|
+
credential = registered.credential;
|
|
69
|
+
device = registered.device;
|
|
70
|
+
}
|
|
71
71
|
}
|
|
72
72
|
if (credential === null)
|
|
73
73
|
throw new Error('DSH 设备凭据初始化失败');
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
let savedCredential = credential;
|
|
75
|
+
try {
|
|
76
|
+
await withAccessToken((accessToken) => options.controlClient.heartbeatDshDevice(accessToken, savedCredential.deviceId, savedCredential.deviceCredential, heartbeatDetails));
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (!isRecoverableDshDeviceCredentialError(error))
|
|
80
|
+
throw error;
|
|
81
|
+
// 设备记录可能在列表检查后、心跳前被删除,此时换发当前 Host 的新凭据。
|
|
82
|
+
await credentialStore.clear();
|
|
83
|
+
const registered = await registerDevice(savedCredential);
|
|
84
|
+
savedCredential = registered.credential;
|
|
85
|
+
device = registered.device;
|
|
86
|
+
await withAccessToken((accessToken) => options.controlClient.heartbeatDshDevice(accessToken, savedCredential.deviceId, savedCredential.deviceCredential, heartbeatDetails));
|
|
87
|
+
}
|
|
76
88
|
const runtimeOptions = {
|
|
77
89
|
controlClient: { createSignalingTicket: async () => { throw new Error('DSH runtime 必须使用 DSH ticket'); } },
|
|
78
90
|
createTicket: ({ identity: material, credentialVersion }) => requestDshTicket(withAccessToken, options.controlClient, savedCredential, material.fingerprint, credentialVersion),
|
|
@@ -124,6 +136,20 @@ async function requestDshTicket(withAccessToken, client, credential, fingerprint
|
|
|
124
136
|
credentialVersion: response.credentialVersion,
|
|
125
137
|
};
|
|
126
138
|
}
|
|
127
|
-
function defaultDshCredentialPath() { return join(
|
|
128
|
-
function defaultDtlsPath() { return join(
|
|
139
|
+
function defaultDshCredentialPath() { return join(defaultStateDirectory(), 'device-credential.json'); }
|
|
140
|
+
function defaultDtlsPath() { return join(defaultStateDirectory(), 'dtls-identity.json'); }
|
|
141
|
+
function defaultStateDirectory() {
|
|
142
|
+
return process.env.CODINGNS4DSH_STATE_DIR?.trim() || join(homedir(), '.config', 'codingns4dsh');
|
|
143
|
+
}
|
|
144
|
+
/** 只装饰远端列表展示值;Transport 握手仍使用原始 DSH 版本。 */
|
|
145
|
+
function formatDshVersion(version, profileName) {
|
|
146
|
+
const profile = profileName?.trim();
|
|
147
|
+
return profile ? `${version} (配置: ${profile})` : version;
|
|
148
|
+
}
|
|
149
|
+
function isRecoverableDshDeviceCredentialError(error) {
|
|
150
|
+
return isRecord(error) && error.status === 404 && error.errorCode === 'DSH_DEVICE_NOT_FOUND';
|
|
151
|
+
}
|
|
152
|
+
function isRecord(value) {
|
|
153
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
154
|
+
}
|
|
129
155
|
//# sourceMappingURL=dsh-device-runtime.js.map
|
|
@@ -4,6 +4,7 @@ import { CodingNsAuthSession } from '../auth-session.js';
|
|
|
4
4
|
import { HttpCodingNsControlApiClient } from '../control-api-client.js';
|
|
5
5
|
import { FileCodingNsCredentialStore, FileDshDeviceCredentialStore } from '../credential-store.js';
|
|
6
6
|
import { startDshHostDeviceRuntime } from '../dsh-device-runtime.js';
|
|
7
|
+
import { FileHostDtlsIdentityStore } from '../relay-tunnel-runtime.js';
|
|
7
8
|
import { CodingNsRpcError } from '../rpc-table.js';
|
|
8
9
|
import { createDshRpcGatewayFeature } from '../dsh-gateway-feature.js';
|
|
9
10
|
import { createLocalDshWebRuntimeProvider, createRemoteWebRuntimeFeature } from '../remote-web-runtime.js';
|
|
@@ -38,6 +39,7 @@ export function createAuthFeature() {
|
|
|
38
39
|
const stateDirectory = process.env.CODINGNS4DSH_STATE_DIR?.trim() || join(homedir(), '.config', 'codingns4dsh');
|
|
39
40
|
const credentials = new FileCodingNsCredentialStore(join(stateDirectory, 'codingns-credentials.json'));
|
|
40
41
|
const dshCredentials = new FileDshDeviceCredentialStore(join(stateDirectory, 'device-credential.json'));
|
|
42
|
+
const dtlsIdentity = new FileHostDtlsIdentityStore(join(stateDirectory, 'dtls-identity.json'));
|
|
41
43
|
const loginProtectionStore = new FileLanAccessDshLoginStore(join(stateDirectory, 'lan-access-login.json'));
|
|
42
44
|
let session = null;
|
|
43
45
|
let sessionBaseUrl = null;
|
|
@@ -88,6 +90,7 @@ export function createAuthFeature() {
|
|
|
88
90
|
controlClient: target.getControlClient(),
|
|
89
91
|
accessToken,
|
|
90
92
|
credentialStore: dshCredentials,
|
|
93
|
+
dtlsStore: dtlsIdentity,
|
|
91
94
|
accessTokenProvider: () => target.getAccessToken(),
|
|
92
95
|
withAccessToken: (operation) => target.withAccessToken(operation),
|
|
93
96
|
...(context.services.dshVersion === undefined ? {} : { dshVersion: context.services.dshVersion }),
|
|
@@ -132,9 +135,14 @@ export function createAuthFeature() {
|
|
|
132
135
|
bind: (payload) => requireSession().bindHost(parseHostBind(payload)),
|
|
133
136
|
unbind: (payload) => requireSession().unbindHost(parseStringField(payload, 'bindingId')),
|
|
134
137
|
signalingTicket: (payload) => requireSession().createClientSignalingTicket(parseOptionalStringField(payload, 'tunnelDomain')),
|
|
135
|
-
'dsh/device/list': () => {
|
|
138
|
+
'dsh/device/list': async () => {
|
|
136
139
|
const target = requireSession();
|
|
137
|
-
return target.withAccessToken((accessToken) =>
|
|
140
|
+
return target.withAccessToken(async (accessToken) => {
|
|
141
|
+
const listed = await target.getControlClient().listDshDevices(accessToken);
|
|
142
|
+
// 即使 Host 当前未启动,也用本地凭据标识当前设备,避免列表回退到账号下另一台 Host。
|
|
143
|
+
const currentDeviceId = dshRuntime?.credential.deviceId ?? (await dshCredentials.read())?.deviceId;
|
|
144
|
+
return currentDeviceId === undefined ? listed : { ...listed, currentDeviceId };
|
|
145
|
+
});
|
|
138
146
|
},
|
|
139
147
|
'dsh/device/start': async () => { await startDsh(requireSession()); return dshRuntime?.device ?? null; },
|
|
140
148
|
'dsh/device/stop': async () => { await dshRuntime?.stop(); dshRuntime = null; return { stopped: true }; },
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { FeatureModule } from '../../shared/contracts/feature.js';
|
|
2
|
+
import type { CodingNsHostServices } from './types.js';
|
|
3
|
+
/** 文件管理增强的 Host 边界;所有路径先解析到已知工作区,再执行文件操作。 */
|
|
4
|
+
export declare function createFileManagementFeature(): FeatureModule<CodingNsHostServices>;
|
|
5
|
+
//# sourceMappingURL=file-management.d.ts.map
|