@ai-workbench/connect 0.0.0-stage → 0.7.6
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/NOTICE.md +18 -0
- package/README.md +26 -2
- package/client/index.cjs +124 -0
- package/cordis.patch.yml +3 -0
- package/helper/auth-status.cjs +18 -0
- package/helper/claude-callback.cjs +29 -0
- package/helper/main.cjs +19 -0
- package/helper/projects.cjs +57 -0
- package/helper/remote-command.cjs +15 -0
- package/helper/remote-login.cjs +25 -0
- package/helper/window.cjs +52 -0
- package/lib/api.d.ts +196 -0
- package/lib/api.js +1 -0
- package/lib/archive.d.ts +1 -0
- package/lib/archive.js +63 -0
- package/lib/auth-status.d.ts +3 -0
- package/lib/auth-status.js +18 -0
- package/lib/cache.d.ts +21 -0
- package/lib/cache.js +156 -0
- package/lib/component-download.d.ts +32 -0
- package/lib/component-download.js +76 -0
- package/lib/component-source.d.ts +52 -0
- package/lib/component-source.js +146 -0
- package/lib/components.d.ts +10 -0
- package/lib/components.js +154 -0
- package/lib/connect.d.ts +10 -0
- package/lib/connect.js +246 -0
- package/lib/cordis.d.ts +15 -0
- package/lib/cordis.js +1 -0
- package/lib/defaults.d.ts +30 -0
- package/lib/defaults.js +4 -0
- package/lib/electron.d.ts +8 -0
- package/lib/electron.js +83 -0
- package/lib/enrollment.d.ts +3 -0
- package/lib/enrollment.js +78 -0
- package/lib/index.d.ts +42 -0
- package/lib/index.js +55 -0
- package/lib/integration.d.ts +6 -0
- package/lib/integration.js +56 -0
- package/lib/login.d.ts +35 -0
- package/lib/login.js +81 -0
- package/lib/native-import-picker.d.ts +11 -0
- package/lib/native-import-picker.js +49 -0
- package/lib/needs.d.ts +9 -0
- package/lib/needs.js +52 -0
- package/lib/network.d.ts +26 -0
- package/lib/network.js +226 -0
- package/lib/physical-fs.d.ts +3 -0
- package/lib/physical-fs.js +7 -0
- package/lib/ports.d.ts +43 -0
- package/lib/ports.js +1 -0
- package/lib/private-files.d.ts +3 -0
- package/lib/private-files.js +41 -0
- package/lib/processes.d.ts +5 -0
- package/lib/processes.js +147 -0
- package/lib/profile-activation.d.ts +32 -0
- package/lib/profile-activation.js +118 -0
- package/lib/profile-enrollment.d.ts +21 -0
- package/lib/profile-enrollment.js +275 -0
- package/lib/profile-runtime.d.ts +6 -0
- package/lib/profile-runtime.js +47 -0
- package/lib/profile-settings.d.ts +2 -0
- package/lib/profile-settings.js +13 -0
- package/lib/projects.d.ts +4 -0
- package/lib/projects.js +41 -0
- package/lib/provider.d.ts +73 -0
- package/lib/provider.js +39 -0
- package/lib/remote-login.d.ts +2 -0
- package/lib/remote-login.js +9 -0
- package/lib/ssh.d.ts +10 -0
- package/lib/ssh.js +78 -0
- package/lib/startup.d.ts +18 -0
- package/lib/startup.js +211 -0
- package/lib/workspace-bindings.d.ts +32 -0
- package/lib/workspace-bindings.js +328 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/node_modules/argparse/LICENSE +254 -0
- package/node_modules/argparse/README.md +84 -0
- package/node_modules/argparse/argparse.js +3707 -0
- package/node_modules/argparse/lib/sub.js +67 -0
- package/node_modules/argparse/lib/textwrap.js +440 -0
- package/node_modules/argparse/package.json +31 -0
- package/node_modules/js-yaml/LICENSE +21 -0
- package/node_modules/js-yaml/README.md +233 -0
- package/node_modules/js-yaml/bin/js-yaml.js +117 -0
- package/node_modules/js-yaml/dist/js-yaml.js +3022 -0
- package/node_modules/js-yaml/dist/js-yaml.js.map +1 -0
- package/node_modules/js-yaml/dist/js-yaml.min.js +2 -0
- package/node_modules/js-yaml/dist/js-yaml.min.js.map +1 -0
- package/node_modules/js-yaml/dist/js-yaml.mjs +3082 -0
- package/node_modules/js-yaml/dist/js-yaml.mjs.map +1 -0
- package/node_modules/js-yaml/index.js +44 -0
- package/node_modules/js-yaml/lib/common.js +50 -0
- package/node_modules/js-yaml/lib/dumper.js +937 -0
- package/node_modules/js-yaml/lib/exception.js +51 -0
- package/node_modules/js-yaml/lib/index_vite_proxy.tmp.mjs +37 -0
- package/node_modules/js-yaml/lib/loader.js +1803 -0
- package/node_modules/js-yaml/lib/schema/core.js +9 -0
- package/node_modules/js-yaml/lib/schema/default.js +20 -0
- package/node_modules/js-yaml/lib/schema/failsafe.js +14 -0
- package/node_modules/js-yaml/lib/schema/json.js +17 -0
- package/node_modules/js-yaml/lib/schema.js +109 -0
- package/node_modules/js-yaml/lib/snippet.js +96 -0
- package/node_modules/js-yaml/lib/type/binary.js +122 -0
- package/node_modules/js-yaml/lib/type/bool.js +35 -0
- package/node_modules/js-yaml/lib/type/float.js +99 -0
- package/node_modules/js-yaml/lib/type/int.js +142 -0
- package/node_modules/js-yaml/lib/type/map.js +8 -0
- package/node_modules/js-yaml/lib/type/merge.js +12 -0
- package/node_modules/js-yaml/lib/type/null.js +35 -0
- package/node_modules/js-yaml/lib/type/omap.js +45 -0
- package/node_modules/js-yaml/lib/type/pairs.js +50 -0
- package/node_modules/js-yaml/lib/type/seq.js +8 -0
- package/node_modules/js-yaml/lib/type/set.js +29 -0
- package/node_modules/js-yaml/lib/type/str.js +8 -0
- package/node_modules/js-yaml/lib/type/timestamp.js +88 -0
- package/node_modules/js-yaml/lib/type.js +66 -0
- package/node_modules/js-yaml/package.json +83 -0
- package/node_modules/pend/LICENSE +23 -0
- package/node_modules/pend/README.md +41 -0
- package/node_modules/pend/index.js +55 -0
- package/node_modules/pend/package.json +18 -0
- package/node_modules/pend/test.js +137 -0
- package/node_modules/yauzl/LICENSE +21 -0
- package/node_modules/yauzl/README.md +1015 -0
- package/node_modules/yauzl/crc32.js +45 -0
- package/node_modules/yauzl/fd-slicer.js +188 -0
- package/node_modules/yauzl/index.js +1096 -0
- package/node_modules/yauzl/package.json +40 -0
- package/package.json +89 -4
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import z from '@deepseek-ai/schemastery';
|
|
3
|
+
export type * from './cordis.ts';
|
|
4
|
+
import type { Ports } from './ports.ts';
|
|
5
|
+
import { type ComponentInventory } from './components.ts';
|
|
6
|
+
import type { NetworkSettings } from './network.ts';
|
|
7
|
+
import type { AssemblySettings } from './profile-activation.ts';
|
|
8
|
+
export { writeNetworkConfig, createNetworkClient } from './network.ts';
|
|
9
|
+
export type * from './api.ts';
|
|
10
|
+
export { createConnect } from './connect.ts';
|
|
11
|
+
export { sshArgs } from './ssh.ts';
|
|
12
|
+
export declare const name = "ai-workbench-connect";
|
|
13
|
+
export declare const inject: string[];
|
|
14
|
+
export interface Config {
|
|
15
|
+
inventory?: ComponentInventory;
|
|
16
|
+
enrollmentMetadataMaxBytes?: number;
|
|
17
|
+
enrollmentPackageDir?: string;
|
|
18
|
+
trustDir?: string;
|
|
19
|
+
commandOutputMaxBytes?: number;
|
|
20
|
+
network?: (NetworkSettings & AssemblySettings);
|
|
21
|
+
helper?: {
|
|
22
|
+
componentDownloadSource?: 'github' | 'npmmirror';
|
|
23
|
+
componentObservationIntervalMs?: number;
|
|
24
|
+
componentDownloadMaxRedirects?: number;
|
|
25
|
+
remoteKillGraceMs?: number;
|
|
26
|
+
maxRedirects?: number;
|
|
27
|
+
source?: string;
|
|
28
|
+
sha256?: string;
|
|
29
|
+
cacheDir?: string;
|
|
30
|
+
maxDownloadBytes?: number;
|
|
31
|
+
maxExtractedBytes?: number;
|
|
32
|
+
executable?: string;
|
|
33
|
+
loginOutputMaxBytes?: number;
|
|
34
|
+
ipcMaxBytes?: number;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export declare const Config: z<Config>;
|
|
38
|
+
export declare function register(ctx: Context, ports: Ports, inventory?: ComponentInventory, extend?: (api: import('./api.ts').ConnectApi) => void): void;
|
|
39
|
+
export declare function apply(ctx: Context, config?: Config): void;
|
|
40
|
+
export { createProfileEnrollment } from './profile-enrollment.ts';
|
|
41
|
+
export { createWorkspaceBindings } from './workspace-bindings.ts';
|
|
42
|
+
export { profileSettings } from './profile-settings.ts';
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import z from '@deepseek-ai/schemastery';
|
|
2
|
+
import { createConnect } from "./connect.js";
|
|
3
|
+
import { configuredPorts } from "./integration.js";
|
|
4
|
+
import { attachProfileRuntime } from "./profile-runtime.js";
|
|
5
|
+
import { profileSettings } from "./profile-settings.js";
|
|
6
|
+
import { createComponentSources, DEFAULT_SOURCE_REASON } from "./component-source.js";
|
|
7
|
+
import { createComponentProgress } from "./component-download.js";
|
|
8
|
+
import { L_DEFAULTS, NETWORK_DEFAULTS } from "./defaults.js";
|
|
9
|
+
import { registerComponents } from "./components.js";
|
|
10
|
+
export { writeNetworkConfig, createNetworkClient } from "./network.js";
|
|
11
|
+
export { createConnect } from "./connect.js";
|
|
12
|
+
export { sshArgs } from "./ssh.js";
|
|
13
|
+
export const name = 'ai-workbench-connect';
|
|
14
|
+
export const inject = ['aiWorkbenchNetwork'];
|
|
15
|
+
const positive = () => z.number().step(1).min(1);
|
|
16
|
+
// Administrator settings are optional in schema so the loaded service can explain missing prerequisites.
|
|
17
|
+
// aiWorkbenchNetwork itself is a required Cordis injection; missing N holds this module from loading.
|
|
18
|
+
// Own technical defaults use the approved settings record; no global DSH policy is changed.
|
|
19
|
+
export const Config = z.object({
|
|
20
|
+
enrollmentMetadataMaxBytes: positive().max(2147483647).default(L_DEFAULTS.enrollmentMetadataMaxBytes).description('开通元数据与工作区关联文件字节上限;默认1MiB,可由管理员修改;不读取key内容'),
|
|
21
|
+
inventory: z.object({ network: z.string(), connect: z.string(), claude: z.string(), codex: z.string() }).description('由bundle真实包版本自动生成,只读交付事实,不是策略默认'),
|
|
22
|
+
enrollmentPackageDir: z.string().description('开通包目录:由安装交付,员工不手填;owner已定稿,交付显式配置'),
|
|
23
|
+
trustDir: z.string().description('主机信任缓存目录;owner已定稿,交付显式配置'),
|
|
24
|
+
commandOutputMaxBytes: positive().default(L_DEFAULTS.commandOutputMaxBytes).description('CLI检查输出内存上限(字节);owner已定1048576,管理员可改'),
|
|
25
|
+
network: z.object({ executable: z.string(), runtimeDir: z.string(), stateDir: z.string(), configFile: z.string(), helperLogFile: z.string(), nodeName: z.string(), startupObservationSeconds: z.number().min(0.001).max(2147483.647).default(NETWORK_DEFAULTS.startupObservationSeconds), shutdownObservationSeconds: z.number().min(0.001).max(2147483.647).default(NETWORK_DEFAULTS.shutdownObservationSeconds), controlResponseMaxBytes: positive().default(NETWORK_DEFAULTS.controlResponseMaxBytes), downloadMaxRedirects: z.number().step(1).min(0).default(NETWORK_DEFAULTS.downloadMaxRedirects), proxyListen: z.string().default(NETWORK_DEFAULTS.proxyListen), logUpload: z.boolean().default(NETWORK_DEFAULTS.logUpload), statusRefreshIntervalSeconds: z.number().min(0.001).max(2147483.647).default(NETWORK_DEFAULTS.statusRefreshIntervalSeconds), dialTimeoutSeconds: z.number().min(1e-9).default(NETWORK_DEFAULTS.dialTimeoutSeconds), headerTimeoutSeconds: z.number().min(0.001).max(2147483.647).default(NETWORK_DEFAULTS.headerTimeoutSeconds), enrollmentObservationSeconds: z.number().min(1e-9).default(NETWORK_DEFAULTS.enrollmentObservationSeconds), httpMaxHeaderBytes: positive().default(NETWORK_DEFAULTS.httpMaxHeaderBytes), httpMaxResponseHeaderBytes: positive().default(NETWORK_DEFAULTS.httpMaxResponseHeaderBytes), controlMaxBytes: positive() }).description('N v1 交接设置;沿owner已定技术值自动装配,管理员可改;路径由当前用户/profile派生,员工不填'),
|
|
26
|
+
helper: z.object({
|
|
27
|
+
componentDownloadSource: z.union([z.const('github'), z.const('npmmirror')]).description('未保存设置节选择时的初始下载源;未设且无旧source时默认npmmirror(编排leo实测可达),可选github;不自动测速/换源,版本与SHA固定'),
|
|
28
|
+
componentObservationIntervalMs: z.number().step(1).min(1).max(2147483647).default(L_DEFAULTS.componentObservationIntervalMs).description('无进展观察刷新间隔毫秒,默认1000;只刷新秒数,不超时/重试/换源'),
|
|
29
|
+
componentDownloadMaxRedirects: z.number().step(1).min(0).default(L_DEFAULTS.componentDownloadMaxRedirects).description('组件直接HTTPS下载的重定向次数上限;默认3,0拒绝重定向;管理员可改,不影响OAuth主机出口'),
|
|
30
|
+
remoteKillGraceMs: z.number().step(1).min(0).max(2147483647).default(L_DEFAULTS.remoteKillGraceMs).description('远端本轮登录进程组结束宽限毫秒;owner已定1000,0立即KILL,管理员可改'),
|
|
31
|
+
maxRedirects: z.number().step(1).min(0).description('兼容旧配置;0.5默认路径改由N插件downloadMaxRedirects控制,此项不再生效'),
|
|
32
|
+
source: z.string().description('兼容初始管理员显式URL;无已保存的设置节选择且未设componentDownloadSource时生效;无隐式换源'), sha256: z.string().description('darwin-arm64默认已定44.0.0下载SHA256;管理员可改,必须校验'),
|
|
33
|
+
cacheDir: z.string().description('默认从当前profile自动派生ai-workbench-connect/login-component-cache;管理员可改,员工不填路径'), maxDownloadBytes: positive().default(L_DEFAULTS.maxDownloadBytes).description('下载字节上限;owner已定200000000,管理员可改'), maxExtractedBytes: positive().default(L_DEFAULTS.maxExtractedBytes).description('解压字节上限;owner已定600000000,管理员可改'),
|
|
34
|
+
executable: z.string().description('darwin-arm64默认Electron.app/Contents/MacOS/Electron;其他平台无默认,管理员可改'), loginOutputMaxBytes: positive().default(L_DEFAULTS.loginOutputMaxBytes).description('登录进程输出字节上限;owner已定1048576,管理员可改'), ipcMaxBytes: positive().default(L_DEFAULTS.ipcMaxBytes).description('私有IPC字节上限;owner已定1048576,管理员可改'),
|
|
35
|
+
}),
|
|
36
|
+
});
|
|
37
|
+
export function register(ctx, ports, inventory, extend) {
|
|
38
|
+
const api = createConnect(ports, state => ctx.emit('ai-workbench/connect-state', state), { providers: inventory && !inventory.codex ? ['claude'] : ['codex', 'claude'], onConnectionState: state => ctx.emit('ai-workbench/connection-state', state), onPromptState: state => ctx.emit('ai-workbench/login-prompt-state', state), onPromptResult: result => ctx.emit('ai-workbench/login-prompt-result', result) });
|
|
39
|
+
extend?.(api);
|
|
40
|
+
ctx.provide('aiWorkbenchConnect', api);
|
|
41
|
+
ctx.effect(() => { void api.refreshLoginPrompts(); return () => api.dispose(); });
|
|
42
|
+
}
|
|
43
|
+
export function apply(ctx, config = {}) {
|
|
44
|
+
const sourceReason = config.helper?.componentDownloadSource !== undefined ? '管理员通过 helper.componentDownloadSource 指定此下载源。' : config.helper?.source !== undefined ? '管理员通过 helper.source 指定此下载源。' : DEFAULT_SOURCE_REASON;
|
|
45
|
+
config = profileSettings(config, ctx.get('profileContext')?.dir);
|
|
46
|
+
const network = ctx.get('aiWorkbenchNetwork'), component = createComponentProgress();
|
|
47
|
+
const sources = createComponentSources({ profileDir: ctx.get('profileContext')?.dir, metadataMaxBytes: config.enrollmentMetadataMaxBytes ?? L_DEFAULTS.enrollmentMetadataMaxBytes, source: config.helper?.source, sha256: config.helper?.sha256, reason: sourceReason, busy: () => ['downloading', 'verifying', 'unpacking'].includes(component.getState().phase) || ctx.get('aiWorkbenchConnect')?.getLoginPromptState().phase === 'authorizing' });
|
|
48
|
+
ctx.effect(() => () => sources.dispose());
|
|
49
|
+
const ports = configuredPorts(config, network, component, sources);
|
|
50
|
+
register(ctx, ports, config.inventory, api => attachProfileRuntime(ctx, api, ports, config, network));
|
|
51
|
+
registerComponents(ctx, config.inventory, config.helper?.cacheDir, component, sources, config.helper?.componentObservationIntervalMs);
|
|
52
|
+
}
|
|
53
|
+
export { createProfileEnrollment } from "./profile-enrollment.js";
|
|
54
|
+
export { createWorkspaceBindings } from "./workspace-bindings.js";
|
|
55
|
+
export { profileSettings } from "./profile-settings.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ComponentSources } from './component-source.ts';
|
|
2
|
+
import type { Config } from './index.ts';
|
|
3
|
+
import type { Ports } from './ports.ts';
|
|
4
|
+
import { type NetworkProvider } from './provider.ts';
|
|
5
|
+
import { type ComponentProgressStore } from './component-download.ts';
|
|
6
|
+
export declare function configuredPorts(config: Config, provider?: NetworkProvider, component?: ComponentProgressStore, sources?: ComponentSources): Ports;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ComponentDownloadError } from "./component-download.js";
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { readEnrollment } from "./enrollment.js";
|
|
4
|
+
import { adaptNetwork } from "./provider.js";
|
|
5
|
+
import { createRunner, createRemoteLogin } from "./processes.js";
|
|
6
|
+
import { createSsh, sshArgs } from "./ssh.js";
|
|
7
|
+
import { createProjects } from "./projects.js";
|
|
8
|
+
import { remoteLoginCommand } from "./remote-login.js";
|
|
9
|
+
import { runLogin } from "./login.js";
|
|
10
|
+
import { ensureHelper } from "./cache.js";
|
|
11
|
+
import { launchElectron } from "./electron.js";
|
|
12
|
+
import { need, fail } from "./needs.js";
|
|
13
|
+
import { createComponentProgress, downloadComponent } from "./component-download.js";
|
|
14
|
+
export function configuredPorts(config, provider, component = createComponentProgress(), sources) {
|
|
15
|
+
const ports = {};
|
|
16
|
+
const network = provider ? adaptNetwork(provider) : undefined;
|
|
17
|
+
if (config.enrollmentPackageDir && config.trustDir)
|
|
18
|
+
ports.enrollment = { read: () => readEnrollment(config.enrollmentPackageDir, config.trustDir) };
|
|
19
|
+
else if (provider)
|
|
20
|
+
ports.enrollment = { read: async () => fail('SETTINGS_REQUIRED') };
|
|
21
|
+
if (config.commandOutputMaxBytes !== undefined) {
|
|
22
|
+
const runner = createRunner(config.commandOutputMaxBytes);
|
|
23
|
+
ports.ssh = createSsh(runner, config.commandOutputMaxBytes);
|
|
24
|
+
ports.projects = createProjects(runner);
|
|
25
|
+
}
|
|
26
|
+
else if (provider)
|
|
27
|
+
ports.ssh = { verify: async () => fail('SETTINGS_REQUIRED'), status: async () => fail('SETTINGS_REQUIRED') };
|
|
28
|
+
if (network) {
|
|
29
|
+
ports.network = network;
|
|
30
|
+
ports.login = { run: async (target, provider, signal) => {
|
|
31
|
+
const h = config.helper;
|
|
32
|
+
if (!h || !Number.isSafeInteger(h.remoteKillGraceMs) || h.remoteKillGraceMs < 0 || h.remoteKillGraceMs > 2147483647 || ![h.source, h.sha256, h.cacheDir, h.executable].every(x => typeof x === 'string' && x) || !Number.isSafeInteger(h.componentDownloadMaxRedirects) || h.componentDownloadMaxRedirects < 0 || ![h.maxDownloadBytes, h.maxExtractedBytes, h.loginOutputMaxBytes, h.ipcMaxBytes].every(x => Number.isSafeInteger(x) && x > 0))
|
|
33
|
+
return { ok: false, error: need('SETTINGS_REQUIRED') };
|
|
34
|
+
let selection;
|
|
35
|
+
try {
|
|
36
|
+
selection = await sources?.resolve();
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
return { ok: false, error: { ...need('SETTINGS_REQUIRED'), officialReason: e instanceof ComponentDownloadError ? e.message : 'COMPONENT_SOURCE_STATE_INVALID' } };
|
|
40
|
+
}
|
|
41
|
+
const cancellation = new AbortController(), downloadSignal = AbortSignal.any([signal, cancellation.signal]);
|
|
42
|
+
const attempt = component.begin(selection, () => cancellation.abort());
|
|
43
|
+
const result = await ensureHelper({ ...h, source: selection?.source ?? h.source }, { download: source => downloadComponent(source, downloadSignal, h.componentDownloadMaxRedirects), signal: downloadSignal, onProgress: attempt.update });
|
|
44
|
+
if (!result.ok)
|
|
45
|
+
return result;
|
|
46
|
+
if (signal.aborted)
|
|
47
|
+
return fail('LOGIN_FAILED');
|
|
48
|
+
return runLogin(target, provider, {
|
|
49
|
+
start: (t, p, s) => createRemoteLogin('ssh', [...sshArgs(t), remoteLoginCommand(p, h.remoteKillGraceMs)], p, h.loginOutputMaxBytes, s, 'remote-control'),
|
|
50
|
+
acquireProxy: (_t, s) => network.acquireProxy(s),
|
|
51
|
+
open: (boot, onCode, s) => launchElectron({ executable: result.value, entry: fileURLToPath(new URL('../helper/main.cjs', import.meta.url)), cacheDir: h.cacheDir, ipcMaxBytes: h.ipcMaxBytes }, boot, onCode, s),
|
|
52
|
+
}, signal);
|
|
53
|
+
} };
|
|
54
|
+
}
|
|
55
|
+
return ports;
|
|
56
|
+
}
|
package/lib/login.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Provider, Result, SshTarget } from './api.ts';
|
|
2
|
+
export interface ProxyLease {
|
|
3
|
+
url: string;
|
|
4
|
+
username: string;
|
|
5
|
+
password: string;
|
|
6
|
+
closed?: Promise<void>;
|
|
7
|
+
release(): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
export interface LoginBoot {
|
|
10
|
+
provider: Provider;
|
|
11
|
+
url: string;
|
|
12
|
+
proxy: Pick<ProxyLease, 'url' | 'username' | 'password'>;
|
|
13
|
+
}
|
|
14
|
+
export interface RemoteLogin {
|
|
15
|
+
ownership?: Promise<{
|
|
16
|
+
pid: number;
|
|
17
|
+
group: number;
|
|
18
|
+
provider: string;
|
|
19
|
+
}>;
|
|
20
|
+
url: Promise<Result<string>>;
|
|
21
|
+
completion: Promise<Result<void>>;
|
|
22
|
+
writeCode(code: string): Promise<void>;
|
|
23
|
+
close(): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
export interface LoginWindow {
|
|
26
|
+
closed: Promise<void>;
|
|
27
|
+
close(): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
export interface LoginPorts {
|
|
30
|
+
start(t: SshTarget, p: Provider, s: AbortSignal): Promise<RemoteLogin>;
|
|
31
|
+
acquireProxy(t: SshTarget, s: AbortSignal): Promise<ProxyLease>;
|
|
32
|
+
open(boot: LoginBoot, onCode: (code: string) => Promise<void>, s: AbortSignal): Promise<LoginWindow>;
|
|
33
|
+
}
|
|
34
|
+
export declare function extractLoginUrl(provider: Provider, text: string): string | undefined;
|
|
35
|
+
export declare function runLogin(t: SshTarget, p: Provider, ports: LoginPorts, parentSignal: AbortSignal): Promise<Result<void>>;
|
package/lib/login.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
const { isClaudeAuthorizeUrl, validateClaudePaste } = createRequire(import.meta.url)('../helper/claude-callback.cjs');
|
|
3
|
+
import { fail, NeedError, need } from "./needs.js";
|
|
4
|
+
export function extractLoginUrl(provider, text) {
|
|
5
|
+
for (const match of text.matchAll(/https:\/\/[^\s<>"']+/g)) {
|
|
6
|
+
try {
|
|
7
|
+
const u = new URL(match[0]);
|
|
8
|
+
if (!u.username && !u.password && u.protocol === 'https:' && ((provider === 'codex' && u.origin === 'https://auth.openai.com' && u.pathname === '/oauth/authorize') || (provider === 'claude' && isClaudeAuthorizeUrl(u))))
|
|
9
|
+
return u.href;
|
|
10
|
+
}
|
|
11
|
+
catch { /* not a complete URL yet */ }
|
|
12
|
+
}
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
export async function runLogin(t, p, ports, parentSignal) {
|
|
16
|
+
const controller = new AbortController(), signal = controller.signal;
|
|
17
|
+
const parentAbort = () => controller.abort();
|
|
18
|
+
parentSignal.addEventListener('abort', parentAbort, { once: true });
|
|
19
|
+
if (parentSignal.aborted)
|
|
20
|
+
controller.abort();
|
|
21
|
+
let remote, lease, window, remove = () => { };
|
|
22
|
+
let result = fail('LOGIN_FAILED'), leaseLost = false, releasing = false;
|
|
23
|
+
try {
|
|
24
|
+
if (signal.aborted)
|
|
25
|
+
return result;
|
|
26
|
+
const cancelled = new Promise(resolve => { const abort = () => resolve(fail('LOGIN_FAILED')); signal.addEventListener('abort', abort, { once: true }); remove = () => signal.removeEventListener('abort', abort); });
|
|
27
|
+
remote = await ports.start(t, p, signal);
|
|
28
|
+
const url = await Promise.race([remote.url, cancelled]);
|
|
29
|
+
if (!url.ok)
|
|
30
|
+
return url;
|
|
31
|
+
if (typeof url.value !== 'string' || extractLoginUrl(p, url.value) !== url.value)
|
|
32
|
+
return result;
|
|
33
|
+
lease = await ports.acquireProxy(t, signal);
|
|
34
|
+
const lost = () => { if (!releasing) {
|
|
35
|
+
leaseLost = true;
|
|
36
|
+
controller.abort();
|
|
37
|
+
} };
|
|
38
|
+
void lease.closed?.then(lost, lost);
|
|
39
|
+
if (signal.aborted)
|
|
40
|
+
throw new NeedError(need(leaseLost ? 'CONNECTION_LOST' : 'LOGIN_FAILED'));
|
|
41
|
+
let codeSent = false, codeError = () => { };
|
|
42
|
+
const failedCode = new Promise(r => codeError = r);
|
|
43
|
+
window = await ports.open({ provider: p, url: url.value, proxy: { url: lease.url, username: lease.username, password: lease.password } }, async (code) => {
|
|
44
|
+
if (p !== 'claude' || codeSent || signal.aborted || !validateClaudePaste(url.value, code)) {
|
|
45
|
+
codeError(fail('LOGIN_FAILED'));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
codeSent = true;
|
|
49
|
+
try {
|
|
50
|
+
await remote.writeCode(code);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
codeError(fail('LOGIN_FAILED'));
|
|
54
|
+
}
|
|
55
|
+
}, signal);
|
|
56
|
+
result = await Promise.race([remote.completion, window.closed.then(() => fail('LOGIN_FAILED')), failedCode, cancelled]);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
result = error instanceof NeedError ? { ok: false, error: error.need } : fail('LOGIN_FAILED');
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
remove();
|
|
63
|
+
parentSignal.removeEventListener('abort', parentAbort);
|
|
64
|
+
// Attempt every cleanup even when one resource fails; surface failure instead of success.
|
|
65
|
+
const cleanup = await Promise.allSettled([window?.close(), remote?.close()]);
|
|
66
|
+
if (cleanup.some(r => r.status === 'rejected'))
|
|
67
|
+
result = fail('LOGIN_FAILED');
|
|
68
|
+
releasing = true;
|
|
69
|
+
if (lease) {
|
|
70
|
+
try {
|
|
71
|
+
await lease.release();
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
result = error instanceof NeedError ? { ok: false, error: error.need } : fail('LOGIN_FAILED');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (leaseLost)
|
|
78
|
+
result = fail('CONNECTION_LOST');
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface DirectoryPicker {
|
|
2
|
+
capability(): {
|
|
3
|
+
kind: string;
|
|
4
|
+
pick?: (signal: AbortSignal) => Promise<string | null>;
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
interface NativeExports {
|
|
8
|
+
pickNativeDirectory?: (signal: AbortSignal) => Promise<string | null>;
|
|
9
|
+
}
|
|
10
|
+
export declare function createNativeImportPicker(service: () => DirectoryPicker | undefined, loadNative?: () => Promise<NativeExports>): (signal: AbortSignal) => Promise<string | null>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
import { NeedError, need } from "./needs.js";
|
|
10
|
+
const officialModule = '@deepseek-ai/dsh-host-directory-picker-native';
|
|
11
|
+
const loadOfficial = async () => import(__rewriteRelativeImportExtension(officialModule));
|
|
12
|
+
const unavailable = (requires, officialReason) => new NeedError({
|
|
13
|
+
...need('DEPENDENCY_REQUIRED'), requires, ...(officialReason ? { officialReason } : {}),
|
|
14
|
+
});
|
|
15
|
+
// L的导入入口固定使用系统目录窗口,不修改DSH自动选择器或工作区流程。
|
|
16
|
+
// 本地原生能力直接消费;SSH启动等上下文选择browse时,调用同一官方
|
|
17
|
+
// 原生后端的公开函数。该分支在调用前确定,失败后绝不换后端或重试。
|
|
18
|
+
export function createNativeImportPicker(service, loadNative = loadOfficial) {
|
|
19
|
+
return async (signal) => {
|
|
20
|
+
const capability = service()?.capability();
|
|
21
|
+
let pick;
|
|
22
|
+
if (capability?.kind === 'native') {
|
|
23
|
+
if (typeof capability.pick !== 'function')
|
|
24
|
+
throw unavailable('官方原生目录选择服务的pick方法');
|
|
25
|
+
pick = signal => capability.pick(signal);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
let native;
|
|
29
|
+
try {
|
|
30
|
+
native = await loadNative();
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
throw unavailable('已安装官方原生目录选择组件 ' + officialModule);
|
|
34
|
+
}
|
|
35
|
+
if (typeof native.pickNativeDirectory !== 'function')
|
|
36
|
+
throw unavailable('官方原生目录选择组件的pickNativeDirectory公开导出');
|
|
37
|
+
pick = signal => native.pickNativeDirectory(signal);
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
return await pick(signal);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
const code = error?.code;
|
|
44
|
+
const officialReason = typeof code === 'number' && Number.isSafeInteger(code) ? 'EXIT_' + code :
|
|
45
|
+
typeof code === 'string' && ['EACCES', 'EPERM', 'ENOENT', 'ENOTDIR', 'ECANCELED', 'ABORT_ERR'].includes(code) ? code : undefined;
|
|
46
|
+
throw unavailable('系统目录选择窗口成功返回结果;未取得结果时不会读取或提交开通资料', officialReason);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
package/lib/needs.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ConnectNeed, NeedCode, Result } from './api.ts';
|
|
2
|
+
export declare const ok: <T>(value: T) => Result<T>;
|
|
3
|
+
export declare const need: (code: NeedCode) => ConnectNeed;
|
|
4
|
+
export declare const fail: <T = never>(code: NeedCode) => Result<T>;
|
|
5
|
+
export declare function dependency(requires: string): Result<never>;
|
|
6
|
+
export declare class NeedError extends Error {
|
|
7
|
+
readonly need: ConnectNeed;
|
|
8
|
+
constructor(value: ConnectNeed);
|
|
9
|
+
}
|
package/lib/needs.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const entries = {
|
|
2
|
+
ACTIVATION_CONFLICT: { layer: 'network', message: '已有另一项开通正在进行,已停止重复开通,请联系管理员。', requires: '唯一的开通请求', actor: 'administrator', action: { id: 'inspect-network-enrollment', label: '联系管理员', available: false } },
|
|
3
|
+
ACTIVATION_CANCELLED: { layer: 'network', message: '本次网络开通已取消。', requires: '完成明确的开通操作', actor: 'plugin', action: { id: 'inspect-network-enrollment', label: '联系管理员', available: false } },
|
|
4
|
+
ENROLLMENT_CLEANUP_UNBOUND: { layer: 'network', message: '连接资料与已保存的入网身份不匹配,已停止清理,请联系管理员。', requires: '匹配的入网身份与原始连接资料', actor: 'administrator', action: { id: 'inspect-network-enrollment', label: '联系管理员', available: false } },
|
|
5
|
+
ENROLLMENT_CLEANUP_FAILED: { layer: 'network', message: '入网身份已保存;连接资料清理未完成,请联系管理员。', requires: '精确清理已消费的连接资料', actor: 'administrator', action: { id: 'inspect-network-enrollment', label: '联系管理员', available: false } },
|
|
6
|
+
ENROLLMENT_MISMATCH: { layer: 'network', message: '当前连接资料与已登记的入网身份不符,请联系管理员。', requires: '一致的连接资料与入网身份', actor: 'administrator', action: { id: 'inspect-network-enrollment', label: '联系管理员', available: false } },
|
|
7
|
+
LOCAL_STATE_INVALID: { layer: 'network', message: '本机网络身份记录无法核实,已停止开通,请联系管理员。', requires: '完整可信的本机网络身份记录', actor: 'administrator', action: { id: 'inspect-network-enrollment', label: '联系管理员', available: false } },
|
|
8
|
+
ALREADY_RUNNING: { layer: 'network', message: '网络组件已在运行,无法确认其归属,请联系管理员。', requires: '唯一且归属明确的网络组件', actor: 'administrator', action: { id: 'inspect-network-enrollment', label: '联系管理员', available: false } },
|
|
9
|
+
ENROLLMENT_RECEIPT_REQUIRED: { layer: 'network', message: '缺少可核验的入网回执,请联系管理员。', requires: '持久且匹配的入网回执', actor: 'administrator', action: { id: 'inspect-network-enrollment', label: '联系管理员', available: false } },
|
|
10
|
+
CONTROL_PROTOCOL_INVALID: { layer: 'network', message: '网络组件接口不匹配,已停止操作,请联系管理员。', requires: '匹配版本的网络组件接口', actor: 'administrator', action: { id: 'inspect-network-enrollment', label: '联系管理员', available: false } },
|
|
11
|
+
HELPER_HANDOFF_FAILED: { layer: 'network', message: '网络组件交接未完成,请联系管理员。', requires: '完成网络组件交接', actor: 'administrator', action: { id: 'inspect-network-enrollment', label: '联系管理员', available: false } },
|
|
12
|
+
HELPER_SHUTDOWN_FORCED: { layer: 'network', message: '网络组件未能正常退出,请联系管理员核实状态。', requires: '核实网络组件退出状态', actor: 'administrator', action: { id: 'inspect-network-enrollment', label: '联系管理员', available: false } },
|
|
13
|
+
ENROLLMENT_RESULT_UNKNOWN: { layer: 'enrollment', message: '上次开通结果无法确认,请联系管理员核实。已停止再次提交开通资料。', requires: '管理员核实上次开通结果', actor: 'administrator', action: { id: 'check-enrollment-result', label: '联系管理员', available: false } },
|
|
14
|
+
ENROLLMENT_REJECTED: { layer: 'enrollment', message: '开通资料已失效,请联系管理员处理。已停止再次提交。', requires: '管理员处理已失效的设备身份', actor: 'administrator', action: { id: 'check-enrollment-result', label: '联系管理员', available: false } },
|
|
15
|
+
ENROLLMENT_REQUIRED: { layer: 'enrollment', message: '我需要开通链接,请联系管理员发放。', requires: '开通文件', actor: 'administrator', action: { id: 'request-enrollment', label: '联系管理员开通', available: false } },
|
|
16
|
+
ENROLLMENT_INVALID: { layer: 'enrollment', message: '开通文件无效,我需要管理员重新发放。', requires: '有效开通文件', actor: 'administrator', action: { id: 'request-enrollment', label: '重新开通', available: false } },
|
|
17
|
+
NETWORK_KEY_INVALID: { layer: 'network', message: '员工开通已失效,请联系管理员重新发放开通链接。', requires: '新的开通链接', actor: 'administrator', action: { id: 'request-enrollment', label: '联系管理员', available: false } },
|
|
18
|
+
HOST_OFFLINE: { layer: 'network', message: '主机离线,请联系管理员;最后在线时间未提供时为未知。', requires: '在线的公司主机', actor: 'administrator', action: { id: 'contact-administrator', label: '联系管理员', available: false } },
|
|
19
|
+
EMPLOYEE_MISSING: { layer: 'ssh', message: '主机上没有你的员工账号,需要管理员运行开通。', requires: '员工主机账号', actor: 'administrator', action: { id: 'provision-employee', label: '管理员开通账号', available: false } },
|
|
20
|
+
SSH_FAILED: { layer: 'ssh', message: '远程主机连接暂时连不上,请联系管理员。', requires: '可用且身份匹配的员工 SSH 连接', actor: 'administrator', action: { id: 'inspect-ssh', label: '检查主机连接', available: false } },
|
|
21
|
+
LOGIN_PROMPT_INVALID: { layer: 'auth', message: '这个登录询问已经更新,请使用当前提示。', requires: '当前登录询问', actor: 'employee', action: { id: 'current-login-prompt', label: '查看当前提示', available: false } },
|
|
22
|
+
LOGIN_REQUIRED: { layer: 'auth', message: '需要登录,请到设置 → 远程主机连接 点登录', requires: 'AI 账号授权', actor: 'employee', action: { id: 'open-login', label: '前往远程主机连接设置', available: true } },
|
|
23
|
+
POLICY_DENIED: { layer: 'auth', message: '公司的 AI 席位设置不允许此操作,需要 AI 账号管理员开启。', requires: '允许此操作的 AI 席位设置', actor: 'ai-administrator', action: { id: 'contact-ai-administrator', label: '联系 AI 账号管理员', available: false } },
|
|
24
|
+
CONNECTION_LOST: { layer: 'network', message: '连接中断,正在等待网络恢复信号,恢复后将重新连接。', requires: '网络恢复', actor: 'plugin', action: { id: 'await-network', label: '等待网络恢复', available: false } },
|
|
25
|
+
DEPENDENCY_REQUIRED: { layer: 'integration', message: '远程主机连接的连接组件没有装好,请联系管理员。', requires: '提供方契约与适配器', actor: 'administrator', action: { id: 'supply-dependency', label: '提供集成前置条件', available: false } },
|
|
26
|
+
SETTINGS_REQUIRED: { layer: 'settings', message: '远程主机连接还没有设置好,请联系管理员。', requires: '已确认的连接设置', actor: 'administrator', action: { id: 'configure-settings', label: '确认具名设置', available: false } },
|
|
27
|
+
AUTH_STATUS_INVALID: { layer: 'auth', message: '暂时无法确认登录状态,请联系管理员。', requires: '有效的官方 CLI 登录状态', actor: 'administrator', action: { id: 'inspect-auth-status', label: '检查官方 CLI', available: false } },
|
|
28
|
+
LOGIN_FAILED: { layer: 'auth', message: '登录未完成,窗口已关闭或助手失败,可以重新授权。', requires: '完成 AI 授权', actor: 'employee', action: { id: 'open-login', label: '重新授权', available: true } },
|
|
29
|
+
HOST_TRUST_REQUIRED: { layer: 'ssh', message: '远程主机连接还缺少可信的连接信息,请联系管理员。', requires: 'host.knownHosts', actor: 'administrator', action: { id: 'supply-host-trust', label: '提供主机信任材料', available: false } },
|
|
30
|
+
HOST_KEY_MISMATCH: { layer: 'ssh', message: '主机身份与已开通的信任材料不符,需要管理员检查。', requires: '匹配的主机公钥', actor: 'administrator', action: { id: 'verify-host-trust', label: '检查主机身份', available: false } },
|
|
31
|
+
PROJECT_NAME_INVALID: { layer: 'projects', message: '工作区名必须是单个目录名称,不能是路径、点目录或控制字符。', requires: '有效工作区名', actor: 'employee', action: { id: 'rename-project', label: '修改工作区名', available: true } },
|
|
32
|
+
PROJECT_ROOT_INVALID: { layer: 'projects', message: '工作区暂时无法打开,请联系管理员。', requires: 'S 开通的 700 工作区根目录', actor: 'administrator', action: { id: 'check-project-root', label: '检查开通目录', available: false } },
|
|
33
|
+
PROJECT_EXISTS: { layer: 'projects', message: '同名工作区或文件已经存在,请换一个工作区名。', requires: '未被占用的工作区名', actor: 'employee', action: { id: 'rename-project', label: '修改工作区名', available: true } },
|
|
34
|
+
PROJECT_INIT_FAILED: { layer: 'projects', message: '工作区创建没有完成,已保留现有文件,请联系管理员。', requires: '可用的主机 Git 与目录检查', actor: 'administrator', action: { id: 'inspect-project-init', label: '检查 Git 初始化', available: false } },
|
|
35
|
+
PROJECT_CREATE_FAILED: { layer: 'projects', message: '工作区创建没有完成,请联系管理员。', requires: '可用的工作区目录与 Git', actor: 'administrator', action: { id: 'inspect-project-create', label: '检查新建工作区', available: false } },
|
|
36
|
+
PROJECT_LIST_FAILED: { layer: 'projects', message: '工作区列表暂时打不开,请联系管理员。', requires: '可读的工作区目录与 Git', actor: 'administrator', action: { id: 'inspect-project-list', label: '检查工作区目录', available: false } },
|
|
37
|
+
PROJECT_PROTOCOL_INVALID: { layer: 'projects', message: '工作区暂时无法打开,已停止操作,请联系管理员。', requires: '匹配版本的工作区服务', actor: 'administrator', action: { id: 'inspect-project-protocol', label: '检查工作区接口', available: false } },
|
|
38
|
+
PROJECT_BINDING_INVALID: { layer: 'projects', message: '工作区关联与当前目录或主机身份不符,已停止操作,请联系管理员。', requires: '原工作区与可信主机身份', actor: 'administrator', action: { id: 'check-workspace-binding', label: '联系管理员', available: false } },
|
|
39
|
+
PROJECT_BINDING_BUSY: { layer: 'projects', message: '此工作区正在关联或说明正在投递,请联系管理员核实。', requires: '完成当前关联或投递', actor: 'administrator', action: { id: 'check-workspace-binding', label: '联系管理员', available: false } },
|
|
40
|
+
PROJECT_CREATE_UNKNOWN: { layer: 'projects', message: '远程工作区创建结果无法确认,已保留现场,请联系管理员。', requires: '核实上次远程工作区创建结果', actor: 'administrator', action: { id: 'check-workspace-create', label: '联系管理员', available: false } },
|
|
41
|
+
PROJECT_NOTICE_DELIVERY_UNKNOWN: { layer: 'projects', message: '工作区说明的投递结果无法确认,已停止远程派发,请联系管理员核实。', requires: '官方会话回复的持久投递证据', actor: 'administrator', action: { id: 'check-workspace-notice', label: '联系管理员', available: false } },
|
|
42
|
+
PROJECT_BINDING_REQUIRED: { layer: 'projects', message: '请在会话中选择已关联的远程工作区。', requires: '当前会话的可信远程工作区关联', actor: 'employee', action: { id: 'select-workspace', label: '选择工作区', available: true } },
|
|
43
|
+
DISPOSED: { layer: 'integration', message: '连接插件已卸载,需要重新启用插件。', requires: '已启用的连接插件', actor: 'employee', action: { id: 'enable-plugin', label: '启用插件', available: false } },
|
|
44
|
+
};
|
|
45
|
+
export const ok = (value) => ({ ok: true, value });
|
|
46
|
+
export const need = (code) => structuredClone({ code, ...entries[code] });
|
|
47
|
+
export const fail = (code) => ({ ok: false, error: need(code) });
|
|
48
|
+
export function dependency(requires) { const error = need('DEPENDENCY_REQUIRED'); error.requires = requires; error.message = '远程主机连接的连接组件没有装好,请联系管理员。'; return { ok: false, error }; }
|
|
49
|
+
export class NeedError extends Error {
|
|
50
|
+
need;
|
|
51
|
+
constructor(value) { super(value.code); this.need = value; }
|
|
52
|
+
}
|
package/lib/network.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Enrollment, NetworkEvent } from './ports.ts';
|
|
2
|
+
import type { Result } from './api.ts';
|
|
3
|
+
import type { ProxyLease } from './login.ts';
|
|
4
|
+
export interface NetworkSettings {
|
|
5
|
+
executable?: string;
|
|
6
|
+
runtimeDir?: string;
|
|
7
|
+
stateDir?: string;
|
|
8
|
+
proxyListen?: string;
|
|
9
|
+
logUpload?: boolean;
|
|
10
|
+
statusRefreshIntervalSeconds?: number;
|
|
11
|
+
dialTimeoutSeconds?: number;
|
|
12
|
+
headerTimeoutSeconds?: number;
|
|
13
|
+
enrollmentObservationSeconds?: number;
|
|
14
|
+
httpMaxHeaderBytes?: number;
|
|
15
|
+
httpMaxResponseHeaderBytes?: number;
|
|
16
|
+
controlMaxBytes?: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function writeNetworkConfig(file: string, e: Enrollment, s: NetworkSettings): Promise<Result<void>>;
|
|
19
|
+
export declare function createNetworkClient(s: NetworkSettings): {
|
|
20
|
+
connect(e: Enrollment, signal: AbortSignal): Promise<Result<{
|
|
21
|
+
proxyCommand: string;
|
|
22
|
+
}>>;
|
|
23
|
+
onState(listener: (event: NetworkEvent) => void): () => void;
|
|
24
|
+
acquireProxy(signal: AbortSignal): Promise<ProxyLease>;
|
|
25
|
+
dispose(): void;
|
|
26
|
+
};
|