@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
@@ -1,10 +1,9 @@
1
- import { createOAuthLocalListener } from '@pellux/goodvibes-sdk/platform/config';
2
1
  import { beginOpenAICodexLogin, exchangeOpenAICodexCode } from '@pellux/goodvibes-sdk/platform/config';
3
2
  import { openExternalUrl } from '@pellux/goodvibes-sdk/platform/utils';
4
3
  import { getProviderIdFromModel } from '../config/provider-model.ts';
5
- import { buildProviderAccountSnapshot } from '@/runtime/index.ts';
4
+ import { buildProviderAccountSnapshot } from '../panels/provider-account-snapshot.ts';
6
5
  import { OnboardingWizardController, type OnboardingWizardAction, type OnboardingWizardApplyFeedback } from './onboarding/onboarding-wizard.ts';
7
- import { applyOnboardingRequest, collectOnboardingSnapshot, verifyOnboardingRequest } from '../runtime/onboarding/index.ts';
6
+ import { applyOnboardingRequest, collectOnboardingSnapshot, verifyOnboardingRequest, writeOnboardingCheckMarker } from '../runtime/onboarding/index.ts';
8
7
  import type { OnboardingApplyRequest, OnboardingVerificationItem } from '../runtime/onboarding/index.ts';
9
8
  import type { ModelPickerTarget } from './model-picker.ts';
10
9
  import { captureOnboardingWizardSnapshot, restoreOnboardingWizardSnapshot } from './handler-ui-state.ts';
@@ -156,6 +155,17 @@ export function handleModelPickerCommitForHandler(handler: InputHandler): boolea
156
155
  }
157
156
 
158
157
  export async function handleOnboardingActionForHandler(handler: InputHandler, action: OnboardingWizardAction): Promise<void> {
158
+ if (action.startsWith('open-agent-workspace:')) {
159
+ const categoryId = action.slice('open-agent-workspace:'.length);
160
+ if (handler.commandContext) handler.openAgentWorkspace(handler.commandContext, categoryId);
161
+ else handler.onboardingWizard.setApplyFeedback({
162
+ severity: 'warning',
163
+ title: 'Workspace unavailable',
164
+ summary: 'The Agent workspace cannot open because the command context is not wired yet.',
165
+ messages: [`Requested workspace: ${categoryId}`],
166
+ });
167
+ return;
168
+ }
159
169
  if (action === 'start-openai-subscription') {
160
170
  await handler.handleOpenAiSubscriptionStart();
161
171
  return;
@@ -219,7 +229,7 @@ export async function handleOnboardingActionForHandler(handler: InputHandler, ac
219
229
  showOnboardingApplyFeedbackForHandler(handler, {
220
230
  severity: 'error',
221
231
  title: 'Apply failed',
222
- summary: 'The wizard could not persist these settings. No service restart was attempted.',
232
+ summary: 'The wizard could not persist these settings. No connected-host restart was attempted.',
223
233
  messages: [error instanceof Error ? error.message : String(error)],
224
234
  });
225
235
  return;
@@ -240,17 +250,34 @@ export async function handleOnboardingActionForHandler(handler: InputHandler, ac
240
250
 
241
251
  handler.syncRuntimeFromOnboardingRequest(request);
242
252
  handler.onboardingWizard.markApplied();
253
+ let markerWarning: string | null = null;
254
+ try {
255
+ writeOnboardingCheckMarker(handler.uiServices.environment.shellPaths, {
256
+ scope: 'user',
257
+ source: 'wizard',
258
+ mode: request.mode,
259
+ });
260
+ } catch (error) {
261
+ markerWarning = error instanceof Error ? error.message : String(error);
262
+ }
243
263
  handler.onboardingWizard.close();
244
264
  for (let index = handler.modalStack.length - 1; index >= 0; index -= 1) {
245
265
  if (handler.modalStack[index] === 'onboarding') handler.modalStack.splice(index, 1);
246
266
  }
247
267
  if (handler.modalStack.length === 0) {
248
268
  const returnFocus = handler.modalReturnFocus;
249
- handler.panelFocused = returnFocus === 'panel';
269
+ handler.panelFocused = false;
250
270
  handler.indicatorFocused = returnFocus === 'indicator';
251
271
  handler.modalReturnFocus = 'prompt';
252
272
  }
253
- handler.commandContext?.print?.(formatOnboardingApplyCompletionMessage(verificationItems));
273
+ const completionMessage = formatOnboardingApplyCompletionMessage(verificationItems);
274
+ handler.commandContext?.print?.(markerWarning
275
+ ? `${completionMessage}\nSetup check marker could not be written: ${markerWarning}`
276
+ : completionMessage);
277
+ if (handler.commandContext) {
278
+ handler.openAgentWorkspace(handler.commandContext, 'setup');
279
+ return;
280
+ }
254
281
  handler.requestRender();
255
282
  }
256
283
 
@@ -275,10 +302,12 @@ export async function refreshOnboardingHydrationForHandler(handler: InputHandler
275
302
  },
276
303
  providerAccounts: {
277
304
  loadSnapshot: () => buildProviderAccountSnapshot({
278
- providerRegistry: handler.uiServices.providers.providerRegistry,
279
- serviceRegistry: handler.uiServices.platform.serviceRegistry,
280
- subscriptionManager: handler.uiServices.platform.subscriptionManager,
281
- secretsManager: handler.uiServices.platform.secretsManager,
305
+ providerModels: handler.uiServices.providers.providerRegistry,
306
+ services: handler.uiServices.platform.serviceRegistry,
307
+ subscriptions: handler.uiServices.platform.subscriptionManager,
308
+ environment: {
309
+ hasEnvironmentVariable: (name: string) => Boolean(process.env[name]),
310
+ },
282
311
  }),
283
312
  },
284
313
  });
@@ -300,7 +329,6 @@ export async function refreshOnboardingHydrationForHandler(handler: InputHandler
300
329
  export async function handleOpenAiSubscriptionStartForHandler(handler: InputHandler): Promise<void> {
301
330
  if (handler.onboardingApplyPending) return;
302
331
  handler.onboardingApplyPending = true;
303
- let listener: Awaited<ReturnType<typeof createOAuthLocalListener>> | null = null;
304
332
  try {
305
333
  const started = await beginOpenAICodexLogin();
306
334
  handler.uiServices.platform.subscriptionManager.savePending({
@@ -310,12 +338,6 @@ export async function handleOpenAiSubscriptionStartForHandler(handler: InputHand
310
338
  redirectUri: started.redirectUri,
311
339
  createdAt: Date.now(),
312
340
  });
313
- listener = await createOAuthLocalListener({
314
- expectedState: started.state,
315
- host: '127.0.0.1',
316
- port: 1455,
317
- path: '/auth/callback',
318
- }).catch(() => null);
319
341
  const browserOpened = await openExternalUrl(started.authorizationUrl);
320
342
  await handler.refreshOnboardingHydration({ preserveValues: true, targetStepId: 'provider-access' });
321
343
  handler.onboardingWizard.setFieldValue('providers.openai-authorization-url', started.authorizationUrl);
@@ -323,31 +345,14 @@ export async function handleOpenAiSubscriptionStartForHandler(handler: InputHand
323
345
  if (providerIndex >= 0) handler.onboardingWizard.setStep(providerIndex);
324
346
  handler.requestRender();
325
347
 
326
- if (listener && browserOpened) {
327
- const serial = ++handler.onboardingOpenAiListenerSerial;
328
- handler.commandContext?.print?.([
329
- 'OpenAI subscription sign-in started from onboarding.',
330
- ' callback listener: waiting on 127.0.0.1:1455',
331
- ' authorizationUrl: shown in the wizard provider step',
332
- 'You can also paste the callback code or URL into the OpenAI callback field.',
333
- ].join('\n'));
334
- void handler.completeOpenAiSubscriptionFromListener(listener, started.verifier, serial);
335
- listener = null;
336
- handler.requestRender();
337
- return;
338
- }
339
-
340
- listener?.close();
341
348
  handler.commandContext?.print?.([
342
349
  'OpenAI subscription sign-in started from onboarding.',
343
350
  ` browser: ${browserOpened ? 'opened' : 'open failed'}`,
344
- ` callback listener: ${listener ? 'ready' : 'unavailable'}`,
351
+ ' completion: paste callback code or URL into the OpenAI callback field',
345
352
  ' authorizationUrl: shown in the wizard provider step',
346
- 'Paste the callback code or URL into the OpenAI callback field after sign-in.',
347
353
  ].join('\n'));
348
354
  handler.requestRender();
349
355
  } catch (error) {
350
- listener?.close();
351
356
  handler.commandContext?.print?.([
352
357
  'OpenAI subscription sign-in could not start.',
353
358
  ` ${error instanceof Error ? error.message : String(error)}`,
@@ -358,51 +363,6 @@ export async function handleOpenAiSubscriptionStartForHandler(handler: InputHand
358
363
  }
359
364
  }
360
365
 
361
- export async function completeOpenAiSubscriptionFromListenerForHandler(
362
- handler: InputHandler,
363
- listener: Awaited<ReturnType<typeof createOAuthLocalListener>>,
364
- verifier: string,
365
- serial: number,
366
- ): Promise<void> {
367
- try {
368
- const callback = await listener.waitForCode();
369
- const pending = handler.uiServices.platform.subscriptionManager.getPending('openai');
370
- if (!pending || pending.verifier !== verifier || serial !== handler.onboardingOpenAiListenerSerial) return;
371
- const token = await exchangeOpenAICodexCode(callback.code, verifier);
372
- const now = Date.now();
373
- const existing = handler.uiServices.platform.subscriptionManager.get('openai');
374
- handler.uiServices.platform.subscriptionManager.saveSubscription({
375
- provider: 'openai',
376
- accessToken: token.accessToken,
377
- refreshToken: token.refreshToken,
378
- tokenType: token.tokenType,
379
- expiresAt: token.expiresAt,
380
- ...(token.scopes ? { scopes: token.scopes } : {}),
381
- authMode: 'oauth',
382
- overrideAmbientApiKeys: false,
383
- createdAt: existing?.createdAt ?? now,
384
- updatedAt: now,
385
- });
386
- handler.uiServices.platform.subscriptionManager.clearPending('openai');
387
- handler.onboardingOpenAiListenerSerial += 1;
388
- handler.commandContext?.print?.([
389
- 'OpenAI subscription sign-in completed from onboarding.',
390
- ` tokenType: ${token.tokenType}`,
391
- ` expiresAt: ${token.expiresAt ? new Date(token.expiresAt).toISOString() : 'n/a'}`,
392
- ].join('\n'));
393
- await handler.refreshOnboardingHydration({ preserveValues: true, targetStepId: 'provider-access' });
394
- } catch (error) {
395
- handler.commandContext?.print?.([
396
- 'OpenAI subscription listener could not complete automatically.',
397
- ` listener: ${error instanceof Error ? error.message : String(error)}`,
398
- 'Paste the callback code or URL into the OpenAI callback field to finish in onboarding.',
399
- ].join('\n'));
400
- handler.requestRender();
401
- } finally {
402
- listener.close();
403
- }
404
- }
405
-
406
366
  export async function handleOpenAiSubscriptionFinishForHandler(handler: InputHandler): Promise<void> {
407
367
  if (handler.onboardingApplyPending) return;
408
368
  const code = extractAuthorizationCode(handler.onboardingWizard.getTextFieldValue('providers.openai-callback-code'));
@@ -455,7 +415,7 @@ export async function handleOpenAiSubscriptionFinishForHandler(handler: InputHan
455
415
  }
456
416
 
457
417
  export function syncRuntimeFromOnboardingRequestForHandler(handler: InputHandler, request: ReturnType<OnboardingWizardController['buildApplyRequest']>): void {
458
- const runtime = handler.commandContext?.session.runtime;
418
+ const runtime = handler.commandContext?.session?.runtime;
459
419
  if (!runtime) return;
460
420
 
461
421
  for (const operation of request.operations) {
@@ -469,12 +429,13 @@ export function syncRuntimeFromOnboardingRequestForHandler(handler: InputHandler
469
429
  }
470
430
 
471
431
  export function getOnboardingConfigValueForHandler(handler: InputHandler, request: OnboardingApplyRequest, key: string): unknown {
472
- const config = handler.uiServices.platform.configManager;
432
+ const configManager: object = handler.uiServices.platform.configManager;
473
433
  for (let index = request.operations.length - 1; index >= 0; index -= 1) {
474
434
  const operation = request.operations[index];
475
435
  if (operation?.kind === 'set-config' && operation.key === key) return operation.value;
476
436
  }
477
- return config.get(key as never);
437
+ const get = Reflect.get(configManager, 'get');
438
+ return typeof get === 'function' ? Reflect.apply(get, configManager, [key]) : undefined;
478
439
  }
479
440
 
480
441
  export function getOnboardingRuntimePostureForHandler(handler: InputHandler, request: OnboardingApplyRequest): OnboardingRuntimePosture {
@@ -487,12 +448,12 @@ export function getOnboardingRuntimePostureForHandler(handler: InputHandler, req
487
448
  export async function restartOnboardingExternalServicesIfNeededForHandler(handler: InputHandler, request: OnboardingApplyRequest): Promise<OnboardingVerificationItem[]> {
488
449
  const externalServices = handler.uiServices.platform.externalServices;
489
450
  const state = externalServices?.inspect();
490
- const serviceStatus = state?.daemonStatus?.reason ?? (state?.daemonRunning ? 'connected GoodVibes services appear active' : 'connected GoodVibes services are not verified from this shell');
451
+ const hostStatus = state?.connectedHostStatus?.reason ?? (state?.connectedHostRunning ? 'connected GoodVibes host appears active' : 'connected GoodVibes host is not verified from this shell');
491
452
  return [{
492
- id: 'runtime:external-service-owned',
453
+ id: 'runtime:external-host-owned',
493
454
  status: 'pass',
494
- message: `GoodVibes Agent did not start, stop, restart, or reconfigure connected services. ${serviceStatus}`,
495
- target: 'service',
455
+ message: `GoodVibes Agent did not start, stop, restart, or reconfigure the connected host. ${hostStatus}`,
456
+ target: 'host',
496
457
  }];
497
458
  }
498
459
 
@@ -500,11 +461,11 @@ export function verifyOnboardingRuntimePostureForHandler(handler: InputHandler,
500
461
  const externalServices = handler.uiServices.platform.externalServices;
501
462
  const externalState = externalServices?.inspect();
502
463
  return [{
503
- id: 'runtime:external-service-owned',
464
+ id: 'runtime:external-host-owned',
504
465
  status: 'pass',
505
466
  message: externalState
506
- ? 'Connected GoodVibes services are managed outside Agent; Agent onboarding did not request shutdown, startup, restart, bind, or surface changes.'
507
- : 'Connected GoodVibes services are managed outside Agent; no local service controller is required for Agent onboarding.',
508
- target: 'service',
467
+ ? 'The connected GoodVibes host is managed outside Agent; Agent onboarding did not request shutdown, startup, restart, bind, or surface changes.'
468
+ : 'The connected GoodVibes host is managed outside Agent; no local host controller is required for Agent onboarding.',
469
+ target: 'host',
509
470
  }];
510
471
  }
@@ -37,12 +37,12 @@ export type GlobalShortcutRouteState = {
37
37
  handleBlockCopy: () => void;
38
38
  handleBookmark: () => void;
39
39
  handleBlockSave: () => void;
40
- handleDiffApply: () => boolean;
41
40
  handleUndo: () => void;
42
41
  handleRedo: () => void;
43
42
  handlePaste: () => void;
44
43
  handleEscape: () => void;
45
- cyclePanelTab: (direction: 'next' | 'prev') => void;
44
+ cycleAgentWorkspaceCategory: (direction: 'next' | 'prev') => void;
45
+ dismissAgentWorkspace: () => boolean;
46
46
  };
47
47
 
48
48
  export function handleGlobalShortcutToken(
@@ -87,6 +87,10 @@ export function handleGlobalShortcutToken(
87
87
  return true;
88
88
 
89
89
  case 'panel-close-all': {
90
+ if (state.dismissAgentWorkspace()) {
91
+ state.panelFocused = false;
92
+ return true;
93
+ }
90
94
  const pm = state.panelManager;
91
95
  for (const p of pm.getAllOpen()) pm.close(p.id);
92
96
  pm.hide();
@@ -96,6 +100,10 @@ export function handleGlobalShortcutToken(
96
100
  }
97
101
 
98
102
  case 'panel-close': {
103
+ if (state.dismissAgentWorkspace()) {
104
+ state.panelFocused = false;
105
+ return true;
106
+ }
99
107
  const pm = state.panelManager;
100
108
  const active = pm.getActivePanel();
101
109
  if (active) {
@@ -108,16 +116,18 @@ export function handleGlobalShortcutToken(
108
116
 
109
117
  case 'panel-picker':
110
118
  state.commandContext?.openPanelPicker?.();
111
- state.panelFocused = state.panelManager.isVisible() && state.panelManager.getAllOpen().length > 0;
119
+ state.panelFocused = false;
112
120
  state.requestRender();
113
121
  return true;
114
122
 
115
123
  case 'panel-tab-next':
116
- state.cyclePanelTab('next');
124
+ state.cycleAgentWorkspaceCategory('next');
125
+ state.panelFocused = false;
117
126
  return true;
118
127
 
119
128
  case 'panel-tab-prev':
120
- state.cyclePanelTab('prev');
129
+ state.cycleAgentWorkspaceCategory('prev');
130
+ state.panelFocused = false;
121
131
  return true;
122
132
 
123
133
  case 'history-search':
@@ -154,8 +164,7 @@ export function handleGlobalShortcutToken(
154
164
  return true;
155
165
  }
156
166
 
157
- case 'apply-diff-line-start': {
158
- if (!state.commandMode && state.handleDiffApply()) return true;
167
+ case 'line-start': {
159
168
  const info = state.getWrappedPromptInfo(state.contentWidth);
160
169
  state.cursorPos = info.wrappedLines.length > 1 ? info.segments[info.cursorWrappedLine].rawStart : 0;
161
170
  state.ensureInputCursorVisible();
@@ -1,12 +1,8 @@
1
1
  import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
2
2
  import { dirname } from 'node:path';
3
3
  import { InputTokenizer } from '@pellux/goodvibes-sdk/platform/core';
4
- import { createOAuthLocalListener } from '@pellux/goodvibes-sdk/platform/config';
5
- import { clearModalStackForHandler, cleanupMarkerRegistryForHandler, executeBlockActionForHandler, expandPromptForHandler, findMarkerAtPosForHandler, getImageAttachmentsForHandler, handleBlockCopyForHandler, handleBlockRerunForHandler, handleBlockSaveForHandler, handleBlockToggleForHandler, handleBookmarkForHandler, handleCopyForHandler, handleCtrlCForHandler, handleDiffApplyForHandler, handleEscapeForHandler, hydrateOnboardingWizardFromRuntimeForHandler, modalOpenedForHandler, openOnboardingWizardForHandler, registerPasteForHandler } from './handler-interactions.ts';
6
- import { clearOnboardingModelPickerCancelStateForHandler, clearOnboardingPendingModelPickerTargetForHandler, completeOpenAiSubscriptionFromListenerForHandler, getOnboardingConfigValueForHandler, getOnboardingRuntimePostureForHandler, handleModelPickerCommitForHandler, handleOnboardingActionForHandler, handleOpenAiSubscriptionFinishForHandler, handleOpenAiSubscriptionStartForHandler, openModelPickerWithTargetForHandler, openProviderModelPickerWithTargetForHandler, refreshOnboardingHydrationForHandler, restartOnboardingExternalServicesIfNeededForHandler, restoreOnboardingModelPickerCancelStateForHandler, syncRuntimeFromOnboardingRequestForHandler, verifyOnboardingRuntimePostureForHandler, type OnboardingRuntimePosture } from './handler-onboarding.ts';
7
- import { beginOpenAICodexLogin, exchangeOpenAICodexCode } from '@pellux/goodvibes-sdk/platform/config';
8
- import { openExternalUrl } from '@pellux/goodvibes-sdk/platform/utils';
9
- import { buildProviderAccountSnapshot } from '@/runtime/index.ts';
4
+ import { clearModalStackForHandler, cleanupMarkerRegistryForHandler, executeBlockActionForHandler, expandPromptForHandler, findMarkerAtPosForHandler, getImageAttachmentsForHandler, handleBlockCopyForHandler, handleBlockRerunForHandler, handleBlockSaveForHandler, handleBlockToggleForHandler, handleBookmarkForHandler, handleCopyForHandler, handleCtrlCForHandler, handleEscapeForHandler, hydrateOnboardingWizardFromRuntimeForHandler, modalOpenedForHandler, openOnboardingWizardForHandler, registerPasteForHandler } from './handler-interactions.ts';
5
+ import { clearOnboardingModelPickerCancelStateForHandler, clearOnboardingPendingModelPickerTargetForHandler, getOnboardingConfigValueForHandler, getOnboardingRuntimePostureForHandler, handleModelPickerCommitForHandler, handleOnboardingActionForHandler, handleOpenAiSubscriptionFinishForHandler, handleOpenAiSubscriptionStartForHandler, openModelPickerWithTargetForHandler, openProviderModelPickerWithTargetForHandler, refreshOnboardingHydrationForHandler, restartOnboardingExternalServicesIfNeededForHandler, restoreOnboardingModelPickerCancelStateForHandler, syncRuntimeFromOnboardingRequestForHandler, verifyOnboardingRuntimePostureForHandler, type OnboardingRuntimePosture } from './handler-onboarding.ts';
10
6
  import { SelectionManager } from './selection.ts';
11
7
  import type { InfiniteBuffer } from '../core/history.ts';
12
8
  import type { CommandRegistry, CommandContext } from './command-registry.ts';
@@ -56,7 +52,6 @@ import {
56
52
  handleClipboardPaste,
57
53
  handleCopy,
58
54
  handleCtrlC,
59
- handleDiffApply,
60
55
  mediaTypeFromExt,
61
56
  registerPaste,
62
57
  } from './handler-content-actions.ts';
@@ -164,14 +159,13 @@ export class InputHandler {
164
159
  public onboardingModelPickerCancelSnapshot: OnboardingWizardSnapshot | null = null;
165
160
  public onboardingHydrationSerial = 0;
166
161
  public onboardingApplyPending = false;
167
- public onboardingOpenAiListenerSerial = 0;
168
162
 
169
163
  /**
170
164
  * Modal navigation stack. Each element is the name of an open modal.
171
165
  * Used to support back-navigation via Escape.
172
166
  */
173
167
  public modalStack: string[] = [];
174
- public modalReturnFocus: 'prompt' | 'panel' | 'indicator' = 'prompt';
168
+ public modalReturnFocus: 'prompt' | 'indicator' = 'prompt';
175
169
  public sessionPickerModal: SessionPickerModal;
176
170
  public profilePickerModal: ProfilePickerModal;
177
171
  /** True when the help overlay is visible. */
@@ -302,7 +296,6 @@ export class InputHandler {
302
296
  handleBlockCopy: () => this.handleBlockCopy(),
303
297
  handleBookmark: () => this.handleBookmark(),
304
298
  handleBlockSave: () => this.handleBlockSave(),
305
- handleDiffApply: () => this.handleDiffApply(),
306
299
  handleUndo: () => { this.handleUndo(); this.syncFeedContextMutableFields(); },
307
300
  handleRedo: () => { this.handleRedo(); this.syncFeedContextMutableFields(); },
308
301
  handlePaste: () => { this.handlePaste(); this.syncFeedContextMutableFields(); },
@@ -310,7 +303,8 @@ export class InputHandler {
310
303
  ensureInputCursorVisible: (contentWidth?: number) => this.ensureInputCursorVisible(contentWidth),
311
304
  registerPaste: (content: string) => this.registerPaste(content),
312
305
  executeBlockAction: (id: string) => this.executeBlockAction(id),
313
- cyclePanelTab: (direction: 'next' | 'prev') => this.cyclePanelTab(direction),
306
+ cycleAgentWorkspaceCategory: (direction: 'next' | 'prev') => this.cycleAgentWorkspaceCategory(direction),
307
+ dismissAgentWorkspace: () => this.dismissAgentWorkspace(),
314
308
  onPanelInputConsumed: (activePanel: Panel | null, key: string) => this.handlePanelIntegrationAction(activePanel, key),
315
309
  getWrappedPromptInfo: (contentWidth: number) => this.getWrappedPromptInfo(contentWidth),
316
310
  moveCursorVertical: (direction: -1 | 1) => this.moveCursorVertical(direction),
@@ -351,7 +345,7 @@ export class InputHandler {
351
345
  this.autocomplete = new AutocompleteEngine(registry);
352
346
  }
353
347
 
354
- /** Wire in the conversation manager for block copy/apply/collapse. */
348
+ /** Wire in the conversation manager for block copy/collapse. */
355
349
  public setConversationManager(cm: ConversationManager): void { this.conversationManager = cm; }
356
350
 
357
351
  /**
@@ -393,7 +387,6 @@ export class InputHandler {
393
387
  public executeBlockAction(actionId: string): void { executeBlockActionForHandler(this, actionId); }
394
388
  public handleBlockRerun(): void { handleBlockRerunForHandler(this); }
395
389
  public handleBlockToggle(): void { handleBlockToggleForHandler(this); }
396
- public handleDiffApply(): boolean { return handleDiffApplyForHandler(this); }
397
390
  public handleCtrlC(): void { handleCtrlCForHandler(this); }
398
391
  public modalOpened(name: string): void { modalOpenedForHandler(this, name); }
399
392
  public clearModalStack(): void { clearModalStackForHandler(this); }
@@ -411,19 +404,37 @@ export class InputHandler {
411
404
  this.mcpWorkspace.open(context);
412
405
  this.requestRender();
413
406
  }
414
- public openAgentWorkspace(context: CommandContext): void {
407
+ public openAgentWorkspace(context: CommandContext, categoryId?: string): void {
415
408
  this.panelFocused = false;
416
409
  this.indicatorFocused = false;
417
410
  this.modalOpened('agentWorkspace');
418
- this.agentWorkspace.open(context, (command) => this.dispatchAgentWorkspaceCommand(command, context));
411
+ this.agentWorkspace.open(
412
+ context,
413
+ (command) => this.dispatchAgentWorkspaceCommand(command, context),
414
+ categoryId,
415
+ (prompt) => this.dispatchAgentWorkspacePrompt(prompt, context),
416
+ );
419
417
  this.requestRender();
420
418
  }
421
419
 
422
- private dispatchAgentWorkspaceCommand(command: string, context: CommandContext): void {
420
+ private closeAgentWorkspaceModal(): void {
423
421
  this.agentWorkspace.close();
424
422
  for (let index = this.modalStack.length - 1; index >= 0; index -= 1) {
425
423
  if (this.modalStack[index] === 'agentWorkspace') this.modalStack.splice(index, 1);
426
424
  }
425
+ }
426
+
427
+ public dismissAgentWorkspace(): boolean {
428
+ if (!this.agentWorkspace.active) return false;
429
+ this.closeAgentWorkspaceModal();
430
+ this.panelFocused = false;
431
+ this.indicatorFocused = false;
432
+ this.requestRender();
433
+ return true;
434
+ }
435
+
436
+ private dispatchAgentWorkspaceCommand(command: string, context: CommandContext): void {
437
+ this.closeAgentWorkspaceModal();
427
438
  const { name, args } = parseSlashCommand(command);
428
439
  if (!name) return;
429
440
  void context.executeCommand?.(name, [...args]).catch((error: unknown) => {
@@ -432,11 +443,16 @@ export class InputHandler {
432
443
  });
433
444
  this.requestRender();
434
445
  }
446
+
447
+ private dispatchAgentWorkspacePrompt(prompt: string, context: CommandContext): void {
448
+ this.closeAgentWorkspaceModal();
449
+ context.submitInput?.(prompt);
450
+ this.requestRender();
451
+ }
435
452
  public handleModelPickerCommit(): boolean { return handleModelPickerCommitForHandler(this); }
436
453
  public async handleOnboardingAction(action: OnboardingWizardAction): Promise<void> { await handleOnboardingActionForHandler(this, action); }
437
454
  public async refreshOnboardingHydration(options: { readonly preserveValues?: boolean; readonly targetStepId?: string } = {}): Promise<void> { await refreshOnboardingHydrationForHandler(this, options); }
438
455
  public async handleOpenAiSubscriptionStart(): Promise<void> { await handleOpenAiSubscriptionStartForHandler(this); }
439
- public async completeOpenAiSubscriptionFromListener(listener: Awaited<ReturnType<typeof createOAuthLocalListener>>, verifier: string, serial: number): Promise<void> { await completeOpenAiSubscriptionFromListenerForHandler(this, listener, verifier, serial); }
440
456
  public async handleOpenAiSubscriptionFinish(): Promise<void> { await handleOpenAiSubscriptionFinishForHandler(this); }
441
457
  public syncRuntimeFromOnboardingRequest(request: ReturnType<OnboardingWizardController['buildApplyRequest']>): void { syncRuntimeFromOnboardingRequestForHandler(this, request); }
442
458
  public getOnboardingConfigValue(request: OnboardingApplyRequest, key: string): unknown { return getOnboardingConfigValueForHandler(this, request, key); }
@@ -696,13 +712,24 @@ export class InputHandler {
696
712
  * Word-wrap a single line to fit within maxW columns.
697
713
  * Breaks at spaces; words wider than maxW are force-broken.
698
714
  */
699
- public cyclePanelTab(direction: 'next' | 'prev'): void {
700
- const pm = this.uiServices.shell.panelManager;
701
- if (pm.isVisible()) {
702
- if (direction === 'next') pm.nextWorkspaceTab();
703
- else pm.prevWorkspaceTab();
704
- this.requestRender();
715
+ public cycleAgentWorkspaceCategory(direction: 'next' | 'prev'): void {
716
+ const context = this.commandContext;
717
+ if (!this.agentWorkspace.active) {
718
+ if (!context) return;
719
+ this.panelFocused = false;
720
+ this.indicatorFocused = false;
721
+ this.modalOpened('agentWorkspace');
722
+ this.agentWorkspace.open(
723
+ context,
724
+ (command) => this.dispatchAgentWorkspaceCommand(command, context),
725
+ undefined,
726
+ (prompt) => this.dispatchAgentWorkspacePrompt(prompt, context),
727
+ );
705
728
  }
729
+ this.agentWorkspace.cycleCategory(direction);
730
+ this.panelFocused = false;
731
+ this.indicatorFocused = false;
732
+ this.requestRender();
706
733
  }
707
734
 
708
735
  public handlePanelIntegrationAction(activePanel: Panel | null, key: string): void {
@@ -284,13 +284,12 @@ export class InputHistory {
284
284
 
285
285
  private normalizeStoredEntry(entry: unknown): StoredInputHistoryEntry | null {
286
286
  if (typeof entry === 'string') return entry;
287
- if (!entry || typeof entry !== 'object') return null;
288
- const record = entry as Record<string, unknown>;
289
- if (typeof record.text !== 'string') return null;
290
- const text = record.text.trim();
287
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) return null;
288
+ if (!('text' in entry) || typeof entry.text !== 'string') return null;
289
+ const text = entry.text.trim();
291
290
  if (!text) return null;
292
- if (typeof record.recallText === 'string' && record.recallText.trim() && record.recallText.trim() !== text) {
293
- return { text, recallText: record.recallText.trim() };
291
+ if ('recallText' in entry && typeof entry.recallText === 'string' && entry.recallText.trim() && entry.recallText.trim() !== text) {
292
+ return { text, recallText: entry.recallText.trim() };
294
293
  }
295
294
  return text;
296
295
  }
@@ -45,7 +45,7 @@ export type KeyAction =
45
45
  | 'bookmark'
46
46
  | 'block-save'
47
47
  | 'delete-word'
48
- | 'apply-diff-line-start'
48
+ | 'line-start'
49
49
  | 'next-error-line-end'
50
50
  | 'kill-line'
51
51
  | 'clear-prompt'
@@ -59,27 +59,40 @@ export const ACTION_DESCRIPTIONS: Record<KeyAction, string> = {
59
59
  'copy-selection': 'Copy selected text to clipboard',
60
60
  'clear-cancel': 'Clear input / cancel generation / exit (double)',
61
61
  'screen-clear': 'Repaint the screen',
62
- 'panel-picker': 'Open, focus, or hide the panel workspace',
63
- 'panel-close': 'Close the currently active panel',
64
- 'panel-close-all': 'Close all open panels',
65
- 'panel-tab-next': 'Next workspace panel tab',
66
- 'panel-tab-prev': 'Previous workspace panel tab',
62
+ 'panel-picker': 'Open the Agent operator workspace',
63
+ 'panel-close': 'Dismiss Agent workspace',
64
+ 'panel-close-all': 'Dismiss Agent workspace focus',
65
+ 'panel-tab-next': 'Cycle Agent workspace category forward',
66
+ 'panel-tab-prev': 'Cycle Agent workspace category backward',
67
67
  'history-search': 'Reverse input history search',
68
68
  'search': 'Toggle conversation search',
69
69
  'block-copy': 'Copy nearest block to clipboard',
70
70
  'bookmark': 'Bookmark / unbookmark nearest block',
71
71
  'block-save': 'Block file save disabled; copy block or export conversation',
72
72
  'delete-word': 'Delete word backward',
73
- 'apply-diff-line-start': 'Delegate diff changes / move to line start',
73
+ 'line-start': 'Move to start of line',
74
74
  'next-error-line-end': 'Navigate to next error / move to line end',
75
75
  'kill-line': 'Kill to end of line',
76
76
  'clear-prompt': 'Clear the prompt',
77
77
  'undo': 'Undo last prompt edit',
78
78
  'redo': 'Redo last undone edit',
79
79
  'paste': 'Paste from clipboard (image priority)',
80
- 'replay-panel': 'Open / close the Replay panel',
80
+ 'replay-panel': 'Reserved replay workspace shortcut',
81
81
  };
82
82
 
83
+ function isKeyAction(action: string): action is KeyAction {
84
+ return Object.hasOwn(ACTION_DESCRIPTIONS, action);
85
+ }
86
+
87
+ function isKeyCombo(value: unknown): value is KeyCombo {
88
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
89
+ if (!('key' in value) || typeof value.key !== 'string' || value.key.length === 0) return false;
90
+ if ('ctrl' in value && value.ctrl !== undefined && typeof value.ctrl !== 'boolean') return false;
91
+ if ('shift' in value && value.shift !== undefined && typeof value.shift !== 'boolean') return false;
92
+ if ('alt' in value && value.alt !== undefined && typeof value.alt !== 'boolean') return false;
93
+ return true;
94
+ }
95
+
83
96
  /** Default key bindings for all actions. */
84
97
  export const DEFAULT_KEYBINDINGS: Record<KeyAction, KeyCombo[]> = {
85
98
  'copy-selection': [{ key: 'c', ctrl: true, shift: true }],
@@ -96,7 +109,7 @@ export const DEFAULT_KEYBINDINGS: Record<KeyAction, KeyCombo[]> = {
96
109
  'bookmark': [{ key: 'b', ctrl: true }],
97
110
  'block-save': [{ key: 's', ctrl: true }],
98
111
  'delete-word': [{ key: 'w', ctrl: true }],
99
- 'apply-diff-line-start': [{ key: 'a', ctrl: true }],
112
+ 'line-start': [{ key: 'a', ctrl: true }],
100
113
  'next-error-line-end': [{ key: 'e', ctrl: true }],
101
114
  'kill-line': [{ key: 'k', ctrl: true }],
102
115
  'clear-prompt': [{ key: 'u', ctrl: true }],
@@ -218,11 +231,7 @@ export class KeybindingsManager {
218
231
  }
219
232
 
220
233
  private validateCombos(combos: unknown[]): combos is KeyCombo[] {
221
- return combos.every((c) => {
222
- if (typeof c !== 'object' || c === null) return false;
223
- const combo = c as Record<string, unknown>;
224
- return typeof combo['key'] === 'string' && combo['key'].length > 0;
225
- });
234
+ return combos.every(isKeyCombo);
226
235
  }
227
236
 
228
237
  /**
@@ -233,9 +242,10 @@ export class KeybindingsManager {
233
242
  * Expects: { logicalName: string; ctrl?: boolean; shift?: boolean; alt?: boolean }
234
243
  */
235
244
  matches(
236
- action: KeyAction,
245
+ action: string,
237
246
  token: { logicalName?: string; ctrl?: boolean; shift?: boolean; alt?: boolean },
238
247
  ): boolean {
248
+ if (!isKeyAction(action)) return false;
239
249
  const combos = this.bindings[action];
240
250
  if (!combos) return false;
241
251
  return combos.some((combo) => this.comboMatches(combo, token));
@@ -267,7 +277,8 @@ export class KeybindingsManager {
267
277
  * getComboLabel — Return a human-readable label for the first combo of an action.
268
278
  * Example: { key: 'f', ctrl: true } → "Ctrl+F"
269
279
  */
270
- getComboLabel(action: KeyAction): string {
280
+ getComboLabel(action: string): string {
281
+ if (!isKeyAction(action)) return '(unbound)';
271
282
  const combos = this.bindings[action];
272
283
  if (!combos?.length) return '(unbound)';
273
284
  return this.formatCombo(combos[0]);