@pellux/goodvibes-agent 0.1.116 → 1.0.0
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 +48 -661
- package/README.md +64 -57
- package/bin/goodvibes-agent.ts +16 -2
- package/dist/package/main.js +199180 -192185
- package/docs/README.md +15 -8
- package/docs/channels-remote-and-api.md +50 -0
- package/docs/connected-host.md +59 -0
- package/docs/getting-started.md +61 -50
- package/docs/knowledge-artifacts-and-multimodal.md +91 -0
- package/docs/project-planning.md +79 -0
- package/docs/providers-and-routing.md +46 -0
- package/docs/release-and-publishing.md +48 -11
- package/docs/tools-and-commands.md +123 -0
- package/docs/voice-and-live-tts.md +51 -0
- package/package.json +3 -6
- package/src/agent/behavior-discovery-summary.ts +1 -3
- package/src/agent/channel-delivery.ts +201 -0
- package/src/agent/media-generation.ts +159 -0
- package/src/agent/memory-prompt.ts +0 -1
- package/src/agent/note-registry.ts +329 -0
- package/src/agent/operator-actions.ts +343 -0
- package/src/agent/persona-discovery.ts +1 -3
- package/src/agent/persona-registry.ts +19 -16
- package/src/agent/record-labels.ts +107 -0
- package/src/agent/reminder-schedule-format.ts +33 -24
- package/src/agent/reminder-schedule.ts +27 -26
- package/src/agent/routine-registry.ts +54 -14
- package/src/agent/routine-schedule-args.ts +2 -1
- package/src/agent/routine-schedule-format.ts +77 -53
- package/src/agent/routine-schedule-promotion.ts +35 -33
- package/src/agent/routine-schedule-receipts.ts +29 -27
- package/src/agent/runtime-profile-starters.ts +5 -5
- package/src/agent/runtime-profile.ts +91 -12
- package/src/agent/skill-registry.ts +210 -32
- package/src/cli/agent-knowledge-args.ts +5 -1
- package/src/cli/agent-knowledge-command.ts +40 -34
- package/src/cli/agent-knowledge-format.ts +80 -67
- package/src/cli/agent-knowledge-methods.ts +1 -1
- package/src/cli/agent-knowledge-runtime.ts +32 -26
- package/src/cli/bundle-command.ts +14 -9
- package/src/cli/config-overrides.ts +37 -36
- package/src/cli/entrypoint.ts +16 -7
- package/src/cli/external-runtime.ts +10 -4
- package/src/cli/help.ts +54 -29
- package/src/cli/local-library-command.ts +203 -83
- package/src/cli/management-commands.ts +105 -68
- package/src/cli/management.ts +55 -74
- package/src/cli/memory-command.ts +66 -32
- package/src/cli/parser.ts +37 -3
- package/src/cli/profiles-command.ts +166 -31
- package/src/cli/provider-auth-routes.ts +2 -1
- package/src/cli/routines-command.ts +176 -40
- package/src/cli/service-posture.ts +16 -16
- package/src/cli/status.ts +56 -135
- package/src/core/conversation-message-snapshot.ts +131 -0
- 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 +35 -8
- package/src/input/agent-workspace-basic-command-editor-submission.ts +351 -94
- package/src/input/agent-workspace-basic-command-editors.ts +502 -9
- package/src/input/agent-workspace-categories.ts +423 -51
- package/src/input/agent-workspace-channel-command-editor-submission.ts +83 -0
- package/src/input/agent-workspace-channel-command-editors.ts +65 -0
- package/src/input/agent-workspace-channels.ts +35 -2
- package/src/input/agent-workspace-command-editor.ts +41 -3
- package/src/input/agent-workspace-config-reader.ts +16 -0
- package/src/input/agent-workspace-delegation-editor-submission.ts +55 -0
- package/src/input/agent-workspace-editors.ts +166 -1
- 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-knowledge-query-editor.ts +1 -1
- 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-local-operations.ts +218 -0
- package/src/input/agent-workspace-local-selection.ts +75 -0
- package/src/input/agent-workspace-mcp-command-editor-submission.ts +95 -0
- package/src/input/agent-workspace-media-command-editor-submission.ts +62 -0
- package/src/input/agent-workspace-media-command-editors.ts +27 -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-memory-editor.ts +2 -2
- package/src/input/agent-workspace-navigation.ts +81 -0
- package/src/input/agent-workspace-notify-editor-submission.ts +121 -0
- package/src/input/agent-workspace-operations-command-editor-submission.ts +208 -0
- package/src/input/agent-workspace-operations-command-editors.ts +196 -0
- package/src/input/agent-workspace-panel-route.ts +43 -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-reminder-schedule-editor.ts +1 -1
- package/src/input/agent-workspace-requirements.ts +11 -0
- package/src/input/agent-workspace-routine-schedule-editor.ts +2 -2
- package/src/input/agent-workspace-search.ts +169 -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 +37 -16
- 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 +162 -21
- 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-token.ts +18 -2
- package/src/input/agent-workspace-types.ts +212 -4
- package/src/input/agent-workspace-voice-media.ts +4 -7
- package/src/input/agent-workspace-web-research-editor.ts +104 -0
- package/src/input/agent-workspace-workplan-editor-submission.ts +153 -0
- package/src/input/agent-workspace.ts +178 -240
- package/src/input/command-registry.ts +5 -2
- package/src/input/commands/agent-runtime-profile-runtime.ts +126 -37
- package/src/input/commands/agent-skills-runtime.ts +149 -72
- package/src/input/commands/agent-workspace-runtime.ts +23 -6
- package/src/input/commands/brief-runtime.ts +55 -25
- package/src/input/commands/channels-runtime.ts +147 -33
- package/src/input/commands/compat-runtime.ts +32 -0
- package/src/input/commands/delegation-runtime.ts +29 -23
- package/src/input/commands/experience-runtime.ts +6 -4
- package/src/input/commands/guidance-runtime.ts +4 -4
- package/src/input/commands/health-runtime.ts +149 -126
- package/src/input/commands/knowledge.ts +82 -60
- package/src/input/commands/local-provider-runtime.ts +43 -21
- package/src/input/commands/local-runtime.ts +138 -16
- package/src/input/commands/local-setup-review.ts +10 -20
- package/src/input/commands/mcp-runtime.ts +63 -46
- package/src/input/commands/notify-runtime.ts +38 -9
- package/src/input/commands/operator-actions-runtime.ts +138 -0
- package/src/input/commands/operator-runtime.ts +6 -76
- package/src/input/commands/personas-runtime.ts +46 -31
- package/src/input/commands/planning-runtime.ts +1 -1
- package/src/input/commands/platform-access-runtime.ts +35 -98
- package/src/input/commands/product-runtime.ts +2 -2
- package/src/input/commands/provider-accounts-runtime.ts +60 -39
- package/src/input/commands/qrcode-runtime.ts +50 -9
- package/src/input/commands/recall-bundle.ts +11 -11
- package/src/input/commands/recall-capture.ts +13 -11
- package/src/input/commands/recall-query.ts +29 -21
- package/src/input/commands/recall-review.ts +2 -1
- package/src/input/commands/routines-runtime.ts +99 -45
- package/src/input/commands/runtime-services.ts +1 -14
- package/src/input/commands/schedule-runtime.ts +33 -20
- package/src/input/commands/security-runtime.ts +6 -11
- package/src/input/commands/session-content.ts +80 -78
- package/src/input/commands/session-workflow.ts +134 -104
- package/src/input/commands/session.ts +3 -174
- package/src/input/commands/shell-core.ts +38 -17
- package/src/input/commands/subscription-runtime.ts +54 -185
- package/src/input/commands/support-bundle-runtime.ts +53 -0
- package/src/input/commands/tasks-runtime.ts +21 -21
- package/src/input/commands/work-plan-runtime.ts +33 -8
- package/src/input/commands.ts +6 -2
- package/src/input/feed-context-factory.ts +2 -2
- package/src/input/file-picker.ts +3 -2
- package/src/input/handler-command-route.ts +4 -7
- package/src/input/handler-content-actions.ts +89 -26
- package/src/input/handler-feed-routes.ts +19 -12
- package/src/input/handler-feed.ts +6 -5
- package/src/input/handler-interactions.ts +9 -36
- package/src/input/handler-modal-stack.ts +3 -3
- package/src/input/handler-onboarding.ts +52 -91
- package/src/input/handler-shortcuts.ts +16 -7
- package/src/input/handler.ts +50 -23
- package/src/input/input-history.ts +5 -6
- package/src/input/keybindings.ts +27 -16
- 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 +62 -0
- package/src/input/onboarding/onboarding-wizard-constants.ts +1 -0
- package/src/input/onboarding/onboarding-wizard-helpers.ts +1 -1
- package/src/input/onboarding/onboarding-wizard-operator-steps.ts +223 -55
- package/src/input/onboarding/onboarding-wizard-steps.ts +59 -35
- package/src/input/onboarding/onboarding-wizard-types.ts +4 -1
- package/src/input/onboarding/onboarding-wizard.ts +87 -2
- package/src/input/profile-picker-modal.ts +31 -12
- package/src/input/session-picker-modal.ts +21 -4
- package/src/input/settings-modal-agent-policy.ts +6 -6
- package/src/input/settings-modal-behavior.ts +0 -3
- package/src/input/settings-modal-subscriptions.ts +3 -3
- package/src/input/settings-modal-types.ts +2 -13
- package/src/input/settings-modal.ts +20 -66
- package/src/input/submission-intent.ts +0 -1
- package/src/input/submission-router.ts +3 -3
- package/src/main.ts +18 -10
- package/src/panels/approval-panel.ts +8 -8
- package/src/panels/automation-control-panel.ts +4 -4
- package/src/panels/base-panel.ts +1 -1
- package/src/panels/builtin/agent.ts +1 -1
- package/src/panels/builtin/operations.ts +3 -12
- package/src/panels/builtin/session.ts +32 -24
- package/src/panels/builtin/shared.ts +7 -7
- package/src/panels/cost-tracker-panel.ts +1 -1
- package/src/panels/docs-panel.ts +5 -3
- package/src/panels/index.ts +0 -1
- package/src/panels/knowledge-panel.ts +6 -5
- package/src/panels/memory-panel.ts +7 -6
- package/src/panels/panel-list-panel.ts +36 -80
- package/src/panels/project-planning-panel.ts +19 -12
- package/src/panels/provider-account-snapshot.ts +51 -25
- package/src/panels/provider-accounts-panel.ts +33 -18
- package/src/panels/provider-health-domains.ts +48 -7
- package/src/panels/provider-health-panel.ts +5 -4
- package/src/panels/qr-panel.ts +44 -20
- package/src/panels/schedule-panel.ts +9 -17
- package/src/panels/security-panel.ts +8 -8
- package/src/panels/session-browser-panel.ts +10 -10
- package/src/panels/subscription-panel.ts +6 -6
- package/src/panels/system-messages-panel.ts +3 -3
- package/src/panels/tasks-panel.ts +21 -14
- package/src/panels/tool-inspector-panel.ts +19 -12
- package/src/panels/work-plan-panel.ts +5 -5
- package/src/planning/project-planning-coordinator.ts +3 -3
- package/src/provider-auth-route-display.ts +9 -0
- package/src/renderer/agent-workspace-style.ts +34 -0
- package/src/renderer/agent-workspace.ts +254 -57
- package/src/renderer/autocomplete-overlay.ts +25 -6
- package/src/renderer/block-actions.ts +1 -3
- package/src/renderer/bookmark-modal.ts +19 -4
- package/src/renderer/buffer.ts +4 -2
- package/src/renderer/context-inspector.ts +50 -13
- package/src/renderer/diff.ts +1 -1
- package/src/renderer/file-picker-overlay.ts +19 -6
- package/src/renderer/help-overlay.ts +112 -34
- package/src/renderer/history-search-overlay.ts +19 -4
- package/src/renderer/live-tail-modal.ts +27 -5
- package/src/renderer/mcp-workspace.ts +176 -43
- package/src/renderer/model-picker-overlay.ts +58 -3
- package/src/renderer/model-workspace.ts +104 -22
- package/src/renderer/onboarding/onboarding-wizard.ts +20 -4
- package/src/renderer/process-modal.ts +27 -6
- package/src/renderer/profile-picker-modal.ts +21 -5
- package/src/renderer/search-overlay.ts +25 -5
- package/src/renderer/selection-modal-overlay.ts +46 -14
- package/src/renderer/session-picker-modal.ts +18 -1
- package/src/renderer/settings-modal-helpers.ts +2 -40
- package/src/renderer/settings-modal.ts +88 -55
- package/src/renderer/system-message.ts +1 -1
- package/src/renderer/tool-call.ts +20 -11
- package/src/runtime/agent-runtime-events.ts +129 -0
- package/src/runtime/bootstrap-command-context.ts +7 -1
- package/src/runtime/bootstrap-command-parts.ts +11 -8
- package/src/runtime/bootstrap-core.ts +23 -65
- package/src/runtime/bootstrap-hook-bridge.ts +7 -18
- package/src/runtime/bootstrap-shell.ts +46 -8
- package/src/runtime/bootstrap.ts +36 -46
- package/src/runtime/connected-host-auth.ts +47 -0
- package/src/runtime/diagnostics/panels/index.ts +2 -2
- package/src/runtime/diagnostics/panels/panel-resources.ts +1 -1
- package/src/runtime/diagnostics/panels/policy.ts +7 -7
- package/src/runtime/index.ts +2 -5
- package/src/runtime/onboarding/apply-file-helpers.ts +66 -0
- package/src/runtime/onboarding/apply.ts +288 -74
- package/src/runtime/onboarding/derivation.ts +10 -13
- package/src/runtime/onboarding/snapshot.ts +2 -17
- package/src/runtime/onboarding/types.ts +34 -20
- package/src/runtime/onboarding/verify.ts +105 -4
- package/src/runtime/operator-token-cleanup.ts +3 -3
- package/src/runtime/services.ts +95 -11
- package/src/runtime/store/selectors/index.ts +3 -3
- package/src/runtime/store/state.ts +1 -4
- package/src/runtime/surface-feature-flags.ts +41 -6
- package/src/runtime/ui-read-models.ts +3 -4
- package/src/runtime/ui-services.ts +6 -6
- package/src/shell/blocking-input.ts +2 -1
- package/src/shell/service-settings-sync.ts +7 -7
- package/src/shell/ui-openers.ts +17 -38
- package/src/tools/agent-analysis-registry-policy.ts +0 -1
- package/src/tools/agent-channel-send-tool.ts +133 -0
- package/src/tools/agent-context-policy.ts +1 -1
- package/src/tools/agent-knowledge-ingest-tool.ts +405 -0
- package/src/tools/agent-knowledge-tool.ts +170 -0
- package/src/tools/agent-local-registry-tool.ts +279 -73
- package/src/tools/agent-media-generate-tool.ts +133 -0
- package/src/tools/agent-notify-tool.ts +143 -0
- package/src/tools/agent-operator-action-tool.ts +137 -0
- package/src/tools/agent-operator-briefing-tool.ts +217 -0
- package/src/tools/agent-reminder-schedule-tool.ts +237 -0
- package/src/tools/agent-tool-policy-guard.ts +13 -15
- package/src/tools/agent-work-plan-tool.ts +256 -0
- package/src/version.ts +1 -1
- package/docs/connected-services.md +0 -51
- package/src/cli/package-verification.ts +0 -274
- package/src/input/commands/policy-dispatch.ts +0 -339
- package/src/input/commands/policy.ts +0 -17
- package/src/panels/panel-picker.ts +0 -105
- package/src/panels/policy-panel.ts +0 -308
- package/src/renderer/panel-picker-overlay.ts +0 -202
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import type { OperatorMethodInput } from '@pellux/goodvibes-sdk/contracts';
|
|
2
|
+
|
|
3
|
+
export type JsonRecord = Record<string, unknown>;
|
|
4
|
+
|
|
5
|
+
export type ApprovalActionId = 'approvals.approve' | 'approvals.deny' | 'approvals.cancel';
|
|
6
|
+
export type AutomationActionId =
|
|
7
|
+
| 'automation.jobs.run'
|
|
8
|
+
| 'automation.jobs.pause'
|
|
9
|
+
| 'automation.jobs.resume'
|
|
10
|
+
| 'automation.runs.cancel'
|
|
11
|
+
| 'automation.runs.retry'
|
|
12
|
+
| 'schedules.run';
|
|
13
|
+
export type OperatorActionId = ApprovalActionId | AutomationActionId;
|
|
14
|
+
|
|
15
|
+
type ApprovalActionInput =
|
|
16
|
+
| OperatorMethodInput<'approvals.approve'>
|
|
17
|
+
| OperatorMethodInput<'approvals.deny'>
|
|
18
|
+
| OperatorMethodInput<'approvals.cancel'>;
|
|
19
|
+
|
|
20
|
+
export interface OperatorActionArgs {
|
|
21
|
+
readonly action?: unknown;
|
|
22
|
+
readonly targetId?: unknown;
|
|
23
|
+
readonly approvalId?: unknown;
|
|
24
|
+
readonly jobId?: unknown;
|
|
25
|
+
readonly runId?: unknown;
|
|
26
|
+
readonly scheduleId?: unknown;
|
|
27
|
+
readonly note?: unknown;
|
|
28
|
+
readonly remember?: unknown;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface OperatorActionDescriptor {
|
|
32
|
+
readonly action: OperatorActionId;
|
|
33
|
+
readonly label: string;
|
|
34
|
+
readonly pathTemplate: string;
|
|
35
|
+
readonly targetField: 'approvalId' | 'jobId' | 'runId' | 'scheduleId';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface OperatorActionRequest {
|
|
39
|
+
readonly ok: true;
|
|
40
|
+
readonly descriptor: OperatorActionDescriptor;
|
|
41
|
+
readonly targetId: string;
|
|
42
|
+
readonly body: JsonRecord | null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface OperatorActionSuccess {
|
|
46
|
+
readonly ok: true;
|
|
47
|
+
readonly methodId: OperatorActionId;
|
|
48
|
+
readonly path: string;
|
|
49
|
+
readonly body: unknown;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface OperatorActionFailure {
|
|
53
|
+
readonly ok: false;
|
|
54
|
+
readonly kind:
|
|
55
|
+
| 'auth_required'
|
|
56
|
+
| 'connected_host_unavailable'
|
|
57
|
+
| 'connected_host_route_unavailable'
|
|
58
|
+
| 'connected_host_error';
|
|
59
|
+
readonly error: string;
|
|
60
|
+
readonly methodId: OperatorActionId;
|
|
61
|
+
readonly path: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type OperatorActionResult = OperatorActionSuccess | OperatorActionFailure;
|
|
65
|
+
export type OperatorActionParseResult = OperatorActionRequest | { readonly ok: false; readonly error: string };
|
|
66
|
+
|
|
67
|
+
export interface OperatorActionConnection {
|
|
68
|
+
readonly baseUrl: string;
|
|
69
|
+
readonly token: string | null;
|
|
70
|
+
readonly tokenPath?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const OPERATOR_ACTIONS: Record<OperatorActionId, OperatorActionDescriptor> = {
|
|
74
|
+
'approvals.approve': {
|
|
75
|
+
action: 'approvals.approve',
|
|
76
|
+
label: 'approve approval',
|
|
77
|
+
pathTemplate: '/api/approvals/{approvalId}/approve',
|
|
78
|
+
targetField: 'approvalId',
|
|
79
|
+
},
|
|
80
|
+
'approvals.deny': {
|
|
81
|
+
action: 'approvals.deny',
|
|
82
|
+
label: 'deny approval',
|
|
83
|
+
pathTemplate: '/api/approvals/{approvalId}/deny',
|
|
84
|
+
targetField: 'approvalId',
|
|
85
|
+
},
|
|
86
|
+
'approvals.cancel': {
|
|
87
|
+
action: 'approvals.cancel',
|
|
88
|
+
label: 'cancel approval',
|
|
89
|
+
pathTemplate: '/api/approvals/{approvalId}/cancel',
|
|
90
|
+
targetField: 'approvalId',
|
|
91
|
+
},
|
|
92
|
+
'automation.jobs.run': {
|
|
93
|
+
action: 'automation.jobs.run',
|
|
94
|
+
label: 'run automation job',
|
|
95
|
+
pathTemplate: '/api/automation/jobs/{jobId}/run',
|
|
96
|
+
targetField: 'jobId',
|
|
97
|
+
},
|
|
98
|
+
'automation.jobs.pause': {
|
|
99
|
+
action: 'automation.jobs.pause',
|
|
100
|
+
label: 'pause automation job',
|
|
101
|
+
pathTemplate: '/api/automation/jobs/{jobId}/pause',
|
|
102
|
+
targetField: 'jobId',
|
|
103
|
+
},
|
|
104
|
+
'automation.jobs.resume': {
|
|
105
|
+
action: 'automation.jobs.resume',
|
|
106
|
+
label: 'resume automation job',
|
|
107
|
+
pathTemplate: '/api/automation/jobs/{jobId}/resume',
|
|
108
|
+
targetField: 'jobId',
|
|
109
|
+
},
|
|
110
|
+
'automation.runs.cancel': {
|
|
111
|
+
action: 'automation.runs.cancel',
|
|
112
|
+
label: 'cancel automation run',
|
|
113
|
+
pathTemplate: '/api/automation/runs/{runId}/cancel',
|
|
114
|
+
targetField: 'runId',
|
|
115
|
+
},
|
|
116
|
+
'automation.runs.retry': {
|
|
117
|
+
action: 'automation.runs.retry',
|
|
118
|
+
label: 'retry automation run',
|
|
119
|
+
pathTemplate: '/api/automation/runs/{runId}/retry',
|
|
120
|
+
targetField: 'runId',
|
|
121
|
+
},
|
|
122
|
+
'schedules.run': {
|
|
123
|
+
action: 'schedules.run',
|
|
124
|
+
label: 'run schedule',
|
|
125
|
+
pathTemplate: '/api/automation/schedules/{scheduleId}/run',
|
|
126
|
+
targetField: 'scheduleId',
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export const OPERATOR_ACTION_IDS = Object.keys(OPERATOR_ACTIONS) as readonly OperatorActionId[];
|
|
131
|
+
|
|
132
|
+
export function isRecord(value: unknown): value is JsonRecord {
|
|
133
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function readOperatorActionString(value: unknown): string {
|
|
137
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function readOperatorActionBoolean(value: unknown): boolean {
|
|
141
|
+
return value === true || value === 'true' || value === 'yes';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function isOperatorActionId(value: unknown): value is OperatorActionId {
|
|
145
|
+
return typeof value === 'string' && value in OPERATOR_ACTIONS;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function targetValue(args: OperatorActionArgs, descriptor: OperatorActionDescriptor): string {
|
|
149
|
+
const specific = args[descriptor.targetField];
|
|
150
|
+
return readOperatorActionString(specific) || readOperatorActionString(args.targetId);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function renderOperatorActionPath(descriptor: OperatorActionDescriptor, targetId: string): string {
|
|
154
|
+
return descriptor.pathTemplate.replace(`{${descriptor.targetField}}`, encodeURIComponent(targetId));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function approvalBody(args: OperatorActionArgs): ApprovalActionInput {
|
|
158
|
+
const note = readOperatorActionString(args.note);
|
|
159
|
+
const body: ApprovalActionInput = {
|
|
160
|
+
approvalId: targetValue(args, OPERATOR_ACTIONS['approvals.approve']),
|
|
161
|
+
};
|
|
162
|
+
if (note) body.note = note;
|
|
163
|
+
if (args.remember !== undefined) body.remember = readOperatorActionBoolean(args.remember);
|
|
164
|
+
return body;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function requestBody(args: OperatorActionArgs, action: OperatorActionId): JsonRecord | null {
|
|
168
|
+
if (action !== 'approvals.approve' && action !== 'approvals.deny' && action !== 'approvals.cancel') return null;
|
|
169
|
+
const body = approvalBody(args);
|
|
170
|
+
const out: JsonRecord = {};
|
|
171
|
+
if (body.note) out.note = body.note;
|
|
172
|
+
if (body.remember !== undefined) out.remember = body.remember;
|
|
173
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function buildOperatorActionRequest(rawArgs: unknown): OperatorActionParseResult {
|
|
177
|
+
if (!isRecord(rawArgs)) return { ok: false, error: 'Operator action arguments must be an object.' };
|
|
178
|
+
const args = rawArgs as OperatorActionArgs;
|
|
179
|
+
if (!isOperatorActionId(args.action)) {
|
|
180
|
+
return {
|
|
181
|
+
ok: false,
|
|
182
|
+
error: `action must be one of: ${OPERATOR_ACTION_IDS.join(', ')}`,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
const descriptor = OPERATOR_ACTIONS[args.action];
|
|
186
|
+
const targetId = targetValue(args, descriptor);
|
|
187
|
+
if (!targetId) {
|
|
188
|
+
return {
|
|
189
|
+
ok: false,
|
|
190
|
+
error: `${descriptor.targetField} or targetId is required for ${descriptor.action}.`,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
ok: true,
|
|
195
|
+
descriptor,
|
|
196
|
+
targetId,
|
|
197
|
+
body: requestBody(args, descriptor.action),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function readResponseBody(response: Response): Promise<unknown> {
|
|
202
|
+
const text = await response.text();
|
|
203
|
+
if (!text.trim()) return {};
|
|
204
|
+
try {
|
|
205
|
+
return JSON.parse(text) as unknown;
|
|
206
|
+
} catch {
|
|
207
|
+
return text;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function summarizeError(error: unknown): string {
|
|
212
|
+
return error instanceof Error ? error.message : String(error);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function classifyHttpFailure(
|
|
216
|
+
methodId: OperatorActionId,
|
|
217
|
+
path: string,
|
|
218
|
+
status: number,
|
|
219
|
+
body: unknown,
|
|
220
|
+
): OperatorActionFailure {
|
|
221
|
+
const detail = isRecord(body) && typeof body.error === 'string' ? body.error : '';
|
|
222
|
+
return {
|
|
223
|
+
ok: false,
|
|
224
|
+
methodId,
|
|
225
|
+
path,
|
|
226
|
+
kind: status === 401 || status === 403
|
|
227
|
+
? 'auth_required'
|
|
228
|
+
: status === 404
|
|
229
|
+
? 'connected_host_route_unavailable'
|
|
230
|
+
: 'connected_host_error',
|
|
231
|
+
error: `HTTP ${status}${detail ? `: ${detail}` : ''}`,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export async function postOperatorAction(
|
|
236
|
+
connection: OperatorActionConnection,
|
|
237
|
+
request: OperatorActionRequest,
|
|
238
|
+
): Promise<OperatorActionResult> {
|
|
239
|
+
const path = renderOperatorActionPath(request.descriptor, request.targetId);
|
|
240
|
+
const token = connection.token;
|
|
241
|
+
if (!token) {
|
|
242
|
+
return {
|
|
243
|
+
ok: false,
|
|
244
|
+
methodId: request.descriptor.action,
|
|
245
|
+
path,
|
|
246
|
+
kind: 'auth_required',
|
|
247
|
+
error: connection.tokenPath ? `no connected-host operator token found at ${connection.tokenPath}` : 'no connected-host operator token found',
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
const headers: HeadersInit = {
|
|
251
|
+
authorization: `Bearer ${token}`,
|
|
252
|
+
};
|
|
253
|
+
const init: RequestInit = {
|
|
254
|
+
method: 'POST',
|
|
255
|
+
headers,
|
|
256
|
+
};
|
|
257
|
+
if (request.body) {
|
|
258
|
+
headers['content-type'] = 'application/json';
|
|
259
|
+
init.body = JSON.stringify(request.body);
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
const response = await fetch(`${connection.baseUrl}${path}`, init);
|
|
263
|
+
const body = await readResponseBody(response);
|
|
264
|
+
if (!response.ok) return classifyHttpFailure(request.descriptor.action, path, response.status, body);
|
|
265
|
+
return {
|
|
266
|
+
ok: true,
|
|
267
|
+
methodId: request.descriptor.action,
|
|
268
|
+
path,
|
|
269
|
+
body,
|
|
270
|
+
};
|
|
271
|
+
} catch (error) {
|
|
272
|
+
return {
|
|
273
|
+
ok: false,
|
|
274
|
+
methodId: request.descriptor.action,
|
|
275
|
+
path,
|
|
276
|
+
kind: 'connected_host_unavailable',
|
|
277
|
+
error: summarizeError(error),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function statusFromOperatorActionBody(body: unknown): string {
|
|
283
|
+
if (!isRecord(body)) return 'ok';
|
|
284
|
+
const approval = body.approval;
|
|
285
|
+
if (isRecord(approval) && typeof approval.status === 'string') return approval.status;
|
|
286
|
+
const run = body.run;
|
|
287
|
+
if (isRecord(run) && typeof run.status === 'string') return run.status;
|
|
288
|
+
if (typeof body.status === 'string') return body.status;
|
|
289
|
+
if (typeof body.enabled === 'boolean') return body.enabled ? 'enabled' : 'disabled';
|
|
290
|
+
return 'ok';
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function formatOperatorActionFailureKind(kind: OperatorActionFailure['kind']): string {
|
|
294
|
+
if (kind === 'auth_required') return 'authorization required';
|
|
295
|
+
if (kind === 'connected_host_unavailable') return 'connected host unavailable';
|
|
296
|
+
if (kind === 'connected_host_route_unavailable') return 'connected host route unavailable';
|
|
297
|
+
return 'connected host error';
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function formatOperatorActionSuccess(baseUrl: string, result: OperatorActionSuccess): string {
|
|
301
|
+
return [
|
|
302
|
+
'Agent operator action completed',
|
|
303
|
+
` method: ${result.methodId}`,
|
|
304
|
+
` route: POST ${result.path}`,
|
|
305
|
+
` connected host: ${baseUrl}`,
|
|
306
|
+
` status: ${statusFromOperatorActionBody(result.body)}`,
|
|
307
|
+
].join('\n');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export function formatOperatorActionFailure(result: OperatorActionFailure): string {
|
|
311
|
+
return [
|
|
312
|
+
`Agent operator action failed: ${result.kind} (${formatOperatorActionFailureKind(result.kind)})`,
|
|
313
|
+
` method: ${result.methodId}`,
|
|
314
|
+
` route: POST ${result.path}`,
|
|
315
|
+
` error: ${result.error}`,
|
|
316
|
+
].join('\n');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export function formatOperatorActionPreview(request: OperatorActionRequest, explicitUserRequest: string): string {
|
|
320
|
+
return [
|
|
321
|
+
'Agent operator action preview',
|
|
322
|
+
` method ${request.descriptor.action}`,
|
|
323
|
+
` action ${request.descriptor.label}`,
|
|
324
|
+
` target ${request.targetId}`,
|
|
325
|
+
` route POST ${renderOperatorActionPath(request.descriptor, request.targetId)}`,
|
|
326
|
+
explicitUserRequest ? ` requested by ${explicitUserRequest}` : ' requested by (missing)',
|
|
327
|
+
'',
|
|
328
|
+
'Confirmation required: type yes in the workspace form or pass --yes only when the user explicitly asked GoodVibes Agent to perform this exact operator action.',
|
|
329
|
+
].join('\n');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export function formatOperatorActionToolPreview(request: OperatorActionRequest, explicitUserRequest: string): string {
|
|
333
|
+
return [
|
|
334
|
+
'Agent operator action preview',
|
|
335
|
+
` method ${request.descriptor.action}`,
|
|
336
|
+
` action ${request.descriptor.label}`,
|
|
337
|
+
` target ${request.targetId}`,
|
|
338
|
+
` route POST ${renderOperatorActionPath(request.descriptor, request.targetId)}`,
|
|
339
|
+
explicitUserRequest ? ` requested by ${explicitUserRequest}` : ' requested by (missing)',
|
|
340
|
+
'',
|
|
341
|
+
'Model tool confirmation required. Call this tool with confirm:true only when the user explicitly asked GoodVibes Agent to perform this exact operator action.',
|
|
342
|
+
].join('\n');
|
|
343
|
+
}
|
|
@@ -14,7 +14,7 @@ export interface DiscoveredPersonaRecord {
|
|
|
14
14
|
readonly frontmatter: Record<string, string>;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const DIRECTORY_MARKERS: readonly string[] = ['PERSONA.md', 'persona.md'
|
|
17
|
+
const DIRECTORY_MARKERS: readonly string[] = ['PERSONA.md', 'persona.md'];
|
|
18
18
|
|
|
19
19
|
function parseFrontmatter(content: string): Record<string, string> {
|
|
20
20
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
@@ -33,10 +33,8 @@ function getPersonaDirectories(cwd: string, homeDir: string): Array<{ root: stri
|
|
|
33
33
|
return [
|
|
34
34
|
{ root: join(cwd, '.goodvibes', 'personas'), origin: 'project-local' },
|
|
35
35
|
{ root: join(cwd, '.goodvibes', GOODVIBES_AGENT_SURFACE_ROOT, 'personas'), origin: 'project-local' },
|
|
36
|
-
{ root: join(cwd, '.goodvibes', 'agents'), origin: 'project-local' },
|
|
37
36
|
{ root: join(homeDir, '.goodvibes', 'personas'), origin: 'global' },
|
|
38
37
|
{ root: join(homeDir, '.goodvibes', GOODVIBES_AGENT_SURFACE_ROOT, 'personas'), origin: 'global' },
|
|
39
|
-
{ root: join(homeDir, '.goodvibes', 'agents'), origin: 'global' },
|
|
40
38
|
];
|
|
41
39
|
}
|
|
42
40
|
|
|
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from '
|
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
3
|
import type { ShellPathService } from '@/runtime/index.ts';
|
|
4
4
|
import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
|
|
5
|
+
import { formatAgentRecordReviewState } from './record-labels.ts';
|
|
5
6
|
|
|
6
7
|
export type AgentPersonaSource = 'user' | 'agent' | 'imported' | 'system';
|
|
7
8
|
export type AgentPersonaReviewState = 'fresh' | 'reviewed' | 'stale';
|
|
@@ -106,9 +107,9 @@ function containsSecretLikeText(text: string): boolean {
|
|
|
106
107
|
return SECRET_PATTERNS.some((pattern) => pattern.test(text));
|
|
107
108
|
}
|
|
108
109
|
|
|
109
|
-
export function assertNoSecretLikeText(fields: readonly string[]): void {
|
|
110
|
+
export function assertNoSecretLikeText(fields: readonly string[], ownerLabel = 'Agent local records'): void {
|
|
110
111
|
if (fields.some((field) => containsSecretLikeText(field))) {
|
|
111
|
-
throw new Error(
|
|
112
|
+
throw new Error(`${ownerLabel} cannot store secret-looking values. Store a secret reference or remove the sensitive text.`);
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
|
|
@@ -158,14 +159,16 @@ function formatStore(store: PersonaStoreFile): string {
|
|
|
158
159
|
return `${JSON.stringify(store, null, 2)}\n`;
|
|
159
160
|
}
|
|
160
161
|
|
|
161
|
-
|
|
162
|
+
type AgentLocalStorePaths = Pick<ShellPathService, 'resolveUserPath'>;
|
|
163
|
+
|
|
164
|
+
export function personaStorePath(shellPaths: AgentLocalStorePaths): string {
|
|
162
165
|
return shellPaths.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, 'personas', 'personas.json');
|
|
163
166
|
}
|
|
164
167
|
|
|
165
168
|
export class AgentPersonaRegistry {
|
|
166
169
|
public constructor(private readonly storePath: string) {}
|
|
167
170
|
|
|
168
|
-
public static fromShellPaths(shellPaths:
|
|
171
|
+
public static fromShellPaths(shellPaths: AgentLocalStorePaths): AgentPersonaRegistry {
|
|
169
172
|
return new AgentPersonaRegistry(personaStorePath(shellPaths));
|
|
170
173
|
}
|
|
171
174
|
|
|
@@ -211,9 +214,9 @@ export class AgentPersonaRegistry {
|
|
|
211
214
|
const description = input.description.trim();
|
|
212
215
|
const body = input.body.trim();
|
|
213
216
|
this.validateRequired(name, description, body);
|
|
214
|
-
assertNoSecretLikeText([name, description, body, ...(input.tags ?? []), ...(input.triggers ?? [])]);
|
|
217
|
+
assertNoSecretLikeText([name, description, body, ...(input.tags ?? []), ...(input.triggers ?? [])], 'Personas');
|
|
215
218
|
const duplicate = store.personas.find((persona) => persona.name.toLowerCase() === name.toLowerCase());
|
|
216
|
-
if (duplicate) throw new Error(`Persona already exists
|
|
219
|
+
if (duplicate) throw new Error(`Persona already exists ${duplicate.id}`);
|
|
217
220
|
const timestamp = nowIso();
|
|
218
221
|
const persona: AgentPersonaRecord = {
|
|
219
222
|
id: this.nextId(name, store.personas),
|
|
@@ -235,14 +238,14 @@ export class AgentPersonaRegistry {
|
|
|
235
238
|
public update(idOrName: string, input: AgentPersonaUpdateInput): AgentPersonaRecord {
|
|
236
239
|
const store = this.readStore();
|
|
237
240
|
const existing = this.findInStore(store, idOrName);
|
|
238
|
-
if (!existing) throw new Error(`Unknown persona
|
|
241
|
+
if (!existing) throw new Error(`Unknown persona ${idOrName}`);
|
|
239
242
|
const name = input.name === undefined ? existing.name : normalizeName(input.name);
|
|
240
243
|
const description = input.description === undefined ? existing.description : input.description.trim();
|
|
241
244
|
const body = input.body === undefined ? existing.body : input.body.trim();
|
|
242
245
|
this.validateRequired(name, description, body);
|
|
243
|
-
assertNoSecretLikeText([name, description, body, ...(input.tags ?? []), ...(input.triggers ?? [])]);
|
|
246
|
+
assertNoSecretLikeText([name, description, body, ...(input.tags ?? []), ...(input.triggers ?? [])], 'Personas');
|
|
244
247
|
const duplicate = store.personas.find((persona) => persona.id !== existing.id && persona.name.toLowerCase() === name.toLowerCase());
|
|
245
|
-
if (duplicate) throw new Error(`Persona already exists
|
|
248
|
+
if (duplicate) throw new Error(`Persona already exists ${duplicate.id}`);
|
|
246
249
|
const updated: AgentPersonaRecord = {
|
|
247
250
|
...existing,
|
|
248
251
|
name,
|
|
@@ -266,7 +269,7 @@ export class AgentPersonaRegistry {
|
|
|
266
269
|
public setActive(idOrName: string): AgentPersonaRecord {
|
|
267
270
|
const store = this.readStore();
|
|
268
271
|
const persona = this.findInStore(store, idOrName);
|
|
269
|
-
if (!persona) throw new Error(`Unknown persona
|
|
272
|
+
if (!persona) throw new Error(`Unknown persona ${idOrName}`);
|
|
270
273
|
this.writeStore({ ...store, activePersonaId: persona.id });
|
|
271
274
|
return persona;
|
|
272
275
|
}
|
|
@@ -279,7 +282,7 @@ export class AgentPersonaRegistry {
|
|
|
279
282
|
public markReviewed(idOrName: string): AgentPersonaRecord {
|
|
280
283
|
const store = this.readStore();
|
|
281
284
|
const existing = this.findInStore(store, idOrName);
|
|
282
|
-
if (!existing) throw new Error(`Unknown persona
|
|
285
|
+
if (!existing) throw new Error(`Unknown persona ${idOrName}`);
|
|
283
286
|
const updated: AgentPersonaRecord = {
|
|
284
287
|
...existing,
|
|
285
288
|
reviewState: 'reviewed',
|
|
@@ -297,7 +300,7 @@ export class AgentPersonaRegistry {
|
|
|
297
300
|
public markStale(idOrName: string, reason: string): AgentPersonaRecord {
|
|
298
301
|
const store = this.readStore();
|
|
299
302
|
const existing = this.findInStore(store, idOrName);
|
|
300
|
-
if (!existing) throw new Error(`Unknown persona
|
|
303
|
+
if (!existing) throw new Error(`Unknown persona ${idOrName}`);
|
|
301
304
|
const updated: AgentPersonaRecord = {
|
|
302
305
|
...existing,
|
|
303
306
|
reviewState: 'stale',
|
|
@@ -314,7 +317,7 @@ export class AgentPersonaRegistry {
|
|
|
314
317
|
public deletePersona(idOrName: string): AgentPersonaRecord {
|
|
315
318
|
const store = this.readStore();
|
|
316
319
|
const existing = this.findInStore(store, idOrName);
|
|
317
|
-
if (!existing) throw new Error(`Unknown persona
|
|
320
|
+
if (!existing) throw new Error(`Unknown persona ${idOrName}`);
|
|
318
321
|
this.writeStore({
|
|
319
322
|
...store,
|
|
320
323
|
activePersonaId: store.activePersonaId === existing.id ? null : store.activePersonaId,
|
|
@@ -351,7 +354,7 @@ export class AgentPersonaRegistry {
|
|
|
351
354
|
try {
|
|
352
355
|
return parseStore(readFileSync(this.storePath, 'utf-8'));
|
|
353
356
|
} catch (error) {
|
|
354
|
-
throw new Error(`Could not read Agent persona store
|
|
357
|
+
throw new Error(`Could not read Agent persona store ${error instanceof Error ? error.message : String(error)}`);
|
|
355
358
|
}
|
|
356
359
|
}
|
|
357
360
|
|
|
@@ -370,10 +373,10 @@ export function buildActivePersonaPrompt(shellPaths: ShellPathService): string |
|
|
|
370
373
|
'## Active GoodVibes Agent Persona',
|
|
371
374
|
`Name: ${active.name}`,
|
|
372
375
|
`Description: ${active.description}`,
|
|
373
|
-
`Review
|
|
376
|
+
`Review: ${formatAgentRecordReviewState(active.reviewState)}`,
|
|
374
377
|
'',
|
|
375
378
|
active.body,
|
|
376
379
|
'',
|
|
377
|
-
'Apply this persona inside the same serial assistant conversation. Do not spawn
|
|
380
|
+
'Apply this persona inside the same serial assistant conversation. Do not spawn separate Agent jobs because a persona is active.',
|
|
378
381
|
].join('\n');
|
|
379
382
|
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
const SOURCE_LABELS = {
|
|
2
|
+
user: 'User',
|
|
3
|
+
agent: 'Agent',
|
|
4
|
+
imported: 'Imported',
|
|
5
|
+
system: 'Built-in',
|
|
6
|
+
builtin: 'Built-in',
|
|
7
|
+
local: 'Local',
|
|
8
|
+
'agent-memory': 'Agent memory',
|
|
9
|
+
} as const satisfies Readonly<Record<string, string>>;
|
|
10
|
+
|
|
11
|
+
const USER_PROVENANCE_LABELS = new Set<string>([
|
|
12
|
+
'Workspace',
|
|
13
|
+
'Workspace capture',
|
|
14
|
+
'Command',
|
|
15
|
+
'Manual',
|
|
16
|
+
'Main conversation',
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
const REVIEW_STATE_LABELS = {
|
|
20
|
+
fresh: 'Needs review',
|
|
21
|
+
reviewed: 'Reviewed',
|
|
22
|
+
stale: 'Needs refresh',
|
|
23
|
+
contradicted: 'Contradicted',
|
|
24
|
+
} as const satisfies Readonly<Record<string, string>>;
|
|
25
|
+
|
|
26
|
+
type SourceLabelKey = keyof typeof SOURCE_LABELS;
|
|
27
|
+
type ReviewStateLabelKey = keyof typeof REVIEW_STATE_LABELS;
|
|
28
|
+
|
|
29
|
+
export interface AgentRecordReference {
|
|
30
|
+
readonly kind: string;
|
|
31
|
+
readonly ref: string;
|
|
32
|
+
readonly label?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function titleCaseWords(value: string): string {
|
|
36
|
+
return value
|
|
37
|
+
.trim()
|
|
38
|
+
.replace(/[_-]+/g, ' ')
|
|
39
|
+
.replace(/\s+/g, ' ')
|
|
40
|
+
.replace(/\b\w/g, (match) => match.toUpperCase());
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function formatAgentRecordSource(source: string | undefined): string {
|
|
44
|
+
const normalized = (source ?? '').trim().toLowerCase();
|
|
45
|
+
if (!normalized) return 'Unknown';
|
|
46
|
+
if (normalized in SOURCE_LABELS) return SOURCE_LABELS[normalized as SourceLabelKey];
|
|
47
|
+
return titleCaseWords(source ?? '');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function formatAgentRecordReviewState(reviewState: string | undefined): string {
|
|
51
|
+
const normalized = (reviewState ?? '').trim().toLowerCase();
|
|
52
|
+
if (!normalized) return REVIEW_STATE_LABELS.fresh;
|
|
53
|
+
if (normalized in REVIEW_STATE_LABELS) return REVIEW_STATE_LABELS[normalized as ReviewStateLabelKey];
|
|
54
|
+
return titleCaseWords(reviewState ?? '');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function formatAgentRecordProvenance(provenance: string | undefined): string {
|
|
58
|
+
const raw = (provenance ?? '').trim();
|
|
59
|
+
const normalized = raw.toLowerCase();
|
|
60
|
+
if (!raw) return '';
|
|
61
|
+
if (normalized === 'agent-workspace') return 'Workspace';
|
|
62
|
+
if (normalized === 'agent-workspace-learned-behavior') return 'Workspace capture';
|
|
63
|
+
if (normalized === 'workspace capture') return 'Workspace capture';
|
|
64
|
+
if (normalized === 'slash-command') return 'Command';
|
|
65
|
+
if (normalized === 'agent-memory') return 'Agent memory';
|
|
66
|
+
if (normalized === 'manual') return 'Manual';
|
|
67
|
+
if (normalized === 'main-conversation') return 'Main conversation';
|
|
68
|
+
if (normalized === 'plan-command') return 'Planning command';
|
|
69
|
+
if (normalized === 'agent-media-generation') return 'Media generation';
|
|
70
|
+
if (normalized === 'agent-channel-send') return 'Channel delivery';
|
|
71
|
+
if (normalized === 'project-memory') return 'Project memory';
|
|
72
|
+
|
|
73
|
+
const discovered = raw.match(/^discovered:([^:]+):(.+)$/i);
|
|
74
|
+
if (discovered) {
|
|
75
|
+
return `Imported file (${discovered[1]}): ${discovered[2]}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const starter = raw.match(/^goodvibes-agent starter:(.+)$/i);
|
|
79
|
+
if (starter) {
|
|
80
|
+
return `Starter profile: ${starter[1]}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return titleCaseWords(raw);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function formatAgentRecordOrigin(source: string | undefined, provenance: string | undefined): string {
|
|
87
|
+
const sourceLabel = formatAgentRecordSource(source);
|
|
88
|
+
const provenanceLabel = formatAgentRecordProvenance(provenance);
|
|
89
|
+
if (!provenanceLabel || provenanceLabel === sourceLabel) return sourceLabel;
|
|
90
|
+
if (sourceLabel === 'User' && USER_PROVENANCE_LABELS.has(provenanceLabel)) {
|
|
91
|
+
return provenanceLabel;
|
|
92
|
+
}
|
|
93
|
+
if (sourceLabel === 'Imported' && provenanceLabel.startsWith('Imported file')) {
|
|
94
|
+
return provenanceLabel;
|
|
95
|
+
}
|
|
96
|
+
return `${sourceLabel} (${provenanceLabel})`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function formatAgentRecordReference(reference: AgentRecordReference): string {
|
|
100
|
+
const kind = titleCaseWords(reference.kind);
|
|
101
|
+
const label = reference.label?.trim();
|
|
102
|
+
return `${kind}: ${reference.ref}${label ? ` (${label})` : ''}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function formatAgentRecordReferences(references: readonly AgentRecordReference[]): string {
|
|
106
|
+
return references.map(formatAgentRecordReference).join(', ');
|
|
107
|
+
}
|
|
@@ -22,20 +22,29 @@ function scheduleValue(preview: ReminderSchedulePreview): string {
|
|
|
22
22
|
return String(preview.payload.at);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
function formatReminderScheduleFailureKind(kind: string): string {
|
|
26
|
+
if (kind === 'auth_required') return 'authorization required';
|
|
27
|
+
if (kind === 'connected_host_unavailable') return 'connected host unavailable';
|
|
28
|
+
if (kind === 'connected_host_route_unavailable') return 'connected host route unavailable';
|
|
29
|
+
if (kind === 'connected_host_error') return 'connected host error';
|
|
30
|
+
if (kind === 'version_mismatch') return 'version mismatch';
|
|
31
|
+
return kind.replace(/[_-]+/g, ' ');
|
|
32
|
+
}
|
|
33
|
+
|
|
25
34
|
export function formatReminderSchedulePreview(preview: ReminderSchedulePreview): string {
|
|
26
35
|
const delivery = preview.payload.delivery;
|
|
27
36
|
const deliveryTargetCount = delivery?.targets.length ?? 0;
|
|
28
37
|
return [
|
|
29
38
|
'GoodVibes schedule preview for Agent reminder',
|
|
30
|
-
` route
|
|
31
|
-
` name
|
|
32
|
-
` reminder
|
|
33
|
-
` schedule
|
|
34
|
-
` enabled
|
|
35
|
-
` delivery
|
|
36
|
-
' target
|
|
37
|
-
' policy
|
|
38
|
-
' next
|
|
39
|
+
` route ${preview.method} ${preview.route}`,
|
|
40
|
+
` name ${String(preview.payload.name ?? '(service default)')}`,
|
|
41
|
+
` reminder ${preview.message}`,
|
|
42
|
+
` schedule ${preview.payload.kind} ${scheduleValue(preview)}`,
|
|
43
|
+
` enabled ${preview.payload.enabled === false ? 'no' : 'yes'}`,
|
|
44
|
+
` delivery ${delivery?.mode ?? 'none'}${deliveryTargetCount > 0 ? ` (${deliveryTargetCount} target${deliveryTargetCount === 1 ? '' : 's'})` : ''}`,
|
|
45
|
+
' target connected GoodVibes host/main conversation route',
|
|
46
|
+
' policy reminder delivery only; isolated Agent Knowledge only; no default knowledge/non-Agent fallback',
|
|
47
|
+
' next rerun with --yes to create this connected reminder schedule',
|
|
39
48
|
].join('\n');
|
|
40
49
|
}
|
|
41
50
|
|
|
@@ -45,31 +54,31 @@ export function formatReminderScheduleSuccess(result: ReminderScheduleSuccess):
|
|
|
45
54
|
const status = readString(record, 'status') ?? (record.enabled === false ? 'paused' : 'enabled');
|
|
46
55
|
return [
|
|
47
56
|
'Created GoodVibes schedule for Agent reminder',
|
|
48
|
-
` schedule
|
|
49
|
-
` status
|
|
50
|
-
` route
|
|
51
|
-
` reminder
|
|
52
|
-
' next
|
|
57
|
+
` schedule ${id}`,
|
|
58
|
+
` status ${status}`,
|
|
59
|
+
` route ${result.kind} ${result.route}`,
|
|
60
|
+
` reminder ${result.message}`,
|
|
61
|
+
' next inspect with /schedule list',
|
|
53
62
|
].join('\n');
|
|
54
63
|
}
|
|
55
64
|
|
|
56
65
|
export function formatReminderScheduleFailure(failure: ReminderScheduleFailure): string {
|
|
57
66
|
return [
|
|
58
|
-
`GoodVibes reminder schedule error
|
|
67
|
+
`GoodVibes reminder schedule error ${formatReminderScheduleFailureKind(failure.kind)}`,
|
|
59
68
|
` ${failure.error}`,
|
|
60
|
-
failure.baseUrl ? `
|
|
61
|
-
` route
|
|
62
|
-
failure.kind === 'version_mismatch' && failure.
|
|
63
|
-
? ` versions
|
|
69
|
+
failure.baseUrl ? ` connected host ${failure.baseUrl}` : null,
|
|
70
|
+
` route ${REMINDER_SCHEDULE_METHOD} ${failure.route}`,
|
|
71
|
+
failure.kind === 'version_mismatch' && failure.connectedHostVersion && failure.expectedSdkVersion
|
|
72
|
+
? ` versions connected host ${failure.connectedHostVersion}; expected ${failure.expectedSdkVersion}`
|
|
64
73
|
: null,
|
|
65
74
|
failure.kind === 'auth_required'
|
|
66
|
-
? ' next
|
|
75
|
+
? ' next pair/authenticate with the connected GoodVibes host, then retry with --yes.'
|
|
67
76
|
: null,
|
|
68
|
-
failure.kind === '
|
|
69
|
-
? ' next
|
|
77
|
+
failure.kind === 'connected_host_unavailable'
|
|
78
|
+
? ' next make the connected GoodVibes host available outside Agent, then retry.'
|
|
70
79
|
: null,
|
|
71
|
-
failure.kind === 'version_mismatch' || failure.kind === '
|
|
72
|
-
? ' next
|
|
80
|
+
failure.kind === 'version_mismatch' || failure.kind === 'connected_host_route_unavailable'
|
|
81
|
+
? ' next update the connected GoodVibes host so public schedules.create is available.'
|
|
73
82
|
: null,
|
|
74
83
|
].filter((line): line is string => Boolean(line)).join('\n');
|
|
75
84
|
}
|