@pellux/goodvibes-agent 0.1.106 → 0.1.108
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 -10
- package/README.md +28 -9
- package/docs/README.md +5 -5
- package/docs/{runtime-connection.md → connected-services.md} +8 -8
- package/docs/getting-started.md +29 -10
- package/docs/release-and-publishing.md +4 -4
- package/package.json +2 -12
- package/src/agent/reminder-schedule-format.ts +75 -0
- package/src/agent/reminder-schedule.ts +494 -0
- package/src/agent/routine-schedule-format.ts +7 -7
- package/src/agent/routine-schedule-promotion.ts +1 -1
- package/src/agent/routine-schedule-receipts.ts +1 -1
- package/src/agent/skill-discovery.ts +2 -0
- package/src/cli/agent-knowledge-args.ts +93 -0
- package/src/cli/agent-knowledge-command.ts +200 -369
- package/src/cli/agent-knowledge-format.ts +58 -7
- package/src/cli/agent-knowledge-methods.ts +84 -0
- package/src/cli/agent-knowledge-runtime.ts +240 -0
- package/src/cli/config-overrides.ts +2 -2
- package/src/cli/help.ts +31 -11
- package/src/cli/management-commands.ts +2 -2
- package/src/cli/management.ts +8 -8
- package/src/cli/package-verification.ts +7 -3
- package/src/cli/parser.ts +10 -2
- package/src/cli/service-posture.ts +6 -6
- package/src/cli/status.ts +32 -32
- package/src/cli/tui-startup.ts +3 -1
- package/src/input/agent-workspace-activation.ts +24 -13
- package/src/input/agent-workspace-basic-command-editors.ts +448 -0
- package/src/input/agent-workspace-categories.ts +42 -34
- package/src/input/agent-workspace-channels.ts +3 -3
- package/src/input/agent-workspace-command-editor.ts +65 -0
- package/src/input/agent-workspace-editors.ts +17 -2
- package/src/input/agent-workspace-knowledge-query-editor.ts +74 -0
- package/src/input/agent-workspace-knowledge-url-editor.ts +95 -0
- package/src/input/agent-workspace-reminder-schedule-editor.ts +125 -0
- package/src/input/agent-workspace-routine-schedule-editor.ts +127 -0
- package/src/input/agent-workspace-setup.ts +2 -2
- package/src/input/agent-workspace-types.ts +20 -2
- package/src/input/agent-workspace-voice-media.ts +5 -5
- package/src/input/agent-workspace.ts +39 -2
- package/src/input/commands/agent-runtime-profile-runtime.ts +1 -1
- package/src/input/commands/agent-skills-runtime.ts +94 -2
- package/src/input/commands/brief-runtime.ts +126 -0
- package/src/input/commands/channels-runtime.ts +47 -0
- package/src/input/commands/health-runtime.ts +10 -10
- package/src/input/commands/knowledge.ts +176 -1
- package/src/input/commands/planning-runtime.ts +1 -1
- package/src/input/commands/platform-access-runtime.ts +10 -10
- package/src/input/commands/policy-dispatch.ts +1 -1
- package/src/input/commands/schedule-runtime.ts +42 -5
- package/src/input/commands/security-runtime.ts +1 -1
- package/src/input/commands/session-content.ts +1 -1
- package/src/input/commands/session-workflow.ts +1 -1
- package/src/input/commands/shell-core.ts +4 -2
- package/src/input/commands/tasks-runtime.ts +3 -3
- package/src/input/commands.ts +4 -0
- package/src/input/handler-onboarding.ts +4 -4
- package/src/input/handler.ts +3 -2
- package/src/input/onboarding/onboarding-wizard-helpers.ts +1 -1
- package/src/input/onboarding/onboarding-wizard-operator-steps.ts +13 -13
- package/src/input/onboarding/onboarding-wizard-steps.ts +8 -8
- package/src/input/settings-modal-agent-policy.ts +1 -1
- package/src/input/slash-command-parser.ts +60 -0
- package/src/panels/builtin/agent.ts +1 -1
- package/src/panels/builtin/operations.ts +2 -2
- package/src/panels/provider-account-snapshot.ts +1 -1
- package/src/panels/provider-health-domains.ts +6 -6
- package/src/panels/subscription-panel.ts +1 -1
- package/src/panels/tasks-panel.ts +3 -3
- package/src/renderer/agent-workspace.ts +43 -30
- package/src/renderer/help-overlay.ts +1 -1
- package/src/renderer/settings-modal.ts +13 -13
- package/src/runtime/bootstrap-hook-bridge.ts +1 -1
- package/src/runtime/bootstrap.ts +8 -8
- package/src/runtime/index.ts +2 -2
- package/src/runtime/onboarding/derivation.ts +6 -6
- package/src/shell/service-settings-sync.ts +1 -1
- package/src/version.ts +1 -1
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { AgentWorkspaceActionResult, AgentWorkspaceLocalEditor } from './agent-workspace-types.ts';
|
|
2
|
+
import { isAffirmative } from './agent-workspace-editors.ts';
|
|
3
|
+
import { quoteSlashCommandArg } from './slash-command-parser.ts';
|
|
4
|
+
|
|
5
|
+
type AgentWorkspaceFieldReader = (fieldId: string) => string;
|
|
6
|
+
type ReminderScheduleKind = 'cron' | 'every' | 'at';
|
|
7
|
+
|
|
8
|
+
export type AgentReminderScheduleEditorSubmission =
|
|
9
|
+
| {
|
|
10
|
+
readonly kind: 'editor';
|
|
11
|
+
readonly editor: AgentWorkspaceLocalEditor;
|
|
12
|
+
readonly status: string;
|
|
13
|
+
readonly actionResult?: AgentWorkspaceActionResult;
|
|
14
|
+
}
|
|
15
|
+
| {
|
|
16
|
+
readonly kind: 'dispatch';
|
|
17
|
+
readonly command: string;
|
|
18
|
+
readonly status: string;
|
|
19
|
+
readonly actionResult: AgentWorkspaceActionResult;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function createReminderScheduleEditor(): AgentWorkspaceLocalEditor {
|
|
23
|
+
return {
|
|
24
|
+
kind: 'reminder-schedule',
|
|
25
|
+
mode: 'create',
|
|
26
|
+
title: 'Create Reminder',
|
|
27
|
+
selectedFieldIndex: 0,
|
|
28
|
+
message: 'Create one connected reminder schedule. Type yes on the final field to confirm.',
|
|
29
|
+
fields: [
|
|
30
|
+
{ id: 'message', label: 'Reminder', value: '', required: true, multiline: true, hint: 'What should the Agent remind you about? Ctrl-J inserts a new line.' },
|
|
31
|
+
{ id: 'scheduleKind', label: 'Schedule type', value: 'at', required: true, multiline: false, hint: 'at, every, or cron.' },
|
|
32
|
+
{ id: 'scheduleValue', label: 'Schedule value', value: '', required: true, multiline: false, hint: 'Examples: 2026-06-01T09:00:00-05:00, 7d, or 0 9 * * *.' },
|
|
33
|
+
{ id: 'timezone', label: 'Timezone', value: '', required: false, multiline: false, hint: 'Optional IANA timezone for cron schedules, for example America/Chicago.' },
|
|
34
|
+
{ id: 'scheduleName', label: 'Schedule name', value: '', required: false, multiline: false, hint: 'Optional display name for the connected reminder.' },
|
|
35
|
+
{ id: 'deliveryChannel', label: 'Delivery channel', value: '', required: false, multiline: false, hint: 'Optional channel target, for example slack or slack:ops-alerts:Ops.' },
|
|
36
|
+
{ id: 'disabled', label: 'Create disabled', value: 'no', required: false, multiline: false, hint: 'yes/no. Default no.' },
|
|
37
|
+
{ id: 'confirm', label: 'Confirm', value: '', required: true, multiline: false, hint: 'Type yes to run /schedule remind with --yes.' },
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function buildAgentReminderScheduleEditorSubmission(
|
|
43
|
+
editor: AgentWorkspaceLocalEditor,
|
|
44
|
+
readField: AgentWorkspaceFieldReader,
|
|
45
|
+
commandDispatchAvailable: boolean,
|
|
46
|
+
): AgentReminderScheduleEditorSubmission {
|
|
47
|
+
const confirm = readField('confirm');
|
|
48
|
+
if (!isAffirmative(confirm)) {
|
|
49
|
+
return {
|
|
50
|
+
kind: 'editor',
|
|
51
|
+
editor: { ...editor, message: 'Type yes to confirm reminder schedule creation.' },
|
|
52
|
+
status: 'Reminder schedule creation not confirmed.',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const scheduleKind = readScheduleKind(readField('scheduleKind'));
|
|
57
|
+
if (!scheduleKind) {
|
|
58
|
+
const detail = 'Schedule type must be at, every, or cron.';
|
|
59
|
+
return {
|
|
60
|
+
kind: 'editor',
|
|
61
|
+
editor: { ...editor, message: detail },
|
|
62
|
+
status: detail,
|
|
63
|
+
actionResult: {
|
|
64
|
+
kind: 'error',
|
|
65
|
+
title: 'Reminder schedule type invalid',
|
|
66
|
+
detail,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!commandDispatchAvailable) {
|
|
72
|
+
return {
|
|
73
|
+
kind: 'editor',
|
|
74
|
+
editor: { ...editor, message: 'Command dispatch is unavailable; cannot create a reminder from this workspace.' },
|
|
75
|
+
status: 'Command dispatch unavailable.',
|
|
76
|
+
actionResult: {
|
|
77
|
+
kind: 'error',
|
|
78
|
+
title: 'Command dispatch unavailable',
|
|
79
|
+
detail: 'The reminder schedule command cannot be opened from this runtime.',
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const parts = [
|
|
85
|
+
'/schedule',
|
|
86
|
+
'remind',
|
|
87
|
+
'--message',
|
|
88
|
+
quoteSlashCommandArg(readField('message')),
|
|
89
|
+
`--${scheduleKind}`,
|
|
90
|
+
quoteSlashCommandArg(readField('scheduleValue')),
|
|
91
|
+
];
|
|
92
|
+
const timezone = readField('timezone');
|
|
93
|
+
if (timezone.length > 0) parts.push('--timezone', quoteSlashCommandArg(timezone));
|
|
94
|
+
const scheduleName = readField('scheduleName');
|
|
95
|
+
if (scheduleName.length > 0) parts.push('--name', quoteSlashCommandArg(scheduleName));
|
|
96
|
+
const deliveryChannel = readField('deliveryChannel');
|
|
97
|
+
if (deliveryChannel.length > 0) parts.push('--delivery-channel', quoteSlashCommandArg(deliveryChannel));
|
|
98
|
+
if (isOptionalAffirmative(readField('disabled'))) parts.push('--disabled');
|
|
99
|
+
parts.push('--yes');
|
|
100
|
+
const command = parts.join(' ');
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
kind: 'dispatch',
|
|
104
|
+
command,
|
|
105
|
+
status: 'Opening reminder schedule creation.',
|
|
106
|
+
actionResult: {
|
|
107
|
+
kind: 'dispatched',
|
|
108
|
+
title: 'Opening reminder schedule creation',
|
|
109
|
+
detail: 'The workspace handed a confirmed reminder schedule command to the shell-owned command router.',
|
|
110
|
+
command,
|
|
111
|
+
safety: 'safe',
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function readScheduleKind(value: string): ReminderScheduleKind | null {
|
|
117
|
+
const normalized = value.trim().toLowerCase();
|
|
118
|
+
if (normalized === 'cron' || normalized === 'every' || normalized === 'at') return normalized;
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function isOptionalAffirmative(value: string): boolean {
|
|
123
|
+
const normalized = value.trim().toLowerCase();
|
|
124
|
+
return normalized === 'yes' || normalized === 'y' || normalized === 'true' || normalized === 'enabled' || normalized === 'on';
|
|
125
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { AgentWorkspaceActionResult, AgentWorkspaceLocalEditor, AgentWorkspaceLocalLibraryItem } from './agent-workspace-types.ts';
|
|
2
|
+
import { isAffirmative } from './agent-workspace-editors.ts';
|
|
3
|
+
import { quoteSlashCommandArg } from './slash-command-parser.ts';
|
|
4
|
+
|
|
5
|
+
type AgentWorkspaceFieldReader = (fieldId: string) => string;
|
|
6
|
+
type RoutineScheduleKind = 'cron' | 'every' | 'at';
|
|
7
|
+
|
|
8
|
+
export type AgentRoutineScheduleEditorSubmission =
|
|
9
|
+
| {
|
|
10
|
+
readonly kind: 'editor';
|
|
11
|
+
readonly editor: AgentWorkspaceLocalEditor;
|
|
12
|
+
readonly status: string;
|
|
13
|
+
readonly actionResult?: AgentWorkspaceActionResult;
|
|
14
|
+
}
|
|
15
|
+
| {
|
|
16
|
+
readonly kind: 'dispatch';
|
|
17
|
+
readonly command: string;
|
|
18
|
+
readonly status: string;
|
|
19
|
+
readonly actionResult: AgentWorkspaceActionResult;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function createRoutineScheduleEditor(
|
|
23
|
+
selectedRoutine: AgentWorkspaceLocalLibraryItem | null,
|
|
24
|
+
): AgentWorkspaceLocalEditor {
|
|
25
|
+
const selected = selectedRoutine ? `${selectedRoutine.id} (${selectedRoutine.name})` : 'No routine selected';
|
|
26
|
+
return {
|
|
27
|
+
kind: 'routine-schedule',
|
|
28
|
+
mode: 'create',
|
|
29
|
+
title: 'Promote Routine to Schedule',
|
|
30
|
+
selectedFieldIndex: 0,
|
|
31
|
+
message: `Create one connected schedule from a reviewed local routine. Selected: ${selected}. Type yes on the final field to confirm.`,
|
|
32
|
+
fields: [
|
|
33
|
+
{ id: 'routineId', label: 'Routine id', value: selectedRoutine?.id ?? '', required: true, multiline: false, hint: 'Local Agent routine id to promote.' },
|
|
34
|
+
{ id: 'scheduleKind', label: 'Schedule type', value: 'cron', required: true, multiline: false, hint: 'cron, every, or at.' },
|
|
35
|
+
{ id: 'scheduleValue', label: 'Schedule value', value: '', required: true, multiline: false, hint: 'Examples: 0 9 * * *, 7d, or 2026-06-01T09:00:00-05:00.' },
|
|
36
|
+
{ id: 'timezone', label: 'Timezone', value: '', required: false, multiline: false, hint: 'Optional IANA timezone, for example America/Chicago.' },
|
|
37
|
+
{ id: 'scheduleName', label: 'Schedule name', value: selectedRoutine?.name ?? '', required: false, multiline: false, hint: 'Optional display name for the connected schedule.' },
|
|
38
|
+
{ id: 'deliveryChannel', label: 'Delivery channel', value: '', required: false, multiline: false, hint: 'Optional channel target, for example slack or slack:ops-alerts:Ops.' },
|
|
39
|
+
{ id: 'disabled', label: 'Create disabled', value: 'no', required: false, multiline: false, hint: 'yes/no. Default no.' },
|
|
40
|
+
{ id: 'confirm', label: 'Confirm', value: '', required: true, multiline: false, hint: 'Type yes to run /schedule promote-routine with --yes.' },
|
|
41
|
+
],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function buildAgentRoutineScheduleEditorSubmission(
|
|
46
|
+
editor: AgentWorkspaceLocalEditor,
|
|
47
|
+
readField: AgentWorkspaceFieldReader,
|
|
48
|
+
commandDispatchAvailable: boolean,
|
|
49
|
+
): AgentRoutineScheduleEditorSubmission {
|
|
50
|
+
const confirm = readField('confirm');
|
|
51
|
+
if (!isAffirmative(confirm)) {
|
|
52
|
+
return {
|
|
53
|
+
kind: 'editor',
|
|
54
|
+
editor: { ...editor, message: 'Type yes to confirm routine schedule promotion.' },
|
|
55
|
+
status: 'Routine schedule promotion not confirmed.',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const scheduleKind = readScheduleKind(readField('scheduleKind'));
|
|
60
|
+
if (!scheduleKind) {
|
|
61
|
+
const detail = 'Schedule type must be cron, every, or at.';
|
|
62
|
+
return {
|
|
63
|
+
kind: 'editor',
|
|
64
|
+
editor: { ...editor, message: detail },
|
|
65
|
+
status: detail,
|
|
66
|
+
actionResult: {
|
|
67
|
+
kind: 'error',
|
|
68
|
+
title: 'Routine schedule type invalid',
|
|
69
|
+
detail,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!commandDispatchAvailable) {
|
|
75
|
+
return {
|
|
76
|
+
kind: 'editor',
|
|
77
|
+
editor: { ...editor, message: 'Command dispatch is unavailable; cannot promote a routine from this workspace.' },
|
|
78
|
+
status: 'Command dispatch unavailable.',
|
|
79
|
+
actionResult: {
|
|
80
|
+
kind: 'error',
|
|
81
|
+
title: 'Command dispatch unavailable',
|
|
82
|
+
detail: 'The routine schedule promotion command cannot be opened from this runtime.',
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const parts = [
|
|
88
|
+
'/schedule',
|
|
89
|
+
'promote-routine',
|
|
90
|
+
quoteSlashCommandArg(readField('routineId')),
|
|
91
|
+
`--${scheduleKind}`,
|
|
92
|
+
quoteSlashCommandArg(readField('scheduleValue')),
|
|
93
|
+
];
|
|
94
|
+
const timezone = readField('timezone');
|
|
95
|
+
if (timezone.length > 0) parts.push('--timezone', quoteSlashCommandArg(timezone));
|
|
96
|
+
const scheduleName = readField('scheduleName');
|
|
97
|
+
if (scheduleName.length > 0) parts.push('--name', quoteSlashCommandArg(scheduleName));
|
|
98
|
+
const deliveryChannel = readField('deliveryChannel');
|
|
99
|
+
if (deliveryChannel.length > 0) parts.push('--delivery-channel', quoteSlashCommandArg(deliveryChannel));
|
|
100
|
+
if (isOptionalAffirmative(readField('disabled'))) parts.push('--disabled');
|
|
101
|
+
parts.push('--yes');
|
|
102
|
+
const command = parts.join(' ');
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
kind: 'dispatch',
|
|
106
|
+
command,
|
|
107
|
+
status: 'Opening routine schedule promotion.',
|
|
108
|
+
actionResult: {
|
|
109
|
+
kind: 'dispatched',
|
|
110
|
+
title: 'Opening routine schedule promotion',
|
|
111
|
+
detail: 'The workspace handed a confirmed routine schedule promotion command to the shell-owned command router.',
|
|
112
|
+
command,
|
|
113
|
+
safety: 'safe',
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function readScheduleKind(value: string): RoutineScheduleKind | null {
|
|
119
|
+
const normalized = value.trim().toLowerCase();
|
|
120
|
+
if (normalized === 'cron' || normalized === 'every' || normalized === 'at') return normalized;
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function isOptionalAffirmative(value: string): boolean {
|
|
125
|
+
const normalized = value.trim().toLowerCase();
|
|
126
|
+
return normalized === 'yes' || normalized === 'y' || normalized === 'true' || normalized === 'enabled' || normalized === 'on';
|
|
127
|
+
}
|
|
@@ -39,9 +39,9 @@ export function buildAgentWorkspaceSetupChecklist(input: AgentWorkspaceSetupChec
|
|
|
39
39
|
return [
|
|
40
40
|
{
|
|
41
41
|
id: 'runtime',
|
|
42
|
-
label: '
|
|
42
|
+
label: 'Connected services',
|
|
43
43
|
status: 'ready',
|
|
44
|
-
detail: `Agent will connect to ${input.runtimeBaseUrl};
|
|
44
|
+
detail: `Agent will connect to ${input.runtimeBaseUrl}; service ownership stays outside this product.`,
|
|
45
45
|
command: '/health',
|
|
46
46
|
},
|
|
47
47
|
{
|
|
@@ -10,6 +10,24 @@ export type AgentWorkspaceActionKind = 'command' | 'guidance' | 'workspace' | 'e
|
|
|
10
10
|
|
|
11
11
|
export type AgentWorkspaceLocalEditorKind = 'memory' | 'persona' | 'skill' | 'routine' | 'profile';
|
|
12
12
|
|
|
13
|
+
export type AgentWorkspaceEditorKind =
|
|
14
|
+
| AgentWorkspaceLocalEditorKind
|
|
15
|
+
| 'knowledge-url'
|
|
16
|
+
| 'knowledge-file'
|
|
17
|
+
| 'knowledge-bookmarks'
|
|
18
|
+
| 'knowledge-browser-history'
|
|
19
|
+
| 'knowledge-connector-ingest'
|
|
20
|
+
| 'knowledge-search'
|
|
21
|
+
| 'knowledge-ask'
|
|
22
|
+
| 'tts-prompt'
|
|
23
|
+
| 'image-input'
|
|
24
|
+
| 'skill-bundle'
|
|
25
|
+
| 'skill-discovery-import'
|
|
26
|
+
| 'profile-template-export'
|
|
27
|
+
| 'profile-template-import'
|
|
28
|
+
| 'routine-schedule'
|
|
29
|
+
| 'reminder-schedule';
|
|
30
|
+
|
|
13
31
|
export type AgentWorkspaceLocalOperation =
|
|
14
32
|
| 'memory-edit'
|
|
15
33
|
| 'memory-review'
|
|
@@ -42,7 +60,7 @@ export interface AgentWorkspaceEditorField {
|
|
|
42
60
|
}
|
|
43
61
|
|
|
44
62
|
export interface AgentWorkspaceLocalEditor {
|
|
45
|
-
readonly kind:
|
|
63
|
+
readonly kind: AgentWorkspaceEditorKind;
|
|
46
64
|
readonly mode: 'create' | 'update' | 'delete';
|
|
47
65
|
readonly recordId?: string;
|
|
48
66
|
readonly title: string;
|
|
@@ -57,7 +75,7 @@ export interface AgentWorkspaceAction {
|
|
|
57
75
|
readonly detail: string;
|
|
58
76
|
readonly command?: string;
|
|
59
77
|
readonly targetCategoryId?: string;
|
|
60
|
-
readonly editorKind?:
|
|
78
|
+
readonly editorKind?: AgentWorkspaceEditorKind;
|
|
61
79
|
readonly localKind?: AgentWorkspaceLocalEditorKind;
|
|
62
80
|
readonly selectionDelta?: number;
|
|
63
81
|
readonly localOperation?: AgentWorkspaceLocalOperation;
|
|
@@ -123,8 +123,8 @@ function buildProviderStatus(
|
|
|
123
123
|
const nextStep = setupState === 'ready'
|
|
124
124
|
? `${label} is ready for explicit Agent voice/media use.`
|
|
125
125
|
: setupState === 'needs-secret'
|
|
126
|
-
? `Configure one of ${spec.secretKeyOptions.join('|')} in the owning
|
|
127
|
-
: `${label} is registered; confirm any provider-specific setup in the owning
|
|
126
|
+
? `Configure one of ${spec.secretKeyOptions.join('|')} in the owning GoodVibes host environment.`
|
|
127
|
+
: `${label} is registered; confirm any provider-specific setup in the owning GoodVibes host.`;
|
|
128
128
|
return {
|
|
129
129
|
id: provider.id,
|
|
130
130
|
label,
|
|
@@ -150,7 +150,7 @@ function missingSelectedProviderStatus(selectedProviderId: string): AgentWorkspa
|
|
|
150
150
|
secretKeyOptions: [],
|
|
151
151
|
configuredSecretKeys: [],
|
|
152
152
|
missingSecretKeyOptions: [],
|
|
153
|
-
nextStep: `Selected TTS provider ${selectedProviderId} is not registered in
|
|
153
|
+
nextStep: `Selected TTS provider ${selectedProviderId} is not registered in connected GoodVibes services.`,
|
|
154
154
|
};
|
|
155
155
|
}
|
|
156
156
|
|
|
@@ -168,9 +168,9 @@ function browserToolState(context: CommandContext): AgentWorkspaceVoiceMediaRead
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
function browserToolNextStep(state: AgentWorkspaceVoiceMediaReadiness['browserToolState']): string {
|
|
171
|
-
if (state === 'disabled') return 'Inspect MCP browser/automation tools; enable browser access in the owning
|
|
171
|
+
if (state === 'disabled') return 'Inspect MCP browser/automation tools; enable browser access in the owning GoodVibes host only when needed.';
|
|
172
172
|
if (state === 'local-only') return 'Browser tooling is local-only; keep external exposure off unless explicitly configured.';
|
|
173
|
-
return 'Public browser URL is configured; use explicit user action and
|
|
173
|
+
return 'Public browser URL is configured; use explicit user action and Agent policy before browser-side effects.';
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
export function buildAgentWorkspaceVoiceMediaReadiness(options: {
|
|
@@ -8,6 +8,8 @@ import { createAgentRuntimeProfile, type AgentRuntimeProfileInfo } from '../agen
|
|
|
8
8
|
import { AgentSkillRegistry } from '../agent/skill-registry.ts';
|
|
9
9
|
import { activateAgentWorkspaceSelection } from './agent-workspace-activation.ts';
|
|
10
10
|
import { AGENT_WORKSPACE_CATEGORIES } from './agent-workspace-categories.ts';
|
|
11
|
+
import { buildAgentWorkspaceCommandEditorSubmission, isAgentWorkspaceCommandEditorKind } from './agent-workspace-command-editor.ts';
|
|
12
|
+
import { quoteSlashCommandArg } from './slash-command-parser.ts';
|
|
11
13
|
import { createDeleteEditor, createMemoryUpdateEditor, createPersonaUpdateEditor, createRoutineUpdateEditor, createSkillUpdateEditor, editorCategoryId, isAffirmative, splitList } from './agent-workspace-editors.ts';
|
|
12
14
|
import { deleteAgentWorkspaceMemoryEditor, submitAgentWorkspaceMemoryEditor } from './agent-workspace-memory-editor.ts';
|
|
13
15
|
import { buildAgentWorkspaceRuntimeSnapshot } from './agent-workspace-snapshot.ts';
|
|
@@ -379,6 +381,19 @@ export class AgentWorkspace {
|
|
|
379
381
|
safety: 'safe',
|
|
380
382
|
};
|
|
381
383
|
} else if (operation === 'routine-start') {
|
|
384
|
+
if (this.hasCommandDispatch()) {
|
|
385
|
+
const command = `/routines start ${quoteSlashCommandArg(selected.id)}`;
|
|
386
|
+
this.dispatchWorkspaceCommand(command);
|
|
387
|
+
this.status = `Opening routine: ${selected.name}.`;
|
|
388
|
+
this.lastActionResult = {
|
|
389
|
+
kind: 'dispatched',
|
|
390
|
+
title: `Opening routine ${selected.name}`,
|
|
391
|
+
detail: `${selected.name} will print its workflow steps in the main conversation. No hidden job was created.`,
|
|
392
|
+
command,
|
|
393
|
+
safety: 'safe',
|
|
394
|
+
};
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
382
397
|
AgentRoutineRegistry.fromShellPaths(shellPaths).markStarted(selected.id);
|
|
383
398
|
this.finishLocalOperation('routine', `Started routine ${selected.name}`, `${selected.name} was marked started for this main-conversation workflow. No hidden job was created.`);
|
|
384
399
|
} else if (operation === 'routine-enable') {
|
|
@@ -472,6 +487,11 @@ export class AgentWorkspace {
|
|
|
472
487
|
this.status = `${missing.label} is required.`;
|
|
473
488
|
return;
|
|
474
489
|
}
|
|
490
|
+
if (isAgentWorkspaceCommandEditorKind(editor.kind)) {
|
|
491
|
+
this.submitCommandEditor(editor);
|
|
492
|
+
requestRender?.();
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
475
495
|
if (editor.kind === 'memory') {
|
|
476
496
|
if (editor.mode === 'delete') {
|
|
477
497
|
try {
|
|
@@ -608,6 +628,21 @@ export class AgentWorkspace {
|
|
|
608
628
|
}
|
|
609
629
|
}
|
|
610
630
|
|
|
631
|
+
private submitCommandEditor(editor: AgentWorkspaceLocalEditor): void {
|
|
632
|
+
const result = buildAgentWorkspaceCommandEditorSubmission(editor, (fieldId) => this.editorField(fieldId), this.hasCommandDispatch());
|
|
633
|
+
if (result.kind === 'editor') {
|
|
634
|
+
this.localEditor = result.editor;
|
|
635
|
+
this.status = result.status;
|
|
636
|
+
if (result.actionResult) this.lastActionResult = result.actionResult;
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
this.localEditor = null;
|
|
641
|
+
this.status = result.status;
|
|
642
|
+
this.lastActionResult = result.actionResult;
|
|
643
|
+
this.dispatchWorkspaceCommand(result.command);
|
|
644
|
+
}
|
|
645
|
+
|
|
611
646
|
private submitLocalDeleteEditor(shellPaths: ShellPathService, editor: AgentWorkspaceLocalEditor): void {
|
|
612
647
|
const expectedId = editor.recordId ?? '';
|
|
613
648
|
const confirmedId = this.editorField('confirm');
|
|
@@ -629,9 +664,11 @@ export class AgentWorkspace {
|
|
|
629
664
|
} else if (editor.kind === 'skill') {
|
|
630
665
|
const removed = AgentSkillRegistry.fromShellPaths(shellPaths).deleteSkill(expectedId);
|
|
631
666
|
this.finishLocalDelete(editor.kind, removed.id, removed.name);
|
|
632
|
-
} else {
|
|
667
|
+
} else if (editor.kind === 'routine') {
|
|
633
668
|
const removed = AgentRoutineRegistry.fromShellPaths(shellPaths).deleteRoutine(expectedId);
|
|
634
669
|
this.finishLocalDelete(editor.kind, removed.id, removed.name);
|
|
670
|
+
} else {
|
|
671
|
+
throw new Error(`Unsupported delete editor kind: ${editor.kind}`);
|
|
635
672
|
}
|
|
636
673
|
}
|
|
637
674
|
|
|
@@ -647,7 +684,7 @@ export class AgentWorkspace {
|
|
|
647
684
|
this.status = 'Deletion not confirmed.';
|
|
648
685
|
return;
|
|
649
686
|
}
|
|
650
|
-
this.finishLocalDelete(
|
|
687
|
+
this.finishLocalDelete('memory', removed.id, removed.name);
|
|
651
688
|
}
|
|
652
689
|
|
|
653
690
|
private async submitMemoryEditor(editor: AgentWorkspaceLocalEditor): Promise<void> {
|
|
@@ -83,7 +83,7 @@ function renderGuide(homeDirectory: string): string {
|
|
|
83
83
|
'5. Create an Agent profile from the imported starter:',
|
|
84
84
|
' /agent-profile create <name> --template <imported-id> --yes',
|
|
85
85
|
'',
|
|
86
|
-
'This writes only Agent-local starter/profile state. It does not mutate
|
|
86
|
+
'This writes only Agent-local starter/profile state. It does not mutate connected GoodVibes services, default wiki, or non-Agent knowledge segments.',
|
|
87
87
|
].join('\n');
|
|
88
88
|
}
|
|
89
89
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AgentSkillRegistry, type AgentSkillBundleRecord, type AgentSkillRecord } from '../../agent/skill-registry.ts';
|
|
2
|
+
import { discoverSkills, type SkillRecord } from '../../agent/skill-discovery.ts';
|
|
2
3
|
import type { CommandContext, CommandRegistry } from '../command-registry.ts';
|
|
3
4
|
import { requireShellPaths } from './runtime-services.ts';
|
|
4
5
|
|
|
@@ -73,6 +74,89 @@ function renderList(title: string, registry: AgentSkillRegistry, skills: readonl
|
|
|
73
74
|
].join('\n');
|
|
74
75
|
}
|
|
75
76
|
|
|
77
|
+
function summarizeDiscoveredSkill(skill: SkillRecord): string {
|
|
78
|
+
const description = skill.description ? ` - ${skill.description}` : '';
|
|
79
|
+
const dependencies = skill.dependencies.length > 0 ? ` deps=${skill.dependencies.join(',')}` : '';
|
|
80
|
+
const includes = skill.includes.length > 0 ? ` includes=${skill.includes.join(',')}` : '';
|
|
81
|
+
return ` ${skill.name} ${skill.origin}${description}${dependencies}${includes}\n path: ${skill.path}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function renderDiscoveredSkills(skills: readonly SkillRecord[]): string {
|
|
85
|
+
if (skills.length === 0) {
|
|
86
|
+
return [
|
|
87
|
+
'Discovered Agent skill files',
|
|
88
|
+
' No SKILL.md or .md skill files found in project/global Agent skill folders.',
|
|
89
|
+
' Search roots: .goodvibes/skills, .goodvibes/agent/skills, ~/.goodvibes/skills, ~/.goodvibes/agent/skills',
|
|
90
|
+
].join('\n');
|
|
91
|
+
}
|
|
92
|
+
return [
|
|
93
|
+
`Discovered Agent skill files (${skills.length})`,
|
|
94
|
+
...skills.map(summarizeDiscoveredSkill),
|
|
95
|
+
'',
|
|
96
|
+
'Import one with: /agent-skills import-discovered <name> --yes',
|
|
97
|
+
].join('\n');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function discoveredSkillLookupValues(skill: SkillRecord): readonly string[] {
|
|
101
|
+
const slug = skill.name.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
102
|
+
const basename = skill.path.split(/[\\/]/).pop()?.replace(/\.md$/i, '') ?? '';
|
|
103
|
+
return [
|
|
104
|
+
skill.name,
|
|
105
|
+
slug,
|
|
106
|
+
skill.path,
|
|
107
|
+
basename,
|
|
108
|
+
].map((value) => value.trim().toLowerCase()).filter(Boolean);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function findDiscoveredSkill(skills: readonly SkillRecord[], idOrName: string): SkillRecord | null {
|
|
112
|
+
const lookup = idOrName.trim().toLowerCase();
|
|
113
|
+
if (!lookup) return null;
|
|
114
|
+
return skills.find((skill) => discoveredSkillLookupValues(skill).includes(lookup)) ?? null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function frontmatterList(skill: SkillRecord, key: string): readonly string[] {
|
|
118
|
+
const value = skill.frontmatter[key];
|
|
119
|
+
if (!value) return [];
|
|
120
|
+
return splitList(value);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function importDiscoveredSkill(args: readonly string[], ctx: CommandContext, skillRegistry: AgentSkillRegistry): Promise<void> {
|
|
124
|
+
const parsed = parseSkillArgs(args);
|
|
125
|
+
const name = parsed.rest.join(' ').trim();
|
|
126
|
+
if (!name) {
|
|
127
|
+
ctx.print('Usage: /agent-skills import-discovered <name> [--enabled] --yes');
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const discovered = findDiscoveredSkill(await discoverSkills(requireShellPaths(ctx)), name);
|
|
131
|
+
if (!discovered) {
|
|
132
|
+
ctx.print(`Unknown discovered Agent skill: ${name}\nRun /agent-skills discover to inspect available skill files.`);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (!parsed.yes) {
|
|
136
|
+
ctx.print([
|
|
137
|
+
'Agent skill import preview',
|
|
138
|
+
` name: ${discovered.name}`,
|
|
139
|
+
` origin: ${discovered.origin}`,
|
|
140
|
+
` path: ${discovered.path}`,
|
|
141
|
+
` description: ${discovered.description || '(none)'}`,
|
|
142
|
+
` procedure characters: ${discovered.body.length}`,
|
|
143
|
+
' next: rerun with --yes to import into the Agent-local skill registry',
|
|
144
|
+
].join('\n'));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const skill = skillRegistry.create({
|
|
148
|
+
name: discovered.name,
|
|
149
|
+
description: discovered.description || `Imported skill from ${discovered.origin} skill file.`,
|
|
150
|
+
procedure: discovered.body,
|
|
151
|
+
triggers: frontmatterList(discovered, 'triggers'),
|
|
152
|
+
tags: frontmatterList(discovered, 'tags'),
|
|
153
|
+
enabled: parsed.flags.get('enabled') === 'true',
|
|
154
|
+
source: 'imported',
|
|
155
|
+
provenance: `discovered:${discovered.origin}:${discovered.path}`,
|
|
156
|
+
});
|
|
157
|
+
ctx.print(`Imported Agent skill ${skill.id}: ${skill.name}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
76
160
|
function renderBundleList(title: string, registry: AgentSkillRegistry, bundles: readonly AgentSkillBundleRecord[]): string {
|
|
77
161
|
const snapshot = registry.snapshot();
|
|
78
162
|
if (bundles.length === 0) {
|
|
@@ -248,6 +332,14 @@ export async function runAgentSkillsRuntimeCommand(args: readonly string[], ctx:
|
|
|
248
332
|
runBundleCommand(args.slice(1), ctx, skillRegistry);
|
|
249
333
|
return;
|
|
250
334
|
}
|
|
335
|
+
if (sub === 'discover' || sub === 'discovered') {
|
|
336
|
+
ctx.print(renderDiscoveredSkills(await discoverSkills(requireShellPaths(ctx))));
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
if (sub === 'import-discovered' || sub === 'import-skill') {
|
|
340
|
+
await importDiscoveredSkill(args.slice(1), ctx, skillRegistry);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
251
343
|
if (sub === 'list' || sub === 'open') {
|
|
252
344
|
ctx.print(renderList('Agent Skills', skillRegistry, skillRegistry.list()));
|
|
253
345
|
return;
|
|
@@ -351,7 +443,7 @@ export async function runAgentSkillsRuntimeCommand(args: readonly string[], ctx:
|
|
|
351
443
|
ctx.print(`Deleted Agent skill ${removed.id}: ${removed.name}`);
|
|
352
444
|
return;
|
|
353
445
|
}
|
|
354
|
-
ctx.print('Usage: /agent-skills [list|enabled|search|show|create|update|enable|disable|review|stale|delete|bundle]');
|
|
446
|
+
ctx.print('Usage: /agent-skills [list|enabled|discover|import-discovered|search|show|create|update|enable|disable|review|stale|delete|bundle]');
|
|
355
447
|
} catch (error) {
|
|
356
448
|
printError(ctx, error);
|
|
357
449
|
}
|
|
@@ -362,7 +454,7 @@ export function registerAgentSkillsRuntimeCommands(registry: CommandRegistry): v
|
|
|
362
454
|
name: 'agent-skills',
|
|
363
455
|
aliases: ['askills', 'local-skills', 'skills', 'skill'],
|
|
364
456
|
description: 'Manage local GoodVibes Agent skills',
|
|
365
|
-
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 ...]',
|
|
457
|
+
usage: '[list|enabled|discover|import-discovered <name> --yes|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 ...]',
|
|
366
458
|
handler: runAgentSkillsRuntimeCommand,
|
|
367
459
|
});
|
|
368
460
|
}
|