@tiphareth/dsh-hardssh 0.1.2 → 0.2.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/LICENSE +30 -0
- package/README.md +49 -25
- package/SKILLS.md +140 -0
- package/lib/base/index.js +95 -0
- package/lib/client.js +1171 -1321
- package/lib/fs.js +307 -416
- package/lib/index.js +5428 -2991
- package/lib/ledger-B-LXlftp.js +43 -0
- package/lib/ledger-D2ezq1iW.js +376 -0
- package/lib/model-Cp7f70Mb.js +5 -0
- package/lib/registry-CViuzKYI.js +421 -0
- package/lib/subprocess.js +92 -549
- package/lib/types/backend.d.ts +73 -78
- package/lib/types/base/capability.d.ts +4 -0
- package/lib/types/base/ledger-router.d.ts +42 -26
- package/lib/types/base/ledger.d.ts +30 -19
- package/lib/types/base/model.d.ts +17 -8
- package/lib/types/base/plugin.d.ts +11 -0
- package/lib/types/base/registry.d.ts +28 -31
- package/lib/types/base/router.d.ts +0 -7
- package/lib/types/client/api.d.ts +8 -8
- package/lib/types/client/connect-host.d.ts +13 -0
- package/lib/types/client/index.d.ts +6 -4
- package/lib/types/client/locales.d.ts +15 -1
- package/lib/types/client/session-connect-gate.d.ts +71 -0
- package/lib/types/client/ssh/api.d.ts +11 -9
- package/lib/types/client/ssh/apply.d.ts +14 -8
- package/lib/types/client/ssh/locales.d.ts +8 -1
- package/lib/types/client/ssh/ops-tab.d.ts +34 -0
- package/lib/types/client/ssh/panel/ClusterTab.d.ts +3 -1
- package/lib/types/client/ssh/panel/ConnectionErrorDialog.d.ts +8 -0
- package/lib/types/client/ssh/panel/SessionSecretDialog.d.ts +4 -1
- package/lib/types/client/ssh/panel/SshPanel.d.ts +8 -8
- package/lib/types/client/ssh/panel/TerminalTab.d.ts +3 -5
- package/lib/types/client/ssh/panel/TransferTab.d.ts +5 -1
- package/lib/types/client/ssh/panel/TunnelsTab.d.ts +3 -1
- package/lib/types/client/ssh/session-target.d.ts +17 -0
- package/lib/types/client/state.d.ts +4 -1
- package/lib/types/client/workspace-panel-entry.d.ts +30 -0
- package/lib/types/client/workspace-panel.d.ts +24 -0
- package/lib/types/client-http.d.ts +77 -6
- package/lib/types/core.d.ts +5 -16
- package/lib/types/fs.d.ts +18 -25
- package/lib/types/index.d.ts +138 -9
- package/lib/types/ledger.d.ts +16 -111
- package/lib/types/protocol.d.ts +0 -5
- package/lib/types/providers/index.d.ts +6 -3
- package/lib/types/providers/local/provider.d.ts +83 -23
- package/lib/types/providers/ssh/provider.d.ts +24 -57
- package/lib/types/remote/environment.d.ts +2 -0
- package/lib/types/remote/remote-fs.d.ts +12 -1
- package/lib/types/remote/remote-process.d.ts +22 -1
- package/lib/types/remote/remote-subprocess.d.ts +13 -1
- package/lib/types/remote/remote-terminal.d.ts +4 -0
- package/lib/types/remote-search.d.ts +3 -3
- package/lib/types/routes.d.ts +36 -8
- package/lib/types/runtime/dsh-capabilities.d.ts +18 -0
- package/lib/types/runtime/workspace-core.d.ts +73 -38
- package/lib/types/runtime/workspace-migration.d.ts +113 -0
- package/lib/types/ssh/connection/lease.d.ts +23 -0
- package/lib/types/ssh/connection/manager.d.ts +359 -0
- package/lib/types/ssh/connection/pool.d.ts +8 -1
- package/lib/types/ssh/engine.d.ts +97 -267
- package/lib/types/ssh/known-hosts.d.ts +22 -5
- package/lib/types/ssh/local-transfer-policy.d.ts +20 -0
- package/lib/types/ssh/plugin.d.ts +10 -15
- package/lib/types/ssh/protocol.d.ts +18 -0
- package/lib/types/ssh/routes.d.ts +15 -7
- package/lib/types/ssh/sftp/service.d.ts +216 -0
- package/lib/types/ssh/store.d.ts +20 -8
- package/lib/types/ssh/terminal/service.d.ts +49 -0
- package/lib/types/ssh/tunnel/service.d.ts +26 -0
- package/lib/types/ssh/vault.d.ts +51 -4
- package/lib/types/subprocess.d.ts +13 -21
- package/lib/types/switch/switch-fs.d.ts +52 -2
- package/lib/types/switch/switch-subprocess.d.ts +32 -3
- package/lib/types/tools.d.ts +7 -6
- package/lib/types/workspace-tool-ops.d.ts +33 -0
- package/lib/types/workspace.d.ts +18 -0
- package/lib/vault-3gpWct2Q.js +559 -0
- package/lib/workspace.js +2 -0
- package/package.json +58 -25
- package/scripts/export-legacy-workspaces.mjs +136 -0
- package/src/backend.ts +155 -483
- package/src/base/capability.ts +4 -0
- package/src/base/ledger-router.ts +214 -65
- package/src/base/ledger.ts +175 -48
- package/src/base/model.ts +18 -8
- package/src/base/plugin.ts +15 -4
- package/src/base/registry.ts +49 -41
- package/src/base/router.ts +0 -8
- package/src/client/api.ts +8 -36
- package/src/client/connect-host.ts +199 -0
- package/src/client/directory-flow.tsx +91 -36
- package/src/client/index.ts +56 -53
- package/src/client/locales.ts +14 -0
- package/src/client/session-connect-gate.ts +163 -0
- package/src/client/ssh/api.ts +245 -108
- package/src/client/ssh/apply.ts +23 -24
- package/src/client/ssh/locales.ts +16 -2
- package/src/client/ssh/ops-tab.tsx +79 -0
- package/src/client/ssh/panel/ClusterTab.tsx +9 -20
- package/src/client/ssh/panel/ConnectionErrorDialog.tsx +33 -0
- package/src/client/ssh/panel/SessionSecretDialog.tsx +5 -1
- package/src/client/ssh/panel/SshPanel.tsx +81 -77
- package/src/client/ssh/panel/TerminalTab.tsx +18 -37
- package/src/client/ssh/panel/TransferTab.tsx +43 -33
- package/src/client/ssh/panel/TunnelsTab.tsx +9 -26
- package/src/client/ssh/panel/panel.module.css +90 -127
- package/src/client/ssh/session-target.ts +69 -0
- package/src/client/state.ts +9 -1
- package/src/client/workspace-badges.ts +99 -96
- package/src/client/workspace-panel-entry.tsx +91 -0
- package/src/client/workspace-panel.tsx +227 -0
- package/src/client/workspace.module.css +76 -2
- package/src/client-http.ts +118 -18
- package/src/core.ts +37 -47
- package/src/fs.ts +137 -85
- package/src/index.ts +535 -244
- package/src/ledger.ts +62 -416
- package/src/protocol.ts +0 -6
- package/src/providers/index.ts +8 -4
- package/src/providers/local/provider.ts +259 -87
- package/src/providers/ssh/provider.ts +158 -155
- package/src/remote/environment.ts +22 -1
- package/src/remote/remote-fs.ts +123 -34
- package/src/remote/remote-process.ts +265 -203
- package/src/remote/remote-subprocess.ts +61 -18
- package/src/remote/remote-terminal.ts +106 -21
- package/src/remote-search.ts +122 -26
- package/src/routes.ts +416 -395
- package/src/runtime/dsh-capabilities.ts +19 -0
- package/src/runtime/workspace-core.ts +171 -88
- package/src/runtime/workspace-migration.ts +472 -0
- package/src/ssh/connection/lease.ts +35 -0
- package/src/ssh/connection/manager.ts +1083 -0
- package/src/ssh/connection/pool.ts +341 -275
- package/src/ssh/engine.ts +269 -1477
- package/src/ssh/known-hosts.ts +42 -18
- package/src/ssh/local-transfer-policy.ts +83 -0
- package/src/ssh/plugin.ts +20 -19
- package/src/ssh/protocol.ts +22 -1
- package/src/ssh/routes.ts +419 -187
- package/src/ssh/sftp/service.ts +967 -0
- package/src/ssh/store.ts +169 -72
- package/src/ssh/terminal/service.ts +177 -0
- package/src/ssh/tools.ts +42 -20
- package/src/ssh/tunnel/service.ts +217 -0
- package/src/ssh/vault.ts +245 -91
- package/src/subprocess.ts +88 -71
- package/src/switch/switch-fs.ts +141 -10
- package/src/switch/switch-subprocess.ts +80 -6
- package/src/tools.ts +217 -221
- package/src/workspace-tool-ops.ts +101 -0
- package/src/workspace.ts +52 -0
- package/lib/environment-BL1jddfB.js +0 -449
- package/lib/switch-fs-CAJpFY9C.js +0 -193
- package/lib/switch-fs-RrZtG2gv.js +0 -210
- package/lib/types/client/manager-button.d.ts +0 -32
- package/lib/types/client/ssh/mount.d.ts +0 -13
- package/lib/types/client/ssh/panel/HostsTab.d.ts +0 -10
- package/lib/types/client/ssh/panel/controller.d.ts +0 -23
- package/lib/types/client/ssh/sidebar-entry.d.ts +0 -25
- package/lib/types/client/workspace-gate.d.ts +0 -15
- package/lib/types/remote-runner.d.ts +0 -83
- package/lib/types/seam-state.d.ts +0 -69
- package/src/client/manager-button.tsx +0 -269
- package/src/client/ssh/mount.tsx +0 -83
- package/src/client/ssh/panel/HostsTab.tsx +0 -236
- package/src/client/ssh/panel/controller.ts +0 -48
- package/src/client/ssh/sidebar-entry.ts +0 -123
- package/src/client/workspace-gate.ts +0 -225
- package/src/remote-runner.ts +0 -201
- package/src/seam-state.ts +0 -185
|
@@ -13,10 +13,11 @@
|
|
|
13
13
|
*
|
|
14
14
|
* @module @tiphareth/dsh-hardssh/providers/ssh
|
|
15
15
|
*/
|
|
16
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
16
17
|
import type { WorkspaceCapabilityMap, WorkspaceConnection, WorkspaceProvider, WorkspaceProviderManifest } from '../../base/model.ts';
|
|
17
|
-
import type {
|
|
18
|
+
import type { WorkspaceSearchHit, WorkspaceSearchService } from '../../base/capability.ts';
|
|
18
19
|
import type { SshEngine } from '../../ssh/engine.ts';
|
|
19
|
-
/** The ssh provider manifest. */
|
|
20
|
+
/** The ssh provider manifest (provider API v2, no separate terminal capability — terminals live on `workspace.process`). */
|
|
20
21
|
export declare const sshProviderManifest: WorkspaceProviderManifest;
|
|
21
22
|
/** One open SSH workspace connection. */
|
|
22
23
|
export declare class SshWorkspaceConnection implements WorkspaceConnection {
|
|
@@ -24,73 +25,35 @@ export declare class SshWorkspaceConnection implements WorkspaceConnection {
|
|
|
24
25
|
private readonly engine;
|
|
25
26
|
private readonly alias;
|
|
26
27
|
private readonly remoteRoot;
|
|
28
|
+
private readonly cordisContext;
|
|
27
29
|
readonly providerId = "ssh";
|
|
28
|
-
|
|
30
|
+
private state;
|
|
31
|
+
private fsInstance;
|
|
32
|
+
private processInstance;
|
|
33
|
+
private searchInstance;
|
|
34
|
+
/** One workspace is one fixed remote execution world for every capability instance. */
|
|
35
|
+
private readonly stateOf;
|
|
36
|
+
constructor(workspaceId: string, engine: SshEngine, alias: string, remoteRoot: string, cordisContext: Context);
|
|
29
37
|
get<K extends keyof WorkspaceCapabilityMap>(capability: K): WorkspaceCapabilityMap[K] | undefined;
|
|
30
38
|
private fs;
|
|
31
39
|
private process;
|
|
32
|
-
private terminal;
|
|
33
40
|
private search;
|
|
34
41
|
status(): 'connecting' | 'ready' | 'degraded' | 'closed';
|
|
35
42
|
close(): Promise<void>;
|
|
36
43
|
}
|
|
37
44
|
/** POSIX-join a remote root with a relative path, confining to the root. */
|
|
38
45
|
export declare function joinRemoteRoot(root: string, path: string): string;
|
|
39
|
-
/** Wrap
|
|
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. */
|
|
46
|
+
/** Wrap the shared RemoteSearchService as the generic WorkspaceSearchService. */
|
|
88
47
|
export declare class SshWorkspaceSearch implements WorkspaceSearchService {
|
|
89
48
|
private readonly engine;
|
|
90
49
|
private readonly alias;
|
|
91
|
-
private readonly
|
|
50
|
+
private readonly remoteRoot;
|
|
92
51
|
private readonly service;
|
|
93
|
-
constructor(engine: SshEngine, alias: string,
|
|
52
|
+
constructor(engine: SshEngine, alias: string, remoteRoot: string);
|
|
53
|
+
/** Workspace-relative POSIX search base → absolute remote root. */
|
|
54
|
+
private searchBase;
|
|
55
|
+
/** Absolute path → path relative to `base`, or undefined when outside it. */
|
|
56
|
+
private relativeTo;
|
|
94
57
|
glob(pattern: string, options?: {
|
|
95
58
|
root?: string;
|
|
96
59
|
maxDepth?: number;
|
|
@@ -106,7 +69,11 @@ export declare class SshWorkspaceSearch implements WorkspaceSearchService {
|
|
|
106
69
|
hits: WorkspaceSearchHit[];
|
|
107
70
|
truncated: boolean;
|
|
108
71
|
}>;
|
|
72
|
+
/** Depth of a base-relative path (root-level entries are depth 1, find -maxdepth style). */
|
|
73
|
+
private depthOf;
|
|
109
74
|
}
|
|
110
|
-
/** The ssh provider factory.
|
|
111
|
-
|
|
75
|
+
/** The ssh provider factory. `context` is mandatory: the provider serves only
|
|
76
|
+
* the real DSH FileSystem/SubprocessRuntime capabilities, which need a Cordis
|
|
77
|
+
* scope to isolate each workspace's resources. */
|
|
78
|
+
export declare function createSshWorkspaceProvider(engine: SshEngine, context: Context): WorkspaceProvider;
|
|
112
79
|
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -24,6 +24,8 @@ export declare function invalidateRemoteEnvironment(engine: SshEngine, alias: st
|
|
|
24
24
|
export declare function scrubRemoteEnvironment(environment: ReadonlyMap<string, string>): Map<string, string>;
|
|
25
25
|
/**
|
|
26
26
|
* Overlay explicit entries and serialize one validated environment for `env -i`.
|
|
27
|
+
* Login-critical names are emitted first (see CRITICAL_ENV_ORDER); everything
|
|
28
|
+
* else keeps the remote's own order.
|
|
27
29
|
*/
|
|
28
30
|
export declare function serializeEnvironment(scrubbed: ReadonlyMap<string, string>, explicit: Readonly<NodeJS.ProcessEnv> | undefined): string;
|
|
29
31
|
//# sourceMappingURL=environment.d.ts.map
|
|
@@ -14,6 +14,8 @@ import type { SshEngine } from '../ssh/engine.ts';
|
|
|
14
14
|
import { FileSystem, FsVersion } from '@deepseek-ai/dsh-fs';
|
|
15
15
|
import type { FsDirEntry, FsEditOutcome, FsEditRequest, FsInfo, FsPathInfo, FsTarget, FsWriteIntent, FsWriteOutcome } from '@deepseek-ai/dsh-fs';
|
|
16
16
|
import type { WorkspaceState } from '../protocol.ts';
|
|
17
|
+
/** Decode a base64-wrapped NUL-terminated canonical path from `realpath -mz`. */
|
|
18
|
+
export declare function decodeCanonicalPath(encoded: string): string;
|
|
17
19
|
/**
|
|
18
20
|
* Remote filesystem backend over the dsh-ssh engine. The working directory
|
|
19
21
|
* follows the mode store: relative paths resolve against the resolved remote
|
|
@@ -24,7 +26,15 @@ export declare class SshFileSystem extends FileSystem {
|
|
|
24
26
|
private readonly engine;
|
|
25
27
|
private readonly getState;
|
|
26
28
|
private readonly locks;
|
|
27
|
-
|
|
29
|
+
/** When set, every resolved target must stay under this root (the SSH
|
|
30
|
+
* workspace's `location.root`). Absent for the legacy/global SSH seam,
|
|
31
|
+
* which intentionally addresses the whole host. */
|
|
32
|
+
private readonly confineRoot;
|
|
33
|
+
constructor(ctx: Context, engine: SshEngine, getState: () => WorkspaceState, confineRoot?: string);
|
|
34
|
+
/** Root-confinement gate for the workspace capability: reject any canonical
|
|
35
|
+
* path that escapes `confineRoot` (including via an in-root symlink whose
|
|
36
|
+
* realpath lands outside). Mirrors the local provider's fail-closed rule. */
|
|
37
|
+
private confine;
|
|
28
38
|
/** The active remote execution world (throws when not in remote mode). */
|
|
29
39
|
private current;
|
|
30
40
|
/**
|
|
@@ -53,6 +63,7 @@ export declare class SshFileSystem extends FileSystem {
|
|
|
53
63
|
version: ReturnType<typeof FsVersion>;
|
|
54
64
|
}, signal?: AbortSignal): Promise<FsEditOutcome>;
|
|
55
65
|
private withLock;
|
|
66
|
+
private canonicalTarget;
|
|
56
67
|
private canonicalPath;
|
|
57
68
|
private probe;
|
|
58
69
|
private requireRegular;
|
|
@@ -22,7 +22,13 @@ export declare class SshSubprocessHandle implements SubprocessHandle {
|
|
|
22
22
|
private readonly stdoutCollector;
|
|
23
23
|
private readonly stderrCollector;
|
|
24
24
|
private session;
|
|
25
|
+
private inputSink;
|
|
26
|
+
private inputErrorListener;
|
|
27
|
+
private terminationPhase;
|
|
25
28
|
private graceTimer;
|
|
29
|
+
private forceTimer;
|
|
30
|
+
private resolveForced;
|
|
31
|
+
private forceClosed;
|
|
26
32
|
private settled;
|
|
27
33
|
constructor(engine: SshEngine, getState: () => WorkspaceState, spec: SubprocessSpawnSpec, spillDir: string);
|
|
28
34
|
/** Remote process id; `-1` because the SSH channel does not expose one. */
|
|
@@ -32,9 +38,24 @@ export declare class SshSubprocessHandle implements SubprocessHandle {
|
|
|
32
38
|
/** @inheritdoc */
|
|
33
39
|
waitForExit(signal?: AbortSignal): Promise<boolean>;
|
|
34
40
|
private readonly onAbort;
|
|
35
|
-
private
|
|
41
|
+
private signalCurrentPhase;
|
|
42
|
+
private startTermination;
|
|
43
|
+
/** Immediately close the owned channel and settle `done`, including while
|
|
44
|
+
* openExec is still pending. A late-opened channel observes forceClosed and
|
|
45
|
+
* closes itself before any stream wiring is installed. */
|
|
46
|
+
forceClose(): void;
|
|
36
47
|
private settle;
|
|
37
48
|
private run;
|
|
49
|
+
/** Install every callback under one ownership boundary. Any synchronous
|
|
50
|
+
* wiring failure or async stdin sink failure closes the established session
|
|
51
|
+
* and rejects the handle instead of leaking it. */
|
|
52
|
+
private runSession;
|
|
53
|
+
/** Route one stream through the engine's leak guard before it reaches the
|
|
54
|
+
* consumer. Without this, a remote `bash`/subprocess channel bypassed the
|
|
55
|
+
* redaction that every exec/cluster result already applies. Redaction is
|
|
56
|
+
* exact-match over known secrets, so it is best-effort and may change byte
|
|
57
|
+
* length — the same trade-off the PTY route documents. */
|
|
58
|
+
private redactBytes;
|
|
38
59
|
private wireStdout;
|
|
39
60
|
private wireStderr;
|
|
40
61
|
}
|
|
@@ -11,6 +11,9 @@ import type { SshEngine } from '../ssh/engine.ts';
|
|
|
11
11
|
import { SubprocessRuntime } from '@deepseek-ai/dsh-subprocess';
|
|
12
12
|
import type { SubprocessHandle, SubprocessSpawnSpec, SubprocessTerminalHandle, SubprocessTerminalSpawnSpec } from '@deepseek-ai/dsh-subprocess';
|
|
13
13
|
import type { WorkspaceState } from '../protocol.ts';
|
|
14
|
+
/** Hard upper bound for plugin/workspace teardown, independent of a caller's
|
|
15
|
+
* potentially very large per-process TERM/KILL grace setting. */
|
|
16
|
+
export declare const SUBPROCESS_DISPOSE_DEADLINE_MS = 5000;
|
|
14
17
|
/** SSH command manager registered as `ctx.subprocess` (remote mode). */
|
|
15
18
|
export declare class SshSubprocessRuntime extends SubprocessRuntime {
|
|
16
19
|
private readonly engine;
|
|
@@ -18,8 +21,17 @@ export declare class SshSubprocessRuntime extends SubprocessRuntime {
|
|
|
18
21
|
private readonly live;
|
|
19
22
|
private readonly terminals;
|
|
20
23
|
private readonly spillDir;
|
|
21
|
-
private
|
|
24
|
+
private closePromise;
|
|
22
25
|
constructor(ctx: Context, engine: SshEngine, getState: () => WorkspaceState);
|
|
26
|
+
/**
|
|
27
|
+
* Public idempotent close that releases every workspace-owned live process
|
|
28
|
+
* and terminal. The ctx.effect teardown cannot be invoked by a workspace
|
|
29
|
+
* connection (it only runs when the cordis scope disposes, which would also
|
|
30
|
+
* tear down the shared engine's scope), so a SshWorkspaceConnection calls
|
|
31
|
+
* this on-demand close() instead — the engine pool itself stays untouched.
|
|
32
|
+
*/
|
|
33
|
+
close(): Promise<void>;
|
|
34
|
+
private closeOwnedResources;
|
|
23
35
|
/** @inheritdoc */
|
|
24
36
|
resolveExecutable(command: string, env?: Readonly<Record<string, string>>, signal?: AbortSignal): Promise<string>;
|
|
25
37
|
/** @inheritdoc */
|
|
@@ -30,6 +30,10 @@ export declare class SshTerminalHandle implements SubprocessTerminalHandle {
|
|
|
30
30
|
signalForeground(_signal: SubprocessTerminalSignal): Promise<number>;
|
|
31
31
|
/** @inheritdoc */
|
|
32
32
|
terminate(): Promise<void>;
|
|
33
|
+
/** Immediately release the channel when the owning runtime's independent
|
|
34
|
+
* disposal deadline expires. This also settles output/done if ssh2 never
|
|
35
|
+
* reports a terminal exit after close(). */
|
|
36
|
+
forceClose(): void;
|
|
33
37
|
private signal;
|
|
34
38
|
private closeOnce;
|
|
35
39
|
}
|
|
@@ -41,10 +41,10 @@ export declare class RemoteSearchService {
|
|
|
41
41
|
private readonly engine;
|
|
42
42
|
constructor(engine: SshEngine);
|
|
43
43
|
/** Literal file-name search under `root` (skips node_modules/.git). */
|
|
44
|
-
searchNames(target: SearchTarget, query: string): Promise<RemoteNameSearch>;
|
|
44
|
+
searchNames(target: SearchTarget, query: string, signal?: AbortSignal): Promise<RemoteNameSearch>;
|
|
45
45
|
/** Glob search (pattern keeps glob semantics; `**` crosses directories). */
|
|
46
|
-
glob(target: SearchTarget, pattern: string): Promise<RemoteGlobResult>;
|
|
46
|
+
glob(target: SearchTarget, pattern: string, signal?: AbortSignal): Promise<RemoteGlobResult>;
|
|
47
47
|
/** Fixed-string grep (literal pattern, `-F`; NUL file boundary `-Z`). */
|
|
48
|
-
grepFixed(target: SearchTarget, pattern: string): Promise<RemoteGrepResult>;
|
|
48
|
+
grepFixed(target: SearchTarget, pattern: string, signal?: AbortSignal): Promise<RemoteGrepResult>;
|
|
49
49
|
}
|
|
50
50
|
//# sourceMappingURL=remote-search.d.ts.map
|
package/lib/types/routes.d.ts
CHANGED
|
@@ -1,24 +1,52 @@
|
|
|
1
1
|
import type { WebRoute } from '@deepseek-ai/dsh-host-webserver';
|
|
2
2
|
import { type SshEngine } from './ssh/engine.ts';
|
|
3
|
-
import type {
|
|
4
|
-
|
|
3
|
+
import type { WorkspaceStoreView } from './backend.ts';
|
|
4
|
+
/** One record whose host-workspace (re)registration failed. */
|
|
5
|
+
export interface HostWorkspaceReconcileFailure {
|
|
6
|
+
id: string;
|
|
7
|
+
error: string;
|
|
8
|
+
}
|
|
9
|
+
/** Result of replaying host-workspace registration for every stored record. */
|
|
10
|
+
export interface HostWorkspaceReconcileReport {
|
|
11
|
+
registered: number;
|
|
12
|
+
failures: HostWorkspaceReconcileFailure[];
|
|
13
|
+
/** Set when the record source itself could not be listed (startup path
|
|
14
|
+
* tolerates it; the route surfaces it as an I/O failure). */
|
|
15
|
+
listError?: string;
|
|
16
|
+
}
|
|
17
|
+
/** Registration replay dependencies (the route and the startup path share it). */
|
|
18
|
+
export interface HostWorkspaceReconcileDeps {
|
|
19
|
+
workspaces: WorkspaceStoreView;
|
|
20
|
+
registerHostWorkspace?: (anchorPath: string, title: string) => Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Replay host-workspace registration for every stored record. Registration
|
|
24
|
+
* happens only on create/delete, so a process restart (or a registration that
|
|
25
|
+
* failed before compensation existed) leaves records whose sidebar entry is
|
|
26
|
+
* missing; `registerHostWorkspace` is create-if-missing, so this is the
|
|
27
|
+
* idempotent startup/retry compensation. Per-record failures are reported and
|
|
28
|
+
* never thrown: a broken host registry must not break plugin startup.
|
|
29
|
+
*
|
|
30
|
+
* Shared by the `/reconcile` route and the boot path so the two halves cannot
|
|
31
|
+
* drift.
|
|
32
|
+
*/
|
|
33
|
+
export declare function reconcileHostWorkspaces(deps: HostWorkspaceReconcileDeps): Promise<HostWorkspaceReconcileReport>;
|
|
5
34
|
/** Route family dependencies. */
|
|
6
35
|
export interface WorkspaceRoutesDeps {
|
|
7
36
|
/** Read-only host surface (list only; the SSH routes own the write path). */
|
|
8
37
|
hosts: import('./core.ts').HostStoreView;
|
|
9
38
|
engine: SshEngine;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
39
|
+
/** SSH-workspace record source: the generic WorkspaceCore-backed
|
|
40
|
+
* projection store (the only runtime). */
|
|
41
|
+
workspaces: WorkspaceStoreView;
|
|
13
42
|
/** Register the anchor dir as a HOST workspace (sidebar visibility). */
|
|
14
43
|
registerHostWorkspace?: (anchorPath: string, title: string) => Promise<void>;
|
|
15
44
|
/** Drop the host workspace registration for an anchor dir. */
|
|
16
45
|
unregisterHostWorkspace?: (anchorPath: string) => Promise<void>;
|
|
17
46
|
}
|
|
18
47
|
/**
|
|
19
|
-
* Build every /api/dsh-hardssh route
|
|
20
|
-
*
|
|
21
|
-
* @returns the routes to register.
|
|
48
|
+
* Build every /api/dsh-hardssh route (workspace CRUD + remote directory
|
|
49
|
+
* browsing for the picker; the per-workspace file surface was removed).
|
|
22
50
|
*/
|
|
23
51
|
export declare function makeRoutes(deps: WorkspaceRoutesDeps): WebRoute[];
|
|
24
52
|
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sole DSH runtime augmentation point for generic workspace capabilities.
|
|
3
|
+
* The platform-neutral model cannot name these runtime services itself.
|
|
4
|
+
*/
|
|
5
|
+
import type { FileSystem } from '@deepseek-ai/dsh-fs';
|
|
6
|
+
import type { SubprocessRuntime } from '@deepseek-ai/dsh-subprocess';
|
|
7
|
+
import type { WorkspaceSearchService } from '../base/capability.ts';
|
|
8
|
+
/** DSH's official workspace capability bindings; no terminal key exists because process owns terminals. */
|
|
9
|
+
export interface DshWorkspaceCapabilityMap {
|
|
10
|
+
'workspace.fs': FileSystem;
|
|
11
|
+
'workspace.process': SubprocessRuntime;
|
|
12
|
+
'workspace.search': WorkspaceSearchService;
|
|
13
|
+
}
|
|
14
|
+
declare module '../base/model.ts' {
|
|
15
|
+
interface WorkspaceCapabilityMap extends DshWorkspaceCapabilityMap {
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=dsh-capabilities.d.ts.map
|
|
@@ -1,56 +1,91 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* `
|
|
6
|
-
*
|
|
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
|
|
2
|
+
* High-level generic WorkspaceCore runtime boundary. The generic core is the
|
|
3
|
+
* ONLY production workspace runtime: it owns the ledger, the record CRUD, and
|
|
4
|
+
* the provider routing used by the fs/subprocess seams, the workspace routes,
|
|
5
|
+
* the `remote_*` agent tools, and the SSH host-delete guard. Mounting is
|
|
6
|
+
* side-effect-free until `initialize()` runs, which the plugin boot does
|
|
7
|
+
* explicitly.
|
|
13
8
|
*/
|
|
14
9
|
import type { Context } from '@deepseek-ai/cordis';
|
|
15
|
-
import {
|
|
16
|
-
import type { WorkspaceRecord } from '../base/model.ts';
|
|
10
|
+
import type { LedgerListener, WorkspaceLedger as WorkspaceLedgerType } from '../base/ledger.ts';
|
|
17
11
|
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
12
|
import { LedgerWorkspaceRouter } from '../base/ledger-router.ts';
|
|
23
|
-
|
|
13
|
+
import type { WorkspaceConnection, WorkspaceCreateInput, WorkspaceId, WorkspaceProvider, WorkspaceRecord, WorkspaceUpdate } from '../base/model.ts';
|
|
14
|
+
import { WorkspaceProviderRegistry, type WorkspaceRegistry } from '../base/registry.ts';
|
|
15
|
+
import type { HostStoreView } from '../core.ts';
|
|
16
|
+
import type { SshEngine } from '../ssh/engine.ts';
|
|
17
|
+
import './dsh-capabilities.ts';
|
|
18
|
+
export type WorkspaceListener = LedgerListener;
|
|
19
|
+
/** Canonical high-level workspace consumer surface. */
|
|
24
20
|
export interface WorkspaceCore {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
21
|
+
initialize(): Promise<void>;
|
|
22
|
+
isReady(): boolean;
|
|
23
|
+
whenReady(): Promise<void>;
|
|
24
|
+
list(): Promise<WorkspaceRecord[]>;
|
|
25
|
+
get(id: WorkspaceId): Promise<WorkspaceRecord | undefined>;
|
|
26
|
+
findByAnchor(path: string): Promise<WorkspaceRecord | undefined>;
|
|
27
|
+
openById(id: WorkspaceId, signal?: AbortSignal): Promise<WorkspaceConnection | undefined>;
|
|
28
|
+
openByAnchor(path: string, signal?: AbortSignal): Promise<WorkspaceConnection | undefined>;
|
|
29
|
+
create(input: WorkspaceCreateInput): Promise<WorkspaceRecord>;
|
|
30
|
+
update(id: WorkspaceId, patch: WorkspaceUpdate): Promise<WorkspaceRecord | undefined>;
|
|
31
|
+
remove(id: WorkspaceId): Promise<boolean>;
|
|
32
|
+
subscribe(listener: WorkspaceListener): () => void;
|
|
33
|
+
registerProvider(provider: WorkspaceProvider): () => void;
|
|
34
|
+
closeAll(): Promise<void>;
|
|
35
|
+
/** @deprecated Internal compatibility surface; ordinary consumers use high-level methods. */
|
|
36
|
+
readonly ledger: WorkspaceLedger;
|
|
37
|
+
/** @deprecated Internal compatibility surface; ordinary consumers use registerProvider(). */
|
|
38
|
+
readonly providers: WorkspaceProviderRegistry;
|
|
39
|
+
/** @deprecated Internal compatibility surface; ordinary consumers use openById/openByAnchor(). */
|
|
40
|
+
readonly router: LedgerWorkspaceRouter;
|
|
41
|
+
/** @deprecated Plugin compatibility surface. */
|
|
42
|
+
readonly registry: WorkspaceRegistry;
|
|
43
|
+
}
|
|
44
|
+
/** mountWorkspaceCore() requires Cordis and builtins, so this class makes the same core testable with injected generic foundations. */
|
|
45
|
+
export declare class DefaultWorkspaceCore implements WorkspaceCore {
|
|
46
|
+
readonly ledger: WorkspaceLedgerType;
|
|
47
|
+
readonly providers: WorkspaceProviderRegistry;
|
|
48
|
+
readonly router: LedgerWorkspaceRouter;
|
|
49
|
+
readonly registry: WorkspaceRegistry;
|
|
50
|
+
constructor(ledger: WorkspaceLedgerType, providers: WorkspaceProviderRegistry, router?: LedgerWorkspaceRouter);
|
|
51
|
+
/** WorkspaceLedger.load() cannot preopen providers or establish router readiness, so initialize delegates to router initialization. */
|
|
52
|
+
initialize(): Promise<void>;
|
|
53
|
+
/** WorkspaceLedger.snapshotSync() cannot prove provider preopen completed, so readiness comes from the router lifecycle. */
|
|
54
|
+
isReady(): boolean;
|
|
55
|
+
/** isReady() cannot await a pending or failed initialization, so whenReady delegates to the router promise. */
|
|
56
|
+
whenReady(): Promise<void>;
|
|
57
|
+
/** WorkspaceLedger.list() alone may expose records before runtime readiness, so list waits for initialization first. */
|
|
58
|
+
list(): Promise<WorkspaceRecord[]>;
|
|
59
|
+
/** WorkspaceLedger.get() alone may run before strict load/preopen, so get waits for initialization first. */
|
|
60
|
+
get(id: WorkspaceId): Promise<WorkspaceRecord | undefined>;
|
|
61
|
+
/** WorkspaceLedger.findByAnchor() alone may run before strict load/preopen, so findByAnchor waits for initialization first. */
|
|
62
|
+
findByAnchor(path: string): Promise<WorkspaceRecord | undefined>;
|
|
63
|
+
/** LedgerWorkspaceRouter.ensureOpen() requires a record, so openById exposes identity-based opening to consumers. */
|
|
64
|
+
openById(id: WorkspaceId, signal?: AbortSignal): Promise<WorkspaceConnection | undefined>;
|
|
65
|
+
/** WorkspaceLedger.findByAnchor() returns data only, so openByAnchor exposes the resolved connection in one operation. */
|
|
66
|
+
openByAnchor(path: string, signal?: AbortSignal): Promise<WorkspaceConnection | undefined>;
|
|
67
|
+
/** WorkspaceLedger.create() cannot validate provider-specific references, so create validates before committing the record. */
|
|
68
|
+
create(input: WorkspaceCreateInput): Promise<WorkspaceRecord>;
|
|
69
|
+
/** WorkspaceLedger.update() cannot validate a changed provider binding, so update checks the proposed record before commit. */
|
|
70
|
+
update(id: WorkspaceId, patch: WorkspaceUpdate): Promise<WorkspaceRecord | undefined>;
|
|
71
|
+
/** WorkspaceLedger.remove() owns persistence while its synchronous change event makes router removal fail closed before this promise returns. */
|
|
72
|
+
remove(id: WorkspaceId): Promise<boolean>;
|
|
73
|
+
/** WorkspaceLedger.list() cannot publish later mutations, so subscribe exposes its detached commit stream. */
|
|
74
|
+
subscribe(listener: WorkspaceListener): () => void;
|
|
75
|
+
/** WorkspaceProviderRegistry.get() cannot establish disposer ownership, so registerProvider returns the registry-owned disposer. */
|
|
76
|
+
registerProvider(provider: WorkspaceProvider): () => void;
|
|
77
|
+
/** WorkspaceConnection.close() only closes one handle, so closeAll delegates complete cached/in-flight teardown to the router. */
|
|
78
|
+
closeAll(): Promise<void>;
|
|
35
79
|
}
|
|
36
|
-
/** Convert a legacy SSH-bound workspace record into the generic model. */
|
|
37
|
-
export declare function sshRecordToWorkspaceRecord(record: SshWorkspaceRecord): WorkspaceRecord;
|
|
38
80
|
/** Persistence path for the generic ledger (~/.dsh/workspaces/index.v1.json). */
|
|
39
81
|
export declare function genericLedgerPath(): string;
|
|
40
82
|
/** Anchor root for managed generic workspaces (~/.dsh/workspaces/anchors). */
|
|
41
83
|
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
|
-
*/
|
|
84
|
+
/** mountWorkspaceCore() cannot register Cordis services or builtin providers, so it performs host assembly (provider registration + `ctx.provide('workspaceCore')`); the boot calls `initialize()` afterwards. */
|
|
48
85
|
export declare function mountWorkspaceCore(ctx: Context, deps: {
|
|
49
86
|
engine?: SshEngine;
|
|
50
87
|
hosts?: HostStoreView;
|
|
51
88
|
}): WorkspaceCore;
|
|
52
|
-
/** The legacy SSH ledger held by the hardssh core (type bridge). */
|
|
53
|
-
export type { SshWorkspaceLedger };
|
|
54
89
|
declare module '@deepseek-ai/cordis' {
|
|
55
90
|
interface Context {
|
|
56
91
|
workspaceCore: WorkspaceCore;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/** Phase 3 legacy-to-generic conversion and side-effect-free shadow comparison. */
|
|
2
|
+
import { type WorkspaceNamespaceCodec } from '../base/namespace.ts';
|
|
3
|
+
import type { WorkspaceRecord } from '../base/model.ts';
|
|
4
|
+
import type { SshWorkspaceRecord } from '../protocol.ts';
|
|
5
|
+
/**
|
|
6
|
+
* Which runtime the migration is preparing. The legacy runtime is gone — the
|
|
7
|
+
* generic core is the ONLY production runtime — so this has a single value; it
|
|
8
|
+
* stays a named field so the cutover marker and the migration report record
|
|
9
|
+
* which runtime wrote them.
|
|
10
|
+
*/
|
|
11
|
+
export type WorkspaceRuntimeMode = 'generic';
|
|
12
|
+
export interface WorkspaceMigrationOptions {
|
|
13
|
+
mode: WorkspaceRuntimeMode;
|
|
14
|
+
legacyPath: string;
|
|
15
|
+
genericPath: string;
|
|
16
|
+
reportPath?: string;
|
|
17
|
+
now?: () => Date;
|
|
18
|
+
}
|
|
19
|
+
export interface WorkspaceMigrationReport {
|
|
20
|
+
schemaVersion: 1;
|
|
21
|
+
mode: WorkspaceRuntimeMode;
|
|
22
|
+
createdAt: string;
|
|
23
|
+
status: 'migrated' | 'unchanged';
|
|
24
|
+
sourceDigest: string;
|
|
25
|
+
targetDigest: string;
|
|
26
|
+
recordCount: number;
|
|
27
|
+
ids: string[];
|
|
28
|
+
backupPath?: string;
|
|
29
|
+
differences: string[];
|
|
30
|
+
}
|
|
31
|
+
export interface ShadowCapabilityObservation {
|
|
32
|
+
providerId: string;
|
|
33
|
+
manifestCapabilities: readonly string[];
|
|
34
|
+
availableCapabilities: readonly string[];
|
|
35
|
+
}
|
|
36
|
+
export interface WorkspaceShadowComparisonInput {
|
|
37
|
+
legacy: readonly SshWorkspaceRecord[];
|
|
38
|
+
generic: readonly WorkspaceRecord[];
|
|
39
|
+
anchorProbes?: readonly string[];
|
|
40
|
+
namespaceProbes?: readonly string[];
|
|
41
|
+
codec?: WorkspaceNamespaceCodec;
|
|
42
|
+
capabilities?: readonly ShadowCapabilityObservation[];
|
|
43
|
+
}
|
|
44
|
+
export interface WorkspaceShadowComparisonReport {
|
|
45
|
+
matches: boolean;
|
|
46
|
+
differences: string[];
|
|
47
|
+
}
|
|
48
|
+
/** The generic ledger's create() generates identity and time fields, so it cannot preserve a legacy record exactly; this mapper exists to retain the source id/anchor/timestamp verbatim. */
|
|
49
|
+
export declare function legacySshRecordToWorkspaceRecord(record: SshWorkspaceRecord): WorkspaceRecord;
|
|
50
|
+
/** Repeated WorkspaceLedger.create() calls cannot provide an all-or-nothing full-snapshot conversion with one backup. */
|
|
51
|
+
export declare function migrateLegacySshLedger(options: WorkspaceMigrationOptions): Promise<WorkspaceMigrationReport>;
|
|
52
|
+
/** LedgerWorkspaceRouter.fromAnchor() cannot compare detached legacy/generic decisions, and capability get() must not run in shadow. */
|
|
53
|
+
export declare function compareWorkspaceShadow(input: WorkspaceShadowComparisonInput): WorkspaceShadowComparisonReport;
|
|
54
|
+
/** Where a recovered generic ledger came from. */
|
|
55
|
+
export type GenericLedgerRecoverySource = 'last-good' | 'backup' | 'legacy';
|
|
56
|
+
/** Outcome of one missing-ledger recovery attempt. */
|
|
57
|
+
export interface GenericLedgerRecoveryResult {
|
|
58
|
+
recovered: boolean;
|
|
59
|
+
source?: GenericLedgerRecoverySource;
|
|
60
|
+
/** The file the records were restored from (absent for a legacy re-import). */
|
|
61
|
+
path?: string;
|
|
62
|
+
/** How many records the restored ledger holds. */
|
|
63
|
+
recordCount?: number;
|
|
64
|
+
/** Why recovery failed, when it did. */
|
|
65
|
+
reason?: string;
|
|
66
|
+
}
|
|
67
|
+
/** Options for {@link recoverGenericLedger}. */
|
|
68
|
+
export interface GenericLedgerRecoveryOptions {
|
|
69
|
+
/** The missing/unreadable generic ledger target. */
|
|
70
|
+
genericPath: string;
|
|
71
|
+
/** The frozen legacy SSH ledger, the last-resort source. */
|
|
72
|
+
legacyPath: string;
|
|
73
|
+
/**
|
|
74
|
+
* Record ids the cutover marker proves existed. A candidate that cannot
|
|
75
|
+
* produce ALL of them is rejected: recovery must never silently restore a
|
|
76
|
+
* SMALLER workspace set than the marker recorded — that would look like a
|
|
77
|
+
* successful recovery while quietly losing workspaces. An empty/absent list
|
|
78
|
+
* disables the id check (count is still required to be non-zero).
|
|
79
|
+
*/
|
|
80
|
+
expectedIds?: readonly string[];
|
|
81
|
+
/** Sink for recovery diagnostics (defaults to console.warn). */
|
|
82
|
+
log?: (message: string) => void;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Classify the generic ledger file so the boot can tell "legitimately empty"
|
|
86
|
+
* (the user deleted every workspace, the file exists with `[]`) from "lost or
|
|
87
|
+
* damaged" (missing, truncated, or unparseable).
|
|
88
|
+
*
|
|
89
|
+
* @param path - the generic ledger path.
|
|
90
|
+
* @returns the ledger state, never throwing.
|
|
91
|
+
*/
|
|
92
|
+
export declare function inspectGenericLedger(path: string): Promise<'absent' | 'readable' | 'corrupt'>;
|
|
93
|
+
/**
|
|
94
|
+
* Restore a generic ledger that the cutover marker proves should exist.
|
|
95
|
+
*
|
|
96
|
+
* The marker records that a migration committed N workspaces, so a missing or
|
|
97
|
+
* unreadable ledger is data loss — never a fresh deployment. Recovery is
|
|
98
|
+
* attempted newest-first: the rolling `.last-good` snapshot, then the newest
|
|
99
|
+
* `.backup-<timestamp>` left by a ledger replacement, then a re-import from the
|
|
100
|
+
* frozen legacy source. EVERY candidate must satisfy `expectedIds`; a source
|
|
101
|
+
* that would restore fewer workspaces than the marker recorded is rejected
|
|
102
|
+
* rather than accepted as success.
|
|
103
|
+
*
|
|
104
|
+
* The legacy tier deliberately migrates WITHOUT a report path: rewriting the
|
|
105
|
+
* marker there could downgrade it (an empty legacy source produces
|
|
106
|
+
* `recordCount: 0`) and permanently disarm the gate that asked for this
|
|
107
|
+
* recovery in the first place.
|
|
108
|
+
*
|
|
109
|
+
* @param options - paths, the expected ids, and an optional logger.
|
|
110
|
+
* @returns which source was used, or `recovered: false` plus the reason.
|
|
111
|
+
*/
|
|
112
|
+
export declare function recoverGenericLedger(options: GenericLedgerRecoveryOptions): Promise<GenericLedgerRecoveryResult>;
|
|
113
|
+
//# sourceMappingURL=workspace-migration.d.ts.map
|
|
@@ -8,9 +8,32 @@ export interface ClientLease {
|
|
|
8
8
|
readonly generation: number;
|
|
9
9
|
readonly kind: LeaseKind;
|
|
10
10
|
readonly released: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* True when this is the only lease on its pooled connection.
|
|
13
|
+
*
|
|
14
|
+
* Cancellation uses it to decide the blast radius: retiring the transport is
|
|
15
|
+
* safe (nobody else is using it) while other holders exist it would destroy
|
|
16
|
+
* unrelated work on the same alias.
|
|
17
|
+
*/
|
|
18
|
+
holdsOnlyLease(): boolean;
|
|
11
19
|
/** Mark the underlying connection broken (surfaces to every holder). */
|
|
12
20
|
markBroken(error?: unknown): void;
|
|
13
21
|
/** Idempotent: release this holder's ownership of the connection. */
|
|
14
22
|
release(): void;
|
|
15
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Tie a lease's release to the OPERATION's settlement, not to the caller's
|
|
26
|
+
* promise.
|
|
27
|
+
*
|
|
28
|
+
* Why this exists: `withClient` rejects its caller as soon as an abort arrives,
|
|
29
|
+
* but the underlying request may keep running (SFTP has no cancel API). If the
|
|
30
|
+
* lease were released at rejection time, `holdsOnlyLease()` would report "no
|
|
31
|
+
* other user" while that request was still in flight — and a later abort in a
|
|
32
|
+
* different session would then close the shared transport underneath it.
|
|
33
|
+
*
|
|
34
|
+
* @param lease - the lease owned by this operation.
|
|
35
|
+
* @param operation - the operation whose settlement frees the lease.
|
|
36
|
+
* @returns the operation, unchanged, with the release chained to it.
|
|
37
|
+
*/
|
|
38
|
+
export declare function holdLeaseUntilSettled<T>(lease: ClientLease, operation: Promise<T>): Promise<T>;
|
|
16
39
|
//# sourceMappingURL=lease.d.ts.map
|