ソースを参照

feat(marketplace): reload v2 service after mutations

Alvin Unreal 4 日 前
コミット
7de0608bd6

+ 4 - 1
README.md

@@ -110,7 +110,10 @@ Package manifests are data-only, exact-version locked, and stored under the XDG
 data directory. The CLI registry `install` command installs a package and
 enables its agent in the active preset in one operation. Local imports still
 require a separate activation step. Activated agents apply after the next
-OpenCode session/reload; the live registry is never hot-swapped.
+OpenCode session/reload; the live registry is never hot-swapped. CLI mutations
+restart an active OpenCode v2 managed service automatically; with no running
+service, the configuration is saved for the next launch. In-session mutations
+never restart their own host service and report whether application is pending.
 Manifests include bounded author, tag, license, plugin compatibility, routing,
 model policy, exact skills/MCPs/tools, and prompt metadata. Schema-v2 manifests
 retain the legacy routing fields; schema-v3 manifests use bounded single-line

+ 15 - 14
docs/marketplace.md

@@ -9,7 +9,9 @@ XDG data directory. The CLI registry `install` command installs a package and
 enables its agent in the active preset in one operation. Local imports and
 in-session tool actions remain separate from preset activation. Activated agents
 apply after the next OpenCode session or reload. The live agent registry is
-never hot-swapped.
+never hot-swapped. CLI mutations automatically restart an active OpenCode v2
+managed service through `opencode service restart`; when no service is running,
+the saved configuration is marked pending for the next launch.
 
 ## CLI
 
@@ -44,12 +46,11 @@ before deleting it locally, so a separate `disable` command is unnecessary.
 The old unsafe `--force` removal path is not supported.
 
 `status` reports installed packages, configured activation, live-session
-agents when used from the in-session tool, diagnostics, and whether a
-reload is required. Diagnostics include store problems (missing, corrupt,
-or operational read failures) and activation rejections (collision,
-missing required dependency, invalid alias). Operational failures leave
-in-session `reload_required` as `unknown` because disk identity cannot be
-compared.
+agents when used from the in-session tool, diagnostics, and reload status.
+Diagnostics include store problems (missing, corrupt, or operational read
+failures) and activation rejections (collision, missing required dependency,
+invalid alias). `unavailable` means the live registry could not be compared;
+it is never represented as an unknown reload state.
 
 ## Manifest routing versions
 
@@ -72,12 +73,12 @@ invoke it.
 
 Read-only actions (list, show, verify, status) do not change activation or
 contact the registry.
-Mutating actions write the local store and plugin config only and report
-`reload_required` only when disk activation differs from this session.
-Inactive or idempotent mutations do not include a reload note. The CLI
-has no live registry, so its reload status is `unknown`. In-session
-status is also `unknown` when the store or desired activation cannot be
-read (for example EACCES).
+Mutating actions write the local store and plugin config only. They never
+restart or terminate the OpenCode service that hosts the tool call. In-session
+mutations report `reload_status: pending` when the live registry differs,
+`applied` when it already matches, and `unavailable` when it cannot be
+compared. CLI mutations report `reloaded`, `pending`, `unsupported`, or
+`unavailable` after attempting the v2 service lifecycle check.
 
 ## Status fields
 
@@ -87,7 +88,7 @@ read (for example EACCES).
 | configured_agents | Active-preset activation on disk |
 | live_packages | Packages already in this session's registry, with version, digest, and runtime name |
 | diagnostics | Store and activation issues (missing, corrupt, operational, collision, missing required dependency, invalid alias, retired), labeled `disk` or `live` |
-| reload_required | `true`/`false` when live and desired identities can be compared; `unknown` for CLI and when store/desired resolution failed operationally |
+| reload_status | `reloaded` after an active v2 service restart; `pending` with no active service or when a tool mutation awaits a future reload; `applied` when the live registry already matches; `unsupported` for v1; `unavailable` when OpenCode or registry state cannot be inspected |
 
 ## Limits
 

+ 5 - 0
src/cli/marketplace.test.ts

@@ -139,6 +139,10 @@ describe('marketplace CLI parsing', () => {
               };
             },
           },
+          reload: async () => ({
+            status: 'pending' as const,
+            detail: 'test reload pending',
+          }),
         },
       );
 
@@ -150,6 +154,7 @@ describe('marketplace CLI parsing', () => {
       expect(output[0]).toContain(
         'Installed and enabled community/example@1.2.3 in the active preset',
       );
+      expect(output[0]).toContain('reload_status: pending');
     } finally {
       console.log = originalLog;
       if (originalConfigDir === undefined) {

+ 21 - 7
src/cli/marketplace.ts

@@ -7,6 +7,10 @@ import {
   enableMarketplaceAgent,
   preflightMarketplaceAgentActivation,
 } from '../marketplace/activation-config';
+import {
+  type MarketplaceReloadResult,
+  reloadOpenCodeService,
+} from '../marketplace/reload';
 import {
   collectMarketplaceStatus,
   formatMarketplaceStatus,
@@ -32,6 +36,10 @@ export interface MarketplaceArgs {
   update?: boolean;
 }
 
+export interface MarketplaceCommandOptions extends MarketplaceServiceOptions {
+  reload?: (projectDir: string) => Promise<MarketplaceReloadResult>;
+}
+
 function commandSet(): readonly string[] {
   return [
     'install',
@@ -105,12 +113,16 @@ export function parseMarketplaceArgs(args: string[]): MarketplaceArgs {
 
 export async function marketplaceCommand(
   args: string[],
-  options: MarketplaceServiceOptions = {},
+  options: MarketplaceCommandOptions = {},
 ): Promise<number> {
   try {
     const parsed = parseMarketplaceArgs(args);
     const service = new MarketplaceService(options);
     const projectDir = options.projectDir ?? process.cwd();
+    const reload = async (): Promise<MarketplaceReloadResult> => {
+      if (options.reload) return options.reload(projectDir);
+      return reloadOpenCodeService({ cwd: projectDir });
+    };
     switch (parsed.command) {
       case 'install': {
         const packageId = (parsed.value as string).trim().split('@', 1)[0];
@@ -120,7 +132,7 @@ export async function marketplaceCommand(
         console.log(
           mutationReloadNotice(
             `Installed and enabled ${pkg.manifest.id}@${pkg.manifest.version} in the active preset`,
-            'unknown',
+            await reload(),
           ),
         );
         return 0;
@@ -132,7 +144,7 @@ export async function marketplaceCommand(
         console.log(
           mutationReloadNotice(
             `${parsed.update ? 'Updated' : 'Imported'} ${pkg.manifest.id}@${pkg.manifest.version}`,
-            'unknown',
+            await reload(),
           ),
         );
         return 0;
@@ -142,7 +154,7 @@ export async function marketplaceCommand(
         console.log(
           mutationReloadNotice(
             `Updated ${pkg.manifest.id}@${pkg.manifest.version}`,
-            'unknown',
+            await reload(),
           ),
         );
         return 0;
@@ -175,7 +187,9 @@ export async function marketplaceCommand(
       }
       case 'remove':
         service.remove(parsed.value as string);
-        console.log(mutationReloadNotice(`Removed ${parsed.value}`, 'unknown'));
+        console.log(
+          mutationReloadNotice(`Removed ${parsed.value}`, await reload()),
+        );
         return 0;
       case 'enable':
         enableMarketplaceAgent(
@@ -186,7 +200,7 @@ export async function marketplaceCommand(
         console.log(
           mutationReloadNotice(
             `Enabled ${parsed.value} in the active preset`,
-            'unknown',
+            await reload(),
           ),
         );
         return 0;
@@ -195,7 +209,7 @@ export async function marketplaceCommand(
         console.log(
           mutationReloadNotice(
             `Disabled ${parsed.value} in the active preset`,
-            'unknown',
+            await reload(),
           ),
         );
         return 0;

+ 4 - 0
src/marketplace/r1-dispatch.test.ts

@@ -82,6 +82,10 @@ describe('R1 explicit CLI and tool dispatch', () => {
         projectDir: root,
         pluginVersion: '3.1.0',
         registryClient,
+        reload: async () => ({
+          status: 'pending' as const,
+          detail: 'test reload pending',
+        }),
       };
       expect(
         await marketplaceCommand(

+ 94 - 0
src/marketplace/reload.test.ts

@@ -0,0 +1,94 @@
+import { describe, expect, test } from 'bun:test';
+import { reloadOpenCodeService } from './reload';
+
+function commandRunner(
+  responses: Record<
+    string,
+    { exitCode: number; stdout?: string; stderr?: string }
+  >,
+) {
+  const calls: string[] = [];
+  const run = async (args: readonly string[]) => {
+    const key = args.join(' ');
+    calls.push(key);
+    const response = responses[key];
+    if (!response) throw new Error(`Unexpected command: ${key}`);
+    return {
+      exitCode: response.exitCode,
+      stdout: response.stdout ?? '',
+      stderr: response.stderr ?? '',
+    };
+  };
+  return { calls, run };
+}
+
+describe('OpenCode marketplace reload', () => {
+  test('restarts an active v2 service', async () => {
+    const runner = commandRunner({
+      '--version': { exitCode: 0, stdout: 'opencode v2.0.2\n' },
+      'service status': { exitCode: 0, stdout: 'http://127.0.0.1:4096\n' },
+      'service restart': { exitCode: 0, stdout: 'http://127.0.0.1:4096\n' },
+    });
+
+    await expect(
+      reloadOpenCodeService({ cwd: '/tmp/project', run: runner.run }),
+    ).resolves.toEqual({
+      status: 'reloaded',
+      detail:
+        'Active OpenCode v2 service restarted; marketplace configuration is applied.',
+    });
+    expect(runner.calls).toEqual([
+      '--version',
+      'service status',
+      'service restart',
+    ]);
+  });
+
+  test('leaves configuration pending when v2 has no service', async () => {
+    const runner = commandRunner({
+      '--version': { exitCode: 0, stdout: 'opencode 2.0.2' },
+      'service status': { exitCode: 0, stdout: 'stopped\n' },
+    });
+
+    await expect(
+      reloadOpenCodeService({ cwd: '/tmp/project', run: runner.run }),
+    ).resolves.toMatchObject({ status: 'pending' });
+    expect(runner.calls).toEqual(['--version', 'service status']);
+  });
+
+  test('does not attempt a service restart for v1', async () => {
+    const runner = commandRunner({
+      '--version': { exitCode: 0, stdout: '1.18.13' },
+    });
+
+    await expect(
+      reloadOpenCodeService({ cwd: '/tmp/project', run: runner.run }),
+    ).resolves.toMatchObject({ status: 'unsupported' });
+    expect(runner.calls).toEqual(['--version']);
+  });
+
+  test('reports unavailable without failing when OpenCode cannot be run', async () => {
+    const runner = commandRunner({
+      '--version': { exitCode: 127, stderr: 'command not found' },
+    });
+
+    await expect(
+      reloadOpenCodeService({ cwd: '/tmp/project', run: runner.run }),
+    ).resolves.toMatchObject({ status: 'unavailable' });
+  });
+
+  test('reports a failed restart without throwing', async () => {
+    const runner = commandRunner({
+      '--version': { exitCode: 0, stdout: '2.0.2' },
+      'service status': { exitCode: 0, stdout: 'http://127.0.0.1:4096' },
+      'service restart': { exitCode: 1, stderr: 'restart failed' },
+    });
+
+    await expect(
+      reloadOpenCodeService({ cwd: '/tmp/project', run: runner.run }),
+    ).resolves.toEqual({
+      status: 'unavailable',
+      detail: 'OpenCode service unavailable: restart failed',
+    });
+  });
+});

+ 136 - 0
src/marketplace/reload.ts

@@ -0,0 +1,136 @@
+import { crossSpawn } from '../utils/compat';
+
+export type MarketplaceReloadStatus =
+  | 'reloaded'
+  | 'applied'
+  | 'pending'
+  | 'unsupported'
+  | 'unavailable';
+
+export interface MarketplaceReloadResult {
+  status: MarketplaceReloadStatus;
+  detail: string;
+}
+
+export interface MarketplaceCommandResult {
+  exitCode: number;
+  stdout: string;
+  stderr: string;
+}
+
+export type MarketplaceCommandRunner = (
+  args: readonly string[],
+  cwd: string,
+) => Promise<MarketplaceCommandResult>;
+
+export interface ReloadOpenCodeServiceOptions {
+  cwd: string;
+  command?: string;
+  run?: MarketplaceCommandRunner;
+}
+
+function defaultCommandRunner(command: string): MarketplaceCommandRunner {
+  return async (args, cwd) => {
+    try {
+      const child = crossSpawn([command, ...args], {
+        cwd,
+        stdout: 'pipe',
+        stderr: 'pipe',
+      });
+      const [exitCode, stdout, stderr] = await Promise.all([
+        child.exited,
+        child.stdout(),
+        child.stderr(),
+      ]);
+      return { exitCode, stdout, stderr };
+    } catch (error) {
+      return {
+        exitCode: 127,
+        stdout: '',
+        stderr: error instanceof Error ? error.message : String(error),
+      };
+    }
+  };
+}
+
+function majorVersion(output: string): number | undefined {
+  const match = output.match(/(?:^|[^\d])v?(\d+)\.\d+(?:\.\d+)?\b/i);
+  return match ? Number(match[1]) : undefined;
+}
+
+function unavailableDetail(result: MarketplaceCommandResult): string {
+  const detail = result.stderr.trim() || result.stdout.trim();
+  return detail
+    ? `OpenCode service unavailable: ${detail}`
+    : 'OpenCode service unavailable.';
+}
+
+async function runSafely(
+  run: MarketplaceCommandRunner,
+  args: readonly string[],
+  cwd: string,
+): Promise<MarketplaceCommandResult> {
+  try {
+    return await run(args, cwd);
+  } catch (error) {
+    return {
+      exitCode: 127,
+      stdout: '',
+      stderr: error instanceof Error ? error.message : String(error),
+    };
+  }
+}
+
+/** Restart an active OpenCode v2 managed service without making mutation fail. */
+export async function reloadOpenCodeService(
+  options: ReloadOpenCodeServiceOptions,
+): Promise<MarketplaceReloadResult> {
+  const command = options.command ?? process.env.OPENCODE_BIN ?? 'opencode';
+  const run = options.run ?? defaultCommandRunner(command);
+  const execute = (args: readonly string[]) =>
+    runSafely(run, args, options.cwd);
+  const version = await execute(['--version']);
+  if (version.exitCode !== 0) {
+    return { status: 'unavailable', detail: unavailableDetail(version) };
+  }
+
+  const major = majorVersion(`${version.stdout}\n${version.stderr}`);
+  if (major !== 2) {
+    return {
+      status: 'unsupported',
+      detail:
+        major === undefined
+          ? 'OpenCode version could not be identified; configuration is ready for the next launch.'
+          : `OpenCode v${major} does not support automatic marketplace service reload; configuration is ready for the next launch.`,
+    };
+  }
+
+  const serviceStatus = await execute(['service', 'status']);
+  if (serviceStatus.exitCode !== 0) {
+    return { status: 'unavailable', detail: unavailableDetail(serviceStatus) };
+  }
+  if (serviceStatus.stdout.trim() === 'stopped') {
+    return {
+      status: 'pending',
+      detail:
+        'No active OpenCode service; configuration will apply on the next launch.',
+    };
+  }
+  if (!/^https?:\/\/\S+$/i.test(serviceStatus.stdout.trim())) {
+    return {
+      status: 'unavailable',
+      detail:
+        'OpenCode service status could not be identified; configuration is ready for the next launch.',
+    };
+  }
+
+  const restart = await execute(['service', 'restart']);
+  if (restart.exitCode !== 0) {
+    return { status: 'unavailable', detail: unavailableDetail(restart) };
+  }
+  return {
+    status: 'reloaded',
+    detail:
+      'Active OpenCode v2 service restarted; marketplace configuration is applied.',
+  };
+}

+ 10 - 10
src/marketplace/status.test.ts

@@ -81,12 +81,12 @@ function setup() {
 }
 
 describe('marketplace status', () => {
-  test('CLI without a live registry reports reload status unknown', () => {
+  test('CLI without a live registry reports reload as unavailable', () => {
     const { root, project, service } = setup();
     try {
       service.install(agentBundle());
       const report = collectMarketplaceStatus({ service, projectDir: project });
-      expect(report.reloadRequired).toBe('unknown');
+      expect(report.reloadStatus).toBe('unavailable');
       expect(report.live).toBeUndefined();
     } finally {
       rmSync(root, { recursive: true, force: true });
@@ -180,14 +180,14 @@ describe('marketplace status', () => {
             entry.code === 'operational' && entry.message === 'lease timed out',
         ),
       ).toBe(true);
-      expect(report.reloadRequired).toBe('unknown');
+      expect(report.reloadStatus).toBe('unavailable');
       inspect.mockRestore();
     } finally {
       rmSync(root, { recursive: true, force: true });
     }
   });
 
-  test('operational inspect leaves reload unknown even with live identities', () => {
+  test('operational inspect leaves reload unavailable even with live identities', () => {
     const { root, project, service } = setup();
     try {
       const inspect = spyOn(service.store, 'inspectAll');
@@ -202,7 +202,7 @@ describe('marketplace status', () => {
         live: { packages: [] },
         desiredLive: { packages: [] },
       });
-      expect(report.reloadRequired).toBe('unknown');
+      expect(report.reloadStatus).toBe('unavailable');
       inspect.mockRestore();
     } finally {
       rmSync(root, { recursive: true, force: true });
@@ -259,7 +259,7 @@ describe('marketplace status', () => {
         live: { packages: desiredLive.packages },
         desiredLive,
       });
-      expect(matching.reloadRequired).toBe(false);
+      expect(matching.reloadStatus).toBe('applied');
       expect(
         matching.diagnostics.some(
           (entry) =>
@@ -279,7 +279,7 @@ describe('marketplace status', () => {
         },
         desiredLive,
       });
-      expect(staleName.reloadRequired).toBe(true);
+      expect(staleName.reloadStatus).toBe('pending');
     } finally {
       rmSync(root, { recursive: true, force: true });
     }
@@ -325,7 +325,7 @@ describe('marketplace status', () => {
         live: { packages: [] },
         desiredLive,
       });
-      expect(report.reloadRequired).toBe(false);
+      expect(report.reloadStatus).toBe('applied');
       expect(report.diagnostics.map((entry) => entry.code).sort()).toEqual([
         'invalid-alias',
         'missing-required-dependency',
@@ -335,7 +335,7 @@ describe('marketplace status', () => {
     }
   });
 
-  test('unreadable lockfile is operational and leaves in-session reload unknown', () => {
+  test('unreadable lockfile is operational and leaves in-session reload unavailable', () => {
     const { root, project, service } = setup();
     try {
       service.install(agentBundle());
@@ -358,7 +358,7 @@ describe('marketplace status', () => {
               /EACCES|permission/i.test(entry.message),
           ),
         ).toBe(true);
-        expect(report.reloadRequired).toBe('unknown');
+        expect(report.reloadStatus).toBe('unavailable');
       } finally {
         chmodSync(service.store.paths.lockfilePath, 0o644);
       }

+ 50 - 17
src/marketplace/status.ts

@@ -2,6 +2,10 @@ import { loadPluginConfig } from '../config/loader';
 import type { MarketplaceActivation } from '../config/schema';
 import type { MarketplaceDiagnostic } from './activation';
 import { MarketplaceLockfileError } from './errors';
+import type {
+  MarketplaceReloadResult,
+  MarketplaceReloadStatus,
+} from './reload';
 import type { MarketplaceService } from './service';
 import type {
   MarketplaceStoreInspection,
@@ -11,7 +15,6 @@ import type {
 export const MARKETPLACE_RELOAD_NOTICE =
   'Applies after OpenCode reload or a new session. The live agent registry is not hot-swapped.';
 
-export type MarketplaceReloadRequired = boolean | 'unknown';
 export type MarketplaceDiagnosticProvenance = 'disk' | 'live';
 
 export interface MarketplaceLivePackage {
@@ -49,7 +52,7 @@ export interface MarketplaceStatusReport {
     diagnostics: MarketplaceStatusDiagnostic[];
   };
   diagnostics: MarketplaceStatusDiagnostic[];
-  reloadRequired: MarketplaceReloadRequired;
+  reloadStatus: MarketplaceReloadStatus;
   note: string;
 }
 
@@ -247,13 +250,15 @@ export function collectMarketplaceStatus(
   const operational =
     Boolean(inspection.operationalError) ||
     desiredDiagnostics.some((entry) => entry.code === 'operational');
-  const reloadRequired: MarketplaceReloadRequired =
+  const reloadStatus: MarketplaceReloadStatus =
     operational || !options.live || !options.desiredLive
-      ? 'unknown'
-      : !sameIdentitySet(
-          options.desiredLive.packages.map(identityKey),
-          options.live.packages.map(identityKey),
-        );
+      ? 'unavailable'
+      : sameIdentitySet(
+            options.desiredLive.packages.map(identityKey),
+            options.live.packages.map(identityKey),
+          )
+        ? 'applied'
+        : 'pending';
   return {
     installed,
     configured: {
@@ -269,8 +274,13 @@ export function collectMarketplaceStatus(
         }
       : {}),
     diagnostics: mergeDiagnostics(disk, liveDiagnostics),
-    reloadRequired,
-    note: reloadRequired === false ? '' : MARKETPLACE_RELOAD_NOTICE,
+    reloadStatus,
+    note:
+      reloadStatus === 'pending'
+        ? MARKETPLACE_RELOAD_NOTICE
+        : reloadStatus === 'unavailable'
+          ? 'Live marketplace registry state is unavailable; configuration status could not be compared.'
+          : '',
   };
 }
 
@@ -279,7 +289,7 @@ export function formatMarketplaceStatus(
 ): string {
   const lines = [
     `preset: ${report.configured.preset ?? '(none)'}`,
-    `reload_required: ${report.reloadRequired}`,
+    `reload_status: ${report.reloadStatus}`,
   ];
   if (report.note) lines.push(`note: ${report.note}`);
   lines.push('', 'installed:');
@@ -326,15 +336,38 @@ export function formatMarketplaceStatus(
 
 export function mutationReloadNotice(
   message: string,
-  reloadRequired: MarketplaceReloadRequired,
+  reload: MarketplaceReloadResult,
 ): string {
-  const lines = [message, `reload_required: ${reloadRequired}`];
-  if (reloadRequired !== false) lines.push(MARKETPLACE_RELOAD_NOTICE);
+  const lines = [
+    message,
+    `reload_status: ${reload.status}`,
+    `reload_detail: ${reload.detail}`,
+  ];
+  if (reload.status === 'pending') lines.push(MARKETPLACE_RELOAD_NOTICE);
   return lines.join('\n');
 }
 
-export function reloadRequiredAfterMutation(
+export function reloadStatusAfterMutation(
   options: CollectMarketplaceStatusOptions,
-): MarketplaceReloadRequired {
-  return collectMarketplaceStatus(options).reloadRequired;
+): MarketplaceReloadResult {
+  const report = collectMarketplaceStatus(options);
+  if (report.reloadStatus === 'applied') {
+    return {
+      status: 'applied',
+      detail:
+        'Live marketplace registry already matches the saved configuration.',
+    };
+  }
+  if (report.reloadStatus === 'pending') {
+    return {
+      status: 'pending',
+      detail:
+        'Live marketplace registry is unchanged; the mutation is pending a future reload.',
+    };
+  }
+  return {
+    status: 'unavailable',
+    detail:
+      'Live marketplace registry state is unavailable; the mutation is saved and will apply after a future reload.',
+  };
 }

+ 8 - 8
src/tools/marketplace.test.ts

@@ -142,7 +142,7 @@ describe('marketplace tool', () => {
           ),
         );
         expect(installed).toContain('Imported community/docs-researcher@1.0.0');
-        expect(installed).toContain('reload_required: false');
+        expect(installed).toContain('reload_status: applied');
         expect(installed).not.toContain(MARKETPLACE_RELOAD_NOTICE);
 
         const listed = String(
@@ -179,7 +179,7 @@ describe('marketplace tool', () => {
           ),
         );
         expect(updated).toContain('Updated community/docs-researcher@1.1.0');
-        expect(updated).toContain('reload_required: false');
+        expect(updated).toContain('reload_status: applied');
 
         const enabled = String(
           await marketplace.execute(
@@ -191,14 +191,14 @@ describe('marketplace tool', () => {
           ),
         );
         expect(enabled).toContain('Enabled community/docs-researcher');
-        expect(enabled).toContain('reload_required: true');
+        expect(enabled).toContain('reload_status: pending');
         expect(enabled).toContain(MARKETPLACE_RELOAD_NOTICE);
         expect(live.packages).toEqual([]);
 
         const statusAfterMutations = String(
           await marketplace.execute({ action: 'status' }, context),
         );
-        expect(statusAfterMutations).toContain('reload_required: true');
+        expect(statusAfterMutations).toContain('reload_status: pending');
         expect(statusAfterMutations).toContain(
           'community/docs-researcher@1.1.0',
         );
@@ -235,7 +235,7 @@ describe('marketplace tool', () => {
           ),
         );
         expect(disabled).toContain('Disabled community/docs-researcher');
-        expect(disabled).toContain('reload_required: false');
+        expect(disabled).toContain('reload_status: applied');
 
         expect(live.packages).toEqual([]);
       } finally {
@@ -271,7 +271,7 @@ describe('marketplace tool', () => {
           context,
         ),
       );
-      expect(updated).toContain('reload_required: true');
+      expect(updated).toContain('reload_status: pending');
     } finally {
       rmSync(root, { recursive: true, force: true });
     }
@@ -305,7 +305,7 @@ describe('marketplace tool', () => {
           context,
         ),
       );
-      expect(removed).toContain('reload_required: true');
+      expect(removed).toContain('reload_status: pending');
     } finally {
       rmSync(root, { recursive: true, force: true });
     }
@@ -336,7 +336,7 @@ describe('marketplace tool', () => {
           context,
         ),
       );
-      expect(again).toContain('reload_required: false');
+      expect(again).toContain('reload_status: applied');
     } finally {
       rmSync(root, { recursive: true, force: true });
     }

+ 2 - 2
src/tools/marketplace.ts

@@ -11,7 +11,7 @@ import {
   formatMarketplaceStatus,
   type MarketplaceLiveSnapshot,
   mutationReloadNotice,
-  reloadRequiredAfterMutation,
+  reloadStatusAfterMutation,
 } from '../marketplace/status';
 
 const toolZ = tool.schema;
@@ -109,7 +109,7 @@ function mutationResult(
 ): string {
   return mutationReloadNotice(
     message,
-    reloadRequiredAfterMutation({
+    reloadStatusAfterMutation({
       service: options.service,
       projectDir: options.projectDir,
       live: options.getLiveSnapshot?.(),