@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.
Files changed (285) hide show
  1. package/CHANGELOG.md +48 -661
  2. package/README.md +64 -57
  3. package/bin/goodvibes-agent.ts +16 -2
  4. package/dist/package/main.js +199180 -192185
  5. package/docs/README.md +15 -8
  6. package/docs/channels-remote-and-api.md +50 -0
  7. package/docs/connected-host.md +59 -0
  8. package/docs/getting-started.md +61 -50
  9. package/docs/knowledge-artifacts-and-multimodal.md +91 -0
  10. package/docs/project-planning.md +79 -0
  11. package/docs/providers-and-routing.md +46 -0
  12. package/docs/release-and-publishing.md +48 -11
  13. package/docs/tools-and-commands.md +123 -0
  14. package/docs/voice-and-live-tts.md +51 -0
  15. package/package.json +3 -6
  16. package/src/agent/behavior-discovery-summary.ts +1 -3
  17. package/src/agent/channel-delivery.ts +201 -0
  18. package/src/agent/media-generation.ts +159 -0
  19. package/src/agent/memory-prompt.ts +0 -1
  20. package/src/agent/note-registry.ts +329 -0
  21. package/src/agent/operator-actions.ts +343 -0
  22. package/src/agent/persona-discovery.ts +1 -3
  23. package/src/agent/persona-registry.ts +19 -16
  24. package/src/agent/record-labels.ts +107 -0
  25. package/src/agent/reminder-schedule-format.ts +33 -24
  26. package/src/agent/reminder-schedule.ts +27 -26
  27. package/src/agent/routine-registry.ts +54 -14
  28. package/src/agent/routine-schedule-args.ts +2 -1
  29. package/src/agent/routine-schedule-format.ts +77 -53
  30. package/src/agent/routine-schedule-promotion.ts +35 -33
  31. package/src/agent/routine-schedule-receipts.ts +29 -27
  32. package/src/agent/runtime-profile-starters.ts +5 -5
  33. package/src/agent/runtime-profile.ts +91 -12
  34. package/src/agent/skill-registry.ts +210 -32
  35. package/src/cli/agent-knowledge-args.ts +5 -1
  36. package/src/cli/agent-knowledge-command.ts +40 -34
  37. package/src/cli/agent-knowledge-format.ts +80 -67
  38. package/src/cli/agent-knowledge-methods.ts +1 -1
  39. package/src/cli/agent-knowledge-runtime.ts +32 -26
  40. package/src/cli/bundle-command.ts +14 -9
  41. package/src/cli/config-overrides.ts +37 -36
  42. package/src/cli/entrypoint.ts +16 -7
  43. package/src/cli/external-runtime.ts +10 -4
  44. package/src/cli/help.ts +54 -29
  45. package/src/cli/local-library-command.ts +203 -83
  46. package/src/cli/management-commands.ts +105 -68
  47. package/src/cli/management.ts +55 -74
  48. package/src/cli/memory-command.ts +66 -32
  49. package/src/cli/parser.ts +37 -3
  50. package/src/cli/profiles-command.ts +166 -31
  51. package/src/cli/provider-auth-routes.ts +2 -1
  52. package/src/cli/routines-command.ts +176 -40
  53. package/src/cli/service-posture.ts +16 -16
  54. package/src/cli/status.ts +56 -135
  55. package/src/core/conversation-message-snapshot.ts +131 -0
  56. package/src/input/agent-workspace-access-command-editor-submission.ts +149 -0
  57. package/src/input/agent-workspace-access-command-editors.ts +170 -0
  58. package/src/input/agent-workspace-activation.ts +35 -8
  59. package/src/input/agent-workspace-basic-command-editor-submission.ts +351 -94
  60. package/src/input/agent-workspace-basic-command-editors.ts +502 -9
  61. package/src/input/agent-workspace-categories.ts +423 -51
  62. package/src/input/agent-workspace-channel-command-editor-submission.ts +83 -0
  63. package/src/input/agent-workspace-channel-command-editors.ts +65 -0
  64. package/src/input/agent-workspace-channels.ts +35 -2
  65. package/src/input/agent-workspace-command-editor.ts +41 -3
  66. package/src/input/agent-workspace-config-reader.ts +16 -0
  67. package/src/input/agent-workspace-delegation-editor-submission.ts +55 -0
  68. package/src/input/agent-workspace-editors.ts +166 -1
  69. package/src/input/agent-workspace-knowledge-command-editor-submission.ts +132 -0
  70. package/src/input/agent-workspace-knowledge-command-editors.ts +110 -0
  71. package/src/input/agent-workspace-knowledge-query-editor.ts +1 -1
  72. package/src/input/agent-workspace-learned-behavior.ts +67 -0
  73. package/src/input/agent-workspace-library-command-editor-submission.ts +50 -0
  74. package/src/input/agent-workspace-library-command-editors.ts +35 -0
  75. package/src/input/agent-workspace-local-operations.ts +218 -0
  76. package/src/input/agent-workspace-local-selection.ts +75 -0
  77. package/src/input/agent-workspace-mcp-command-editor-submission.ts +95 -0
  78. package/src/input/agent-workspace-media-command-editor-submission.ts +62 -0
  79. package/src/input/agent-workspace-media-command-editors.ts +27 -0
  80. package/src/input/agent-workspace-memory-command-editor-submission.ts +232 -0
  81. package/src/input/agent-workspace-memory-command-editors.ts +180 -0
  82. package/src/input/agent-workspace-memory-editor.ts +2 -2
  83. package/src/input/agent-workspace-navigation.ts +81 -0
  84. package/src/input/agent-workspace-notify-editor-submission.ts +121 -0
  85. package/src/input/agent-workspace-operations-command-editor-submission.ts +208 -0
  86. package/src/input/agent-workspace-operations-command-editors.ts +196 -0
  87. package/src/input/agent-workspace-panel-route.ts +43 -0
  88. package/src/input/agent-workspace-provider-command-editor-submission.ts +155 -0
  89. package/src/input/agent-workspace-provider-command-editors.ts +93 -0
  90. package/src/input/agent-workspace-reminder-schedule-editor.ts +1 -1
  91. package/src/input/agent-workspace-requirements.ts +11 -0
  92. package/src/input/agent-workspace-routine-schedule-editor.ts +2 -2
  93. package/src/input/agent-workspace-search.ts +169 -0
  94. package/src/input/agent-workspace-secret-editor-submission.ts +153 -0
  95. package/src/input/agent-workspace-session-command-editor-submission.ts +199 -0
  96. package/src/input/agent-workspace-session-command-editors.ts +249 -0
  97. package/src/input/agent-workspace-setup.ts +37 -16
  98. package/src/input/agent-workspace-skill-bundle-command-editor-submission.ts +101 -0
  99. package/src/input/agent-workspace-skill-bundle-command-editors.ts +110 -0
  100. package/src/input/agent-workspace-snapshot.ts +162 -21
  101. package/src/input/agent-workspace-task-command-editor-submission.ts +61 -0
  102. package/src/input/agent-workspace-task-command-editors.ts +47 -0
  103. package/src/input/agent-workspace-token.ts +18 -2
  104. package/src/input/agent-workspace-types.ts +212 -4
  105. package/src/input/agent-workspace-voice-media.ts +4 -7
  106. package/src/input/agent-workspace-web-research-editor.ts +104 -0
  107. package/src/input/agent-workspace-workplan-editor-submission.ts +153 -0
  108. package/src/input/agent-workspace.ts +178 -240
  109. package/src/input/command-registry.ts +5 -2
  110. package/src/input/commands/agent-runtime-profile-runtime.ts +126 -37
  111. package/src/input/commands/agent-skills-runtime.ts +149 -72
  112. package/src/input/commands/agent-workspace-runtime.ts +23 -6
  113. package/src/input/commands/brief-runtime.ts +55 -25
  114. package/src/input/commands/channels-runtime.ts +147 -33
  115. package/src/input/commands/compat-runtime.ts +32 -0
  116. package/src/input/commands/delegation-runtime.ts +29 -23
  117. package/src/input/commands/experience-runtime.ts +6 -4
  118. package/src/input/commands/guidance-runtime.ts +4 -4
  119. package/src/input/commands/health-runtime.ts +149 -126
  120. package/src/input/commands/knowledge.ts +82 -60
  121. package/src/input/commands/local-provider-runtime.ts +43 -21
  122. package/src/input/commands/local-runtime.ts +138 -16
  123. package/src/input/commands/local-setup-review.ts +10 -20
  124. package/src/input/commands/mcp-runtime.ts +63 -46
  125. package/src/input/commands/notify-runtime.ts +38 -9
  126. package/src/input/commands/operator-actions-runtime.ts +138 -0
  127. package/src/input/commands/operator-runtime.ts +6 -76
  128. package/src/input/commands/personas-runtime.ts +46 -31
  129. package/src/input/commands/planning-runtime.ts +1 -1
  130. package/src/input/commands/platform-access-runtime.ts +35 -98
  131. package/src/input/commands/product-runtime.ts +2 -2
  132. package/src/input/commands/provider-accounts-runtime.ts +60 -39
  133. package/src/input/commands/qrcode-runtime.ts +50 -9
  134. package/src/input/commands/recall-bundle.ts +11 -11
  135. package/src/input/commands/recall-capture.ts +13 -11
  136. package/src/input/commands/recall-query.ts +29 -21
  137. package/src/input/commands/recall-review.ts +2 -1
  138. package/src/input/commands/routines-runtime.ts +99 -45
  139. package/src/input/commands/runtime-services.ts +1 -14
  140. package/src/input/commands/schedule-runtime.ts +33 -20
  141. package/src/input/commands/security-runtime.ts +6 -11
  142. package/src/input/commands/session-content.ts +80 -78
  143. package/src/input/commands/session-workflow.ts +134 -104
  144. package/src/input/commands/session.ts +3 -174
  145. package/src/input/commands/shell-core.ts +38 -17
  146. package/src/input/commands/subscription-runtime.ts +54 -185
  147. package/src/input/commands/support-bundle-runtime.ts +53 -0
  148. package/src/input/commands/tasks-runtime.ts +21 -21
  149. package/src/input/commands/work-plan-runtime.ts +33 -8
  150. package/src/input/commands.ts +6 -2
  151. package/src/input/feed-context-factory.ts +2 -2
  152. package/src/input/file-picker.ts +3 -2
  153. package/src/input/handler-command-route.ts +4 -7
  154. package/src/input/handler-content-actions.ts +89 -26
  155. package/src/input/handler-feed-routes.ts +19 -12
  156. package/src/input/handler-feed.ts +6 -5
  157. package/src/input/handler-interactions.ts +9 -36
  158. package/src/input/handler-modal-stack.ts +3 -3
  159. package/src/input/handler-onboarding.ts +52 -91
  160. package/src/input/handler-shortcuts.ts +16 -7
  161. package/src/input/handler.ts +50 -23
  162. package/src/input/input-history.ts +5 -6
  163. package/src/input/keybindings.ts +27 -16
  164. package/src/input/mcp-workspace.ts +90 -26
  165. package/src/input/onboarding/handler-onboarding-routes.ts +3 -1
  166. package/src/input/onboarding/onboarding-wizard-apply.ts +62 -0
  167. package/src/input/onboarding/onboarding-wizard-constants.ts +1 -0
  168. package/src/input/onboarding/onboarding-wizard-helpers.ts +1 -1
  169. package/src/input/onboarding/onboarding-wizard-operator-steps.ts +223 -55
  170. package/src/input/onboarding/onboarding-wizard-steps.ts +59 -35
  171. package/src/input/onboarding/onboarding-wizard-types.ts +4 -1
  172. package/src/input/onboarding/onboarding-wizard.ts +87 -2
  173. package/src/input/profile-picker-modal.ts +31 -12
  174. package/src/input/session-picker-modal.ts +21 -4
  175. package/src/input/settings-modal-agent-policy.ts +6 -6
  176. package/src/input/settings-modal-behavior.ts +0 -3
  177. package/src/input/settings-modal-subscriptions.ts +3 -3
  178. package/src/input/settings-modal-types.ts +2 -13
  179. package/src/input/settings-modal.ts +20 -66
  180. package/src/input/submission-intent.ts +0 -1
  181. package/src/input/submission-router.ts +3 -3
  182. package/src/main.ts +18 -10
  183. package/src/panels/approval-panel.ts +8 -8
  184. package/src/panels/automation-control-panel.ts +4 -4
  185. package/src/panels/base-panel.ts +1 -1
  186. package/src/panels/builtin/agent.ts +1 -1
  187. package/src/panels/builtin/operations.ts +3 -12
  188. package/src/panels/builtin/session.ts +32 -24
  189. package/src/panels/builtin/shared.ts +7 -7
  190. package/src/panels/cost-tracker-panel.ts +1 -1
  191. package/src/panels/docs-panel.ts +5 -3
  192. package/src/panels/index.ts +0 -1
  193. package/src/panels/knowledge-panel.ts +6 -5
  194. package/src/panels/memory-panel.ts +7 -6
  195. package/src/panels/panel-list-panel.ts +36 -80
  196. package/src/panels/project-planning-panel.ts +19 -12
  197. package/src/panels/provider-account-snapshot.ts +51 -25
  198. package/src/panels/provider-accounts-panel.ts +33 -18
  199. package/src/panels/provider-health-domains.ts +48 -7
  200. package/src/panels/provider-health-panel.ts +5 -4
  201. package/src/panels/qr-panel.ts +44 -20
  202. package/src/panels/schedule-panel.ts +9 -17
  203. package/src/panels/security-panel.ts +8 -8
  204. package/src/panels/session-browser-panel.ts +10 -10
  205. package/src/panels/subscription-panel.ts +6 -6
  206. package/src/panels/system-messages-panel.ts +3 -3
  207. package/src/panels/tasks-panel.ts +21 -14
  208. package/src/panels/tool-inspector-panel.ts +19 -12
  209. package/src/panels/work-plan-panel.ts +5 -5
  210. package/src/planning/project-planning-coordinator.ts +3 -3
  211. package/src/provider-auth-route-display.ts +9 -0
  212. package/src/renderer/agent-workspace-style.ts +34 -0
  213. package/src/renderer/agent-workspace.ts +254 -57
  214. package/src/renderer/autocomplete-overlay.ts +25 -6
  215. package/src/renderer/block-actions.ts +1 -3
  216. package/src/renderer/bookmark-modal.ts +19 -4
  217. package/src/renderer/buffer.ts +4 -2
  218. package/src/renderer/context-inspector.ts +50 -13
  219. package/src/renderer/diff.ts +1 -1
  220. package/src/renderer/file-picker-overlay.ts +19 -6
  221. package/src/renderer/help-overlay.ts +112 -34
  222. package/src/renderer/history-search-overlay.ts +19 -4
  223. package/src/renderer/live-tail-modal.ts +27 -5
  224. package/src/renderer/mcp-workspace.ts +176 -43
  225. package/src/renderer/model-picker-overlay.ts +58 -3
  226. package/src/renderer/model-workspace.ts +104 -22
  227. package/src/renderer/onboarding/onboarding-wizard.ts +20 -4
  228. package/src/renderer/process-modal.ts +27 -6
  229. package/src/renderer/profile-picker-modal.ts +21 -5
  230. package/src/renderer/search-overlay.ts +25 -5
  231. package/src/renderer/selection-modal-overlay.ts +46 -14
  232. package/src/renderer/session-picker-modal.ts +18 -1
  233. package/src/renderer/settings-modal-helpers.ts +2 -40
  234. package/src/renderer/settings-modal.ts +88 -55
  235. package/src/renderer/system-message.ts +1 -1
  236. package/src/renderer/tool-call.ts +20 -11
  237. package/src/runtime/agent-runtime-events.ts +129 -0
  238. package/src/runtime/bootstrap-command-context.ts +7 -1
  239. package/src/runtime/bootstrap-command-parts.ts +11 -8
  240. package/src/runtime/bootstrap-core.ts +23 -65
  241. package/src/runtime/bootstrap-hook-bridge.ts +7 -18
  242. package/src/runtime/bootstrap-shell.ts +46 -8
  243. package/src/runtime/bootstrap.ts +36 -46
  244. package/src/runtime/connected-host-auth.ts +47 -0
  245. package/src/runtime/diagnostics/panels/index.ts +2 -2
  246. package/src/runtime/diagnostics/panels/panel-resources.ts +1 -1
  247. package/src/runtime/diagnostics/panels/policy.ts +7 -7
  248. package/src/runtime/index.ts +2 -5
  249. package/src/runtime/onboarding/apply-file-helpers.ts +66 -0
  250. package/src/runtime/onboarding/apply.ts +288 -74
  251. package/src/runtime/onboarding/derivation.ts +10 -13
  252. package/src/runtime/onboarding/snapshot.ts +2 -17
  253. package/src/runtime/onboarding/types.ts +34 -20
  254. package/src/runtime/onboarding/verify.ts +105 -4
  255. package/src/runtime/operator-token-cleanup.ts +3 -3
  256. package/src/runtime/services.ts +95 -11
  257. package/src/runtime/store/selectors/index.ts +3 -3
  258. package/src/runtime/store/state.ts +1 -4
  259. package/src/runtime/surface-feature-flags.ts +41 -6
  260. package/src/runtime/ui-read-models.ts +3 -4
  261. package/src/runtime/ui-services.ts +6 -6
  262. package/src/shell/blocking-input.ts +2 -1
  263. package/src/shell/service-settings-sync.ts +7 -7
  264. package/src/shell/ui-openers.ts +17 -38
  265. package/src/tools/agent-analysis-registry-policy.ts +0 -1
  266. package/src/tools/agent-channel-send-tool.ts +133 -0
  267. package/src/tools/agent-context-policy.ts +1 -1
  268. package/src/tools/agent-knowledge-ingest-tool.ts +405 -0
  269. package/src/tools/agent-knowledge-tool.ts +170 -0
  270. package/src/tools/agent-local-registry-tool.ts +279 -73
  271. package/src/tools/agent-media-generate-tool.ts +133 -0
  272. package/src/tools/agent-notify-tool.ts +143 -0
  273. package/src/tools/agent-operator-action-tool.ts +137 -0
  274. package/src/tools/agent-operator-briefing-tool.ts +217 -0
  275. package/src/tools/agent-reminder-schedule-tool.ts +237 -0
  276. package/src/tools/agent-tool-policy-guard.ts +13 -15
  277. package/src/tools/agent-work-plan-tool.ts +256 -0
  278. package/src/version.ts +1 -1
  279. package/docs/connected-services.md +0 -51
  280. package/src/cli/package-verification.ts +0 -274
  281. package/src/input/commands/policy-dispatch.ts +0 -339
  282. package/src/input/commands/policy.ts +0 -17
  283. package/src/panels/panel-picker.ts +0 -105
  284. package/src/panels/policy-panel.ts +0 -308
  285. 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', 'AGENT.md', 'agent.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('Personas cannot store secret-looking values. Store a secret reference or remove the sensitive text.');
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
- export function personaStorePath(shellPaths: ShellPathService): string {
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: ShellPathService): AgentPersonaRegistry {
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: ${duplicate.id}`);
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: ${idOrName}`);
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: ${duplicate.id}`);
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: ${idOrName}`);
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: ${idOrName}`);
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: ${idOrName}`);
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: ${idOrName}`);
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: ${error instanceof Error ? error.message : String(error)}`);
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 state: ${active.reviewState}`,
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 background agents because a persona is active.',
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: ${preview.method} ${preview.route}`,
31
- ` name: ${String(preview.payload.name ?? '(service default)')}`,
32
- ` reminder: ${preview.message}`,
33
- ` schedule: ${preview.payload.kind} ${scheduleValue(preview)}`,
34
- ` enabled: ${preview.payload.enabled === false ? 'no' : 'yes'}`,
35
- ` delivery: ${delivery?.mode ?? 'none'}${deliveryTargetCount > 0 ? ` (${deliveryTargetCount} target${deliveryTargetCount === 1 ? '' : 's'})` : ''}`,
36
- ' target: connected GoodVibes services/main conversation route',
37
- ' policy: reminder delivery only; isolated Agent Knowledge only; no default wiki/non-Agent fallback',
38
- ' next: rerun with --yes to create this connected reminder schedule',
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: ${id}`,
49
- ` status: ${status}`,
50
- ` route: ${result.kind} ${result.route}`,
51
- ` reminder: ${result.message}`,
52
- ' next: inspect with /schedule list',
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: ${failure.kind}`,
67
+ `GoodVibes reminder schedule error ${formatReminderScheduleFailureKind(failure.kind)}`,
59
68
  ` ${failure.error}`,
60
- failure.baseUrl ? ` service: ${failure.baseUrl}` : null,
61
- ` route: ${REMINDER_SCHEDULE_METHOD} ${failure.route}`,
62
- failure.kind === 'version_mismatch' && failure.daemonVersion && failure.expectedSdkVersion
63
- ? ` versions: service=${failure.daemonVersion} expected=${failure.expectedSdkVersion}`
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: pair/authenticate with connected GoodVibes services, then retry with --yes.'
75
+ ? ' next pair/authenticate with the connected GoodVibes host, then retry with --yes.'
67
76
  : null,
68
- failure.kind === 'daemon_unavailable'
69
- ? ' next: make connected GoodVibes services available outside Agent, then retry.'
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 === 'daemon_route_unavailable'
72
- ? ' next: update connected GoodVibes services so public schedules.create is available.'
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
  }