@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
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
|
|
6
|
+
import type { FileSystem } from '@deepseek-ai/dsh-fs'
|
|
7
|
+
import type { SubprocessRuntime } from '@deepseek-ai/dsh-subprocess'
|
|
8
|
+
import type { WorkspaceSearchService } from '../base/capability.ts'
|
|
9
|
+
|
|
10
|
+
/** DSH's official workspace capability bindings; no terminal key exists because process owns terminals. */
|
|
11
|
+
export interface DshWorkspaceCapabilityMap {
|
|
12
|
+
'workspace.fs': FileSystem
|
|
13
|
+
'workspace.process': SubprocessRuntime
|
|
14
|
+
'workspace.search': WorkspaceSearchService
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
declare module '../base/model.ts' {
|
|
18
|
+
interface WorkspaceCapabilityMap extends DshWorkspaceCapabilityMap {}
|
|
19
|
+
}
|
|
@@ -1,48 +1,63 @@
|
|
|
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
|
|
|
15
10
|
import type { Context } from '@deepseek-ai/cordis'
|
|
11
|
+
import { randomUUID } from 'node:crypto'
|
|
16
12
|
import { homedir } from 'node:os'
|
|
17
13
|
import { join } from 'node:path'
|
|
18
|
-
import {
|
|
19
|
-
import type { WorkspaceRecord } from '../base/model.ts'
|
|
14
|
+
import type { LedgerListener, WorkspaceLedger as WorkspaceLedgerType } from '../base/ledger.ts'
|
|
20
15
|
import { WorkspaceLedger } from '../base/ledger.ts'
|
|
16
|
+
import { LedgerWorkspaceRouter } from '../base/ledger-router.ts'
|
|
17
|
+
import type {
|
|
18
|
+
WorkspaceConnection,
|
|
19
|
+
WorkspaceCreateInput,
|
|
20
|
+
WorkspaceId,
|
|
21
|
+
WorkspaceProvider,
|
|
22
|
+
WorkspaceRecord,
|
|
23
|
+
WorkspaceUpdate,
|
|
24
|
+
} from '../base/model.ts'
|
|
25
|
+
import { WorkspaceProviderRegistry, type WorkspaceRegistry } from '../base/registry.ts'
|
|
21
26
|
import { registerBuiltinProviders } from '../providers/index.ts'
|
|
22
|
-
import type { SshEngine } from '../ssh/engine.ts'
|
|
23
27
|
import type { HostStoreView } from '../core.ts'
|
|
24
|
-
import type {
|
|
25
|
-
import
|
|
26
|
-
import { LedgerWorkspaceRouter } from '../base/ledger-router.ts'
|
|
27
|
-
import type { WorkspaceProvider } from '../base/model.ts'
|
|
28
|
+
import type { SshEngine } from '../ssh/engine.ts'
|
|
29
|
+
import './dsh-capabilities.ts'
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
export type WorkspaceListener = LedgerListener
|
|
32
|
+
|
|
33
|
+
/** Canonical high-level workspace consumer surface. */
|
|
30
34
|
export interface WorkspaceCore {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
initialize(): Promise<void>
|
|
36
|
+
isReady(): boolean
|
|
37
|
+
whenReady(): Promise<void>
|
|
38
|
+
list(): Promise<WorkspaceRecord[]>
|
|
39
|
+
get(id: WorkspaceId): Promise<WorkspaceRecord | undefined>
|
|
40
|
+
findByAnchor(path: string): Promise<WorkspaceRecord | undefined>
|
|
41
|
+
openById(id: WorkspaceId, signal?: AbortSignal): Promise<WorkspaceConnection | undefined>
|
|
42
|
+
openByAnchor(path: string, signal?: AbortSignal): Promise<WorkspaceConnection | undefined>
|
|
43
|
+
create(input: WorkspaceCreateInput): Promise<WorkspaceRecord>
|
|
44
|
+
update(id: WorkspaceId, patch: WorkspaceUpdate): Promise<WorkspaceRecord | undefined>
|
|
45
|
+
remove(id: WorkspaceId): Promise<boolean>
|
|
46
|
+
subscribe(listener: WorkspaceListener): () => void
|
|
47
|
+
registerProvider(provider: WorkspaceProvider): () => void
|
|
48
|
+
closeAll(): Promise<void>
|
|
49
|
+
|
|
50
|
+
/** @deprecated Internal compatibility surface; ordinary consumers use high-level methods. */
|
|
51
|
+
readonly ledger: WorkspaceLedger
|
|
52
|
+
/** @deprecated Internal compatibility surface; ordinary consumers use registerProvider(). */
|
|
53
|
+
readonly providers: WorkspaceProviderRegistry
|
|
54
|
+
/** @deprecated Internal compatibility surface; ordinary consumers use openById/openByAnchor(). */
|
|
55
|
+
readonly router: LedgerWorkspaceRouter
|
|
56
|
+
/** @deprecated Plugin compatibility surface. */
|
|
57
|
+
readonly registry: WorkspaceRegistry
|
|
41
58
|
}
|
|
42
59
|
|
|
43
|
-
/**
|
|
44
|
-
* A WorkspaceRegistry backed by the generic ledger + provider registry.
|
|
45
|
-
*/
|
|
60
|
+
/** Ledger-backed plugin surface retained while current plugins migrate to WorkspaceCore. */
|
|
46
61
|
class LedgerBasedWorkspaceRegistry implements WorkspaceRegistry {
|
|
47
62
|
constructor(
|
|
48
63
|
private readonly ledger: WorkspaceLedger,
|
|
@@ -54,11 +69,17 @@ class LedgerBasedWorkspaceRegistry implements WorkspaceRegistry {
|
|
|
54
69
|
}
|
|
55
70
|
|
|
56
71
|
subscribe(listener: (workspace: WorkspaceRecord) => void): () => void {
|
|
57
|
-
return this.ledger.subscribe(
|
|
72
|
+
return this.ledger.subscribe(change => {
|
|
73
|
+
if (change.type === 'replaced') {
|
|
74
|
+
for (const workspace of change.records) listener(workspace)
|
|
75
|
+
} else {
|
|
76
|
+
listener(change.record)
|
|
77
|
+
}
|
|
78
|
+
})
|
|
58
79
|
}
|
|
59
80
|
|
|
60
|
-
register(provider: WorkspaceProvider): void {
|
|
61
|
-
this.providerRegistry.register(provider)
|
|
81
|
+
register(provider: WorkspaceProvider): () => void {
|
|
82
|
+
return this.providerRegistry.register(provider)
|
|
62
83
|
}
|
|
63
84
|
|
|
64
85
|
unregister(providerId: string): void {
|
|
@@ -74,27 +95,108 @@ class LedgerBasedWorkspaceRegistry implements WorkspaceRegistry {
|
|
|
74
95
|
}
|
|
75
96
|
}
|
|
76
97
|
|
|
77
|
-
/**
|
|
78
|
-
export
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
+
/** mountWorkspaceCore() requires Cordis and builtins, so this class makes the same core testable with injected generic foundations. */
|
|
99
|
+
export class DefaultWorkspaceCore implements WorkspaceCore {
|
|
100
|
+
readonly registry: WorkspaceRegistry
|
|
101
|
+
|
|
102
|
+
constructor(
|
|
103
|
+
readonly ledger: WorkspaceLedgerType,
|
|
104
|
+
readonly providers: WorkspaceProviderRegistry,
|
|
105
|
+
readonly router = new LedgerWorkspaceRouter(ledger, providers),
|
|
106
|
+
) {
|
|
107
|
+
this.registry = new LedgerBasedWorkspaceRegistry(ledger, providers)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** WorkspaceLedger.load() cannot preopen providers or establish router readiness, so initialize delegates to router initialization. */
|
|
111
|
+
initialize(): Promise<void> {
|
|
112
|
+
return this.router.initialize()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** WorkspaceLedger.snapshotSync() cannot prove provider preopen completed, so readiness comes from the router lifecycle. */
|
|
116
|
+
isReady(): boolean {
|
|
117
|
+
return this.router.isReady()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** isReady() cannot await a pending or failed initialization, so whenReady delegates to the router promise. */
|
|
121
|
+
whenReady(): Promise<void> {
|
|
122
|
+
return this.router.whenReady()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** WorkspaceLedger.list() alone may expose records before runtime readiness, so list waits for initialization first. */
|
|
126
|
+
async list(): Promise<WorkspaceRecord[]> {
|
|
127
|
+
await this.whenReady()
|
|
128
|
+
return this.ledger.list()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** WorkspaceLedger.get() alone may run before strict load/preopen, so get waits for initialization first. */
|
|
132
|
+
async get(id: WorkspaceId): Promise<WorkspaceRecord | undefined> {
|
|
133
|
+
await this.whenReady()
|
|
134
|
+
return this.ledger.get(id)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** WorkspaceLedger.findByAnchor() alone may run before strict load/preopen, so findByAnchor waits for initialization first. */
|
|
138
|
+
async findByAnchor(path: string): Promise<WorkspaceRecord | undefined> {
|
|
139
|
+
await this.whenReady()
|
|
140
|
+
return this.ledger.findByAnchor(path)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** LedgerWorkspaceRouter.ensureOpen() requires a record, so openById exposes identity-based opening to consumers. */
|
|
144
|
+
async openById(id: WorkspaceId, signal?: AbortSignal): Promise<WorkspaceConnection | undefined> {
|
|
145
|
+
await this.whenReady()
|
|
146
|
+
return this.router.openById(id, signal)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** WorkspaceLedger.findByAnchor() returns data only, so openByAnchor exposes the resolved connection in one operation. */
|
|
150
|
+
async openByAnchor(path: string, signal?: AbortSignal): Promise<WorkspaceConnection | undefined> {
|
|
151
|
+
await this.whenReady()
|
|
152
|
+
return this.router.openByAnchor(path, signal)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** WorkspaceLedger.create() cannot validate provider-specific references, so create validates before committing the record. */
|
|
156
|
+
async create(input: WorkspaceCreateInput): Promise<WorkspaceRecord> {
|
|
157
|
+
await this.whenReady()
|
|
158
|
+
const provider = this.providers.get(input.provider.id)
|
|
159
|
+
if (provider === undefined) throw new Error(`workspace.provider-missing: '${input.provider.id}'`)
|
|
160
|
+
const timestamp = new Date().toISOString()
|
|
161
|
+
await provider.validate({ ...structuredClone(input), id: input.id ?? randomUUID(), createdAt: timestamp, updatedAt: timestamp })
|
|
162
|
+
const record = await this.ledger.create(input)
|
|
163
|
+
await this.router.openById(record.id)
|
|
164
|
+
return record
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** WorkspaceLedger.update() cannot validate a changed provider binding, so update checks the proposed record before commit. */
|
|
168
|
+
async update(id: WorkspaceId, patch: WorkspaceUpdate): Promise<WorkspaceRecord | undefined> {
|
|
169
|
+
await this.whenReady()
|
|
170
|
+
const before = await this.ledger.get(id)
|
|
171
|
+
if (before === undefined) return undefined
|
|
172
|
+
const proposed: WorkspaceRecord = { ...before, ...structuredClone(patch), id: before.id, schemaVersion: before.schemaVersion, createdAt: before.createdAt }
|
|
173
|
+
const provider = this.providers.get(proposed.provider.id)
|
|
174
|
+
if (provider === undefined) throw new Error(`workspace.provider-missing: '${proposed.provider.id}'`)
|
|
175
|
+
await provider.validate(proposed)
|
|
176
|
+
const record = await this.ledger.update(id, patch)
|
|
177
|
+
if (record !== undefined) await this.router.openById(id)
|
|
178
|
+
return record
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** WorkspaceLedger.remove() owns persistence while its synchronous change event makes router removal fail closed before this promise returns. */
|
|
182
|
+
async remove(id: WorkspaceId): Promise<boolean> {
|
|
183
|
+
await this.whenReady()
|
|
184
|
+
return this.ledger.remove(id)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** WorkspaceLedger.list() cannot publish later mutations, so subscribe exposes its detached commit stream. */
|
|
188
|
+
subscribe(listener: WorkspaceListener): () => void {
|
|
189
|
+
return this.ledger.subscribe(listener)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** WorkspaceProviderRegistry.get() cannot establish disposer ownership, so registerProvider returns the registry-owned disposer. */
|
|
193
|
+
registerProvider(provider: WorkspaceProvider): () => void {
|
|
194
|
+
return this.providers.register(provider)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** WorkspaceConnection.close() only closes one handle, so closeAll delegates complete cached/in-flight teardown to the router. */
|
|
198
|
+
closeAll(): Promise<void> {
|
|
199
|
+
return this.router.closeAll()
|
|
98
200
|
}
|
|
99
201
|
}
|
|
100
202
|
|
|
@@ -108,47 +210,28 @@ export function genericAnchorRoot(): string {
|
|
|
108
210
|
return join(homedir(), '.dsh', 'workspaces', 'anchors')
|
|
109
211
|
}
|
|
110
212
|
|
|
111
|
-
/**
|
|
112
|
-
* Build and provide the workspace core on the cordis context: registers the
|
|
113
|
-
* builtin providers, instantiates the generic ledger (persisting under
|
|
114
|
-
* ~/.dsh/workspaces/index.v1.json), constructs the ledger router, and
|
|
115
|
-
* provides `workspaceCore`.
|
|
116
|
-
*/
|
|
213
|
+
/** mountWorkspaceCore() cannot register Cordis services or builtin providers, so it performs host assembly (provider registration + `ctx.provide('workspaceCore')`); the boot calls `initialize()` afterwards. */
|
|
117
214
|
export function mountWorkspaceCore(ctx: Context, deps: { engine?: SshEngine; hosts?: HostStoreView }): WorkspaceCore {
|
|
118
215
|
const providers = new WorkspaceProviderRegistry()
|
|
119
216
|
const ledger = new WorkspaceLedger(genericLedgerPath(), genericAnchorRoot())
|
|
120
|
-
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
const router = new LedgerWorkspaceRouter(ledger, registry, {
|
|
126
|
-
open: async (record) => {
|
|
127
|
-
const provider = providers.get(record.provider.id)
|
|
128
|
-
if (provider === undefined) return undefined
|
|
129
|
-
const connection = await provider.open(record)
|
|
130
|
-
return connection
|
|
131
|
-
},
|
|
132
|
-
onRemoved: () => { /* connection cleanup handled by closeAll */ },
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
const core: WorkspaceCore = {
|
|
136
|
-
ledger,
|
|
137
|
-
providers,
|
|
138
|
-
router,
|
|
139
|
-
registry,
|
|
140
|
-
fromSshRecord: sshRecordToWorkspaceRecord,
|
|
141
|
-
}
|
|
217
|
+
// The SSH/local providers build real DSH FileSystem / SubprocessRuntime
|
|
218
|
+
// capabilities on isolated per-workspace scopes, so they require this host
|
|
219
|
+
// ctx (mandatory — there is no capability fallback path any more).
|
|
220
|
+
const providerDisposers = registerBuiltinProviders(providers, deps, ctx)
|
|
221
|
+
const core = new DefaultWorkspaceCore(ledger, providers)
|
|
142
222
|
|
|
143
223
|
ctx.provide('workspaceCore', core)
|
|
224
|
+
// Unload must release every opened connection and provider registration this
|
|
225
|
+
// assembly created.
|
|
226
|
+
ctx.effect(() => async () => {
|
|
227
|
+
await core.closeAll()
|
|
228
|
+
for (const dispose of providerDisposers) dispose()
|
|
229
|
+
}, 'dsh-hardssh: generic workspace core')
|
|
144
230
|
return core
|
|
145
231
|
}
|
|
146
232
|
|
|
147
|
-
/** The legacy SSH ledger held by the hardssh core (type bridge). */
|
|
148
|
-
export type { SshWorkspaceLedger }
|
|
149
|
-
|
|
150
233
|
declare module '@deepseek-ai/cordis' {
|
|
151
234
|
interface Context {
|
|
152
235
|
workspaceCore: WorkspaceCore
|
|
153
236
|
}
|
|
154
|
-
}
|
|
237
|
+
}
|