@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
@@ -5,6 +5,7 @@ import type { OperatorMethodInput, OperatorMethodOutput } from '@pellux/goodvibe
5
5
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
6
6
  import { getModelIdFromProviderModel, getProviderIdFromModel } from '../config/provider-model.ts';
7
7
  import { SDK_VERSION } from '../version.ts';
8
+ import { formatAgentRecordReviewState } from './record-labels.ts';
8
9
  import type { AgentRoutineRecord } from './routine-registry.ts';
9
10
 
10
11
  export const ROUTINE_SCHEDULE_ROUTE = '/api/automation/schedules';
@@ -16,11 +17,11 @@ type ScheduleCreateOutput = OperatorMethodOutput<'schedules.create'>;
16
17
  type ScheduleDeliveryInput = NonNullable<ScheduleCreateInput['delivery']>;
17
18
  type ScheduleDeliveryTargetInput = ScheduleDeliveryInput['targets'] extends readonly (infer T)[] ? T : never;
18
19
 
19
- export interface AgentDaemonConfigReader {
20
+ export interface AgentConnectedHostConfigReader {
20
21
  get(key: string): unknown;
21
22
  }
22
23
 
23
- export interface AgentDaemonConnection {
24
+ export interface AgentConnectedHostConnection {
24
25
  readonly baseUrl: string;
25
26
  readonly token: string | null;
26
27
  readonly tokenPath: string;
@@ -46,6 +47,7 @@ export type RoutineScheduleDeliverySurfaceKind =
46
47
  | 'google-chat'
47
48
  | 'signal'
48
49
  | 'whatsapp'
50
+ | 'telephony'
49
51
  | 'imessage'
50
52
  | 'msteams'
51
53
  | 'bluebubbles'
@@ -105,14 +107,14 @@ export interface RoutineSchedulePromotionFailure {
105
107
  readonly kind:
106
108
  | 'confirmation_required'
107
109
  | 'auth_required'
108
- | 'daemon_unavailable'
110
+ | 'connected_host_unavailable'
109
111
  | 'version_mismatch'
110
- | 'daemon_route_unavailable'
111
- | 'daemon_error';
112
+ | 'connected_host_route_unavailable'
113
+ | 'connected_host_error';
112
114
  readonly error: string;
113
115
  readonly route: typeof ROUTINE_SCHEDULE_ROUTE;
114
116
  readonly baseUrl?: string;
115
- readonly daemonVersion?: string;
117
+ readonly connectedHostVersion?: string;
116
118
  readonly expectedSdkVersion?: string;
117
119
  }
118
120
 
@@ -128,7 +130,7 @@ export interface RoutineScheduleReceipt {
128
130
  readonly route: typeof ROUTINE_SCHEDULE_ROUTE;
129
131
  readonly method: typeof ROUTINE_SCHEDULE_METHOD;
130
132
  readonly status: 'created' | 'failed';
131
- readonly daemonBaseUrl: string;
133
+ readonly connectedHostBaseUrl: string;
132
134
  readonly scheduleId?: string;
133
135
  readonly scheduleStatus?: string;
134
136
  readonly scheduleName: string;
@@ -191,14 +193,14 @@ export interface RoutineScheduleCorrelationFailure {
191
193
  readonly ok: false;
192
194
  readonly kind:
193
195
  | 'auth_required'
194
- | 'daemon_unavailable'
196
+ | 'connected_host_unavailable'
195
197
  | 'version_mismatch'
196
- | 'daemon_route_unavailable'
197
- | 'daemon_error';
198
+ | 'connected_host_route_unavailable'
199
+ | 'connected_host_error';
198
200
  readonly error: string;
199
201
  readonly route: typeof ROUTINE_SCHEDULE_ROUTE;
200
202
  readonly baseUrl?: string;
201
- readonly daemonVersion?: string;
203
+ readonly connectedHostVersion?: string;
202
204
  readonly expectedSdkVersion?: string;
203
205
  }
204
206
 
@@ -236,17 +238,17 @@ function deliveryModeFromTargets(targets: readonly RoutineScheduleDeliveryTarget
236
238
  function toDeliveryTargetInput(target: RoutineScheduleDeliveryTargetSpec): ScheduleDeliveryTargetInput {
237
239
  return {
238
240
  kind: target.kind,
239
- surfaceKind: target.surfaceKind,
241
+ surfaceKind: target.surfaceKind as ScheduleDeliveryTargetInput['surfaceKind'],
240
242
  address: target.address,
241
243
  routeId: target.routeId,
242
244
  label: target.label,
243
245
  };
244
246
  }
245
247
 
246
- export function resolveAgentDaemonConnection(
247
- configManager: AgentDaemonConfigReader,
248
+ export function resolveAgentConnectedHostConnection(
249
+ configManager: AgentConnectedHostConfigReader,
248
250
  homeDirectory: string,
249
- ): AgentDaemonConnection {
251
+ ): AgentConnectedHostConnection {
250
252
  const host = String(configManager.get('controlPlane.host') ?? '127.0.0.1');
251
253
  const port = Number(configManager.get('controlPlane.port') ?? 3421);
252
254
  const tokenPath = join(homeDirectory, '.goodvibes', 'daemon', 'operator-tokens.json');
@@ -265,16 +267,16 @@ export function buildRoutineSchedulePrompt(routine: AgentRoutineRecord): string
265
267
  'GoodVibes Agent scheduled routine.',
266
268
  '',
267
269
  `Routine: ${routine.name}`,
268
- `Routine id: ${routine.id}`,
269
- `Review state: ${routine.reviewState}`,
270
+ `Routine reference: ${routine.id}`,
271
+ `Review: ${formatAgentRecordReviewState(routine.reviewState)}`,
270
272
  `Tags: ${routine.tags.join(', ') || '(none)'}`,
271
273
  `Triggers: ${routine.triggers.join(', ') || '(manual)'}`,
272
274
  '',
273
275
  'Operator policy:',
274
276
  '- Run this as a serial GoodVibes Agent operator routine.',
275
- '- Use isolated Agent Knowledge routes only; never use default Knowledge/Wiki or non-Agent knowledge spaces as fallback.',
277
+ '- Use isolated Agent Knowledge routes only; never use default knowledge or non-Agent knowledge spaces as fallback.',
276
278
  '- Do not perform destructive, costly, externally visible, or secret-handling actions without explicit approval.',
277
- '- Do not request WRFC unless this scheduled routine explicitly delegates build/fix/review work to GoodVibes TUI.',
279
+ '- Do not request GoodVibes TUI delegation unless this scheduled routine explicitly delegates build/fix/review work to GoodVibes TUI.',
278
280
  '- Summarize what was checked, what changed, and what still needs user review.',
279
281
  '',
280
282
  'Routine description:',
@@ -354,7 +356,7 @@ export function buildRoutineSchedulePreview(
354
356
  };
355
357
  }
356
358
 
357
- async function fetchDaemonStatus(connection: AgentDaemonConnection): Promise<{
359
+ async function fetchConnectedHostStatus(connection: AgentConnectedHostConnection): Promise<{
358
360
  readonly ok: boolean;
359
361
  readonly status: number;
360
362
  readonly body: unknown;
@@ -378,7 +380,7 @@ async function fetchDaemonStatus(connection: AgentDaemonConnection): Promise<{
378
380
 
379
381
  async function classifyScheduleError(
380
382
  error: unknown,
381
- connection: AgentDaemonConnection,
383
+ connection: AgentConnectedHostConnection,
382
384
  ): Promise<RoutineSchedulePromotionFailure> {
383
385
  const message = summarizeError(error);
384
386
  const lower = message.toLowerCase();
@@ -386,37 +388,37 @@ async function classifyScheduleError(
386
388
  return { ok: false, kind: 'auth_required', error: message, route: ROUTINE_SCHEDULE_ROUTE, baseUrl: connection.baseUrl };
387
389
  }
388
390
  if (lower.includes('404') || lower.includes('not found')) {
389
- const daemon = await fetchDaemonStatus(connection);
390
- const record = isRecord(daemon.body) ? daemon.body : {};
391
- const daemonVersion = readString(record, 'version') ?? 'unknown';
392
- if (daemon.ok && daemonVersion !== SDK_VERSION) {
391
+ const connectedHost = await fetchConnectedHostStatus(connection);
392
+ const record = isRecord(connectedHost.body) ? connectedHost.body : {};
393
+ const connectedHostVersion = readString(record, 'version') ?? 'unknown';
394
+ if (connectedHost.ok && connectedHostVersion !== SDK_VERSION) {
393
395
  return {
394
396
  ok: false,
395
397
  kind: 'version_mismatch',
396
- error: `Connected GoodVibes service SDK version ${daemonVersion} does not match Agent SDK pin ${SDK_VERSION}; schedules.create is unavailable.`,
398
+ error: `Connected GoodVibes host SDK version ${connectedHostVersion} does not match Agent SDK pin ${SDK_VERSION}; schedules.create is unavailable.`,
397
399
  route: ROUTINE_SCHEDULE_ROUTE,
398
400
  baseUrl: connection.baseUrl,
399
- daemonVersion,
401
+ connectedHostVersion,
400
402
  expectedSdkVersion: SDK_VERSION,
401
403
  };
402
404
  }
403
- return { ok: false, kind: 'daemon_route_unavailable', error: message, route: ROUTINE_SCHEDULE_ROUTE, baseUrl: connection.baseUrl };
405
+ return { ok: false, kind: 'connected_host_route_unavailable', error: message, route: ROUTINE_SCHEDULE_ROUTE, baseUrl: connection.baseUrl };
404
406
  }
405
407
  if (lower.includes('fetch') || lower.includes('connect') || lower.includes('econnrefused')) {
406
- return { ok: false, kind: 'daemon_unavailable', error: message, route: ROUTINE_SCHEDULE_ROUTE, baseUrl: connection.baseUrl };
408
+ return { ok: false, kind: 'connected_host_unavailable', error: message, route: ROUTINE_SCHEDULE_ROUTE, baseUrl: connection.baseUrl };
407
409
  }
408
- return { ok: false, kind: 'daemon_error', error: message, route: ROUTINE_SCHEDULE_ROUTE, baseUrl: connection.baseUrl };
410
+ return { ok: false, kind: 'connected_host_error', error: message, route: ROUTINE_SCHEDULE_ROUTE, baseUrl: connection.baseUrl };
409
411
  }
410
412
 
411
- export async function promoteRoutineToDaemonSchedule(
412
- connection: AgentDaemonConnection,
413
+ export async function promoteRoutineToConnectedSchedule(
414
+ connection: AgentConnectedHostConnection,
413
415
  preview: RoutineSchedulePromotionPreview,
414
416
  ): Promise<RoutineSchedulePromotionResult> {
415
417
  if (!connection.token) {
416
418
  return {
417
419
  ok: false,
418
420
  kind: 'auth_required',
419
- error: `No daemon operator token found at ${connection.tokenPath}`,
421
+ error: `No connected-host operator token found at ${connection.tokenPath}`,
420
422
  route: ROUTINE_SCHEDULE_ROUTE,
421
423
  baseUrl: connection.baseUrl,
422
424
  };
@@ -12,7 +12,7 @@ import {
12
12
  ROUTINE_SCHEDULE_LIST_METHOD,
13
13
  ROUTINE_SCHEDULE_METHOD,
14
14
  ROUTINE_SCHEDULE_ROUTE,
15
- type AgentDaemonConnection,
15
+ type AgentConnectedHostConnection,
16
16
  type RoutineScheduleCorrelation,
17
17
  type RoutineScheduleCorrelationFailure,
18
18
  type RoutineScheduleCorrelationResult,
@@ -82,6 +82,15 @@ function redactedDeliveryTargets(delivery: ScheduleDeliveryInput | undefined): r
82
82
  }));
83
83
  }
84
84
 
85
+ function normalizeFailureKind(value: unknown): RoutineScheduleReceipt['failureKind'] {
86
+ if (value === 'confirmation_required' || value === 'auth_required' || value === 'version_mismatch') return value;
87
+ if (value === 'connected_host_unavailable' || value === 'connected_host_route_unavailable' || value === 'connected_host_error') return value;
88
+ if (value === 'daemon_unavailable') return 'connected_host_unavailable';
89
+ if (value === 'daemon_route_unavailable') return 'connected_host_route_unavailable';
90
+ if (value === 'daemon_error') return 'connected_host_error';
91
+ return undefined;
92
+ }
93
+
85
94
  function readReceipt(value: unknown): RoutineScheduleReceipt | null {
86
95
  if (!isRecord(value)) return null;
87
96
  const id = readString(value, 'id')?.trim();
@@ -120,7 +129,7 @@ function readReceipt(value: unknown): RoutineScheduleReceipt | null {
120
129
  route: ROUTINE_SCHEDULE_ROUTE,
121
130
  method: ROUTINE_SCHEDULE_METHOD,
122
131
  status,
123
- daemonBaseUrl: readString(value, 'daemonBaseUrl') ?? '',
132
+ connectedHostBaseUrl: readString(value, 'connectedHostBaseUrl') ?? readString(value, 'daemonBaseUrl') ?? '',
124
133
  scheduleId: readString(value, 'scheduleId') ?? undefined,
125
134
  scheduleStatus: readString(value, 'scheduleStatus') ?? undefined,
126
135
  scheduleName: readString(value, 'scheduleName') ?? routineName,
@@ -138,14 +147,7 @@ function readReceipt(value: unknown): RoutineScheduleReceipt | null {
138
147
  },
139
148
  deliveryMode: readString(value, 'deliveryMode') ?? undefined,
140
149
  deliveryTargets,
141
- failureKind: value.failureKind === 'confirmation_required'
142
- || value.failureKind === 'auth_required'
143
- || value.failureKind === 'daemon_unavailable'
144
- || value.failureKind === 'version_mismatch'
145
- || value.failureKind === 'daemon_route_unavailable'
146
- || value.failureKind === 'daemon_error'
147
- ? value.failureKind
148
- : undefined,
150
+ failureKind: normalizeFailureKind(value.failureKind),
149
151
  failureError: readString(value, 'failureError') ?? undefined,
150
152
  };
151
153
  }
@@ -209,7 +211,7 @@ function resultScheduleRecord(result: RoutineSchedulePromotionResult): Record<st
209
211
 
210
212
  function buildReceipt(
211
213
  existing: readonly RoutineScheduleReceipt[],
212
- connection: AgentDaemonConnection,
214
+ connection: AgentConnectedHostConnection,
213
215
  preview: RoutineSchedulePromotionPreview,
214
216
  result: RoutineSchedulePromotionResult,
215
217
  ): RoutineScheduleReceipt {
@@ -226,7 +228,7 @@ function buildReceipt(
226
228
  route: ROUTINE_SCHEDULE_ROUTE,
227
229
  method: ROUTINE_SCHEDULE_METHOD,
228
230
  status: result.ok ? 'created' : 'failed',
229
- daemonBaseUrl: connection.baseUrl,
231
+ connectedHostBaseUrl: connection.baseUrl,
230
232
  scheduleId,
231
233
  scheduleStatus,
232
234
  scheduleName: String(preview.payload.name ?? `Agent routine: ${preview.routineName}`),
@@ -269,7 +271,7 @@ export class RoutineScheduleReceiptStore {
269
271
  return this.snapshot().receipts.find((receipt) => receipt.id.toLowerCase() === normalized) ?? null;
270
272
  }
271
273
 
272
- public append(connection: AgentDaemonConnection, preview: RoutineSchedulePromotionPreview, result: RoutineSchedulePromotionResult): RoutineScheduleReceipt {
274
+ public append(connection: AgentConnectedHostConnection, preview: RoutineSchedulePromotionPreview, result: RoutineSchedulePromotionResult): RoutineScheduleReceipt {
273
275
  const store = this.readStore();
274
276
  const receipt = buildReceipt(store.receipts, connection, preview, result);
275
277
  this.writeStore({ ...store, receipts: [...store.receipts, receipt] });
@@ -281,7 +283,7 @@ export class RoutineScheduleReceiptStore {
281
283
  try {
282
284
  return parseReceiptStore(readFileSync(this.storePath, 'utf-8'));
283
285
  } catch (error) {
284
- throw new Error(`Could not read Agent routine schedule receipt store: ${summarizeError(error)}`);
286
+ throw new Error(`Could not read Agent routine schedule receipt store ${summarizeError(error)}`);
285
287
  }
286
288
  }
287
289
 
@@ -415,7 +417,7 @@ function correlateReceipts(
415
417
  });
416
418
  }
417
419
 
418
- async function fetchDaemonStatus(connection: AgentDaemonConnection): Promise<{
420
+ async function fetchConnectedHostStatus(connection: AgentConnectedHostConnection): Promise<{
419
421
  readonly ok: boolean;
420
422
  readonly status: number;
421
423
  readonly body: unknown;
@@ -439,7 +441,7 @@ async function fetchDaemonStatus(connection: AgentDaemonConnection): Promise<{
439
441
 
440
442
  async function classifyScheduleListError(
441
443
  error: unknown,
442
- connection: AgentDaemonConnection,
444
+ connection: AgentConnectedHostConnection,
443
445
  ): Promise<RoutineScheduleCorrelationFailure> {
444
446
  const message = summarizeError(error);
445
447
  const lower = message.toLowerCase();
@@ -447,37 +449,37 @@ async function classifyScheduleListError(
447
449
  return { ok: false, kind: 'auth_required', error: message, route: ROUTINE_SCHEDULE_ROUTE, baseUrl: connection.baseUrl };
448
450
  }
449
451
  if (lower.includes('404') || lower.includes('not found')) {
450
- const daemon = await fetchDaemonStatus(connection);
451
- const record = isRecord(daemon.body) ? daemon.body : {};
452
- const daemonVersion = readString(record, 'version') ?? 'unknown';
453
- if (daemon.ok && daemonVersion !== SDK_VERSION) {
452
+ const connectedHost = await fetchConnectedHostStatus(connection);
453
+ const record = isRecord(connectedHost.body) ? connectedHost.body : {};
454
+ const connectedHostVersion = readString(record, 'version') ?? 'unknown';
455
+ if (connectedHost.ok && connectedHostVersion !== SDK_VERSION) {
454
456
  return {
455
457
  ok: false,
456
458
  kind: 'version_mismatch',
457
- error: `Connected GoodVibes service SDK version ${daemonVersion} does not match Agent SDK pin ${SDK_VERSION}; schedules.list is unavailable.`,
459
+ error: `Connected GoodVibes host SDK version ${connectedHostVersion} does not match Agent SDK pin ${SDK_VERSION}; schedules.list is unavailable.`,
458
460
  route: ROUTINE_SCHEDULE_ROUTE,
459
461
  baseUrl: connection.baseUrl,
460
- daemonVersion,
462
+ connectedHostVersion,
461
463
  expectedSdkVersion: SDK_VERSION,
462
464
  };
463
465
  }
464
- return { ok: false, kind: 'daemon_route_unavailable', error: message, route: ROUTINE_SCHEDULE_ROUTE, baseUrl: connection.baseUrl };
466
+ return { ok: false, kind: 'connected_host_route_unavailable', error: message, route: ROUTINE_SCHEDULE_ROUTE, baseUrl: connection.baseUrl };
465
467
  }
466
468
  if (lower.includes('fetch') || lower.includes('connect') || lower.includes('econnrefused')) {
467
- return { ok: false, kind: 'daemon_unavailable', error: message, route: ROUTINE_SCHEDULE_ROUTE, baseUrl: connection.baseUrl };
469
+ return { ok: false, kind: 'connected_host_unavailable', error: message, route: ROUTINE_SCHEDULE_ROUTE, baseUrl: connection.baseUrl };
468
470
  }
469
- return { ok: false, kind: 'daemon_error', error: message, route: ROUTINE_SCHEDULE_ROUTE, baseUrl: connection.baseUrl };
471
+ return { ok: false, kind: 'connected_host_error', error: message, route: ROUTINE_SCHEDULE_ROUTE, baseUrl: connection.baseUrl };
470
472
  }
471
473
 
472
474
  export async function reconcileRoutineScheduleReceipts(
473
- connection: AgentDaemonConnection,
475
+ connection: AgentConnectedHostConnection,
474
476
  snapshot: RoutineScheduleReceiptSnapshot,
475
477
  ): Promise<RoutineScheduleCorrelationResult> {
476
478
  if (!connection.token) {
477
479
  return {
478
480
  ok: false,
479
481
  kind: 'auth_required',
480
- error: `No daemon operator token found at ${connection.tokenPath}`,
482
+ error: `No connected-host operator token found at ${connection.tokenPath}`,
481
483
  route: ROUTINE_SCHEDULE_ROUTE,
482
484
  baseUrl: connection.baseUrl,
483
485
  };
@@ -44,7 +44,7 @@ export const STARTER_TEMPLATES: readonly AgentRuntimeProfileStarterTemplate[] =
44
44
  body: [
45
45
  'Operate as a calm household coordinator.',
46
46
  'Track preferences, routines, device/service notes, and recurring decisions in Agent-local memory after they are durable and non-secret.',
47
- 'Use read-only daemon/operator routes for status checks. Require explicit approval for purchases, messages to other people, service changes, deletions, or secret handling.',
47
+ 'Use read-only connected-host/operator routes for status checks. Require explicit approval for purchases, messages to other people, service changes, deletions, or secret handling.',
48
48
  'Keep replies concrete: next action, owner, date, and open question when one is needed.',
49
49
  ].join('\n'),
50
50
  tags: ['household', 'home', 'coordination'],
@@ -81,7 +81,7 @@ export const STARTER_TEMPLATES: readonly AgentRuntimeProfileStarterTemplate[] =
81
81
  description: 'Review open household commitments, pending approvals, and stale home notes.',
82
82
  steps: [
83
83
  '1. List pending local routines, workplan tasks, and approvals.',
84
- '2. Summarize what changed since the last review from local Agent state.',
84
+ '2. Summarize what changed since the last review from Agent-local state.',
85
85
  '3. Ask for confirmation before scheduling, sending messages, purchasing, or changing services.',
86
86
  '4. Record reviewed preferences and stale notes locally.',
87
87
  ].join('\n'),
@@ -200,7 +200,7 @@ export const STARTER_TEMPLATES: readonly AgentRuntimeProfileStarterTemplate[] =
200
200
  name: 'Pre-trip Readiness Review',
201
201
  description: 'Check documents, reservations, transport, packing, weather, and open decisions.',
202
202
  steps: [
203
- '1. Review trip dates, locations, reservations, and open decisions from local Agent state.',
203
+ '1. Review trip dates, locations, reservations, and open decisions from Agent-local state.',
204
204
  '2. Refresh current weather, schedule, and rule data when needed.',
205
205
  '3. Summarize blockers and next confirmed action.',
206
206
  '4. Ask before external sends, purchases, or reservation changes.',
@@ -223,7 +223,7 @@ export const STARTER_TEMPLATES: readonly AgentRuntimeProfileStarterTemplate[] =
223
223
  description: 'Operator persona for systems, incidents, runbooks, approvals, and service posture.',
224
224
  body: [
225
225
  'Favor explicit state, logs, health, approvals, and next action.',
226
- 'Use read-only daemon/operator routes by default.',
226
+ 'Use read-only connected-host/operator routes by default.',
227
227
  'Require explicit confirmation for run, pause, resume, cancel, retry, approve, deny, service changes, or writes.',
228
228
  'Delegate code/build fixes to GoodVibes TUI when explicitly requested.',
229
229
  ].join('\n'),
@@ -260,7 +260,7 @@ export const STARTER_TEMPLATES: readonly AgentRuntimeProfileStarterTemplate[] =
260
260
  name: 'Daily Operations Sweep',
261
261
  description: 'Inspect service posture, pending approvals, failed runs, and stale tasks.',
262
262
  steps: [
263
- '1. Refresh daemon status, compat, approvals, workplan, automation snapshot, runs, schedules, and capacity.',
263
+ '1. Refresh connected-host status, compat, approvals, workplan, automation snapshot, runs, schedules, and capacity.',
264
264
  '2. Summarize degraded items and blocked work.',
265
265
  '3. Recommend exact follow-up commands with confirmation gates for side effects.',
266
266
  ].join('\n'),
@@ -36,6 +36,12 @@ export interface AgentRuntimeProfileInfo extends AgentRuntimeProfileResolution {
36
36
  readonly starterTemplateApplication?: AgentRuntimeProfileTemplateApplication;
37
37
  }
38
38
 
39
+ export interface AgentRuntimeProfileSelection extends AgentRuntimeProfileResolution {
40
+ readonly selectedAt: string | null;
41
+ readonly path: string;
42
+ readonly exists: boolean;
43
+ }
44
+
39
45
  export interface AgentRuntimeProfileTemplateApplication {
40
46
  readonly id: AgentRuntimeProfileTemplateId;
41
47
  readonly name: string;
@@ -76,11 +82,14 @@ export interface AgentRuntimeProfileCommandResult {
76
82
  | 'agent.profiles.template.from_discovered'
77
83
  | 'agent.profiles.create_from_discovered'
78
84
  | 'agent.profiles.create'
85
+ | 'agent.profiles.default'
86
+ | 'agent.profiles.default.clear'
79
87
  | 'agent.profiles.delete'
80
88
  | 'agent.profiles.error';
81
89
  readonly data?: {
82
90
  readonly profiles?: readonly AgentRuntimeProfileInfo[];
83
91
  readonly profile?: AgentRuntimeProfileInfo;
92
+ readonly selectedProfile?: AgentRuntimeProfileSelection;
84
93
  readonly templates?: readonly AgentRuntimeProfileTemplateSummary[];
85
94
  readonly appliedTemplate?: AgentRuntimeProfileTemplateApplication;
86
95
  readonly template?: AgentRuntimeProfileTemplateSummary;
@@ -91,8 +100,13 @@ export interface AgentRuntimeProfileCommandResult {
91
100
  }
92
101
 
93
102
  const PROFILE_CREATED_FILE = 'profile.json';
103
+ const PROFILE_SELECTION_FILE = 'profile-selection.json';
94
104
  const PROFILE_ID_PATTERN = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/;
95
105
 
106
+ function isRecord(value: unknown): value is Record<string, unknown> {
107
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
108
+ }
109
+
96
110
  export function normalizeAgentRuntimeProfileId(value: string): string {
97
111
  return value
98
112
  .trim()
@@ -122,6 +136,10 @@ export function getAgentRuntimeProfileTemplatesRoot(baseHomeDirectory: string):
122
136
  return join(baseHomeDirectory, '.goodvibes', GOODVIBES_AGENT_SURFACE_ROOT, 'profile-starters');
123
137
  }
124
138
 
139
+ export function getAgentRuntimeProfileSelectionPath(baseHomeDirectory: string): string {
140
+ return join(baseHomeDirectory, '.goodvibes', GOODVIBES_AGENT_SURFACE_ROOT, PROFILE_SELECTION_FILE);
141
+ }
142
+
125
143
  export function resolveAgentRuntimeProfileHome(baseHomeDirectory: string, profileName: string): AgentRuntimeProfileResolution {
126
144
  const id = assertValidAgentRuntimeProfileId(profileName);
127
145
  return {
@@ -130,6 +148,36 @@ export function resolveAgentRuntimeProfileHome(baseHomeDirectory: string, profil
130
148
  };
131
149
  }
132
150
 
151
+ export function readAgentRuntimeProfileSelection(baseHomeDirectory: string): AgentRuntimeProfileSelection | null {
152
+ const path = getAgentRuntimeProfileSelectionPath(baseHomeDirectory);
153
+ if (!existsSync(path)) return null;
154
+ try {
155
+ const raw: unknown = JSON.parse(readFileSync(path, 'utf-8'));
156
+ if (!isRecord(raw)) return null;
157
+ const profileId = raw.profileId;
158
+ if (typeof profileId !== 'string') return null;
159
+ const resolution = resolveAgentRuntimeProfileHome(baseHomeDirectory, profileId);
160
+ const selectedAt = typeof raw.selectedAt === 'string' ? raw.selectedAt : null;
161
+ return {
162
+ ...resolution,
163
+ selectedAt,
164
+ path,
165
+ exists: existsSync(resolution.homeDirectory),
166
+ };
167
+ } catch {
168
+ return null;
169
+ }
170
+ }
171
+
172
+ export function resolveSelectedAgentRuntimeProfileHome(baseHomeDirectory: string): AgentRuntimeProfileResolution | null {
173
+ const selection = readAgentRuntimeProfileSelection(baseHomeDirectory);
174
+ if (!selection?.exists) return null;
175
+ return {
176
+ id: selection.id,
177
+ homeDirectory: selection.homeDirectory,
178
+ };
179
+ }
180
+
133
181
  function readProfileCreatedAt(homeDirectory: string): string | null {
134
182
  const path = join(homeDirectory, PROFILE_CREATED_FILE);
135
183
  if (!existsSync(path)) return null;
@@ -147,13 +195,11 @@ function readProfileStarterTemplate(homeDirectory: string): Pick<AgentRuntimePro
147
195
  if (!existsSync(path)) return {};
148
196
  try {
149
197
  const raw: unknown = JSON.parse(readFileSync(path, 'utf-8'));
150
- if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return {};
151
- const record = raw as Record<string, unknown>;
152
- const starter = record.starterTemplate;
153
- if (!starter || typeof starter !== 'object' || Array.isArray(starter)) return {};
154
- const starterRecord = starter as Record<string, unknown>;
155
- const id = starterRecord.id;
156
- const name = starterRecord.name;
198
+ if (!isRecord(raw)) return {};
199
+ const starter = raw.starterTemplate;
200
+ if (!isRecord(starter)) return {};
201
+ const id = starter.id;
202
+ const name = starter.name;
157
203
  if (!isAgentRuntimeProfileTemplateId(id) || typeof name !== 'string') return {};
158
204
  return {
159
205
  starterTemplateId: id,
@@ -226,7 +272,7 @@ function selectDiscoveredRecord<T extends { readonly name: string; readonly path
226
272
  if (!selector?.trim()) return records[0]!;
227
273
  const lookup = selector.trim().toLowerCase();
228
274
  const match = records.find((record) => normalizedLookupValues(record.name, record.path).includes(lookup));
229
- if (!match) throw new Error(`Unknown discovered Agent ${label}: ${selector}.`);
275
+ if (!match) throw new Error(`Unknown discovered Agent ${label} ${selector}.`);
230
276
  return match;
231
277
  }
232
278
 
@@ -278,8 +324,8 @@ function readTemplateTextBlock(value: unknown, field: string): string {
278
324
  }
279
325
 
280
326
  function readTemplateObject(value: unknown, field: string): Record<string, unknown> {
281
- if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`Starter template ${field} must be an object.`);
282
- return value as Record<string, unknown>;
327
+ if (!isRecord(value)) throw new Error(`Starter template ${field} must be an object.`);
328
+ return value;
283
329
  }
284
330
 
285
331
  function parseTemplateSkill(value: unknown): AgentRuntimeProfileStarterTemplate['skills'][number] {
@@ -360,7 +406,7 @@ function resolveAgentRuntimeProfileTemplate(templateId: AgentRuntimeProfileTempl
360
406
  const local = baseHomeDirectory ? listLocalTemplates(baseHomeDirectory).find((template) => template.id === id) : undefined;
361
407
  if (local) return local;
362
408
  const suffix = baseHomeDirectory ? ' Use profiles templates to list starters.' : '';
363
- throw new Error(`Unknown Agent starter profile template: ${templateId}.${suffix}`);
409
+ throw new Error(`Unknown Agent starter profile template ${templateId}.${suffix}`);
364
410
  }
365
411
 
366
412
  export function listAgentRuntimeProfileTemplates(baseHomeDirectory?: string): readonly AgentRuntimeProfileTemplateSummary[] {
@@ -423,7 +469,7 @@ export async function createAgentRuntimeProfileTemplateFromDiscovered(
423
469
  const selectedRoutines = selectDiscoveredRecords(routines, options.routines, 'routine');
424
470
  const target = join(getAgentRuntimeProfileTemplatesRoot(shellPaths.homeDirectory), `${id}.json`);
425
471
  if (existsSync(target) && options.replace !== true) {
426
- throw new Error(`Agent starter template already exists: ${id}. Rerun with --replace to overwrite it.`);
472
+ throw new Error(`Agent starter template already exists ${id}. Rerun with --replace to overwrite it.`);
427
473
  }
428
474
  const persona = discoveredPersonaToTemplate(selectedPersona);
429
475
  const template: AgentRuntimeProfileStarterTemplate = {
@@ -573,9 +619,42 @@ export function createAgentRuntimeProfile(baseHomeDirectory: string, profileName
573
619
  };
574
620
  }
575
621
 
622
+ export function setAgentRuntimeProfileSelection(baseHomeDirectory: string, profileName: string): AgentRuntimeProfileSelection {
623
+ const resolution = resolveAgentRuntimeProfileHome(baseHomeDirectory, profileName);
624
+ if (!existsSync(resolution.homeDirectory)) {
625
+ throw new Error(`Agent profile not found ${resolution.id}`);
626
+ }
627
+ const path = getAgentRuntimeProfileSelectionPath(baseHomeDirectory);
628
+ mkdirSync(dirname(path), { recursive: true });
629
+ const selectedAt = new Date().toISOString();
630
+ writeFileSync(
631
+ path,
632
+ `${JSON.stringify({
633
+ profileId: resolution.id,
634
+ selectedAt,
635
+ }, null, 2)}\n`,
636
+ 'utf-8',
637
+ );
638
+ return {
639
+ ...resolution,
640
+ selectedAt,
641
+ path,
642
+ exists: true,
643
+ };
644
+ }
645
+
646
+ export function clearAgentRuntimeProfileSelection(baseHomeDirectory: string): boolean {
647
+ const path = getAgentRuntimeProfileSelectionPath(baseHomeDirectory);
648
+ if (!existsSync(path)) return false;
649
+ rmSync(path, { force: true });
650
+ return true;
651
+ }
652
+
576
653
  export function deleteAgentRuntimeProfile(baseHomeDirectory: string, profileName: string): boolean {
577
654
  const resolution = resolveAgentRuntimeProfileHome(baseHomeDirectory, profileName);
578
655
  if (!existsSync(resolution.homeDirectory)) return false;
579
656
  rmSync(resolution.homeDirectory, { recursive: true, force: true });
657
+ const selection = readAgentRuntimeProfileSelection(baseHomeDirectory);
658
+ if (selection?.id === resolution.id) clearAgentRuntimeProfileSelection(baseHomeDirectory);
580
659
  return true;
581
660
  }