@pellux/goodvibes-agent 0.1.117 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -4
- package/README.md +35 -14
- package/bin/goodvibes-agent.ts +16 -2
- package/dist/package/main.js +176073 -170980
- package/docs/README.md +11 -5
- package/docs/channels-remote-and-api.md +50 -0
- package/docs/connected-host.md +3 -3
- package/docs/getting-started.md +29 -15
- package/docs/knowledge-artifacts-and-multimodal.md +91 -0
- package/docs/project-planning.md +79 -0
- package/docs/providers-and-routing.md +46 -0
- package/docs/release-and-publishing.md +44 -9
- package/docs/tools-and-commands.md +123 -0
- package/docs/voice-and-live-tts.md +51 -0
- package/package.json +2 -5
- package/src/agent/channel-delivery.ts +201 -0
- package/src/agent/media-generation.ts +159 -0
- package/src/agent/memory-prompt.ts +0 -1
- package/src/agent/note-registry.ts +329 -0
- package/src/agent/operator-actions.ts +343 -0
- package/src/agent/persona-registry.ts +15 -14
- package/src/agent/record-labels.ts +107 -0
- package/src/agent/reminder-schedule-format.ts +33 -24
- package/src/agent/reminder-schedule.ts +26 -25
- package/src/agent/routine-registry.ts +13 -12
- package/src/agent/routine-schedule-args.ts +2 -1
- package/src/agent/routine-schedule-format.ts +77 -53
- package/src/agent/routine-schedule-promotion.ts +34 -32
- package/src/agent/routine-schedule-receipts.ts +28 -26
- package/src/agent/runtime-profile-starters.ts +2 -2
- package/src/agent/runtime-profile.ts +18 -17
- package/src/agent/skill-registry.ts +25 -24
- package/src/cli/agent-knowledge-args.ts +5 -1
- package/src/cli/agent-knowledge-command.ts +39 -33
- package/src/cli/agent-knowledge-format.ts +80 -67
- package/src/cli/agent-knowledge-methods.ts +1 -1
- package/src/cli/agent-knowledge-runtime.ts +32 -26
- package/src/cli/bundle-command.ts +13 -8
- package/src/cli/config-overrides.ts +37 -36
- package/src/cli/external-runtime.ts +10 -4
- package/src/cli/help.ts +29 -11
- package/src/cli/local-library-command.ts +134 -68
- package/src/cli/management-commands.ts +98 -62
- package/src/cli/management.ts +52 -26
- package/src/cli/memory-command.ts +66 -32
- package/src/cli/parser.ts +37 -24
- package/src/cli/profiles-command.ts +52 -35
- package/src/cli/provider-auth-routes.ts +2 -1
- package/src/cli/routines-command.ts +44 -36
- package/src/cli/service-posture.ts +6 -6
- package/src/cli/status.ts +46 -121
- package/src/core/conversation-message-snapshot.ts +131 -0
- package/src/input/agent-workspace-activation.ts +33 -7
- package/src/input/agent-workspace-basic-command-editor-submission.ts +7 -3
- package/src/input/agent-workspace-basic-command-editors.ts +30 -10
- package/src/input/agent-workspace-categories.ts +276 -64
- package/src/input/agent-workspace-channel-command-editor-submission.ts +34 -0
- package/src/input/agent-workspace-channel-command-editors.ts +23 -5
- package/src/input/agent-workspace-channels.ts +35 -2
- package/src/input/agent-workspace-command-editor.ts +18 -2
- package/src/input/agent-workspace-config-reader.ts +16 -0
- package/src/input/agent-workspace-delegation-editor-submission.ts +1 -1
- package/src/input/agent-workspace-editors.ts +140 -2
- package/src/input/agent-workspace-knowledge-query-editor.ts +1 -1
- package/src/input/agent-workspace-learned-behavior.ts +2 -2
- package/src/input/agent-workspace-library-command-editor-submission.ts +1 -1
- package/src/input/agent-workspace-library-command-editors.ts +2 -2
- package/src/input/agent-workspace-local-operations.ts +218 -0
- package/src/input/agent-workspace-local-selection.ts +75 -0
- package/src/input/agent-workspace-media-command-editor-submission.ts +62 -0
- package/src/input/agent-workspace-media-command-editors.ts +27 -0
- package/src/input/agent-workspace-memory-command-editors.ts +1 -1
- package/src/input/agent-workspace-memory-editor.ts +2 -2
- package/src/input/agent-workspace-navigation.ts +38 -2
- package/src/input/agent-workspace-notify-editor-submission.ts +16 -2
- package/src/input/agent-workspace-operations-command-editor-submission.ts +63 -1
- package/src/input/agent-workspace-operations-command-editors.ts +80 -3
- package/src/input/agent-workspace-panel-route.ts +43 -0
- package/src/input/agent-workspace-reminder-schedule-editor.ts +1 -1
- package/src/input/agent-workspace-routine-schedule-editor.ts +2 -2
- package/src/input/agent-workspace-search.ts +169 -0
- package/src/input/agent-workspace-setup.ts +22 -11
- package/src/input/agent-workspace-skill-bundle-command-editor-submission.ts +6 -6
- package/src/input/agent-workspace-skill-bundle-command-editors.ts +10 -10
- package/src/input/agent-workspace-snapshot.ts +112 -13
- package/src/input/agent-workspace-task-command-editors.ts +4 -4
- package/src/input/agent-workspace-token.ts +18 -2
- package/src/input/agent-workspace-types.ts +92 -4
- package/src/input/agent-workspace-voice-media.ts +3 -6
- package/src/input/agent-workspace-web-research-editor.ts +104 -0
- package/src/input/agent-workspace.ts +136 -208
- package/src/input/command-registry.ts +4 -1
- package/src/input/commands/agent-runtime-profile-runtime.ts +45 -41
- package/src/input/commands/agent-skills-runtime.ts +83 -70
- package/src/input/commands/agent-workspace-runtime.ts +23 -7
- package/src/input/commands/brief-runtime.ts +25 -24
- package/src/input/commands/channels-runtime.ts +145 -31
- package/src/input/commands/delegation-runtime.ts +29 -23
- package/src/input/commands/experience-runtime.ts +6 -4
- package/src/input/commands/guidance-runtime.ts +4 -4
- package/src/input/commands/health-runtime.ts +140 -115
- package/src/input/commands/knowledge.ts +57 -56
- package/src/input/commands/local-provider-runtime.ts +36 -18
- package/src/input/commands/local-runtime.ts +138 -16
- package/src/input/commands/local-setup-review.ts +7 -7
- package/src/input/commands/mcp-runtime.ts +56 -35
- package/src/input/commands/notify-runtime.ts +38 -9
- package/src/input/commands/operator-actions-runtime.ts +138 -0
- package/src/input/commands/operator-runtime.ts +6 -17
- package/src/input/commands/personas-runtime.ts +45 -30
- package/src/input/commands/planning-runtime.ts +1 -1
- package/src/input/commands/platform-access-runtime.ts +29 -29
- package/src/input/commands/provider-accounts-runtime.ts +60 -39
- package/src/input/commands/qrcode-runtime.ts +45 -6
- package/src/input/commands/recall-bundle.ts +11 -11
- package/src/input/commands/recall-capture.ts +13 -11
- package/src/input/commands/recall-query.ts +29 -21
- package/src/input/commands/recall-review.ts +2 -1
- package/src/input/commands/routines-runtime.ts +59 -43
- package/src/input/commands/schedule-runtime.ts +33 -20
- package/src/input/commands/security-runtime.ts +4 -4
- package/src/input/commands/session-content.ts +80 -78
- package/src/input/commands/session-workflow.ts +132 -93
- package/src/input/commands/session.ts +3 -174
- package/src/input/commands/shell-core.ts +32 -17
- package/src/input/commands/subscription-runtime.ts +53 -179
- package/src/input/commands/tasks-runtime.ts +20 -20
- package/src/input/commands/work-plan-runtime.ts +33 -8
- package/src/input/commands.ts +2 -2
- package/src/input/feed-context-factory.ts +2 -1
- package/src/input/file-picker.ts +3 -2
- package/src/input/handler-command-route.ts +4 -7
- package/src/input/handler-content-actions.ts +89 -1
- package/src/input/handler-feed-routes.ts +19 -12
- package/src/input/handler-feed.ts +6 -3
- package/src/input/handler-interactions.ts +7 -5
- package/src/input/handler-modal-stack.ts +3 -3
- package/src/input/handler-onboarding.ts +24 -80
- package/src/input/handler-shortcuts.ts +15 -4
- package/src/input/handler.ts +47 -17
- package/src/input/input-history.ts +5 -6
- package/src/input/keybindings.ts +22 -11
- package/src/input/onboarding/onboarding-wizard-apply.ts +13 -0
- package/src/input/onboarding/onboarding-wizard-constants.ts +1 -0
- package/src/input/onboarding/onboarding-wizard-operator-steps.ts +147 -57
- package/src/input/onboarding/onboarding-wizard-steps.ts +46 -29
- package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
- package/src/input/onboarding/onboarding-wizard.ts +68 -0
- package/src/input/profile-picker-modal.ts +31 -12
- package/src/input/session-picker-modal.ts +21 -4
- package/src/input/settings-modal-behavior.ts +0 -3
- package/src/input/settings-modal-subscriptions.ts +3 -3
- package/src/input/settings-modal-types.ts +2 -13
- package/src/input/settings-modal.ts +6 -52
- package/src/input/submission-intent.ts +0 -1
- package/src/input/submission-router.ts +3 -3
- package/src/main.ts +18 -8
- package/src/panels/approval-panel.ts +8 -8
- package/src/panels/automation-control-panel.ts +2 -2
- package/src/panels/base-panel.ts +1 -1
- package/src/panels/builtin/agent.ts +1 -1
- package/src/panels/builtin/operations.ts +1 -10
- package/src/panels/builtin/session.ts +9 -9
- package/src/panels/builtin/shared.ts +2 -2
- package/src/panels/cost-tracker-panel.ts +1 -1
- package/src/panels/docs-panel.ts +5 -3
- package/src/panels/index.ts +0 -1
- package/src/panels/knowledge-panel.ts +6 -5
- package/src/panels/memory-panel.ts +7 -6
- package/src/panels/panel-list-panel.ts +36 -80
- package/src/panels/project-planning-panel.ts +18 -11
- package/src/panels/provider-account-snapshot.ts +51 -25
- package/src/panels/provider-accounts-panel.ts +33 -18
- package/src/panels/provider-health-domains.ts +45 -4
- package/src/panels/provider-health-panel.ts +5 -4
- package/src/panels/qr-panel.ts +1 -1
- package/src/panels/schedule-panel.ts +9 -17
- package/src/panels/security-panel.ts +8 -8
- package/src/panels/session-browser-panel.ts +10 -10
- package/src/panels/subscription-panel.ts +3 -3
- package/src/panels/system-messages-panel.ts +1 -1
- package/src/panels/tasks-panel.ts +20 -13
- package/src/panels/tool-inspector-panel.ts +19 -12
- package/src/panels/work-plan-panel.ts +5 -5
- package/src/planning/project-planning-coordinator.ts +1 -1
- package/src/provider-auth-route-display.ts +9 -0
- package/src/renderer/agent-workspace-style.ts +34 -0
- package/src/renderer/agent-workspace.ts +228 -52
- package/src/renderer/autocomplete-overlay.ts +25 -6
- package/src/renderer/bookmark-modal.ts +19 -4
- package/src/renderer/buffer.ts +4 -2
- package/src/renderer/context-inspector.ts +50 -13
- package/src/renderer/diff.ts +1 -1
- package/src/renderer/file-picker-overlay.ts +19 -6
- package/src/renderer/help-overlay.ts +106 -33
- package/src/renderer/history-search-overlay.ts +19 -4
- package/src/renderer/live-tail-modal.ts +27 -5
- package/src/renderer/mcp-workspace.ts +164 -50
- package/src/renderer/model-picker-overlay.ts +58 -2
- package/src/renderer/model-workspace.ts +104 -20
- package/src/renderer/process-modal.ts +27 -6
- package/src/renderer/profile-picker-modal.ts +20 -4
- package/src/renderer/search-overlay.ts +25 -5
- package/src/renderer/selection-modal-overlay.ts +46 -14
- package/src/renderer/session-picker-modal.ts +18 -1
- package/src/renderer/settings-modal-helpers.ts +2 -40
- package/src/renderer/settings-modal.ts +83 -50
- package/src/renderer/tool-call.ts +20 -11
- package/src/runtime/agent-runtime-events.ts +6 -6
- package/src/runtime/bootstrap-command-context.ts +7 -1
- package/src/runtime/bootstrap-command-parts.ts +9 -7
- package/src/runtime/bootstrap-core.ts +19 -1
- package/src/runtime/bootstrap-hook-bridge.ts +7 -18
- package/src/runtime/bootstrap-shell.ts +4 -4
- package/src/runtime/bootstrap.ts +31 -22
- package/src/runtime/connected-host-auth.ts +7 -2
- package/src/runtime/diagnostics/panels/index.ts +2 -2
- package/src/runtime/diagnostics/panels/policy.ts +7 -7
- package/src/runtime/index.ts +2 -1
- package/src/runtime/onboarding/apply-file-helpers.ts +66 -0
- package/src/runtime/onboarding/apply.ts +80 -79
- package/src/runtime/onboarding/derivation.ts +5 -8
- package/src/runtime/onboarding/snapshot.ts +0 -15
- package/src/runtime/onboarding/types.ts +8 -19
- package/src/runtime/onboarding/verify.ts +32 -10
- package/src/runtime/operator-token-cleanup.ts +1 -1
- package/src/runtime/services.ts +91 -22
- package/src/runtime/store/selectors/index.ts +3 -3
- package/src/runtime/store/state.ts +1 -4
- package/src/runtime/surface-feature-flags.ts +41 -6
- package/src/runtime/ui-read-models.ts +3 -4
- package/src/runtime/ui-services.ts +6 -6
- package/src/shell/blocking-input.ts +2 -1
- package/src/shell/ui-openers.ts +3 -13
- package/src/tools/agent-analysis-registry-policy.ts +0 -1
- package/src/tools/agent-channel-send-tool.ts +133 -0
- package/src/tools/agent-context-policy.ts +1 -1
- package/src/tools/agent-knowledge-ingest-tool.ts +405 -0
- package/src/tools/agent-knowledge-tool.ts +170 -0
- package/src/tools/agent-local-registry-tool.ts +269 -69
- package/src/tools/agent-media-generate-tool.ts +133 -0
- package/src/tools/agent-notify-tool.ts +143 -0
- package/src/tools/agent-operator-action-tool.ts +137 -0
- package/src/tools/agent-operator-briefing-tool.ts +217 -0
- package/src/tools/agent-reminder-schedule-tool.ts +237 -0
- package/src/tools/agent-tool-policy-guard.ts +8 -8
- package/src/tools/agent-work-plan-tool.ts +256 -0
- package/src/version.ts +1 -1
- package/src/input/commands/policy-dispatch.ts +0 -339
- package/src/input/commands/policy.ts +0 -13
- package/src/panels/panel-picker.ts +0 -105
- package/src/panels/policy-panel.ts +0 -308
- package/src/renderer/panel-picker-overlay.ts +0 -202
|
@@ -3,6 +3,7 @@ import { dirname } from 'node:path';
|
|
|
3
3
|
import { SecretsManager } from '../config/secrets.ts';
|
|
4
4
|
import { BUILTIN_SECRET_PROVIDER_SOURCES, describeSecretRef, isSecretRefInput, resolveSecretRef } from '@pellux/goodvibes-sdk/platform/config';
|
|
5
5
|
import { getSubscriptionProviderConfig, listAvailableSubscriptionProviders } from '@pellux/goodvibes-sdk/platform/config';
|
|
6
|
+
import type { OAuthProviderConfig } from '@pellux/goodvibes-sdk/platform/config';
|
|
6
7
|
import { beginOpenAICodexLogin, exchangeOpenAICodexCode } from '@pellux/goodvibes-sdk/platform/config';
|
|
7
8
|
import { inspectProviderAuth } from '@/runtime/index.ts';
|
|
8
9
|
import { buildCompanionConnectionInfo, encodeConnectionPayload, formatConnectionBlock } from '@pellux/goodvibes-sdk/platform/pairing';
|
|
@@ -12,6 +13,13 @@ import type { CliCommandRuntime } from './management.ts';
|
|
|
12
13
|
import { extractAuthorizationCode, formatJsonOrText, hasCommandFlag, openBrowser, urlHostForBindHost, withRuntimeServices, yesNo } from './management.ts';
|
|
13
14
|
import { GOODVIBES_AGENT_PAIRING_SURFACE } from '../config/surface.ts';
|
|
14
15
|
import { connectedHostTokenRequiredMessage, readConnectedHostOperatorToken } from '../runtime/connected-host-auth.ts';
|
|
16
|
+
import { formatAgentRecordSource } from '../agent/record-labels.ts';
|
|
17
|
+
|
|
18
|
+
function resolveManualSubscriptionConfig(config: OAuthProviderConfig): OAuthProviderConfig {
|
|
19
|
+
return config.manualRedirectUri
|
|
20
|
+
? { ...config, redirectUri: config.manualRedirectUri }
|
|
21
|
+
: config;
|
|
22
|
+
}
|
|
15
23
|
|
|
16
24
|
export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<string> {
|
|
17
25
|
return await withRuntimeServices(runtime, async (services) => {
|
|
@@ -23,7 +31,7 @@ export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<s
|
|
|
23
31
|
if (sub === 'providers') {
|
|
24
32
|
return formatJsonOrText(runtime.cli)(available, [
|
|
25
33
|
'GoodVibes subscription providers',
|
|
26
|
-
...available.map((provider) => ` ${provider.provider}
|
|
34
|
+
...available.map((provider) => ` ${provider.provider} origin ${provider.source} redirect ${provider.oauth.redirectUri}`),
|
|
27
35
|
].join('\n'));
|
|
28
36
|
}
|
|
29
37
|
if (sub === 'inspect' || sub === 'show') {
|
|
@@ -41,21 +49,21 @@ export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<s
|
|
|
41
49
|
const stored = services.subscriptionManager.get(provider);
|
|
42
50
|
return formatJsonOrText(runtime.cli)({ provider, resolved, inspection, stored }, [
|
|
43
51
|
`GoodVibes subscription ${provider}`,
|
|
44
|
-
` configured
|
|
45
|
-
` freshness
|
|
46
|
-
|
|
52
|
+
` configured ${yesNo(inspection.configured)}`,
|
|
53
|
+
` freshness ${inspection.freshness}`,
|
|
54
|
+
' finish mode explicit manual',
|
|
47
55
|
...(resolved ? [
|
|
48
|
-
`
|
|
49
|
-
`
|
|
56
|
+
` origin ${formatAgentRecordSource(resolved.source)}`,
|
|
57
|
+
` redirect URI ${resolved.oauth.redirectUri}`,
|
|
50
58
|
] : []),
|
|
51
59
|
...(stored ? [
|
|
52
|
-
`
|
|
53
|
-
`
|
|
54
|
-
`
|
|
55
|
-
`
|
|
56
|
-
] : [' stored
|
|
57
|
-
...inspection.issues.map((issue) => ` issue
|
|
58
|
-
...inspection.nextActions.map((action) => ` next
|
|
60
|
+
` token type ${stored.tokenType}`,
|
|
61
|
+
` expires ${stored.expiresAt ? new Date(stored.expiresAt).toISOString() : 'n/a'}`,
|
|
62
|
+
` refresh token ${stored.refreshToken ? 'present' : 'absent'}`,
|
|
63
|
+
` override ambient ${yesNo(stored.overrideAmbientApiKeys)}`,
|
|
64
|
+
] : [' stored no']),
|
|
65
|
+
...inspection.issues.map((issue) => ` issue ${issue}`),
|
|
66
|
+
...inspection.nextActions.map((action) => ` next ${action}`),
|
|
59
67
|
].join('\n'));
|
|
60
68
|
}
|
|
61
69
|
if (sub === 'login' || sub === 'start') {
|
|
@@ -63,7 +71,7 @@ export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<s
|
|
|
63
71
|
const mode = sub === 'start' ? 'start' : rest[1]?.toLowerCase();
|
|
64
72
|
if (!provider || mode !== 'start') return `Usage: ${binary} subscription login <provider> start [--open]`;
|
|
65
73
|
const resolved = getSubscriptionProviderConfig(provider, services.serviceRegistry.get(provider));
|
|
66
|
-
if (!resolved) return `No subscription provider found
|
|
74
|
+
if (!resolved) return `No subscription provider found ${provider}`;
|
|
67
75
|
if (provider === 'openai' && resolved.source === 'builtin') {
|
|
68
76
|
const started = await beginOpenAICodexLogin();
|
|
69
77
|
services.subscriptionManager.savePending({
|
|
@@ -75,26 +83,29 @@ export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<s
|
|
|
75
83
|
});
|
|
76
84
|
const openResult = runtime.cli.flags.open || hasCommandFlag(rest, '--open') ? openBrowser(started.authorizationUrl) : null;
|
|
77
85
|
return [
|
|
78
|
-
`Subscription OAuth started
|
|
79
|
-
`
|
|
80
|
-
` state
|
|
81
|
-
`
|
|
82
|
-
...(openResult ? [` open
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
`Subscription OAuth started ${provider}`,
|
|
87
|
+
` origin ${formatAgentRecordSource(resolved.source)}`,
|
|
88
|
+
` state ${started.state}`,
|
|
89
|
+
` redirect URI ${started.redirectUri}`,
|
|
90
|
+
...(openResult ? [` open ${openResult}`] : []),
|
|
91
|
+
' completion paste the callback code or redirect URL into the finish command',
|
|
92
|
+
` next ${binary} subscription login ${provider} finish <code-or-url>`,
|
|
93
|
+
' authorization URL',
|
|
85
94
|
` ${started.authorizationUrl}`,
|
|
86
95
|
].join('\n');
|
|
87
96
|
}
|
|
88
|
-
const
|
|
97
|
+
const activeConfig = resolveManualSubscriptionConfig(resolved.oauth);
|
|
98
|
+
const started = await services.subscriptionManager.beginOAuthLogin(provider, activeConfig);
|
|
89
99
|
const openResult = runtime.cli.flags.open || hasCommandFlag(rest, '--open') ? openBrowser(started.authorizationUrl) : null;
|
|
90
100
|
return [
|
|
91
|
-
`Subscription OAuth started
|
|
92
|
-
`
|
|
93
|
-
` state
|
|
94
|
-
`
|
|
95
|
-
...(openResult ? [` open
|
|
96
|
-
|
|
97
|
-
|
|
101
|
+
`Subscription OAuth started ${provider}`,
|
|
102
|
+
` origin ${formatAgentRecordSource(resolved.source)}`,
|
|
103
|
+
` state ${started.pending.state}`,
|
|
104
|
+
` redirect URI ${started.pending.redirectUri}`,
|
|
105
|
+
...(openResult ? [` open ${openResult}`] : []),
|
|
106
|
+
' completion paste the callback code or redirect URL into the finish command',
|
|
107
|
+
` next ${binary} subscription login ${provider} finish <code-or-url>`,
|
|
108
|
+
' authorization URL',
|
|
98
109
|
` ${started.authorizationUrl}`,
|
|
99
110
|
].join('\n');
|
|
100
111
|
}
|
|
@@ -103,7 +114,7 @@ export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<s
|
|
|
103
114
|
const codeInput = sub === 'finish' ? rest[1] : rest[2];
|
|
104
115
|
if (!provider || !codeInput) return `Usage: ${binary} subscription login <provider> finish <code-or-url>`;
|
|
105
116
|
const resolved = getSubscriptionProviderConfig(provider, services.serviceRegistry.get(provider));
|
|
106
|
-
if (!resolved) return `No subscription provider found
|
|
117
|
+
if (!resolved) return `No subscription provider found ${provider}`;
|
|
107
118
|
const code = extractAuthorizationCode(codeInput);
|
|
108
119
|
if (provider === 'openai' && resolved.source === 'builtin') {
|
|
109
120
|
const pendingLogin = services.subscriptionManager.getPending(provider);
|
|
@@ -122,24 +133,36 @@ export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<s
|
|
|
122
133
|
createdAt: services.subscriptionManager.get(provider)?.createdAt ?? now,
|
|
123
134
|
updatedAt: now,
|
|
124
135
|
});
|
|
125
|
-
return
|
|
136
|
+
return [
|
|
137
|
+
`Subscription stored ${provider}`,
|
|
138
|
+
` token type ${record.tokenType}`,
|
|
139
|
+
` expires ${record.expiresAt ? new Date(record.expiresAt).toISOString() : 'n/a'}`,
|
|
140
|
+
].join('\n');
|
|
126
141
|
}
|
|
127
|
-
const
|
|
128
|
-
|
|
142
|
+
const activeConfig = resolveManualSubscriptionConfig(resolved.oauth);
|
|
143
|
+
const record = await services.subscriptionManager.completeOAuthLogin(provider, activeConfig, code);
|
|
144
|
+
return [
|
|
145
|
+
`Subscription stored ${provider}`,
|
|
146
|
+
` token type ${record.tokenType}`,
|
|
147
|
+
` expires ${record.expiresAt ? new Date(record.expiresAt).toISOString() : 'n/a'}`,
|
|
148
|
+
].join('\n');
|
|
129
149
|
}
|
|
130
150
|
if (sub === 'refresh') {
|
|
131
151
|
const provider = rest[0];
|
|
132
152
|
if (!provider) return `Usage: ${binary} subscription refresh <provider>`;
|
|
133
153
|
const resolved = getSubscriptionProviderConfig(provider, services.serviceRegistry.get(provider));
|
|
134
|
-
if (!resolved) return `No subscription provider found
|
|
154
|
+
if (!resolved) return `No subscription provider found ${provider}`;
|
|
135
155
|
const record = await services.subscriptionManager.refreshOAuthToken(provider, resolved.oauth);
|
|
136
|
-
return
|
|
156
|
+
return [
|
|
157
|
+
`Subscription refreshed ${provider}`,
|
|
158
|
+
` expires ${record.expiresAt ? new Date(record.expiresAt).toISOString() : 'n/a'}`,
|
|
159
|
+
].join('\n');
|
|
137
160
|
}
|
|
138
161
|
if (sub === 'logout' || sub === 'remove') {
|
|
139
162
|
const provider = rest[0];
|
|
140
163
|
if (!provider) return `Usage: ${binary} subscription logout <provider>`;
|
|
141
164
|
const removed = services.subscriptionManager.logout(provider);
|
|
142
|
-
return removed ? `Subscription removed
|
|
165
|
+
return removed ? `Subscription removed ${provider}` : `No stored subscription session existed for ${provider}.`;
|
|
143
166
|
}
|
|
144
167
|
if (sub !== 'list' && sub !== 'status' && sub !== 'review') {
|
|
145
168
|
return `Usage: ${binary} subscription [list|providers|inspect <provider>|login <provider> start|finish <code-or-url>|refresh <provider>|logout <provider>]`;
|
|
@@ -155,10 +178,10 @@ export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<s
|
|
|
155
178
|
};
|
|
156
179
|
return formatJsonOrText(runtime.cli)(value, [
|
|
157
180
|
'GoodVibes subscriptions',
|
|
158
|
-
subscriptions.length === 0 ? ' active
|
|
159
|
-
...subscriptions.map((sub) => ` ${sub.provider} token
|
|
160
|
-
pending.length === 0 ? ' pending
|
|
161
|
-
...pending.map((sub) => ` ${sub.provider} created
|
|
181
|
+
subscriptions.length === 0 ? ' active none' : ' active',
|
|
182
|
+
...subscriptions.map((sub) => ` ${sub.provider} token ${sub.tokenType} expires ${sub.expiresAt ? new Date(sub.expiresAt).toISOString() : 'n/a'} override ambient ${yesNo(sub.overrideAmbientApiKeys)}`),
|
|
183
|
+
pending.length === 0 ? ' pending none' : ' pending',
|
|
184
|
+
...pending.map((sub) => ` ${sub.provider} created ${new Date(sub.createdAt).toISOString()}`),
|
|
162
185
|
].join('\n'));
|
|
163
186
|
});
|
|
164
187
|
}
|
|
@@ -186,7 +209,11 @@ export async function handleSecrets(runtime: CliCommandRuntime): Promise<string>
|
|
|
186
209
|
}
|
|
187
210
|
const resolved = await resolveSecretRef(ref, { resolveLocalSecret: (key) => secrets.get(key) });
|
|
188
211
|
const value = { ref: describeSecretRef(ref), resolved: Boolean(resolved.value) };
|
|
189
|
-
return formatJsonOrText(runtime.cli)(value,
|
|
212
|
+
return formatJsonOrText(runtime.cli)(value, [
|
|
213
|
+
'GoodVibes secret test',
|
|
214
|
+
` ref ${value.ref}`,
|
|
215
|
+
` result ${value.resolved ? 'resolved <redacted>' : 'missing'}`,
|
|
216
|
+
].join('\n'));
|
|
190
217
|
}
|
|
191
218
|
if (sub === 'set' || sub === 'link') {
|
|
192
219
|
const flags = new Set(rest.filter((arg) => arg.startsWith('--')));
|
|
@@ -201,7 +228,10 @@ export async function handleSecrets(runtime: CliCommandRuntime): Promise<string>
|
|
|
201
228
|
scope: flags.has('--user') ? 'user' : 'project',
|
|
202
229
|
medium: flags.has('--plaintext') ? 'plaintext' : 'secure',
|
|
203
230
|
});
|
|
204
|
-
return
|
|
231
|
+
return [
|
|
232
|
+
`Secret ${sub === 'link' ? 'linked' : 'stored'}`,
|
|
233
|
+
` key ${key}`,
|
|
234
|
+
].join('\n');
|
|
205
235
|
}
|
|
206
236
|
if (sub === 'delete') {
|
|
207
237
|
const key = rest.find((arg) => !arg.startsWith('--'));
|
|
@@ -211,18 +241,21 @@ export async function handleSecrets(runtime: CliCommandRuntime): Promise<string>
|
|
|
211
241
|
scope: flags.has('--user') ? 'user' : flags.has('--project') ? 'project' : undefined,
|
|
212
242
|
medium: flags.has('--secure') ? 'secure' : flags.has('--plaintext') ? 'plaintext' : undefined,
|
|
213
243
|
});
|
|
214
|
-
return
|
|
244
|
+
return [
|
|
245
|
+
'Secret deleted',
|
|
246
|
+
` key ${key}`,
|
|
247
|
+
].join('\n');
|
|
215
248
|
}
|
|
216
249
|
const [records, review] = await Promise.all([secrets.listDetailed(), secrets.inspect()]);
|
|
217
250
|
const stored = records.filter((record) => record.source !== 'env');
|
|
218
251
|
const value = { policy: review.policy, records: stored, warnings: review.warnings };
|
|
219
252
|
return formatJsonOrText(runtime.cli)(value, [
|
|
220
253
|
'GoodVibes secrets',
|
|
221
|
-
` policy
|
|
222
|
-
` secure available
|
|
223
|
-
` stored keys
|
|
254
|
+
` policy ${review.policy}`,
|
|
255
|
+
` secure available ${yesNo(review.secureAvailable)}`,
|
|
256
|
+
` stored keys ${stored.length}`,
|
|
224
257
|
...stored.map((record) => ` ${record.key} (${record.source}${record.refSource ? `, ref:${record.refSource}` : ''}${record.overriddenByEnv ? ', env override' : ''})`),
|
|
225
|
-
...review.warnings.map((warning) => ` warning
|
|
258
|
+
...review.warnings.map((warning) => ` warning ${warning}`),
|
|
226
259
|
].join('\n'));
|
|
227
260
|
}
|
|
228
261
|
|
|
@@ -235,21 +268,21 @@ export async function handleSessions(runtime: CliCommandRuntime): Promise<string
|
|
|
235
268
|
const value = sessions;
|
|
236
269
|
return formatJsonOrText(runtime.cli)(value, [
|
|
237
270
|
`GoodVibes sessions (${sessions.length})`,
|
|
238
|
-
...sessions.slice(0, 50).map((session) => ` ${session.name} messages
|
|
271
|
+
...sessions.slice(0, 50).map((session) => ` ${session.name} messages ${session.messageCount} ${new Date(session.timestamp).toISOString()} ${session.title || '(untitled)'}`),
|
|
239
272
|
].join('\n'));
|
|
240
273
|
}
|
|
241
274
|
if (sub === 'show' || sub === 'info') {
|
|
242
275
|
const target = rest.join(' ').trim();
|
|
243
276
|
if (!target) return `Usage: ${binary} sessions show <id|name>`;
|
|
244
277
|
const found = sessions.find((session) => session.name === target || session.name.startsWith(target) || session.title.toLowerCase() === target.toLowerCase());
|
|
245
|
-
if (!found) return `Session not found
|
|
278
|
+
if (!found) return `Session not found ${target}`;
|
|
246
279
|
return formatJsonOrText(runtime.cli)(found, [
|
|
247
280
|
`Session ${found.name}`,
|
|
248
|
-
` title
|
|
249
|
-
` messages
|
|
250
|
-
` provider/model
|
|
251
|
-
` updated
|
|
252
|
-
` file
|
|
281
|
+
` title ${found.title || '(untitled)'}`,
|
|
282
|
+
` messages ${found.messageCount}`,
|
|
283
|
+
` provider/model ${found.provider}/${found.model}`,
|
|
284
|
+
` updated ${new Date(found.timestamp).toISOString()}`,
|
|
285
|
+
` file ${found.filePath}`,
|
|
253
286
|
].join('\n'));
|
|
254
287
|
}
|
|
255
288
|
if (sub === 'export') {
|
|
@@ -257,14 +290,17 @@ export async function handleSessions(runtime: CliCommandRuntime): Promise<string
|
|
|
257
290
|
const outputPath = rest[1];
|
|
258
291
|
if (!target) return `Usage: ${binary} sessions export <id|name> [path]`;
|
|
259
292
|
const found = sessions.find((session) => session.name === target || session.name.startsWith(target) || session.title.toLowerCase() === target.toLowerCase());
|
|
260
|
-
if (!found) return `Session not found
|
|
293
|
+
if (!found) return `Session not found ${target}`;
|
|
261
294
|
const data = services.sessionManager.load(found.name);
|
|
262
295
|
const text = JSON.stringify({ name: found.name, ...data }, null, 2) + '\n';
|
|
263
296
|
if (outputPath) {
|
|
264
297
|
const targetPath = services.shellPaths.resolveWorkspacePath(outputPath);
|
|
265
298
|
mkdirSync(dirname(targetPath), { recursive: true });
|
|
266
299
|
writeFileSync(targetPath, text, 'utf-8');
|
|
267
|
-
return
|
|
300
|
+
return [
|
|
301
|
+
'Session exported',
|
|
302
|
+
` path ${targetPath}`,
|
|
303
|
+
].join('\n');
|
|
268
304
|
}
|
|
269
305
|
return text.trimEnd();
|
|
270
306
|
}
|
|
@@ -281,9 +317,9 @@ export async function handleTasks(runtime: CliCommandRuntime): Promise<string> {
|
|
|
281
317
|
if (sub === 'submit') {
|
|
282
318
|
return [
|
|
283
319
|
'GoodVibes Agent blocks CLI task submission from the host-owned task workflow.',
|
|
284
|
-
' policy
|
|
285
|
-
' build/fix/review
|
|
286
|
-
' result
|
|
320
|
+
' policy do normal assistant work in the main Agent conversation or use `goodvibes-agent run <prompt>` for an explicit one-shot run.',
|
|
321
|
+
' build/fix/review use `goodvibes-agent delegate <task>` for explicit GoodVibes TUI handoff.',
|
|
322
|
+
' result no local task was started.',
|
|
287
323
|
].join('\n');
|
|
288
324
|
}
|
|
289
325
|
return await withRuntimeServices(runtime, (services) => {
|
|
@@ -296,7 +332,7 @@ export async function handleTasks(runtime: CliCommandRuntime): Promise<string> {
|
|
|
296
332
|
if (sub === 'show') {
|
|
297
333
|
if (!rest[0]) return `Usage: ${runtime.cli.binary} tasks show <taskId>`;
|
|
298
334
|
const task = tasks.find((candidate) => candidate.id === rest[0]);
|
|
299
|
-
return task ? JSON.stringify(task, null, 2) : `Unknown task
|
|
335
|
+
return task ? JSON.stringify(task, null, 2) : `Unknown task ${rest[0] ?? ''}`;
|
|
300
336
|
}
|
|
301
337
|
return `Usage: ${runtime.cli.binary} tasks list|show <taskId>`;
|
|
302
338
|
});
|
|
@@ -306,9 +342,9 @@ export async function renderPairing(runtime: CliCommandRuntime): Promise<string>
|
|
|
306
342
|
const tokenRecord = readConnectedHostOperatorToken(runtime.homeDirectory);
|
|
307
343
|
if (!tokenRecord.token) return connectedHostTokenRequiredMessage(tokenRecord.path);
|
|
308
344
|
const binding = resolveRuntimeEndpointBinding(runtime.configManager, 'controlPlane');
|
|
309
|
-
const
|
|
345
|
+
const connectedHostUrl = `http://${urlHostForBindHost(binding.host)}:${binding.port}`;
|
|
310
346
|
const info = buildCompanionConnectionInfo({
|
|
311
|
-
daemonUrl,
|
|
347
|
+
daemonUrl: connectedHostUrl,
|
|
312
348
|
token: tokenRecord.token,
|
|
313
349
|
username: 'admin',
|
|
314
350
|
surface: GOODVIBES_AGENT_PAIRING_SURFACE,
|
package/src/cli/management.ts
CHANGED
|
@@ -13,6 +13,9 @@ import type { RuntimeServices } from '../runtime/services.ts';
|
|
|
13
13
|
import { SecretsManager } from '../config/secrets.ts';
|
|
14
14
|
import { RuntimeEventBus, type TurnEvent } from '@/runtime/index.ts';
|
|
15
15
|
import { createShellPathService } from '@/runtime/index.ts';
|
|
16
|
+
import { buildPersistedSessionContext } from '@/runtime/index.ts';
|
|
17
|
+
import type { SessionSnapshot } from '@/runtime/index.ts';
|
|
18
|
+
import { conversationMessagesAsSessionRecords } from '../core/conversation-message-snapshot.ts';
|
|
16
19
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
17
20
|
import { listProviderRuntimeSnapshots } from '@pellux/goodvibes-sdk/platform/providers';
|
|
18
21
|
import { BUILTIN_SECRET_PROVIDER_SOURCES, describeSecretRef, isSecretRefInput, resolveSecretRef } from '@pellux/goodvibes-sdk/platform/config';
|
|
@@ -56,8 +59,8 @@ export function formatJsonOrText(cli: GoodVibesCliParseResult): Formatter {
|
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
function exitCodeForText(output: string): number {
|
|
59
|
-
if (output.startsWith('Usage:') || output.startsWith('Invalid ') || output.startsWith('Unsupported
|
|
60
|
-
if (output.startsWith('Session not found
|
|
62
|
+
if (output.startsWith('Usage:') || output.startsWith('Invalid ') || output.startsWith('Unsupported')) return 2;
|
|
63
|
+
if (output.startsWith('Session not found') || output.startsWith('Unknown task') || output.startsWith('Task submit failed ')) return 1;
|
|
61
64
|
if (output.startsWith('No stored ') || output.startsWith('No pending ') || output.startsWith('No model ') || output.startsWith('No provider ') || output.startsWith('No auth ')) return 1;
|
|
62
65
|
if (output.startsWith('Unknown ')) return 1;
|
|
63
66
|
if (output === 'Bundle has no config object to import.') return 1;
|
|
@@ -98,7 +101,7 @@ export function getNestedValue(source: unknown, key: string): unknown {
|
|
|
98
101
|
let cursor = source;
|
|
99
102
|
for (const part of key.split('.')) {
|
|
100
103
|
if (cursor == null || typeof cursor !== 'object') return undefined;
|
|
101
|
-
cursor = (cursor
|
|
104
|
+
cursor = Reflect.get(cursor, part);
|
|
102
105
|
}
|
|
103
106
|
return cursor;
|
|
104
107
|
}
|
|
@@ -278,7 +281,13 @@ export async function runNonInteractiveAgent(runtime: CliCommandRuntime): Promis
|
|
|
278
281
|
}) + '\n');
|
|
279
282
|
}
|
|
280
283
|
} finally {
|
|
281
|
-
const
|
|
284
|
+
const messages = ctx.conversation.getMessageSnapshot();
|
|
285
|
+
const snapshot: SessionSnapshot = {
|
|
286
|
+
messages: conversationMessagesAsSessionRecords(messages),
|
|
287
|
+
timestamp: Date.now(),
|
|
288
|
+
title: ctx.conversation.title,
|
|
289
|
+
...buildPersistedSessionContext(messages, ctx.conversation.getTitleSource()),
|
|
290
|
+
};
|
|
282
291
|
await ctx.shutdown(snapshot);
|
|
283
292
|
}
|
|
284
293
|
return exitCode;
|
|
@@ -326,18 +335,25 @@ async function renderProviders(runtime: CliCommandRuntime): Promise<string> {
|
|
|
326
335
|
} else {
|
|
327
336
|
runtime.configManager.setDynamic('provider.model', formatProviderModel(provider, getModelIdFromProviderModel(runtime.configManager.get('provider.model'))));
|
|
328
337
|
}
|
|
329
|
-
return
|
|
330
|
-
|
|
331
|
-
|
|
338
|
+
return [
|
|
339
|
+
'Provider selected',
|
|
340
|
+
` provider ${provider}`,
|
|
341
|
+
...(requestedModel ? [` model ${requestedModel}`] : []),
|
|
342
|
+
` warning model catalog entry was not available locally; ${requestedModel ? 'saved explicit selection' : 'model selection was left unchanged'}.`,
|
|
343
|
+
].join('\n');
|
|
332
344
|
}
|
|
333
345
|
runtime.configManager.setDynamic('provider.model', selected.registryKey);
|
|
334
|
-
return
|
|
346
|
+
return [
|
|
347
|
+
'Provider selected',
|
|
348
|
+
` provider ${selected.provider}`,
|
|
349
|
+
` model ${selected.registryKey}`,
|
|
350
|
+
].join('\n');
|
|
335
351
|
}
|
|
336
352
|
if (sub === 'inspect' || sub === 'show') {
|
|
337
353
|
const provider = rest[0];
|
|
338
354
|
if (!provider) return `Usage: ${binary} providers inspect <provider>`;
|
|
339
355
|
const snapshot = snapshots.find((candidate) => candidate.providerId === provider);
|
|
340
|
-
if (!snapshot) return `No provider found
|
|
356
|
+
if (!snapshot) return `No provider found ${provider}`;
|
|
341
357
|
const setup = classifyProviderSetup({
|
|
342
358
|
providerId: snapshot.providerId,
|
|
343
359
|
authMode: snapshot.runtime.auth?.mode,
|
|
@@ -383,7 +399,7 @@ async function renderProviders(runtime: CliCommandRuntime): Promise<string> {
|
|
|
383
399
|
return formatJsonOrText(runtime.cli)(value, [
|
|
384
400
|
'GoodVibes providers',
|
|
385
401
|
...value.map((provider) =>
|
|
386
|
-
` ${provider.current ? '*' : ' '} ${provider.provider.padEnd(18)} setup
|
|
402
|
+
` ${provider.current ? '*' : ' '} ${provider.provider.padEnd(18)} setup ${provider.setupClass} configured ${yesNo(provider.configured)} via ${provider.configuredVia ?? 'n/a'} models ${provider.models} routes ${provider.authRouteSummary} ${provider.detail ?? ''}`.trimEnd(),
|
|
387
403
|
),
|
|
388
404
|
].join('\n'));
|
|
389
405
|
});
|
|
@@ -436,18 +452,28 @@ async function renderModels(runtime: CliCommandRuntime): Promise<string> {
|
|
|
436
452
|
const provider = inferProviderFromRegistryKey(modelKey);
|
|
437
453
|
runtime.configManager.setDynamic('provider.model', formatProviderModel(provider, modelKey));
|
|
438
454
|
await services.favoritesStore.recordUsage(modelKey);
|
|
439
|
-
return
|
|
455
|
+
return [
|
|
456
|
+
'Model selected',
|
|
457
|
+
` model ${modelKey}`,
|
|
458
|
+
' warning model catalog entry was not available locally; saved explicit selection.',
|
|
459
|
+
].join('\n');
|
|
440
460
|
}
|
|
441
461
|
runtime.configManager.setDynamic('provider.model', model.registryKey);
|
|
442
462
|
await services.favoritesStore.recordUsage(model.registryKey);
|
|
443
|
-
return
|
|
463
|
+
return [
|
|
464
|
+
'Model selected',
|
|
465
|
+
` model ${model.registryKey}`,
|
|
466
|
+
].join('\n');
|
|
444
467
|
}
|
|
445
468
|
if (subOrFilter === 'pin' || subOrFilter === 'unpin') {
|
|
446
469
|
const modelKey = rest[0];
|
|
447
470
|
if (!modelKey) return `Usage: ${binary} models ${subOrFilter} <registryKey>`;
|
|
448
471
|
if (subOrFilter === 'pin') await services.favoritesStore.pinModel(modelKey);
|
|
449
472
|
else await services.favoritesStore.unpinModel(modelKey);
|
|
450
|
-
return
|
|
473
|
+
return [
|
|
474
|
+
`Model ${subOrFilter === 'pin' ? 'pinned' : 'unpinned'}`,
|
|
475
|
+
` model ${modelKey}`,
|
|
476
|
+
].join('\n');
|
|
451
477
|
}
|
|
452
478
|
if (subOrFilter === 'pinned') {
|
|
453
479
|
const pinned = await services.favoritesStore.getPinned();
|
|
@@ -479,7 +505,7 @@ async function renderModels(runtime: CliCommandRuntime): Promise<string> {
|
|
|
479
505
|
}));
|
|
480
506
|
return formatJsonOrText(runtime.cli)(value, [
|
|
481
507
|
`GoodVibes models${filter ? ` (${filter})` : ''}`,
|
|
482
|
-
...value.map((model) => ` ${model.current ? '*' : ' '} ${model.registryKey.padEnd(42)} setup
|
|
508
|
+
...value.map((model) => ` ${model.current ? '*' : ' '} ${model.registryKey.padEnd(42)} setup ${model.setupClass} context ${model.contextWindow.toLocaleString()} ${model.displayName}`),
|
|
483
509
|
].join('\n'));
|
|
484
510
|
});
|
|
485
511
|
}
|
|
@@ -521,23 +547,23 @@ async function renderAuth(runtime: CliCommandRuntime): Promise<string> {
|
|
|
521
547
|
if (sub === 'users' || sub === 'sessions') {
|
|
522
548
|
return formatJsonOrText(runtime.cli)(value, [
|
|
523
549
|
`GoodVibes Agent auth ${sub}`,
|
|
524
|
-
' owner
|
|
525
|
-
` operator token
|
|
526
|
-
` operator token path
|
|
527
|
-
` ${sub}
|
|
550
|
+
' owner connected GoodVibes host',
|
|
551
|
+
` operator token ${paths.operatorTokenPresent ? 'present' : 'missing'}`,
|
|
552
|
+
` operator token path ${paths.operatorTokenPath}`,
|
|
553
|
+
` ${sub} managed outside Agent`,
|
|
528
554
|
' Agent does not enumerate or mutate connected-host users/sessions from the local CLI.',
|
|
529
555
|
].join('\n'));
|
|
530
556
|
}
|
|
531
557
|
return formatJsonOrText(runtime.cli)(value, [
|
|
532
558
|
'GoodVibes Agent auth',
|
|
533
|
-
' owner
|
|
534
|
-
` permission mode
|
|
535
|
-
` operator token
|
|
536
|
-
` compatibility user store
|
|
537
|
-
` compatibility bootstrap credential
|
|
538
|
-
' connected-host user/session administration
|
|
539
|
-
' next
|
|
540
|
-
' next
|
|
559
|
+
' owner connected GoodVibes host',
|
|
560
|
+
` permission mode ${String(value.permissionMode)}`,
|
|
561
|
+
` operator token ${paths.operatorTokenPresent ? 'present' : 'missing'} (${paths.operatorTokenPath})`,
|
|
562
|
+
` compatibility user store ${paths.userStorePresent ? 'present' : 'missing'} (${paths.userStorePath})`,
|
|
563
|
+
` compatibility bootstrap credential ${paths.bootstrapCredentialPresent ? 'present' : 'missing'} (${paths.bootstrapCredentialPath})`,
|
|
564
|
+
' connected-host user/session administration outside Agent',
|
|
565
|
+
' next goodvibes-agent providers',
|
|
566
|
+
' next goodvibes-agent subscription providers',
|
|
541
567
|
].join('\n'));
|
|
542
568
|
}
|
|
543
569
|
|