@pellux/goodvibes-agent 0.1.116 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -661
- package/README.md +64 -57
- package/bin/goodvibes-agent.ts +16 -2
- package/dist/package/main.js +199180 -192185
- package/docs/README.md +15 -8
- package/docs/channels-remote-and-api.md +50 -0
- package/docs/connected-host.md +59 -0
- package/docs/getting-started.md +61 -50
- package/docs/knowledge-artifacts-and-multimodal.md +91 -0
- package/docs/project-planning.md +79 -0
- package/docs/providers-and-routing.md +46 -0
- package/docs/release-and-publishing.md +48 -11
- package/docs/tools-and-commands.md +123 -0
- package/docs/voice-and-live-tts.md +51 -0
- package/package.json +3 -6
- package/src/agent/behavior-discovery-summary.ts +1 -3
- package/src/agent/channel-delivery.ts +201 -0
- package/src/agent/media-generation.ts +159 -0
- package/src/agent/memory-prompt.ts +0 -1
- package/src/agent/note-registry.ts +329 -0
- package/src/agent/operator-actions.ts +343 -0
- package/src/agent/persona-discovery.ts +1 -3
- package/src/agent/persona-registry.ts +19 -16
- package/src/agent/record-labels.ts +107 -0
- package/src/agent/reminder-schedule-format.ts +33 -24
- package/src/agent/reminder-schedule.ts +27 -26
- package/src/agent/routine-registry.ts +54 -14
- package/src/agent/routine-schedule-args.ts +2 -1
- package/src/agent/routine-schedule-format.ts +77 -53
- package/src/agent/routine-schedule-promotion.ts +35 -33
- package/src/agent/routine-schedule-receipts.ts +29 -27
- package/src/agent/runtime-profile-starters.ts +5 -5
- package/src/agent/runtime-profile.ts +91 -12
- package/src/agent/skill-registry.ts +210 -32
- package/src/cli/agent-knowledge-args.ts +5 -1
- package/src/cli/agent-knowledge-command.ts +40 -34
- package/src/cli/agent-knowledge-format.ts +80 -67
- package/src/cli/agent-knowledge-methods.ts +1 -1
- package/src/cli/agent-knowledge-runtime.ts +32 -26
- package/src/cli/bundle-command.ts +14 -9
- package/src/cli/config-overrides.ts +37 -36
- package/src/cli/entrypoint.ts +16 -7
- package/src/cli/external-runtime.ts +10 -4
- package/src/cli/help.ts +54 -29
- package/src/cli/local-library-command.ts +203 -83
- package/src/cli/management-commands.ts +105 -68
- package/src/cli/management.ts +55 -74
- package/src/cli/memory-command.ts +66 -32
- package/src/cli/parser.ts +37 -3
- package/src/cli/profiles-command.ts +166 -31
- package/src/cli/provider-auth-routes.ts +2 -1
- package/src/cli/routines-command.ts +176 -40
- package/src/cli/service-posture.ts +16 -16
- package/src/cli/status.ts +56 -135
- package/src/core/conversation-message-snapshot.ts +131 -0
- package/src/input/agent-workspace-access-command-editor-submission.ts +149 -0
- package/src/input/agent-workspace-access-command-editors.ts +170 -0
- package/src/input/agent-workspace-activation.ts +35 -8
- package/src/input/agent-workspace-basic-command-editor-submission.ts +351 -94
- package/src/input/agent-workspace-basic-command-editors.ts +502 -9
- package/src/input/agent-workspace-categories.ts +423 -51
- package/src/input/agent-workspace-channel-command-editor-submission.ts +83 -0
- package/src/input/agent-workspace-channel-command-editors.ts +65 -0
- package/src/input/agent-workspace-channels.ts +35 -2
- package/src/input/agent-workspace-command-editor.ts +41 -3
- package/src/input/agent-workspace-config-reader.ts +16 -0
- package/src/input/agent-workspace-delegation-editor-submission.ts +55 -0
- package/src/input/agent-workspace-editors.ts +166 -1
- package/src/input/agent-workspace-knowledge-command-editor-submission.ts +132 -0
- package/src/input/agent-workspace-knowledge-command-editors.ts +110 -0
- package/src/input/agent-workspace-knowledge-query-editor.ts +1 -1
- package/src/input/agent-workspace-learned-behavior.ts +67 -0
- package/src/input/agent-workspace-library-command-editor-submission.ts +50 -0
- package/src/input/agent-workspace-library-command-editors.ts +35 -0
- package/src/input/agent-workspace-local-operations.ts +218 -0
- package/src/input/agent-workspace-local-selection.ts +75 -0
- package/src/input/agent-workspace-mcp-command-editor-submission.ts +95 -0
- package/src/input/agent-workspace-media-command-editor-submission.ts +62 -0
- package/src/input/agent-workspace-media-command-editors.ts +27 -0
- package/src/input/agent-workspace-memory-command-editor-submission.ts +232 -0
- package/src/input/agent-workspace-memory-command-editors.ts +180 -0
- package/src/input/agent-workspace-memory-editor.ts +2 -2
- package/src/input/agent-workspace-navigation.ts +81 -0
- package/src/input/agent-workspace-notify-editor-submission.ts +121 -0
- package/src/input/agent-workspace-operations-command-editor-submission.ts +208 -0
- package/src/input/agent-workspace-operations-command-editors.ts +196 -0
- package/src/input/agent-workspace-panel-route.ts +43 -0
- package/src/input/agent-workspace-provider-command-editor-submission.ts +155 -0
- package/src/input/agent-workspace-provider-command-editors.ts +93 -0
- package/src/input/agent-workspace-reminder-schedule-editor.ts +1 -1
- package/src/input/agent-workspace-requirements.ts +11 -0
- package/src/input/agent-workspace-routine-schedule-editor.ts +2 -2
- package/src/input/agent-workspace-search.ts +169 -0
- package/src/input/agent-workspace-secret-editor-submission.ts +153 -0
- package/src/input/agent-workspace-session-command-editor-submission.ts +199 -0
- package/src/input/agent-workspace-session-command-editors.ts +249 -0
- package/src/input/agent-workspace-setup.ts +37 -16
- package/src/input/agent-workspace-skill-bundle-command-editor-submission.ts +101 -0
- package/src/input/agent-workspace-skill-bundle-command-editors.ts +110 -0
- package/src/input/agent-workspace-snapshot.ts +162 -21
- package/src/input/agent-workspace-task-command-editor-submission.ts +61 -0
- package/src/input/agent-workspace-task-command-editors.ts +47 -0
- package/src/input/agent-workspace-token.ts +18 -2
- package/src/input/agent-workspace-types.ts +212 -4
- package/src/input/agent-workspace-voice-media.ts +4 -7
- package/src/input/agent-workspace-web-research-editor.ts +104 -0
- package/src/input/agent-workspace-workplan-editor-submission.ts +153 -0
- package/src/input/agent-workspace.ts +178 -240
- package/src/input/command-registry.ts +5 -2
- package/src/input/commands/agent-runtime-profile-runtime.ts +126 -37
- package/src/input/commands/agent-skills-runtime.ts +149 -72
- package/src/input/commands/agent-workspace-runtime.ts +23 -6
- package/src/input/commands/brief-runtime.ts +55 -25
- package/src/input/commands/channels-runtime.ts +147 -33
- package/src/input/commands/compat-runtime.ts +32 -0
- package/src/input/commands/delegation-runtime.ts +29 -23
- package/src/input/commands/experience-runtime.ts +6 -4
- package/src/input/commands/guidance-runtime.ts +4 -4
- package/src/input/commands/health-runtime.ts +149 -126
- package/src/input/commands/knowledge.ts +82 -60
- package/src/input/commands/local-provider-runtime.ts +43 -21
- package/src/input/commands/local-runtime.ts +138 -16
- package/src/input/commands/local-setup-review.ts +10 -20
- package/src/input/commands/mcp-runtime.ts +63 -46
- package/src/input/commands/notify-runtime.ts +38 -9
- package/src/input/commands/operator-actions-runtime.ts +138 -0
- package/src/input/commands/operator-runtime.ts +6 -76
- package/src/input/commands/personas-runtime.ts +46 -31
- package/src/input/commands/planning-runtime.ts +1 -1
- package/src/input/commands/platform-access-runtime.ts +35 -98
- package/src/input/commands/product-runtime.ts +2 -2
- package/src/input/commands/provider-accounts-runtime.ts +60 -39
- package/src/input/commands/qrcode-runtime.ts +50 -9
- package/src/input/commands/recall-bundle.ts +11 -11
- package/src/input/commands/recall-capture.ts +13 -11
- package/src/input/commands/recall-query.ts +29 -21
- package/src/input/commands/recall-review.ts +2 -1
- package/src/input/commands/routines-runtime.ts +99 -45
- package/src/input/commands/runtime-services.ts +1 -14
- package/src/input/commands/schedule-runtime.ts +33 -20
- package/src/input/commands/security-runtime.ts +6 -11
- package/src/input/commands/session-content.ts +80 -78
- package/src/input/commands/session-workflow.ts +134 -104
- package/src/input/commands/session.ts +3 -174
- package/src/input/commands/shell-core.ts +38 -17
- package/src/input/commands/subscription-runtime.ts +54 -185
- package/src/input/commands/support-bundle-runtime.ts +53 -0
- package/src/input/commands/tasks-runtime.ts +21 -21
- package/src/input/commands/work-plan-runtime.ts +33 -8
- package/src/input/commands.ts +6 -2
- package/src/input/feed-context-factory.ts +2 -2
- package/src/input/file-picker.ts +3 -2
- package/src/input/handler-command-route.ts +4 -7
- package/src/input/handler-content-actions.ts +89 -26
- package/src/input/handler-feed-routes.ts +19 -12
- package/src/input/handler-feed.ts +6 -5
- package/src/input/handler-interactions.ts +9 -36
- package/src/input/handler-modal-stack.ts +3 -3
- package/src/input/handler-onboarding.ts +52 -91
- package/src/input/handler-shortcuts.ts +16 -7
- package/src/input/handler.ts +50 -23
- package/src/input/input-history.ts +5 -6
- package/src/input/keybindings.ts +27 -16
- package/src/input/mcp-workspace.ts +90 -26
- package/src/input/onboarding/handler-onboarding-routes.ts +3 -1
- package/src/input/onboarding/onboarding-wizard-apply.ts +62 -0
- package/src/input/onboarding/onboarding-wizard-constants.ts +1 -0
- package/src/input/onboarding/onboarding-wizard-helpers.ts +1 -1
- package/src/input/onboarding/onboarding-wizard-operator-steps.ts +223 -55
- package/src/input/onboarding/onboarding-wizard-steps.ts +59 -35
- package/src/input/onboarding/onboarding-wizard-types.ts +4 -1
- package/src/input/onboarding/onboarding-wizard.ts +87 -2
- package/src/input/profile-picker-modal.ts +31 -12
- package/src/input/session-picker-modal.ts +21 -4
- package/src/input/settings-modal-agent-policy.ts +6 -6
- package/src/input/settings-modal-behavior.ts +0 -3
- package/src/input/settings-modal-subscriptions.ts +3 -3
- package/src/input/settings-modal-types.ts +2 -13
- package/src/input/settings-modal.ts +20 -66
- package/src/input/submission-intent.ts +0 -1
- package/src/input/submission-router.ts +3 -3
- package/src/main.ts +18 -10
- package/src/panels/approval-panel.ts +8 -8
- package/src/panels/automation-control-panel.ts +4 -4
- package/src/panels/base-panel.ts +1 -1
- package/src/panels/builtin/agent.ts +1 -1
- package/src/panels/builtin/operations.ts +3 -12
- package/src/panels/builtin/session.ts +32 -24
- package/src/panels/builtin/shared.ts +7 -7
- package/src/panels/cost-tracker-panel.ts +1 -1
- package/src/panels/docs-panel.ts +5 -3
- package/src/panels/index.ts +0 -1
- package/src/panels/knowledge-panel.ts +6 -5
- package/src/panels/memory-panel.ts +7 -6
- package/src/panels/panel-list-panel.ts +36 -80
- package/src/panels/project-planning-panel.ts +19 -12
- package/src/panels/provider-account-snapshot.ts +51 -25
- package/src/panels/provider-accounts-panel.ts +33 -18
- package/src/panels/provider-health-domains.ts +48 -7
- package/src/panels/provider-health-panel.ts +5 -4
- package/src/panels/qr-panel.ts +44 -20
- package/src/panels/schedule-panel.ts +9 -17
- package/src/panels/security-panel.ts +8 -8
- package/src/panels/session-browser-panel.ts +10 -10
- package/src/panels/subscription-panel.ts +6 -6
- package/src/panels/system-messages-panel.ts +3 -3
- package/src/panels/tasks-panel.ts +21 -14
- package/src/panels/tool-inspector-panel.ts +19 -12
- package/src/panels/work-plan-panel.ts +5 -5
- package/src/planning/project-planning-coordinator.ts +3 -3
- package/src/provider-auth-route-display.ts +9 -0
- package/src/renderer/agent-workspace-style.ts +34 -0
- package/src/renderer/agent-workspace.ts +254 -57
- package/src/renderer/autocomplete-overlay.ts +25 -6
- package/src/renderer/block-actions.ts +1 -3
- package/src/renderer/bookmark-modal.ts +19 -4
- package/src/renderer/buffer.ts +4 -2
- package/src/renderer/context-inspector.ts +50 -13
- package/src/renderer/diff.ts +1 -1
- package/src/renderer/file-picker-overlay.ts +19 -6
- package/src/renderer/help-overlay.ts +112 -34
- package/src/renderer/history-search-overlay.ts +19 -4
- package/src/renderer/live-tail-modal.ts +27 -5
- package/src/renderer/mcp-workspace.ts +176 -43
- package/src/renderer/model-picker-overlay.ts +58 -3
- package/src/renderer/model-workspace.ts +104 -22
- package/src/renderer/onboarding/onboarding-wizard.ts +20 -4
- package/src/renderer/process-modal.ts +27 -6
- package/src/renderer/profile-picker-modal.ts +21 -5
- package/src/renderer/search-overlay.ts +25 -5
- package/src/renderer/selection-modal-overlay.ts +46 -14
- package/src/renderer/session-picker-modal.ts +18 -1
- package/src/renderer/settings-modal-helpers.ts +2 -40
- package/src/renderer/settings-modal.ts +88 -55
- package/src/renderer/system-message.ts +1 -1
- package/src/renderer/tool-call.ts +20 -11
- package/src/runtime/agent-runtime-events.ts +129 -0
- package/src/runtime/bootstrap-command-context.ts +7 -1
- package/src/runtime/bootstrap-command-parts.ts +11 -8
- package/src/runtime/bootstrap-core.ts +23 -65
- package/src/runtime/bootstrap-hook-bridge.ts +7 -18
- package/src/runtime/bootstrap-shell.ts +46 -8
- package/src/runtime/bootstrap.ts +36 -46
- package/src/runtime/connected-host-auth.ts +47 -0
- package/src/runtime/diagnostics/panels/index.ts +2 -2
- package/src/runtime/diagnostics/panels/panel-resources.ts +1 -1
- package/src/runtime/diagnostics/panels/policy.ts +7 -7
- package/src/runtime/index.ts +2 -5
- package/src/runtime/onboarding/apply-file-helpers.ts +66 -0
- package/src/runtime/onboarding/apply.ts +288 -74
- package/src/runtime/onboarding/derivation.ts +10 -13
- package/src/runtime/onboarding/snapshot.ts +2 -17
- package/src/runtime/onboarding/types.ts +34 -20
- package/src/runtime/onboarding/verify.ts +105 -4
- package/src/runtime/operator-token-cleanup.ts +3 -3
- package/src/runtime/services.ts +95 -11
- package/src/runtime/store/selectors/index.ts +3 -3
- package/src/runtime/store/state.ts +1 -4
- package/src/runtime/surface-feature-flags.ts +41 -6
- package/src/runtime/ui-read-models.ts +3 -4
- package/src/runtime/ui-services.ts +6 -6
- package/src/shell/blocking-input.ts +2 -1
- package/src/shell/service-settings-sync.ts +7 -7
- package/src/shell/ui-openers.ts +17 -38
- package/src/tools/agent-analysis-registry-policy.ts +0 -1
- package/src/tools/agent-channel-send-tool.ts +133 -0
- package/src/tools/agent-context-policy.ts +1 -1
- package/src/tools/agent-knowledge-ingest-tool.ts +405 -0
- package/src/tools/agent-knowledge-tool.ts +170 -0
- package/src/tools/agent-local-registry-tool.ts +279 -73
- package/src/tools/agent-media-generate-tool.ts +133 -0
- package/src/tools/agent-notify-tool.ts +143 -0
- package/src/tools/agent-operator-action-tool.ts +137 -0
- package/src/tools/agent-operator-briefing-tool.ts +217 -0
- package/src/tools/agent-reminder-schedule-tool.ts +237 -0
- package/src/tools/agent-tool-policy-guard.ts +13 -15
- package/src/tools/agent-work-plan-tool.ts +256 -0
- package/src/version.ts +1 -1
- package/docs/connected-services.md +0 -51
- package/src/cli/package-verification.ts +0 -274
- package/src/input/commands/policy-dispatch.ts +0 -339
- package/src/input/commands/policy.ts +0 -17
- package/src/panels/panel-picker.ts +0 -105
- package/src/panels/policy-panel.ts +0 -308
- package/src/renderer/panel-picker-overlay.ts +0 -202
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PanelListPanel — Shows
|
|
2
|
+
* PanelListPanel — Shows registered panel routes grouped by category.
|
|
3
3
|
*
|
|
4
4
|
* Features:
|
|
5
|
-
* - Arrow keys to navigate, Enter to
|
|
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 /
|
|
10
|
+
* Open the current operator workspace via /agent.
|
|
14
11
|
*/
|
|
15
|
-
import type { Line
|
|
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 {
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
|
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
|
|
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: '/
|
|
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
|
|
325
|
-
const
|
|
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: '
|
|
329
|
-
{ label: '
|
|
330
|
-
{ label: '
|
|
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, [[`
|
|
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
|
|
314
|
+
const routeCategory = agentWorkspaceCategoryForPanel(entry.reg.id);
|
|
365
315
|
const blockLines: Line[] = [
|
|
366
316
|
buildPanelListRow(width, [
|
|
367
317
|
{ text: dot, fg: dotColor },
|
|
368
|
-
{ text:
|
|
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, '
|
|
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
|
|
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,
|
|
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
|
-
['
|
|
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: '
|
|
363
|
-
lines: [buildPanelLine(width, [[' No
|
|
369
|
+
title: 'Open Gaps',
|
|
370
|
+
lines: [buildPanelLine(width, [[' No open gaps.', C.good]])],
|
|
364
371
|
};
|
|
365
372
|
}
|
|
366
373
|
return {
|
|
367
|
-
title: '
|
|
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, [['
|
|
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
|
|
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
|
|
539
|
+
answer: 'Use standard completion checks for this goal.',
|
|
533
540
|
});
|
|
534
541
|
}
|
|
535
542
|
if (question.recommendedAnswer?.trim() && !this.isGenericRecommendation(question.recommendedAnswer)) {
|
|
@@ -600,7 +607,7 @@ export class ProjectPlanningPanel extends BasePanel {
|
|
|
600
607
|
return;
|
|
601
608
|
}
|
|
602
609
|
if (!this.submitAnswer) {
|
|
603
|
-
this.setError('Planning answer submission is
|
|
610
|
+
this.setError('Planning answer submission is unavailable in this Agent runtime. Return to the composer and answer there.');
|
|
604
611
|
this.requestRender();
|
|
605
612
|
return;
|
|
606
613
|
}
|
|
@@ -78,7 +78,7 @@ function determineFreshness(input: {
|
|
|
78
78
|
readonly hasSubscription: boolean;
|
|
79
79
|
readonly expiresAt?: number;
|
|
80
80
|
readonly pending: boolean;
|
|
81
|
-
readonly
|
|
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.
|
|
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
|
|
98
|
-
{ label: '1-week window', detail: 'Subscription-backed
|
|
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.
|
|
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
|
|
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 (
|
|
171
|
+
if (hasUsableServiceOAuth) usableRoutes.push('service-oauth');
|
|
152
172
|
|
|
153
|
-
const activeRoute = determineActiveRoute(usableRoutes
|
|
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
|
-
|
|
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:
|
|
189
|
-
freshness: 'healthy',
|
|
190
|
-
detail:
|
|
191
|
-
|
|
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[] = [`${
|
|
197
|
-
if (serviceConfig) notes.push(`
|
|
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
|
|
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 && !
|
|
219
|
-
issues.push('
|
|
220
|
-
recommendedActions.push(`Repair
|
|
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:
|
|
227
|
-
configured: hasApiKey || hasSubscription || hasServiceOAuth ||
|
|
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
|
-
? '
|
|
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 ?
|
|
242
|
-
fallbackRisk: hasSubscription && hasApiKey
|
|
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: ` ${
|
|
57
|
-
{ text: ` models
|
|
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
|
|
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: '
|
|
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
|
|
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
|
|
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, [[`
|
|
144
|
-
buildPanelLine(width, [[` Available routes
|
|
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
|
|
163
|
+
[' Expires ', C.label],
|
|
149
164
|
[new Date(selected.expiresAt).toISOString(), C.dim],
|
|
150
|
-
[' Token
|
|
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, [[`
|
|
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}
|
|
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
|
|
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}
|
|
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
|
|
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
|
|
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
|
|
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]]));
|