dsh-diff-approval 0.30.1 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -23
- package/README.zh.md +9 -21
- package/assets/fonts/README.md +10 -8
- package/assets/fonts/manifest.json +1 -1
- package/lib/client.js +2791 -894
- package/lib/index.js +1755 -269
- package/lib/types/annotate-skill.d.ts +1 -1
- package/lib/types/annotate-tool.d.ts +11 -0
- package/lib/types/annotate.d.ts +28 -0
- package/lib/types/client/PendingPanel.d.ts +31 -21
- package/lib/types/client/category-color.d.ts +62 -0
- package/lib/types/client/chords.d.ts +70 -17
- package/lib/types/client/discussion.d.ts +10 -3
- package/lib/types/client/dock.d.ts +8 -3
- package/lib/types/client/dsh-icons.d.ts +39 -1
- package/lib/types/client/header-entry.d.ts +8 -7
- package/lib/types/client/locales.d.ts +52 -12
- package/lib/types/client/panel-memory.d.ts +24 -6
- package/lib/types/client/path-match.d.ts +50 -0
- package/lib/types/client/port.d.ts +29 -2
- package/lib/types/client/produced-diff.d.ts +83 -6
- package/lib/types/client/reference.d.ts +0 -2
- package/lib/types/client/remap-sync.d.ts +18 -1
- package/lib/types/client/session-seat.d.ts +47 -7
- package/lib/types/client/settings.d.ts +40 -14
- package/lib/types/client/slots.d.ts +115 -3
- package/lib/types/client/store.d.ts +50 -0
- package/lib/types/client/whole-file-diff.d.ts +42 -0
- package/lib/types/comment-ask.d.ts +47 -20
- package/lib/types/comments.d.ts +141 -33
- package/lib/types/index.d.ts +1 -1
- package/lib/types/pending.d.ts +30 -4
- package/lib/types/types.d.ts +186 -4
- package/lib/types/vcs.d.ts +35 -1
- package/lib/types/version.d.ts +162 -0
- package/package.json +4 -2
|
@@ -204,32 +204,58 @@ export declare function setSplitMode(value: boolean): void;
|
|
|
204
204
|
export declare function mdPreviewEnabled(): boolean;
|
|
205
205
|
/** Persist the Markdown-preview default preference. */
|
|
206
206
|
export declare function setMdPreviewEnabled(value: boolean): void;
|
|
207
|
+
/**
|
|
208
|
+
* Whether this plugin MOUNTS its own file menu at all — the press bridge that takes over a file press in
|
|
209
|
+
* the shell's lists, a file link in a message, and a relative link in the Markdown preview.
|
|
210
|
+
*
|
|
211
|
+
* Default ON, and the default is the interesting half: only an explicit `'0'` turns it off, so a reader who
|
|
212
|
+
* never touches the setting keeps the menu they have always had (and a storage that cannot be read — a
|
|
213
|
+
* privacy mode, a cleared profile — leaves the plugin working rather than silently disabled).
|
|
214
|
+
*
|
|
215
|
+
* Off means the bridge does not take the press over AT ALL: no menu, and the shell's own behaviour runs
|
|
216
|
+
* exactly as if this plugin were not installed. That includes this plugin's own preview, where a relative
|
|
217
|
+
* link therefore goes back to the browser's own navigation — which is the honest consequence of asking this
|
|
218
|
+
* plugin to keep its hands off, not a bug to special-case.
|
|
219
|
+
*
|
|
220
|
+
* @returns whether the file menu is enabled.
|
|
221
|
+
*/
|
|
222
|
+
export declare function chipMenuEnabled(): boolean;
|
|
223
|
+
/** Persist the file-menu preference (see {@link chipMenuEnabled}). */
|
|
224
|
+
export declare function setChipMenuEnabled(value: boolean): void;
|
|
207
225
|
/**
|
|
208
226
|
* Whether commenting on a diff range is offered: the selection frame's comment button,
|
|
209
|
-
* and the chord that stands for it. Defaults to
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
227
|
+
* and the chord that stands for it. **Defaults to ON** — comment mode is the mode now,
|
|
228
|
+
* not a preview — so only an explicit `'0'` turns it off (the `chipMenuEnabled` idiom a
|
|
229
|
+
* few lines up), and an absent field means on for every profile that predates the flip.
|
|
230
|
+
* This gates STARTING a thread, not the threads themselves — one already written keeps
|
|
231
|
+
* rendering, so turning the mode off never hides work or interrupts an answer on its way
|
|
232
|
+
* back.
|
|
213
233
|
* @returns whether comment mode is on.
|
|
214
234
|
*/
|
|
215
235
|
export declare function commentModeEnabled(): boolean;
|
|
216
|
-
/** Window event: comment mode was toggled (see {@link setCommentModeEnabled}).
|
|
217
|
-
|
|
218
|
-
|
|
236
|
+
/** Window event: comment mode was toggled (see {@link setCommentModeEnabled}). A DIFFERENT string from
|
|
237
|
+
* {@link COMMENT_MODE_KEY} on purpose: a key literal and an event literal that read the same make every
|
|
238
|
+
* grep a guess (the hazard this file already names for the file-menu key at the top). */
|
|
239
|
+
export declare const COMMENT_MODE_CHANGED_EVENT = "diff-approval:comment-mode-changed";
|
|
240
|
+
/** Persist the comment-mode preference (`'1'` on, `'0'` off — see {@link commentModeEnabled}). */
|
|
219
241
|
export declare function setCommentModeEnabled(value: boolean): void;
|
|
220
242
|
/**
|
|
221
243
|
* Whether the diff's code uses the font this plugin bundles.
|
|
222
244
|
*
|
|
223
|
-
* Defaults to
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
* is
|
|
227
|
-
*
|
|
245
|
+
* **Defaults to ON**, like comment mode: absent means on, and only an explicit `'0'` turns
|
|
246
|
+
* it off. The old field's answer is deliberately NOT carried over (see the constant): a
|
|
247
|
+
* reader who had turned the bundled face off before this change comes back with it on, which
|
|
248
|
+
* is the one behaviour the reader asked for. The cost is real and worth naming where the
|
|
249
|
+
* switch is: the face is a JetBrains Maple Mono subset sliced into 138 woff2 files, and a
|
|
250
|
+
* page pulls only the slices its characters land in — tens of kilobytes for a comment, up to
|
|
251
|
+
* ~120 KB for a rare character — downloaded on demand rather than shipped.
|
|
228
252
|
* @returns whether the bundled code font is enabled.
|
|
229
253
|
*/
|
|
230
254
|
export declare function codeFontEnabled(): boolean;
|
|
231
|
-
/** Window event: the bundled code font was turned on or off (see {@link setCodeFontEnabled}).
|
|
232
|
-
|
|
255
|
+
/** Window event: the bundled code font was turned on or off (see {@link setCodeFontEnabled}). A DIFFERENT
|
|
256
|
+
* string from {@link CODE_FONT_KEY} on purpose — they used to be the same literal, which made a grep for
|
|
257
|
+
* either one a guess (the hazard this file names for the file-menu key at the top). */
|
|
258
|
+
export declare const CODE_FONT_CHANGED_EVENT = "diff-approval:code-font-changed";
|
|
233
259
|
/** Persist the bundled-code-font preference. */
|
|
234
260
|
export declare function setCodeFontEnabled(value: boolean): void;
|
|
235
261
|
/**
|
|
@@ -4,7 +4,7 @@ import type { SessionId } from '@deepseek-ai/dsh-client-connection/client';
|
|
|
4
4
|
import type { DockSnapshot } from './dock.tsx';
|
|
5
5
|
import type { CommentDraft } from './port.ts';
|
|
6
6
|
import type { CommentLineRange } from '../comment-lines.ts';
|
|
7
|
-
import type { CommentRecord, DiffApprovalAddValue, DiffApprovalBlockRange, DiffApprovalBrowseValue, DiffApprovalCommentAddValue, DiffApprovalCommentAskValue, DiffApprovalCommentRemoveManyValue, DiffApprovalCommentRemoveValue, DiffApprovalOpenAction, DiffApprovalRefreshValue, PendingFileDiff, VcsImportValue } from '../types.ts';
|
|
7
|
+
import type { CommentRecord, DiffApprovalAddValue, DiffApprovalBlockRange, DiffApprovalBrowseValue, DiffApprovalCommentAddValue, DiffApprovalCommentAskValue, DiffApprovalCommentRemoveManyValue, DiffApprovalCommentRemoveValue, DiffApprovalOpenAction, DiffApprovalRefreshValue, DiffApprovalUpdateValue, PendingFileDiff, VcsImportValue } from '../types.ts';
|
|
8
8
|
/** What the panel reads and drives: the pending list plus in-flight entries. */
|
|
9
9
|
export interface PendingDiffSnapshot {
|
|
10
10
|
/** Whether a list read has completed at least once. */
|
|
@@ -32,6 +32,21 @@ export interface PendingDiffSnapshot {
|
|
|
32
32
|
commentAnswers: Record<string, string>;
|
|
33
33
|
/** A read failure's message; absent while the latest read succeeded. */
|
|
34
34
|
error?: string;
|
|
35
|
+
/**
|
|
36
|
+
* How many rows the host says this session has, from the LIGHT read (`list-count`) — the badge's
|
|
37
|
+
* question, answered without shipping any file content.
|
|
38
|
+
*
|
|
39
|
+
* It is the number a full read would have carried, but it is a separate fact with a separate age: a
|
|
40
|
+
* count lands on ticks that ask only for a count (when nothing is showing), and a FULL read clears it,
|
|
41
|
+
* because that read carries the same fact in `files` and is newer. So a reader's rule is
|
|
42
|
+
* `count ?? files.length` — the fallback covers both "no count has arrived" and "the list is fresher",
|
|
43
|
+
* and a count that FAILED leaves the previous number in place rather than showing zero.
|
|
44
|
+
*
|
|
45
|
+
* Absent on a host that predates the endpoint: there the store keeps doing full reads, the value stays
|
|
46
|
+
* absent, and every reader stays on the fallback — today's behaviour, at today's cost (see
|
|
47
|
+
* `PendingDiffStore.refreshCount`).
|
|
48
|
+
*/
|
|
49
|
+
count?: number | undefined;
|
|
35
50
|
/** Entry ids whose keep/revert is in flight; their controls are disabled. */
|
|
36
51
|
busy: ReadonlySet<string>;
|
|
37
52
|
/** Entry ids whose last keep/revert failed, mapped to the error message; the panel surfaces these inline (row tag + detail banner) instead of hiding the list. */
|
|
@@ -69,8 +84,81 @@ export interface PendingDiffSnapshot {
|
|
|
69
84
|
* panel is closed) via a bottom-right notice. */
|
|
70
85
|
redoCleared?: boolean;
|
|
71
86
|
}
|
|
87
|
+
/** One filter over one session's view of the pending list; the shape every view hook takes. */
|
|
88
|
+
export type PendingViewSelector<T> = (view: PendingDiffSnapshot) => T;
|
|
89
|
+
/**
|
|
90
|
+
* The number a badge, a header entry or a dock chip shows for one session: the light count when one has
|
|
91
|
+
* arrived, else the length of the list that read carried.
|
|
92
|
+
*
|
|
93
|
+
* The fallback is not a nicety — it is the whole older-host story and the state before the first count
|
|
94
|
+
* lands. `count` is absent when no count has arrived (the page just loaded, or the host predates the
|
|
95
|
+
* endpoint, where the store keeps doing full reads) and it is cleared by every full read, whose `files`
|
|
96
|
+
* are the same fact and newer. So `count` is only ever shown while it is the FRESHEST number the client
|
|
97
|
+
* has; otherwise the list speaks. See `PendingDiffSnapshot.count`.
|
|
98
|
+
* @param view - one session's view.
|
|
99
|
+
* @returns how many rows it holds.
|
|
100
|
+
*/
|
|
101
|
+
export declare function countOf(view: PendingDiffSnapshot): number;
|
|
102
|
+
/**
|
|
103
|
+
* The view a seat draws when it has NO reviewable session: a session that is absent, or one the shell has
|
|
104
|
+
* told us is blank — a brand-new session with nothing said in it yet.
|
|
105
|
+
*
|
|
106
|
+
* This exists because the page-wide fallback is the wrong answer for such a seat. `viewFor(undefined)`
|
|
107
|
+
* and `getSnapshot()` deliberately answer the newest session read on the page (the whole-page contract the
|
|
108
|
+
* remap follows), and with the store's slots keyed only by session id and no workspace comparison anywhere
|
|
109
|
+
* in this client, "the newest read" can be another session's list — even another workspace's. A seat that
|
|
110
|
+
* has nothing of its own to show must draw NOTHING, and this is the nothing: no rows (so `countOf` is 0),
|
|
111
|
+
* no comments, no workspace, and `read: false` because nothing has been read FOR this seat.
|
|
112
|
+
*
|
|
113
|
+
* Shared rather than re-spelled per seat: the panel's badge and body, the header entry's count and the
|
|
114
|
+
* dock chip all have to agree that "no reviewable session" means zero, and a second literal here is how
|
|
115
|
+
* they would drift. Frozen because it is handed to readers as a snapshot: nothing may write through it.
|
|
116
|
+
*/
|
|
117
|
+
export declare const EMPTY_PENDING_VIEW: PendingDiffSnapshot;
|
|
118
|
+
/**
|
|
119
|
+
* The view a seat DRAWS when there is nothing to review: the same emptiness as {@link EMPTY_PENDING_VIEW},
|
|
120
|
+
* but READ.
|
|
121
|
+
*
|
|
122
|
+
* The difference is what the panel draws: an unread view is a session whose list is still coming, so it
|
|
123
|
+
* shows the loading state, while this one says "no pending changes" — the truth for a session that is
|
|
124
|
+
* absent or blank. Using the unread view here would leave a docked panel spinning forever behind an entry
|
|
125
|
+
* that is disabled and can never be opened.
|
|
126
|
+
*/
|
|
127
|
+
export declare const NOTHING_TO_REVIEW_VIEW: PendingDiffSnapshot;
|
|
128
|
+
/** Function shape of {@link PendingViewHooks.pendingView}; named so a seat can call it directly. */
|
|
129
|
+
export type PendingViewReader = <T>(sessionId: SessionId | undefined, select: PendingViewSelector<T>) => T;
|
|
130
|
+
/**
|
|
131
|
+
* How a seat reads pending state for the session it is actually about.
|
|
132
|
+
*
|
|
133
|
+
* The page runs several seats at once — the sidebar footer's badge, the Session header's entry, the
|
|
134
|
+
* docked tab's chip and body — and they can belong to different sessions. Reading the ONE page-wide
|
|
135
|
+
* snapshot is what let a badge show another session's count, so a seat that knows its session reads
|
|
136
|
+
* that session's own view through {@link PendingViewHooks.pendingView} and nothing else.
|
|
137
|
+
*
|
|
138
|
+
* This lives BESIDE the face's `hooks`, not inside it: that compartment is a map of observables
|
|
139
|
+
* (`HooksSources`), and the framework binds each one onto the component props as a `use<Name>` selector
|
|
140
|
+
* hook. Folding a non-observable in there would stop the whole compartment from matching, and the panel
|
|
141
|
+
* would lose `usePending` and `useDock` with it. `pendingView` is a read, not an observable: the panel's
|
|
142
|
+
* `usePending` subscription is what re-renders it.
|
|
143
|
+
*
|
|
144
|
+
* The hook is optional because the shapes are read structurally: a host (or a test) that offers only
|
|
145
|
+
* `pending` still renders, with the panel falling back to the page-wide snapshot.
|
|
146
|
+
*/
|
|
147
|
+
export interface PendingViewHooks {
|
|
148
|
+
/**
|
|
149
|
+
* Read one session's own view, with `select` applied inside the subscription.
|
|
150
|
+
*
|
|
151
|
+
* `sessionId === undefined` means "I have no session of my own": the answer is then the page-wide
|
|
152
|
+
* view (the newest session read), which is what a whole-page caller wants. A session that has never
|
|
153
|
+
* been read answers an unread empty view — never another session's list.
|
|
154
|
+
* @param sessionId - the session whose view to read.
|
|
155
|
+
* @param select - what to take out of that view.
|
|
156
|
+
* @returns the selected value.
|
|
157
|
+
*/
|
|
158
|
+
pendingView?: PendingViewReader | undefined;
|
|
159
|
+
}
|
|
72
160
|
/** The injected face the panel component receives from the plugin body. */
|
|
73
|
-
export interface PendingPanelFace {
|
|
161
|
+
export interface PendingPanelFace extends PendingViewHooks {
|
|
74
162
|
hooks: {
|
|
75
163
|
/** Live pending-diff snapshot for the current page. */
|
|
76
164
|
pending: HostObservable<PendingDiffSnapshot>;
|
|
@@ -91,8 +179,26 @@ export interface PendingPanelFace {
|
|
|
91
179
|
/** Close the docked tab now, when one exists (the quick-summon chord's job
|
|
92
180
|
* while the panel lives in the sidebar). */
|
|
93
181
|
closeDock?: (() => void) | undefined;
|
|
182
|
+
/**
|
|
183
|
+
* Ask the host whether a newer release of this plugin is published.
|
|
184
|
+
*
|
|
185
|
+
* Optional, and deliberately so: a seat that is not given one (an older shell wiring, or a
|
|
186
|
+
* harness that does not stage it) simply draws no update chip. The panel calls it ONCE, after
|
|
187
|
+
* mount, and never waits on it — a host that cannot answer, or has no answer to give, leaves the
|
|
188
|
+
* chip's site exactly as it was. The answer is drawn in ONE place (the open file's status bar, at
|
|
189
|
+
* the panel's bottom edge) out of one piece of panel state.
|
|
190
|
+
*/
|
|
191
|
+
onCheckUpdate?: (() => Promise<DiffApprovalUpdateValue>) | undefined;
|
|
94
192
|
/** Read the pending list for the current session into the snapshot. */
|
|
95
193
|
onRefresh: (sessionId: SessionId | undefined) => void;
|
|
194
|
+
/**
|
|
195
|
+
* The badge's tick, for a seat with nothing on screen: ask how many rows the session has, with no file
|
|
196
|
+
* content on the wire. Optional so a face built against an older shape still renders — the panel then
|
|
197
|
+
* keeps the full read, which is what such a page did before the verb existed.
|
|
198
|
+
*/
|
|
199
|
+
onRefreshCount?: ((sessionId: SessionId | undefined) => void) | undefined;
|
|
200
|
+
/** The reader is looking at this file now: its row's unseen dot goes out. */
|
|
201
|
+
onMarkSeen?: ((sessionId: SessionId | undefined, id: string) => void) | undefined;
|
|
96
202
|
/** Keep one operation. `keepListed` leaves the resolved entry in the list. */
|
|
97
203
|
onKeep: (sessionId: SessionId, id: string, keepListed?: boolean) => Promise<void>;
|
|
98
204
|
/** Revert one operation (restore its prior content, or remove a created file).
|
|
@@ -149,7 +255,7 @@ export interface PendingPanelFace {
|
|
|
149
255
|
/**
|
|
150
256
|
* Drop several annotations in one host call. Ending a pick of comments is one action the reader
|
|
151
257
|
* asked for, so it goes to the host as one request and comes back as one read — see
|
|
152
|
-
* `
|
|
258
|
+
* `PendingDiffStore.commentRemoveMany`.
|
|
153
259
|
*/
|
|
154
260
|
onCommentRemoveMany: (sessionId: SessionId, ids: readonly string[]) => Promise<DiffApprovalCommentRemoveManyValue>;
|
|
155
261
|
/**
|
|
@@ -159,6 +265,12 @@ export interface PendingPanelFace {
|
|
|
159
265
|
* question so the thread can draw what was written.
|
|
160
266
|
*/
|
|
161
267
|
onCommentAsk: (sessionId: SessionId, id: string, prompt: string, text: string) => Promise<DiffApprovalCommentAskValue>;
|
|
268
|
+
/**
|
|
269
|
+
* The reader has this comment's card in view: its unseen dot goes out, and the answers on it become
|
|
270
|
+
* the baseline a later rewrite is measured against. A reader action, so it is its own call rather
|
|
271
|
+
* than something a read does — a read that cleared attention would make the dot impossible to see.
|
|
272
|
+
*/
|
|
273
|
+
onCommentSeen?: ((sessionId: SessionId, id: string) => void) | undefined;
|
|
162
274
|
/** Acknowledge the redo-cleared notice so it is only surfaced once. */
|
|
163
275
|
onAckRedoCleared: () => void;
|
|
164
276
|
/** Acknowledge a refused-undo notice once the panel has said it (see `undoNotice`). */
|
|
@@ -12,8 +12,51 @@ import type { PendingDiffSnapshot } from './slots.ts';
|
|
|
12
12
|
import type { CommentDraft, DiffApprovalPort } from './port.ts';
|
|
13
13
|
/** The observable the panel reads and the plugin body drives. */
|
|
14
14
|
export interface PendingDiffStore extends HostObservable<PendingDiffSnapshot> {
|
|
15
|
+
/**
|
|
16
|
+
* One session's own view of the pending list.
|
|
17
|
+
*
|
|
18
|
+
* The page runs several mounts of the panel at once — the footer entry, the Session header entry and
|
|
19
|
+
* the docked tab — and they can be about DIFFERENT sessions (the reader is in one, while the docked
|
|
20
|
+
* tab was opened from another). One page-wide snapshot made every badge read whichever session's read
|
|
21
|
+
* landed last, which is the cross-talk the reader reported: a badge showing another session's count,
|
|
22
|
+
* and a session with a real list showing zero. So each session's read publishes only into its own
|
|
23
|
+
* slot, and every consumer takes its own slot, by the session it is actually about.
|
|
24
|
+
*
|
|
25
|
+
* A session that has never been read answers an unread empty view — never another session's list.
|
|
26
|
+
* `sessionId === undefined` answers the most recently published view — exactly what
|
|
27
|
+
* {@link PendingDiffStore.getSnapshot} answers — because that is what a caller with no session of its
|
|
28
|
+
* own (a whole-page concern) is asking for.
|
|
29
|
+
* @param sessionId - the session whose view to read.
|
|
30
|
+
* @returns that session's snapshot, identity-stable until it is next published.
|
|
31
|
+
*/
|
|
32
|
+
viewFor: (sessionId: SessionId | undefined) => PendingDiffSnapshot;
|
|
33
|
+
/**
|
|
34
|
+
* Every session's view this page is HOLDING, one per session it has read.
|
|
35
|
+
*
|
|
36
|
+
* This is the reader a press needs (see `produced-diff.ts`): a shell press names a file and asks whether
|
|
37
|
+
* ANY session this page has read holds it, and the press cannot wait for a poll. The slots are where the
|
|
38
|
+
* fresh paths live — the light read's `paths` and the last full read's `files` — so answering from the
|
|
39
|
+
* slots is what keeps a file edited since the last full read from being a press nothing recognises.
|
|
40
|
+
* @returns the held views; a session never read contributes nothing (it has no slot).
|
|
41
|
+
*/
|
|
42
|
+
views: () => readonly PendingDiffSnapshot[];
|
|
15
43
|
/** Re-read one session's pending list (an absent session empties the view). */
|
|
16
44
|
refresh: (sessionId: SessionId | undefined) => Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Ask how many rows this session has, THE LIGHT WAY: one number, no file content.
|
|
47
|
+
*
|
|
48
|
+
* This is what a seat polls while it is showing nothing but its badge. It publishes into the same
|
|
49
|
+
* per-session slot as `refresh` — as `count`, never as `files` — so a badge keeps reading the view it
|
|
50
|
+
* already subscribes to, and it deliberately does NOT set `read` (a count is not a list: a session
|
|
51
|
+
* whose list has never been read stays unread, and its list stays empty rather than looking loaded).
|
|
52
|
+
*
|
|
53
|
+
* Failure keeps the LAST GOOD number: a count that failed knows nothing, and showing 0 or a blank
|
|
54
|
+
* badge for a list that may well be full is worse than showing the previous figure. A host that
|
|
55
|
+
* predates the endpoint (`unknown endpoint "list-count"`) is detected here ONCE and then this call
|
|
56
|
+
* degrades to `refresh`, which is exactly the old behaviour at exactly the old cost — one full read
|
|
57
|
+
* per tick — and an absent session has no count to ask for, so it returns without a call.
|
|
58
|
+
*/
|
|
59
|
+
refreshCount: (sessionId: SessionId | undefined) => Promise<void>;
|
|
17
60
|
/** Keep one operation. `keepListed` leaves the resolved entry in the list. */
|
|
18
61
|
keep: (sessionId: SessionId, id: string, keepListed?: boolean) => Promise<void>;
|
|
19
62
|
/** Revert one operation. `keepListed` leaves the resolved entry in the list. */
|
|
@@ -35,6 +78,11 @@ export interface PendingDiffStore extends HostObservable<PendingDiffSnapshot> {
|
|
|
35
78
|
browse: (sessionId: SessionId, path?: string) => Promise<DiffApprovalBrowseValue>;
|
|
36
79
|
/** Add one named path to the list, then refresh; resolves to what the scan did. */
|
|
37
80
|
addPath: (sessionId: SessionId, path: string, includeUnchanged: boolean, exact?: boolean) => Promise<DiffApprovalAddValue>;
|
|
81
|
+
/**
|
|
82
|
+
* The reader is looking at this file now: its row's unseen dot goes out. Deliberately NOT followed by a
|
|
83
|
+
* re-read — see the note at the implementation.
|
|
84
|
+
*/
|
|
85
|
+
markSeen: (sessionId: SessionId, id: string) => Promise<void>;
|
|
38
86
|
/** Open one file with its default application or reveal it in the folder. */
|
|
39
87
|
open: (sessionId: SessionId, id: string, action: DiffApprovalOpenAction) => Promise<void>;
|
|
40
88
|
/** Keep every pending entry of one session in a single host call, then refresh. */
|
|
@@ -70,6 +118,8 @@ export interface PendingDiffStore extends HostObservable<PendingDiffSnapshot> {
|
|
|
70
118
|
commentRemoveMany: (sessionId: SessionId, ids: readonly string[]) => Promise<DiffApprovalCommentRemoveManyValue>;
|
|
71
119
|
/** Ask one stored comment as its own turn, then refresh (the answer is derived on read). */
|
|
72
120
|
commentAsk: (sessionId: SessionId, id: string, prompt: string, text: string) => Promise<DiffApprovalCommentAskValue>;
|
|
121
|
+
/** The reader is looking at a comment: its card's dot goes out, then the list is re-read. */
|
|
122
|
+
commentSeen: (sessionId: SessionId, id: string) => Promise<void>;
|
|
73
123
|
/** Drop every local fact (used on connection reset). */
|
|
74
124
|
reset: () => void;
|
|
75
125
|
/** Acknowledge a redo-cleared notice so the panel surfaces it only once. */
|
|
@@ -33,6 +33,21 @@ export interface ChangeBlock {
|
|
|
33
33
|
}
|
|
34
34
|
/** Split a row list into maximal runs of non-context rows. */
|
|
35
35
|
export declare function changeBlocksOf(diff: WholeFileDiff): ChangeBlock[];
|
|
36
|
+
/**
|
|
37
|
+
* The change region a row range sits in, widened to whole runs: from the first row of the run `start`
|
|
38
|
+
* belongs to, to the last row of the run `end` belongs to. A row range that names one side of a change —
|
|
39
|
+
* one line of a replacement, or the additions alone — grows to cover that change's removals as well, which
|
|
40
|
+
* is what a reader pointing at it means. Rows that are context stand alone (a run is a maximal non-context
|
|
41
|
+
* run), so a range that touches no change is returned as it is.
|
|
42
|
+
* @param rows - the whole-file diff rows.
|
|
43
|
+
* @param start - the range's first row.
|
|
44
|
+
* @param end - the range's last row.
|
|
45
|
+
* @returns the widened row range.
|
|
46
|
+
*/
|
|
47
|
+
export declare function changeRunSpanOf(rows: readonly WholeFileDiffRow[], start: number, end: number): {
|
|
48
|
+
start: number;
|
|
49
|
+
end: number;
|
|
50
|
+
};
|
|
36
51
|
/**
|
|
37
52
|
* One diff block's old/new line ranges, 1-based inclusive, for block-level
|
|
38
53
|
* keep/revert. A side with no lines (a pure addition or deletion) is empty; its
|
|
@@ -46,6 +61,33 @@ export declare function changeBlocksOf(diff: WholeFileDiff): ChangeBlock[];
|
|
|
46
61
|
* @returns the old and new line ranges.
|
|
47
62
|
*/
|
|
48
63
|
export declare function blockRangesOf(rows: readonly WholeFileDiffRow[], block: ChangeBlock): DiffApprovalBlockRange;
|
|
64
|
+
/** One side-by-side pair: the whole-file row index each side holds, when it has one. */
|
|
65
|
+
export interface DiffPairRows {
|
|
66
|
+
old?: number;
|
|
67
|
+
new?: number;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* One side-by-side pair's old/new line ranges, 1-based inclusive, for pair-level keep/revert. A pair is
|
|
71
|
+
* one old line against one new line, so each side contributes a single line; a side the pair does not
|
|
72
|
+
* have (an unpaired insertion or removal) is the empty range at that side's insertion point, read off the
|
|
73
|
+
* row before the pair — the same rule `blockRangesOf` applies to a side with no lines at all.
|
|
74
|
+
* @param rows - the whole-file diff rows.
|
|
75
|
+
* @param pair - the rows the pair holds.
|
|
76
|
+
* @returns the old and new line ranges, or undefined for a pair that holds no row.
|
|
77
|
+
*/
|
|
78
|
+
export declare function pairRangeOf(rows: readonly WholeFileDiffRow[], pair: DiffPairRows): DiffApprovalBlockRange | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* The old/new ranges keep/revert act on for the pairs a side-by-side selection names, in the order they
|
|
81
|
+
* must be sent. Pairs of context rows are dropped (nothing to act on). When the changed pairs occupy one
|
|
82
|
+
* contiguous span of the row list with no other changed row inside it, ONE range covers them all: one
|
|
83
|
+
* host call, one undo step, and the same range a whole-block action would send. Otherwise each pair gets
|
|
84
|
+
* its own range, ordered from the BOTTOM of the file up, so a splice never moves the line numbers of a
|
|
85
|
+
* pair still to be applied.
|
|
86
|
+
* @param rows - the whole-file diff rows.
|
|
87
|
+
* @param pairs - the pairs the selection names, in row order.
|
|
88
|
+
* @returns one range, several, or none when the selection carries no change.
|
|
89
|
+
*/
|
|
90
|
+
export declare function pairsRangeOf(rows: readonly WholeFileDiffRow[], pairs: readonly DiffPairRows[]): DiffApprovalBlockRange[];
|
|
49
91
|
/**
|
|
50
92
|
* Compute the whole-file view between two contents. The context budget is the
|
|
51
93
|
* side lengths, so every hunk covers the file and unchanged lines survive as
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import type { Context } from '@deepseek-ai/cordis';
|
|
21
21
|
import type { SessionId } from '@deepseek-ai/dsh-session';
|
|
22
|
-
import type { DiffApprovalCommentAskValue } from './types.ts';
|
|
23
|
-
import type { CommentStore } from './comments.ts';
|
|
22
|
+
import type { DiffApprovalCommentAskValue, CommentRecord } from './types.ts';
|
|
23
|
+
import type { CommentScope, CommentStore } from './comments.ts';
|
|
24
24
|
/** One derived transcript message, narrowed to what answering a comment needs. */
|
|
25
25
|
interface DerivedMessage {
|
|
26
26
|
role?: unknown;
|
|
@@ -75,6 +75,14 @@ export interface AskRead {
|
|
|
75
75
|
/** The claiming turn is over (`turn/end` in the log), whatever it did or did not write. */
|
|
76
76
|
ended: boolean;
|
|
77
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* One request's already-read transcripts, keyed by the session read.
|
|
80
|
+
*
|
|
81
|
+
* `undefined` is cached as a value (hence `has`, not a truthy check): a session whose log cannot be
|
|
82
|
+
* read must not be asked for again within the same read, or a folded-over lineage would pay for the
|
|
83
|
+
* same failure once per transcript.
|
|
84
|
+
*/
|
|
85
|
+
export type TranscriptCache = Map<SessionId, readonly LogEvent[] | undefined>;
|
|
78
86
|
/**
|
|
79
87
|
* The answer to one submitted comment, read from the session's own event log.
|
|
80
88
|
*
|
|
@@ -99,13 +107,6 @@ export interface AskRead {
|
|
|
99
107
|
* @returns the claiming turn, its answer text, and whether the turn is over.
|
|
100
108
|
*/
|
|
101
109
|
export declare function answerForRequest(events: readonly LogEvent[], requestId: string): AskRead;
|
|
102
|
-
/**
|
|
103
|
-
* Read every question one session has asked, keyed by its request id.
|
|
104
|
-
* @param events - the session's event log, in sequence order.
|
|
105
|
-
* @param requestIds - the questions' request ids.
|
|
106
|
-
* @returns the read per request id; a question the log holds nothing for reads as nothing.
|
|
107
|
-
*/
|
|
108
|
-
export declare function answersIn(events: readonly LogEvent[], requestIds: readonly string[]): Record<string, AskRead>;
|
|
109
110
|
/**
|
|
110
111
|
* Submits comments as turns of their session, and reads their answers back.
|
|
111
112
|
*
|
|
@@ -140,24 +141,44 @@ export declare class CommentAsker {
|
|
|
140
141
|
* arrives while the submission is still in flight already finds its comment. A
|
|
141
142
|
* submission that rejects is marked dropped rather than left looking pending.
|
|
142
143
|
*
|
|
143
|
-
* @param sessionId - the session the
|
|
144
|
+
* @param sessionId - the session the question is asked FROM: the prompt goes into it, so its
|
|
145
|
+
* transcript is where the answer will appear.
|
|
144
146
|
* @param commentId - the comment to ask.
|
|
145
147
|
* @param prompt - the prompt text to send verbatim.
|
|
146
148
|
* @param text - the reader's own words, which the prompt wraps: the thread keeps these.
|
|
147
149
|
* @param signal - aborts the submission.
|
|
150
|
+
* @param inScope - which comment authors the caller's lineage covers, so a thread another seat
|
|
151
|
+
* wrote can be asked from this one.
|
|
148
152
|
* @returns what the request did.
|
|
149
153
|
*/
|
|
150
|
-
ask(sessionId: SessionId, commentId: string, prompt: string, text: string, signal: AbortSignal): Promise<DiffApprovalCommentAskValue>;
|
|
154
|
+
ask(sessionId: SessionId, commentId: string, prompt: string, text: string, signal: AbortSignal, inScope: CommentScope): Promise<DiffApprovalCommentAskValue>;
|
|
155
|
+
/**
|
|
156
|
+
* One scope's questions, grouped by THE TRANSCRIPT each was submitted into.
|
|
157
|
+
*
|
|
158
|
+
* A lineage's threads may hold questions asked from several seats, and an answer lives in the
|
|
159
|
+
* transcript of the session that was asked — so the read is per transcript, not per thread. Grouping
|
|
160
|
+
* here (rather than reading each thread separately) is what keeps a poll to ONE log walk per
|
|
161
|
+
* transcript, however many threads asked there.
|
|
162
|
+
*
|
|
163
|
+
* The legacy case is why `askTranscript` exists: a question asked before the asking session was
|
|
164
|
+
* recorded has only the comment's own session to go on, which is where it went.
|
|
165
|
+
* @param visible - the comments this read may see (already scoped by the caller).
|
|
166
|
+
* @returns the request ids per transcript; empty when nothing was ever asked.
|
|
167
|
+
*/
|
|
168
|
+
answerGroups(visible: readonly CommentRecord[]): Map<SessionId, string[]>;
|
|
151
169
|
/**
|
|
152
|
-
* Every question
|
|
170
|
+
* Every question submitted into ONE transcript and what it says about them, keyed by the
|
|
153
171
|
* question's request id (which is what `CommentAsk` records and what a client matches
|
|
154
|
-
* against). Derived from
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* @param sessionId - the
|
|
172
|
+
* against). Derived from that session's event log on every call and never stored: the log
|
|
173
|
+
* is the one source of truth for what the agent said, so a stored copy could only ever
|
|
174
|
+
* disagree with it.
|
|
175
|
+
* @param sessionId - the transcript to read.
|
|
176
|
+
* @param requestIds - the questions that were submitted into it.
|
|
177
|
+
* @param cache - this request's already-read logs, so the answer fold and the turn-end read
|
|
178
|
+
* below share ONE walk of a log that can hold >100k events.
|
|
158
179
|
* @returns the read per question; empty when the session holds no questions.
|
|
159
180
|
*/
|
|
160
|
-
|
|
181
|
+
answersFor(sessionId: SessionId, requestIds: readonly string[], cache?: TranscriptCache): Record<string, AskRead>;
|
|
161
182
|
/**
|
|
162
183
|
* The turns the session's log shows as over, for the questions it holds.
|
|
163
184
|
*
|
|
@@ -167,10 +188,11 @@ export declare class CommentAsker {
|
|
|
167
188
|
* a client that connected afterwards, a deployment whose agent events cannot be
|
|
168
189
|
* subscribed at all — because `turn/end` is durable and the event is not.
|
|
169
190
|
*
|
|
170
|
-
* @param sessionId - the
|
|
191
|
+
* @param sessionId - the transcript whose turns to check.
|
|
192
|
+
* @param cache - this request's already-read logs (see `answersFor`).
|
|
171
193
|
* @returns the turns the log closed, or empty when the log cannot be read.
|
|
172
194
|
*/
|
|
173
|
-
endedTurns(sessionId: SessionId): number[];
|
|
195
|
+
endedTurns(sessionId: SessionId, cache?: TranscriptCache): number[];
|
|
174
196
|
/**
|
|
175
197
|
* One session's event log, as the reads above take it.
|
|
176
198
|
*
|
|
@@ -180,7 +202,12 @@ export declare class CommentAsker {
|
|
|
180
202
|
* and `snapshotEvents` hands back a frozen, sequence-ordered copy of it, so walking it
|
|
181
203
|
* is a plain read that cannot disturb the session.
|
|
182
204
|
*
|
|
205
|
+
* The cache is per REQUEST, not per session: one list read asks this twice for every transcript
|
|
206
|
+
* (the answer fold and the turn-end read), and the copy is the expensive part of a long session's
|
|
207
|
+
* log. `has` rather than a truthy check, so "cannot be read" is cached as such instead of being
|
|
208
|
+
* asked again.
|
|
183
209
|
* @param sessionId - the session to read.
|
|
210
|
+
* @param cache - this request's already-read logs, when the caller is doing several reads.
|
|
184
211
|
* @returns its events, or undefined when no live session can be read.
|
|
185
212
|
*/
|
|
186
213
|
private sessionEvents;
|
|
@@ -200,7 +227,7 @@ export declare class CommentAsker {
|
|
|
200
227
|
* released first, so re-arming is not a leak: one set of handlers per live agent,
|
|
201
228
|
* not one more per identity change.
|
|
202
229
|
*
|
|
203
|
-
* Called on every ask AND on every poll read (`
|
|
230
|
+
* Called on every ask AND on every poll read (`answersFor`), because the agent can be
|
|
204
231
|
* replaced while a question is still pending — the case where nothing asks again.
|
|
205
232
|
* @param sessionId - the session whose agent to watch.
|
|
206
233
|
*/
|