@openvcs/sdk 0.4.0 → 0.4.1-beta.128

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openvcs/sdk",
3
- "version": "0.4.0",
3
+ "version": "0.4.1-beta.128",
4
4
  "description": "OpenVCS SDK CLI for plugin scaffolding and runtime asset builds",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "homepage": "https://openvcs.app/",
@@ -12,7 +12,7 @@
12
12
  "url": "https://github.com/Open-VCS/OpenVCS-SDK/issues"
13
13
  },
14
14
  "engines": {
15
- "node": ">=18"
15
+ "node": ">=20"
16
16
  },
17
17
  "bin": {
18
18
  "openvcs": "bin/openvcs.js"
@@ -32,7 +32,9 @@
32
32
  },
33
33
  "scripts": {
34
34
  "build": "node scripts/build-sdk.js",
35
- "test": "npm run build && npm run test:types && node --test test/*.test.js",
35
+ "test": "npm run build && npm run test:types && node scripts/run-tests.js",
36
+ "coverage": "npm run build && npm run test:types && c8 --check-coverage --lines 95 --functions 95 --branches 85 --statements 95 --reporter=text --reporter=lcov node scripts/run-tests.js",
37
+ "test:coverage": "npm run coverage",
36
38
  "test:types": "node ./node_modules/typescript/bin/tsc -p test/tsconfig.json",
37
39
  "prepack": "npm run build",
38
40
  "preopenvcs": "npm run build",
@@ -40,6 +42,7 @@
40
42
  },
41
43
  "devDependencies": {
42
44
  "@types/node": "^25.3.3",
45
+ "c8": "^11.0.0",
43
46
  "typescript": "^6.0.3"
44
47
  },
45
48
  "files": [
package/src/lib/build.ts CHANGED
@@ -6,6 +6,9 @@ import * as path from "node:path";
6
6
  import { spawnSync } from "node:child_process";
7
7
 
8
8
  import { isPathInside } from "./fs-utils";
9
+ import { npmArgsPrefix, npmExecutable } from "./npm-runner";
10
+
11
+ export { npmExecutable } from "./npm-runner";
9
12
 
10
13
  type UsageError = Error & { code?: string };
11
14
 
@@ -23,6 +26,10 @@ export interface ManifestInfo {
23
26
  manifestPath: string;
24
27
  }
25
28
 
29
+ function writeBuildProgress(message: string): void {
30
+ process.stderr.write(`openvcs build: ${message}\n`);
31
+ }
32
+
26
33
  interface CommandResult {
27
34
  status: number | null;
28
35
  error?: Error;
@@ -34,21 +41,6 @@ interface PackageScripts {
34
41
 
35
42
  const AUTHORED_PLUGIN_MODULE_BASENAME = "plugin.js";
36
43
 
37
- /** Returns the npm executable name for the current platform. */
38
- export function npmExecutable(): string {
39
- return "npm";
40
- }
41
-
42
- /** Returns whether a command must be launched via the Windows shell. */
43
- export function shouldUseWindowsShell(program: string): boolean {
44
- if (process.platform !== "win32") {
45
- return false;
46
- }
47
-
48
- const normalized = program.toLowerCase();
49
- return normalized === "npm" || normalized.endsWith(".cmd") || normalized.endsWith(".bat");
50
- }
51
-
52
44
  /** Formats help text for the build command. */
53
45
  export function buildUsage(commandName = "openvcs"): string {
54
46
  return `${commandName} build [args]\n\n --plugin-dir <path> Plugin repository root (contains package.json with openvcs metadata)\n -V, --verbose Enable verbose output\n`;
@@ -88,11 +80,14 @@ export function parseBuildArgs(args: string[]): BuildArgs {
88
80
  }
89
81
 
90
82
  /** Reads and validates the plugin manifest. */
91
- export function readManifest(pluginDir: string): ManifestInfo {
83
+ export function readManifest(pluginDir: string, verbose = false): ManifestInfo {
92
84
  const manifestPath = path.join(pluginDir, "package.json");
93
85
  let manifestRaw: string;
94
86
  let manifestFd: number | undefined;
95
87
  let manifest: unknown;
88
+ if (verbose) {
89
+ writeBuildProgress(`reading manifest at ${manifestPath}`);
90
+ }
96
91
  try {
97
92
  manifestFd = fs.openSync(manifestPath, "r");
98
93
  const manifestStat = fs.fstatSync(manifestFd);
@@ -142,6 +137,10 @@ export function readManifest(pluginDir: string): ManifestInfo {
142
137
  const entryValue = openvcs.entry;
143
138
  const entry = typeof entryValue === "string" ? entryValue.trim() : undefined;
144
139
 
140
+ if (verbose) {
141
+ writeBuildProgress(`manifest loaded for ${pluginId}${moduleExec ? ` (module.exec: ${moduleExec})` : ""}`);
142
+ }
143
+
145
144
  return {
146
145
  pluginId,
147
146
  moduleExec,
@@ -151,12 +150,19 @@ export function readManifest(pluginDir: string): ManifestInfo {
151
150
  }
152
151
 
153
152
  /** Verifies that a declared module entry resolves to a real file under `bin/`. */
154
- export function validateDeclaredModuleExec(pluginDir: string, moduleExec: string | undefined): void {
153
+ export function validateDeclaredModuleExec(
154
+ pluginDir: string,
155
+ moduleExec: string | undefined,
156
+ verbose = false,
157
+ ): void {
155
158
  if (!moduleExec) {
156
159
  return;
157
160
  }
158
161
 
159
162
  const targetPath = resolveDeclaredModuleExecPath(pluginDir, moduleExec);
163
+ if (verbose) {
164
+ writeBuildProgress(`checking declared bootstrap at ${targetPath}`);
165
+ }
160
166
  if (!fs.existsSync(targetPath) || !fs.lstatSync(targetPath).isFile()) {
161
167
  throw new Error(`module entrypoint not found at ${targetPath}`);
162
168
  }
@@ -191,11 +197,15 @@ export function authoredPluginModulePath(pluginDir: string): string {
191
197
  export function validateGeneratedBootstrapTargets(
192
198
  pluginDir: string,
193
199
  moduleExec: string | undefined,
200
+ verbose = false,
194
201
  ): void {
195
202
  if (!moduleExec) {
196
203
  return;
197
204
  }
198
205
 
206
+ if (verbose) {
207
+ writeBuildProgress(`validating generated bootstrap target for ${moduleExec}`);
208
+ }
199
209
  resolveDeclaredModuleExecPath(pluginDir, moduleExec);
200
210
  const normalizedExec = moduleExec.trim().toLowerCase();
201
211
  if (normalizedExec === AUTHORED_PLUGIN_MODULE_BASENAME.toLowerCase()) {
@@ -242,18 +252,28 @@ export function renderGeneratedBootstrap(
242
252
  }
243
253
 
244
254
  /** Writes the generated SDK-owned module entrypoint under `bin/<module.exec>`. */
245
- export function generateModuleBootstrap(pluginDir: string, moduleExec: string | undefined): void {
255
+ export function generateModuleBootstrap(
256
+ pluginDir: string,
257
+ moduleExec: string | undefined,
258
+ verbose = false,
259
+ ): void {
246
260
  if (!moduleExec) {
247
- console.debug(`generateModuleBootstrap: no module.exec defined, skipping bootstrap generation for ${pluginDir}`);
261
+ if (verbose) {
262
+ writeBuildProgress(`no module.exec defined for ${pluginDir}; skipping bootstrap generation`);
263
+ }
248
264
  return;
249
265
  }
250
266
 
251
- validateGeneratedBootstrapTargets(pluginDir, moduleExec);
267
+ validateGeneratedBootstrapTargets(pluginDir, moduleExec, verbose);
252
268
  const execPath = resolveDeclaredModuleExecPath(pluginDir, moduleExec);
253
269
  const pluginModulePath = authoredPluginModulePath(pluginDir);
254
270
  const pluginModuleImportPath = relativeBinImport(execPath, pluginModulePath);
255
271
  const isEsm = detectEsmMode(pluginDir, moduleExec);
256
272
 
273
+ if (verbose) {
274
+ writeBuildProgress(`writing bootstrap ${execPath} -> ${pluginModuleImportPath}${isEsm ? " (esm)" : " (cjs)"}`);
275
+ }
276
+
257
277
  fs.mkdirSync(path.dirname(execPath), { recursive: true });
258
278
  fs.writeFileSync(execPath, renderGeneratedBootstrap(pluginModuleImportPath, isEsm), "utf8");
259
279
  }
@@ -288,8 +308,8 @@ export function runCommand(program: string, args: string[], cwd: string, verbose
288
308
 
289
309
  const result = spawnSync(program, args, {
290
310
  cwd,
291
- shell: shouldUseWindowsShell(program),
292
311
  stdio: ["ignore", verbose ? "inherit" : "ignore", "inherit"],
312
+ windowsHide: true,
293
313
  }) as CommandResult;
294
314
 
295
315
  if (result.error) {
@@ -325,11 +345,14 @@ function readPackageScripts(pluginDir: string): PackageScripts {
325
345
 
326
346
  /** Builds a plugin's runtime assets when it declares a code module. */
327
347
  export function buildPluginAssets(parsedArgs: BuildArgs): ManifestInfo {
328
- const manifest = readManifest(parsedArgs.pluginDir);
348
+ writeBuildProgress(`reading plugin manifest in ${parsedArgs.pluginDir}`);
349
+ const manifest = readManifest(parsedArgs.pluginDir, parsedArgs.verbose);
329
350
  if (!manifest.moduleExec) {
351
+ writeBuildProgress(`theme-only plugin ${manifest.pluginId}; nothing to build`);
330
352
  return manifest;
331
353
  }
332
354
 
355
+ writeBuildProgress(`building plugin ${manifest.pluginId}`);
333
356
  if (!hasPackageJson(parsedArgs.pluginDir)) {
334
357
  throw new Error(`code plugins must include package.json: ${path.join(parsedArgs.pluginDir, "package.json")}`);
335
358
  }
@@ -342,8 +365,12 @@ export function buildPluginAssets(parsedArgs: BuildArgs): ManifestInfo {
342
365
  );
343
366
  }
344
367
 
345
- runCommand(npmExecutable(), ["run", "build:plugin"], parsedArgs.pluginDir, parsedArgs.verbose);
346
- generateModuleBootstrap(parsedArgs.pluginDir, manifest.moduleExec);
347
- validateDeclaredModuleExec(parsedArgs.pluginDir, manifest.moduleExec);
368
+ writeBuildProgress(`running build:plugin`);
369
+ runCommand(npmExecutable(), [...npmArgsPrefix(), "run", "build:plugin"], parsedArgs.pluginDir, parsedArgs.verbose);
370
+ writeBuildProgress(`generating bootstrap ${manifest.moduleExec}`);
371
+ generateModuleBootstrap(parsedArgs.pluginDir, manifest.moduleExec, parsedArgs.verbose);
372
+ writeBuildProgress(`validating bootstrap ${manifest.moduleExec}`);
373
+ validateDeclaredModuleExec(parsedArgs.pluginDir, manifest.moduleExec, parsedArgs.verbose);
374
+ writeBuildProgress(`build complete for ${manifest.pluginId}`);
348
375
  return manifest;
349
376
  }
package/src/lib/init.ts CHANGED
@@ -4,6 +4,8 @@ import * as readline from "node:readline/promises";
4
4
  import { stdin, stdout } from "node:process";
5
5
  import { spawnSync } from "node:child_process";
6
6
 
7
+ import { npmArgsPrefix, npmExecutable } from "./npm-runner";
8
+
7
9
  const packageJson: { version: string } = require("../package.json");
8
10
 
9
11
  type UsageError = Error & { code?: string };
@@ -33,19 +35,6 @@ interface InitCommandError {
33
35
  code?: string;
34
36
  }
35
37
 
36
- function npmExecutable(): string {
37
- return "npm";
38
- }
39
-
40
- function shouldUseWindowsShell(program: string): boolean {
41
- if (process.platform !== "win32") {
42
- return false;
43
- }
44
-
45
- const normalized = program.toLowerCase();
46
- return normalized === "npm" || normalized.endsWith(".cmd") || normalized.endsWith(".bat");
47
- }
48
-
49
38
  export function initUsage(commandName = "openvcs"): string {
50
39
  return `Usage: ${commandName} init [--theme] [target-dir]\n\nOptions:\n --theme Start with a theme-only plugin template\n`;
51
40
  }
@@ -214,10 +203,10 @@ async function collectAnswers(
214
203
  }
215
204
 
216
205
  function runNpmInstall(targetDir: string): void {
217
- const result = spawnSync(npmExecutable(), ["install"], {
206
+ const result = spawnSync(npmExecutable(), [...npmArgsPrefix(), "install"], {
218
207
  cwd: targetDir,
219
- shell: shouldUseWindowsShell(npmExecutable()),
220
208
  stdio: "inherit",
209
+ windowsHide: true,
221
210
  });
222
211
  if (result.error) {
223
212
  throw new Error(`failed to spawn npm install in ${targetDir}: ${result.error.message}`);
@@ -241,7 +230,6 @@ function writeModuleTemplate(answers: InitAnswers): void {
241
230
  openvcs: {
242
231
  id: answers.pluginId,
243
232
  name: answers.pluginName,
244
- version: answers.pluginVersion,
245
233
  default_enabled: answers.defaultEnabled,
246
234
  module: { exec: "openvcs-plugin.js" },
247
235
  },
@@ -286,7 +274,6 @@ function writeThemeTemplate(answers: InitAnswers): void {
286
274
  openvcs: {
287
275
  id: answers.pluginId,
288
276
  name: answers.pluginName,
289
- version: answers.pluginVersion,
290
277
  default_enabled: answers.defaultEnabled,
291
278
  },
292
279
  scripts: {
@@ -376,8 +363,10 @@ export function isUsageError(error: unknown): error is InitCommandError {
376
363
  export const __private = {
377
364
  collectAnswers,
378
365
  createReadlinePromptDriver,
366
+ defaultPluginNameFromId,
379
367
  defaultPluginIdFromDir,
380
368
  sanitizeIdToken,
381
369
  validatePluginId,
382
370
  writeModuleTemplate,
371
+ writeThemeTemplate,
383
372
  };
@@ -0,0 +1,47 @@
1
+ // Copyright © 2025-2026 OpenVCS Contributors
2
+ // SPDX-License-Identifier: GPL-3.0-or-later
3
+
4
+ import * as fs from "node:fs";
5
+ import * as path from "node:path";
6
+
7
+ type ExistsFn = (path: string) => boolean;
8
+ type ResolveFn = (specifier: string) => string;
9
+
10
+ export interface NpmCommand {
11
+ program: string;
12
+ argsPrefix: string[];
13
+ }
14
+
15
+ export function resolveNpmCli(
16
+ execPath = process.execPath,
17
+ exists: ExistsFn = fs.existsSync,
18
+ resolve: ResolveFn = require.resolve,
19
+ ): string {
20
+ const localNodeModules = path.join(path.dirname(execPath), "node_modules", "npm", "bin", "npm-cli.js");
21
+ if (exists(localNodeModules)) {
22
+ return localNodeModules;
23
+ }
24
+
25
+ return resolve("npm/bin/npm-cli.js");
26
+ }
27
+
28
+ export function npmCommand(
29
+ platform = process.platform,
30
+ execPath = process.execPath,
31
+ exists: ExistsFn = fs.existsSync,
32
+ resolve: ResolveFn = require.resolve,
33
+ ): NpmCommand {
34
+ if (platform !== "win32") {
35
+ return { program: "npm", argsPrefix: [] };
36
+ }
37
+
38
+ return { program: execPath, argsPrefix: [resolveNpmCli(execPath, exists, resolve)] };
39
+ }
40
+
41
+ export function npmExecutable(): string {
42
+ return npmCommand().program;
43
+ }
44
+
45
+ export function npmArgsPrefix(): string[] {
46
+ return npmCommand().argsPrefix;
47
+ }
@@ -133,22 +133,30 @@ export function createRuntimeDispatcher(
133
133
 
134
134
  let result: unknown;
135
135
  if (timeout && timeout > 0) {
136
- const controller = new AbortController();
137
- const timeoutId = setTimeout(() => controller.abort(), timeout);
136
+ let timeoutId: ReturnType<typeof setTimeout> | undefined;
137
+ const timeoutPromise = new Promise<never>((_resolve, reject) => {
138
+ timeoutId = setTimeout(() => {
139
+ reject(pluginError('request-timeout', `method '${method}' timed out after ${timeout}ms`));
140
+ }, timeout);
141
+ });
138
142
  try {
139
- result = await handler(params, {
140
- host,
141
- requestId: id,
142
- method,
143
- });
143
+ result = await Promise.race([
144
+ handler(params, {
145
+ host,
146
+ requestId: id,
147
+ method,
148
+ }),
149
+ timeoutPromise,
150
+ ]);
144
151
  } catch (error) {
145
- clearTimeout(timeoutId);
146
- if ((error as Error).name === 'AbortError') {
147
- throw pluginError('request-timeout', `method '${method}' timed out after ${timeout}ms`);
152
+ if (timeoutId !== undefined) {
153
+ clearTimeout(timeoutId);
148
154
  }
149
155
  throw error;
150
156
  }
151
- clearTimeout(timeoutId);
157
+ if (timeoutId !== undefined) {
158
+ clearTimeout(timeoutId);
159
+ }
152
160
  } else {
153
161
  result = await handler(params, {
154
162
  host,
@@ -259,7 +259,7 @@ export abstract class VcsDelegateBase<
259
259
  diffFile(
260
260
  _params: VcsTypes.VcsDiffFileParams,
261
261
  _context: TContext,
262
- ): VcsHandlerResult<string[]> {
262
+ ): VcsHandlerResult<VcsTypes.VcsDiffFileResponse> {
263
263
  return this.unimplemented('diffFile');
264
264
  }
265
265
 
@@ -303,6 +303,14 @@ export abstract class VcsDelegateBase<
303
303
  return this.unimplemented('stagePatch');
304
304
  }
305
305
 
306
+ /** Handles `vcs.stage_selections` (structured hunk/line selections). */
307
+ stageSelections(
308
+ _params: VcsTypes.VcsStageSelectionsParams,
309
+ _context: TContext,
310
+ ): VcsHandlerResult<null> {
311
+ return this.unimplemented('stageSelections');
312
+ }
313
+
306
314
  /** Handles `vcs.stage_paths`. */
307
315
  stagePaths(
308
316
  _params: VcsTypes.VcsStagePathsParams,
@@ -53,6 +53,7 @@ export type VcsDelegateBindings<TContext> = {
53
53
  VcsTypes.VcsDelegates<TContext>['vcs.write_merge_result']
54
54
  >;
55
55
  stagePatch: NonNullable<VcsTypes.VcsDelegates<TContext>['vcs.stage_patch']>;
56
+ stageSelections: NonNullable<VcsTypes.VcsDelegates<TContext>['vcs.stage_selections']>;
56
57
  stagePaths: NonNullable<VcsTypes.VcsDelegates<TContext>['vcs.stage_paths']>;
57
58
  discardPaths: NonNullable<VcsTypes.VcsDelegates<TContext>['vcs.discard_paths']>;
58
59
  applyReversePatch: NonNullable<
@@ -123,6 +124,7 @@ export const VCS_DELEGATE_METHOD_MAPPINGS = {
123
124
  checkoutConflictSide: 'vcs.checkout_conflict_side',
124
125
  writeMergeResult: 'vcs.write_merge_result',
125
126
  stagePatch: 'vcs.stage_patch',
127
+ stageSelections: 'vcs.stage_selections',
126
128
  stagePaths: 'vcs.stage_paths',
127
129
  discardPaths: 'vcs.discard_paths',
128
130
  applyReversePatch: 'vcs.apply_reverse_patch',
@@ -17,6 +17,8 @@ export interface VcsCapabilities {
17
17
  push_pull: boolean;
18
18
  /** Indicates whether fast-forward helpers are supported. */
19
19
  fast_forward: boolean;
20
+ /** Merge strategies the backend supports (values like "merge", "squash", "rebase"). */
21
+ merge_strategies?: string[];
20
22
  }
21
23
 
22
24
  /** Describes params that carry a repository session id. */
@@ -110,20 +112,13 @@ export interface VcsRemoveRemoteParams extends VcsSessionParams {
110
112
  name: string;
111
113
  }
112
114
 
113
- /** Describes optional fetch flags. */
114
- export interface VcsFetchOptions {
115
- /** Indicates whether stale remote references should be pruned. */
116
- prune?: boolean;
117
- }
118
-
119
115
  /** Describes params for fetch methods. */
116
+
120
117
  export interface VcsFetchParams extends VcsSessionParams {
121
118
  /** Stores the remote to fetch when one is supplied. */
122
119
  remote?: string;
123
120
  /** Stores the refspec to fetch when one is supplied. */
124
121
  refspec?: string;
125
- /** Stores optional fetch flags. */
126
- opts?: VcsFetchOptions;
127
122
  }
128
123
 
129
124
  /** Describes params for push. */
@@ -180,6 +175,8 @@ export interface StatusFileEntry {
180
175
  resolved_conflict: boolean;
181
176
  /** Stores placeholder hunk information until richer diff support exists. */
182
177
  hunks: never[];
178
+ /** Indicates whether the file content should be treated as binary. */
179
+ binary?: boolean | null;
183
180
  }
184
181
 
185
182
  /** Describes the structured status payload returned to the host. */
@@ -256,6 +253,17 @@ export interface VcsDiffCommitParams extends VcsSessionParams {
256
253
  rev: string;
257
254
  }
258
255
 
256
+ /** Describes one structured diff payload returned for a file. */
257
+ export interface VcsDiffResult {
258
+ /** Stores line-oriented diff output. */
259
+ lines: string[];
260
+ /** Indicates whether the diff target should be treated as binary. */
261
+ binary?: boolean | null;
262
+ }
263
+
264
+ /** Describes the accepted `vcs.diff_file` response shapes. */
265
+ export type VcsDiffFileResponse = VcsDiffResult | string[];
266
+
259
267
  /** Describes params for `vcs.get_conflict_details`. */
260
268
  export interface VcsGetConflictDetailsParams extends VcsSessionParams {
261
269
  /** Stores the conflicted path. */
@@ -274,8 +282,6 @@ export interface VcsConflictDetails {
274
282
  theirs: string | null;
275
283
  /** Indicates whether the conflict is binary. */
276
284
  binary: boolean;
277
- /** Indicates whether the conflict references Git LFS content. */
278
- lfs_pointer: boolean;
279
285
  }
280
286
 
281
287
  /** Describes params for checking out one side of a conflict. */
@@ -300,6 +306,22 @@ export interface VcsStagePatchParams extends VcsSessionParams {
300
306
  patch: string;
301
307
  }
302
308
 
309
+ /** Describes a single file's hunk/line selection for partial staging. */
310
+ export interface HunkSelection {
311
+ /** Repository-relative file path. */
312
+ path: string;
313
+ /** Indices of whole hunks to include. */
314
+ whole_hunks: number[];
315
+ /** Per-hunk line selections: maps hunk index → 1-based line offsets. */
316
+ partial_hunks: Record<number, number[]>;
317
+ }
318
+
319
+ /** Describes params for staging structured selections (VCS-agnostic). */
320
+ export interface VcsStageSelectionsParams extends VcsSessionParams {
321
+ /** Structured hunk/line selections for multiple files. */
322
+ selections: HunkSelection[];
323
+ }
324
+
303
325
  /** Describes params for staging repository-relative paths into the index. */
304
326
  export interface VcsStagePathsParams extends VcsSessionParams {
305
327
  /** Stores the paths to stage. */
@@ -334,12 +356,17 @@ export interface VcsRenameBranchParams extends VcsSessionParams {
334
356
  new: string;
335
357
  }
336
358
 
359
+ /** Describes the supported merge strategies. */
360
+ export type VcsMergeStrategy = 'merge' | 'squash' | 'rebase';
361
+
337
362
  /** Describes params for merging another branch into the current branch. */
338
363
  export interface VcsMergeIntoCurrentParams extends VcsSessionParams {
339
364
  /** Stores the branch name to merge. */
340
365
  name: string;
341
366
  /** Stores an optional merge commit message. */
342
367
  message?: string;
368
+ /** Stores the merge strategy. Defaults to 'merge' when absent. */
369
+ strategy?: VcsMergeStrategy;
343
370
  }
344
371
 
345
372
  /** Describes params for setting a branch upstream. */
@@ -503,7 +530,7 @@ export interface VcsDelegates<TContext = unknown> {
503
530
  TContext
504
531
  >;
505
532
  /** Handles `vcs.diff_file`. */
506
- 'vcs.diff_file'?: RpcMethodHandler<VcsDiffFileParams, string[], TContext>;
533
+ 'vcs.diff_file'?: RpcMethodHandler<VcsDiffFileParams, VcsDiffFileResponse, TContext>;
507
534
  /** Handles `vcs.diff_commit`. */
508
535
  'vcs.diff_commit'?: RpcMethodHandler<VcsDiffCommitParams, string[], TContext>;
509
536
  /** Handles `vcs.get_conflict_details`. */
@@ -526,6 +553,8 @@ export interface VcsDelegates<TContext = unknown> {
526
553
  >;
527
554
  /** Handles `vcs.stage_patch`. */
528
555
  'vcs.stage_patch'?: RpcMethodHandler<VcsStagePatchParams, null, TContext>;
556
+ /** Handles `vcs.stage_selections` (structured hunk/line selections). */
557
+ 'vcs.stage_selections'?: RpcMethodHandler<VcsStageSelectionsParams, null, TContext>;
529
558
  /** Handles `vcs.stage_paths`. */
530
559
  'vcs.stage_paths'?: RpcMethodHandler<VcsStagePathsParams, null, TContext>;
531
560
  /** Handles `vcs.discard_paths`. */