@pellux/goodvibes-agent 0.1.2 → 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.
- package/CHANGELOG.md +6 -0
- package/LICENSE +21 -0
- package/README.md +12 -1
- package/docs/README.md +2 -0
- package/docs/getting-started.md +19 -1
- package/docs/release-and-publishing.md +3 -1
- package/package.json +10 -1
- package/src/agent/persona-registry.ts +379 -0
- package/src/agent/skill-registry.ts +360 -0
- package/src/audio/spoken-turn-model-routing.ts +2 -1
- package/src/cli/management-commands.ts +3 -1
- package/src/config/surface.ts +1 -0
- package/src/input/agent-workspace.ts +32 -2
- package/src/input/command-registry.ts +4 -1
- package/src/input/commands/agent-skills-runtime.ts +216 -0
- package/src/input/commands/knowledge.ts +18 -18
- package/src/input/commands/personas-runtime.ts +219 -0
- package/src/input/commands/skills-runtime.ts +7 -2
- package/src/input/commands.ts +4 -0
- package/src/input/panel-integration-actions.ts +0 -52
- package/src/main.ts +2 -1
- package/src/panels/builtin/session.ts +4 -3
- package/src/panels/index.ts +0 -5
- package/src/panels/orchestration-panel.ts +4 -5
- package/src/panels/qr-panel.ts +3 -2
- package/src/panels/tasks-panel.ts +4 -4
- package/src/renderer/agent-workspace.ts +2 -0
- package/src/runtime/bootstrap-command-context.ts +3 -0
- package/src/runtime/bootstrap-command-parts.ts +6 -2
- package/src/runtime/bootstrap-core.ts +8 -4
- package/src/runtime/bootstrap-shell.ts +3 -1
- package/src/runtime/bootstrap.ts +10 -2
- package/src/runtime/cloudflare-control-plane.ts +2 -1
- package/src/version.ts +1 -1
- package/src/daemon/cli.ts +0 -55
- package/src/daemon/safe-serve.ts +0 -61
- package/src/panels/diff-panel.ts +0 -520
- package/src/panels/file-explorer-panel.ts +0 -584
- package/src/panels/file-preview-panel.ts +0 -434
- package/src/panels/git-panel.ts +0 -638
- package/src/panels/sandbox-panel.ts +0 -283
- package/src/panels/symbol-outline-panel.ts +0 -486
- package/src/panels/worktree-panel.ts +0 -182
- package/src/panels/wrfc-panel.ts +0 -609
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { AgentSkillRegistry, type AgentSkillRecord } from '../../agent/skill-registry.ts';
|
|
2
|
+
import type { CommandContext, CommandRegistry } from '../command-registry.ts';
|
|
3
|
+
import { requireShellPaths } from './runtime-services.ts';
|
|
4
|
+
|
|
5
|
+
interface ParsedSkillArgs {
|
|
6
|
+
readonly rest: readonly string[];
|
|
7
|
+
readonly flags: ReadonlyMap<string, string>;
|
|
8
|
+
readonly yes: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function parseSkillArgs(args: readonly string[]): ParsedSkillArgs {
|
|
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): AgentSkillRegistry {
|
|
43
|
+
return AgentSkillRegistry.fromShellPaths(requireShellPaths(ctx));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function requiredFlag(flags: ReadonlyMap<string, string>, key: string): string {
|
|
47
|
+
const value = flags.get(key)?.trim();
|
|
48
|
+
if (!value) throw new Error(`Missing --${key}.`);
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function summarizeSkill(skill: AgentSkillRecord): string {
|
|
53
|
+
const enabled = skill.enabled ? 'enabled' : 'disabled';
|
|
54
|
+
const tags = skill.tags.length > 0 ? ` tags=${skill.tags.join(',')}` : '';
|
|
55
|
+
return ` ${skill.id} ${enabled} ${skill.reviewState} ${skill.name} - ${skill.description}${tags}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function renderList(title: string, registry: AgentSkillRegistry, skills: readonly AgentSkillRecord[]): string {
|
|
59
|
+
const snapshot = registry.snapshot();
|
|
60
|
+
if (skills.length === 0) {
|
|
61
|
+
return `${title}\n No local Agent skills yet. Create one with /agent-skills create --name <name> --description <summary> --procedure <steps>.`;
|
|
62
|
+
}
|
|
63
|
+
return [
|
|
64
|
+
`${title} (${skills.length})`,
|
|
65
|
+
` store: ${snapshot.path}`,
|
|
66
|
+
` enabled: ${snapshot.enabledSkills.length}`,
|
|
67
|
+
...skills.map(summarizeSkill),
|
|
68
|
+
].join('\n');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function renderSkill(skill: AgentSkillRecord): string {
|
|
72
|
+
return [
|
|
73
|
+
`Skill ${skill.name}`,
|
|
74
|
+
` id: ${skill.id}`,
|
|
75
|
+
` enabled: ${skill.enabled ? 'yes' : 'no'}`,
|
|
76
|
+
` review: ${skill.reviewState}`,
|
|
77
|
+
` source: ${skill.source}`,
|
|
78
|
+
` provenance: ${skill.provenance}`,
|
|
79
|
+
` tags: ${skill.tags.join(', ') || '(none)'}`,
|
|
80
|
+
` triggers: ${skill.triggers.join(', ') || '(manual)'}`,
|
|
81
|
+
` created: ${skill.createdAt}`,
|
|
82
|
+
` updated: ${skill.updatedAt}`,
|
|
83
|
+
skill.staleReason ? ` stale reason: ${skill.staleReason}` : '',
|
|
84
|
+
'',
|
|
85
|
+
skill.description,
|
|
86
|
+
'',
|
|
87
|
+
skill.procedure,
|
|
88
|
+
].filter(Boolean).join('\n');
|
|
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 async function runAgentSkillsRuntimeCommand(args: readonly string[], ctx: CommandContext): Promise<void> {
|
|
96
|
+
const sub = (args[0] ?? 'list').toLowerCase();
|
|
97
|
+
const skillRegistry = registryFromContext(ctx);
|
|
98
|
+
try {
|
|
99
|
+
if (sub === 'list' || sub === 'open') {
|
|
100
|
+
ctx.print(renderList('Agent Skills', skillRegistry, skillRegistry.list()));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (sub === 'enabled') {
|
|
104
|
+
const snapshot = skillRegistry.snapshot();
|
|
105
|
+
ctx.print(renderList('Enabled Agent Skills', skillRegistry, snapshot.enabledSkills));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (sub === 'search') {
|
|
109
|
+
const query = args.slice(1).join(' ').trim();
|
|
110
|
+
ctx.print(renderList(query ? `Agent Skills matching "${query}"` : 'Agent Skills', skillRegistry, skillRegistry.search(query)));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (sub === 'show') {
|
|
114
|
+
const id = args[1];
|
|
115
|
+
if (!id) {
|
|
116
|
+
ctx.print('Usage: /agent-skills show <id>');
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const skill = skillRegistry.get(id);
|
|
120
|
+
ctx.print(skill ? renderSkill(skill) : `Unknown Agent skill: ${id}`);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (sub === 'create') {
|
|
124
|
+
const parsed = parseSkillArgs(args.slice(1));
|
|
125
|
+
const procedure = parsed.flags.get('procedure')?.trim() || parsed.rest.join(' ').trim();
|
|
126
|
+
const skill = skillRegistry.create({
|
|
127
|
+
name: requiredFlag(parsed.flags, 'name'),
|
|
128
|
+
description: requiredFlag(parsed.flags, 'description'),
|
|
129
|
+
procedure,
|
|
130
|
+
triggers: splitList(parsed.flags.get('triggers')),
|
|
131
|
+
tags: splitList(parsed.flags.get('tags')),
|
|
132
|
+
enabled: parsed.flags.get('enabled') === 'true',
|
|
133
|
+
source: 'user',
|
|
134
|
+
provenance: 'slash-command',
|
|
135
|
+
});
|
|
136
|
+
ctx.print(`Created Agent skill ${skill.id}: ${skill.name}`);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (sub === 'update') {
|
|
140
|
+
const id = args[1];
|
|
141
|
+
if (!id) {
|
|
142
|
+
ctx.print('Usage: /agent-skills update <id> [--name ...] [--description ...] [--procedure ...]');
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const parsed = parseSkillArgs(args.slice(2));
|
|
146
|
+
const updated = skillRegistry.update(id, {
|
|
147
|
+
name: parsed.flags.get('name'),
|
|
148
|
+
description: parsed.flags.get('description'),
|
|
149
|
+
procedure: parsed.flags.get('procedure'),
|
|
150
|
+
triggers: parsed.flags.has('triggers') ? splitList(parsed.flags.get('triggers')) : undefined,
|
|
151
|
+
tags: parsed.flags.has('tags') ? splitList(parsed.flags.get('tags')) : undefined,
|
|
152
|
+
provenance: 'slash-command',
|
|
153
|
+
});
|
|
154
|
+
ctx.print(`Updated Agent skill ${updated.id}: ${updated.name}`);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (sub === 'enable' || sub === 'disable') {
|
|
158
|
+
const id = args[1];
|
|
159
|
+
if (!id) {
|
|
160
|
+
ctx.print(`Usage: /agent-skills ${sub} <id>`);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const skill = skillRegistry.setEnabled(id, sub === 'enable');
|
|
164
|
+
ctx.print(`${sub === 'enable' ? 'Enabled' : 'Disabled'} Agent skill ${skill.id}: ${skill.name}`);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (sub === 'review') {
|
|
168
|
+
const id = args[1];
|
|
169
|
+
if (!id) {
|
|
170
|
+
ctx.print('Usage: /agent-skills review <id>');
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const skill = skillRegistry.markReviewed(id);
|
|
174
|
+
ctx.print(`Reviewed Agent skill ${skill.id}.`);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (sub === 'stale') {
|
|
178
|
+
const id = args[1];
|
|
179
|
+
if (!id) {
|
|
180
|
+
ctx.print('Usage: /agent-skills stale <id> <reason...>');
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const skill = skillRegistry.markStale(id, args.slice(2).join(' '));
|
|
184
|
+
ctx.print(`Marked Agent skill ${skill.id} stale.`);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (sub === 'delete' || sub === 'remove') {
|
|
188
|
+
const parsed = parseSkillArgs(args.slice(1));
|
|
189
|
+
const id = parsed.rest[0];
|
|
190
|
+
if (!id) {
|
|
191
|
+
ctx.print('Usage: /agent-skills delete <id> --yes');
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (!parsed.yes) {
|
|
195
|
+
ctx.print(`Refusing to delete Agent skill ${id} without --yes.`);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const removed = skillRegistry.deleteSkill(id);
|
|
199
|
+
ctx.print(`Deleted Agent skill ${removed.id}: ${removed.name}`);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
ctx.print('Usage: /agent-skills [list|enabled|search|show|create|update|enable|disable|review|stale|delete]');
|
|
203
|
+
} catch (error) {
|
|
204
|
+
printError(ctx, error);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function registerAgentSkillsRuntimeCommands(registry: CommandRegistry): void {
|
|
209
|
+
registry.register({
|
|
210
|
+
name: 'agent-skills',
|
|
211
|
+
aliases: ['askills', 'local-skills'],
|
|
212
|
+
description: 'Manage local GoodVibes Agent skills',
|
|
213
|
+
usage: '[list|enabled|search <query>|show <id>|create --name <name> --description <summary> --procedure <steps>|update <id> [--name ...] [--description ...] [--procedure ...]|enable <id>|disable <id>|review <id>|stale <id> <reason...>|delete <id> --yes]',
|
|
214
|
+
handler: runAgentSkillsRuntimeCommand,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
@@ -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
|
|
12
|
-
const knowledgeApi = context.clients?.
|
|
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
|
|
61
|
-
const serviceAsk = context.extensions.
|
|
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
|
-
|
|
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: '
|
|
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 =
|
|
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 =
|
|
149
|
+
const ask = requireAgentKnowledgeAsk(context);
|
|
153
150
|
if (!ask) return;
|
|
154
|
-
const valuedFlags = ['--
|
|
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> [--
|
|
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]
|
|
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') ?? '
|
|
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> [--
|
|
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
|
+
}
|
|
@@ -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
|
}
|
package/src/input/commands.ts
CHANGED
|
@@ -55,6 +55,8 @@ 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
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';
|
|
58
60
|
|
|
59
61
|
/**
|
|
60
62
|
* registerBuiltinCommands - Register all built-in slash commands into the registry.
|
|
@@ -63,6 +65,8 @@ import { registerDelegationRuntimeCommands } from './commands/delegation-runtime
|
|
|
63
65
|
export function registerBuiltinCommands(registry: CommandRegistry): void {
|
|
64
66
|
registerShellCoreCommands(registry);
|
|
65
67
|
registerAgentWorkspaceRuntimeCommands(registry);
|
|
68
|
+
registerPersonasRuntimeCommands(registry);
|
|
69
|
+
registerAgentSkillsRuntimeCommands(registry);
|
|
66
70
|
registerDelegationRuntimeCommands(registry);
|
|
67
71
|
registerConfigCommand(registry);
|
|
68
72
|
registerOperatorRuntimeCommands(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;
|
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:
|
|
324
|
+
surface: GOODVIBES_AGENT_PAIRING_SURFACE,
|
|
324
325
|
metadata: {
|
|
325
326
|
projectId: ctx.services.projectPlanningProjectId,
|
|
326
327
|
knowledgeSpaceId: planning.state.knowledgeSpaceId,
|