@tiphareth/dsh-hardssh 0.1.2 → 0.2.3
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 +77 -50
- package/SKILLS.md +140 -0
- package/lib/base/index.js +95 -0
- package/lib/client.js +1171 -1321
- package/lib/fs.js +333 -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 +42 -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 +59 -26
- 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 +171 -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
package/lib/types/backend.d.ts
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The generic record source the SSH workspace API projects.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* `GenericWorkspaceStore` is the single record source: it projects the generic
|
|
5
|
+
* `WorkspaceLedger` (via `WorkspaceCore`) into the client SSH DTO and performs
|
|
6
|
+
* workspace CRUD through the generic core. There is no implicit local fallback
|
|
7
|
+
* and no dependency on any retired global SSH mode state.
|
|
7
8
|
*/
|
|
8
|
-
import type {
|
|
9
|
-
import {
|
|
10
|
-
import { RemoteSearchService } from './remote-search.ts';
|
|
11
|
-
import type { SshWorkspaceLedger } from './ledger.ts';
|
|
12
|
-
import type { DirListing, FileRead, FileWriteResult, SearchView, SshWorkspaceRecord, WorkspaceEntry } from './protocol.ts';
|
|
13
|
-
export declare const SEARCH_HIT_CAP = 200;
|
|
14
|
-
export declare const SEARCH_SCAN_CAP = 20000;
|
|
15
|
-
export declare const SEARCH_MAX_DEPTH = 4;
|
|
16
|
-
export declare const REMOTE_SEARCH_TIMEOUT_MS = 20000;
|
|
9
|
+
import type { WorkspaceCore } from './runtime/workspace-core.ts';
|
|
10
|
+
import type { SshWorkspaceRecord, WorkspaceEntry } from './protocol.ts';
|
|
17
11
|
export type BackendErrorCode = 'binary' | 'too-large' | 'conflict' | 'outside-root' | 'not-remote' | 'root-mismatch' | 'not-found' | 'forbidden' | 'invalid' | 'io';
|
|
18
12
|
export declare class BackendError extends Error {
|
|
19
13
|
readonly code: BackendErrorCode;
|
|
@@ -21,26 +15,29 @@ export declare class BackendError extends Error {
|
|
|
21
15
|
cause?: unknown;
|
|
22
16
|
});
|
|
23
17
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
18
|
+
/**
|
|
19
|
+
* The SSH-workspace record source the workspace routes, agent tools, and the
|
|
20
|
+
* SSH host-delete reference guard satisfy. Every consumer depends on this
|
|
21
|
+
* narrow view, so none of them knows which ledger implementation is
|
|
22
|
+
* authoritative.
|
|
23
|
+
*/
|
|
24
|
+
export interface WorkspaceStoreView {
|
|
25
|
+
/** Every SSH-bound workspace as the client DTO. */
|
|
26
|
+
list(): Promise<SshWorkspaceRecord[]>;
|
|
27
|
+
get(id: string): Promise<SshWorkspaceRecord | undefined>;
|
|
28
|
+
findByAnchor(path: string): Promise<SshWorkspaceRecord | undefined>;
|
|
29
|
+
/** Synchronous anchor lookup used by the session announcement. */
|
|
30
|
+
findByAnchorSync(path: string): SshWorkspaceRecord | undefined;
|
|
31
|
+
/** Create an SSH workspace (title/alias/remoteRoot); the anchor is managed by the store. */
|
|
32
|
+
create(input: {
|
|
33
|
+
title: string;
|
|
34
|
+
alias: string;
|
|
35
|
+
remoteRoot: string;
|
|
36
|
+
}): Promise<SshWorkspaceRecord>;
|
|
37
|
+
rename(id: string, title: string): Promise<SshWorkspaceRecord | undefined>;
|
|
38
|
+
remove(id: string): Promise<boolean>;
|
|
39
|
+
/** The shared anchor root (fail-closed gating before the runtime is ready). */
|
|
40
|
+
anchorsRoot(): string;
|
|
44
41
|
}
|
|
45
42
|
/**
|
|
46
43
|
* Normalize a workspace-relative path.
|
|
@@ -54,55 +51,53 @@ export declare function normalizeRel(raw: string): string;
|
|
|
54
51
|
export declare function isInside(root: string, abs: string): boolean;
|
|
55
52
|
/** Resolve a normalized relative path below a POSIX root. */
|
|
56
53
|
export declare function relToAbs(root: string, rel: string): string;
|
|
57
|
-
|
|
58
|
-
/** Stable dir-first, case-insensitive ordering shared by every backend. */
|
|
54
|
+
/** Stable dir-first, case-insensitive ordering shared by every listing. */
|
|
59
55
|
export declare function sortWorkspaceEntries(entries: WorkspaceEntry[]): WorkspaceEntry[];
|
|
60
|
-
export type WorkspacePathFlavor = 'local' | 'remote';
|
|
61
|
-
/** Return abs relative to root, rejecting prefix-collision/parent traversal. */
|
|
62
|
-
export declare function relativeWorkspacePath(root: string, abs: string, flavor: WorkspacePathFlavor): string;
|
|
63
|
-
/** Normalize filesystem/SFTP errors to stable route-visible codes. */
|
|
64
|
-
export declare function toBackendIoError(error: unknown, path: string): BackendError;
|
|
65
56
|
/** Map a BackendError to its stable HTTP status (routes use this). */
|
|
66
57
|
export declare function backendErrorStatus(error: BackendError, ioStatus: 500 | 502): number;
|
|
67
58
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
59
|
+
* The single record source: projects the generic `WorkspaceLedger` (via
|
|
60
|
+
* `WorkspaceCore`) into the client SSH DTO and performs workspace CRUD through
|
|
61
|
+
* the generic core. Routes, agent tools, and the SSH host-delete guard stay
|
|
62
|
+
* store-agnostic behind `WorkspaceStoreView`. New SSH workspaces keep the
|
|
63
|
+
* established anchor layout (~/.dsh/ssh-workspaces/<id>) so existing
|
|
64
|
+
* sidebar/session bindings and the host workspace registry stay unchanged.
|
|
70
65
|
*/
|
|
71
|
-
export declare class
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
66
|
+
export declare class GenericWorkspaceStore implements WorkspaceStoreView {
|
|
67
|
+
private readonly core;
|
|
68
|
+
/** Resolves when the generic runtime (migration + initialize) finished;
|
|
69
|
+
* rejects on any startup failure so every consumer fails closed instead
|
|
70
|
+
* of silently reading an un-migrated or corrupt ledger. */
|
|
71
|
+
private readonly ready;
|
|
72
|
+
/** Anchor root for newly created SSH workspaces (~/.dsh/ssh-workspaces). */
|
|
73
|
+
private readonly anchorBase;
|
|
74
|
+
constructor(core: WorkspaceCore,
|
|
75
|
+
/** Resolves when the generic runtime (migration + initialize) finished;
|
|
76
|
+
* rejects on any startup failure so every consumer fails closed instead
|
|
77
|
+
* of silently reading an un-migrated or corrupt ledger. */
|
|
78
|
+
ready: Promise<void>,
|
|
79
|
+
/** Anchor root for newly created SSH workspaces (~/.dsh/ssh-workspaces). */
|
|
80
|
+
anchorBase: string);
|
|
81
|
+
/** WorkspaceCore.list() exposes every provider, but the SSH DTO surface is SSH-only, so this filters before projecting. */
|
|
82
|
+
list(): Promise<SshWorkspaceRecord[]>;
|
|
83
|
+
/** WorkspaceCore.get() returns generic records, so this narrows to SSH and projects the DTO. */
|
|
84
|
+
get(id: string): Promise<SshWorkspaceRecord | undefined>;
|
|
85
|
+
/** WorkspaceCore.findByAnchor() also resolves non-SSH workspaces, so this projects only SSH owners. */
|
|
86
|
+
findByAnchor(path: string): Promise<SshWorkspaceRecord | undefined>;
|
|
87
|
+
/** WorkspaceCore has no synchronous anchor lookup on the public surface, so this resolves the detached ledger snapshot lexically. */
|
|
88
|
+
findByAnchorSync(path: string): SshWorkspaceRecord | undefined;
|
|
89
|
+
/** WorkspaceLedger.create() cannot generate SSH defaults or the SSH anchor layout, so this maps the SSH create input into a generic record first. */
|
|
90
|
+
create(input: {
|
|
91
|
+
title: string;
|
|
92
|
+
alias: string;
|
|
93
|
+
remoteRoot: string;
|
|
94
|
+
}): Promise<SshWorkspaceRecord>;
|
|
95
|
+
/** Title edits route through the generic update (the generic ledger has no SSH-specific rename). */
|
|
96
|
+
rename(id: string, title: string): Promise<SshWorkspaceRecord | undefined>;
|
|
97
|
+
/** WorkspaceCore.remove() is the generic delete; the anchor directory stays in place (the host workspace may still reference it). */
|
|
98
|
+
remove(id: string): Promise<boolean>;
|
|
99
|
+
/** The SSH anchor base this store creates its managed anchors under
|
|
100
|
+
* (~/.dsh/ssh-workspaces). */
|
|
101
|
+
anchorsRoot(): string;
|
|
107
102
|
}
|
|
108
103
|
//# sourceMappingURL=backend.d.ts.map
|
|
@@ -83,6 +83,10 @@ export interface WorkspaceSearchHit {
|
|
|
83
83
|
/** Path relative to the search root. */
|
|
84
84
|
rel: string;
|
|
85
85
|
isDir: boolean;
|
|
86
|
+
/** Optional raw matched line (`path:line:content`, engine-style) for a
|
|
87
|
+
* content grep when the backend can report snippets; absent when the
|
|
88
|
+
* search only returns matching paths. */
|
|
89
|
+
match?: string;
|
|
86
90
|
}
|
|
87
91
|
/** Filename / content search capability. */
|
|
88
92
|
export interface WorkspaceSearchService {
|
|
@@ -1,48 +1,64 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* together. Any provider can be routed through this router with zero SSH
|
|
5
|
-
* knowledge: a session cwd inside a workspace's anchor resolves to that
|
|
6
|
-
* workspace's provider connection; an explicit `wfs://…` (or legacy `ssh:…`)
|
|
7
|
-
* target key resolves to the owning connection.
|
|
8
|
-
*
|
|
9
|
-
* The router opens connections lazily and caches them per workspace id;
|
|
10
|
-
* removed workspaces drop their connections so stale keys fail closed.
|
|
11
|
-
*
|
|
12
|
-
* @module @tiphareth/dsh-hardssh/base/ledger-router
|
|
13
|
-
*/
|
|
14
|
-
import type { WorkspaceConnection, WorkspaceRecord } from './model.ts';
|
|
1
|
+
/** Ledger-backed routing, readiness, single-flight opening, and connection ownership. */
|
|
2
|
+
import type { WorkspaceLedger } from './ledger.ts';
|
|
3
|
+
import type { WorkspaceConnection, WorkspaceId, WorkspaceRecord } from './model.ts';
|
|
15
4
|
import { type WorkspaceNamespaceCodec, type WorkspaceRoute } from './namespace.ts';
|
|
16
|
-
import type {
|
|
17
|
-
import { type WorkspaceLedger } from './ledger.ts';
|
|
5
|
+
import type { WorkspaceProviderRegistry } from './registry.ts';
|
|
18
6
|
import type { WorkspaceResolution, WorkspaceRouter } from './router.ts';
|
|
19
|
-
/**
|
|
7
|
+
/** Router configuration that remains independent of any concrete provider. */
|
|
20
8
|
export interface LedgerRouterOptions {
|
|
21
9
|
codec?: WorkspaceNamespaceCodec;
|
|
22
|
-
|
|
23
|
-
open(record: WorkspaceRecord, signal?: AbortSignal): Promise<WorkspaceConnection | undefined>;
|
|
24
|
-
/** Called when a workspace is removed (drop cache / pool leases). */
|
|
25
|
-
onRemoved?(workspaceId: string): void;
|
|
10
|
+
logger?: Pick<Console, 'warn' | 'error' | 'info' | 'debug'>;
|
|
26
11
|
}
|
|
27
|
-
/** Ledger + registry based router. */
|
|
12
|
+
/** Ledger + provider-registry based router. */
|
|
28
13
|
export declare class LedgerWorkspaceRouter implements WorkspaceRouter {
|
|
29
14
|
private readonly ledger;
|
|
30
|
-
private readonly
|
|
15
|
+
private readonly providers;
|
|
31
16
|
private readonly options;
|
|
32
17
|
readonly codec: WorkspaceNamespaceCodec;
|
|
33
18
|
private readonly connections;
|
|
19
|
+
private readonly connectionBindings;
|
|
20
|
+
private readonly opening;
|
|
21
|
+
private readonly closing;
|
|
34
22
|
private readonly anchors;
|
|
35
|
-
|
|
23
|
+
private readonly lifecycleAbort;
|
|
24
|
+
private initializePromise;
|
|
25
|
+
private ready;
|
|
26
|
+
private closed;
|
|
27
|
+
private ledgerDisposer;
|
|
28
|
+
private providerDisposer;
|
|
29
|
+
constructor(ledger: WorkspaceLedger, providers: WorkspaceProviderRegistry, options?: LedgerRouterOptions);
|
|
30
|
+
/** ensureOpen() warms one supplied record only, so initialize loads, subscribes, and preopens the complete persisted ledger before readiness. */
|
|
31
|
+
initialize(): Promise<void>;
|
|
32
|
+
/** initialize() is asynchronous, so isReady provides the synchronous seam gate it cannot provide. */
|
|
33
|
+
isReady(): boolean;
|
|
34
|
+
/** isReady() only reports a snapshot, so whenReady lets asynchronous consumers await the initialization outcome. */
|
|
35
|
+
whenReady(): Promise<void>;
|
|
36
|
+
/** ensureOpen() requires a record, so openById performs fail-closed ledger identity lookup before single-flight opening. */
|
|
37
|
+
openById(id: WorkspaceId, signal?: AbortSignal): Promise<WorkspaceConnection | undefined>;
|
|
38
|
+
/** openById() cannot translate a host anchor, so openByAnchor resolves ledger ownership before opening. */
|
|
39
|
+
openByAnchor(path: string, signal?: AbortSignal): Promise<WorkspaceConnection | undefined>;
|
|
40
|
+
/** initialize() owns promise reuse, so initializeInternal performs the one actual load/subscribe/preopen sequence. */
|
|
41
|
+
private initializeInternal;
|
|
36
42
|
private reindex;
|
|
37
43
|
private openFor;
|
|
38
44
|
fromNamespace(key: string): WorkspaceResolution | undefined;
|
|
39
45
|
fromAnchor(cwd: string | undefined): WorkspaceConnection | undefined;
|
|
40
46
|
resolveRoute(route: WorkspaceRoute): WorkspaceResolution | undefined;
|
|
41
|
-
/** Async
|
|
47
|
+
/** Async compatibility entry used by older adapters that already hold a detached record. */
|
|
42
48
|
ensureOpen(record: WorkspaceRecord, signal?: AbortSignal): Promise<WorkspaceConnection | undefined>;
|
|
43
49
|
/** All currently cached connections (for lifecycle/teardown). */
|
|
44
50
|
connectionsSnapshot(): WorkspaceConnection[];
|
|
45
|
-
/** Close every cached connection
|
|
51
|
+
/** Close every cached and in-flight connection and release both subscriptions. */
|
|
46
52
|
closeAll(): Promise<void>;
|
|
53
|
+
/** reindex() only updates synchronous ownership, so this handler also closes and reopens resources after ledger commits. */
|
|
54
|
+
private handleLedgerChange;
|
|
55
|
+
/** openFor() cannot observe provider lifecycle events itself, so this handler invalidates unregisters and preopens later registrations. */
|
|
56
|
+
private handleProviderChange;
|
|
57
|
+
/** openFor() reuses an in-flight promise, so reopen waits for stale work before retrying the current record. */
|
|
58
|
+
private reopen;
|
|
59
|
+
/** reindex() cannot await resource release, so detach removes routing synchronously and tracks close completion separately. */
|
|
60
|
+
private detach;
|
|
61
|
+
/** WorkspaceConnection.close() alone is not tracked, so closeConnection lets closeAll await invalidation work already in progress. */
|
|
62
|
+
private closeConnection;
|
|
47
63
|
}
|
|
48
64
|
//# sourceMappingURL=ledger-router.d.ts.map
|
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* mechanics (atomic write, sync anchor index, subscription) without any
|
|
5
|
-
* SSH-typed fields: a record is a `WorkspaceRecord` whose `provider` ref
|
|
6
|
-
* names the owning provider, and anchors are plain local directories.
|
|
7
|
-
*
|
|
8
|
-
* Concrete providers keep their own specialized stores (e.g. the SSH host
|
|
9
|
-
* store) and reference them through `provider.connectionRef`; the ledger
|
|
10
|
-
* itself never touches provider configs.
|
|
11
|
-
*
|
|
12
|
-
* @module @tiphareth/dsh-hardssh/base/ledger
|
|
2
|
+
* Provider-neutral workspace ledger with strict loading, atomic persistence,
|
|
3
|
+
* detached snapshots, and one serialized mutation queue.
|
|
13
4
|
*/
|
|
14
|
-
import type { WorkspaceRecord } from './model.ts';
|
|
5
|
+
import type { WorkspaceCreateInput, WorkspaceRecord, WorkspaceUpdate } from './model.ts';
|
|
15
6
|
/** One committed ledger snapshot. */
|
|
16
7
|
export interface LedgerSnapshot {
|
|
17
8
|
revision: number;
|
|
@@ -27,20 +18,36 @@ export type LedgerChange = {
|
|
|
27
18
|
revision: number;
|
|
28
19
|
before: WorkspaceRecord;
|
|
29
20
|
record: WorkspaceRecord;
|
|
21
|
+
} | {
|
|
22
|
+
type: 'updated';
|
|
23
|
+
revision: number;
|
|
24
|
+
before: WorkspaceRecord;
|
|
25
|
+
record: WorkspaceRecord;
|
|
30
26
|
} | {
|
|
31
27
|
type: 'removed';
|
|
32
28
|
revision: number;
|
|
33
29
|
record: WorkspaceRecord;
|
|
30
|
+
} | {
|
|
31
|
+
type: 'replaced';
|
|
32
|
+
revision: number;
|
|
33
|
+
before: WorkspaceRecord[];
|
|
34
|
+
records: WorkspaceRecord[];
|
|
34
35
|
};
|
|
35
36
|
export type LedgerListener = (change: LedgerChange) => void;
|
|
36
37
|
/** Normalize an anchor for comparison (Windows case-insensitive; POSIX not). */
|
|
37
38
|
export declare function normalizeAnchorPath(path: string): string;
|
|
38
39
|
/** True when `candidate` equals `anchor` or is one of its descendants (lexical). */
|
|
39
40
|
export declare function isPathUnderAnchor(anchor: string, candidate: string): boolean;
|
|
41
|
+
/** Options for one atomic whole-ledger replacement. */
|
|
42
|
+
export interface LedgerReplaceOptions {
|
|
43
|
+
/** Copy the current persisted ledger here before overwriting it. */
|
|
44
|
+
backupPath?: string;
|
|
45
|
+
}
|
|
46
|
+
/** Generic ledger schema currently accepted by strict loading. */
|
|
47
|
+
export declare const WORKSPACE_LEDGER_SCHEMA_VERSION: 1;
|
|
40
48
|
/**
|
|
41
|
-
* The generic ledger.
|
|
42
|
-
*
|
|
43
|
-
* maintained so the switch layer can resolve a cwd without awaiting I/O.
|
|
49
|
+
* The generic ledger. The in-memory state changes only after an atomic save,
|
|
50
|
+
* and every public observation is detached from that state.
|
|
44
51
|
*/
|
|
45
52
|
export declare class WorkspaceLedger {
|
|
46
53
|
private readonly fileOverride?;
|
|
@@ -70,13 +77,17 @@ export declare class WorkspaceLedger {
|
|
|
70
77
|
get(id: string): Promise<WorkspaceRecord | undefined>;
|
|
71
78
|
findByAnchor(path: string): Promise<WorkspaceRecord | undefined>;
|
|
72
79
|
findByAnchorSync(path: string): WorkspaceRecord | undefined;
|
|
73
|
-
/** Create
|
|
74
|
-
create(input:
|
|
75
|
-
id?: string;
|
|
76
|
-
}): Promise<WorkspaceRecord>;
|
|
80
|
+
/** Create cannot preserve caller timestamps, so migration uses replaceAll instead. */
|
|
81
|
+
create(input: WorkspaceCreateInput): Promise<WorkspaceRecord>;
|
|
77
82
|
rename(id: string, title: string): Promise<WorkspaceRecord | undefined>;
|
|
83
|
+
/** WorkspaceLedger.rename cannot change provider bindings or extension data, so update applies a generic patch. */
|
|
84
|
+
update(id: string, patch: WorkspaceUpdate): Promise<WorkspaceRecord | undefined>;
|
|
85
|
+
/** WorkspaceLedger.create persists one generated record, so it cannot atomically import a complete authoritative snapshot. */
|
|
86
|
+
replaceAll(records: readonly WorkspaceRecord[], options?: LedgerReplaceOptions): Promise<void>;
|
|
78
87
|
/** Remove a record. The anchor directory is left in place (caller decides). */
|
|
79
88
|
remove(id: string): Promise<boolean>;
|
|
80
89
|
anchorsRoot(): string;
|
|
81
90
|
}
|
|
91
|
+
/** isRecord validates one shape only, so this validator also rejects duplicate ids and ambiguous anchors across records. */
|
|
92
|
+
export declare function assertValidRecords(values: readonly unknown[]): asserts values is readonly WorkspaceRecord[];
|
|
82
93
|
//# sourceMappingURL=ledger.d.ts.map
|
|
@@ -62,13 +62,22 @@ export interface WorkspaceRecord {
|
|
|
62
62
|
/** Provider/feature extension bag. Never holds secrets. */
|
|
63
63
|
extensions?: Record<string, unknown>;
|
|
64
64
|
}
|
|
65
|
-
/**
|
|
65
|
+
/** Provider API major implemented by the generic workspace foundations. */
|
|
66
|
+
export declare const WORKSPACE_PROVIDER_API_VERSION: 2;
|
|
67
|
+
/**
|
|
68
|
+
* Provider capability augmentation point. Runtime packages add their official
|
|
69
|
+
* capability types here; the platform-neutral base deliberately defines none.
|
|
70
|
+
*/
|
|
66
71
|
export interface WorkspaceCapabilityMap {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
'workspace.terminal': import('./capability.ts').WorkspaceTerminalService;
|
|
70
|
-
'workspace.search': import('./capability.ts').WorkspaceSearchService;
|
|
72
|
+
/** Unaugmented providers may carry private keys; runtime-known keys gain precise types through augmentation. */
|
|
73
|
+
[capability: string]: unknown;
|
|
71
74
|
}
|
|
75
|
+
/** Input accepted when creating a record; identity and timestamps are ledger-owned. */
|
|
76
|
+
export type WorkspaceCreateInput = Omit<WorkspaceRecord, 'id' | 'createdAt' | 'updatedAt'> & {
|
|
77
|
+
id?: WorkspaceId;
|
|
78
|
+
};
|
|
79
|
+
/** Mutable record fields; identity, schema, and creation time remain immutable. */
|
|
80
|
+
export type WorkspaceUpdate = Partial<Pick<WorkspaceRecord, 'title' | 'provider' | 'location' | 'anchor' | 'labels' | 'extensions'>>;
|
|
72
81
|
/** A resolved, open connection to one workspace's provider. */
|
|
73
82
|
export interface WorkspaceConnection {
|
|
74
83
|
readonly workspaceId: WorkspaceId;
|
|
@@ -91,8 +100,8 @@ export interface WorkspaceProvider {
|
|
|
91
100
|
readonly manifest: WorkspaceProviderManifest;
|
|
92
101
|
/** Validate a record before open (e.g. root shape, ref resolvable). */
|
|
93
102
|
validate(record: WorkspaceRecord): void | Promise<void>;
|
|
94
|
-
/** Open a connection for one record. */
|
|
95
|
-
open(record: WorkspaceRecord, context
|
|
103
|
+
/** Open a connection for one record under the router-owned lifecycle context. */
|
|
104
|
+
open(record: WorkspaceRecord, context: WorkspaceOpenContext): Promise<WorkspaceConnection>;
|
|
96
105
|
}
|
|
97
106
|
/** Static provider identity, checked before any plugin code runs. */
|
|
98
107
|
export interface WorkspaceProviderManifest {
|
|
@@ -103,6 +112,6 @@ export interface WorkspaceProviderManifest {
|
|
|
103
112
|
apiVersion: number;
|
|
104
113
|
displayName: string;
|
|
105
114
|
/** Capabilities this provider implements. */
|
|
106
|
-
capabilities:
|
|
115
|
+
capabilities: string[];
|
|
107
116
|
}
|
|
108
117
|
//# sourceMappingURL=model.d.ts.map
|
|
@@ -76,6 +76,17 @@ export interface WorkspacePlugin {
|
|
|
76
76
|
/**
|
|
77
77
|
* Registry of loaded plugins (provider + feature plugins together). Keeps
|
|
78
78
|
* the workspace registry and plugin lifecycle in one place.
|
|
79
|
+
*
|
|
80
|
+
* NOT WIRED INTO PRODUCTION YET: the DSH runtime registers the builtin
|
|
81
|
+
* providers directly (`runtime/workspace-core.ts`) and no third-party plugin
|
|
82
|
+
* loads through this host — only tests do. It is kept because it is the
|
|
83
|
+
* documented distribution contract for out-of-tree providers (see SKILLS.md);
|
|
84
|
+
* provider teardown stays ownership-correct for when it is wired up: each
|
|
85
|
+
* registration releases the disposer that `register()` returned for THAT call,
|
|
86
|
+
* so a duplicate (same id+version) registration never unregisters a provider it
|
|
87
|
+
* does not own.
|
|
88
|
+
*
|
|
89
|
+
* @internal Not reachable from any production assembly today.
|
|
79
90
|
*/
|
|
80
91
|
export declare class WorkspacePluginHost {
|
|
81
92
|
private readonly registries;
|
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Workspace provider registry — the single place providers register and are
|
|
3
|
-
* looked up by id. The registry is provider-agnostic: SSH, local, and any
|
|
4
|
-
* future provider all register here, and the switch layer resolves
|
|
5
|
-
* workspaces through the registry without knowing which provider backs them.
|
|
6
|
-
*
|
|
7
|
-
* Registration is idempotent per (id, version, apiVersion): re-registering
|
|
8
|
-
* the same provider is a no-op / reuse; registering a different or
|
|
9
|
-
* incompatible implementation with the same id is an error.
|
|
10
|
-
*
|
|
11
|
-
* @module @tiphareth/dsh-hardssh/base/registry
|
|
12
|
-
*/
|
|
1
|
+
/** Provider-neutral registry with observable, ownership-safe registration lifecycle. */
|
|
13
2
|
import type { WorkspaceProvider, WorkspaceProviderManifest } from './model.ts';
|
|
14
|
-
/**
|
|
15
|
-
export type
|
|
3
|
+
/** Provider registration lifecycle event consumed by routers. */
|
|
4
|
+
export type ProviderChange = {
|
|
5
|
+
type: 'registered';
|
|
6
|
+
provider: WorkspaceProvider;
|
|
7
|
+
} | {
|
|
8
|
+
type: 'unregistered';
|
|
9
|
+
provider: WorkspaceProvider;
|
|
10
|
+
};
|
|
11
|
+
export type ProviderListener = (change: ProviderChange) => void;
|
|
16
12
|
/** Duplicate / incompatible registration error. */
|
|
17
13
|
export declare class ProviderRegistrationError extends Error {
|
|
18
14
|
constructor(message: string);
|
|
@@ -21,40 +17,41 @@ export declare class ProviderRegistrationError extends Error {
|
|
|
21
17
|
export declare class WorkspaceProviderRegistry {
|
|
22
18
|
private readonly providers;
|
|
23
19
|
private readonly listeners;
|
|
24
|
-
/** Register a provider
|
|
20
|
+
/** Register a provider and return a disposer that only removes that exact registration. */
|
|
25
21
|
register(provider: WorkspaceProvider): () => void;
|
|
26
22
|
/** Look up a provider by id. */
|
|
27
23
|
get(id: string): WorkspaceProvider | undefined;
|
|
28
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Remove a provider by id, whoever registered it.
|
|
26
|
+
*
|
|
27
|
+
* Ownership-unsafe by construction: callers that registered a provider must
|
|
28
|
+
* use the disposer returned by register() instead, otherwise teardown can
|
|
29
|
+
* delete a provider owned by another plugin. Kept for whole-registry
|
|
30
|
+
* operations (tests, process-level resets).
|
|
31
|
+
*/
|
|
29
32
|
unregister(id: string): void;
|
|
30
33
|
/** All registered providers. */
|
|
31
34
|
list(): WorkspaceProvider[];
|
|
32
|
-
/** Subscribe
|
|
35
|
+
/** Subscribe must expose both registration and removal, which the old registration-only listener could not report. */
|
|
33
36
|
subscribe(listener: ProviderListener): () => void;
|
|
34
37
|
/** True when a provider id is registered. */
|
|
35
38
|
has(id: string): boolean;
|
|
36
39
|
/** Static identity of a registered provider (for manifests / UI). */
|
|
37
40
|
manifestOf(id: string): WorkspaceProviderManifest | undefined;
|
|
41
|
+
/** register() cannot safely repeat listener isolation for both lifecycle directions, so emit centralizes it. */
|
|
42
|
+
private emit;
|
|
38
43
|
}
|
|
39
|
-
/**
|
|
40
|
-
export declare const globalWorkspaceRegistry: WorkspaceProviderRegistry;
|
|
41
|
-
/**
|
|
42
|
-
* Workspace registry surface used by plugins: list / watch workspace records
|
|
43
|
-
* and register providers. Implemented by the runtime over the ledger + the
|
|
44
|
-
* provider registry.
|
|
45
|
-
*/
|
|
44
|
+
/** Plugin-facing workspace registry surface. */
|
|
46
45
|
export interface WorkspaceRegistry {
|
|
47
|
-
/** All known workspace records (provider-agnostic). */
|
|
48
46
|
listWorkspaces(): Promise<import('./model.ts').WorkspaceRecord[]>;
|
|
49
|
-
/** Subscribe to workspace record changes. Returns a disposer. */
|
|
50
47
|
subscribe(listener: (workspace: import('./model.ts').WorkspaceRecord) => void): () => void;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
register(provider: WorkspaceProvider): () => void;
|
|
49
|
+
/**
|
|
50
|
+
* Ownership-unsafe id-based removal; plugin teardown must release the
|
|
51
|
+
* disposer returned by `register()` instead of calling this.
|
|
52
|
+
*/
|
|
54
53
|
unregister?(providerId: string): void;
|
|
55
|
-
/** Look up a provider by id. */
|
|
56
54
|
provider(id: string): WorkspaceProvider | undefined;
|
|
57
|
-
/** All registered providers. */
|
|
58
55
|
providers(): WorkspaceProvider[];
|
|
59
56
|
}
|
|
60
57
|
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -30,11 +30,4 @@ export interface WorkspaceRouter {
|
|
|
30
30
|
/** Map a route back to a resolution (used by the switch to re-encode child keys). */
|
|
31
31
|
resolveRoute(route: WorkspaceRoute): WorkspaceResolution | undefined;
|
|
32
32
|
}
|
|
33
|
-
/** Convenience base: default codec wiring plus helper for resolution lookup. */
|
|
34
|
-
export declare abstract class BaseWorkspaceRouter implements WorkspaceRouter {
|
|
35
|
-
abstract readonly codec: WorkspaceNamespaceCodec;
|
|
36
|
-
abstract fromNamespace(key: string): WorkspaceResolution | undefined;
|
|
37
|
-
abstract fromAnchor(cwd: string | undefined): WorkspaceConnection | undefined;
|
|
38
|
-
abstract resolveRoute(route: WorkspaceRoute): WorkspaceResolution | undefined;
|
|
39
|
-
}
|
|
40
33
|
//# sourceMappingURL=router.d.ts.map
|
|
@@ -4,11 +4,15 @@
|
|
|
4
4
|
* endpoints the config dialog needs (host create + test). Plain fetch, same
|
|
5
5
|
* origin 鈥?the only data path the panel components use.
|
|
6
6
|
*/
|
|
7
|
-
import { HttpApiError } from '../client-http.ts';
|
|
8
|
-
import { type
|
|
9
|
-
/**
|
|
7
|
+
import { HttpApiError, type HttpErrorBody } from '../client-http.ts';
|
|
8
|
+
import { type RemoteDirEntry, type SshWorkspaceRecord } from '../protocol.ts';
|
|
9
|
+
/**
|
|
10
|
+
* Workspace-route error: the shared transport error under this family's name.
|
|
11
|
+
* It adds no parsing — `readJson` in ../client-http.ts owns status/body
|
|
12
|
+
* decoding for both route families.
|
|
13
|
+
*/
|
|
10
14
|
export declare class WorkspaceApiError extends HttpApiError {
|
|
11
|
-
constructor(message: string,
|
|
15
|
+
constructor(message: string, status?: number, body?: HttpErrorBody);
|
|
12
16
|
}
|
|
13
17
|
/** The workspace route family client. */
|
|
14
18
|
export declare class WorkspaceApi {
|
|
@@ -36,10 +40,6 @@ export declare class WorkspaceApi {
|
|
|
36
40
|
path: string;
|
|
37
41
|
entries: RemoteDirEntry[];
|
|
38
42
|
}>;
|
|
39
|
-
list(root: string, path: string): Promise<DirListing>;
|
|
40
|
-
read(root: string, path: string): Promise<FileRead>;
|
|
41
|
-
write(root: string, path: string, content: string, expectedMtime?: number): Promise<FileWriteResult>;
|
|
42
|
-
search(root: string, queryText: string): Promise<SearchView>;
|
|
43
43
|
}
|
|
44
44
|
/** The two /api/dsh-ssh endpoints the config dialog needs (host create/test). */
|
|
45
45
|
export interface SshHostPayload {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SshApi } from './ssh/api.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Probe + prompt until connected. Resolves true when the alias may proceed;
|
|
4
|
+
* false when the user cancelled or the failure is not interactive. When a
|
|
5
|
+
* credential was already supplied and the retry still fails (wrong password,
|
|
6
|
+
* unreachable host, network drop, …), the password dialog re-opens WITH the
|
|
7
|
+
* failure reason shown, VSCode style — no silent "stuck" states.
|
|
8
|
+
*
|
|
9
|
+
* @param api - the SSH API client.
|
|
10
|
+
* @param alias - the host alias to gate.
|
|
11
|
+
*/
|
|
12
|
+
export declare function connectHost(api: SshApi, alias: string): Promise<boolean>;
|
|
13
|
+
//# sourceMappingURL=connect-host.d.ts.map
|
|
@@ -17,17 +17,19 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
|
17
17
|
'dsh-hardssh': WorkspaceKey;
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
-
export type { PanelControllerSnapshot } from './ssh/panel/controller.ts';
|
|
21
20
|
export type { SshPanelProps } from './ssh/panel/SshPanel.tsx';
|
|
22
|
-
export type { HostsTabProps } from './ssh/panel/HostsTab.tsx';
|
|
23
21
|
export type { HostFormDialogProps } from './ssh/panel/HostFormDialog.tsx';
|
|
24
22
|
export type { TerminalTabProps } from './ssh/panel/TerminalTab.tsx';
|
|
25
23
|
export type { TransferTabProps } from './ssh/panel/TransferTab.tsx';
|
|
26
24
|
export type { TunnelsTabProps } from './ssh/panel/TunnelsTab.tsx';
|
|
27
25
|
export type { ClusterTabProps } from './ssh/panel/ClusterTab.tsx';
|
|
28
26
|
export type { SshKey } from './ssh/locales.ts';
|
|
29
|
-
/** Required services: slots for the
|
|
30
|
-
*
|
|
27
|
+
/** Required services: slots for the extension seats, locale for copy,
|
|
28
|
+
* workspaces for the native local-directory chooser, sessions for the
|
|
29
|
+
* open/new-session connection gate, and sidebarRightTabs for the SSH
|
|
30
|
+
* operations console type. Keep the registry explicit: without it Cordis may
|
|
31
|
+
* activate this client before ui-sidebar-right provides the service; the
|
|
32
|
+
* guarded registration then degrades silently and no HardSSH tab exists. */
|
|
31
33
|
export declare const inject: string[];
|
|
32
34
|
/** Apply the browser half. */
|
|
33
35
|
export declare function apply(ctx: ClientContext): void;
|