@tiphareth/dsh-hardssh 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -0
- package/cordis.patch.yml +26 -0
- package/lib/client.js +17780 -0
- package/lib/environment-BL1jddfB.js +449 -0
- package/lib/fs.js +478 -0
- package/lib/index.js +6758 -0
- package/lib/subprocess.js +600 -0
- package/lib/switch-fs-CAJpFY9C.js +193 -0
- package/lib/switch-fs-RrZtG2gv.js +210 -0
- package/lib/types/backend.d.ts +108 -0
- package/lib/types/base/capability.d.ts +107 -0
- package/lib/types/base/index.d.ts +18 -0
- package/lib/types/base/ledger-router.d.ts +48 -0
- package/lib/types/base/ledger.d.ts +82 -0
- package/lib/types/base/model.d.ts +108 -0
- package/lib/types/base/namespace.d.ts +57 -0
- package/lib/types/base/plugin.d.ts +102 -0
- package/lib/types/base/registry.d.ts +60 -0
- package/lib/types/base/router.d.ts +40 -0
- package/lib/types/client/api.d.ts +85 -0
- package/lib/types/client/directory-flow.d.ts +82 -0
- package/lib/types/client/icons.d.ts +19 -0
- package/lib/types/client/index.d.ts +34 -0
- package/lib/types/client/locales.d.ts +104 -0
- package/lib/types/client/manager-button.d.ts +32 -0
- package/lib/types/client/migrate.d.ts +20 -0
- package/lib/types/client/ssh/api.d.ts +89 -0
- package/lib/types/client/ssh/apply.d.ts +23 -0
- package/lib/types/client/ssh/locales.d.ts +156 -0
- package/lib/types/client/ssh/mount.d.ts +13 -0
- package/lib/types/client/ssh/panel/ClusterTab.d.ts +8 -0
- package/lib/types/client/ssh/panel/HostFingerprintDialog.d.ts +16 -0
- package/lib/types/client/ssh/panel/HostFormDialog.d.ts +13 -0
- package/lib/types/client/ssh/panel/HostsTab.d.ts +10 -0
- package/lib/types/client/ssh/panel/SessionSecretDialog.d.ts +16 -0
- package/lib/types/client/ssh/panel/SshPanel.d.ts +14 -0
- package/lib/types/client/ssh/panel/TerminalTab.d.ts +12 -0
- package/lib/types/client/ssh/panel/TransferTab.d.ts +8 -0
- package/lib/types/client/ssh/panel/TunnelsTab.d.ts +8 -0
- package/lib/types/client/ssh/panel/controller.d.ts +23 -0
- package/lib/types/client/ssh/panel/helpers.d.ts +15 -0
- package/lib/types/client/ssh/panel/xterm.css.d.ts +3 -0
- package/lib/types/client/ssh/sidebar-entry.d.ts +25 -0
- package/lib/types/client/state.d.ts +32 -0
- package/lib/types/client/text.d.ts +11 -0
- package/lib/types/client/workspace-badges.d.ts +38 -0
- package/lib/types/client/workspace-gate.d.ts +15 -0
- package/lib/types/client-http.d.ts +15 -0
- package/lib/types/core.d.ts +43 -0
- package/lib/types/fs.d.ts +36 -0
- package/lib/types/host-http.d.ts +25 -0
- package/lib/types/index.d.ts +53 -0
- package/lib/types/ledger.d.ts +132 -0
- package/lib/types/protocol.d.ts +101 -0
- package/lib/types/providers/index.d.ts +19 -0
- package/lib/types/providers/local/provider.d.ts +58 -0
- package/lib/types/providers/ssh/provider.d.ts +112 -0
- package/lib/types/remote/environment.d.ts +29 -0
- package/lib/types/remote/output.d.ts +38 -0
- package/lib/types/remote/remote-fs.d.ts +69 -0
- package/lib/types/remote/remote-process.d.ts +41 -0
- package/lib/types/remote/remote-subprocess.d.ts +31 -0
- package/lib/types/remote/remote-terminal.d.ts +41 -0
- package/lib/types/remote-runner.d.ts +83 -0
- package/lib/types/remote-search.d.ts +50 -0
- package/lib/types/routes.d.ts +24 -0
- package/lib/types/runtime/workspace-core.d.ts +59 -0
- package/lib/types/seam-state.d.ts +69 -0
- package/lib/types/shell.d.ts +8 -0
- package/lib/types/ssh/connection/lease.d.ts +16 -0
- package/lib/types/ssh/connection/pool.d.ts +57 -0
- package/lib/types/ssh/engine.d.ts +434 -0
- package/lib/types/ssh/exec/output.d.ts +31 -0
- package/lib/types/ssh/known-hosts.d.ts +89 -0
- package/lib/types/ssh/plugin.d.ts +57 -0
- package/lib/types/ssh/protocol.d.ts +236 -0
- package/lib/types/ssh/routes.d.ts +44 -0
- package/lib/types/ssh/store.d.ts +107 -0
- package/lib/types/ssh/tools.d.ts +35 -0
- package/lib/types/ssh/transfer/progress.d.ts +14 -0
- package/lib/types/ssh/vault.d.ts +110 -0
- package/lib/types/subprocess.d.ts +32 -0
- package/lib/types/switch/switch-fs.d.ts +88 -0
- package/lib/types/switch/switch-subprocess.d.ts +34 -0
- package/lib/types/tools.d.ts +12 -0
- package/package.json +134 -0
- package/src/backend.ts +624 -0
- package/src/base/capability.ts +86 -0
- package/src/base/index.ts +18 -0
- package/src/base/ledger-router.ts +128 -0
- package/src/base/ledger.ts +299 -0
- package/src/base/model.ts +118 -0
- package/src/base/namespace.ts +102 -0
- package/src/base/plugin.ts +170 -0
- package/src/base/registry.ts +109 -0
- package/src/base/router.ts +43 -0
- package/src/client/api.ts +196 -0
- package/src/client/css-modules.d.ts +5 -0
- package/src/client/directory-flow.tsx +482 -0
- package/src/client/icons.tsx +50 -0
- package/src/client/index.ts +210 -0
- package/src/client/locales.ts +105 -0
- package/src/client/manager-button.tsx +269 -0
- package/src/client/migrate.ts +109 -0
- package/src/client/ssh/api.ts +411 -0
- package/src/client/ssh/apply.ts +50 -0
- package/src/client/ssh/locales.ts +322 -0
- package/src/client/ssh/mount.tsx +83 -0
- package/src/client/ssh/panel/ClusterTab.tsx +123 -0
- package/src/client/ssh/panel/HostFingerprintDialog.tsx +82 -0
- package/src/client/ssh/panel/HostFormDialog.tsx +228 -0
- package/src/client/ssh/panel/HostsTab.tsx +236 -0
- package/src/client/ssh/panel/SessionSecretDialog.tsx +80 -0
- package/src/client/ssh/panel/SshPanel.tsx +77 -0
- package/src/client/ssh/panel/TerminalTab.tsx +176 -0
- package/src/client/ssh/panel/TransferTab.tsx +232 -0
- package/src/client/ssh/panel/TunnelsTab.tsx +177 -0
- package/src/client/ssh/panel/controller.ts +48 -0
- package/src/client/ssh/panel/helpers.ts +26 -0
- package/src/client/ssh/panel/panel.module.css +1006 -0
- package/src/client/ssh/panel/xterm.css.ts +2 -0
- package/src/client/ssh/sidebar-entry.ts +123 -0
- package/src/client/state.ts +99 -0
- package/src/client/text.ts +22 -0
- package/src/client/workspace-badges.ts +96 -0
- package/src/client/workspace-gate.ts +225 -0
- package/src/client/workspace.module.css +283 -0
- package/src/client-http.ts +45 -0
- package/src/core.ts +47 -0
- package/src/fs.ts +85 -0
- package/src/host-http.ts +74 -0
- package/src/index.ts +244 -0
- package/src/ledger.ts +416 -0
- package/src/protocol.ts +114 -0
- package/src/providers/index.ts +34 -0
- package/src/providers/local/provider.ts +179 -0
- package/src/providers/ssh/provider.ts +274 -0
- package/src/remote/environment.ts +123 -0
- package/src/remote/output.ts +142 -0
- package/src/remote/remote-fs.ts +532 -0
- package/src/remote/remote-process.ts +203 -0
- package/src/remote/remote-subprocess.ts +159 -0
- package/src/remote/remote-terminal.ts +141 -0
- package/src/remote-runner.ts +201 -0
- package/src/remote-search.ts +163 -0
- package/src/routes.ts +395 -0
- package/src/runtime/workspace-core.ts +154 -0
- package/src/seam-state.ts +185 -0
- package/src/shell.ts +10 -0
- package/src/ssh/connection/lease.ts +17 -0
- package/src/ssh/connection/pool.ts +275 -0
- package/src/ssh/engine.ts +1761 -0
- package/src/ssh/exec/output.ts +70 -0
- package/src/ssh/known-hosts.ts +214 -0
- package/src/ssh/plugin.ts +184 -0
- package/src/ssh/protocol.ts +227 -0
- package/src/ssh/routes.ts +892 -0
- package/src/ssh/store.ts +544 -0
- package/src/ssh/tools.ts +402 -0
- package/src/ssh/transfer/progress.ts +75 -0
- package/src/ssh/vault.ts +477 -0
- package/src/subprocess.ts +71 -0
- package/src/switch/switch-fs.ts +253 -0
- package/src/switch/switch-subprocess.ts +59 -0
- package/src/tools.ts +221 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSH provider — wraps the SSH engine/host store as a generic
|
|
3
|
+
* `WorkspaceProvider`, so the workspace base can route SSH-bound workspaces
|
|
4
|
+
* without knowing anything about ssh2. This is the concrete "provider-ssh"
|
|
5
|
+
* layer: everything SSH lives here (or in the legacy ./ssh, ./remote
|
|
6
|
+
* modules it adapts), and the base + runtime never import it directly by
|
|
7
|
+
* type.
|
|
8
|
+
*
|
|
9
|
+
* A record with `provider.id === 'ssh'` maps:
|
|
10
|
+
* - `provider.connectionRef.id` → HostStore alias
|
|
11
|
+
* - `location.root` → the remote root directory
|
|
12
|
+
* - `anchor.path` → the local anchor (session-visible)
|
|
13
|
+
*
|
|
14
|
+
* @module @tiphareth/dsh-hardssh/providers/ssh
|
|
15
|
+
*/
|
|
16
|
+
import type { WorkspaceCapabilityMap, WorkspaceConnection, WorkspaceProvider, WorkspaceProviderManifest } from '../../base/model.ts';
|
|
17
|
+
import type { WorkspaceFileSystem, WorkspaceProcessRuntime, WorkspaceSearchService, WorkspaceStat, WorkspaceTerminalService, WorkspaceDirEntry, WorkspaceSearchHit } from '../../base/capability.ts';
|
|
18
|
+
import type { SshEngine } from '../../ssh/engine.ts';
|
|
19
|
+
/** The ssh provider manifest. */
|
|
20
|
+
export declare const sshProviderManifest: WorkspaceProviderManifest;
|
|
21
|
+
/** One open SSH workspace connection. */
|
|
22
|
+
export declare class SshWorkspaceConnection implements WorkspaceConnection {
|
|
23
|
+
readonly workspaceId: string;
|
|
24
|
+
private readonly engine;
|
|
25
|
+
private readonly alias;
|
|
26
|
+
private readonly remoteRoot;
|
|
27
|
+
readonly providerId = "ssh";
|
|
28
|
+
constructor(workspaceId: string, engine: SshEngine, alias: string, remoteRoot: string);
|
|
29
|
+
get<K extends keyof WorkspaceCapabilityMap>(capability: K): WorkspaceCapabilityMap[K] | undefined;
|
|
30
|
+
private fs;
|
|
31
|
+
private process;
|
|
32
|
+
private terminal;
|
|
33
|
+
private search;
|
|
34
|
+
status(): 'connecting' | 'ready' | 'degraded' | 'closed';
|
|
35
|
+
close(): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
/** POSIX-join a remote root with a relative path, confining to the root. */
|
|
38
|
+
export declare function joinRemoteRoot(root: string, path: string): string;
|
|
39
|
+
/** Wrap engine FS calls as the generic WorkspaceFileSystem. */
|
|
40
|
+
export declare class SshWorkspaceFileSystem implements WorkspaceFileSystem {
|
|
41
|
+
private readonly engine;
|
|
42
|
+
private readonly alias;
|
|
43
|
+
private readonly root;
|
|
44
|
+
constructor(engine: SshEngine, alias: string, root: string);
|
|
45
|
+
private full;
|
|
46
|
+
stat(path: string, signal?: AbortSignal): Promise<WorkspaceStat | undefined>;
|
|
47
|
+
list(path: string, signal?: AbortSignal): Promise<WorkspaceDirEntry[]>;
|
|
48
|
+
readFile(path: string, signal?: AbortSignal): Promise<Uint8Array>;
|
|
49
|
+
writeFile(path: string, data: Uint8Array, signal?: AbortSignal): Promise<void>;
|
|
50
|
+
mkdir(path: string, options?: {
|
|
51
|
+
recursive?: boolean;
|
|
52
|
+
signal?: AbortSignal;
|
|
53
|
+
}): Promise<void>;
|
|
54
|
+
rm(path: string, options?: {
|
|
55
|
+
recursive?: boolean;
|
|
56
|
+
signal?: AbortSignal;
|
|
57
|
+
}): Promise<void>;
|
|
58
|
+
rename(from: string, to: string, signal?: AbortSignal): Promise<void>;
|
|
59
|
+
}
|
|
60
|
+
/** Wrap engine exec as the generic WorkspaceProcessRuntime. */
|
|
61
|
+
export declare class SshWorkspaceProcess implements WorkspaceProcessRuntime {
|
|
62
|
+
private readonly engine;
|
|
63
|
+
private readonly alias;
|
|
64
|
+
private readonly root;
|
|
65
|
+
constructor(engine: SshEngine, alias: string, root: string);
|
|
66
|
+
exec(command: string, options?: {
|
|
67
|
+
timeoutMs?: number;
|
|
68
|
+
cwd?: string;
|
|
69
|
+
signal?: AbortSignal;
|
|
70
|
+
}): Promise<{
|
|
71
|
+
stdout: string;
|
|
72
|
+
stderr: string;
|
|
73
|
+
exitCode: number;
|
|
74
|
+
timedOut?: boolean;
|
|
75
|
+
durationMs?: number;
|
|
76
|
+
}>;
|
|
77
|
+
private full;
|
|
78
|
+
}
|
|
79
|
+
/** Wrap engine PTY as the generic WorkspaceTerminalService. */
|
|
80
|
+
export declare class SshWorkspaceTerminal implements WorkspaceTerminalService {
|
|
81
|
+
private readonly engine;
|
|
82
|
+
private readonly alias;
|
|
83
|
+
private readonly root;
|
|
84
|
+
constructor(engine: SshEngine, alias: string, root: string);
|
|
85
|
+
openTerminal(cols: number, rows: number): Promise<import('../../base/capability.ts').WorkspaceTerminalHandle>;
|
|
86
|
+
}
|
|
87
|
+
/** Wrap the remote search service as the generic WorkspaceSearchService. */
|
|
88
|
+
export declare class SshWorkspaceSearch implements WorkspaceSearchService {
|
|
89
|
+
private readonly engine;
|
|
90
|
+
private readonly alias;
|
|
91
|
+
private readonly root;
|
|
92
|
+
private readonly service;
|
|
93
|
+
constructor(engine: SshEngine, alias: string, root: string);
|
|
94
|
+
glob(pattern: string, options?: {
|
|
95
|
+
root?: string;
|
|
96
|
+
maxDepth?: number;
|
|
97
|
+
signal?: AbortSignal;
|
|
98
|
+
}): Promise<{
|
|
99
|
+
hits: WorkspaceSearchHit[];
|
|
100
|
+
truncated: boolean;
|
|
101
|
+
}>;
|
|
102
|
+
grep(fixedPhrase: string, options?: {
|
|
103
|
+
root?: string;
|
|
104
|
+
signal?: AbortSignal;
|
|
105
|
+
}): Promise<{
|
|
106
|
+
hits: WorkspaceSearchHit[];
|
|
107
|
+
truncated: boolean;
|
|
108
|
+
}>;
|
|
109
|
+
}
|
|
110
|
+
/** The ssh provider factory. */
|
|
111
|
+
export declare function createSshWorkspaceProvider(engine: SshEngine): WorkspaceProvider;
|
|
112
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote-environment scrubbing and caching for the SSH process and terminal
|
|
3
|
+
* launchers. Ported from UynajGI/dsh-ssh (MIT) — adapted to the dsh-ssh
|
|
4
|
+
* engine. The read side is cached per (engine, alias) with a short TTL and
|
|
5
|
+
* merged concurrent requests, so repeated spawns/terminals on one host stop
|
|
6
|
+
* issuing an `env` exec each time (P1-12).
|
|
7
|
+
*/
|
|
8
|
+
import type { SshEngine } from '../ssh/engine.ts';
|
|
9
|
+
import { shellQuote as quoteShellArg } from '../shell.ts';
|
|
10
|
+
/** Quote one argument for a POSIX login shell (canonical implementation in shell.ts). */
|
|
11
|
+
export { quoteShellArg };
|
|
12
|
+
/** Wrap a remote command so it runs from the given working directory. */
|
|
13
|
+
export declare function wrapCwd(cwd: string, command: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Read + scrub the remote login environment, cached per (engine, alias).
|
|
16
|
+
* Concurrent requests share one fetch; failures are never cached.
|
|
17
|
+
*/
|
|
18
|
+
export declare function readScrubbedRemoteEnvironment(engine: SshEngine, alias: string): Promise<ReadonlyMap<string, string>>;
|
|
19
|
+
/** Drop cached environments for one alias (call after a host config change). */
|
|
20
|
+
export declare function invalidateRemoteEnvironment(engine: SshEngine, alias: string): void;
|
|
21
|
+
/**
|
|
22
|
+
* Remove harness-private and credential-shaped names from a remote environment.
|
|
23
|
+
*/
|
|
24
|
+
export declare function scrubRemoteEnvironment(environment: ReadonlyMap<string, string>): Map<string, string>;
|
|
25
|
+
/**
|
|
26
|
+
* Overlay explicit entries and serialize one validated environment for `env -i`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function serializeEnvironment(scrubbed: ReadonlyMap<string, string>, explicit: Readonly<NodeJS.ProcessEnv> | undefined): string;
|
|
29
|
+
//# sourceMappingURL=environment.d.ts.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded host-side projection of one remote output stream with a local
|
|
3
|
+
* spill file. Ported from UynajGI/dsh-ssh (MIT) — verbatim semantics.
|
|
4
|
+
*/
|
|
5
|
+
import type { CollectedOutput, SubprocessOutputRead, SubprocessOutputReader } from '@deepseek-ai/dsh-subprocess';
|
|
6
|
+
/**
|
|
7
|
+
* Collects one remote stream with a bounded in-memory tail. On first overflow
|
|
8
|
+
* a spill file is opened (when a spill cap is configured) and every chunk —
|
|
9
|
+
* already-collected ones included — is appended there while the full stream
|
|
10
|
+
* stays within the cap; without one, only the in-memory tail is retained.
|
|
11
|
+
*/
|
|
12
|
+
export declare class SshOutputCollector implements SubprocessOutputReader {
|
|
13
|
+
private readonly maxBytes;
|
|
14
|
+
private readonly maxSpillBytes;
|
|
15
|
+
private readonly label;
|
|
16
|
+
private readonly spillDir;
|
|
17
|
+
private chunks;
|
|
18
|
+
private retained;
|
|
19
|
+
private total;
|
|
20
|
+
private dropped;
|
|
21
|
+
private spillFd;
|
|
22
|
+
private spillFile;
|
|
23
|
+
private spillDisabled;
|
|
24
|
+
constructor(maxBytes: number, maxSpillBytes: number | undefined, label: string, spillDir?: string);
|
|
25
|
+
/** Append one byte-faithful remote chunk, trimming the retained tail to the cap. */
|
|
26
|
+
push(chunk: Uint8Array): void;
|
|
27
|
+
/** @inheritdoc */
|
|
28
|
+
readFrom(fromByte: number): SubprocessOutputRead;
|
|
29
|
+
/** Open the spill file lazily and append one chunk (plus any prior chunks once). */
|
|
30
|
+
private spillAll;
|
|
31
|
+
/** Stop spilling and remove the file once it can no longer hold the complete stream. */
|
|
32
|
+
private discardSpill;
|
|
33
|
+
/** Close the spill file once the stream has ended; stop advertising it on a failed close. */
|
|
34
|
+
seal(): void;
|
|
35
|
+
/** Seal the spill and return the final collected output. */
|
|
36
|
+
finalize(): CollectedOutput;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=output.d.ts.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote filesystem provider for the `ctx.fs` capability seam: paths,
|
|
3
|
+
* contents, and atomic staging files live on the remote host, reached through
|
|
4
|
+
* the dsh-ssh engine's SFTP/exec primitives. Ported and adapted from
|
|
5
|
+
* UynajGI/dsh-ssh (MIT, https://github.com/UynajGI/dsh-ssh) — the seam
|
|
6
|
+
* contract (targets, versions, atomic writes, CRLF handling, canonical path
|
|
7
|
+
* transport) is preserved; the connection owner is replaced by the shared
|
|
8
|
+
* SshEngine and the working directory follows the mode store's remote root.
|
|
9
|
+
*
|
|
10
|
+
* @module dsh-hardssh/remote-fs
|
|
11
|
+
*/
|
|
12
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
13
|
+
import type { SshEngine } from '../ssh/engine.ts';
|
|
14
|
+
import { FileSystem, FsVersion } from '@deepseek-ai/dsh-fs';
|
|
15
|
+
import type { FsDirEntry, FsEditOutcome, FsEditRequest, FsInfo, FsPathInfo, FsTarget, FsWriteIntent, FsWriteOutcome } from '@deepseek-ai/dsh-fs';
|
|
16
|
+
import type { WorkspaceState } from '../protocol.ts';
|
|
17
|
+
/**
|
|
18
|
+
* Remote filesystem backend over the dsh-ssh engine. The working directory
|
|
19
|
+
* follows the mode store: relative paths resolve against the resolved remote
|
|
20
|
+
* root; a POSIX-absolute cwd override is honored; local (Windows) cwds are
|
|
21
|
+
* ignored so the model's relative-path habit keeps working remotely.
|
|
22
|
+
*/
|
|
23
|
+
export declare class SshFileSystem extends FileSystem {
|
|
24
|
+
private readonly engine;
|
|
25
|
+
private readonly getState;
|
|
26
|
+
private readonly locks;
|
|
27
|
+
constructor(ctx: Context, engine: SshEngine, getState: () => WorkspaceState);
|
|
28
|
+
/** The active remote execution world (throws when not in remote mode). */
|
|
29
|
+
private current;
|
|
30
|
+
/**
|
|
31
|
+
* Resolve the working directory for a path: a POSIX-absolute cwd wins;
|
|
32
|
+
* anything else (relative or a local Windows path) falls back to the
|
|
33
|
+
* remote root.
|
|
34
|
+
*/
|
|
35
|
+
resolveRemoteCwd(cwd: string | undefined): string;
|
|
36
|
+
resolve(path: string, opts?: {
|
|
37
|
+
cwd?: string;
|
|
38
|
+
signal?: AbortSignal;
|
|
39
|
+
}): Promise<FsTarget>;
|
|
40
|
+
processPath(target: FsTarget): string;
|
|
41
|
+
fileUrl(target: FsTarget): string;
|
|
42
|
+
contains(parent: FsTarget, child: FsTarget): boolean;
|
|
43
|
+
stat(target: FsTarget, signal?: AbortSignal): Promise<FsInfo | undefined>;
|
|
44
|
+
lstat(path: string, opts?: {
|
|
45
|
+
cwd?: string;
|
|
46
|
+
}, signal?: AbortSignal): Promise<FsPathInfo | undefined>;
|
|
47
|
+
readText(target: FsTarget, signal?: AbortSignal): Promise<string>;
|
|
48
|
+
readBytes(target: FsTarget, signal: AbortSignal | undefined, maxBytes: number): Promise<Uint8Array>;
|
|
49
|
+
streamText(target: FsTarget, signal?: AbortSignal): Promise<AsyncIterable<string>>;
|
|
50
|
+
listDir(target: FsTarget, signal?: AbortSignal): Promise<FsDirEntry[]>;
|
|
51
|
+
writeText(target: FsTarget, content: string, expected?: FsWriteIntent, signal?: AbortSignal): Promise<FsWriteOutcome>;
|
|
52
|
+
editText(target: FsTarget, edit: FsEditRequest, expected?: {
|
|
53
|
+
version: ReturnType<typeof FsVersion>;
|
|
54
|
+
}, signal?: AbortSignal): Promise<FsEditOutcome>;
|
|
55
|
+
private withLock;
|
|
56
|
+
private canonicalPath;
|
|
57
|
+
private probe;
|
|
58
|
+
private requireRegular;
|
|
59
|
+
private readBytesRaw;
|
|
60
|
+
private checkWriteIntent;
|
|
61
|
+
private readForDiff;
|
|
62
|
+
private readForEdit;
|
|
63
|
+
private writeAtomic;
|
|
64
|
+
private removeStaging;
|
|
65
|
+
/** Normalize an engine stat/ls shape into the RemoteStats the helpers expect. */
|
|
66
|
+
private asStats;
|
|
67
|
+
}
|
|
68
|
+
export default SshFileSystem;
|
|
69
|
+
//# sourceMappingURL=remote-fs.d.ts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One asynchronously-started SSH command projected onto the subprocess seam.
|
|
3
|
+
* Ported from UynajGI/dsh-ssh (MIT) — the raw ssh2 channel is replaced by the
|
|
4
|
+
* engine's streaming ExecSession.
|
|
5
|
+
*/
|
|
6
|
+
import { PassThrough } from 'node:stream';
|
|
7
|
+
import type { SshEngine } from '../ssh/engine.ts';
|
|
8
|
+
import type { SubprocessHandle, SubprocessOutcome, SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess';
|
|
9
|
+
import type { WorkspaceState } from '../protocol.ts';
|
|
10
|
+
/** SSH-backed subprocess handle. The channel does not expose a remote pid, so `pid` is `-1`. */
|
|
11
|
+
export declare class SshSubprocessHandle implements SubprocessHandle {
|
|
12
|
+
private readonly engine;
|
|
13
|
+
private readonly getState;
|
|
14
|
+
private readonly spec;
|
|
15
|
+
private readonly spillDir;
|
|
16
|
+
readonly stdin: PassThrough | undefined;
|
|
17
|
+
readonly stdout: PassThrough | undefined;
|
|
18
|
+
readonly stderr: PassThrough | undefined;
|
|
19
|
+
readonly collected: SubprocessHandle['collected'];
|
|
20
|
+
readonly done: Promise<SubprocessOutcome>;
|
|
21
|
+
private readonly terminationController;
|
|
22
|
+
private readonly stdoutCollector;
|
|
23
|
+
private readonly stderrCollector;
|
|
24
|
+
private session;
|
|
25
|
+
private graceTimer;
|
|
26
|
+
private settled;
|
|
27
|
+
constructor(engine: SshEngine, getState: () => WorkspaceState, spec: SubprocessSpawnSpec, spillDir: string);
|
|
28
|
+
/** Remote process id; `-1` because the SSH channel does not expose one. */
|
|
29
|
+
get pid(): number;
|
|
30
|
+
/** @inheritdoc */
|
|
31
|
+
terminate(): void;
|
|
32
|
+
/** @inheritdoc */
|
|
33
|
+
waitForExit(signal?: AbortSignal): Promise<boolean>;
|
|
34
|
+
private readonly onAbort;
|
|
35
|
+
private signalTerm;
|
|
36
|
+
private settle;
|
|
37
|
+
private run;
|
|
38
|
+
private wireStdout;
|
|
39
|
+
private wireStderr;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=remote-process.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote subprocess provider for the `ctx.subprocess` capability seam: each
|
|
3
|
+
* spawn opens a streaming exec channel (or a PTY for terminals) on the
|
|
4
|
+
* current SSH-mode host through the dsh-ssh engine; output spill files stay
|
|
5
|
+
* on the local host. Ported from UynajGI/dsh-ssh (MIT).
|
|
6
|
+
*
|
|
7
|
+
* @module dsh-hardssh/remote-subprocess
|
|
8
|
+
*/
|
|
9
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
10
|
+
import type { SshEngine } from '../ssh/engine.ts';
|
|
11
|
+
import { SubprocessRuntime } from '@deepseek-ai/dsh-subprocess';
|
|
12
|
+
import type { SubprocessHandle, SubprocessSpawnSpec, SubprocessTerminalHandle, SubprocessTerminalSpawnSpec } from '@deepseek-ai/dsh-subprocess';
|
|
13
|
+
import type { WorkspaceState } from '../protocol.ts';
|
|
14
|
+
/** SSH command manager registered as `ctx.subprocess` (remote mode). */
|
|
15
|
+
export declare class SshSubprocessRuntime extends SubprocessRuntime {
|
|
16
|
+
private readonly engine;
|
|
17
|
+
private readonly getState;
|
|
18
|
+
private readonly live;
|
|
19
|
+
private readonly terminals;
|
|
20
|
+
private readonly spillDir;
|
|
21
|
+
private disposing;
|
|
22
|
+
constructor(ctx: Context, engine: SshEngine, getState: () => WorkspaceState);
|
|
23
|
+
/** @inheritdoc */
|
|
24
|
+
resolveExecutable(command: string, env?: Readonly<Record<string, string>>, signal?: AbortSignal): Promise<string>;
|
|
25
|
+
/** @inheritdoc */
|
|
26
|
+
spawn(spec: SubprocessSpawnSpec): SubprocessHandle;
|
|
27
|
+
/** @inheritdoc */
|
|
28
|
+
spawnTerminal(spec: SubprocessTerminalSpawnSpec): Promise<SubprocessTerminalHandle>;
|
|
29
|
+
}
|
|
30
|
+
export default SshSubprocessRuntime;
|
|
31
|
+
//# sourceMappingURL=remote-subprocess.d.ts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSH PTY allocation and process-session ownership for the subprocess seam.
|
|
3
|
+
* Ported from UynajGI/dsh-ssh (MIT) — the raw ssh2 shell channel is replaced
|
|
4
|
+
* by the engine's openShell session.
|
|
5
|
+
*/
|
|
6
|
+
import { PassThrough } from 'node:stream';
|
|
7
|
+
import type { SshEngine } from '../ssh/engine.ts';
|
|
8
|
+
import type { SubprocessOutcome, SubprocessTerminalForeground, SubprocessTerminalHandle, SubprocessTerminalSignal, SubprocessTerminalSpawnSpec } from '@deepseek-ai/dsh-subprocess';
|
|
9
|
+
import type { WorkspaceState } from '../protocol.ts';
|
|
10
|
+
/** One SSH PTY and its remote login shell, projected onto the subprocess terminal seam. */
|
|
11
|
+
export declare class SshTerminalHandle implements SubprocessTerminalHandle {
|
|
12
|
+
private readonly closeChannel;
|
|
13
|
+
private readonly sendChannel;
|
|
14
|
+
private readonly signalChannel;
|
|
15
|
+
private readonly graceMs;
|
|
16
|
+
readonly pid = -1;
|
|
17
|
+
readonly output: PassThrough;
|
|
18
|
+
readonly done: Promise<SubprocessOutcome>;
|
|
19
|
+
private exitResolve;
|
|
20
|
+
topLevelExited: boolean;
|
|
21
|
+
private cleanup;
|
|
22
|
+
constructor(closeChannel: () => void, sendChannel: (data: string) => void, signalChannel: (name: string) => void, graceMs: number);
|
|
23
|
+
/** Settle the exit promise (called by the runtime when the channel closes). */
|
|
24
|
+
resolveExit(outcome: SubprocessOutcome): void;
|
|
25
|
+
/** @inheritdoc */
|
|
26
|
+
write(data: string): Promise<void>;
|
|
27
|
+
/** The SSH channel does not expose a foreground process group. */
|
|
28
|
+
inspectForeground(): Promise<SubprocessTerminalForeground | undefined>;
|
|
29
|
+
/** @inheritdoc */
|
|
30
|
+
signalForeground(_signal: SubprocessTerminalSignal): Promise<number>;
|
|
31
|
+
/** @inheritdoc */
|
|
32
|
+
terminate(): Promise<void>;
|
|
33
|
+
private signal;
|
|
34
|
+
private closeOnce;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Allocate an SSH PTY, replace its login shell with the requested argv, and
|
|
38
|
+
* return the live terminal handle.
|
|
39
|
+
*/
|
|
40
|
+
export declare function spawnSshTerminal(engine: SshEngine, getState: () => WorkspaceState, spec: SubprocessTerminalSpawnSpec): Promise<SshTerminalHandle>;
|
|
41
|
+
//# sourceMappingURL=remote-terminal.d.ts.map
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote-workspace runner: lets other plugins (dsh-workbench-tiphareth) run
|
|
3
|
+
* git/files/terminals against an SSH-bound workspace. The workbench host asks
|
|
4
|
+
* `resolveRemote(root)` for a local anchor path; a hit returns the remote
|
|
5
|
+
* context + callable channels (git exec via the engine, SFTP ops, PTY).
|
|
6
|
+
* Kept duck-typed so the workbench never needs a compile-time dependency.
|
|
7
|
+
*/
|
|
8
|
+
import type { SshEngine } from './ssh/engine.ts';
|
|
9
|
+
import type { SshWorkspaceLedger } from './ledger.ts';
|
|
10
|
+
/** The remote context one SSH-bound workspace resolves to. */
|
|
11
|
+
export interface RemoteWorkspaceContext {
|
|
12
|
+
alias: string;
|
|
13
|
+
remoteRoot: string;
|
|
14
|
+
/** Run `git [args...]` inside remoteRoot. Mirrors runGit's result shape. */
|
|
15
|
+
git(args: readonly string[], opts?: {
|
|
16
|
+
timeoutMs?: number;
|
|
17
|
+
input?: string;
|
|
18
|
+
allowNonZero?: boolean;
|
|
19
|
+
}): Promise<{
|
|
20
|
+
stdout: string;
|
|
21
|
+
stderr: string;
|
|
22
|
+
exitCode: number;
|
|
23
|
+
}>;
|
|
24
|
+
/** Run one arbitrary command (used by tools that shell out). */
|
|
25
|
+
run(command: string, opts?: {
|
|
26
|
+
timeoutMs?: number;
|
|
27
|
+
}): Promise<{
|
|
28
|
+
stdout: string;
|
|
29
|
+
stderr: string;
|
|
30
|
+
exitCode: number;
|
|
31
|
+
}>;
|
|
32
|
+
/** SFTP operations. */
|
|
33
|
+
list(path: string): Promise<Array<{
|
|
34
|
+
name: string;
|
|
35
|
+
type: 'dir' | 'file' | 'other';
|
|
36
|
+
size: number;
|
|
37
|
+
mtimeMs: number;
|
|
38
|
+
}>>;
|
|
39
|
+
stat(path: string): Promise<{
|
|
40
|
+
type: 'dir' | 'file' | 'other';
|
|
41
|
+
size: number;
|
|
42
|
+
mtimeMs: number;
|
|
43
|
+
mode: number;
|
|
44
|
+
}>;
|
|
45
|
+
readFile(path: string): Promise<{
|
|
46
|
+
content: Buffer;
|
|
47
|
+
mtime: number;
|
|
48
|
+
size: number;
|
|
49
|
+
}>;
|
|
50
|
+
writeFile(path: string, content: Buffer, expectedMtime?: number): Promise<{
|
|
51
|
+
mtime: number;
|
|
52
|
+
}>;
|
|
53
|
+
mkdir(path: string): Promise<void>;
|
|
54
|
+
rm(path: string, recursive: boolean): Promise<void>;
|
|
55
|
+
rename(from: string, to: string): Promise<void>;
|
|
56
|
+
/** Open an interactive SSH PTY in remoteRoot (columns/rows for the channel). */
|
|
57
|
+
openTerminal(cols: number, rows: number): Promise<RemotePtyHandle>;
|
|
58
|
+
}
|
|
59
|
+
/** Minimal PTY handle (duck-typed for consumers like dsh-workbench-tiphareth). */
|
|
60
|
+
export interface RemotePtyHandle {
|
|
61
|
+
shell: string;
|
|
62
|
+
write(data: string): void;
|
|
63
|
+
resize(cols: number, rows: number): void;
|
|
64
|
+
kill(): void;
|
|
65
|
+
onData(handler: (data: string) => void): {
|
|
66
|
+
dispose(): void;
|
|
67
|
+
};
|
|
68
|
+
onExit(handler: (event: {
|
|
69
|
+
exitCode: number;
|
|
70
|
+
}) => void): {
|
|
71
|
+
dispose(): void;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** Builds remote-workspace contexts off the shared ledger + engine. */
|
|
75
|
+
export declare class RemoteWorkspaceRunner {
|
|
76
|
+
private readonly engine;
|
|
77
|
+
private readonly ledger;
|
|
78
|
+
constructor(engine: SshEngine, ledger: SshWorkspaceLedger);
|
|
79
|
+
/** Resolve a LOCAL anchor path to a remote context, or null if not SSH-bound. */
|
|
80
|
+
resolveRemote(root: string): RemoteWorkspaceContext | null;
|
|
81
|
+
private build;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=remote-runner.d.ts.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote search semantics shared by the UI file-name search (backend) and the
|
|
3
|
+
* remote_search agent tool (glob / grep modes). Centralizes command building,
|
|
4
|
+
* literal escaping, NUL-delimited parsing, and truncation rules (P1-11) so
|
|
5
|
+
* the three former hand-rolled implementations cannot drift again.
|
|
6
|
+
*
|
|
7
|
+
* Command output is never treated as authorization evidence: this service
|
|
8
|
+
* returns raw absolute paths; callers (backend) re-authorize each hit against
|
|
9
|
+
* the workspace root before exposing it.
|
|
10
|
+
*/
|
|
11
|
+
import type { SshEngine } from './ssh/engine.ts';
|
|
12
|
+
/** One SSH workspace to search on. */
|
|
13
|
+
export interface SearchTarget {
|
|
14
|
+
alias: string;
|
|
15
|
+
/** Remote root; every path is reported absolute under it. */
|
|
16
|
+
root: string;
|
|
17
|
+
}
|
|
18
|
+
/** One raw file-name hit (absolute path, type). */
|
|
19
|
+
export interface RemoteNameHit {
|
|
20
|
+
path: string;
|
|
21
|
+
isDir: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface RemoteNameSearch {
|
|
24
|
+
query: string;
|
|
25
|
+
hits: RemoteNameHit[];
|
|
26
|
+
truncated: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface RemoteGlobResult {
|
|
29
|
+
hits: string[];
|
|
30
|
+
truncated: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface RemoteGrepResult {
|
|
33
|
+
lines: string[];
|
|
34
|
+
truncated: boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* One search implementation shared by the UI and the agent tools.
|
|
38
|
+
* Stateless apart from the engine; safe to construct per call or share.
|
|
39
|
+
*/
|
|
40
|
+
export declare class RemoteSearchService {
|
|
41
|
+
private readonly engine;
|
|
42
|
+
constructor(engine: SshEngine);
|
|
43
|
+
/** Literal file-name search under `root` (skips node_modules/.git). */
|
|
44
|
+
searchNames(target: SearchTarget, query: string): Promise<RemoteNameSearch>;
|
|
45
|
+
/** Glob search (pattern keeps glob semantics; `**` crosses directories). */
|
|
46
|
+
glob(target: SearchTarget, pattern: string): Promise<RemoteGlobResult>;
|
|
47
|
+
/** Fixed-string grep (literal pattern, `-F`; NUL file boundary `-Z`). */
|
|
48
|
+
grepFixed(target: SearchTarget, pattern: string): Promise<RemoteGrepResult>;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=remote-search.d.ts.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { WebRoute } from '@deepseek-ai/dsh-host-webserver';
|
|
2
|
+
import { type SshEngine } from './ssh/engine.ts';
|
|
3
|
+
import type { SshWorkspaceLedger } from './ledger.ts';
|
|
4
|
+
import { type WorkspaceFileService } from './backend.ts';
|
|
5
|
+
/** Route family dependencies. */
|
|
6
|
+
export interface WorkspaceRoutesDeps {
|
|
7
|
+
/** Read-only host surface (list only; the SSH routes own the write path). */
|
|
8
|
+
hosts: import('./core.ts').HostStoreView;
|
|
9
|
+
engine: SshEngine;
|
|
10
|
+
ledger: SshWorkspaceLedger;
|
|
11
|
+
/** Gated workspace file service (tree / file / search). */
|
|
12
|
+
files: WorkspaceFileService;
|
|
13
|
+
/** Register the anchor dir as a HOST workspace (sidebar visibility). */
|
|
14
|
+
registerHostWorkspace?: (anchorPath: string, title: string) => Promise<void>;
|
|
15
|
+
/** Drop the host workspace registration for an anchor dir. */
|
|
16
|
+
unregisterHostWorkspace?: (anchorPath: string) => Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Build every /api/dsh-hardssh route.
|
|
20
|
+
* @param deps - host store (alias listing), ssh engine, workspace ledger, file service.
|
|
21
|
+
* @returns the routes to register.
|
|
22
|
+
*/
|
|
23
|
+
export declare function makeRoutes(deps: WorkspaceRoutesDeps): WebRoute[];
|
|
24
|
+
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime bridge: exposes the generic workspace base (ledger, provider
|
|
3
|
+
* registry, router, plugin host) as a cordis service under the canonical
|
|
4
|
+
* `workspaceCore` name, while keeping the legacy `hardsshCore` /
|
|
5
|
+
* `sshWorkspaceCore` aliases pointing at the same instances so existing
|
|
6
|
+
* consumers keep working.
|
|
7
|
+
*
|
|
8
|
+
* This module is the DSH boundary: it imports the base (provider-agnostic)
|
|
9
|
+
* and the legacy SSH modules, but the base itself never imports this
|
|
10
|
+
* module — direction of dependency stays base ← runtime.
|
|
11
|
+
*
|
|
12
|
+
* @module @tiphareth/dsh-hardssh/runtime/workspace-core
|
|
13
|
+
*/
|
|
14
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
15
|
+
import { WorkspaceProviderRegistry, type WorkspaceRegistry } from '../base/registry.ts';
|
|
16
|
+
import type { WorkspaceRecord } from '../base/model.ts';
|
|
17
|
+
import { WorkspaceLedger } from '../base/ledger.ts';
|
|
18
|
+
import type { SshEngine } from '../ssh/engine.ts';
|
|
19
|
+
import type { HostStoreView } from '../core.ts';
|
|
20
|
+
import type { SshWorkspaceLedger } from '../ledger.ts';
|
|
21
|
+
import type { SshWorkspaceRecord } from '../protocol.ts';
|
|
22
|
+
import { LedgerWorkspaceRouter } from '../base/ledger-router.ts';
|
|
23
|
+
/** The canonical workspace-core service shape. */
|
|
24
|
+
export interface WorkspaceCore {
|
|
25
|
+
/** Generic ledger (WorkspaceRecord CRUD + anchor index). */
|
|
26
|
+
ledger: WorkspaceLedger;
|
|
27
|
+
/** Provider registry (local/ssh/builtin + third-party). */
|
|
28
|
+
providers: WorkspaceProviderRegistry;
|
|
29
|
+
/** Ledger+provider router for the switch facades. */
|
|
30
|
+
router: LedgerWorkspaceRouter;
|
|
31
|
+
/** Registry surface exposed to plugins. */
|
|
32
|
+
registry: WorkspaceRegistry;
|
|
33
|
+
/** Map a legacy SSH record into a generic WorkspaceRecord. */
|
|
34
|
+
fromSshRecord(record: SshWorkspaceRecord): WorkspaceRecord;
|
|
35
|
+
}
|
|
36
|
+
/** Convert a legacy SSH-bound workspace record into the generic model. */
|
|
37
|
+
export declare function sshRecordToWorkspaceRecord(record: SshWorkspaceRecord): WorkspaceRecord;
|
|
38
|
+
/** Persistence path for the generic ledger (~/.dsh/workspaces/index.v1.json). */
|
|
39
|
+
export declare function genericLedgerPath(): string;
|
|
40
|
+
/** Anchor root for managed generic workspaces (~/.dsh/workspaces/anchors). */
|
|
41
|
+
export declare function genericAnchorRoot(): string;
|
|
42
|
+
/**
|
|
43
|
+
* Build and provide the workspace core on the cordis context: registers the
|
|
44
|
+
* builtin providers, instantiates the generic ledger (persisting under
|
|
45
|
+
* ~/.dsh/workspaces/index.v1.json), constructs the ledger router, and
|
|
46
|
+
* provides `workspaceCore`.
|
|
47
|
+
*/
|
|
48
|
+
export declare function mountWorkspaceCore(ctx: Context, deps: {
|
|
49
|
+
engine?: SshEngine;
|
|
50
|
+
hosts?: HostStoreView;
|
|
51
|
+
}): WorkspaceCore;
|
|
52
|
+
/** The legacy SSH ledger held by the hardssh core (type bridge). */
|
|
53
|
+
export type { SshWorkspaceLedger };
|
|
54
|
+
declare module '@deepseek-ai/cordis' {
|
|
55
|
+
interface Context {
|
|
56
|
+
workspaceCore: WorkspaceCore;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=workspace-core.d.ts.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared derived seam state: ONE ledger-derived routing snapshot
|
|
3
|
+
* (records + normalized anchor index) plus the per-record fs/subprocess
|
|
4
|
+
* instances, owned by the hardssh core and consumed by BOTH switch rows
|
|
5
|
+
* (./fs and ./subprocess). Because both seams read the SAME state, they can
|
|
6
|
+
* never disagree with each other or with the ledger's own index for a given
|
|
7
|
+
* session cwd — eliminating the split-brain window of the old per-seam
|
|
8
|
+
* async refresh caches.
|
|
9
|
+
*
|
|
10
|
+
* The state re-applies SYNCHRONOUSLY on every ledger commit: ledger
|
|
11
|
+
* listeners run synchronously AFTER the commit already swapped the
|
|
12
|
+
* in-memory records, so a synchronous re-apply is always current — no async
|
|
13
|
+
* refresh pipeline, no stale-snapshot race, no revision-guard interleaving.
|
|
14
|
+
* The initial file load is awaited before the state is marked ready; until
|
|
15
|
+
* then routing degrades to local with a one-time warning from the rows.
|
|
16
|
+
*
|
|
17
|
+
* Per-record instances are built lazily on first route and kept alive
|
|
18
|
+
* across refreshes (connections survive); removing a workspace drops its
|
|
19
|
+
* instances so stale target keys resolve to NOTHING (fail closed), never to
|
|
20
|
+
* the local backend.
|
|
21
|
+
*
|
|
22
|
+
* @module dsh-hardssh/seam-state
|
|
23
|
+
*/
|
|
24
|
+
import type { SshWorkspaceRecord } from './protocol.ts';
|
|
25
|
+
import { type SshWorkspaceLedger } from './ledger.ts';
|
|
26
|
+
import { type WorkspaceWorld } from './switch/switch-fs.ts';
|
|
27
|
+
import type { SshSubprocessRuntime } from './remote/remote-subprocess.ts';
|
|
28
|
+
/** Shared routing state for the fs/subprocess switch rows. */
|
|
29
|
+
export declare class WorkspaceSeamState {
|
|
30
|
+
private readonly ledger;
|
|
31
|
+
private readonly records;
|
|
32
|
+
private readonly instances;
|
|
33
|
+
private anchors;
|
|
34
|
+
private appliedRevision;
|
|
35
|
+
private ready;
|
|
36
|
+
private readyResolvers;
|
|
37
|
+
private fsFactory;
|
|
38
|
+
private subFactory;
|
|
39
|
+
constructor(ledger: SshWorkspaceLedger);
|
|
40
|
+
/** Bind the fs-row's per-record backend builder (called once at apply). */
|
|
41
|
+
bindFs(factory: (record: SshWorkspaceRecord) => WorkspaceWorld): void;
|
|
42
|
+
/** Bind the subprocess-row's per-record runtime builder (called once at apply). */
|
|
43
|
+
bindSub(factory: (record: SshWorkspaceRecord) => SshSubprocessRuntime): void;
|
|
44
|
+
/** True once the initial ledger snapshot has been applied. */
|
|
45
|
+
isReady(): boolean;
|
|
46
|
+
/** Resolves once the initial ledger snapshot has been applied. */
|
|
47
|
+
whenReady(): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Subscribe to ledger commits and kick the initial load. Returns the
|
|
50
|
+
* subscription disposer (register it through ctx.effect for HMR cleanup).
|
|
51
|
+
*/
|
|
52
|
+
attach(): () => void;
|
|
53
|
+
/** Re-apply the ledger's current in-memory state (idempotent, guarded). */
|
|
54
|
+
applySync(): void;
|
|
55
|
+
/** The record owning a session cwd, or undefined when the cwd is local. */
|
|
56
|
+
private recordFor;
|
|
57
|
+
private ensureFs;
|
|
58
|
+
private ensureSubprocess;
|
|
59
|
+
/** The fs world owning a session cwd, or undefined (local). */
|
|
60
|
+
worldForFs(cwd: string | undefined): WorkspaceWorld | undefined;
|
|
61
|
+
/** The fs world for one namespaced target key (`wfs://<id>/…` or legacy
|
|
62
|
+
* `ssh:<id>:`), or undefined when the owning workspace no longer exists
|
|
63
|
+
* (stale key → fail closed). */
|
|
64
|
+
worldForFsNamespace(namespace: string): WorkspaceWorld | undefined;
|
|
65
|
+
/** The subprocess runtime owning a session cwd, or undefined (local). */
|
|
66
|
+
runtimeForSub(cwd: string | undefined): SshSubprocessRuntime | undefined;
|
|
67
|
+
private markReady;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=seam-state.d.ts.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical POSIX shell single-quoting for remote command construction.
|
|
3
|
+
* Replaces the three near-identical copies that used to live in backend.ts,
|
|
4
|
+
* tools.ts and remote/environment.ts.
|
|
5
|
+
*/
|
|
6
|
+
/** Quote one argument for a POSIX login shell (single-quote, `'\''` for embedded quotes). */
|
|
7
|
+
export declare function shellQuote(value: string): string;
|
|
8
|
+
//# sourceMappingURL=shell.d.ts.map
|