dsh-over-ssh 0.1.0 → 0.2.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/README.md +19 -17
- package/agents/agent-manifest.json +6 -6
- package/agents/linux-amd64/dsh-over-ssh-agent +0 -0
- package/agents/linux-arm64/dsh-over-ssh-agent +0 -0
- package/lib/client.js +422 -337
- package/lib/file-references.js +92 -35
- package/lib/fs.js +295 -113
- package/lib/index.js +6912 -4330
- package/lib/terminal-wrapper.js +9 -4
- package/lib/types/src/config.d.ts +156 -9
- package/lib/types/src/file-references.d.ts +0 -1
- package/lib/types/src/host/app/backend.d.ts +44 -3
- package/lib/types/src/host/app/recycle-bin/recycle-bin-contract.d.ts +0 -1
- package/lib/types/src/host/app/recycle-bin/recycle-bin-service.d.ts +0 -1
- package/lib/types/src/host/app/session-admin/session-admin-contract.d.ts +12 -1
- package/lib/types/src/host/app/session-admin/session-admin-service.d.ts +0 -1
- package/lib/types/src/host/app/transport/transport-selector.d.ts +0 -1
- package/lib/types/src/host/app/transport/transport-service.d.ts +0 -1
- package/lib/types/src/host/domain/capability.d.ts +0 -1
- package/lib/types/src/host/domain/errors.d.ts +0 -1
- package/lib/types/src/host/domain/roots/connection.d.ts +0 -1
- package/lib/types/src/host/domain/roots/root.d.ts +24 -9
- package/lib/types/src/host/domain/roots/session-roots.d.ts +23 -1
- package/lib/types/src/host/domain/roots/ssh-config.d.ts +0 -1
- package/lib/types/src/host/domain/session/session-admin.d.ts +0 -1
- package/lib/types/src/host/domain/transfer/recycle-bin.d.ts +0 -1
- package/lib/types/src/host/domain/transfer/transfer.d.ts +0 -1
- package/lib/types/src/host/infra/transport/agent/agent-installer.d.ts +0 -1
- package/lib/types/src/host/infra/transport/forward.d.ts +75 -0
- package/lib/types/src/host/infra/transport/ssh/ssh2-transport.d.ts +5 -1
- package/lib/types/src/host/infra/transport/transport-base.d.ts +0 -1
- package/lib/types/src/host/infra/transport/transport.d.ts +42 -2
- package/lib/types/src/host/integration/agent/agent-preflight.d.ts +0 -1
- package/lib/types/src/host/integration/fs/file-reference-provider.d.ts +0 -1
- package/lib/types/src/host/integration/fs/fs-provider.d.ts +18 -3
- package/lib/types/src/host/integration/transfer/transfer-service.d.ts +0 -1
- package/lib/types/src/index.d.ts +23 -1
- package/lib/types/src/local-bridge-refs.d.ts +0 -1
- package/lib/types/src/local-bridge.d.ts +0 -1
- package/lib/types/src/shared/root-keys.d.ts +120 -0
- package/lib/types/src/wiring/helpers.d.ts +0 -1
- package/package.json +5 -3
- package/lib/types/src/client/controls.d.ts +0 -131
- package/lib/types/src/client/core/card-focus-trap.d.ts +0 -45
- package/lib/types/src/client/core/controls.d.ts +0 -164
- package/lib/types/src/client/core/latest.d.ts +0 -48
- package/lib/types/src/client/core/locale.d.ts +0 -38
- package/lib/types/src/client/core/message.d.ts +0 -37
- package/lib/types/src/client/core/panel-model.d.ts +0 -134
- package/lib/types/src/client/core/portal.d.ts +0 -266
- package/lib/types/src/client/core/remote.d.ts +0 -84
- package/lib/types/src/client/core/styles.d.ts +0 -458
- package/lib/types/src/client/file-tree-io.d.ts +0 -19
- package/lib/types/src/client/file-tree.d.ts +0 -271
- package/lib/types/src/client/flows/directory-browser.d.ts +0 -587
- package/lib/types/src/client/flows/session-directory.d.ts +0 -415
- package/lib/types/src/client/flows/workspace-directory-flow.d.ts +0 -480
- package/lib/types/src/client/locale.d.ts +0 -31
- package/lib/types/src/client/panel-model.d.ts +0 -37
- package/lib/types/src/client/panel.d.ts +0 -77
- package/lib/types/src/client/roots/connection-test.d.ts +0 -69
- package/lib/types/src/client/roots/roots-panel-state.d.ts +0 -307
- package/lib/types/src/client/roots/roots-panel-view.d.ts +0 -280
- package/lib/types/src/client/roots/roots-panel.d.ts +0 -364
- package/lib/types/src/client/roots/transfer-panel-view.d.ts +0 -35
- package/lib/types/src/client/roots/transfer-panel.d.ts +0 -241
- package/lib/types/src/client/roots/transfer-progress.d.ts +0 -67
- package/lib/types/src/client/roots-panel-view.d.ts +0 -236
- package/lib/types/src/client/roots-panel.d.ts +0 -347
- package/lib/types/src/client/session/session-panel-view.d.ts +0 -336
- package/lib/types/src/client/session/session-panel.d.ts +0 -322
- package/lib/types/src/client/session/session-remote-mark.d.ts +0 -110
- package/lib/types/src/client/session/session-row-actions.d.ts +0 -398
- package/lib/types/src/client/session-panel.d.ts +0 -152
- package/lib/types/src/client/styles.d.ts +0 -252
- package/lib/types/src/client/workspace-directory-flow.d.ts +0 -342
- package/lib/types/src/fs.d.ts +0 -46
- package/lib/types/src/host/app/recycle-bin/recycle-bin-move.d.ts +0 -18
- package/lib/types/src/host/app/recycle-bin/recycle-bin-world.d.ts +0 -38
- package/lib/types/src/host/app/recycle-bin-contract.d.ts +0 -215
- package/lib/types/src/host/app/recycle-bin-move.d.ts +0 -18
- package/lib/types/src/host/app/recycle-bin-service.d.ts +0 -45
- package/lib/types/src/host/app/recycle-bin-world.d.ts +0 -35
- package/lib/types/src/host/app/roots/roots-api-connections.d.ts +0 -49
- package/lib/types/src/host/app/roots/roots-api-contract.d.ts +0 -369
- package/lib/types/src/host/app/roots/roots-api-directories.d.ts +0 -13
- package/lib/types/src/host/app/roots/roots-api-http.d.ts +0 -77
- package/lib/types/src/host/app/roots/roots-api-long-poll.d.ts +0 -130
- package/lib/types/src/host/app/roots/roots-api-projections.d.ts +0 -102
- package/lib/types/src/host/app/roots/roots-api-roots.d.ts +0 -13
- package/lib/types/src/host/app/roots/roots-api-state.d.ts +0 -22
- package/lib/types/src/host/app/roots/roots-api-transfers.d.ts +0 -26
- package/lib/types/src/host/app/roots/roots-api.d.ts +0 -55
- package/lib/types/src/host/app/roots/user-roots.d.ts +0 -552
- package/lib/types/src/host/app/roots-api-connections.d.ts +0 -49
- package/lib/types/src/host/app/roots-api-contract.d.ts +0 -339
- package/lib/types/src/host/app/roots-api-directories.d.ts +0 -13
- package/lib/types/src/host/app/roots-api-http.d.ts +0 -77
- package/lib/types/src/host/app/roots-api-projections.d.ts +0 -102
- package/lib/types/src/host/app/roots-api-roots.d.ts +0 -13
- package/lib/types/src/host/app/roots-api-state.d.ts +0 -22
- package/lib/types/src/host/app/roots-api-support.d.ts +0 -182
- package/lib/types/src/host/app/roots-api-transfers.d.ts +0 -13
- package/lib/types/src/host/app/roots-api.d.ts +0 -55
- package/lib/types/src/host/app/routed-fs.d.ts +0 -239
- package/lib/types/src/host/app/serialize.d.ts +0 -37
- package/lib/types/src/host/app/session-admin/session-admin-context.d.ts +0 -36
- package/lib/types/src/host/app/session-admin/session-admin-delete.d.ts +0 -34
- package/lib/types/src/host/app/session-admin-context.d.ts +0 -36
- package/lib/types/src/host/app/session-admin-contract.d.ts +0 -214
- package/lib/types/src/host/app/session-admin-delete.d.ts +0 -34
- package/lib/types/src/host/app/session-admin-service.d.ts +0 -38
- package/lib/types/src/host/app/takeover/takeover-plan.d.ts +0 -227
- package/lib/types/src/host/app/takeover/takeover-rows.d.ts +0 -55
- package/lib/types/src/host/app/transport-selector.d.ts +0 -98
- package/lib/types/src/host/app/transport-service.d.ts +0 -75
- package/lib/types/src/host/app/user-roots.d.ts +0 -461
- package/lib/types/src/host/domain/bin-location.d.ts +0 -79
- package/lib/types/src/host/domain/connection.d.ts +0 -95
- package/lib/types/src/host/domain/glob-pattern.d.ts +0 -62
- package/lib/types/src/host/domain/literal-text.d.ts +0 -63
- package/lib/types/src/host/domain/path-grammar.d.ts +0 -84
- package/lib/types/src/host/domain/recycle-bin.d.ts +0 -337
- package/lib/types/src/host/domain/root.d.ts +0 -74
- package/lib/types/src/host/domain/roots/bin-location.d.ts +0 -91
- package/lib/types/src/host/domain/roots/route-table.d.ts +0 -102
- package/lib/types/src/host/domain/route-table.d.ts +0 -80
- package/lib/types/src/host/domain/session/terminal-target.d.ts +0 -136
- package/lib/types/src/host/domain/session-admin.d.ts +0 -330
- package/lib/types/src/host/domain/shell.d.ts +0 -47
- package/lib/types/src/host/domain/ssh-config.d.ts +0 -73
- package/lib/types/src/host/domain/terminal-target.d.ts +0 -132
- package/lib/types/src/host/domain/tool-requests.d.ts +0 -369
- package/lib/types/src/host/domain/transfer.d.ts +0 -260
- package/lib/types/src/host/infra/backend/local-backend.d.ts +0 -84
- package/lib/types/src/host/infra/backend/local-version.d.ts +0 -43
- package/lib/types/src/host/infra/backend/platform-local-backend.d.ts +0 -89
- package/lib/types/src/host/infra/backend/remote-backend.d.ts +0 -239
- package/lib/types/src/host/infra/cache/lru.d.ts +0 -52
- package/lib/types/src/host/infra/cache/versioned.d.ts +0 -77
- package/lib/types/src/host/infra/temp-name.d.ts +0 -19
- package/lib/types/src/host/infra/transport/agent/agent-protocol-client.d.ts +0 -132
- package/lib/types/src/host/infra/transport/agent/agent-protocol-codec.d.ts +0 -140
- package/lib/types/src/host/infra/transport/agent/agent-protocol-errors.d.ts +0 -41
- package/lib/types/src/host/infra/transport/agent/agent-protocol-frames.d.ts +0 -197
- package/lib/types/src/host/infra/transport/agent/agent-protocol-types.d.ts +0 -564
- package/lib/types/src/host/infra/transport/agent/agent-transport.d.ts +0 -418
- package/lib/types/src/host/infra/transport/agent-installer.d.ts +0 -121
- package/lib/types/src/host/infra/transport/agent-protocol-client.d.ts +0 -123
- package/lib/types/src/host/infra/transport/agent-protocol-codec.d.ts +0 -131
- package/lib/types/src/host/infra/transport/agent-protocol-errors.d.ts +0 -41
- package/lib/types/src/host/infra/transport/agent-protocol-frames.d.ts +0 -197
- package/lib/types/src/host/infra/transport/agent-protocol-types.d.ts +0 -509
- package/lib/types/src/host/infra/transport/agent-transport.d.ts +0 -353
- package/lib/types/src/host/infra/transport/alias-transport.d.ts +0 -206
- package/lib/types/src/host/infra/transport/askpass-fs.d.ts +0 -20
- package/lib/types/src/host/infra/transport/askpass.d.ts +0 -133
- package/lib/types/src/host/infra/transport/command-pool.d.ts +0 -61
- package/lib/types/src/host/infra/transport/fs-commands.d.ts +0 -140
- package/lib/types/src/host/infra/transport/git-status-route.d.ts +0 -63
- package/lib/types/src/host/infra/transport/git-status.d.ts +0 -101
- package/lib/types/src/host/infra/transport/node-spawn.d.ts +0 -26
- package/lib/types/src/host/infra/transport/session-window.d.ts +0 -51
- package/lib/types/src/host/infra/transport/shell-quote.d.ts +0 -59
- package/lib/types/src/host/infra/transport/ssh/alias-transport.d.ts +0 -206
- package/lib/types/src/host/infra/transport/ssh/askpass-fs.d.ts +0 -20
- package/lib/types/src/host/infra/transport/ssh/askpass.d.ts +0 -133
- package/lib/types/src/host/infra/transport/ssh/shell-quote.d.ts +0 -62
- package/lib/types/src/host/infra/transport/ssh/ssh-argv.d.ts +0 -43
- package/lib/types/src/host/infra/transport/ssh-argv.d.ts +0 -43
- package/lib/types/src/host/infra/transport/ssh2-transport.d.ts +0 -302
- package/lib/types/src/host/integration/agent/agent-install-io.d.ts +0 -33
- package/lib/types/src/host/integration/agent/askpass-registry.d.ts +0 -66
- package/lib/types/src/host/integration/agent-install-io.d.ts +0 -33
- package/lib/types/src/host/integration/agent-preflight.d.ts +0 -56
- package/lib/types/src/host/integration/anchor-directory.d.ts +0 -56
- package/lib/types/src/host/integration/askpass-registry.d.ts +0 -59
- package/lib/types/src/host/integration/bin-root-resolver.d.ts +0 -81
- package/lib/types/src/host/integration/connection-test.d.ts +0 -73
- package/lib/types/src/host/integration/file-reference-provider.d.ts +0 -115
- package/lib/types/src/host/integration/fs/git-status-wiring.d.ts +0 -21
- package/lib/types/src/host/integration/fs/platform-errors.d.ts +0 -94
- package/lib/types/src/host/integration/fs/remote-file-tools.d.ts +0 -369
- package/lib/types/src/host/integration/fs/remote-index.d.ts +0 -177
- package/lib/types/src/host/integration/fs-provider.d.ts +0 -226
- package/lib/types/src/host/integration/lsp/lsp-framing.d.ts +0 -38
- package/lib/types/src/host/integration/lsp/lsp-registry.d.ts +0 -67
- package/lib/types/src/host/integration/lsp/lsp-session.d.ts +0 -130
- package/lib/types/src/host/integration/management-tools.d.ts +0 -64
- package/lib/types/src/host/integration/recycle-bin-service.d.ts +0 -247
- package/lib/types/src/host/integration/remote/connection-test.d.ts +0 -73
- package/lib/types/src/host/integration/remote/remote-face.d.ts +0 -127
- package/lib/types/src/host/integration/remote/remote-tool-backend.d.ts +0 -108
- package/lib/types/src/host/integration/remote-face.d.ts +0 -127
- package/lib/types/src/host/integration/remote-file-tools.d.ts +0 -241
- package/lib/types/src/host/integration/remote-home.d.ts +0 -69
- package/lib/types/src/host/integration/remote-index.d.ts +0 -165
- package/lib/types/src/host/integration/remote-job-tools.d.ts +0 -295
- package/lib/types/src/host/integration/remote-tool-backend.d.ts +0 -97
- package/lib/types/src/host/integration/remote-tools.d.ts +0 -206
- package/lib/types/src/host/integration/root-registry.d.ts +0 -115
- package/lib/types/src/host/integration/roots/anchor-directory.d.ts +0 -56
- package/lib/types/src/host/integration/roots/bin-root-resolver.d.ts +0 -81
- package/lib/types/src/host/integration/roots/remote-home.d.ts +0 -69
- package/lib/types/src/host/integration/roots/root-registry.d.ts +0 -144
- package/lib/types/src/host/integration/roots/roots-wiring.d.ts +0 -219
- package/lib/types/src/host/integration/roots/session-root-index.d.ts +0 -52
- package/lib/types/src/host/integration/roots-api.d.ts +0 -407
- package/lib/types/src/host/integration/roots-wiring.d.ts +0 -211
- package/lib/types/src/host/integration/session/session-api.d.ts +0 -88
- package/lib/types/src/host/integration/session/session-bin-lock.d.ts +0 -80
- package/lib/types/src/host/integration/session/session-header.d.ts +0 -151
- package/lib/types/src/host/integration/session/session-labels.d.ts +0 -56
- package/lib/types/src/host/integration/session/session-route-wiring.d.ts +0 -50
- package/lib/types/src/host/integration/session-admin-service.d.ts +0 -236
- package/lib/types/src/host/integration/session-api.d.ts +0 -81
- package/lib/types/src/host/integration/session-bin-lock.d.ts +0 -80
- package/lib/types/src/host/integration/session-header.d.ts +0 -118
- package/lib/types/src/host/integration/session-labels.d.ts +0 -56
- package/lib/types/src/host/integration/session-route-wiring.d.ts +0 -50
- package/lib/types/src/host/integration/session-scope.d.ts +0 -113
- package/lib/types/src/host/integration/session-tools.d.ts +0 -44
- package/lib/types/src/host/integration/strict-arguments.d.ts +0 -79
- package/lib/types/src/host/integration/takeover/takeover-api.d.ts +0 -62
- package/lib/types/src/host/integration/takeover/takeover-route-wiring.d.ts +0 -60
- package/lib/types/src/host/integration/takeover/takeover-service.d.ts +0 -167
- package/lib/types/src/host/integration/terminal-manifest.d.ts +0 -94
- package/lib/types/src/host/integration/tools/management-tools.d.ts +0 -245
- package/lib/types/src/host/integration/tools/remote-job-tools.d.ts +0 -316
- package/lib/types/src/host/integration/tools/remote-lsp-tools.d.ts +0 -43
- package/lib/types/src/host/integration/tools/remote-tools.d.ts +0 -236
- package/lib/types/src/host/integration/tools/session-target.d.ts +0 -52
- package/lib/types/src/host/integration/tools/session-tools.d.ts +0 -46
- package/lib/types/src/host/integration/tools/strict-arguments.d.ts +0 -79
- package/lib/types/src/host/integration/tools/terminal-manifest.d.ts +0 -94
- package/lib/types/src/host/integration/transfer/remote-copy.d.ts +0 -66
- package/lib/types/src/host/integration/transfer/transfer-adapters.d.ts +0 -48
- package/lib/types/src/host/integration/transfer/transfer-tracker.d.ts +0 -106
- package/lib/types/src/host/integration/transfer-adapters.d.ts +0 -44
- package/lib/types/src/host/integration/transfer-service.d.ts +0 -164
- package/lib/types/src/host/integration/transfer-tracker.d.ts +0 -106
- package/lib/types/src/host/integration/user-roots.d.ts +0 -461
- package/lib/types/src/wiring/connections.d.ts +0 -189
- package/lib/types/src/wiring/fs.d.ts +0 -13
- package/lib/types/src/wiring/install-memo.d.ts +0 -32
- package/lib/types/src/wiring/legacy-dirs.d.ts +0 -62
- package/lib/types/src/wiring/package-paths.d.ts +0 -35
- package/lib/types/src/wiring/roots.d.ts +0 -21
- package/lib/types/src/wiring/state-dir.d.ts +0 -29
- package/lib/types/src/wiring/tools.d.ts +0 -35
package/lib/fs.js
CHANGED
|
@@ -128,6 +128,33 @@ var LruCache = class {
|
|
|
128
128
|
this.#bytes -= entry.bytes;
|
|
129
129
|
return true;
|
|
130
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Remove every entry whose key starts with `prefix`, in one pass over the internal map.
|
|
133
|
+
*
|
|
134
|
+
* 存在的理由:调用方(`VersionedCache.invalidatePrefix`)在**每次写**之后都要按前缀失效,而在此之前
|
|
135
|
+
* 它只有 `keys()` 可用——`keys()` 会把整张表**物化**成一个数组,随后又用 `delete(key)` 逐个做哈希
|
|
136
|
+
* 查找。这里直接在 `#entries` 上过一遍:不建中间数组,字节记账就地扣减,边迭代边删除(Map 迭代器
|
|
137
|
+
* 规范保证删除当前条目后仍会访问后续条目)。
|
|
138
|
+
*
|
|
139
|
+
* **为什么是"前缀"而不是更通用的"谓词"**:谓词要为每条 key 付一次 JS 回调,而一次 `keys()` 物化只是
|
|
140
|
+
* 一次指针批量拷贝。实测(20 000 条 × 2 000 次,只命中 1 条)旧写法 1 289 ms、前缀版 962 ms、
|
|
141
|
+
* 谓词版 953 ms——两者同量级;但在**生产上限**(读缓存 256 条 / 目录缓存 512 条)下谓词版反而比旧
|
|
142
|
+
* 写法慢(20 000 次:160 ms vs 148 ms),前缀版打平(147 ms)。也就是说:谓词版只在大表上不亏,
|
|
143
|
+
* 在小表上亏;前缀版两边都不亏。热路径上的方法按真实规模选形状。
|
|
144
|
+
*
|
|
145
|
+
* @param prefix - key prefix.
|
|
146
|
+
* @returns the number of removed entries.
|
|
147
|
+
*/
|
|
148
|
+
deleteByPrefix(prefix) {
|
|
149
|
+
let removed = 0;
|
|
150
|
+
for (const [key, entry] of this.#entries) {
|
|
151
|
+
if (!key.startsWith(prefix)) continue;
|
|
152
|
+
this.#entries.delete(key);
|
|
153
|
+
this.#bytes -= entry.bytes;
|
|
154
|
+
removed++;
|
|
155
|
+
}
|
|
156
|
+
return removed;
|
|
157
|
+
}
|
|
131
158
|
/** Remove everything. */
|
|
132
159
|
clear() {
|
|
133
160
|
this.#entries.clear();
|
|
@@ -148,16 +175,38 @@ var LruCache = class {
|
|
|
148
175
|
rejectedOversize: this.#rejectedOversize
|
|
149
176
|
};
|
|
150
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Drop the least-recently-used entry.
|
|
180
|
+
*
|
|
181
|
+
* Exposed so a shared {@link CacheBudget} can make this cache give bytes back; `#evictToBounds` is the
|
|
182
|
+
* same operation driven by this cache's own limits.
|
|
183
|
+
*
|
|
184
|
+
* @returns `false` when the cache is empty (the caller must not retry in a loop).
|
|
185
|
+
*/
|
|
186
|
+
evictOldest() {
|
|
187
|
+
const oldest = this.#entries.keys().next();
|
|
188
|
+
if (oldest.done === true) return false;
|
|
189
|
+
const entry = this.#entries.get(oldest.value);
|
|
190
|
+
if (entry === void 0) return false;
|
|
191
|
+
this.#entries.delete(oldest.value);
|
|
192
|
+
this.#bytes -= entry.bytes;
|
|
193
|
+
this.#evictions++;
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Come down to `maxBytes` by dropping the oldest entries.
|
|
198
|
+
*
|
|
199
|
+
* @param maxBytes - the ceiling to reach.
|
|
200
|
+
* @returns how many entries were dropped.
|
|
201
|
+
*/
|
|
202
|
+
trimTo(maxBytes) {
|
|
203
|
+
let dropped = 0;
|
|
204
|
+
while (this.#bytes > maxBytes && this.evictOldest()) dropped++;
|
|
205
|
+
return dropped;
|
|
206
|
+
}
|
|
151
207
|
#evictToBounds() {
|
|
152
208
|
while (this.#entries.size > this.#maxEntries || this.#bytes > this.#maxBytes) {
|
|
153
|
-
|
|
154
|
-
if (oldest.done === true) return;
|
|
155
|
-
const key = oldest.value;
|
|
156
|
-
const entry = this.#entries.get(key);
|
|
157
|
-
if (entry === void 0) return;
|
|
158
|
-
this.#entries.delete(key);
|
|
159
|
-
this.#bytes -= entry.bytes;
|
|
160
|
-
this.#evictions++;
|
|
209
|
+
if (!this.evictOldest()) return;
|
|
161
210
|
}
|
|
162
211
|
}
|
|
163
212
|
};
|
|
@@ -167,6 +216,7 @@ var VersionedCache = class {
|
|
|
167
216
|
#ttlMs;
|
|
168
217
|
#now;
|
|
169
218
|
#lru;
|
|
219
|
+
#budget;
|
|
170
220
|
#hits = 0;
|
|
171
221
|
#misses = 0;
|
|
172
222
|
#freshHits = 0;
|
|
@@ -177,6 +227,29 @@ var VersionedCache = class {
|
|
|
177
227
|
this.#ttlMs = options.ttlMs;
|
|
178
228
|
this.#now = options.now ?? Date.now;
|
|
179
229
|
this.#lru = new LruCache({ maxEntries: options.maxEntries, maxBytes: options.maxBytes }, (entry) => sizeOf(entry.value));
|
|
230
|
+
this.#budget = options.budget;
|
|
231
|
+
this.#budget?.join(this);
|
|
232
|
+
}
|
|
233
|
+
/** Bytes this cache holds (the budget member contract). */
|
|
234
|
+
get bytes() {
|
|
235
|
+
return this.#lru.stats.bytes;
|
|
236
|
+
}
|
|
237
|
+
/** Drop oldest entries down to `maxBytes` (the budget member contract). */
|
|
238
|
+
trimTo(maxBytes) {
|
|
239
|
+
return this.#lru.trimTo(maxBytes);
|
|
240
|
+
}
|
|
241
|
+
/** Drop the oldest entry (the budget member contract). */
|
|
242
|
+
evictOldest() {
|
|
243
|
+
return this.#lru.evictOldest();
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Leave the shared budget, so a retired cache stops counting against the total.
|
|
247
|
+
*
|
|
248
|
+
* Called by whoever owns the cache when its root goes away. Without it a removed root's cache stayed in the
|
|
249
|
+
* accounting forever, which is the leak the budget exists to prevent.
|
|
250
|
+
*/
|
|
251
|
+
leaveBudget() {
|
|
252
|
+
this.#budget?.leave(this);
|
|
180
253
|
}
|
|
181
254
|
/**
|
|
182
255
|
* Look up an entry.
|
|
@@ -199,7 +272,9 @@ var VersionedCache = class {
|
|
|
199
272
|
}
|
|
200
273
|
/** Insert or replace an entry, stamping it as just-read. */
|
|
201
274
|
set(key, value, version) {
|
|
202
|
-
|
|
275
|
+
const stored = this.#lru.set(key, { value, version, storedAt: this.#now() });
|
|
276
|
+
if (stored) this.#budget?.enforce(this);
|
|
277
|
+
return stored;
|
|
203
278
|
}
|
|
204
279
|
/**
|
|
205
280
|
* Refresh an entry's timestamp after a cheap remote version check matched.
|
|
@@ -226,11 +301,7 @@ var VersionedCache = class {
|
|
|
226
301
|
* @returns the number of removed entries.
|
|
227
302
|
*/
|
|
228
303
|
invalidatePrefix(prefix) {
|
|
229
|
-
|
|
230
|
-
for (const key of this.#lru.keys()) {
|
|
231
|
-
if (key.startsWith(prefix) && this.#lru.delete(key)) removed++;
|
|
232
|
-
}
|
|
233
|
-
return removed;
|
|
304
|
+
return this.#lru.deleteByPrefix(prefix);
|
|
234
305
|
}
|
|
235
306
|
/** Remove everything. */
|
|
236
307
|
clear() {
|
|
@@ -267,12 +338,35 @@ function isSshDevError(value) {
|
|
|
267
338
|
return value instanceof SshDevError;
|
|
268
339
|
}
|
|
269
340
|
|
|
270
|
-
// src/
|
|
271
|
-
function
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
341
|
+
// src/shared/root-keys.ts
|
|
342
|
+
function normalizeLocalPath(path, options = {}) {
|
|
343
|
+
const caseInsensitive = options.caseInsensitive ?? false;
|
|
344
|
+
const unified = path.replace(/\\/g, "/");
|
|
345
|
+
const unc = unified.startsWith("//");
|
|
346
|
+
const posixAbsolute = unified.startsWith("/");
|
|
347
|
+
const driveMatch = /^([A-Za-z]):/.exec(unified);
|
|
348
|
+
const driveLetter = driveMatch?.[1];
|
|
349
|
+
const drive = driveLetter === void 0 ? "" : `${driveLetter.toUpperCase()}:`;
|
|
350
|
+
const rest = unc ? unified.slice(2) : drive === "" ? unified : unified.slice(2);
|
|
351
|
+
const segments = [];
|
|
352
|
+
for (const segment of rest.split("/")) {
|
|
353
|
+
if (segment === "" || segment === ".") continue;
|
|
354
|
+
if (segment === "..") {
|
|
355
|
+
segments.pop();
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
segments.push(segment);
|
|
275
359
|
}
|
|
360
|
+
const body = segments.join("/");
|
|
361
|
+
let normalized;
|
|
362
|
+
if (unc) normalized = `//${body}`;
|
|
363
|
+
else if (drive !== "") normalized = body === "" ? `${drive}/` : `${drive}/${body}`;
|
|
364
|
+
else if (posixAbsolute) normalized = body === "" ? "/" : `/${body}`;
|
|
365
|
+
else normalized = body === "" ? "." : body;
|
|
366
|
+
return caseInsensitive ? normalized.toLowerCase() : normalized;
|
|
367
|
+
}
|
|
368
|
+
function normalizePosixPath(path) {
|
|
369
|
+
if (!path.startsWith("/")) return void 0;
|
|
276
370
|
const segments = [];
|
|
277
371
|
for (const segment of path.split("/")) {
|
|
278
372
|
if (segment === "" || segment === ".") continue;
|
|
@@ -284,12 +378,31 @@ function normalizeRemotePath(path) {
|
|
|
284
378
|
}
|
|
285
379
|
return segments.length === 0 ? "/" : `/${segments.join("/")}`;
|
|
286
380
|
}
|
|
381
|
+
|
|
382
|
+
// src/host/domain/path-grammar.ts
|
|
383
|
+
function normalizeRemotePath(path) {
|
|
384
|
+
if (/[\u0000-\u001f]/.test(path)) {
|
|
385
|
+
throw new SshDevError("ROOT_INVALID", "\u8FDC\u7A0B\u8DEF\u5F84\u4E0D\u80FD\u5305\u542B\u63A7\u5236\u5B57\u7B26", { path: JSON.stringify(path) });
|
|
386
|
+
}
|
|
387
|
+
const normalized = normalizePosixPath(path);
|
|
388
|
+
if (normalized !== void 0) return normalized;
|
|
389
|
+
if (path === "") throw new SshDevError("ROOT_INVALID", "\u8FDC\u7A0B\u8DEF\u5F84\u4E0D\u80FD\u4E3A\u7A7A", {});
|
|
390
|
+
throw new SshDevError("ROOT_INVALID", `\u8FDC\u7A0B\u8DEF\u5F84\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84\uFF1A${path}`, { path });
|
|
391
|
+
}
|
|
287
392
|
function remotePathContains(root, candidate) {
|
|
288
393
|
const normalizedRoot = normalizeRemotePath(root);
|
|
289
394
|
const normalizedCandidate = normalizeRemotePath(candidate);
|
|
290
395
|
if (normalizedRoot === "/") return true;
|
|
291
396
|
return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(`${normalizedRoot}/`);
|
|
292
397
|
}
|
|
398
|
+
function longestContainingRoot(roots, candidate) {
|
|
399
|
+
let best;
|
|
400
|
+
for (const root of roots) {
|
|
401
|
+
if (!remotePathContains(root, candidate)) continue;
|
|
402
|
+
if (best === void 0 || normalizeRemotePath(root).length > normalizeRemotePath(best).length) best = root;
|
|
403
|
+
}
|
|
404
|
+
return best;
|
|
405
|
+
}
|
|
293
406
|
|
|
294
407
|
// src/host/infra/transport/git-status.ts
|
|
295
408
|
function isGitStatusTransport(value) {
|
|
@@ -429,31 +542,27 @@ function relativeDirectory(root, path) {
|
|
|
429
542
|
}
|
|
430
543
|
|
|
431
544
|
// src/host/domain/roots/root.ts
|
|
432
|
-
function
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
const
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
const rest = unc ? unified.slice(2) : drive === "" ? unified : unified.slice(2);
|
|
441
|
-
const segments = [];
|
|
442
|
-
for (const segment of rest.split("/")) {
|
|
443
|
-
if (segment === "" || segment === ".") continue;
|
|
444
|
-
if (segment === "..") {
|
|
445
|
-
segments.pop();
|
|
446
|
-
continue;
|
|
447
|
-
}
|
|
448
|
-
segments.push(segment);
|
|
545
|
+
function normalizeLocalPath2(path, options = {}) {
|
|
546
|
+
return normalizeLocalPath(path, { caseInsensitive: options.caseInsensitive ?? process.platform === "win32" });
|
|
547
|
+
}
|
|
548
|
+
function localPathContains(root, candidate, options = {}) {
|
|
549
|
+
const normalizedRoot = normalizeLocalPath2(root, options);
|
|
550
|
+
const normalizedCandidate = normalizeLocalPath2(candidate, options);
|
|
551
|
+
if (normalizedRoot === "/" || /^[a-z]:\/$/.test(normalizedRoot)) {
|
|
552
|
+
return normalizedCandidate.startsWith(normalizedRoot);
|
|
449
553
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
554
|
+
return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(`${normalizedRoot}/`);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// src/host/app/roots/user-roots.ts
|
|
558
|
+
var USER_ROOTS_FILE_NAME = "user-roots.json";
|
|
559
|
+
var USER_ROOTS_LOCK_FILE_NAME = `${USER_ROOTS_FILE_NAME}.lock`;
|
|
560
|
+
|
|
561
|
+
// src/shared/routes.ts
|
|
562
|
+
function asRouteHost(value) {
|
|
563
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
564
|
+
const register = value.register;
|
|
565
|
+
return typeof register === "function" ? value : void 0;
|
|
457
566
|
}
|
|
458
567
|
|
|
459
568
|
// src/host/app/roots/roots-api-contract.ts
|
|
@@ -507,14 +616,14 @@ function parseAuthority(authority) {
|
|
|
507
616
|
}
|
|
508
617
|
|
|
509
618
|
// src/host/app/roots/roots-api-http.ts
|
|
510
|
-
async function readBody(req) {
|
|
619
|
+
async function readBody(req, maxBytes) {
|
|
511
620
|
const chunks = [];
|
|
512
621
|
let total = 0;
|
|
513
622
|
for await (const chunk of req) {
|
|
514
623
|
const buffer = toBytes(chunk);
|
|
515
624
|
total += buffer.length;
|
|
516
|
-
if (total >
|
|
517
|
-
throw new
|
|
625
|
+
if (total > maxBytes) {
|
|
626
|
+
throw new SshDevError("ROOT_INVALID", `\u8BF7\u6C42\u4F53\u8D85\u8FC7\u4E0A\u9650 ${maxBytes} \u5B57\u8282\uFF0C\u62D2\u7EDD`);
|
|
518
627
|
}
|
|
519
628
|
chunks.push(buffer);
|
|
520
629
|
}
|
|
@@ -543,11 +652,6 @@ function messageOf(error) {
|
|
|
543
652
|
|
|
544
653
|
// src/host/infra/transport/git-status-route.ts
|
|
545
654
|
var GIT_STATUS_API_PATH = "/api/over-ssh/git-status";
|
|
546
|
-
function asRouteHost(value) {
|
|
547
|
-
if (typeof value !== "object" || value === null) return void 0;
|
|
548
|
-
const register = value.register;
|
|
549
|
-
return typeof register === "function" ? value : void 0;
|
|
550
|
-
}
|
|
551
655
|
function toPublicAnswer(snapshot) {
|
|
552
656
|
return {
|
|
553
657
|
ok: true,
|
|
@@ -579,7 +683,7 @@ function createGitStatusApi(deps) {
|
|
|
579
683
|
return;
|
|
580
684
|
}
|
|
581
685
|
try {
|
|
582
|
-
const body = asRecord(JSON.parse(await readBody(req)));
|
|
686
|
+
const body = asRecord(JSON.parse(await readBody(req, ROOTS_API_MAX_BODY_BYTES)));
|
|
583
687
|
const connectionId = body?.["connectionId"];
|
|
584
688
|
const path = body?.["path"];
|
|
585
689
|
if (typeof connectionId !== "string" || connectionId === "") {
|
|
@@ -641,15 +745,8 @@ function wireGitStatus(ctx) {
|
|
|
641
745
|
resolveRoot: (connectionId, path) => {
|
|
642
746
|
const registry = ctx.get("sshDevRoots");
|
|
643
747
|
if (registry === void 0) return void 0;
|
|
644
|
-
const
|
|
645
|
-
|
|
646
|
-
for (const record of registry.records) {
|
|
647
|
-
const root = record.root;
|
|
648
|
-
if (root.location !== "remote" || root.connectionId !== connectionId) continue;
|
|
649
|
-
if (!remotePathContains(root.path, normalized)) continue;
|
|
650
|
-
if (best === void 0 || root.path.length > best.length) best = root.path;
|
|
651
|
-
}
|
|
652
|
-
return best;
|
|
748
|
+
const roots = registry.records.map((record) => record.root).filter((root) => root.location === "remote" && root.connectionId === connectionId).map((root) => root.path);
|
|
749
|
+
return longestContainingRoot(roots, normalizeRemotePath(path));
|
|
653
750
|
}
|
|
654
751
|
});
|
|
655
752
|
registerGitStatusRoute(ctx, { service });
|
|
@@ -699,9 +796,20 @@ var ancestorCache = /* @__PURE__ */ new Map();
|
|
|
699
796
|
var ANCESTOR_CACHE_MAX = 256;
|
|
700
797
|
function readSessionAncestors(ctx, sessionId) {
|
|
701
798
|
const cached = ancestorCache.get(sessionId);
|
|
702
|
-
if (cached !== void 0)
|
|
799
|
+
if (cached !== void 0) {
|
|
800
|
+
ancestorCache.delete(sessionId);
|
|
801
|
+
ancestorCache.set(sessionId, cached);
|
|
802
|
+
return cached;
|
|
803
|
+
}
|
|
703
804
|
const walked = walkAncestors(ctx, sessionId);
|
|
704
|
-
if (walked.settled
|
|
805
|
+
if (walked.settled) {
|
|
806
|
+
ancestorCache.set(sessionId, walked.ancestors);
|
|
807
|
+
while (ancestorCache.size > ANCESTOR_CACHE_MAX) {
|
|
808
|
+
const oldest = ancestorCache.keys().next();
|
|
809
|
+
if (oldest.done === true) break;
|
|
810
|
+
ancestorCache.delete(oldest.value);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
705
813
|
return walked.ancestors;
|
|
706
814
|
}
|
|
707
815
|
function walkAncestors(ctx, sessionId) {
|
|
@@ -730,6 +838,92 @@ function walkAncestors(ctx, sessionId) {
|
|
|
730
838
|
}
|
|
731
839
|
return { ancestors, settled };
|
|
732
840
|
}
|
|
841
|
+
function readSessionCwd(ctx, sessionId) {
|
|
842
|
+
const cached = sessionCwdCache.get(sessionId);
|
|
843
|
+
if (cached !== void 0) {
|
|
844
|
+
sessionCwdCache.delete(sessionId);
|
|
845
|
+
sessionCwdCache.set(sessionId, cached);
|
|
846
|
+
return cached.value === "" ? void 0 : cached.value;
|
|
847
|
+
}
|
|
848
|
+
const found = walkSessionCwd(ctx, sessionId);
|
|
849
|
+
if (found.settled) {
|
|
850
|
+
sessionCwdCache.set(sessionId, { value: found.cwd ?? "" });
|
|
851
|
+
while (sessionCwdCache.size > SESSION_CWD_CACHE_MAX) {
|
|
852
|
+
const oldest = sessionCwdCache.keys().next();
|
|
853
|
+
if (oldest.done === true) break;
|
|
854
|
+
sessionCwdCache.delete(oldest.value);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
return found.cwd;
|
|
858
|
+
}
|
|
859
|
+
function readSessionCwds(ctx) {
|
|
860
|
+
let list;
|
|
861
|
+
try {
|
|
862
|
+
const sessions = ctx.get("sessions");
|
|
863
|
+
list = sessions?.list?.();
|
|
864
|
+
} catch {
|
|
865
|
+
return [];
|
|
866
|
+
}
|
|
867
|
+
if (!Array.isArray(list)) return [];
|
|
868
|
+
const cwds = [];
|
|
869
|
+
for (const session of list) {
|
|
870
|
+
const cwd = session?.header?.cwd;
|
|
871
|
+
if (typeof cwd === "string" && cwd !== "") cwds.push(cwd);
|
|
872
|
+
}
|
|
873
|
+
return cwds;
|
|
874
|
+
}
|
|
875
|
+
var sessionCwdCache = /* @__PURE__ */ new Map();
|
|
876
|
+
var SESSION_CWD_CACHE_MAX = 256;
|
|
877
|
+
function walkSessionCwd(ctx, sessionId) {
|
|
878
|
+
let list;
|
|
879
|
+
try {
|
|
880
|
+
const sessions = ctx.get("sessions");
|
|
881
|
+
list = sessions?.list?.();
|
|
882
|
+
} catch {
|
|
883
|
+
return { cwd: void 0, settled: false };
|
|
884
|
+
}
|
|
885
|
+
if (!Array.isArray(list)) return { cwd: void 0, settled: false };
|
|
886
|
+
for (const session of list) {
|
|
887
|
+
if (session?.header?.id !== sessionId) continue;
|
|
888
|
+
const cwd = session.header.cwd;
|
|
889
|
+
return { cwd: typeof cwd === "string" && cwd !== "" ? cwd : void 0, settled: true };
|
|
890
|
+
}
|
|
891
|
+
return { cwd: void 0, settled: false };
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// src/host/domain/roots/session-roots.ts
|
|
895
|
+
function workspaceWriteArea(root, workspaceRoot, cwd, identity = {}) {
|
|
896
|
+
const sessions = root.sessions ?? [];
|
|
897
|
+
if (sessions.length > 0 && identity.sessionId !== void 0 && identity.sessionId !== "") {
|
|
898
|
+
if (addedBySession(sessions, identity)) return "whole-root";
|
|
899
|
+
} else if (sessions.length === 0 && cwd !== void 0 && cwd !== "") {
|
|
900
|
+
if ((root.workspaces ?? []).some((candidate) => sameWorkspacePath(candidate, cwd))) return "whole-root";
|
|
901
|
+
}
|
|
902
|
+
if (workspaceRoot === void 0 || workspaceRoot === "") return "deny";
|
|
903
|
+
if (root.location === "remote") {
|
|
904
|
+
const anchor = root.anchorPath;
|
|
905
|
+
if (anchor === void 0 || anchor === "") return "deny";
|
|
906
|
+
if (sameWorkspacePath(anchor, workspaceRoot) || localPathContains(anchor, workspaceRoot)) return "whole-root";
|
|
907
|
+
if (localPathContains(workspaceRoot, anchor)) return "whole-root";
|
|
908
|
+
return "deny";
|
|
909
|
+
}
|
|
910
|
+
if (localPathContains(root.path, workspaceRoot)) return "workspace-only";
|
|
911
|
+
if (cwd !== void 0 && cwd !== "" && sameWorkspacePath(root.path, cwd)) return "workspace-only";
|
|
912
|
+
return "deny";
|
|
913
|
+
}
|
|
914
|
+
function addedBySession(sessions, identity) {
|
|
915
|
+
const sessionId = identity.sessionId;
|
|
916
|
+
if (sessionId === void 0 || sessionId === "") return false;
|
|
917
|
+
if (sessions.includes(sessionId)) return true;
|
|
918
|
+
return (identity.ancestors ?? []).some((ancestor) => sessions.includes(ancestor));
|
|
919
|
+
}
|
|
920
|
+
function sameWorkspacePath(left, right) {
|
|
921
|
+
const fold = (value) => {
|
|
922
|
+
const normalized = normalizeLocalPath2(value, { caseInsensitive: false }).replace(/\/+/g, "/");
|
|
923
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
924
|
+
};
|
|
925
|
+
return fold(left) === fold(right);
|
|
926
|
+
}
|
|
733
927
|
|
|
734
928
|
// src/host/integration/fs/fs-provider.ts
|
|
735
929
|
var MAX_WHOLE_READ_BYTES = 16 * 1024 * 1024;
|
|
@@ -798,7 +992,7 @@ var RoutedFsProvider = class extends FileSystem {
|
|
|
798
992
|
*/
|
|
799
993
|
processPathFromHostPath(hostPath) {
|
|
800
994
|
if (!isAbsoluteHostPath(hostPath)) return void 0;
|
|
801
|
-
return
|
|
995
|
+
return normalizeLocalPath2(hostPath, { caseInsensitive: false });
|
|
802
996
|
}
|
|
803
997
|
/** Canonical `file:` URI in the target's world. */
|
|
804
998
|
fileUrl(target) {
|
|
@@ -932,16 +1126,6 @@ var RoutedFsProvider = class extends FileSystem {
|
|
|
932
1126
|
const chunk = await this.call(() => routed.backend.readBytes(routed.absolute, end, signal));
|
|
933
1127
|
return chunk.bytes.subarray(start, Math.min(end, chunk.bytes.byteLength));
|
|
934
1128
|
}
|
|
935
|
-
/**
|
|
936
|
-
* Read a window through the injected offset reader.
|
|
937
|
-
*
|
|
938
|
-
* Kept as a thin method so the instance is the receiver here (a direct call is fine); the work happens in
|
|
939
|
-
* the free {@link readChunkedWindow}, because the public read path invokes it from inside `this.call(...)`,
|
|
940
|
-
* where `this` is the Cordis service Proxy and instance state would not be reachable.
|
|
941
|
-
*/
|
|
942
|
-
#readChunkedWindow(routed, offset, length, signal) {
|
|
943
|
-
return readChunkedWindow(this.rangeReader, this.rangeChunkBytes, routed, offset, length, signal);
|
|
944
|
-
}
|
|
945
1129
|
/** Direct children, in stable name order, with resolved child targets. */
|
|
946
1130
|
async listDir(target, signal) {
|
|
947
1131
|
const routed = this.routedTarget(target);
|
|
@@ -968,7 +1152,7 @@ var RoutedFsProvider = class extends FileSystem {
|
|
|
968
1152
|
}
|
|
969
1153
|
const guard = expected?.kind === "replaceIfVersion" ? expected.version : void 0;
|
|
970
1154
|
const outcome = await this.call(
|
|
971
|
-
() => routed.backend.writeText(routed.absolute, content, guard, signal)
|
|
1155
|
+
() => routed.backend.writeText(routed.absolute, content, guard, signal, sandboxPolicy)
|
|
972
1156
|
).catch((error) => {
|
|
973
1157
|
throw mapWriteError(error, routed.displayPath);
|
|
974
1158
|
});
|
|
@@ -1013,7 +1197,8 @@ var RoutedFsProvider = class extends FileSystem {
|
|
|
1013
1197
|
edit.newString,
|
|
1014
1198
|
edit.replaceAll === true,
|
|
1015
1199
|
chunk.version,
|
|
1016
|
-
signal
|
|
1200
|
+
signal,
|
|
1201
|
+
sandboxPolicy
|
|
1017
1202
|
)
|
|
1018
1203
|
);
|
|
1019
1204
|
return { version: FsVersion(native.version), before, after };
|
|
@@ -1024,7 +1209,7 @@ var RoutedFsProvider = class extends FileSystem {
|
|
|
1024
1209
|
}
|
|
1025
1210
|
}
|
|
1026
1211
|
const outcome = await this.call(
|
|
1027
|
-
() => routed.backend.writeText(routed.absolute, restoreLineEndings(after, lineEndings), chunk.version, signal)
|
|
1212
|
+
() => routed.backend.writeText(routed.absolute, restoreLineEndings(after, lineEndings), chunk.version, signal, sandboxPolicy)
|
|
1028
1213
|
).catch((error) => {
|
|
1029
1214
|
throw mapWriteError(error, routed.displayPath);
|
|
1030
1215
|
});
|
|
@@ -1153,7 +1338,24 @@ var RoutedFsProvider = class extends FileSystem {
|
|
|
1153
1338
|
throw isSshDevError(error) ? toFsError(error) : error;
|
|
1154
1339
|
}
|
|
1155
1340
|
}
|
|
1156
|
-
/**
|
|
1341
|
+
/**
|
|
1342
|
+
* Enforce the per-call policy and the root's own writability.
|
|
1343
|
+
*
|
|
1344
|
+
* Three gates, in order:
|
|
1345
|
+
* 1. `read-only` denies every mutation (the platform's own contract);
|
|
1346
|
+
* 2. a root registered `writable: false` denies;
|
|
1347
|
+
* 3. under **`workspace-write`**, the target must be inside the session's workspace — where "workspace"
|
|
1348
|
+
* includes the directories **this session added** (product decision 2026-11-04: 「添加到会话的目录」=
|
|
1349
|
+
* 该目录临时成为 workspace 的一部分). Before that decision the plugin admitted every writable root it
|
|
1350
|
+
* could route, which made the mode wider than the platform's own `fs-sandbox` fence
|
|
1351
|
+
* (`workspace-write` = "canonicalizes under `workspaceRoot` or the platform temp area").
|
|
1352
|
+
*
|
|
1353
|
+
* A call without a per-call policy keeps the historical behaviour (the mode comes from
|
|
1354
|
+
* {@link sandboxMode}); the area rule needs `workspaceRoot`, which only the policy carries.
|
|
1355
|
+
*
|
|
1356
|
+
* @param routed - the resolved target.
|
|
1357
|
+
* @param policy - the per-call sandbox policy the tool layer resolved.
|
|
1358
|
+
*/
|
|
1157
1359
|
assertWritable(routed, policy) {
|
|
1158
1360
|
const mode = policy?.mode ?? this.sandboxMode;
|
|
1159
1361
|
if (mode === "read-only") {
|
|
@@ -1162,6 +1364,19 @@ var RoutedFsProvider = class extends FileSystem {
|
|
|
1162
1364
|
if (routed.root.writable === false) {
|
|
1163
1365
|
throw platformFsError(`the root of "${routed.displayPath}" is registered read-only`, "FS_SANDBOX_DENIED");
|
|
1164
1366
|
}
|
|
1367
|
+
if (mode !== "workspace-write" || policy === void 0) return;
|
|
1368
|
+
const sessionId = currentSessionId();
|
|
1369
|
+
const identity = sessionId === void 0 || sessionId === "" ? {} : { sessionId, ancestors: this.sessionAncestors(sessionId) };
|
|
1370
|
+
const cwd = sessionId === void 0 || sessionId === "" ? void 0 : this.sessionWorkspaceCwdOf(sessionId);
|
|
1371
|
+
const area = workspaceWriteArea(routed.root, policy.workspaceRoot, cwd, identity);
|
|
1372
|
+
if (area === "whole-root") return;
|
|
1373
|
+
if (area === "workspace-only" && routed.root.location === "local" && localPathContains(policy.workspaceRoot, routed.absolute, { caseInsensitive: process.platform === "win32" })) {
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
throw platformFsError(
|
|
1377
|
+
`sandbox denies writing "${routed.displayPath}": the path is outside this session's workspace (its workspace, or a directory this session added with \u300C\u6DFB\u52A0\u76EE\u5F55\u300D)`,
|
|
1378
|
+
"FS_SANDBOX_DENIED"
|
|
1379
|
+
);
|
|
1165
1380
|
}
|
|
1166
1381
|
/**
|
|
1167
1382
|
* Previous text content, when it is readable text inside the cap; `undefined` otherwise.
|
|
@@ -1177,9 +1392,8 @@ var RoutedFsProvider = class extends FileSystem {
|
|
|
1177
1392
|
return void 0;
|
|
1178
1393
|
});
|
|
1179
1394
|
if (chunk === void 0 || chunk.truncated) return void 0;
|
|
1180
|
-
const
|
|
1181
|
-
|
|
1182
|
-
if (sniff.includes(0)) return void 0;
|
|
1395
|
+
const nul = chunk.text.indexOf("\0");
|
|
1396
|
+
if (nul !== -1 && nul < BINARY_SNIFF_BYTES) return void 0;
|
|
1183
1397
|
return chunk.text;
|
|
1184
1398
|
}
|
|
1185
1399
|
/** Run a backend call, translating domain errors into core codes. */
|
|
@@ -1269,38 +1483,6 @@ function sessionSyncSignature(cwds, registry) {
|
|
|
1269
1483
|
const roots = registry.sessionWorkspaceRecords.map((record) => record.key).join("\0");
|
|
1270
1484
|
return `${cwds.join("\0")}${roots}`;
|
|
1271
1485
|
}
|
|
1272
|
-
function readSessionCwd(ctx, sessionId) {
|
|
1273
|
-
let list;
|
|
1274
|
-
try {
|
|
1275
|
-
const sessions = ctx.get("sessions");
|
|
1276
|
-
list = sessions?.list?.();
|
|
1277
|
-
} catch {
|
|
1278
|
-
return void 0;
|
|
1279
|
-
}
|
|
1280
|
-
if (!Array.isArray(list)) return void 0;
|
|
1281
|
-
for (const session of list) {
|
|
1282
|
-
if (session?.header?.id !== sessionId) continue;
|
|
1283
|
-
const cwd = session.header.cwd;
|
|
1284
|
-
return typeof cwd === "string" && cwd !== "" ? cwd : void 0;
|
|
1285
|
-
}
|
|
1286
|
-
return void 0;
|
|
1287
|
-
}
|
|
1288
|
-
function readSessionCwds(ctx) {
|
|
1289
|
-
let list;
|
|
1290
|
-
try {
|
|
1291
|
-
const sessions = ctx.get("sessions");
|
|
1292
|
-
list = sessions?.list?.();
|
|
1293
|
-
} catch {
|
|
1294
|
-
return [];
|
|
1295
|
-
}
|
|
1296
|
-
if (!Array.isArray(list)) return [];
|
|
1297
|
-
const cwds = [];
|
|
1298
|
-
for (const session of list) {
|
|
1299
|
-
const cwd = session?.header?.cwd;
|
|
1300
|
-
if (typeof cwd === "string" && cwd !== "") cwds.push(cwd);
|
|
1301
|
-
}
|
|
1302
|
-
return cwds;
|
|
1303
|
-
}
|
|
1304
1486
|
function assertText(displayPath, bytes) {
|
|
1305
1487
|
if (bytes.subarray(0, BINARY_SNIFF_BYTES).includes(0)) {
|
|
1306
1488
|
throw platformFsError(`cannot read "${displayPath}": not a UTF-8 text file`, "FS_NOT_TEXT");
|