dsh-over-ssh 0.1.0 → 0.1.1
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 +10 -10
- package/agents/agent-manifest.json +1 -1
- package/lib/client.js +363 -314
- package/lib/file-references.js +80 -35
- package/lib/fs.js +158 -75
- package/lib/index.js +4103 -3397
- package/lib/terminal-wrapper.js +9 -4
- package/lib/types/src/config.d.ts +80 -9
- package/lib/types/src/file-references.d.ts +0 -1
- package/lib/types/src/host/app/backend.d.ts +9 -1
- 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 +0 -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/ssh/ssh2-transport.d.ts +0 -1
- package/lib/types/src/host/infra/transport/transport-base.d.ts +0 -1
- package/lib/types/src/host/infra/transport/transport.d.ts +16 -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 +0 -2
- 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
|
@@ -1,336 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The session panel's React container.
|
|
3
|
-
*
|
|
4
|
-
* Deliberately thin: it renders what [`session-panel.ts`](./session-panel.ts) computed and owns only the
|
|
5
|
-
* interaction state (selection, the in-flight flag, the last outcome, the confirmation dialog). All the
|
|
6
|
-
* parsing, validation and HTTP work lives in the model module, which is why this file has almost no logic to
|
|
7
|
-
* test — and why the logic that matters is tested without a browser.
|
|
8
|
-
*
|
|
9
|
-
* **Nothing destructive happens on render.** Deleting requires a click; an irreversible delete or a purge
|
|
10
|
-
* additionally requires the confirmation **dialog** (a modal card, portalled to the page body), and the model only
|
|
11
|
-
* sends `confirm: true` after that.
|
|
12
|
-
*
|
|
13
|
-
* @module dsh-over-ssh/client/session-panel-view
|
|
14
|
-
*/
|
|
15
|
-
import { type ReactNode } from 'react';
|
|
16
|
-
import type { MessageKey } from '../core/message.ts';
|
|
17
|
-
import { type BinRowView, type SessionGroup, type SessionPanelIo, type SessionPanelSnapshot, type SessionRowView } from './session-panel.ts';
|
|
18
|
-
/** Text the component shows; the caller supplies the locale's dictionary. */
|
|
19
|
-
export interface SessionPanelCopy {
|
|
20
|
-
readonly title: string;
|
|
21
|
-
readonly description: string;
|
|
22
|
-
readonly unavailable: string;
|
|
23
|
-
/**
|
|
24
|
-
* "Still reading the inventory."
|
|
25
|
-
*
|
|
26
|
-
* A read in flight used to render as the red {@link SessionPanelCopy.unavailable} error: `emptySnapshot()` is
|
|
27
|
-
* the initial state **and** the failure, and its `available` is `false`. The panel therefore has three statuses
|
|
28
|
-
* where it had two — reading, failed (with the reason), and the host's own "no session channel" answer
|
|
29
|
-
* (audit 2026-11-01b ui P1-3).
|
|
30
|
-
*/
|
|
31
|
-
readonly loading: string;
|
|
32
|
-
/** The read failed; the reason comes from `readSessionPanel`, which keeps it. */
|
|
33
|
-
readonly loadFailed: (reason: string) => string;
|
|
34
|
-
/** The inventory holds no session at all (an **answer**, not a read in flight). */
|
|
35
|
-
readonly sessionsEmpty: string;
|
|
36
|
-
readonly sessionsHeading: string;
|
|
37
|
-
readonly binHeading: string;
|
|
38
|
-
readonly binEmpty: string;
|
|
39
|
-
readonly selectAll: string;
|
|
40
|
-
readonly clearSelection: string;
|
|
41
|
-
readonly deleteRecycle: string;
|
|
42
|
-
readonly deletePermanent: string;
|
|
43
|
-
readonly restore: string;
|
|
44
|
-
readonly purge: string;
|
|
45
|
-
readonly sweep: string;
|
|
46
|
-
readonly refresh: string;
|
|
47
|
-
readonly close: string;
|
|
48
|
-
/** Badge of a session that is actually running (the green one). */
|
|
49
|
-
readonly liveBadge: string;
|
|
50
|
-
/** Badge of a session the host merely has loaded (opened) — neutral, never green. */
|
|
51
|
-
readonly openBadge: string;
|
|
52
|
-
readonly subsessionBadge: string;
|
|
53
|
-
readonly unreadableBadge: string;
|
|
54
|
-
readonly confirmTitle: string;
|
|
55
|
-
/**
|
|
56
|
-
* The confirmation sentence for an irreversible session delete.
|
|
57
|
-
*
|
|
58
|
-
* A function of the id list for the same reason as {@link SessionPanelCopy.confirmWorkspaceBody}: the
|
|
59
|
-
* sentence has to **name** what is about to be destroyed, and only the dictionary knows the language. It was a
|
|
60
|
-
* plain string here while the sentence itself was spelled in Chinese inside this file, so the English UI read
|
|
61
|
-
* 「永久删除会话 …,不可恢复。」 (audit 2026-10-31 client.md §2.3).
|
|
62
|
-
*/
|
|
63
|
-
readonly confirmBody: (targets: string) => string;
|
|
64
|
-
/** The same, for one recycle-bin entry — `purge` speaks of bin entries, not of sessions. */
|
|
65
|
-
readonly confirmPurgeBody: (targets: string) => string;
|
|
66
|
-
readonly confirmCancel: string;
|
|
67
|
-
readonly confirmAccept: string;
|
|
68
|
-
/**
|
|
69
|
-
* The extra sentence the permanent-delete confirmation adds when the selection contains sessions the host
|
|
70
|
-
* still has loaded: they are deletable, but a later write recreates their log directory.
|
|
71
|
-
*/
|
|
72
|
-
readonly runningWarning: (count: number) => string;
|
|
73
|
-
/** Delete every session of one workspace; the label on the per-workspace button. */
|
|
74
|
-
readonly deleteWorkspace: string;
|
|
75
|
-
/** Confirmation sentence for an irreversible workspace delete; names the directory. */
|
|
76
|
-
readonly confirmWorkspaceBody: (cwd: string) => string;
|
|
77
|
-
/** How many sub-sessions the list is hiding, and why they are not listed. */
|
|
78
|
-
readonly subsessionsHidden: (count: number) => string;
|
|
79
|
-
/**
|
|
80
|
-
* The label of the platform's no-workspace bucket.
|
|
81
|
-
*
|
|
82
|
-
* `groupByWorkspace` leaves the label empty for it (`session-panel.ts` no longer spells a sentence), so the
|
|
83
|
-
* substitution happens here, where the dictionary is.
|
|
84
|
-
*/
|
|
85
|
-
readonly workspaceUnknown: string;
|
|
86
|
-
/** Resolve one deferred message: the model's action results come out as keys, not as sentences. */
|
|
87
|
-
readonly resolve: (message: MessageKey) => string;
|
|
88
|
-
readonly totals: (snapshot: SessionPanelSnapshot) => string;
|
|
89
|
-
}
|
|
90
|
-
export interface SessionPanelProps {
|
|
91
|
-
readonly copy: SessionPanelCopy;
|
|
92
|
-
readonly io: SessionPanelIo;
|
|
93
|
-
readonly close: () => void;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* The badge text for one row, plus the tone it should read in.
|
|
97
|
-
*
|
|
98
|
-
* Three states, and the first two must not be merged: **running** is the green 「运行中」, **loaded but idle**
|
|
99
|
-
* is the neutral 「已打开」. Rendering the second as the first is the bug the user reported with a screenshot
|
|
100
|
-
* (every session claimed to be running). Sub-sessions and unreadable rows keep their own neutral badges.
|
|
101
|
-
*
|
|
102
|
-
* Exported so the badge choice is a unit-testable decision instead of a line inside the render tree.
|
|
103
|
-
*
|
|
104
|
-
* @param row - the row to describe.
|
|
105
|
-
* @param copy - the dictionary.
|
|
106
|
-
* @returns the badge, or `undefined` for a plain idle row (no badge at all).
|
|
107
|
-
*/
|
|
108
|
-
export declare function badgeFor(row: SessionRowView, copy: SessionPanelCopy): {
|
|
109
|
-
readonly text: string;
|
|
110
|
-
readonly success: boolean;
|
|
111
|
-
} | undefined;
|
|
112
|
-
/** What one session row needs. */
|
|
113
|
-
export interface SessionRowProps {
|
|
114
|
-
readonly row: SessionRowView;
|
|
115
|
-
readonly copy: SessionPanelCopy;
|
|
116
|
-
/**
|
|
117
|
-
* Whether **this** row's checkbox is checked.
|
|
118
|
-
*
|
|
119
|
-
* A boolean, not the selection: that is the whole point of the memo boundary below. Passing the selection (a new
|
|
120
|
-
* array, or a new `Set`, on every toggle) made every row's props change on every click, so `memo` could never
|
|
121
|
-
* bail out — the whole list re-rendered while the user was selecting 88 sessions one at a time.
|
|
122
|
-
*/
|
|
123
|
-
readonly checked: boolean;
|
|
124
|
-
readonly onToggle: (id: string) => void;
|
|
125
|
-
/**
|
|
126
|
-
* Per-mount prefix for this row's checkbox `id` / `<label for>`.
|
|
127
|
-
*
|
|
128
|
-
* The row used to hard-code `session-pick-${row.id}`. The session ids are unique inside one mount, but the
|
|
129
|
-
* platform re-registers this section on every inject-face change (a locale switch does it), and for one commit
|
|
130
|
-
* the old and the new mount share the DOM — with one module-level prefix, two inputs carry the same `id` and
|
|
131
|
-
* every `<label for>` in that overlap points at whichever the browser found first (audit 2026-11-01b ui P3-6,
|
|
132
|
-
* the same defect the roots panel and the directory flow fixed with `useId()`).
|
|
133
|
-
*/
|
|
134
|
-
readonly pickIdPrefix: string;
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* One session row inside a workspace group — the pure template.
|
|
138
|
-
*
|
|
139
|
-
* Exported so the row's markup (and its cost) can be asserted by a test: the component below is a `memo` boundary,
|
|
140
|
-
* and this repository's offline renderer deliberately does not call components (it would run their hooks), so a
|
|
141
|
-
* test reaches the markup through this function instead.
|
|
142
|
-
*
|
|
143
|
-
* @param props - the row, the dictionary, its checked state, and the container's toggle.
|
|
144
|
-
* @returns the list item.
|
|
145
|
-
*/
|
|
146
|
-
export declare function sessionRowTemplate(props: SessionRowProps): ReactNode;
|
|
147
|
-
/**
|
|
148
|
-
* One row, behind a `memo` boundary.
|
|
149
|
-
*
|
|
150
|
-
* Why: a checkbox click used to re-render **every** row of the list. `selected` is a new array on each toggle, so
|
|
151
|
-
* every row's props changed and React re-ran all of them; with 88 sessions (the inventory the user reported) that
|
|
152
|
-
* is 88 rows of `createElement` per click, and "select all" then toggling one row repeats it. Behind this
|
|
153
|
-
* boundary a click re-renders **one** row: `row` and `copy` keep their identity, `onToggle` is `useCallback`-fixed
|
|
154
|
-
* in {@link SessionPanel}, and `checked` is a boolean that changes only for the row that was clicked.
|
|
155
|
-
*
|
|
156
|
-
* Measured on the reference machine (2026-10-31, `node`, no DOM; `client-session-panel-view.test.ts` pins the
|
|
157
|
-
* element counts). One row is **11** elements and the group's own chrome is 7, so the list is 975 elements at 88
|
|
158
|
-
* rows and 5507 at 500. What that costs, per checkbox click:
|
|
159
|
-
*
|
|
160
|
-
* | rows | elements | build with the boundary | row templates it skips |
|
|
161
|
-
* | ---- | -------- | ----------------------- | ---------------------- |
|
|
162
|
-
* | 88 | 975 | 0.16 ms | 0.86 ms |
|
|
163
|
-
* | 500 | 5507 | 0.79 ms | 4.52 ms |
|
|
164
|
-
*
|
|
165
|
-
* "build with the boundary" is what `SessionPanel` still does on every click (the groups re-create one element
|
|
166
|
-
* per row and `memo` compares props); "row templates it skips" is the work React no longer runs, because only the
|
|
167
|
-
* clicked row's `checked` changed. At the inventory the user reported (88 sessions) that is 5× less work per
|
|
168
|
-
* click, and the click is the interaction that happens 88 times in a row.
|
|
169
|
-
*
|
|
170
|
-
* **No virtualisation**, and that is a decision rather than an omission. The remaining cost at 500 rows is a full
|
|
171
|
-
* re-render's, and a full re-render now happens only on a snapshot refresh or a language switch — not on a click.
|
|
172
|
-
* A window would trade two behaviours this list depends on for that: the action bar is `position: sticky` inside
|
|
173
|
-
* the panel's own scroll box, and the checkboxes are Tab/arrow traversable, which a windowed list defeats as soon
|
|
174
|
-
* as a row scrolls out (its DOM node, and with it its place in the focus order, disappears). The scroll-position
|
|
175
|
-
* bookkeeping a window needs cannot be verified in this repository — there is no renderer here, only an offline
|
|
176
|
-
* `createElement` walker — so the trade would ship an unverifiable change to the two things users rely on.
|
|
177
|
-
*/
|
|
178
|
-
export declare const SessionRow: import("react").NamedExoticComponent<SessionRowProps>;
|
|
179
|
-
/** What one recycle-bin row needs from the container. */
|
|
180
|
-
export interface BinRowProps {
|
|
181
|
-
readonly row: BinRowView;
|
|
182
|
-
readonly copy: SessionPanelCopy;
|
|
183
|
-
readonly busy: boolean;
|
|
184
|
-
readonly onRestore: (entryId: string) => void;
|
|
185
|
-
readonly onPurge: (entryId: string) => void;
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* One recycle-bin row.
|
|
189
|
-
*
|
|
190
|
-
* The confirmation it raises is **not** rendered here any more: a modal card is drawn once by the container, so
|
|
191
|
-
* the row only reports the click. Placement stopped being a per-site question the moment the question became a
|
|
192
|
-
* dialog — there is exactly one card, in one place, whatever was clicked.
|
|
193
|
-
*
|
|
194
|
-
* @param props - the row, the dictionary, and the container's callbacks.
|
|
195
|
-
* @returns the list item.
|
|
196
|
-
*/
|
|
197
|
-
export declare function renderBinRow(props: BinRowProps): ReactNode;
|
|
198
|
-
/** What one workspace group needs from the container. */
|
|
199
|
-
export interface WorkspaceGroupProps {
|
|
200
|
-
readonly group: SessionGroup;
|
|
201
|
-
readonly copy: SessionPanelCopy;
|
|
202
|
-
readonly selected: readonly string[];
|
|
203
|
-
readonly busy: boolean;
|
|
204
|
-
readonly onToggle: (id: string) => void;
|
|
205
|
-
readonly onRemove: (action: WorkspaceDeleteAction) => void;
|
|
206
|
-
/** Per-mount checkbox-id prefix, forwarded to every row (see {@link SessionRowProps.pickIdPrefix}). */
|
|
207
|
-
readonly pickIdPrefix: string;
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* One workspace section: heading, its delete button, and its rows.
|
|
211
|
-
*
|
|
212
|
-
* The delete button reports the click; the confirmation itself is the container's single modal card.
|
|
213
|
-
*
|
|
214
|
-
* @param props - the group, the dictionary, and the container's callbacks.
|
|
215
|
-
* @returns the section element.
|
|
216
|
-
*/
|
|
217
|
-
export declare function renderWorkspaceGroup(props: WorkspaceGroupProps): ReactNode;
|
|
218
|
-
/**
|
|
219
|
-
* What the panel knows right now.
|
|
220
|
-
*
|
|
221
|
-
* Three states, not two. The component used to hold a `SessionPanelSnapshot` whose `available` flag doubled as
|
|
222
|
-
* "nothing has loaded yet" (it starts `false`) and as "the host has no session channel" — so the first paint of
|
|
223
|
-
* the settings section was a red 「宿主会话数据通道不可用(插件未装配或该 profile 无 webServer)」, and a real
|
|
224
|
-
* HTTP failure kept the same sentence while the reason was thrown away (audit 2026-11-01b ui P1-3).
|
|
225
|
-
*/
|
|
226
|
-
export type SessionPanelPhase = {
|
|
227
|
-
readonly kind: 'loading';
|
|
228
|
-
} | {
|
|
229
|
-
readonly kind: 'failed';
|
|
230
|
-
readonly error: string;
|
|
231
|
-
} | {
|
|
232
|
-
readonly kind: 'ready';
|
|
233
|
-
readonly snapshot: SessionPanelSnapshot;
|
|
234
|
-
};
|
|
235
|
-
/**
|
|
236
|
-
* The one status line above the inventory.
|
|
237
|
-
*
|
|
238
|
-
* Pure, so the three states are asserted without a renderer (this repository has none).
|
|
239
|
-
*
|
|
240
|
-
* @param phase - what the panel knows.
|
|
241
|
-
* @param copy - the active dictionary.
|
|
242
|
-
* @returns the notice, or `null` for an ordinary answer.
|
|
243
|
-
*/
|
|
244
|
-
export declare function sessionPanelStatus(phase: SessionPanelPhase, copy: SessionPanelCopy): ReactNode;
|
|
245
|
-
/**
|
|
246
|
-
* The inventory's placeholder — only for an **answer** with nothing in it.
|
|
247
|
-
*
|
|
248
|
-
* While a read is in flight (or failed) the status line is the whole story; claiming "there is nothing here" is
|
|
249
|
-
* the other half of the first-paint lie the status line exists to prevent.
|
|
250
|
-
*
|
|
251
|
-
* @param phase - what the panel knows.
|
|
252
|
-
* @param copy - the active dictionary.
|
|
253
|
-
* @returns the empty-state paragraph, or `null`.
|
|
254
|
-
*/
|
|
255
|
-
export declare function sessionInventoryEmpty(phase: SessionPanelPhase, copy: SessionPanelCopy): ReactNode;
|
|
256
|
-
/** Render the panel. */
|
|
257
|
-
export declare function SessionPanel({ copy, io, close }: SessionPanelProps): ReactNode;
|
|
258
|
-
/** One action awaiting confirmation. */
|
|
259
|
-
export type PendingAction = {
|
|
260
|
-
readonly kind: 'delete-permanent';
|
|
261
|
-
readonly ids: readonly string[];
|
|
262
|
-
} | {
|
|
263
|
-
readonly kind: 'delete-workspace';
|
|
264
|
-
readonly cwd: string;
|
|
265
|
-
} | {
|
|
266
|
-
readonly kind: 'purge';
|
|
267
|
-
readonly ids: readonly string[];
|
|
268
|
-
};
|
|
269
|
-
/** The workspace-delete confirmation, narrowed so call sites can read `cwd` without a cast. */
|
|
270
|
-
export type WorkspaceDeleteAction = Extract<PendingAction, {
|
|
271
|
-
kind: 'delete-workspace';
|
|
272
|
-
}>;
|
|
273
|
-
/** What the confirmation's two buttons call. */
|
|
274
|
-
export interface ConfirmationHandlers {
|
|
275
|
-
readonly accept: () => void;
|
|
276
|
-
readonly cancel: () => void;
|
|
277
|
-
}
|
|
278
|
-
/**
|
|
279
|
-
* The confirmation card: a modal dialog with the title, the sentence naming what will be destroyed, and the
|
|
280
|
-
* confirm/cancel pair.
|
|
281
|
-
*
|
|
282
|
-
* Extracted from the render tree for the reason the rest of this file states: this repository's client tests
|
|
283
|
-
* cannot render a hook-using component, so a template left inline is a template no test can reach.
|
|
284
|
-
*
|
|
285
|
-
* The card is a **dialog**, not an inline notice (2026-10-27): an inline card had to be found wherever it landed,
|
|
286
|
-
* while a dialog is where the user is looking and cannot be scrolled away from. Esc closes it, and the platform's
|
|
287
|
-
* own Esc handling must not also run — this card is the topmost surface while it is up.
|
|
288
|
-
*
|
|
289
|
-
* @param pending - the action awaiting confirmation.
|
|
290
|
-
* @param copy - the dictionary.
|
|
291
|
-
* @param handlers - what the two buttons call.
|
|
292
|
-
* @param titleId - the heading's id, referenced by `aria-labelledby`. **Required**, and supplied by the caller
|
|
293
|
-
* (`useId()` in {@link SessionPanel}): it used to default to a module-level constant, which is one id for every
|
|
294
|
-
* mount of this card — the "half-new-half-old" shape the id audit (2026-10-31 client.md §2.4) is about.
|
|
295
|
-
* @param loadedIdle - how many targets the host still has loaded ({@link loadedIdleIds}); adds the warning.
|
|
296
|
-
* @returns the dialog.
|
|
297
|
-
*/
|
|
298
|
-
export declare function renderConfirmationDialog(pending: PendingAction, copy: SessionPanelCopy, handlers: ConfirmationHandlers, titleId: string, loadedIdle?: number): ReactNode;
|
|
299
|
-
/**
|
|
300
|
-
* The confirmation a workspace's delete button should raise, or `undefined` when it must not be offered.
|
|
301
|
-
*
|
|
302
|
-
* Extracted from the JSX because this repository's tests cannot render a hook-using component (see the note
|
|
303
|
-
* in `styles.ts`): a decision left inline in the render tree is a decision no test can reach, and "which
|
|
304
|
-
* directory does this button act on" is exactly the kind of thing that silently becomes wrong — a stray
|
|
305
|
-
* empty string would ask the host to delete the sessions of no workspace at all.
|
|
306
|
-
*
|
|
307
|
-
* @param group - one workspace section.
|
|
308
|
-
* @returns the pending action, or `undefined` for the platform's no-cwd bucket (nothing to scope a delete to).
|
|
309
|
-
*/
|
|
310
|
-
export declare function workspaceDeleteAction(group: SessionGroup): WorkspaceDeleteAction | undefined;
|
|
311
|
-
/**
|
|
312
|
-
* The confirmation text for one pending irreversible action.
|
|
313
|
-
*
|
|
314
|
-
* Split out so the component's JSX stays readable and so the wording (which names what will be destroyed)
|
|
315
|
-
* is unit-testable. Every branch names its target: "确认永久删除" without saying *what* is the kind of
|
|
316
|
-
* dialog a user clicks through by reflex.
|
|
317
|
-
*
|
|
318
|
-
* @param pending - the action awaiting confirmation.
|
|
319
|
-
* @param copy - the dictionary.
|
|
320
|
-
* @param loadedIdle - how many of the targets the host still has loaded ({@link loadedIdleIds}); when
|
|
321
|
-
* non-zero the sentence gains the "its log directory may be recreated" warning.
|
|
322
|
-
* @returns the sentence shown above the buttons.
|
|
323
|
-
*/
|
|
324
|
-
export declare function confirmationBody(pending: PendingAction, copy: SessionPanelCopy, loadedIdle?: number): string;
|
|
325
|
-
/**
|
|
326
|
-
* The ids in a permanent-delete selection that the host still has loaded but that are not running.
|
|
327
|
-
*
|
|
328
|
-
* Those rows **are** deletable (that is the point of the running/live split), but deleting one is not quite
|
|
329
|
-
* final: if the host writes to that session again, its log directory is recreated. The confirmation has to say
|
|
330
|
-
* so, otherwise 「永久删除」 reads as a promise the host does not keep.
|
|
331
|
-
*
|
|
332
|
-
* @param pending - the action awaiting confirmation.
|
|
333
|
-
* @param rows - every row of the snapshot the selection was made in.
|
|
334
|
-
* @returns the matching ids, in the order of `rows`; empty for every other kind of pending action.
|
|
335
|
-
*/
|
|
336
|
-
export declare function loadedIdleIds(pending: PendingAction, rows: readonly SessionRowView[]): readonly string[];
|
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The session panel's whole decision surface: wire parsing, HTTP calls, validation and the view model.
|
|
3
|
-
* **No React, no DOM, no `@deepseek-ai/*`** — the component renders what this module returns (红线 2).
|
|
4
|
-
*
|
|
5
|
-
* ## Three rules this module owns
|
|
6
|
-
*
|
|
7
|
-
* 1. **`GET` is the only read.** The panel never mutates on render; every destructive action is an explicit
|
|
8
|
-
* user click that becomes a `POST`.
|
|
9
|
-
* 2. **Irreversible actions carry `confirm: true`.** The host route refuses `purge` and
|
|
10
|
-
* `mode: 'permanent'` without it, so the panel must send it — and it only does so after the user
|
|
11
|
-
* confirms. Two locks, not one.
|
|
12
|
-
* 3. **A sub-session is not an orphan.** The model keeps `subsession` rows visibly distinct and refuses to
|
|
13
|
-
* offer them a delete button, because deleting one alone is what the requirement warns against.
|
|
14
|
-
* 4. **「已打开」is not 「运行中」.** The host reports two separate facts: `live` (this session is loaded in
|
|
15
|
-
* this run, i.e. it was opened) and `running` (it has an active turn). The panel used to render the first
|
|
16
|
-
* one as the second, so every session the user had merely opened showed 「运行中」 — the reported bug.
|
|
17
|
-
* Only `running` blocks a deletion now; a loaded-but-idle session is deletable, and the confirmation says
|
|
18
|
-
* that its log directory may be recreated if it is written to again.
|
|
19
|
-
*
|
|
20
|
-
* @module dsh-over-ssh/client/session-panel
|
|
21
|
-
*/
|
|
22
|
-
/**
|
|
23
|
-
* Where the host route lives, as the browser must request it.
|
|
24
|
-
*
|
|
25
|
-
* **Duplicated from the host module on purpose**, exactly like `ROOTS_API_PATH` in `roots-panel.ts`: the
|
|
26
|
-
* client bundle must not pull host code in (it would drag `node:fs`/`node:child_process` into the browser
|
|
27
|
-
* build), and the two halves are separate bundles. The value is the contract; both sides assert it in their
|
|
28
|
-
* own tests, so a divergence is a red test rather than a silent 404.
|
|
29
|
-
*
|
|
30
|
-
* Relative on purpose: `dsh web` serves the page from the host origin, and the Desktop shell forwards
|
|
31
|
-
* `/api/...` from `dsh-app://app/` to the owned host with its session cookie (see `roots-panel.ts` for the
|
|
32
|
-
* full reasoning).
|
|
33
|
-
*/
|
|
34
|
-
export declare const SESSION_API_PATH = "/api/over-ssh/sessions";
|
|
35
|
-
import type { MessageKey } from '../core/message.ts';
|
|
36
|
-
/** One inventory row as the panel uses it. */
|
|
37
|
-
export interface SessionRowView {
|
|
38
|
-
readonly id: string;
|
|
39
|
-
readonly classification: 'normal' | 'subsession' | 'unreadable';
|
|
40
|
-
readonly parentSessionId: string | undefined;
|
|
41
|
-
readonly title: string | undefined;
|
|
42
|
-
readonly cwd: string | undefined;
|
|
43
|
-
/** Display title of the workspace owning {@link cwd}, when the host resolved one. */
|
|
44
|
-
readonly workspaceTitle: string | undefined;
|
|
45
|
-
readonly bytes: number;
|
|
46
|
-
readonly updatedAtMs: number;
|
|
47
|
-
/**
|
|
48
|
-
* Whether the **host currently has this session loaded** (it was opened in this run).
|
|
49
|
-
*
|
|
50
|
-
* This is *not* "running": the host used to report exactly this flag as "live" and the panel rendered it as
|
|
51
|
-
* 「运行中」, which is the bug the screenshot reported (every session read as running while nothing ran).
|
|
52
|
-
*/
|
|
53
|
-
readonly live: boolean;
|
|
54
|
-
/** Whether the session is actually **running** (the host has an active turn for it). */
|
|
55
|
-
readonly running: boolean;
|
|
56
|
-
/** Whether this row may be deleted on its own (a running or unreadable row may not). */
|
|
57
|
-
readonly deletable: boolean;
|
|
58
|
-
/** Why it may not be deleted, when it may not. */
|
|
59
|
-
readonly blockedReason: 'running' | 'subsession' | 'unreadable' | undefined;
|
|
60
|
-
}
|
|
61
|
-
/** One bin row as the panel uses it. */
|
|
62
|
-
export interface BinRowView {
|
|
63
|
-
readonly entryId: string;
|
|
64
|
-
readonly sessionId: string;
|
|
65
|
-
readonly title: string | undefined;
|
|
66
|
-
readonly bytes: number;
|
|
67
|
-
readonly deletedAtMs: number;
|
|
68
|
-
readonly movedBy: 'rename' | 'copy';
|
|
69
|
-
}
|
|
70
|
-
/** The whole panel state. */
|
|
71
|
-
export interface SessionPanelSnapshot {
|
|
72
|
-
readonly available: boolean;
|
|
73
|
-
readonly sessions: readonly SessionRowView[];
|
|
74
|
-
readonly bin: readonly BinRowView[];
|
|
75
|
-
readonly policy: {
|
|
76
|
-
readonly maxEntries: number;
|
|
77
|
-
readonly maxBytes: number;
|
|
78
|
-
readonly maxAgeMs: number;
|
|
79
|
-
};
|
|
80
|
-
/** Totals the header shows without recomputing in the component. */
|
|
81
|
-
readonly totals: {
|
|
82
|
-
readonly sessions: number;
|
|
83
|
-
readonly bytes: number;
|
|
84
|
-
readonly subsessions: number;
|
|
85
|
-
/** Sessions the host has loaded (opened) — 「已打开」. */
|
|
86
|
-
readonly live: number;
|
|
87
|
-
/** Sessions that are actually running — 「运行中」. */
|
|
88
|
-
readonly running: number;
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
/** What the panel needs from the browser, injected so a test needs no `fetch`. */
|
|
92
|
-
export interface SessionPanelIo {
|
|
93
|
-
readonly fetch: (path: string, init?: {
|
|
94
|
-
readonly method?: string;
|
|
95
|
-
readonly body?: string;
|
|
96
|
-
readonly headers?: Record<string, string>;
|
|
97
|
-
}) => Promise<{
|
|
98
|
-
readonly ok: boolean;
|
|
99
|
-
readonly status: number;
|
|
100
|
-
json: () => Promise<unknown>;
|
|
101
|
-
}>;
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* A panel action's outcome, as the view renders it.
|
|
105
|
-
*
|
|
106
|
-
* The message is a **`MessageKey`, not a sentence**: this module has no dictionary, and the sentences it used to
|
|
107
|
-
* spell were Chinese in both locales (audit 2026-10-31 `client.md` §2.3). The view resolves them through
|
|
108
|
-
* `copy.resolve`, which is the dictionary of the active locale.
|
|
109
|
-
*/
|
|
110
|
-
export type PanelActionResult = {
|
|
111
|
-
readonly kind: 'ok';
|
|
112
|
-
readonly message: MessageKey;
|
|
113
|
-
} | {
|
|
114
|
-
readonly kind: 'error';
|
|
115
|
-
readonly message: MessageKey;
|
|
116
|
-
readonly status?: number;
|
|
117
|
-
} | {
|
|
118
|
-
readonly kind: 'needs-confirmation';
|
|
119
|
-
readonly message: MessageKey;
|
|
120
|
-
};
|
|
121
|
-
/**
|
|
122
|
-
* Byte formatting, **re-exported from `src/shared/format.ts`** rather than implemented here.
|
|
123
|
-
*
|
|
124
|
-
* Two copies of this function existed (this one and the shared one), which is how "the same byte count rendered
|
|
125
|
-
* two ways inside one page" happened before (audit 0057's units table). The client half and the host half already
|
|
126
|
-
* share `shared/format.ts`; there is no reason for a third spelling, and a future unit change must land in one
|
|
127
|
-
* place only (audit 2026-11-01b ui P3-2). Kept as a named re-export so every existing import path still works.
|
|
128
|
-
*/
|
|
129
|
-
import { formatBytes } from '../../shared/format.ts';
|
|
130
|
-
export { formatBytes };
|
|
131
|
-
/** Classify one raw row and decide whether it is deletable on its own. */
|
|
132
|
-
export declare function toSessionRowView(raw: Record<string, unknown>): SessionRowView | undefined;
|
|
133
|
-
/** Project one raw bin row. */
|
|
134
|
-
export declare function toBinRowView(raw: Record<string, unknown>): BinRowView | undefined;
|
|
135
|
-
/** An empty snapshot, used before the first answer and when the route is unavailable. */
|
|
136
|
-
export declare function emptySnapshot(): SessionPanelSnapshot;
|
|
137
|
-
/**
|
|
138
|
-
* The final path segment of a path spelled either way, or the whole string when it has no separator.
|
|
139
|
-
*
|
|
140
|
-
* Used for the workspace fallback label: a session's `cwd` is a path, and its leaf names the project far
|
|
141
|
-
* better than the uuid beside it does.
|
|
142
|
-
*
|
|
143
|
-
* @param path - a POSIX or Windows path.
|
|
144
|
-
* @returns the last non-empty segment.
|
|
145
|
-
*/
|
|
146
|
-
export declare function leafName(path: string): string;
|
|
147
|
-
/**
|
|
148
|
-
* The human label for one row: its own title, else its workspace.
|
|
149
|
-
*
|
|
150
|
-
* FR-3.2 asks the panel for the session's 标题 and 根归属; this is the chain that answers it from whatever the
|
|
151
|
-
* host could resolve. The id is **not** part of this string — the row renders it separately as `shortId`, which
|
|
152
|
-
* is why an empty answer here is honest rather than a lost row (the earlier doc claimed the id was the last
|
|
153
|
-
* resort; the code never did that).
|
|
154
|
-
*
|
|
155
|
-
* @param row - the row to label.
|
|
156
|
-
* @returns the text shown as the row's name; `''` when the host resolved neither a title nor a workspace.
|
|
157
|
-
*/
|
|
158
|
-
export declare function sessionLabel(row: SessionRowView): string;
|
|
159
|
-
/**
|
|
160
|
-
* The workspace line for one row: the registry's display title, else the `cwd` leaf.
|
|
161
|
-
*
|
|
162
|
-
* @param row - the row to describe.
|
|
163
|
-
* @returns the workspace text; **empty** for the platform's no-cwd bucket. The view substitutes
|
|
164
|
-
* `copy.workspaceUnknown` there: a marker spelled in this module would be Chinese in the English UI.
|
|
165
|
-
*/
|
|
166
|
-
export declare function workspaceLabel(row: SessionRowView): string;
|
|
167
|
-
/** One workspace section of the inventory list. */
|
|
168
|
-
export interface SessionGroup {
|
|
169
|
-
/** Stable key: the `cwd` when known, else the no-workspace marker. */
|
|
170
|
-
readonly key: string;
|
|
171
|
-
readonly label: string;
|
|
172
|
-
readonly cwd: string | undefined;
|
|
173
|
-
readonly sessions: readonly SessionRowView[];
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* The rows the list shows: everything except sub-sessions that still have a visible parent.
|
|
177
|
-
*
|
|
178
|
-
* The user asked not to show sub-sessions at all, and that is right for the reachable ones: they are an
|
|
179
|
-
* implementation detail of how a session spawned help, they are deleted together with their parent, and 44
|
|
180
|
-
* of 88 rows on the reference machine were sub-sessions. Showing them doubles the list and adds rows the
|
|
181
|
-
* user cannot act on individually.
|
|
182
|
-
*
|
|
183
|
-
* **A sub-session whose parent is not in the inventory stays visible.** That is an orphan: nothing navigates
|
|
184
|
-
* to it (the platform nests children under their parent) and nothing will ever delete it with a parent, so
|
|
185
|
-
* hiding it would make its bytes permanently invisible and unreachable — exactly the "会话数据只增不减"
|
|
186
|
-
* problem FR-3.3 exists to expose. Hiding must not create garbage.
|
|
187
|
-
*
|
|
188
|
-
* @param rows - the full inventory.
|
|
189
|
-
* @returns the rows to render, in the input order.
|
|
190
|
-
*/
|
|
191
|
-
export declare function visibleSessions(rows: readonly SessionRowView[]): readonly SessionRowView[];
|
|
192
|
-
/**
|
|
193
|
-
* How many rows {@link visibleSessions} hid, so the panel can say so instead of silently dropping them.
|
|
194
|
-
*
|
|
195
|
-
* @param rows - the full inventory.
|
|
196
|
-
* @returns the number of hidden sub-sessions.
|
|
197
|
-
*/
|
|
198
|
-
export declare function hiddenSubsessionCount(rows: readonly SessionRowView[]): number;
|
|
199
|
-
/**
|
|
200
|
-
* Group rows by workspace, most recently updated workspace first, newest session first inside a group.
|
|
201
|
-
*
|
|
202
|
-
* Grouping is what makes the list answerable: 88 bare uuids become a handful of named projects, and a row's
|
|
203
|
-
* id is only ever needed for the rare case where two sessions share a workspace and a title. FR-3.2 asks for
|
|
204
|
-
* the session's 根归属, and a section heading states it once for the whole group instead of on every row.
|
|
205
|
-
*
|
|
206
|
-
* @param rows - the inventory rows to group (see {@link visibleSessions} for what the list actually shows).
|
|
207
|
-
* @returns the sections, in display order.
|
|
208
|
-
*/
|
|
209
|
-
export declare function groupByWorkspace(rows: readonly SessionRowView[]): readonly SessionGroup[];
|
|
210
|
-
/**
|
|
211
|
-
* Format an instant as a short, local, sortable-enough stamp.
|
|
212
|
-
*
|
|
213
|
-
* Deliberately compact: the panel lives in a narrow card, and the exact second never matters for deciding
|
|
214
|
-
* which session to delete. The locale is the browser's own, so no dictionary entry is needed for it.
|
|
215
|
-
*
|
|
216
|
-
* **The year is shown when it is not the current one** (audit 2026-11-01b ui P3-1). A bare `10-14 13:41` read
|
|
217
|
-
* exactly like today's session for one written eleven months ago — and this column is what the user reads before
|
|
218
|
-
* deciding which session to delete, so "which year" is part of the answer. Same-year stamps keep the short shape
|
|
219
|
-
* (the common case stays compact); older ones switch to `YYYY-MM-DD`.
|
|
220
|
-
*
|
|
221
|
-
* @param ms - milliseconds since the epoch; a non-finite value yields an empty string.
|
|
222
|
-
* @param now - "today", injectable so the year rollover is a testable fact rather than a wall-clock accident.
|
|
223
|
-
* @returns e.g. `10-14 13:41` (this year) / `2025-10-14` (another year), or `''` when the value is unusable.
|
|
224
|
-
*/
|
|
225
|
-
export declare function formatStamp(ms: number, now?: number): string;
|
|
226
|
-
/**
|
|
227
|
-
* A short, unambiguous prefix of a session id, for the row's id line.
|
|
228
|
-
*
|
|
229
|
-
* The full uuid stays in the row's `title` attribute and in the delete confirmation, so nothing is lost;
|
|
230
|
-
* this is only what the eye needs to tell two rows in the same workspace apart.
|
|
231
|
-
*
|
|
232
|
-
* @param id - the session id.
|
|
233
|
-
* @returns the first 8 characters, or the whole id when shorter.
|
|
234
|
-
*/
|
|
235
|
-
export declare function shortId(id: string): string;
|
|
236
|
-
/**
|
|
237
|
-
* Parse one `GET` answer.
|
|
238
|
-
*
|
|
239
|
-
* Field by field, like `parseRootsSnapshot`: a host answer carrying extra keys cannot reach the DOM.
|
|
240
|
-
*
|
|
241
|
-
* @param value - the decoded JSON body.
|
|
242
|
-
* @returns the snapshot; an unusable body yields {@link emptySnapshot}.
|
|
243
|
-
*/
|
|
244
|
-
export declare function parseSessionSnapshot(value: unknown): SessionPanelSnapshot;
|
|
245
|
-
/** One read of the panel, with the failure kept so the view can tell "failed" from "nothing loaded yet". */
|
|
246
|
-
export type SessionPanelRead = {
|
|
247
|
-
readonly ok: true;
|
|
248
|
-
readonly snapshot: SessionPanelSnapshot;
|
|
249
|
-
} | {
|
|
250
|
-
readonly ok: false;
|
|
251
|
-
readonly error: string;
|
|
252
|
-
};
|
|
253
|
-
/**
|
|
254
|
-
* Read the panel **and keep the failure**.
|
|
255
|
-
*
|
|
256
|
-
* {@link loadSessionPanel} cannot answer "nothing loaded yet" vs "the host refused": it folds both into
|
|
257
|
-
* {@link emptySnapshot}, whose `available` is `false`. The settings section rendered that as a red
|
|
258
|
-
* "the session channel is unavailable (the plugin is not wired)" on its very first paint, and it kept the same
|
|
259
|
-
* sentence when the real cause was an HTTP 500 or a dead network — the reason was thrown away (audit 2026-11-01b
|
|
260
|
-
* ui P1-3). A caller that only needs rows keeps using `loadSessionPanel`; the panel uses this one, so "still
|
|
261
|
-
* reading", "the read failed, here is why" and "the host says it has no session channel" are three states.
|
|
262
|
-
*
|
|
263
|
-
* @param io - injected fetch.
|
|
264
|
-
* @returns the snapshot, or the reason the read failed.
|
|
265
|
-
*/
|
|
266
|
-
export declare function readSessionPanel(io: SessionPanelIo): Promise<SessionPanelRead>;
|
|
267
|
-
/**
|
|
268
|
-
* Load the panel's data, keeping **no** trace of a failure.
|
|
269
|
-
*
|
|
270
|
-
* For callers that only want the rows (the sidebar mark, the row-action dialog's lookup). The settings section
|
|
271
|
-
* uses {@link readSessionPanel} instead: `available: false` here means "failed **or** the host said no", and the
|
|
272
|
-
* two need different sentences (audit 2026-11-01b ui P1-3).
|
|
273
|
-
*
|
|
274
|
-
* @param io - injected fetch.
|
|
275
|
-
* @returns the snapshot; a failure is `emptySnapshot()` rather than a throw, because a panel that cannot reach
|
|
276
|
-
* the host must still render.
|
|
277
|
-
*/
|
|
278
|
-
export declare function loadSessionPanel(io: SessionPanelIo): Promise<SessionPanelSnapshot>;
|
|
279
|
-
/**
|
|
280
|
-
* Delete sessions.
|
|
281
|
-
*
|
|
282
|
-
* Sub-sessions are **not** a caller decision any more: the host always takes a parent's whole subtree along
|
|
283
|
-
* (see `planDelete`), because leaving children behind orphans their logs. The request therefore carries no
|
|
284
|
-
* `includeSubsessions` flag.
|
|
285
|
-
*
|
|
286
|
-
* @param io - injected fetch.
|
|
287
|
-
* @param ids - session ids to delete.
|
|
288
|
-
* @param options - `permanent` requires `confirm: true`, which the caller only passes after the user agreed.
|
|
289
|
-
* @returns the outcome as the view renders it.
|
|
290
|
-
*/
|
|
291
|
-
export declare function deleteSessions(io: SessionPanelIo, ids: readonly string[], options?: {
|
|
292
|
-
readonly permanent?: boolean;
|
|
293
|
-
}): Promise<PanelActionResult>;
|
|
294
|
-
/**
|
|
295
|
-
* Delete every session belonging to one workspace directory.
|
|
296
|
-
*
|
|
297
|
-
* The workspace itself stays registered: the user asked to clear its sessions, not to unregister it.
|
|
298
|
-
*
|
|
299
|
-
* @param io - injected fetch.
|
|
300
|
-
* @param cwd - the workspace directory whose sessions should go.
|
|
301
|
-
* @param options - `permanent` requires `confirm: true`, which the caller only passes after the user agreed.
|
|
302
|
-
* @returns the outcome as the view renders it.
|
|
303
|
-
*/
|
|
304
|
-
export declare function deleteWorkspaceSessions(io: SessionPanelIo, cwd: string, options?: {
|
|
305
|
-
readonly permanent?: boolean;
|
|
306
|
-
}): Promise<PanelActionResult>;
|
|
307
|
-
/** Restore one bin entry. */
|
|
308
|
-
export declare function restoreSession(io: SessionPanelIo, entryId: string): Promise<PanelActionResult>;
|
|
309
|
-
/** Permanently remove one bin entry. Requires the caller to have confirmed. */
|
|
310
|
-
export declare function purgeSession(io: SessionPanelIo, entryId: string): Promise<PanelActionResult>;
|
|
311
|
-
/** Apply the retention policy. */
|
|
312
|
-
export declare function sweepSessions(io: SessionPanelIo): Promise<PanelActionResult>;
|
|
313
|
-
export declare function previewDelete(io: SessionPanelIo, ids: readonly string[]): Promise<{
|
|
314
|
-
readonly delete: readonly string[];
|
|
315
|
-
readonly refusals: readonly {
|
|
316
|
-
readonly id: string;
|
|
317
|
-
readonly reason: string;
|
|
318
|
-
}[];
|
|
319
|
-
readonly bytes: number;
|
|
320
|
-
}>;
|
|
321
|
-
/** Whether an action needs the user to confirm before it may run. */
|
|
322
|
-
export declare function needsConfirmation(action: 'delete-permanent' | 'purge'): boolean;
|