@pellux/goodvibes-agent 0.1.79 → 0.1.81
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/package.json +3 -1
- package/src/agent/skill-registry.ts +255 -5
- package/src/input/agent-workspace-categories.ts +2 -0
- package/src/input/agent-workspace-setup.ts +6 -4
- package/src/input/agent-workspace-snapshot.ts +26 -3
- package/src/input/agent-workspace-types.ts +4 -0
- package/src/input/commands/agent-skills-runtime.ts +151 -3
- package/src/input/onboarding/onboarding-wizard-steps.ts +99 -2
- package/src/renderer/agent-workspace.ts +9 -6
- package/src/version.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to GoodVibes Agent will be recorded here.
|
|
4
4
|
|
|
5
|
+
## 0.1.81 - 2026-06-01
|
|
6
|
+
|
|
7
|
+
- Added local Agent skill bundles so users can group related local skills, enable or disable the bundle, and inject the bundle's member procedures into the same serial assistant conversation.
|
|
8
|
+
- Added `/agent-skills bundle ...` commands plus Agent workspace visibility for skill bundle counts, enabled bundle state, active skill count, and bundle membership.
|
|
9
|
+
- Restored the GitHub CI eval gate script and replaced the copied TUI/daemon release workflow with an Agent package release workflow that validates Bun global install, package contents, compiled binary launch, and optional npm publish.
|
|
10
|
+
- Kept bundles Agent-local and reviewable with no daemon lifecycle behavior, hidden background agents, or non-Agent knowledge fallback.
|
|
11
|
+
|
|
12
|
+
## 0.1.80 - 2026-06-01
|
|
13
|
+
|
|
14
|
+
- Added an Agent day-one readiness checklist to the onboarding review step covering runtime connection, default model route, profile setup, isolated Agent Knowledge, local behavior, channels, routines/schedules, and explicit build delegation.
|
|
15
|
+
- Kept the first-run review focused on Agent setup outcomes instead of copied runtime lifecycle or non-Agent knowledge behavior.
|
|
16
|
+
|
|
5
17
|
## 0.1.79 - 2026-06-01
|
|
6
18
|
|
|
7
19
|
- Expanded the Voice & Media workspace into a provider readiness matrix covering selected TTS readiness, missing secret key names, media understanding/generation posture, and browser-tool state.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.81",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "GoodVibes personal operator assistant TUI with a proactive Agent product brain, isolated Agent Knowledge, local profiles, routines, skills, personas, and explicit build delegation.",
|
|
6
6
|
"type": "module",
|
|
@@ -92,6 +92,8 @@
|
|
|
92
92
|
"publish:dry-run": "bun run scripts/publish-package.ts --dry-run",
|
|
93
93
|
"publish:check": "bun run scripts/publish-check.ts",
|
|
94
94
|
"package:install-check": "bun run scripts/package-install-check.ts",
|
|
95
|
+
"eval:gate": "bun test src/test/runtime/eval/runner.test.ts",
|
|
96
|
+
"ci:gate": "bun run typecheck && bun run test && bun run architecture:check && bun run perf:check && bun run eval:gate && bun run build && bun run publish:check && bun run package:install-check && bun run verification:ledger",
|
|
95
97
|
"build:prod": "bun run scripts/build.ts",
|
|
96
98
|
"build:all": "bun run scripts/build.ts --all",
|
|
97
99
|
"perf:check": "bun run scripts/perf-check.ts",
|
|
@@ -24,6 +24,21 @@ export interface AgentSkillRecord {
|
|
|
24
24
|
readonly reviewedAt?: string;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
export interface AgentSkillBundleRecord {
|
|
28
|
+
readonly id: string;
|
|
29
|
+
readonly name: string;
|
|
30
|
+
readonly description: string;
|
|
31
|
+
readonly skillIds: readonly string[];
|
|
32
|
+
readonly enabled: boolean;
|
|
33
|
+
readonly source: AgentSkillSource;
|
|
34
|
+
readonly provenance: string;
|
|
35
|
+
readonly reviewState: AgentSkillReviewState;
|
|
36
|
+
readonly staleReason?: string;
|
|
37
|
+
readonly createdAt: string;
|
|
38
|
+
readonly updatedAt: string;
|
|
39
|
+
readonly reviewedAt?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
27
42
|
export interface AgentSkillCreateInput {
|
|
28
43
|
readonly name: string;
|
|
29
44
|
readonly description: string;
|
|
@@ -35,6 +50,15 @@ export interface AgentSkillCreateInput {
|
|
|
35
50
|
readonly provenance?: string;
|
|
36
51
|
}
|
|
37
52
|
|
|
53
|
+
export interface AgentSkillBundleCreateInput {
|
|
54
|
+
readonly name: string;
|
|
55
|
+
readonly description: string;
|
|
56
|
+
readonly skillIds: readonly string[];
|
|
57
|
+
readonly enabled?: boolean;
|
|
58
|
+
readonly source?: AgentSkillSource;
|
|
59
|
+
readonly provenance?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
38
62
|
export interface AgentSkillUpdateInput {
|
|
39
63
|
readonly name?: string;
|
|
40
64
|
readonly description?: string;
|
|
@@ -44,15 +68,26 @@ export interface AgentSkillUpdateInput {
|
|
|
44
68
|
readonly provenance?: string;
|
|
45
69
|
}
|
|
46
70
|
|
|
71
|
+
export interface AgentSkillBundleUpdateInput {
|
|
72
|
+
readonly name?: string;
|
|
73
|
+
readonly description?: string;
|
|
74
|
+
readonly skillIds?: readonly string[];
|
|
75
|
+
readonly provenance?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
47
78
|
export interface AgentSkillSnapshot {
|
|
48
79
|
readonly path: string;
|
|
49
80
|
readonly skills: readonly AgentSkillRecord[];
|
|
50
81
|
readonly enabledSkills: readonly AgentSkillRecord[];
|
|
82
|
+
readonly bundles: readonly AgentSkillBundleRecord[];
|
|
83
|
+
readonly enabledBundles: readonly AgentSkillBundleRecord[];
|
|
84
|
+
readonly activeSkills: readonly AgentSkillRecord[];
|
|
51
85
|
}
|
|
52
86
|
|
|
53
87
|
interface SkillStoreFile {
|
|
54
88
|
readonly version: 1;
|
|
55
89
|
readonly skills: readonly AgentSkillRecord[];
|
|
90
|
+
readonly bundles: readonly AgentSkillBundleRecord[];
|
|
56
91
|
}
|
|
57
92
|
|
|
58
93
|
const STORE_VERSION = 1;
|
|
@@ -126,14 +161,44 @@ function parseSkill(value: unknown): AgentSkillRecord | null {
|
|
|
126
161
|
};
|
|
127
162
|
}
|
|
128
163
|
|
|
164
|
+
function parseBundle(value: unknown): AgentSkillBundleRecord | null {
|
|
165
|
+
if (!isRecord(value)) return null;
|
|
166
|
+
const id = readString(value.id).trim();
|
|
167
|
+
const name = normalizeName(readString(value.name));
|
|
168
|
+
const description = readString(value.description).trim();
|
|
169
|
+
const skillIds = readStringArray(value.skillIds).map(slugify).filter(Boolean);
|
|
170
|
+
if (!id || !name || !description || skillIds.length === 0) return null;
|
|
171
|
+
const reviewState = value.reviewState === 'reviewed' || value.reviewState === 'stale' ? value.reviewState : 'fresh';
|
|
172
|
+
const source = value.source === 'agent' || value.source === 'imported' || value.source === 'system' ? value.source : 'user';
|
|
173
|
+
const staleReason = readString(value.staleReason).trim();
|
|
174
|
+
const reviewedAt = readString(value.reviewedAt).trim();
|
|
175
|
+
return {
|
|
176
|
+
id,
|
|
177
|
+
name,
|
|
178
|
+
description,
|
|
179
|
+
skillIds: normalizeList(skillIds).map(slugify),
|
|
180
|
+
enabled: value.enabled === true,
|
|
181
|
+
source,
|
|
182
|
+
provenance: readString(value.provenance, source).trim() || source,
|
|
183
|
+
reviewState,
|
|
184
|
+
staleReason: staleReason || undefined,
|
|
185
|
+
createdAt: readString(value.createdAt, nowIso()),
|
|
186
|
+
updatedAt: readString(value.updatedAt, nowIso()),
|
|
187
|
+
reviewedAt: reviewedAt || undefined,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
129
191
|
function parseStore(raw: string): SkillStoreFile {
|
|
130
192
|
const parsed: unknown = JSON.parse(raw);
|
|
131
|
-
if (!isRecord(parsed)) return { version: STORE_VERSION, skills: [] };
|
|
193
|
+
if (!isRecord(parsed)) return { version: STORE_VERSION, skills: [], bundles: [] };
|
|
132
194
|
return {
|
|
133
195
|
version: STORE_VERSION,
|
|
134
196
|
skills: Array.isArray(parsed.skills)
|
|
135
197
|
? parsed.skills.map(parseSkill).filter((entry): entry is AgentSkillRecord => entry !== null)
|
|
136
198
|
: [],
|
|
199
|
+
bundles: Array.isArray(parsed.bundles)
|
|
200
|
+
? parsed.bundles.map(parseBundle).filter((entry): entry is AgentSkillBundleRecord => entry !== null)
|
|
201
|
+
: [],
|
|
137
202
|
};
|
|
138
203
|
}
|
|
139
204
|
|
|
@@ -154,10 +219,18 @@ export class AgentSkillRegistry {
|
|
|
154
219
|
|
|
155
220
|
public snapshot(): AgentSkillSnapshot {
|
|
156
221
|
const store = this.readStore();
|
|
222
|
+
const enabledBundles = store.bundles.filter((bundle) => bundle.enabled);
|
|
223
|
+
const activeSkillIds = new Set<string>(store.skills.filter((skill) => skill.enabled).map((skill) => skill.id));
|
|
224
|
+
for (const bundle of enabledBundles) {
|
|
225
|
+
for (const skillId of bundle.skillIds) activeSkillIds.add(skillId);
|
|
226
|
+
}
|
|
157
227
|
return {
|
|
158
228
|
path: this.storePath,
|
|
159
229
|
skills: [...store.skills],
|
|
160
230
|
enabledSkills: store.skills.filter((skill) => skill.enabled),
|
|
231
|
+
bundles: [...store.bundles],
|
|
232
|
+
enabledBundles,
|
|
233
|
+
activeSkills: store.skills.filter((skill) => activeSkillIds.has(skill.id)),
|
|
161
234
|
};
|
|
162
235
|
}
|
|
163
236
|
|
|
@@ -178,12 +251,33 @@ export class AgentSkillRegistry {
|
|
|
178
251
|
].some((field) => field.toLowerCase().includes(normalized)));
|
|
179
252
|
}
|
|
180
253
|
|
|
254
|
+
public listBundles(): readonly AgentSkillBundleRecord[] {
|
|
255
|
+
return this.snapshot().bundles;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
public searchBundles(query: string): readonly AgentSkillBundleRecord[] {
|
|
259
|
+
const normalized = query.trim().toLowerCase();
|
|
260
|
+
if (!normalized) return this.listBundles();
|
|
261
|
+
return this.listBundles().filter((bundle) => [
|
|
262
|
+
bundle.id,
|
|
263
|
+
bundle.name,
|
|
264
|
+
bundle.description,
|
|
265
|
+
...bundle.skillIds,
|
|
266
|
+
].some((field) => field.toLowerCase().includes(normalized)));
|
|
267
|
+
}
|
|
268
|
+
|
|
181
269
|
public get(idOrName: string): AgentSkillRecord | null {
|
|
182
270
|
const lookup = idOrName.trim().toLowerCase();
|
|
183
271
|
if (!lookup) return null;
|
|
184
272
|
return this.list().find((skill) => skill.id.toLowerCase() === lookup || skill.name.toLowerCase() === lookup) ?? null;
|
|
185
273
|
}
|
|
186
274
|
|
|
275
|
+
public getBundle(idOrName: string): AgentSkillBundleRecord | null {
|
|
276
|
+
const lookup = idOrName.trim().toLowerCase();
|
|
277
|
+
if (!lookup) return null;
|
|
278
|
+
return this.listBundles().find((bundle) => bundle.id.toLowerCase() === lookup || bundle.name.toLowerCase() === lookup) ?? null;
|
|
279
|
+
}
|
|
280
|
+
|
|
187
281
|
public create(input: AgentSkillCreateInput): AgentSkillRecord {
|
|
188
282
|
const store = this.readStore();
|
|
189
283
|
const name = normalizeName(input.name);
|
|
@@ -212,6 +306,32 @@ export class AgentSkillRegistry {
|
|
|
212
306
|
return skill;
|
|
213
307
|
}
|
|
214
308
|
|
|
309
|
+
public createBundle(input: AgentSkillBundleCreateInput): AgentSkillBundleRecord {
|
|
310
|
+
const store = this.readStore();
|
|
311
|
+
const name = normalizeName(input.name);
|
|
312
|
+
const description = input.description.trim();
|
|
313
|
+
const skillIds = this.normalizeExistingSkillIds(store, input.skillIds);
|
|
314
|
+
this.validateBundleRequired(name, description, skillIds);
|
|
315
|
+
assertNoSecretLikeText([name, description, ...skillIds]);
|
|
316
|
+
const duplicate = store.bundles.find((bundle) => bundle.name.toLowerCase() === name.toLowerCase());
|
|
317
|
+
if (duplicate) throw new Error(`Skill bundle already exists: ${duplicate.id}`);
|
|
318
|
+
const timestamp = nowIso();
|
|
319
|
+
const bundle: AgentSkillBundleRecord = {
|
|
320
|
+
id: this.nextBundleId(name, store.bundles),
|
|
321
|
+
name,
|
|
322
|
+
description,
|
|
323
|
+
skillIds,
|
|
324
|
+
enabled: input.enabled === true,
|
|
325
|
+
source: input.source ?? 'user',
|
|
326
|
+
provenance: input.provenance?.trim() || input.source || 'user',
|
|
327
|
+
reviewState: 'fresh',
|
|
328
|
+
createdAt: timestamp,
|
|
329
|
+
updatedAt: timestamp,
|
|
330
|
+
};
|
|
331
|
+
this.writeStore({ ...store, bundles: [...store.bundles, bundle] });
|
|
332
|
+
return bundle;
|
|
333
|
+
}
|
|
334
|
+
|
|
215
335
|
public update(idOrName: string, input: AgentSkillUpdateInput): AgentSkillRecord {
|
|
216
336
|
const store = this.readStore();
|
|
217
337
|
const existing = this.findInStore(store, idOrName);
|
|
@@ -243,6 +363,35 @@ export class AgentSkillRegistry {
|
|
|
243
363
|
return updated;
|
|
244
364
|
}
|
|
245
365
|
|
|
366
|
+
public updateBundle(idOrName: string, input: AgentSkillBundleUpdateInput): AgentSkillBundleRecord {
|
|
367
|
+
const store = this.readStore();
|
|
368
|
+
const existing = this.findBundleInStore(store, idOrName);
|
|
369
|
+
if (!existing) throw new Error(`Unknown skill bundle: ${idOrName}`);
|
|
370
|
+
const name = input.name === undefined ? existing.name : normalizeName(input.name);
|
|
371
|
+
const description = input.description === undefined ? existing.description : input.description.trim();
|
|
372
|
+
const skillIds = input.skillIds === undefined ? existing.skillIds : this.normalizeExistingSkillIds(store, input.skillIds);
|
|
373
|
+
this.validateBundleRequired(name, description, skillIds);
|
|
374
|
+
assertNoSecretLikeText([name, description, ...skillIds]);
|
|
375
|
+
const duplicate = store.bundles.find((bundle) => bundle.id !== existing.id && bundle.name.toLowerCase() === name.toLowerCase());
|
|
376
|
+
if (duplicate) throw new Error(`Skill bundle already exists: ${duplicate.id}`);
|
|
377
|
+
const updated: AgentSkillBundleRecord = {
|
|
378
|
+
...existing,
|
|
379
|
+
name,
|
|
380
|
+
description,
|
|
381
|
+
skillIds,
|
|
382
|
+
provenance: input.provenance === undefined ? existing.provenance : input.provenance.trim() || existing.provenance,
|
|
383
|
+
reviewState: 'fresh',
|
|
384
|
+
staleReason: undefined,
|
|
385
|
+
reviewedAt: undefined,
|
|
386
|
+
updatedAt: nowIso(),
|
|
387
|
+
};
|
|
388
|
+
this.writeStore({
|
|
389
|
+
...store,
|
|
390
|
+
bundles: store.bundles.map((bundle) => bundle.id === existing.id ? updated : bundle),
|
|
391
|
+
});
|
|
392
|
+
return updated;
|
|
393
|
+
}
|
|
394
|
+
|
|
246
395
|
public setEnabled(idOrName: string, enabled: boolean): AgentSkillRecord {
|
|
247
396
|
const store = this.readStore();
|
|
248
397
|
const existing = this.findInStore(store, idOrName);
|
|
@@ -255,6 +404,18 @@ export class AgentSkillRegistry {
|
|
|
255
404
|
return updated;
|
|
256
405
|
}
|
|
257
406
|
|
|
407
|
+
public setBundleEnabled(idOrName: string, enabled: boolean): AgentSkillBundleRecord {
|
|
408
|
+
const store = this.readStore();
|
|
409
|
+
const existing = this.findBundleInStore(store, idOrName);
|
|
410
|
+
if (!existing) throw new Error(`Unknown skill bundle: ${idOrName}`);
|
|
411
|
+
const updated: AgentSkillBundleRecord = { ...existing, enabled, updatedAt: nowIso() };
|
|
412
|
+
this.writeStore({
|
|
413
|
+
...store,
|
|
414
|
+
bundles: store.bundles.map((bundle) => bundle.id === existing.id ? updated : bundle),
|
|
415
|
+
});
|
|
416
|
+
return updated;
|
|
417
|
+
}
|
|
418
|
+
|
|
258
419
|
public markReviewed(idOrName: string): AgentSkillRecord {
|
|
259
420
|
const store = this.readStore();
|
|
260
421
|
const existing = this.findInStore(store, idOrName);
|
|
@@ -273,6 +434,24 @@ export class AgentSkillRegistry {
|
|
|
273
434
|
return updated;
|
|
274
435
|
}
|
|
275
436
|
|
|
437
|
+
public markBundleReviewed(idOrName: string): AgentSkillBundleRecord {
|
|
438
|
+
const store = this.readStore();
|
|
439
|
+
const existing = this.findBundleInStore(store, idOrName);
|
|
440
|
+
if (!existing) throw new Error(`Unknown skill bundle: ${idOrName}`);
|
|
441
|
+
const updated: AgentSkillBundleRecord = {
|
|
442
|
+
...existing,
|
|
443
|
+
reviewState: 'reviewed',
|
|
444
|
+
staleReason: undefined,
|
|
445
|
+
reviewedAt: nowIso(),
|
|
446
|
+
updatedAt: nowIso(),
|
|
447
|
+
};
|
|
448
|
+
this.writeStore({
|
|
449
|
+
...store,
|
|
450
|
+
bundles: store.bundles.map((bundle) => bundle.id === existing.id ? updated : bundle),
|
|
451
|
+
});
|
|
452
|
+
return updated;
|
|
453
|
+
}
|
|
454
|
+
|
|
276
455
|
public markStale(idOrName: string, reason: string): AgentSkillRecord {
|
|
277
456
|
const store = this.readStore();
|
|
278
457
|
const existing = this.findInStore(store, idOrName);
|
|
@@ -290,6 +469,23 @@ export class AgentSkillRegistry {
|
|
|
290
469
|
return updated;
|
|
291
470
|
}
|
|
292
471
|
|
|
472
|
+
public markBundleStale(idOrName: string, reason: string): AgentSkillBundleRecord {
|
|
473
|
+
const store = this.readStore();
|
|
474
|
+
const existing = this.findBundleInStore(store, idOrName);
|
|
475
|
+
if (!existing) throw new Error(`Unknown skill bundle: ${idOrName}`);
|
|
476
|
+
const updated: AgentSkillBundleRecord = {
|
|
477
|
+
...existing,
|
|
478
|
+
reviewState: 'stale',
|
|
479
|
+
staleReason: reason.trim() || 'Marked stale by user.',
|
|
480
|
+
updatedAt: nowIso(),
|
|
481
|
+
};
|
|
482
|
+
this.writeStore({
|
|
483
|
+
...store,
|
|
484
|
+
bundles: store.bundles.map((bundle) => bundle.id === existing.id ? updated : bundle),
|
|
485
|
+
});
|
|
486
|
+
return updated;
|
|
487
|
+
}
|
|
488
|
+
|
|
293
489
|
public deleteSkill(idOrName: string): AgentSkillRecord {
|
|
294
490
|
const store = this.readStore();
|
|
295
491
|
const existing = this.findInStore(store, idOrName);
|
|
@@ -297,6 +493,20 @@ export class AgentSkillRegistry {
|
|
|
297
493
|
this.writeStore({
|
|
298
494
|
...store,
|
|
299
495
|
skills: store.skills.filter((skill) => skill.id !== existing.id),
|
|
496
|
+
bundles: store.bundles
|
|
497
|
+
.map((bundle) => ({ ...bundle, skillIds: bundle.skillIds.filter((skillId) => skillId !== existing.id) }))
|
|
498
|
+
.filter((bundle) => bundle.skillIds.length > 0),
|
|
499
|
+
});
|
|
500
|
+
return existing;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
public deleteBundle(idOrName: string): AgentSkillBundleRecord {
|
|
504
|
+
const store = this.readStore();
|
|
505
|
+
const existing = this.findBundleInStore(store, idOrName);
|
|
506
|
+
if (!existing) throw new Error(`Unknown skill bundle: ${idOrName}`);
|
|
507
|
+
this.writeStore({
|
|
508
|
+
...store,
|
|
509
|
+
bundles: store.bundles.filter((bundle) => bundle.id !== existing.id),
|
|
300
510
|
});
|
|
301
511
|
return existing;
|
|
302
512
|
}
|
|
@@ -307,6 +517,12 @@ export class AgentSkillRegistry {
|
|
|
307
517
|
if (!procedure) throw new Error('Skill procedure is required.');
|
|
308
518
|
}
|
|
309
519
|
|
|
520
|
+
private validateBundleRequired(name: string, description: string, skillIds: readonly string[]): void {
|
|
521
|
+
if (!name) throw new Error('Skill bundle name is required.');
|
|
522
|
+
if (!description) throw new Error('Skill bundle description is required.');
|
|
523
|
+
if (skillIds.length === 0) throw new Error('Skill bundle must include at least one existing skill.');
|
|
524
|
+
}
|
|
525
|
+
|
|
310
526
|
private nextId(name: string, skills: readonly AgentSkillRecord[]): string {
|
|
311
527
|
const base = slugify(name);
|
|
312
528
|
const ids = new Set(skills.map((skill) => skill.id));
|
|
@@ -318,14 +534,40 @@ export class AgentSkillRegistry {
|
|
|
318
534
|
throw new Error(`Could not allocate skill id for ${name}.`);
|
|
319
535
|
}
|
|
320
536
|
|
|
537
|
+
private nextBundleId(name: string, bundles: readonly AgentSkillBundleRecord[]): string {
|
|
538
|
+
const base = slugify(name);
|
|
539
|
+
const ids = new Set(bundles.map((bundle) => bundle.id));
|
|
540
|
+
if (!ids.has(base)) return base;
|
|
541
|
+
for (let index = 2; index < 1000; index += 1) {
|
|
542
|
+
const candidate = `${base}-${index}`;
|
|
543
|
+
if (!ids.has(candidate)) return candidate;
|
|
544
|
+
}
|
|
545
|
+
throw new Error(`Could not allocate skill bundle id for ${name}.`);
|
|
546
|
+
}
|
|
547
|
+
|
|
321
548
|
private findInStore(store: SkillStoreFile, idOrName: string): AgentSkillRecord | null {
|
|
322
549
|
const lookup = idOrName.trim().toLowerCase();
|
|
323
550
|
if (!lookup) return null;
|
|
324
551
|
return store.skills.find((skill) => skill.id.toLowerCase() === lookup || skill.name.toLowerCase() === lookup) ?? null;
|
|
325
552
|
}
|
|
326
553
|
|
|
554
|
+
private findBundleInStore(store: SkillStoreFile, idOrName: string): AgentSkillBundleRecord | null {
|
|
555
|
+
const lookup = idOrName.trim().toLowerCase();
|
|
556
|
+
if (!lookup) return null;
|
|
557
|
+
return store.bundles.find((bundle) => bundle.id.toLowerCase() === lookup || bundle.name.toLowerCase() === lookup) ?? null;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
private normalizeExistingSkillIds(store: SkillStoreFile, skillIds: readonly string[]): readonly string[] {
|
|
561
|
+
const normalized = normalizeList(skillIds).map(slugify);
|
|
562
|
+
const known = new Set(store.skills.map((skill) => skill.id));
|
|
563
|
+
for (const skillId of normalized) {
|
|
564
|
+
if (!known.has(skillId)) throw new Error(`Unknown skill for bundle: ${skillId}`);
|
|
565
|
+
}
|
|
566
|
+
return normalized;
|
|
567
|
+
}
|
|
568
|
+
|
|
327
569
|
private readStore(): SkillStoreFile {
|
|
328
|
-
if (!existsSync(this.storePath)) return { version: STORE_VERSION, skills: [] };
|
|
570
|
+
if (!existsSync(this.storePath)) return { version: STORE_VERSION, skills: [], bundles: [] };
|
|
329
571
|
try {
|
|
330
572
|
return parseStore(readFileSync(this.storePath, 'utf-8'));
|
|
331
573
|
} catch (error) {
|
|
@@ -342,13 +584,21 @@ export class AgentSkillRegistry {
|
|
|
342
584
|
}
|
|
343
585
|
|
|
344
586
|
export function buildEnabledSkillsPrompt(shellPaths: ShellPathService): string | null {
|
|
345
|
-
const
|
|
346
|
-
|
|
587
|
+
const snapshot = AgentSkillRegistry.fromShellPaths(shellPaths).snapshot();
|
|
588
|
+
const active = snapshot.activeSkills;
|
|
589
|
+
if (active.length === 0 && snapshot.enabledBundles.length === 0) return null;
|
|
347
590
|
return [
|
|
348
591
|
'## Enabled GoodVibes Agent Skills',
|
|
349
592
|
'Use these local reusable procedures inside the same serial assistant conversation when they fit the user request.',
|
|
350
593
|
'',
|
|
351
|
-
...
|
|
594
|
+
...snapshot.enabledBundles.slice(0, 4).flatMap((bundle) => [
|
|
595
|
+
`### Skill Bundle: ${bundle.name}`,
|
|
596
|
+
`Description: ${bundle.description}`,
|
|
597
|
+
`Review state: ${bundle.reviewState}`,
|
|
598
|
+
`Included skills: ${bundle.skillIds.join(', ')}`,
|
|
599
|
+
'',
|
|
600
|
+
]),
|
|
601
|
+
...active.slice(0, 8).flatMap((skill) => [
|
|
352
602
|
`### ${skill.name}`,
|
|
353
603
|
`Description: ${skill.description}`,
|
|
354
604
|
`Review state: ${skill.reviewState}`,
|
|
@@ -144,6 +144,8 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
|
|
|
144
144
|
actions: [
|
|
145
145
|
{ id: 'skills-list', label: 'List skills', detail: 'Print the full local Agent skill library.', command: '/agent-skills list', kind: 'command', safety: 'read-only' },
|
|
146
146
|
{ id: 'skills-enabled', label: 'Enabled skills', detail: 'Show only skills currently injected into Agent guidance.', command: '/agent-skills enabled', kind: 'command', safety: 'read-only' },
|
|
147
|
+
{ id: 'skills-bundles', label: 'Skill bundles', detail: 'List reviewable groups of local skills that can be enabled together.', command: '/agent-skills bundle list', kind: 'command', safety: 'read-only' },
|
|
148
|
+
{ id: 'skills-create-bundle', label: 'Create bundle', detail: 'Create a named skill bundle from existing skill ids with an explicit command.', command: '/agent-skills bundle create --name <name> --description <summary> --skills <id,id>', kind: 'command', safety: 'safe' },
|
|
147
149
|
{ id: 'skills-prev', label: 'Previous skill', detail: 'Move the local skill selection up without changing enabled state.', localKind: 'skill', selectionDelta: -1, kind: 'local-selection', safety: 'safe' },
|
|
148
150
|
{ id: 'skills-next', label: 'Next skill', detail: 'Move the local skill selection down without changing enabled state.', localKind: 'skill', selectionDelta: 1, kind: 'local-selection', safety: 'safe' },
|
|
149
151
|
{ id: 'skills-create', label: 'Create skill', detail: 'Open an in-workspace form for a reusable local procedure. No placeholder command is dispatched.', editorKind: 'skill', kind: 'editor', safety: 'safe' },
|
|
@@ -17,6 +17,8 @@ export interface AgentWorkspaceSetupChecklistInput {
|
|
|
17
17
|
readonly enabledRoutineCount: number;
|
|
18
18
|
readonly skillCount: number;
|
|
19
19
|
readonly enabledSkillCount: number;
|
|
20
|
+
readonly skillBundleCount: number;
|
|
21
|
+
readonly enabledSkillBundleCount: number;
|
|
20
22
|
readonly activePersonaName: string;
|
|
21
23
|
readonly readyChannelCount: number;
|
|
22
24
|
readonly voiceProviderCount: number;
|
|
@@ -77,10 +79,10 @@ export function buildAgentWorkspaceSetupChecklist(input: AgentWorkspaceSetupChec
|
|
|
77
79
|
{
|
|
78
80
|
id: 'skills',
|
|
79
81
|
label: 'Skills',
|
|
80
|
-
status:
|
|
81
|
-
detail: input.skillCount > 0
|
|
82
|
-
? `${input.enabledSkillCount}/${input.skillCount} local skill(s) enabled.`
|
|
83
|
-
: 'Create reusable local skills for repeated workflows.',
|
|
82
|
+
status: input.enabledSkillCount > 0 || input.enabledSkillBundleCount > 0 ? 'ready' : input.skillCount > 0 || input.skillBundleCount > 0 ? 'recommended' : 'optional',
|
|
83
|
+
detail: input.skillCount > 0 || input.skillBundleCount > 0
|
|
84
|
+
? `${input.enabledSkillCount}/${input.skillCount} local skill(s) enabled; ${input.enabledSkillBundleCount}/${input.skillBundleCount} bundle(s) enabled.`
|
|
85
|
+
: 'Create reusable local skills and bundles for repeated workflows.',
|
|
84
86
|
command: '/agent-skills',
|
|
85
87
|
},
|
|
86
88
|
{
|
|
@@ -2,7 +2,7 @@ import { basename, sep } from 'node:path';
|
|
|
2
2
|
import type { CommandContext } from './command-registry.ts';
|
|
3
3
|
import { AgentPersonaRegistry, type AgentPersonaRecord } from '../agent/persona-registry.ts';
|
|
4
4
|
import { AgentRoutineRegistry, type AgentRoutineRecord } from '../agent/routine-registry.ts';
|
|
5
|
-
import { AgentSkillRegistry, type AgentSkillRecord } from '../agent/skill-registry.ts';
|
|
5
|
+
import { AgentSkillRegistry, type AgentSkillBundleRecord, type AgentSkillRecord } from '../agent/skill-registry.ts';
|
|
6
6
|
import { getAgentRuntimeProfilesRoot, listAgentRuntimeProfiles, listAgentRuntimeProfileTemplates } from '../agent/runtime-profile.ts';
|
|
7
7
|
import { buildAgentWorkspaceChannels } from './agent-workspace-channels.ts';
|
|
8
8
|
import { buildAgentWorkspaceSetupChecklist } from './agent-workspace-setup.ts';
|
|
@@ -86,6 +86,19 @@ function summarizeSkillItem(skill: AgentSkillRecord): AgentWorkspaceLocalLibrary
|
|
|
86
86
|
};
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
function summarizeSkillBundleItem(bundle: AgentSkillBundleRecord): AgentWorkspaceLocalLibraryItem {
|
|
90
|
+
return {
|
|
91
|
+
id: bundle.id,
|
|
92
|
+
name: bundle.name,
|
|
93
|
+
description: `${bundle.description} Skills: ${bundle.skillIds.join(', ')}`,
|
|
94
|
+
reviewState: bundle.reviewState,
|
|
95
|
+
source: bundle.source,
|
|
96
|
+
tags: bundle.skillIds,
|
|
97
|
+
triggers: [],
|
|
98
|
+
enabled: bundle.enabled,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
89
102
|
function summarizeRoutineItem(routine: AgentRoutineRecord): AgentWorkspaceLocalLibraryItem {
|
|
90
103
|
return {
|
|
91
104
|
id: routine.id,
|
|
@@ -158,15 +171,19 @@ export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): Age
|
|
|
158
171
|
const skillSnapshot = (() => {
|
|
159
172
|
try {
|
|
160
173
|
const shellPaths = context.workspace?.shellPaths;
|
|
161
|
-
if (!shellPaths) return { count: 0, enabled: 0, items: [] };
|
|
174
|
+
if (!shellPaths) return { count: 0, enabled: 0, active: 0, bundleCount: 0, enabledBundleCount: 0, items: [], bundleItems: [] };
|
|
162
175
|
const snapshot = AgentSkillRegistry.fromShellPaths(shellPaths).snapshot();
|
|
163
176
|
return {
|
|
164
177
|
count: snapshot.skills.length,
|
|
165
178
|
enabled: snapshot.enabledSkills.length,
|
|
179
|
+
active: snapshot.activeSkills.length,
|
|
180
|
+
bundleCount: snapshot.bundles.length,
|
|
181
|
+
enabledBundleCount: snapshot.enabledBundles.length,
|
|
166
182
|
items: snapshot.skills.map(summarizeSkillItem),
|
|
183
|
+
bundleItems: snapshot.bundles.map(summarizeSkillBundleItem),
|
|
167
184
|
};
|
|
168
185
|
} catch {
|
|
169
|
-
return { count: 0, enabled: 0, items: [] };
|
|
186
|
+
return { count: 0, enabled: 0, active: 0, bundleCount: 0, enabledBundleCount: 0, items: [], bundleItems: [] };
|
|
170
187
|
}
|
|
171
188
|
})();
|
|
172
189
|
const routineSnapshot = (() => {
|
|
@@ -251,6 +268,8 @@ export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): Age
|
|
|
251
268
|
enabledRoutineCount: routineSnapshot.enabled,
|
|
252
269
|
skillCount: skillSnapshot.count,
|
|
253
270
|
enabledSkillCount: skillSnapshot.enabled,
|
|
271
|
+
skillBundleCount: skillSnapshot.bundleCount,
|
|
272
|
+
enabledSkillBundleCount: skillSnapshot.enabledBundleCount,
|
|
254
273
|
activePersonaName: personaSnapshot.activeName,
|
|
255
274
|
readyChannelCount: channels.filter((channel) => channel.ready).length,
|
|
256
275
|
voiceProviderCount: voiceProviders.length,
|
|
@@ -274,6 +293,10 @@ export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): Age
|
|
|
274
293
|
localRoutines: routineSnapshot.items,
|
|
275
294
|
localSkillCount: skillSnapshot.count,
|
|
276
295
|
enabledSkillCount: skillSnapshot.enabled,
|
|
296
|
+
localSkillBundleCount: skillSnapshot.bundleCount,
|
|
297
|
+
enabledSkillBundleCount: skillSnapshot.enabledBundleCount,
|
|
298
|
+
activeSkillCount: skillSnapshot.active,
|
|
299
|
+
localSkillBundles: skillSnapshot.bundleItems,
|
|
277
300
|
localSkills: skillSnapshot.items,
|
|
278
301
|
localPersonaCount: personaSnapshot.count,
|
|
279
302
|
activePersonaName: personaSnapshot.activeName,
|
|
@@ -128,6 +128,10 @@ export interface AgentWorkspaceRuntimeSnapshot {
|
|
|
128
128
|
readonly localRoutines: readonly AgentWorkspaceLocalLibraryItem[];
|
|
129
129
|
readonly localSkillCount: number;
|
|
130
130
|
readonly enabledSkillCount: number;
|
|
131
|
+
readonly localSkillBundleCount: number;
|
|
132
|
+
readonly enabledSkillBundleCount: number;
|
|
133
|
+
readonly activeSkillCount: number;
|
|
134
|
+
readonly localSkillBundles: readonly AgentWorkspaceLocalLibraryItem[];
|
|
131
135
|
readonly localSkills: readonly AgentWorkspaceLocalLibraryItem[];
|
|
132
136
|
readonly localPersonaCount: number;
|
|
133
137
|
readonly activePersonaName: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AgentSkillRegistry, type AgentSkillRecord } from '../../agent/skill-registry.ts';
|
|
1
|
+
import { AgentSkillRegistry, type AgentSkillBundleRecord, type AgentSkillRecord } from '../../agent/skill-registry.ts';
|
|
2
2
|
import type { CommandContext, CommandRegistry } from '../command-registry.ts';
|
|
3
3
|
import { requireShellPaths } from './runtime-services.ts';
|
|
4
4
|
|
|
@@ -55,6 +55,11 @@ function summarizeSkill(skill: AgentSkillRecord): string {
|
|
|
55
55
|
return ` ${skill.id} ${enabled} ${skill.reviewState} ${skill.name} - ${skill.description}${tags}`;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
function summarizeBundle(bundle: AgentSkillBundleRecord): string {
|
|
59
|
+
const enabled = bundle.enabled ? 'enabled' : 'disabled';
|
|
60
|
+
return ` ${bundle.id} ${enabled} ${bundle.reviewState} ${bundle.name} - ${bundle.description} skills=${bundle.skillIds.join(',')}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
58
63
|
function renderList(title: string, registry: AgentSkillRegistry, skills: readonly AgentSkillRecord[]): string {
|
|
59
64
|
const snapshot = registry.snapshot();
|
|
60
65
|
if (skills.length === 0) {
|
|
@@ -68,6 +73,20 @@ function renderList(title: string, registry: AgentSkillRegistry, skills: readonl
|
|
|
68
73
|
].join('\n');
|
|
69
74
|
}
|
|
70
75
|
|
|
76
|
+
function renderBundleList(title: string, registry: AgentSkillRegistry, bundles: readonly AgentSkillBundleRecord[]): string {
|
|
77
|
+
const snapshot = registry.snapshot();
|
|
78
|
+
if (bundles.length === 0) {
|
|
79
|
+
return `${title}\n No local Agent skill bundles yet. Create one with /agent-skills bundle create --name <name> --description <summary> --skills <id,id>.`;
|
|
80
|
+
}
|
|
81
|
+
return [
|
|
82
|
+
`${title} (${bundles.length})`,
|
|
83
|
+
` store: ${snapshot.path}`,
|
|
84
|
+
` enabled bundles: ${snapshot.enabledBundles.length}`,
|
|
85
|
+
` active skills: ${snapshot.activeSkills.length}`,
|
|
86
|
+
...bundles.map(summarizeBundle),
|
|
87
|
+
].join('\n');
|
|
88
|
+
}
|
|
89
|
+
|
|
71
90
|
function renderSkill(skill: AgentSkillRecord): string {
|
|
72
91
|
return [
|
|
73
92
|
`Skill ${skill.name}`,
|
|
@@ -88,14 +107,143 @@ function renderSkill(skill: AgentSkillRecord): string {
|
|
|
88
107
|
].filter(Boolean).join('\n');
|
|
89
108
|
}
|
|
90
109
|
|
|
110
|
+
function renderBundle(bundle: AgentSkillBundleRecord, registry: AgentSkillRegistry): string {
|
|
111
|
+
const skills = bundle.skillIds
|
|
112
|
+
.map((skillId) => registry.get(skillId))
|
|
113
|
+
.filter((skill): skill is AgentSkillRecord => skill !== null);
|
|
114
|
+
return [
|
|
115
|
+
`Skill Bundle ${bundle.name}`,
|
|
116
|
+
` id: ${bundle.id}`,
|
|
117
|
+
` enabled: ${bundle.enabled ? 'yes' : 'no'}`,
|
|
118
|
+
` review: ${bundle.reviewState}`,
|
|
119
|
+
` source: ${bundle.source}`,
|
|
120
|
+
` provenance: ${bundle.provenance}`,
|
|
121
|
+
` skills: ${bundle.skillIds.join(', ')}`,
|
|
122
|
+
` created: ${bundle.createdAt}`,
|
|
123
|
+
` updated: ${bundle.updatedAt}`,
|
|
124
|
+
bundle.staleReason ? ` stale reason: ${bundle.staleReason}` : '',
|
|
125
|
+
'',
|
|
126
|
+
bundle.description,
|
|
127
|
+
'',
|
|
128
|
+
...skills.map((skill) => `- ${skill.id}: ${skill.name} - ${skill.description}`),
|
|
129
|
+
].filter(Boolean).join('\n');
|
|
130
|
+
}
|
|
131
|
+
|
|
91
132
|
function printError(ctx: CommandContext, error: unknown): void {
|
|
92
133
|
ctx.print(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
93
134
|
}
|
|
94
135
|
|
|
136
|
+
function runBundleCommand(args: readonly string[], ctx: CommandContext, skillRegistry: AgentSkillRegistry): void {
|
|
137
|
+
const sub = (args[0] ?? 'list').toLowerCase();
|
|
138
|
+
if (sub === 'list' || sub === 'open') {
|
|
139
|
+
ctx.print(renderBundleList('Agent Skill Bundles', skillRegistry, skillRegistry.listBundles()));
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (sub === 'enabled') {
|
|
143
|
+
const snapshot = skillRegistry.snapshot();
|
|
144
|
+
ctx.print(renderBundleList('Enabled Agent Skill Bundles', skillRegistry, snapshot.enabledBundles));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (sub === 'search') {
|
|
148
|
+
const query = args.slice(1).join(' ').trim();
|
|
149
|
+
ctx.print(renderBundleList(query ? `Agent Skill Bundles matching "${query}"` : 'Agent Skill Bundles', skillRegistry, skillRegistry.searchBundles(query)));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (sub === 'show') {
|
|
153
|
+
const id = args[1];
|
|
154
|
+
if (!id) {
|
|
155
|
+
ctx.print('Usage: /agent-skills bundle show <id>');
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const bundle = skillRegistry.getBundle(id);
|
|
159
|
+
ctx.print(bundle ? renderBundle(bundle, skillRegistry) : `Unknown Agent skill bundle: ${id}`);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (sub === 'create') {
|
|
163
|
+
const parsed = parseSkillArgs(args.slice(1));
|
|
164
|
+
const bundle = skillRegistry.createBundle({
|
|
165
|
+
name: requiredFlag(parsed.flags, 'name'),
|
|
166
|
+
description: requiredFlag(parsed.flags, 'description'),
|
|
167
|
+
skillIds: splitList(requiredFlag(parsed.flags, 'skills')),
|
|
168
|
+
enabled: parsed.flags.get('enabled') === 'true',
|
|
169
|
+
source: 'user',
|
|
170
|
+
provenance: 'slash-command',
|
|
171
|
+
});
|
|
172
|
+
ctx.print(`Created Agent skill bundle ${bundle.id}: ${bundle.name}`);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (sub === 'update') {
|
|
176
|
+
const id = args[1];
|
|
177
|
+
if (!id) {
|
|
178
|
+
ctx.print('Usage: /agent-skills bundle update <id> [--name ...] [--description ...] [--skills id,id]');
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const parsed = parseSkillArgs(args.slice(2));
|
|
182
|
+
const updated = skillRegistry.updateBundle(id, {
|
|
183
|
+
name: parsed.flags.get('name'),
|
|
184
|
+
description: parsed.flags.get('description'),
|
|
185
|
+
skillIds: parsed.flags.has('skills') ? splitList(parsed.flags.get('skills')) : undefined,
|
|
186
|
+
provenance: 'slash-command',
|
|
187
|
+
});
|
|
188
|
+
ctx.print(`Updated Agent skill bundle ${updated.id}: ${updated.name}`);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (sub === 'enable' || sub === 'disable') {
|
|
192
|
+
const id = args[1];
|
|
193
|
+
if (!id) {
|
|
194
|
+
ctx.print(`Usage: /agent-skills bundle ${sub} <id>`);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const bundle = skillRegistry.setBundleEnabled(id, sub === 'enable');
|
|
198
|
+
ctx.print(`${sub === 'enable' ? 'Enabled' : 'Disabled'} Agent skill bundle ${bundle.id}: ${bundle.name}`);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (sub === 'review') {
|
|
202
|
+
const id = args[1];
|
|
203
|
+
if (!id) {
|
|
204
|
+
ctx.print('Usage: /agent-skills bundle review <id>');
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const bundle = skillRegistry.markBundleReviewed(id);
|
|
208
|
+
ctx.print(`Reviewed Agent skill bundle ${bundle.id}.`);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (sub === 'stale') {
|
|
212
|
+
const id = args[1];
|
|
213
|
+
if (!id) {
|
|
214
|
+
ctx.print('Usage: /agent-skills bundle stale <id> <reason...>');
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const bundle = skillRegistry.markBundleStale(id, args.slice(2).join(' '));
|
|
218
|
+
ctx.print(`Marked Agent skill bundle ${bundle.id} stale.`);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (sub === 'delete' || sub === 'remove') {
|
|
222
|
+
const parsed = parseSkillArgs(args.slice(1));
|
|
223
|
+
const id = parsed.rest[0];
|
|
224
|
+
if (!id) {
|
|
225
|
+
ctx.print('Usage: /agent-skills bundle delete <id> --yes');
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (!parsed.yes) {
|
|
229
|
+
ctx.print(`Refusing to delete Agent skill bundle ${id} without --yes.`);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const removed = skillRegistry.deleteBundle(id);
|
|
233
|
+
ctx.print(`Deleted Agent skill bundle ${removed.id}: ${removed.name}`);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
ctx.print('Usage: /agent-skills bundle [list|enabled|search|show|create|update|enable|disable|review|stale|delete]');
|
|
237
|
+
}
|
|
238
|
+
|
|
95
239
|
export async function runAgentSkillsRuntimeCommand(args: readonly string[], ctx: CommandContext): Promise<void> {
|
|
96
240
|
const sub = (args[0] ?? 'list').toLowerCase();
|
|
97
241
|
const skillRegistry = registryFromContext(ctx);
|
|
98
242
|
try {
|
|
243
|
+
if (sub === 'bundle' || sub === 'bundles') {
|
|
244
|
+
runBundleCommand(args.slice(1), ctx, skillRegistry);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
99
247
|
if (sub === 'list' || sub === 'open') {
|
|
100
248
|
ctx.print(renderList('Agent Skills', skillRegistry, skillRegistry.list()));
|
|
101
249
|
return;
|
|
@@ -199,7 +347,7 @@ export async function runAgentSkillsRuntimeCommand(args: readonly string[], ctx:
|
|
|
199
347
|
ctx.print(`Deleted Agent skill ${removed.id}: ${removed.name}`);
|
|
200
348
|
return;
|
|
201
349
|
}
|
|
202
|
-
ctx.print('Usage: /agent-skills [list|enabled|search|show|create|update|enable|disable|review|stale|delete]');
|
|
350
|
+
ctx.print('Usage: /agent-skills [list|enabled|search|show|create|update|enable|disable|review|stale|delete|bundle]');
|
|
203
351
|
} catch (error) {
|
|
204
352
|
printError(ctx, error);
|
|
205
353
|
}
|
|
@@ -210,7 +358,7 @@ export function registerAgentSkillsRuntimeCommands(registry: CommandRegistry): v
|
|
|
210
358
|
name: 'agent-skills',
|
|
211
359
|
aliases: ['askills', 'local-skills'],
|
|
212
360
|
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]',
|
|
361
|
+
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|bundle ...]',
|
|
214
362
|
handler: runAgentSkillsRuntimeCommand,
|
|
215
363
|
});
|
|
216
364
|
}
|
|
@@ -3,6 +3,7 @@ import { modelSelectionLabel, normalizeText } from './onboarding-wizard-helpers.
|
|
|
3
3
|
import { listAgentRuntimeProfileTemplates } from '../../agent/runtime-profile.ts';
|
|
4
4
|
import type { OnboardingWizardController } from './onboarding-wizard.ts';
|
|
5
5
|
import type { OnboardingWizardActionFieldDefinition, OnboardingWizardFieldDefinition, OnboardingWizardModelPickerFieldDefinition, OnboardingWizardRadioFieldDefinition, OnboardingWizardRadioOption, OnboardingWizardStepDefinition } from './onboarding-wizard-types.ts';
|
|
6
|
+
import type { OnboardingStep1CapabilityId, OnboardingStep1CapabilityItem } from '../../runtime/onboarding/index.ts';
|
|
6
7
|
|
|
7
8
|
function buildStarterTemplateOptions(): readonly OnboardingWizardRadioOption[] {
|
|
8
9
|
return [
|
|
@@ -58,6 +59,98 @@ function addApplyAndContinueAction(step: OnboardingWizardStepDefinition): Onboar
|
|
|
58
59
|
};
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
function findRuntimeCapability(
|
|
63
|
+
controller: OnboardingWizardController,
|
|
64
|
+
id: OnboardingStep1CapabilityId,
|
|
65
|
+
): OnboardingStep1CapabilityItem | null {
|
|
66
|
+
return controller.runtimeDerived.step1Capabilities.find((capability) => capability.id === id) ?? null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function currentMainModelLabel(controller: OnboardingWizardController): string {
|
|
70
|
+
const routing = controller.runtimeSnapshot?.providerRouting;
|
|
71
|
+
return modelSelectionLabel(controller.modelSelectionState.get('main') ?? {
|
|
72
|
+
providerId: normalizeText(routing?.primaryProviderId),
|
|
73
|
+
modelId: normalizeText(routing?.primaryModelId),
|
|
74
|
+
enabled: true,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function profileSetupLabel(controller: OnboardingWizardController): string {
|
|
79
|
+
const profileName = normalizeText(controller.getStringFieldValue('agent-setup.profile-name', ''));
|
|
80
|
+
const templateId = controller.getStringFieldValue('agent-setup.profile-template', 'none');
|
|
81
|
+
if (profileName.length === 0) return 'Current home';
|
|
82
|
+
return templateId === 'none' ? `Create ${profileName}` : `Create ${profileName} from ${templateId}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function buildReviewReadinessFields(controller: OnboardingWizardController): readonly OnboardingWizardFieldDefinition[] {
|
|
86
|
+
const localBehavior = findRuntimeCapability(controller, 'local-behavior');
|
|
87
|
+
const channels = findRuntimeCapability(controller, 'communication-channels');
|
|
88
|
+
const automation = findRuntimeCapability(controller, 'automation-review');
|
|
89
|
+
const collectionIssues = controller.runtimeSnapshot?.collectionIssues.length ?? 0;
|
|
90
|
+
|
|
91
|
+
return [
|
|
92
|
+
{
|
|
93
|
+
kind: 'status',
|
|
94
|
+
id: 'review.readiness.connection',
|
|
95
|
+
label: 'Runtime connection snapshot',
|
|
96
|
+
hint: collectionIssues > 0
|
|
97
|
+
? `${collectionIssues} setup snapshot issue(s) need attention before this Agent is day-one ready.`
|
|
98
|
+
: 'The setup snapshot loaded cleanly from the external GoodVibes runtime.',
|
|
99
|
+
defaultValue: collectionIssues > 0 ? 'Needs attention' : 'Ready',
|
|
100
|
+
spacerBeforeRows: 1,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
kind: 'status',
|
|
104
|
+
id: 'review.readiness.model',
|
|
105
|
+
label: 'Default model route',
|
|
106
|
+
hint: 'Normal assistant turns use this selected provider/model route unless changed later from the model picker.',
|
|
107
|
+
defaultValue: currentMainModelLabel(controller),
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
kind: 'status',
|
|
111
|
+
id: 'review.readiness.profile',
|
|
112
|
+
label: 'Agent profile',
|
|
113
|
+
hint: 'Profiles isolate Agent-local config, sessions, memory, personas, skills, routines, and setup state.',
|
|
114
|
+
defaultValue: profileSetupLabel(controller),
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
kind: 'status',
|
|
118
|
+
id: 'review.readiness.knowledge',
|
|
119
|
+
label: 'Agent Knowledge segment',
|
|
120
|
+
hint: 'Ask, search, status, and ingest stay on /api/goodvibes-agent/knowledge/* with no default wiki or non-Agent fallback.',
|
|
121
|
+
defaultValue: 'Isolated',
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
kind: 'status',
|
|
125
|
+
id: 'review.readiness.local-behavior',
|
|
126
|
+
label: 'Local behavior library',
|
|
127
|
+
hint: localBehavior?.detail ?? 'Agent-local memory, routines, skills, and personas remain local until a stable shared registry exists.',
|
|
128
|
+
defaultValue: localBehavior?.selected ? 'Customized' : 'Starter ready',
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
kind: 'status',
|
|
132
|
+
id: 'review.readiness.channels',
|
|
133
|
+
label: 'Channels and notifications',
|
|
134
|
+
hint: channels?.detail ?? 'Connect only the channels the Agent should use, and keep outbound delivery explicit.',
|
|
135
|
+
defaultValue: channels?.selected ? 'Review configured' : 'Optional setup',
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
kind: 'status',
|
|
139
|
+
id: 'review.readiness.automation',
|
|
140
|
+
label: 'Routines and schedules',
|
|
141
|
+
hint: automation?.detail ?? 'Local routines run in the main conversation; external schedules require explicit promotion and confirmation.',
|
|
142
|
+
defaultValue: automation?.selected ? 'Review configured' : 'Local first',
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
kind: 'status',
|
|
146
|
+
id: 'review.readiness.delegation',
|
|
147
|
+
label: 'Build delegation',
|
|
148
|
+
hint: 'Build, fix, implementation, and review work is handed to GoodVibes TUI only when explicitly requested.',
|
|
149
|
+
defaultValue: 'Explicit only',
|
|
150
|
+
},
|
|
151
|
+
];
|
|
152
|
+
}
|
|
153
|
+
|
|
61
154
|
export function buildCommunicationStep(): OnboardingWizardStepDefinition {
|
|
62
155
|
return {
|
|
63
156
|
id: 'agent-communication',
|
|
@@ -636,14 +729,17 @@ export function buildReviewStep(controller: OnboardingWizardController): Onboard
|
|
|
636
729
|
const unsavedLabel = controller.dirtyStepCount === 1
|
|
637
730
|
? '1 screen has unapplied changes'
|
|
638
731
|
: `${controller.dirtyStepCount} screens have unapplied changes`;
|
|
732
|
+
const collectionIssues = controller.runtimeSnapshot?.collectionIssues.length ?? 0;
|
|
733
|
+
const dayOneReadiness = collectionIssues > 0 ? `${collectionIssues} connection issue(s) before day-one ready` : 'operator checklist ready';
|
|
639
734
|
|
|
640
735
|
return {
|
|
641
736
|
id: 'review',
|
|
642
737
|
title: 'Review and apply',
|
|
643
738
|
shortLabel: 'Review',
|
|
644
|
-
description: 'Review Agent-
|
|
645
|
-
summaryTitle: '
|
|
739
|
+
description: 'Review the Agent day-one checklist and apply setup directly from the wizard.',
|
|
740
|
+
summaryTitle: 'Agent day-one readiness',
|
|
646
741
|
summaryLines: [
|
|
742
|
+
`Day-one readiness: ${dayOneReadiness}`,
|
|
647
743
|
unsavedLabel,
|
|
648
744
|
`${controller.buildApplyRequest().operations.length} Agent setting change(s) ready to apply`,
|
|
649
745
|
feedback ? `Last apply: ${feedback.title}` : 'No apply errors reported',
|
|
@@ -651,6 +747,7 @@ export function buildReviewStep(controller: OnboardingWizardController): Onboard
|
|
|
651
747
|
],
|
|
652
748
|
fields: [
|
|
653
749
|
...feedbackFields,
|
|
750
|
+
...buildReviewReadinessFields(controller),
|
|
654
751
|
{
|
|
655
752
|
kind: 'status',
|
|
656
753
|
id: 'review.global-marker',
|
|
@@ -196,10 +196,10 @@ function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCatego
|
|
|
196
196
|
base.push(
|
|
197
197
|
{ text: `GoodVibes runtime: ${snapshot.daemonBaseUrl}`, fg: PALETTE.info },
|
|
198
198
|
{ text: `Runtime owner: ${snapshot.daemonOwnership}; Agent connects but never starts or restarts it`, fg: PALETTE.good },
|
|
199
|
+
...setupChecklistLines(snapshot),
|
|
200
|
+
{ text: '' },
|
|
199
201
|
{ text: `Workspace: ${snapshot.workingDirectory}`, fg: PALETTE.muted },
|
|
200
202
|
{ text: `Home: ${snapshot.homeDirectory}`, fg: PALETTE.muted },
|
|
201
|
-
{ text: '' },
|
|
202
|
-
...setupChecklistLines(snapshot),
|
|
203
203
|
);
|
|
204
204
|
} else if (category.id === 'channels') {
|
|
205
205
|
const enabledCount = snapshot.channels.filter((channel) => channel.enabled).length;
|
|
@@ -283,10 +283,11 @@ function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCatego
|
|
|
283
283
|
base.push(
|
|
284
284
|
{ text: `Active Agent profile: ${snapshot.activeRuntimeProfile}`, fg: PALETTE.info },
|
|
285
285
|
{ text: `Agent profiles under this home: ${snapshot.runtimeProfileCount}`, fg: PALETTE.info },
|
|
286
|
-
{ text: `Agent profile root: ${snapshot.runtimeProfileRoot}`, fg: PALETTE.muted },
|
|
287
286
|
{ text: `Starter templates: ${snapshot.runtimeStarterTemplateCount}; local custom: ${snapshot.localStarterTemplateCount}`, fg: PALETTE.info },
|
|
288
287
|
{ text: `Config profiles: ${snapshot.configProfileCount}`, fg: PALETTE.info },
|
|
289
288
|
{ text: `Starter ids: ${snapshot.runtimeStarterTemplates.map((template) => template.id).join(', ') || 'none'}`, fg: PALETTE.info },
|
|
289
|
+
{ text: 'Starter Templates', fg: PALETTE.title, bold: true },
|
|
290
|
+
{ text: `Agent profile root: ${snapshot.runtimeProfileRoot}`, fg: PALETTE.muted },
|
|
290
291
|
{ text: '' },
|
|
291
292
|
...profileLines(snapshot),
|
|
292
293
|
{ text: '' },
|
|
@@ -301,7 +302,7 @@ function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCatego
|
|
|
301
302
|
base.push(
|
|
302
303
|
{ text: `Session memories: ${snapshot.sessionMemoryCount}`, fg: PALETTE.info },
|
|
303
304
|
{ text: `Local routines: ${snapshot.localRoutineCount}; enabled: ${snapshot.enabledRoutineCount}`, fg: PALETTE.info },
|
|
304
|
-
{ text: `Local skills: ${snapshot.localSkillCount}; enabled: ${snapshot.enabledSkillCount}`, fg: PALETTE.info },
|
|
305
|
+
{ text: `Local skills: ${snapshot.localSkillCount}; enabled: ${snapshot.enabledSkillCount}; bundles: ${snapshot.localSkillBundleCount}; active skills: ${snapshot.activeSkillCount}`, fg: PALETTE.info },
|
|
305
306
|
{ text: `Local personas: ${snapshot.localPersonaCount}; active: ${snapshot.activePersonaName}`, fg: PALETTE.info },
|
|
306
307
|
{ text: 'Durable memory, routines, skills, and personas remain Agent-local until shared registry contracts exist.', fg: PALETTE.good },
|
|
307
308
|
{ text: 'Secrets are rejected/redacted; store secret references instead of secret values.', fg: PALETTE.warn },
|
|
@@ -316,11 +317,13 @@ function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCatego
|
|
|
316
317
|
);
|
|
317
318
|
} else if (category.id === 'skills') {
|
|
318
319
|
base.push(
|
|
319
|
-
{ text: `Skills: ${snapshot.localSkillCount}; enabled: ${snapshot.enabledSkillCount}`, fg: PALETTE.info },
|
|
320
|
+
{ text: `Skills: ${snapshot.localSkillCount}; enabled: ${snapshot.enabledSkillCount}; bundles: ${snapshot.localSkillBundleCount}; enabled bundles: ${snapshot.enabledSkillBundleCount}; active skills: ${snapshot.activeSkillCount}`, fg: PALETTE.info },
|
|
320
321
|
{ text: 'Skills are reusable local procedures the assistant can apply from the main conversation.', fg: PALETTE.good },
|
|
321
|
-
{ text: 'Enabled skills are injected as operating guidance; secret-looking content is rejected.', fg: PALETTE.warn },
|
|
322
|
+
{ text: 'Enabled skills and enabled bundles are injected as operating guidance; secret-looking content is rejected.', fg: PALETTE.warn },
|
|
322
323
|
{ text: '' },
|
|
323
324
|
...localLibraryLines('Skill Library', snapshot.localSkills, 'No local skills yet. Create one here with Create skill.', workspace.selectedLocalLibraryItem('skill')?.id ?? null),
|
|
325
|
+
{ text: '' },
|
|
326
|
+
...localLibraryLines('Skill Bundles', snapshot.localSkillBundles, 'No local skill bundles yet. Use Skill bundles and Create bundle after creating skills.', null),
|
|
324
327
|
);
|
|
325
328
|
} else if (category.id === 'routines') {
|
|
326
329
|
base.push(
|
package/src/version.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { join } from 'node:path';
|
|
|
6
6
|
// The prebuild script updates the fallback value before compilation.
|
|
7
7
|
// Uses import.meta.dir (Bun) to locate package.json relative to this file,
|
|
8
8
|
// which is correct regardless of the process working directory.
|
|
9
|
-
let _version = '0.1.
|
|
9
|
+
let _version = '0.1.81';
|
|
10
10
|
let _sdkVersion = '0.33.35';
|
|
11
11
|
try {
|
|
12
12
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8')) as {
|