@jingyi0605/codingns4dsh 0.1.7-rc.3 → 0.2.0-beta.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/LICENSE +695 -0
- package/README.en.md +23 -9
- package/README.md +23 -9
- package/data/build/dist/bootstrap/dsh-connection-adapter.d.ts +1 -1
- package/data/build/dist/bootstrap/index.d.ts +1 -1
- package/data/build/dist/client/bundle.js +1200 -170
- package/data/build/dist/client/context-breakdown-dom.d.ts +53 -0
- package/data/build/dist/client/context-breakdown-dom.js +171 -0
- package/data/build/dist/client/dsh-h5-bootstrap.js +7 -1
- package/data/build/dist/client/features/cli-adapters.js +11 -0
- package/data/build/dist/client/features/index.d.ts +1 -0
- package/data/build/dist/client/features/index.js +1 -0
- package/data/build/dist/client/features/subscription-usage-panel.d.ts +15 -0
- package/data/build/dist/client/features/subscription-usage-panel.js +117 -0
- package/data/build/dist/client/features/workspace-session-enhancement-panel.js +22 -5
- package/data/build/dist/client/features/workspace-session-enhancement.js +17 -2
- package/data/build/dist/client/git-management.js +80 -10
- package/data/build/dist/client/index.d.ts +1 -0
- package/data/build/dist/client/index.js +16 -49
- package/data/build/dist/client/locale.js +20 -2
- package/data/build/dist/client/remote-web-context.js +26 -2
- package/data/build/dist/client/settings-bridge.d.ts +20 -5
- package/data/build/dist/client/settings-bridge.js +30 -7
- package/data/build/dist/client/subscription-slot.d.ts +3 -1
- package/data/build/dist/client/subscription-slot.js +31 -6
- package/data/build/dist/client/terminal/ui.js +1 -1
- package/data/build/dist/client/terminal/xterm-view.js +1 -1
- package/data/build/dist/dsh-capabilities/client/config-forms-adapter.d.ts +10 -0
- package/data/build/dist/dsh-capabilities/client/config-forms-adapter.js +22 -0
- package/data/build/dist/dsh-capabilities/client/primitives-adapter.d.ts +5 -2
- package/data/build/dist/dsh-capabilities/client/primitives-adapter.js +3 -2
- package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.d.ts +2 -2
- package/data/build/dist/dsh-capabilities/host/config-forms-adapter.d.ts +22 -1
- package/data/build/dist/dsh-capabilities/host/config-forms-adapter.js +29 -3
- package/data/build/dist/dsh-capabilities/host/settings-scope-adapter.d.ts +2 -2
- package/data/build/dist/dsh-capabilities/matrix.js +31 -1
- package/data/build/dist/dsh-capabilities/routes.d.ts +8 -1
- package/data/build/dist/dsh-capabilities/routes.js +173 -5
- package/data/build/dist/dsh-capabilities/types.d.ts +1 -1
- package/data/build/dist/features/registry.d.ts +7 -0
- package/data/build/dist/features/registry.js +20 -2
- package/data/build/dist/host/cli-adapters/feature.js +24 -2
- package/data/build/dist/host/cli-adapters/native-team-proxy.d.ts +36 -0
- package/data/build/dist/host/cli-adapters/native-team-proxy.js +108 -0
- package/data/build/dist/host/cli-adapters/provider-subscription.d.ts +35 -0
- package/data/build/dist/host/cli-adapters/provider-subscription.js +405 -5
- package/data/build/dist/host/cli-adapters/registry.d.ts +9 -3
- package/data/build/dist/host/cli-adapters/registry.js +30 -0
- package/data/build/dist/host/cli-adapters/session-store.d.ts +8 -2
- package/data/build/dist/host/cli-adapters/session-store.js +19 -0
- package/data/build/dist/host/features/git-management.js +41 -18
- package/data/build/dist/host/features/types.d.ts +6 -3
- package/data/build/dist/host/index-injection.d.ts +12 -5
- package/data/build/dist/host/index-injection.js +32 -11
- package/data/build/dist/host/index.js +15 -4
- package/data/build/dist/host/lan-access-dsh.d.ts +2 -2
- package/data/build/dist/host/native-session-bridge.d.ts +4 -0
- package/data/build/dist/host/native-session-bridge.js +51 -9
- package/data/build/dist/host/relay-tunnel-runtime.js +10 -1
- package/data/build/dist/host/remote-web-runtime.js +40 -17
- package/data/build/dist/host/rpc.d.ts +24 -5
- package/data/build/dist/host/rpc.js +38 -9
- package/data/build/dist/host/session-migration-repair.js +14 -0
- package/data/build/dist/host/settings.d.ts +2 -2
- package/data/build/dist/host/settings.js +11 -1
- package/data/build/dist/host/terminal/startup.d.ts +2 -2
- package/data/build/dist/shared/contracts/cli-adapter.d.ts +22 -1
- package/data/build/dist/shared/contracts/config.d.ts +21 -0
- package/data/build/dist/shared/contracts/config.js +17 -0
- package/data/build/dist/shared/contracts/errors.d.ts +2 -2
- package/data/build/dist/shared/contracts/subscription.d.ts +7 -0
- package/data/build/dist/shared/contracts/subscription.js +14 -1
- package/data/build/dist/shared/contracts/version.d.ts +4 -4
- package/data/build/dist/shared/contracts/version.js +3 -3
- package/data/build/dist/shared/index.d.ts +3 -2
- package/data/build/dist/shared/index.js +2 -1
- package/data/build/dist/transport/carrier.d.ts +11 -1
- package/data/build/dist/transport/carrier.js +77 -26
- package/data/build/dist/transport/debug.d.ts +5 -0
- package/data/build/dist/transport/debug.js +17 -2
- package/data/build/dist/transport/dsh-session.d.ts +18 -1
- package/data/build/dist/transport/dsh-session.js +48 -13
- package/data/build/dist/transport/index.d.ts +1 -1
- package/data/build/dist/transport/index.js +1 -1
- package/package.json +23 -19
- package/profile/README.md +2 -2
- package/profile/package.json +4 -3
- package/profile/version.json +3 -3
- package/version.json +3 -3
|
@@ -204,19 +204,20 @@ async function readHistory(_workspaceId, root, rawLimit, rawOffset) {
|
|
|
204
204
|
const offset = Math.max(0, Math.min(1_000_000, Number.isSafeInteger(rawOffset) ? Number(rawOffset) : 0));
|
|
205
205
|
let result;
|
|
206
206
|
try {
|
|
207
|
-
result = await runGit(root, ['log', `--skip=${String(offset)}`, `--max-count=${String(limit)}`, '--format=%H%x1f%an%x1f%aI%x1f%s%x1f%b%x1e']);
|
|
207
|
+
result = await runGit(root, ['log', `--skip=${String(offset)}`, `--max-count=${String(limit)}`, '--decorate=short', '--format=%H%x1f%an%x1f%aI%x1f%s%x1f%b%x1f%D%x1e']);
|
|
208
208
|
}
|
|
209
209
|
catch (error) {
|
|
210
210
|
if (isEmptyRepositoryError(error))
|
|
211
211
|
return { items: [], cursor: String(offset), nextCursor: null, totalCount: 0 };
|
|
212
212
|
throw error;
|
|
213
213
|
}
|
|
214
|
+
const refByShortName = new Map((await readRefRecords(root)).map((record) => [record.shortName, record]));
|
|
214
215
|
const items = [];
|
|
215
216
|
for (const record of result.stdout.split('\x1e')) {
|
|
216
217
|
const fields = record.trim().split('\x1f');
|
|
217
218
|
if (fields.length < 5 || !fields[0])
|
|
218
219
|
continue;
|
|
219
|
-
items.push({ commitHash: fields[0], authorName: fields[1] ?? '', authoredAt: fields[2] ?? '', subject: fields[3] ?? '', body: fields[4] ?? '', refs: [] });
|
|
220
|
+
items.push({ commitHash: fields[0], authorName: fields[1] ?? '', authoredAt: fields[2] ?? '', subject: fields[3] ?? '', body: fields[4] ?? '', refs: parseHistoryRefs(fields[5] ?? '', refByShortName) });
|
|
220
221
|
}
|
|
221
222
|
let total = 0;
|
|
222
223
|
try {
|
|
@@ -228,24 +229,46 @@ async function readHistory(_workspaceId, root, rawLimit, rawOffset) {
|
|
|
228
229
|
}
|
|
229
230
|
return { items, cursor: String(offset), nextCursor: offset + items.length < total ? String(offset + items.length) : null, totalCount: total };
|
|
230
231
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
232
|
+
/** 读取本地与远程分支的 ref 表;记录之间换行分隔,字段使用 Git 原生支持的 NUL 分隔符(for-each-ref 不展开 %xNN)。 */
|
|
233
|
+
async function readRefRecords(root) {
|
|
234
|
+
const result = await runGit(root, ['for-each-ref', '--format=%(refname)%00%(refname:short)%00%(objectname)%00%(upstream:short)%00%(HEAD)', 'refs/heads', 'refs/remotes']);
|
|
235
|
+
const records = [];
|
|
236
|
+
for (const line of result.stdout.split(/\r?\n/u)) {
|
|
237
|
+
if (line === '')
|
|
238
|
+
continue;
|
|
239
|
+
const [fullName = '', shortName = '', commitHash = '', upstream = '', head = ''] = line.split('\0');
|
|
240
|
+
if (shortName === '')
|
|
241
|
+
continue;
|
|
242
|
+
const remote = fullName.startsWith('refs/remotes/');
|
|
243
|
+
records.push({ fullName, shortName, commitHash, upstream: upstream.trim() || null, current: head.trim() === '*', kind: remote ? 'remote' : 'local', remoteName: remote && shortName.includes('/') ? shortName.split('/')[0] ?? null : null });
|
|
244
|
+
}
|
|
245
|
+
return records;
|
|
246
|
+
}
|
|
247
|
+
/** 解析 git log 的 %D 装饰字段:标签不显示,HEAD -> 分支 记为 head,其余通过 ref 表补全类型。 */
|
|
248
|
+
function parseHistoryRefs(decoration, refByShortName) {
|
|
249
|
+
const refs = [];
|
|
250
|
+
for (const rawToken of decoration.split(',')) {
|
|
251
|
+
const token = rawToken.trim();
|
|
252
|
+
if (token === '' || token.startsWith('tag: '))
|
|
244
253
|
continue;
|
|
245
|
-
|
|
246
|
-
|
|
254
|
+
if (token.startsWith('HEAD -> ')) {
|
|
255
|
+
const name = token.slice('HEAD -> '.length).trim();
|
|
256
|
+
if (name !== '')
|
|
257
|
+
refs.push({ name, kind: 'head', remoteName: null });
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
const record = refByShortName.get(token);
|
|
261
|
+
if (record === undefined || record.shortName.endsWith('/HEAD'))
|
|
262
|
+
continue;
|
|
263
|
+
refs.push({ name: record.shortName, kind: record.kind, remoteName: record.remoteName });
|
|
247
264
|
}
|
|
248
|
-
return
|
|
265
|
+
return refs;
|
|
266
|
+
}
|
|
267
|
+
async function readBranches(root) {
|
|
268
|
+
const currentBranch = (await runGit(root, ['branch', '--show-current'])).stdout.trim() || 'HEAD';
|
|
269
|
+
const records = await readRefRecords(root);
|
|
270
|
+
const toItem = (record) => ({ name: record.shortName, current: record.current, upstream: record.upstream, remote: record.kind === 'remote' });
|
|
271
|
+
return { currentBranch, local: records.filter((record) => record.kind === 'local').map(toItem), remote: records.filter((record) => record.kind === 'remote').map(toItem) };
|
|
249
272
|
}
|
|
250
273
|
async function switchBranch(root, branchName, create) {
|
|
251
274
|
if (!/^[A-Za-z0-9._/-]+$/u.test(branchName) || branchName.startsWith('-'))
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { CodingNsRpcTable } from '../rpc-table.js';
|
|
2
|
-
import type { SettingsProvider, SettingsScope } from '@deepseek-ai/dsh-settings';
|
|
3
2
|
import type { CodingNsSettings } from '../../shared/contracts/config.js';
|
|
3
|
+
import type { DshHostSettingsProvider, DshHostSettingsScope } from '../../dsh-capabilities/host/config-forms-adapter.js';
|
|
4
4
|
import type { CodingNsNativeSessionBridge } from '../native-session-bridge.js';
|
|
5
5
|
import type { TerminalProcessService } from '../terminal/terminal-process-service.js';
|
|
6
6
|
import type { DebugWorkspaceService } from '../debug.js';
|
|
7
|
+
import type { CodingNsNativeTeamProxy } from '../cli-adapters/native-team-proxy.js';
|
|
7
8
|
export interface CodingNsHostEvents {
|
|
8
9
|
on(name: string, listener: (...args: any[]) => any): unknown;
|
|
9
10
|
}
|
|
@@ -18,9 +19,9 @@ export interface CodingNsHostServices {
|
|
|
18
19
|
/** 当前 DSH 宿主的真实版本;由 Host 入口启动门禁解析并向功能模块传递。 */
|
|
19
20
|
readonly dshVersion?: string;
|
|
20
21
|
/** 持久化设置;测试或嵌入式调用未提供时,局域网映射仍可手动启动。 */
|
|
21
|
-
readonly settings?:
|
|
22
|
+
readonly settings?: DshHostSettingsScope<CodingNsSettings>;
|
|
22
23
|
/** Host 设置提供器,供远程设置 RPC 做版本校验和持久化写入。 */
|
|
23
|
-
readonly settingsProvider?:
|
|
24
|
+
readonly settingsProvider?: DshHostSettingsProvider;
|
|
24
25
|
/** 当前 DSH Web 服务实际监听端口,用于自动定位本机 DSH。 */
|
|
25
26
|
readonly dshWebPort?: number;
|
|
26
27
|
/** DSH 官方生成的一次性 Web 认证 URL;只留在 Host 内部完成 Cookie 交换。 */
|
|
@@ -29,6 +30,8 @@ export interface CodingNsHostServices {
|
|
|
29
30
|
readonly events?: CodingNsHostEvents;
|
|
30
31
|
/** DSH 原生会话桥接;不可用时为 undefined,插件不因此阻断启动。 */
|
|
31
32
|
readonly nativeSessions?: CodingNsNativeSessionBridge;
|
|
33
|
+
/** DSH 0.2 原生 Agent Team Proxy;未安装实验 Team 包时为空。 */
|
|
34
|
+
readonly nativeTeam?: CodingNsNativeTeamProxy;
|
|
32
35
|
/** 终端启动项和 PTY 进程服务;只由 Host RPC 使用。 */
|
|
33
36
|
readonly terminalProcesses?: TerminalProcessService;
|
|
34
37
|
/** Workspace 级调试服务;只使用 Host 解析出的根目录。 */
|
|
@@ -6,12 +6,19 @@ export interface DshIndexInjectionEntry {
|
|
|
6
6
|
readonly [key: string]: unknown;
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
|
-
* 在启动页声明 Host
|
|
9
|
+
* 在启动页声明 Host 所有权,让 ui-settings 对非回环页面(局域网直连、中继)
|
|
10
|
+
* 保持 host 模式,同时保留 Desktop 已经提供的完整 Transport。
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* 页面级 `__DSH_TRANSPORT__` 同时被 Desktop 壳、中继桥和这里写入,因此注入只做
|
|
13
|
+
* 合并写、且只追加脚本行(kind: "script"):
|
|
14
|
+
*
|
|
15
|
+
* - 不追加同名全局行。全局行由 DSH 在文档里直接赋值,一旦排在 Desktop 的行之后
|
|
16
|
+
* 就会整体覆盖 Transport,丢掉 `streamBaseUrl`,让 `/api/remote.mux` 退回
|
|
17
|
+
* `ws://app` 并卡住插件页。
|
|
18
|
+
* - 脚本行在客户端启动前执行:已有 Transport 时只补 `ownsHost`,没有 Transport
|
|
19
|
+
* 且页面也不是 Desktop(`dshDesktopBoot` 未定义,Desktop 自己声明所有权)时
|
|
20
|
+
* 才创建 `{ ownsHost: true }`。
|
|
21
|
+
* - 未知形状(例如 Desktop 托管的字符串标记)保持原样,不被覆盖。
|
|
15
22
|
*/
|
|
16
23
|
export declare function injectDshWebTransportOwnership(table: unknown[]): void;
|
|
17
24
|
//# sourceMappingURL=index-injection.d.ts.map
|
|
@@ -1,20 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 在启动页声明 Host
|
|
2
|
+
* 在启动页声明 Host 所有权,让 ui-settings 对非回环页面(局域网直连、中继)
|
|
3
|
+
* 保持 host 模式,同时保留 Desktop 已经提供的完整 Transport。
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* 页面级 `__DSH_TRANSPORT__` 同时被 Desktop 壳、中继桥和这里写入,因此注入只做
|
|
6
|
+
* 合并写、且只追加脚本行(kind: "script"):
|
|
7
|
+
*
|
|
8
|
+
* - 不追加同名全局行。全局行由 DSH 在文档里直接赋值,一旦排在 Desktop 的行之后
|
|
9
|
+
* 就会整体覆盖 Transport,丢掉 `streamBaseUrl`,让 `/api/remote.mux` 退回
|
|
10
|
+
* `ws://app` 并卡住插件页。
|
|
11
|
+
* - 脚本行在客户端启动前执行:已有 Transport 时只补 `ownsHost`,没有 Transport
|
|
12
|
+
* 且页面也不是 Desktop(`dshDesktopBoot` 未定义,Desktop 自己声明所有权)时
|
|
13
|
+
* 才创建 `{ ownsHost: true }`。
|
|
14
|
+
* - 未知形状(例如 Desktop 托管的字符串标记)保持原样,不被覆盖。
|
|
8
15
|
*/
|
|
9
16
|
export function injectDshWebTransportOwnership(table) {
|
|
10
17
|
const index = table.findIndex((entry) => isTransportInjection(entry));
|
|
11
|
-
if (index
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
if (index >= 0) {
|
|
19
|
+
const entry = table[index];
|
|
20
|
+
if (!isRecord(entry) || !isRecord(entry.value))
|
|
21
|
+
return;
|
|
22
|
+
table[index] = { ...entry, value: { ...entry.value, ownsHost: true } };
|
|
23
|
+
}
|
|
24
|
+
table.push({ kind: 'script', placement: 'head', text: TRANSPORT_OWNERSHIP_SCRIPT });
|
|
17
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* 在客户端读取 Transport 之前补齐 Host 所有权。
|
|
28
|
+
*
|
|
29
|
+
* 只做合并写和“无 Transport 时创建”,不覆盖任何已有字段;`dshDesktopBoot`
|
|
30
|
+
* 存在时交给 Desktop 壳自己的 Transport(它已经带 `ownsHost`)。
|
|
31
|
+
*/
|
|
32
|
+
const TRANSPORT_OWNERSHIP_SCRIPT = [
|
|
33
|
+
'(function(){',
|
|
34
|
+
'var t=globalThis.__DSH_TRANSPORT__;',
|
|
35
|
+
'if(t===void 0||t===null){if(globalThis.dshDesktopBoot===void 0)globalThis.__DSH_TRANSPORT__={ownsHost:true};return}',
|
|
36
|
+
'if(typeof t==="object")t.ownsHost=true;',
|
|
37
|
+
'})()',
|
|
38
|
+
].join('');
|
|
18
39
|
function isTransportInjection(value) {
|
|
19
40
|
return isRecord(value) && value.name === '__DSH_TRANSPORT__';
|
|
20
41
|
}
|
|
@@ -12,6 +12,7 @@ import { createDshCapabilityRegistry } from '../dsh-capabilities/index.js';
|
|
|
12
12
|
import { debugInfo, debugWarn } from '../shared/debug.js';
|
|
13
13
|
import { repairLegacySessionLogs } from './session-migration-repair.js';
|
|
14
14
|
import { injectDshWebTransportOwnership } from './index-injection.js';
|
|
15
|
+
import { DshNativeTeamProxy } from './cli-adapters/native-team-proxy.js';
|
|
15
16
|
export function apply(ctx) {
|
|
16
17
|
if (ctx === undefined)
|
|
17
18
|
return;
|
|
@@ -53,9 +54,10 @@ export function apply(ctx) {
|
|
|
53
54
|
debugInfo('codingns4dsh: legacy session repair finished', report);
|
|
54
55
|
});
|
|
55
56
|
ctx.inject(['settings', 'connection', 'webServer'], async (hostCtx) => {
|
|
57
|
+
const settingsContext = hostCtx;
|
|
56
58
|
debugInfo('codingns4dsh: host inject ready', {
|
|
57
59
|
hasConnection: hostCtx.connection !== undefined,
|
|
58
|
-
hasSettings:
|
|
60
|
+
hasSettings: settingsContext.settings !== undefined,
|
|
59
61
|
hasWebServer: hostCtx.webServer !== undefined,
|
|
60
62
|
});
|
|
61
63
|
const webServerPort = hostCtx.webServer.port;
|
|
@@ -77,12 +79,12 @@ export function apply(ctx) {
|
|
|
77
79
|
});
|
|
78
80
|
debugInfo('codingns4dsh: host index injection registration ready');
|
|
79
81
|
debugInfo('codingns4dsh: host settings registration begin');
|
|
80
|
-
const settings = registerCodingNsSettings(
|
|
82
|
+
const settings = registerCodingNsSettings(settingsContext);
|
|
81
83
|
debugInfo('codingns4dsh: host settings registered');
|
|
82
84
|
const workspaceRoots = new Map();
|
|
83
85
|
// controller 必须在功能模块和浏览器 Client 开始消费状态前完成装配。
|
|
84
86
|
// 工厂在本次启动只读取一次开关,设置 watcher 不会热切同名 service。
|
|
85
|
-
const terminal = await installTerminalController(hostCtx, settings,
|
|
87
|
+
const terminal = await installTerminalController(hostCtx, settings, settingsContext.settings, {
|
|
86
88
|
resolveWorkspaceRoot: (workspaceId) => workspaceRoots.get(workspaceId) ?? resolveWorkspaceRoot(hostCtx, workspaceId),
|
|
87
89
|
registerWorkspaceRoot: (workspaceId, cwd) => workspaceRoots.set(workspaceId, cwd),
|
|
88
90
|
});
|
|
@@ -91,11 +93,12 @@ export function apply(ctx) {
|
|
|
91
93
|
rpc: new CodingNsRpcTable(),
|
|
92
94
|
dshVersion,
|
|
93
95
|
settings,
|
|
94
|
-
settingsProvider:
|
|
96
|
+
settingsProvider: settingsContext.settings,
|
|
95
97
|
dshWebPort: webServerPort,
|
|
96
98
|
dshWebAuthenticatedUrl: hostCtx.connection.authenticatedUrl(`http://127.0.0.1:${String(webServerPort)}`),
|
|
97
99
|
events: { on: hostCtx.on.bind(hostCtx) },
|
|
98
100
|
nativeSessions: createCodingNsNativeSessionBridge(hostCtx, dshVersion),
|
|
101
|
+
nativeTeam: new DshNativeTeamProxy(readOptionalService(hostCtx, 'agentTeams'), readOptionalService(hostCtx, 'agents')),
|
|
99
102
|
terminalProcesses: terminal.processService,
|
|
100
103
|
resolveWorkspaceRoot: (workspaceId) => workspaceRoots.get(workspaceId) ?? resolveWorkspaceRoot(hostCtx, workspaceId),
|
|
101
104
|
listWorkspaceRoots: () => [...workspaceRoots.values(), ...readWorkspaceRoots(hostCtx)],
|
|
@@ -158,6 +161,14 @@ export function apply(ctx) {
|
|
|
158
161
|
}, 'codingns4dsh: 功能模块启停同步');
|
|
159
162
|
});
|
|
160
163
|
}
|
|
164
|
+
function readOptionalService(ctx, name) {
|
|
165
|
+
try {
|
|
166
|
+
return ctx.get(name);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
161
172
|
function isRecord(value) {
|
|
162
173
|
return typeof value === 'object' && value !== null;
|
|
163
174
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Transform, type TransformCallback } from 'node:stream';
|
|
2
|
-
import type { SettingsScope } from '@deepseek-ai/dsh-settings';
|
|
3
2
|
import type { CodingNsSettings, LanAccessDshLoginSettings, LoginProtectionScopes } from '../shared/contracts/config.js';
|
|
4
3
|
import type { LanAccessDshConfig, LanAccessDshLoginConfig, LanAccessDshSnapshot } from '../shared/contracts/lan-access-dsh.js';
|
|
4
|
+
import type { DshHostSettingsScope } from '../dsh-capabilities/host/config-forms-adapter.js';
|
|
5
5
|
export interface LanAccessDshStream {
|
|
6
6
|
pipe(destination: LanAccessDshStream): LanAccessDshStream;
|
|
7
7
|
destroy(error?: Error): void;
|
|
@@ -112,5 +112,5 @@ export declare class LanAccessDshError extends Error {
|
|
|
112
112
|
constructor(code: 'LAN_ACCESS_DSH_INVALID' | 'DSH_PORT_NOT_FOUND' | 'DSH_PORT_AMBIGUOUS', message: string);
|
|
113
113
|
}
|
|
114
114
|
export declare function normalizeLanAccessDshConfig(value: Partial<LanAccessDshConfig>, allowedListenHosts?: readonly string[]): LanAccessDshConfig;
|
|
115
|
-
export declare function createLanAccessDshRpcHandler(proxy: LanAccessDshProxy, settings?:
|
|
115
|
+
export declare function createLanAccessDshRpcHandler(proxy: LanAccessDshProxy, settings?: DshHostSettingsScope<CodingNsSettings>, loginStore?: LanAccessDshLoginStore): (action: string, payload: unknown) => unknown | Promise<unknown>;
|
|
116
116
|
//# sourceMappingURL=lan-access-dsh.d.ts.map
|
|
@@ -12,6 +12,8 @@ export interface CodingNsNativeSessionStore {
|
|
|
12
12
|
list(): readonly unknown[];
|
|
13
13
|
flush?(session: unknown): Promise<boolean> | Promise<void> | boolean | void;
|
|
14
14
|
}
|
|
15
|
+
/** 插件可安全写入的 DSH Session generation;未来 generation 必须先有专用 adapter。 */
|
|
16
|
+
export type CodingNsNativeSessionFormat = 3 | 4 | 'unknown' | 'unsupported';
|
|
15
17
|
/** 公共 CLI 投影层写入 DSH 的工具调用,不包含任何 Provider 私有字段。 */
|
|
16
18
|
export interface CodingNsNativeToolCall {
|
|
17
19
|
readonly callId: string;
|
|
@@ -123,6 +125,8 @@ export interface CodingNsNativeSessionBridge {
|
|
|
123
125
|
readonly workspaceController?: CodingNsNativeWorkspaceController;
|
|
124
126
|
/** 获取已经进入 DSH 原生 SessionStore 的会话。 */
|
|
125
127
|
get(sessionId: string): unknown | undefined;
|
|
128
|
+
/** 读取 Session header generation,用于阻止未来格式被旧投影器误写。 */
|
|
129
|
+
formatVersion?(sessionId: string): CodingNsNativeSessionFormat;
|
|
126
130
|
/** 获取当前 Host 已装载的原生会话;失败时返回空数组。 */
|
|
127
131
|
list(): readonly unknown[];
|
|
128
132
|
/** 调用 DSH 原生 session/list;无 Controller 时回退到本地 SessionStore。 */
|
|
@@ -23,11 +23,24 @@ export function createCodingNsNativeSessionBridge(ctx, dshVersion) {
|
|
|
23
23
|
// 下一步的工具写进旧 step;step/start 到达后由工具投影器重试。
|
|
24
24
|
const pendingStepTransitions = new Set();
|
|
25
25
|
let injectedStepSequence = 0;
|
|
26
|
-
const
|
|
26
|
+
const modernProducerSource = isModernDshVersion(dshVersion);
|
|
27
|
+
// 当前会话的 generation 比运行时版本号更直接:v4 只接受生产者自持的
|
|
28
|
+
// source.kind,旧的 plugin 包装会被准入拒绝;v3 仍由 dsh-compaction 的
|
|
29
|
+
// plugin 判定识别。会话头缺少版本时(精简 Host 或旧夹具)才退回版本号推断。
|
|
30
|
+
const usesProducerOwnedSource = (session) => {
|
|
31
|
+
const format = sessionFormat(session);
|
|
32
|
+
if (format === 4)
|
|
33
|
+
return true;
|
|
34
|
+
if (format === 3)
|
|
35
|
+
return false;
|
|
36
|
+
return modernProducerSource;
|
|
37
|
+
};
|
|
27
38
|
const appendNativeToolCall = (sessionId, call) => {
|
|
28
39
|
if (on !== undefined && pendingStepTransitions.has(sessionId))
|
|
29
40
|
return null;
|
|
30
41
|
const session = appendableSession(store?.get(sessionId));
|
|
42
|
+
if (sessionFormat(session) === 'unsupported')
|
|
43
|
+
return null;
|
|
31
44
|
const position = session === null ? null : activeStep(session);
|
|
32
45
|
if (session === null || position === null)
|
|
33
46
|
return null;
|
|
@@ -66,7 +79,7 @@ export function createCodingNsNativeSessionBridge(ctx, dshVersion) {
|
|
|
66
79
|
};
|
|
67
80
|
const appendNativeToolResult = (handle, result) => {
|
|
68
81
|
const session = appendableSession(store?.get(handle.sessionId));
|
|
69
|
-
if (session === null)
|
|
82
|
+
if (session === null || sessionFormat(session) === 'unsupported')
|
|
70
83
|
return false;
|
|
71
84
|
const error = result.error?.trim();
|
|
72
85
|
session.append('tool/result', {
|
|
@@ -92,7 +105,7 @@ export function createCodingNsNativeSessionBridge(ctx, dshVersion) {
|
|
|
92
105
|
};
|
|
93
106
|
const appendNativeRequestContext = (sessionId, context) => {
|
|
94
107
|
const session = appendableSession(store?.get(sessionId));
|
|
95
|
-
if (session === null || context.provider.trim() === '' || context.model.trim() === '')
|
|
108
|
+
if (session === null || sessionFormat(session) === 'unsupported' || context.provider.trim() === '' || context.model.trim() === '')
|
|
96
109
|
return false;
|
|
97
110
|
try {
|
|
98
111
|
// request/context 是 token-meter 的容量来源。相同路由重复追加会让
|
|
@@ -174,7 +187,7 @@ export function createCodingNsNativeSessionBridge(ctx, dshVersion) {
|
|
|
174
187
|
const appendNativeUsageSample = (sessionId, usage) => {
|
|
175
188
|
const session = appendableSession(store?.get(sessionId));
|
|
176
189
|
const position = session === null ? null : activeStep(session);
|
|
177
|
-
if (session === null || position === null)
|
|
190
|
+
if (session === null || sessionFormat(session) === 'unsupported' || position === null)
|
|
178
191
|
return false;
|
|
179
192
|
try {
|
|
180
193
|
const previousContext = latestRequestContext(session);
|
|
@@ -209,7 +222,7 @@ export function createCodingNsNativeSessionBridge(ctx, dshVersion) {
|
|
|
209
222
|
};
|
|
210
223
|
const appendNativeCompactionEvent = (sessionId, event) => {
|
|
211
224
|
const session = appendableSession(store?.get(sessionId));
|
|
212
|
-
if (session === null)
|
|
225
|
+
if (session === null || sessionFormat(session) === 'unsupported')
|
|
213
226
|
return false;
|
|
214
227
|
try {
|
|
215
228
|
if (event.phase === 'start') {
|
|
@@ -245,13 +258,13 @@ export function createCodingNsNativeSessionBridge(ctx, dshVersion) {
|
|
|
245
258
|
model: event.model?.trim() || 'external-agent',
|
|
246
259
|
});
|
|
247
260
|
// DSH 前端的 compaction 节点由紧随 summary 的 checkpoint 替换事件生成。
|
|
248
|
-
//
|
|
249
|
-
//
|
|
261
|
+
// v4 的检查点必须携带同一次 compaction/start 的 compactionId,v3 继续
|
|
262
|
+
// 使用该版本 dsh-compaction 识别的 plugin 标记。
|
|
250
263
|
session.append('user/message', {
|
|
251
264
|
id: `codingns-compaction-${state.compactionId}`,
|
|
252
265
|
role: 'user',
|
|
253
266
|
content: [{ type: 'text', text: summary }],
|
|
254
|
-
source:
|
|
267
|
+
source: usesProducerOwnedSource(session)
|
|
255
268
|
? { kind: 'compact-checkpoint', compactionId: state.compactionId }
|
|
256
269
|
: { kind: 'plugin', plugin: 'compact' },
|
|
257
270
|
}, {
|
|
@@ -290,7 +303,7 @@ export function createCodingNsNativeSessionBridge(ctx, dshVersion) {
|
|
|
290
303
|
id: `codingns-external-step-${injectedStepSequence}-${randomUUID()}`,
|
|
291
304
|
role: 'user',
|
|
292
305
|
content: [{ type: 'text', text: boundedSummary }],
|
|
293
|
-
source:
|
|
306
|
+
source: usesProducerOwnedSource(appendableSession(store?.get(sessionId)))
|
|
294
307
|
? { kind: 'model-selection', form: 'notice', summary: boundedSummary }
|
|
295
308
|
: { kind: 'plugin', plugin: 'codingns4dsh', form: 'notice', summary: boundedSummary },
|
|
296
309
|
});
|
|
@@ -316,6 +329,9 @@ export function createCodingNsNativeSessionBridge(ctx, dshVersion) {
|
|
|
316
329
|
get(sessionId) {
|
|
317
330
|
return store?.get(sessionId);
|
|
318
331
|
},
|
|
332
|
+
formatVersion(sessionId) {
|
|
333
|
+
return sessionFormat(appendableSession(store?.get(sessionId)));
|
|
334
|
+
},
|
|
319
335
|
list() {
|
|
320
336
|
try {
|
|
321
337
|
return store?.list() ?? [];
|
|
@@ -491,6 +507,32 @@ function appendableSession(value) {
|
|
|
491
507
|
return null;
|
|
492
508
|
return value;
|
|
493
509
|
}
|
|
510
|
+
/**
|
|
511
|
+
* Session V4 的 header 可能直接暴露 version,也可能包在 data 中;旧测试夹具
|
|
512
|
+
* 没有 header 时返回 unknown,继续保持 0.1.x 的兼容行为。对未来 generation
|
|
513
|
+
* 直接停止插件写入,避免把 V4 envelope 当作未知事件追加后破坏 projection。
|
|
514
|
+
*/
|
|
515
|
+
function sessionFormat(session) {
|
|
516
|
+
if (session === null)
|
|
517
|
+
return 'unknown';
|
|
518
|
+
let events;
|
|
519
|
+
try {
|
|
520
|
+
events = session.snapshotEvents();
|
|
521
|
+
}
|
|
522
|
+
catch {
|
|
523
|
+
return 'unknown';
|
|
524
|
+
}
|
|
525
|
+
const header = events[0];
|
|
526
|
+
if (!isRecord(header))
|
|
527
|
+
return 'unknown';
|
|
528
|
+
const data = isRecord(header.data) ? header.data : undefined;
|
|
529
|
+
const version = finiteInteger(header.version) ?? (data === undefined ? null : finiteInteger(data.version));
|
|
530
|
+
if (version === null)
|
|
531
|
+
return 'unknown';
|
|
532
|
+
if (version === 3 || version === 4)
|
|
533
|
+
return version;
|
|
534
|
+
return version > 4 ? 'unsupported' : 'unknown';
|
|
535
|
+
}
|
|
494
536
|
function isNativeStepStart(value) {
|
|
495
537
|
return isRecord(value) && value.type === 'step/start';
|
|
496
538
|
}
|
|
@@ -11,6 +11,7 @@ import { RTCCertificate, RTCDtlsTransport, RTCPeerConnection } from 'werift';
|
|
|
11
11
|
import { acceptWebRtcHost, createHostSignalingTicketRequest, } from '../transport/webrtc-host.js';
|
|
12
12
|
import { DshGateway } from '../transport/dsh-gateway.js';
|
|
13
13
|
import { createDshTransportDebugLogger } from '../transport/debug.js';
|
|
14
|
+
import { DATA_CHANNEL_MAX_MESSAGE_BYTES } from '../transport/carrier.js';
|
|
14
15
|
/** Relay Tunnel 规定的 DataChannel label,不允许使用插件自定义值。 */
|
|
15
16
|
export const CODINGNS_TUNNEL_DATA_CHANNEL_LABEL = 'codingns-tunnel';
|
|
16
17
|
/** 文件存储适用于 Host 桌面进程;先写临时文件再 rename,避免半截证书。 */
|
|
@@ -227,7 +228,15 @@ function isSignalingOpen(socket) {
|
|
|
227
228
|
export function createWeriftPeerConnectionFactory(identity) {
|
|
228
229
|
return ({ iceServers, iceTransportPolicy }) => {
|
|
229
230
|
const certificate = identity ? new RTCCertificate(identity.privateKeyPem, identity.certPem, identity.signatureHash) : undefined;
|
|
230
|
-
|
|
231
|
+
// 浏览器按对端 SDP 宣告的 max-message-size 限制 RTCDataChannel.send()。werift 默认
|
|
232
|
+
// 只宣告 64 KiB,而 Carrier 的整分片是 64 KiB 正文加 20 字节头,浏览器一旦发送
|
|
233
|
+
// 超过 64 KiB 的消息就会抛错,中继下的原生设置与附件因此失败。
|
|
234
|
+
const peer = new RTCPeerConnection({
|
|
235
|
+
iceServers,
|
|
236
|
+
iceTransportPolicy,
|
|
237
|
+
maxMessageSize: DATA_CHANNEL_MAX_MESSAGE_BYTES,
|
|
238
|
+
...(certificate ? { certificates: [certificate] } : {}),
|
|
239
|
+
});
|
|
231
240
|
const adapted = {
|
|
232
241
|
get connectionState() { return peer.connectionState; },
|
|
233
242
|
get iceConnectionState() { return peer.iceConnectionState; },
|
|
@@ -128,8 +128,16 @@ export function createRemoteWebRuntimeFeature(options) {
|
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
catch (error) {
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
// 稳定错误码 + 请求路径是定位中继设置页空白这类问题的最小信息;
|
|
132
|
+
// 报文正文与凭据仍然不进日志。
|
|
133
|
+
const errorCode = error instanceof RemoteWebRuntimeError ? error.code : 'WEB_RUNTIME_FAILED';
|
|
134
|
+
debug.log('web.request.error', {
|
|
135
|
+
operation,
|
|
136
|
+
streamId: context.envelope.streamId,
|
|
137
|
+
errorCode,
|
|
138
|
+
...(isRecord(request) && typeof request.path === 'string' ? { path: request.path } : {}),
|
|
139
|
+
});
|
|
140
|
+
context.send('stream.error', { errorCode, detail: error instanceof Error ? error.message : 'DSH Web Runtime 失败', retryable: false });
|
|
133
141
|
}
|
|
134
142
|
finally {
|
|
135
143
|
if (operation !== 'web.ws.open') {
|
|
@@ -350,19 +358,30 @@ export function createLocalDshWebRuntimeProvider(options) {
|
|
|
350
358
|
},
|
|
351
359
|
async getPluginBundle(session, _pluginId, path) { return this.getAsset(session, path); },
|
|
352
360
|
async request(session, input) {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
361
|
+
const method = input.method ?? 'GET';
|
|
362
|
+
let path = input.path;
|
|
363
|
+
try {
|
|
364
|
+
ensureSession(sessions, session);
|
|
365
|
+
path = normalizePath(input.path);
|
|
366
|
+
if (!allowed.some((prefix) => path === prefix || path.startsWith(prefix)))
|
|
367
|
+
throw new RemoteWebRuntimeError('WEB_PATH_NOT_ALLOWED', 'DSH Web 请求路径不在白名单内');
|
|
368
|
+
const init = { method };
|
|
369
|
+
if (input.headers !== undefined)
|
|
370
|
+
init.headers = Object.fromEntries(input.headers);
|
|
371
|
+
if (input.body !== undefined)
|
|
372
|
+
init.body = normalizeConnectionRpcBody(path, method, input.body);
|
|
373
|
+
await ensureAuthenticated();
|
|
374
|
+
const response = await fetchLocal(path, init);
|
|
375
|
+
debug.log('web.provider.request.response', { sessionId: session.sessionId, path, method, status: response.status });
|
|
376
|
+
return { status: response.status, headers: [...response.headers.entries()], body: await response.text() };
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
// 失败原因只以稳定错误码进入日志:路径和状态足够定位是哪类请求,
|
|
380
|
+
// 具体报文仍然不进日志。
|
|
381
|
+
const errorCode = error instanceof RemoteWebRuntimeError ? error.code : 'WEB_REQUEST_FAILED';
|
|
382
|
+
debug.log('web.provider.request.error', { sessionId: safeSessionId(session), path, method, errorCode });
|
|
383
|
+
throw error instanceof RemoteWebRuntimeError ? error : new RemoteWebRuntimeError(errorCode, error instanceof Error ? error.message : String(error));
|
|
384
|
+
}
|
|
366
385
|
},
|
|
367
386
|
async openWebSocket(session, path) {
|
|
368
387
|
ensureSession(sessions, session);
|
|
@@ -527,11 +546,15 @@ async function readAsset(response) {
|
|
|
527
546
|
}
|
|
528
547
|
function ensureSession(sessions, session) {
|
|
529
548
|
if (sessions.get(session.sessionId) !== session)
|
|
530
|
-
throw new
|
|
549
|
+
throw new RemoteWebRuntimeError('WEB_SESSION_NOT_FOUND', 'DSH Web Session 已失效');
|
|
550
|
+
}
|
|
551
|
+
/** 会话可能已经不在 map 里(例如 Host 重启或旧 iframe 的收尾请求),日志取不到 id 也要能记录。 */
|
|
552
|
+
function safeSessionId(session) {
|
|
553
|
+
return session === undefined ? undefined : session.sessionId;
|
|
531
554
|
}
|
|
532
555
|
function normalizePath(value) {
|
|
533
556
|
if (!value.startsWith('/') || value.includes('\\') || value.split('/').some((part) => part === '..'))
|
|
534
|
-
throw new
|
|
557
|
+
throw new RemoteWebRuntimeError('WEB_PATH_INVALID', 'DSH Web 路径无效');
|
|
535
558
|
return value;
|
|
536
559
|
}
|
|
537
560
|
function toBytes(value) {
|
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
-
import type { ConnectionRpcHandler } from '@deepseek-ai/dsh-client-connection';
|
|
3
|
-
import type { SettingsProvider } from '@deepseek-ai/dsh-settings';
|
|
4
2
|
import { type CodingNsRpcHandler, type CodingNsRpcTable } from './rpc-table.js';
|
|
3
|
+
import type { DshHostSettingsProvider } from '../dsh-capabilities/host/config-forms-adapter.js';
|
|
4
|
+
/** 0.2 Connection handler 的 Peer 参数;旧版 handler 仍可通过可选参数调用。 */
|
|
5
|
+
/** CodingNS 自有的 Connection RPC 结果契约,避免绑定 DSH 具体导出名称。 */
|
|
6
|
+
type CodingNsConnectionRpcResult<T = unknown> = {
|
|
7
|
+
readonly ok: true;
|
|
8
|
+
readonly value: T;
|
|
9
|
+
readonly attachments?: readonly CodingNsConnectionRpcAttachment[];
|
|
10
|
+
} | {
|
|
11
|
+
readonly ok: false;
|
|
12
|
+
readonly error: {
|
|
13
|
+
readonly code: string;
|
|
14
|
+
readonly message: string;
|
|
15
|
+
readonly details: object;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
interface CodingNsConnectionRpcAttachment {
|
|
19
|
+
readonly path: readonly (string | number)[];
|
|
20
|
+
readonly bytes: Uint8Array;
|
|
21
|
+
}
|
|
22
|
+
type CodingNsConnectionRpcHandler = (endpoint: string, payload: unknown, signal: AbortSignal, peer?: unknown) => Promise<CodingNsConnectionRpcResult<unknown>>;
|
|
5
23
|
/**
|
|
6
24
|
* 创建 Codingns4DSH Host RPC 主处理器。
|
|
7
25
|
*
|
|
@@ -10,9 +28,10 @@ import { type CodingNsRpcHandler, type CodingNsRpcTable } from './rpc-table.js';
|
|
|
10
28
|
* 之间的唯一边界:密码只在一次 RPC 请求中经过 Host,refresh token 只进入 Host
|
|
11
29
|
* 凭据存储。
|
|
12
30
|
*/
|
|
13
|
-
export declare function createCodingNsRpcHandler(table: CodingNsRpcTable):
|
|
31
|
+
export declare function createCodingNsRpcHandler(table: CodingNsRpcTable): CodingNsConnectionRpcHandler;
|
|
14
32
|
/** 在当前 Connection 上挂载 Codingns4DSH RPC 主处理器;注销由调用方的 effect 负责。 */
|
|
15
|
-
export declare function registerCodingNsRpc(ctx: Context, table: CodingNsRpcTable, settingsProvider?:
|
|
33
|
+
export declare function registerCodingNsRpc(ctx: Context, table: CodingNsRpcTable, settingsProvider?: DshHostSettingsProvider): void;
|
|
16
34
|
/** 创建远程设置处理器;只允许 Codingns4DSH 自己的 namespace 和路径编辑。 */
|
|
17
|
-
export declare function createCodingNsSettingsRpcHandler(provider:
|
|
35
|
+
export declare function createCodingNsSettingsRpcHandler(provider: DshHostSettingsProvider): CodingNsRpcHandler;
|
|
36
|
+
export {};
|
|
18
37
|
//# sourceMappingURL=rpc.d.ts.map
|