dsh-remote-vs 0.0.0-stage → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.en.md +385 -0
- package/README.md +347 -2
- package/cordis.patch.yml +259 -0
- package/icon.svg +7 -0
- package/lib/client.js +62602 -0
- package/lib/index.js +4287 -0
- package/lib/p2-bridge-refs.js +10 -0
- package/lib/p2-bridge.js +16 -0
- package/lib/p2-router-fs.js +252 -0
- package/lib/p2-router-refs.js +466 -0
- package/lib/p2-router-spill.js +62 -0
- package/lib/p2-router-subprocess.js +64 -0
- package/lib/p2-shell-bash.js +24 -0
- package/lib/ssh-proc-arI5lm4W.js +632 -0
- package/lib/types/changes/recorder.d.ts +240 -0
- package/lib/types/changes/status.d.ts +201 -0
- package/lib/types/changes/text-diff.d.ts +65 -0
- package/lib/types/client/CodeEditor.d.ts +125 -0
- package/lib/types/client/Dialog.d.ts +14 -0
- package/lib/types/client/DirectoryFlow.d.ts +30 -0
- package/lib/types/client/EditorPane.d.ts +30 -0
- package/lib/types/client/HostsView.d.ts +10 -0
- package/lib/types/client/NativeExplorer.d.ts +39 -0
- package/lib/types/client/RemoteDirectoryPicker.d.ts +16 -0
- package/lib/types/client/RemoteEditorView.d.ts +20 -0
- package/lib/types/client/RemoteExplorer.d.ts +18 -0
- package/lib/types/client/RemoteRoot.d.ts +16 -0
- package/lib/types/client/SearchView.d.ts +14 -0
- package/lib/types/client/SettingsHosts.d.ts +15 -0
- package/lib/types/client/TerminalView.d.ts +16 -0
- package/lib/types/client/WorkspaceWizard.d.ts +16 -0
- package/lib/types/client/WorkspacesView.d.ts +14 -0
- package/lib/types/client/XTerm.d.ts +15 -0
- package/lib/types/client/api.d.ts +162 -0
- package/lib/types/client/changes/ChangesDock.d.ts +35 -0
- package/lib/types/client/changes/model.d.ts +36 -0
- package/lib/types/client/changes/undo-affordance.d.ts +48 -0
- package/lib/types/client/changes/undo-context.d.ts +25 -0
- package/lib/types/client/composer.d.ts +165 -0
- package/lib/types/client/i18n.d.ts +17 -0
- package/lib/types/client/index.d.ts +14 -0
- package/lib/types/client/remote-file-open-target.d.ts +247 -0
- package/lib/types/client/remote-file-open.d.ts +35 -0
- package/lib/types/client/reveal.d.ts +15 -0
- package/lib/types/client/scm/DiffView.d.ts +24 -0
- package/lib/types/client/scm/HistoryPanel.d.ts +12 -0
- package/lib/types/client/scm/RepoPicker.d.ts +24 -0
- package/lib/types/client/scm/ScmPanel.d.ts +15 -0
- package/lib/types/client/scm/actions.d.ts +170 -0
- package/lib/types/client/scm/diff-model.d.ts +29 -0
- package/lib/types/client/scm/stage-queue.d.ts +99 -0
- package/lib/types/client/scm/status-map.d.ts +157 -0
- package/lib/types/client/scm/untracked-dirs.d.ts +72 -0
- package/lib/types/client/selection-popup.d.ts +61 -0
- package/lib/types/client/store.d.ts +437 -0
- package/lib/types/client/theme.d.ts +15 -0
- package/lib/types/client/ui.d.ts +46 -0
- package/lib/types/client/upload-plan.d.ts +78 -0
- package/lib/types/client/upload-source.d.ts +46 -0
- package/lib/types/client/use-remote-chat-target.d.ts +17 -0
- package/lib/types/client/vsc/FindWidget.d.ts +81 -0
- package/lib/types/client/vsc/change-marks.d.ts +150 -0
- package/lib/types/client/vsc/editor-langs.d.ts +5 -0
- package/lib/types/client/vsc/editor-palette.d.ts +37 -0
- package/lib/types/client/vsc/editor-theme.d.ts +32 -0
- package/lib/types/client/vsc/fallback-highlight.d.ts +26 -0
- package/lib/types/client/vsc/kit.d.ts +195 -0
- package/lib/types/client/vsc/markdown.d.ts +11 -0
- package/lib/types/client/vsc/reveal-line.d.ts +38 -0
- package/lib/types/client/vsc/tokenize.d.ts +104 -0
- package/lib/types/deploy.d.ts +94 -0
- package/lib/types/fs-ops.d.ts +8 -0
- package/lib/types/git-format.d.ts +34 -0
- package/lib/types/git-ops.d.ts +183 -0
- package/lib/types/http.d.ts +14 -0
- package/lib/types/importers.d.ts +20 -0
- package/lib/types/index.d.ts +87 -0
- package/lib/types/keygen.d.ts +19 -0
- package/lib/types/loopback.d.ts +7 -0
- package/lib/types/p2/bridge-refs.d.ts +21 -0
- package/lib/types/p2/bridge.d.ts +24 -0
- package/lib/types/p2/file-refs.d.ts +73 -0
- package/lib/types/p2/helper-rpc.d.ts +63 -0
- package/lib/types/p2/in-flight-gate.d.ts +25 -0
- package/lib/types/p2/router-fs.d.ts +92 -0
- package/lib/types/p2/router-refs.d.ts +45 -0
- package/lib/types/p2/router-spill.d.ts +11 -0
- package/lib/types/p2/router-subprocess.d.ts +23 -0
- package/lib/types/p2/shell-bash.d.ts +16 -0
- package/lib/types/p2/ssh-proc.d.ts +22 -0
- package/lib/types/p2/worlds.d.ts +179 -0
- package/lib/types/paths.d.ts +12 -0
- package/lib/types/pool.d.ts +105 -0
- package/lib/types/protocol.d.ts +452 -0
- package/lib/types/routes.d.ts +27 -0
- package/lib/types/search-ops.d.ts +68 -0
- package/lib/types/sftp.d.ts +90 -0
- package/lib/types/store.d.ts +66 -0
- package/lib/types/terminal.d.ts +51 -0
- package/lib/types/timeouts.d.ts +83 -0
- package/lib/types/tools.d.ts +41 -0
- package/lib/worlds-PUumRIh7.js +18765 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +189 -4
- package/src/changes/recorder.ts +902 -0
- package/src/changes/status.ts +448 -0
- package/src/changes/text-diff.ts +377 -0
- package/src/client/CodeEditor.tsx +923 -0
- package/src/client/Dialog.tsx +76 -0
- package/src/client/DirectoryFlow.tsx +431 -0
- package/src/client/EditorPane.tsx +512 -0
- package/src/client/HostsView.tsx +312 -0
- package/src/client/NativeExplorer.tsx +121 -0
- package/src/client/RemoteDirectoryPicker.tsx +157 -0
- package/src/client/RemoteEditorView.tsx +54 -0
- package/src/client/RemoteExplorer.tsx +1398 -0
- package/src/client/RemoteRoot.tsx +389 -0
- package/src/client/SearchView.tsx +407 -0
- package/src/client/SettingsHosts.tsx +136 -0
- package/src/client/TerminalView.tsx +308 -0
- package/src/client/WorkspaceWizard.tsx +199 -0
- package/src/client/WorkspacesView.tsx +93 -0
- package/src/client/XTerm.tsx +308 -0
- package/src/client/api.ts +331 -0
- package/src/client/changes/ChangesDock.tsx +348 -0
- package/src/client/changes/changes.css +230 -0
- package/src/client/changes/model.ts +347 -0
- package/src/client/changes/undo-affordance.ts +389 -0
- package/src/client/changes/undo-context.ts +34 -0
- package/src/client/composer.ts +436 -0
- package/src/client/css.d.ts +7 -0
- package/src/client/i18n.ts +702 -0
- package/src/client/index.tsx +286 -0
- package/src/client/remote-file-open-target.ts +553 -0
- package/src/client/remote-file-open.ts +261 -0
- package/src/client/reveal.ts +33 -0
- package/src/client/scm/DiffView.tsx +570 -0
- package/src/client/scm/HistoryPanel.tsx +353 -0
- package/src/client/scm/RepoPicker.tsx +238 -0
- package/src/client/scm/ScmPanel.tsx +857 -0
- package/src/client/scm/actions.ts +460 -0
- package/src/client/scm/diff-model.ts +206 -0
- package/src/client/scm/stage-queue.ts +264 -0
- package/src/client/scm/status-map.ts +381 -0
- package/src/client/scm/untracked-dirs.ts +139 -0
- package/src/client/selection-popup.ts +157 -0
- package/src/client/store.tsx +1670 -0
- package/src/client/theme.ts +54 -0
- package/src/client/ui.tsx +136 -0
- package/src/client/upload-plan.ts +153 -0
- package/src/client/upload-source.ts +145 -0
- package/src/client/use-remote-chat-target.ts +153 -0
- package/src/client/vsc/FindWidget.tsx +446 -0
- package/src/client/vsc/change-marks.ts +586 -0
- package/src/client/vsc/editor-langs.ts +137 -0
- package/src/client/vsc/editor-palette.ts +107 -0
- package/src/client/vsc/editor-theme.ts +93 -0
- package/src/client/vsc/fallback-highlight.ts +145 -0
- package/src/client/vsc/kit.tsx +777 -0
- package/src/client/vsc/markdown.tsx +172 -0
- package/src/client/vsc/reveal-line.ts +142 -0
- package/src/client/vsc/tokenize.ts +569 -0
- package/src/client/vsc/vsc.css +2669 -0
- package/src/deploy.ts +481 -0
- package/src/fs-ops.ts +57 -0
- package/src/git-format.ts +205 -0
- package/src/git-ops.ts +693 -0
- package/src/http.ts +55 -0
- package/src/importers.ts +141 -0
- package/src/index.ts +442 -0
- package/src/keygen.ts +77 -0
- package/src/loopback.ts +47 -0
- package/src/p2/bridge-refs.ts +27 -0
- package/src/p2/bridge.ts +32 -0
- package/src/p2/file-refs.ts +392 -0
- package/src/p2/helper-rpc.ts +394 -0
- package/src/p2/in-flight-gate.ts +63 -0
- package/src/p2/router-fs.ts +327 -0
- package/src/p2/router-refs.ts +166 -0
- package/src/p2/router-spill.ts +75 -0
- package/src/p2/router-subprocess.ts +92 -0
- package/src/p2/shell-bash.ts +30 -0
- package/src/p2/ssh-proc.ts +802 -0
- package/src/p2/worlds.ts +735 -0
- package/src/paths.ts +50 -0
- package/src/pool.ts +811 -0
- package/src/protocol.ts +460 -0
- package/src/routes.ts +802 -0
- package/src/search-ops.ts +220 -0
- package/src/sftp.ts +677 -0
- package/src/store.ts +364 -0
- package/src/terminal.ts +291 -0
- package/src/timeouts.ts +122 -0
- package/src/tools.ts +320 -0
- package/vendor/dsh-remote-helpers/dsh-ssh-helper-0.2.1-alpha.2-linux-x64.helper.json +16 -0
- package/vendor/dsh-remote-helpers/dsh-ssh-helper-0.2.1-alpha.2-linux-x64.tar.gz +4 -0
- package/vendor/dsh-remote-helpers/dsh-ssh-helper-0.2.1-alpha.2-linux-x64.tar.gz.sha256 +1 -0
- package/vendor/dsh-remote-helpers/glibc-ceiling.patch +31 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-session change ledger for remote workspaces.
|
|
3
|
+
*
|
|
4
|
+
* DSH already records "what did this turn change" — but only for local
|
|
5
|
+
* sessions: `@deepseek-ai/dsh-workspace-changes` parses git's POSIX output with
|
|
6
|
+
* host path semantics and stages its scratch index through `node:fs`, so on a
|
|
7
|
+
* routed session the first baseline snapshot fails and the recorder returns
|
|
8
|
+
* before appending anything (measured: 58 remote-bound sessions, 390 fs
|
|
9
|
+
* writes, zero `workspace/changes` events).
|
|
10
|
+
*
|
|
11
|
+
* This ledger re-expresses the idea for a remote host, and one measurement
|
|
12
|
+
* decided its shape. A first version snapshotted the worktree with `git add
|
|
13
|
+
* --all` into a private index; on a monorepo (AOSP, ~1M files) that never
|
|
14
|
+
* finished — it was still hashing two minutes in, and the panel sat at
|
|
15
|
+
* "computing…" forever. Everything here is therefore **read-only and
|
|
16
|
+
* proportional to the files that changed**, never to the size of the
|
|
17
|
+
* repository:
|
|
18
|
+
*
|
|
19
|
+
* 1. `git status --porcelain -uno` names the tracked files that differ (1.7s
|
|
20
|
+
* measured), `git ls-files --others` names the new ones (11.3s measured,
|
|
21
|
+
* run as its own best-effort exec).
|
|
22
|
+
* 2. Only those paths are read (their current text, and `HEAD:<path>` for the
|
|
23
|
+
* ones the session started clean).
|
|
24
|
+
* 3. The diff itself is computed here, line by line, with no git call at all.
|
|
25
|
+
*
|
|
26
|
+
* Nothing in this file writes to the repository: no `git add`, no index, no
|
|
27
|
+
* object store, no temp files under the repo. "What changed" is answered by
|
|
28
|
+
* git's own stat cache; "what changed *since the session started*" is answered
|
|
29
|
+
* by keeping the pre-existing dirty files' contents at session start and
|
|
30
|
+
* treating an unchanged one as untouched.
|
|
31
|
+
*/
|
|
32
|
+
import type { RemoteChangesDiff, RemoteChangesSummary, RemoteTimeouts } from '../protocol.ts';
|
|
33
|
+
import type { RemotePool } from '../pool.ts';
|
|
34
|
+
import type { RemoteWorlds } from '../p2/worlds.ts';
|
|
35
|
+
/** The two facts a turn boundary needs from a Session, kept cordis-free. */
|
|
36
|
+
export interface LedgerTarget {
|
|
37
|
+
sessionId: string;
|
|
38
|
+
/** Session working directory as the host sees it (an alias path for remote sessions). */
|
|
39
|
+
cwd: string;
|
|
40
|
+
}
|
|
41
|
+
/** Collaborators the ledger needs; all injectable so it can be unit-tested. */
|
|
42
|
+
export interface SessionChangesDeps {
|
|
43
|
+
pool: RemotePool;
|
|
44
|
+
worlds: RemoteWorlds;
|
|
45
|
+
/** Effective git budget, read per call (a `volatile()` edit applies live). */
|
|
46
|
+
timeouts: () => RemoteTimeouts;
|
|
47
|
+
log?: (level: 'info' | 'warn', message: string) => void;
|
|
48
|
+
/** Cap on listed files; the header totals stay whole. */
|
|
49
|
+
maxFiles?: number;
|
|
50
|
+
/** Cap on one file's text, in bytes. */
|
|
51
|
+
maxFileBytes?: number;
|
|
52
|
+
/** Cap on one refresh's total text, in bytes. */
|
|
53
|
+
maxTotalBytes?: number;
|
|
54
|
+
/** Budget for the untracked scan, which is an order of magnitude slower. */
|
|
55
|
+
untrackedBudgetMs?: number;
|
|
56
|
+
/** How often the untracked scan may run again (default five seconds). */
|
|
57
|
+
untrackedRefreshMs?: number;
|
|
58
|
+
/**
|
|
59
|
+
* Override the per-exec budget outright.
|
|
60
|
+
*
|
|
61
|
+
* The timeouts setting is clamped to a sane range, which is right for a Host
|
|
62
|
+
* but useless for a check that must not wait five seconds to see a watchdog
|
|
63
|
+
* fire.
|
|
64
|
+
*/
|
|
65
|
+
execBudgetMs?: number;
|
|
66
|
+
}
|
|
67
|
+
export declare class SessionChanges {
|
|
68
|
+
private readonly ledgers;
|
|
69
|
+
private readonly deps;
|
|
70
|
+
private disposed;
|
|
71
|
+
constructor(deps: SessionChangesDeps);
|
|
72
|
+
/** Open (or reuse) the ledger for one session at the start of a turn. */
|
|
73
|
+
beginTurn(target: LedgerTarget): void;
|
|
74
|
+
/** Refresh the ledger at the end of a turn. */
|
|
75
|
+
endTurn(target: LedgerTarget, turn: number): void;
|
|
76
|
+
/** The current summary for one session, or null when it has no ledger. */
|
|
77
|
+
summary(sessionId: string): RemoteChangesSummary | null;
|
|
78
|
+
/**
|
|
79
|
+
* Compare one listed file across the whole session.
|
|
80
|
+
*
|
|
81
|
+
* The `before` side comes from the session baseline, which never moves. The
|
|
82
|
+
* `after` side is read from the host on every call rather than replayed from
|
|
83
|
+
* the last scan: a revert, a save from the editor or a shell command all
|
|
84
|
+
* change the file between scans, and replaying a cached copy against a fresh
|
|
85
|
+
* buffer is exactly how the painted lines end up at the wrong places.
|
|
86
|
+
*
|
|
87
|
+
* @param sessionId - live session owning the ledger.
|
|
88
|
+
* @param path - the file's remote absolute path (as listed in the summary).
|
|
89
|
+
* @returns hunks for decorations, or null when the session/file is unknown.
|
|
90
|
+
*/
|
|
91
|
+
diff(sessionId: string, path: string): Promise<RemoteChangesDiff | null>;
|
|
92
|
+
/** Resolve once every queued refresh has settled, including coalesced work. */
|
|
93
|
+
settled(): Promise<void>;
|
|
94
|
+
/** Drop one session's ledger. */
|
|
95
|
+
forget(sessionId: string): void;
|
|
96
|
+
/** Release every ledger (plugin unmount). */
|
|
97
|
+
dispose(): void;
|
|
98
|
+
/**
|
|
99
|
+
* Force a fresh pass, ignoring the untracked cadence.
|
|
100
|
+
*
|
|
101
|
+
* The panel's refresh button calls this: the cadence exists so a slow scan
|
|
102
|
+
* does not run on every turn, but "I clicked refresh" means the user wants
|
|
103
|
+
* the truth now — including files that appeared seconds ago.
|
|
104
|
+
*/
|
|
105
|
+
rescan(sessionId: string): void;
|
|
106
|
+
/**
|
|
107
|
+
* Self-heal a ledger whose data has gone stale.
|
|
108
|
+
*
|
|
109
|
+
* The panel polls `list`, and a poll is the one moment we know a human is
|
|
110
|
+
* looking at the answer. If the data is older than `maxAgeMs`, re-scan — so a
|
|
111
|
+
* turn boundary this Host never delivered costs a few seconds of staleness
|
|
112
|
+
* instead of a manual refresh.
|
|
113
|
+
*
|
|
114
|
+
* @returns whether a refresh was queued.
|
|
115
|
+
*/
|
|
116
|
+
touch(sessionId: string, maxAgeMs?: number): boolean;
|
|
117
|
+
/**
|
|
118
|
+
* Point one ledger at a subdirectory of the session root.
|
|
119
|
+
*
|
|
120
|
+
* This is the Source Control panel's "repository directory": an explicit
|
|
121
|
+
* choice by the user, and therefore the authority whenever it is set. The
|
|
122
|
+
* automatic one-level probe is only a fallback for a container workspace that
|
|
123
|
+
* holds exactly one repository.
|
|
124
|
+
*
|
|
125
|
+
* @param subdir - workspace-relative directory, or undefined for the session
|
|
126
|
+
* root itself. Absolute paths and `..` are refused: they would move the
|
|
127
|
+
* ledger outside the workspace it was created for.
|
|
128
|
+
*/
|
|
129
|
+
useRepoSubdir(sessionId: string, subdir: string | undefined): void;
|
|
130
|
+
/** Resolve the routing target once per session, caching the ledger. */
|
|
131
|
+
private ensure;
|
|
132
|
+
/** Queue a refresh, coalescing bursts (a turn can end while one is running). */
|
|
133
|
+
private schedule;
|
|
134
|
+
/**
|
|
135
|
+
* One refresh: the cheap scan first, then the slow one in the background.
|
|
136
|
+
*
|
|
137
|
+
* The split is not cosmetic. On the reference monorepo `git status -uno`
|
|
138
|
+
* answers in ~3s while `git ls-files --others` needs over a minute, so a
|
|
139
|
+
* single sequential refresh would leave the panel empty for that whole
|
|
140
|
+
* minute even though the tracked changes were known after three seconds.
|
|
141
|
+
* Phase one publishes, phase two adds the new files when (and if) it lands.
|
|
142
|
+
*/
|
|
143
|
+
private refresh;
|
|
144
|
+
/** Recompute the panel's list from the scans currently known. */
|
|
145
|
+
private publish;
|
|
146
|
+
/**
|
|
147
|
+
* The cheap scan: tracked files whose working-tree bytes differ from the
|
|
148
|
+
* index (1.4s measured on the reference monorepo).
|
|
149
|
+
*
|
|
150
|
+
* A session working directory is often a CONTAINER for repositories rather
|
|
151
|
+
* than one itself (the reference host has `/home/xiray/dsh_workbase` holding
|
|
152
|
+
* `emv-toolbox-web`), and `git status` there answers exit 128 — "not a git
|
|
153
|
+
* repository (or any parent up to mount point /)". When that happens the
|
|
154
|
+
* ledger looks one level down once and adopts the repository it finds, which
|
|
155
|
+
* is the same idea the SCM panel's `repoSubdir` expresses by hand.
|
|
156
|
+
*
|
|
157
|
+
* @returns entries relative to the ledger's root, or undefined when no
|
|
158
|
+
* repository could be found (the ledger then reports why).
|
|
159
|
+
*/
|
|
160
|
+
private scanTracked;
|
|
161
|
+
/**
|
|
162
|
+
* Look for a repository in the session root's immediate subdirectories.
|
|
163
|
+
*
|
|
164
|
+
* **Only an unambiguous answer is adopted.** A workspace that holds several
|
|
165
|
+
* repositories has no single right one, and silently picking the first would
|
|
166
|
+
* report changes from a project the session never touched — worse than saying
|
|
167
|
+
* "I don't know". The explicit repository directory the Source Control panel
|
|
168
|
+
* stores is the real answer, and this fallback exists only to spare the user
|
|
169
|
+
* from setting it when there is exactly one candidate.
|
|
170
|
+
*
|
|
171
|
+
* @returns the adopted root, or undefined when nothing (or too much) was found.
|
|
172
|
+
*/
|
|
173
|
+
private adoptRepositoryBelow;
|
|
174
|
+
/**
|
|
175
|
+
* The slow scan: new files, in the background.
|
|
176
|
+
*
|
|
177
|
+
* The same command costs ~11s on a warm tree and over a minute on a cold one,
|
|
178
|
+
* and on the reference AOSP monorepo it has been measured past 60s with no
|
|
179
|
+
* answer at all. It therefore never runs inline with the tracked scan, never
|
|
180
|
+
* runs twice at once, and only runs again after {@link untrackedCadence}. A
|
|
181
|
+
* repository that cannot answer inside {@link untrackedBudget} is written off
|
|
182
|
+
* for the session (`untrackedGivenUp`) rather than retried every turn.
|
|
183
|
+
*/
|
|
184
|
+
private kickUntracked;
|
|
185
|
+
/**
|
|
186
|
+
* How long before the untracked scan may run again.
|
|
187
|
+
*
|
|
188
|
+
* Self-tuning on purpose: the same command costs ~0.2s in a small repository
|
|
189
|
+
* and over a minute in a monorepo, so a fixed cadence is either wasteful or
|
|
190
|
+
* useless. Backing off to four times the last duration keeps small projects
|
|
191
|
+
* near-real-time while a big one settles into a scan every couple of minutes
|
|
192
|
+
* instead of one per turn.
|
|
193
|
+
*/
|
|
194
|
+
private untrackedCadence;
|
|
195
|
+
/**
|
|
196
|
+
* Fold files that already existed when the session began into the baseline.
|
|
197
|
+
*
|
|
198
|
+
* The untracked walk is slow enough (a minute on a monorepo) that the baseline
|
|
199
|
+
* refresh publishes before it lands, so its first result arrives without a
|
|
200
|
+
* "before" side. Without this, every file the checkout already had — the
|
|
201
|
+
* user's own scratch files, a build's leftovers — would be announced as this
|
|
202
|
+
* session's work, complete with a full-file addition count.
|
|
203
|
+
*
|
|
204
|
+
* mtime decides: strictly older than the ledger's own start means the session
|
|
205
|
+
* cannot have created it, and its present content is therefore its baseline.
|
|
206
|
+
*/
|
|
207
|
+
private absorbPreExisting;
|
|
208
|
+
/**
|
|
209
|
+
* Race one host call against its own budget.
|
|
210
|
+
*
|
|
211
|
+
* A budget handed to `exec` is a request, not a guarantee: the transport may
|
|
212
|
+
* keep the promise open long after the command should have been killed (the
|
|
213
|
+
* remote process can even be gone while the channel stays). Awaiting such a
|
|
214
|
+
* promise blocks the ledger's whole queue, so the panel shows "computing…"
|
|
215
|
+
* for as long as the user is willing to stare at it. This bounds that wait.
|
|
216
|
+
*/
|
|
217
|
+
private withWatchdog;
|
|
218
|
+
/** Read a batch of files, capped per file and in total. */
|
|
219
|
+
private readPaths;
|
|
220
|
+
/** Run one command on the host, normalizing failure into a reason string. */
|
|
221
|
+
private run;
|
|
222
|
+
/** Turn a failed scan into something a user can act on. */
|
|
223
|
+
private reasonOf;
|
|
224
|
+
/** One exec's budget: the git setting, clamped so a hung host cannot pile up work. */
|
|
225
|
+
private execBudget;
|
|
226
|
+
/** The untracked scan gets its own, more generous budget. */
|
|
227
|
+
/**
|
|
228
|
+
* The untracked scan gets its own, much more generous budget.
|
|
229
|
+
*
|
|
230
|
+
* Three minutes is a deliberate choice for a monorepo: the walk is proportional
|
|
231
|
+
* to the whole checkout, not to the session, and the reference AOSP tree has
|
|
232
|
+
* been measured past a minute cold. Waiting is acceptable here because the
|
|
233
|
+
* scan runs in the background and the tracked changes are already on screen;
|
|
234
|
+
* giving up early is not, because the files a session creates with a shell
|
|
235
|
+
* command are visible ONLY through this scan.
|
|
236
|
+
*/
|
|
237
|
+
private untrackedBudget;
|
|
238
|
+
private budget;
|
|
239
|
+
private warn;
|
|
240
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote command builders and reply parsers for the session change ledger.
|
|
3
|
+
*
|
|
4
|
+
* Why this module exists: `@deepseek-ai/dsh-workspace-changes` (and the first
|
|
5
|
+
* cut of this plugin) answer "what did this session change?" by staging the
|
|
6
|
+
* whole worktree (`git add --all`) and diffing two trees. On a monorepo the
|
|
7
|
+
* staging step alone ran past 40s without finishing, so the panel never left
|
|
8
|
+
* "正在统计". Measured on a real AOSP checkout with a warm cache, same repo:
|
|
9
|
+
*
|
|
10
|
+
* | command | time |
|
|
11
|
+
* | -------------------------------------------- | ------ |
|
|
12
|
+
* | `git diff-files --name-status -z` | 1.40s |
|
|
13
|
+
* | `git status --porcelain -z -uno` | 1.67s |
|
|
14
|
+
* | `git ls-files --others --exclude-standard -z`| 11.25s |
|
|
15
|
+
* | `git add --all` (the old baseline) | >40s |
|
|
16
|
+
*
|
|
17
|
+
* So the cheap path is: ask git *which* paths changed (status), read only those
|
|
18
|
+
* paths' bytes, and diff locally. Cost becomes proportional to what the session
|
|
19
|
+
* touched instead of to the size of the repository.
|
|
20
|
+
*
|
|
21
|
+
* Everything here is pure string in / string out — no pool, no fs, no DSH
|
|
22
|
+
* import — so the recorder owns all the IO and this half stays testable.
|
|
23
|
+
*
|
|
24
|
+
* ## Paths, quoting, exit codes
|
|
25
|
+
*
|
|
26
|
+
* Every path is a POSIX absolute path on the host and every shell word is
|
|
27
|
+
* single-quoted ({@link shq}); a path that reaches a shell unquoted is a bug.
|
|
28
|
+
* A command exits `0` when it produced a reply, `90` when its root directory
|
|
29
|
+
* does not exist (a stale alias), `92` when the host has no `base64`, and
|
|
30
|
+
* otherwise with git's own exit status. The per-file problems of the read
|
|
31
|
+
* commands (missing, unreadable, too big) are *not* failures: they are flags
|
|
32
|
+
* inside a successful reply.
|
|
33
|
+
*
|
|
34
|
+
* ## Read reply wire format
|
|
35
|
+
*
|
|
36
|
+
* One block per requested path, in request order:
|
|
37
|
+
*
|
|
38
|
+
* ```
|
|
39
|
+
* @@RVS-FILE <base64(path)> ok@@
|
|
40
|
+
* <base64(content)>
|
|
41
|
+
* @@RVS-FILE <base64(path)> big@@
|
|
42
|
+
* @@RVS-FILE <base64(path)> absent@@
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* - Paths travel base64-encoded in the marker so spaces, quotes, CJK and even
|
|
46
|
+
* newlines cannot break the framing; content travels base64-encoded on the
|
|
47
|
+
* single line after the marker so NUL bytes and `\r\n` survive a text-only
|
|
48
|
+
* transport (one line, no wrapping — the shell strips base64's newlines).
|
|
49
|
+
* - `ok` is followed by exactly one content line. An empty file is an empty
|
|
50
|
+
* content line (a bare `\n` after the marker line).
|
|
51
|
+
* - `big` means "not transferred": the file alone is over `capBytes`, or the
|
|
52
|
+
* running total would pass `totalCapBytes`.
|
|
53
|
+
* - `absent` means the path is not there *or* could not be read (permissions,
|
|
54
|
+
* a directory, a dangling blob); the recorder treats both as "no content".
|
|
55
|
+
*
|
|
56
|
+
* Binary detection is deliberately done in {@link parseReads} (any NUL byte in
|
|
57
|
+
* the decoded content) rather than in the shell: one definition of "binary" for
|
|
58
|
+
* every caller, and no dependence on `grep`/`od` behaviour across hosts.
|
|
59
|
+
*/
|
|
60
|
+
/** What happened to one path in this session. */
|
|
61
|
+
export type ChangeStatus = 'A' | 'M' | 'D' | 'R' | 'C' | 'T' | 'U' | '?';
|
|
62
|
+
/** One changed path, relative to the session root. */
|
|
63
|
+
export interface StatusEntry {
|
|
64
|
+
path: string;
|
|
65
|
+
status: ChangeStatus;
|
|
66
|
+
}
|
|
67
|
+
/** What one read asked for and what came back. */
|
|
68
|
+
export interface ReadResult {
|
|
69
|
+
/** Decoded text, or null when nothing usable was transferred. */
|
|
70
|
+
text: string | null;
|
|
71
|
+
/** Withheld because of a size cap (single file or the running total). */
|
|
72
|
+
truncated: boolean;
|
|
73
|
+
/** The content contains a NUL byte, so it is not text. */
|
|
74
|
+
binary: boolean;
|
|
75
|
+
/** Not there, or unreadable. */
|
|
76
|
+
absent: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Modification time in whole seconds (worktree reads only).
|
|
79
|
+
*
|
|
80
|
+
* A large file whose bytes were withheld can still be compared by mtime: two
|
|
81
|
+
* equal mtimes mean the session did not touch it, which is the difference
|
|
82
|
+
* between a clean list and one padded with every big file the checkout
|
|
83
|
+
* happens to carry. Zero when the host could not answer.
|
|
84
|
+
*/
|
|
85
|
+
mtime?: number;
|
|
86
|
+
/** Size in bytes, when known (`0` for a missing path). */
|
|
87
|
+
size?: number;
|
|
88
|
+
}
|
|
89
|
+
/** Exit statuses of the read commands (git's own statuses pass through as-is). */
|
|
90
|
+
export declare const READ_EXIT: {
|
|
91
|
+
/** The reported root does not exist on the host (stale alias). */
|
|
92
|
+
readonly noRoot: 90;
|
|
93
|
+
/**
|
|
94
|
+
* No `base64` on the host. Probed up front because without it every read
|
|
95
|
+
* would come back empty and look like "this file has no content", which is a
|
|
96
|
+
* far worse failure than a clear refusal.
|
|
97
|
+
*/
|
|
98
|
+
readonly noBase64: 92;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Tracked paths that changed, HEAD/index vs worktree, staged changes included.
|
|
102
|
+
*
|
|
103
|
+
* `-uno` is what keeps this cheap: untracked detection is the expensive half
|
|
104
|
+
* (11s above) and it is answered by {@link untrackedCommand} instead. `-- .`
|
|
105
|
+
* scopes the walk to the session root, so a session sitting in one package of
|
|
106
|
+
* a monorepo does not pay for the rest.
|
|
107
|
+
*
|
|
108
|
+
* @param root - remote absolute POSIX directory (the session cwd).
|
|
109
|
+
* @returns a `sh` script printing NUL-separated porcelain v1 records.
|
|
110
|
+
*/
|
|
111
|
+
export declare function trackedStatusCommand(root: string): string;
|
|
112
|
+
/**
|
|
113
|
+
* New files git is not tracking yet (the ones a session usually creates).
|
|
114
|
+
*
|
|
115
|
+
* @param root - remote absolute POSIX directory (the session cwd).
|
|
116
|
+
* @returns a `sh` script printing NUL-separated paths.
|
|
117
|
+
*/
|
|
118
|
+
export declare function untrackedCommand(root: string): string;
|
|
119
|
+
/**
|
|
120
|
+
* Parse `git status --porcelain -z -uno` output.
|
|
121
|
+
*
|
|
122
|
+
* Porcelain v1 with `-z` writes `XY <path>\0`, and for a rename or copy the
|
|
123
|
+
* source path follows as its own record: `R <new>\0<old>\0` (verified against
|
|
124
|
+
* git 2.x: the NEW path comes first). The source record is consumed, not parsed
|
|
125
|
+
* as its own entry, and the reported path is the NEW one — the only path the
|
|
126
|
+
* editor can open.
|
|
127
|
+
*
|
|
128
|
+
* The two status columns mean "index vs HEAD" (X) and "worktree vs index" (Y);
|
|
129
|
+
* one letter is reported, chosen for how the file reads in a session review:
|
|
130
|
+
* conflict > deleted > renamed > copied > added > typechange > modified.
|
|
131
|
+
*
|
|
132
|
+
* @param stdout - raw reply of {@link trackedStatusCommand}.
|
|
133
|
+
*/
|
|
134
|
+
export declare function parseStatusZ(stdout: string): StatusEntry[];
|
|
135
|
+
/**
|
|
136
|
+
* Parse `git ls-files --others --exclude-standard -z` output into untracked
|
|
137
|
+
* entries. Git already filtered ignored files out, and its paths are never
|
|
138
|
+
* quoted in `-z` mode.
|
|
139
|
+
*/
|
|
140
|
+
export declare function parseUntrackedZ(stdout: string): StatusEntry[];
|
|
141
|
+
/**
|
|
142
|
+
* Read several worktree files in one remote run.
|
|
143
|
+
*
|
|
144
|
+
* One `sh` script walks the paths in order and emits a {@link ReadResult} block
|
|
145
|
+
* per path. Sizes come from `wc -c` before anything is transferred, so a 4GB
|
|
146
|
+
* build artifact costs one `stat` instead of 5GB of base64; the running total
|
|
147
|
+
* stops the reply from growing past `totalCapBytes`, after which the remaining
|
|
148
|
+
* paths are reported `big` without being read.
|
|
149
|
+
*
|
|
150
|
+
* A symlink reports its target as content, because that is what git stores for
|
|
151
|
+
* mode 120000 — catting the link's target file would diff the wrong bytes.
|
|
152
|
+
*
|
|
153
|
+
* @param root - remote absolute POSIX directory (the session cwd).
|
|
154
|
+
* @param paths - paths relative to `root`.
|
|
155
|
+
* @param capBytes - per-file cap; larger files are reported `big`.
|
|
156
|
+
* @param totalCapBytes - cap on the sum of transferred content bytes.
|
|
157
|
+
*/
|
|
158
|
+
export declare function readWorktreeCommand(root: string, paths: readonly string[], capBytes: number, totalCapBytes: number): string;
|
|
159
|
+
/**
|
|
160
|
+
* Read several `HEAD:<path>` blobs in one remote run — the "before" side of a
|
|
161
|
+
* file that was already committed and clean when the session started.
|
|
162
|
+
*
|
|
163
|
+
* `git cat-file -s` answers existence *and* size before any bytes move, so the
|
|
164
|
+
* caps are exact, and a path that is not in `HEAD` (a new file, a repository
|
|
165
|
+
* with no commit yet, a submodule gitlink) is reported `absent` rather than
|
|
166
|
+
* failing the whole read. `cat-file blob` is used instead of `show` on purpose:
|
|
167
|
+
* no textconv, no filters, no pager — the bytes git will diff.
|
|
168
|
+
*
|
|
169
|
+
* @param root - remote absolute POSIX directory (the session cwd).
|
|
170
|
+
* @param paths - paths relative to `root`.
|
|
171
|
+
* @param capBytes - per-file cap; larger blobs are reported `big`.
|
|
172
|
+
* @param totalCapBytes - cap on the sum of transferred content bytes.
|
|
173
|
+
*/
|
|
174
|
+
export declare function readHeadCommand(root: string, paths: readonly string[], capBytes: number, totalCapBytes: number): string;
|
|
175
|
+
/**
|
|
176
|
+
* Name every immediate subdirectory that is a git repository.
|
|
177
|
+
*
|
|
178
|
+
* A session working directory is often a CONTAINER for repositories (the
|
|
179
|
+
* reference host has `/home/xiray/dsh_workbase` holding `emv-toolbox-web`), and
|
|
180
|
+
* `git status` at the container answers 128. This is a one-level `ls`-style
|
|
181
|
+
* probe — bounded work, no `find` — so it can never become the expensive part
|
|
182
|
+
* of a scan.
|
|
183
|
+
*
|
|
184
|
+
* It prints ALL matches on purpose: a workspace with several repositories has no
|
|
185
|
+
* single right answer, and the caller must refuse to guess rather than pick the
|
|
186
|
+
* first one. The SCM panel's explicit repository directory is the real answer.
|
|
187
|
+
*
|
|
188
|
+
* @param root - remote absolute POSIX directory (the session cwd).
|
|
189
|
+
*/
|
|
190
|
+
export declare function reposBelowCommand(root: string): string;
|
|
191
|
+
/**
|
|
192
|
+
* Parse a {@link readWorktreeCommand} / {@link readHeadCommand} reply.
|
|
193
|
+
*
|
|
194
|
+
* Keys are the decoded paths, exactly as requested. A marker without its
|
|
195
|
+
* content line can only mean the reply was cut short (a timeout killing the
|
|
196
|
+
* pipe); such a path is reported truncated rather than as an empty file, so an
|
|
197
|
+
* aborted read never masquerades as "the file is empty".
|
|
198
|
+
*
|
|
199
|
+
* @param stdout - raw reply of either read command.
|
|
200
|
+
*/
|
|
201
|
+
export declare function parseReads(stdout: string): Map<string, ReadResult>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Line-level diff for the session change ledger.
|
|
3
|
+
*
|
|
4
|
+
* The recorder gets *content* (see `status.ts`) and needs the same thing git
|
|
5
|
+
* would have printed with `-U3`: hunks a reviewer can read, counts for the file
|
|
6
|
+
* list, and a patch that can undo the change. Doing it here, on the host side,
|
|
7
|
+
* means a file's before/after text is compared without another round trip to
|
|
8
|
+
* the remote — and without teaching git about the session baseline.
|
|
9
|
+
*
|
|
10
|
+
* No dependency, no IO, on purpose. The algorithm is Myers' greedy edit script
|
|
11
|
+
* (the one git's own xdiff implements): common prefix and suffix are stripped
|
|
12
|
+
* first, then the middle is diffed, so the cost follows the size of the edit
|
|
13
|
+
* rather than the size of the file.
|
|
14
|
+
*
|
|
15
|
+
* Line conventions, chosen to match `git diff` byte for byte:
|
|
16
|
+
*
|
|
17
|
+
* - a final `\n` does not produce an extra empty line;
|
|
18
|
+
* - `\r` stays inside the line, so a CRLF file keeps its `\r` and a CRLF/LF
|
|
19
|
+
* mismatch shows up as changed lines instead of a phantom whole-file edit;
|
|
20
|
+
* - a file that does not end in `\n` keeps its last line, and the hunk is
|
|
21
|
+
* flagged `oldNoEol` / `newNoEol` so {@link hunkPatch} can emit git's
|
|
22
|
+
* `` marker — without it `git apply -R` refuses to
|
|
23
|
+
* touch such a file.
|
|
24
|
+
*/
|
|
25
|
+
/** One unified-diff hunk. Line numbers are 1-based, like git's. */
|
|
26
|
+
export interface TextHunk {
|
|
27
|
+
/** First old-side line in the hunk; 0 when the hunk only adds lines. */
|
|
28
|
+
oldStart: number;
|
|
29
|
+
/** Number of old-side lines the hunk covers (context + removed). */
|
|
30
|
+
oldLines: number;
|
|
31
|
+
/** First new-side line in the hunk; 0 when the hunk only removes lines. */
|
|
32
|
+
newStart: number;
|
|
33
|
+
/** Number of new-side lines the hunk covers (context + added). */
|
|
34
|
+
newLines: number;
|
|
35
|
+
/**
|
|
36
|
+
* Lines with their leading `' '`, `'-'` or `'+'`, and without the trailing
|
|
37
|
+
* `\n` — the shape the client's diff view already consumes.
|
|
38
|
+
*/
|
|
39
|
+
lines: string[];
|
|
40
|
+
/** The hunk reaches the end of `before`, which does not end in a newline. */
|
|
41
|
+
oldNoEol?: true;
|
|
42
|
+
/** The hunk reaches the end of `after`, which does not end in a newline. */
|
|
43
|
+
newNoEol?: true;
|
|
44
|
+
}
|
|
45
|
+
/** A whole text comparison: hunks plus the counts the file list shows. */
|
|
46
|
+
export interface TextDiff {
|
|
47
|
+
hunks: TextHunk[];
|
|
48
|
+
/** `'+'` lines, counted from {@link hunks} so the two can never disagree. */
|
|
49
|
+
added: number;
|
|
50
|
+
/** `'-'` lines, counted from {@link hunks}. */
|
|
51
|
+
deleted: number;
|
|
52
|
+
}
|
|
53
|
+
/** Compare two texts into git-shaped hunks (default 3 lines of context). */
|
|
54
|
+
export declare function diffText(before: string, after: string, context?: number): TextDiff;
|
|
55
|
+
/**
|
|
56
|
+
* Serialize one hunk as a patch `git apply` accepts (and `git apply -R` undoes).
|
|
57
|
+
*
|
|
58
|
+
* The `@@ -a,b +c,d @@` counts are always written out — git accepts the elided
|
|
59
|
+
* `,1` and `,-0` spellings, but a reviewer reading a log is better served by the
|
|
60
|
+
* explicit form, and one spelling means one code path to test.
|
|
61
|
+
*
|
|
62
|
+
* @param displayPath - repo-relative path, as the panel shows it.
|
|
63
|
+
* @param hunk - one hunk of {@link diffText}.
|
|
64
|
+
*/
|
|
65
|
+
export declare function hunkPatch(displayPath: string, hunk: TextHunk): string;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
/**
|
|
3
|
+
* The bits of an editor's view state that must survive an unmount.
|
|
4
|
+
*
|
|
5
|
+
* Switching file tabs remounts `CodeEditor` (its `key` is the tab id, so an
|
|
6
|
+
* identity change gets a fresh buffer rather than inheriting the previous
|
|
7
|
+
* file's undo history). Without carrying this across, every tab switch threw
|
|
8
|
+
* the reader back to line 1 with the caret at the start.
|
|
9
|
+
*/
|
|
10
|
+
export interface CodeEditorViewState {
|
|
11
|
+
/** Scroll offset of the code scroller, in pixels. */
|
|
12
|
+
scrollTop: number;
|
|
13
|
+
/** Scroll offset of the code scroller, in pixels. */
|
|
14
|
+
scrollLeft: number;
|
|
15
|
+
/** Caret position as a document offset. */
|
|
16
|
+
cursor: number;
|
|
17
|
+
/** All selection ranges (multi-cursor survives a tab switch too). */
|
|
18
|
+
ranges: {
|
|
19
|
+
anchor: number;
|
|
20
|
+
head: number;
|
|
21
|
+
}[];
|
|
22
|
+
}
|
|
23
|
+
export interface CodeEditorHandle {
|
|
24
|
+
getContent: () => string;
|
|
25
|
+
isDirty: () => boolean;
|
|
26
|
+
markSaved: () => void;
|
|
27
|
+
/** Snapshot scroll + selection, for restoring after a remount. */
|
|
28
|
+
getViewState: () => CodeEditorViewState | undefined;
|
|
29
|
+
/** Scroll back and put the caret where it was. */
|
|
30
|
+
applyViewState: (state: CodeEditorViewState) => void;
|
|
31
|
+
/** 选中一个行区间(1-based 行号)并滚到视野中央。 */
|
|
32
|
+
revealRange: (from: number, to: number) => void;
|
|
33
|
+
/**
|
|
34
|
+
* 改动标记是否已被编辑作废(true = 屏幕上不再画任何改动点)。
|
|
35
|
+
*
|
|
36
|
+
* 暴露成独立的一个布尔查询、而不是把 `EditorView` 交出去:外部唯一需要知道的
|
|
37
|
+
* 就是"这份数据还准不准",拿到了整个 view 则能改文档、改配置,是另一回事。
|
|
38
|
+
*/
|
|
39
|
+
getChangeStale: () => boolean;
|
|
40
|
+
}
|
|
41
|
+
import { type ChangeMark, type ChangeSet } from './vsc/change-marks.ts';
|
|
42
|
+
/**
|
|
43
|
+
* The editor surface is OPAQUE and scrolls in the editor's own palette
|
|
44
|
+
* (`vsc/editor-theme.ts`): a code editor is not a translucent DSH panel, and
|
|
45
|
+
* the VS Code theme is what gives the tokens their readable colors.
|
|
46
|
+
*/
|
|
47
|
+
export declare const CodeEditor: import("react").ForwardRefExoticComponent<{
|
|
48
|
+
initial: string;
|
|
49
|
+
path: string;
|
|
50
|
+
readOnly?: boolean;
|
|
51
|
+
/** Parent sub-tab visibility (views stay mounted; remeasure on show). */
|
|
52
|
+
visible?: boolean;
|
|
53
|
+
/** Chat-insert target: absent/disabled hides the selection popup. */
|
|
54
|
+
ctx?: Context;
|
|
55
|
+
sessionId?: string | undefined;
|
|
56
|
+
remoteRoot?: string | undefined;
|
|
57
|
+
chatEnabled?: boolean;
|
|
58
|
+
addLabel?: string;
|
|
59
|
+
/** 1-based line to reveal after mount (a content-search hit). */
|
|
60
|
+
revealLine?: number;
|
|
61
|
+
/** Bumped per reveal request, so the same line can be jumped to twice. */
|
|
62
|
+
revealNonce?: number;
|
|
63
|
+
/**
|
|
64
|
+
* 本会话在这个文件里的改动(行号 1-based)。`undefined` = 不开启改动标记:
|
|
65
|
+
* 不装扩展、不出现 gutter —— 没传这个 prop 的调用方(diff 面板等)行为与
|
|
66
|
+
* 加入本功能之前完全一致。
|
|
67
|
+
*/
|
|
68
|
+
changeSet?: ChangeSet | null;
|
|
69
|
+
/**
|
|
70
|
+
* 用户在某一处改动上按下「撤销这处改动」时回调。
|
|
71
|
+
*
|
|
72
|
+
* 收到的是 `changeSet.marks` 里的那个对象(`hunk` 是 `changeSet.patches` 的下标)。
|
|
73
|
+
* 不传就不装悬浮撤销那一整套(`undoAffordance`),编辑器行为与没有这个功能时
|
|
74
|
+
* 完全一致 —— 也就不会点出一个没人接的动作。
|
|
75
|
+
*/
|
|
76
|
+
onUndoChange?: (mark: ChangeMark) => void;
|
|
77
|
+
/** 悬浮按钮的文案(由调用方按 `kind` 给出它的 i18n 说法)。 */
|
|
78
|
+
undoLabel?: (mark: ChangeMark) => string;
|
|
79
|
+
/** 改动点跳转的目标行(1-based),配合 `revealHunkNonce` 重跳同一行。 */
|
|
80
|
+
revealHunkLine?: number;
|
|
81
|
+
/** 改动点跳转的 nonce(同 `revealNonce` 的道理)。 */
|
|
82
|
+
revealHunkNonce?: number;
|
|
83
|
+
/**
|
|
84
|
+
* 用户按下「上一个/下一个改动点」快捷键时回调。
|
|
85
|
+
*
|
|
86
|
+
* 列表算在 store 那边(`gotoChange`),编辑器只转发方向:组件内自己维护一份
|
|
87
|
+
* hunk 列表会与 tab 上的 `activeHunk` 漂成两份。
|
|
88
|
+
*/
|
|
89
|
+
onStepHunk?: (dir: 1 | -1) => void;
|
|
90
|
+
/**
|
|
91
|
+
* 文档被编辑、改动标记已作废时回调一次(false→true 的跳变)。
|
|
92
|
+
*
|
|
93
|
+
* 用回调而不是让上层轮询 handle:这件事发生在一次按键里,轮询会漏。
|
|
94
|
+
*/
|
|
95
|
+
onChangeStale?: () => void;
|
|
96
|
+
/** Reports how many token spans the grammar produced (diagnostics). */
|
|
97
|
+
onTokens?: (count: number) => void;
|
|
98
|
+
/**
|
|
99
|
+
* Reports this editor's scroll + caret as they change, and once more when it
|
|
100
|
+
* unmounts. The FIRST argument is this editor's own tab id, so the consumer
|
|
101
|
+
* never has to guess which tab a reading belongs to.
|
|
102
|
+
*
|
|
103
|
+
* The editor reports it, rather than the parent asking through the ref,
|
|
104
|
+
* because the parent's ref is already `null` by the time its layout-effect
|
|
105
|
+
* cleanup runs — React clears a child's ref before running the parent's
|
|
106
|
+
* cleanup. Only this component still holds the `EditorView` at that moment.
|
|
107
|
+
*/
|
|
108
|
+
onViewState?: (tabId: string, state: CodeEditorViewState) => void;
|
|
109
|
+
/**
|
|
110
|
+
* This editor's tab id, echoed back with every report (see `onViewState`).
|
|
111
|
+
* Optional because the diff panes mount `CodeEditor` too and have no tab of
|
|
112
|
+
* their own; they pass no `onViewState`, so nothing is reported for them.
|
|
113
|
+
*/
|
|
114
|
+
tabId?: string;
|
|
115
|
+
/**
|
|
116
|
+
* Scroll + caret to restore after this instance mounts.
|
|
117
|
+
*
|
|
118
|
+
* Read ONCE on mount (through a ref, so a later change to the prop cannot
|
|
119
|
+
* re-scroll a live editor out from under the reader). Used to come back to
|
|
120
|
+
* where a file was when its tab is re-selected.
|
|
121
|
+
*/
|
|
122
|
+
viewState?: CodeEditorViewState;
|
|
123
|
+
onSave: () => void;
|
|
124
|
+
onDirty: (dirty: boolean) => void;
|
|
125
|
+
} & import("react").RefAttributes<CodeEditorHandle>>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small inline input dialog (new file / folder / rename). Rendered as a
|
|
3
|
+
* fixed overlay card; Enter confirms, Escape cancels.
|
|
4
|
+
*/
|
|
5
|
+
import { type ReactNode } from 'react';
|
|
6
|
+
export declare function InputDialog(props: {
|
|
7
|
+
title: string;
|
|
8
|
+
label: string;
|
|
9
|
+
initial?: string;
|
|
10
|
+
placeholder?: string;
|
|
11
|
+
okText?: string;
|
|
12
|
+
onOk: (value: string) => void;
|
|
13
|
+
onCancel: () => void;
|
|
14
|
+
}): ReactNode;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 4 native directory-flow occupant: a Local/Remote tabbed dialog that
|
|
3
|
+
* shadows the stock picker (registered at priority -1 into both
|
|
4
|
+
* directory-flow holes).
|
|
5
|
+
*
|
|
6
|
+
* - Local leg: dual-mode. When the Host serves the browse capability, a
|
|
7
|
+
* compact in-app browser drives uiWorkspace.listDirectory/createDirectory.
|
|
8
|
+
* When it serves native (this Windows machine), one button opens the OS
|
|
9
|
+
* chooser via uiWorkspace.pickDirectory. The mode is probed once per open:
|
|
10
|
+
* a working list() means browse, `directory-picker/unavailable` means
|
|
11
|
+
* native, anything else is an inline error with retry.
|
|
12
|
+
* - Remote leg: the shared WorkspaceWizard; on save the materialized alias
|
|
13
|
+
* path goes to onPicked and the owner adopts it as a real workspace
|
|
14
|
+
* (its title was already set to `<server> > <base>` by syncWorkspaces).
|
|
15
|
+
*
|
|
16
|
+
* The occupant never drives the owner's onError arm: every failure stays
|
|
17
|
+
* inside the dialog (stock BrowseDirectoryFlow does the same), and Cancel is
|
|
18
|
+
* always available. If this entry ever crashes, slot shadowing retires it and
|
|
19
|
+
* the stock picker renders (graceful fallback, no dead surface).
|
|
20
|
+
*/
|
|
21
|
+
import { type ReactNode } from 'react';
|
|
22
|
+
import type { DirectoryFlowOwnerProps } from '@deepseek-ai/dsh-client-ui-workspace/client';
|
|
23
|
+
import type { DirectoryListing } from '@deepseek-ai/dsh-host-directory-picker/types';
|
|
24
|
+
/** Injected face: uiWorkspace directory primitives (bound in apply's closure). */
|
|
25
|
+
export interface DirectoryFlowInjected {
|
|
26
|
+
listDirectory: (path?: string, signal?: AbortSignal) => Promise<DirectoryListing>;
|
|
27
|
+
createDirectory: (path: string, name: string) => Promise<string>;
|
|
28
|
+
pickDirectory: () => Promise<string | null>;
|
|
29
|
+
}
|
|
30
|
+
export declare function RemoteDirectoryFlow(props: DirectoryFlowOwnerProps & DirectoryFlowInjected): ReactNode;
|