dsh-over-ssh 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +119 -2
- package/agents/agent-manifest.json +20 -0
- package/agents/linux-amd64/dsh-over-ssh-agent +0 -0
- package/agents/linux-arm64/dsh-over-ssh-agent +0 -0
- package/cordis.patch.yml +84 -0
- package/lib/client.js +6611 -0
- package/lib/file-references.js +366 -0
- package/lib/fs.js +1368 -0
- package/lib/index.js +19034 -0
- package/lib/local-bridge-refs.js +16 -0
- package/lib/local-bridge.js +16 -0
- package/lib/terminal-wrapper.js +294 -0
- package/lib/types/src/client/controls.d.ts +131 -0
- package/lib/types/src/client/core/card-focus-trap.d.ts +45 -0
- package/lib/types/src/client/core/controls.d.ts +164 -0
- package/lib/types/src/client/core/latest.d.ts +48 -0
- package/lib/types/src/client/core/locale.d.ts +38 -0
- package/lib/types/src/client/core/message.d.ts +37 -0
- package/lib/types/src/client/core/panel-model.d.ts +134 -0
- package/lib/types/src/client/core/portal.d.ts +266 -0
- package/lib/types/src/client/core/remote.d.ts +84 -0
- package/lib/types/src/client/core/styles.d.ts +458 -0
- package/lib/types/src/client/file-tree-io.d.ts +19 -0
- package/lib/types/src/client/file-tree.d.ts +271 -0
- package/lib/types/src/client/flows/directory-browser.d.ts +587 -0
- package/lib/types/src/client/flows/session-directory.d.ts +415 -0
- package/lib/types/src/client/flows/workspace-directory-flow.d.ts +480 -0
- package/lib/types/src/client/index.d.ts +49 -0
- package/lib/types/src/client/locale.d.ts +31 -0
- package/lib/types/src/client/panel-model.d.ts +37 -0
- package/lib/types/src/client/panel.d.ts +77 -0
- package/lib/types/src/client/roots/connection-test.d.ts +69 -0
- package/lib/types/src/client/roots/roots-panel-state.d.ts +307 -0
- package/lib/types/src/client/roots/roots-panel-view.d.ts +280 -0
- package/lib/types/src/client/roots/roots-panel.d.ts +364 -0
- package/lib/types/src/client/roots/transfer-panel-view.d.ts +35 -0
- package/lib/types/src/client/roots/transfer-panel.d.ts +241 -0
- package/lib/types/src/client/roots/transfer-progress.d.ts +67 -0
- package/lib/types/src/client/roots-panel-view.d.ts +236 -0
- package/lib/types/src/client/roots-panel.d.ts +347 -0
- package/lib/types/src/client/session/session-panel-view.d.ts +336 -0
- package/lib/types/src/client/session/session-panel.d.ts +322 -0
- package/lib/types/src/client/session/session-remote-mark.d.ts +110 -0
- package/lib/types/src/client/session/session-row-actions.d.ts +398 -0
- package/lib/types/src/client/session-panel.d.ts +152 -0
- package/lib/types/src/client/styles.d.ts +252 -0
- package/lib/types/src/client/workspace-directory-flow.d.ts +342 -0
- package/lib/types/src/config.d.ts +312 -0
- package/lib/types/src/file-references.d.ts +25 -0
- package/lib/types/src/fs.d.ts +46 -0
- package/lib/types/src/host/app/backend.d.ts +170 -0
- package/lib/types/src/host/app/recycle-bin/recycle-bin-contract.d.ts +247 -0
- package/lib/types/src/host/app/recycle-bin/recycle-bin-move.d.ts +18 -0
- package/lib/types/src/host/app/recycle-bin/recycle-bin-service.d.ts +45 -0
- package/lib/types/src/host/app/recycle-bin/recycle-bin-world.d.ts +38 -0
- package/lib/types/src/host/app/recycle-bin-contract.d.ts +215 -0
- package/lib/types/src/host/app/recycle-bin-move.d.ts +18 -0
- package/lib/types/src/host/app/recycle-bin-service.d.ts +45 -0
- package/lib/types/src/host/app/recycle-bin-world.d.ts +35 -0
- package/lib/types/src/host/app/roots/roots-api-connections.d.ts +49 -0
- package/lib/types/src/host/app/roots/roots-api-contract.d.ts +369 -0
- package/lib/types/src/host/app/roots/roots-api-directories.d.ts +13 -0
- package/lib/types/src/host/app/roots/roots-api-http.d.ts +77 -0
- package/lib/types/src/host/app/roots/roots-api-long-poll.d.ts +130 -0
- package/lib/types/src/host/app/roots/roots-api-projections.d.ts +102 -0
- package/lib/types/src/host/app/roots/roots-api-roots.d.ts +13 -0
- package/lib/types/src/host/app/roots/roots-api-state.d.ts +22 -0
- package/lib/types/src/host/app/roots/roots-api-transfers.d.ts +26 -0
- package/lib/types/src/host/app/roots/roots-api.d.ts +55 -0
- package/lib/types/src/host/app/roots/user-roots.d.ts +552 -0
- package/lib/types/src/host/app/roots-api-connections.d.ts +49 -0
- package/lib/types/src/host/app/roots-api-contract.d.ts +339 -0
- package/lib/types/src/host/app/roots-api-directories.d.ts +13 -0
- package/lib/types/src/host/app/roots-api-http.d.ts +77 -0
- package/lib/types/src/host/app/roots-api-projections.d.ts +102 -0
- package/lib/types/src/host/app/roots-api-roots.d.ts +13 -0
- package/lib/types/src/host/app/roots-api-state.d.ts +22 -0
- package/lib/types/src/host/app/roots-api-support.d.ts +182 -0
- package/lib/types/src/host/app/roots-api-transfers.d.ts +13 -0
- package/lib/types/src/host/app/roots-api.d.ts +55 -0
- package/lib/types/src/host/app/routed-fs.d.ts +239 -0
- package/lib/types/src/host/app/serialize.d.ts +37 -0
- package/lib/types/src/host/app/session-admin/session-admin-context.d.ts +36 -0
- package/lib/types/src/host/app/session-admin/session-admin-contract.d.ts +221 -0
- package/lib/types/src/host/app/session-admin/session-admin-delete.d.ts +34 -0
- package/lib/types/src/host/app/session-admin/session-admin-service.d.ts +40 -0
- package/lib/types/src/host/app/session-admin-context.d.ts +36 -0
- package/lib/types/src/host/app/session-admin-contract.d.ts +214 -0
- package/lib/types/src/host/app/session-admin-delete.d.ts +34 -0
- package/lib/types/src/host/app/session-admin-service.d.ts +38 -0
- package/lib/types/src/host/app/takeover/takeover-plan.d.ts +227 -0
- package/lib/types/src/host/app/takeover/takeover-rows.d.ts +55 -0
- package/lib/types/src/host/app/transport/transport-selector.d.ts +98 -0
- package/lib/types/src/host/app/transport/transport-service.d.ts +82 -0
- package/lib/types/src/host/app/transport-selector.d.ts +98 -0
- package/lib/types/src/host/app/transport-service.d.ts +75 -0
- package/lib/types/src/host/app/user-roots.d.ts +461 -0
- package/lib/types/src/host/domain/bin-location.d.ts +79 -0
- package/lib/types/src/host/domain/capability.d.ts +78 -0
- package/lib/types/src/host/domain/connection.d.ts +95 -0
- package/lib/types/src/host/domain/errors.d.ts +55 -0
- package/lib/types/src/host/domain/glob-pattern.d.ts +62 -0
- package/lib/types/src/host/domain/literal-text.d.ts +63 -0
- package/lib/types/src/host/domain/path-grammar.d.ts +84 -0
- package/lib/types/src/host/domain/recycle-bin.d.ts +337 -0
- package/lib/types/src/host/domain/root.d.ts +74 -0
- package/lib/types/src/host/domain/roots/bin-location.d.ts +91 -0
- package/lib/types/src/host/domain/roots/connection.d.ts +106 -0
- package/lib/types/src/host/domain/roots/root.d.ts +108 -0
- package/lib/types/src/host/domain/roots/route-table.d.ts +102 -0
- package/lib/types/src/host/domain/roots/session-roots.d.ts +151 -0
- package/lib/types/src/host/domain/roots/ssh-config.d.ts +73 -0
- package/lib/types/src/host/domain/route-table.d.ts +80 -0
- package/lib/types/src/host/domain/session/session-admin.d.ts +345 -0
- package/lib/types/src/host/domain/session/terminal-target.d.ts +136 -0
- package/lib/types/src/host/domain/session-admin.d.ts +330 -0
- package/lib/types/src/host/domain/shell.d.ts +47 -0
- package/lib/types/src/host/domain/ssh-config.d.ts +73 -0
- package/lib/types/src/host/domain/terminal-target.d.ts +132 -0
- package/lib/types/src/host/domain/tool-requests.d.ts +369 -0
- package/lib/types/src/host/domain/transfer/recycle-bin.d.ts +337 -0
- package/lib/types/src/host/domain/transfer/transfer.d.ts +260 -0
- package/lib/types/src/host/domain/transfer.d.ts +260 -0
- package/lib/types/src/host/infra/backend/local-backend.d.ts +84 -0
- package/lib/types/src/host/infra/backend/local-version.d.ts +43 -0
- package/lib/types/src/host/infra/backend/platform-local-backend.d.ts +89 -0
- package/lib/types/src/host/infra/backend/remote-backend.d.ts +239 -0
- package/lib/types/src/host/infra/cache/lru.d.ts +52 -0
- package/lib/types/src/host/infra/cache/versioned.d.ts +77 -0
- package/lib/types/src/host/infra/temp-name.d.ts +19 -0
- package/lib/types/src/host/infra/transport/agent/agent-installer.d.ts +121 -0
- package/lib/types/src/host/infra/transport/agent/agent-protocol-client.d.ts +132 -0
- package/lib/types/src/host/infra/transport/agent/agent-protocol-codec.d.ts +140 -0
- package/lib/types/src/host/infra/transport/agent/agent-protocol-errors.d.ts +41 -0
- package/lib/types/src/host/infra/transport/agent/agent-protocol-frames.d.ts +197 -0
- package/lib/types/src/host/infra/transport/agent/agent-protocol-types.d.ts +564 -0
- package/lib/types/src/host/infra/transport/agent/agent-transport.d.ts +418 -0
- package/lib/types/src/host/infra/transport/agent-installer.d.ts +121 -0
- package/lib/types/src/host/infra/transport/agent-protocol-client.d.ts +123 -0
- package/lib/types/src/host/infra/transport/agent-protocol-codec.d.ts +131 -0
- package/lib/types/src/host/infra/transport/agent-protocol-errors.d.ts +41 -0
- package/lib/types/src/host/infra/transport/agent-protocol-frames.d.ts +197 -0
- package/lib/types/src/host/infra/transport/agent-protocol-types.d.ts +509 -0
- package/lib/types/src/host/infra/transport/agent-transport.d.ts +353 -0
- package/lib/types/src/host/infra/transport/alias-transport.d.ts +206 -0
- package/lib/types/src/host/infra/transport/askpass-fs.d.ts +20 -0
- package/lib/types/src/host/infra/transport/askpass.d.ts +133 -0
- package/lib/types/src/host/infra/transport/command-pool.d.ts +61 -0
- package/lib/types/src/host/infra/transport/fs-commands.d.ts +140 -0
- package/lib/types/src/host/infra/transport/git-status-route.d.ts +63 -0
- package/lib/types/src/host/infra/transport/git-status.d.ts +101 -0
- package/lib/types/src/host/infra/transport/node-spawn.d.ts +26 -0
- package/lib/types/src/host/infra/transport/session-window.d.ts +51 -0
- package/lib/types/src/host/infra/transport/shell-quote.d.ts +59 -0
- package/lib/types/src/host/infra/transport/ssh/alias-transport.d.ts +206 -0
- package/lib/types/src/host/infra/transport/ssh/askpass-fs.d.ts +20 -0
- package/lib/types/src/host/infra/transport/ssh/askpass.d.ts +133 -0
- package/lib/types/src/host/infra/transport/ssh/shell-quote.d.ts +62 -0
- package/lib/types/src/host/infra/transport/ssh/ssh-argv.d.ts +43 -0
- package/lib/types/src/host/infra/transport/ssh/ssh2-transport.d.ts +328 -0
- package/lib/types/src/host/infra/transport/ssh-argv.d.ts +43 -0
- package/lib/types/src/host/infra/transport/ssh2-transport.d.ts +302 -0
- package/lib/types/src/host/infra/transport/transport-base.d.ts +34 -0
- package/lib/types/src/host/infra/transport/transport.d.ts +527 -0
- package/lib/types/src/host/integration/agent/agent-install-io.d.ts +33 -0
- package/lib/types/src/host/integration/agent/agent-preflight.d.ts +56 -0
- package/lib/types/src/host/integration/agent/askpass-registry.d.ts +66 -0
- package/lib/types/src/host/integration/agent-install-io.d.ts +33 -0
- package/lib/types/src/host/integration/agent-preflight.d.ts +56 -0
- package/lib/types/src/host/integration/anchor-directory.d.ts +56 -0
- package/lib/types/src/host/integration/askpass-registry.d.ts +59 -0
- package/lib/types/src/host/integration/bin-root-resolver.d.ts +81 -0
- package/lib/types/src/host/integration/connection-test.d.ts +73 -0
- package/lib/types/src/host/integration/file-reference-provider.d.ts +115 -0
- package/lib/types/src/host/integration/fs/file-reference-provider.d.ts +142 -0
- package/lib/types/src/host/integration/fs/fs-provider.d.ts +368 -0
- package/lib/types/src/host/integration/fs/git-status-wiring.d.ts +21 -0
- package/lib/types/src/host/integration/fs/platform-errors.d.ts +94 -0
- package/lib/types/src/host/integration/fs/remote-file-tools.d.ts +369 -0
- package/lib/types/src/host/integration/fs/remote-index.d.ts +177 -0
- package/lib/types/src/host/integration/fs-provider.d.ts +226 -0
- package/lib/types/src/host/integration/lsp/lsp-framing.d.ts +38 -0
- package/lib/types/src/host/integration/lsp/lsp-registry.d.ts +67 -0
- package/lib/types/src/host/integration/lsp/lsp-session.d.ts +130 -0
- package/lib/types/src/host/integration/management-tools.d.ts +64 -0
- package/lib/types/src/host/integration/recycle-bin-service.d.ts +247 -0
- package/lib/types/src/host/integration/remote/connection-test.d.ts +73 -0
- package/lib/types/src/host/integration/remote/remote-face.d.ts +127 -0
- package/lib/types/src/host/integration/remote/remote-tool-backend.d.ts +108 -0
- package/lib/types/src/host/integration/remote-face.d.ts +127 -0
- package/lib/types/src/host/integration/remote-file-tools.d.ts +241 -0
- package/lib/types/src/host/integration/remote-home.d.ts +69 -0
- package/lib/types/src/host/integration/remote-index.d.ts +165 -0
- package/lib/types/src/host/integration/remote-job-tools.d.ts +295 -0
- package/lib/types/src/host/integration/remote-tool-backend.d.ts +97 -0
- package/lib/types/src/host/integration/remote-tools.d.ts +206 -0
- package/lib/types/src/host/integration/root-registry.d.ts +115 -0
- package/lib/types/src/host/integration/roots/anchor-directory.d.ts +56 -0
- package/lib/types/src/host/integration/roots/bin-root-resolver.d.ts +81 -0
- package/lib/types/src/host/integration/roots/remote-home.d.ts +69 -0
- package/lib/types/src/host/integration/roots/root-registry.d.ts +144 -0
- package/lib/types/src/host/integration/roots/roots-wiring.d.ts +219 -0
- package/lib/types/src/host/integration/roots/session-root-index.d.ts +52 -0
- package/lib/types/src/host/integration/roots-api.d.ts +407 -0
- package/lib/types/src/host/integration/roots-wiring.d.ts +211 -0
- package/lib/types/src/host/integration/session/session-api.d.ts +88 -0
- package/lib/types/src/host/integration/session/session-bin-lock.d.ts +80 -0
- package/lib/types/src/host/integration/session/session-header.d.ts +151 -0
- package/lib/types/src/host/integration/session/session-labels.d.ts +56 -0
- package/lib/types/src/host/integration/session/session-route-wiring.d.ts +50 -0
- package/lib/types/src/host/integration/session-admin-service.d.ts +236 -0
- package/lib/types/src/host/integration/session-api.d.ts +81 -0
- package/lib/types/src/host/integration/session-bin-lock.d.ts +80 -0
- package/lib/types/src/host/integration/session-header.d.ts +118 -0
- package/lib/types/src/host/integration/session-labels.d.ts +56 -0
- package/lib/types/src/host/integration/session-route-wiring.d.ts +50 -0
- package/lib/types/src/host/integration/session-scope.d.ts +113 -0
- package/lib/types/src/host/integration/session-tools.d.ts +44 -0
- package/lib/types/src/host/integration/strict-arguments.d.ts +79 -0
- package/lib/types/src/host/integration/takeover/takeover-api.d.ts +62 -0
- package/lib/types/src/host/integration/takeover/takeover-route-wiring.d.ts +60 -0
- package/lib/types/src/host/integration/takeover/takeover-service.d.ts +167 -0
- package/lib/types/src/host/integration/terminal-manifest.d.ts +94 -0
- package/lib/types/src/host/integration/tools/management-tools.d.ts +245 -0
- package/lib/types/src/host/integration/tools/remote-job-tools.d.ts +316 -0
- package/lib/types/src/host/integration/tools/remote-lsp-tools.d.ts +43 -0
- package/lib/types/src/host/integration/tools/remote-tools.d.ts +236 -0
- package/lib/types/src/host/integration/tools/session-target.d.ts +52 -0
- package/lib/types/src/host/integration/tools/session-tools.d.ts +46 -0
- package/lib/types/src/host/integration/tools/strict-arguments.d.ts +79 -0
- package/lib/types/src/host/integration/tools/terminal-manifest.d.ts +94 -0
- package/lib/types/src/host/integration/transfer/remote-copy.d.ts +66 -0
- package/lib/types/src/host/integration/transfer/transfer-adapters.d.ts +48 -0
- package/lib/types/src/host/integration/transfer/transfer-service.d.ts +212 -0
- package/lib/types/src/host/integration/transfer/transfer-tracker.d.ts +106 -0
- package/lib/types/src/host/integration/transfer-adapters.d.ts +44 -0
- package/lib/types/src/host/integration/transfer-service.d.ts +164 -0
- package/lib/types/src/host/integration/transfer-tracker.d.ts +106 -0
- package/lib/types/src/host/integration/user-roots.d.ts +461 -0
- package/lib/types/src/index.d.ts +66 -0
- package/lib/types/src/local-bridge-refs.d.ts +26 -0
- package/lib/types/src/local-bridge.d.ts +36 -0
- package/lib/types/src/shared/format.d.ts +13 -0
- package/lib/types/src/wiring/connections.d.ts +189 -0
- package/lib/types/src/wiring/fs.d.ts +13 -0
- package/lib/types/src/wiring/helpers.d.ts +144 -0
- package/lib/types/src/wiring/install-memo.d.ts +32 -0
- package/lib/types/src/wiring/legacy-dirs.d.ts +62 -0
- package/lib/types/src/wiring/package-paths.d.ts +35 -0
- package/lib/types/src/wiring/roots.d.ts +21 -0
- package/lib/types/src/wiring/state-dir.d.ts +29 -0
- package/lib/types/src/wiring/tools.d.ts +35 -0
- package/package.json +120 -4
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 阶段 2「一键接管」的**纯模型**:四行的开关 → 组合状态 / 操作序列 / 运行态核对 / 手动改法。
|
|
3
|
+
*
|
|
4
|
+
* 本模块没有 I/O、不 import 任何 `@deepseek-ai/*`(AGENTS.md 红线 2),输入只是一张
|
|
5
|
+
* `patchId → {enabled, readOnlyReason, fiberPhase}` 表,输出是纯数据。执行器在
|
|
6
|
+
* `src/host/integration/takeover/takeover-service.ts`。
|
|
7
|
+
*
|
|
8
|
+
* ## 身份:`patchId` 才是本插件认识的那一行
|
|
9
|
+
*
|
|
10
|
+
* 宿主把两套 id 混在同一个行对象里,**不能互相顶替**(`plugin-manager/src/index.ts:256-272`):
|
|
11
|
+
*
|
|
12
|
+
* | 字段 | 含义 | 真机上的例子 |
|
|
13
|
+
* |---|---|---|
|
|
14
|
+
* | `entryId` | **Loader 条目身份**。挂在 `include` 下的条目带前缀 | `include:fs-sandbox` |
|
|
15
|
+
* | `patchId` | profile patch 里的**行 id**,即 `actual.options.id`(`index.ts:261,270`) | `fs-sandbox` |
|
|
16
|
+
* | `moduleName` | 该条目 import 的模块说明符(`entry.options.name`) | `@deepseek-ai/dsh-fs-sandbox` |
|
|
17
|
+
*
|
|
18
|
+
* 用户在自己的 `cordis.patch.yml` 里看到、也需要改的是 **patchId**;`setPluginEnabled` 收的却是
|
|
19
|
+
* **entryId**(宿主自己再去查 `patchId`)。所以:
|
|
20
|
+
*
|
|
21
|
+
* - 本模块的常量、步骤、UI 一律用 **patchId**(`TAKEOVER_PATCH_IDS`);
|
|
22
|
+
* - 真实 `entryId` 只作为诊断信息随行保存(`TakeoverRow.entryId`),**从不**用来判断身份 ——
|
|
23
|
+
* 拿它跟 `fs-sandbox` 这种常量比,在真机上永远匹配不上(真值是 `include:fs-sandbox`)。
|
|
24
|
+
*
|
|
25
|
+
* ## 为什么顺序要单独建模
|
|
26
|
+
*
|
|
27
|
+
* `ctx.fs` / `ctx.fileReferences` 都是单例服务,两个提供方同时在场时后来者注册会抛
|
|
28
|
+
* `service "fs" has been registered at …`(`doc/02-platform/fs-seam.md` §1);而本插件的 `./fs`
|
|
29
|
+
* 条目在核心行仍占用时会 warn 后 `return undefined`(`src/fs.ts:55-62`),并且**不会自己重试**。
|
|
30
|
+
* 所以 apply 的顺序是正确性的一部分:
|
|
31
|
+
*
|
|
32
|
+
* 1. 先 enable 我们的两条 —— 核心仍占用时会被跳过(无害);
|
|
33
|
+
* 2. 再 disable 两条核心 —— 让出两个服务;
|
|
34
|
+
* 3. 最后把我们的两条各做一次 disable + enable —— 强制在核心让位后重新 apply。
|
|
35
|
+
*
|
|
36
|
+
* revert 反向:先 disable 我们的两条,再 enable 两条核心。
|
|
37
|
+
*
|
|
38
|
+
* ## 三件它不假装的事
|
|
39
|
+
*
|
|
40
|
+
* 1. **`enabled` 是配置值,不是运行态。** 宿主 `PluginInfo.enabled` = `!entry.disabled`
|
|
41
|
+
* (`packages/host/plugin-inventory/src/index.ts:89-95`),只有 `fiberPhase` 才说明「提供方真的在跑」。
|
|
42
|
+
* 两者不一致时 {@link liveMismatch} 会说出来,而不是让面板显示一个漂亮的谎。
|
|
43
|
+
* 2. **只读行只挡它真正要碰的行**:`readOnlyReason` 非空的行不能通过 profile patch 改,必须在动第一行
|
|
44
|
+
* 之前拒绝;但**已经满足的请求**(空序列)不算「要碰」。
|
|
45
|
+
* 3. **幂等**:已处于目标态时步骤序列为空,执行器因此不会为了「确认一下」去写 profile patch。
|
|
46
|
+
*
|
|
47
|
+
* @module dsh-remote-workspace/app/takeover-plan
|
|
48
|
+
*/
|
|
49
|
+
/** 我们自己的两行(`cordis.patch.yml` 里的 `remote-workspace-fs` 与 `remote-workspace-file-references`)。 */
|
|
50
|
+
export declare const OUR_PATCH_IDS: readonly ["remote-workspace-fs", "remote-workspace-file-references"];
|
|
51
|
+
/** 核心的两行(bundle patch 里的 `fs-sandbox` 与 `file-reference-local`)。 */
|
|
52
|
+
export declare const CORE_PATCH_IDS: readonly ["fs-sandbox", "file-reference-local"];
|
|
53
|
+
/** 接管涉及的全部四行,顺序固定:先我们的、后核心的(与 apply 的叙述顺序一致)。 */
|
|
54
|
+
export declare const TAKEOVER_PATCH_IDS: readonly ["remote-workspace-fs", "remote-workspace-file-references", "fs-sandbox", "file-reference-local"];
|
|
55
|
+
/** 一行属于哪一边。 */
|
|
56
|
+
export type TakeoverSide = 'ours' | 'core';
|
|
57
|
+
/** 宿主 `PluginInfo.fiberPhase` 的取值(`plugin-inventory/src/types.ts:8-14`);`null` = 没有活着的根 fiber。 */
|
|
58
|
+
export type TakeoverFiberPhase = 'pending' | 'loading' | 'active' | 'failed' | 'unloading' | null;
|
|
59
|
+
/** 四行之一在宿主 `listPlugins()` 里的最小投影。 */
|
|
60
|
+
export interface TakeoverRow {
|
|
61
|
+
/** **规范身份**:profile patch 里的行 id。内部判断与 UI 都用它。 */
|
|
62
|
+
readonly patchId: string;
|
|
63
|
+
/** Loader 条目的真实 id(如 `include:fs-sandbox`)。只作诊断,**不参与身份判断**。 */
|
|
64
|
+
readonly entryId?: string | undefined;
|
|
65
|
+
/** 该条目 import 的模块说明符,身份交叉校验用。 */
|
|
66
|
+
readonly moduleName?: string | undefined;
|
|
67
|
+
/** 该行当前是否启用:`PluginInfo.enabled`,即**配置/组合**的开关,不是「fiber 正在跑」。 */
|
|
68
|
+
readonly enabled: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* 非空表示这一行**不能**通过 profile patch 修改(`'management-required'` | `'unaddressable'`)。
|
|
71
|
+
* 原样透传,不做枚举校验:宿主将来多一种原因时,这里要如实报告而不是把它当「没有原因」。
|
|
72
|
+
*/
|
|
73
|
+
readonly readOnlyReason?: string | undefined;
|
|
74
|
+
/** 该行根 fiber 的运行状态;`undefined` = 宿主没给这个字段(无法判断)。 */
|
|
75
|
+
readonly fiberPhase?: TakeoverFiberPhase | undefined;
|
|
76
|
+
}
|
|
77
|
+
/** 四行组合起来的接管状态。 */
|
|
78
|
+
export type TakeoverState =
|
|
79
|
+
/** 核心两行在、我们的两行不在:阶段 1(本插件没有接管)。 */
|
|
80
|
+
'off'
|
|
81
|
+
/** 我们的两行在、核心两行不在:阶段 2(本插件已接管)。 */
|
|
82
|
+
| 'on'
|
|
83
|
+
/** 其它组合(含两方同时在场、只有一行切换)。 */
|
|
84
|
+
| 'mixed'
|
|
85
|
+
/** 这台宿主上无法表达接管:没有 `pluginManager`,或插件清单里缺行。 */
|
|
86
|
+
| 'unsupported';
|
|
87
|
+
/** 一键接管的两个方向。 */
|
|
88
|
+
export type TakeoverAction = 'apply' | 'revert';
|
|
89
|
+
/** 一步为什么存在。 */
|
|
90
|
+
export type TakeoverStepWhy =
|
|
91
|
+
/** apply 的第 1 段:预置我们自己的行。核心仍占用时这一步会被跳过,**按设计无害**。 */
|
|
92
|
+
'enable-ours'
|
|
93
|
+
/** apply 的第 2 段:让核心的两行让位。 */
|
|
94
|
+
| 'disable-core'
|
|
95
|
+
/** apply 的第 3 段:把我们自己的行各做一次 disable + enable,强制重新 apply。 */
|
|
96
|
+
| 'reapply-ours'
|
|
97
|
+
/** revert 的第 1 段:先撤掉我们自己的两行。 */
|
|
98
|
+
| 'disable-ours'
|
|
99
|
+
/** revert 的第 2 段:再把核心的两行恢复回来。 */
|
|
100
|
+
| 'enable-core';
|
|
101
|
+
/** 一条**原始**操作:对某一行调用一次 `setPluginEnabled`。 */
|
|
102
|
+
export interface TakeoverStep {
|
|
103
|
+
/** 规范身份(patchId)。真正写盘时宿主还要用它自己的 `entryId` 换算,那是执行器的事。 */
|
|
104
|
+
readonly patchId: string;
|
|
105
|
+
/** 目标值:`true` = 启用,`false` = 禁用。 */
|
|
106
|
+
readonly enabled: boolean;
|
|
107
|
+
readonly why: TakeoverStepWhy;
|
|
108
|
+
}
|
|
109
|
+
/** 整次操作被拒绝的原因(在**动第一行之前**就判定,不留半成品)。 */
|
|
110
|
+
export type TakeoverRefusal =
|
|
111
|
+
/** 计划要碰的行里有 `readOnlyReason` 非空的。 */
|
|
112
|
+
'read-only'
|
|
113
|
+
/** 插件清单里找不到四行中的某一行(或身份有歧义,按缺行处理)。 */
|
|
114
|
+
| 'rows-missing';
|
|
115
|
+
/** 规划结果:要么一串步骤,要么一个拒绝(附原因与涉及的行)。 */
|
|
116
|
+
export type TakeoverPlan = {
|
|
117
|
+
readonly ok: true;
|
|
118
|
+
readonly action: TakeoverAction;
|
|
119
|
+
readonly steps: readonly TakeoverStep[];
|
|
120
|
+
} | {
|
|
121
|
+
readonly ok: false;
|
|
122
|
+
readonly refusal: TakeoverRefusal;
|
|
123
|
+
/** 造成拒绝的行(patchId)。 */
|
|
124
|
+
readonly patchIds: readonly string[];
|
|
125
|
+
/** 面向用户的说明,直接可进 HTTP 的 `error` 字段。 */
|
|
126
|
+
readonly message: string;
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* 一行属于哪一边。
|
|
130
|
+
*
|
|
131
|
+
* @param patchId - profile patch 行 id。
|
|
132
|
+
* @returns `'ours'` / `'core'`,不认识的行返回 `undefined`。
|
|
133
|
+
*/
|
|
134
|
+
export declare function sideOf(patchId: string): TakeoverSide | undefined;
|
|
135
|
+
/**
|
|
136
|
+
* 插件清单里缺了哪几行(按 {@link TAKEOVER_PATCH_IDS} 的顺序)。
|
|
137
|
+
*
|
|
138
|
+
* 「缺」也包括**身份有歧义**(两行都声称是 `fs-sandbox`):识别层会把歧义行丢掉,
|
|
139
|
+
* 于是这里如实报缺 —— 宁可说「没有这一行」,也不猜哪一行是真的。
|
|
140
|
+
*
|
|
141
|
+
* @param rows - 已投影的四行(可能不全)。
|
|
142
|
+
* @returns 缺失的 `patchId` 列表;齐全时为空数组。
|
|
143
|
+
*/
|
|
144
|
+
export declare function missingPatchIds(rows: readonly TakeoverRow[]): readonly string[];
|
|
145
|
+
/**
|
|
146
|
+
* 四行的当前组合属于哪种接管状态。
|
|
147
|
+
*
|
|
148
|
+
* @param rows - 已投影的四行(可能不全)。
|
|
149
|
+
* @returns {@link TakeoverState};行不齐时是 `'unsupported'`(无法表达,而不是猜一个)。
|
|
150
|
+
*/
|
|
151
|
+
export declare function takeoverState(rows: readonly TakeoverRow[]): TakeoverState;
|
|
152
|
+
/**
|
|
153
|
+
* 规划一次 apply / revert。
|
|
154
|
+
*
|
|
155
|
+
* 步骤只包含**真正会改变状态**的操作(幂等),并且严格按模块头注释的顺序排列:
|
|
156
|
+
*
|
|
157
|
+
* - apply:`enable-ours*` → `disable-core*` → (`reapply-ours` 的 disable+enable)*
|
|
158
|
+
* —— 最后一段只在前面确实有动作时追加;已经 `on` 时整串为空。
|
|
159
|
+
* - revert:`disable-ours*` → `enable-core*`;已经 `off` 时整串为空。
|
|
160
|
+
*
|
|
161
|
+
* @param rows - 四行的当前状态(`listPlugins()` 的投影)。
|
|
162
|
+
* @param action - `'apply'` 或 `'revert'`。
|
|
163
|
+
* @returns 步骤序列,或一个拒绝。
|
|
164
|
+
*/
|
|
165
|
+
export declare function planTakeover(rows: readonly TakeoverRow[], action: TakeoverAction): TakeoverPlan;
|
|
166
|
+
/**
|
|
167
|
+
* 每一行在这次操作里的目标值。
|
|
168
|
+
*
|
|
169
|
+
* @param action - `'apply'`(接管)或 `'revert'`(交还)。
|
|
170
|
+
* @returns `patchId → enabled` 的目标表。
|
|
171
|
+
*/
|
|
172
|
+
export declare function targetEnabled(action: TakeoverAction): ReadonlyMap<string, boolean>;
|
|
173
|
+
/**
|
|
174
|
+
* 计划要碰的行里,哪些是只读的。
|
|
175
|
+
*
|
|
176
|
+
* 只读行不能被 profile patch 改,所以必须在**动第一行之前**拒绝;但只针对真的要改的行 —— 已经满足的
|
|
177
|
+
* 请求(空序列)不该因为某行只读而失败。
|
|
178
|
+
*
|
|
179
|
+
* @param present - 按 `patchId` 索引的四行。
|
|
180
|
+
* @param steps - 已排好的步骤。
|
|
181
|
+
* @returns 只读且被计划碰到的 `patchId`(按步骤首次出现顺序,去重)。
|
|
182
|
+
*/
|
|
183
|
+
export declare function readOnlyTouched(present: ReadonlyMap<string, TakeoverRow>, steps: readonly TakeoverStep[]): readonly string[];
|
|
184
|
+
/**
|
|
185
|
+
* 面向用户的拒绝说明(HTTP `error` 字段直接用这一段)。
|
|
186
|
+
*
|
|
187
|
+
* @param refusal - 拒绝原因。
|
|
188
|
+
* @param patchIds - 涉及的行。
|
|
189
|
+
* @returns 一句话,说明「为什么不行、下一步怎么办」。
|
|
190
|
+
*/
|
|
191
|
+
export declare function refusalMessage(refusal: TakeoverRefusal, patchIds: readonly string[]): string;
|
|
192
|
+
/** 一处「配置说这样、运行态不是这样」。 */
|
|
193
|
+
export interface TakeoverLiveMismatch {
|
|
194
|
+
readonly patchId: string;
|
|
195
|
+
/** 组合状态要求它是什么:提供方该在跑,还是该没有 fiber。 */
|
|
196
|
+
readonly expected: 'active' | 'off';
|
|
197
|
+
/** 实际看到的 fiber 状态;`undefined` = 宿主没给这个字段(判断不了)。 */
|
|
198
|
+
readonly actual: TakeoverFiberPhase | undefined;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* 配置状态与运行态不一致的行。
|
|
202
|
+
*
|
|
203
|
+
* `enabled` 是**配置**(`PluginInfo.enabled` = `!entry.disabled`),`fiberPhase` 才是**运行**。两者不一致
|
|
204
|
+
* 的典型场景:改完 profile patch 但没重启(或宿主没有 hmr)—— 面板若只看 `enabled`,会显示「已接管」
|
|
205
|
+
* 而实际提供方根本没在跑。
|
|
206
|
+
*
|
|
207
|
+
* 只对 `on` / `off` 两个自洽形态回答;`mixed` / `unsupported` 没有「应有的运行态」可言,返回空。
|
|
208
|
+
*
|
|
209
|
+
* @param rows - 四行(含 `fiberPhase`)。
|
|
210
|
+
* @returns 不一致的行;全部一致或状态不自洽时为空数组。
|
|
211
|
+
*/
|
|
212
|
+
export declare function liveMismatch(rows: readonly TakeoverRow[]): readonly TakeoverLiveMismatch[];
|
|
213
|
+
/**
|
|
214
|
+
* 用户自己改 profile patch 时要写的行(YAML 片段,直接可粘贴)。
|
|
215
|
+
*
|
|
216
|
+
* 为什么需要它:宿主的写入口只接受「一行一个布尔值」,也没有批量/事务接口;本插件不替用户改文件
|
|
217
|
+
* (见 `takeover-service.ts` 模块头)。所以这张表就是拒绝时交还给用户的东西 —— 行 id 用的是
|
|
218
|
+
* **patchId**,正是他 `cordis.patch.yml` 里写的那一个。
|
|
219
|
+
*
|
|
220
|
+
* 只列出**需要改**的行:已经是目标值的行不出现(避免用户把无意义的覆盖写进自己的 patch)。
|
|
221
|
+
*
|
|
222
|
+
* @param rows - 四行的当前状态。
|
|
223
|
+
* @param action - `'apply'` 或 `'revert'`。
|
|
224
|
+
* @returns YAML 行;已经处于目标态时为空数组。
|
|
225
|
+
*/
|
|
226
|
+
export declare function manualPatchRows(rows: readonly TakeoverRow[], action: TakeoverAction): readonly string[];
|
|
227
|
+
//# sourceMappingURL=takeover-plan.d.ts.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pluginManager.listPlugins()` 的答案 → 本插件认识的四行。
|
|
3
|
+
*
|
|
4
|
+
* 这一层唯一的职责是**认身份**,而身份在真机上是个坑:宿主行同时带 `entryId`(Loader 条目 id,
|
|
5
|
+
* 挂在 `include` 下的条目带前缀,例如 `include:fs-sandbox`)与 `patchId`(profile patch 的行 id,
|
|
6
|
+
* 例如 `fs-sandbox`)。用户 `cordis.patch.yml` 里写的是后者,`setPluginEnabled` 收的却是前者。
|
|
7
|
+
* 拿 `entryId` 去跟常量比,在真机上**永远匹配不上** —— 那正是这一层要挡住的事。
|
|
8
|
+
*
|
|
9
|
+
* 识别规则(按顺序,全部 fail closed):
|
|
10
|
+
*
|
|
11
|
+
* 1. **有 `patchId`**:必须是我们认识的四个之一,且 `moduleName` 与预期一致(防止别的插件复用了同一个
|
|
12
|
+
* 行 id);两行声称同一个 `patchId` → 有歧义,两行都不要。
|
|
13
|
+
* 2. **没有 `patchId`**(宿主只在不可改的行上省略它):只能靠 `moduleName` 认 —— 该模块名必须**恰好**
|
|
14
|
+
* 出现在一行上,出现两行或零行都不认。
|
|
15
|
+
* 3. 认不出来就是「缺行」,由上层报 `rows-missing` / `unsupported`,而不是猜一个。
|
|
16
|
+
*
|
|
17
|
+
* 纯逻辑、无 I/O(AGENTS.md 红线 2),因此可以离线逐字段断言。
|
|
18
|
+
*
|
|
19
|
+
* @module dsh-remote-workspace/app/takeover-rows
|
|
20
|
+
*/
|
|
21
|
+
import { type TakeoverRow } from './takeover-plan.ts';
|
|
22
|
+
/**
|
|
23
|
+
* 每个 patch 行应该 import 的模块说明符。
|
|
24
|
+
*
|
|
25
|
+
* 出处:核心两行在 bundle patch 里声明(`packages/bundle/base/cordis.patch.yml:518-519` 的
|
|
26
|
+
* `fs-sandbox`、`packages/bundle/web-app/cordis.patch.yml:97-98` 的 `file-reference-local`),
|
|
27
|
+
* 我们两行在本仓库的 `cordis.patch.yml:34-37`。
|
|
28
|
+
*/
|
|
29
|
+
export declare const EXPECTED_MODULE_NAMES: Readonly<Record<string, string>>;
|
|
30
|
+
/** 一行离开宿主后允许携带的字段(wire 形态,与 `GET` 的 `rows` 元素一一对应)。 */
|
|
31
|
+
export interface PublicTakeoverRow {
|
|
32
|
+
/** 规范身份:profile patch 的行 id,UI 显示这个。 */
|
|
33
|
+
readonly patchId: string;
|
|
34
|
+
/** Loader 条目的真实 id(`include:fs-sandbox`),只作诊断。 */
|
|
35
|
+
readonly loaderEntryId?: string;
|
|
36
|
+
readonly enabled: boolean;
|
|
37
|
+
readonly readOnlyReason?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 把宿主插件清单投影成四行。
|
|
41
|
+
*
|
|
42
|
+
* @param listed - `pluginManager.listPlugins()` 的返回值(形状未知,逐字段校验)。
|
|
43
|
+
* @returns 认出来的那几行,按 {@link TAKEOVER_PATCH_IDS} 的顺序;认不出的、歧义的都丢掉。
|
|
44
|
+
*/
|
|
45
|
+
export declare function readTakeoverRows(listed: unknown): readonly TakeoverRow[];
|
|
46
|
+
/**
|
|
47
|
+
* 一行的 wire 投影:规范 id + 可选诊断 id,`readOnlyReason` 缺省时不出现。
|
|
48
|
+
*
|
|
49
|
+
* `fiberPhase` 刻意不出去:客户端按 `enabled` 画开关,运行态差异由 `GET` 的 `reason` 说清楚。
|
|
50
|
+
*
|
|
51
|
+
* @param row - 内部行。
|
|
52
|
+
* @returns `{ patchId, loaderEntryId?, enabled, readOnlyReason? }`。
|
|
53
|
+
*/
|
|
54
|
+
export declare function publicTakeoverRow(row: TakeoverRow): PublicTakeoverRow;
|
|
55
|
+
//# sourceMappingURL=takeover-rows.d.ts.map
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport selection: which implementation serves a connection, and why.
|
|
3
|
+
*
|
|
4
|
+
* The decision is **probe-driven**, never platform-guessed (ADR-0006): a mux failure measured on
|
|
5
|
+
* this machine is remembered, and a later decision for the same connection changes accordingly.
|
|
6
|
+
* Everything observable about the decision is returned as a structured record so the connection
|
|
7
|
+
* UI can show *why* a connection is slower instead of hiding it.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-over-ssh/app/transport-selector
|
|
10
|
+
*/
|
|
11
|
+
import type { ConnectionProfile, TransportKind } from '../../domain/roots/connection.ts';
|
|
12
|
+
import type { MultiplexingCache } from '../../infra/transport/transport.ts';
|
|
13
|
+
/** Stable reasons a transport was chosen. */
|
|
14
|
+
export type TransportReason = 'explicit-ssh2' | 'explicit-alias' | 'explicit-agent' | 'agent-unavailable' | 'password-requires-ssh2' | 'no-multiplexing' | 'ssh2-unavailable' | 'alias-required' | 'default-alias';
|
|
15
|
+
/** A non-fatal loss of quality the user should know about. */
|
|
16
|
+
export interface TransportDegradation {
|
|
17
|
+
/** Stable code for the UI to branch on. */
|
|
18
|
+
readonly code: 'no-connection-reuse' | 'ssh2-unavailable' | 'alias-unavailable' | 'password-needs-key' | 'agent-unavailable';
|
|
19
|
+
/** Actionable, user-facing sentence. */
|
|
20
|
+
readonly hint: string;
|
|
21
|
+
}
|
|
22
|
+
/** The outcome of one selection. */
|
|
23
|
+
export interface TransportDecision {
|
|
24
|
+
readonly connectionId: string;
|
|
25
|
+
readonly kind: TransportKind;
|
|
26
|
+
readonly reason: TransportReason;
|
|
27
|
+
/** One sentence for the UI/status badge. */
|
|
28
|
+
readonly hint: string;
|
|
29
|
+
readonly degradations: readonly TransportDegradation[];
|
|
30
|
+
/** Whether commands can share one connection (`false` on Windows without ssh2, for example). */
|
|
31
|
+
readonly reusesConnection: boolean;
|
|
32
|
+
}
|
|
33
|
+
/** Whether the optional Tier 2 agent can serve this connection right now. */
|
|
34
|
+
export interface AgentAvailability {
|
|
35
|
+
/**
|
|
36
|
+
* `false` only when a previous ladder run already failed (the reason is then required).
|
|
37
|
+
*
|
|
38
|
+
* `true` and `undefined` both mean "the selector cannot know yet": an explicit agent choice is honoured
|
|
39
|
+
* optimistically and the async preflight downgrades it after running the ladder.
|
|
40
|
+
*/
|
|
41
|
+
readonly available: boolean;
|
|
42
|
+
/** Why it is unavailable (the ladder's reason), for the UI. */
|
|
43
|
+
readonly reason?: string;
|
|
44
|
+
/** One sentence for the status badge. */
|
|
45
|
+
readonly hint?: string;
|
|
46
|
+
}
|
|
47
|
+
/** Facts the selector needs. All of them are injected so this module stays testable offline. */
|
|
48
|
+
export interface TransportSelectionInput {
|
|
49
|
+
readonly profile: ConnectionProfile;
|
|
50
|
+
/** Whether the `ssh2` dependency is loadable in this build. */
|
|
51
|
+
readonly ssh2Available: boolean;
|
|
52
|
+
/** Remembers hosts whose `ssh` client cannot multiplex. */
|
|
53
|
+
readonly multiplexing: Pick<MultiplexingCache, 'isUnsupported' | 'reasonFor'>;
|
|
54
|
+
/** `ssh` executable name, for the multiplexing cache key. */
|
|
55
|
+
readonly sshBinary?: string;
|
|
56
|
+
/** Tier 2 availability. Absent means "not considered", which keeps the agent strictly opt-in. */
|
|
57
|
+
readonly agent?: AgentAvailability;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Choose a transport for one connection.
|
|
61
|
+
*
|
|
62
|
+
* Order of concerns: an explicit user choice wins unless it cannot work at all (password auth on
|
|
63
|
+
* the system client, a missing dependency); only then is another implementation substituted, and
|
|
64
|
+
* the substitution is always reported as a degradation.
|
|
65
|
+
*
|
|
66
|
+
* @param input - profile plus injected environment facts.
|
|
67
|
+
* @returns the decision, including every degradation.
|
|
68
|
+
*/
|
|
69
|
+
export declare function selectTransport(input: TransportSelectionInput): TransportDecision;
|
|
70
|
+
/** Records observed facts so later decisions reflect reality. */
|
|
71
|
+
export declare class TransportSelector {
|
|
72
|
+
#private;
|
|
73
|
+
constructor(options: {
|
|
74
|
+
readonly multiplexing: Pick<MultiplexingCache, 'isUnsupported' | 'reasonFor'> & Partial<Pick<MultiplexingCache, 'markUnsupported'>>;
|
|
75
|
+
readonly ssh2Available: boolean;
|
|
76
|
+
readonly sshBinary?: string;
|
|
77
|
+
});
|
|
78
|
+
/** Whether the `ssh2` dependency is currently loadable. */
|
|
79
|
+
get ssh2Available(): boolean;
|
|
80
|
+
/** Late-bound availability (the dependency is loaded lazily). */
|
|
81
|
+
setSsh2Available(available: boolean): void;
|
|
82
|
+
/**
|
|
83
|
+
* Decide for one profile.
|
|
84
|
+
*
|
|
85
|
+
* @param profile - the saved connection.
|
|
86
|
+
* @returns the decision.
|
|
87
|
+
* @throws SshDevError `CONNECTION_INVALID` when no implementation can serve the connection.
|
|
88
|
+
*/
|
|
89
|
+
decide(profile: ConnectionProfile): TransportDecision;
|
|
90
|
+
/**
|
|
91
|
+
* Record that this connection's `ssh` client could not multiplex.
|
|
92
|
+
*
|
|
93
|
+
* Called by the integration layer right after an {@link AliasTransport} connect attempt, so the
|
|
94
|
+
* next decision (and every other connection sharing the same client+alias) picks ssh2.
|
|
95
|
+
*/
|
|
96
|
+
noteMultiplexingFailure(profile: ConnectionProfile, reason: string): void;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=transport-selector.d.ts.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport lifecycle: acquire, reuse, replace and release one transport per connection.
|
|
3
|
+
*
|
|
4
|
+
* The service owns three behaviours the rest of the plugin relies on:
|
|
5
|
+
*
|
|
6
|
+
* 1. **One transport per connection**, created lazily and shared by every caller (single flight).
|
|
7
|
+
* 2. **Replacement on a changed decision**: when the selector's answer changes (for example after
|
|
8
|
+
* a mux failure is recorded), the old transport is disposed and a new one built, so a stale
|
|
9
|
+
* decision can never keep serving traffic.
|
|
10
|
+
* 3. **Explicit release**, so shutting a connection down closes its process/connection rather
|
|
11
|
+
* than leaving it to the garbage collector.
|
|
12
|
+
*
|
|
13
|
+
* The transport contract lives in `infra/transport/transport.ts` and is imported as a type only,
|
|
14
|
+
* which keeps this module free of implementation and of the core packages.
|
|
15
|
+
*
|
|
16
|
+
* @module dsh-over-ssh/app/transport-service
|
|
17
|
+
*/
|
|
18
|
+
import type { ConnectionProfile } from '../../domain/roots/connection.ts';
|
|
19
|
+
import type { Transport } from '../../infra/transport/transport.ts';
|
|
20
|
+
import type { TransportDecision } from './transport-selector.ts';
|
|
21
|
+
import type { TransportSelector } from './transport-selector.ts';
|
|
22
|
+
/** Creates the implementation chosen by the selector. */
|
|
23
|
+
export interface TransportFactories {
|
|
24
|
+
readonly createAlias: (profile: ConnectionProfile, decision: TransportDecision) => Transport;
|
|
25
|
+
readonly createSsh2: (profile: ConnectionProfile, decision: TransportDecision) => Transport;
|
|
26
|
+
/**
|
|
27
|
+
* Creates the Tier 2 agent transport. Optional: a build without the agent (or without a shipped binary)
|
|
28
|
+
* simply omits it, and the selector never chooses `agent` in that case.
|
|
29
|
+
*/
|
|
30
|
+
readonly createAgent?: (profile: ConnectionProfile, decision: TransportDecision) => Transport;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Optional asynchronous step between deciding and connecting.
|
|
34
|
+
*
|
|
35
|
+
* The synchronous selector cannot know whether the agent's install ladder succeeded — that requires
|
|
36
|
+
* talking to the host. A preflight therefore gets the first decision and may revise it (for example
|
|
37
|
+
* `agent` → `alias` once the ladder reported a reason), which is how "agent unavailable ⇒ fall back to
|
|
38
|
+
* Tier 1" is enforced *before* a transport is built.
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* Preflight hook: may replace the decision (e.g. "install the agent and use Tier 2 after all").
|
|
42
|
+
*
|
|
43
|
+
* `signal` is the caller's cancellation for the whole acquire. It must reach the hook: the ladder it runs
|
|
44
|
+
* (ssh connect, manifest load, `$HOME` probe, agent upload) can take minutes, and a cancelled call used to
|
|
45
|
+
* keep uploading anyway because only `transport.connect()` saw the signal.
|
|
46
|
+
*/
|
|
47
|
+
export type TransportPreflight = (profile: ConnectionProfile, decision: TransportDecision, signal?: AbortSignal) => Promise<TransportDecision>;
|
|
48
|
+
/** One acquired transport plus the decision that produced it. */
|
|
49
|
+
export interface AcquiredTransport {
|
|
50
|
+
readonly transport: Transport;
|
|
51
|
+
readonly decision: TransportDecision;
|
|
52
|
+
}
|
|
53
|
+
/** Facts injected for testability. */
|
|
54
|
+
export interface TransportServiceOptions {
|
|
55
|
+
readonly selector: TransportSelector;
|
|
56
|
+
readonly factories: TransportFactories;
|
|
57
|
+
/** See {@link TransportPreflight}. Omitted means "no revision". */
|
|
58
|
+
readonly preflight?: TransportPreflight;
|
|
59
|
+
}
|
|
60
|
+
/** Owns one transport instance per connection. */
|
|
61
|
+
export declare class TransportService {
|
|
62
|
+
#private;
|
|
63
|
+
constructor(options: TransportServiceOptions);
|
|
64
|
+
/**
|
|
65
|
+
* Get a connected transport for a profile, creating it if needed.
|
|
66
|
+
*
|
|
67
|
+
* @param profile - the connection to serve.
|
|
68
|
+
* @param signal - cancellation for the connect attempt.
|
|
69
|
+
* @returns the transport and the decision behind it (after any preflight revision).
|
|
70
|
+
* @throws SshDevError from the selector or from the transport's own connect.
|
|
71
|
+
*/
|
|
72
|
+
acquire(profile: ConnectionProfile, signal?: AbortSignal): Promise<AcquiredTransport>;
|
|
73
|
+
/** The cached transport for a connection, if any. */
|
|
74
|
+
current(connectionId: string): AcquiredTransport | undefined;
|
|
75
|
+
/** Decisions currently in force, for the connection status UI. */
|
|
76
|
+
decisions(): readonly TransportDecision[];
|
|
77
|
+
/** Dispose and forget one connection. */
|
|
78
|
+
release(connectionId: string): Promise<void>;
|
|
79
|
+
/** Dispose every connection (plugin unload, connection-service shutdown). */
|
|
80
|
+
disposeAll(): Promise<void>;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=transport-service.d.ts.map
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport selection: which implementation serves a connection, and why.
|
|
3
|
+
*
|
|
4
|
+
* The decision is **probe-driven**, never platform-guessed (ADR-0006): a mux failure measured on
|
|
5
|
+
* this machine is remembered, and a later decision for the same connection changes accordingly.
|
|
6
|
+
* Everything observable about the decision is returned as a structured record so the connection
|
|
7
|
+
* UI can show *why* a connection is slower instead of hiding it.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-remote-workspace/app/transport-selector
|
|
10
|
+
*/
|
|
11
|
+
import type { ConnectionProfile, TransportKind } from '../domain/connection.ts';
|
|
12
|
+
import type { MultiplexingCache } from '../infra/transport/transport.ts';
|
|
13
|
+
/** Stable reasons a transport was chosen. */
|
|
14
|
+
export type TransportReason = 'explicit-ssh2' | 'explicit-alias' | 'explicit-agent' | 'agent-unavailable' | 'password-requires-ssh2' | 'no-multiplexing' | 'ssh2-unavailable' | 'alias-required' | 'default-alias';
|
|
15
|
+
/** A non-fatal loss of quality the user should know about. */
|
|
16
|
+
export interface TransportDegradation {
|
|
17
|
+
/** Stable code for the UI to branch on. */
|
|
18
|
+
readonly code: 'no-connection-reuse' | 'ssh2-unavailable' | 'alias-unavailable' | 'password-needs-key' | 'agent-unavailable';
|
|
19
|
+
/** Actionable, user-facing sentence. */
|
|
20
|
+
readonly hint: string;
|
|
21
|
+
}
|
|
22
|
+
/** The outcome of one selection. */
|
|
23
|
+
export interface TransportDecision {
|
|
24
|
+
readonly connectionId: string;
|
|
25
|
+
readonly kind: TransportKind;
|
|
26
|
+
readonly reason: TransportReason;
|
|
27
|
+
/** One sentence for the UI/status badge. */
|
|
28
|
+
readonly hint: string;
|
|
29
|
+
readonly degradations: readonly TransportDegradation[];
|
|
30
|
+
/** Whether commands can share one connection (`false` on Windows without ssh2, for example). */
|
|
31
|
+
readonly reusesConnection: boolean;
|
|
32
|
+
}
|
|
33
|
+
/** Whether the optional Tier 2 agent can serve this connection right now. */
|
|
34
|
+
export interface AgentAvailability {
|
|
35
|
+
/**
|
|
36
|
+
* `false` only when a previous ladder run already failed (the reason is then required).
|
|
37
|
+
*
|
|
38
|
+
* `true` and `undefined` both mean "the selector cannot know yet": an explicit agent choice is honoured
|
|
39
|
+
* optimistically and the async preflight downgrades it after running the ladder.
|
|
40
|
+
*/
|
|
41
|
+
readonly available: boolean;
|
|
42
|
+
/** Why it is unavailable (the ladder's reason), for the UI. */
|
|
43
|
+
readonly reason?: string;
|
|
44
|
+
/** One sentence for the status badge. */
|
|
45
|
+
readonly hint?: string;
|
|
46
|
+
}
|
|
47
|
+
/** Facts the selector needs. All of them are injected so this module stays testable offline. */
|
|
48
|
+
export interface TransportSelectionInput {
|
|
49
|
+
readonly profile: ConnectionProfile;
|
|
50
|
+
/** Whether the `ssh2` dependency is loadable in this build. */
|
|
51
|
+
readonly ssh2Available: boolean;
|
|
52
|
+
/** Remembers hosts whose `ssh` client cannot multiplex. */
|
|
53
|
+
readonly multiplexing: Pick<MultiplexingCache, 'isUnsupported' | 'reasonFor'>;
|
|
54
|
+
/** `ssh` executable name, for the multiplexing cache key. */
|
|
55
|
+
readonly sshBinary?: string;
|
|
56
|
+
/** Tier 2 availability. Absent means "not considered", which keeps the agent strictly opt-in. */
|
|
57
|
+
readonly agent?: AgentAvailability;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Choose a transport for one connection.
|
|
61
|
+
*
|
|
62
|
+
* Order of concerns: an explicit user choice wins unless it cannot work at all (password auth on
|
|
63
|
+
* the system client, a missing dependency); only then is another implementation substituted, and
|
|
64
|
+
* the substitution is always reported as a degradation.
|
|
65
|
+
*
|
|
66
|
+
* @param input - profile plus injected environment facts.
|
|
67
|
+
* @returns the decision, including every degradation.
|
|
68
|
+
*/
|
|
69
|
+
export declare function selectTransport(input: TransportSelectionInput): TransportDecision;
|
|
70
|
+
/** Records observed facts so later decisions reflect reality. */
|
|
71
|
+
export declare class TransportSelector {
|
|
72
|
+
#private;
|
|
73
|
+
constructor(options: {
|
|
74
|
+
readonly multiplexing: Pick<MultiplexingCache, 'isUnsupported' | 'reasonFor'> & Partial<Pick<MultiplexingCache, 'markUnsupported'>>;
|
|
75
|
+
readonly ssh2Available: boolean;
|
|
76
|
+
readonly sshBinary?: string;
|
|
77
|
+
});
|
|
78
|
+
/** Whether the `ssh2` dependency is currently loadable. */
|
|
79
|
+
get ssh2Available(): boolean;
|
|
80
|
+
/** Late-bound availability (the dependency is loaded lazily). */
|
|
81
|
+
setSsh2Available(available: boolean): void;
|
|
82
|
+
/**
|
|
83
|
+
* Decide for one profile.
|
|
84
|
+
*
|
|
85
|
+
* @param profile - the saved connection.
|
|
86
|
+
* @returns the decision.
|
|
87
|
+
* @throws SshDevError `CONNECTION_INVALID` when no implementation can serve the connection.
|
|
88
|
+
*/
|
|
89
|
+
decide(profile: ConnectionProfile): TransportDecision;
|
|
90
|
+
/**
|
|
91
|
+
* Record that this connection's `ssh` client could not multiplex.
|
|
92
|
+
*
|
|
93
|
+
* Called by the integration layer right after an {@link AliasTransport} connect attempt, so the
|
|
94
|
+
* next decision (and every other connection sharing the same client+alias) picks ssh2.
|
|
95
|
+
*/
|
|
96
|
+
noteMultiplexingFailure(profile: ConnectionProfile, reason: string): void;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=transport-selector.d.ts.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport lifecycle: acquire, reuse, replace and release one transport per connection.
|
|
3
|
+
*
|
|
4
|
+
* The service owns three behaviours the rest of the plugin relies on:
|
|
5
|
+
*
|
|
6
|
+
* 1. **One transport per connection**, created lazily and shared by every caller (single flight).
|
|
7
|
+
* 2. **Replacement on a changed decision**: when the selector's answer changes (for example after
|
|
8
|
+
* a mux failure is recorded), the old transport is disposed and a new one built, so a stale
|
|
9
|
+
* decision can never keep serving traffic.
|
|
10
|
+
* 3. **Explicit release**, so shutting a connection down closes its process/connection rather
|
|
11
|
+
* than leaving it to the garbage collector.
|
|
12
|
+
*
|
|
13
|
+
* The transport contract lives in `infra/transport/transport.ts` and is imported as a type only,
|
|
14
|
+
* which keeps this module free of implementation and of the core packages.
|
|
15
|
+
*
|
|
16
|
+
* @module dsh-remote-workspace/app/transport-service
|
|
17
|
+
*/
|
|
18
|
+
import type { ConnectionProfile } from '../domain/connection.ts';
|
|
19
|
+
import type { Transport } from '../infra/transport/transport.ts';
|
|
20
|
+
import type { TransportDecision } from './transport-selector.ts';
|
|
21
|
+
import type { TransportSelector } from './transport-selector.ts';
|
|
22
|
+
/** Creates the implementation chosen by the selector. */
|
|
23
|
+
export interface TransportFactories {
|
|
24
|
+
readonly createAlias: (profile: ConnectionProfile, decision: TransportDecision) => Transport;
|
|
25
|
+
readonly createSsh2: (profile: ConnectionProfile, decision: TransportDecision) => Transport;
|
|
26
|
+
/**
|
|
27
|
+
* Creates the Tier 2 agent transport. Optional: a build without the agent (or without a shipped binary)
|
|
28
|
+
* simply omits it, and the selector never chooses `agent` in that case.
|
|
29
|
+
*/
|
|
30
|
+
readonly createAgent?: (profile: ConnectionProfile, decision: TransportDecision) => Transport;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Optional asynchronous step between deciding and connecting.
|
|
34
|
+
*
|
|
35
|
+
* The synchronous selector cannot know whether the agent's install ladder succeeded — that requires
|
|
36
|
+
* talking to the host. A preflight therefore gets the first decision and may revise it (for example
|
|
37
|
+
* `agent` → `alias` once the ladder reported a reason), which is how "agent unavailable ⇒ fall back to
|
|
38
|
+
* Tier 1" is enforced *before* a transport is built.
|
|
39
|
+
*/
|
|
40
|
+
export type TransportPreflight = (profile: ConnectionProfile, decision: TransportDecision) => Promise<TransportDecision>;
|
|
41
|
+
/** One acquired transport plus the decision that produced it. */
|
|
42
|
+
export interface AcquiredTransport {
|
|
43
|
+
readonly transport: Transport;
|
|
44
|
+
readonly decision: TransportDecision;
|
|
45
|
+
}
|
|
46
|
+
/** Facts injected for testability. */
|
|
47
|
+
export interface TransportServiceOptions {
|
|
48
|
+
readonly selector: TransportSelector;
|
|
49
|
+
readonly factories: TransportFactories;
|
|
50
|
+
/** See {@link TransportPreflight}. Omitted means "no revision". */
|
|
51
|
+
readonly preflight?: TransportPreflight;
|
|
52
|
+
}
|
|
53
|
+
/** Owns one transport instance per connection. */
|
|
54
|
+
export declare class TransportService {
|
|
55
|
+
#private;
|
|
56
|
+
constructor(options: TransportServiceOptions);
|
|
57
|
+
/**
|
|
58
|
+
* Get a connected transport for a profile, creating it if needed.
|
|
59
|
+
*
|
|
60
|
+
* @param profile - the connection to serve.
|
|
61
|
+
* @param signal - cancellation for the connect attempt.
|
|
62
|
+
* @returns the transport and the decision behind it (after any preflight revision).
|
|
63
|
+
* @throws SshDevError from the selector or from the transport's own connect.
|
|
64
|
+
*/
|
|
65
|
+
acquire(profile: ConnectionProfile, signal?: AbortSignal): Promise<AcquiredTransport>;
|
|
66
|
+
/** The cached transport for a connection, if any. */
|
|
67
|
+
current(connectionId: string): AcquiredTransport | undefined;
|
|
68
|
+
/** Decisions currently in force, for the connection status UI. */
|
|
69
|
+
decisions(): readonly TransportDecision[];
|
|
70
|
+
/** Dispose and forget one connection. */
|
|
71
|
+
release(connectionId: string): Promise<void>;
|
|
72
|
+
/** Dispose every connection (plugin unload, connection-service shutdown). */
|
|
73
|
+
disposeAll(): Promise<void>;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=transport-service.d.ts.map
|