@pellux/goodvibes-agent 0.1.70 → 0.1.72

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.
Files changed (78) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +3 -3
  3. package/docs/README.md +2 -2
  4. package/docs/getting-started.md +1 -1
  5. package/docs/runtime-connection.md +37 -0
  6. package/package.json +43 -2
  7. package/src/agent/skill-discovery.ts +119 -0
  8. package/src/cli/config-overrides.ts +1 -5
  9. package/src/cli/entrypoint.ts +0 -6
  10. package/src/cli/help.ts +0 -43
  11. package/src/cli/index.ts +0 -2
  12. package/src/cli/management-commands.ts +1 -109
  13. package/src/cli/management.ts +1 -32
  14. package/src/cli/package-verification.ts +12 -4
  15. package/src/cli/parser.ts +0 -16
  16. package/src/cli/status.ts +1 -1
  17. package/src/cli/types.ts +0 -8
  18. package/src/input/commands/delegation-runtime.ts +0 -8
  19. package/src/input/commands/experience-runtime.ts +0 -177
  20. package/src/input/commands/guidance-runtime.ts +0 -69
  21. package/src/input/commands/local-runtime.ts +1 -57
  22. package/src/input/commands/local-setup-review.ts +1 -1
  23. package/src/input/commands/operator-runtime.ts +1 -145
  24. package/src/input/commands/platform-access-runtime.ts +2 -195
  25. package/src/input/commands/product-runtime.ts +0 -116
  26. package/src/input/commands/security-runtime.ts +88 -0
  27. package/src/input/commands/session-content.ts +0 -97
  28. package/src/input/commands/shell-core.ts +0 -13
  29. package/src/input/commands.ts +2 -95
  30. package/src/panels/builtin/operations.ts +3 -184
  31. package/src/panels/confirm-state.ts +1 -1
  32. package/src/panels/index.ts +0 -11
  33. package/src/version.ts +1 -1
  34. package/docs/deployment-and-services.md +0 -52
  35. package/src/cli/service-command.ts +0 -26
  36. package/src/cli/surface-command.ts +0 -247
  37. package/src/input/commands/branch-runtime.ts +0 -72
  38. package/src/input/commands/control-room-runtime.ts +0 -234
  39. package/src/input/commands/discovery-runtime.ts +0 -61
  40. package/src/input/commands/hooks-runtime.ts +0 -207
  41. package/src/input/commands/incident-runtime.ts +0 -106
  42. package/src/input/commands/integration-runtime.ts +0 -437
  43. package/src/input/commands/local-setup.ts +0 -288
  44. package/src/input/commands/managed-runtime.ts +0 -240
  45. package/src/input/commands/marketplace-runtime.ts +0 -305
  46. package/src/input/commands/memory-product-runtime.ts +0 -148
  47. package/src/input/commands/operator-panel-runtime.ts +0 -146
  48. package/src/input/commands/platform-services-runtime.ts +0 -271
  49. package/src/input/commands/profile-sync-runtime.ts +0 -110
  50. package/src/input/commands/provider.ts +0 -363
  51. package/src/input/commands/remote-runtime-pool.ts +0 -89
  52. package/src/input/commands/remote-runtime-setup.ts +0 -226
  53. package/src/input/commands/remote-runtime.ts +0 -432
  54. package/src/input/commands/replay-runtime.ts +0 -25
  55. package/src/input/commands/services-runtime.ts +0 -220
  56. package/src/input/commands/settings-sync-runtime.ts +0 -197
  57. package/src/input/commands/share-runtime.ts +0 -127
  58. package/src/input/commands/skills-runtime.ts +0 -226
  59. package/src/input/commands/teleport-runtime.ts +0 -68
  60. package/src/panels/cockpit-panel.ts +0 -183
  61. package/src/panels/communication-panel.ts +0 -153
  62. package/src/panels/control-plane-panel.ts +0 -211
  63. package/src/panels/forensics-panel.ts +0 -364
  64. package/src/panels/hooks-panel.ts +0 -239
  65. package/src/panels/incident-review-panel.ts +0 -197
  66. package/src/panels/marketplace-panel.ts +0 -212
  67. package/src/panels/ops-control-panel.ts +0 -150
  68. package/src/panels/ops-strategy-panel.ts +0 -235
  69. package/src/panels/orchestration-panel.ts +0 -272
  70. package/src/panels/plugins-panel.ts +0 -178
  71. package/src/panels/remote-panel.ts +0 -449
  72. package/src/panels/routes-panel.ts +0 -178
  73. package/src/panels/services-panel.ts +0 -231
  74. package/src/panels/settings-sync-panel.ts +0 -120
  75. package/src/panels/skills-panel.ts +0 -431
  76. package/src/panels/watchers-panel.ts +0 -193
  77. package/src/verification/live-verifier.ts +0 -588
  78. package/src/verification/verification-ledger.ts +0 -239
@@ -1,247 +0,0 @@
1
- import type { ConfigKey } from '../config/index.ts';
2
- import {
3
- GOODVIBES_NTFY_AGENT_TOPIC,
4
- GOODVIBES_NTFY_CHAT_TOPIC,
5
- GOODVIBES_NTFY_REMOTE_TOPIC,
6
- resolveGoodVibesNtfyTopics,
7
- } from '@pellux/goodvibes-sdk/platform/integrations';
8
- import { getMissingSurfaceFeatureFlags } from '../runtime/surface-feature-flags.ts';
9
- import { resolveRuntimeEndpointBinding } from './endpoints.ts';
10
- import { classifyBindPosture, isNetworkFacing } from './network-posture.ts';
11
- import type { CliCommandRuntime } from './management.ts';
12
- import {
13
- formatJsonOrText,
14
- isPresentConfigValue,
15
- probeTcp,
16
- readAuthPaths,
17
- yesNo,
18
- } from './management.ts';
19
-
20
- export const SURFACE_CONFIGS = [
21
- ['slack', 'Slack', ['surfaces.slack.signingSecret', 'surfaces.slack.botToken']],
22
- ['discord', 'Discord', ['surfaces.discord.publicKey', 'surfaces.discord.botToken', 'surfaces.discord.applicationId']],
23
- ['telegram', 'Telegram', ['surfaces.telegram.botToken']],
24
- ['webhook', 'Webhook', ['surfaces.webhook.secret']],
25
- ['ntfy', 'ntfy', ['surfaces.ntfy.baseUrl']],
26
- ['googleChat', 'Google Chat', ['surfaces.googleChat.webhookUrl']],
27
- ['signal', 'Signal', ['surfaces.signal.bridgeUrl', 'surfaces.signal.account']],
28
- ['whatsapp', 'WhatsApp', ['surfaces.whatsapp.accessToken', 'surfaces.whatsapp.phoneNumberId']],
29
- ['imessage', 'iMessage', ['surfaces.imessage.bridgeUrl', 'surfaces.imessage.account']],
30
- ['msteams', 'Microsoft Teams', ['surfaces.msteams.appId', 'surfaces.msteams.appPassword']],
31
- ['bluebubbles', 'BlueBubbles', ['surfaces.bluebubbles.serverUrl', 'surfaces.bluebubbles.password']],
32
- ['mattermost', 'Mattermost', ['surfaces.mattermost.baseUrl', 'surfaces.mattermost.botToken']],
33
- ['matrix', 'Matrix', ['surfaces.matrix.homeserverUrl', 'surfaces.matrix.accessToken', 'surfaces.matrix.userId']],
34
- ] as const;
35
-
36
- export async function handleSurfacesCommand(runtime: CliCommandRuntime): Promise<{ readonly output: string; readonly exitCode: number }> {
37
- const config = runtime.configManager;
38
- const [sub = 'list', ...rest] = runtime.cli.commandArgs;
39
- const target = rest[0];
40
- if (sub === 'enable' || sub === 'disable') {
41
- if (!target) return { output: `Usage: goodvibes-agent surfaces ${sub} <web|listener|control-plane|surfaceId>`, exitCode: 2 };
42
- const text = [
43
- 'GoodVibes Agent does not mutate runtime, listener, web, or channel surface posture.',
44
- 'Configure those surfaces from GoodVibes TUI or the external GoodVibes runtime host, then use `goodvibes-agent surfaces check` for read-only diagnostics.',
45
- ].join(' ');
46
- return {
47
- output: formatJsonOrText(runtime.cli)({
48
- ok: false,
49
- kind: 'daemon_lifecycle_external',
50
- action: `surfaces.${sub}`,
51
- target,
52
- error: text,
53
- }, text),
54
- exitCode: 2,
55
- };
56
- }
57
- if (sub !== 'list' && sub !== 'status' && sub !== 'check' && sub !== 'show') {
58
- return { output: 'Usage: goodvibes-agent surfaces [list|check|show <surfaceId>]', exitCode: 2 };
59
- }
60
- const controlPlane = resolveRuntimeEndpointBinding(config, 'controlPlane');
61
- const web = resolveRuntimeEndpointBinding(config, 'web');
62
- const httpListener = resolveRuntimeEndpointBinding(config, 'httpListener');
63
- const includeProbe = sub === 'check';
64
- const targetExternalSurface = target && SURFACE_CONFIGS.some(([id]) => id === target);
65
- const shouldProbeControlPlane = includeProbe && !target;
66
- const shouldProbeWeb = includeProbe && !target;
67
- const shouldProbeListener = includeProbe && (!target || targetExternalSurface);
68
- const [controlPlaneReachable, webReachable, listenerReachable] = includeProbe
69
- ? await Promise.all([
70
- shouldProbeControlPlane ? probeTcp(controlPlane.host, controlPlane.port) : Promise.resolve(undefined),
71
- shouldProbeWeb ? probeTcp(web.host, web.port) : Promise.resolve(undefined),
72
- shouldProbeListener ? probeTcp(httpListener.host, httpListener.port) : Promise.resolve(undefined),
73
- ])
74
- : [undefined, undefined, undefined];
75
- const externalSurfaces = SURFACE_CONFIGS.map(([id, label, requiredKeys]) => {
76
- const enabled = config.get(`surfaces.${id}.enabled` as ConfigKey);
77
- const missing = requiredKeys.filter((key) => !isPresentConfigValue(config.get(key as ConfigKey)));
78
- const missingFeatureFlags = enabled === true ? getMissingSurfaceFeatureFlags(config, id) : [];
79
- return {
80
- id,
81
- label,
82
- enabled,
83
- ready: !enabled || (missing.length === 0 && missingFeatureFlags.length === 0),
84
- missing,
85
- missingFeatureFlags,
86
- };
87
- });
88
- const filteredSurfaces = target ? externalSurfaces.filter((surface) => surface.id === target) : externalSurfaces;
89
- if (target && filteredSurfaces.length === 0) return { output: `Unknown surface: ${target}`, exitCode: 1 };
90
- const ntfyTopics = resolveGoodVibesNtfyTopics({
91
- chatTopic: String(config.get('surfaces.ntfy.chatTopic' as ConfigKey) || GOODVIBES_NTFY_CHAT_TOPIC),
92
- agentTopic: String(config.get('surfaces.ntfy.agentTopic' as ConfigKey) || GOODVIBES_NTFY_AGENT_TOPIC),
93
- remoteTopic: String(config.get('surfaces.ntfy.remoteTopic' as ConfigKey) || GOODVIBES_NTFY_REMOTE_TOPIC),
94
- });
95
- const readinessIssues: string[] = [];
96
- if (shouldProbeControlPlane && config.get('controlPlane.enabled') === true && !controlPlaneReachable) {
97
- readinessIssues.push(`Control plane is enabled but not reachable on ${controlPlane.host}:${controlPlane.port}.`);
98
- }
99
- if (shouldProbeWeb && config.get('web.enabled') === true && !webReachable) {
100
- readinessIssues.push(`Web surface is enabled but not reachable on ${web.host}:${web.port}.`);
101
- }
102
- if (shouldProbeListener && config.get('danger.httpListener') === true && !listenerReachable) {
103
- readinessIssues.push(`HTTP listener is enabled but not reachable on ${httpListener.host}:${httpListener.port}.`);
104
- }
105
- for (const surface of filteredSurfaces) {
106
- if (surface.enabled !== true) continue;
107
- if (config.get('danger.httpListener') !== true) {
108
- readinessIssues.push(`${surface.label} is enabled but the HTTP listener is disabled.`);
109
- }
110
- if (surface.missing.length > 0) {
111
- readinessIssues.push(`${surface.label} is enabled but missing ${surface.missing.join(', ')}.`);
112
- }
113
- if (surface.missingFeatureFlags.length > 0) {
114
- readinessIssues.push(`${surface.label} is enabled but feature gates are disabled: ${surface.missingFeatureFlags.join(', ')}.`);
115
- }
116
- }
117
- const value = {
118
- controlPlane: {
119
- enabled: config.get('controlPlane.enabled'),
120
- hostMode: controlPlane.hostMode,
121
- configuredHost: controlPlane.configuredHost,
122
- host: controlPlane.host,
123
- port: controlPlane.port,
124
- reachable: controlPlaneReachable,
125
- },
126
- web: {
127
- enabled: config.get('web.enabled'),
128
- hostMode: web.hostMode,
129
- configuredHost: web.configuredHost,
130
- host: web.host,
131
- port: web.port,
132
- reachable: webReachable,
133
- },
134
- httpListener: {
135
- enabled: config.get('danger.httpListener'),
136
- hostMode: httpListener.hostMode,
137
- configuredHost: httpListener.configuredHost,
138
- host: httpListener.host,
139
- port: httpListener.port,
140
- reachable: listenerReachable,
141
- },
142
- surfaces: filteredSurfaces,
143
- readinessIssues,
144
- };
145
- const output = formatJsonOrText(runtime.cli)(value, [
146
- 'GoodVibes Agent surfaces',
147
- ` control-plane: ${yesNo(value.controlPlane.enabled)} (${value.controlPlane.hostMode} ${value.controlPlane.host}:${value.controlPlane.port})${includeProbe ? ` reachable=${yesNo(value.controlPlane.reachable)}` : ''}`,
148
- ` web: ${yesNo(value.web.enabled)} (${value.web.hostMode} ${value.web.host}:${value.web.port})${includeProbe ? ` reachable=${yesNo(value.web.reachable)}` : ''}`,
149
- ` http-listener: ${yesNo(value.httpListener.enabled)} (${value.httpListener.hostMode} ${value.httpListener.host}:${value.httpListener.port})${includeProbe ? ` reachable=${yesNo(value.httpListener.reachable)}` : ''}`,
150
- '',
151
- 'External surfaces:',
152
- ...value.surfaces.map((surface) => ` ${surface.label.padEnd(16)} enabled=${yesNo(surface.enabled)} ready=${yesNo(surface.ready)}${surface.enabled && surface.missing.length > 0 ? ` missing=${surface.missing.join(',')}` : ''}${surface.enabled && surface.missingFeatureFlags.length > 0 ? ` featureGates=${surface.missingFeatureFlags.join(',')}` : ''}`),
153
- ...(filteredSurfaces.some((surface) => surface.id === 'ntfy') ? [
154
- '',
155
- 'ntfy inbound topics:',
156
- ` chat: ${ntfyTopics.chatTopic}`,
157
- ` agent: ${ntfyTopics.agentTopic}`,
158
- ` runtime-only remote: ${ntfyTopics.remoteTopic}`,
159
- ` default delivery topic: ${String(config.get('surfaces.ntfy.topic') || '(none)')}`,
160
- ] : []),
161
- ...(includeProbe ? [
162
- readinessIssues.length === 0 ? 'Readiness: ready' : 'Readiness: needs attention',
163
- ...readinessIssues.map((issue) => ` - ${issue}`),
164
- ] : []),
165
- ].join('\n'));
166
- return { output, exitCode: includeProbe && readinessIssues.length > 0 ? 1 : 0 };
167
- }
168
-
169
- export interface ListenerTestResult {
170
- readonly enabled: unknown;
171
- readonly hostMode: string;
172
- readonly configuredHost: string;
173
- readonly host: string;
174
- readonly port: number;
175
- readonly posture: ReturnType<typeof classifyBindPosture>;
176
- readonly reachable: boolean;
177
- readonly service: {
178
- readonly enabled: unknown;
179
- readonly autostart: unknown;
180
- readonly restartOnFailure: unknown;
181
- };
182
- readonly auth: ReturnType<typeof readAuthPaths>;
183
- readonly surfaces: readonly {
184
- readonly id: string;
185
- readonly label: string;
186
- readonly enabled: unknown;
187
- readonly ready: boolean;
188
- readonly missing: readonly string[];
189
- readonly missingFeatureFlags: readonly string[];
190
- }[];
191
- readonly issues: readonly string[];
192
- }
193
-
194
- export async function buildListenerTestResult(runtime: CliCommandRuntime): Promise<ListenerTestResult> {
195
- const enabled = runtime.configManager.get('danger.httpListener');
196
- const binding = resolveRuntimeEndpointBinding(runtime.configManager, 'httpListener');
197
- const posture = classifyBindPosture(binding);
198
- const reachable = enabled === true ? await probeTcp(binding.host, binding.port) : false;
199
- const auth = readAuthPaths(runtime);
200
- const service = {
201
- enabled: runtime.configManager.get('service.enabled'),
202
- autostart: runtime.configManager.get('service.autostart'),
203
- restartOnFailure: runtime.configManager.get('service.restartOnFailure'),
204
- };
205
- const surfaces = SURFACE_CONFIGS.map(([id, label, requiredKeys]) => {
206
- const surfaceEnabled = runtime.configManager.get(`surfaces.${id}.enabled` as ConfigKey);
207
- const missing = requiredKeys.filter((key) => !isPresentConfigValue(runtime.configManager.get(key as ConfigKey)));
208
- const missingFeatureFlags = surfaceEnabled === true ? getMissingSurfaceFeatureFlags(runtime.configManager, id) : [];
209
- return {
210
- id,
211
- label,
212
- enabled: surfaceEnabled,
213
- ready: surfaceEnabled !== true || (missing.length === 0 && missingFeatureFlags.length === 0),
214
- missing,
215
- missingFeatureFlags,
216
- };
217
- }).filter((surface) => surface.enabled === true);
218
- const issues: string[] = [];
219
- if (enabled !== true) issues.push('HTTP listener is disabled.');
220
- if (enabled === true && service.enabled !== true) issues.push('HTTP listener is enabled on the external runtime config, but Agent service ownership is disabled.');
221
- if (enabled === true && service.autostart !== true) issues.push('HTTP listener is enabled on the external runtime config, but autostart is off.');
222
- if (enabled === true && service.restartOnFailure !== true) issues.push('HTTP listener is enabled on the external runtime config, but restart-on-failure is off.');
223
- if (isNetworkFacing(enabled, binding) && !auth.userStorePresent) issues.push('Network-facing listener has no local auth user store.');
224
- if (isNetworkFacing(enabled, binding) && auth.bootstrapCredentialPresent) issues.push('Network-facing listener still has a bootstrap credential file.');
225
- for (const surface of surfaces) {
226
- if (surface.missing.length > 0) issues.push(`${surface.label} is enabled but missing ${surface.missing.join(', ')}.`);
227
- if (surface.missingFeatureFlags.length > 0) issues.push(`${surface.label} is enabled but feature gates are disabled: ${surface.missingFeatureFlags.join(', ')}.`);
228
- }
229
- return { enabled, ...binding, posture, reachable, service, auth, surfaces, issues };
230
- }
231
-
232
- export function formatListenerTestResult(runtime: CliCommandRuntime, value: ListenerTestResult): string {
233
- return formatJsonOrText(runtime.cli)(value, [
234
- 'GoodVibes Agent listener test',
235
- ` enabled: ${yesNo(value.enabled)}`,
236
- ` endpoint: ${value.hostMode} ${value.host}:${value.port}`,
237
- ` bind posture: ${value.posture.label}`,
238
- ` reachable: ${yesNo(value.reachable)}`,
239
- ` service: enabled=${yesNo(value.service.enabled)} autostart=${yesNo(value.service.autostart)} restartOnFailure=${yesNo(value.service.restartOnFailure)}`,
240
- ` local auth users: ${value.auth.userStorePresent ? 'present' : 'missing'}`,
241
- ` bootstrap credential: ${value.auth.bootstrapCredentialPresent ? 'present' : 'missing'}`,
242
- value.surfaces.length === 0 ? ' enabled webhook surfaces: none' : ' enabled webhook surfaces:',
243
- ...value.surfaces.map((surface) => ` ${surface.label}: ready=${yesNo(surface.ready)}${surface.missing.length > 0 ? ` missing=${surface.missing.join(',')}` : ''}${surface.missingFeatureFlags.length > 0 ? ` featureGates=${surface.missingFeatureFlags.join(',')}` : ''}`),
244
- value.issues.length === 0 ? ' readiness: ready' : ' readiness: needs attention',
245
- ...value.issues.map((issue) => ` - ${issue}`),
246
- ].join('\n'));
247
- }
@@ -1,72 +0,0 @@
1
- import type { CommandRegistry } from '../command-registry.ts';
2
-
3
- export function registerBranchRuntimeCommands(registry: CommandRegistry): void {
4
- registry.register({
5
- name: 'fork',
6
- aliases: ['branch-save'],
7
- description: 'Save a named snapshot of the current conversation',
8
- usage: '[name]',
9
- argsHint: '[name]',
10
- handler(args, ctx) {
11
- const name = args[0];
12
- const branchName = ctx.session.conversationManager.forkBranch(name);
13
- const msgCount = ctx.session.conversationManager.getMessageCount();
14
- ctx.print(`Forked conversation as "${branchName}" (${msgCount} message${msgCount === 1 ? '' : 's'}).`);
15
- },
16
- });
17
-
18
- registry.register({
19
- name: 'branch',
20
- aliases: ['br'],
21
- description: 'List conversation branches or switch to one',
22
- usage: '[name]',
23
- argsHint: '[name]',
24
- handler(args, ctx) {
25
- if (args.length === 0) {
26
- const branches = ctx.session.conversationManager.listBranches();
27
- if (branches.length === 0) {
28
- ctx.print('No branches. Use /fork [name] to create one.');
29
- return;
30
- }
31
- const current = ctx.session.conversationManager.getCurrentBranch();
32
- const lines = [`Branches (current: ${current}):`];
33
- for (const branch of branches) {
34
- const marker = branch.isCurrent ? '▶' : ' ';
35
- lines.push(` ${marker} ${branch.name} (${branch.messageCount} message${branch.messageCount === 1 ? '' : 's'})`);
36
- }
37
- ctx.print(lines.join('\n'));
38
- return;
39
- }
40
- const name = args[0];
41
- const ok = ctx.session.conversationManager.switchBranch(name);
42
- if (!ok) {
43
- ctx.print(`Branch "${name}" not found. Use /fork [name] to create one, or /branch to list.`);
44
- return;
45
- }
46
- ctx.print(`Switched to branch "${name}".`);
47
- ctx.renderRequest();
48
- },
49
- });
50
-
51
- registry.register({
52
- name: 'merge',
53
- aliases: [],
54
- description: 'Append messages from a branch after the fork point',
55
- usage: '<name>',
56
- argsHint: '<name>',
57
- handler(args, ctx) {
58
- const name = args[0];
59
- if (!name) {
60
- ctx.print('Usage: /merge <branch-name>\nSee /branch for available branches.');
61
- return;
62
- }
63
- const ok = ctx.session.conversationManager.mergeBranch(name);
64
- if (!ok) {
65
- ctx.print(`Branch "${name}" not found. Use /branch to list available branches.`);
66
- return;
67
- }
68
- ctx.print(`Merged branch "${name}" into current conversation.`);
69
- ctx.renderRequest();
70
- },
71
- });
72
- }
@@ -1,234 +0,0 @@
1
- import type { CommandRegistry } from '../command-registry.ts';
2
- import { buildMcpAttackPathReview } from '@/runtime/index.ts';
3
- import { buildKnowledgeInjectionPrompt, selectKnowledgeForTask } from '@pellux/goodvibes-sdk/platform/state';
4
- import { listBuiltinSubscriptionProviders } from '@pellux/goodvibes-sdk/platform/config';
5
- import { requireReadModels, requireSubscriptionManager, requireTokenAuditor } from './runtime-services.ts';
6
- import { getMemoryApi } from './recall-query.ts';
7
-
8
- export function registerControlRoomRuntimeCommands(registry: CommandRegistry): void {
9
- registry.register({
10
- name: 'cockpit',
11
- aliases: [],
12
- description: 'Open the unified operator cockpit',
13
- usage: '',
14
- handler(_args, ctx) {
15
- if (ctx.openCockpitPanel) {
16
- ctx.openCockpitPanel();
17
- return;
18
- }
19
- ctx.print('Cockpit panel is not available in this runtime.');
20
- },
21
- });
22
-
23
- registry.register({
24
- name: 'orchestration',
25
- aliases: ['orch'],
26
- description: 'Inspect orchestration graphs; local Agent graph cancellation is blocked',
27
- usage: '[show [graphId]]',
28
- handler(args, ctx) {
29
- const graphs = [...requireReadModels(ctx).orchestration.getSnapshot().graphs];
30
- if (args.length === 0) {
31
- if (ctx.openOrchestrationPanel) {
32
- ctx.openOrchestrationPanel();
33
- return;
34
- }
35
- if (graphs.length === 0) {
36
- ctx.print('Orchestration panel is not available in this runtime.');
37
- return;
38
- }
39
- }
40
- const subcommand = args[0]?.toLowerCase() ?? 'show';
41
-
42
- if (subcommand === 'show') {
43
- const graphId = args[1];
44
- const graph = graphId ? graphs.find((entry) => entry.id === graphId) : graphs[0];
45
- if (!graph) {
46
- ctx.print(graphId ? `Unknown orchestration graph: ${graphId}` : 'No orchestration graphs recorded yet.');
47
- return;
48
- }
49
- const lines = [
50
- `Graph ${graph.id}`,
51
- ` title: ${graph.title}`,
52
- ` status: ${graph.status}`,
53
- ` mode: ${graph.mode}`,
54
- ` nodes: ${graph.nodeOrder.length}`,
55
- ];
56
- if (graph.lastRecursionGuard) {
57
- lines.push(` last guard: depth ${graph.lastRecursionGuard.depth}, active ${graph.lastRecursionGuard.activeAgents}, ${graph.lastRecursionGuard.reason}`);
58
- }
59
- for (const nodeId of graph.nodeOrder.slice(0, 12)) {
60
- const node = graph.nodes.get(nodeId);
61
- if (!node) continue;
62
- lines.push(` - ${node.id} ${node.role} ${node.status} ${node.title}`);
63
- }
64
- ctx.print(lines.join('\n'));
65
- return;
66
- }
67
-
68
- if (subcommand === 'cancel') {
69
- ctx.print([
70
- 'GoodVibes Agent orchestration is read-only.',
71
- 'Local graph/subtree cancellation belongs to the copied coding runtime and is blocked here.',
72
- 'For explicit build/fix/review work, use /delegate so GoodVibes TUI owns the execution chain.',
73
- ].join('\n'));
74
- return;
75
- }
76
-
77
- ctx.print(`Unknown orchestration subcommand: ${subcommand}`);
78
- },
79
- });
80
-
81
- registry.register({
82
- name: 'communication',
83
- aliases: ['comms'],
84
- description: 'Inspect structured agent communication routes and recent activity',
85
- usage: '',
86
- handler(_args, ctx) {
87
- if (ctx.openCommunicationPanel) {
88
- ctx.openCommunicationPanel();
89
- return;
90
- }
91
- ctx.print('Communication panel is not available in this runtime.');
92
- },
93
- });
94
-
95
- registry.register({
96
- name: 'security',
97
- aliases: [],
98
- description: 'Inspect security posture, attack paths, and review state',
99
- usage: '[review | attack-paths | tokens]',
100
- handler(args, ctx) {
101
- if (args.length === 0) {
102
- if (ctx.openSecurityPanel) {
103
- ctx.openSecurityPanel();
104
- return;
105
- }
106
- ctx.print('Security panel is not available in this runtime.');
107
- return;
108
- }
109
-
110
- const subcommand = args[0]?.toLowerCase() ?? 'review';
111
- const audit = requireTokenAuditor(ctx).auditAll(Date.now());
112
- const securitySnapshot = requireReadModels(ctx).security.getSnapshot();
113
- const policySnapshot = ctx.extensions.policyRuntimeState?.getSnapshot();
114
- if (!policySnapshot) {
115
- ctx.print('Policy runtime state is not available in this runtime.');
116
- return;
117
- }
118
- const attackPaths = buildMcpAttackPathReview({
119
- servers: securitySnapshot.mcpServers,
120
- recentDecisions: securitySnapshot.recentMcpDecisions,
121
- });
122
-
123
- if (subcommand === 'tokens') {
124
- if (audit.results.length === 0) {
125
- ctx.print('No registered API tokens are currently under audit.');
126
- return;
127
- }
128
- ctx.print([
129
- `Token Audit (${audit.results.length})`,
130
- ...audit.results.map((result) => (
131
- ` ${result.label} policy=${result.scope.policyId} scope=${result.scope.outcome} rotation=${result.rotation.outcome} blocked=${result.blocked ? 'yes' : 'no'}`
132
- )),
133
- ].join('\n'));
134
- return;
135
- }
136
-
137
- if (subcommand === 'attack-paths') {
138
- if (attackPaths.findings.length === 0) {
139
- ctx.print('No MCP attack-path findings are currently active.');
140
- return;
141
- }
142
- ctx.print([
143
- `MCP Attack-Path Review`,
144
- ` summary: ${attackPaths.summary}`,
145
- ...attackPaths.findings.slice(0, 12).map((finding) => (
146
- ` ${finding.severity.toUpperCase()} ${finding.serverName} ${finding.route}\n ${finding.reason}`
147
- )),
148
- ].join('\n'));
149
- return;
150
- }
151
-
152
- const plugins = ctx.extensions.pluginManager?.list() ?? [];
153
- const subscriptions = requireSubscriptionManager(ctx);
154
- const builtinProviders = listBuiltinSubscriptionProviders();
155
- ctx.print([
156
- 'Security Review',
157
- ` tokens: ${audit.results.length}`,
158
- ` blocked tokens: ${audit.blocked.length}`,
159
- ` scope violations: ${audit.scopeViolations.length}`,
160
- ` rotation overdue: ${audit.rotationOverdue.length}`,
161
- ` rotation warnings: ${audit.rotationWarnings.length}`,
162
- ` built-in subscription providers: ${builtinProviders.length}`,
163
- ` active subscriptions: ${subscriptions.list().length}`,
164
- ` pending subscriptions: ${subscriptions.listPending().length}`,
165
- ` policy lint findings: ${policySnapshot.lintFindings.length}`,
166
- ` policy preflight: ${policySnapshot.lastPreflightReview?.status ?? 'n/a'}`,
167
- ` mcp servers: ${securitySnapshot.mcpServers.length}`,
168
- ` mcp quarantined: ${securitySnapshot.mcpServers.filter((server) => server.schemaFreshness === 'quarantined').length}`,
169
- ` mcp elevated: ${securitySnapshot.mcpServers.filter((server) => server.trustMode === 'allow-all').length}`,
170
- ` mcp attack-path findings: ${attackPaths.findings.length}`,
171
- ` quarantined plugins: ${plugins.filter((plugin) => plugin.quarantined).length}`,
172
- ` untrusted plugins: ${plugins.filter((plugin) => plugin.trustTier === 'untrusted').length}`,
173
- ].join('\n'));
174
- },
175
- });
176
-
177
- registry.register({
178
- name: 'knowledge',
179
- aliases: ['know'],
180
- description: 'Inspect durable project knowledge, risks, runbooks, and architecture notes',
181
- usage: '[open | queue [limit] | explain <task...> [--scope <path> ...]]',
182
- handler(args, ctx) {
183
- const subcommand = (args[0] ?? 'open').toLowerCase();
184
- if (subcommand === 'open') {
185
- if (ctx.openKnowledgePanel) {
186
- ctx.openKnowledgePanel();
187
- return;
188
- }
189
- ctx.print('Knowledge panel is not available in this runtime.');
190
- return;
191
- }
192
- const memory = getMemoryApi(ctx);
193
- if (!memory) return;
194
- if (subcommand === 'queue') {
195
- const limit = Math.max(1, parseInt(args[1] ?? '10', 10) || 10);
196
- const queue = memory.reviewQueue(limit);
197
- if (queue.length === 0) {
198
- ctx.print('Knowledge review queue is empty.');
199
- return;
200
- }
201
- ctx.print([
202
- `Knowledge Review Queue (${queue.length})`,
203
- ...queue.map((record) => ` ${record.id} [${record.scope}/${record.cls}] ${record.reviewState} ${record.confidence}% ${record.summary}`),
204
- ].join('\n'));
205
- return;
206
- }
207
- if (subcommand === 'explain') {
208
- const scopeIdx = args.indexOf('--scope');
209
- const scopeValues = scopeIdx !== -1
210
- ? args.slice(scopeIdx + 1).filter((token) => !token.startsWith('--'))
211
- : [];
212
- const taskTokens = args.slice(1).filter((token, index) => {
213
- if (token === '--scope') return false;
214
- if (scopeIdx !== -1 && index + 1 > scopeIdx) return false;
215
- return true;
216
- });
217
- const task = taskTokens.join(' ').trim();
218
- if (!task) {
219
- ctx.print('Usage: /knowledge explain <task...> [--scope <path> ...]');
220
- return;
221
- }
222
- const injections = selectKnowledgeForTask(memory, task, scopeValues);
223
- const prompt = buildKnowledgeInjectionPrompt(injections);
224
- ctx.print(prompt ?? 'No reviewed project knowledge matched that task.');
225
- return;
226
- }
227
- if (ctx.openKnowledgePanel) {
228
- ctx.openKnowledgePanel();
229
- return;
230
- }
231
- ctx.print(`Unknown knowledge subcommand: ${subcommand}`);
232
- },
233
- });
234
- }
@@ -1,61 +0,0 @@
1
- import type { CommandRegistry } from '../command-registry.ts';
2
- import { scan, persistProviders } from '@pellux/goodvibes-sdk/platform/discovery';
3
- import { requireProviderApi, requireShellPaths } from './runtime-services.ts';
4
- import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
5
- import { GOODVIBES_AGENT_SURFACE_ROOT } from '../../config/surface.ts';
6
- import { requireYesFlag, stripYesFlag } from './confirmation.ts';
7
-
8
- export function registerDiscoveryRuntimeCommands(registry: CommandRegistry): void {
9
- registry.register({
10
- name: 'scan',
11
- aliases: [],
12
- description: 'Scan localhost and LAN for local LLM servers',
13
- usage: '[--yes]',
14
- async handler(args, ctx) {
15
- const { yes } = stripYesFlag(args);
16
- ctx.print('Scanning for local LLM servers...');
17
- ctx.renderRequest();
18
-
19
- const result = await scan();
20
-
21
- if (result.servers.length === 0) {
22
- ctx.print(
23
- `[Scan] No local LLM servers found (scanned ${result.scannedHosts} hosts, ` +
24
- `${result.scannedPorts} ports in ${Math.round(result.durationMs / 1000)}s)`,
25
- );
26
- } else {
27
- const lines = [
28
- `[Scan] Found ${result.servers.length} server(s) in ${Math.round(result.durationMs / 1000)}s:`,
29
- '',
30
- ...result.servers.map((server) =>
31
- ` ${server.name.padEnd(30)} ${server.models.length} model(s) ${server.host}:${server.port}`,
32
- ),
33
- '',
34
- 'Use /model to select a discovered model.',
35
- ];
36
- ctx.print(lines.join('\n'));
37
- }
38
-
39
- if (result.servers.length > 0) {
40
- if (!yes) {
41
- requireYesFlag(ctx, 'persist discovered local provider configuration', '/scan --yes');
42
- ctx.print('[Scan] Discovery results were not saved. Rerun /scan --yes to register and persist providers.');
43
- ctx.renderRequest();
44
- return;
45
- }
46
- try {
47
- await requireProviderApi(ctx).registerDiscoveredProviders(result.servers);
48
- } catch (err) {
49
- ctx.print(`[Scan] Warning: failed to register some providers: ${summarizeError(err)}`);
50
- }
51
- const shellPaths = requireShellPaths(ctx);
52
- persistProviders({
53
- homeDirectory: shellPaths.homeDirectory,
54
- surfaceRoot: GOODVIBES_AGENT_SURFACE_ROOT,
55
- }, result.servers);
56
- ctx.print('[Scan] Discovered providers registered and persisted.');
57
- }
58
- ctx.renderRequest();
59
- },
60
- });
61
- }