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,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The session-administration HTTP route: `/api/remote-workspace/sessions`.
|
|
3
|
+
*
|
|
4
|
+
* The route is the client half's only way to reach the session store. It reuses the roots route's
|
|
5
|
+
* **same-origin trust check** ({@link trustVerdict}) rather than inventing a second policy: a request that may
|
|
6
|
+
* edit roots may also delete sessions, and one that may not, may not.
|
|
7
|
+
*
|
|
8
|
+
* ## Two rules that shape the wire
|
|
9
|
+
*
|
|
10
|
+
* 1. **`GET` never mutates.** It answers the inventory and the bin. Deletion and restore are `POST` actions,
|
|
11
|
+
* so a prefetch, a link preview or an over-eager browser cannot destroy anything.
|
|
12
|
+
* 2. **Irreversible actions are refused here unless the caller says it means it.** `purge` and
|
|
13
|
+
* `mode: 'permanent'` require `confirm: true` in the body. The UI already asks; this is the second lock, so
|
|
14
|
+
* a bug in the client cannot turn a recycle into an irreversible delete.
|
|
15
|
+
*
|
|
16
|
+
* @module dsh-remote-workspace/integration/session-api
|
|
17
|
+
*/
|
|
18
|
+
import { type RootsApiRequest, type RootsApiResponse } from '../app/roots-api.ts';
|
|
19
|
+
import type { SessionAdminService } from '../app/session-admin-service.ts';
|
|
20
|
+
import type { SessionRecord, SessionTrashEntry } from '../domain/session-admin.ts';
|
|
21
|
+
/** The one path this plugin claims for session administration. */
|
|
22
|
+
export declare const SESSION_API_PATH = "/api/remote-workspace/sessions";
|
|
23
|
+
/** Largest accepted request body. An id list is a few hundred bytes. */
|
|
24
|
+
export declare const SESSION_API_MAX_BODY_BYTES: number;
|
|
25
|
+
/** One accepted `POST` action; anything else is refused with the list. */
|
|
26
|
+
export declare const SESSION_API_ACTIONS: readonly ["delete", "delete-workspace", "restore", "purge", "sweep", "preview"];
|
|
27
|
+
/** One accepted action. */
|
|
28
|
+
export type SessionApiAction = (typeof SESSION_API_ACTIONS)[number];
|
|
29
|
+
/** One inventory row as the wire carries it (a field-by-field projection, never the record itself). */
|
|
30
|
+
export interface SessionRow {
|
|
31
|
+
readonly id: string;
|
|
32
|
+
readonly classification: 'normal' | 'subsession' | 'unreadable';
|
|
33
|
+
readonly parentSessionId?: string;
|
|
34
|
+
readonly title?: string;
|
|
35
|
+
readonly cwd?: string;
|
|
36
|
+
/** Display title of the workspace owning `cwd`; absent when no registered workspace owns it. */
|
|
37
|
+
readonly workspaceTitle?: string;
|
|
38
|
+
readonly bytes: number;
|
|
39
|
+
readonly updatedAtMs: number;
|
|
40
|
+
readonly live: boolean;
|
|
41
|
+
}
|
|
42
|
+
/** One bin row as the wire carries it. */
|
|
43
|
+
export interface SessionBinRow {
|
|
44
|
+
readonly entryId: string;
|
|
45
|
+
readonly sessionId: string;
|
|
46
|
+
readonly title?: string;
|
|
47
|
+
readonly bytes: number;
|
|
48
|
+
readonly deletedAtMs: number;
|
|
49
|
+
readonly movedBy: 'rename' | 'copy';
|
|
50
|
+
readonly confirmedBy?: string;
|
|
51
|
+
}
|
|
52
|
+
/** The seams the route needs. */
|
|
53
|
+
export interface SessionApiDeps {
|
|
54
|
+
/** The session administration service (or a getter, since services can appear after activation). */
|
|
55
|
+
readonly service: () => SessionAdminService | undefined;
|
|
56
|
+
/** Retention limits used by `sweep`, from configuration. */
|
|
57
|
+
readonly policy: () => {
|
|
58
|
+
readonly maxEntries: number;
|
|
59
|
+
readonly maxBytes: number;
|
|
60
|
+
readonly maxAgeMs: number;
|
|
61
|
+
};
|
|
62
|
+
/** Injected clock, so a test can pin `sweep`'s "now". */
|
|
63
|
+
readonly now?: () => number;
|
|
64
|
+
}
|
|
65
|
+
/** The handler pair the entry registers. */
|
|
66
|
+
export interface SessionApi {
|
|
67
|
+
readonly handle: (req: RootsApiRequest, res: RootsApiResponse) => Promise<void>;
|
|
68
|
+
dispose(): void;
|
|
69
|
+
}
|
|
70
|
+
/** Project one inventory record to the wire shape. */
|
|
71
|
+
export declare function toSessionRow(record: SessionRecord): SessionRow;
|
|
72
|
+
/** Project one bin entry to the wire shape. */
|
|
73
|
+
export declare function toSessionBinRow(entry: SessionTrashEntry): SessionBinRow;
|
|
74
|
+
/**
|
|
75
|
+
* Build the handler pair.
|
|
76
|
+
*
|
|
77
|
+
* @param deps - injected seams.
|
|
78
|
+
* @returns `handle` plus a no-op `dispose`.
|
|
79
|
+
*/
|
|
80
|
+
export declare function createSessionApi(deps: SessionApiDeps): SessionApi;
|
|
81
|
+
//# sourceMappingURL=session-api.d.ts.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A cross-process advisory lock for the session recycle-bin index.
|
|
3
|
+
*
|
|
4
|
+
* Why this exists: the in-process promise chain in `session-admin-service.ts` cannot see **another DSH
|
|
5
|
+
* process**. Two processes deleting a session at the same time can each read the old index and then each
|
|
6
|
+
* write back their own version, so one entry is lost — and a lost entry is not a cosmetic problem: the
|
|
7
|
+
* moved bytes stay on disk while the index does not mention them, making them invisible *and* unrestorable.
|
|
8
|
+
*
|
|
9
|
+
* ## Design
|
|
10
|
+
*
|
|
11
|
+
* A lock file created with `wx` (exclusive create) is the mutual exclusion primitive: the filesystem
|
|
12
|
+
* guarantees exactly one creator wins, on local and network filesystems alike, without any dependency.
|
|
13
|
+
* The file records `{ pid, host, acquiredAtMs }` so a human can see who holds it.
|
|
14
|
+
*
|
|
15
|
+
* **Stale locks are reclaimed by age.** A process that dies while holding the lock leaves the file behind;
|
|
16
|
+
* without reclamation every later deletion would fail forever. The threshold is deliberately generous
|
|
17
|
+
* (see {@link DEFAULT_STALE_LOCK_MS}) because reclaiming a *live* holder's lock is worse than waiting: it
|
|
18
|
+
* reintroduces exactly the race this module prevents.
|
|
19
|
+
*
|
|
20
|
+
* This is advisory, not a guarantee: it coordinates cooperating processes of this plugin, and does not
|
|
21
|
+
* protect the bin from a process that ignores the protocol.
|
|
22
|
+
*
|
|
23
|
+
* @module dsh-remote-workspace/integration/session-bin-lock
|
|
24
|
+
*/
|
|
25
|
+
/** How long a lock may sit before another process may reclaim it. */
|
|
26
|
+
export declare const DEFAULT_STALE_LOCK_MS = 60000;
|
|
27
|
+
/** How long to wait between acquisition attempts. */
|
|
28
|
+
export declare const DEFAULT_LOCK_RETRY_MS = 25;
|
|
29
|
+
/** How long to keep trying before giving up and reporting contention. */
|
|
30
|
+
export declare const DEFAULT_LOCK_TIMEOUT_MS = 30000;
|
|
31
|
+
/** Options for {@link createSessionBinLock}. */
|
|
32
|
+
export interface SessionBinLockOptions {
|
|
33
|
+
/** Lock file path. */
|
|
34
|
+
readonly path: string;
|
|
35
|
+
/** Stale threshold; non-positive disables reclamation (waiting forever is then the caller's risk). */
|
|
36
|
+
readonly staleMs?: number;
|
|
37
|
+
readonly retryMs?: number;
|
|
38
|
+
readonly timeoutMs?: number;
|
|
39
|
+
/** Injected sleep so tests need no real waiting. */
|
|
40
|
+
readonly sleep?: (ms: number) => Promise<void>;
|
|
41
|
+
/** Injected clock. */
|
|
42
|
+
readonly now?: () => number;
|
|
43
|
+
/** Identity recorded in the lock file (defaults to pid/host). */
|
|
44
|
+
readonly owner?: string;
|
|
45
|
+
/** Called when a stale lock is reclaimed, so the event is observable instead of silent. */
|
|
46
|
+
readonly onReclaim?: (event: {
|
|
47
|
+
readonly path: string;
|
|
48
|
+
readonly ageMs: number;
|
|
49
|
+
readonly holder: Partial<SessionBinLockRecord>;
|
|
50
|
+
}) => void;
|
|
51
|
+
}
|
|
52
|
+
/** What one lock file records. */
|
|
53
|
+
export interface SessionBinLockRecord {
|
|
54
|
+
readonly pid: number;
|
|
55
|
+
readonly host: string;
|
|
56
|
+
readonly acquiredAtMs: number;
|
|
57
|
+
/** Human-readable holder, for diagnosis (see `onReclaim`). **Not** used for ownership checks. */
|
|
58
|
+
readonly owner?: string;
|
|
59
|
+
/**
|
|
60
|
+
* Per-acquisition ownership token, compared before release.
|
|
61
|
+
*
|
|
62
|
+
* Separate from `owner` on purpose: `owner` is documented as a **diagnostic** field and is asserted as
|
|
63
|
+
* such by `tests/session-bin-lock.test.ts`; overloading it with a token would change what a reader of
|
|
64
|
+
* the lock file sees. This field exists only so a holder whose lock was reclaimed cannot delete its
|
|
65
|
+
* successor's lock.
|
|
66
|
+
*/
|
|
67
|
+
readonly token?: string;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Run one operation under the cross-process lock.
|
|
71
|
+
*
|
|
72
|
+
* @param options - lock file path plus timing knobs.
|
|
73
|
+
* @returns a `withLock(key, operation)` function; `key` is accepted for interface compatibility and is not
|
|
74
|
+
* used to derive the path (there is exactly one session-bin index).
|
|
75
|
+
*/
|
|
76
|
+
export declare function createSessionBinLock(options: SessionBinLockOptions): {
|
|
77
|
+
withLock: <T>(_key: string, operation: () => Promise<T>) => Promise<T>;
|
|
78
|
+
lockPath: string;
|
|
79
|
+
};
|
|
80
|
+
//# sourceMappingURL=session-bin-lock.d.ts.map
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read a session log's header line, with a **bounded** memory footprint.
|
|
3
|
+
*
|
|
4
|
+
* Why this is its own module: the reader used to live in the composition root (`src/index.ts`), where no
|
|
5
|
+
* test can reach it (审计 0046 记录了同款教训). A file-system reader is exactly the kind of code that needs a
|
|
6
|
+
* test to pin its *bounds*, and the bound is the whole point here.
|
|
7
|
+
*
|
|
8
|
+
* ## What went wrong before
|
|
9
|
+
*
|
|
10
|
+
* The old reader claimed to "decompress only the head" but read the **whole file** first:
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* const handle = await readFile(path); // 10.9 MiB for one real session
|
|
14
|
+
* text = zstdDecompressSync(handle.subarray(0, 256 * 1024)).toString(); // cap applies to the SLICE only
|
|
15
|
+
* } catch {
|
|
16
|
+
* text = zstdDecompressSync(await readFile(path)).toString(); // and the fallback reads it AGAIN
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* `readFile` has no `maxBytes`, so the 256 KiB constant bounded the decompressor's *input*, never the read.
|
|
21
|
+
* Measured on the reference machine's real store (73 sessions): **105.3 MiB** read into memory to extract
|
|
22
|
+
* 73 first lines (19 KiB of output), worst single session **10.9 MiB**, and 51 of 73 logs exceeded the
|
|
23
|
+
* "cap" that was supposedly in force. See `doc/06-audits/0050`.
|
|
24
|
+
*
|
|
25
|
+
* ## The contract this module implements
|
|
26
|
+
*
|
|
27
|
+
* 1. **Never read more than `cap` bytes of any one file.** The read is a positioned `read(2)` off a
|
|
28
|
+
* descriptor, so the file's size is irrelevant to peak memory.
|
|
29
|
+
* 2. **The fallback is bounded too.** A slice that ends inside a zstd frame can be rejected by the
|
|
30
|
+
* decoder; the retry asks for a larger head (`retryBytes`), never for the whole file.
|
|
31
|
+
* 3. **Failure is `undefined`, never a throw.** A directory with no log, a corrupt frame, or an
|
|
32
|
+
* unparsable first line all yield `undefined`, so the inventory classifies the session as
|
|
33
|
+
* `unreadable` instead of guessing from the lossy directory name (ADR-0009).
|
|
34
|
+
*
|
|
35
|
+
* @module dsh-remote-workspace/integration/session-header
|
|
36
|
+
*/
|
|
37
|
+
import type { SessionHeader } from '../app/session-admin-service.ts';
|
|
38
|
+
/**
|
|
39
|
+
* Bytes read from the head of a session log on the first attempt.
|
|
40
|
+
*
|
|
41
|
+
* Chosen from measurement, not taste. On the reference machine's real store (73 sessions, `0050`):
|
|
42
|
+
*
|
|
43
|
+
* - the largest **header line** is **309 B** of UTF-8 (p50 279 B);
|
|
44
|
+
* - the largest compressed frame carrying a lone header is **253 B**;
|
|
45
|
+
* - the smallest head that recovered **all 73** headers is **4 KiB**.
|
|
46
|
+
*
|
|
47
|
+
* 64 KiB is therefore **16x** the empirically sufficient head and **212x** the largest header line, while
|
|
48
|
+
* being 4x smaller than the previous (and, as it turned out, entirely non-binding) 256 KiB constant.
|
|
49
|
+
*/
|
|
50
|
+
export declare const SESSION_HEADER_READ_BYTES: number;
|
|
51
|
+
/**
|
|
52
|
+
* Bytes read on the retry, when the first slice ended inside the frame.
|
|
53
|
+
*
|
|
54
|
+
* Still bounded, and still independent of the file's real size — that is the property the old fallback
|
|
55
|
+
* broke. A log whose header genuinely cannot be recovered from 1 MiB is reported `unreadable`.
|
|
56
|
+
*/
|
|
57
|
+
export declare const SESSION_HEADER_RETRY_BYTES: number;
|
|
58
|
+
/** The I/O this reader needs; injected so a test can pin the byte bounds exactly (AGENTS.md 红线 3). */
|
|
59
|
+
export interface SessionHeaderIo {
|
|
60
|
+
/** List one directory's entry names. */
|
|
61
|
+
readonly listDir: (dir: string) => Promise<readonly string[]>;
|
|
62
|
+
/**
|
|
63
|
+
* Read at most `maxBytes` from the **head** of one file.
|
|
64
|
+
*
|
|
65
|
+
* An implementation must not read the whole file and slice: that is precisely the defect this module
|
|
66
|
+
* exists to prevent. It may return fewer bytes (a short file, or a short read).
|
|
67
|
+
*/
|
|
68
|
+
readonly readHead: (path: string, maxBytes: number) => Promise<Uint8Array>;
|
|
69
|
+
/** Decompress one zstd frame (or frame prefix) to UTF-8 text. */
|
|
70
|
+
readonly decompress: (bytes: Uint8Array) => string;
|
|
71
|
+
/** Head caps to try, in order. Defaults to `[SESSION_HEADER_READ_BYTES, SESSION_HEADER_RETRY_BYTES]`. */
|
|
72
|
+
readonly caps?: readonly number[];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Parse the first JSONL line into the header fields this feature needs.
|
|
76
|
+
*
|
|
77
|
+
* @param text - decompressed log text (only the head is present).
|
|
78
|
+
* @returns the header, or `undefined` when the first line is absent or not a session record.
|
|
79
|
+
*/
|
|
80
|
+
export declare function parseSessionHeaderLine(text: string): SessionHeader | undefined;
|
|
81
|
+
/**
|
|
82
|
+
* Build the header reader.
|
|
83
|
+
*
|
|
84
|
+
* Every failure is reported as `undefined` rather than thrown: an inventory must survive one unreadable
|
|
85
|
+
* session.
|
|
86
|
+
*
|
|
87
|
+
* @param io - directory listing, bounded head read and decompressor.
|
|
88
|
+
* @returns a reader from one session directory to its header, or `undefined` when unreadable.
|
|
89
|
+
*/
|
|
90
|
+
export declare function createSessionHeaderReader(io: SessionHeaderIo): (dir: string) => Promise<SessionHeader | undefined>;
|
|
91
|
+
/**
|
|
92
|
+
* Read at most `maxBytes` from the head of a file, off a descriptor.
|
|
93
|
+
*
|
|
94
|
+
* A positioned read is what makes the bound real: `readFile` would materialise the entire log first.
|
|
95
|
+
*
|
|
96
|
+
* @param path - file to read.
|
|
97
|
+
* @param maxBytes - hard ceiling on the bytes returned.
|
|
98
|
+
* @returns the bytes actually read (possibly fewer, possibly none).
|
|
99
|
+
*/
|
|
100
|
+
export declare function readHeadFromDisk(path: string, maxBytes: number): Promise<Uint8Array>;
|
|
101
|
+
/**
|
|
102
|
+
* The production I/O: real `readdir`, a **bounded** head read and the real zstd decoder.
|
|
103
|
+
*
|
|
104
|
+
* Exported so a test can pin the one property the reader's whole contract rests on — that the production
|
|
105
|
+
* `readHead` respects its cap. Exercising the assembled reader through an *injected* fake cannot catch a
|
|
106
|
+
* regression in this wiring, because the fake stays bounded either way; a mutation experiment during
|
|
107
|
+
* audit 0050 exposed exactly that gap.
|
|
108
|
+
*
|
|
109
|
+
* @returns the production I/O bundle.
|
|
110
|
+
*/
|
|
111
|
+
export declare function createDiskSessionHeaderIo(): SessionHeaderIo;
|
|
112
|
+
/**
|
|
113
|
+
* The production reader: real `readdir`, a bounded head read and the real zstd decoder.
|
|
114
|
+
*
|
|
115
|
+
* @returns a reader from one session directory to its header, or `undefined` when unreadable.
|
|
116
|
+
*/
|
|
117
|
+
export declare function createDiskSessionHeaderReader(): (dir: string) => Promise<SessionHeader | undefined>;
|
|
118
|
+
//# sourceMappingURL=session-header.d.ts.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the two human labels a session listing needs: the session's own title and the display name of the
|
|
3
|
+
* workspace that owns its directory.
|
|
4
|
+
*
|
|
5
|
+
* Both live in **optional platform services**, and both are read through a structural context shape rather
|
|
6
|
+
* than a Cordis import, so this module is unit-testable with a plain object and the domain layer keeps its
|
|
7
|
+
* no-`@deepseek-ai/*` rule.
|
|
8
|
+
*
|
|
9
|
+
* Why this is its own module: these are the only two places where the session feature reaches into the
|
|
10
|
+
* platform, and the composition root that calls them cannot be exercised by a test. Anything decision-like
|
|
11
|
+
* that lands there is untested by construction (审计 0046 记录了同款教训), so the decisions live here.
|
|
12
|
+
*
|
|
13
|
+
* @module dsh-remote-workspace/integration/session-labels
|
|
14
|
+
*/
|
|
15
|
+
import type { SessionHeader } from '../app/session-admin-service.ts';
|
|
16
|
+
/** The one context method these resolvers need, kept structural so no Cordis import is required. */
|
|
17
|
+
export interface OptionalServiceLookup {
|
|
18
|
+
get(name: string): unknown;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolve a session's generated title through the platform's projection cache.
|
|
22
|
+
*
|
|
23
|
+
* The title is a **projection**, never header metadata: the header's own `title` field is always absent (all
|
|
24
|
+
* 88 sessions on the reference machine had none), which is why the panel could only ever show a uuid. The
|
|
25
|
+
* cache is the sanctioned zero-I/O listing read — `cachedSnapshot` validates the stored row against the
|
|
26
|
+
* header's lifecycle identity (`formatVersion`, `createdAt`, `cwd`, `isSeeded`), so a title from a different
|
|
27
|
+
* lifecycle that reused the same id can never be served.
|
|
28
|
+
*
|
|
29
|
+
* Every failure is benign and yields `undefined`: a session persisted before the cache was composed has no
|
|
30
|
+
* row until it is opened once, and the row's unit version must match the live title projection. The caller
|
|
31
|
+
* then falls back to the workspace, and finally to the id.
|
|
32
|
+
*
|
|
33
|
+
* The service is looked up **per call**, not once at activation: on this runtime an optional service can
|
|
34
|
+
* still be absent while a plugin entry activates (`session-route-wiring.ts` documents the same trap for
|
|
35
|
+
* `webServer`), and a one-time check would then disable titles for the whole process lifetime.
|
|
36
|
+
*
|
|
37
|
+
* @param ctx - anything with cordis's `get`; usually the plugin context.
|
|
38
|
+
* @returns a resolver from header to title; it yields `undefined` whenever the service or the row is absent.
|
|
39
|
+
*/
|
|
40
|
+
export declare function createSessionTitleResolver(ctx: OptionalServiceLookup): (header: SessionHeader) => string | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Resolve a `cwd` to its registered workspace's display title.
|
|
43
|
+
*
|
|
44
|
+
* The registry is the platform's own durable mapping from directory to a human name (`libaw_vep`,
|
|
45
|
+
* `a733_android13`), which is exactly the label a session list needs and the one the sidebar shows. A
|
|
46
|
+
* directory no workspace owns yields `undefined`; the client then uses the path's leaf name, so a session
|
|
47
|
+
* outside a workspace is still identifiable.
|
|
48
|
+
*
|
|
49
|
+
* Like {@link createSessionTitleResolver}, the service is looked up **per call** so a late-arriving registry
|
|
50
|
+
* still works.
|
|
51
|
+
*
|
|
52
|
+
* @param ctx - anything with cordis's `get`; usually the plugin context.
|
|
53
|
+
* @returns a resolver from `cwd` to workspace title; it yields `undefined` whenever none applies.
|
|
54
|
+
*/
|
|
55
|
+
export declare function createWorkspaceTitleResolver(ctx: OptionalServiceLookup): (cwd: string | undefined) => string | undefined;
|
|
56
|
+
//# sourceMappingURL=session-labels.d.ts.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Own the session-administration route (`/api/remote-workspace/sessions`) on the browser carrier.
|
|
3
|
+
*
|
|
4
|
+
* This is the same **deferred carrier** dance as `roots-wiring.ts`, and for the same measured reason: on the
|
|
5
|
+
* packaged 0.2.0-rc.2 runtime `ctx.get('webServer')` is `undefined` while a plugin entry activates (the
|
|
6
|
+
* carrier row is constructed later), so a `ctx.get` lookup silently skips registration and every request
|
|
7
|
+
* answers `http-404` from the SPA fallback while the plugin looks perfectly healthy.
|
|
8
|
+
* `ctx.inject(['webServer'], ...)` resolves the service when it appears.
|
|
9
|
+
*
|
|
10
|
+
* A profile without a carrier (headless) is **not** an error: the plugin works, the panel simply has no
|
|
11
|
+
* session data channel. The reason is collected as a warning the entry logs once `ctx.logger` resolves.
|
|
12
|
+
*
|
|
13
|
+
* @module dsh-remote-workspace/integration/session-route-wiring
|
|
14
|
+
*/
|
|
15
|
+
import { type SessionApi, type SessionApiDeps } from './session-api.ts';
|
|
16
|
+
/** The browser HTTP carrier, narrowed to the one method this wiring calls. */
|
|
17
|
+
export interface SessionRouteHost {
|
|
18
|
+
register(route: {
|
|
19
|
+
readonly kind: 'exact';
|
|
20
|
+
readonly path: string;
|
|
21
|
+
readonly handler: (req: never, res: never) => Promise<void>;
|
|
22
|
+
}): () => void;
|
|
23
|
+
}
|
|
24
|
+
/** The provider that hands out the carrier once it exists. */
|
|
25
|
+
export interface SessionRouteHostProvider {
|
|
26
|
+
inject(names: readonly string[], callback: (scoped: {
|
|
27
|
+
get(name: string): unknown;
|
|
28
|
+
}) => void | (() => void)): unknown;
|
|
29
|
+
}
|
|
30
|
+
/** Everything the wiring needs from the entry. */
|
|
31
|
+
export interface SessionRouteWiringDeps extends SessionApiDeps {
|
|
32
|
+
readonly webServerProvider?: SessionRouteHostProvider | undefined;
|
|
33
|
+
}
|
|
34
|
+
/** What the entry holds on to. */
|
|
35
|
+
export interface SessionRouteWiring {
|
|
36
|
+
readonly api: SessionApi;
|
|
37
|
+
/** Human-readable reasons the route may not be live; logged after activation. */
|
|
38
|
+
readonly routeWarnings: () => readonly string[];
|
|
39
|
+
/** Whether the deferred carrier callback ever ran. */
|
|
40
|
+
readonly injectRan: () => boolean;
|
|
41
|
+
readonly dispose: () => void;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Register the route as soon as the carrier exists, and again if it ever comes back.
|
|
45
|
+
*
|
|
46
|
+
* @param deps - the API seams plus the carrier provider.
|
|
47
|
+
* @returns the wiring handle.
|
|
48
|
+
*/
|
|
49
|
+
export declare function createSessionRouteWiring(deps: SessionRouteWiringDeps): SessionRouteWiring;
|
|
50
|
+
//# sourceMappingURL=session-route-wiring.d.ts.map
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which session is asking — the identity channel the platform does not hand to `ctx.fs`.
|
|
3
|
+
*
|
|
4
|
+
* Why this exists. 「添加目录」 roots belong to **one conversation** (ADR-0013). The route table already refuses
|
|
5
|
+
* a root to another *workspace* (ADR-0012) by comparing the caller's cwd, but two conversations can run in the
|
|
6
|
+
* same workspace directory, and `FsProvider`'s methods receive only `{ cwd?, signal? }`
|
|
7
|
+
* (`@deepseek-ai/dsh-fs` 0.2.1-rc.2, `lib/types/index.d.ts`) — at that seam those two conversations are
|
|
8
|
+
* indistinguishable, so a directory one of them added stays reachable by the other.
|
|
9
|
+
*
|
|
10
|
+
* The identity is available one level up. Every tool execution carries its agent, and the session id is
|
|
11
|
+
* `exec.agent.session.header.id` — the platform itself reads exactly that field (`dsh-tool-fs-search`:
|
|
12
|
+
* `const sessionId = exec.agent?.session.header.id`) and hands the session to the sandbox policy
|
|
13
|
+
* (`dsh-tools`: `policy.resolve({ session: exec.agent.session })`). This module carries it **down** to the
|
|
14
|
+
* filesystem seam: the dispatcher resolves the tool definition once per call
|
|
15
|
+
* (`ctx.tools.resolveExecution(name, agent, …)`) and then invokes that definition's `execute`, so wrapping the
|
|
16
|
+
* resolver lets the body run inside an `AsyncLocalStorage` context. Every `ctx.fs.resolve` / `lstat` issued
|
|
17
|
+
* from the body — directly or after any number of awaits — reads it back through {@link currentSessionId}.
|
|
18
|
+
*
|
|
19
|
+
* Why the store lives on `globalThis`. The host half is built as several independent ESM bundles
|
|
20
|
+
* (`lib/index.js`, `lib/fs.js`, `lib/file-references.js`, …; see `tools/build-host.mjs`), so a module-level
|
|
21
|
+
* `AsyncLocalStorage` would be **one instance per bundle**: the wrapper installed by `index.js` would write into
|
|
22
|
+
* a store that `fs.js` never reads. A `Symbol.for` slot is the only thing the bundles provably share.
|
|
23
|
+
*
|
|
24
|
+
* Degradation. Everything here is best-effort by construction: when the tools service does not expose
|
|
25
|
+
* `resolveExecution` (a future core renames it), {@link installToolSessionScope} returns `false` and the plugin
|
|
26
|
+
* keeps working with the workspace-level gate alone — narrower isolation, never a broken filesystem.
|
|
27
|
+
*
|
|
28
|
+
* @module dsh-over-ssh/integration/session-scope
|
|
29
|
+
*/
|
|
30
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
31
|
+
import type { SessionIdentity } from '../domain/roots/session-roots.ts';
|
|
32
|
+
/** The per-call context carried across the filesystem seam. */
|
|
33
|
+
export interface SessionScope {
|
|
34
|
+
/** The calling session, or `undefined` for an agentless call (a CLI, a replay). */
|
|
35
|
+
readonly sessionId: string | undefined;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The session the current call is running for.
|
|
39
|
+
*
|
|
40
|
+
* @returns the session id, or `undefined` when the call is not inside a session's tool execution.
|
|
41
|
+
*/
|
|
42
|
+
export declare function currentSessionId(): string | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Run one call inside a session's scope.
|
|
45
|
+
*
|
|
46
|
+
* Exported for tests and for callers that already know the identity (the plugin's own `remote_*` tools get it
|
|
47
|
+
* from their execution context directly and do not need this).
|
|
48
|
+
*
|
|
49
|
+
* @param sessionId - the session the call belongs to, or `undefined` for an agentless call.
|
|
50
|
+
* @param run - the work to run inside the scope.
|
|
51
|
+
* @returns whatever `run` returns.
|
|
52
|
+
*/
|
|
53
|
+
export declare function runInSession<T>(sessionId: string | undefined, run: () => T): T;
|
|
54
|
+
/**
|
|
55
|
+
* Fork lineage of one session, nearest first, read from the platform's session service.
|
|
56
|
+
*
|
|
57
|
+
* A fork inherits its parent's 「添加目录」 entries (ADR-0013): the durable header carries `parentSession`, so the
|
|
58
|
+
* chain is walked here rather than copied into the roots file at fork time — a copy would have to be kept in
|
|
59
|
+
* sync with every later add and remove on either side, and would go stale the moment either session acts.
|
|
60
|
+
*
|
|
61
|
+
* Bounded and cycle-safe: the walk stops after {@link MAX_FORK_DEPTH} hops or when it revisits an id, so a
|
|
62
|
+
* corrupt header cannot spin. Degrades to `[]` on any doubt — the caller then behaves exactly as before
|
|
63
|
+
* ADR-0013 (no inheritance), which is the safe direction.
|
|
64
|
+
*
|
|
65
|
+
* @param ctx - a context that may carry the session service.
|
|
66
|
+
* @param sessionId - the asking session.
|
|
67
|
+
* @returns the ancestor ids, nearest first.
|
|
68
|
+
*/
|
|
69
|
+
export declare function readSessionAncestors(ctx: Context, sessionId: string): readonly string[];
|
|
70
|
+
/**
|
|
71
|
+
* The identity to hand the domain rule for one session.
|
|
72
|
+
*
|
|
73
|
+
* @param ctx - a context that may carry the session service.
|
|
74
|
+
* @param sessionId - the asking session, or `undefined` for an agentless call.
|
|
75
|
+
* @returns the identity, or `{}` when there is no session.
|
|
76
|
+
*/
|
|
77
|
+
export declare function sessionIdentityOf(ctx: Context, sessionId: string | undefined): SessionIdentity;
|
|
78
|
+
/** The slice of a tool execution this module reads; structural, so no platform type is imported. */
|
|
79
|
+
export interface ToolExecutionLike {
|
|
80
|
+
readonly agent?: {
|
|
81
|
+
readonly session?: {
|
|
82
|
+
readonly header?: {
|
|
83
|
+
readonly id?: string;
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
} | undefined;
|
|
87
|
+
}
|
|
88
|
+
/** The slice of a tool definition this module wraps. */
|
|
89
|
+
export interface ToolDefinitionLike {
|
|
90
|
+
readonly execute?: ((args: unknown, exec: ToolExecutionLike) => unknown) | undefined;
|
|
91
|
+
}
|
|
92
|
+
/** The slice of the platform's tools service this module patches. */
|
|
93
|
+
export interface ToolsServiceLike {
|
|
94
|
+
resolveExecution?: ((name: string, agent: unknown, parent: boolean) => ToolDefinitionLike | undefined) | undefined;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The session id one tool execution belongs to.
|
|
98
|
+
*
|
|
99
|
+
* @param exec - the execution context handed to `execute`.
|
|
100
|
+
* @returns the session id, or `undefined` when the call has no agent.
|
|
101
|
+
*/
|
|
102
|
+
export declare function sessionIdOf(exec: ToolExecutionLike | undefined): string | undefined;
|
|
103
|
+
/**
|
|
104
|
+
* Install the session scope on the platform's tools service.
|
|
105
|
+
*
|
|
106
|
+
* Idempotent, and safe to call before or after other plugins register tools: the patch is on the **resolver**,
|
|
107
|
+
* not on `register`, so tools registered earlier are covered too.
|
|
108
|
+
*
|
|
109
|
+
* @param tools - the `tools` service, or anything else when the platform did not provide one.
|
|
110
|
+
* @returns whether the scope is installed (`false` means the plugin runs with workspace-level isolation only).
|
|
111
|
+
*/
|
|
112
|
+
export declare function installToolSessionScope(tools: unknown): boolean;
|
|
113
|
+
//# sourceMappingURL=session-scope.d.ts.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The session-administration tool surface: `remote_session_list`, `remote_session_delete`, `remote_session_restore`.
|
|
3
|
+
*
|
|
4
|
+
* These operate on the **platform's session store**, not on a remote root, so they take no connection or root:
|
|
5
|
+
* a session is identified by its id, and its location is derived from its own header (`cwd` → `projectKey`).
|
|
6
|
+
* That is also why they do not go through `ctx.fs` — the store is not a workspace file.
|
|
7
|
+
*
|
|
8
|
+
* **Irreversible calls are gated.** `mode: "permanent"` goes through the platform's pre-execute waterfall and is
|
|
9
|
+
* answered with `ask`; when the composition has no approval surface the registry turns that into a denial (fail
|
|
10
|
+
* closed). Recycle-mode deletion is an ordinary call: it is recoverable.
|
|
11
|
+
*
|
|
12
|
+
* @module dsh-remote-workspace/integration/session-tools
|
|
13
|
+
*/
|
|
14
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
15
|
+
import { type ToolDefinition } from '@deepseek-ai/dsh-tools';
|
|
16
|
+
import type { DeleteSessionResult, SessionAdminService } from '../app/session-admin-service.ts';
|
|
17
|
+
import type { SessionTrashEntry } from '../domain/session-admin.ts';
|
|
18
|
+
import type { SessionRecord } from '../domain/session-admin.ts';
|
|
19
|
+
/** Format one inventory record as a human-readable line. */
|
|
20
|
+
export declare function formatSession(record: SessionRecord): string;
|
|
21
|
+
/** Human summary of a session deletion. */
|
|
22
|
+
export declare function formatSessionDelete(result: DeleteSessionResult): string;
|
|
23
|
+
/** Human summary of the bin. */
|
|
24
|
+
export declare function formatSessionBin(entries: readonly SessionTrashEntry[]): string;
|
|
25
|
+
/**
|
|
26
|
+
* Build the session-administration definitions.
|
|
27
|
+
*
|
|
28
|
+
* @param service - the session administration service.
|
|
29
|
+
* @returns definitions ready for `ctx.tools.register`.
|
|
30
|
+
*/
|
|
31
|
+
export declare function sessionToolDefinitions(service: SessionAdminService): ToolDefinition[];
|
|
32
|
+
/**
|
|
33
|
+
* Gate irreversible session operations behind the platform's approval surface.
|
|
34
|
+
*
|
|
35
|
+
* Denial by default is deliberate: these calls destroy conversation history that cannot be regenerated, and a
|
|
36
|
+
* composition without an approval surface must refuse rather than proceed.
|
|
37
|
+
*
|
|
38
|
+
* @param ctx - host context with the `tools` service.
|
|
39
|
+
* @returns a disposer removing the gate.
|
|
40
|
+
*/
|
|
41
|
+
export declare function sessionDestructionGate(ctx: Context): () => void;
|
|
42
|
+
/** Register the three tools and the gate. */
|
|
43
|
+
export declare function registerSessionTools(ctx: Context, service: SessionAdminService): () => void;
|
|
44
|
+
//# sourceMappingURL=session-tools.d.ts.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strict root-level argument validation for this plugin's tool surface.
|
|
3
|
+
*
|
|
4
|
+
* **Why this exists.** `defineTool` compiles the parameter map into an object-rooted JSON Schema that
|
|
5
|
+
* carries `properties` and `required` but **no `additionalProperties`**, and the core validator only
|
|
6
|
+
* reports an undeclared property when that keyword is explicitly `false`
|
|
7
|
+
* (`@deepseek-ai/dsh-tools` `json-schema.ts`, the `object` case). A model that writes `stdin`, `shell`,
|
|
8
|
+
* `waitMs`, `mode` or `executable` therefore gets a **successful** call in which the field was silently
|
|
9
|
+
* dropped — the most dangerous failure mode there is, because the caller believes the setting took effect.
|
|
10
|
+
*
|
|
11
|
+
* The plugin cannot close the core's implicit root, so it enforces closure itself on the documented
|
|
12
|
+
* pre-dispatch extension point (`tools/pre-execute`), the same hook `permanentDeleteGate` already uses.
|
|
13
|
+
*
|
|
14
|
+
* **One message, everything wrong at once.** The core reports missing/typed arguments from inside
|
|
15
|
+
* `execute`, but a pre-execute gate runs *before* that, so this gate can name every unknown field and every
|
|
16
|
+
* missing required field in a single refusal instead of sending the caller back one field at a time.
|
|
17
|
+
*
|
|
18
|
+
* @module dsh-remote-workspace/integration/strict-arguments
|
|
19
|
+
*/
|
|
20
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
21
|
+
/**
|
|
22
|
+
* The name prefix every tool of this plugin uses.
|
|
23
|
+
*
|
|
24
|
+
* The gate is scoped by prefix rather than by a copied name list: a list would drift the moment a tool is
|
|
25
|
+
* added, and this plugin's namespace is `remote_*`. `tests/strict-arguments.test.ts` pins that every tool
|
|
26
|
+
* name this plugin registers starts with the prefix, so the assumption cannot rot silently.
|
|
27
|
+
*/
|
|
28
|
+
export declare const STRICT_TOOL_PREFIX = "remote_";
|
|
29
|
+
/** Whether a tool name belongs to this plugin's surface. */
|
|
30
|
+
export declare function isStrictTool(name: string): boolean;
|
|
31
|
+
/** What one argument check found. */
|
|
32
|
+
export interface ArgumentViolations {
|
|
33
|
+
/** Fields the caller sent that the tool does not declare. */
|
|
34
|
+
readonly unknown: readonly string[];
|
|
35
|
+
/** Fields the tool requires that the caller did not send. */
|
|
36
|
+
readonly missing: readonly string[];
|
|
37
|
+
/** Every field the tool does declare, in declaration order. */
|
|
38
|
+
readonly declared: readonly string[];
|
|
39
|
+
}
|
|
40
|
+
/** The parts of one registered parameter schema this gate reads. */
|
|
41
|
+
export interface StrictParameterSchema {
|
|
42
|
+
readonly properties?: Readonly<Record<string, unknown>> | undefined;
|
|
43
|
+
readonly required?: readonly string[] | undefined;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Compare a call's arguments against the tool's own declared parameters.
|
|
47
|
+
*
|
|
48
|
+
* Only the **root** is checked: a nested open map (such as `remote_exec`'s `env`) keeps whatever openness it
|
|
49
|
+
* declared, because closing it would reject the very keys that field exists to carry.
|
|
50
|
+
*
|
|
51
|
+
* A non-object argument (or a missing schema) yields no violations here: the core's own validation owns
|
|
52
|
+
* those cases, and reporting them twice would produce two different messages for one mistake.
|
|
53
|
+
*
|
|
54
|
+
* @param schema - the registered parameter schema (`properties` / `required`).
|
|
55
|
+
* @param args - the model-supplied arguments, however malformed.
|
|
56
|
+
* @returns the unknown fields, the missing required fields, and the declared field list.
|
|
57
|
+
*/
|
|
58
|
+
export declare function argumentViolations(schema: StrictParameterSchema | undefined, args: unknown): ArgumentViolations;
|
|
59
|
+
/** Whether anything was found worth refusing. */
|
|
60
|
+
export declare function hasViolations(violations: ArgumentViolations): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* The model-facing refusal.
|
|
63
|
+
*
|
|
64
|
+
* It names what was rejected **and** what is accepted: the caller's next attempt should be a correction, not
|
|
65
|
+
* a guess. "Unknown field" alone would leave the model trying spellings one at a time.
|
|
66
|
+
*
|
|
67
|
+
* @param toolName - the tool that was called.
|
|
68
|
+
* @param violations - what the check found.
|
|
69
|
+
* @returns one actionable sentence.
|
|
70
|
+
*/
|
|
71
|
+
export declare function formatArgumentViolations(toolName: string, violations: ArgumentViolations): string;
|
|
72
|
+
/**
|
|
73
|
+
* Register the gate.
|
|
74
|
+
*
|
|
75
|
+
* @param ctx - host context with the `tools` service.
|
|
76
|
+
* @returns a disposer removing the listener (idempotent).
|
|
77
|
+
*/
|
|
78
|
+
export declare function strictArgumentsGate(ctx: Context): () => void;
|
|
79
|
+
//# sourceMappingURL=strict-arguments.d.ts.map
|