@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,363 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* /provider command handler.
|
|
3
|
-
*
|
|
4
|
-
* Implements the Provider Optimizer panel commands:
|
|
5
|
-
*
|
|
6
|
-
* /provider route auto|manual — Set optimizer routing mode
|
|
7
|
-
* /provider explain-route — Print current route explanation
|
|
8
|
-
* /provider pin <provider:model> — Pin routing to a specific provider/model
|
|
9
|
-
* /provider fallback test — Simulate the fallback chain
|
|
10
|
-
*
|
|
11
|
-
* When the optimizer is disabled, commands report its status and
|
|
12
|
-
* explain-route still works (reads current model capabilities).
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import type { SlashCommand, CommandContext } from '../command-registry.ts';
|
|
16
|
-
import type { RouteExplanation } from '@pellux/goodvibes-sdk/platform/providers';
|
|
17
|
-
import type { FallbackTestResult, FallbackTransition } from '@pellux/goodvibes-sdk/platform/providers';
|
|
18
|
-
import type { ProviderApiModelRecord } from '@pellux/goodvibes-sdk/platform/providers';
|
|
19
|
-
import { requireProviderApi } from './runtime-services.ts';
|
|
20
|
-
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
// Formatting helpers
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
|
|
25
|
-
function fmtBool(value: boolean): string {
|
|
26
|
-
return value ? 'yes' : 'no';
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function fmtTs(epochMs: number): string {
|
|
30
|
-
return new Date(epochMs).toISOString().replace('T', ' ').slice(0, 19);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function requireProviderOptimizer(context: CommandContext) {
|
|
34
|
-
if (!context.provider.providerOptimizer) {
|
|
35
|
-
context.print('[provider] Provider optimizer is not wired into this runtime.');
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
return context.provider.providerOptimizer;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function fmtExplanation(expl: RouteExplanation, context: CommandContext): void {
|
|
42
|
-
const status = expl.accepted ? '[accepted]' : '[rejected]';
|
|
43
|
-
context.print(` ${status} ${expl.providerId}/${expl.modelId}`);
|
|
44
|
-
context.print(` ${expl.summary}`);
|
|
45
|
-
if (!expl.accepted && expl.rejections.length > 0) {
|
|
46
|
-
context.print(' Unmet requirements:');
|
|
47
|
-
for (const r of expl.rejections) {
|
|
48
|
-
context.print(
|
|
49
|
-
` - ${r.code}: ${r.reason} (actual=${r.actual}, required=${r.required})`,
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
if (expl.accepted) {
|
|
54
|
-
const c = expl.capability;
|
|
55
|
-
context.print(
|
|
56
|
-
` Capabilities: streaming=${fmtBool(c.streaming)}, tools=${fmtBool(c.toolCalling)}, ` +
|
|
57
|
-
`parallel=${fmtBool(c.parallelTools)}, json=${fmtBool(c.jsonMode)}, ` +
|
|
58
|
-
`reasoning=${fmtBool(c.reasoningControls)}`,
|
|
59
|
-
);
|
|
60
|
-
context.print(
|
|
61
|
-
` Context=${c.maxContextTokens.toLocaleString()} tokens, ` +
|
|
62
|
-
`output=${c.maxOutputTokens.toLocaleString()} tokens, ` +
|
|
63
|
-
`timeout=${c.timeoutMs}ms, caching=${c.caching}`,
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// ---------------------------------------------------------------------------
|
|
69
|
-
// /provider route auto|manual
|
|
70
|
-
// ---------------------------------------------------------------------------
|
|
71
|
-
|
|
72
|
-
function handleRoute(
|
|
73
|
-
args: string[],
|
|
74
|
-
context: CommandContext,
|
|
75
|
-
): void {
|
|
76
|
-
const optimizer = requireProviderOptimizer(context);
|
|
77
|
-
if (!optimizer) return;
|
|
78
|
-
const sub = args[0];
|
|
79
|
-
|
|
80
|
-
if (sub !== 'auto' && sub !== 'manual') {
|
|
81
|
-
context.print('[provider] Usage: /provider route auto|manual');
|
|
82
|
-
context.print(` Current mode: ${optimizer.mode} (optimizer ${optimizer.enabled ? 'on' : 'off'})`);
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (!optimizer.enabled) {
|
|
87
|
-
context.print(
|
|
88
|
-
'[provider] Optimizer is currently disabled. Enable it with the provider-optimizer feature flag.',
|
|
89
|
-
);
|
|
90
|
-
context.print(` Routing mode set to: ${sub} (no-op until optimizer is enabled)`);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
optimizer.setMode(sub);
|
|
94
|
-
context.print(`[provider] Routing mode → ${sub}`);
|
|
95
|
-
|
|
96
|
-
if (sub === 'auto') {
|
|
97
|
-
context.print(
|
|
98
|
-
' Auto mode: optimizer selects the best capable provider for each request profile.',
|
|
99
|
-
);
|
|
100
|
-
} else {
|
|
101
|
-
context.print(
|
|
102
|
-
' Manual mode: optimizer is advisory only; provider selection is caller-driven.',
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// ---------------------------------------------------------------------------
|
|
108
|
-
// /provider explain-route
|
|
109
|
-
// ---------------------------------------------------------------------------
|
|
110
|
-
|
|
111
|
-
async function handleExplainRoute(
|
|
112
|
-
_args: string[],
|
|
113
|
-
context: CommandContext,
|
|
114
|
-
): Promise<void> {
|
|
115
|
-
const optimizer = requireProviderOptimizer(context);
|
|
116
|
-
if (!optimizer) return;
|
|
117
|
-
const providerApi = requireProviderApi(context);
|
|
118
|
-
|
|
119
|
-
let currentModel: ProviderApiModelRecord;
|
|
120
|
-
try {
|
|
121
|
-
currentModel = await providerApi.getCurrentModel();
|
|
122
|
-
} catch {
|
|
123
|
-
context.print('[provider] No current model selected.');
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
context.print(
|
|
128
|
-
`[provider] Route explanation for current model: ${currentModel.providerId}/${currentModel.modelId}`,
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
// Always explain current route regardless of optimizer enabled state
|
|
132
|
-
const expl = optimizer.explainCurrentRoute();
|
|
133
|
-
fmtExplanation(expl, context);
|
|
134
|
-
|
|
135
|
-
// Show optimizer status
|
|
136
|
-
context.print(`
|
|
137
|
-
Optimizer: ${optimizer.enabled ? 'enabled' : 'disabled'}, mode=${optimizer.mode}`);
|
|
138
|
-
if (optimizer.pinnedTarget) {
|
|
139
|
-
context.print(
|
|
140
|
-
` Pinned to: ${optimizer.pinnedTarget.providerId}/${optimizer.pinnedTarget.modelId}`,
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Show recent fallback transitions
|
|
145
|
-
const log = optimizer.fallbackLog;
|
|
146
|
-
if (log.length > 0) {
|
|
147
|
-
const recent = log.slice(-5); // last 5 transitions
|
|
148
|
-
context.print(` Fallback log (last ${recent.length} of ${log.length}):`);
|
|
149
|
-
for (const t of recent) {
|
|
150
|
-
context.print(
|
|
151
|
-
` ${fmtTs(t.ts)} ${t.from} → ${t.to} (${t.reason})`,
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
} else {
|
|
155
|
-
context.print(' Fallback log: empty');
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// ---------------------------------------------------------------------------
|
|
160
|
-
// /provider pin <provider:model>
|
|
161
|
-
// ---------------------------------------------------------------------------
|
|
162
|
-
|
|
163
|
-
async function handlePin(
|
|
164
|
-
args: string[],
|
|
165
|
-
context: CommandContext,
|
|
166
|
-
): Promise<void> {
|
|
167
|
-
const optimizer = requireProviderOptimizer(context);
|
|
168
|
-
if (!optimizer) return;
|
|
169
|
-
const target = args[0];
|
|
170
|
-
|
|
171
|
-
if (!target) {
|
|
172
|
-
// Show current pin status
|
|
173
|
-
if (optimizer.pinnedTarget) {
|
|
174
|
-
context.print(
|
|
175
|
-
`[provider] Currently pinned to: ${optimizer.pinnedTarget.providerId}/${optimizer.pinnedTarget.modelId}`,
|
|
176
|
-
);
|
|
177
|
-
context.print(' Use "/provider pin <provider:model>" to change, or "/provider route manual" to unpin.');
|
|
178
|
-
} else {
|
|
179
|
-
context.print('[provider] No pin active. Usage: /provider pin <provider:model>');
|
|
180
|
-
context.print(' Example: /provider pin anthropic:claude-opus-4-5');
|
|
181
|
-
}
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Parse provider:model format
|
|
186
|
-
const colonIdx = target.indexOf(':');
|
|
187
|
-
if (colonIdx === -1) {
|
|
188
|
-
context.print(`[provider] Invalid format "${target}". Expected: <provider>:<model>`);
|
|
189
|
-
context.print(' Example: /provider pin anthropic:claude-opus-4-5');
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const providerId = target.slice(0, colonIdx);
|
|
194
|
-
const modelId = target.slice(colonIdx + 1);
|
|
195
|
-
|
|
196
|
-
if (!providerId || !modelId) {
|
|
197
|
-
context.print(`[provider] Invalid format "${target}". Both provider and model must be non-empty.`);
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Validate that the model exists in registry
|
|
202
|
-
const providerApi = requireProviderApi(context);
|
|
203
|
-
const models = await providerApi.listModels();
|
|
204
|
-
const currentModel = await providerApi.getCurrentModel();
|
|
205
|
-
const match = models.find(
|
|
206
|
-
(m) => m.providerId === providerId && (m.modelId === modelId || m.registryKey === target),
|
|
207
|
-
) ?? (
|
|
208
|
-
currentModel.providerId === providerId
|
|
209
|
-
&& (currentModel.modelId === modelId || currentModel.registryKey === target)
|
|
210
|
-
? currentModel
|
|
211
|
-
: undefined
|
|
212
|
-
);
|
|
213
|
-
|
|
214
|
-
if (!match) {
|
|
215
|
-
context.print(
|
|
216
|
-
`[provider] Model not found in registry: ${providerId}/${modelId}`,
|
|
217
|
-
);
|
|
218
|
-
context.print(' Use "/model" to see available models, or check the provider and model ID.');
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Enable optimizer if it's off
|
|
223
|
-
if (!optimizer.enabled) {
|
|
224
|
-
optimizer.setEnabled(true);
|
|
225
|
-
context.print('⚠ Optimizer was disabled — enabling it for pin to take effect.');
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
optimizer.pin(match.providerId, match.modelId);
|
|
229
|
-
context.print(`[provider] Pinned → ${match.providerId}/${match.modelId}`);
|
|
230
|
-
context.print(' All routed requests will target this provider/model.');
|
|
231
|
-
context.print(' Unpin with: /provider route manual');
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// ---------------------------------------------------------------------------
|
|
235
|
-
// /provider fallback test
|
|
236
|
-
// ---------------------------------------------------------------------------
|
|
237
|
-
|
|
238
|
-
function handleFallbackTest(
|
|
239
|
-
args: string[],
|
|
240
|
-
context: CommandContext,
|
|
241
|
-
): void {
|
|
242
|
-
const optimizer = requireProviderOptimizer(context);
|
|
243
|
-
if (!optimizer) return;
|
|
244
|
-
const sub = args[0];
|
|
245
|
-
|
|
246
|
-
if (sub !== 'test') {
|
|
247
|
-
context.print('[provider] Usage: /provider fallback test');
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
context.print('[provider] Simulating fallback chain (empty request profile — no requirements)...');
|
|
252
|
-
|
|
253
|
-
const result: FallbackTestResult = optimizer.testFallback();
|
|
254
|
-
|
|
255
|
-
context.print(
|
|
256
|
-
`[provider] Fallback chain: ${result.viableCount} capable / ${result.totalCount} total providers`,
|
|
257
|
-
);
|
|
258
|
-
|
|
259
|
-
// Show first capable then rejected
|
|
260
|
-
const capable = result.chain.filter((n) => n.capable);
|
|
261
|
-
const rejected = result.chain.filter((n) => !n.capable);
|
|
262
|
-
|
|
263
|
-
if (capable.length > 0) {
|
|
264
|
-
context.print(' Capable providers (would succeed):');
|
|
265
|
-
for (const node of capable.slice(0, 10)) {
|
|
266
|
-
context.print(` [${node.position}] ${node.providerId}/${node.modelId}`);
|
|
267
|
-
}
|
|
268
|
-
if (capable.length > 10) {
|
|
269
|
-
context.print(` ... and ${capable.length - 10} more`);
|
|
270
|
-
}
|
|
271
|
-
} else {
|
|
272
|
-
context.print(' No capable providers found for this profile.');
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (rejected.length > 0) {
|
|
276
|
-
context.print(` Rejected providers (${rejected.length}):`);
|
|
277
|
-
for (const node of rejected.slice(0, 5)) {
|
|
278
|
-
const reasons = !node.explanation.accepted
|
|
279
|
-
? node.explanation.rejections.map((r) => r.code).join(', ')
|
|
280
|
-
: '';
|
|
281
|
-
context.print(
|
|
282
|
-
` [${node.position}] ${node.providerId}/${node.modelId} — ${reasons || 'unknown'}`,
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
if (rejected.length > 5) {
|
|
286
|
-
context.print(` ... and ${rejected.length - 5} more`);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// Show fallback log
|
|
291
|
-
const log = optimizer.fallbackLog;
|
|
292
|
-
if (log.length > 0) {
|
|
293
|
-
context.print(` Logged transitions (${log.length} total):`);
|
|
294
|
-
const recent: readonly FallbackTransition[] = log.slice(-5);
|
|
295
|
-
for (const t of recent) {
|
|
296
|
-
context.print(
|
|
297
|
-
` ${fmtTs(t.ts)} ${t.from} → ${t.to} (${t.reason})`,
|
|
298
|
-
);
|
|
299
|
-
}
|
|
300
|
-
} else {
|
|
301
|
-
context.print(' No fallback transitions logged this session.');
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
context.print(` Test completed at: ${fmtTs(result.testedAt)}`);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// ---------------------------------------------------------------------------
|
|
308
|
-
// Top-level command definition
|
|
309
|
-
// ---------------------------------------------------------------------------
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* providerCommand — The `/provider` slash command.
|
|
313
|
-
*
|
|
314
|
-
* Routes to subcommand handlers based on args[0].
|
|
315
|
-
*/
|
|
316
|
-
export const providerCommand: SlashCommand = {
|
|
317
|
-
name: 'provider-opt',
|
|
318
|
-
aliases: ['prov-opt'],
|
|
319
|
-
description: 'Manage provider routing optimizer (route, pin, explain, fallback).',
|
|
320
|
-
usage: '<subcommand> [args]',
|
|
321
|
-
argsHint: 'route|explain-route|pin|fallback',
|
|
322
|
-
handler: async (args: string[], context: CommandContext): Promise<void> => {
|
|
323
|
-
const [sub, ...rest] = args;
|
|
324
|
-
|
|
325
|
-
switch (sub) {
|
|
326
|
-
case 'route':
|
|
327
|
-
handleRoute(rest, context);
|
|
328
|
-
break;
|
|
329
|
-
|
|
330
|
-
case 'explain-route':
|
|
331
|
-
case 'explain':
|
|
332
|
-
await handleExplainRoute(rest, context);
|
|
333
|
-
break;
|
|
334
|
-
|
|
335
|
-
case 'pin':
|
|
336
|
-
await handlePin(rest, context);
|
|
337
|
-
break;
|
|
338
|
-
|
|
339
|
-
case 'fallback':
|
|
340
|
-
handleFallbackTest(rest, context);
|
|
341
|
-
break;
|
|
342
|
-
|
|
343
|
-
default: {
|
|
344
|
-
const optimizer = requireProviderOptimizer(context);
|
|
345
|
-
if (!optimizer) return;
|
|
346
|
-
const lines = [
|
|
347
|
-
'Usage: /provider <subcommand>',
|
|
348
|
-
' route auto|manual — Set optimizer routing mode',
|
|
349
|
-
' explain-route — Show current route explanation',
|
|
350
|
-
' pin <provider:model> — Pin routing to specific provider/model',
|
|
351
|
-
' fallback test — Simulate the full fallback chain',
|
|
352
|
-
'',
|
|
353
|
-
` Optimizer: ${optimizer.enabled ? 'enabled' : 'disabled'} mode=${optimizer.mode}`,
|
|
354
|
-
];
|
|
355
|
-
if (optimizer.pinnedTarget) {
|
|
356
|
-
lines.push(` Pinned: ${optimizer.pinnedTarget.providerId}/${optimizer.pinnedTarget.modelId}`);
|
|
357
|
-
}
|
|
358
|
-
context.print(lines.join('\n'));
|
|
359
|
-
break;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
},
|
|
363
|
-
};
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import type { CommandContext } from '../command-registry.ts';
|
|
2
|
-
|
|
3
|
-
type RemotePoolLike = {
|
|
4
|
-
id: string;
|
|
5
|
-
label: string;
|
|
6
|
-
trustClass: string;
|
|
7
|
-
preferredTemplate?: string;
|
|
8
|
-
maxRunners?: number;
|
|
9
|
-
runnerIds: readonly string[];
|
|
10
|
-
description?: string;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
type RemoteRegistryLike = {
|
|
14
|
-
listPools(): readonly RemotePoolLike[];
|
|
15
|
-
getPool(id: string): RemotePoolLike | null | undefined;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export function handleRemotePoolCommand(
|
|
19
|
-
args: string[],
|
|
20
|
-
ctx: Pick<CommandContext, 'print'>,
|
|
21
|
-
remoteRegistry: RemoteRegistryLike,
|
|
22
|
-
): boolean {
|
|
23
|
-
const subcommand = args[0]?.toLowerCase() ?? 'show';
|
|
24
|
-
if (subcommand !== 'pool') return false;
|
|
25
|
-
const mode = args[1]?.toLowerCase() ?? 'list';
|
|
26
|
-
if (mode === 'list') {
|
|
27
|
-
const pools = remoteRegistry.listPools();
|
|
28
|
-
if (pools.length === 0) {
|
|
29
|
-
ctx.print('No remote worker pools defined yet.');
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
ctx.print([
|
|
33
|
-
`Remote Worker Pools (${pools.length})`,
|
|
34
|
-
...pools.map((pool) => ` ${pool.id} ${pool.runnerIds.length} workers trust=${pool.trustClass} template=${pool.preferredTemplate ?? '(none)'}`),
|
|
35
|
-
].join('\n'));
|
|
36
|
-
return true;
|
|
37
|
-
}
|
|
38
|
-
if (mode === 'show') {
|
|
39
|
-
const poolId = args[2];
|
|
40
|
-
if (!poolId) {
|
|
41
|
-
ctx.print('Usage: /remote pool show <poolId>');
|
|
42
|
-
return true;
|
|
43
|
-
}
|
|
44
|
-
const pool = remoteRegistry.getPool(poolId);
|
|
45
|
-
if (!pool) {
|
|
46
|
-
ctx.print(`Unknown remote worker pool: ${poolId}`);
|
|
47
|
-
return true;
|
|
48
|
-
}
|
|
49
|
-
ctx.print([
|
|
50
|
-
`Remote Worker Pool ${pool.id}`,
|
|
51
|
-
` label: ${pool.label}`,
|
|
52
|
-
` trustClass: ${pool.trustClass}`,
|
|
53
|
-
` preferredTemplate: ${pool.preferredTemplate ?? '(none)'}`,
|
|
54
|
-
` maxWorkers: ${pool.maxRunners ?? '(unbounded)'}`,
|
|
55
|
-
` workers: ${pool.runnerIds.join(', ') || '(none)'}`,
|
|
56
|
-
` description: ${pool.description ?? '(none)'}`,
|
|
57
|
-
].join('\n'));
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
if (mode === 'create') {
|
|
61
|
-
ctx.print([
|
|
62
|
-
'Remote worker pool mutation is blocked in GoodVibes Agent.',
|
|
63
|
-
' requested: /remote pool create',
|
|
64
|
-
' policy: Agent inspects remote worker pools but does not manage worker topology',
|
|
65
|
-
' next: use the owning GoodVibes runtime or delegated build environment for worker-pool administration',
|
|
66
|
-
].join('\n'));
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
if (mode === 'assign') {
|
|
70
|
-
ctx.print([
|
|
71
|
-
'Remote worker pool mutation is blocked in GoodVibes Agent.',
|
|
72
|
-
' requested: /remote pool assign',
|
|
73
|
-
' policy: Agent inspects remote worker pools but does not manage worker topology',
|
|
74
|
-
' next: use the owning GoodVibes runtime or delegated build environment for worker-pool administration',
|
|
75
|
-
].join('\n'));
|
|
76
|
-
return true;
|
|
77
|
-
}
|
|
78
|
-
if (mode === 'unassign') {
|
|
79
|
-
ctx.print([
|
|
80
|
-
'Remote worker pool mutation is blocked in GoodVibes Agent.',
|
|
81
|
-
' requested: /remote pool unassign',
|
|
82
|
-
' policy: Agent inspects remote worker pools but does not manage worker topology',
|
|
83
|
-
' next: use the owning GoodVibes runtime or delegated build environment for worker-pool administration',
|
|
84
|
-
].join('\n'));
|
|
85
|
-
return true;
|
|
86
|
-
}
|
|
87
|
-
ctx.print('Usage: /remote pool <list|show|create|assign|unassign> ...');
|
|
88
|
-
return true;
|
|
89
|
-
}
|
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { dirname, resolve } from 'node:path';
|
|
3
|
-
import { getDefaultAcpAgentCommand } from '@pellux/goodvibes-sdk/platform/acp';
|
|
4
|
-
import type { CommandContext, RemoteCommandService } from '../command-registry.ts';
|
|
5
|
-
import type { RemoteSessionBundle } from '@/runtime/index.ts';
|
|
6
|
-
import { requireShellPaths } from './runtime-services.ts';
|
|
7
|
-
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
8
|
-
|
|
9
|
-
type RemoteRegistryLike = Pick<RemoteCommandService, 'listContracts' | 'exportSessionBundle' | 'importSessionBundle'>;
|
|
10
|
-
|
|
11
|
-
type ActiveConnectionLike = {
|
|
12
|
-
agentId: string;
|
|
13
|
-
transportState: string;
|
|
14
|
-
messageCount: number;
|
|
15
|
-
errorCount: number;
|
|
16
|
-
label: string;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export function inspectRemoteSessionBundle(bundle: RemoteSessionBundle): string {
|
|
20
|
-
return [
|
|
21
|
-
'Remote Session Bundle Review',
|
|
22
|
-
` session: ${bundle.sessionId}`,
|
|
23
|
-
` exportedAt: ${new Date(bundle.exportedAt).toISOString()}`,
|
|
24
|
-
` active connections: ${bundle.activeConnectionIds.length}`,
|
|
25
|
-
` pools: ${bundle.pools.length}`,
|
|
26
|
-
` contracts: ${bundle.contracts.length}`,
|
|
27
|
-
` artifacts: ${bundle.artifacts.length}`,
|
|
28
|
-
].join('\n');
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export async function handleRemoteSetupCommand(
|
|
32
|
-
args: string[],
|
|
33
|
-
ctx: CommandContext,
|
|
34
|
-
activeConnections: ActiveConnectionLike[],
|
|
35
|
-
remoteRegistry: RemoteRegistryLike,
|
|
36
|
-
): Promise<boolean> {
|
|
37
|
-
const parsed = stripYesFlag(args);
|
|
38
|
-
const commandArgs = [...parsed.rest];
|
|
39
|
-
const subcommand = commandArgs[0]?.toLowerCase() ?? 'show';
|
|
40
|
-
if (subcommand === 'setup') {
|
|
41
|
-
const command = getDefaultAcpAgentCommand();
|
|
42
|
-
const danger = ctx.platform.configManager.getCategory('danger');
|
|
43
|
-
const lines = [
|
|
44
|
-
'Remote Setup Review',
|
|
45
|
-
` acp agent command: ${command.join(' ')}`,
|
|
46
|
-
` runtime host enabled: ${danger.daemon ? 'yes' : 'no'}`,
|
|
47
|
-
` inbound listener enabled: ${danger.httpListener ? 'yes' : 'no'}`,
|
|
48
|
-
` remote worker contracts: ${remoteRegistry.listContracts().length}`,
|
|
49
|
-
` active acp connections: ${activeConnections.length}`,
|
|
50
|
-
'',
|
|
51
|
-
' guidance:',
|
|
52
|
-
' - set ACP_AGENT_CMD to override the spawned remote agent command',
|
|
53
|
-
' - use /remote env to export a reusable shell snippet',
|
|
54
|
-
' - runtime-host and inbound-listener posture belongs to the runtime owner, not Agent onboarding',
|
|
55
|
-
];
|
|
56
|
-
if (commandArgs[1]?.toLowerCase() === 'export') {
|
|
57
|
-
const pathArg = commandArgs[2];
|
|
58
|
-
if (!pathArg) {
|
|
59
|
-
ctx.print('Usage: /remote setup export <path> --yes');
|
|
60
|
-
return true;
|
|
61
|
-
}
|
|
62
|
-
if (!parsed.yes) {
|
|
63
|
-
requireYesFlag(ctx, `export remote setup bundle to ${pathArg}`, '/remote setup export <path> --yes');
|
|
64
|
-
return true;
|
|
65
|
-
}
|
|
66
|
-
const shellPaths = requireShellPaths(ctx);
|
|
67
|
-
const targetPath = shellPaths.resolveWorkspacePath(pathArg);
|
|
68
|
-
mkdirSync(dirname(targetPath), { recursive: true });
|
|
69
|
-
writeFileSync(targetPath, `${JSON.stringify({
|
|
70
|
-
exportedAt: Date.now(),
|
|
71
|
-
acpAgentCommand: command,
|
|
72
|
-
runtimeHostEnabled: Boolean(danger.daemon),
|
|
73
|
-
inboundListenerEnabled: Boolean(danger.httpListener),
|
|
74
|
-
remoteRunnerContracts: remoteRegistry.listContracts().length,
|
|
75
|
-
}, null, 2)}\n`, 'utf-8');
|
|
76
|
-
ctx.print(`Exported remote setup bundle to ${targetPath}`);
|
|
77
|
-
return true;
|
|
78
|
-
}
|
|
79
|
-
ctx.print(lines.join('\n'));
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (subcommand === 'env') {
|
|
84
|
-
const command = getDefaultAcpAgentCommand();
|
|
85
|
-
const shellSnippet = [
|
|
86
|
-
`export ACP_AGENT_CMD='${command.join(' ')}'`,
|
|
87
|
-
`export GOODVIBES_REMOTE_SESSION='${ctx.session.runtime.sessionId}'`,
|
|
88
|
-
].join('\n');
|
|
89
|
-
if (commandArgs[1]?.toLowerCase() === 'export') {
|
|
90
|
-
const pathArg = commandArgs[2];
|
|
91
|
-
if (!pathArg) {
|
|
92
|
-
ctx.print('Usage: /remote env export <path> --yes');
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
if (!parsed.yes) {
|
|
96
|
-
requireYesFlag(ctx, `export remote environment snippet to ${pathArg}`, '/remote env export <path> --yes');
|
|
97
|
-
return true;
|
|
98
|
-
}
|
|
99
|
-
const shellPaths = requireShellPaths(ctx);
|
|
100
|
-
const targetPath = shellPaths.resolveWorkspacePath(pathArg);
|
|
101
|
-
mkdirSync(dirname(targetPath), { recursive: true });
|
|
102
|
-
writeFileSync(targetPath, `${shellSnippet}\n`, 'utf-8');
|
|
103
|
-
ctx.print(`Exported remote environment snippet to ${targetPath}`);
|
|
104
|
-
return true;
|
|
105
|
-
}
|
|
106
|
-
ctx.print(['Remote Environment', shellSnippet].join('\n'));
|
|
107
|
-
return true;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (subcommand === 'tunnel') {
|
|
111
|
-
const mode = commandArgs[1]?.toLowerCase() ?? 'review';
|
|
112
|
-
const lines = [
|
|
113
|
-
'Remote Tunnel Review',
|
|
114
|
-
' transport: self-hosted ACP / runtime relay',
|
|
115
|
-
` session: ${ctx.session.runtime.sessionId}`,
|
|
116
|
-
` active remote connections: ${activeConnections.length}`,
|
|
117
|
-
' guidance: forward ACP agent traffic through your chosen self-hosted tunnel or SSH transport',
|
|
118
|
-
];
|
|
119
|
-
if (mode === 'export') {
|
|
120
|
-
const pathArg = commandArgs[2];
|
|
121
|
-
if (!pathArg) {
|
|
122
|
-
ctx.print('Usage: /remote tunnel export <path> --yes');
|
|
123
|
-
return true;
|
|
124
|
-
}
|
|
125
|
-
if (!parsed.yes) {
|
|
126
|
-
requireYesFlag(ctx, `export remote tunnel review to ${pathArg}`, '/remote tunnel export <path> --yes');
|
|
127
|
-
return true;
|
|
128
|
-
}
|
|
129
|
-
const shellPaths = requireShellPaths(ctx);
|
|
130
|
-
const targetPath = shellPaths.resolveWorkspacePath(pathArg);
|
|
131
|
-
mkdirSync(dirname(targetPath), { recursive: true });
|
|
132
|
-
writeFileSync(targetPath, `${lines.join('\n')}\n`, 'utf-8');
|
|
133
|
-
ctx.print(`Exported remote tunnel review to ${targetPath}`);
|
|
134
|
-
return true;
|
|
135
|
-
}
|
|
136
|
-
ctx.print(lines.join('\n'));
|
|
137
|
-
return true;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (subcommand === 'bootstrap') {
|
|
141
|
-
const mode = commandArgs[1]?.toLowerCase() ?? 'export';
|
|
142
|
-
const payload = {
|
|
143
|
-
exportedAt: Date.now(),
|
|
144
|
-
sessionId: ctx.session.runtime.sessionId,
|
|
145
|
-
acpAgentCommand: getDefaultAcpAgentCommand(),
|
|
146
|
-
env: {
|
|
147
|
-
ACP_AGENT_CMD: getDefaultAcpAgentCommand().join(' '),
|
|
148
|
-
GOODVIBES_REMOTE_SESSION: ctx.session.runtime.sessionId,
|
|
149
|
-
},
|
|
150
|
-
links: [
|
|
151
|
-
'goodvibes://open/remote',
|
|
152
|
-
'goodvibes://open/cockpit?target=remote',
|
|
153
|
-
],
|
|
154
|
-
};
|
|
155
|
-
if (mode === 'inspect') {
|
|
156
|
-
const pathArg = commandArgs[2];
|
|
157
|
-
if (!pathArg) {
|
|
158
|
-
ctx.print('Usage: /remote bootstrap inspect <path>');
|
|
159
|
-
return true;
|
|
160
|
-
}
|
|
161
|
-
const shellPaths = requireShellPaths(ctx);
|
|
162
|
-
const targetPath = shellPaths.resolveWorkspacePath(pathArg);
|
|
163
|
-
const parsed = JSON.parse(readFileSync(targetPath, 'utf-8')) as typeof payload;
|
|
164
|
-
ctx.print([
|
|
165
|
-
'Remote Bootstrap Bundle Review',
|
|
166
|
-
` session: ${parsed.sessionId}`,
|
|
167
|
-
` acp agent command: ${parsed.acpAgentCommand.join(' ')}`,
|
|
168
|
-
` links: ${parsed.links.length}`,
|
|
169
|
-
].join('\n'));
|
|
170
|
-
return true;
|
|
171
|
-
}
|
|
172
|
-
const pathArg = commandArgs[2] ?? commandArgs[1];
|
|
173
|
-
if (!pathArg || mode !== 'export') {
|
|
174
|
-
ctx.print('Usage: /remote bootstrap export <path> --yes | /remote bootstrap inspect <path>');
|
|
175
|
-
return true;
|
|
176
|
-
}
|
|
177
|
-
if (!parsed.yes) {
|
|
178
|
-
requireYesFlag(ctx, `export remote bootstrap bundle to ${pathArg}`, '/remote bootstrap export <path> --yes');
|
|
179
|
-
return true;
|
|
180
|
-
}
|
|
181
|
-
const shellPaths = requireShellPaths(ctx);
|
|
182
|
-
const targetPath = shellPaths.resolveWorkspacePath(pathArg);
|
|
183
|
-
mkdirSync(dirname(targetPath), { recursive: true });
|
|
184
|
-
writeFileSync(targetPath, `${JSON.stringify(payload, null, 2)}\n`, 'utf-8');
|
|
185
|
-
ctx.print(`Exported remote bootstrap bundle to ${targetPath}`);
|
|
186
|
-
return true;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (subcommand === 'session') {
|
|
190
|
-
const mode = commandArgs[1]?.toLowerCase();
|
|
191
|
-
const pathArg = commandArgs[2];
|
|
192
|
-
if (!mode || !pathArg) {
|
|
193
|
-
ctx.print('Usage: /remote session <export|inspect|import> <path> [--yes]');
|
|
194
|
-
return true;
|
|
195
|
-
}
|
|
196
|
-
const shellPaths = requireShellPaths(ctx);
|
|
197
|
-
const targetPath = shellPaths.resolveWorkspacePath(pathArg);
|
|
198
|
-
if (mode === 'export') {
|
|
199
|
-
if (!parsed.yes) {
|
|
200
|
-
requireYesFlag(ctx, `export remote session bundle to ${pathArg}`, '/remote session export <path> --yes');
|
|
201
|
-
return true;
|
|
202
|
-
}
|
|
203
|
-
const exported = await remoteRegistry.exportSessionBundle(targetPath);
|
|
204
|
-
ctx.print(`Exported remote session bundle ${exported.bundle.sessionId} to ${exported.path}`);
|
|
205
|
-
return true;
|
|
206
|
-
}
|
|
207
|
-
if (mode === 'inspect') {
|
|
208
|
-
const bundle = JSON.parse(readFileSync(targetPath, 'utf-8')) as RemoteSessionBundle;
|
|
209
|
-
ctx.print(inspectRemoteSessionBundle(bundle));
|
|
210
|
-
return true;
|
|
211
|
-
}
|
|
212
|
-
if (mode === 'import') {
|
|
213
|
-
if (!parsed.yes) {
|
|
214
|
-
requireYesFlag(ctx, `import remote session bundle from ${pathArg}`, '/remote session import <path> --yes');
|
|
215
|
-
return true;
|
|
216
|
-
}
|
|
217
|
-
const bundle = await remoteRegistry.importSessionBundle(targetPath);
|
|
218
|
-
ctx.print(`Imported remote session bundle ${bundle.sessionId} with ${bundle.contracts.length} contracts.`);
|
|
219
|
-
return true;
|
|
220
|
-
}
|
|
221
|
-
ctx.print('Usage: /remote session <export|inspect|import> <path> [--yes]');
|
|
222
|
-
return true;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return false;
|
|
226
|
-
}
|