dsh-over-ssh 0.1.0 → 0.2.0
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 +19 -17
- package/agents/agent-manifest.json +6 -6
- package/agents/linux-amd64/dsh-over-ssh-agent +0 -0
- package/agents/linux-arm64/dsh-over-ssh-agent +0 -0
- package/lib/client.js +422 -337
- package/lib/file-references.js +92 -35
- package/lib/fs.js +295 -113
- package/lib/index.js +6912 -4330
- package/lib/terminal-wrapper.js +9 -4
- package/lib/types/src/config.d.ts +156 -9
- package/lib/types/src/file-references.d.ts +0 -1
- package/lib/types/src/host/app/backend.d.ts +44 -3
- package/lib/types/src/host/app/recycle-bin/recycle-bin-contract.d.ts +0 -1
- package/lib/types/src/host/app/recycle-bin/recycle-bin-service.d.ts +0 -1
- package/lib/types/src/host/app/session-admin/session-admin-contract.d.ts +12 -1
- package/lib/types/src/host/app/session-admin/session-admin-service.d.ts +0 -1
- package/lib/types/src/host/app/transport/transport-selector.d.ts +0 -1
- package/lib/types/src/host/app/transport/transport-service.d.ts +0 -1
- package/lib/types/src/host/domain/capability.d.ts +0 -1
- package/lib/types/src/host/domain/errors.d.ts +0 -1
- package/lib/types/src/host/domain/roots/connection.d.ts +0 -1
- package/lib/types/src/host/domain/roots/root.d.ts +24 -9
- package/lib/types/src/host/domain/roots/session-roots.d.ts +23 -1
- package/lib/types/src/host/domain/roots/ssh-config.d.ts +0 -1
- package/lib/types/src/host/domain/session/session-admin.d.ts +0 -1
- package/lib/types/src/host/domain/transfer/recycle-bin.d.ts +0 -1
- package/lib/types/src/host/domain/transfer/transfer.d.ts +0 -1
- package/lib/types/src/host/infra/transport/agent/agent-installer.d.ts +0 -1
- package/lib/types/src/host/infra/transport/forward.d.ts +75 -0
- package/lib/types/src/host/infra/transport/ssh/ssh2-transport.d.ts +5 -1
- package/lib/types/src/host/infra/transport/transport-base.d.ts +0 -1
- package/lib/types/src/host/infra/transport/transport.d.ts +42 -2
- package/lib/types/src/host/integration/agent/agent-preflight.d.ts +0 -1
- package/lib/types/src/host/integration/fs/file-reference-provider.d.ts +0 -1
- package/lib/types/src/host/integration/fs/fs-provider.d.ts +18 -3
- package/lib/types/src/host/integration/transfer/transfer-service.d.ts +0 -1
- package/lib/types/src/index.d.ts +23 -1
- package/lib/types/src/local-bridge-refs.d.ts +0 -1
- package/lib/types/src/local-bridge.d.ts +0 -1
- package/lib/types/src/shared/root-keys.d.ts +120 -0
- package/lib/types/src/wiring/helpers.d.ts +0 -1
- package/package.json +5 -3
- package/lib/types/src/client/controls.d.ts +0 -131
- package/lib/types/src/client/core/card-focus-trap.d.ts +0 -45
- package/lib/types/src/client/core/controls.d.ts +0 -164
- package/lib/types/src/client/core/latest.d.ts +0 -48
- package/lib/types/src/client/core/locale.d.ts +0 -38
- package/lib/types/src/client/core/message.d.ts +0 -37
- package/lib/types/src/client/core/panel-model.d.ts +0 -134
- package/lib/types/src/client/core/portal.d.ts +0 -266
- package/lib/types/src/client/core/remote.d.ts +0 -84
- package/lib/types/src/client/core/styles.d.ts +0 -458
- package/lib/types/src/client/file-tree-io.d.ts +0 -19
- package/lib/types/src/client/file-tree.d.ts +0 -271
- package/lib/types/src/client/flows/directory-browser.d.ts +0 -587
- package/lib/types/src/client/flows/session-directory.d.ts +0 -415
- package/lib/types/src/client/flows/workspace-directory-flow.d.ts +0 -480
- package/lib/types/src/client/locale.d.ts +0 -31
- package/lib/types/src/client/panel-model.d.ts +0 -37
- package/lib/types/src/client/panel.d.ts +0 -77
- package/lib/types/src/client/roots/connection-test.d.ts +0 -69
- package/lib/types/src/client/roots/roots-panel-state.d.ts +0 -307
- package/lib/types/src/client/roots/roots-panel-view.d.ts +0 -280
- package/lib/types/src/client/roots/roots-panel.d.ts +0 -364
- package/lib/types/src/client/roots/transfer-panel-view.d.ts +0 -35
- package/lib/types/src/client/roots/transfer-panel.d.ts +0 -241
- package/lib/types/src/client/roots/transfer-progress.d.ts +0 -67
- package/lib/types/src/client/roots-panel-view.d.ts +0 -236
- package/lib/types/src/client/roots-panel.d.ts +0 -347
- package/lib/types/src/client/session/session-panel-view.d.ts +0 -336
- package/lib/types/src/client/session/session-panel.d.ts +0 -322
- package/lib/types/src/client/session/session-remote-mark.d.ts +0 -110
- package/lib/types/src/client/session/session-row-actions.d.ts +0 -398
- package/lib/types/src/client/session-panel.d.ts +0 -152
- package/lib/types/src/client/styles.d.ts +0 -252
- package/lib/types/src/client/workspace-directory-flow.d.ts +0 -342
- package/lib/types/src/fs.d.ts +0 -46
- package/lib/types/src/host/app/recycle-bin/recycle-bin-move.d.ts +0 -18
- package/lib/types/src/host/app/recycle-bin/recycle-bin-world.d.ts +0 -38
- package/lib/types/src/host/app/recycle-bin-contract.d.ts +0 -215
- package/lib/types/src/host/app/recycle-bin-move.d.ts +0 -18
- package/lib/types/src/host/app/recycle-bin-service.d.ts +0 -45
- package/lib/types/src/host/app/recycle-bin-world.d.ts +0 -35
- package/lib/types/src/host/app/roots/roots-api-connections.d.ts +0 -49
- package/lib/types/src/host/app/roots/roots-api-contract.d.ts +0 -369
- package/lib/types/src/host/app/roots/roots-api-directories.d.ts +0 -13
- package/lib/types/src/host/app/roots/roots-api-http.d.ts +0 -77
- package/lib/types/src/host/app/roots/roots-api-long-poll.d.ts +0 -130
- package/lib/types/src/host/app/roots/roots-api-projections.d.ts +0 -102
- package/lib/types/src/host/app/roots/roots-api-roots.d.ts +0 -13
- package/lib/types/src/host/app/roots/roots-api-state.d.ts +0 -22
- package/lib/types/src/host/app/roots/roots-api-transfers.d.ts +0 -26
- package/lib/types/src/host/app/roots/roots-api.d.ts +0 -55
- package/lib/types/src/host/app/roots/user-roots.d.ts +0 -552
- package/lib/types/src/host/app/roots-api-connections.d.ts +0 -49
- package/lib/types/src/host/app/roots-api-contract.d.ts +0 -339
- package/lib/types/src/host/app/roots-api-directories.d.ts +0 -13
- package/lib/types/src/host/app/roots-api-http.d.ts +0 -77
- package/lib/types/src/host/app/roots-api-projections.d.ts +0 -102
- package/lib/types/src/host/app/roots-api-roots.d.ts +0 -13
- package/lib/types/src/host/app/roots-api-state.d.ts +0 -22
- package/lib/types/src/host/app/roots-api-support.d.ts +0 -182
- package/lib/types/src/host/app/roots-api-transfers.d.ts +0 -13
- package/lib/types/src/host/app/roots-api.d.ts +0 -55
- package/lib/types/src/host/app/routed-fs.d.ts +0 -239
- package/lib/types/src/host/app/serialize.d.ts +0 -37
- package/lib/types/src/host/app/session-admin/session-admin-context.d.ts +0 -36
- package/lib/types/src/host/app/session-admin/session-admin-delete.d.ts +0 -34
- package/lib/types/src/host/app/session-admin-context.d.ts +0 -36
- package/lib/types/src/host/app/session-admin-contract.d.ts +0 -214
- package/lib/types/src/host/app/session-admin-delete.d.ts +0 -34
- package/lib/types/src/host/app/session-admin-service.d.ts +0 -38
- package/lib/types/src/host/app/takeover/takeover-plan.d.ts +0 -227
- package/lib/types/src/host/app/takeover/takeover-rows.d.ts +0 -55
- package/lib/types/src/host/app/transport-selector.d.ts +0 -98
- package/lib/types/src/host/app/transport-service.d.ts +0 -75
- package/lib/types/src/host/app/user-roots.d.ts +0 -461
- package/lib/types/src/host/domain/bin-location.d.ts +0 -79
- package/lib/types/src/host/domain/connection.d.ts +0 -95
- package/lib/types/src/host/domain/glob-pattern.d.ts +0 -62
- package/lib/types/src/host/domain/literal-text.d.ts +0 -63
- package/lib/types/src/host/domain/path-grammar.d.ts +0 -84
- package/lib/types/src/host/domain/recycle-bin.d.ts +0 -337
- package/lib/types/src/host/domain/root.d.ts +0 -74
- package/lib/types/src/host/domain/roots/bin-location.d.ts +0 -91
- package/lib/types/src/host/domain/roots/route-table.d.ts +0 -102
- package/lib/types/src/host/domain/route-table.d.ts +0 -80
- package/lib/types/src/host/domain/session/terminal-target.d.ts +0 -136
- package/lib/types/src/host/domain/session-admin.d.ts +0 -330
- package/lib/types/src/host/domain/shell.d.ts +0 -47
- package/lib/types/src/host/domain/ssh-config.d.ts +0 -73
- package/lib/types/src/host/domain/terminal-target.d.ts +0 -132
- package/lib/types/src/host/domain/tool-requests.d.ts +0 -369
- package/lib/types/src/host/domain/transfer.d.ts +0 -260
- package/lib/types/src/host/infra/backend/local-backend.d.ts +0 -84
- package/lib/types/src/host/infra/backend/local-version.d.ts +0 -43
- package/lib/types/src/host/infra/backend/platform-local-backend.d.ts +0 -89
- package/lib/types/src/host/infra/backend/remote-backend.d.ts +0 -239
- package/lib/types/src/host/infra/cache/lru.d.ts +0 -52
- package/lib/types/src/host/infra/cache/versioned.d.ts +0 -77
- package/lib/types/src/host/infra/temp-name.d.ts +0 -19
- package/lib/types/src/host/infra/transport/agent/agent-protocol-client.d.ts +0 -132
- package/lib/types/src/host/infra/transport/agent/agent-protocol-codec.d.ts +0 -140
- package/lib/types/src/host/infra/transport/agent/agent-protocol-errors.d.ts +0 -41
- package/lib/types/src/host/infra/transport/agent/agent-protocol-frames.d.ts +0 -197
- package/lib/types/src/host/infra/transport/agent/agent-protocol-types.d.ts +0 -564
- package/lib/types/src/host/infra/transport/agent/agent-transport.d.ts +0 -418
- package/lib/types/src/host/infra/transport/agent-installer.d.ts +0 -121
- package/lib/types/src/host/infra/transport/agent-protocol-client.d.ts +0 -123
- package/lib/types/src/host/infra/transport/agent-protocol-codec.d.ts +0 -131
- package/lib/types/src/host/infra/transport/agent-protocol-errors.d.ts +0 -41
- package/lib/types/src/host/infra/transport/agent-protocol-frames.d.ts +0 -197
- package/lib/types/src/host/infra/transport/agent-protocol-types.d.ts +0 -509
- package/lib/types/src/host/infra/transport/agent-transport.d.ts +0 -353
- package/lib/types/src/host/infra/transport/alias-transport.d.ts +0 -206
- package/lib/types/src/host/infra/transport/askpass-fs.d.ts +0 -20
- package/lib/types/src/host/infra/transport/askpass.d.ts +0 -133
- package/lib/types/src/host/infra/transport/command-pool.d.ts +0 -61
- package/lib/types/src/host/infra/transport/fs-commands.d.ts +0 -140
- package/lib/types/src/host/infra/transport/git-status-route.d.ts +0 -63
- package/lib/types/src/host/infra/transport/git-status.d.ts +0 -101
- package/lib/types/src/host/infra/transport/node-spawn.d.ts +0 -26
- package/lib/types/src/host/infra/transport/session-window.d.ts +0 -51
- package/lib/types/src/host/infra/transport/shell-quote.d.ts +0 -59
- package/lib/types/src/host/infra/transport/ssh/alias-transport.d.ts +0 -206
- package/lib/types/src/host/infra/transport/ssh/askpass-fs.d.ts +0 -20
- package/lib/types/src/host/infra/transport/ssh/askpass.d.ts +0 -133
- package/lib/types/src/host/infra/transport/ssh/shell-quote.d.ts +0 -62
- package/lib/types/src/host/infra/transport/ssh/ssh-argv.d.ts +0 -43
- package/lib/types/src/host/infra/transport/ssh-argv.d.ts +0 -43
- package/lib/types/src/host/infra/transport/ssh2-transport.d.ts +0 -302
- package/lib/types/src/host/integration/agent/agent-install-io.d.ts +0 -33
- package/lib/types/src/host/integration/agent/askpass-registry.d.ts +0 -66
- package/lib/types/src/host/integration/agent-install-io.d.ts +0 -33
- package/lib/types/src/host/integration/agent-preflight.d.ts +0 -56
- package/lib/types/src/host/integration/anchor-directory.d.ts +0 -56
- package/lib/types/src/host/integration/askpass-registry.d.ts +0 -59
- package/lib/types/src/host/integration/bin-root-resolver.d.ts +0 -81
- package/lib/types/src/host/integration/connection-test.d.ts +0 -73
- package/lib/types/src/host/integration/file-reference-provider.d.ts +0 -115
- package/lib/types/src/host/integration/fs/git-status-wiring.d.ts +0 -21
- package/lib/types/src/host/integration/fs/platform-errors.d.ts +0 -94
- package/lib/types/src/host/integration/fs/remote-file-tools.d.ts +0 -369
- package/lib/types/src/host/integration/fs/remote-index.d.ts +0 -177
- package/lib/types/src/host/integration/fs-provider.d.ts +0 -226
- package/lib/types/src/host/integration/lsp/lsp-framing.d.ts +0 -38
- package/lib/types/src/host/integration/lsp/lsp-registry.d.ts +0 -67
- package/lib/types/src/host/integration/lsp/lsp-session.d.ts +0 -130
- package/lib/types/src/host/integration/management-tools.d.ts +0 -64
- package/lib/types/src/host/integration/recycle-bin-service.d.ts +0 -247
- package/lib/types/src/host/integration/remote/connection-test.d.ts +0 -73
- package/lib/types/src/host/integration/remote/remote-face.d.ts +0 -127
- package/lib/types/src/host/integration/remote/remote-tool-backend.d.ts +0 -108
- package/lib/types/src/host/integration/remote-face.d.ts +0 -127
- package/lib/types/src/host/integration/remote-file-tools.d.ts +0 -241
- package/lib/types/src/host/integration/remote-home.d.ts +0 -69
- package/lib/types/src/host/integration/remote-index.d.ts +0 -165
- package/lib/types/src/host/integration/remote-job-tools.d.ts +0 -295
- package/lib/types/src/host/integration/remote-tool-backend.d.ts +0 -97
- package/lib/types/src/host/integration/remote-tools.d.ts +0 -206
- package/lib/types/src/host/integration/root-registry.d.ts +0 -115
- package/lib/types/src/host/integration/roots/anchor-directory.d.ts +0 -56
- package/lib/types/src/host/integration/roots/bin-root-resolver.d.ts +0 -81
- package/lib/types/src/host/integration/roots/remote-home.d.ts +0 -69
- package/lib/types/src/host/integration/roots/root-registry.d.ts +0 -144
- package/lib/types/src/host/integration/roots/roots-wiring.d.ts +0 -219
- package/lib/types/src/host/integration/roots/session-root-index.d.ts +0 -52
- package/lib/types/src/host/integration/roots-api.d.ts +0 -407
- package/lib/types/src/host/integration/roots-wiring.d.ts +0 -211
- package/lib/types/src/host/integration/session/session-api.d.ts +0 -88
- package/lib/types/src/host/integration/session/session-bin-lock.d.ts +0 -80
- package/lib/types/src/host/integration/session/session-header.d.ts +0 -151
- package/lib/types/src/host/integration/session/session-labels.d.ts +0 -56
- package/lib/types/src/host/integration/session/session-route-wiring.d.ts +0 -50
- package/lib/types/src/host/integration/session-admin-service.d.ts +0 -236
- package/lib/types/src/host/integration/session-api.d.ts +0 -81
- package/lib/types/src/host/integration/session-bin-lock.d.ts +0 -80
- package/lib/types/src/host/integration/session-header.d.ts +0 -118
- package/lib/types/src/host/integration/session-labels.d.ts +0 -56
- package/lib/types/src/host/integration/session-route-wiring.d.ts +0 -50
- package/lib/types/src/host/integration/session-scope.d.ts +0 -113
- package/lib/types/src/host/integration/session-tools.d.ts +0 -44
- package/lib/types/src/host/integration/strict-arguments.d.ts +0 -79
- package/lib/types/src/host/integration/takeover/takeover-api.d.ts +0 -62
- package/lib/types/src/host/integration/takeover/takeover-route-wiring.d.ts +0 -60
- package/lib/types/src/host/integration/takeover/takeover-service.d.ts +0 -167
- package/lib/types/src/host/integration/terminal-manifest.d.ts +0 -94
- package/lib/types/src/host/integration/tools/management-tools.d.ts +0 -245
- package/lib/types/src/host/integration/tools/remote-job-tools.d.ts +0 -316
- package/lib/types/src/host/integration/tools/remote-lsp-tools.d.ts +0 -43
- package/lib/types/src/host/integration/tools/remote-tools.d.ts +0 -236
- package/lib/types/src/host/integration/tools/session-target.d.ts +0 -52
- package/lib/types/src/host/integration/tools/session-tools.d.ts +0 -46
- package/lib/types/src/host/integration/tools/strict-arguments.d.ts +0 -79
- package/lib/types/src/host/integration/tools/terminal-manifest.d.ts +0 -94
- package/lib/types/src/host/integration/transfer/remote-copy.d.ts +0 -66
- package/lib/types/src/host/integration/transfer/transfer-adapters.d.ts +0 -48
- package/lib/types/src/host/integration/transfer/transfer-tracker.d.ts +0 -106
- package/lib/types/src/host/integration/transfer-adapters.d.ts +0 -44
- package/lib/types/src/host/integration/transfer-service.d.ts +0 -164
- package/lib/types/src/host/integration/transfer-tracker.d.ts +0 -106
- package/lib/types/src/host/integration/user-roots.d.ts +0 -461
- package/lib/types/src/wiring/connections.d.ts +0 -189
- package/lib/types/src/wiring/fs.d.ts +0 -13
- package/lib/types/src/wiring/install-memo.d.ts +0 -32
- package/lib/types/src/wiring/legacy-dirs.d.ts +0 -62
- package/lib/types/src/wiring/package-paths.d.ts +0 -35
- package/lib/types/src/wiring/roots.d.ts +0 -21
- package/lib/types/src/wiring/state-dir.d.ts +0 -29
- package/lib/types/src/wiring/tools.d.ts +0 -35
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The remote backend: one remote directory served over a {@link Transport}.
|
|
3
|
-
*
|
|
4
|
-
* This is the Tier 1 shape — no agent on the remote host, only POSIX tools the server already has
|
|
5
|
-
* (`sh`, `wc`, `stat`, `base64`). Directory listing is therefore one bounded shell script rather
|
|
6
|
-
* than a protocol method, and the script is built from a fixed template with a single quoted path,
|
|
7
|
-
* so there is no injection surface.
|
|
8
|
-
*
|
|
9
|
-
* Version tokens are whatever **one** transport reports for the same file state, and this backend never
|
|
10
|
-
* mixes two sources. On Tier 1 that is the shell `size:mtimeSeconds` shape (comparable with the ssh2
|
|
11
|
-
* transport); on Tier 2 it is the agent's `size:mtimeNs`, obtained from {@link Transport.statPath} so that a
|
|
12
|
-
* token read by `readText` and a token compared by `writeText` come from the same producer. Deriving one
|
|
13
|
-
* from the other is what made every read→write guard fail on Tier 2 (audit 0055).
|
|
14
|
-
*
|
|
15
|
-
* @module dsh-over-ssh/infra/backend/remote-backend
|
|
16
|
-
*/
|
|
17
|
-
import type { Backend, BackendEntry, BackendRead, BackendSandboxMode, BackendStat, BackendText, BackendWrite } from '../../app/backend.ts';
|
|
18
|
-
import type { RemoteCapabilities } from '../../domain/capability.ts';
|
|
19
|
-
import { type VersionedCacheStats } from '../cache/versioned.ts';
|
|
20
|
-
import type { Transport } from '../transport/transport.ts';
|
|
21
|
-
/** Options for {@link RemoteBackend}. */
|
|
22
|
-
export interface RemoteBackendOptions {
|
|
23
|
-
/** Backend id; the router namespaces target keys with it (equals the root key). */
|
|
24
|
-
readonly id: string;
|
|
25
|
-
/** A ready transport. Mutually exclusive with {@link getTransport}. */
|
|
26
|
-
readonly transport?: Transport;
|
|
27
|
-
/**
|
|
28
|
-
* Lazy transport resolver, called once on first use.
|
|
29
|
-
*
|
|
30
|
-
* The root registry's backend factory is synchronous while acquiring a transport is not, so the
|
|
31
|
-
* connection is established when the first operation needs it and reused afterwards.
|
|
32
|
-
*/
|
|
33
|
-
readonly getTransport?: () => Promise<Transport>;
|
|
34
|
-
/** Read cap applied by the backend itself; the router passes a smaller one when it wants to. */
|
|
35
|
-
readonly maxReadBytes?: number;
|
|
36
|
-
/**
|
|
37
|
-
* Freshness cache policy for the read path (FR-2.7). Omitted means {@link DEFAULT_CACHE}.
|
|
38
|
-
*
|
|
39
|
-
* This is the switch that makes "re-opening the same file within the TTL costs **zero** round trips"
|
|
40
|
-
* true. Before it existed, `VersionedCache` was written, benchmarked and then never constructed by
|
|
41
|
-
* any production code: every read went to the host, and the numbers in `baseline.md` measured a
|
|
42
|
-
* component nobody called (audit 0044).
|
|
43
|
-
*/
|
|
44
|
-
readonly cache?: RemoteBackendCacheOptions;
|
|
45
|
-
}
|
|
46
|
-
/** Cache policy for one remote backend. */
|
|
47
|
-
export interface RemoteBackendCacheOptions {
|
|
48
|
-
/** Freshness window; the FR-2.7 budget is 5 s. `0` disables retention (every read goes remote). */
|
|
49
|
-
readonly ttlMs?: number;
|
|
50
|
-
/** Bound on retained entries. */
|
|
51
|
-
readonly maxEntries?: number;
|
|
52
|
-
/** Bound on retained payload bytes. */
|
|
53
|
-
readonly maxBytes?: number;
|
|
54
|
-
/** Injectable clock, so a TTL can be tested without sleeping. */
|
|
55
|
-
readonly now?: () => number;
|
|
56
|
-
}
|
|
57
|
-
/** Filesystem access for one remote root over a transport. */
|
|
58
|
-
export declare class RemoteBackend implements Backend {
|
|
59
|
-
#private;
|
|
60
|
-
readonly id: string;
|
|
61
|
-
readonly sandboxMode: BackendSandboxMode | undefined;
|
|
62
|
-
/**
|
|
63
|
-
* Whether this root has a **native** watcher: the transport is one that speaks `watch.*` *and* its handshake
|
|
64
|
-
* advertised `watch === "native"`. It reads `false` until the transport is known — the capability is a fact
|
|
65
|
-
* about the connected agent, and claiming it early would advertise a watcher for a Tier 1 host that can
|
|
66
|
-
* never serve one.
|
|
67
|
-
*/
|
|
68
|
-
get watches(): boolean;
|
|
69
|
-
constructor(options: RemoteBackendOptions);
|
|
70
|
-
/**
|
|
71
|
-
* Read-path cache counters, for diagnostics and the performance baseline.
|
|
72
|
-
*
|
|
73
|
-
* Reported rather than inferred: the FR-2.7 claims ("0 round trips inside the TTL", "≈5× on
|
|
74
|
-
* revalidation") are only checkable if the counters behind them are reachable from outside.
|
|
75
|
-
*/
|
|
76
|
-
get cacheStats(): {
|
|
77
|
-
readonly reads: VersionedCacheStats;
|
|
78
|
-
readonly listings: VersionedCacheStats;
|
|
79
|
-
};
|
|
80
|
-
/** Remote capabilities discovered so far (probe results live on the transport). */
|
|
81
|
-
capabilities(): Promise<RemoteCapabilities>;
|
|
82
|
-
/**
|
|
83
|
-
* Stat a path, preferring the transport's **own** token source when it has one.
|
|
84
|
-
*
|
|
85
|
-
* This is where the two tiers are reconciled, and it is the *only* place that decides. A transport that
|
|
86
|
-
* exposes {@link Transport.statPath} (Tier 2: the agent's `fs.stat`) reports the very same token its
|
|
87
|
-
* `readFile` reports (`size:mtimeNs`), so a version read by one and compared by the other is comparing
|
|
88
|
-
* like with like. Tier 1 has no native stat, so the shell script below runs exactly as it always has and
|
|
89
|
-
* its long-standing `size:mtimeSeconds` tokens are untouched.
|
|
90
|
-
*
|
|
91
|
-
* Before this, `stat` always ran the shell script: on Tier 2 it produced seconds while every read produced
|
|
92
|
-
* nanoseconds, so the `remote_read` → `remote_write`/`remote_edit` version guard could never pass and the
|
|
93
|
-
* FR-2.7 stale-revalidation branch never matched either.
|
|
94
|
-
*/
|
|
95
|
-
stat(absolute: string, signal?: AbortSignal): Promise<BackendStat>;
|
|
96
|
-
list(absolute: string, signal?: AbortSignal): Promise<readonly BackendEntry[]>;
|
|
97
|
-
/**
|
|
98
|
-
* Read a file, going through the FR-2.7 freshness cache.
|
|
99
|
-
*
|
|
100
|
-
* The three states the budget is written in terms of:
|
|
101
|
-
* - **fresh** (inside the TTL): returned from memory, **zero** round trips;
|
|
102
|
-
* - **stale**: one cheap `stat` decides. If the version still matches, the retained body is reused and
|
|
103
|
-
* only the timestamp is refreshed — this is the ≈5× revalidation path;
|
|
104
|
-
* - **gone or changed**: re-read and replace.
|
|
105
|
-
*
|
|
106
|
-
* A read with a different cap is a different cache entry (see the field notes), so a capped probe can
|
|
107
|
-
* never satisfy a full read.
|
|
108
|
-
*/
|
|
109
|
-
readBytes(absolute: string, maxBytes: number, signal?: AbortSignal): Promise<BackendRead>;
|
|
110
|
-
/**
|
|
111
|
-
* Atomic in-place edit through the agent's `fs.edit` (审计 0030).
|
|
112
|
-
*
|
|
113
|
-
* The caller passes the version it just read; the agent re-checks it, so a concurrent modification
|
|
114
|
-
* between our read and this call is refused rather than clobbered. Throws `REMOTE_UNSUPPORTED` when the
|
|
115
|
-
* agent predates the method, which is what makes `ctx.fs` fall back to read → replace → write.
|
|
116
|
-
*/
|
|
117
|
-
editText(absolute: string, oldString: string, newString: string, replaceAll: boolean, expectedVersion: string | undefined, signal?: AbortSignal): Promise<BackendWrite & {
|
|
118
|
-
readonly replaced: number;
|
|
119
|
-
}>;
|
|
120
|
-
/**
|
|
121
|
-
* Apply a unified diff server-side: every hunk in one round trip, all-or-nothing.
|
|
122
|
-
*
|
|
123
|
-
* No client-side fallback on purpose. Reading the file, patching it here and writing it back would cost
|
|
124
|
-
* three round trips for two hunks — more than the `remote_edit` calls it is meant to replace — and would
|
|
125
|
-
* need a second implementation of the hunk-matching rules.
|
|
126
|
-
*/
|
|
127
|
-
patchText(absolute: string, diff: string, expectedVersion: string | undefined, signal?: AbortSignal): Promise<BackendWrite & {
|
|
128
|
-
readonly hunks: number;
|
|
129
|
-
}>;
|
|
130
|
-
readText(absolute: string, maxBytes: number, signal?: AbortSignal): Promise<BackendText>;
|
|
131
|
-
/**
|
|
132
|
-
* Cache-aware wrapper around {@link readManyUncached}, so a batch sweep leaves the cache warm.
|
|
133
|
-
*
|
|
134
|
-
* Satisfying the whole batch from cache is deliberately **not** attempted: the batch exists to collapse
|
|
135
|
-
* N round trips into one, and splitting it into cached and uncached halves would give back the round trips
|
|
136
|
-
* it was built to save. Cached entries are therefore refreshed from the batch's answer, and files already
|
|
137
|
-
* fresh are still fetched (one shared round trip) rather than removed from the batch.
|
|
138
|
-
*/
|
|
139
|
-
readMany(absolutePaths: readonly string[], options: {
|
|
140
|
-
readonly maxBytesEach: number;
|
|
141
|
-
readonly totalBytesCap?: number;
|
|
142
|
-
readonly signal?: AbortSignal;
|
|
143
|
-
}): Promise<{
|
|
144
|
-
readonly chunks: readonly (BackendText | undefined)[];
|
|
145
|
-
readonly missing: readonly string[];
|
|
146
|
-
}>;
|
|
147
|
-
/**
|
|
148
|
-
* Write text remotely.
|
|
149
|
-
*
|
|
150
|
-
* **The version guard is tier-aware, and that is the point.** The token a caller passes back is one this
|
|
151
|
-
* backend just handed out, so the guard must be verified by the *same* token source:
|
|
152
|
-
*
|
|
153
|
-
* - a transport with a native version token (Tier 2) verifies the token itself through
|
|
154
|
-
* {@link WriteFileRequest.expectedVersion}, which is what its own `fs.write` guard compares — no digest
|
|
155
|
-
* is involved and none could be, because the agent advertises no sha256 tool;
|
|
156
|
-
* - a Tier 1 transport verifies a content digest, so the current content is hashed and handed over as
|
|
157
|
-
* `expectedSha256`. That path is unchanged.
|
|
158
|
-
*
|
|
159
|
-
* A plain write is one round trip plus one `stat` for the new version.
|
|
160
|
-
*/
|
|
161
|
-
writeText(absolute: string, text: string, expectedVersion: string | undefined, signal?: AbortSignal): Promise<BackendWrite>;
|
|
162
|
-
/**
|
|
163
|
-
* Watch one remote path (directory, or one file) through the agent's native watcher.
|
|
164
|
-
*
|
|
165
|
-
* The agent watches **directories**: `watch.start` registers recursively from the given roots, and each
|
|
166
|
-
* reported event carries the path of the changed entry. A file target therefore watches its parent directory
|
|
167
|
-
* and filters events by exact path — the same shape the platform's own local watcher uses, and the only way
|
|
168
|
-
* inotify can report changes to one file.
|
|
169
|
-
*
|
|
170
|
-
* Degradation is **not** silent:
|
|
171
|
-
* - `watch.start` reporting `truncated` (maxWatches / `max_user_watches` cut registration short) means the
|
|
172
|
-
* tree is only partially covered; the watcher is stopped and this call fails rather than promising
|
|
173
|
-
* notifications it cannot deliver;
|
|
174
|
-
* - a poll whose cumulative `dropped` grew means events were lost (queue limit / kernel overflow); the
|
|
175
|
-
* owner is told through `changed(error)` and the watcher stops, so the caller rescans instead of trusting
|
|
176
|
-
* an incomplete stream.
|
|
177
|
-
*
|
|
178
|
-
* `changed()` also covers "nothing to re-stat but something happened" — the platform's contract is
|
|
179
|
-
* "re-stat this path", so events are coalesced per poll.
|
|
180
|
-
*
|
|
181
|
-
* @param absolute - root-absolute remote path (directory or file).
|
|
182
|
-
* @param changed - platform callback: no argument = re-stat; an error = the subscription is dead.
|
|
183
|
-
* @param signal - cancels the subscription; stopping is immediate and idempotent.
|
|
184
|
-
* @returns the disposer (stops the watcher; idempotent).
|
|
185
|
-
* @throws SshDevError `REMOTE_UNSUPPORTED` when this connection has no native watcher, `REMOTE_NOT_FOUND`
|
|
186
|
-
* when a file target's parent directory does not exist, `CANCELLED` when aborted during setup.
|
|
187
|
-
*/
|
|
188
|
-
watch(absolute: string, changed: (error?: Error) => void, signal: AbortSignal): Promise<() => Promise<void>>;
|
|
189
|
-
/**
|
|
190
|
-
* Stop every watcher this backend started.
|
|
191
|
-
*
|
|
192
|
-
* Not part of {@link Backend}: the wiring owns backend teardown and is outside this change's scope. It is
|
|
193
|
-
* public so whoever retires the backend can end the poll loops, and so tests can prove they stop.
|
|
194
|
-
*/
|
|
195
|
-
dispose(): Promise<void>;
|
|
196
|
-
/**
|
|
197
|
-
* Create a directory (FR-2.2.2).
|
|
198
|
-
*
|
|
199
|
-
* Prefers the agent's `fs.mkdir` (Tier 2) and falls back to one bounded shell script. The shell path is not
|
|
200
|
-
* dead weight: Tier 1 has no agent, and an older agent answers `E_METHOD`, which
|
|
201
|
-
* {@link Transport.makeDir} reports as `REMOTE_UNSUPPORTED`.
|
|
202
|
-
*
|
|
203
|
-
* **Why the protocol path is worth preferring**: the shell version depends on the remote having a POSIX
|
|
204
|
-
* `mkdir`, and its refusals have to be parsed back out of the script's own `DSH_ERROR=` line. Windows
|
|
205
|
-
* targets have neither. The product rule — `recursive: false` refuses a name that is already taken, so a
|
|
206
|
-
* 「新建目录」 dialog cannot silently "succeed" into someone else's folder — is applied **here**, once, for
|
|
207
|
-
* both paths.
|
|
208
|
-
*/
|
|
209
|
-
mkdir(absolute: string, options: {
|
|
210
|
-
readonly recursive: boolean;
|
|
211
|
-
}, signal?: AbortSignal): Promise<void>;
|
|
212
|
-
/**
|
|
213
|
-
* Rename/move a path (FR-2.2.2), refusing to clobber.
|
|
214
|
-
*
|
|
215
|
-
* The no-clobber rule lives in {@link renameCommand}'s script (see its notes): `mv` would either replace a
|
|
216
|
-
* file or move the source *inside* an existing directory, and both are silent.
|
|
217
|
-
*/
|
|
218
|
-
rename(from: string, to: string, options: {
|
|
219
|
-
readonly createParents: boolean;
|
|
220
|
-
}, signal?: AbortSignal): Promise<void>;
|
|
221
|
-
/** Remove a path (FR-2.2.2); `recursive: false` refuses a directory. */
|
|
222
|
-
remove(absolute: string, options: {
|
|
223
|
-
readonly recursive: boolean;
|
|
224
|
-
}, signal?: AbortSignal): Promise<void>;
|
|
225
|
-
/**
|
|
226
|
-
* Change one path's permission bits.
|
|
227
|
-
*
|
|
228
|
-
* Runs through `exec` rather than a transport primitive because the agent has no `fs.chmod`; the same
|
|
229
|
-
* script therefore works on both tiers, which is what keeps `remote_write`'s `mode` from meaning two
|
|
230
|
-
* different things depending on the host (audit 0064 P4). `chmodCommand` re-validates the octal digits, so
|
|
231
|
-
* this is not a place where model-supplied text reaches a shell unchecked.
|
|
232
|
-
*/
|
|
233
|
-
chmod(absolute: string, mode: string, signal?: AbortSignal): Promise<void>;
|
|
234
|
-
/** Whether this backend's root contains a path (used by the router's `contains`). */
|
|
235
|
-
contains(parent: string, candidate: string): boolean;
|
|
236
|
-
}
|
|
237
|
-
/** Parse `KEY=value` header lines into a map. */
|
|
238
|
-
export declare function parseHeaders(stdout: string): Map<string, string>;
|
|
239
|
-
//# sourceMappingURL=remote-backend.d.ts.map
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A bounded LRU cache: at most `maxEntries` entries and at most `maxBytes` of payload.
|
|
3
|
-
*
|
|
4
|
-
* Both bounds are enforced because "number of open files" and "memory held by cached file
|
|
5
|
-
* bodies" fail differently: a repository of tiny files hits the entry bound, a few large
|
|
6
|
-
* generated files hit the byte bound.
|
|
7
|
-
*
|
|
8
|
-
* @module dsh-over-ssh/infra/cache/lru
|
|
9
|
-
*/
|
|
10
|
-
/** Bounds for one cache. */
|
|
11
|
-
export interface LruCacheOptions {
|
|
12
|
-
/** Maximum number of retained entries; at least 1. */
|
|
13
|
-
readonly maxEntries: number;
|
|
14
|
-
/** Maximum total payload bytes; at least 1. */
|
|
15
|
-
readonly maxBytes: number;
|
|
16
|
-
}
|
|
17
|
-
/** Observable cache counters. */
|
|
18
|
-
export interface LruStats {
|
|
19
|
-
readonly entries: number;
|
|
20
|
-
readonly bytes: number;
|
|
21
|
-
readonly hits: number;
|
|
22
|
-
readonly misses: number;
|
|
23
|
-
readonly evictions: number;
|
|
24
|
-
/** Values rejected because a single value exceeded `maxBytes`. */
|
|
25
|
-
readonly rejectedOversize: number;
|
|
26
|
-
}
|
|
27
|
-
/** A size-bounded, recency-ordered map. */
|
|
28
|
-
export declare class LruCache<V> {
|
|
29
|
-
#private;
|
|
30
|
-
constructor(options: LruCacheOptions, sizeOf: (value: V) => number);
|
|
31
|
-
/** Look up a value and mark it most-recently used. */
|
|
32
|
-
get(key: string): V | undefined;
|
|
33
|
-
/** Look up a value without changing recency or counters. */
|
|
34
|
-
peek(key: string): V | undefined;
|
|
35
|
-
/** Whether the key is retained. */
|
|
36
|
-
has(key: string): boolean;
|
|
37
|
-
/**
|
|
38
|
-
* Insert or replace a value.
|
|
39
|
-
*
|
|
40
|
-
* @returns `false` when the value alone exceeds `maxBytes` and was therefore not stored.
|
|
41
|
-
*/
|
|
42
|
-
set(key: string, value: V): boolean;
|
|
43
|
-
/** Remove a key, returning whether it was present. */
|
|
44
|
-
delete(key: string): boolean;
|
|
45
|
-
/** Remove everything. */
|
|
46
|
-
clear(): void;
|
|
47
|
-
/** Keys from least to most recently used. */
|
|
48
|
-
keys(): readonly string[];
|
|
49
|
-
/** Current counters. */
|
|
50
|
-
get stats(): LruStats;
|
|
51
|
-
}
|
|
52
|
-
//# sourceMappingURL=lru.d.ts.map
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TTL + version cache used by the remote read path.
|
|
3
|
-
*
|
|
4
|
-
* The performance budget depends on this three-state behaviour:
|
|
5
|
-
* - within TTL: a hit with zero remote round trips;
|
|
6
|
-
* - past TTL: a *stale* hit whose body may still be reused if a cheap version check matches
|
|
7
|
-
* (this is what makes revalidation ≈5× cheaper than a first open);
|
|
8
|
-
* - invalidated: no usable body.
|
|
9
|
-
*
|
|
10
|
-
* @module dsh-over-ssh/infra/cache/versioned
|
|
11
|
-
*/
|
|
12
|
-
/** Cache policy for one cache instance. */
|
|
13
|
-
export interface VersionedCacheOptions {
|
|
14
|
-
/** Freshness window in milliseconds. */
|
|
15
|
-
readonly ttlMs: number;
|
|
16
|
-
readonly maxEntries: number;
|
|
17
|
-
readonly maxBytes: number;
|
|
18
|
-
/** Monotonic-or-wall clock; injectable so tests are deterministic. */
|
|
19
|
-
readonly now?: () => number;
|
|
20
|
-
}
|
|
21
|
-
/** One lookup result. */
|
|
22
|
-
export interface VersionedHit<V> {
|
|
23
|
-
readonly value: V;
|
|
24
|
-
/** The version token recorded when the body was cached. */
|
|
25
|
-
readonly version: string;
|
|
26
|
-
/** Whether the entry is still inside its freshness window. */
|
|
27
|
-
readonly fresh: boolean;
|
|
28
|
-
readonly ageMs: number;
|
|
29
|
-
}
|
|
30
|
-
/** Counters reported for diagnostics and the performance baseline. */
|
|
31
|
-
export interface VersionedCacheStats {
|
|
32
|
-
readonly entries: number;
|
|
33
|
-
readonly bytes: number;
|
|
34
|
-
readonly hits: number;
|
|
35
|
-
readonly misses: number;
|
|
36
|
-
readonly freshHits: number;
|
|
37
|
-
readonly staleHits: number;
|
|
38
|
-
/** Version checks that reused an existing body. */
|
|
39
|
-
readonly revalidations: number;
|
|
40
|
-
readonly evictions: number;
|
|
41
|
-
}
|
|
42
|
-
/** Freshness-aware cache keyed by route key. */
|
|
43
|
-
export declare class VersionedCache<V> {
|
|
44
|
-
#private;
|
|
45
|
-
constructor(options: VersionedCacheOptions, sizeOf: (value: V) => number);
|
|
46
|
-
/**
|
|
47
|
-
* Look up an entry.
|
|
48
|
-
*
|
|
49
|
-
* @param key - route key.
|
|
50
|
-
* @returns the entry with its freshness, or `undefined` when nothing is retained.
|
|
51
|
-
*/
|
|
52
|
-
get(key: string): VersionedHit<V> | undefined;
|
|
53
|
-
/** Insert or replace an entry, stamping it as just-read. */
|
|
54
|
-
set(key: string, value: V, version: string): boolean;
|
|
55
|
-
/**
|
|
56
|
-
* Refresh an entry's timestamp after a cheap remote version check matched.
|
|
57
|
-
*
|
|
58
|
-
* @param key - route key.
|
|
59
|
-
* @param version - the version observed by the check; a mismatch refuses the touch.
|
|
60
|
-
* @returns whether the entry was refreshed.
|
|
61
|
-
*/
|
|
62
|
-
touch(key: string, version: string): boolean;
|
|
63
|
-
/** Drop one entry. */
|
|
64
|
-
invalidate(key: string): boolean;
|
|
65
|
-
/**
|
|
66
|
-
* Drop every entry whose key starts with the prefix, used when a directory epoch changes.
|
|
67
|
-
*
|
|
68
|
-
* @param prefix - key prefix.
|
|
69
|
-
* @returns the number of removed entries.
|
|
70
|
-
*/
|
|
71
|
-
invalidatePrefix(prefix: string): number;
|
|
72
|
-
/** Remove everything. */
|
|
73
|
-
clear(): void;
|
|
74
|
-
/** Current counters. */
|
|
75
|
-
get stats(): VersionedCacheStats;
|
|
76
|
-
}
|
|
77
|
-
//# sourceMappingURL=versioned.d.ts.map
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A temporary file name that is unique per **write**, not just per process.
|
|
3
|
-
*
|
|
4
|
-
* The pid alone is not enough: two concurrent writes to the same target from one process picked the same
|
|
5
|
-
* temp name, so A's rename could publish B's bytes while reporting A's byte count, and B's rename then
|
|
6
|
-
* failed because the file was already gone (audit 0097). A per-process sequence closes that window, and the
|
|
7
|
-
* pid keeps the name distinct from another host process writing the same file.
|
|
8
|
-
*
|
|
9
|
-
* @module dsh-over-ssh/infra/temp-name
|
|
10
|
-
*/
|
|
11
|
-
/**
|
|
12
|
-
* Build a temp path next to `path` that no other in-flight write in this process will reuse.
|
|
13
|
-
*
|
|
14
|
-
* @param path - the target the temp file is later renamed onto.
|
|
15
|
-
* @returns `path` plus a `.dsh-tmp-<pid>-<n>` suffix, keeping the file in the same directory so the rename
|
|
16
|
-
* stays a rename and not a cross-device copy.
|
|
17
|
-
*/
|
|
18
|
-
export declare function temporaryNameFor(path: string): string;
|
|
19
|
-
//# sourceMappingURL=temp-name.d.ts.map
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 代理协议的**客户端层**:请求/响应配对、在飞窗口、blob 通道与关闭时清场。
|
|
3
|
-
*
|
|
4
|
-
* 线上格式的读写移到 `agent-protocol-codec.ts`,错误与截止移到 `agent-protocol-errors.ts`
|
|
5
|
-
* (审计 0079)。本文件只剩状态机本身:发请求、配对响应、管理 blob 通道、关闭时清场。
|
|
6
|
-
*
|
|
7
|
-
* @module dsh-over-ssh/infra/transport/agent-protocol-client
|
|
8
|
-
*/
|
|
9
|
-
import { SshDevError } from '../../../domain/errors.ts';
|
|
10
|
-
import type { AgentSession } from './agent-protocol-types.ts';
|
|
11
|
-
/**
|
|
12
|
-
* 代理协议的**客户端层**:请求/响应配对、在飞窗口、blob 通道、截止与取消、参数压缩与结果读取。
|
|
13
|
-
*
|
|
14
|
-
* 从 `agent-transport.ts` 拆出(审计 0077)。它只依赖帧层与类型层,**不依赖**任何 Transport 实现,
|
|
15
|
-
* 因此可以脱离传输单独测试(既有测试正是这么用的)。
|
|
16
|
-
*
|
|
17
|
-
* @module dsh-over-ssh/infra/transport/agent-protocol-client
|
|
18
|
-
*/
|
|
19
|
-
export declare class AgentProtocolClient {
|
|
20
|
-
#private;
|
|
21
|
-
constructor(session: AgentSession, options?: {
|
|
22
|
-
readonly requestTimeoutMs?: number;
|
|
23
|
-
readonly maxInFlight?: number;
|
|
24
|
-
readonly now?: () => number;
|
|
25
|
-
readonly epoch?: number;
|
|
26
|
-
readonly epochNow?: () => number;
|
|
27
|
-
/**
|
|
28
|
-
* Called once when the connection is declared dead.
|
|
29
|
-
*
|
|
30
|
-
* Without it a client-side death is invisible to the owner: a per-request deadline that expires
|
|
31
|
-
* poisons this client permanently (`#lost` is never cleared), yet the transport still reports
|
|
32
|
-
* `state === 'ready'`, so the transport service keeps handing the same dead client to every later
|
|
33
|
-
* call. Every one of them then fails instantly with the *first* timeout's message and numbers.
|
|
34
|
-
*/
|
|
35
|
-
readonly onFatal?: () => void;
|
|
36
|
-
/**
|
|
37
|
-
* Default session for every request on this connection, sent as `hello.params.sessionId`.
|
|
38
|
-
*
|
|
39
|
-
* Omitted (the default) means the field is not sent at all — byte-for-byte the pre-session
|
|
40
|
-
* handshake, which the agent answers with `sessionId: "default"`.
|
|
41
|
-
*/
|
|
42
|
-
readonly sessionId?: string;
|
|
43
|
-
});
|
|
44
|
-
/** Whether the `hello` handshake already succeeded. */
|
|
45
|
-
get handshaked(): boolean;
|
|
46
|
-
/** Whether the connection has been declared dead. */
|
|
47
|
-
get lost(): boolean;
|
|
48
|
-
/** Whether the agent accepted request-level cancellation (`capabilities.cancel === true`). */
|
|
49
|
-
get cancelSupported(): boolean;
|
|
50
|
-
/**
|
|
51
|
-
* Record the cancellation capability the handshake reported.
|
|
52
|
-
*
|
|
53
|
-
* Called by the owning transport after it adopts `hello`/`capabilities`; a client that is never told keeps
|
|
54
|
-
* the old behaviour. `true` only when the agent literally sent `true` — a missing bit is not a maybe.
|
|
55
|
-
*/
|
|
56
|
-
setCancelSupport(supported: boolean): void;
|
|
57
|
-
/** Whether {@link close} already ran. */
|
|
58
|
-
get closed(): boolean;
|
|
59
|
-
/** Feed stdout bytes; called by the transport's data handler. */
|
|
60
|
-
handleData(chunk: Uint8Array): void;
|
|
61
|
-
/**
|
|
62
|
-
* Handshake. The first method on a fresh session must be `hello`; a version mismatch is fatal and is
|
|
63
|
-
* **not** retried with a guessed version.
|
|
64
|
-
*/
|
|
65
|
-
handshake(signal?: AbortSignal, clientId?: string): Promise<unknown>;
|
|
66
|
-
/**
|
|
67
|
-
* Session the agent echoed for this connection, or `undefined` when it did not send one.
|
|
68
|
-
*
|
|
69
|
-
* `undefined` is meaningful: it means the agent predates session support, so this client must not claim
|
|
70
|
-
* the requests are isolated from one another.
|
|
71
|
-
*/
|
|
72
|
-
get negotiatedSessionId(): string | undefined;
|
|
73
|
-
/**
|
|
74
|
-
* Whether the agent reported that jobs outlive this connection (`hello.result.daemon`).
|
|
75
|
-
*
|
|
76
|
-
* Defaults to `false`, which is the honest reading of "the agent said nothing": today's agent does tie
|
|
77
|
-
* job lifetime to the connection, so assuming otherwise would make callers skip cleanup that is still
|
|
78
|
-
* required.
|
|
79
|
-
*/
|
|
80
|
-
get daemon(): boolean;
|
|
81
|
-
/** `capabilities`. Requires a completed handshake. */
|
|
82
|
-
capabilities(signal?: AbortSignal): Promise<unknown>;
|
|
83
|
-
/**
|
|
84
|
-
* Send one control-frame request (plus an optional blob frame) and await its response.
|
|
85
|
-
*
|
|
86
|
-
* @throws SshDevError `CONNECTION_LOST` when the session is gone, the deadline expired mid-request, or
|
|
87
|
-
* the stream desynchronised; `CANCELLED` when the caller's signal aborted.
|
|
88
|
-
* @throws AgentProtocolError when the agent answered with a structured error.
|
|
89
|
-
*/
|
|
90
|
-
request(method: string, params: Readonly<Record<string, unknown>>, options?: RequestOptions): Promise<unknown>;
|
|
91
|
-
/**
|
|
92
|
-
* The blob payload belonging to `control`.
|
|
93
|
-
*
|
|
94
|
-
* v1 semantics: a response with `result.blob === true` is followed by exactly one blob frame, so the
|
|
95
|
-
* blob cannot be awaited *before* the control response arrives; it is persisted on arrival instead
|
|
96
|
-
* (a fast agent may deliver it before this call is even entered).
|
|
97
|
-
*
|
|
98
|
-
* Under pipelining several requests may have declared a blob at once, so the channel is resolved by the
|
|
99
|
-
* identity of the control promise instead of from one global "current blob" slot.
|
|
100
|
-
*
|
|
101
|
-
* Bounded on purpose: the blob shares the request deadline, so an agent that declares a blob and then
|
|
102
|
-
* goes silent produces one failed request instead of a promise that never settles.
|
|
103
|
-
*
|
|
104
|
-
* @throws AgentProtocolError `E_PROTOCOL` when the response carried no blob within the deadline.
|
|
105
|
-
*/
|
|
106
|
-
readBlob(control: Promise<unknown>, options?: {
|
|
107
|
-
readonly signal?: AbortSignal;
|
|
108
|
-
readonly timeoutMs?: number;
|
|
109
|
-
}): Promise<Uint8Array>;
|
|
110
|
-
/** Declare the connection dead: reject everything outstanding and stop accepting work. */
|
|
111
|
-
failConnection(error: SshDevError): void;
|
|
112
|
-
/** Stop the client; idempotent. */
|
|
113
|
-
close(): void;
|
|
114
|
-
}
|
|
115
|
-
interface RequestOptions {
|
|
116
|
-
readonly signal?: AbortSignal;
|
|
117
|
-
readonly timeoutMs?: number;
|
|
118
|
-
/** Blob payload to send immediately after the control frame (`fs.write`). */
|
|
119
|
-
readonly blob?: Uint8Array;
|
|
120
|
-
/**
|
|
121
|
-
* Session this request belongs to; overrides the client's {@link AgentProtocolClientOptions.sessionId}
|
|
122
|
-
* default for this one request.
|
|
123
|
-
*
|
|
124
|
-
* The agent gives each session its own share of the per-connection in-flight window, so naming the
|
|
125
|
-
* session is what keeps one session's long request from queueing another's short one behind it.
|
|
126
|
-
* Measured on a real host before the agent grew sessions (audit 0021): a `fs.stat` on session B waited
|
|
127
|
-
* **3502 ms** behind eight long requests on session A, and **0.6 ms** when the window was not full.
|
|
128
|
-
*/
|
|
129
|
-
readonly session?: string;
|
|
130
|
-
}
|
|
131
|
-
export {};
|
|
132
|
-
//# sourceMappingURL=agent-protocol-client.d.ts.map
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 代理协议的**编解码层**:请求参数压缩与响应结果读取。
|
|
3
|
-
*
|
|
4
|
-
* 从 `agent-protocol-client.ts` 拆出(审计 0079)。全部是纯函数——无 I/O、无类、无状态,
|
|
5
|
-
* 因此可以脱离客户端单独测试。客户端只负责"发出去、等回来、配对",
|
|
6
|
-
* 把线上格式(ns→ms、缺省值、字符串枚举)留给这一层。
|
|
7
|
-
*
|
|
8
|
-
* @module dsh-over-ssh/infra/transport/agent-protocol-codec
|
|
9
|
-
*/
|
|
10
|
-
import { type ExecRequest } from '../transport.ts';
|
|
11
|
-
import type { AgentIndexEntry, AgentIndexScanResult, AgentProcStartRequest, AgentJobStatus, AgentJobOutput, AgentLspStartRequest, AgentLspStatus, AgentLspRead, AgentIndexScanRequest, AgentSearchMatch, AgentSearchResult, AgentWatchEvent, AgentWatchStartResult, AgentWatchPollResult, AgentWatchStopResult, AgentGitStatusResult, AgentCapabilities, AgentHelloExtras } from './agent-protocol-types.ts';
|
|
12
|
-
export declare const AGENT_MAX_JOB_WAIT_MS = 30000;
|
|
13
|
-
/**
|
|
14
|
-
* Add `waitMs` to a `proc.*` request only when it is positive.
|
|
15
|
-
*
|
|
16
|
-
* Omitted means "answer immediately", which is what an agent that predates the field does anyway —
|
|
17
|
-
* so a client that never passes a wait is byte-for-byte the pre-long-poll request.
|
|
18
|
-
*
|
|
19
|
-
* @param params - the method's own parameters.
|
|
20
|
-
* @param waitMs - requested server-side wait, or `undefined` for none.
|
|
21
|
-
* @returns the parameters to send.
|
|
22
|
-
*/
|
|
23
|
-
export declare function waitParams(params: Record<string, unknown>, waitMs: number | undefined): Record<string, unknown>;
|
|
24
|
-
/**
|
|
25
|
-
* Client deadline for a long-polled request.
|
|
26
|
-
*
|
|
27
|
-
* The server may legitimately block for `waitMs`, so the request budget must outlive it: a per-request
|
|
28
|
-
* deadline here is **fatal** to the whole connection (see `isFatalAgentFailure`), so an under-sized
|
|
29
|
-
* budget would turn a normal wait into a reconnect.
|
|
30
|
-
*
|
|
31
|
-
* @param waitMs - requested server-side wait, or `undefined` for none.
|
|
32
|
-
* @returns the request budget, or `undefined` to keep the transport default.
|
|
33
|
-
*/
|
|
34
|
-
export declare function waitBudget(waitMs: number | undefined): number | undefined;
|
|
35
|
-
export declare function asRecord(value: unknown): Record<string, unknown> | undefined;
|
|
36
|
-
/** Read the `capabilities` object out of a `hello`/`capabilities` result, dropping wrong-typed fields. */
|
|
37
|
-
export declare function readAgentCaps(result: Record<string, unknown> | undefined): AgentCapabilities | undefined;
|
|
38
|
-
/** Read the `agent` object of a `hello` result. */
|
|
39
|
-
export declare function readAgentExtras(result: Record<string, unknown> | undefined): AgentHelloExtras;
|
|
40
|
-
/** Drop `undefined` request fields so a control frame stays minimal (and deterministic in tests). */
|
|
41
|
-
export declare function compactParams(request: ExecRequest): Readonly<Record<string, unknown>>;
|
|
42
|
-
/** Read a finite number field, or `null` when the agent sent nothing usable. */
|
|
43
|
-
export declare function readOptionalNumber(source: Record<string, unknown>, key: string): number | null;
|
|
44
|
-
/**
|
|
45
|
-
* A boolean field, falling back only when the field is **absent**.
|
|
46
|
-
*
|
|
47
|
-
* `??`-style semantics rather than `||`: an explicit `false` from the agent is a real answer ("this stream
|
|
48
|
-
* was not cut") and must not be replaced by the fallback. Using `||` here would silently convert every
|
|
49
|
-
* `false` into the legacy value, which is exactly how a complete stderr gets reported as truncated.
|
|
50
|
-
*/
|
|
51
|
-
export declare function booleanOr(value: unknown, fallback: boolean): boolean;
|
|
52
|
-
/** A non-empty string field, else `undefined` (an empty spill path is not a readable path). */
|
|
53
|
-
export declare function nonEmptyString(value: unknown): string | undefined;
|
|
54
|
-
/**
|
|
55
|
-
* Convert Unix nanoseconds from the wire into epoch milliseconds.
|
|
56
|
-
*
|
|
57
|
-
* @returns `null` when the agent sent nothing usable, so a caller can tell "no timestamp" apart from
|
|
58
|
-
* "the epoch". A negative value is passed through as a negative time rather than clamped.
|
|
59
|
-
*/
|
|
60
|
-
export declare function nsToMs(value: number | null): number | null;
|
|
61
|
-
/** Narrow a `proc.*` job status; unknown fields are dropped, missing numbers become 0. */
|
|
62
|
-
export declare function readJobStatus(raw: unknown): AgentJobStatus;
|
|
63
|
-
/** Narrow a `proc.output` result. */
|
|
64
|
-
export declare function readJobOutput(raw: unknown): AgentJobOutput;
|
|
65
|
-
/** Narrow an `lsp.*` session status. */
|
|
66
|
-
export declare function readLspStatus(raw: unknown): AgentLspStatus;
|
|
67
|
-
/** Narrow an `lsp.read` result. */
|
|
68
|
-
export declare function readLspRead(raw: unknown): AgentLspRead;
|
|
69
|
-
/**
|
|
70
|
-
* Build the `proc.start` frame.
|
|
71
|
-
*
|
|
72
|
-
* Everything the caller omits is **omitted**, not defaulted: the agent's own defaults apply. The one
|
|
73
|
-
* conversion is `stdinBytes` → base64, because the wire has no byte type.
|
|
74
|
-
*/
|
|
75
|
-
export declare function compactProcParams(request: AgentProcStartRequest): Record<string, unknown>;
|
|
76
|
-
/** Build the `lsp.start` frame, omitting what the caller did not set. */
|
|
77
|
-
export declare function compactLspParams(request: AgentLspStartRequest): Record<string, unknown>;
|
|
78
|
-
/** Narrow one `index.scan` entry (`{path,size,mtimeNs,isDir?}`); unknown fields are dropped. */
|
|
79
|
-
export declare function readIndexEntry(value: unknown): AgentIndexEntry;
|
|
80
|
-
/**
|
|
81
|
-
* Narrow one `index.scan` result.
|
|
82
|
-
*
|
|
83
|
-
* Defaults follow the protocol: `strategy` `"unknown"` when the agent sent nothing usable (an unknown
|
|
84
|
-
* strategy is *not* silently mapped onto a known one), `filesScanned` `0`, `warnings` empty, and
|
|
85
|
-
* `truncated` `false` — the last one is the only optimistic default in this block, and it means
|
|
86
|
-
* "the agent did not report truncation", not "the result is complete".
|
|
87
|
-
*/
|
|
88
|
-
export declare function readIndexScanResult(value: unknown): AgentIndexScanResult;
|
|
89
|
-
/** Narrow one `search.grep` match; `column` defaults to `0` and the context arrays to empty. */
|
|
90
|
-
export declare function readSearchMatch(value: unknown): AgentSearchMatch;
|
|
91
|
-
/**
|
|
92
|
-
* Narrow one `search.grep` result.
|
|
93
|
-
*
|
|
94
|
-
* `dropped` is not a `search.grep` field in protocol v1; it is left at `0` and documented as
|
|
95
|
-
* meaningless here rather than being invented from `truncated`.
|
|
96
|
-
*/
|
|
97
|
-
export declare function readSearchResult(value: unknown): AgentSearchResult;
|
|
98
|
-
/** Narrow one `watch.start` result; `watched` lists the **roots** the agent really registered. */
|
|
99
|
-
export declare function readWatchStartResult(value: unknown): AgentWatchStartResult;
|
|
100
|
-
/**
|
|
101
|
-
* Narrow one `git.status` result.
|
|
102
|
-
*
|
|
103
|
-
* Unknown `status` values and empty paths are **dropped**, not coerced: the consumer draws a badge per entry,
|
|
104
|
-
* and a made-up status would be a claim the agent never made (the UI rule is "unknown status → no badge").
|
|
105
|
-
* A missing `changes` array reads as empty, and a non-boolean `truncated` reads as `false` — claiming
|
|
106
|
-
* truncation would tell the caller to rescan for no reason.
|
|
107
|
-
*/
|
|
108
|
-
export declare function readGitStatusResult(value: unknown): AgentGitStatusResult;
|
|
109
|
-
/** Narrow one `watch.poll` event; `atNs` is converted to `atMs`. */
|
|
110
|
-
export declare function readWatchEvent(value: unknown): AgentWatchEvent;
|
|
111
|
-
/**
|
|
112
|
-
* Narrow one `watch.poll` result.
|
|
113
|
-
*
|
|
114
|
-
* `dropped` is passed through untouched because it is **cumulative**; the client never derives a delta
|
|
115
|
-
* from it (doing so would under-report drops exactly when the stream is already incomplete).
|
|
116
|
-
*/
|
|
117
|
-
export declare function readWatchPollResult(value: unknown): AgentWatchPollResult;
|
|
118
|
-
/** Narrow one `watch.stop` result; a missing `stopped` counts as `false`, which is not a failure. */
|
|
119
|
-
export declare function readWatchStopResult(value: unknown): AgentWatchStopResult;
|
|
120
|
-
/**
|
|
121
|
-
* Drop `undefined` request fields so a control frame stays minimal (and deterministic in tests).
|
|
122
|
-
*
|
|
123
|
-
* The same rule is applied inline by `search.grep` / `watch.*`: an omitted option means "let the agent
|
|
124
|
-
* use its own default", never "send this client's idea of the default". Only `index.scan` needs a helper
|
|
125
|
-
* because it has more optional fields than the others put together.
|
|
126
|
-
*/
|
|
127
|
-
export declare function compactIndexParams(request: AgentIndexScanRequest): Readonly<Record<string, unknown>>;
|
|
128
|
-
/**
|
|
129
|
-
* The agent transport: one stdio session, serialised requests, explicit degradation.
|
|
130
|
-
*
|
|
131
|
-
* Lifecycle matches the other two implementations (`idle` → `connecting` → `ready`, `lost` on an
|
|
132
|
-
* unexpected death) so callers cannot tell them apart except through `id` and `capabilities`.
|
|
133
|
-
*/
|
|
134
|
-
/**
|
|
135
|
-
* Whether a plugin error means the frame stream can no longer be trusted.
|
|
136
|
-
*
|
|
137
|
-
* Only these close the connection: everything else (a missing file, a stale version, an unsupported
|
|
138
|
-
* method) is a normal answered request and leaves the session healthy.
|
|
139
|
-
*/
|
|
140
|
-
//# sourceMappingURL=agent-protocol-codec.d.ts.map
|