@robota-sdk/agent-command 3.0.0-beta.84 → 3.0.0-beta.85
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/CHANGELOG.md +42 -0
- package/dist/node/index.cjs +3 -3
- package/dist/node/index.js +44 -44
- package/dist/node/index.js.map +1 -1
- package/package.json +7 -7
- package/src/doctor/__tests__/doctor-fixture.ts +1 -0
- package/src/doctor/__tests__/doctor-runner.test.ts +26 -6
- package/src/doctor/doctor-extensions-probe.ts +36 -12
- package/src/mcp-activation/__tests__/mcp-activation-command.test.ts +97 -4
- package/src/mcp-activation/mcp-activation-command.ts +28 -6
- package/src/plugin/__tests__/plugin-command-module.test.ts +7 -6
- package/src/plugin/plugin-command.ts +3 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@robota-sdk/agent-command",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.85",
|
|
4
4
|
"description": "Slash-command modules for Robota SDK sessions (/help, /permissions, /mcp, /rewind, …)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/node/index.js",
|
|
@@ -40,12 +40,12 @@
|
|
|
40
40
|
"CHANGELOG.md"
|
|
41
41
|
],
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@robota-sdk/agent-
|
|
44
|
-
"@robota-sdk/agent-framework": "3.0.0-beta.
|
|
45
|
-
"@robota-sdk/agent-
|
|
46
|
-
"@robota-sdk/agent-interface-execution": "3.0.0-beta.
|
|
47
|
-
"@robota-sdk/agent-interface-session": "3.0.0-beta.
|
|
48
|
-
"@robota-sdk/agent-preset": "3.0.0-beta.
|
|
43
|
+
"@robota-sdk/agent-interface-command": "3.0.0-beta.85",
|
|
44
|
+
"@robota-sdk/agent-framework": "3.0.0-beta.85",
|
|
45
|
+
"@robota-sdk/agent-core": "3.0.0-beta.85",
|
|
46
|
+
"@robota-sdk/agent-interface-execution": "3.0.0-beta.85",
|
|
47
|
+
"@robota-sdk/agent-interface-session": "3.0.0-beta.85",
|
|
48
|
+
"@robota-sdk/agent-preset": "3.0.0-beta.85"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@types/node": "^22.19.21",
|
|
@@ -139,6 +139,7 @@ export function installBrokenHome(fixture: IDoctorFixture): void {
|
|
|
139
139
|
JSON.stringify({
|
|
140
140
|
mcpServers: {
|
|
141
141
|
ghost: { command: 'robota-doctor-missing-binary', env: { GHOST_TOKEN: MARKERS.mcp } },
|
|
142
|
+
ready: { type: 'http', url: 'https://mcp.example.test' },
|
|
142
143
|
},
|
|
143
144
|
}),
|
|
144
145
|
);
|
|
@@ -53,13 +53,16 @@ describe('runDoctor (OBSERVABILITY-1991)', () => {
|
|
|
53
53
|
expect(byId(neutral, 'provider.resolution').detail?.join(' ')).not.toMatch(/robota/i);
|
|
54
54
|
expect(byId(neutral, 'workspace.trust').detail?.join(' ')).not.toMatch(/robota/i);
|
|
55
55
|
|
|
56
|
-
const hosted = await runDoctor(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
const hosted = await runDoctor(
|
|
57
|
+
{
|
|
58
|
+
...f.inputs,
|
|
59
|
+
diagnosticGuidance: {
|
|
60
|
+
providerResolution: 'Run atlas configure.',
|
|
61
|
+
projectTrust: 'Run atlas trust.',
|
|
62
|
+
},
|
|
61
63
|
},
|
|
62
|
-
|
|
64
|
+
f.deps,
|
|
65
|
+
);
|
|
63
66
|
expect(byId(hosted, 'provider.resolution').detail).toEqual(['Run atlas configure.']);
|
|
64
67
|
expect(byId(hosted, 'workspace.trust').detail).toEqual(['Run atlas trust.']);
|
|
65
68
|
});
|
|
@@ -143,6 +146,9 @@ describe('runDoctor (OBSERVABILITY-1991)', () => {
|
|
|
143
146
|
const ghost = byId(report, 'mcp.plugin.mcp-plugin@fixture-market.ghost');
|
|
144
147
|
expect(ghost).toMatchObject({ status: 'warn' });
|
|
145
148
|
expect(ghost.cause).toContain('robota-doctor-missing-binary');
|
|
149
|
+
const declaration = byId(report, 'mcp.plugin.mcp-plugin@fixture-market.ready');
|
|
150
|
+
expect(declaration.status).toBe('warn');
|
|
151
|
+
expect(declaration.detail?.join(' ')).toContain('not a runtime source');
|
|
146
152
|
expect(byId(report, 'mcp.activation').status).toBe('not-configured');
|
|
147
153
|
expect(byId(report, 'mcp.connection').status).toBe('not-probed');
|
|
148
154
|
expect(byId(report, 'hooks.execution').status).toBe('not-probed');
|
|
@@ -197,6 +203,20 @@ describe('runDoctor (OBSERVABILITY-1991)', () => {
|
|
|
197
203
|
expect(byId(report, 'settings.user.robota')).toMatchObject({ status: 'fail' });
|
|
198
204
|
});
|
|
199
205
|
|
|
206
|
+
it('an unparseable user settings file is a failed plugin check, not a crash', async () => {
|
|
207
|
+
const f = fixture({ env: {} });
|
|
208
|
+
f.mkdir('.robota');
|
|
209
|
+
f.mkdir('.robota/sessions');
|
|
210
|
+
f.write('.robota/settings.json', '{ "enabledPlugins": { "helper": false }, }');
|
|
211
|
+
|
|
212
|
+
const report = await runDoctor(f.inputs, f.deps);
|
|
213
|
+
|
|
214
|
+
expect(byId(report, 'plugins')).toMatchObject({
|
|
215
|
+
status: 'fail',
|
|
216
|
+
cause: expect.stringContaining('not valid JSON'),
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
200
220
|
it('TC-04: derives the reachability host from profile baseURL, then defaults.baseURL, then endpoint', async () => {
|
|
201
221
|
const profile = fixture({ env: {} });
|
|
202
222
|
profile.write(
|
|
@@ -179,22 +179,29 @@ function hookChecks(
|
|
|
179
179
|
|
|
180
180
|
function mcpServerCheck(server: IBundlePluginMcpServer, deps: IDoctorDeps): IDoctorCheck {
|
|
181
181
|
const id = `mcp.plugin.${server.pluginId}.${server.name}`;
|
|
182
|
+
const detail = [
|
|
183
|
+
'Declaration only: plugin .mcp.json is not a runtime source. Copy a supported definition to mcpServers in settings, then use /mcp approve <name>.',
|
|
184
|
+
];
|
|
182
185
|
if (server.transport === 'stdio' && server.command !== undefined) {
|
|
183
186
|
return deps.resolveCommand(server.command)
|
|
184
187
|
? {
|
|
185
188
|
id,
|
|
186
|
-
label: 'MCP
|
|
187
|
-
status: '
|
|
189
|
+
label: 'MCP declaration (plugin)',
|
|
190
|
+
status: 'warn',
|
|
188
191
|
path: server.mcpPath,
|
|
189
192
|
cause: `stdio command ${server.command} resolves`,
|
|
193
|
+
detail,
|
|
190
194
|
}
|
|
191
195
|
: {
|
|
192
196
|
id,
|
|
193
|
-
label: 'MCP
|
|
197
|
+
label: 'MCP declaration (plugin)',
|
|
194
198
|
status: 'warn',
|
|
195
199
|
path: server.mcpPath,
|
|
196
200
|
cause: `stdio command ${server.command} not found on PATH`,
|
|
197
|
-
detail:
|
|
201
|
+
detail: [
|
|
202
|
+
...detail,
|
|
203
|
+
...(server.envKeys.length === 0 ? [] : [`env keys: ${server.envKeys.join(', ')}`]),
|
|
204
|
+
],
|
|
198
205
|
};
|
|
199
206
|
}
|
|
200
207
|
if (server.transport === 'http' && server.url !== undefined) {
|
|
@@ -202,28 +209,31 @@ function mcpServerCheck(server: IBundlePluginMcpServer, deps: IDoctorDeps): IDoc
|
|
|
202
209
|
new URL(server.url);
|
|
203
210
|
return {
|
|
204
211
|
id,
|
|
205
|
-
label: 'MCP
|
|
206
|
-
status: '
|
|
212
|
+
label: 'MCP declaration (plugin)',
|
|
213
|
+
status: 'warn',
|
|
207
214
|
path: server.mcpPath,
|
|
208
215
|
cause: `url ${server.url}`,
|
|
216
|
+
detail,
|
|
209
217
|
};
|
|
210
218
|
} catch {
|
|
211
219
|
// allow-fallback: an unparseable url is the finding
|
|
212
220
|
return {
|
|
213
221
|
id,
|
|
214
|
-
label: 'MCP
|
|
222
|
+
label: 'MCP declaration (plugin)',
|
|
215
223
|
status: 'warn',
|
|
216
224
|
path: server.mcpPath,
|
|
217
225
|
cause: 'url is not parseable',
|
|
226
|
+
detail,
|
|
218
227
|
};
|
|
219
228
|
}
|
|
220
229
|
}
|
|
221
230
|
return {
|
|
222
231
|
id,
|
|
223
|
-
label: 'MCP
|
|
232
|
+
label: 'MCP declaration (plugin)',
|
|
224
233
|
status: 'warn',
|
|
225
234
|
path: server.mcpPath,
|
|
226
235
|
cause: 'neither command nor url declared',
|
|
236
|
+
detail,
|
|
227
237
|
};
|
|
228
238
|
}
|
|
229
239
|
|
|
@@ -287,11 +297,25 @@ export function probeExtensions(
|
|
|
287
297
|
// Enablement is read the way the session loader reads it — from the user settings file through the
|
|
288
298
|
// plugin settings store — so the doctor and the session agree even where that store's own policy is
|
|
289
299
|
// a separate open item (recorded on #2670).
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
300
|
+
let plugins: IBundlePluginInspection[];
|
|
301
|
+
let settingsProblem: string | undefined;
|
|
302
|
+
try {
|
|
303
|
+
plugins = loadHostBundlePluginInspectionFromScopes(inputs.pluginsDirs, {
|
|
304
|
+
settingsPath: inputs.userSettingsPath,
|
|
305
|
+
});
|
|
306
|
+
} catch (error) {
|
|
307
|
+
// Unreadable plugin settings load no plugin in a session. The doctor says so, and still
|
|
308
|
+
// inspects every installed plugin so its other problems are not hidden behind this one.
|
|
309
|
+
settingsProblem = error instanceof Error ? error.message : String(error);
|
|
310
|
+
plugins = loadHostBundlePluginInspectionFromScopes(inputs.pluginsDirs, { enabledPlugins: {} });
|
|
311
|
+
}
|
|
312
|
+
const pluginSummary = pluginChecks(plugins).map((check) =>
|
|
313
|
+
settingsProblem !== undefined && check.id === 'plugins'
|
|
314
|
+
? { ...check, status: 'fail' as const, cause: `no plugin loads: ${settingsProblem}` }
|
|
315
|
+
: check,
|
|
316
|
+
);
|
|
293
317
|
return [
|
|
294
|
-
...
|
|
318
|
+
...pluginSummary,
|
|
295
319
|
...skillChecks(inputs),
|
|
296
320
|
...hookChecks(settings, plugins, deps),
|
|
297
321
|
...mcpChecks(inputs, plugins, deps),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
2
|
|
|
3
3
|
import { createTestCommandHost } from '@robota-sdk/agent-framework/testing';
|
|
4
4
|
|
|
@@ -659,13 +659,14 @@ describe('/mcp reload (#3282 §4 part b-2)', () => {
|
|
|
659
659
|
function harness(
|
|
660
660
|
reload: ICommandMCPActivationAdapter['reload'],
|
|
661
661
|
taken: (names: string[]) => string[] = (names) => names,
|
|
662
|
+
approve: ICommandMCPActivationAdapter['approve'] = async () => summary(),
|
|
662
663
|
) {
|
|
663
664
|
const added: IToolWithEventService[][] = [];
|
|
664
665
|
const reloadToolsAddedCalls: { token: string; added: readonly string[] }[] = [];
|
|
665
666
|
const port: ICommandMCPActivationAdapter = {
|
|
666
667
|
list: () => [],
|
|
667
|
-
approve
|
|
668
|
-
reject: async () => summary(),
|
|
668
|
+
approve,
|
|
669
|
+
reject: async () => summary({ status: 'rejected', allowed: false }),
|
|
669
670
|
revoke: async () => summary(),
|
|
670
671
|
reload,
|
|
671
672
|
reloadToolsAdded: (token, names) => reloadToolsAddedCalls.push({ token, added: names }),
|
|
@@ -689,7 +690,11 @@ describe('/mcp reload (#3282 §4 part b-2)', () => {
|
|
|
689
690
|
}
|
|
690
691
|
|
|
691
692
|
const forecast = new FunctionTool(
|
|
692
|
-
{
|
|
693
|
+
{
|
|
694
|
+
name: 'weather__forecast',
|
|
695
|
+
description: 'Forecast',
|
|
696
|
+
parameters: { type: 'object', properties: {} },
|
|
697
|
+
},
|
|
693
698
|
async () => 'sunny',
|
|
694
699
|
);
|
|
695
700
|
|
|
@@ -721,6 +726,94 @@ describe('/mcp reload (#3282 §4 part b-2)', () => {
|
|
|
721
726
|
expect(result.message).toContain('No server newly connected.');
|
|
722
727
|
});
|
|
723
728
|
|
|
729
|
+
it('an approval connects the server in the running session', async () => {
|
|
730
|
+
const reload = vi.fn(async () => ({
|
|
731
|
+
tools: [forecast],
|
|
732
|
+
connectedServerIds: ['weather'],
|
|
733
|
+
failedServerIds: [],
|
|
734
|
+
reloadToken: 'token-1',
|
|
735
|
+
}));
|
|
736
|
+
const h = harness(reload, undefined, async () =>
|
|
737
|
+
summary({ serverId: 'weather', status: 'approved', allowed: true, reason: 'Approved.' }),
|
|
738
|
+
);
|
|
739
|
+
|
|
740
|
+
const result = await h.run('approve weather');
|
|
741
|
+
|
|
742
|
+
expect(reload).toHaveBeenCalledOnce();
|
|
743
|
+
expect(result.success).toBe(true);
|
|
744
|
+
expect(result.message).toContain('is now approved');
|
|
745
|
+
expect(result.message).toContain('Connected: weather.');
|
|
746
|
+
expect(h.added).toEqual([[forecast]]);
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
it('acknowledges an empty-tool connection before approving another server', async () => {
|
|
750
|
+
let released = false;
|
|
751
|
+
const h = harness(
|
|
752
|
+
async () =>
|
|
753
|
+
released
|
|
754
|
+
? {
|
|
755
|
+
tools: [forecast],
|
|
756
|
+
connectedServerIds: ['weather'],
|
|
757
|
+
failedServerIds: [],
|
|
758
|
+
reloadToken: 'second',
|
|
759
|
+
}
|
|
760
|
+
: { tools: [], connectedServerIds: ['empty'], failedServerIds: [], reloadToken: 'first' },
|
|
761
|
+
undefined,
|
|
762
|
+
async (id) => summary({ serverId: id, status: 'approved', allowed: true }),
|
|
763
|
+
);
|
|
764
|
+
await h.run('approve empty');
|
|
765
|
+
released = h.reloadToolsAddedCalls.some((call) => call.token === 'first');
|
|
766
|
+
const result = await h.run('approve weather');
|
|
767
|
+
expect(result.message).toContain('Connected: weather.');
|
|
768
|
+
expect(h.reloadToolsAddedCalls).toEqual([
|
|
769
|
+
{ token: 'first', added: [] },
|
|
770
|
+
{ token: 'second', added: ['weather__forecast'] },
|
|
771
|
+
]);
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
it('preserves a saved approval when connecting fails, and suggests reload', async () => {
|
|
775
|
+
const h = harness(
|
|
776
|
+
async () => {
|
|
777
|
+
throw new Error('connection failed');
|
|
778
|
+
},
|
|
779
|
+
undefined,
|
|
780
|
+
async () => summary({ status: 'approved', allowed: true }),
|
|
781
|
+
);
|
|
782
|
+
const result = await h.run('approve weather');
|
|
783
|
+
expect(result.success).toBe(true);
|
|
784
|
+
expect(result.data).toMatchObject({ status: 'approved', allowed: true });
|
|
785
|
+
expect(result.message).toContain('connection failed');
|
|
786
|
+
expect(result.message).toContain('/mcp reload');
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
it('releases a staged reload even when adding tools throws', async () => {
|
|
790
|
+
const h = harness(
|
|
791
|
+
async () => ({
|
|
792
|
+
tools: [forecast],
|
|
793
|
+
connectedServerIds: ['weather'],
|
|
794
|
+
failedServerIds: [],
|
|
795
|
+
reloadToken: 'token-failed',
|
|
796
|
+
}),
|
|
797
|
+
() => {
|
|
798
|
+
throw new Error('cannot add tools');
|
|
799
|
+
},
|
|
800
|
+
async () => summary({ status: 'approved', allowed: true }),
|
|
801
|
+
);
|
|
802
|
+
const result = await h.run('approve weather');
|
|
803
|
+
expect(result.success).toBe(true);
|
|
804
|
+
expect(h.reloadToolsAddedCalls).toEqual([{ token: 'token-failed', added: [] }]);
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
it('a decision that is not an approval connects nothing', async () => {
|
|
808
|
+
const reload = vi.fn(async () => ({ tools: [], connectedServerIds: [], failedServerIds: [] }));
|
|
809
|
+
const h = harness(reload);
|
|
810
|
+
|
|
811
|
+
await h.run('reject weather');
|
|
812
|
+
await h.run('approve weather');
|
|
813
|
+
|
|
814
|
+
expect(reload).not.toHaveBeenCalled();
|
|
815
|
+
});
|
|
816
|
+
|
|
724
817
|
it('is refused when the host offers no reload', async () => {
|
|
725
818
|
const h = harness(undefined);
|
|
726
819
|
const result = await h.run('reload');
|
|
@@ -213,7 +213,7 @@ export async function executeMCPActivationCommand(
|
|
|
213
213
|
|
|
214
214
|
try {
|
|
215
215
|
const result = await mcp[verb](serverId);
|
|
216
|
-
|
|
216
|
+
const decided = {
|
|
217
217
|
message: `MCP server ${serverId} is now ${result.status}. ${result.reason}`,
|
|
218
218
|
success:
|
|
219
219
|
result.status === 'approved' || result.status === 'rejected' || result.status === 'revoked',
|
|
@@ -227,6 +227,24 @@ export async function executeMCPActivationCommand(
|
|
|
227
227
|
securityIdentity: result.securityIdentity,
|
|
228
228
|
},
|
|
229
229
|
};
|
|
230
|
+
// An approval takes effect now: the session connects the server as `/mcp reload` would.
|
|
231
|
+
if (verb !== 'approve' || result.status !== 'approved' || mcp.reload === undefined) {
|
|
232
|
+
return decided;
|
|
233
|
+
}
|
|
234
|
+
try {
|
|
235
|
+
const reloaded = await reloadResult(context);
|
|
236
|
+
return {
|
|
237
|
+
...decided,
|
|
238
|
+
message: `${decided.message} ${reloaded.message}`,
|
|
239
|
+
data: { ...decided.data, reload: reloaded.data ?? {} },
|
|
240
|
+
};
|
|
241
|
+
} catch (error) {
|
|
242
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
243
|
+
return {
|
|
244
|
+
...decided,
|
|
245
|
+
message: `${decided.message} Could not connect now: ${reason}. Run /mcp reload to retry.`,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
230
248
|
} catch (error) {
|
|
231
249
|
return {
|
|
232
250
|
message: error instanceof Error ? error.message : String(error),
|
|
@@ -272,13 +290,17 @@ async function reloadResult(context: TMCPActivationCommandContext): Promise<ICom
|
|
|
272
290
|
};
|
|
273
291
|
}
|
|
274
292
|
if (mcp.reload === undefined) {
|
|
275
|
-
return {
|
|
293
|
+
return {
|
|
294
|
+
message: 'Reloading MCP servers is not available in this environment.',
|
|
295
|
+
success: false,
|
|
296
|
+
};
|
|
276
297
|
}
|
|
277
298
|
const result = await mcp.reload();
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
299
|
+
let added: readonly string[] = [];
|
|
300
|
+
try {
|
|
301
|
+
added = result.tools.length === 0 ? [] : await context.getSession().addTools(result.tools);
|
|
302
|
+
} finally {
|
|
303
|
+
if (result.reloadToken !== undefined) mcp.reloadToolsAdded?.(result.reloadToken, added);
|
|
282
304
|
}
|
|
283
305
|
const dropped = result.tools.length - added.length;
|
|
284
306
|
const parts = [
|
|
@@ -46,10 +46,7 @@ function createCommandSessionRuntime() {
|
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
function createCommandHostContext(
|
|
50
|
-
adapter?: ICommandPluginAdapter,
|
|
51
|
-
locality?: 'local' | 'remote',
|
|
52
|
-
) {
|
|
49
|
+
function createCommandHostContext(adapter?: ICommandPluginAdapter, locality?: 'local' | 'remote') {
|
|
53
50
|
const checkpoint = {
|
|
54
51
|
id: 'checkpoint_1',
|
|
55
52
|
sessionId: 'session_1',
|
|
@@ -111,7 +108,9 @@ describe('createPluginCommandModule', () => {
|
|
|
111
108
|
}),
|
|
112
109
|
expect.objectContaining({
|
|
113
110
|
name: 'reload-plugins',
|
|
114
|
-
description:
|
|
111
|
+
description: expect.stringContaining(
|
|
112
|
+
'refresh the command list, skill routing and model skill catalogue',
|
|
113
|
+
),
|
|
115
114
|
source: 'plugin-manager',
|
|
116
115
|
modelInvocable: false,
|
|
117
116
|
}),
|
|
@@ -278,7 +277,8 @@ describe('executeReloadPluginsCommand', () => {
|
|
|
278
277
|
executeReloadPluginsCommand(createCommandHostContext(adapter), ''),
|
|
279
278
|
).resolves.toEqual({
|
|
280
279
|
success: true,
|
|
281
|
-
message:
|
|
280
|
+
message:
|
|
281
|
+
'Reloaded 3 plugin resources. Plugin commands and skills are active now; hook changes apply to new sessions.',
|
|
282
282
|
data: { pluginRegistryReloaded: true },
|
|
283
283
|
});
|
|
284
284
|
expect(adapter.reloadPlugins).toHaveBeenCalled();
|
|
@@ -294,6 +294,7 @@ describe('executeReloadPluginsCommand', () => {
|
|
|
294
294
|
).resolves.toEqual({
|
|
295
295
|
success: false,
|
|
296
296
|
message: 'Plugin error: manifest failed',
|
|
297
|
+
data: { pluginRegistryReloaded: true },
|
|
297
298
|
});
|
|
298
299
|
});
|
|
299
300
|
});
|
|
@@ -259,11 +259,10 @@ export async function executeReloadPluginsCommand(
|
|
|
259
259
|
result.loadedPluginCount === 1
|
|
260
260
|
? '1 plugin resource'
|
|
261
261
|
: `${result.loadedPluginCount} plugin resources`;
|
|
262
|
-
return `Reloaded ${suffix}.`;
|
|
262
|
+
return `Reloaded ${suffix}. Plugin commands and skills are active now; hook changes apply to new sessions.`;
|
|
263
263
|
}).then((result) => {
|
|
264
|
-
|
|
265
|
-
//
|
|
266
|
-
// the requester-local registry/autocomplete refresh rides the result as a data hint.
|
|
264
|
+
// A failed reload must also refresh the admitted snapshot: malformed settings must not leave
|
|
265
|
+
// previously loaded code executable. Preserve the error while requesting the same refresh.
|
|
267
266
|
return {
|
|
268
267
|
...result,
|
|
269
268
|
data: { ...result.data, pluginRegistryReloaded: true },
|