@pellux/goodvibes-agent 0.1.117 → 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 (253) hide show
  1. package/CHANGELOG.md +13 -4
  2. package/README.md +35 -14
  3. package/bin/goodvibes-agent.ts +16 -2
  4. package/dist/package/main.js +176073 -170980
  5. package/docs/README.md +11 -5
  6. package/docs/channels-remote-and-api.md +50 -0
  7. package/docs/connected-host.md +3 -3
  8. package/docs/getting-started.md +29 -15
  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 +44 -9
  13. package/docs/tools-and-commands.md +123 -0
  14. package/docs/voice-and-live-tts.md +51 -0
  15. package/package.json +2 -5
  16. package/src/agent/channel-delivery.ts +201 -0
  17. package/src/agent/media-generation.ts +159 -0
  18. package/src/agent/memory-prompt.ts +0 -1
  19. package/src/agent/note-registry.ts +329 -0
  20. package/src/agent/operator-actions.ts +343 -0
  21. package/src/agent/persona-registry.ts +15 -14
  22. package/src/agent/record-labels.ts +107 -0
  23. package/src/agent/reminder-schedule-format.ts +33 -24
  24. package/src/agent/reminder-schedule.ts +26 -25
  25. package/src/agent/routine-registry.ts +13 -12
  26. package/src/agent/routine-schedule-args.ts +2 -1
  27. package/src/agent/routine-schedule-format.ts +77 -53
  28. package/src/agent/routine-schedule-promotion.ts +34 -32
  29. package/src/agent/routine-schedule-receipts.ts +28 -26
  30. package/src/agent/runtime-profile-starters.ts +2 -2
  31. package/src/agent/runtime-profile.ts +18 -17
  32. package/src/agent/skill-registry.ts +25 -24
  33. package/src/cli/agent-knowledge-args.ts +5 -1
  34. package/src/cli/agent-knowledge-command.ts +39 -33
  35. package/src/cli/agent-knowledge-format.ts +80 -67
  36. package/src/cli/agent-knowledge-methods.ts +1 -1
  37. package/src/cli/agent-knowledge-runtime.ts +32 -26
  38. package/src/cli/bundle-command.ts +13 -8
  39. package/src/cli/config-overrides.ts +37 -36
  40. package/src/cli/external-runtime.ts +10 -4
  41. package/src/cli/help.ts +29 -11
  42. package/src/cli/local-library-command.ts +134 -68
  43. package/src/cli/management-commands.ts +98 -62
  44. package/src/cli/management.ts +52 -26
  45. package/src/cli/memory-command.ts +66 -32
  46. package/src/cli/parser.ts +37 -24
  47. package/src/cli/profiles-command.ts +52 -35
  48. package/src/cli/provider-auth-routes.ts +2 -1
  49. package/src/cli/routines-command.ts +44 -36
  50. package/src/cli/service-posture.ts +6 -6
  51. package/src/cli/status.ts +46 -121
  52. package/src/core/conversation-message-snapshot.ts +131 -0
  53. package/src/input/agent-workspace-activation.ts +33 -7
  54. package/src/input/agent-workspace-basic-command-editor-submission.ts +7 -3
  55. package/src/input/agent-workspace-basic-command-editors.ts +30 -10
  56. package/src/input/agent-workspace-categories.ts +276 -64
  57. package/src/input/agent-workspace-channel-command-editor-submission.ts +34 -0
  58. package/src/input/agent-workspace-channel-command-editors.ts +23 -5
  59. package/src/input/agent-workspace-channels.ts +35 -2
  60. package/src/input/agent-workspace-command-editor.ts +18 -2
  61. package/src/input/agent-workspace-config-reader.ts +16 -0
  62. package/src/input/agent-workspace-delegation-editor-submission.ts +1 -1
  63. package/src/input/agent-workspace-editors.ts +140 -2
  64. package/src/input/agent-workspace-knowledge-query-editor.ts +1 -1
  65. package/src/input/agent-workspace-learned-behavior.ts +2 -2
  66. package/src/input/agent-workspace-library-command-editor-submission.ts +1 -1
  67. package/src/input/agent-workspace-library-command-editors.ts +2 -2
  68. package/src/input/agent-workspace-local-operations.ts +218 -0
  69. package/src/input/agent-workspace-local-selection.ts +75 -0
  70. package/src/input/agent-workspace-media-command-editor-submission.ts +62 -0
  71. package/src/input/agent-workspace-media-command-editors.ts +27 -0
  72. package/src/input/agent-workspace-memory-command-editors.ts +1 -1
  73. package/src/input/agent-workspace-memory-editor.ts +2 -2
  74. package/src/input/agent-workspace-navigation.ts +38 -2
  75. package/src/input/agent-workspace-notify-editor-submission.ts +16 -2
  76. package/src/input/agent-workspace-operations-command-editor-submission.ts +63 -1
  77. package/src/input/agent-workspace-operations-command-editors.ts +80 -3
  78. package/src/input/agent-workspace-panel-route.ts +43 -0
  79. package/src/input/agent-workspace-reminder-schedule-editor.ts +1 -1
  80. package/src/input/agent-workspace-routine-schedule-editor.ts +2 -2
  81. package/src/input/agent-workspace-search.ts +169 -0
  82. package/src/input/agent-workspace-setup.ts +22 -11
  83. package/src/input/agent-workspace-skill-bundle-command-editor-submission.ts +6 -6
  84. package/src/input/agent-workspace-skill-bundle-command-editors.ts +10 -10
  85. package/src/input/agent-workspace-snapshot.ts +112 -13
  86. package/src/input/agent-workspace-task-command-editors.ts +4 -4
  87. package/src/input/agent-workspace-token.ts +18 -2
  88. package/src/input/agent-workspace-types.ts +92 -4
  89. package/src/input/agent-workspace-voice-media.ts +3 -6
  90. package/src/input/agent-workspace-web-research-editor.ts +104 -0
  91. package/src/input/agent-workspace.ts +136 -208
  92. package/src/input/command-registry.ts +4 -1
  93. package/src/input/commands/agent-runtime-profile-runtime.ts +45 -41
  94. package/src/input/commands/agent-skills-runtime.ts +83 -70
  95. package/src/input/commands/agent-workspace-runtime.ts +23 -7
  96. package/src/input/commands/brief-runtime.ts +25 -24
  97. package/src/input/commands/channels-runtime.ts +145 -31
  98. package/src/input/commands/delegation-runtime.ts +29 -23
  99. package/src/input/commands/experience-runtime.ts +6 -4
  100. package/src/input/commands/guidance-runtime.ts +4 -4
  101. package/src/input/commands/health-runtime.ts +140 -115
  102. package/src/input/commands/knowledge.ts +57 -56
  103. package/src/input/commands/local-provider-runtime.ts +36 -18
  104. package/src/input/commands/local-runtime.ts +138 -16
  105. package/src/input/commands/local-setup-review.ts +7 -7
  106. package/src/input/commands/mcp-runtime.ts +56 -35
  107. package/src/input/commands/notify-runtime.ts +38 -9
  108. package/src/input/commands/operator-actions-runtime.ts +138 -0
  109. package/src/input/commands/operator-runtime.ts +6 -17
  110. package/src/input/commands/personas-runtime.ts +45 -30
  111. package/src/input/commands/planning-runtime.ts +1 -1
  112. package/src/input/commands/platform-access-runtime.ts +29 -29
  113. package/src/input/commands/provider-accounts-runtime.ts +60 -39
  114. package/src/input/commands/qrcode-runtime.ts +45 -6
  115. package/src/input/commands/recall-bundle.ts +11 -11
  116. package/src/input/commands/recall-capture.ts +13 -11
  117. package/src/input/commands/recall-query.ts +29 -21
  118. package/src/input/commands/recall-review.ts +2 -1
  119. package/src/input/commands/routines-runtime.ts +59 -43
  120. package/src/input/commands/schedule-runtime.ts +33 -20
  121. package/src/input/commands/security-runtime.ts +4 -4
  122. package/src/input/commands/session-content.ts +80 -78
  123. package/src/input/commands/session-workflow.ts +132 -93
  124. package/src/input/commands/session.ts +3 -174
  125. package/src/input/commands/shell-core.ts +32 -17
  126. package/src/input/commands/subscription-runtime.ts +53 -179
  127. package/src/input/commands/tasks-runtime.ts +20 -20
  128. package/src/input/commands/work-plan-runtime.ts +33 -8
  129. package/src/input/commands.ts +2 -2
  130. package/src/input/feed-context-factory.ts +2 -1
  131. package/src/input/file-picker.ts +3 -2
  132. package/src/input/handler-command-route.ts +4 -7
  133. package/src/input/handler-content-actions.ts +89 -1
  134. package/src/input/handler-feed-routes.ts +19 -12
  135. package/src/input/handler-feed.ts +6 -3
  136. package/src/input/handler-interactions.ts +7 -5
  137. package/src/input/handler-modal-stack.ts +3 -3
  138. package/src/input/handler-onboarding.ts +24 -80
  139. package/src/input/handler-shortcuts.ts +15 -4
  140. package/src/input/handler.ts +47 -17
  141. package/src/input/input-history.ts +5 -6
  142. package/src/input/keybindings.ts +22 -11
  143. package/src/input/onboarding/onboarding-wizard-apply.ts +13 -0
  144. package/src/input/onboarding/onboarding-wizard-constants.ts +1 -0
  145. package/src/input/onboarding/onboarding-wizard-operator-steps.ts +147 -57
  146. package/src/input/onboarding/onboarding-wizard-steps.ts +46 -29
  147. package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
  148. package/src/input/onboarding/onboarding-wizard.ts +68 -0
  149. package/src/input/profile-picker-modal.ts +31 -12
  150. package/src/input/session-picker-modal.ts +21 -4
  151. package/src/input/settings-modal-behavior.ts +0 -3
  152. package/src/input/settings-modal-subscriptions.ts +3 -3
  153. package/src/input/settings-modal-types.ts +2 -13
  154. package/src/input/settings-modal.ts +6 -52
  155. package/src/input/submission-intent.ts +0 -1
  156. package/src/input/submission-router.ts +3 -3
  157. package/src/main.ts +18 -8
  158. package/src/panels/approval-panel.ts +8 -8
  159. package/src/panels/automation-control-panel.ts +2 -2
  160. package/src/panels/base-panel.ts +1 -1
  161. package/src/panels/builtin/agent.ts +1 -1
  162. package/src/panels/builtin/operations.ts +1 -10
  163. package/src/panels/builtin/session.ts +9 -9
  164. package/src/panels/builtin/shared.ts +2 -2
  165. package/src/panels/cost-tracker-panel.ts +1 -1
  166. package/src/panels/docs-panel.ts +5 -3
  167. package/src/panels/index.ts +0 -1
  168. package/src/panels/knowledge-panel.ts +6 -5
  169. package/src/panels/memory-panel.ts +7 -6
  170. package/src/panels/panel-list-panel.ts +36 -80
  171. package/src/panels/project-planning-panel.ts +18 -11
  172. package/src/panels/provider-account-snapshot.ts +51 -25
  173. package/src/panels/provider-accounts-panel.ts +33 -18
  174. package/src/panels/provider-health-domains.ts +45 -4
  175. package/src/panels/provider-health-panel.ts +5 -4
  176. package/src/panels/qr-panel.ts +1 -1
  177. package/src/panels/schedule-panel.ts +9 -17
  178. package/src/panels/security-panel.ts +8 -8
  179. package/src/panels/session-browser-panel.ts +10 -10
  180. package/src/panels/subscription-panel.ts +3 -3
  181. package/src/panels/system-messages-panel.ts +1 -1
  182. package/src/panels/tasks-panel.ts +20 -13
  183. package/src/panels/tool-inspector-panel.ts +19 -12
  184. package/src/panels/work-plan-panel.ts +5 -5
  185. package/src/planning/project-planning-coordinator.ts +1 -1
  186. package/src/provider-auth-route-display.ts +9 -0
  187. package/src/renderer/agent-workspace-style.ts +34 -0
  188. package/src/renderer/agent-workspace.ts +228 -52
  189. package/src/renderer/autocomplete-overlay.ts +25 -6
  190. package/src/renderer/bookmark-modal.ts +19 -4
  191. package/src/renderer/buffer.ts +4 -2
  192. package/src/renderer/context-inspector.ts +50 -13
  193. package/src/renderer/diff.ts +1 -1
  194. package/src/renderer/file-picker-overlay.ts +19 -6
  195. package/src/renderer/help-overlay.ts +106 -33
  196. package/src/renderer/history-search-overlay.ts +19 -4
  197. package/src/renderer/live-tail-modal.ts +27 -5
  198. package/src/renderer/mcp-workspace.ts +164 -50
  199. package/src/renderer/model-picker-overlay.ts +58 -2
  200. package/src/renderer/model-workspace.ts +104 -20
  201. package/src/renderer/process-modal.ts +27 -6
  202. package/src/renderer/profile-picker-modal.ts +20 -4
  203. package/src/renderer/search-overlay.ts +25 -5
  204. package/src/renderer/selection-modal-overlay.ts +46 -14
  205. package/src/renderer/session-picker-modal.ts +18 -1
  206. package/src/renderer/settings-modal-helpers.ts +2 -40
  207. package/src/renderer/settings-modal.ts +83 -50
  208. package/src/renderer/tool-call.ts +20 -11
  209. package/src/runtime/agent-runtime-events.ts +6 -6
  210. package/src/runtime/bootstrap-command-context.ts +7 -1
  211. package/src/runtime/bootstrap-command-parts.ts +9 -7
  212. package/src/runtime/bootstrap-core.ts +19 -1
  213. package/src/runtime/bootstrap-hook-bridge.ts +7 -18
  214. package/src/runtime/bootstrap-shell.ts +4 -4
  215. package/src/runtime/bootstrap.ts +31 -22
  216. package/src/runtime/connected-host-auth.ts +7 -2
  217. package/src/runtime/diagnostics/panels/index.ts +2 -2
  218. package/src/runtime/diagnostics/panels/policy.ts +7 -7
  219. package/src/runtime/index.ts +2 -1
  220. package/src/runtime/onboarding/apply-file-helpers.ts +66 -0
  221. package/src/runtime/onboarding/apply.ts +80 -79
  222. package/src/runtime/onboarding/derivation.ts +5 -8
  223. package/src/runtime/onboarding/snapshot.ts +0 -15
  224. package/src/runtime/onboarding/types.ts +8 -19
  225. package/src/runtime/onboarding/verify.ts +32 -10
  226. package/src/runtime/operator-token-cleanup.ts +1 -1
  227. package/src/runtime/services.ts +91 -22
  228. package/src/runtime/store/selectors/index.ts +3 -3
  229. package/src/runtime/store/state.ts +1 -4
  230. package/src/runtime/surface-feature-flags.ts +41 -6
  231. package/src/runtime/ui-read-models.ts +3 -4
  232. package/src/runtime/ui-services.ts +6 -6
  233. package/src/shell/blocking-input.ts +2 -1
  234. package/src/shell/ui-openers.ts +3 -13
  235. package/src/tools/agent-analysis-registry-policy.ts +0 -1
  236. package/src/tools/agent-channel-send-tool.ts +133 -0
  237. package/src/tools/agent-context-policy.ts +1 -1
  238. package/src/tools/agent-knowledge-ingest-tool.ts +405 -0
  239. package/src/tools/agent-knowledge-tool.ts +170 -0
  240. package/src/tools/agent-local-registry-tool.ts +269 -69
  241. package/src/tools/agent-media-generate-tool.ts +133 -0
  242. package/src/tools/agent-notify-tool.ts +143 -0
  243. package/src/tools/agent-operator-action-tool.ts +137 -0
  244. package/src/tools/agent-operator-briefing-tool.ts +217 -0
  245. package/src/tools/agent-reminder-schedule-tool.ts +237 -0
  246. package/src/tools/agent-tool-policy-guard.ts +8 -8
  247. package/src/tools/agent-work-plan-tool.ts +256 -0
  248. package/src/version.ts +1 -1
  249. package/src/input/commands/policy-dispatch.ts +0 -339
  250. package/src/input/commands/policy.ts +0 -13
  251. package/src/panels/panel-picker.ts +0 -105
  252. package/src/panels/policy-panel.ts +0 -308
  253. package/src/renderer/panel-picker-overlay.ts +0 -202
@@ -1,18 +1,15 @@
1
1
  /**
2
- * PanelListPanel — Shows all registered panels grouped by category.
2
+ * PanelListPanel — Shows registered panel routes grouped by category.
3
3
  *
4
4
  * Features:
5
- * - Arrow keys to navigate, Enter to open the selected panel
5
+ * - Arrow keys to navigate, Enter to show the matching Agent workspace route
6
6
  * - Open/closed indicator (● open, ○ closed)
7
7
  * - Search/filter by typing
8
8
  * - Grouped by category
9
- * - `T` / `B` to place a panel in the top/bottom pane
10
- * - `M` to move an open panel to the other pane
11
- * - `S` to toggle the split and Tab to switch pane focus
12
9
  *
13
- * Open via /panel list.
10
+ * Open the current operator workspace via /agent.
14
11
  */
15
- import type { Line, Cell } from '../types/grid.ts';
12
+ import type { Line } from '../types/grid.ts';
16
13
  import type { PanelCategory, PanelRegistration } from './types.ts';
17
14
  import { BasePanel } from './base-panel.ts';
18
15
  import type { PanelManager } from './panel-manager.ts';
@@ -39,7 +36,7 @@ import {
39
36
  isPanelSearchCommit,
40
37
  isPanelSearchPrintable,
41
38
  } from './search-focus.ts';
42
- import { logger } from '@pellux/goodvibes-sdk/platform/utils';
39
+ import { agentWorkspaceCategoryForPanel, agentWorkspaceCommandForPanel } from '../input/agent-workspace-panel-route.ts';
43
40
 
44
41
  // ── Colour palette ────────────────────────────────────────────────────────────
45
42
  const C = {
@@ -60,8 +57,6 @@ const C = {
60
57
  search: '#f97316',
61
58
  searchBg: '#1e293b',
62
59
  dim: '#334155',
63
- paneTop: '#38bdf8',
64
- paneBottom: '#a78bfa',
65
60
  intro: '#94a3b8',
66
61
  } as const;
67
62
 
@@ -82,18 +77,6 @@ type ListEntry =
82
77
  | { kind: 'header'; category: PanelCategory }
83
78
  | { kind: 'panel'; reg: PanelRegistration };
84
79
 
85
- function panelPlacementMarker(options: {
86
- isTopOpen: boolean;
87
- isBottomOpen: boolean;
88
- focusedPane: 'top' | 'bottom';
89
- }): { text: string; color: string } {
90
- const { isTopOpen, isBottomOpen, focusedPane } = options;
91
- if (isTopOpen && isBottomOpen) return { text: '◆', color: C.selected };
92
- if (isTopOpen) return { text: focusedPane === 'top' ? '▲' : '△', color: C.paneTop };
93
- if (isBottomOpen) return { text: focusedPane === 'bottom' ? '▼' : '▽', color: C.paneBottom };
94
- return { text: ' ', color: C.dim };
95
- }
96
-
97
80
  function wrapPanelDescription(text: string, width: number, maxLines = 2): string[] {
98
81
  if (width <= 0) return [''];
99
82
  const lines = wrapWithHangingIndent(text, width, '', maxLines);
@@ -107,6 +90,7 @@ export class PanelListPanel extends BasePanel {
107
90
  private _scrollOffset = 0;
108
91
  private _query = '';
109
92
  private _filterFocused = false;
93
+ private _routeMessage = 'This list is read-only in Agent. Press Enter for the matching Agent workspace route.';
110
94
  private _cachedEntries: ListEntry[] | null = null;
111
95
  private _entriesDirty = true;
112
96
 
@@ -123,6 +107,7 @@ export class PanelListPanel extends BasePanel {
123
107
  this._scrollOffset = 0;
124
108
  this._query = '';
125
109
  this._filterFocused = false;
110
+ this._routeMessage = 'This list is read-only in Agent. Press Enter for the matching Agent workspace route.';
126
111
  this._entriesDirty = true;
127
112
  }
128
113
 
@@ -160,11 +145,7 @@ export class PanelListPanel extends BasePanel {
160
145
  this._filterFocused = false;
161
146
  const selectedPanel = this._getSelectedPanelEntry(this._buildEntries());
162
147
  if (selectedPanel) {
163
- try {
164
- this.panelManager.open(selectedPanel.reg.id);
165
- } catch (err) {
166
- logger.warn(`[panel-list] failed to open panel: ${err}`);
167
- }
148
+ this._setRouteMessage(selectedPanel.reg);
168
149
  }
169
150
  this.markDirty();
170
151
  return true;
@@ -198,57 +179,24 @@ export class PanelListPanel extends BasePanel {
198
179
  return true;
199
180
  }
200
181
 
201
- // Open selected panel
182
+ // Route selected panel through the Agent workspace.
202
183
  if (key === 'return' || key === 'enter') {
203
184
  const selectedPanel = this._getSelectedPanelEntry(entries);
204
185
  if (selectedPanel) {
205
- try {
206
- this.panelManager.open(selectedPanel.reg.id);
207
- } catch (err) {
208
- logger.warn(`[panel-list] failed to open panel: ${err}`);
209
- }
210
- this.markDirty();
211
- }
212
- return true;
213
- }
214
-
215
- if (key === 'T' || key === 'B') {
216
- const selectedPanel = this._getSelectedPanelEntry(entries);
217
- if (selectedPanel) {
218
- const pane = key === 'T' ? 'top' : 'bottom';
219
- try {
220
- const pm = this.panelManager;
221
- pm.open(selectedPanel.reg.id, pane);
222
- pm.show();
223
- } catch (err) {
224
- logger.warn(`[panel-list] failed to place panel: ${err}`);
225
- }
186
+ this._setRouteMessage(selectedPanel.reg);
226
187
  this.markDirty();
227
188
  }
228
189
  return true;
229
190
  }
230
191
 
231
- if (key === 'M') {
192
+ if (key === 'T' || key === 'B' || key === 'M' || key === 'S' || key === 'tab') {
232
193
  const selectedPanel = this._getSelectedPanelEntry(entries);
233
194
  if (selectedPanel) {
234
- try {
235
- this.panelManager.moveToOtherPane(selectedPanel.reg.id);
236
- } catch (err) {
237
- logger.warn(`[panel-list] failed to move panel: ${err}`);
238
- }
239
- this.markDirty();
195
+ const command = agentWorkspaceCommandForPanel(selectedPanel.reg.id);
196
+ this._routeMessage = `Panel placement keys are inactive in Agent. Use ${command} for ${selectedPanel.reg.name}.`;
197
+ } else {
198
+ this._routeMessage = 'Panel placement keys are inactive in Agent. Use /agent for current operator controls.';
240
199
  }
241
- return true;
242
- }
243
-
244
- if (key === 'S') {
245
- this.panelManager.toggleBottomPane();
246
- this.markDirty();
247
- return true;
248
- }
249
-
250
- if (key === 'tab') {
251
- this.panelManager.togglePaneFocus();
252
200
  this.markDirty();
253
201
  return true;
254
202
  }
@@ -285,12 +233,12 @@ export class PanelListPanel extends BasePanel {
285
233
  }
286
234
  const start = Date.now();
287
235
  this.needsRender = false;
288
- const intro = 'Browse, place, split, and move panels without dropping into raw panel-management commands.';
236
+ const intro = 'Browse registered panel routes and jump to the matching Agent workspace area.';
289
237
  const entries = this._buildEntries();
290
238
 
291
239
  if (entries.length === 0) {
292
240
  const lines = buildPanelWorkspace(width, height, {
293
- title: 'Panel Workspace',
241
+ title: 'Agent Panel Routes',
294
242
  intro,
295
243
  sections: [{
296
244
  title: 'Filter',
@@ -305,7 +253,7 @@ export class PanelListPanel extends BasePanel {
305
253
  width,
306
254
  ' No panels match filter.',
307
255
  'Clear the filter or search for another panel by id, name, description, or category.',
308
- [{ command: '/panel list', summary: 'reopen the panel workspace from the shell' }],
256
+ [{ command: '/agent', summary: 'open the Agent workspace' }],
309
257
  C,
310
258
  ),
311
259
  }],
@@ -321,16 +269,18 @@ export class PanelListPanel extends BasePanel {
321
269
  const pm = this.panelManager;
322
270
  const topIds = new Set(pm.getTopPane().panels.map(p => p.id));
323
271
  const bottomIds = new Set(pm.getBottomPane().panels.map(p => p.id));
324
- const focusedPane = pm.getFocusedPane();
325
- const footerLines = [buildPanelLine(width, [[` [${this._selectedIndex + 1}/${panelEntries.length}] ↑/↓ nav Enter open T/B place M move S split Tab focus`.slice(0, width), C.hint]])];
272
+ const selectedPanel = this._getSelectedPanelEntry(entries);
273
+ const selectedRoute = selectedPanel ? agentWorkspaceCommandForPanel(selectedPanel.reg.id) : '/agent';
274
+ const selectedArea = selectedPanel ? agentWorkspaceCategoryForPanel(selectedPanel.reg.id) : 'home';
275
+ const footerLines = [buildPanelLine(width, [[` [${this._selectedIndex + 1}/${panelEntries.length}] ↑/↓ nav Enter route / filter Esc clear`.slice(0, width), C.hint]])];
326
276
  const postureLines: Line[] = [
327
277
  buildKeyValueLine(width, [
328
- { label: 'visible panels', value: String(pm.getAllOpen().length), valueColor: pm.getAllOpen().length > 0 ? C.name : C.dim },
329
- { label: 'focused pane', value: focusedPane, valueColor: focusedPane === 'top' ? C.paneTop : C.paneBottom },
330
- { label: 'split', value: pm.isBottomPaneVisible() ? 'dual' : 'single', valueColor: pm.isBottomPaneVisible() ? C.info : C.dim },
278
+ { label: 'copied open', value: String(pm.getAllOpen().length), valueColor: pm.getAllOpen().length > 0 ? C.name : C.dim },
279
+ { label: 'agent route', value: selectedRoute, valueColor: C.info },
280
+ { label: 'area', value: selectedArea, valueColor: C.value },
331
281
  { label: 'results', value: String(panelEntries.length), valueColor: C.value },
332
282
  ], C),
333
- buildPanelLine(width, [[` Filter owns input only when selected. Open and switch operations should land directly in focused panel state.`, C.intro]]),
283
+ buildPanelLine(width, [[` ${truncateDisplay(this._routeMessage, Math.max(0, width - 1))}`, C.intro]]),
334
284
  ];
335
285
  const entryLines: Line[] = [
336
286
  buildSearchInputLine(width, 'Filter: ', `${this._query}${this._filterFocused ? '_' : ''}`, C, {
@@ -361,11 +311,11 @@ export class PanelListPanel extends BasePanel {
361
311
  const descStartCol = PREFIX_WIDTH + NAME_COL_WIDTH + 1;
362
312
  const descWidth = Math.max(1, width - descStartCol);
363
313
  const descLines = wrapPanelDescription(entry.reg.description, descWidth, 2);
364
- const placement = panelPlacementMarker({ isTopOpen, isBottomOpen, focusedPane });
314
+ const routeCategory = agentWorkspaceCategoryForPanel(entry.reg.id);
365
315
  const blockLines: Line[] = [
366
316
  buildPanelListRow(width, [
367
317
  { text: dot, fg: dotColor },
368
- { text: placement.text, fg: placement.color },
318
+ { text: routeCategory.charAt(0).toUpperCase(), fg: isSelected ? C.selIcon : C.icon },
369
319
  { text: ' ', fg: C.dim },
370
320
  { text: `${nameStr} `, fg: nameColor },
371
321
  { text: descLines[0] ?? '', fg: C.desc },
@@ -390,7 +340,7 @@ export class PanelListPanel extends BasePanel {
390
340
  }
391
341
  const selectedEntryIndex = renderedBlocks.findIndex((block) => block.panelFlatIndex === this._selectedIndex);
392
342
  const selectedRow = selectedEntryIndex >= 0 ? blockStarts[selectedEntryIndex] ?? 0 : 0;
393
- const postureSection: PanelWorkspaceSection = { lines: buildSummaryBlock(width, 'Panel posture', postureLines, C) };
343
+ const postureSection: PanelWorkspaceSection = { lines: buildSummaryBlock(width, 'Route posture', postureLines, C) };
394
344
  const resolvedSection = resolvePrimaryScrollableSection(width, height, {
395
345
  intro,
396
346
  footerLines,
@@ -416,7 +366,7 @@ export class PanelListPanel extends BasePanel {
416
366
  resolvedSection.section,
417
367
  ];
418
368
  const lines = buildPanelWorkspace(width, height, {
419
- title: 'Panel Workspace',
369
+ title: 'Agent Panel Routes',
420
370
  intro,
421
371
  sections,
422
372
  footerLines,
@@ -488,6 +438,12 @@ export class PanelListPanel extends BasePanel {
488
438
  return null;
489
439
  }
490
440
 
441
+ private _setRouteMessage(reg: PanelRegistration): void {
442
+ const command = agentWorkspaceCommandForPanel(reg.id);
443
+ const area = agentWorkspaceCategoryForPanel(reg.id);
444
+ this._routeMessage = `${reg.name} routes through ${command} (${area}) in Agent workspace.`;
445
+ }
446
+
491
447
  /** Move selection to the previous panel entry. */
492
448
  private _movePrevPanel(): void {
493
449
  this._selectedIndex = Math.max(0, this._selectedIndex - 1);
@@ -214,7 +214,7 @@ export class ProjectPlanningPanel extends BasePanel {
214
214
 
215
215
  const { lines: flattened, selectedIndex } = this.flattenSections(width, sections);
216
216
  const scroll = resolveScrollablePanelSection(width, height, {
217
- intro: 'Project planning state, readiness gaps, decisions, language, task graph, verification gates, and agent handoff metadata.',
217
+ intro: 'Project planning state, open gaps, decisions, language, task graph, completion checks, and handoff candidates.',
218
218
  footerLines: this.footerLines(width),
219
219
  palette: C,
220
220
  section: {
@@ -227,22 +227,28 @@ export class ProjectPlanningPanel extends BasePanel {
227
227
  },
228
228
  });
229
229
  this.scrollOffset = scroll.scrollOffset;
230
+ const sectionLines = [...scroll.section.lines];
231
+ const footerSummary = flattened.length > sectionLines.length
232
+ ? `showing ${scroll.window.start + 1}-${scroll.window.end} of ${flattened.length}`
233
+ : undefined;
230
234
 
231
235
  return buildPanelWorkspace(width, height, {
232
236
  title: this.loading ? 'Project Planning - loading' : 'Project Planning',
233
237
  intro: 'SDK-backed planning artifacts for the current Agent workspace. Conversation control stays in the main Agent turn.',
234
- sections: [scroll.section],
235
- footerLines: this.footerLines(width),
238
+ sections: [{ ...scroll.section, lines: sectionLines }],
239
+ footerLines: this.footerLines(width, footerSummary),
236
240
  palette: C,
237
241
  });
238
242
  });
239
243
  }
240
244
 
241
- private footerLines(width: number): Line[] {
245
+ private footerLines(width: number, scrollSummary?: string): Line[] {
242
246
  const hasQuestion = this.getCurrentQuestion() !== null;
247
+ const summaryParts: Array<[string, string]> = scrollSummary ? [[` ${scrollSummary} `, C.dim]] : [];
243
248
  if (hasQuestion) {
244
249
  return [
245
250
  buildPanelLine(width, [
251
+ ...summaryParts,
246
252
  [' Up/Down', C.info],
247
253
  [' choose answer ', C.dim],
248
254
  ['type', C.info],
@@ -256,12 +262,13 @@ export class ProjectPlanningPanel extends BasePanel {
256
262
  }
257
263
  return [
258
264
  buildPanelLine(width, [
265
+ ...summaryParts,
259
266
  [' Up/Down', C.info],
260
267
  [' scroll ', C.dim],
261
268
  ['r', C.info],
262
269
  [' refresh ', C.dim],
263
270
  ['a', C.info],
264
- [' approve execution-ready plan Esc prompt focus Ctrl+X close panel', C.dim],
271
+ [' mark planning ready Esc prompt focus Ctrl+X close panel', C.dim],
265
272
  ]),
266
273
  ];
267
274
  }
@@ -359,12 +366,12 @@ export class ProjectPlanningPanel extends BasePanel {
359
366
  const gaps = evaluation?.gaps ?? [];
360
367
  if (gaps.length === 0) {
361
368
  return {
362
- title: 'Readiness Gaps',
363
- lines: [buildPanelLine(width, [[' No readiness gaps.', C.good]])],
369
+ title: 'Open Gaps',
370
+ lines: [buildPanelLine(width, [[' No open gaps.', C.good]])],
364
371
  };
365
372
  }
366
373
  return {
367
- title: 'Readiness Gaps',
374
+ title: 'Open Gaps',
368
375
  lines: gaps.slice(0, 12).flatMap((gap) => buildBodyText(
369
376
  width,
370
377
  `${gap.severity.toUpperCase()} ${gap.kind}: ${gap.message}`,
@@ -392,7 +399,7 @@ export class ProjectPlanningPanel extends BasePanel {
392
399
  }
393
400
 
394
401
  if (state.verificationGates.length) {
395
- lines.push(buildPanelLine(width, [[' Verification gates:', C.label]]));
402
+ lines.push(buildPanelLine(width, [[' Completion checks:', C.label]]));
396
403
  for (const gate of state.verificationGates) {
397
404
  lines.push(...buildBodyText(width, `${gate.id}: ${gate.description} [${gate.status ?? 'pending'}]`, C, gate.required === false ? C.dim : C.good));
398
405
  }
@@ -527,9 +534,9 @@ export class ProjectPlanningPanel extends BasePanel {
527
534
  if (isVerificationQuestion) {
528
535
  actions.push({
529
536
  id: 'verification-default-gates',
530
- label: 'Use standard verification gates',
537
+ label: 'Use standard completion checks',
531
538
  detail: 'Require focused regression coverage, typecheck/build validation, and a runtime smoke where feasible.',
532
- answer: 'Use standard verification gates for this goal.',
539
+ answer: 'Use standard completion checks for this goal.',
533
540
  });
534
541
  }
535
542
  if (question.recommendedAnswer?.trim() && !this.isGenericRecommendation(question.recommendedAnswer)) {
@@ -78,7 +78,7 @@ function determineFreshness(input: {
78
78
  readonly hasSubscription: boolean;
79
79
  readonly expiresAt?: number;
80
80
  readonly pending: boolean;
81
- readonly hasServiceOAuth: boolean;
81
+ readonly hasUsableServiceOAuth: boolean;
82
82
  readonly hasApiKey: boolean;
83
83
  }): ProviderAuthFreshness {
84
84
  if (input.hasSubscription) {
@@ -87,20 +87,33 @@ function determineFreshness(input: {
87
87
  if (input.pending) return 'pending';
88
88
  return 'healthy';
89
89
  }
90
- if (input.hasServiceOAuth || input.hasApiKey) return 'healthy';
90
+ if (input.hasUsableServiceOAuth || input.hasApiKey) return 'healthy';
91
91
  return 'unconfigured';
92
92
  }
93
93
 
94
94
  function builtinWindowsForProvider(providerId: string): readonly ProviderUsageWindow[] {
95
95
  if (providerId === 'openai') {
96
96
  return [
97
- { label: '5-hour window', detail: 'Subscription-backed Codex access may be constrained by rolling 5-hour usage limits.' },
98
- { label: '1-week window', detail: 'Subscription-backed Codex access may also be constrained by a rolling weekly limit.' },
97
+ { label: '5-hour window', detail: 'Subscription-backed OpenAI access may be constrained by rolling 5-hour usage limits.' },
98
+ { label: '1-week window', detail: 'Subscription-backed OpenAI access may also be constrained by a rolling weekly limit.' },
99
99
  ];
100
100
  }
101
101
  return [];
102
102
  }
103
103
 
104
+ function readProviderEnvVars(model: unknown): readonly string[] {
105
+ if (typeof model !== 'object' || model === null) return [];
106
+ const value = (model as { readonly providerEnvVars?: unknown }).providerEnvVars;
107
+ if (!Array.isArray(value)) return [];
108
+ return value.filter((entry): entry is string => typeof entry === 'string' && entry.trim().length > 0);
109
+ }
110
+
111
+ function defaultProviderEnvKeys(providerId: string): readonly string[] {
112
+ const normalized = providerId.toUpperCase().replace(/[^A-Z0-9]+/g, '_').replace(/^_+|_+$/g, '');
113
+ if (normalized.length === 0) return [];
114
+ return [`${normalized}_API_KEY`, `${normalized}_KEY`];
115
+ }
116
+
104
117
  export async function buildProviderAccountSnapshot(
105
118
  deps: ProviderAccountSnapshotDeps,
106
119
  ): Promise<ProviderAccountSnapshot> {
@@ -125,20 +138,27 @@ export async function buildProviderAccountSnapshot(
125
138
 
126
139
  const providerIds = new Set<string>([
127
140
  ...models.map((model) => model.provider),
128
- ...Object.keys(services),
141
+ ...Object.values(services).map((service) => service.providerId ?? service.name),
129
142
  ...subscriptions.list().map((entry) => entry.provider),
130
143
  ...subscriptions.listPending().map((entry) => entry.provider),
131
144
  ...builtinSubscriptionProviders,
132
145
  ]);
133
146
 
134
147
  const providers = await Promise.all([...providerIds].sort((a, b) => a.localeCompare(b)).map(async (providerId) => {
148
+ const providerModels = models.filter((model) => model.provider === providerId);
135
149
  const subscription = subscriptions.get(providerId);
136
150
  const pending = subscriptions.getPending(providerId);
137
151
  const serviceConfig = Object.values(services).find((entry) => (entry.providerId ?? entry.name) === providerId) ?? null;
138
152
  const serviceOauth = serviceOauthByProvider.get(providerId);
139
- const hasApiKey = Boolean(serviceConfig?.tokenKey && deps.environment.hasEnvironmentVariable(serviceConfig.tokenKey));
153
+ const apiKeyCandidates = new Set<string>([
154
+ ...(serviceConfig?.tokenKey ? [serviceConfig.tokenKey] : []),
155
+ ...providerModels.flatMap((model) => readProviderEnvVars(model)),
156
+ ...defaultProviderEnvKeys(providerId),
157
+ ]);
158
+ const hasApiKey = [...apiKeyCandidates].some((key) => deps.environment.hasEnvironmentVariable(key));
140
159
  const hasSubscription = subscription != null;
141
- const hasServiceOAuth = Boolean(serviceOauth?.configured);
160
+ const hasServiceOAuth = Boolean(serviceOauth?.configured || serviceConfig?.authType === 'oauth' || serviceConfig?.oauth);
161
+ const hasUsableServiceOAuth = Boolean(serviceOauth?.usable);
142
162
  const routes: ProviderAuthRoute[] = [];
143
163
  if (hasApiKey) routes.push('api-key');
144
164
  if (hasSubscription) routes.push('subscription');
@@ -148,15 +168,15 @@ export async function buildProviderAccountSnapshot(
148
168
  const usableRoutes: Exclude<ProviderAuthRoute, 'unconfigured'>[] = [];
149
169
  if (hasApiKey) usableRoutes.push('api-key');
150
170
  if (hasSubscription && !isExpired(subscription.expiresAt)) usableRoutes.push('subscription');
151
- if (hasServiceOAuth) usableRoutes.push('service-oauth');
171
+ if (hasUsableServiceOAuth) usableRoutes.push('service-oauth');
152
172
 
153
- const activeRoute = determineActiveRoute(usableRoutes.length > 0 ? usableRoutes : routes);
173
+ const activeRoute = determineActiveRoute(usableRoutes);
154
174
  const preferredRoute = determineActiveRoute(routes);
155
175
  const freshness = determineFreshness({
156
176
  hasSubscription,
157
177
  expiresAt: subscription?.expiresAt,
158
178
  pending: pending != null,
159
- hasServiceOAuth,
179
+ hasUsableServiceOAuth,
160
180
  hasApiKey,
161
181
  });
162
182
  const usageWindows = builtinWindowsForProvider(providerId);
@@ -185,20 +205,22 @@ export async function buildProviderAccountSnapshot(
185
205
  if (hasServiceOAuth) {
186
206
  routeRecords.push({
187
207
  route: 'service-oauth',
188
- usable: true,
189
- freshness: 'healthy',
190
- detail: 'Service OAuth credential is available for this provider.',
191
- issues: [],
208
+ usable: hasUsableServiceOAuth,
209
+ freshness: hasUsableServiceOAuth ? 'healthy' : 'unconfigured',
210
+ detail: hasUsableServiceOAuth
211
+ ? 'Provider OAuth credential is available for this provider.'
212
+ : 'Provider OAuth is configured but missing a usable credential.',
213
+ issues: hasUsableServiceOAuth ? [] : ['Provider OAuth credential is missing or unavailable.'],
192
214
  });
193
215
  }
194
216
 
195
217
  const issues: string[] = [];
196
- const notes: string[] = [`${models.filter((model) => model.provider === providerId).length} model${models.filter((model) => model.provider === providerId).length === 1 ? '' : 's'} registered`];
197
- if (serviceConfig) notes.push(`service config: ${serviceConfig.authType}`);
218
+ const notes: string[] = [`${providerModels.length} model${providerModels.length === 1 ? '' : 's'} registered`];
219
+ if (serviceConfig) notes.push(`provider config: ${serviceConfig.authType}`);
198
220
  const recommendedActions: string[] = [];
199
221
  if (routes.length === 1 && routes[0] === 'unconfigured') {
200
222
  issues.push('Provider has no configured auth route.');
201
- recommendedActions.push(`Configure API keys, subscriptions, or service OAuth for ${providerId}.`);
223
+ recommendedActions.push(`Configure API keys, subscriptions, or provider OAuth for ${providerId}.`);
202
224
  }
203
225
  if (hasSubscription && isExpired(subscription?.expiresAt)) {
204
226
  issues.push('Stored subscription session is expired and needs refresh.');
@@ -215,16 +237,16 @@ export async function buildProviderAccountSnapshot(
215
237
  issues.push('Provider has both subscription and API-key auth paths; routing must remain explicit.');
216
238
  recommendedActions.push('Review provider routing before switching models or auth paths.');
217
239
  }
218
- if (hasServiceOAuth && !serviceOauth?.usable) {
219
- issues.push('Service OAuth is configured but missing a usable credential.');
220
- recommendedActions.push(`Repair service OAuth credentials for ${providerId} in /settings or the owning GoodVibes host.`);
240
+ if (hasServiceOAuth && !hasUsableServiceOAuth) {
241
+ issues.push('Provider OAuth is configured but missing a usable credential.');
242
+ recommendedActions.push(`Repair provider OAuth credentials for ${providerId} in /settings or the owning GoodVibes host.`);
221
243
  }
222
244
 
223
245
  return {
224
246
  providerId,
225
247
  active: activeRoute !== 'unconfigured',
226
- modelCount: models.filter((model) => model.provider === providerId).length,
227
- configured: hasApiKey || hasSubscription || hasServiceOAuth || models.some((model) => model.provider === providerId),
248
+ modelCount: providerModels.length,
249
+ configured: hasApiKey || hasSubscription || hasServiceOAuth || providerModels.length > 0,
228
250
  oauthReady: Boolean(serviceConfig?.oauth),
229
251
  pendingLogin: Boolean(pending),
230
252
  availableRoutes: routes,
@@ -233,13 +255,17 @@ export async function buildProviderAccountSnapshot(
233
255
  activeRouteReason: activeRoute === 'subscription'
234
256
  ? 'Subscription route is currently preferred.'
235
257
  : activeRoute === 'service-oauth'
236
- ? 'Service OAuth route is currently preferred.'
258
+ ? 'Provider OAuth route is currently preferred.'
237
259
  : activeRoute === 'api-key'
238
260
  ? 'Ambient API-key route is currently preferred.'
239
261
  : 'No usable auth route is configured for this provider.',
240
262
  authFreshness: freshness,
241
- fallbackRoute: activeRoute !== preferredRoute ? preferredRoute : undefined,
242
- fallbackRisk: hasSubscription && hasApiKey ? 'Both subscription and API key are present; check route priority.' : undefined,
263
+ fallbackRoute: activeRoute !== preferredRoute ? activeRoute : undefined,
264
+ fallbackRisk: hasSubscription && hasApiKey
265
+ ? (isExpired(subscription?.expiresAt)
266
+ ? 'preferred subscription path is expired; active route falls back to API key.'
267
+ : 'Both subscription and API key are present; check route priority.')
268
+ : undefined,
243
269
  expiresAt: subscription?.expiresAt,
244
270
  tokenType: subscription?.tokenType,
245
271
  notes,
@@ -15,10 +15,12 @@ import {
15
15
  type PanelWorkspaceSection,
16
16
  } from './polish.ts';
17
17
  import {
18
+ type ProviderAuthRoute,
18
19
  type ProviderAccountRecord,
19
20
  type ProviderAccountSnapshot,
20
21
  type ProviderAccountSnapshotQuery,
21
22
  } from './provider-account-snapshot.ts';
23
+ import { formatProviderAuthRouteId } from '../provider-auth-route-display.ts';
22
24
 
23
25
  export interface ProviderAccountsPanelDeps {
24
26
  readonly providerAccounts: ProviderAccountSnapshotQuery;
@@ -29,6 +31,18 @@ const C = {
29
31
  selectBg: '#1e293b',
30
32
  } as const;
31
33
 
34
+ function routeColor(route: ProviderAuthRoute): string {
35
+ if (route === 'subscription') return C.info;
36
+ if (route === 'api-key') return C.warn;
37
+ if (route === 'service-oauth') return C.value;
38
+ if (route === 'unconfigured') return C.bad;
39
+ return C.dim;
40
+ }
41
+
42
+ function formatRouteList(routes: readonly ProviderAuthRoute[]): string {
43
+ return routes.map((route) => formatProviderAuthRouteId(route)).join(', ');
44
+ }
45
+
32
46
  export class ProviderAccountsPanel extends ScrollableListPanel<ProviderAccountRecord> {
33
47
  private records: ProviderAccountRecord[] = [];
34
48
  private loading = false;
@@ -51,12 +65,13 @@ export class ProviderAccountsPanel extends ScrollableListPanel<ProviderAccountRe
51
65
  }
52
66
 
53
67
  protected renderItem(item: ProviderAccountRecord, _index: number, selected: boolean, width: number): Line {
68
+ const activeRoute = formatProviderAuthRouteId(item.activeRoute);
54
69
  return buildPanelListRow(width, [
55
70
  { text: item.providerId.padEnd(16), fg: item.active ? C.good : C.value },
56
- { text: ` ${item.activeRoute.padEnd(14)}`, fg: item.activeRoute === 'subscription' ? C.info : item.activeRoute === 'api-key' ? C.warn : item.activeRoute === 'service-oauth' ? C.value : C.dim },
57
- { text: ` models=${String(item.modelCount).padEnd(4)}`, fg: C.dim },
71
+ { text: ` ${activeRoute.padEnd(14)}`, fg: routeColor(item.activeRoute) },
72
+ { text: ` models ${String(item.modelCount).padEnd(4)}`, fg: C.dim },
58
73
  { text: ` ${item.authFreshness.padEnd(10)}`, fg: item.authFreshness === 'expired' ? C.bad : item.authFreshness === 'expiring' || item.authFreshness === 'pending' ? C.warn : C.dim },
59
- { text: ` issues=${String(item.issues.length).padEnd(2)}`, fg: item.issues.length > 0 ? C.bad : C.good },
74
+ { text: ` issues ${String(item.issues.length).padEnd(2)}`, fg: item.issues.length > 0 ? C.bad : C.good },
60
75
  ], C, { selected });
61
76
  }
62
77
 
@@ -118,12 +133,12 @@ export class ProviderAccountsPanel extends ScrollableListPanel<ProviderAccountRe
118
133
  { label: 'providers', value: String(this.records.length), valueColor: C.value },
119
134
  { label: 'expired auth', value: String(expiredCount), valueColor: expiredCount > 0 ? C.bad : C.good },
120
135
  { label: 'pending login', value: String(pendingCount), valueColor: pendingCount > 0 ? C.warn : C.dim },
121
- { label: 'fallback risk', value: String(fallbackCount), valueColor: fallbackCount > 0 ? C.warn : C.good },
136
+ { label: 'routing risk', value: String(fallbackCount), valueColor: fallbackCount > 0 ? C.warn : C.good },
122
137
  ], C),
123
138
  buildKeyValueLine(width, [
124
139
  { label: 'total issues', value: String(issueCount), valueColor: issueCount > 0 ? C.bad : C.good },
125
140
  { label: 'selected', value: selected.providerId, valueColor: C.info },
126
- { label: 'route', value: selected.activeRoute, valueColor: selected.activeRoute === 'subscription' ? C.info : selected.activeRoute === 'api-key' ? C.warn : C.value },
141
+ { label: 'route', value: formatProviderAuthRouteId(selected.activeRoute), valueColor: routeColor(selected.activeRoute) },
127
142
  { label: 'freshness', value: selected.authFreshness, valueColor: selected.authFreshness === 'expired' ? C.bad : selected.authFreshness === 'expiring' || selected.authFreshness === 'pending' ? C.warn : C.good },
128
143
  ], C),
129
144
  buildGuidanceLine(width, '/accounts repair <provider>', 'review routing safety, fallback cost, and provider-specific recovery steps', C),
@@ -131,8 +146,8 @@ export class ProviderAccountsPanel extends ScrollableListPanel<ProviderAccountRe
131
146
  const detailRows: Line[] = [
132
147
  buildKeyValueLine(width, [
133
148
  { label: 'provider', value: selected.providerId, valueColor: C.value },
134
- { label: 'active route', value: selected.activeRoute, valueColor: selected.activeRoute === 'subscription' ? C.info : selected.activeRoute === 'api-key' ? C.warn : selected.activeRoute === 'service-oauth' ? C.value : C.bad },
135
- { label: 'preferred route', value: selected.preferredRoute, valueColor: C.dim },
149
+ { label: 'active route', value: formatProviderAuthRouteId(selected.activeRoute), valueColor: routeColor(selected.activeRoute) },
150
+ { label: 'preferred route', value: formatProviderAuthRouteId(selected.preferredRoute), valueColor: C.dim },
136
151
  { label: 'freshness', value: selected.authFreshness, valueColor: selected.authFreshness === 'expired' ? C.bad : selected.authFreshness === 'expiring' || selected.authFreshness === 'pending' ? C.warn : C.good },
137
152
  ], C),
138
153
  buildKeyValueLine(width, [
@@ -140,40 +155,40 @@ export class ProviderAccountsPanel extends ScrollableListPanel<ProviderAccountRe
140
155
  { label: 'oauth ready', value: selected.oauthReady ? 'yes' : 'no', valueColor: selected.oauthReady ? C.info : C.dim },
141
156
  { label: 'pending login', value: selected.pendingLogin ? 'yes' : 'no', valueColor: selected.pendingLogin ? C.warn : C.dim },
142
157
  ], C),
143
- buildPanelLine(width, [[` Active route reason: ${selected.activeRouteReason}`.slice(0, width), C.dim]]),
144
- buildPanelLine(width, [[` Available routes: ${selected.availableRoutes.join(', ') || 'unconfigured'}`.slice(0, width), C.dim]]),
158
+ buildPanelLine(width, [[` Route reason ${selected.activeRouteReason}`.slice(0, width), C.dim]]),
159
+ buildPanelLine(width, [[` Available routes ${formatRouteList(selected.availableRoutes) || 'unconfigured'}`.slice(0, width), C.dim]]),
145
160
  ];
146
161
  if (selected.expiresAt) {
147
162
  detailRows.push(buildPanelLine(width, [
148
- [' Expires: ', C.label],
163
+ [' Expires ', C.label],
149
164
  [new Date(selected.expiresAt).toISOString(), C.dim],
150
- [' Token: ', C.label],
165
+ [' Token ', C.label],
151
166
  [selected.tokenType ?? 'n/a', C.value],
152
167
  ]));
153
168
  }
154
169
  if (selected.fallbackRisk) {
155
- detailRows.push(buildPanelLine(width, [[` fallback: ${selected.fallbackRisk}`.slice(0, width), C.warn]]));
170
+ detailRows.push(buildPanelLine(width, [[` routing risk ${selected.fallbackRisk}`.slice(0, width), C.warn]]));
156
171
  }
157
172
  for (const route of selected.routeRecords) {
158
173
  detailRows.push(buildPanelLine(width, [[
159
- ` route ${route.route}: ${route.usable ? 'usable' : 'blocked'} • ${route.freshness} • ${route.detail}`.slice(0, width),
174
+ ` route ${formatProviderAuthRouteId(route.route)} ${route.usable ? 'usable' : 'blocked'} • ${route.freshness} • ${route.detail}`.slice(0, width),
160
175
  route.usable ? C.dim : C.bad,
161
176
  ]]));
162
177
  for (const issue of route.issues) {
163
- detailRows.push(buildPanelLine(width, [[` issue: ${issue}`.slice(0, width), C.bad]]));
178
+ detailRows.push(buildPanelLine(width, [[` issue ${issue}`.slice(0, width), C.bad]]));
164
179
  }
165
180
  }
166
181
  for (const windowHint of selected.usageWindows) {
167
- detailRows.push(buildPanelLine(width, [[` ${windowHint.label}: ${windowHint.detail}`.slice(0, width), C.dim]]));
182
+ detailRows.push(buildPanelLine(width, [[` ${windowHint.label} ${windowHint.detail}`.slice(0, width), C.dim]]));
168
183
  }
169
184
  for (const issue of selected.issues) {
170
- detailRows.push(buildPanelLine(width, [[` issue: ${issue}`.slice(0, width), C.bad]]));
185
+ detailRows.push(buildPanelLine(width, [[` issue ${issue}`.slice(0, width), C.bad]]));
171
186
  }
172
187
  for (const note of selected.notes) {
173
- detailRows.push(buildPanelLine(width, [[` note: ${note}`.slice(0, width), C.info]]));
188
+ detailRows.push(buildPanelLine(width, [[` note ${note}`.slice(0, width), C.info]]));
174
189
  }
175
190
  for (const action of selected.recommendedActions) {
176
- detailRows.push(buildPanelLine(width, [[` next: ${action}`.slice(0, width), C.value]]));
191
+ detailRows.push(buildPanelLine(width, [[` next ${action}`.slice(0, width), C.value]]));
177
192
  }
178
193
  if (selected.issues.length === 0 && selected.notes.length === 0 && selected.usageWindows.length === 0 && selected.recommendedActions.length === 0) {
179
194
  detailRows.push(buildPanelLine(width, [[' No active account warnings for this provider.', C.dim]]));