dsh-diff-approval 0.32.0 → 0.34.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/lib/index.js
CHANGED
|
@@ -3347,9 +3347,12 @@ function refusalText(refusal, path) {
|
|
|
3347
3347
|
/**
|
|
3348
3348
|
* The sentence a file that could not be listed is refused with.
|
|
3349
3349
|
*
|
|
3350
|
-
*
|
|
3351
|
-
*
|
|
3352
|
-
*
|
|
3350
|
+
* Both are the workspace's rules rather than the annotation's: there is either no workspace to hold a
|
|
3351
|
+
* list, or the file service could not read the file as text. Naming which one failed is what lets the
|
|
3352
|
+
* agent pick another file, or stop annotating that one.
|
|
3353
|
+
*
|
|
3354
|
+
* A path OUTSIDE the workspace is not in this list: the panel can hold such a file (an edit to one is
|
|
3355
|
+
* captured like any other), so it is listed and annotated rather than refused (see `resolveForListing`).
|
|
3353
3356
|
*
|
|
3354
3357
|
* @param kind - why listing failed.
|
|
3355
3358
|
* @param path - the path the caller named.
|
|
@@ -3357,7 +3360,6 @@ function refusalText(refusal, path) {
|
|
|
3357
3360
|
*/
|
|
3358
3361
|
function unlistedText(kind, path) {
|
|
3359
3362
|
switch (kind) {
|
|
3360
|
-
case "outside": return `refused: "${path}" is not inside this session's workspace, so the review panel cannot list it. Annotate a file of the workspace the user is working in.`;
|
|
3361
3363
|
case "no-workspace": return `refused: this session has no workspace, so there is no file list to add "${path}" to.`;
|
|
3362
3364
|
default: return `refused: "${path}" could not be read as a text file, so there is nothing to annotate. Check the path (a workspace-relative path is fine) and try again.`;
|
|
3363
3365
|
}
|
|
@@ -4392,6 +4394,30 @@ function resolveInsideWorkspace(root, input) {
|
|
|
4392
4394
|
const absolute = isAbsolute(input) ? resolve(input) : resolve(root, input);
|
|
4393
4395
|
return workspaceRelativeOf(root, absolute) === void 0 ? void 0 : absolute;
|
|
4394
4396
|
}
|
|
4397
|
+
/**
|
|
4398
|
+
* Resolve one caller-supplied path for LISTING, which an absolute path need not keep inside the workspace.
|
|
4399
|
+
*
|
|
4400
|
+
* A relative path is workspace-relative, exactly as before. An absolute one is taken as it is, wherever it
|
|
4401
|
+
* points: the fence `resolveInsideWorkspace` draws is the right one for a SUBTREE question — a scan of the
|
|
4402
|
+
* workspace's own VCS — and the wrong one for "show me this file".
|
|
4403
|
+
*
|
|
4404
|
+
* The panel can already hold a file outside the workspace: the tracked capture behind an `edit`/`write`/`
|
|
4405
|
+
* `str_replace_editor` has never had a workspace fence (`tools/result` folds `outcome.path` as the tool
|
|
4406
|
+
* spelled it), so a file the agent edited elsewhere is listed, kept and reverted like any other. Refusing
|
|
4407
|
+
* to READ one into the list therefore only made the plugin disagree with itself: a card could not hang on a
|
|
4408
|
+
* file the panel was already able to show. Both ways in — the agent's annotate call and the reader's own
|
|
4409
|
+
* "add this path" — go through this, so they agree.
|
|
4410
|
+
*
|
|
4411
|
+
* What is read is still the harness's file service, whose reads are not workspace-confined (only its
|
|
4412
|
+
* mutations are fenced, by the sandbox policy), so this adds no read the deployment does not already allow.
|
|
4413
|
+
*
|
|
4414
|
+
* @param root - the workspace root, for a relative path.
|
|
4415
|
+
* @param input - the caller's path (absolute anywhere, or workspace-relative).
|
|
4416
|
+
* @returns the absolute path to list.
|
|
4417
|
+
*/
|
|
4418
|
+
function resolveForListing(root, input) {
|
|
4419
|
+
return isAbsolute(input) ? resolve(input) : resolve(root, input);
|
|
4420
|
+
}
|
|
4395
4421
|
/** The workspace-relative parent of a relative directory path (`''` at the root). */
|
|
4396
4422
|
/** Fold one path for comparison: absolute, `/`-separated, case-folded on Windows.
|
|
4397
4423
|
* Entries are keyed by the path spelling their capture carried (a tool's display
|
|
@@ -4610,8 +4636,8 @@ function apply(ctx, config) {
|
|
|
4610
4636
|
* already lands with — so the file opens at its own content and the card sits on the lines it names.
|
|
4611
4637
|
*
|
|
4612
4638
|
* It is deliberately that ROUTE and not a raw insert: a path already listed is answered from the list
|
|
4613
|
-
* (so annotating twice never re-baselines a review in progress), a path outside the workspace is
|
|
4614
|
-
*
|
|
4639
|
+
* (so annotating twice never re-baselines a review in progress), a path outside the workspace is listed
|
|
4640
|
+
* like any other (see `resolveForListing`), and what lands goes through the same fold + persist + undo
|
|
4615
4641
|
* bookkeeping as every other admission.
|
|
4616
4642
|
*
|
|
4617
4643
|
* @param sessionId - the session whose list gains the file.
|
|
@@ -4623,8 +4649,7 @@ function apply(ctx, config) {
|
|
|
4623
4649
|
await ensureLoaded();
|
|
4624
4650
|
const workspace = workspaceOf(sessionId);
|
|
4625
4651
|
if (workspace === void 0) return { kind: "no-workspace" };
|
|
4626
|
-
const absolute =
|
|
4627
|
-
if (absolute === void 0) return { kind: "outside" };
|
|
4652
|
+
const absolute = resolveForListing(workspace.path, asked);
|
|
4628
4653
|
const listed = store.list(sessionId).find((entry) => pathIdentity(entry.path) === pathIdentity(absolute));
|
|
4629
4654
|
if (listed !== void 0) return {
|
|
4630
4655
|
kind: "listed",
|
|
@@ -6888,8 +6913,8 @@ function apply(ctx, config) {
|
|
|
6888
6913
|
if (target === void 0) return rpcError("sessionId and path must be non-empty strings");
|
|
6889
6914
|
const workspace = workspaceOf(target.sessionId);
|
|
6890
6915
|
if (workspace === void 0) return rpcError("add unavailable: the session has no workspace");
|
|
6891
|
-
const absolute =
|
|
6892
|
-
if (absolute === void 0) return {
|
|
6916
|
+
const absolute = resolveForListing(workspace.path, target.path);
|
|
6917
|
+
if (!target.exact && workspaceRelativeOf(workspace.path, absolute) === void 0) return {
|
|
6893
6918
|
ok: true,
|
|
6894
6919
|
value: {
|
|
6895
6920
|
outcome: "outside",
|
|
@@ -7316,7 +7341,7 @@ function commentAddOf(payload) {
|
|
|
7316
7341
|
if (typeof startLine !== "number" || !Number.isFinite(startLine)) return void 0;
|
|
7317
7342
|
if (typeof endLine !== "number" || !Number.isFinite(endLine)) return void 0;
|
|
7318
7343
|
if (typeof quote !== "string") return void 0;
|
|
7319
|
-
if (typeof text !== "string"
|
|
7344
|
+
if (typeof text !== "string") return void 0;
|
|
7320
7345
|
const id = body.id;
|
|
7321
7346
|
const quoteContext = body.quoteContext;
|
|
7322
7347
|
const quoteLines = [];
|
|
@@ -168,8 +168,6 @@ export type ListFileOutcome = {
|
|
|
168
168
|
kind: 'listed';
|
|
169
169
|
entry: PendingEntry;
|
|
170
170
|
added: boolean;
|
|
171
|
-
} | {
|
|
172
|
-
kind: 'outside';
|
|
173
171
|
} | {
|
|
174
172
|
kind: 'missing';
|
|
175
173
|
} | {
|
|
@@ -205,15 +203,18 @@ export interface AnnotateRunDeps {
|
|
|
205
203
|
/**
|
|
206
204
|
* The sentence a file that could not be listed is refused with.
|
|
207
205
|
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
206
|
+
* Both are the workspace's rules rather than the annotation's: there is either no workspace to hold a
|
|
207
|
+
* list, or the file service could not read the file as text. Naming which one failed is what lets the
|
|
208
|
+
* agent pick another file, or stop annotating that one.
|
|
209
|
+
*
|
|
210
|
+
* A path OUTSIDE the workspace is not in this list: the panel can hold such a file (an edit to one is
|
|
211
|
+
* captured like any other), so it is listed and annotated rather than refused (see `resolveForListing`).
|
|
211
212
|
*
|
|
212
213
|
* @param kind - why listing failed.
|
|
213
214
|
* @param path - the path the caller named.
|
|
214
215
|
* @returns the sentence the caller reads.
|
|
215
216
|
*/
|
|
216
|
-
export declare function unlistedText(kind: '
|
|
217
|
+
export declare function unlistedText(kind: 'missing' | 'no-workspace', path: string): string;
|
|
217
218
|
/**
|
|
218
219
|
* Build the body one tool call runs: resolve the file, apply the rule, store the record.
|
|
219
220
|
*
|
|
@@ -15,6 +15,13 @@
|
|
|
15
15
|
* @module dsh-diff-approval/client/panel-memory
|
|
16
16
|
*/
|
|
17
17
|
import type { Discussion, DiscussionQuoteLine } from './discussion.ts';
|
|
18
|
+
/** One place a deliberate jump left behind: the file, and how far down it was. */
|
|
19
|
+
export interface PanelJumpPlace {
|
|
20
|
+
/** The pending entry the reader was in. */
|
|
21
|
+
fileId: string;
|
|
22
|
+
/** The code view's `scrollTop` there — what `rememberPanelView` keeps current as the reader scrolls. */
|
|
23
|
+
scrollTop: number;
|
|
24
|
+
}
|
|
18
25
|
/**
|
|
19
26
|
* What the panel keeps about one thread that is NOT the host's to keep: what the reader is
|
|
20
27
|
* typing, whether the block is folded, and the body height the panel last measured for it.
|
|
@@ -182,6 +189,29 @@ export declare function lastPanelFile(sessionId: string | undefined): string | u
|
|
|
182
189
|
* @returns the remembered scrollTop, or undefined when there is none.
|
|
183
190
|
*/
|
|
184
191
|
export declare function panelFileOffset(sessionId: string | undefined, fileId: string): number | undefined;
|
|
192
|
+
/**
|
|
193
|
+
* Remember the place a deliberate jump is leaving, so the reader can come back to it.
|
|
194
|
+
*
|
|
195
|
+
* ONLY JUMPS PASS THROUGH HERE — never a scroll of the reader's own, which is the whole point. Their place
|
|
196
|
+
* is already kept per file (`rememberPanelView`), kept current by every scroll they make, so a back step
|
|
197
|
+
* has nothing to measure: it hands that same memory back. Reading it from there rather than from the DOM is
|
|
198
|
+
* also what makes this work in both views, one column and side by side, whose scrollers are different
|
|
199
|
+
* elements and only one of which is ever mounted.
|
|
200
|
+
*
|
|
201
|
+
* A jump that would push the place already on top is dropped: two entries for one place would cost the
|
|
202
|
+
* reader two presses of the back button to get anywhere.
|
|
203
|
+
*
|
|
204
|
+
* @param sessionId - the session the panel is reviewing; nothing is recorded without one.
|
|
205
|
+
* @param fileId - the entry being LEFT, not the one being jumped to.
|
|
206
|
+
*/
|
|
207
|
+
export declare function rememberJumpAway(sessionId: string | undefined, fileId: string | undefined): void;
|
|
208
|
+
/**
|
|
209
|
+
* Take the last place a deliberate jump left, for a back step. Spent here, so a second press goes one step
|
|
210
|
+
* further back rather than landing where the reader already is.
|
|
211
|
+
* @param sessionId - the session, if any.
|
|
212
|
+
* @returns that place, or undefined when there is nothing to go back to.
|
|
213
|
+
*/
|
|
214
|
+
export declare function popJumpAway(sessionId: string | undefined): PanelJumpPlace | undefined;
|
|
185
215
|
/**
|
|
186
216
|
* Forget every session's record. A page reload does this by itself; a test calls
|
|
187
217
|
* it to start from a clean page rather than inheriting the previous case's place.
|
|
@@ -8,16 +8,27 @@
|
|
|
8
8
|
* `[data-produced-files-row] button` 0.1.5's produced-file card `title` = the path
|
|
9
9
|
* `[data-presented-files-row] button` 0.1.7's presented-file card `title` = the path
|
|
10
10
|
* `[data-changed-files] button` 0.1.7's changed-files row `aria-describedby` = the path
|
|
11
|
-
* `button[title]` a message's file LINK (0.1.7) `title` = the path
|
|
12
11
|
* `button[data-ref-chip]` a `@file` chip in a message `title` = the token
|
|
13
|
-
* `
|
|
12
|
+
* `button._fileLink_…` / `._fileMention_…` a message's file LINK `title` = the path
|
|
14
13
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
14
|
+
* A PRESS IS ADMITTED BY THE SHELL'S OWN MARK, NEVER BY THE SHAPE OF ITS TEXT. The link row's classes are
|
|
15
|
+
* the shell's CSS-module names — measured on the live shell as `_fileMention_1ypvv_85 _fileLink_1ypvv_59`,
|
|
16
|
+
* carrying `title="src/client/produced-diff.ts"` — and that name survives the hash and line suffix the
|
|
17
|
+
* module adds per build, so the marker is the `_fileLink_` / `_fileMention_` substring rather than the
|
|
18
|
+
* whole class.
|
|
19
|
+
*
|
|
20
|
+
* This replaced two blanket rules, `button[title]` and every `a[href]`, which admitted a press by how its
|
|
21
|
+
* text was spelled and were measured wrong twice on 2026-10-09: a model picker's button carries a tooltip
|
|
22
|
+
* naming the model, and a version-like name (`…v3.2`) ends in the ".extension" the text filter accepted, so
|
|
23
|
+
* pressing 修改模型 raised this menu over the shell's own control; an in-app route link (`/settings`) holds a
|
|
24
|
+
* separator and was admitted the same way. A file press is one the shell marks as one.
|
|
25
|
+
*
|
|
26
|
+
* Everything else is left entirely alone: a link to a URL, an in-page anchor, a button whose tooltip names
|
|
27
|
+
* something that is not a file, a file LINK whose file this panel does not hold, a modified press
|
|
28
|
+
* (Ctrl/Cmd/Shift/Alt, or a middle click — gestures the harness may give its own meaning later), and any
|
|
29
|
+
* press inside this plugin's own surface, whose buttons carry paths too and belong to the panel that is
|
|
30
|
+
* already showing the file. A file-list ROW is the one press taken over whether or not the panel holds its
|
|
31
|
+
* file (see `ROW_SELECTOR`); "we do not hold it" then decides what the menu offers, not whether it appears.
|
|
21
32
|
*
|
|
22
33
|
* The name of the module (and of the event it raises) dates from when the produced-file card was the
|
|
23
34
|
* only press of this kind; `data-presented-files-row` is that same list under 0.1.7's name for it.
|
|
@@ -38,6 +38,11 @@ export interface SplitDiff {
|
|
|
38
38
|
* alignment, each deletion pairs with its most-similar addition (order
|
|
39
39
|
* preserved, threshold-bounded) so a mixed insert/delete block does not force a
|
|
40
40
|
* wrong line together. A stray deletion or addition stays a one-sided pair.
|
|
41
|
+
*
|
|
42
|
+
* THE ORDER OF THE RESULT IS PART OF THE CONTRACT: going down `pairs`, neither
|
|
43
|
+
* column's line numbers ever go backwards. Alignment may therefore move a line
|
|
44
|
+
* onto the row opposite it, but never past a line of its own side — an unpaired
|
|
45
|
+
* line between two pairs is emitted where it belongs, not after them.
|
|
41
46
|
* @param rows - the whole-file diff rows.
|
|
42
47
|
* @param alignBySimilarity - align a change block by similarity instead of order.
|
|
43
48
|
* @returns the split pairs plus the row→pair index map.
|
package/package.json
CHANGED