@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.
- package/CHANGELOG.md +12 -0
- package/README.md +3 -3
- package/docs/README.md +2 -2
- package/docs/getting-started.md +1 -1
- package/docs/runtime-connection.md +37 -0
- package/package.json +43 -2
- package/src/agent/skill-discovery.ts +119 -0
- package/src/cli/config-overrides.ts +1 -5
- package/src/cli/entrypoint.ts +0 -6
- package/src/cli/help.ts +0 -43
- package/src/cli/index.ts +0 -2
- package/src/cli/management-commands.ts +1 -109
- package/src/cli/management.ts +1 -32
- package/src/cli/package-verification.ts +12 -4
- package/src/cli/parser.ts +0 -16
- package/src/cli/status.ts +1 -1
- package/src/cli/types.ts +0 -8
- package/src/input/commands/delegation-runtime.ts +0 -8
- package/src/input/commands/experience-runtime.ts +0 -177
- package/src/input/commands/guidance-runtime.ts +0 -69
- package/src/input/commands/local-runtime.ts +1 -57
- package/src/input/commands/local-setup-review.ts +1 -1
- package/src/input/commands/operator-runtime.ts +1 -145
- package/src/input/commands/platform-access-runtime.ts +2 -195
- package/src/input/commands/product-runtime.ts +0 -116
- package/src/input/commands/security-runtime.ts +88 -0
- package/src/input/commands/session-content.ts +0 -97
- package/src/input/commands/shell-core.ts +0 -13
- package/src/input/commands.ts +2 -95
- package/src/panels/builtin/operations.ts +3 -184
- package/src/panels/confirm-state.ts +1 -1
- package/src/panels/index.ts +0 -11
- package/src/version.ts +1 -1
- package/docs/deployment-and-services.md +0 -52
- package/src/cli/service-command.ts +0 -26
- package/src/cli/surface-command.ts +0 -247
- package/src/input/commands/branch-runtime.ts +0 -72
- package/src/input/commands/control-room-runtime.ts +0 -234
- package/src/input/commands/discovery-runtime.ts +0 -61
- package/src/input/commands/hooks-runtime.ts +0 -207
- package/src/input/commands/incident-runtime.ts +0 -106
- package/src/input/commands/integration-runtime.ts +0 -437
- package/src/input/commands/local-setup.ts +0 -288
- package/src/input/commands/managed-runtime.ts +0 -240
- package/src/input/commands/marketplace-runtime.ts +0 -305
- package/src/input/commands/memory-product-runtime.ts +0 -148
- package/src/input/commands/operator-panel-runtime.ts +0 -146
- package/src/input/commands/platform-services-runtime.ts +0 -271
- package/src/input/commands/profile-sync-runtime.ts +0 -110
- package/src/input/commands/provider.ts +0 -363
- package/src/input/commands/remote-runtime-pool.ts +0 -89
- package/src/input/commands/remote-runtime-setup.ts +0 -226
- package/src/input/commands/remote-runtime.ts +0 -432
- package/src/input/commands/replay-runtime.ts +0 -25
- package/src/input/commands/services-runtime.ts +0 -220
- package/src/input/commands/settings-sync-runtime.ts +0 -197
- package/src/input/commands/share-runtime.ts +0 -127
- package/src/input/commands/skills-runtime.ts +0 -226
- package/src/input/commands/teleport-runtime.ts +0 -68
- package/src/panels/cockpit-panel.ts +0 -183
- package/src/panels/communication-panel.ts +0 -153
- package/src/panels/control-plane-panel.ts +0 -211
- package/src/panels/forensics-panel.ts +0 -364
- package/src/panels/hooks-panel.ts +0 -239
- package/src/panels/incident-review-panel.ts +0 -197
- package/src/panels/marketplace-panel.ts +0 -212
- package/src/panels/ops-control-panel.ts +0 -150
- package/src/panels/ops-strategy-panel.ts +0 -235
- package/src/panels/orchestration-panel.ts +0 -272
- package/src/panels/plugins-panel.ts +0 -178
- package/src/panels/remote-panel.ts +0 -449
- package/src/panels/routes-panel.ts +0 -178
- package/src/panels/services-panel.ts +0 -231
- package/src/panels/settings-sync-panel.ts +0 -120
- package/src/panels/skills-panel.ts +0 -431
- package/src/panels/watchers-panel.ts +0 -193
- package/src/verification/live-verifier.ts +0 -588
- 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
|
-
}
|