@pellux/goodvibes-agent 0.1.69 → 0.1.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +42 -1
  3. package/src/agent/skill-discovery.ts +119 -0
  4. package/src/input/commands/delegation-runtime.ts +0 -8
  5. package/src/input/commands/experience-runtime.ts +0 -177
  6. package/src/input/commands/guidance-runtime.ts +9 -77
  7. package/src/input/commands/local-runtime.ts +1 -57
  8. package/src/input/commands/local-setup-review.ts +1 -1
  9. package/src/input/commands/operator-runtime.ts +1 -145
  10. package/src/input/commands/platform-access-runtime.ts +2 -195
  11. package/src/input/commands/product-runtime.ts +0 -116
  12. package/src/input/commands/security-runtime.ts +88 -0
  13. package/src/input/commands/session-content.ts +0 -97
  14. package/src/input/commands/shell-core.ts +1 -22
  15. package/src/input/commands.ts +2 -43
  16. package/src/panels/builtin/operations.ts +3 -184
  17. package/src/panels/index.ts +0 -11
  18. package/src/version.ts +1 -1
  19. package/src/input/commands/branch-runtime.ts +0 -72
  20. package/src/input/commands/control-room-runtime.ts +0 -234
  21. package/src/input/commands/discovery-runtime.ts +0 -61
  22. package/src/input/commands/hooks-runtime.ts +0 -207
  23. package/src/input/commands/incident-runtime.ts +0 -106
  24. package/src/input/commands/integration-runtime.ts +0 -437
  25. package/src/input/commands/local-setup.ts +0 -288
  26. package/src/input/commands/managed-runtime.ts +0 -240
  27. package/src/input/commands/marketplace-runtime.ts +0 -305
  28. package/src/input/commands/memory-product-runtime.ts +0 -148
  29. package/src/input/commands/operator-panel-runtime.ts +0 -146
  30. package/src/input/commands/platform-services-runtime.ts +0 -271
  31. package/src/input/commands/profile-sync-runtime.ts +0 -110
  32. package/src/input/commands/provider.ts +0 -363
  33. package/src/input/commands/remote-runtime-pool.ts +0 -89
  34. package/src/input/commands/remote-runtime-setup.ts +0 -226
  35. package/src/input/commands/remote-runtime.ts +0 -432
  36. package/src/input/commands/replay-runtime.ts +0 -25
  37. package/src/input/commands/services-runtime.ts +0 -220
  38. package/src/input/commands/settings-sync-runtime.ts +0 -197
  39. package/src/input/commands/share-runtime.ts +0 -127
  40. package/src/input/commands/skills-runtime.ts +0 -226
  41. package/src/input/commands/teleport-runtime.ts +0 -68
  42. package/src/panels/cockpit-panel.ts +0 -183
  43. package/src/panels/communication-panel.ts +0 -153
  44. package/src/panels/control-plane-panel.ts +0 -211
  45. package/src/panels/forensics-panel.ts +0 -364
  46. package/src/panels/hooks-panel.ts +0 -239
  47. package/src/panels/incident-review-panel.ts +0 -197
  48. package/src/panels/marketplace-panel.ts +0 -212
  49. package/src/panels/ops-control-panel.ts +0 -150
  50. package/src/panels/ops-strategy-panel.ts +0 -235
  51. package/src/panels/orchestration-panel.ts +0 -272
  52. package/src/panels/plugins-panel.ts +0 -178
  53. package/src/panels/remote-panel.ts +0 -449
  54. package/src/panels/routes-panel.ts +0 -178
  55. package/src/panels/services-panel.ts +0 -231
  56. package/src/panels/settings-sync-panel.ts +0 -120
  57. package/src/panels/skills-panel.ts +0 -431
  58. package/src/panels/watchers-panel.ts +0 -193
  59. package/src/verification/live-verifier.ts +0 -588
  60. package/src/verification/verification-ledger.ts +0 -239
@@ -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
- }
@@ -1,207 +0,0 @@
1
- import { readFileSync } from 'node:fs';
2
- import type { CommandRegistry } from '../command-registry.ts';
3
- import { requireHookApi } from './runtime-services.ts';
4
- import { requireYesFlag, stripYesFlag } from './confirmation.ts';
5
-
6
- function isRecord(value: unknown): value is Record<string, unknown> {
7
- return typeof value === 'object' && value !== null && !Array.isArray(value);
8
- }
9
-
10
- function containsAgentHookType(value: unknown): boolean {
11
- if (Array.isArray(value)) return value.some((entry) => containsAgentHookType(entry));
12
- if (!isRecord(value)) return false;
13
- if (value.type === 'agent') return true;
14
- return Object.values(value).some((entry) => containsAgentHookType(entry));
15
- }
16
-
17
- function fileContainsAgentHookType(path: string): boolean {
18
- try {
19
- return containsAgentHookType(JSON.parse(readFileSync(path, 'utf-8')) as unknown);
20
- } catch {
21
- return false;
22
- }
23
- }
24
-
25
- export function registerHooksRuntimeCommands(registry: CommandRegistry): void {
26
- registry.register({
27
- name: 'hooks',
28
- aliases: [],
29
- description: 'Inspect, author, simulate, and reload managed hook workflows',
30
- usage: '[contracts [filter] | reload --yes | scaffold <name> <match> <type> --yes | chain <name> <event1,event2,...> --yes | remove <name> --yes | enable <name> --yes | disable <name> --yes | simulate <eventPath> | inspect <path> | import <path> [merge|replace] --yes | export [path] --yes]',
31
- argsHint: '[subcommand]',
32
- async handler(args, ctx) {
33
- const parsed = stripYesFlag(args);
34
- const commandArgs = [...parsed.rest];
35
- const hookApi = requireHookApi(ctx);
36
- const workbench = hookApi.workbench;
37
- if (commandArgs.length === 0 && ctx.openHooksPanel) {
38
- ctx.openHooksPanel();
39
- return;
40
- }
41
-
42
- const subcommand = (commandArgs[0] ?? 'contracts').toLowerCase();
43
- if (subcommand === 'reload') {
44
- if (!parsed.yes) {
45
- requireYesFlag(ctx, 'reload managed hook workflows', '/hooks reload --yes');
46
- return;
47
- }
48
- await workbench.reload();
49
- ctx.print(`Reloaded managed hooks from ${workbench.getFilePath()}`);
50
- return;
51
- }
52
- if (subcommand === 'scaffold') {
53
- const [name, match, type] = commandArgs.slice(1);
54
- if (!name || !match || !type) {
55
- ctx.print('Usage: /hooks scaffold <name> <match> <command|prompt|http|ts> --yes');
56
- return;
57
- }
58
- if (!parsed.yes) {
59
- requireYesFlag(ctx, `scaffold managed hook ${name}`, '/hooks scaffold <name> <match> <command|prompt|http|ts> --yes');
60
- return;
61
- }
62
- if (type === 'agent') {
63
- ctx.print('Blocked: GoodVibes Agent does not author local agent-spawning hooks. Use command, prompt, http, or ts hooks, or delegate explicit build work to GoodVibes TUI.');
64
- return;
65
- }
66
- if (!['command', 'prompt', 'http', 'ts'].includes(type)) {
67
- ctx.print(`Unknown hook type: ${type}`);
68
- return;
69
- }
70
- const hook = await workbench.scaffoldHook(name, match, type as Parameters<typeof workbench.scaffoldHook>[2]);
71
- ctx.print(`Scaffolded managed hook ${hook.name} at ${match} in ${workbench.getFilePath()}`);
72
- return;
73
- }
74
- if (subcommand === 'chain') {
75
- const name = commandArgs[1];
76
- const matches = commandArgs[2]?.split(',').map((entry) => entry.trim()).filter(Boolean) ?? [];
77
- if (!name || matches.length === 0) {
78
- ctx.print('Usage: /hooks chain <name> <event1,event2,...> --yes');
79
- return;
80
- }
81
- if (!parsed.yes) {
82
- requireYesFlag(ctx, `scaffold managed hook chain ${name}`, '/hooks chain <name> <event1,event2,...> --yes');
83
- return;
84
- }
85
- const chain = await workbench.scaffoldChain(name, matches);
86
- ctx.print(`Scaffolded managed hook chain ${chain.name} with ${chain.steps.length} step(s).`);
87
- return;
88
- }
89
- if (subcommand === 'remove') {
90
- const name = commandArgs[1];
91
- if (!name) {
92
- ctx.print('Usage: /hooks remove <name> --yes');
93
- return;
94
- }
95
- if (!parsed.yes) {
96
- requireYesFlag(ctx, `remove managed hook workflow ${name}`, '/hooks remove <name> --yes');
97
- return;
98
- }
99
- const removed = await workbench.remove(name);
100
- if (!removed) {
101
- ctx.print(`No managed hook or chain named ${name}.`);
102
- return;
103
- }
104
- ctx.print(`Removed managed hook workflow entry ${name}.`);
105
- return;
106
- }
107
- if (subcommand === 'enable' || subcommand === 'disable') {
108
- const name = commandArgs[1];
109
- if (!name) {
110
- ctx.print(`Usage: /hooks ${subcommand} <name> --yes`);
111
- return;
112
- }
113
- if (!parsed.yes) {
114
- requireYesFlag(ctx, `${subcommand} managed hook ${name}`, `/hooks ${subcommand} <name> --yes`);
115
- return;
116
- }
117
- const changed = await workbench.toggle(name, subcommand === 'enable');
118
- if (!changed) {
119
- ctx.print(`No managed hook named ${name}.`);
120
- return;
121
- }
122
- ctx.print(`${subcommand === 'enable' ? 'Enabled' : 'Disabled'} managed hook ${name}.`);
123
- return;
124
- }
125
- if (subcommand === 'simulate') {
126
- const eventPath = commandArgs[1];
127
- if (!eventPath) {
128
- ctx.print('Usage: /hooks simulate <eventPath>');
129
- return;
130
- }
131
- const result = workbench.simulate(eventPath);
132
- ctx.print([
133
- `Hook simulation for ${result.eventPath}`,
134
- ` matched hooks: ${result.matchedHooks.length}`,
135
- ...result.matchedHooks.map((entry) => ` ${entry.name} ${entry.pattern} ${entry.type}`),
136
- ` matched chains: ${result.matchedChains.length}`,
137
- ...result.matchedChains.map((entry) => ` ${entry.name} stepMatches=${entry.stepMatches}`),
138
- ].join('\n'));
139
- return;
140
- }
141
- if (subcommand === 'export') {
142
- if (!parsed.yes) {
143
- requireYesFlag(ctx, 'export managed hook workflows', '/hooks export [path] --yes');
144
- return;
145
- }
146
- const path = await workbench.export(commandArgs[1] ?? workbench.getFilePath());
147
- ctx.print(`Exported managed hooks to ${path}`);
148
- return;
149
- }
150
- if (subcommand === 'inspect') {
151
- const path = commandArgs[1];
152
- if (!path) {
153
- ctx.print('Usage: /hooks inspect <path>');
154
- return;
155
- }
156
- const inspection = workbench.inspect(path);
157
- ctx.print([
158
- `Hook bundle inspection: ${inspection.path}`,
159
- ` hooks: ${inspection.hookCount}`,
160
- ` chains: ${inspection.chainCount}`,
161
- ` patterns: ${inspection.patterns.join(', ') || '(none)'}`,
162
- ].join('\n'));
163
- return;
164
- }
165
- if (subcommand === 'import') {
166
- const path = commandArgs[1];
167
- const strategy = commandArgs[2] === 'replace' ? 'replace' : 'merge';
168
- if (!path) {
169
- ctx.print('Usage: /hooks import <path> [merge|replace] --yes');
170
- return;
171
- }
172
- if (!parsed.yes) {
173
- requireYesFlag(ctx, `import managed hook workflows from ${path}`, '/hooks import <path> [merge|replace] --yes');
174
- return;
175
- }
176
- if (fileContainsAgentHookType(path)) {
177
- ctx.print('Blocked: hook bundle contains type=agent entries. GoodVibes Agent does not import local agent-spawning hooks.');
178
- return;
179
- }
180
- await workbench.import(path, strategy);
181
- ctx.print(`Imported managed hooks from ${path} using ${strategy} strategy.`);
182
- return;
183
- }
184
-
185
- const filter = (subcommand === 'contracts' ? commandArgs.slice(1) : commandArgs).join(' ').trim().toLowerCase();
186
- const contracts = hookApi.contracts(filter);
187
-
188
- if (contracts.length === 0) {
189
- ctx.print(filter.length === 0 ? 'No hook contracts registered.' : `No hook contracts matched "${filter}".`);
190
- return;
191
- }
192
-
193
- const lines: string[] = [`Hook Contracts (${contracts.length}):`];
194
- for (const contract of contracts) {
195
- lines.push(` ${contract.pattern}`);
196
- lines.push(` authority=${contract.authority} mode=${contract.executionMode} deny=${contract.canDeny ? 'yes' : 'no'} mutate=${contract.canMutateInput ? 'yes' : 'no'} inject=${contract.canInjectContext ? 'yes' : 'no'} timeout=${contract.timeoutMs}ms policy=${contract.failurePolicy}`);
197
- lines.push(` ${contract.description}`);
198
- }
199
- const managedHooks = workbench.listManagedHooks();
200
- const managedChains = workbench.listManagedChains();
201
- lines.push('');
202
- lines.push(`Managed hooks file: ${workbench.getFilePath()}`);
203
- lines.push(`Managed entries: hooks=${managedHooks.length} chains=${managedChains.length}`);
204
- ctx.print(lines.join('\n'));
205
- },
206
- });
207
- }
@@ -1,106 +0,0 @@
1
- import { dirname, resolve } from 'path';
2
- import { mkdirSync, writeFileSync } from 'node:fs';
3
- import type { CommandRegistry } from '../command-registry.ts';
4
- import { buildIncidentMemoryAddOptions } from '@pellux/goodvibes-sdk/platform/state';
5
- import { requireShellPaths } from './runtime-services.ts';
6
- import { getMemoryApi } from './recall-query.ts';
7
- import { requireYesFlag, stripYesFlag } from './confirmation.ts';
8
-
9
- export function registerIncidentRuntimeCommands(registry: CommandRegistry): void {
10
- registry.register({
11
- name: 'incident',
12
- aliases: [],
13
- description: 'Open, export, and capture incident review bundles',
14
- usage: '[open | latest | show <id|latest> | export <id|latest> <path> --yes | capture <id|latest> --yes]',
15
- async handler(args, ctx) {
16
- const parsed = stripYesFlag(args);
17
- const commandArgs = [...parsed.rest];
18
- const shellPaths = requireShellPaths(ctx);
19
- const subcommand = (commandArgs[0] ?? 'open').toLowerCase();
20
- const forensicRegistry = ctx.extensions.forensicsRegistry;
21
- if (subcommand === 'open') {
22
- if (ctx.openIncidentPanel) {
23
- ctx.openIncidentPanel();
24
- return;
25
- }
26
- ctx.print('Incident panel is not available in this runtime.');
27
- return;
28
- }
29
- if (!forensicRegistry) {
30
- ctx.print('Forensics registry is not available in this runtime.');
31
- return;
32
- }
33
- const requestedId = commandArgs[1];
34
- const report = !requestedId || requestedId === 'latest'
35
- ? forensicRegistry.latest()
36
- : forensicRegistry.getById(requestedId);
37
- if (subcommand === 'latest' || subcommand === 'show') {
38
- if (!report) {
39
- ctx.print('No incident bundle is available.');
40
- return;
41
- }
42
- const bundle = forensicRegistry.buildBundle(report.id);
43
- if (!bundle) {
44
- ctx.print(`Failed to build incident bundle for ${report.id}.`);
45
- return;
46
- }
47
- ctx.print([
48
- `Incident ${report.id}`,
49
- ` classification: ${report.classification}`,
50
- ` summary: ${report.summary}`,
51
- ` root cause: ${bundle.evidence.rootCause ?? 'n/a'}`,
52
- ` denied permissions: ${bundle.evidence.deniedPermissionCount}`,
53
- ` budget breaches: ${bundle.evidence.budgetBreachCount}`,
54
- ` replay mismatches: ${bundle.replay.mismatchCount}`,
55
- ].join('\n'));
56
- return;
57
- }
58
- if (subcommand === 'export') {
59
- const pathArg = commandArgs[2];
60
- if (!requestedId || !pathArg) {
61
- ctx.print('Usage: /incident export <id|latest> <path> --yes');
62
- return;
63
- }
64
- if (!parsed.yes) {
65
- requireYesFlag(ctx, `export incident bundle ${requestedId}`, '/incident export <id|latest> <path> --yes');
66
- return;
67
- }
68
- if (!report) {
69
- ctx.print(`Incident not found: ${requestedId}`);
70
- return;
71
- }
72
- const bundleJson = forensicRegistry.exportBundleAsJson(report.id);
73
- if (!bundleJson) {
74
- ctx.print(`Failed to export incident bundle for ${report.id}.`);
75
- return;
76
- }
77
- const targetPath = shellPaths.resolveWorkspacePath(pathArg);
78
- mkdirSync(dirname(targetPath), { recursive: true });
79
- writeFileSync(targetPath, `${bundleJson}\n`, 'utf-8');
80
- ctx.print(`Exported incident bundle ${report.id} to ${targetPath}`);
81
- return;
82
- }
83
- if (subcommand === 'capture') {
84
- if (!parsed.yes) {
85
- requireYesFlag(ctx, `capture incident ${requestedId ?? 'latest'} into durable memory`, '/incident capture <id|latest> --yes');
86
- return;
87
- }
88
- const memory = getMemoryApi(ctx);
89
- if (!memory) return;
90
- if (!report) {
91
- ctx.print(`Incident not found: ${requestedId ?? 'latest'}`);
92
- return;
93
- }
94
- const bundle = forensicRegistry.buildBundle(report.id);
95
- if (!bundle) {
96
- ctx.print(`Failed to build incident bundle for ${report.id}.`);
97
- return;
98
- }
99
- const record = await memory.add(buildIncidentMemoryAddOptions(bundle));
100
- ctx.print(`Captured incident ${report.id} into durable memory as ${record.id}`);
101
- return;
102
- }
103
- ctx.print('Usage: /incident [open | latest | show <id|latest> | export <id|latest> <path> --yes | capture <id|latest> --yes]');
104
- },
105
- });
106
- }