@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,432 +0,0 @@
|
|
|
1
|
-
import { resolve } from 'node:path';
|
|
2
|
-
import type { CommandRegistry, CommandContext } from '../command-registry.ts';
|
|
3
|
-
import { AGENT_TEMPLATES } from '@pellux/goodvibes-sdk/platform/tools';
|
|
4
|
-
import { handleRemoteSetupCommand } from './remote-runtime-setup.ts';
|
|
5
|
-
import { handleRemotePoolCommand } from './remote-runtime-pool.ts';
|
|
6
|
-
import { requirePeerClient } from './runtime-services.ts';
|
|
7
|
-
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
8
|
-
|
|
9
|
-
type RemoteConnectionLike = { agentId: string };
|
|
10
|
-
type RemoteCancelContext = Pick<CommandContext, 'print'>;
|
|
11
|
-
|
|
12
|
-
function printRemoteDelegationBoundary(ctx: Pick<CommandContext, 'print'>, requestedAction: string): void {
|
|
13
|
-
ctx.print([
|
|
14
|
-
'GoodVibes Agent does not dispatch remote/local coding workers from this surface.',
|
|
15
|
-
` requested: ${requestedAction}`,
|
|
16
|
-
' policy: keep ordinary work serial in the main assistant conversation',
|
|
17
|
-
' build/fix/review: delegate one request to GoodVibes TUI through the public shared-session/build-delegation contract',
|
|
18
|
-
' preserve: full original user ask and executionIntent; let TUI own execution and WRFC chain details when explicitly requested',
|
|
19
|
-
].join('\n'));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function handleRemoteCancelCommand(
|
|
23
|
-
agentId: string | undefined,
|
|
24
|
-
activeConnections: RemoteConnectionLike[],
|
|
25
|
-
ctx: RemoteCancelContext,
|
|
26
|
-
): void {
|
|
27
|
-
if (!agentId) {
|
|
28
|
-
ctx.print('Usage: /remote cancel <agentId>');
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
const connection = activeConnections.find((entry) => entry.agentId === agentId);
|
|
32
|
-
if (!connection) {
|
|
33
|
-
ctx.print(`Unknown remote connection: ${agentId}`);
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
ctx.print([
|
|
37
|
-
'GoodVibes Agent remote control is read-only.',
|
|
38
|
-
` requested: /remote cancel ${agentId}`,
|
|
39
|
-
' policy: Agent does not cancel local remote-worker processes from this surface',
|
|
40
|
-
' next: inspect with /remote show or delegate explicit build/fix/review work to GoodVibes TUI',
|
|
41
|
-
].join('\n'));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function registerRemoteRuntimeCommands(registry: CommandRegistry): void {
|
|
45
|
-
registry.register({
|
|
46
|
-
name: 'remote',
|
|
47
|
-
aliases: [],
|
|
48
|
-
description: 'Inspect remote workers, delegated work receipts, and review artifacts',
|
|
49
|
-
usage: '[list | show [agentId] | supervisor [workerId] | support [workerId] | recover [workerId] | setup [export <path> --yes] | env [export <path> --yes] | tunnel [review|export <path> --yes] | bootstrap [export <path> --yes|inspect <path>] | session <export|inspect|import> <path> [--yes] | pool <list|show> ... | dispatch [template] <description> | dispatch-pool <pool> [template] <description> | contract [workerId] | cancel <agentId> | export <agentId> [path] --yes | artifact list | artifact show <id> | artifact export <id> [path] --yes | review <id> | rerun-local <id> | import <path> --yes]',
|
|
50
|
-
async handler(args, ctx) {
|
|
51
|
-
if (args.length === 0) {
|
|
52
|
-
if (ctx.openRemotePanel) {
|
|
53
|
-
ctx.openRemotePanel();
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
ctx.print('Remote panel is not available in this runtime.');
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
let peerClient;
|
|
61
|
-
try {
|
|
62
|
-
peerClient = requirePeerClient(ctx);
|
|
63
|
-
} catch {
|
|
64
|
-
ctx.print('Remote runtime services are not available in this runtime.');
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
const peerSnapshot = peerClient.getSnapshot();
|
|
68
|
-
const remoteRunners = peerClient.runners;
|
|
69
|
-
const activeConnections = [...peerSnapshot.acp.activeConnections];
|
|
70
|
-
const subcommand = args[0]?.toLowerCase() ?? 'show';
|
|
71
|
-
|
|
72
|
-
if (await handleRemoteSetupCommand(args, ctx, activeConnections, {
|
|
73
|
-
listContracts: () => remoteRunners.listContracts(),
|
|
74
|
-
exportSessionBundle: (path) => remoteRunners.exportSessionBundle(path),
|
|
75
|
-
importSessionBundle: (path) => remoteRunners.importSessionBundle(path),
|
|
76
|
-
})) {
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (subcommand === 'list') {
|
|
81
|
-
const supervisor = peerSnapshot.supervisor;
|
|
82
|
-
const contracts = peerSnapshot.runners.contracts;
|
|
83
|
-
const pools = peerSnapshot.runners.pools;
|
|
84
|
-
const artifacts = peerSnapshot.runners.artifacts;
|
|
85
|
-
const lines = [
|
|
86
|
-
'Remote Control Surface',
|
|
87
|
-
` active connections: ${activeConnections.length}`,
|
|
88
|
-
` worker contracts: ${contracts.length}`,
|
|
89
|
-
` worker pools: ${pools.length}`,
|
|
90
|
-
` review artifacts: ${artifacts.length}`,
|
|
91
|
-
` supervisor sessions: ${supervisor.sessions.length}`,
|
|
92
|
-
` degraded sessions: ${supervisor.degradedConnections}`,
|
|
93
|
-
];
|
|
94
|
-
if (activeConnections.length > 0) {
|
|
95
|
-
lines.push(' connections:');
|
|
96
|
-
for (const connection of activeConnections.slice(0, 12)) {
|
|
97
|
-
lines.push(` ${connection.agentId} ${connection.transportState} msgs=${connection.messageCount} errs=${connection.errorCount} ${connection.label}`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
if (contracts.length > 0) {
|
|
101
|
-
lines.push(' contracts:');
|
|
102
|
-
for (const contract of contracts.slice(0, 12)) {
|
|
103
|
-
lines.push(` ${contract.runnerId} ${contract.template} ${contract.transport.state} ${contract.capabilityCeiling.executionProtocol}/${contract.capabilityCeiling.reviewMode}`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
ctx.print(lines.join('\n'));
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (subcommand === 'supervisor') {
|
|
111
|
-
const snapshot = peerSnapshot.supervisor;
|
|
112
|
-
const runnerId = args[1];
|
|
113
|
-
const selected = runnerId
|
|
114
|
-
? snapshot.sessions.find((entry) => entry.runnerId === runnerId)
|
|
115
|
-
: snapshot.sessions[0];
|
|
116
|
-
if (!selected) {
|
|
117
|
-
ctx.print(runnerId ? `Unknown remote supervisor session: ${runnerId}` : 'No remote supervisor sessions are currently tracked.');
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
ctx.print([
|
|
121
|
-
`Remote Supervisor ${selected.runnerId}`,
|
|
122
|
-
` label: ${selected.label}`,
|
|
123
|
-
` transport: ${selected.transportState}`,
|
|
124
|
-
` heartbeat: ${selected.heartbeat.status}`,
|
|
125
|
-
` heartbeat detail: ${selected.heartbeat.detail}`,
|
|
126
|
-
` executionProtocol: ${selected.negotiation.executionProtocol}`,
|
|
127
|
-
` reviewMode: ${selected.negotiation.reviewMode}`,
|
|
128
|
-
` communicationLane: ${selected.negotiation.communicationLane}`,
|
|
129
|
-
` trustClass: ${selected.negotiation.trustClass}`,
|
|
130
|
-
` taskId: ${selected.taskId ?? 'n/a'}`,
|
|
131
|
-
` messageCount: ${selected.messageCount}`,
|
|
132
|
-
` errorCount: ${selected.errorCount}`,
|
|
133
|
-
...(selected.lastError ? [` lastError: ${selected.lastError}`] : []),
|
|
134
|
-
' worker support:',
|
|
135
|
-
...selected.capabilities.map((capability) => ` ${capability.id}: ${capability.supported ? 'yes' : 'no'} (${capability.detail})`),
|
|
136
|
-
' recovery:',
|
|
137
|
-
...selected.recovery.map((action) => ` ${action.command} — ${action.reason}`),
|
|
138
|
-
].join('\n'));
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (subcommand === 'support') {
|
|
143
|
-
const snapshot = peerSnapshot.supervisor;
|
|
144
|
-
const runnerId = args[1];
|
|
145
|
-
const selected = runnerId
|
|
146
|
-
? snapshot.sessions.find((entry) => entry.runnerId === runnerId)
|
|
147
|
-
: snapshot.sessions[0];
|
|
148
|
-
if (!selected) {
|
|
149
|
-
ctx.print(runnerId ? `Unknown remote worker: ${runnerId}` : 'No remote supervisor sessions are currently tracked.');
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
ctx.print([
|
|
153
|
-
`Remote Support ${selected.runnerId}`,
|
|
154
|
-
` label: ${selected.label}`,
|
|
155
|
-
` transport: ${selected.transportState}`,
|
|
156
|
-
` executionProtocol: ${selected.negotiation.executionProtocol}`,
|
|
157
|
-
` reviewMode: ${selected.negotiation.reviewMode}`,
|
|
158
|
-
` communicationLane: ${selected.negotiation.communicationLane}`,
|
|
159
|
-
` trustClass: ${selected.negotiation.trustClass}`,
|
|
160
|
-
' worker support:',
|
|
161
|
-
...selected.capabilities.map((capability) => (
|
|
162
|
-
` ${capability.id}: ${capability.supported ? 'supported' : 'missing'} — ${capability.detail}`
|
|
163
|
-
)),
|
|
164
|
-
].join('\n'));
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (subcommand === 'recover') {
|
|
169
|
-
const snapshot = peerSnapshot.supervisor;
|
|
170
|
-
const runnerId = args[1];
|
|
171
|
-
const selected = runnerId
|
|
172
|
-
? snapshot.sessions.find((entry) => entry.runnerId === runnerId)
|
|
173
|
-
: snapshot.sessions.find((entry) => entry.recovery.length > 0) ?? snapshot.sessions[0];
|
|
174
|
-
if (!selected) {
|
|
175
|
-
ctx.print(runnerId ? `Unknown remote worker: ${runnerId}` : 'No remote supervisor sessions are currently tracked.');
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
const nextSteps = selected.recovery.length > 0
|
|
179
|
-
? selected.recovery
|
|
180
|
-
: [{
|
|
181
|
-
id: 'show',
|
|
182
|
-
label: 'Review remote runtime',
|
|
183
|
-
command: `/remote show ${selected.runnerId}`,
|
|
184
|
-
reason: 'Inspect the current remote session before deciding on recovery.',
|
|
185
|
-
}];
|
|
186
|
-
ctx.print([
|
|
187
|
-
`Remote Recovery ${selected.runnerId}`,
|
|
188
|
-
` label: ${selected.label}`,
|
|
189
|
-
` transport: ${selected.transportState}`,
|
|
190
|
-
` heartbeat: ${selected.heartbeat.status}`,
|
|
191
|
-
` detail: ${selected.heartbeat.detail}`,
|
|
192
|
-
...(selected.lastError ? [` lastError: ${selected.lastError}`] : []),
|
|
193
|
-
...(selected.taskId ? [` bound task: ${selected.taskId}`] : []),
|
|
194
|
-
' actions:',
|
|
195
|
-
...nextSteps.map((action) => ` ${action.command} — ${action.reason}`),
|
|
196
|
-
].join('\n'));
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (handleRemotePoolCommand(args, ctx, remoteRunners)) {
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (subcommand === 'show') {
|
|
205
|
-
const agentId = args[1];
|
|
206
|
-
const connection = agentId
|
|
207
|
-
? activeConnections.find((entry) => entry.agentId === agentId)
|
|
208
|
-
: activeConnections[0];
|
|
209
|
-
if (!connection) {
|
|
210
|
-
ctx.print(agentId ? `Unknown remote connection: ${agentId}` : 'No active remote connections.');
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
const contract = remoteRunners.getContract(connection.agentId);
|
|
214
|
-
ctx.print([
|
|
215
|
-
`Remote connection ${connection.agentId}`,
|
|
216
|
-
` label: ${connection.label}`,
|
|
217
|
-
` transport: ${connection.transportState}`,
|
|
218
|
-
` completing: ${connection.completing ? 'yes' : 'no'}`,
|
|
219
|
-
` connectedAt: ${connection.connectedAt ? new Date(connection.connectedAt).toISOString() : 'n/a'}`,
|
|
220
|
-
` messageCount: ${connection.messageCount}`,
|
|
221
|
-
` errorCount: ${connection.errorCount}`,
|
|
222
|
-
` taskId: ${connection.taskId ?? 'n/a'}`,
|
|
223
|
-
` lastError: ${connection.lastError ?? 'n/a'}`,
|
|
224
|
-
` contract: ${contract?.id ?? 'n/a'}`,
|
|
225
|
-
` pool: ${contract?.poolId ?? 'n/a'}`,
|
|
226
|
-
` executionProtocol: ${contract?.capabilityCeiling.executionProtocol ?? 'n/a'}`,
|
|
227
|
-
` reviewMode: ${contract?.capabilityCeiling.reviewMode ?? 'n/a'}`,
|
|
228
|
-
` communicationLane: ${contract?.capabilityCeiling.communicationLane ?? 'n/a'}`,
|
|
229
|
-
].join('\n'));
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if (subcommand === 'dispatch') {
|
|
234
|
-
let template = 'general';
|
|
235
|
-
let descriptionArgs = args.slice(1);
|
|
236
|
-
if (descriptionArgs.length > 0 && descriptionArgs[0] in AGENT_TEMPLATES) {
|
|
237
|
-
template = descriptionArgs[0]!;
|
|
238
|
-
descriptionArgs = descriptionArgs.slice(1);
|
|
239
|
-
}
|
|
240
|
-
const description = descriptionArgs.join(' ').trim();
|
|
241
|
-
if (description.length === 0) {
|
|
242
|
-
ctx.print('Usage: /remote dispatch [template] <description>');
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
printRemoteDelegationBoundary(ctx, `/remote dispatch ${template} ${description}`);
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (subcommand === 'dispatch-pool') {
|
|
250
|
-
const poolId = args[1];
|
|
251
|
-
if (!poolId) {
|
|
252
|
-
ctx.print('Usage: /remote dispatch-pool <pool> [template] <description>');
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
const pool = remoteRunners.getPool(poolId);
|
|
256
|
-
if (!pool) {
|
|
257
|
-
ctx.print(`Unknown remote worker pool: ${poolId}`);
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
let template = pool.preferredTemplate ?? 'general';
|
|
261
|
-
let descriptionArgs = args.slice(2);
|
|
262
|
-
if (descriptionArgs.length > 0 && descriptionArgs[0] in AGENT_TEMPLATES) {
|
|
263
|
-
template = descriptionArgs[0]!;
|
|
264
|
-
descriptionArgs = descriptionArgs.slice(1);
|
|
265
|
-
}
|
|
266
|
-
const description = descriptionArgs.join(' ').trim();
|
|
267
|
-
if (description.length === 0) {
|
|
268
|
-
ctx.print('Usage: /remote dispatch-pool <pool> [template] <description>');
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
printRemoteDelegationBoundary(ctx, `/remote dispatch-pool ${pool.id} ${template} ${description}`);
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (subcommand === 'contract') {
|
|
276
|
-
const agentId = args[1] ?? activeConnections[0]?.agentId;
|
|
277
|
-
if (!agentId) {
|
|
278
|
-
ctx.print('No remote worker contracts are available yet.');
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
const contract = remoteRunners.getContract(agentId);
|
|
282
|
-
if (!contract) {
|
|
283
|
-
ctx.print(`Unknown remote worker: ${agentId}`);
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
ctx.print([
|
|
287
|
-
`Remote worker contract ${contract.id}`,
|
|
288
|
-
` workerId: ${contract.runnerId}`,
|
|
289
|
-
` label: ${contract.label}`,
|
|
290
|
-
` pool: ${contract.poolId ?? '(none)'}`,
|
|
291
|
-
` trustClass: ${contract.trustClass}`,
|
|
292
|
-
` template: ${contract.template}`,
|
|
293
|
-
` transport: ${contract.transport.state}`,
|
|
294
|
-
` tools: ${contract.capabilityCeiling.allowedTools.join(', ') || '(none)'}`,
|
|
295
|
-
` ceiling: ${contract.capabilityCeiling.capabilityCeilingTools.join(', ') || '(none)'}`,
|
|
296
|
-
` protocol: ${contract.capabilityCeiling.executionProtocol}`,
|
|
297
|
-
` reviewMode: ${contract.capabilityCeiling.reviewMode}`,
|
|
298
|
-
` communicationLane: ${contract.capabilityCeiling.communicationLane}`,
|
|
299
|
-
` writeScope: ${contract.capabilityCeiling.writeScope.join(', ') || '(none)'}`,
|
|
300
|
-
].join('\n'));
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if (subcommand === 'cancel') {
|
|
305
|
-
handleRemoteCancelCommand(
|
|
306
|
-
args[1],
|
|
307
|
-
activeConnections,
|
|
308
|
-
ctx,
|
|
309
|
-
);
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (subcommand === 'export') {
|
|
314
|
-
const { rest, yes } = stripYesFlag(args);
|
|
315
|
-
const agentId = rest[1];
|
|
316
|
-
if (!agentId) {
|
|
317
|
-
ctx.print('Usage: /remote export <agentId> [path] --yes');
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
if (!yes) {
|
|
321
|
-
requireYesFlag(ctx, `export remote review artifact for ${agentId}`, '/remote export <agentId> [path] --yes');
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
const artifact = remoteRunners.captureArtifactForRunner(agentId);
|
|
325
|
-
if (!artifact) {
|
|
326
|
-
ctx.print(`Remote artifact export failed for ${agentId}.`);
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
const exported = await remoteRunners.exportArtifact(artifact.id, rest[2]);
|
|
330
|
-
if (!exported) {
|
|
331
|
-
ctx.print(`Remote artifact export failed for ${agentId}.`);
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
ctx.print(`Exported remote review artifact ${exported.artifact.id} to ${exported.path}`);
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
if (subcommand === 'artifact') {
|
|
339
|
-
const mode = args[1]?.toLowerCase() ?? 'list';
|
|
340
|
-
if (mode === 'list') {
|
|
341
|
-
const artifacts = remoteRunners.listArtifacts();
|
|
342
|
-
if (artifacts.length === 0) {
|
|
343
|
-
ctx.print('No remote review artifacts captured yet.');
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
ctx.print([
|
|
347
|
-
`Remote Review Artifacts (${artifacts.length})`,
|
|
348
|
-
...artifacts.slice(0, 12).map((artifact) => (
|
|
349
|
-
` ${artifact.id} ${artifact.runnerId} ${artifact.task.status} ${artifact.task.summary}`
|
|
350
|
-
)),
|
|
351
|
-
].join('\n'));
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
if (mode === 'show') {
|
|
355
|
-
const artifactId = args[2];
|
|
356
|
-
if (!artifactId) {
|
|
357
|
-
ctx.print('Usage: /remote artifact show <artifactId>');
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
const summary = remoteRunners.buildReviewSummary(artifactId);
|
|
361
|
-
ctx.print(summary ?? `Unknown remote artifact: ${artifactId}`);
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
if (mode === 'export') {
|
|
365
|
-
const { rest, yes } = stripYesFlag(args);
|
|
366
|
-
const artifactId = rest[2];
|
|
367
|
-
if (!artifactId) {
|
|
368
|
-
ctx.print('Usage: /remote artifact export <artifactId> [path] --yes');
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
if (!yes) {
|
|
372
|
-
requireYesFlag(ctx, `export remote review artifact ${artifactId}`, '/remote artifact export <artifactId> [path] --yes');
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
const exported = await remoteRunners.exportArtifact(artifactId, rest[3]);
|
|
376
|
-
if (!exported) {
|
|
377
|
-
ctx.print(`Unknown remote artifact: ${artifactId}`);
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
ctx.print(`Exported remote review artifact ${exported.artifact.id} to ${exported.path}`);
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
ctx.print(`Unknown remote artifact subcommand: ${mode}`);
|
|
384
|
-
return;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
if (subcommand === 'review') {
|
|
388
|
-
const artifactId = args[1];
|
|
389
|
-
if (!artifactId) {
|
|
390
|
-
ctx.print('Usage: /remote review <artifactId>');
|
|
391
|
-
return;
|
|
392
|
-
}
|
|
393
|
-
const summary = remoteRunners.buildReviewSummary(artifactId);
|
|
394
|
-
ctx.print(summary ?? `Unknown remote artifact: ${artifactId}`);
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
if (subcommand === 'rerun-local') {
|
|
399
|
-
const artifactId = args[1];
|
|
400
|
-
if (!artifactId) {
|
|
401
|
-
ctx.print('Usage: /remote rerun-local <artifactId>');
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
const artifact = remoteRunners.getArtifact(artifactId);
|
|
405
|
-
if (!artifact) {
|
|
406
|
-
ctx.print(`Unknown remote artifact: ${artifactId}`);
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
|
-
printRemoteDelegationBoundary(ctx, `/remote rerun-local ${artifactId}`);
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
if (subcommand === 'import') {
|
|
414
|
-
const { rest, yes } = stripYesFlag(args);
|
|
415
|
-
const path = rest[1];
|
|
416
|
-
if (!path) {
|
|
417
|
-
ctx.print('Usage: /remote import <path> --yes');
|
|
418
|
-
return;
|
|
419
|
-
}
|
|
420
|
-
if (!yes) {
|
|
421
|
-
requireYesFlag(ctx, `import remote review artifact from ${path}`, '/remote import <path> --yes');
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
const artifact = await remoteRunners.importArtifact(path);
|
|
425
|
-
ctx.print(`Imported remote review artifact ${artifact.id} for worker ${artifact.runnerId}.`);
|
|
426
|
-
return;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
ctx.print(`Unknown remote subcommand: ${subcommand}`);
|
|
430
|
-
},
|
|
431
|
-
});
|
|
432
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { CommandRegistry } from '../command-registry.ts';
|
|
2
|
-
import { handleReplayCommand } from '@pellux/goodvibes-sdk/platform/core';
|
|
3
|
-
import { requireReplayEngine } from './runtime-services.ts';
|
|
4
|
-
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
5
|
-
|
|
6
|
-
export function registerReplayRuntimeCommands(registry: CommandRegistry): void {
|
|
7
|
-
registry.register({
|
|
8
|
-
name: 'replay',
|
|
9
|
-
aliases: ['rep'],
|
|
10
|
-
description: 'Deterministic replay: load, step, seek, diff, and export recorded runs',
|
|
11
|
-
usage: '[load [runId] | step [n] | seek <rev> | diff | export <path> --yes]',
|
|
12
|
-
argsHint: '[load|step|seek|diff|export]',
|
|
13
|
-
handler(args, ctx) {
|
|
14
|
-
const command = args[0] ?? 'help';
|
|
15
|
-
const { rest, yes } = stripYesFlag(args);
|
|
16
|
-
if (command === 'export' && !yes) {
|
|
17
|
-
requireYesFlag(ctx, `export replay run to ${args[1] ?? '<path>'}`, '/replay export <path> --yes');
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
const replayEngine = requireReplayEngine(ctx);
|
|
21
|
-
const result = handleReplayCommand({ replayEngine }, command, rest.slice(1));
|
|
22
|
-
ctx.print(result.output);
|
|
23
|
-
},
|
|
24
|
-
});
|
|
25
|
-
}
|
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { dirname, join, resolve } from 'node:path';
|
|
3
|
-
import type { CommandRegistry } from '../command-registry.ts';
|
|
4
|
-
import type { SelectionAction, SelectionItem } from '../selection-modal.ts';
|
|
5
|
-
import { openCommandPanel, requireServiceRegistry, requireShellPaths } from './runtime-services.ts';
|
|
6
|
-
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
7
|
-
import { GOODVIBES_AGENT_SURFACE_ROOT } from '../../config/surface.ts';
|
|
8
|
-
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
9
|
-
|
|
10
|
-
export function registerServicesRuntimeCommands(registry: CommandRegistry): void {
|
|
11
|
-
registry.register({
|
|
12
|
-
name: 'services',
|
|
13
|
-
aliases: ['svc'],
|
|
14
|
-
description: 'Manage API service configurations',
|
|
15
|
-
usage: '[open|list|inspect <name>|test <name>|resolve <name>|auth <name>|auth-review|doctor|export <path> --yes|import <path> --yes]',
|
|
16
|
-
async handler(args, ctx) {
|
|
17
|
-
const parsed = stripYesFlag(args);
|
|
18
|
-
const commandArgs = [...parsed.rest];
|
|
19
|
-
const sub = commandArgs[0] ?? 'open';
|
|
20
|
-
const shellPaths = requireShellPaths(ctx);
|
|
21
|
-
if (sub === 'open' || sub === 'panel') {
|
|
22
|
-
openCommandPanel(ctx, 'services');
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
const svcRegistry = requireServiceRegistry(ctx);
|
|
26
|
-
const all = svcRegistry.getAll();
|
|
27
|
-
const keys = Object.keys(all);
|
|
28
|
-
if (sub === 'inspect') {
|
|
29
|
-
const name = commandArgs[1];
|
|
30
|
-
if (!name) {
|
|
31
|
-
ctx.print('Usage: /services inspect <name>');
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
const inspection = await svcRegistry.inspect(name);
|
|
35
|
-
if (!inspection) {
|
|
36
|
-
ctx.print(`Unknown service: ${name}`);
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
ctx.print([
|
|
40
|
-
`Service ${name}`,
|
|
41
|
-
` authType: ${inspection.config.authType}`,
|
|
42
|
-
` baseUrl: ${inspection.config.baseUrl ?? '(none)'}`,
|
|
43
|
-
` primaryCredential: ${inspection.hasPrimaryCredential ? 'present' : 'missing'}`,
|
|
44
|
-
` passwordCredential: ${inspection.hasPasswordCredential ? 'present' : 'missing'}`,
|
|
45
|
-
` webhookUrl: ${inspection.hasWebhookUrl ? 'present' : 'missing'}`,
|
|
46
|
-
` signingSecret: ${inspection.hasSigningSecret ? 'present' : 'missing'}`,
|
|
47
|
-
` publicKey: ${inspection.hasPublicKey ? 'present' : 'missing'}`,
|
|
48
|
-
` appToken: ${inspection.hasAppToken ? 'present' : 'missing'}`,
|
|
49
|
-
].join('\n'));
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
if (sub === 'test') {
|
|
53
|
-
const name = commandArgs[1];
|
|
54
|
-
if (!name) {
|
|
55
|
-
ctx.print('Usage: /services test <name>');
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
const result = await svcRegistry.testConnection(name);
|
|
59
|
-
ctx.print([
|
|
60
|
-
`Service test ${name}`,
|
|
61
|
-
` ok: ${result.ok ? 'yes' : 'no'}`,
|
|
62
|
-
` status: ${result.status ?? 'n/a'}`,
|
|
63
|
-
` url: ${result.testedUrl ?? 'n/a'}`,
|
|
64
|
-
` error: ${result.error ?? 'none'}`,
|
|
65
|
-
].join('\n'));
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
if (sub === 'resolve') {
|
|
69
|
-
const name = commandArgs[1];
|
|
70
|
-
if (!name) {
|
|
71
|
-
ctx.print('Usage: /services resolve <name>');
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
const headers = await svcRegistry.resolveAuth(name);
|
|
75
|
-
if (!headers) {
|
|
76
|
-
ctx.print(`Service ${name} has no resolvable auth headers right now.`);
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
ctx.print([
|
|
80
|
-
`Resolved auth headers for ${name}`,
|
|
81
|
-
...Object.keys(headers).map((key) => ` ${key}: <redacted>`),
|
|
82
|
-
].join('\n'));
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
if (sub === 'auth') {
|
|
86
|
-
const name = commandArgs[1];
|
|
87
|
-
if (!name) {
|
|
88
|
-
ctx.print('Usage: /services auth <name>');
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
const headers = await svcRegistry.resolveAuth(name);
|
|
92
|
-
if (!headers) {
|
|
93
|
-
ctx.print(`Service ${name} has no resolvable auth headers right now.`);
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
ctx.print([
|
|
97
|
-
`Service auth ${name}`,
|
|
98
|
-
...Object.keys(headers).map((key) => ` ${key}: <resolved>`),
|
|
99
|
-
].join('\n'));
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
if (sub === 'doctor') {
|
|
103
|
-
const inspections = await Promise.all(keys.map((name) => svcRegistry.inspect(name)));
|
|
104
|
-
const issues = inspections
|
|
105
|
-
.filter((inspection): inspection is NonNullable<typeof inspection> => inspection !== null)
|
|
106
|
-
.flatMap((inspection) => {
|
|
107
|
-
const findings: string[] = [];
|
|
108
|
-
if (!inspection.hasPrimaryCredential) findings.push(`${inspection.config.name}: missing primary credential`);
|
|
109
|
-
if (inspection.config.authType === 'basic' && !inspection.hasPasswordCredential) findings.push(`${inspection.config.name}: missing password credential`);
|
|
110
|
-
if (!inspection.config.baseUrl) findings.push(`${inspection.config.name}: no baseUrl configured`);
|
|
111
|
-
return findings;
|
|
112
|
-
});
|
|
113
|
-
ctx.print([
|
|
114
|
-
'Service Doctor',
|
|
115
|
-
` configured: ${keys.length}`,
|
|
116
|
-
` issues: ${issues.length}`,
|
|
117
|
-
...(issues.length > 0 ? issues.map((issue) => ` ${issue}`) : [' all configured services passed readiness checks']),
|
|
118
|
-
].join('\n'));
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
if (sub === 'auth-review') {
|
|
122
|
-
const inspections = await Promise.all(keys.map((name) => svcRegistry.inspect(name)));
|
|
123
|
-
const authCounts = new Map<string, number>();
|
|
124
|
-
const issues = inspections
|
|
125
|
-
.filter((inspection): inspection is NonNullable<typeof inspection> => inspection !== null)
|
|
126
|
-
.flatMap((inspection) => {
|
|
127
|
-
authCounts.set(inspection.config.authType, (authCounts.get(inspection.config.authType) ?? 0) + 1);
|
|
128
|
-
const findings: string[] = [];
|
|
129
|
-
if (!inspection.config.baseUrl) findings.push(`${inspection.config.name}: missing baseUrl`);
|
|
130
|
-
if ((inspection.config.authType === 'bearer' || inspection.config.authType === 'api-key') && !inspection.hasPrimaryCredential) {
|
|
131
|
-
findings.push(`${inspection.config.name}: missing primary credential`);
|
|
132
|
-
}
|
|
133
|
-
if (inspection.config.authType === 'basic' && !inspection.hasPasswordCredential) {
|
|
134
|
-
findings.push(`${inspection.config.name}: missing password credential`);
|
|
135
|
-
}
|
|
136
|
-
return findings;
|
|
137
|
-
});
|
|
138
|
-
ctx.print([
|
|
139
|
-
'Service Auth Review',
|
|
140
|
-
` configured: ${keys.length}`,
|
|
141
|
-
...[...authCounts.entries()].map(([authType, count]) => ` ${authType}: ${count}`),
|
|
142
|
-
...(issues.length > 0 ? ['', ...issues.map((issue) => ` issue: ${issue}`)] : ['', ' all configured services have a complete auth posture']),
|
|
143
|
-
].join('\n'));
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
if (sub === 'export') {
|
|
147
|
-
const pathArg = commandArgs[1];
|
|
148
|
-
if (!pathArg) {
|
|
149
|
-
ctx.print('Usage: /services export <path> --yes');
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
if (!parsed.yes) {
|
|
153
|
-
requireYesFlag(ctx, `export services config to ${pathArg}`, '/services export <path> --yes');
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
const targetPath = shellPaths.resolveWorkspacePath(pathArg);
|
|
157
|
-
mkdirSync(dirname(targetPath), { recursive: true });
|
|
158
|
-
writeFileSync(targetPath, JSON.stringify(all, null, 2) + '\n', 'utf-8');
|
|
159
|
-
ctx.print(`Exported services config to ${targetPath}`);
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
if (sub === 'import') {
|
|
163
|
-
const pathArg = commandArgs[1];
|
|
164
|
-
if (!pathArg) {
|
|
165
|
-
ctx.print('Usage: /services import <path> --yes');
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
if (!parsed.yes) {
|
|
169
|
-
requireYesFlag(ctx, `import services config from ${pathArg}`, '/services import <path> --yes');
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
const sourcePath = shellPaths.resolveWorkspacePath(pathArg);
|
|
173
|
-
try {
|
|
174
|
-
const parsed = JSON.parse(readFileSync(sourcePath, 'utf-8')) as Record<string, unknown>;
|
|
175
|
-
const targetPath = shellPaths.resolveProjectPath(GOODVIBES_AGENT_SURFACE_ROOT, 'services.json');
|
|
176
|
-
mkdirSync(dirname(targetPath), { recursive: true });
|
|
177
|
-
writeFileSync(targetPath, JSON.stringify(parsed, null, 2) + '\n', 'utf-8');
|
|
178
|
-
ctx.print(`Imported services config from ${sourcePath}`);
|
|
179
|
-
} catch (error) {
|
|
180
|
-
ctx.print(`Failed to import services config: ${summarizeError(error)}`);
|
|
181
|
-
}
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
if (ctx.openSelection) {
|
|
185
|
-
const testAction = new Map<string, SelectionAction>([['t', 'select' as const]]);
|
|
186
|
-
const items: SelectionItem[] = keys.length === 0
|
|
187
|
-
? [{ id: '_empty', label: 'No services configured', detail: '.goodvibes/agent/services.json' }]
|
|
188
|
-
: keys.map((key) => ({ id: key, label: all[key].name ?? key, detail: `${all[key].authType} ${all[key].baseUrl ?? '(no url)'}`, actions: '[t] test' }));
|
|
189
|
-
ctx.openSelection('Services', items, { allowSearch: true, customActions: testAction }, (result) => {
|
|
190
|
-
if (!result || result.item.id === '_empty') return;
|
|
191
|
-
const svc = all[result.item.id];
|
|
192
|
-
if (!svc) return;
|
|
193
|
-
if (result.action === 'select') {
|
|
194
|
-
void svcRegistry.testConnection(result.item.id).then((testResult) => {
|
|
195
|
-
ctx.print([
|
|
196
|
-
`Service test ${result.item.id}`,
|
|
197
|
-
` ok: ${testResult.ok ? 'yes' : 'no'}`,
|
|
198
|
-
` status: ${testResult.status ?? 'n/a'}`,
|
|
199
|
-
` url: ${testResult.testedUrl ?? 'n/a'}`,
|
|
200
|
-
` error: ${testResult.error ?? 'none'}`,
|
|
201
|
-
].join('\n'));
|
|
202
|
-
});
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
ctx.print([
|
|
206
|
-
`Service ${result.item.id}`,
|
|
207
|
-
` authType: ${svc.authType}`,
|
|
208
|
-
` baseUrl: ${svc.baseUrl ?? '(none)'}`,
|
|
209
|
-
].join('\n'));
|
|
210
|
-
});
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
if (keys.length === 0) {
|
|
214
|
-
ctx.print('[services] No services configured. Add entries to .goodvibes/agent/services.json');
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
ctx.print(['Services:', '', ...keys.map((key) => ` ${key.padEnd(20)} ${all[key].authType.padEnd(10)} ${all[key].baseUrl ?? '(no url)'}`)].join('\n'));
|
|
218
|
-
},
|
|
219
|
-
});
|
|
220
|
-
}
|