@pellux/goodvibes-agent 0.1.116 → 0.1.117
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 +41 -663
- package/README.md +38 -52
- package/dist/package/main.js +37826 -35924
- package/docs/README.md +5 -4
- package/docs/{connected-services.md → connected-host.md} +18 -10
- package/docs/getting-started.md +41 -44
- package/docs/release-and-publishing.md +6 -4
- package/package.json +3 -3
- package/src/agent/behavior-discovery-summary.ts +1 -3
- package/src/agent/persona-discovery.ts +1 -3
- package/src/agent/persona-registry.ts +5 -3
- package/src/agent/reminder-schedule-format.ts +6 -6
- package/src/agent/reminder-schedule.ts +1 -1
- package/src/agent/routine-registry.ts +43 -4
- package/src/agent/routine-schedule-format.ts +6 -6
- package/src/agent/routine-schedule-promotion.ts +1 -1
- package/src/agent/routine-schedule-receipts.ts +1 -1
- package/src/agent/runtime-profile-starters.ts +3 -3
- package/src/agent/runtime-profile.ts +78 -0
- package/src/agent/skill-registry.ts +190 -13
- package/src/cli/agent-knowledge-command.ts +2 -2
- package/src/cli/agent-knowledge-format.ts +2 -2
- package/src/cli/agent-knowledge-runtime.ts +1 -1
- package/src/cli/bundle-command.ts +1 -1
- package/src/cli/entrypoint.ts +16 -7
- package/src/cli/help.ts +26 -19
- package/src/cli/local-library-command.ts +75 -21
- package/src/cli/management-commands.ts +7 -6
- package/src/cli/management.ts +6 -51
- package/src/cli/parser.ts +23 -2
- package/src/cli/profiles-command.ts +119 -1
- package/src/cli/routines-command.ts +139 -11
- package/src/cli/service-posture.ts +11 -11
- package/src/cli/status.ts +21 -25
- package/src/input/agent-workspace-access-command-editor-submission.ts +149 -0
- package/src/input/agent-workspace-access-command-editors.ts +170 -0
- package/src/input/agent-workspace-activation.ts +2 -1
- package/src/input/agent-workspace-basic-command-editor-submission.ts +345 -92
- package/src/input/agent-workspace-basic-command-editors.ts +480 -7
- package/src/input/agent-workspace-categories.ts +175 -15
- package/src/input/agent-workspace-channel-command-editor-submission.ts +49 -0
- package/src/input/agent-workspace-channel-command-editors.ts +47 -0
- package/src/input/agent-workspace-command-editor.ts +23 -1
- package/src/input/agent-workspace-delegation-editor-submission.ts +55 -0
- package/src/input/agent-workspace-editors.ts +27 -0
- package/src/input/agent-workspace-knowledge-command-editor-submission.ts +132 -0
- package/src/input/agent-workspace-knowledge-command-editors.ts +110 -0
- package/src/input/agent-workspace-learned-behavior.ts +67 -0
- package/src/input/agent-workspace-library-command-editor-submission.ts +50 -0
- package/src/input/agent-workspace-library-command-editors.ts +35 -0
- package/src/input/agent-workspace-mcp-command-editor-submission.ts +95 -0
- package/src/input/agent-workspace-memory-command-editor-submission.ts +232 -0
- package/src/input/agent-workspace-memory-command-editors.ts +180 -0
- package/src/input/agent-workspace-navigation.ts +45 -0
- package/src/input/agent-workspace-notify-editor-submission.ts +107 -0
- package/src/input/agent-workspace-operations-command-editor-submission.ts +146 -0
- package/src/input/agent-workspace-operations-command-editors.ts +119 -0
- package/src/input/agent-workspace-provider-command-editor-submission.ts +155 -0
- package/src/input/agent-workspace-provider-command-editors.ts +93 -0
- package/src/input/agent-workspace-requirements.ts +11 -0
- package/src/input/agent-workspace-secret-editor-submission.ts +153 -0
- package/src/input/agent-workspace-session-command-editor-submission.ts +199 -0
- package/src/input/agent-workspace-session-command-editors.ts +249 -0
- package/src/input/agent-workspace-setup.ts +24 -14
- package/src/input/agent-workspace-skill-bundle-command-editor-submission.ts +101 -0
- package/src/input/agent-workspace-skill-bundle-command-editors.ts +110 -0
- package/src/input/agent-workspace-snapshot.ts +50 -8
- package/src/input/agent-workspace-task-command-editor-submission.ts +61 -0
- package/src/input/agent-workspace-task-command-editors.ts +47 -0
- package/src/input/agent-workspace-types.ts +120 -0
- package/src/input/agent-workspace-voice-media.ts +1 -1
- package/src/input/agent-workspace-workplan-editor-submission.ts +153 -0
- package/src/input/agent-workspace.ts +47 -37
- package/src/input/command-registry.ts +1 -1
- package/src/input/commands/agent-runtime-profile-runtime.ts +89 -4
- package/src/input/commands/agent-skills-runtime.ts +76 -12
- package/src/input/commands/agent-workspace-runtime.ts +4 -3
- package/src/input/commands/brief-runtime.ts +38 -9
- package/src/input/commands/channels-runtime.ts +3 -3
- package/src/input/commands/compat-runtime.ts +32 -0
- package/src/input/commands/health-runtime.ts +22 -24
- package/src/input/commands/knowledge.ts +26 -5
- package/src/input/commands/local-provider-runtime.ts +7 -3
- package/src/input/commands/local-setup-review.ts +4 -14
- package/src/input/commands/mcp-runtime.ts +8 -12
- package/src/input/commands/operator-runtime.ts +8 -67
- package/src/input/commands/personas-runtime.ts +1 -1
- package/src/input/commands/platform-access-runtime.ts +7 -70
- package/src/input/commands/policy-dispatch.ts +1 -1
- package/src/input/commands/policy.ts +1 -5
- package/src/input/commands/product-runtime.ts +2 -2
- package/src/input/commands/qrcode-runtime.ts +6 -4
- package/src/input/commands/routines-runtime.ts +44 -6
- package/src/input/commands/runtime-services.ts +1 -14
- package/src/input/commands/security-runtime.ts +3 -8
- package/src/input/commands/session-content.ts +1 -1
- package/src/input/commands/session-workflow.ts +5 -14
- package/src/input/commands/shell-core.ts +8 -2
- package/src/input/commands/subscription-runtime.ts +1 -6
- package/src/input/commands/support-bundle-runtime.ts +53 -0
- package/src/input/commands/tasks-runtime.ts +2 -2
- package/src/input/commands.ts +4 -0
- package/src/input/feed-context-factory.ts +0 -1
- package/src/input/handler-content-actions.ts +0 -25
- package/src/input/handler-feed.ts +0 -2
- package/src/input/handler-interactions.ts +2 -31
- package/src/input/handler-onboarding.ts +29 -12
- package/src/input/handler-shortcuts.ts +1 -3
- package/src/input/handler.ts +4 -7
- package/src/input/keybindings.ts +9 -9
- package/src/input/mcp-workspace.ts +90 -26
- package/src/input/onboarding/handler-onboarding-routes.ts +3 -1
- package/src/input/onboarding/onboarding-wizard-apply.ts +49 -0
- package/src/input/onboarding/onboarding-wizard-helpers.ts +1 -1
- package/src/input/onboarding/onboarding-wizard-operator-steps.ts +81 -3
- package/src/input/onboarding/onboarding-wizard-steps.ts +14 -7
- package/src/input/onboarding/onboarding-wizard-types.ts +1 -0
- package/src/input/onboarding/onboarding-wizard.ts +19 -2
- package/src/input/profile-picker-modal.ts +2 -2
- package/src/input/settings-modal-agent-policy.ts +6 -6
- package/src/input/settings-modal-subscriptions.ts +3 -3
- package/src/input/settings-modal.ts +14 -14
- package/src/main.ts +0 -2
- package/src/panels/automation-control-panel.ts +3 -3
- package/src/panels/builtin/agent.ts +1 -1
- package/src/panels/builtin/operations.ts +2 -2
- package/src/panels/builtin/session.ts +26 -18
- package/src/panels/builtin/shared.ts +6 -6
- package/src/panels/project-planning-panel.ts +1 -1
- package/src/panels/provider-health-domains.ts +3 -3
- package/src/panels/qr-panel.ts +44 -20
- package/src/panels/subscription-panel.ts +3 -3
- package/src/panels/system-messages-panel.ts +2 -2
- package/src/panels/tasks-panel.ts +4 -4
- package/src/planning/project-planning-coordinator.ts +2 -2
- package/src/renderer/agent-workspace.ts +26 -5
- package/src/renderer/block-actions.ts +1 -3
- package/src/renderer/help-overlay.ts +13 -8
- package/src/renderer/mcp-workspace.ts +32 -13
- package/src/renderer/model-picker-overlay.ts +0 -1
- package/src/renderer/model-workspace.ts +0 -2
- package/src/renderer/onboarding/onboarding-wizard.ts +20 -4
- package/src/renderer/panel-picker-overlay.ts +1 -1
- package/src/renderer/profile-picker-modal.ts +2 -2
- package/src/renderer/settings-modal-helpers.ts +23 -23
- package/src/renderer/settings-modal.ts +18 -18
- package/src/renderer/system-message.ts +1 -1
- package/src/runtime/agent-runtime-events.ts +129 -0
- package/src/runtime/bootstrap-command-parts.ts +3 -2
- package/src/runtime/bootstrap-core.ts +4 -64
- package/src/runtime/bootstrap-shell.ts +42 -4
- package/src/runtime/bootstrap.ts +8 -27
- package/src/runtime/connected-host-auth.ts +42 -0
- package/src/runtime/diagnostics/panels/panel-resources.ts +1 -1
- package/src/runtime/index.ts +1 -5
- package/src/runtime/onboarding/apply.ts +213 -0
- package/src/runtime/onboarding/derivation.ts +5 -5
- package/src/runtime/onboarding/snapshot.ts +2 -2
- package/src/runtime/onboarding/types.ts +26 -1
- package/src/runtime/onboarding/verify.ts +81 -2
- package/src/runtime/operator-token-cleanup.ts +2 -2
- package/src/runtime/services.ts +27 -12
- package/src/shell/service-settings-sync.ts +7 -7
- package/src/shell/ui-openers.ts +27 -38
- package/src/tools/agent-local-registry-tool.ts +10 -4
- package/src/tools/agent-tool-policy-guard.ts +5 -7
- package/src/version.ts +1 -1
- package/src/cli/package-verification.ts +0 -274
|
@@ -477,7 +477,7 @@ export async function reconcileRoutineScheduleReceipts(
|
|
|
477
477
|
return {
|
|
478
478
|
ok: false,
|
|
479
479
|
kind: 'auth_required',
|
|
480
|
-
error: `No
|
|
480
|
+
error: `No connected-host operator token found at ${connection.tokenPath}`,
|
|
481
481
|
route: ROUTINE_SCHEDULE_ROUTE,
|
|
482
482
|
baseUrl: connection.baseUrl,
|
|
483
483
|
};
|
|
@@ -44,7 +44,7 @@ export const STARTER_TEMPLATES: readonly AgentRuntimeProfileStarterTemplate[] =
|
|
|
44
44
|
body: [
|
|
45
45
|
'Operate as a calm household coordinator.',
|
|
46
46
|
'Track preferences, routines, device/service notes, and recurring decisions in Agent-local memory after they are durable and non-secret.',
|
|
47
|
-
'Use read-only
|
|
47
|
+
'Use read-only connected-host/operator routes for status checks. Require explicit approval for purchases, messages to other people, service changes, deletions, or secret handling.',
|
|
48
48
|
'Keep replies concrete: next action, owner, date, and open question when one is needed.',
|
|
49
49
|
].join('\n'),
|
|
50
50
|
tags: ['household', 'home', 'coordination'],
|
|
@@ -223,7 +223,7 @@ export const STARTER_TEMPLATES: readonly AgentRuntimeProfileStarterTemplate[] =
|
|
|
223
223
|
description: 'Operator persona for systems, incidents, runbooks, approvals, and service posture.',
|
|
224
224
|
body: [
|
|
225
225
|
'Favor explicit state, logs, health, approvals, and next action.',
|
|
226
|
-
'Use read-only
|
|
226
|
+
'Use read-only connected-host/operator routes by default.',
|
|
227
227
|
'Require explicit confirmation for run, pause, resume, cancel, retry, approve, deny, service changes, or writes.',
|
|
228
228
|
'Delegate code/build fixes to GoodVibes TUI when explicitly requested.',
|
|
229
229
|
].join('\n'),
|
|
@@ -260,7 +260,7 @@ export const STARTER_TEMPLATES: readonly AgentRuntimeProfileStarterTemplate[] =
|
|
|
260
260
|
name: 'Daily Operations Sweep',
|
|
261
261
|
description: 'Inspect service posture, pending approvals, failed runs, and stale tasks.',
|
|
262
262
|
steps: [
|
|
263
|
-
'1. Refresh
|
|
263
|
+
'1. Refresh connected-host status, compat, approvals, workplan, automation snapshot, runs, schedules, and capacity.',
|
|
264
264
|
'2. Summarize degraded items and blocked work.',
|
|
265
265
|
'3. Recommend exact follow-up commands with confirmation gates for side effects.',
|
|
266
266
|
].join('\n'),
|
|
@@ -36,6 +36,12 @@ export interface AgentRuntimeProfileInfo extends AgentRuntimeProfileResolution {
|
|
|
36
36
|
readonly starterTemplateApplication?: AgentRuntimeProfileTemplateApplication;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
export interface AgentRuntimeProfileSelection extends AgentRuntimeProfileResolution {
|
|
40
|
+
readonly selectedAt: string | null;
|
|
41
|
+
readonly path: string;
|
|
42
|
+
readonly exists: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
39
45
|
export interface AgentRuntimeProfileTemplateApplication {
|
|
40
46
|
readonly id: AgentRuntimeProfileTemplateId;
|
|
41
47
|
readonly name: string;
|
|
@@ -76,11 +82,14 @@ export interface AgentRuntimeProfileCommandResult {
|
|
|
76
82
|
| 'agent.profiles.template.from_discovered'
|
|
77
83
|
| 'agent.profiles.create_from_discovered'
|
|
78
84
|
| 'agent.profiles.create'
|
|
85
|
+
| 'agent.profiles.default'
|
|
86
|
+
| 'agent.profiles.default.clear'
|
|
79
87
|
| 'agent.profiles.delete'
|
|
80
88
|
| 'agent.profiles.error';
|
|
81
89
|
readonly data?: {
|
|
82
90
|
readonly profiles?: readonly AgentRuntimeProfileInfo[];
|
|
83
91
|
readonly profile?: AgentRuntimeProfileInfo;
|
|
92
|
+
readonly selectedProfile?: AgentRuntimeProfileSelection;
|
|
84
93
|
readonly templates?: readonly AgentRuntimeProfileTemplateSummary[];
|
|
85
94
|
readonly appliedTemplate?: AgentRuntimeProfileTemplateApplication;
|
|
86
95
|
readonly template?: AgentRuntimeProfileTemplateSummary;
|
|
@@ -91,6 +100,7 @@ export interface AgentRuntimeProfileCommandResult {
|
|
|
91
100
|
}
|
|
92
101
|
|
|
93
102
|
const PROFILE_CREATED_FILE = 'profile.json';
|
|
103
|
+
const PROFILE_SELECTION_FILE = 'profile-selection.json';
|
|
94
104
|
const PROFILE_ID_PATTERN = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/;
|
|
95
105
|
|
|
96
106
|
export function normalizeAgentRuntimeProfileId(value: string): string {
|
|
@@ -122,6 +132,10 @@ export function getAgentRuntimeProfileTemplatesRoot(baseHomeDirectory: string):
|
|
|
122
132
|
return join(baseHomeDirectory, '.goodvibes', GOODVIBES_AGENT_SURFACE_ROOT, 'profile-starters');
|
|
123
133
|
}
|
|
124
134
|
|
|
135
|
+
export function getAgentRuntimeProfileSelectionPath(baseHomeDirectory: string): string {
|
|
136
|
+
return join(baseHomeDirectory, '.goodvibes', GOODVIBES_AGENT_SURFACE_ROOT, PROFILE_SELECTION_FILE);
|
|
137
|
+
}
|
|
138
|
+
|
|
125
139
|
export function resolveAgentRuntimeProfileHome(baseHomeDirectory: string, profileName: string): AgentRuntimeProfileResolution {
|
|
126
140
|
const id = assertValidAgentRuntimeProfileId(profileName);
|
|
127
141
|
return {
|
|
@@ -130,6 +144,37 @@ export function resolveAgentRuntimeProfileHome(baseHomeDirectory: string, profil
|
|
|
130
144
|
};
|
|
131
145
|
}
|
|
132
146
|
|
|
147
|
+
export function readAgentRuntimeProfileSelection(baseHomeDirectory: string): AgentRuntimeProfileSelection | null {
|
|
148
|
+
const path = getAgentRuntimeProfileSelectionPath(baseHomeDirectory);
|
|
149
|
+
if (!existsSync(path)) return null;
|
|
150
|
+
try {
|
|
151
|
+
const raw: unknown = JSON.parse(readFileSync(path, 'utf-8'));
|
|
152
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null;
|
|
153
|
+
const record = raw as Record<string, unknown>;
|
|
154
|
+
const profileId = record.profileId;
|
|
155
|
+
if (typeof profileId !== 'string') return null;
|
|
156
|
+
const resolution = resolveAgentRuntimeProfileHome(baseHomeDirectory, profileId);
|
|
157
|
+
const selectedAt = typeof record.selectedAt === 'string' ? record.selectedAt : null;
|
|
158
|
+
return {
|
|
159
|
+
...resolution,
|
|
160
|
+
selectedAt,
|
|
161
|
+
path,
|
|
162
|
+
exists: existsSync(resolution.homeDirectory),
|
|
163
|
+
};
|
|
164
|
+
} catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function resolveSelectedAgentRuntimeProfileHome(baseHomeDirectory: string): AgentRuntimeProfileResolution | null {
|
|
170
|
+
const selection = readAgentRuntimeProfileSelection(baseHomeDirectory);
|
|
171
|
+
if (!selection?.exists) return null;
|
|
172
|
+
return {
|
|
173
|
+
id: selection.id,
|
|
174
|
+
homeDirectory: selection.homeDirectory,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
133
178
|
function readProfileCreatedAt(homeDirectory: string): string | null {
|
|
134
179
|
const path = join(homeDirectory, PROFILE_CREATED_FILE);
|
|
135
180
|
if (!existsSync(path)) return null;
|
|
@@ -573,9 +618,42 @@ export function createAgentRuntimeProfile(baseHomeDirectory: string, profileName
|
|
|
573
618
|
};
|
|
574
619
|
}
|
|
575
620
|
|
|
621
|
+
export function setAgentRuntimeProfileSelection(baseHomeDirectory: string, profileName: string): AgentRuntimeProfileSelection {
|
|
622
|
+
const resolution = resolveAgentRuntimeProfileHome(baseHomeDirectory, profileName);
|
|
623
|
+
if (!existsSync(resolution.homeDirectory)) {
|
|
624
|
+
throw new Error(`Agent profile not found: ${resolution.id}`);
|
|
625
|
+
}
|
|
626
|
+
const path = getAgentRuntimeProfileSelectionPath(baseHomeDirectory);
|
|
627
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
628
|
+
const selectedAt = new Date().toISOString();
|
|
629
|
+
writeFileSync(
|
|
630
|
+
path,
|
|
631
|
+
`${JSON.stringify({
|
|
632
|
+
profileId: resolution.id,
|
|
633
|
+
selectedAt,
|
|
634
|
+
}, null, 2)}\n`,
|
|
635
|
+
'utf-8',
|
|
636
|
+
);
|
|
637
|
+
return {
|
|
638
|
+
...resolution,
|
|
639
|
+
selectedAt,
|
|
640
|
+
path,
|
|
641
|
+
exists: true,
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
export function clearAgentRuntimeProfileSelection(baseHomeDirectory: string): boolean {
|
|
646
|
+
const path = getAgentRuntimeProfileSelectionPath(baseHomeDirectory);
|
|
647
|
+
if (!existsSync(path)) return false;
|
|
648
|
+
rmSync(path, { force: true });
|
|
649
|
+
return true;
|
|
650
|
+
}
|
|
651
|
+
|
|
576
652
|
export function deleteAgentRuntimeProfile(baseHomeDirectory: string, profileName: string): boolean {
|
|
577
653
|
const resolution = resolveAgentRuntimeProfileHome(baseHomeDirectory, profileName);
|
|
578
654
|
if (!existsSync(resolution.homeDirectory)) return false;
|
|
579
655
|
rmSync(resolution.homeDirectory, { recursive: true, force: true });
|
|
656
|
+
const selection = readAgentRuntimeProfileSelection(baseHomeDirectory);
|
|
657
|
+
if (selection?.id === resolution.id) clearAgentRuntimeProfileSelection(baseHomeDirectory);
|
|
580
658
|
return true;
|
|
581
659
|
}
|
|
@@ -1,11 +1,31 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { dirname } from 'node:path';
|
|
1
|
+
import { accessSync, constants, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { delimiter, dirname, join } from 'node:path';
|
|
3
3
|
import type { ShellPathService } from '@/runtime/index.ts';
|
|
4
4
|
import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
|
|
5
5
|
import { assertNoSecretLikeText } from './persona-registry.ts';
|
|
6
6
|
|
|
7
7
|
export type AgentSkillSource = 'user' | 'agent' | 'imported' | 'system';
|
|
8
8
|
export type AgentSkillReviewState = 'fresh' | 'reviewed' | 'stale';
|
|
9
|
+
export type AgentSkillRequirementKind = 'env' | 'command';
|
|
10
|
+
|
|
11
|
+
export interface AgentSkillRequirement {
|
|
12
|
+
readonly kind: AgentSkillRequirementKind;
|
|
13
|
+
readonly name: string;
|
|
14
|
+
readonly description?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface AgentSkillReadiness {
|
|
18
|
+
readonly ready: boolean;
|
|
19
|
+
readonly met: readonly AgentSkillRequirement[];
|
|
20
|
+
readonly missing: readonly AgentSkillRequirement[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface AgentSkillBundleReadiness {
|
|
24
|
+
readonly ready: boolean;
|
|
25
|
+
readonly includedSkills: readonly AgentSkillRecord[];
|
|
26
|
+
readonly missingSkillIds: readonly string[];
|
|
27
|
+
readonly missingRequirements: readonly AgentSkillRequirement[];
|
|
28
|
+
}
|
|
9
29
|
|
|
10
30
|
export interface AgentSkillRecord {
|
|
11
31
|
readonly id: string;
|
|
@@ -14,6 +34,7 @@ export interface AgentSkillRecord {
|
|
|
14
34
|
readonly procedure: string;
|
|
15
35
|
readonly triggers: readonly string[];
|
|
16
36
|
readonly tags: readonly string[];
|
|
37
|
+
readonly requirements: readonly AgentSkillRequirement[];
|
|
17
38
|
readonly enabled: boolean;
|
|
18
39
|
readonly source: AgentSkillSource;
|
|
19
40
|
readonly provenance: string;
|
|
@@ -45,6 +66,7 @@ export interface AgentSkillCreateInput {
|
|
|
45
66
|
readonly procedure: string;
|
|
46
67
|
readonly triggers?: readonly string[];
|
|
47
68
|
readonly tags?: readonly string[];
|
|
69
|
+
readonly requirements?: readonly AgentSkillRequirement[];
|
|
48
70
|
readonly enabled?: boolean;
|
|
49
71
|
readonly source?: AgentSkillSource;
|
|
50
72
|
readonly provenance?: string;
|
|
@@ -65,6 +87,7 @@ export interface AgentSkillUpdateInput {
|
|
|
65
87
|
readonly procedure?: string;
|
|
66
88
|
readonly triggers?: readonly string[];
|
|
67
89
|
readonly tags?: readonly string[];
|
|
90
|
+
readonly requirements?: readonly AgentSkillRequirement[];
|
|
68
91
|
readonly provenance?: string;
|
|
69
92
|
}
|
|
70
93
|
|
|
@@ -105,6 +128,11 @@ function readStringArray(value: unknown): string[] {
|
|
|
105
128
|
return value.filter((entry): entry is string => typeof entry === 'string').map((entry) => entry.trim()).filter(Boolean);
|
|
106
129
|
}
|
|
107
130
|
|
|
131
|
+
function readRequirementKind(value: unknown): AgentSkillRequirementKind | null {
|
|
132
|
+
if (value === 'env' || value === 'command') return value;
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
108
136
|
function normalizeName(name: string): string {
|
|
109
137
|
return name.trim().replace(/\s+/g, ' ');
|
|
110
138
|
}
|
|
@@ -123,6 +151,48 @@ function normalizeList(values: readonly string[] | undefined): string[] {
|
|
|
123
151
|
return result;
|
|
124
152
|
}
|
|
125
153
|
|
|
154
|
+
function validateRequirementName(requirement: AgentSkillRequirement): void {
|
|
155
|
+
if (requirement.kind === 'env' && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(requirement.name)) {
|
|
156
|
+
throw new Error(`Invalid skill env requirement: ${requirement.name}`);
|
|
157
|
+
}
|
|
158
|
+
if (requirement.kind === 'command' && !/^[A-Za-z0-9._+-]+$/.test(requirement.name)) {
|
|
159
|
+
throw new Error(`Invalid skill command requirement: ${requirement.name}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function normalizeRequirements(values: readonly AgentSkillRequirement[] | undefined): AgentSkillRequirement[] {
|
|
164
|
+
const seen = new Set<string>();
|
|
165
|
+
const result: AgentSkillRequirement[] = [];
|
|
166
|
+
for (const value of values ?? []) {
|
|
167
|
+
const kind = readRequirementKind(value.kind);
|
|
168
|
+
const name = typeof value.name === 'string' ? value.name.trim() : '';
|
|
169
|
+
if (!kind || !name) continue;
|
|
170
|
+
const description = typeof value.description === 'string' ? value.description.trim() : '';
|
|
171
|
+
const requirement: AgentSkillRequirement = {
|
|
172
|
+
kind,
|
|
173
|
+
name,
|
|
174
|
+
...(description ? { description } : {}),
|
|
175
|
+
};
|
|
176
|
+
validateRequirementName(requirement);
|
|
177
|
+
const key = `${requirement.kind}:${requirement.name.toLowerCase()}`;
|
|
178
|
+
if (seen.has(key)) continue;
|
|
179
|
+
seen.add(key);
|
|
180
|
+
result.push(requirement);
|
|
181
|
+
}
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function parseRequirements(value: unknown): AgentSkillRequirement[] {
|
|
186
|
+
if (!Array.isArray(value)) return [];
|
|
187
|
+
return normalizeRequirements(value
|
|
188
|
+
.filter(isRecord)
|
|
189
|
+
.map((entry) => ({
|
|
190
|
+
kind: readRequirementKind(entry.kind) ?? 'env',
|
|
191
|
+
name: readString(entry.name).trim(),
|
|
192
|
+
description: readString(entry.description).trim(),
|
|
193
|
+
})));
|
|
194
|
+
}
|
|
195
|
+
|
|
126
196
|
function slugify(value: string): string {
|
|
127
197
|
const slug = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
128
198
|
return slug || 'skill';
|
|
@@ -132,6 +202,23 @@ function nowIso(): string {
|
|
|
132
202
|
return new Date().toISOString();
|
|
133
203
|
}
|
|
134
204
|
|
|
205
|
+
function canExecute(path: string): boolean {
|
|
206
|
+
try {
|
|
207
|
+
accessSync(path, constants.X_OK);
|
|
208
|
+
return true;
|
|
209
|
+
} catch {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function commandExists(command: string, pathValue: string | undefined): boolean {
|
|
215
|
+
if (!pathValue) return false;
|
|
216
|
+
return pathValue
|
|
217
|
+
.split(delimiter)
|
|
218
|
+
.filter(Boolean)
|
|
219
|
+
.some((pathEntry) => canExecute(join(pathEntry, command)));
|
|
220
|
+
}
|
|
221
|
+
|
|
135
222
|
function parseSkill(value: unknown): AgentSkillRecord | null {
|
|
136
223
|
if (!isRecord(value)) return null;
|
|
137
224
|
const id = readString(value.id).trim();
|
|
@@ -150,6 +237,7 @@ function parseSkill(value: unknown): AgentSkillRecord | null {
|
|
|
150
237
|
procedure,
|
|
151
238
|
triggers: readStringArray(value.triggers),
|
|
152
239
|
tags: readStringArray(value.tags),
|
|
240
|
+
requirements: parseRequirements(value.requirements),
|
|
153
241
|
enabled: value.enabled === true,
|
|
154
242
|
source,
|
|
155
243
|
provenance: readString(value.provenance, source).trim() || source,
|
|
@@ -206,14 +294,16 @@ function formatStore(store: SkillStoreFile): string {
|
|
|
206
294
|
return `${JSON.stringify(store, null, 2)}\n`;
|
|
207
295
|
}
|
|
208
296
|
|
|
209
|
-
|
|
297
|
+
type AgentLocalStorePaths = Pick<ShellPathService, 'resolveUserPath'>;
|
|
298
|
+
|
|
299
|
+
export function skillStorePath(shellPaths: AgentLocalStorePaths): string {
|
|
210
300
|
return shellPaths.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, 'skills', 'skills.json');
|
|
211
301
|
}
|
|
212
302
|
|
|
213
303
|
export class AgentSkillRegistry {
|
|
214
304
|
public constructor(private readonly storePath: string) {}
|
|
215
305
|
|
|
216
|
-
public static fromShellPaths(shellPaths:
|
|
306
|
+
public static fromShellPaths(shellPaths: AgentLocalStorePaths): AgentSkillRegistry {
|
|
217
307
|
return new AgentSkillRegistry(skillStorePath(shellPaths));
|
|
218
308
|
}
|
|
219
309
|
|
|
@@ -284,7 +374,8 @@ export class AgentSkillRegistry {
|
|
|
284
374
|
const description = input.description.trim();
|
|
285
375
|
const procedure = input.procedure.trim();
|
|
286
376
|
this.validateRequired(name, description, procedure);
|
|
287
|
-
|
|
377
|
+
const requirements = normalizeRequirements(input.requirements);
|
|
378
|
+
assertNoSecretLikeText([name, description, procedure, ...(input.tags ?? []), ...(input.triggers ?? []), ...requirements.flatMap((requirement) => [requirement.name, requirement.description ?? ''])]);
|
|
288
379
|
const duplicate = store.skills.find((skill) => skill.name.toLowerCase() === name.toLowerCase());
|
|
289
380
|
if (duplicate) throw new Error(`Skill already exists: ${duplicate.id}`);
|
|
290
381
|
const timestamp = nowIso();
|
|
@@ -295,6 +386,7 @@ export class AgentSkillRegistry {
|
|
|
295
386
|
procedure,
|
|
296
387
|
triggers: normalizeList(input.triggers),
|
|
297
388
|
tags: normalizeList(input.tags),
|
|
389
|
+
requirements,
|
|
298
390
|
enabled: input.enabled === true,
|
|
299
391
|
source: input.source ?? 'user',
|
|
300
392
|
provenance: input.provenance?.trim() || input.source || 'user',
|
|
@@ -340,7 +432,8 @@ export class AgentSkillRegistry {
|
|
|
340
432
|
const description = input.description === undefined ? existing.description : input.description.trim();
|
|
341
433
|
const procedure = input.procedure === undefined ? existing.procedure : input.procedure.trim();
|
|
342
434
|
this.validateRequired(name, description, procedure);
|
|
343
|
-
|
|
435
|
+
const requirements = input.requirements === undefined ? existing.requirements : normalizeRequirements(input.requirements);
|
|
436
|
+
assertNoSecretLikeText([name, description, procedure, ...(input.tags ?? []), ...(input.triggers ?? []), ...requirements.flatMap((requirement) => [requirement.name, requirement.description ?? ''])]);
|
|
344
437
|
const duplicate = store.skills.find((skill) => skill.id !== existing.id && skill.name.toLowerCase() === name.toLowerCase());
|
|
345
438
|
if (duplicate) throw new Error(`Skill already exists: ${duplicate.id}`);
|
|
346
439
|
const updated: AgentSkillRecord = {
|
|
@@ -350,6 +443,7 @@ export class AgentSkillRegistry {
|
|
|
350
443
|
procedure,
|
|
351
444
|
triggers: input.triggers === undefined ? existing.triggers : normalizeList(input.triggers),
|
|
352
445
|
tags: input.tags === undefined ? existing.tags : normalizeList(input.tags),
|
|
446
|
+
requirements,
|
|
353
447
|
provenance: input.provenance === undefined ? existing.provenance : input.provenance.trim() || existing.provenance,
|
|
354
448
|
reviewState: 'fresh',
|
|
355
449
|
staleReason: undefined,
|
|
@@ -583,6 +677,80 @@ export class AgentSkillRegistry {
|
|
|
583
677
|
}
|
|
584
678
|
}
|
|
585
679
|
|
|
680
|
+
export function buildAgentSkillRequirements(input: {
|
|
681
|
+
readonly env?: readonly string[];
|
|
682
|
+
readonly commands?: readonly string[];
|
|
683
|
+
}): readonly AgentSkillRequirement[] {
|
|
684
|
+
return normalizeRequirements([
|
|
685
|
+
...(input.env ?? []).map((name) => ({ kind: 'env' as const, name })),
|
|
686
|
+
...(input.commands ?? []).map((name) => ({ kind: 'command' as const, name })),
|
|
687
|
+
]);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
export function normalizeAgentSkillRequirements(values: readonly AgentSkillRequirement[] | undefined): readonly AgentSkillRequirement[] {
|
|
691
|
+
return normalizeRequirements(values);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
export function formatAgentSkillRequirement(requirement: AgentSkillRequirement): string {
|
|
695
|
+
return `${requirement.kind}:${requirement.name}`;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
export function evaluateAgentSkillReadiness(
|
|
699
|
+
skill: Pick<AgentSkillRecord, 'requirements'>,
|
|
700
|
+
options: {
|
|
701
|
+
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
702
|
+
readonly pathValue?: string;
|
|
703
|
+
} = {},
|
|
704
|
+
): AgentSkillReadiness {
|
|
705
|
+
const env = options.env ?? process.env;
|
|
706
|
+
const pathValue = options.pathValue ?? env.PATH;
|
|
707
|
+
const met: AgentSkillRequirement[] = [];
|
|
708
|
+
const missing: AgentSkillRequirement[] = [];
|
|
709
|
+
for (const requirement of skill.requirements) {
|
|
710
|
+
const present = requirement.kind === 'env'
|
|
711
|
+
? typeof env[requirement.name] === 'string' && (env[requirement.name] ?? '').length > 0
|
|
712
|
+
: commandExists(requirement.name, pathValue);
|
|
713
|
+
if (present) met.push(requirement);
|
|
714
|
+
else missing.push(requirement);
|
|
715
|
+
}
|
|
716
|
+
return { ready: missing.length === 0, met, missing };
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
export function evaluateAgentSkillBundleReadiness(
|
|
720
|
+
bundle: Pick<AgentSkillBundleRecord, 'skillIds'>,
|
|
721
|
+
skills: readonly AgentSkillRecord[],
|
|
722
|
+
options: {
|
|
723
|
+
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
724
|
+
readonly pathValue?: string;
|
|
725
|
+
} = {},
|
|
726
|
+
): AgentSkillBundleReadiness {
|
|
727
|
+
const skillById = new Map(skills.map((skill) => [skill.id, skill]));
|
|
728
|
+
const includedSkills: AgentSkillRecord[] = [];
|
|
729
|
+
const missingSkillIds: string[] = [];
|
|
730
|
+
const missingRequirements: AgentSkillRequirement[] = [];
|
|
731
|
+
const seenRequirements = new Set<string>();
|
|
732
|
+
for (const skillId of bundle.skillIds) {
|
|
733
|
+
const skill = skillById.get(skillId);
|
|
734
|
+
if (!skill) {
|
|
735
|
+
missingSkillIds.push(skillId);
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
includedSkills.push(skill);
|
|
739
|
+
for (const requirement of evaluateAgentSkillReadiness(skill, options).missing) {
|
|
740
|
+
const key = `${requirement.kind}:${requirement.name.toLowerCase()}`;
|
|
741
|
+
if (seenRequirements.has(key)) continue;
|
|
742
|
+
seenRequirements.add(key);
|
|
743
|
+
missingRequirements.push(requirement);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
return {
|
|
747
|
+
ready: missingSkillIds.length === 0 && missingRequirements.length === 0,
|
|
748
|
+
includedSkills,
|
|
749
|
+
missingSkillIds,
|
|
750
|
+
missingRequirements,
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
|
|
586
754
|
export function buildEnabledSkillsPrompt(shellPaths: ShellPathService): string | null {
|
|
587
755
|
const snapshot = AgentSkillRegistry.fromShellPaths(shellPaths).snapshot();
|
|
588
756
|
const active = snapshot.activeSkills;
|
|
@@ -591,18 +759,27 @@ export function buildEnabledSkillsPrompt(shellPaths: ShellPathService): string |
|
|
|
591
759
|
'## Enabled GoodVibes Agent Skills',
|
|
592
760
|
'Use these local reusable procedures inside the same serial assistant conversation when they fit the user request.',
|
|
593
761
|
'',
|
|
594
|
-
...snapshot.enabledBundles.slice(0, 4).flatMap((bundle) =>
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
762
|
+
...snapshot.enabledBundles.slice(0, 4).flatMap((bundle) => {
|
|
763
|
+
const readiness = evaluateAgentSkillBundleReadiness(bundle, snapshot.skills);
|
|
764
|
+
const missing = [
|
|
765
|
+
...readiness.missingRequirements.map(formatAgentSkillRequirement),
|
|
766
|
+
...readiness.missingSkillIds.map((skillId) => `skill:${skillId}`),
|
|
767
|
+
];
|
|
768
|
+
return [
|
|
769
|
+
`### Skill Bundle: ${bundle.name}`,
|
|
770
|
+
`Description: ${bundle.description}`,
|
|
771
|
+
`Review state: ${bundle.reviewState}`,
|
|
772
|
+
`Readiness: ${readiness.ready ? 'ready' : `missing ${missing.join(', ')}`}`,
|
|
773
|
+
`Included skills: ${bundle.skillIds.join(', ')}`,
|
|
774
|
+
'',
|
|
775
|
+
];
|
|
776
|
+
}),
|
|
601
777
|
...active.slice(0, 8).flatMap((skill) => [
|
|
602
778
|
`### ${skill.name}`,
|
|
603
779
|
`Description: ${skill.description}`,
|
|
604
780
|
`Review state: ${skill.reviewState}`,
|
|
605
781
|
`Triggers: ${skill.triggers.join(', ') || '(manual)'}`,
|
|
782
|
+
`Readiness: ${evaluateAgentSkillReadiness(skill).ready ? 'ready' : `missing ${evaluateAgentSkillReadiness(skill).missing.map(formatAgentSkillRequirement).join(', ')}`}`,
|
|
606
783
|
skill.procedure,
|
|
607
784
|
'',
|
|
608
785
|
]),
|
|
@@ -493,7 +493,7 @@ export async function handleCompatCommand(runtime: CliCommandRuntime): Promise<C
|
|
|
493
493
|
ok: versionCompatible && knowledgeRouteReady,
|
|
494
494
|
packageVersion: metadata.version,
|
|
495
495
|
sdkPin: metadata.sdkVersion,
|
|
496
|
-
|
|
496
|
+
connectedHost: {
|
|
497
497
|
baseUrl: connection.baseUrl,
|
|
498
498
|
status: daemon.status,
|
|
499
499
|
version: daemonVersion,
|
|
@@ -518,7 +518,7 @@ export async function handleCompatCommand(runtime: CliCommandRuntime): Promise<C
|
|
|
518
518
|
` version compatible: ${yesNo(versionCompatible)}`,
|
|
519
519
|
` operator token: ${connection.token ? 'present' : 'missing'} (${connection.tokenPath})`,
|
|
520
520
|
` Agent knowledge route: ${knowledgeRouteReady ? 'ready' : `not ready (${knowledgeRoute.ok ? 'unknown' : knowledgeRoute.kind})`}`,
|
|
521
|
-
...(versionCompatible ? [] : [' next: update connected GoodVibes
|
|
521
|
+
...(versionCompatible ? [] : [' next: update the connected GoodVibes host so /status matches the Agent SDK pin.']),
|
|
522
522
|
].join('\n');
|
|
523
523
|
return {
|
|
524
524
|
output: runtime.cli.flags.outputFormat === 'json' ? JSON.stringify(value, null, 2) : text,
|
|
@@ -367,10 +367,10 @@ export function formatFailure(failure: AgentKnowledgeFailureLike, json: boolean)
|
|
|
367
367
|
? ` versions: runtime=${failure.daemonVersion} expected=${failure.expectedSdkVersion}`
|
|
368
368
|
: null,
|
|
369
369
|
failure.kind === 'version_mismatch'
|
|
370
|
-
? ' next: update connected GoodVibes
|
|
370
|
+
? ' next: update the connected GoodVibes host so /status matches the Agent SDK pin.'
|
|
371
371
|
: null,
|
|
372
372
|
failure.kind === 'daemon_route_unavailable'
|
|
373
|
-
? ' next: update connected GoodVibes
|
|
373
|
+
? ' next: update the connected GoodVibes host to the SDK version required by this Agent package.'
|
|
374
374
|
: null,
|
|
375
375
|
].filter((line): line is string => Boolean(line)).join('\n');
|
|
376
376
|
}
|
|
@@ -94,7 +94,7 @@ export async function classifyKnowledgeError(error: unknown, connection: AgentDa
|
|
|
94
94
|
return {
|
|
95
95
|
ok: false,
|
|
96
96
|
kind: 'version_mismatch',
|
|
97
|
-
error: `Connected GoodVibes
|
|
97
|
+
error: `Connected GoodVibes host SDK version ${daemonVersion} does not match Agent SDK pin ${metadata.sdkVersion}; Agent Knowledge route is unavailable.`,
|
|
98
98
|
baseUrl: connection.baseUrl,
|
|
99
99
|
route,
|
|
100
100
|
daemonVersion,
|
|
@@ -132,7 +132,7 @@ export async function handleBundleCommand(runtime: CliCommandRuntime): Promise<C
|
|
|
132
132
|
const summary = inspectBundle(sourcePath, parsed.value);
|
|
133
133
|
return {
|
|
134
134
|
output: formatJsonOrText(runtime, summary, [
|
|
135
|
-
'GoodVibes bundle',
|
|
135
|
+
'GoodVibes Agent support bundle',
|
|
136
136
|
` type: ${summary.type}`,
|
|
137
137
|
` version: ${summary.version}`,
|
|
138
138
|
` path: ${summary.path}`,
|
package/src/cli/entrypoint.ts
CHANGED
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
import { buildCliServicePosture } from './service-posture.ts';
|
|
26
26
|
import { inspectCliExternalRuntime } from './external-runtime.ts';
|
|
27
27
|
import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
|
|
28
|
-
import { resolveAgentRuntimeProfileHome } from '../agent/runtime-profile.ts';
|
|
28
|
+
import { resolveAgentRuntimeProfileHome, resolveSelectedAgentRuntimeProfileHome } from '../agent/runtime-profile.ts';
|
|
29
29
|
|
|
30
30
|
type ShellEntrypointOwnership = {
|
|
31
31
|
readonly workingDirectory: string;
|
|
@@ -52,14 +52,20 @@ export type PreparedShellCliRuntime = {
|
|
|
52
52
|
readonly bootstrapHomeDirectory: string;
|
|
53
53
|
};
|
|
54
54
|
|
|
55
|
-
function resolveShellEntrypointOwnership(
|
|
55
|
+
export function resolveShellEntrypointOwnership(
|
|
56
56
|
roots: ShellEntrypointRoots,
|
|
57
57
|
workingDirOverride?: string,
|
|
58
|
-
|
|
58
|
+
options: {
|
|
59
|
+
readonly agentProfile?: string;
|
|
60
|
+
readonly useSelectedProfile: boolean;
|
|
61
|
+
} = { useSelectedProfile: true },
|
|
59
62
|
): ShellEntrypointOwnership {
|
|
60
|
-
const
|
|
61
|
-
? resolveAgentRuntimeProfileHome(roots.homeDirectory, agentProfile)
|
|
62
|
-
:
|
|
63
|
+
const selectedProfile = options.agentProfile
|
|
64
|
+
? resolveAgentRuntimeProfileHome(roots.homeDirectory, options.agentProfile)
|
|
65
|
+
: options.useSelectedProfile
|
|
66
|
+
? resolveSelectedAgentRuntimeProfileHome(roots.homeDirectory)
|
|
67
|
+
: null;
|
|
68
|
+
const homeDirectory = selectedProfile?.homeDirectory ?? roots.homeDirectory;
|
|
63
69
|
return {
|
|
64
70
|
workingDirectory: workingDirOverride ?? roots.defaultWorkingDirectory,
|
|
65
71
|
homeDirectory,
|
|
@@ -103,7 +109,10 @@ export async function prepareShellCliRuntime(
|
|
|
103
109
|
ownership = resolveShellEntrypointOwnership(
|
|
104
110
|
roots,
|
|
105
111
|
cli.flags.workingDir ?? (cli.command === 'tui' ? cli.commandArgs[0] : undefined),
|
|
106
|
-
|
|
112
|
+
{
|
|
113
|
+
agentProfile: cli.command === 'profiles' ? undefined : cli.flags.agentProfile,
|
|
114
|
+
useSelectedProfile: cli.command !== 'profiles',
|
|
115
|
+
},
|
|
107
116
|
);
|
|
108
117
|
} catch (error) {
|
|
109
118
|
console.error(error instanceof Error ? error.message : String(error));
|