@pellux/goodvibes-agent 0.1.1 → 0.1.3

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 (52) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +12 -1
  3. package/docs/README.md +2 -0
  4. package/docs/getting-started.md +19 -1
  5. package/docs/release-and-publishing.md +3 -1
  6. package/package.json +10 -1
  7. package/src/agent/persona-registry.ts +379 -0
  8. package/src/agent/skill-registry.ts +360 -0
  9. package/src/audio/spoken-turn-model-routing.ts +2 -1
  10. package/src/cli/agent-knowledge-command.ts +525 -0
  11. package/src/cli/help.ts +35 -0
  12. package/src/cli/management-commands.ts +3 -1
  13. package/src/cli/management.ts +33 -9
  14. package/src/cli/parser.ts +7 -0
  15. package/src/cli/types.ts +3 -0
  16. package/src/config/surface.ts +1 -0
  17. package/src/input/agent-workspace.ts +33 -3
  18. package/src/input/command-registry.ts +4 -1
  19. package/src/input/commands/agent-skills-runtime.ts +216 -0
  20. package/src/input/commands/delegation-runtime.ts +129 -0
  21. package/src/input/commands/knowledge.ts +18 -18
  22. package/src/input/commands/personas-runtime.ts +219 -0
  23. package/src/input/commands/shell-core.ts +9 -6
  24. package/src/input/commands/skills-runtime.ts +7 -2
  25. package/src/input/commands.ts +6 -0
  26. package/src/input/panel-integration-actions.ts +0 -52
  27. package/src/input/submission-router.ts +1 -1
  28. package/src/main.ts +2 -1
  29. package/src/panels/builtin/agent.ts +0 -14
  30. package/src/panels/builtin/session.ts +4 -3
  31. package/src/panels/index.ts +0 -5
  32. package/src/panels/orchestration-panel.ts +4 -5
  33. package/src/panels/qr-panel.ts +3 -2
  34. package/src/panels/tasks-panel.ts +4 -4
  35. package/src/renderer/agent-workspace.ts +2 -0
  36. package/src/runtime/bootstrap-command-context.ts +3 -0
  37. package/src/runtime/bootstrap-command-parts.ts +6 -2
  38. package/src/runtime/bootstrap-core.ts +8 -4
  39. package/src/runtime/bootstrap-shell.ts +5 -2
  40. package/src/runtime/bootstrap.ts +10 -2
  41. package/src/runtime/cloudflare-control-plane.ts +2 -1
  42. package/src/version.ts +1 -1
  43. package/src/daemon/cli.ts +0 -55
  44. package/src/daemon/safe-serve.ts +0 -61
  45. package/src/panels/diff-panel.ts +0 -520
  46. package/src/panels/file-explorer-panel.ts +0 -584
  47. package/src/panels/file-preview-panel.ts +0 -434
  48. package/src/panels/git-panel.ts +0 -638
  49. package/src/panels/sandbox-panel.ts +0 -283
  50. package/src/panels/symbol-outline-panel.ts +0 -486
  51. package/src/panels/worktree-panel.ts +0 -182
  52. package/src/panels/wrfc-panel.ts +0 -609
@@ -8,10 +8,10 @@ type KnowledgeAskInput = Parameters<KnowledgeService['ask']>[0];
8
8
  type KnowledgeAskResult = Awaited<ReturnType<KnowledgeService['ask']>>;
9
9
  type KnowledgeAskMode = NonNullable<KnowledgeAskInput['mode']>;
10
10
 
11
- function requireKnowledgeApi(context: CommandContext) {
12
- const knowledgeApi = context.clients?.knowledgeApi;
11
+ function requireAgentKnowledgeApi(context: CommandContext) {
12
+ const knowledgeApi = context.clients?.agentKnowledgeApi;
13
13
  if (!knowledgeApi) {
14
- context.print('[knowledge] Knowledge API is not available in this runtime.');
14
+ context.print('[knowledge] Agent Knowledge API is not available in this runtime. Refusing to use default Knowledge/Wiki or HomeGraph fallback.');
15
15
  return null;
16
16
  }
17
17
  return knowledgeApi;
@@ -57,14 +57,11 @@ function positionalArgs(args: string[], valuedFlags: readonly string[] = []): st
57
57
  });
58
58
  }
59
59
 
60
- function requireKnowledgeAsk(context: CommandContext): ((input: KnowledgeAskInput) => Promise<KnowledgeAskResult>) | null {
61
- const serviceAsk = context.extensions.knowledgeService?.ask?.bind(context.extensions.knowledgeService);
60
+ function requireAgentKnowledgeAsk(context: CommandContext): ((input: KnowledgeAskInput) => Promise<KnowledgeAskResult>) | null {
61
+ const serviceAsk = context.extensions.agentKnowledgeService?.ask?.bind(context.extensions.agentKnowledgeService);
62
62
  if (serviceAsk) return serviceAsk;
63
63
 
64
- const clientAsk = (context.clients?.knowledgeApi as unknown as { ask?: (input: KnowledgeAskInput) => Promise<KnowledgeAskResult> } | undefined)?.ask;
65
- if (clientAsk) return clientAsk;
66
-
67
- context.print('[knowledge] Knowledge ask is not available in this runtime.');
64
+ context.print('[knowledge] Agent Knowledge ask is not available in this runtime. Refusing to use default Knowledge/Wiki or HomeGraph fallback.');
68
65
  return null;
69
66
  }
70
67
 
@@ -132,11 +129,11 @@ function renderKnowledgeAskResult(result: KnowledgeAskResult): string {
132
129
  export const knowledgeCommand: SlashCommand = {
133
130
  name: 'knowledge',
134
131
  aliases: ['know', 'kb'],
135
- description: 'Structured knowledge graph: ingest URLs/bookmarks, inspect issues, and build compact prompt packets.',
132
+ description: 'Agent Knowledge/Wiki: isolated Agent-owned sources, graph, review queue, and compact prompt packets.',
136
133
  usage: '<subcommand> [args]',
137
134
  argsHint: 'status|ask|ingest-url|import-bookmarks|import-urls|list|search|get|queue|review-issue|candidates|reports|schedules|lint|packet|explain|reindex|consolidate',
138
135
  handler: async (args: string[], context: CommandContext): Promise<void> => {
139
- const knowledge = requireKnowledgeApi(context);
136
+ const knowledge = requireAgentKnowledgeApi(context);
140
137
  if (!knowledge) {
141
138
  return;
142
139
  }
@@ -149,12 +146,16 @@ export const knowledgeCommand: SlashCommand = {
149
146
 
150
147
  switch (sub) {
151
148
  case 'ask': {
152
- const ask = requireKnowledgeAsk(context);
149
+ const ask = requireAgentKnowledgeAsk(context);
153
150
  if (!ask) return;
154
- const valuedFlags = ['--space', '--knowledge-space', '--limit', '--mode'];
151
+ const valuedFlags = ['--limit', '--mode'];
155
152
  const query = positionalArgs(rest, valuedFlags).join(' ').trim();
156
153
  if (!query) {
157
- context.print('[knowledge] Usage: /knowledge ask <query> [--space <knowledgeSpaceId>] [--limit <n>] [--mode <concise|standard|detailed>]');
154
+ context.print('[knowledge] Usage: /knowledge ask <query> [--limit <n>] [--mode <concise|standard|detailed>]');
155
+ return;
156
+ }
157
+ if (readFlag(rest, '--space') || readFlag(rest, '--knowledge-space')) {
158
+ context.print('[knowledge] Agent Knowledge is isolated. --space/--knowledge-space is not accepted because Agent must not fall back to default Knowledge/Wiki or HomeGraph.');
158
159
  return;
159
160
  }
160
161
  const requestedMode = readFlag(rest, '--mode') as KnowledgeAskMode | undefined;
@@ -163,7 +164,6 @@ export const knowledgeCommand: SlashCommand = {
163
164
  : 'standard';
164
165
  const result = await ask({
165
166
  query,
166
- knowledgeSpaceId: readFlag(rest, '--space') ?? readFlag(rest, '--knowledge-space'),
167
167
  limit: readPositiveIntFlag(rest, '--limit', 10),
168
168
  mode,
169
169
  includeSources: true,
@@ -177,7 +177,7 @@ export const knowledgeCommand: SlashCommand = {
177
177
  case 'status': {
178
178
  const status = await knowledge.status.get();
179
179
  context.print([
180
- '[knowledge] Structured knowledge status',
180
+ '[knowledge] Agent Knowledge status',
181
181
  ` ready: ${status.ready ? 'yes' : 'no'}`,
182
182
  ` storage: ${status.storagePath}`,
183
183
  ` sources: ${status.sourceCount}`,
@@ -385,7 +385,7 @@ export const knowledgeCommand: SlashCommand = {
385
385
  const result = await knowledge.graph.issues.review({
386
386
  issueId,
387
387
  action: action as KnowledgeReviewAction,
388
- reviewer: readFlag(rest, '--reviewer') ?? 'tui',
388
+ reviewer: readFlag(rest, '--reviewer') ?? 'agent',
389
389
  ...(value ? { value } : {}),
390
390
  });
391
391
  context.print([
@@ -508,7 +508,7 @@ export const knowledgeCommand: SlashCommand = {
508
508
  context.print([
509
509
  'Usage: /knowledge <subcommand>',
510
510
  ' status',
511
- ' ask <query> [--space <knowledgeSpaceId>] [--limit <n>] [--mode <concise|standard|detailed>]',
511
+ ' ask <query> [--limit <n>] [--mode <concise|standard|detailed>]',
512
512
  ' ingest-url <url> [--title <title>] [--tags <a,b>] [--folder <path>]',
513
513
  ' import-bookmarks <path>',
514
514
  ' import-urls <path>',
@@ -0,0 +1,219 @@
1
+ import { AgentPersonaRegistry, type AgentPersonaRecord } from '../../agent/persona-registry.ts';
2
+ import type { CommandContext, CommandRegistry } from '../command-registry.ts';
3
+ import { requireShellPaths } from './runtime-services.ts';
4
+
5
+ interface ParsedPersonaArgs {
6
+ readonly rest: readonly string[];
7
+ readonly flags: ReadonlyMap<string, string>;
8
+ readonly yes: boolean;
9
+ }
10
+
11
+ function parsePersonaArgs(args: readonly string[]): ParsedPersonaArgs {
12
+ const flags = new Map<string, string>();
13
+ const rest: string[] = [];
14
+ let yes = false;
15
+ for (let index = 0; index < args.length; index += 1) {
16
+ const token = args[index] ?? '';
17
+ if (token === '--yes') {
18
+ yes = true;
19
+ continue;
20
+ }
21
+ if (token.startsWith('--')) {
22
+ const key = token.slice(2);
23
+ const next = args[index + 1];
24
+ if (next !== undefined && !next.startsWith('--')) {
25
+ flags.set(key, next);
26
+ index += 1;
27
+ } else {
28
+ flags.set(key, 'true');
29
+ }
30
+ continue;
31
+ }
32
+ rest.push(token);
33
+ }
34
+ return { rest, flags, yes };
35
+ }
36
+
37
+ function splitList(value: string | undefined): readonly string[] {
38
+ if (!value) return [];
39
+ return value.split(',').map((entry) => entry.trim()).filter(Boolean);
40
+ }
41
+
42
+ function registryFromContext(ctx: CommandContext): AgentPersonaRegistry {
43
+ return AgentPersonaRegistry.fromShellPaths(requireShellPaths(ctx));
44
+ }
45
+
46
+ function summarizePersona(persona: AgentPersonaRecord, activePersonaId: string | null): string {
47
+ const active = persona.id === activePersonaId ? 'active' : 'inactive';
48
+ const tags = persona.tags.length > 0 ? ` tags=${persona.tags.join(',')}` : '';
49
+ return ` ${persona.id} ${active} ${persona.reviewState} ${persona.name} - ${persona.description}${tags}`;
50
+ }
51
+
52
+ function renderList(title: string, registry: AgentPersonaRegistry, personas: readonly AgentPersonaRecord[]): string {
53
+ const snapshot = registry.snapshot();
54
+ if (personas.length === 0) {
55
+ return `${title}\n No local Agent personas yet. Create one with /personas create --name <name> --description <summary> --body <instructions>.`;
56
+ }
57
+ return [
58
+ `${title} (${personas.length})`,
59
+ ` store: ${snapshot.path}`,
60
+ ` active: ${snapshot.activePersona?.name ?? '(none)'}`,
61
+ ...personas.map((persona) => summarizePersona(persona, snapshot.activePersonaId)),
62
+ ].join('\n');
63
+ }
64
+
65
+ function renderPersona(persona: AgentPersonaRecord, activePersonaId: string | null): string {
66
+ return [
67
+ `Persona ${persona.name}`,
68
+ ` id: ${persona.id}`,
69
+ ` active: ${persona.id === activePersonaId ? 'yes' : 'no'}`,
70
+ ` review: ${persona.reviewState}`,
71
+ ` source: ${persona.source}`,
72
+ ` provenance: ${persona.provenance}`,
73
+ ` tags: ${persona.tags.join(', ') || '(none)'}`,
74
+ ` triggers: ${persona.triggers.join(', ') || '(none)'}`,
75
+ ` created: ${persona.createdAt}`,
76
+ ` updated: ${persona.updatedAt}`,
77
+ persona.staleReason ? ` stale reason: ${persona.staleReason}` : '',
78
+ '',
79
+ persona.description,
80
+ '',
81
+ persona.body,
82
+ ].filter(Boolean).join('\n');
83
+ }
84
+
85
+ function requiredFlag(flags: ReadonlyMap<string, string>, key: string): string {
86
+ const value = flags.get(key)?.trim();
87
+ if (!value) throw new Error(`Missing --${key}.`);
88
+ return value;
89
+ }
90
+
91
+ function printError(ctx: CommandContext, error: unknown): void {
92
+ ctx.print(`Error: ${error instanceof Error ? error.message : String(error)}`);
93
+ }
94
+
95
+ export function registerPersonasRuntimeCommands(registry: CommandRegistry): void {
96
+ registry.register({
97
+ name: 'personas',
98
+ aliases: ['persona'],
99
+ description: 'Manage local GoodVibes Agent personas',
100
+ usage: '[list|search <query>|show <id>|create --name <name> --description <summary> --body <instructions>|update <id> [--name ...] [--description ...] [--body ...]|use <id>|active|clear|review <id>|stale <id> <reason...>|delete <id> --yes]',
101
+ async handler(args, ctx) {
102
+ const sub = (args[0] ?? 'list').toLowerCase();
103
+ const registryStore = registryFromContext(ctx);
104
+ try {
105
+ if (sub === 'list' || sub === 'open') {
106
+ ctx.print(renderList('Agent Personas', registryStore, registryStore.list()));
107
+ return;
108
+ }
109
+ if (sub === 'search') {
110
+ const query = args.slice(1).join(' ').trim();
111
+ ctx.print(renderList(query ? `Agent Personas matching "${query}"` : 'Agent Personas', registryStore, registryStore.search(query)));
112
+ return;
113
+ }
114
+ if (sub === 'show') {
115
+ const id = args[1];
116
+ if (!id) {
117
+ ctx.print('Usage: /personas show <id>');
118
+ return;
119
+ }
120
+ const snapshot = registryStore.snapshot();
121
+ const persona = registryStore.get(id);
122
+ ctx.print(persona ? renderPersona(persona, snapshot.activePersonaId) : `Unknown persona: ${id}`);
123
+ return;
124
+ }
125
+ if (sub === 'create') {
126
+ const parsed = parsePersonaArgs(args.slice(1));
127
+ const body = parsed.flags.get('body')?.trim() || parsed.rest.join(' ').trim();
128
+ const persona = registryStore.create({
129
+ name: requiredFlag(parsed.flags, 'name'),
130
+ description: requiredFlag(parsed.flags, 'description'),
131
+ body,
132
+ tags: splitList(parsed.flags.get('tags')),
133
+ triggers: splitList(parsed.flags.get('triggers')),
134
+ source: 'user',
135
+ provenance: 'slash-command',
136
+ });
137
+ ctx.print(`Created Agent persona ${persona.id}: ${persona.name}`);
138
+ return;
139
+ }
140
+ if (sub === 'update') {
141
+ const id = args[1];
142
+ if (!id) {
143
+ ctx.print('Usage: /personas update <id> [--name ...] [--description ...] [--body ...]');
144
+ return;
145
+ }
146
+ const parsed = parsePersonaArgs(args.slice(2));
147
+ const updated = registryStore.update(id, {
148
+ name: parsed.flags.get('name'),
149
+ description: parsed.flags.get('description'),
150
+ body: parsed.flags.get('body'),
151
+ tags: parsed.flags.has('tags') ? splitList(parsed.flags.get('tags')) : undefined,
152
+ triggers: parsed.flags.has('triggers') ? splitList(parsed.flags.get('triggers')) : undefined,
153
+ provenance: 'slash-command',
154
+ });
155
+ ctx.print(`Updated Agent persona ${updated.id}: ${updated.name}`);
156
+ return;
157
+ }
158
+ if (sub === 'use') {
159
+ const id = args[1];
160
+ if (!id) {
161
+ ctx.print('Usage: /personas use <id>');
162
+ return;
163
+ }
164
+ const persona = registryStore.setActive(id);
165
+ ctx.print(`Active Agent persona: ${persona.name} (${persona.id})`);
166
+ return;
167
+ }
168
+ if (sub === 'active') {
169
+ const snapshot = registryStore.snapshot();
170
+ ctx.print(snapshot.activePersona ? renderPersona(snapshot.activePersona, snapshot.activePersonaId) : 'No active Agent persona.');
171
+ return;
172
+ }
173
+ if (sub === 'clear') {
174
+ registryStore.clearActive();
175
+ ctx.print('Cleared active Agent persona.');
176
+ return;
177
+ }
178
+ if (sub === 'review') {
179
+ const id = args[1];
180
+ if (!id) {
181
+ ctx.print('Usage: /personas review <id>');
182
+ return;
183
+ }
184
+ const persona = registryStore.markReviewed(id);
185
+ ctx.print(`Reviewed Agent persona ${persona.id}.`);
186
+ return;
187
+ }
188
+ if (sub === 'stale') {
189
+ const id = args[1];
190
+ if (!id) {
191
+ ctx.print('Usage: /personas stale <id> <reason...>');
192
+ return;
193
+ }
194
+ const persona = registryStore.markStale(id, args.slice(2).join(' '));
195
+ ctx.print(`Marked Agent persona ${persona.id} stale.`);
196
+ return;
197
+ }
198
+ if (sub === 'delete' || sub === 'remove') {
199
+ const parsed = parsePersonaArgs(args.slice(1));
200
+ const id = parsed.rest[0];
201
+ if (!id) {
202
+ ctx.print('Usage: /personas delete <id> --yes');
203
+ return;
204
+ }
205
+ if (!parsed.yes) {
206
+ ctx.print(`Refusing to delete Agent persona ${id} without --yes.`);
207
+ return;
208
+ }
209
+ const removed = registryStore.deletePersona(id);
210
+ ctx.print(`Deleted Agent persona ${removed.id}: ${removed.name}`);
211
+ return;
212
+ }
213
+ ctx.print('Usage: /personas [list|search <query>|show <id>|create|update|use|active|clear|review|stale|delete]');
214
+ } catch (error) {
215
+ printError(ctx, error);
216
+ }
217
+ },
218
+ });
219
+ }
@@ -2,7 +2,6 @@ import type { CommandRegistry } from '../command-registry.ts';
2
2
  import type { SelectionItem } from '../selection-modal.ts';
3
3
  import { EFFORT_DESCRIPTIONS } from '@pellux/goodvibes-sdk/platform/providers';
4
4
  import { REASONING_BUDGET_MAP } from '@pellux/goodvibes-sdk/platform/providers';
5
- import { executeWriteQuit } from './quit-shared.ts';
6
5
  import { compactConversation, requireKeybindingsManager, requireProviderApi } from './runtime-services.ts';
7
6
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
8
7
  import { logger } from '@pellux/goodvibes-sdk/platform/utils';
@@ -159,7 +158,7 @@ export function registerShellCoreCommands(registry: CommandRegistry): void {
159
158
  { id: '/secrets', label: '/secrets set|link|get|test|list|delete', detail: 'Manage encrypted and provider-backed secrets', category: 'Tools & System' },
160
159
  { id: '/help', label: '/help', detail: 'This help', category: 'Tools & System' },
161
160
  { id: '/quit', label: '/quit', detail: 'Exit', category: 'Tools & System' },
162
- { id: '/wq', label: '/wq', detail: 'Commit all git changes and then exit', category: 'Tools & System' },
161
+ { id: '/wq', label: '/wq', detail: 'Blocked in Agent; git commit/exit belongs to GoodVibes TUI', category: 'Tools & System' },
163
162
  ];
164
163
  ctx.openSelection('Help — Commands', items, { allowSearch: true }, (result) => {
165
164
  if (!result) return;
@@ -173,7 +172,7 @@ export function registerShellCoreCommands(registry: CommandRegistry): void {
173
172
  });
174
173
  return;
175
174
  }
176
- ctx.print('Use /help to open the help modal. Commands: /agent, /model, /provider, /config, /template, /tools, /paste, /sessions, /bookmarks, /save, /load, /undo, /redo, /retry, /clear, /reset, /compact, /export, /title, /effort, /expand, /collapse, /debug, /quit, /wq');
175
+ ctx.print('Use /help to open the help modal. Commands: /agent, /model, /provider, /config, /template, /tools, /paste, /sessions, /bookmarks, /save, /load, /undo, /redo, /retry, /clear, /reset, /compact, /export, /title, /effort, /expand, /collapse, /debug, /quit');
177
176
  },
178
177
  });
179
178
 
@@ -225,9 +224,13 @@ export function registerShellCoreCommands(registry: CommandRegistry): void {
225
224
  registry.register({
226
225
  name: 'wq',
227
226
  aliases: [':wq'],
228
- description: 'Commit all git changes and then exit',
229
- async handler(_args, ctx) {
230
- await executeWriteQuit(ctx);
227
+ description: 'Blocked in Agent; git commit/exit is owned by GoodVibes TUI',
228
+ handler(_args, ctx) {
229
+ ctx.print([
230
+ 'Blocked: /wq is not available in GoodVibes Agent.',
231
+ 'Git commit, worktree, and coding-session exit flows belong to GoodVibes TUI.',
232
+ 'No files, commits, or repository state were changed.',
233
+ ].join('\n'));
231
234
  },
232
235
  });
233
236
 
@@ -12,15 +12,20 @@ import {
12
12
  upsertEcosystemCatalogEntry,
13
13
  } from '@/runtime/index.ts';
14
14
  import { requireEcosystemCatalogPaths, requirePanelManager, requireShellPaths } from './runtime-services.ts';
15
+ import { runAgentSkillsRuntimeCommand } from './agent-skills-runtime.ts';
15
16
 
16
17
  export function registerSkillsRuntimeCommands(registry: CommandRegistry): void {
17
18
  registry.register({
18
19
  name: 'skills',
19
20
  aliases: ['skill'],
20
21
  description: 'Inspect installed skill packs',
21
- usage: '[open|list|show <name>|origins|browse [query]|installed|catalog-review <id>|publish-local <id> <path> <summary...>|unpublish <id>|install-hint <catalog-id>|install <id> [project|user]|update <id> [project|user]|uninstall <id> [project|user]]',
22
+ usage: '[open|local ...|list|show <name>|origins|browse [query]|installed|catalog-review <id>|publish-local <id> <path> <summary...>|unpublish <id>|install-hint <catalog-id>|install <id> [project|user]|update <id> [project|user]|uninstall <id> [project|user]]',
22
23
  async handler(args, ctx) {
23
24
  const sub = args[0] ?? 'open';
25
+ if (sub === 'local' || sub === 'agent') {
26
+ await runAgentSkillsRuntimeCommand(args.slice(1), ctx);
27
+ return;
28
+ }
24
29
  if (sub === 'open' || sub === 'panel') {
25
30
  if (ctx.showPanel) ctx.showPanel('skills');
26
31
  else {
@@ -215,7 +220,7 @@ export function registerSkillsRuntimeCommands(registry: CommandRegistry): void {
215
220
  ctx.print(result.ok ? `Uninstalled curated skill ${entryId} from ${result.removedPath}` : `Error: ${result.error}`);
216
221
  return;
217
222
  }
218
- ctx.print('Usage: /skills [open|list|show <name>|origins|browse [query]|installed|catalog-review <id>|publish-local <id> <path> <summary...>|unpublish <id>|install-hint <catalog-id>|install <id> [project|user]|update <id> [project|user]|uninstall <id> [project|user]]');
223
+ ctx.print('Usage: /skills [open|local ...|list|show <name>|origins|browse [query]|installed|catalog-review <id>|publish-local <id> <path> <summary...>|unpublish <id>|install-hint <catalog-id>|install <id> [project|user]|update <id> [project|user]|uninstall <id> [project|user]]');
219
224
  },
220
225
  });
221
226
  }
@@ -54,6 +54,9 @@ import { registerCloudflareRuntimeCommands } from './commands/cloudflare-runtime
54
54
  import { registerWorkPlanRuntimeCommands } from './commands/work-plan-runtime.ts';
55
55
  import { registerAgentWorkspaceRuntimeCommands } from './commands/agent-workspace-runtime.ts';
56
56
  import { registerAgentExternalizedTuiCommands } from './commands/agent-externalized-tui.ts';
57
+ import { registerDelegationRuntimeCommands } from './commands/delegation-runtime.ts';
58
+ import { registerPersonasRuntimeCommands } from './commands/personas-runtime.ts';
59
+ import { registerAgentSkillsRuntimeCommands } from './commands/agent-skills-runtime.ts';
57
60
 
58
61
  /**
59
62
  * registerBuiltinCommands - Register all built-in slash commands into the registry.
@@ -62,6 +65,9 @@ import { registerAgentExternalizedTuiCommands } from './commands/agent-externali
62
65
  export function registerBuiltinCommands(registry: CommandRegistry): void {
63
66
  registerShellCoreCommands(registry);
64
67
  registerAgentWorkspaceRuntimeCommands(registry);
68
+ registerPersonasRuntimeCommands(registry);
69
+ registerAgentSkillsRuntimeCommands(registry);
70
+ registerDelegationRuntimeCommands(registry);
65
71
  registerConfigCommand(registry);
66
72
  registerOperatorRuntimeCommands(registry);
67
73
  registerIntegrationRuntimeCommands(registry);
@@ -2,37 +2,8 @@ import type { CommandContext } from './command-registry.ts';
2
2
  import { logger } from '@pellux/goodvibes-sdk/platform/utils';
3
3
  import type { Panel } from '../panels/types.ts';
4
4
  import type { PanelManager } from '../panels/panel-manager.ts';
5
- import { FileExplorerPanel } from '../panels/file-explorer-panel.ts';
6
- import { FilePreviewPanel } from '../panels/file-preview-panel.ts';
7
- import { SymbolOutlinePanel } from '../panels/symbol-outline-panel.ts';
8
5
  import { ApprovalPanel } from '../panels/approval-panel.ts';
9
6
 
10
- function ensurePreviewPanel(panelManager: PanelManager): FilePreviewPanel | null {
11
- const existing = panelManager.getPanel('preview');
12
- if (existing instanceof FilePreviewPanel) {
13
- const pane = panelManager.getPaneOf('preview');
14
- panelManager.activateById('preview');
15
- if (pane) panelManager.focusPane(pane);
16
- return existing;
17
- }
18
- const targetPane: 'top' | 'bottom' = panelManager.isBottomPaneVisible()
19
- ? (panelManager.getFocusedPane() === 'top' ? 'bottom' : 'top')
20
- : 'bottom';
21
- const opened = panelManager.open('preview', targetPane);
22
- panelManager.show();
23
- panelManager.focusPane(targetPane);
24
- return opened instanceof FilePreviewPanel ? opened : null;
25
- }
26
-
27
- function syncSymbolOutlineFromPreview(panelManager: PanelManager, previewPanel: FilePreviewPanel): void {
28
- const symbols = panelManager.getPanel('symbols');
29
- const filePath = previewPanel.getCurrentFilePath();
30
- const source = previewPanel.getSource();
31
- if (symbols instanceof SymbolOutlinePanel && filePath && source !== null) {
32
- symbols.loadFile(filePath, source);
33
- }
34
- }
35
-
36
7
  export function handlePanelIntegrationAction(
37
8
  panelManager: PanelManager,
38
9
  activePanel: Panel | null,
@@ -41,29 +12,6 @@ export function handlePanelIntegrationAction(
41
12
  ): boolean {
42
13
  if (!activePanel) return false;
43
14
 
44
- if ((key === 'enter' || key === 'return' || key === 'right') && activePanel instanceof FileExplorerPanel) {
45
- const filePath = activePanel.getFocusedFilePath();
46
- if (!filePath) return false;
47
- const previewPanel = ensurePreviewPanel(panelManager);
48
- if (!previewPanel) return false;
49
- previewPanel.openFile(filePath);
50
- syncSymbolOutlineFromPreview(panelManager, previewPanel);
51
- return true;
52
- }
53
-
54
- if ((key === 'enter' || key === 'return') && activePanel instanceof SymbolOutlinePanel) {
55
- const location = activePanel.getSelectedLocation();
56
- if (!location) return false;
57
- const previewPanel = ensurePreviewPanel(panelManager);
58
- if (!previewPanel) return false;
59
- if (previewPanel.getCurrentFilePath() !== location.path) {
60
- previewPanel.openFile(location.path);
61
- syncSymbolOutlineFromPreview(panelManager, previewPanel);
62
- }
63
- previewPanel.goToLine(location.line);
64
- return true;
65
- }
66
-
67
15
  if ((key === 'enter' || key === 'return') && activePanel instanceof ApprovalPanel) {
68
16
  const command = activePanel.getSelectedCommand();
69
17
  if (!command || !commandContext?.executeCommand) return false;
@@ -8,7 +8,7 @@ export interface SubmissionRouterInput {
8
8
  }
9
9
 
10
10
  const PLAN_COMMANDS = new Set(['plan']);
11
- const DELEGATION_COMMANDS = new Set(['review', 'wrfc', 'teamwork', 'agents', 'remote']);
11
+ const DELEGATION_COMMANDS = new Set(['delegate', 'build', 'review', 'wrfc', 'teamwork', 'agents', 'remote']);
12
12
  const PANEL_COMMANDS = new Set(['panel']);
13
13
  const ORCHESTRATION_COMMANDS = new Set([
14
14
  'orchestration',
package/src/main.ts CHANGED
@@ -54,6 +54,7 @@ import { allowTerminalWrite, installTuiTerminalOutputGuard } from './runtime/ter
54
54
  import { ProjectPlanningCoordinator } from './planning/project-planning-coordinator.ts';
55
55
  import { buildCommandArgsHint } from './input/command-args-hint.ts';
56
56
  import { summarizeRunningAgents } from './renderer/process-summary.ts';
57
+ import { GOODVIBES_AGENT_PAIRING_SURFACE } from './config/surface.ts';
57
58
 
58
59
  const ALT_SCREEN_ENTER = '\x1b[?1049h';
59
60
  const ALT_SCREEN_EXIT = '\x1b[?1049l';
@@ -320,7 +321,7 @@ async function main() {
320
321
  inputOptions = {
321
322
  origin: {
322
323
  source: 'project-planning',
323
- surface: 'tui',
324
+ surface: GOODVIBES_AGENT_PAIRING_SURFACE,
324
325
  metadata: {
325
326
  projectId: ctx.services.projectPlanningProjectId,
326
327
  knowledgeSpaceId: planning.state.knowledgeSpaceId,
@@ -3,7 +3,6 @@ import { AgentLogsPanel } from '../agent-logs-panel.ts';
3
3
  import { ContextVisualizerPanel } from '../context-visualizer-panel.ts';
4
4
  import { ThinkingPanel } from '../thinking-panel.ts';
5
5
  import { ToolInspectorPanel } from '../tool-inspector-panel.ts';
6
- import { WrfcPanel } from '../wrfc-panel.ts';
7
6
  import { SchedulePanel } from '../schedule-panel.ts';
8
7
  import { ProjectPlanningPanel } from '../project-planning-panel.ts';
9
8
  import { WorkPlanPanel } from '../work-plan-panel.ts';
@@ -67,19 +66,6 @@ export function registerAgentPanels(manager: PanelManager, deps: ResolvedBuiltin
67
66
  },
68
67
  });
69
68
 
70
- manager.registerType({
71
- id: 'wrfc',
72
- name: 'WRFC',
73
- icon: 'W',
74
- category: 'agent',
75
- description: 'WRFC chain view: write, review, fix, and confirm cycle status',
76
- preload: true,
77
- factory: () => {
78
- const ui = requireUiServices(deps);
79
- return new WrfcPanel(ui.events.workflows, { controller: ui.agents.wrfcController });
80
- },
81
- });
82
-
83
69
  manager.registerType({
84
70
  id: 'work-plan',
85
71
  name: 'Work Plan',
@@ -14,6 +14,7 @@ import {
14
14
  buildCompanionConnectionInfo,
15
15
  } from '@pellux/goodvibes-sdk/platform/pairing';
16
16
  import { copyToClipboard } from '../../utils/clipboard.ts';
17
+ import { GOODVIBES_AGENT_PAIRING_SURFACE } from '../../config/surface.ts';
17
18
 
18
19
  function getLocalNetworkIp(): string {
19
20
  const nets = networkInterfaces();
@@ -51,7 +52,7 @@ export function registerSessionPanels(manager: PanelManager, deps: ResolvedBuilt
51
52
  factory: () => {
52
53
  if (!deps.daemonHomeDir) throw new Error('daemonHomeDir must be provided to the session panel factory via BuiltinPanelDeps');
53
54
  const daemonHomeDir = deps.daemonHomeDir;
54
- const tokenRecord = getOrCreateCompanionToken('tui', { daemonHomeDir });
55
+ const tokenRecord = getOrCreateCompanionToken(GOODVIBES_AGENT_PAIRING_SURFACE, { daemonHomeDir });
55
56
  const daemonPort = deps.configManager.get('controlPlane.port');
56
57
  const daemonHost = String(process.env['GOODVIBES_DAEMON_HOST'] ?? getLocalNetworkIp());
57
58
  const daemonUrl = `http://${daemonHost}:${daemonPort}`;
@@ -60,7 +61,7 @@ export function registerSessionPanels(manager: PanelManager, deps: ResolvedBuilt
60
61
  daemonUrl,
61
62
  token: tokenRecord.token,
62
63
  password: bootstrapPassword,
63
- surface: 'tui',
64
+ surface: GOODVIBES_AGENT_PAIRING_SURFACE,
64
65
  });
65
66
  const regenerate = (): typeof connectionInfo => {
66
67
  const newRecord = regenerateCompanionToken({ daemonHomeDir });
@@ -68,7 +69,7 @@ export function registerSessionPanels(manager: PanelManager, deps: ResolvedBuilt
68
69
  daemonUrl,
69
70
  token: newRecord.token,
70
71
  password: bootstrapPassword,
71
- surface: 'tui',
72
+ surface: GOODVIBES_AGENT_PAIRING_SURFACE,
72
73
  });
73
74
  };
74
75
  return new QrPanel(connectionInfo, regenerate, copyToClipboard);
@@ -6,13 +6,9 @@ export { TokenBudgetPanel } from './token-budget-panel.ts';
6
6
  export { CostTrackerPanel } from './cost-tracker-panel.ts';
7
7
  export { AgentInspectorPanel } from './agent-inspector-panel.ts';
8
8
  export { AgentLogsPanel } from './agent-logs-panel.ts';
9
- export { WrfcPanel } from './wrfc-panel.ts';
10
9
  export { ProviderHealthPanel } from './provider-health-panel.ts';
11
10
  export { ProviderHealthTracker } from './provider-health-tracker.ts';
12
11
  export type { ProviderHealth, ProviderStatus } from './provider-health-tracker.ts';
13
- export { GitPanel } from './git-panel.ts';
14
- export { SymbolOutlinePanel } from './symbol-outline-panel.ts';
15
- export type { SymbolEntry, SymbolKind } from './symbol-outline-panel.ts';
16
12
  export { ProviderStatsPanel } from './provider-stats-panel.ts';
17
13
  export { SessionBrowserPanel } from './session-browser-panel.ts';
18
14
  export { DocsPanel } from './docs-panel.ts';
@@ -38,7 +34,6 @@ export { SubscriptionPanel } from './subscription-panel.ts';
38
34
  export { HooksPanel } from './hooks-panel.ts';
39
35
  export { SecurityPanel } from './security-panel.ts';
40
36
  export { MarketplacePanel } from './marketplace-panel.ts';
41
- export { SandboxPanel } from './sandbox-panel.ts';
42
37
  export { ApprovalPanel } from './approval-panel.ts';
43
38
  export { KnowledgePanel } from './knowledge-panel.ts';
44
39
  export { SystemMessagesPanel } from './system-messages-panel.ts';
@@ -1,6 +1,5 @@
1
1
  /**
2
- * OrchestrationPanel — displays task graphs, node contracts, recursion guards,
3
- * and WRFC-visible orchestration state.
2
+ * OrchestrationPanel — displays task graphs, node contracts, and recursion guards.
4
3
  *
5
4
  * Migrated (Wave B2): extends ScrollableListPanel<OrchestrationGraphRecord>.
6
5
  * Navigation (up/down/j/k) is handled by the base class.
@@ -102,7 +101,7 @@ export class OrchestrationPanel extends ScrollableListPanel<OrchestrationGraphRe
102
101
  // ---------------------------------------------------------------------------
103
102
 
104
103
  public render(width: number, height: number): Line[] {
105
- const intro = 'Task graphs, node contracts, recursion guards, and WRFC-visible orchestration state.';
104
+ const intro = 'Read-only task graph posture, node contracts, and recursion guard state. Agent does not start local worker chains.';
106
105
 
107
106
  if (!this.readModel) {
108
107
  this.needsRender = false;
@@ -134,7 +133,7 @@ export class OrchestrationPanel extends ScrollableListPanel<OrchestrationGraphRe
134
133
  { label: 'failed', value: String(snapshot.totalFailedGraphs), valueColor: snapshot.totalFailedGraphs > 0 ? C.failed : C.dim },
135
134
  { label: 'guards', value: String(snapshot.recursionGuardTrips), valueColor: snapshot.recursionGuardTrips > 0 ? C.blocked : C.dim },
136
135
  ], C),
137
- buildGuidanceLine(width, '/orchestration', 'inspect recursive execution posture, graph health, and node contract flow', C),
136
+ buildGuidanceLine(width, '/orchestration', 'inspect graph health without spawning local workers', C),
138
137
  ];
139
138
  if (graphs.length === 0) {
140
139
  this.needsRender = false;
@@ -148,7 +147,7 @@ export class OrchestrationPanel extends ScrollableListPanel<OrchestrationGraphRe
148
147
  ...buildEmptyState(
149
148
  width,
150
149
  this.getEmptyStateMessage(),
151
- 'Graphs, nodes, child contracts, and recursion guard trips will appear here as orchestration starts.',
150
+ 'Graphs, nodes, child contracts, and recursion guard trips appear here only if a runtime record already exists.',
152
151
  [
153
152
  { command: '/tasks', summary: 'create or inspect task flows that feed orchestration graphs' },
154
153
  { command: '/communication', summary: 'review structured agent communication alongside graph execution' },