@pellux/goodvibes-agent 0.1.10 → 0.1.11
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 +27 -0
- package/package.json +1 -1
- package/src/cli/agent-knowledge-command.ts +30 -3
- package/src/cli/help.ts +2 -2
- package/src/input/commands/cloudflare-runtime.ts +20 -5
- package/src/input/commands/confirmation.ts +24 -0
- package/src/input/commands/discovery-runtime.ts +16 -7
- package/src/input/commands/eval.ts +27 -14
- package/src/input/commands/experience-runtime.ts +65 -26
- package/src/input/commands/health-runtime.ts +1 -1
- package/src/input/commands/hooks-runtime.ts +50 -19
- package/src/input/commands/incident-runtime.ts +17 -6
- package/src/input/commands/integration-runtime.ts +93 -50
- package/src/input/commands/knowledge.ts +38 -12
- package/src/input/commands/local-auth-runtime.ts +36 -13
- package/src/input/commands/local-provider-runtime.ts +22 -11
- package/src/input/commands/local-runtime.ts +21 -11
- package/src/input/commands/local-setup.ts +35 -16
- package/src/input/commands/managed-runtime.ts +51 -20
- package/src/input/commands/marketplace-runtime.ts +31 -16
- package/src/input/commands/mcp-runtime.ts +65 -34
- package/src/input/commands/memory-product-runtime.ts +72 -35
- package/src/input/commands/memory.ts +9 -9
- package/src/input/commands/notify-runtime.ts +27 -8
- package/src/input/commands/operator-runtime.ts +85 -17
- package/src/input/commands/planning-runtime.ts +14 -2
- package/src/input/commands/platform-access-runtime.ts +88 -45
- package/src/input/commands/platform-services-runtime.ts +51 -25
- package/src/input/commands/product-runtime.ts +54 -27
- package/src/input/commands/profile-sync-runtime.ts +17 -6
- package/src/input/commands/recall-bundle.ts +38 -17
- package/src/input/commands/recall-query.ts +15 -4
- package/src/input/commands/recall-review.ts +9 -3
- package/src/input/commands/remote-runtime-setup.ts +45 -18
- package/src/input/commands/remote-runtime.ts +25 -9
- package/src/input/commands/replay-runtime.ts +9 -2
- package/src/input/commands/services-runtime.ts +21 -10
- package/src/input/commands/session-content.ts +53 -51
- package/src/input/commands/session-workflow.ts +10 -4
- package/src/input/commands/session.ts +1 -1
- package/src/input/commands/settings-sync-runtime.ts +40 -17
- package/src/input/commands/share-runtime.ts +12 -4
- package/src/input/commands/shell-core.ts +3 -3
- package/src/input/commands/subscription-runtime.ts +35 -20
- package/src/input/commands/teleport-runtime.ts +16 -5
- package/src/input/commands/work-plan-runtime.ts +23 -12
- package/src/input/handler-content-actions.ts +11 -62
- package/src/input/handler-interactions.ts +1 -1
- package/src/input/handler-onboarding-cloudflare.ts +48 -117
- package/src/input/keybindings.ts +1 -1
- package/src/input/mcp-workspace.ts +25 -49
- package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +8 -8
- package/src/input/onboarding/onboarding-wizard-cloudflare.ts +1 -6
- package/src/input/profile-picker-modal.ts +13 -31
- package/src/input/session-picker-modal.ts +4 -30
- package/src/input/settings-modal-subscriptions.ts +3 -3
- package/src/panels/incident-review-panel.ts +1 -1
- package/src/panels/local-auth-panel.ts +4 -4
- package/src/panels/provider-account-snapshot.ts +1 -1
- package/src/panels/provider-health-domains.ts +2 -2
- package/src/panels/settings-sync-panel.ts +2 -2
- package/src/panels/subscription-panel.ts +7 -7
- package/src/renderer/block-actions.ts +1 -1
- package/src/renderer/help-overlay.ts +2 -2
- package/src/renderer/mcp-workspace.ts +12 -12
- package/src/renderer/profile-picker-modal.ts +3 -11
- package/src/renderer/session-picker-modal.ts +2 -10
- package/src/version.ts +1 -1
|
@@ -4,9 +4,6 @@ import {
|
|
|
4
4
|
type CloudflareComponentSelection,
|
|
5
5
|
type CloudflareDaemonClient,
|
|
6
6
|
type CloudflareDiscoverResult,
|
|
7
|
-
type CloudflareOperationalTokenResult,
|
|
8
|
-
type CloudflareProvisionRequest,
|
|
9
|
-
type CloudflareProvisionResult,
|
|
10
7
|
type CloudflareTokenRequirementsResult,
|
|
11
8
|
type CloudflareValidateResult,
|
|
12
9
|
type CloudflareVerifyResult,
|
|
@@ -16,7 +13,6 @@ import type { InputHandler } from './handler.ts';
|
|
|
16
13
|
import type { OnboardingWizardAction, OnboardingWizardApplyFeedback } from './onboarding/onboarding-wizard.ts';
|
|
17
14
|
import {
|
|
18
15
|
buildCloudflareApiTokenRef,
|
|
19
|
-
buildCloudflareProvisionRequest,
|
|
20
16
|
getCloudflareBatchMode,
|
|
21
17
|
getCloudflareComponentSelection,
|
|
22
18
|
getCloudflareSetupSource,
|
|
@@ -113,26 +109,6 @@ function formatCloudflareDiscovery(result: CloudflareDiscoverResult): string[] {
|
|
|
113
109
|
];
|
|
114
110
|
}
|
|
115
111
|
|
|
116
|
-
function formatCloudflareTokenCreate(result: CloudflareOperationalTokenResult): string[] {
|
|
117
|
-
return [
|
|
118
|
-
`token: ${result.tokenName}${result.tokenId ? ` (${result.tokenId})` : ''}`,
|
|
119
|
-
`account: ${result.accountId}`,
|
|
120
|
-
`stored ref: ${result.apiTokenRef ?? 'not stored'}`,
|
|
121
|
-
`permissions: ${result.permissions.length}`,
|
|
122
|
-
'Delete or expire the temporary bootstrap token in Cloudflare after confirming the operational token works.',
|
|
123
|
-
];
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function formatCloudflareProvision(result: CloudflareProvisionResult): string[] {
|
|
127
|
-
return [
|
|
128
|
-
`result: ${result.ok ? 'ok' : 'needs attention'}`,
|
|
129
|
-
...(result.worker ? [`worker: ${result.worker.name}${result.worker.baseUrl ? ` at ${result.worker.baseUrl}` : ''}`] : []),
|
|
130
|
-
...(result.queues ? [`queue: ${result.queues.queueName}; DLQ: ${result.queues.deadLetterQueueName}`] : []),
|
|
131
|
-
...result.steps.map((step) => `${step.status}: ${step.name}${step.message ? ` - ${step.message}` : ''}`),
|
|
132
|
-
...(result.verification ? formatCloudflareVerify(result.verification).map((line) => `verify ${line}`) : []),
|
|
133
|
-
];
|
|
134
|
-
}
|
|
135
|
-
|
|
136
112
|
function formatCloudflareVerify(result: CloudflareVerifyResult): string[] {
|
|
137
113
|
return [
|
|
138
114
|
`worker health: ${result.workerHealth.ok ? 'ok' : 'failed'} (HTTP ${result.workerHealth.status})${result.workerHealth.error ? ` - ${result.workerHealth.error}` : ''}`,
|
|
@@ -171,40 +147,6 @@ function getCloudflareApiTokenRefFromWizard(handler: InputHandler): string {
|
|
|
171
147
|
return wizard.runtimeSnapshot?.config.cloudflare.apiTokenRef ?? '';
|
|
172
148
|
}
|
|
173
149
|
|
|
174
|
-
async function createCloudflareOperationalTokenForHandler(handler: InputHandler): Promise<CloudflareOperationalTokenResult> {
|
|
175
|
-
const wizard = handler.onboardingWizard;
|
|
176
|
-
const bootstrapToken = getCloudflareBootstrapTokenFromWizard(handler);
|
|
177
|
-
if (!bootstrapToken) {
|
|
178
|
-
throw new Error('A bootstrap token is required. Paste it in the wizard or select an environment variable that is set in this TUI process.');
|
|
179
|
-
}
|
|
180
|
-
const accountId = wizard.getStringFieldValue('cloudflare.account-id', wizard.runtimeSnapshot?.config.cloudflare.accountId ?? '');
|
|
181
|
-
const zoneId = wizard.getStringFieldValue('cloudflare.zone-id', wizard.runtimeSnapshot?.config.cloudflare.zoneId ?? '');
|
|
182
|
-
const zoneName = wizard.getStringFieldValue('cloudflare.zone-name', wizard.runtimeSnapshot?.config.cloudflare.zoneName ?? '');
|
|
183
|
-
return await getCloudflareDaemonClientForHandler(handler).createOperationalToken({
|
|
184
|
-
components: getCloudflareComponentSelection(wizard),
|
|
185
|
-
bootstrapToken,
|
|
186
|
-
...(accountId ? { accountId } : {}),
|
|
187
|
-
...(zoneId ? { zoneId } : {}),
|
|
188
|
-
...(zoneName ? { zoneName } : {}),
|
|
189
|
-
storeApiToken: true,
|
|
190
|
-
persistConfig: true,
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
async function buildCloudflareProvisionInputForHandler(handler: InputHandler): Promise<CloudflareProvisionRequest> {
|
|
195
|
-
const input = buildCloudflareProvisionRequest(handler.onboardingWizard, { includeTransientSecrets: true });
|
|
196
|
-
const setupSource = getCloudflareSetupSource(handler.onboardingWizard);
|
|
197
|
-
if (setupSource === 'bootstrap-token' || setupSource === 'bootstrap-env') {
|
|
198
|
-
const tokenResult = await createCloudflareOperationalTokenForHandler(handler);
|
|
199
|
-
if (tokenResult.apiTokenRef) {
|
|
200
|
-
const withoutInlineToken = { ...input };
|
|
201
|
-
delete withoutInlineToken.apiToken;
|
|
202
|
-
return { ...withoutInlineToken, apiTokenRef: tokenResult.apiTokenRef };
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
return input;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
150
|
function buildCloudflareDiscoveryInputForHandler(handler: InputHandler): Parameters<CloudflareDaemonClient['discover']>[0] {
|
|
209
151
|
const wizard = handler.onboardingWizard;
|
|
210
152
|
const accountId = wizard.getStringFieldValue('cloudflare.account-id', wizard.runtimeSnapshot?.config.cloudflare.accountId ?? '');
|
|
@@ -223,6 +165,34 @@ function buildCloudflareDiscoveryInputForHandler(handler: InputHandler): Paramet
|
|
|
223
165
|
};
|
|
224
166
|
}
|
|
225
167
|
|
|
168
|
+
function blockedCloudflareMutationLines(action: CloudflareOnboardingAction): string[] {
|
|
169
|
+
switch (action) {
|
|
170
|
+
case 'cloudflare-create-operational-token':
|
|
171
|
+
return [
|
|
172
|
+
'Creating and storing Cloudflare tokens is a side-effecting operation.',
|
|
173
|
+
'Run /cloudflare create-token [flags] --yes from the main prompt when you explicitly want that mutation.',
|
|
174
|
+
];
|
|
175
|
+
case 'cloudflare-provision':
|
|
176
|
+
return [
|
|
177
|
+
'Provisioning creates or updates Cloudflare resources.',
|
|
178
|
+
'Run /cloudflare provision [flags] --yes from the main prompt when you explicitly want that mutation.',
|
|
179
|
+
];
|
|
180
|
+
case 'cloudflare-disable':
|
|
181
|
+
return [
|
|
182
|
+
'Disabling Cloudflare changes persisted daemon integration config.',
|
|
183
|
+
'Run /cloudflare disable [flags] --yes from the main prompt when you explicitly want that mutation.',
|
|
184
|
+
];
|
|
185
|
+
default:
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function isBlockedCloudflareMutation(action: CloudflareOnboardingAction): boolean {
|
|
191
|
+
return action === 'cloudflare-create-operational-token'
|
|
192
|
+
|| action === 'cloudflare-provision'
|
|
193
|
+
|| action === 'cloudflare-disable';
|
|
194
|
+
}
|
|
195
|
+
|
|
226
196
|
function buildCloudflareValidateInputForHandler(handler: InputHandler): Parameters<CloudflareDaemonClient['validate']>[0] {
|
|
227
197
|
const wizard = handler.onboardingWizard;
|
|
228
198
|
const accountId = wizard.getStringFieldValue('cloudflare.account-id', wizard.runtimeSnapshot?.config.cloudflare.accountId ?? '');
|
|
@@ -244,6 +214,16 @@ export async function handleCloudflareOnboardingActionForHandler(
|
|
|
244
214
|
handler.onboardingWizard.clearApplyFeedback();
|
|
245
215
|
handler.requestRender();
|
|
246
216
|
try {
|
|
217
|
+
if (isBlockedCloudflareMutation(action)) {
|
|
218
|
+
setCloudflareWizardStatusForHandler(
|
|
219
|
+
handler,
|
|
220
|
+
'Cloudflare mutation requires explicit command',
|
|
221
|
+
blockedCloudflareMutationLines(action),
|
|
222
|
+
'warning',
|
|
223
|
+
);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
247
227
|
const client = getCloudflareDaemonClientForHandler(handler);
|
|
248
228
|
if (action === 'cloudflare-token-requirements') {
|
|
249
229
|
const result = await client.tokenRequirements({
|
|
@@ -254,13 +234,6 @@ export async function handleCloudflareOnboardingActionForHandler(
|
|
|
254
234
|
return;
|
|
255
235
|
}
|
|
256
236
|
|
|
257
|
-
if (action === 'cloudflare-create-operational-token') {
|
|
258
|
-
const result = await createCloudflareOperationalTokenForHandler(handler);
|
|
259
|
-
setCloudflareWizardStatusForHandler(handler, 'Cloudflare operational token created', formatCloudflareTokenCreate(result));
|
|
260
|
-
await handler.refreshOnboardingHydration({ preserveValues: true, targetStepId: 'cloudflare' });
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
237
|
if (action === 'cloudflare-discover') {
|
|
265
238
|
const result = await client.discover(buildCloudflareDiscoveryInputForHandler(handler));
|
|
266
239
|
if (result.selectedAccount && !handler.onboardingWizard.getStringFieldValue('cloudflare.account-id', '')) {
|
|
@@ -293,19 +266,6 @@ export async function handleCloudflareOnboardingActionForHandler(
|
|
|
293
266
|
return;
|
|
294
267
|
}
|
|
295
268
|
|
|
296
|
-
if (action === 'cloudflare-provision') {
|
|
297
|
-
const input = await buildCloudflareProvisionInputForHandler(handler);
|
|
298
|
-
const result = await client.provision(input);
|
|
299
|
-
setCloudflareWizardStatusForHandler(
|
|
300
|
-
handler,
|
|
301
|
-
result.ok ? 'Cloudflare provisioning completed' : 'Cloudflare provisioning needs attention',
|
|
302
|
-
formatCloudflareProvision(result),
|
|
303
|
-
result.ok ? 'info' : 'warning',
|
|
304
|
-
);
|
|
305
|
-
await handler.refreshOnboardingHydration({ preserveValues: true, targetStepId: 'cloudflare' });
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
269
|
if (action === 'cloudflare-verify') {
|
|
310
270
|
const result = await client.verify({
|
|
311
271
|
workerBaseUrl: handler.onboardingWizard.getStringFieldValue('cloudflare.worker-base-url', handler.onboardingWizard.runtimeSnapshot?.config.cloudflare.workerBaseUrl ?? ''),
|
|
@@ -319,22 +279,6 @@ export async function handleCloudflareOnboardingActionForHandler(
|
|
|
319
279
|
);
|
|
320
280
|
return;
|
|
321
281
|
}
|
|
322
|
-
|
|
323
|
-
if (action === 'cloudflare-disable') {
|
|
324
|
-
const result = await client.disable({
|
|
325
|
-
accountId: handler.onboardingWizard.getStringFieldValue('cloudflare.account-id', handler.onboardingWizard.runtimeSnapshot?.config.cloudflare.accountId ?? ''),
|
|
326
|
-
apiTokenRef: getCloudflareApiTokenRefFromWizard(handler),
|
|
327
|
-
workerName: handler.onboardingWizard.getStringFieldValue('cloudflare.worker-name', handler.onboardingWizard.runtimeSnapshot?.config.cloudflare.workerName ?? 'goodvibes-batch-worker'),
|
|
328
|
-
persistConfig: true,
|
|
329
|
-
});
|
|
330
|
-
setCloudflareWizardStatusForHandler(
|
|
331
|
-
handler,
|
|
332
|
-
result.ok ? 'Cloudflare integration disabled' : 'Cloudflare disable needs attention',
|
|
333
|
-
result.steps.map((step) => `${step.status}: ${step.name}${step.message ? ` - ${step.message}` : ''}`),
|
|
334
|
-
result.ok ? 'info' : 'warning',
|
|
335
|
-
);
|
|
336
|
-
await handler.refreshOnboardingHydration({ preserveValues: true, targetStepId: 'cloudflare' });
|
|
337
|
-
}
|
|
338
282
|
} catch (error) {
|
|
339
283
|
setCloudflareWizardStatusForHandler(handler, 'Cloudflare action failed', [normalizeCloudflareActionError(error)], 'error');
|
|
340
284
|
} finally {
|
|
@@ -365,27 +309,14 @@ export async function maybeProvisionCloudflareOnFinalApplyForHandler(handler: In
|
|
|
365
309
|
}];
|
|
366
310
|
}
|
|
367
311
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
message: result.ok
|
|
379
|
-
? 'Cloudflare resources were provisioned and verified through the daemon SDK route.'
|
|
380
|
-
: 'Cloudflare provisioning returned warnings or failed verification. Settings were saved; rerun the Cloudflare wizard action after correcting token/resource issues.',
|
|
381
|
-
target: 'cloudflare',
|
|
382
|
-
}];
|
|
383
|
-
} catch (error) {
|
|
384
|
-
return [{
|
|
385
|
-
id: 'cloudflare:provision',
|
|
386
|
-
status: 'warn',
|
|
387
|
-
message: `Cloudflare provisioning did not complete: ${normalizeCloudflareActionError(error)} Settings were saved; retry from the Cloudflare wizard or /cloudflare command.`,
|
|
388
|
-
target: 'cloudflare',
|
|
389
|
-
}];
|
|
390
|
-
}
|
|
312
|
+
handler.onboardingWizard.textState.set('cloudflare.action-status', [
|
|
313
|
+
'Cloudflare provisioning was not run during final apply.',
|
|
314
|
+
'GoodVibes Agent requires an explicit /cloudflare provision [flags] --yes command for Cloudflare resource mutations.',
|
|
315
|
+
].join('\n'));
|
|
316
|
+
return [{
|
|
317
|
+
id: 'cloudflare:provision',
|
|
318
|
+
status: 'warn',
|
|
319
|
+
message: 'Cloudflare settings were saved, but provisioning was blocked because Agent onboarding cannot create/update Cloudflare resources. Run /cloudflare provision [flags] --yes explicitly.',
|
|
320
|
+
target: 'cloudflare',
|
|
321
|
+
}];
|
|
391
322
|
}
|
package/src/input/keybindings.ts
CHANGED
|
@@ -68,7 +68,7 @@ export const ACTION_DESCRIPTIONS: Record<KeyAction, string> = {
|
|
|
68
68
|
'search': 'Toggle conversation search',
|
|
69
69
|
'block-copy': 'Copy nearest block to clipboard',
|
|
70
70
|
'bookmark': 'Bookmark / unbookmark nearest block',
|
|
71
|
-
'block-save': '
|
|
71
|
+
'block-save': 'Block file save blocked; use /share --yes',
|
|
72
72
|
'delete-word': 'Delete word backward',
|
|
73
73
|
'apply-diff-line-start': 'Apply nearest diff / move to line start',
|
|
74
74
|
'next-error-line-end': 'Navigate to next error / move to line end',
|
|
@@ -214,7 +214,7 @@ export class McpWorkspace {
|
|
|
214
214
|
public formIndex = 0;
|
|
215
215
|
public form: McpWorkspaceForm = serverConfigToForm();
|
|
216
216
|
public editingServerName: string | null = null;
|
|
217
|
-
public status = 'Ready.
|
|
217
|
+
public status = 'Ready. Inspect MCP servers and tools. Config writes/reloads require explicit /mcp ... --yes commands.';
|
|
218
218
|
public tools: readonly RegisteredTool[] = [];
|
|
219
219
|
public loadingTools = false;
|
|
220
220
|
public lastError: string | null = null;
|
|
@@ -234,7 +234,7 @@ export class McpWorkspace {
|
|
|
234
234
|
this.formIndex = 0;
|
|
235
235
|
this.lastError = null;
|
|
236
236
|
this.refreshSnapshot();
|
|
237
|
-
void this.refreshTools();
|
|
237
|
+
void this.refreshTools(false);
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
reopen(): void {
|
|
@@ -257,8 +257,8 @@ export class McpWorkspace {
|
|
|
257
257
|
get rows(): readonly McpWorkspaceRow[] {
|
|
258
258
|
return [
|
|
259
259
|
...this.snapshot.servers.map((server): McpWorkspaceRow => ({ type: 'server', server })),
|
|
260
|
-
{ type: 'action', id: 'add', label: 'Add server', detail: `
|
|
261
|
-
{ type: 'action', id: 'reload', label: 'Reload
|
|
260
|
+
{ type: 'action', id: 'add', label: 'Add server preview', detail: `Draft server details here, then run /mcp add ... --scope ${this.form.scope} --yes to write config.` },
|
|
261
|
+
{ type: 'action', id: 'reload', label: 'Reload guidance', detail: 'Runtime reload is blocked from the workspace; run /mcp reload --yes explicitly.' },
|
|
262
262
|
{ type: 'action', id: 'refresh-tools', label: 'Refresh tools', detail: 'Fetch the currently available MCP tool list from connected servers.' },
|
|
263
263
|
{ type: 'action', id: 'config', label: 'Config locations', detail: 'Show SDK-scanned config files and writable project/global paths.' },
|
|
264
264
|
];
|
|
@@ -284,7 +284,7 @@ export class McpWorkspace {
|
|
|
284
284
|
{ id: 'env', label: 'Environment', value: this.form.env, help: 'Comma-separated KEY=VALUE entries. Prefer env var references or secure secrets for sensitive values.', editable: true },
|
|
285
285
|
{ id: 'allowedPaths', label: 'Allowed paths', value: this.form.allowedPaths, help: 'Comma-separated path prefixes for filesystem-oriented servers.', editable: true },
|
|
286
286
|
{ id: 'allowedHosts', label: 'Allowed hosts', value: this.form.allowedHosts, help: 'Comma-separated hostnames for network-oriented servers.', editable: true },
|
|
287
|
-
{ id: 'save', label: '
|
|
287
|
+
{ id: 'save', label: 'Show save command', value: '', help: 'No workspace write. Shows the explicit /mcp add ... --yes command to run from the prompt.', editable: false },
|
|
288
288
|
{ id: 'cancel', label: 'Cancel', value: '', help: 'Return to the MCP server browser without changing config.', editable: false },
|
|
289
289
|
];
|
|
290
290
|
}
|
|
@@ -306,14 +306,14 @@ export class McpWorkspace {
|
|
|
306
306
|
this.selectedIndex = Math.max(0, Math.min(this.selectedIndex, this.rows.length - 1));
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
-
async refreshTools(): Promise<void> {
|
|
309
|
+
async refreshTools(updateStatus = true): Promise<void> {
|
|
310
310
|
if (!this.context) return;
|
|
311
311
|
this.loadingTools = true;
|
|
312
312
|
this.lastError = null;
|
|
313
313
|
try {
|
|
314
314
|
const api = requireMcpApi(this.context);
|
|
315
315
|
this.tools = await api.listAllTools();
|
|
316
|
-
this.status = `Tool list refreshed: ${this.tools.length} tool(s) available.`;
|
|
316
|
+
if (updateStatus) this.status = `Tool list refreshed: ${this.tools.length} tool(s) available.`;
|
|
317
317
|
} catch (error) {
|
|
318
318
|
this.lastError = summarizeError(error);
|
|
319
319
|
this.status = `Tool refresh failed: ${this.lastError}`;
|
|
@@ -326,20 +326,8 @@ export class McpWorkspace {
|
|
|
326
326
|
async reloadRuntime(): Promise<void> {
|
|
327
327
|
if (!this.context) return;
|
|
328
328
|
this.lastError = null;
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const roots = requireShellPaths(this.context);
|
|
332
|
-
const result = await api.reload(roots);
|
|
333
|
-
this.refreshSnapshot();
|
|
334
|
-
const connected = this.snapshot.servers.filter((server) => server.connected).length;
|
|
335
|
-
this.status = `Reloaded MCP runtime: ${connected}/${this.snapshot.servers.length} server(s) connected. Result: +${result.added} ~${result.changed} -${result.removed}, unchanged ${result.unchanged}.`;
|
|
336
|
-
void this.refreshTools();
|
|
337
|
-
} catch (error) {
|
|
338
|
-
this.lastError = summarizeError(error);
|
|
339
|
-
this.status = `Reload failed: ${this.lastError}`;
|
|
340
|
-
} finally {
|
|
341
|
-
this.context?.renderRequest();
|
|
342
|
-
}
|
|
329
|
+
this.status = 'MCP runtime reload is blocked in the workspace. Run /mcp reload --yes from the prompt to explicitly reload.';
|
|
330
|
+
this.context.renderRequest();
|
|
343
331
|
}
|
|
344
332
|
|
|
345
333
|
openAddForm(): void {
|
|
@@ -347,7 +335,7 @@ export class McpWorkspace {
|
|
|
347
335
|
this.formIndex = 0;
|
|
348
336
|
this.editingServerName = null;
|
|
349
337
|
this.form = serverConfigToForm();
|
|
350
|
-
this.status = '
|
|
338
|
+
this.status = 'Draft an MCP server. Saving from this workspace is blocked; use the shown /mcp add ... --yes command.';
|
|
351
339
|
}
|
|
352
340
|
|
|
353
341
|
openEditForm(serverName: string): void {
|
|
@@ -357,8 +345,8 @@ export class McpWorkspace {
|
|
|
357
345
|
this.editingServerName = serverName;
|
|
358
346
|
this.form = { ...serverConfigToForm(entry?.server), scope: entry?.source.scope === 'global' ? 'global' : 'project' };
|
|
359
347
|
this.status = entry
|
|
360
|
-
? `
|
|
361
|
-
: `
|
|
348
|
+
? `Viewing ${serverName}. Workspace saves are blocked; copy the generated /mcp add ... --yes command if you intend to change it.`
|
|
349
|
+
: `Viewing ${serverName}. Runtime status exists, but no launch config was found.`;
|
|
362
350
|
}
|
|
363
351
|
|
|
364
352
|
requestDelete(serverName: string): void {
|
|
@@ -366,24 +354,23 @@ export class McpWorkspace {
|
|
|
366
354
|
this.editingServerName = serverName;
|
|
367
355
|
const entry = this.snapshot.effectiveConfig.servers.find((configEntry) => configEntry.server.name === serverName);
|
|
368
356
|
const scope = entry?.source.scope === 'global' ? 'global' : 'project';
|
|
369
|
-
this.status = `
|
|
357
|
+
this.status = `MCP server removal is blocked in the workspace. Run /mcp remove ${serverName} --scope ${scope} --yes from the prompt.`;
|
|
370
358
|
}
|
|
371
359
|
|
|
372
360
|
async saveForm(): Promise<void> {
|
|
373
361
|
if (!this.context) return;
|
|
374
362
|
try {
|
|
375
363
|
const server = formToServerConfig(this.form);
|
|
376
|
-
this.status = `Saving ${server.name} to ${this.form.scope} MCP config and reloading runtime...`;
|
|
377
364
|
this.mode = 'browse';
|
|
378
365
|
this.editingServerName = null;
|
|
379
|
-
const
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
this.status = `
|
|
383
|
-
|
|
366
|
+
const args = server.args?.length ? ` ${server.args.join(' ')}` : '';
|
|
367
|
+
const role = server.role ? ` --role ${server.role}` : '';
|
|
368
|
+
const trust = server.trustMode ? ` --trust ${server.trustMode}` : '';
|
|
369
|
+
this.status = `MCP config write blocked here. Run: /mcp add ${server.name} ${server.command}${args} --scope ${this.form.scope}${role}${trust} --yes`;
|
|
370
|
+
this.context.renderRequest();
|
|
384
371
|
} catch (error) {
|
|
385
372
|
this.lastError = summarizeError(error);
|
|
386
|
-
this.status = `Save failed: ${this.lastError}`;
|
|
373
|
+
this.status = `Save command preview failed: ${this.lastError}`;
|
|
387
374
|
this.context.renderRequest();
|
|
388
375
|
}
|
|
389
376
|
}
|
|
@@ -391,23 +378,12 @@ export class McpWorkspace {
|
|
|
391
378
|
async confirmDelete(): Promise<void> {
|
|
392
379
|
if (!this.context || !this.editingServerName) return;
|
|
393
380
|
const name = this.editingServerName;
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
this.editingServerName = null;
|
|
401
|
-
this.refreshSnapshot();
|
|
402
|
-
this.status = result.removed
|
|
403
|
-
? `Removed ${scope} server "${name}" from ${result.path}. Reload result: +${result.reload.added} ~${result.reload.changed} -${result.reload.removed}.`
|
|
404
|
-
: `No ${scope} MCP server named "${name}" exists in ${result.path}.`;
|
|
405
|
-
void this.refreshTools();
|
|
406
|
-
} catch (error) {
|
|
407
|
-
this.lastError = summarizeError(error);
|
|
408
|
-
this.status = `Remove failed: ${this.lastError}`;
|
|
409
|
-
this.context.renderRequest();
|
|
410
|
-
}
|
|
381
|
+
const server = this.snapshot.effectiveConfig.servers.find((entry) => entry.server.name === name);
|
|
382
|
+
const scope = server?.source.scope === 'global' ? 'global' : 'project';
|
|
383
|
+
this.mode = 'browse';
|
|
384
|
+
this.editingServerName = null;
|
|
385
|
+
this.status = `MCP removal blocked here. Run: /mcp remove ${name} --scope ${scope} --yes`;
|
|
386
|
+
this.context.renderRequest();
|
|
411
387
|
}
|
|
412
388
|
|
|
413
389
|
cancelForm(): void {
|
|
@@ -380,8 +380,8 @@ export function buildCloudflareStep(controller: OnboardingWizardController): Onb
|
|
|
380
380
|
{
|
|
381
381
|
kind: 'radio',
|
|
382
382
|
id: 'cloudflare.provision-on-apply',
|
|
383
|
-
label: '
|
|
384
|
-
hint: '
|
|
383
|
+
label: 'Final apply Cloudflare provisioning',
|
|
384
|
+
hint: 'Agent onboarding saves config only. Run /cloudflare provision [flags] --yes explicitly for resource changes.',
|
|
385
385
|
options: CLOUDFLARE_PROVISION_OPTIONS,
|
|
386
386
|
defaultValue: 'no',
|
|
387
387
|
},
|
|
@@ -404,8 +404,8 @@ export function buildCloudflareStep(controller: OnboardingWizardController): Onb
|
|
|
404
404
|
kind: 'action',
|
|
405
405
|
id: 'cloudflare.create-token',
|
|
406
406
|
action: 'cloudflare-create-operational-token',
|
|
407
|
-
label: '
|
|
408
|
-
hint: '
|
|
407
|
+
label: 'Show create-token command',
|
|
408
|
+
hint: 'Token creation is side-effecting. The wizard shows the explicit /cloudflare create-token ... --yes path instead of running it.',
|
|
409
409
|
defaultValue: 'Action',
|
|
410
410
|
},
|
|
411
411
|
{
|
|
@@ -428,8 +428,8 @@ export function buildCloudflareStep(controller: OnboardingWizardController): Onb
|
|
|
428
428
|
kind: 'action',
|
|
429
429
|
id: 'cloudflare.provision',
|
|
430
430
|
action: 'cloudflare-provision',
|
|
431
|
-
label: '
|
|
432
|
-
hint: '
|
|
431
|
+
label: 'Show provision command',
|
|
432
|
+
hint: 'Provisioning is side-effecting. The wizard shows the explicit /cloudflare provision ... --yes path instead of running it.',
|
|
433
433
|
defaultValue: 'Action',
|
|
434
434
|
},
|
|
435
435
|
{
|
|
@@ -444,8 +444,8 @@ export function buildCloudflareStep(controller: OnboardingWizardController): Onb
|
|
|
444
444
|
kind: 'action',
|
|
445
445
|
id: 'cloudflare.disable',
|
|
446
446
|
action: 'cloudflare-disable',
|
|
447
|
-
label: '
|
|
448
|
-
hint: '
|
|
447
|
+
label: 'Show disable command',
|
|
448
|
+
hint: 'Disabling persists config changes. The wizard shows the explicit /cloudflare disable ... --yes path instead of running it.',
|
|
449
449
|
defaultValue: 'Action',
|
|
450
450
|
},
|
|
451
451
|
);
|
|
@@ -66,12 +66,7 @@ export const CLOUDFLARE_PROVISION_OPTIONS: readonly OnboardingWizardRadioOption[
|
|
|
66
66
|
{
|
|
67
67
|
id: 'no',
|
|
68
68
|
label: 'No, save configuration only',
|
|
69
|
-
hint: 'Final Apply saves the settings.
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
id: 'yes',
|
|
73
|
-
label: 'Yes, create or update Cloudflare resources',
|
|
74
|
-
hint: 'Final Apply asks the daemon SDK route to create/update selected Cloudflare resources and verify the Worker when possible.',
|
|
69
|
+
hint: 'Final Apply saves the settings. Run /cloudflare provision [flags] --yes explicitly when you want resource changes.',
|
|
75
70
|
},
|
|
76
71
|
];
|
|
77
72
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* ProfilePickerModal — state management for the /profiles picker modal.
|
|
3
3
|
*
|
|
4
4
|
* Lists profiles from ProfileManager.list(), tracks selected index,
|
|
5
|
-
* and handles load
|
|
5
|
+
* and handles load actions.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { ProfileInfo, ProfileData, ProfileManager } from '@pellux/goodvibes-sdk/platform/profiles';
|
|
@@ -141,39 +141,12 @@ export class ProfilePickerModal {
|
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
/**
|
|
145
|
-
* Delete the selected profile from disk.
|
|
146
|
-
* Refreshes the list after deletion.
|
|
147
|
-
*/
|
|
148
144
|
deleteSelected(): boolean {
|
|
149
145
|
const profile = this.getSelected();
|
|
150
146
|
if (!profile) return false;
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
try {
|
|
158
|
-
const deleted = this.profileManager.delete(profile.name);
|
|
159
|
-
if (!deleted) {
|
|
160
|
-
this.statusMessage = `Profile not found: ${profile.name}`;
|
|
161
|
-
this.deleteConfirmationTarget = null;
|
|
162
|
-
return false;
|
|
163
|
-
}
|
|
164
|
-
this.profiles = this.profileManager.list();
|
|
165
|
-
if (this.selectedIndex >= this.profiles.length) {
|
|
166
|
-
this.selectedIndex = Math.max(0, this.profiles.length - 1);
|
|
167
|
-
}
|
|
168
|
-
this._clampScroll();
|
|
169
|
-
this.deleteConfirmationTarget = null;
|
|
170
|
-
this.statusMessage = `Deleted: ${profile.name}`;
|
|
171
|
-
return true;
|
|
172
|
-
} catch (e) {
|
|
173
|
-
this.deleteConfirmationTarget = null;
|
|
174
|
-
this.statusMessage = `Error: ${summarizeError(e)}`;
|
|
175
|
-
return false;
|
|
176
|
-
}
|
|
147
|
+
this.deleteConfirmationTarget = null;
|
|
148
|
+
this.statusMessage = `Deletion requires an explicit command: /profiles delete ${profile.name} --yes`;
|
|
149
|
+
return false;
|
|
177
150
|
}
|
|
178
151
|
|
|
179
152
|
/**
|
|
@@ -184,7 +157,16 @@ export class ProfilePickerModal {
|
|
|
184
157
|
this.statusMessage = 'Profile name cannot be empty';
|
|
185
158
|
return false;
|
|
186
159
|
}
|
|
160
|
+
void configManager;
|
|
161
|
+
this.statusMessage = `Saving requires an explicit command: /profiles save ${name} --yes`;
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
187
164
|
|
|
165
|
+
public saveCurrentAsConfirmed(name: string, configManager: ConfigManager): boolean {
|
|
166
|
+
if (!name || !name.trim()) {
|
|
167
|
+
this.statusMessage = 'Profile name cannot be empty';
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
188
170
|
try {
|
|
189
171
|
const all = configManager.getAll();
|
|
190
172
|
const data: ProfileData = {
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
* SessionPickerModal — state management for the /sessions picker modal.
|
|
3
3
|
*
|
|
4
4
|
* Lists sessions from SessionManager.list(), tracks selected index,
|
|
5
|
-
* and handles load
|
|
5
|
+
* and handles load actions.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { unlinkSync } from 'node:fs';
|
|
9
8
|
import type { SessionInfo, SessionManager } from '@pellux/goodvibes-sdk/platform/sessions';
|
|
10
9
|
import type { ConversationManager } from '../core/conversation';
|
|
11
10
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
@@ -90,37 +89,12 @@ export class SessionPickerModal {
|
|
|
90
89
|
}
|
|
91
90
|
}
|
|
92
91
|
|
|
93
|
-
/**
|
|
94
|
-
* Delete the currently selected session from disk.
|
|
95
|
-
* Refreshes the list after deletion.
|
|
96
|
-
*/
|
|
97
92
|
deleteSelected(): boolean {
|
|
98
93
|
const session = this.getSelected();
|
|
99
94
|
if (!session) return false;
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
try {
|
|
107
|
-
// Delete directly via filePath so it works with any session directory
|
|
108
|
-
unlinkSync(session.filePath);
|
|
109
|
-
// Reload list from the global session manager (removes the deleted entry)
|
|
110
|
-
this.sessions = this.sessionManager.list();
|
|
111
|
-
// Adjust selection
|
|
112
|
-
if (this.selectedIndex >= this.sessions.length) {
|
|
113
|
-
this.selectedIndex = Math.max(0, this.sessions.length - 1);
|
|
114
|
-
}
|
|
115
|
-
this._clampScroll();
|
|
116
|
-
this.deleteConfirmationTarget = null;
|
|
117
|
-
this.statusMessage = `Deleted: ${session.name}`;
|
|
118
|
-
return true;
|
|
119
|
-
} catch (e) {
|
|
120
|
-
this.deleteConfirmationTarget = null;
|
|
121
|
-
this.statusMessage = `Error: ${summarizeError(e)}`;
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
95
|
+
this.deleteConfirmationTarget = null;
|
|
96
|
+
this.statusMessage = `Deletion requires an explicit command: /session delete ${session.name} --yes`;
|
|
97
|
+
return false;
|
|
124
98
|
}
|
|
125
99
|
|
|
126
100
|
private _clampScroll(): void {
|
|
@@ -23,7 +23,7 @@ export function buildSubscriptionEntries(
|
|
|
23
23
|
activeRoute: 'unconfigured',
|
|
24
24
|
authFreshness: 'unconfigured',
|
|
25
25
|
routeReason: 'Built-in subscription adapter is available, but no active subscription session is stored yet.',
|
|
26
|
-
nextActions: [`Use /subscription login ${provider} start to begin browser sign-in.`],
|
|
26
|
+
nextActions: [`Use /subscription login ${provider} start --yes to begin browser sign-in.`],
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -38,7 +38,7 @@ export function buildSubscriptionEntries(
|
|
|
38
38
|
activeRoute: providers.get(provider)?.activeRoute ?? 'unconfigured',
|
|
39
39
|
authFreshness: providers.get(provider)?.authFreshness ?? 'unconfigured',
|
|
40
40
|
routeReason: providers.get(provider)?.routeReason ?? 'OAuth metadata is configured for this provider.',
|
|
41
|
-
nextActions: providers.get(provider)?.nextActions ?? [`Use /subscription login ${provider} start to begin browser sign-in.`],
|
|
41
|
+
nextActions: providers.get(provider)?.nextActions ?? [`Use /subscription login ${provider} start --yes to begin browser sign-in.`],
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -51,7 +51,7 @@ export function buildSubscriptionEntries(
|
|
|
51
51
|
activeRoute: 'unconfigured',
|
|
52
52
|
authFreshness: 'pending',
|
|
53
53
|
routeReason: 'OAuth login is pending completion for this provider.',
|
|
54
|
-
nextActions: [`Finish /subscription login ${pending.provider} finish <code> to activate this session.`],
|
|
54
|
+
nextActions: [`Finish /subscription login ${pending.provider} finish <code> --yes to activate this session.`],
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
57
|
|
|
@@ -185,7 +185,7 @@ export class IncidentReviewPanel extends ScrollableListPanel<FailureReport> {
|
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
footerLines.push(buildPanelLine(width, [[' Action Rail', C.label]]));
|
|
188
|
-
footerLines.push(buildPanelLine(width, [[` /incident latest /incident export ${selected.id} /
|
|
188
|
+
footerLines.push(buildPanelLine(width, [[` /incident latest /incident export ${selected.id} <path> --yes /incident capture ${selected.id} --yes`, C.info]]));
|
|
189
189
|
footerLines.push(buildGuidanceLine(width, '/security', 'open the broader trust and incident posture control room', C));
|
|
190
190
|
|
|
191
191
|
return this.renderList(width, height, {
|
|
@@ -81,7 +81,7 @@ export class LocalAuthPanel extends ScrollableListPanel<LocalAuthUser> {
|
|
|
81
81
|
...(issueMessages.length > 0
|
|
82
82
|
? issueMessages.map((issue) => buildPanelLine(width, [[` issue: ${issue}`.slice(0, Math.max(0, width)), C.warn]]))
|
|
83
83
|
: [buildPanelLine(width, [[' local auth posture looks healthy.', C.good]])]),
|
|
84
|
-
buildGuidanceLine(width, '/auth local rotate-password <user> <password>', 'rotate bootstrap/default credentials and revoke older sessions as needed', C),
|
|
84
|
+
buildGuidanceLine(width, '/auth local rotate-password <user> <password> --yes', 'rotate bootstrap/default credentials and revoke older sessions as needed', C),
|
|
85
85
|
], C),
|
|
86
86
|
];
|
|
87
87
|
|
|
@@ -104,8 +104,8 @@ export class LocalAuthPanel extends ScrollableListPanel<LocalAuthUser> {
|
|
|
104
104
|
footerLines.push(
|
|
105
105
|
...buildDetailBlock(width, 'Selected user', [
|
|
106
106
|
buildPanelLine(width, [[' username ', C.label], [selected.username, C.value], [' roles ', C.label], [formatRoles(selected.roles).slice(0, Math.max(0, width - 23)), C.info]]),
|
|
107
|
-
buildPanelLine(width, [[` next: /auth local rotate-password ${selected.username} <password
|
|
108
|
-
buildPanelLine(width, [[` next: /auth local delete-user ${selected.username}`.slice(0, Math.max(0, width)), C.dim]]),
|
|
107
|
+
buildPanelLine(width, [[` next: /auth local rotate-password ${selected.username} <password> --yes`.slice(0, Math.max(0, width)), C.dim]]),
|
|
108
|
+
buildPanelLine(width, [[` next: /auth local delete-user ${selected.username} --yes`.slice(0, Math.max(0, width)), C.dim]]),
|
|
109
109
|
], C),
|
|
110
110
|
);
|
|
111
111
|
}
|
|
@@ -119,7 +119,7 @@ export class LocalAuthPanel extends ScrollableListPanel<LocalAuthUser> {
|
|
|
119
119
|
])),
|
|
120
120
|
);
|
|
121
121
|
}
|
|
122
|
-
footerLines.push(buildPanelLine(width, [[' /auth local review
|
|
122
|
+
footerLines.push(buildPanelLine(width, [[' /auth local review mutations require --yes: add-user rotate-password revoke-session ', C.dim]]));
|
|
123
123
|
|
|
124
124
|
return this.renderList(width, height, {
|
|
125
125
|
title: 'Local Auth Control Room',
|