dsh-diff-approval 0.7.0 → 0.8.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.
@@ -3,5 +3,15 @@ import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-cli
3
3
  import type { PendingPanelFace } from './slots.ts';
4
4
  /** Full panel props composed by the sidebar footer-action slot. */
5
5
  export type PendingPanelProps = PropsRuntime<'sidebar.footer.action'> & InjectFace<PendingPanelFace> & PropsLocale<'diff-approval'>;
6
+ /**
7
+ * Open the DSH settings dialog and switch to this plugin's section. The
8
+ * settings shell keeps its open state and the active section id as
9
+ * component-local viewing state with no cross-plugin service, so the dialog
10
+ * is opened by clicking the sidebar's settings trigger and then the nav cell
11
+ * for this section. `element.click()` still fires React's delegated click
12
+ * handlers, reaching the same state transitions a user gesture would.
13
+ * @param sectionLabel - the nav label of this plugin's settings section.
14
+ */
15
+ export declare function openSettingsSection(sectionLabel: string): void;
6
16
  /** Render the pending-edit review panel and its unified footer action. */
7
- export declare function PendingPanel({ wide, useSessions, usePending, onRefresh, onKeep, onRevert, onBlockKeep, onBlockRevert, onOpen, t, }: PendingPanelProps): import("react").JSX.Element;
17
+ export declare function PendingPanel({ wide, useSessions, usePending, onRefresh, onKeep, onRevert, onBlockKeep, onBlockRevert, onOpen, onPasteReference, onUndo, onRedo, onImportVcs, t, }: PendingPanelProps): import("react").JSX.Element;
@@ -0,0 +1,11 @@
1
+ /** DSH Settings top-level section for this plugin's preferences. */
2
+ import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
3
+ /** Full component props assembled by the Settings slot renderer. */
4
+ export type DiffApprovalSettingsTabProps = PropsRuntime<'settings.section'> & PropsLocale<'diff-approval'>;
5
+ /**
6
+ * The plugin's preferences: auto-paste a copied reference into the input, and
7
+ * whether importing workspace VCS changes includes untracked files. Each row
8
+ * mirrors the harness's Agent-preset row (title + description on the left, a
9
+ * pill picker on the right) with the two boolean states 打开 / 关闭.
10
+ */
11
+ export declare function DiffApprovalSettingsTab({ t }: DiffApprovalSettingsTabProps): import("react").JSX.Element;
@@ -16,11 +16,19 @@ export declare const zh: {
16
16
  'panel.missing': string;
17
17
  'panel.missingHint': string;
18
18
  'panel.createHint': string;
19
+ 'panel.pasteOnCopy': string;
20
+ 'panel.pasteOnCopyDesc': string;
21
+ 'panel.importUntracked': string;
22
+ 'panel.importUntrackedDesc': string;
23
+ 'settings.tabLabel': string;
19
24
  'row.create': string;
25
+ 'row.failed': string;
20
26
  'row.added': string;
21
27
  'row.removed': string;
22
28
  'action.keep': string;
23
29
  'action.revert': string;
30
+ 'action.keepAll': string;
31
+ 'action.revertAll': string;
24
32
  'action.openFile': string;
25
33
  'action.revealFile': string;
26
34
  'action.busy': string;
@@ -31,7 +39,16 @@ export declare const zh: {
31
39
  'action.langAuto': string;
32
40
  'action.langAutoDetected': string;
33
41
  'action.langSelect': string;
42
+ 'action.toggleOn': string;
43
+ 'action.toggleOff': string;
44
+ 'action.importVcs': string;
45
+ 'action.importVcsBusy': string;
46
+ 'panel.importNone': string;
47
+ 'panel.importDone': string;
48
+ 'panel.importNoVcs': string;
49
+ 'panel.importFailed': string;
34
50
  'action.search': string;
51
+ 'action.settings': string;
35
52
  'action.expand': string;
36
53
  'action.exitFullscreen': string;
37
54
  'action.close': string;
@@ -63,11 +80,19 @@ export declare const en: {
63
80
  'panel.missing': string;
64
81
  'panel.missingHint': string;
65
82
  'panel.createHint': string;
83
+ 'panel.pasteOnCopy': string;
84
+ 'panel.pasteOnCopyDesc': string;
85
+ 'panel.importUntracked': string;
86
+ 'panel.importUntrackedDesc': string;
87
+ 'settings.tabLabel': string;
66
88
  'row.create': string;
89
+ 'row.failed': string;
67
90
  'row.added': string;
68
91
  'row.removed': string;
69
92
  'action.keep': string;
70
93
  'action.revert': string;
94
+ 'action.keepAll': string;
95
+ 'action.revertAll': string;
71
96
  'action.openFile': string;
72
97
  'action.revealFile': string;
73
98
  'action.busy': string;
@@ -78,7 +103,16 @@ export declare const en: {
78
103
  'action.langAuto': string;
79
104
  'action.langAutoDetected': string;
80
105
  'action.langSelect': string;
106
+ 'action.toggleOn': string;
107
+ 'action.toggleOff': string;
108
+ 'action.importVcs': string;
109
+ 'action.importVcsBusy': string;
110
+ 'panel.importNone': string;
111
+ 'panel.importDone': string;
112
+ 'panel.importNoVcs': string;
113
+ 'panel.importFailed': string;
81
114
  'action.search': string;
115
+ 'action.settings': string;
82
116
  'action.expand': string;
83
117
  'action.exitFullscreen': string;
84
118
  'action.close': string;
@@ -5,7 +5,7 @@
5
5
  * @module dsh-diff-approval/client/port
6
6
  */
7
7
  import type { ClientConnectionRpc, SessionId } from '@deepseek-ai/dsh-client-connection/client';
8
- import type { DiffApprovalActionValue, DiffApprovalBlockRange, DiffApprovalListValue, DiffApprovalOpenAction, DiffApprovalOpenValue } from '../types.ts';
8
+ import type { DiffApprovalActionValue, DiffApprovalBlockRange, DiffApprovalListValue, DiffApprovalOpenAction, DiffApprovalOpenValue, VcsImportValue } from '../types.ts';
9
9
  /** The channel the host half registers and this port calls. */
10
10
  export declare const DIFF_APPROVAL_CHANNEL = "/diff-approval";
11
11
  /** This package's business verbs over the review channel. */
@@ -20,6 +20,12 @@ export interface DiffApprovalPort {
20
20
  blockKeep(sessionId: SessionId, id: string, block: DiffApprovalBlockRange): Promise<DiffApprovalActionValue>;
21
21
  /** Revert one diff block (restore its old lines in the file). */
22
22
  blockRevert(sessionId: SessionId, id: string, block: DiffApprovalBlockRange): Promise<DiffApprovalActionValue>;
23
+ /** Undo the session's last keep/revert (restore the before state). */
24
+ undo(sessionId: SessionId): Promise<DiffApprovalActionValue>;
25
+ /** Redo the session's last undone keep/revert (re-apply the after state). */
26
+ redo(sessionId: SessionId): Promise<DiffApprovalActionValue>;
27
+ /** Import the workspace's local VCS changes as pending entries. */
28
+ importVcs(sessionId: SessionId, includeUntracked: boolean): Promise<VcsImportValue>;
23
29
  /** Open one file with its default application or reveal it in the folder. */
24
30
  open(sessionId: SessionId, id: string, action: DiffApprovalOpenAction): Promise<DiffApprovalOpenValue>;
25
31
  }
@@ -0,0 +1,19 @@
1
+ /** Client preferences for the review panel, persisted in localStorage. */
2
+ /**
3
+ * Whether copying a reference should also paste it into the chat input and
4
+ * focus it. Defaults to on; only an explicit `'0'` disables it.
5
+ * @returns whether auto-paste is enabled.
6
+ */
7
+ export declare function pasteOnCopyEnabled(): boolean;
8
+ /** Persist the auto-paste preference. */
9
+ export declare function setPasteOnCopyEnabled(value: boolean): void;
10
+ /**
11
+ * Whether importing workspace VCS changes includes new/untracked files (git
12
+ * `??`, svn `?`, p4 unversioned). Defaults to off: collecting them scans the
13
+ * whole workspace, which can be slow on large trees, so the preference is
14
+ * opt-in. Only an explicit `'1'` enables it.
15
+ * @returns whether untracked files are imported.
16
+ */
17
+ export declare function includeUntrackedEnabled(): boolean;
18
+ /** Persist the import-untracked preference. */
19
+ export declare function setIncludeUntrackedEnabled(value: boolean): void;
@@ -1,7 +1,7 @@
1
1
  /** The panel's injected business face and its observable snapshot. */
2
2
  import type { HostObservable } from '@deepseek-ai/dsh-client-ui-slots';
3
3
  import type { SessionId } from '@deepseek-ai/dsh-client-connection/client';
4
- import type { DiffApprovalBlockRange, DiffApprovalOpenAction, PendingFileDiff } from '../types.ts';
4
+ import type { DiffApprovalBlockRange, DiffApprovalOpenAction, PendingFileDiff, VcsImportValue } from '../types.ts';
5
5
  /** What the panel reads and drives: the pending list plus in-flight entries. */
6
6
  export interface PendingDiffSnapshot {
7
7
  /** Whether a list read has completed at least once. */
@@ -12,6 +12,8 @@ export interface PendingDiffSnapshot {
12
12
  error?: string;
13
13
  /** Entry ids whose keep/revert is in flight; their controls are disabled. */
14
14
  busy: ReadonlySet<string>;
15
+ /** 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. */
16
+ failed?: ReadonlyMap<string, string> | undefined;
15
17
  /** The viewing session's workspace root (when it has one); enables workspace-relative references. */
16
18
  workspacePath?: string | undefined;
17
19
  }
@@ -33,4 +35,12 @@ export interface PendingPanelFace {
33
35
  onBlockRevert: (sessionId: SessionId, id: string, block: DiffApprovalBlockRange) => Promise<void>;
34
36
  /** Open one file with its default application or reveal it in the folder. */
35
37
  onOpen: (sessionId: SessionId, id: string, action: DiffApprovalOpenAction) => Promise<void>;
38
+ /** Paste a copied reference into the session's chat input and focus it. */
39
+ onPasteReference: (sessionId: SessionId, reference: string) => void;
40
+ /** Undo the session's last keep/revert, then refresh the list; resolves to the affected entry id when it is still pending. */
41
+ onUndo: (sessionId: SessionId) => Promise<string | undefined>;
42
+ /** Redo the session's last undone keep/revert, then refresh the list; resolves to the affected entry id when it is still pending. */
43
+ onRedo: (sessionId: SessionId) => Promise<string | undefined>;
44
+ /** Import the workspace's local VCS changes as pending entries (detection included). */
45
+ onImportVcs: (sessionId: SessionId, includeUntracked: boolean) => Promise<VcsImportValue>;
36
46
  }
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import type { SessionId } from '@deepseek-ai/dsh-client-connection/client';
9
9
  import type { HostObservable } from '@deepseek-ai/dsh-client-ui-slots';
10
- import type { DiffApprovalBlockRange, DiffApprovalOpenAction } from '../types.ts';
10
+ import type { DiffApprovalBlockRange, DiffApprovalOpenAction, VcsImportValue } from '../types.ts';
11
11
  import type { PendingDiffSnapshot } from './slots.ts';
12
12
  import type { DiffApprovalPort } from './port.ts';
13
13
  /** The observable the panel reads and the plugin body drives. */
@@ -22,6 +22,12 @@ export interface PendingDiffStore extends HostObservable<PendingDiffSnapshot> {
22
22
  blockKeep: (sessionId: SessionId, id: string, block: DiffApprovalBlockRange) => Promise<void>;
23
23
  /** Revert one diff block, then refresh so the entry's diff reflects the undo. */
24
24
  blockRevert: (sessionId: SessionId, id: string, block: DiffApprovalBlockRange) => Promise<void>;
25
+ /** Undo the session's last keep/revert, then refresh; resolves to the affected entry id when it is still pending. */
26
+ undo: (sessionId: SessionId) => Promise<string | undefined>;
27
+ /** Redo the session's last undone keep/revert, then refresh; resolves to the affected entry id when it is still pending. */
28
+ redo: (sessionId: SessionId) => Promise<string | undefined>;
29
+ /** Import the workspace's local VCS changes as pending entries, then refresh. */
30
+ importVcs: (sessionId: SessionId, includeUntracked: boolean) => Promise<VcsImportValue>;
25
31
  /** Open one file with its default application or reveal it in the folder. */
26
32
  open: (sessionId: SessionId, id: string, action: DiffApprovalOpenAction) => Promise<void>;
27
33
  /** Drop every local fact (used on connection reset). */
@@ -69,6 +69,18 @@ export declare class PendingDiffStore {
69
69
  oldText?: string;
70
70
  newText?: string;
71
71
  }): boolean;
72
+ /**
73
+ * Restore one entry exactly as given (an undo/redo replays a snapshot). The
74
+ * entry is inserted or replaced by id, and the path index points at it so a
75
+ * later capture folds into the restored entry. The store keeps one entry per
76
+ * path: restoring an entry for a path that a DIFFERENT entry currently owns
77
+ * drops that occupant first, so an undo can never leave two list items for
78
+ * the same file (e.g. an imported entry and a replayed keep of the same path).
79
+ * @param sessionId - the owning session.
80
+ * @param entry - the entry state to restore.
81
+ * @returns whether the store changed.
82
+ */
83
+ restore(sessionId: SessionId, entry: PendingEntry): boolean;
72
84
  /** Total entry count across all sessions. */
73
85
  get size(): number;
74
86
  }
@@ -82,10 +82,21 @@ export interface DiffApprovalBlockTarget {
82
82
  id: string;
83
83
  block: DiffApprovalBlockRange;
84
84
  }
85
- /** Outcome of one keep/revert request. */
86
- export type DiffApprovalActionOutcome = 'kept' | 'reverted' | 'missing';
85
+ /** Outcome of one keep/revert/undo/redo request. */
86
+ export type DiffApprovalActionOutcome = 'kept' | 'reverted' | 'missing' | 'undone' | 'redone' | 'nothing';
87
+ /** The version-control systems the import integration knows. */
88
+ export type VcsKind = 'git' | 'svn' | 'p4';
89
+ /** Value returned by the channel's vcs-import endpoint. */
90
+ export interface VcsImportValue {
91
+ /** How many pending entries were created (0 when nothing was imported). */
92
+ imported: number;
93
+ /** Whether a VCS root was found at all (false when the workspace is not in a git/svn/p4 checkout). */
94
+ detected: boolean;
95
+ }
87
96
  /** Value returned by the channel's keep and revert endpoints. */
88
97
  export interface DiffApprovalActionValue {
89
98
  /** What the request did; `missing` means no pending entry existed. */
90
99
  outcome: DiffApprovalActionOutcome;
100
+ /** Entry id the undo/redo affected; absent for a no-op or a non-undo action. */
101
+ id?: string | undefined;
91
102
  }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Version-control integration, host half: detect which VCS (git/svn/p4)
3
+ * encloses a workspace by walking up the directory tree, and enumerate the
4
+ * workspace's LOCAL changes for import into the pending list. The workspace
5
+ * is often a subdirectory of the VCS root, so the root is found by walking up
6
+ * from the workspace path and the imported changes are filtered to files
7
+ * inside the workspace. Commands run through the deployment's `ctx.shell`
8
+ * executor (applying its sandbox/policy) with the VCS root as the working
9
+ * directory.
10
+ *
11
+ * Scope of one import (mirrors the panel's preferences):
12
+ * - modified files (git: working tree vs index — "仅未暂存"; svn: vs BASE;
13
+ * p4: opened edits), imported as `edit` (old = baseline, new = working);
14
+ * - deleted files, imported as `edit` with an empty new side (revert restores);
15
+ * - new/untracked files (git `??`, svn `?`, p4 not-yet-opened files), imported
16
+ * as `create` ONLY when the untracked preference is on. For p4 that means a
17
+ * full workspace scan (`p4 status`, which can be slow); with the preference
18
+ * off only already-opened files are read.
19
+ * @module dsh-diff-approval/vcs
20
+ */
21
+ /** The version-control systems this integration knows. */
22
+ export type VcsKind = 'git' | 'svn' | 'p4';
23
+ /** A detected version-control root and its kind. */
24
+ export interface VcsRoot {
25
+ kind: VcsKind;
26
+ /** The directory whose VCS marker was found (the repo/checkout/client root). */
27
+ root: string;
28
+ }
29
+ /** One change imported from the VCS, ready to become a pending entry. */
30
+ export interface VcsChange {
31
+ /** Absolute path of the changed file (inside the workspace). */
32
+ path: string;
33
+ kind: 'edit' | 'create';
34
+ /** Baseline (pre-change) content; empty for a brand-new file. */
35
+ oldText: string;
36
+ /** Working content; empty for a deleted file. */
37
+ newText: string;
38
+ }
39
+ /** The subset of `ctx.shell`'s executor this module calls (kept structural so
40
+ * the module stays dependency-light and testable with a fake). */
41
+ export interface ShellExecutorLike {
42
+ resolve(request: {
43
+ command: string;
44
+ workdir?: string | undefined;
45
+ timeoutMs?: number | undefined;
46
+ signal?: AbortSignal | undefined;
47
+ }): unknown;
48
+ run(spec: unknown): Promise<{
49
+ exitCode: number | null;
50
+ stdout: {
51
+ text: string;
52
+ };
53
+ stderr: {
54
+ text: string;
55
+ };
56
+ }>;
57
+ }
58
+ /** Reads one file's working content; undefined when the file is absent. */
59
+ export type VcsFileReader = (absolutePath: string) => Promise<string | undefined>;
60
+ /** Everything an import needs, in one call shape. */
61
+ export interface VcsImportInput {
62
+ kind: VcsKind;
63
+ /** The VCS root found by {@link detectVcsRoot}. */
64
+ root: string;
65
+ /** The session's workspace root; only changes under it are imported. */
66
+ workspaceRoot: string;
67
+ /** Whether new/untracked files are imported (git `??`, svn `?`). */
68
+ includeUntracked: boolean;
69
+ shell: ShellExecutorLike;
70
+ /** Reads working-file content (the host passes a node fs reader). */
71
+ readText: VcsFileReader;
72
+ signal?: AbortSignal | undefined;
73
+ }
74
+ /**
75
+ * Find the VCS enclosing `start` by walking up the directory tree: the first
76
+ * directory (deepest) holding a marker wins, with git > svn > p4 when several
77
+ * markers share one directory. Stops at the filesystem root.
78
+ * @param start - the workspace directory to start from.
79
+ * @returns the detected root, or undefined when no VCS marker is found.
80
+ */
81
+ export declare function detectVcsRoot(start: string): VcsRoot | undefined;
82
+ /**
83
+ * Enumerate the workspace's local changes for one VCS. Runs the VCS read-only
84
+ * commands and returns one {@link VcsChange} per changed file inside the
85
+ * workspace.
86
+ * @param input - the VCS, its root, the workspace root, and the reading tools.
87
+ * @returns the changes; an absent/unusable VCS surfaces as a thrown error.
88
+ */
89
+ export declare function listVcsChanges(input: VcsImportInput): Promise<VcsChange[]>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-diff-approval",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "DeepSeek Harness plugin: pending-edit review with whole-file diff and Keep/Revert",
5
5
  "packageManager": "pnpm@11.21.0",
6
6
  "author": "Wu Zhiwei",
@@ -68,6 +68,7 @@
68
68
  "@deepseek-ai/dsh-client-locale": ">=0.1.0-rc.5",
69
69
  "@deepseek-ai/dsh-client-runtime": ">=0.1.0-rc.5",
70
70
  "@deepseek-ai/dsh-client-ui-primitives": ">=0.1.0-rc.5",
71
+ "@deepseek-ai/dsh-client-ui-settings": ">=0.1.0-rc.5",
71
72
  "@deepseek-ai/dsh-client-ui-sidebar": ">=0.1.0-rc.5",
72
73
  "@deepseek-ai/dsh-client-ui-slots": ">=0.1.0-rc.5",
73
74
  "@deepseek-ai/dsh-fs": ">=0.1.0-rc.5",
@@ -83,6 +84,7 @@
83
84
  "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.5",
84
85
  "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.5",
85
86
  "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.5",
87
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.5",
86
88
  "@deepseek-ai/dsh-client-ui-sidebar": "^0.1.0-rc.5",
87
89
  "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.5",
88
90
  "@deepseek-ai/dsh-fs": "^0.1.0-rc.5",