dsh-diff-approval 0.29.1 → 0.31.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 +7 -4
- package/README.zh.md +7 -4
- package/assets/fonts/README.md +10 -8
- package/assets/fonts/manifest.json +1 -1
- package/lib/client.js +5161 -1972
- package/lib/index.js +4331 -442
- package/lib/types/annotate-skill.d.ts +34 -0
- package/lib/types/annotate-tool.d.ts +227 -0
- package/lib/types/annotate.d.ts +186 -0
- package/lib/types/atomic-write.d.ts +77 -0
- package/lib/types/client/PendingPanel.d.ts +92 -24
- 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 +76 -16
- package/lib/types/client/dock.d.ts +8 -8
- package/lib/types/client/dsh-icons.d.ts +98 -0
- package/lib/types/client/header-entry.d.ts +27 -11
- package/lib/types/client/ids.d.ts +18 -0
- package/lib/types/client/locales.d.ts +88 -36
- package/lib/types/client/panel-memory.d.ts +118 -35
- package/lib/types/client/path-match.d.ts +50 -0
- package/lib/types/client/port.d.ts +77 -1
- package/lib/types/client/presentation-menu.d.ts +1 -1
- package/lib/types/client/produced-diff.d.ts +136 -22
- 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 +93 -0
- package/lib/types/client/settings.d.ts +40 -14
- package/lib/types/client/slots.d.ts +184 -12
- package/lib/types/client/store.d.ts +85 -2
- package/lib/types/comment-ask.d.ts +236 -0
- package/lib/types/comment-lines.d.ts +131 -0
- package/lib/types/comments.d.ts +465 -0
- package/lib/types/index.d.ts +7 -2
- package/lib/types/pending.d.ts +53 -4
- package/lib/types/types.d.ts +356 -4
- package/lib/types/vcs.d.ts +53 -26
- package/lib/types/version.d.ts +162 -0
- package/package.json +7 -2
- package/lib/types/client/chat-bridge.d.ts +0 -70
package/lib/types/pending.d.ts
CHANGED
|
@@ -18,6 +18,29 @@ import type { PendingEntry } from './types.ts';
|
|
|
18
18
|
*/
|
|
19
19
|
export declare class PendingDiffStore {
|
|
20
20
|
private readonly entries;
|
|
21
|
+
/**
|
|
22
|
+
* The content version of each entry: bumped whenever its tracked `newText` moves, and never for a
|
|
23
|
+
* change that leaves the content alone (a block keep advances `oldText` only).
|
|
24
|
+
*
|
|
25
|
+
* It is what lets derived, content-dependent state — the lines a comment's quote resolves to, read
|
|
26
|
+
* off `newText` — be cached and refreshed when the content moved rather than recomputed on every
|
|
27
|
+
* read. The versions come from one monotonic counter and are never reused, so an entry that leaves
|
|
28
|
+
* the list and comes back cannot hand back the number a stale cache was keyed to.
|
|
29
|
+
*/
|
|
30
|
+
private readonly contentVersions;
|
|
31
|
+
/** The last version handed out; only ever climbs (see `contentVersions`). */
|
|
32
|
+
private contentVersionCounter;
|
|
33
|
+
/** Note that one path's tracked content moved, and give it a fresh version. */
|
|
34
|
+
private bumpContent;
|
|
35
|
+
/**
|
|
36
|
+
* The version of one path's tracked content, or 0 while it has none.
|
|
37
|
+
*
|
|
38
|
+
* Two calls that return the same number describe the same `newText`: a caller caching something
|
|
39
|
+
* derived from that content may serve its copy until the number changes.
|
|
40
|
+
* @param path - the file path (the global entry key).
|
|
41
|
+
* @returns the path's content version.
|
|
42
|
+
*/
|
|
43
|
+
contentVersion(path: string): number;
|
|
21
44
|
/**
|
|
22
45
|
* Merge one captured operation into its file's entry. A no-op (equal before
|
|
23
46
|
* and after) folds nothing.
|
|
@@ -28,10 +51,11 @@ export declare class PendingDiffStore {
|
|
|
28
51
|
/** Fold a captured entry into the path's single global entry. */
|
|
29
52
|
private merge;
|
|
30
53
|
/**
|
|
31
|
-
* Combine a stored entry and a new capture. `oldText` comes from the earlier
|
|
32
|
-
* capture
|
|
33
|
-
*
|
|
34
|
-
* whole pending change regardless of
|
|
54
|
+
* Combine a stored entry and a new capture. `oldText` comes from the earlier capture (the file's
|
|
55
|
+
* original basis) — except where that capture was the file's creation and this one edits it, when
|
|
56
|
+
* the creation's own content becomes the basis — and `newText` from the later (latest content).
|
|
57
|
+
* The session set is the union, so the entry always spans the whole pending change regardless of
|
|
58
|
+
* which session contributed which part.
|
|
35
59
|
*/
|
|
36
60
|
private mergeEntry;
|
|
37
61
|
/**
|
|
@@ -59,6 +83,15 @@ export declare class PendingDiffStore {
|
|
|
59
83
|
/**
|
|
60
84
|
* Advance one entry's tracked content after a block-level keep/revert. The
|
|
61
85
|
* entry keeps its path; only the given side's text and capture time move.
|
|
86
|
+
*
|
|
87
|
+
* A keep that advances `oldText` also ends the entry's life as a creation. `earlierVersion` is the fact
|
|
88
|
+
* "this file did not exist before", and once part of the file has been accepted there IS an earlier
|
|
89
|
+
* version to restore — what the reader kept. The whole-file action must therefore become 回退, not 删除:
|
|
90
|
+
* deleting would throw away the part they just accepted. (Leaving the field alone kept a partly-kept new
|
|
91
|
+
* file offering to delete itself, which is what sent the reader here.)
|
|
92
|
+
*
|
|
93
|
+
* Every caller of this is a reader action — a block keep or revert, a refresh from version control — so it
|
|
94
|
+
* also takes the unseen dot down: the reader has just handled this file.
|
|
62
95
|
* @param path - the file path.
|
|
63
96
|
* @param patch - the side to advance (`oldText` for keep, `newText` for revert).
|
|
64
97
|
* @returns whether the entry changed.
|
|
@@ -67,6 +100,22 @@ export declare class PendingDiffStore {
|
|
|
67
100
|
oldText?: string;
|
|
68
101
|
newText?: string;
|
|
69
102
|
}): boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Mark paths the reader has now seen — the file was opened, or its row was acted on. The dot is about
|
|
105
|
+
* attention rather than content, so this and the entry leaving the list are the only ways it goes out, and
|
|
106
|
+
* nothing brings it back except a new capture.
|
|
107
|
+
* @param paths - one path, or several.
|
|
108
|
+
* @returns whether anything changed.
|
|
109
|
+
*/
|
|
110
|
+
markSeen(paths: string | readonly string[]): boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Raise the dot for paths whose content moved WITHOUT the reader asking — a writer outside the panel, a
|
|
113
|
+
* second process. The capture sites set the field on the entry they fold; this is for the changes the
|
|
114
|
+
* plugin notices by itself while refreshing, which do not come through a capture.
|
|
115
|
+
* @param paths - one path, or several.
|
|
116
|
+
* @returns whether anything changed.
|
|
117
|
+
*/
|
|
118
|
+
markUnseen(paths: string | readonly string[]): boolean;
|
|
70
119
|
/**
|
|
71
120
|
* Restore one entry exactly as given (an undo/redo replays a snapshot). The
|
|
72
121
|
* entry is inserted or replaced by path.
|
package/lib/types/types.d.ts
CHANGED
|
@@ -4,6 +4,27 @@
|
|
|
4
4
|
* @module dsh-diff-approval/types
|
|
5
5
|
*/
|
|
6
6
|
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
7
|
+
import type { CommentLineRange } from './comment-lines.ts';
|
|
8
|
+
/**
|
|
9
|
+
* Where a session sits in the lineage a merged view groups by.
|
|
10
|
+
*
|
|
11
|
+
* Read off the session's own header when an entry is CAPTURED (`SessionHeader.parentSession`, `.origin`,
|
|
12
|
+
* `.delegationDepth`, `.cwd`) and carried on the entry, so a client can group without a second wire
|
|
13
|
+
* field. Every part is optional and absent when the header — or that one field of it — was not
|
|
14
|
+
* available: an ordinary root session has no parent by design, and a host older than these header
|
|
15
|
+
* fields reports none of them. An unknown lineage is recorded as unknown; guessing a root would file a
|
|
16
|
+
* session under the wrong one. Nothing reads this yet: the record is written for the merged view to use.
|
|
17
|
+
*/
|
|
18
|
+
export interface SessionLineage {
|
|
19
|
+
/** The session this one was forked from, or absent for an ordinary root. */
|
|
20
|
+
parentSessionId?: SessionId | undefined;
|
|
21
|
+
/** The header's coarse product classification — `'subagent'` for a subagent child. */
|
|
22
|
+
origin?: string | undefined;
|
|
23
|
+
/** How deep below its root the session was delegated, when the shell reports it. */
|
|
24
|
+
delegationDepth?: number | undefined;
|
|
25
|
+
/** The session's own working directory, when its header names one. */
|
|
26
|
+
cwd?: string | undefined;
|
|
27
|
+
}
|
|
7
28
|
/**
|
|
8
29
|
* One file's pending entry, global and unique per `path`: the complete set of
|
|
9
30
|
* unhandled changes folded into a single cumulative span across every session
|
|
@@ -16,8 +37,12 @@ export interface PendingEntry {
|
|
|
16
37
|
id: string;
|
|
17
38
|
/** Backend-resolved display path (the tool's output `path`). */
|
|
18
39
|
path: string;
|
|
19
|
-
/**
|
|
20
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Whether there is an earlier version of the file to restore: `'file'` when
|
|
42
|
+
* the whole-file action writes the earlier text back, `'none'` when the file
|
|
43
|
+
* did not exist, so the whole-file action deletes it.
|
|
44
|
+
*/
|
|
45
|
+
earlierVersion: PendingEntryKind;
|
|
21
46
|
/** File content before the first captured operation (empty for a creation). */
|
|
22
47
|
oldText: string;
|
|
23
48
|
/** File content after the latest captured operation. */
|
|
@@ -28,14 +53,70 @@ export interface PendingEntry {
|
|
|
28
53
|
sessionId: SessionId;
|
|
29
54
|
/** Every session that touched the file (drives the per-session list filter). */
|
|
30
55
|
sessionIds: SessionId[];
|
|
56
|
+
/**
|
|
57
|
+
* Where this entry's session sits in its lineage, as the shell reported it when the entry was
|
|
58
|
+
* CAPTURED — display-only data for a merged view. The entry keeps its real `sessionId`/`sessionIds`
|
|
59
|
+
* either way: grouping by lineage never rewrites who did the work. Absent on a record written before
|
|
60
|
+
* this field existed, on a session whose header carried none of it, and on an ordinary root (which
|
|
61
|
+
* has no parent by design) — so a reader must treat absent as "not known", never as "a root".
|
|
62
|
+
*/
|
|
63
|
+
lineage?: SessionLineage | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* Whether a change the reader has not looked at yet arrived on this path — the dot beside its row. Set
|
|
66
|
+
* when an agent operation (or an agent-side admission) changes the entry, cleared when the reader opens
|
|
67
|
+
* the file or acts on the row, and deliberately NOT part of an undo snapshot: a snapshot restores
|
|
68
|
+
* CONTENT, and having seen something is not content.
|
|
69
|
+
*/
|
|
70
|
+
unseen?: boolean | undefined;
|
|
31
71
|
}
|
|
32
|
-
/**
|
|
33
|
-
export type PendingEntryKind = '
|
|
72
|
+
/** Whether an entry has an earlier version of its file to restore. */
|
|
73
|
+
export type PendingEntryKind = 'file' | 'none';
|
|
74
|
+
/**
|
|
75
|
+
* Which way a row's other contributor sits relative to the session LISTING it, walked from the recorded
|
|
76
|
+
* subagent parent links (see `lineageView` on the host).
|
|
77
|
+
*
|
|
78
|
+
* `child` — the other session descends from the lister (the lister is on the other's upward chain).
|
|
79
|
+
* `parent` — the other session is ABOVE the lister (the lister descends from it). It means an ANCESTOR,
|
|
80
|
+
* possibly more than one hop up, not only the direct parent: the Chinese 上级 says exactly that without
|
|
81
|
+
* ambiguity, so the English copy and this name keep that meaning.
|
|
82
|
+
* `sibling` — same lineage root, neither is on the other's chain (two children of one parent, say).
|
|
83
|
+
* `mixed` — non-requester owners of DIFFERENT directions: never one of them, and never a guess at one.
|
|
84
|
+
*/
|
|
85
|
+
export type LineageDirection = 'child' | 'parent' | 'sibling' | 'mixed';
|
|
34
86
|
/**
|
|
35
87
|
* One listed pending entry: the stored operation plus the live file state the
|
|
36
88
|
* list endpoint computes by reading the file's current content.
|
|
37
89
|
*/
|
|
38
90
|
export interface PendingFileDiff extends PendingEntry {
|
|
91
|
+
/**
|
|
92
|
+
* Whether this row reached the list through the LINEAGE MERGE rather than because the LISTING session
|
|
93
|
+
* itself touched it. The HOST computes it per read (`listWithState`), because only the host can walk a
|
|
94
|
+
* lineage — the client cannot, and must not try: it is what lets the panel show a child session's row
|
|
95
|
+
* without re-deriving the merge in the browser. Absent on a host older than the merge and absent on a
|
|
96
|
+
* row this session touched, so a client that does not know the field behaves exactly as it did before.
|
|
97
|
+
*/
|
|
98
|
+
viaLineage?: boolean | undefined;
|
|
99
|
+
/**
|
|
100
|
+
* Whether some session OTHER than the listing one — and inside the listing session's lineage — also
|
|
101
|
+
* contributed to this row, so a row the session touched itself still says a child had a hand in it. Kept
|
|
102
|
+
* apart from `viaLineage` on purpose: that one answers "why is this row here" (visibility), this one
|
|
103
|
+
* answers "whose change does it carry" (the mark). Only a session in the same lineage counts: entries are
|
|
104
|
+
* keyed by path globally, so an unrelated session's touch of the same file must never be called a child's
|
|
105
|
+
* work. Host-computed per read (`listWithState`), absent on a host older than the mark.
|
|
106
|
+
*/
|
|
107
|
+
hasChildContribution?: boolean | undefined;
|
|
108
|
+
/**
|
|
109
|
+
* WHICH WAY the row's other contributor stands, so the marker's sentence tells the truth from the seat it
|
|
110
|
+
* is drawn in: the same row is a child's change in its parent's panel and a parent's change in its
|
|
111
|
+
* child's. Host-computed per read from the SAME recorded parent links the merge walks, and carried to the
|
|
112
|
+
* browser like `viaLineage` — the client never infers lineage.
|
|
113
|
+
*
|
|
114
|
+
* Absent whenever no mark applies (the lister is the only contributor), and absent on a host older than
|
|
115
|
+
* this field — in which case the client says the neutral sentence rather than picking a direction.
|
|
116
|
+
* `mixed` is likewise the honest answer when the row carries non-requester owners of different
|
|
117
|
+
* directions; it is never resolved to one of them.
|
|
118
|
+
*/
|
|
119
|
+
lineageDirection?: LineageDirection | undefined;
|
|
39
120
|
/**
|
|
40
121
|
* Whether the file no longer exists (or cannot be resolved). Reverting a
|
|
41
122
|
* missing entry restores its old content, which recreates the file.
|
|
@@ -72,6 +153,83 @@ export interface DiffApprovalListValue {
|
|
|
72
153
|
* told about it simply finds the list gone later, with nothing to explain it (issue #6).
|
|
73
154
|
*/
|
|
74
155
|
persistError?: string | undefined;
|
|
156
|
+
/**
|
|
157
|
+
* The last failure to write a COMMENT file, or absent while those writes are working.
|
|
158
|
+
* Separate from `persistError` because the two live in different files and one may be
|
|
159
|
+
* writable while the other is not; a comment that only exists in memory is erased by a
|
|
160
|
+
* restart exactly like an unpersisted list, and the panel says so once.
|
|
161
|
+
*/
|
|
162
|
+
commentPersistError?: string | undefined;
|
|
163
|
+
/**
|
|
164
|
+
* The requested session's comment threads, oldest first. They ride the list read
|
|
165
|
+
* so a poll sees the entries and the comments that hang off them in one
|
|
166
|
+
* consistent snapshot — a comment list that arrived on its own channel could
|
|
167
|
+
* name an entry the same read had already dropped.
|
|
168
|
+
*/
|
|
169
|
+
comments: CommentRecord[];
|
|
170
|
+
/**
|
|
171
|
+
* The new-file lines each comment sits on NOW, keyed by comment id — where its quote was found in
|
|
172
|
+
* the entry's current content (see `resolveCommentLines`, which both halves import).
|
|
173
|
+
*
|
|
174
|
+
* DERIVED on every read and never persisted: a resolved line written to the comment file would
|
|
175
|
+
* outlive the content it was true of, and a stale stored figure is worse than none. A comment the
|
|
176
|
+
* host cannot place — its quote is gone from the content — is simply ABSENT here, and a caller
|
|
177
|
+
* then falls back to `record.anchor`: the line the comment was WRITTEN on, which is what an
|
|
178
|
+
* outdated thread shows anyway. Both panes draw this one map, so the list and the open card
|
|
179
|
+
* cannot name two lines for one comment (issue: one comment read `[382]` in the list while its
|
|
180
|
+
* card read 378, because only the open file's pane had resolved it).
|
|
181
|
+
*/
|
|
182
|
+
commentLines: Record<string, CommentLineRange>;
|
|
183
|
+
/** Bumped on every comment change, so a poller can tell "nothing new" from "re-read". */
|
|
184
|
+
commentsRevision: number;
|
|
185
|
+
/**
|
|
186
|
+
* The answer text for the questions the session has answered, keyed by the
|
|
187
|
+
* question's `requestId` (see `CommentAsk`). DERIVED on every read from the
|
|
188
|
+
* session's own transcript and never stored: the transcript is the one source of
|
|
189
|
+
* truth for what the agent said, so a stored copy could only ever disagree with it.
|
|
190
|
+
*/
|
|
191
|
+
commentAnswers: Record<string, string>;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Value returned by the channel's `list-count` endpoint: how many rows a full `list` read for the same
|
|
195
|
+
* session would carry.
|
|
196
|
+
*
|
|
197
|
+
* The badge needs a number once a second, and a full read is enormously more than a number: it ships
|
|
198
|
+
* every visible entry's whole `oldText`+`newText` (measured at 6.77 MB across 302 stored entries, with
|
|
199
|
+
* single rows at 1.2-1.6 MB). So this is the LIGHT read — the same visibility rule (`lineageView().sees`
|
|
200
|
+
* for the requesting session) and the same `ensureLoaded` discipline as `list`, and nothing else.
|
|
201
|
+
*
|
|
202
|
+
* WHAT IT DELIBERATELY DOES NOT DO: it reads no file content (`ctx.fs.readText` is never called), so it
|
|
203
|
+
* computes no diff, adopts no external change, creates no undo checkpoint, sweeps no comments and does not
|
|
204
|
+
* persist anything. It sends no texts, no comment records, no `commentLines`, no revision and no errors —
|
|
205
|
+
* `count` is the whole answer.
|
|
206
|
+
*
|
|
207
|
+
* WHAT THAT COSTS THE CALLER: a count is not a list. It asks `stat` about each visible row (the cheap
|
|
208
|
+
* liveness signal) and drops a row whose file is GONE, which is the one state `list` also drops without
|
|
209
|
+
* reading — but a file that is present and merely unreadable cannot be told from a readable one without
|
|
210
|
+
* reading it, so it is counted here and dropped by the next full read. The number can therefore be one
|
|
211
|
+
* poll stale, and the next full read is what corrects it; nothing else in this value is authoritative.
|
|
212
|
+
*/
|
|
213
|
+
export interface DiffApprovalListCountValue {
|
|
214
|
+
/** How many rows this session's next full `list` read would carry. */
|
|
215
|
+
count: number;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Value returned by the channel's `update-check` endpoint: whether a newer release of
|
|
219
|
+
* this package is published, and what changed since the installed one.
|
|
220
|
+
*
|
|
221
|
+
* The comparison itself is the HOST's (`src/version.ts`): it is the side that knows the
|
|
222
|
+
* installed version, and the only side that can reach the registry (the browser cannot —
|
|
223
|
+
* `registry.npmjs.org` sends no CORS header). `newer` is therefore an ANSWER, not a hint:
|
|
224
|
+
* the client never re-derives it, so exactly one rule decides "is this newer".
|
|
225
|
+
*/
|
|
226
|
+
export interface DiffApprovalUpdateValue {
|
|
227
|
+
/** The installed version as this host read it; empty when it could not be read at all. */
|
|
228
|
+
current: string;
|
|
229
|
+
/** The published version; absent when the registry was unreachable or answered nonsense. */
|
|
230
|
+
latest?: string | undefined;
|
|
231
|
+
/** Whether `latest` is STRICTLY newer than `current`; always false when either is unparsable. */
|
|
232
|
+
newer: boolean;
|
|
75
233
|
}
|
|
76
234
|
/** What the open endpoint asks the OS to do with a file. */
|
|
77
235
|
export type DiffApprovalOpenAction = 'open' | 'reveal';
|
|
@@ -223,3 +381,197 @@ export interface DiffApprovalAddValue {
|
|
|
223
381
|
/** Failure detail for `outcome: 'failed'`. */
|
|
224
382
|
message?: string | undefined;
|
|
225
383
|
}
|
|
384
|
+
/** The rows a comment was written about, as new-file line numbers. */
|
|
385
|
+
export interface CommentAnchor {
|
|
386
|
+
/** First new-file line of the annotation. */
|
|
387
|
+
startLine: number;
|
|
388
|
+
/** Last new-file line of the annotation. */
|
|
389
|
+
endLine: number;
|
|
390
|
+
}
|
|
391
|
+
/** The gutter pair of one quoted line, kept so an outdated thread can lay its quote out. */
|
|
392
|
+
export interface CommentQuoteLine {
|
|
393
|
+
/** Old-file number, absent on a row the old side does not have (an added line). */
|
|
394
|
+
old?: number | undefined;
|
|
395
|
+
/** New-file number — the second gutter — absent on a removed line. */
|
|
396
|
+
new?: number | undefined;
|
|
397
|
+
/** Which side of the change the row was on when the thread quoted it. */
|
|
398
|
+
kind?: 'add' | 'del' | 'context' | undefined;
|
|
399
|
+
}
|
|
400
|
+
/** One question asked inside a comment thread, and what became of it. */
|
|
401
|
+
export interface CommentAsk {
|
|
402
|
+
/**
|
|
403
|
+
* The identity of the prompt request this question was submitted as. The session
|
|
404
|
+
* persists it on the exact user message it accepted (`source.rpcId`), so it is the
|
|
405
|
+
* join key between the question and its message in the transcript — and it is the
|
|
406
|
+
* idempotency key: a request carrying it again is the same submission, not a second
|
|
407
|
+
* one. Minted by this plugin, so a question knows its own message before the session
|
|
408
|
+
* has admitted anything.
|
|
409
|
+
*/
|
|
410
|
+
requestId: string;
|
|
411
|
+
/**
|
|
412
|
+
* The SESSION THIS QUESTION WAS SUBMITTED INTO — the transcript that will carry its answer.
|
|
413
|
+
*
|
|
414
|
+
* It is not always the comment's own `sessionId`: once a lineage reads every seat's threads (`list`
|
|
415
|
+
* fans the read out across the lineage root), any seat may ask a question on a thread another seat
|
|
416
|
+
* wrote, and the prompt goes into the seat the human is using. The answer fold then has to know which
|
|
417
|
+
* transcript to read for which question, and turn numbers are only unique WITHIN one session — so this
|
|
418
|
+
* is what `markTurnEnded` matches on as well.
|
|
419
|
+
*
|
|
420
|
+
* Absent on a question asked before this field existed, when the only session a question could have
|
|
421
|
+
* gone to was the comment's own: the fold reads the comment's `sessionId` for those (see
|
|
422
|
+
* `askTranscript` in the comment store), which is exactly where their answers already are.
|
|
423
|
+
*/
|
|
424
|
+
sessionId?: SessionId | undefined;
|
|
425
|
+
/**
|
|
426
|
+
* The question's own words, as the reader typed them — without the marker, the
|
|
427
|
+
* `(path:lines)` reference and the answer rules that wrap them in the submitted
|
|
428
|
+
* prompt. The prompt is what the agent is asked; this is what the thread shows, so a
|
|
429
|
+
* follow-up is drawn as the words that were written. Set for every question asked
|
|
430
|
+
* now; absent only on a record written before this field existed, whose words are
|
|
431
|
+
* not recoverable from anywhere.
|
|
432
|
+
*/
|
|
433
|
+
text?: string | undefined;
|
|
434
|
+
/** The turn that claimed the message, when the session reported one. */
|
|
435
|
+
turn?: number | undefined;
|
|
436
|
+
/**
|
|
437
|
+
* The session discarded the message before any turn claimed it (a stopped turn, a
|
|
438
|
+
* cancelled queue). Nothing is coming, and the reader is told so rather than left
|
|
439
|
+
* waiting — this is what the client used to infer from its own queue snapshot.
|
|
440
|
+
*/
|
|
441
|
+
dropped?: boolean | undefined;
|
|
442
|
+
/**
|
|
443
|
+
* The turn that claimed this question has stopped. A question whose turn is over and
|
|
444
|
+
* that the transcript shows no answer for was cut off: the reader is told that and
|
|
445
|
+
* offered another try, instead of a block that waits forever on a turn that ended.
|
|
446
|
+
* Only ever set when true.
|
|
447
|
+
*/
|
|
448
|
+
ended?: boolean | undefined;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* One comment thread's durable record: the annotation, where it was written, and the
|
|
452
|
+
* questions asked inside it.
|
|
453
|
+
*
|
|
454
|
+
* The ANSWER's text is deliberately absent: `asks[].requestId` names each question's
|
|
455
|
+
* message, and the session transcript stays the one source of truth for what the agent
|
|
456
|
+
* said — a client renders an answer by reading the transcript at that id, and the list
|
|
457
|
+
* read carries the derived text keyed by the same id. Row indices are absent too: they
|
|
458
|
+
* are model-relative and meaningless after a reload, so the record keeps new-file LINE
|
|
459
|
+
* numbers plus the quote the reader was looking at, which is what a re-anchor needs.
|
|
460
|
+
*/
|
|
461
|
+
export interface CommentRecord {
|
|
462
|
+
/** Stable comment id, minted by the host when the annotation is written. */
|
|
463
|
+
id: string;
|
|
464
|
+
/** The session the comment belongs to: comments never cross sessions. */
|
|
465
|
+
sessionId: SessionId;
|
|
466
|
+
/** The pending entry the comment hangs off (its id, which is its path). */
|
|
467
|
+
entryId: string;
|
|
468
|
+
/** The entry's display path when the comment was written. */
|
|
469
|
+
path: string;
|
|
470
|
+
/** The lines the annotation was made on, as NEW-file line numbers: the numbers every label,
|
|
471
|
+
* jump and reference shows, and the ones the host resolves the quote against. */
|
|
472
|
+
anchor: CommentAnchor;
|
|
473
|
+
/** The anchored lines as they read then (the re-anchor fingerprint). */
|
|
474
|
+
quote: string;
|
|
475
|
+
/** `quote` with one row of context on each side, as it read then. */
|
|
476
|
+
quoteContext?: string | undefined;
|
|
477
|
+
/** The gutter numbers of `quote`'s lines, in the same order. */
|
|
478
|
+
quoteLines?: CommentQuoteLine[] | undefined;
|
|
479
|
+
/** The annotation itself: what the reader wrote. */
|
|
480
|
+
text: string;
|
|
481
|
+
/**
|
|
482
|
+
* Who wrote `text`, when it was not the reader.
|
|
483
|
+
*
|
|
484
|
+
* The panel draws a thread's first turn as the reader's own words, which is what it is for every
|
|
485
|
+
* comment the reader makes. An AGENT-authored annotation is the other direction (see
|
|
486
|
+
* `annotate-tool.ts`): the panel is showing the agent's explanation on those lines, and drawing it as
|
|
487
|
+
* something the reader said would put words in their mouth. Absent means the reader.
|
|
488
|
+
*/
|
|
489
|
+
author?: 'agent' | undefined;
|
|
490
|
+
/**
|
|
491
|
+
* Which CLASS of annotation this one belongs to, as the calling agent named it (see
|
|
492
|
+
* `annotate-tool.ts`'s `category` argument).
|
|
493
|
+
*
|
|
494
|
+
* The panel draws it as a coloured round dot on the comment list's row, so an agent that annotates
|
|
495
|
+
* several passes or topics can be told apart at a glance. OPAQUE to every reader of this record: the
|
|
496
|
+
* panel hashes the string to pick a colour and shows the string itself in the mark's own label, and
|
|
497
|
+
* nothing parses it, orders it or validates its shape. Absent means "no class, no dot" — which is
|
|
498
|
+
* every comment the reader writes themselves, and every annotation written before this field existed.
|
|
499
|
+
* Short by construction (the tool trims it and caps it at `MAX_ANNOTATION_CATEGORY_CHARS`).
|
|
500
|
+
*/
|
|
501
|
+
category?: string | undefined;
|
|
502
|
+
/** Epoch milliseconds the comment was written. */
|
|
503
|
+
createdAt: number;
|
|
504
|
+
/** Epoch milliseconds of the last change to this record. */
|
|
505
|
+
updatedAt: number;
|
|
506
|
+
/** The questions asked in this thread, oldest first; empty until one is asked. */
|
|
507
|
+
asks?: CommentAsk[] | undefined;
|
|
508
|
+
/**
|
|
509
|
+
* Whether something the reader has not looked at yet arrived in this thread — the dot beside its
|
|
510
|
+
* card. Set when an agent-authored annotation is written or when an answer lands in the thread,
|
|
511
|
+
* cleared when the card comes into view, and deliberately NOT part of an undo snapshot: a snapshot
|
|
512
|
+
* restores CONTENT, and having seen something is not content.
|
|
513
|
+
*/
|
|
514
|
+
unseen?: boolean | undefined;
|
|
515
|
+
/**
|
|
516
|
+
* The answer text the reader has already been told about, keyed by question id.
|
|
517
|
+
*
|
|
518
|
+
* The answer's text is deliberately absent from this record (see the doc above) — the transcript is
|
|
519
|
+
* the one source
|
|
520
|
+
* of truth for what the agent said — but "is this the answer I have already seen?" cannot be
|
|
521
|
+
* answered from the transcript alone, because a read has nothing to compare against. This is the
|
|
522
|
+
* baseline the comparison takes: the host writes the current answer here when the card goes out of
|
|
523
|
+
* the reader's way, and an answer whose text differs from this is news. Persisted rather than held
|
|
524
|
+
* in memory, so a restart does not light a dot the reader had already cleared.
|
|
525
|
+
*/
|
|
526
|
+
answerSeen?: Record<string, string> | undefined;
|
|
527
|
+
/**
|
|
528
|
+
* The latest answer text the host has observed for each question, keyed by question id.
|
|
529
|
+
*
|
|
530
|
+
* The other half of the comparison above, and kept apart from it on purpose: this follows what the
|
|
531
|
+
* agent said, while `answerSeen` follows what the reader has looked at. An answer whose text differs
|
|
532
|
+
* from its stored `answerSeen` entry is what lights the dot — and an answer that disappears leaves
|
|
533
|
+
* this empty rather than looking like a change, so a transcript that cannot be read never re-lights
|
|
534
|
+
* a thread the reader already read.
|
|
535
|
+
*/
|
|
536
|
+
answerNow?: Record<string, string> | undefined;
|
|
537
|
+
}
|
|
538
|
+
/** Value returned by the channel's comment-add endpoint. */
|
|
539
|
+
export interface DiffApprovalCommentAddValue {
|
|
540
|
+
/**
|
|
541
|
+
* What the request did. `missing` means the named entry is not in the session's
|
|
542
|
+
* list — a comment on a file that has left it is refused rather than stored,
|
|
543
|
+
* which is the first of the two guards against a comment outliving its entry.
|
|
544
|
+
*/
|
|
545
|
+
outcome: 'added' | 'missing';
|
|
546
|
+
/** The stored comment; present only when `outcome` is `added`. */
|
|
547
|
+
comment?: CommentRecord | undefined;
|
|
548
|
+
}
|
|
549
|
+
/** Value returned by the channel's comment-remove endpoint. */
|
|
550
|
+
export interface DiffApprovalCommentRemoveValue {
|
|
551
|
+
/** What the request did; `missing` means no such comment in that session. */
|
|
552
|
+
outcome: 'removed' | 'missing';
|
|
553
|
+
}
|
|
554
|
+
/** Value returned by the channel's comment-remove-many endpoint. */
|
|
555
|
+
export interface DiffApprovalCommentRemoveManyValue {
|
|
556
|
+
/**
|
|
557
|
+
* How many of the named comments the request dropped.
|
|
558
|
+
*
|
|
559
|
+
* The ids that were not there are not named back: the reader asked for those comments to be
|
|
560
|
+
* gone, and one that another client already ended is gone. What is left to say is how many
|
|
561
|
+
* the request itself did, which is what a test and a log can hold on to.
|
|
562
|
+
*/
|
|
563
|
+
removed: number;
|
|
564
|
+
}
|
|
565
|
+
/** Value returned by the channel's comment-ask endpoint. */
|
|
566
|
+
export interface DiffApprovalCommentAskValue {
|
|
567
|
+
/**
|
|
568
|
+
* What the request did. `missing` means no such comment in that session;
|
|
569
|
+
* `no-agent` means the session has no live agent to ask (it ended, or this host
|
|
570
|
+
* drives no agent for it), and `failed` carries `message`.
|
|
571
|
+
*/
|
|
572
|
+
outcome: 'asked' | 'missing' | 'no-agent' | 'failed';
|
|
573
|
+
/** The request identity the comment now carries (present when `outcome` is `asked`). */
|
|
574
|
+
requestId?: string | undefined;
|
|
575
|
+
/** Failure detail for `outcome: 'failed'`. */
|
|
576
|
+
message?: string | undefined;
|
|
577
|
+
}
|
package/lib/types/vcs.d.ts
CHANGED
|
@@ -9,13 +9,17 @@
|
|
|
9
9
|
* directory.
|
|
10
10
|
*
|
|
11
11
|
* Scope of one import (mirrors the panel's preferences):
|
|
12
|
-
* - modified files (
|
|
13
|
-
*
|
|
12
|
+
* - modified files, imported as `edit` (old = baseline, new = working). The GIT baseline is the
|
|
13
|
+
* last commit, not the index: this list answers "what is not committed yet", so a change the
|
|
14
|
+
* reader already `git add`ed is as much a review item as one they have not — staging is a step
|
|
15
|
+
* towards a commit, not a decision this panel records (`git diff HEAD` semantics). svn compares
|
|
16
|
+
* against BASE and p4 against `#have`, which are their "last commit" already;
|
|
14
17
|
* - deleted files, imported as `edit` with an empty new side (revert restores);
|
|
15
|
-
* - new/untracked files
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
18
|
+
* - new/untracked files, imported as `create` ONLY when the untracked preference is on: git `??`
|
|
19
|
+
* (never added), svn unversioned, p4 not-yet-opened. For p4 that means a full workspace scan
|
|
20
|
+
* (`p4 status`, which can be slow); with the preference off only already-opened files are read.
|
|
21
|
+
* A file that is new but already under version control (git `A`, svn `added`) needs no
|
|
22
|
+
* preference: it is an uncommitted change like any other.
|
|
19
23
|
* @module dsh-diff-approval/vcs
|
|
20
24
|
*/
|
|
21
25
|
/** The version-control systems this integration knows. */
|
|
@@ -30,32 +34,54 @@ export interface VcsRoot {
|
|
|
30
34
|
export interface VcsChange {
|
|
31
35
|
/** Absolute path of the changed file (inside the workspace). */
|
|
32
36
|
path: string;
|
|
33
|
-
|
|
37
|
+
/** Whether an earlier version of the file exists: `'file'` restores it, `'none'` deletes the file. */
|
|
38
|
+
earlierVersion: 'file' | 'none';
|
|
34
39
|
/** Baseline (pre-change) content; empty for a brand-new file. */
|
|
35
40
|
oldText: string;
|
|
36
41
|
/** Working content; empty for a deleted file. */
|
|
37
42
|
newText: string;
|
|
38
43
|
}
|
|
39
|
-
/**
|
|
40
|
-
*
|
|
44
|
+
/**
|
|
45
|
+
* The subset of `ctx.shell`'s executor this module calls — for BOTH eras of that seam.
|
|
46
|
+
*
|
|
47
|
+
* Kept structural so the module stays dependency-light and testable with a fake, and both eras are here
|
|
48
|
+
* because the seam changed under this plugin. Up to 0.1.6 the executor was `resolve(request)` +
|
|
49
|
+
* `run(spec)`, and 0.1.7 replaced `run` with `execute(spec)`, which resolves to a PROCESS HANDLE whose
|
|
50
|
+
* foreground projection is `result()` (measured in `@deepseek-ai/dsh-shell`: `abstract resolve`,
|
|
51
|
+
* `abstract execute`, `ShellExecution.result(): Promise<ShellRunResult>`). On 0.1.7 the old shape failed
|
|
52
|
+
* with `shell.run is not a function` — the whole text the import button could show — so both are
|
|
53
|
+
* accepted, and an executor offering neither is reported by the methods it does have.
|
|
54
|
+
*/
|
|
41
55
|
export interface ShellExecutorLike {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
/** 0.1.7: apply the implementation's defaults and caps before execution. */
|
|
57
|
+
resolve?(request: ShellExecRequestLike): unknown;
|
|
58
|
+
/** 0.1.7: prepare and spawn; the returned handle carries `result()`. */
|
|
59
|
+
execute?(spec: unknown): Promise<ShellExecutionLike>;
|
|
60
|
+
/** Up to 0.1.6: resolve and run in one call. */
|
|
61
|
+
run?(spec: unknown): Promise<ShellRunResultLike>;
|
|
62
|
+
}
|
|
63
|
+
/** One command, as the seam's request takes it (`workdir`/`timeoutMs` are required only on the spec). */
|
|
64
|
+
interface ShellExecRequestLike {
|
|
65
|
+
command: string;
|
|
66
|
+
workdir: string;
|
|
67
|
+
timeoutMs: number;
|
|
68
|
+
signal?: AbortSignal | undefined;
|
|
69
|
+
/** Foreground stdout capture budget in bytes; absent uses the executor's cap. */
|
|
70
|
+
stdoutMaxBytes?: number | undefined;
|
|
71
|
+
}
|
|
72
|
+
/** 0.1.7's execution handle, reduced to the part this module reads. */
|
|
73
|
+
interface ShellExecutionLike {
|
|
74
|
+
result?: (() => Promise<ShellRunResultLike>) | undefined;
|
|
75
|
+
}
|
|
76
|
+
/** The outcome this module reads, in the shape both eras report it. */
|
|
77
|
+
interface ShellRunResultLike {
|
|
78
|
+
exitCode: number | null;
|
|
79
|
+
stdout: {
|
|
80
|
+
text: string;
|
|
81
|
+
};
|
|
82
|
+
stderr: {
|
|
83
|
+
text: string;
|
|
84
|
+
};
|
|
59
85
|
}
|
|
60
86
|
/** Reads one file's working content; undefined when the file is absent. */
|
|
61
87
|
export type VcsFileReader = (absolutePath: string) => Promise<string | undefined>;
|
|
@@ -95,3 +121,4 @@ export declare function detectVcsRoot(start: string): VcsRoot | undefined;
|
|
|
95
121
|
* @returns the changes; an absent/unusable VCS surfaces as a thrown error.
|
|
96
122
|
*/
|
|
97
123
|
export declare function listVcsChanges(input: VcsImportInput): Promise<VcsChange[]>;
|
|
124
|
+
export {};
|