dsh-over-ssh 0.2.2 → 0.2.4
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.md +82 -4
- package/agents/agent-manifest.json +7 -7
- package/agents/linux-amd64/dsh-over-ssh-agent +0 -0
- package/agents/linux-arm64/dsh-over-ssh-agent +0 -0
- package/cordis.patch.yml +10 -12
- package/lib/client.js +161 -60
- package/lib/file-references.js +136 -13
- package/lib/fs.js +2 -1
- package/lib/index.js +1245 -165
- package/lib/types/src/config.d.ts +44 -0
- package/lib/types/src/host/app/backend.d.ts +31 -0
- package/lib/types/src/host/app/routed-fs.d.ts +238 -0
- package/lib/types/src/host/app/session-admin/session-admin-contract.d.ts +17 -0
- package/lib/types/src/host/app/transport/transport-service.d.ts +66 -11
- package/lib/types/src/host/domain/capability.d.ts +6 -0
- package/lib/types/src/host/domain/roots/route-table.d.ts +101 -0
- package/lib/types/src/host/domain/session/session-admin.d.ts +8 -0
- package/lib/types/src/host/infra/backend/poll-watch.d.ts +119 -0
- package/lib/types/src/host/infra/backend/remote-backend.d.ts +297 -0
- package/lib/types/src/host/infra/cache/budget.d.ts +68 -0
- package/lib/types/src/host/infra/cache/versioned.d.ts +98 -0
- package/lib/types/src/host/infra/transport/ssh/askpass.d.ts +132 -0
- package/lib/types/src/host/infra/transport/transport.d.ts +48 -0
- package/lib/types/src/host/integration/agent/askpass-registry.d.ts +65 -0
- package/lib/types/src/host/integration/fs/remote-index.d.ts +183 -0
- package/lib/types/src/host/integration/roots/remote-home.d.ts +68 -0
- package/lib/types/src/host/integration/roots/root-registry.d.ts +148 -0
- package/lib/types/src/index.d.ts +7 -0
- package/lib/types/src/local-bridge.d.ts +4 -4
- package/lib/types/src/wiring/connections.d.ts +261 -0
- package/lib/types/src/wiring/install-memo.d.ts +31 -0
- package/package.json +1 -5
- package/lib/local-bridge-refs.js +0 -16
- package/lib/types/src/local-bridge-refs.d.ts +0 -25
package/lib/types/src/index.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { Config } from './config.ts';
|
|
|
19
19
|
import { type TransferService } from './host/integration/transfer/transfer-service.ts';
|
|
20
20
|
import { type RecycleBinService } from './host/app/recycle-bin/recycle-bin-service.ts';
|
|
21
21
|
import { type SessionAdminService } from './host/app/session-admin/session-admin-service.ts';
|
|
22
|
+
import { type TransportLaneRouter } from './wiring/connections.ts';
|
|
22
23
|
import type { ConnectionEntry, RootEntry, ConnectionDirectory, AgentStatus, ResolvedConfig } from './wiring/helpers.ts';
|
|
23
24
|
/** Plugin name as the loader sees it. */
|
|
24
25
|
export declare const name = "dsh-over-ssh";
|
|
@@ -43,6 +44,10 @@ export interface LimitsConfig {
|
|
|
43
44
|
readonly indexMaxEntries?: number;
|
|
44
45
|
/** Directory the terminal wrapper reads its manifest from (default ~/.dsh/over-ssh). */
|
|
45
46
|
readonly terminalStateDir?: string;
|
|
47
|
+
/** Split traffic into interactive/bulk/compute transports (default true). */
|
|
48
|
+
readonly transportLanes?: boolean;
|
|
49
|
+
/** Upper bound on concurrent stateful lane transports per connection (default 8). */
|
|
50
|
+
readonly statefulLanes?: number;
|
|
46
51
|
}
|
|
47
52
|
/** The plugin configuration, as a human-readable contract (the loader uses the schema above). */
|
|
48
53
|
export interface PluginConfig {
|
|
@@ -59,6 +64,8 @@ declare module '@deepseek-ai/cordis' {
|
|
|
59
64
|
* decisions currently in force instead of guessing why a connection behaves the way it does.
|
|
60
65
|
*/
|
|
61
66
|
sshDevTransports: TransportService;
|
|
67
|
+
/** Lane-aware transport routing (interactive/bulk/compute) with interactive fallback. */
|
|
68
|
+
sshDevTransportLanes: TransportLaneRouter;
|
|
62
69
|
/** Tier 2 install outcomes (why the agent is or is not in use). */
|
|
63
70
|
sshDevAgent: AgentStatus;
|
|
64
71
|
/** Chunked upload/download between the local machine and one remote root. */
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The `./local-bridge` entry: republish the private realm's stock `fs` under a name the router can inject.
|
|
3
3
|
*
|
|
4
|
-
* Composition (see `cordis.patch.yml`): a `cordis:group` with `isolate: { fs: true
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Composition (see `cordis.patch.yml`): a `cordis:group` with `isolate: { fs: true }` instantiates the
|
|
5
|
+
* **stock** `fs-sandbox` a second time. Because that realm owns its own `fs` slot, the stock sandbox keeps
|
|
6
|
+
* running and keeps enforcing the platform's write fences for local paths — this plugin no longer
|
|
7
|
+
* re-implements them.
|
|
8
8
|
*
|
|
9
9
|
* The bridge is what carries that instance back out. It is provided under a **new name** (`localFs`), which is
|
|
10
10
|
* not in the realm's `isolate` list, so the value reaches the root scope where the router injects it.
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* connections
|
|
3
|
+
*
|
|
4
|
+
* 建立连接配置、传输服务、根注册表与按连接的索引。
|
|
5
|
+
*
|
|
6
|
+
* 从 `src/index.ts` 的 `apply()` 抽出(审计 0074 §2.6):函数体是**逐字搬移**,
|
|
7
|
+
* 依赖通过"从 env 解构 + 返回后续要用的量"显式化,行为不变。
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-over-ssh/wiring/connections
|
|
10
|
+
*/
|
|
11
|
+
import { TransportSelector } from '../host/app/transport/transport-selector.ts';
|
|
12
|
+
import { TransportService, type AcquiredTransport, type TransportLane } from '../host/app/transport/transport-service.ts';
|
|
13
|
+
import { type ConnectionProfile } from '../host/domain/roots/connection.ts';
|
|
14
|
+
import { type AgentInstallation } from '../host/infra/transport/agent/agent-installer.ts';
|
|
15
|
+
/**
|
|
16
|
+
* 降级/失败结果缓存多久之后重试一次安装。
|
|
17
|
+
*
|
|
18
|
+
* 30 秒——重试一次的代价是几条 ssh 命令,而永久降级的代价是整个会话没有 Tier 2。
|
|
19
|
+
*
|
|
20
|
+
* 曾经是一个可写的 `let` 加一个"仅供测试"的 setter,但**没有任何测试用它**:那个 setter 是死代码,
|
|
21
|
+
* 而可写状态让"这个值到底是谁定的"变得不可回答。现在它是常量(审计 0112 P3-7)。
|
|
22
|
+
*/
|
|
23
|
+
export declare const degradedRetryMs = 30000;
|
|
24
|
+
import { RemoteBackend } from '../host/infra/backend/remote-backend.ts';
|
|
25
|
+
import { RemoteIndex } from '../host/integration/fs/remote-index.ts';
|
|
26
|
+
import { RootRegistry } from '../host/integration/roots/root-registry.ts';
|
|
27
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
28
|
+
import { type ResolvedConfig, type ConnectionDirectory } from './helpers.ts';
|
|
29
|
+
import type { BackendRangeReader } from '../host/integration/fs/fs-provider.ts';
|
|
30
|
+
import type { FileReferenceIndex } from '../host/integration/fs/file-reference-provider.ts';
|
|
31
|
+
/**
|
|
32
|
+
* Route each call to a traffic lane, falling back to the legacy interactive transport when a lane cannot be
|
|
33
|
+
* created or connected.
|
|
34
|
+
*
|
|
35
|
+
* The split is deliberately best-effort: a lane is an optimisation, never a new failure mode. A connection
|
|
36
|
+
* whose agent cannot open a second channel (or a deployment with `limits.transportLanes: false`) keeps
|
|
37
|
+
* working exactly as before, with every call on the one interactive transport.
|
|
38
|
+
*/
|
|
39
|
+
export interface TransportLaneRouter {
|
|
40
|
+
/** Whether lane separation is enabled by configuration. */
|
|
41
|
+
readonly enabled: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Acquire the transport for one lane.
|
|
44
|
+
*
|
|
45
|
+
* @param profile - the connection to serve.
|
|
46
|
+
* @param lane - traffic class; defaults to `interactive`.
|
|
47
|
+
* @param signal - cancellation for the connect attempt.
|
|
48
|
+
* @param sessionKey - per-session key for the reserved `stateful` lane.
|
|
49
|
+
* @returns the acquired transport (possibly the interactive fallback).
|
|
50
|
+
*/
|
|
51
|
+
acquire(profile: ConnectionProfile, lane?: TransportLane, signal?: AbortSignal, sessionKey?: string): Promise<AcquiredTransport>;
|
|
52
|
+
/** Release every `stateful` lane that belongs to one DSH session. */
|
|
53
|
+
releaseSession(sessionKey: string): Promise<void>;
|
|
54
|
+
/** DSH session keys currently owning a stateful lane. */
|
|
55
|
+
statefulSessions(): readonly string[];
|
|
56
|
+
/** Release stateful lanes whose session is no longer live. */
|
|
57
|
+
pruneStatefulSessions(isLive: (sessionKey: string) => boolean): Promise<void>;
|
|
58
|
+
}
|
|
59
|
+
/** Facts injected into {@link createTransportLaneRouter}. */
|
|
60
|
+
export interface TransportLaneRouterOptions {
|
|
61
|
+
readonly transports: Pick<TransportService, 'acquire' | 'releaseSession' | 'statefulSessions' | 'pruneStatefulSessions'>;
|
|
62
|
+
readonly enabled: boolean;
|
|
63
|
+
/** Called once per fallback with the lane that failed; wiring logs it so a degraded lane is visible. */
|
|
64
|
+
readonly onFallback?: (event: {
|
|
65
|
+
readonly connectionId: string;
|
|
66
|
+
readonly lane: TransportLane;
|
|
67
|
+
readonly error: unknown;
|
|
68
|
+
}) => void;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Build the lane router used by the composition.
|
|
72
|
+
*
|
|
73
|
+
* `interactive` (and the disabled case) goes through the legacy two-argument `acquire`, so the
|
|
74
|
+
* no-lane cache slot and all pre-existing behaviour are untouched.
|
|
75
|
+
*
|
|
76
|
+
* @param options - transport service, enable switch and fallback reporter.
|
|
77
|
+
* @returns the router.
|
|
78
|
+
*/
|
|
79
|
+
export declare function createTransportLaneRouter(options: TransportLaneRouterOptions): TransportLaneRouter;
|
|
80
|
+
/**
|
|
81
|
+
* 剪掉已不存在的根的后端映射。
|
|
82
|
+
*
|
|
83
|
+
* `backendConnections` 是"后端 id → 连接 id"的反查表,只在建后端时写入;单根移除走 root-registry 的
|
|
84
|
+
* dispose,不经过连接编辑那条路,所以没有这一步时它会随"历史注册过的根数"单调增长。
|
|
85
|
+
*
|
|
86
|
+
* @param owners - 反查表本身(就地修改)。
|
|
87
|
+
* @param liveRootKeys - 当前仍存在的根 key。
|
|
88
|
+
*/
|
|
89
|
+
export declare function pruneBackendConnections(owners: Map<string, string>, liveRootKeys: ReadonlySet<string>): void;
|
|
90
|
+
export declare function wireConnections(ctx: Context, config: ResolvedConfig): {
|
|
91
|
+
ctx: Context;
|
|
92
|
+
config: Schemastery.ObjectT<NoInfer<{
|
|
93
|
+
connections: import("@deepseek-ai/schemastery").default<({
|
|
94
|
+
id?: string | null;
|
|
95
|
+
name?: string | null;
|
|
96
|
+
host?: string | null;
|
|
97
|
+
port?: number | null;
|
|
98
|
+
user?: string | null;
|
|
99
|
+
transport?: "alias" | "ssh2" | "agent" | null;
|
|
100
|
+
alias?: string | null;
|
|
101
|
+
authKind?: "agent" | "key" | "password" | null;
|
|
102
|
+
keyPath?: string | null;
|
|
103
|
+
credentialKey?: string | null;
|
|
104
|
+
proxyJump?: string[] | null;
|
|
105
|
+
hostKeyPolicy?: "strict" | "accept-new" | null;
|
|
106
|
+
remoteDefaultRoot?: string | null;
|
|
107
|
+
} & import("@deepseek-ai/cosmokit").Dict)[], Schemastery.ObjectT<NoInfer<{
|
|
108
|
+
id: import("@deepseek-ai/schemastery").default<string, string, "defined">;
|
|
109
|
+
name: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
110
|
+
host: import("@deepseek-ai/schemastery").default<string, string, "defined">;
|
|
111
|
+
port: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
112
|
+
user: import("@deepseek-ai/schemastery").default<string, string, "defined">;
|
|
113
|
+
transport: import("@deepseek-ai/schemastery").default<"alias" | "ssh2" | "agent", "alias" | "ssh2" | "agent", "defined">;
|
|
114
|
+
alias: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
115
|
+
authKind: import("@deepseek-ai/schemastery").default<"agent" | "key" | "password", "agent" | "key" | "password", "defined">;
|
|
116
|
+
keyPath: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
117
|
+
credentialKey: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
118
|
+
proxyJump: import("@deepseek-ai/schemastery").default<string[], string[], "defined">;
|
|
119
|
+
hostKeyPolicy: import("@deepseek-ai/schemastery").default<"strict" | "accept-new", "strict" | "accept-new", "defined">;
|
|
120
|
+
remoteDefaultRoot: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
121
|
+
}>>[], "defined">;
|
|
122
|
+
roots: import("@deepseek-ai/schemastery").default<({
|
|
123
|
+
location?: "local" | "remote" | null;
|
|
124
|
+
path?: string | null;
|
|
125
|
+
connectionId?: string | null;
|
|
126
|
+
label?: string | null;
|
|
127
|
+
writable?: boolean | null;
|
|
128
|
+
anchorPath?: string | null;
|
|
129
|
+
} & import("@deepseek-ai/cosmokit").Dict)[], Schemastery.ObjectT<NoInfer<{
|
|
130
|
+
location: import("@deepseek-ai/schemastery").default<"local" | "remote", "local" | "remote", "defined">;
|
|
131
|
+
path: import("@deepseek-ai/schemastery").default<string, string, "defined">;
|
|
132
|
+
connectionId: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
133
|
+
label: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
134
|
+
writable: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
|
|
135
|
+
anchorPath: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
136
|
+
}>>[], "defined">;
|
|
137
|
+
limits: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<NoInfer<{
|
|
138
|
+
maxReadBytes: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
139
|
+
connectTimeoutMs: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
140
|
+
commandTimeoutMs: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
141
|
+
keepAliveSeconds: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
142
|
+
controlDir: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
143
|
+
knownHostsFile: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
144
|
+
agentManifestPath: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
145
|
+
agentRemoteDir: import("@deepseek-ai/schemastery").default<string, string, "defined">;
|
|
146
|
+
indexTtlMs: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
147
|
+
indexMaxEntries: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
148
|
+
readCacheTtlMs: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
149
|
+
readCacheMaxEntries: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
150
|
+
readCacheMaxBytes: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
151
|
+
readCacheTotalMaxBytes: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
152
|
+
forwardIdleTimeoutMs: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
153
|
+
terminalStateDir: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
154
|
+
execWriteFence: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
|
|
155
|
+
transferChunkBytes: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
156
|
+
transferConcurrency: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
157
|
+
transportLanes: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
|
|
158
|
+
statefulLanes: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
159
|
+
transferHistoryEntries: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
160
|
+
binDir: import("@deepseek-ai/schemastery").default<string, string, "defined">;
|
|
161
|
+
binMaxEntries: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
162
|
+
binMaxBytes: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
163
|
+
binMaxAgeMs: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
164
|
+
undoDepth: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
165
|
+
rangeChunkBytes: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
166
|
+
sessionsRoot: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
167
|
+
sessionBinRoot: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
168
|
+
sessionBinMaxEntries: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
169
|
+
sessionBinMaxBytes: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
170
|
+
sessionBinMaxAgeMs: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
171
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
172
|
+
maxReadBytes: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
173
|
+
connectTimeoutMs: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
174
|
+
commandTimeoutMs: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
175
|
+
keepAliveSeconds: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
176
|
+
controlDir: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
177
|
+
knownHostsFile: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
178
|
+
agentManifestPath: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
179
|
+
agentRemoteDir: import("@deepseek-ai/schemastery").default<string, string, "defined">;
|
|
180
|
+
indexTtlMs: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
181
|
+
indexMaxEntries: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
182
|
+
readCacheTtlMs: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
183
|
+
readCacheMaxEntries: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
184
|
+
readCacheMaxBytes: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
185
|
+
readCacheTotalMaxBytes: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
186
|
+
forwardIdleTimeoutMs: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
187
|
+
terminalStateDir: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
188
|
+
execWriteFence: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
|
|
189
|
+
transferChunkBytes: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
190
|
+
transferConcurrency: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
191
|
+
transportLanes: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
|
|
192
|
+
statefulLanes: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
193
|
+
transferHistoryEntries: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
194
|
+
binDir: import("@deepseek-ai/schemastery").default<string, string, "defined">;
|
|
195
|
+
binMaxEntries: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
196
|
+
binMaxBytes: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
197
|
+
binMaxAgeMs: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
198
|
+
undoDepth: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
199
|
+
rangeChunkBytes: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
200
|
+
sessionsRoot: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
201
|
+
sessionBinRoot: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
202
|
+
sessionBinMaxEntries: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
203
|
+
sessionBinMaxBytes: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
204
|
+
sessionBinMaxAgeMs: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
205
|
+
}>>, "defined">;
|
|
206
|
+
}>>;
|
|
207
|
+
limits: Schemastery.ObjectT<NoInfer<{
|
|
208
|
+
maxReadBytes: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
209
|
+
connectTimeoutMs: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
210
|
+
commandTimeoutMs: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
211
|
+
keepAliveSeconds: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
212
|
+
controlDir: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
213
|
+
knownHostsFile: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
214
|
+
agentManifestPath: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
215
|
+
agentRemoteDir: import("@deepseek-ai/schemastery").default<string, string, "defined">;
|
|
216
|
+
indexTtlMs: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
217
|
+
indexMaxEntries: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
218
|
+
readCacheTtlMs: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
219
|
+
readCacheMaxEntries: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
220
|
+
readCacheMaxBytes: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
221
|
+
readCacheTotalMaxBytes: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
222
|
+
forwardIdleTimeoutMs: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
223
|
+
terminalStateDir: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
224
|
+
execWriteFence: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
|
|
225
|
+
transferChunkBytes: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
226
|
+
transferConcurrency: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
227
|
+
transportLanes: import("@deepseek-ai/schemastery").default<boolean, boolean, "defined">;
|
|
228
|
+
statefulLanes: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
229
|
+
transferHistoryEntries: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
230
|
+
binDir: import("@deepseek-ai/schemastery").default<string, string, "defined">;
|
|
231
|
+
binMaxEntries: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
232
|
+
binMaxBytes: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
233
|
+
binMaxAgeMs: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
234
|
+
undoDepth: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
235
|
+
rangeChunkBytes: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
236
|
+
sessionsRoot: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
237
|
+
sessionBinRoot: import("@deepseek-ai/schemastery").default<string, string, "plain">;
|
|
238
|
+
sessionBinMaxEntries: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
239
|
+
sessionBinMaxBytes: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
240
|
+
sessionBinMaxAgeMs: import("@deepseek-ai/schemastery").default<number, number, "defined">;
|
|
241
|
+
}>>;
|
|
242
|
+
anchorRootDir: string;
|
|
243
|
+
profiles: Map<string, ConnectionProfile>;
|
|
244
|
+
selector: TransportSelector;
|
|
245
|
+
askpass: import("../host/integration/agent/askpass-registry.ts").AskpassRegistry;
|
|
246
|
+
installations: import("./install-memo.ts").InstallMemo<AgentInstallation>;
|
|
247
|
+
agentBridge: import("../host/integration/agent/agent-preflight.ts").AgentPreflight;
|
|
248
|
+
transports: TransportService;
|
|
249
|
+
transportLanes: TransportLaneRouter;
|
|
250
|
+
remoteHome: import("../host/integration/roots/remote-home.ts").RemoteHomeResolver;
|
|
251
|
+
forgetBackendsOf: (connectionId: string) => void;
|
|
252
|
+
browseBackends: Map<string, RemoteBackend>;
|
|
253
|
+
readBytesRange: BackendRangeReader;
|
|
254
|
+
registry: RootRegistry;
|
|
255
|
+
directory: ConnectionDirectory;
|
|
256
|
+
indexes: Map<string, RemoteIndex>;
|
|
257
|
+
indexFor: (connectionId: string) => RemoteIndex;
|
|
258
|
+
index: FileReferenceIndex;
|
|
259
|
+
cacheBudget: import("../host/infra/cache/budget.ts").CacheBudget;
|
|
260
|
+
};
|
|
261
|
+
export type Connections = ReturnType<typeof wireConnections>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 安装阶梯结果的记忆化。
|
|
3
|
+
*
|
|
4
|
+
* 单独的模块不是为了好看,是因为这段逻辑有两个容易写错、又很难从外部观察的点:
|
|
5
|
+
*
|
|
6
|
+
* - **成功的安装永久缓存。** 重新上传一次代理要几条 ssh 命令加一次传输,而结果不会自己变。
|
|
7
|
+
* - **降级/失败只缓存一小段时间。** 一次瞬时故障(连接被拒、磁盘满、超时、用户取消)如果被
|
|
8
|
+
* 记一辈子,这条连接在整个进程生命周期里都只能跑零安装模式——没有 LSP、没有后台作业、
|
|
9
|
+
* 没有原子写——而唯一的线索是一行 info 日志。这类故障自己不会恢复,但下次尝试通常会成功。
|
|
10
|
+
*
|
|
11
|
+
* 组合根(`wireConnections`)不适合承载这种需要断言时序的逻辑:它连一个测试文件都没有。
|
|
12
|
+
*/
|
|
13
|
+
/** 一次安装尝试的结局:真正装上(installed/reused)或降级到 Tier 1。 */
|
|
14
|
+
export interface InstallOutcome {
|
|
15
|
+
readonly kind: string;
|
|
16
|
+
}
|
|
17
|
+
export interface InstallMemo<T extends InstallOutcome> {
|
|
18
|
+
/** 命中缓存时返回那次尝试的 promise;降级条目过期则返回 `undefined` 并清掉它。 */
|
|
19
|
+
get(id: string): Promise<T> | undefined;
|
|
20
|
+
/** 记下一次尝试;结算时按结局决定它是不是「降级」。 */
|
|
21
|
+
set(id: string, attempt: Promise<T>): void;
|
|
22
|
+
/** 连接被编辑/删除时调用。 */
|
|
23
|
+
forget(id: string): void;
|
|
24
|
+
/** 当前缓存条数(诊断用)。 */
|
|
25
|
+
readonly size: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* @param retryMs 降级条目缓存多久;传 `() => 0` 表示立刻重试(测试用)。
|
|
29
|
+
* @param now 时钟,默认 `Date.now`。
|
|
30
|
+
*/
|
|
31
|
+
export declare function createInstallMemo<T extends InstallOutcome>(retryMs: () => number, now?: () => number): InstallMemo<T>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-over-ssh",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "SSH remote development for DeepSeek Harness: connections (key/password/ProxyJump/ssh config), remote files without sync, remote terminal, remote workspaces, remote @-completion, upload/download.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -26,10 +26,6 @@
|
|
|
26
26
|
"types": "./lib/types/src/local-bridge.d.ts",
|
|
27
27
|
"default": "./lib/local-bridge.js"
|
|
28
28
|
},
|
|
29
|
-
"./local-bridge-refs": {
|
|
30
|
-
"types": "./lib/types/src/local-bridge-refs.d.ts",
|
|
31
|
-
"default": "./lib/local-bridge-refs.js"
|
|
32
|
-
},
|
|
33
29
|
"./package.json": "./package.json"
|
|
34
30
|
},
|
|
35
31
|
"dsh": {
|
package/lib/local-bridge-refs.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
// src/local-bridge-refs.ts
|
|
2
|
-
import "@deepseek-ai/cordis";
|
|
3
|
-
var LocalFileReferencesBridge = class {
|
|
4
|
-
static inject = ["fileReferences"];
|
|
5
|
-
constructor(ctx) {
|
|
6
|
-
const local = ctx.get("fileReferences");
|
|
7
|
-
if (local === void 0) {
|
|
8
|
-
ctx.logger?.warn?.("local-bridge-refs: \u9694\u79BB\u57DF\u91CC\u6CA1\u6709 fileReferences\uFF1B\u8DEF\u7531\u5668\u5C06\u6CA1\u6709\u672C\u5730\u540E\u7AEF");
|
|
9
|
-
return;
|
|
10
|
-
}
|
|
11
|
-
ctx.provide("localFileReferences", local);
|
|
12
|
-
}
|
|
13
|
-
};
|
|
14
|
-
export {
|
|
15
|
-
LocalFileReferencesBridge as default
|
|
16
|
-
};
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The `./local-bridge-refs` entry: republish the private realm's stock `fileReferences` as `localFileReferences`.
|
|
3
|
-
*
|
|
4
|
-
* A **separate row** from `./local-bridge`, and that separation is load-bearing: the stock
|
|
5
|
-
* `file-reference-local` injects `workingDirectory`, which sits downstream of the routed `fs`. If one bridge
|
|
6
|
-
* gated both services it would have to wait for `fileReferences` too, and `fileReferences` cannot activate
|
|
7
|
-
* until `workingDirectory` does — which cannot happen until the router it is waiting behind exists. Two rows
|
|
8
|
-
* keep that dependency out of the way: `localFs` appears as soon as `fs` does, `localFileReferences` as soon as
|
|
9
|
-
* `fileReferences` does.
|
|
10
|
-
*
|
|
11
|
-
* @module dsh-over-ssh/local-bridge-refs
|
|
12
|
-
*/
|
|
13
|
-
import { Context } from '@deepseek-ai/cordis';
|
|
14
|
-
import type { FileReferenceService } from '@deepseek-ai/dsh-file-reference';
|
|
15
|
-
declare module '@deepseek-ai/cordis' {
|
|
16
|
-
interface Context {
|
|
17
|
-
/** The stock `file-reference-local` instance living in this plugin's private realm. */
|
|
18
|
-
localFileReferences?: FileReferenceService;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
/** Publish the realm's `fileReferences` as `localFileReferences`. */
|
|
22
|
-
export default class LocalFileReferencesBridge {
|
|
23
|
-
static inject: readonly ["fileReferences"];
|
|
24
|
-
constructor(ctx: Context);
|
|
25
|
-
}
|