@pellux/goodvibes-agent 1.4.4 → 1.5.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 (227) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +7 -7
  3. package/dist/package/main.js +7424 -12241
  4. package/docs/README.md +2 -2
  5. package/docs/channels-remote-and-api.md +1 -1
  6. package/docs/getting-started.md +2 -2
  7. package/docs/release-and-publishing.md +1 -1
  8. package/docs/tools-and-commands.md +5 -5
  9. package/package.json +4 -2
  10. package/release/performance-snapshot.json +2 -2
  11. package/release/release-notes.md +11 -7
  12. package/release/release-readiness.json +6 -6
  13. package/src/agent/behavior-discovery-summary.ts +4 -18
  14. package/src/agent/calendar-registry.ts +322 -0
  15. package/src/agent/competitive-feature-inventory.ts +268 -130
  16. package/src/agent/document-registry.ts +5 -1
  17. package/src/agent/email/email-service.ts +350 -0
  18. package/src/agent/email/imap-client.ts +596 -0
  19. package/src/agent/email/smtp-client.ts +453 -0
  20. package/src/agent/ics-calendar.ts +662 -0
  21. package/src/agent/ics-timezone.ts +172 -0
  22. package/src/agent/markdown-frontmatter.ts +31 -0
  23. package/src/agent/memory-safety.ts +1 -1
  24. package/src/agent/note-registry.ts +3 -9
  25. package/src/agent/persona-discovery.ts +3 -15
  26. package/src/agent/persona-registry.ts +1 -10
  27. package/src/agent/research-source-registry.ts +5 -1
  28. package/src/agent/routine-discovery.ts +3 -15
  29. package/src/agent/runtime-profile.ts +3 -0
  30. package/src/agent/skill-discovery.ts +3 -15
  31. package/src/agent/skill-draft-proposer.ts +203 -0
  32. package/src/agent/skill-draft-runner.ts +201 -0
  33. package/src/agent/skill-registry.ts +36 -1
  34. package/src/agent/skill-standard.ts +99 -0
  35. package/src/agent/vibe-file.ts +7 -22
  36. package/src/cli/completion.ts +1 -1
  37. package/src/cli/config-overrides.ts +10 -1
  38. package/src/cli/redaction.ts +17 -5
  39. package/src/config/provider-model.ts +2 -1
  40. package/src/config/secret-config.ts +13 -6
  41. package/src/core/activity-feed.ts +97 -0
  42. package/src/core/away-digest.ts +161 -0
  43. package/src/core/conversation-rendering.ts +7 -3
  44. package/src/core/conversation.ts +22 -15
  45. package/src/core/hardware-profile.ts +362 -0
  46. package/src/core/last-seen-store.ts +78 -0
  47. package/src/core/plain-language.ts +52 -0
  48. package/src/core/setup-incomplete-hint.ts +90 -0
  49. package/src/core/system-message-router.ts +38 -87
  50. package/src/input/agent-workspace-basic-command-editor-submission.ts +60 -220
  51. package/src/input/agent-workspace-basic-command-editors.ts +39 -141
  52. package/src/input/agent-workspace-categories.ts +40 -125
  53. package/src/input/agent-workspace-command-editor.ts +4 -0
  54. package/src/input/agent-workspace-host-category.ts +0 -5
  55. package/src/input/agent-workspace-local-editor-submission.ts +3 -1
  56. package/src/input/agent-workspace-navigation.ts +10 -3
  57. package/src/input/agent-workspace-onboarding-actions.ts +132 -0
  58. package/src/input/agent-workspace-onboarding-categories.ts +29 -26
  59. package/src/input/agent-workspace-onboarding-finish.ts +11 -4
  60. package/src/input/agent-workspace-onboarding-state.ts +111 -0
  61. package/src/input/agent-workspace-profile-editor-submission.ts +260 -0
  62. package/src/input/agent-workspace-profile-editors.ts +155 -0
  63. package/src/input/agent-workspace-subscription-editor.ts +7 -0
  64. package/src/input/agent-workspace-types.ts +5 -1
  65. package/src/input/agent-workspace.ts +65 -39
  66. package/src/input/command-registry.ts +18 -5
  67. package/src/input/commands/agent-runtime-profile-runtime.ts +2 -1
  68. package/src/input/commands/agent-skills-runtime.ts +60 -3
  69. package/src/input/commands/calendar-runtime.ts +209 -0
  70. package/src/input/commands/channels-runtime.ts +1 -0
  71. package/src/input/commands/command-error.ts +9 -0
  72. package/src/input/commands/compat-runtime.ts +1 -0
  73. package/src/input/commands/config.ts +1 -0
  74. package/src/input/commands/conversation-runtime.ts +1 -1
  75. package/src/input/commands/delegation-runtime.ts +5 -6
  76. package/src/input/commands/email-runtime.ts +387 -0
  77. package/src/input/commands/experience-runtime.ts +17 -4
  78. package/src/input/commands/guidance-runtime.ts +1 -1
  79. package/src/input/commands/health-runtime.ts +6 -1
  80. package/src/input/commands/knowledge-format.ts +73 -0
  81. package/src/input/commands/knowledge.ts +9 -74
  82. package/src/input/commands/local-provider-runtime.ts +2 -1
  83. package/src/input/commands/local-runtime.ts +10 -4
  84. package/src/input/commands/mcp-runtime.ts +7 -0
  85. package/src/input/commands/notify-runtime.ts +17 -1
  86. package/src/input/commands/onboarding-runtime.ts +1 -0
  87. package/src/input/commands/operator-actions-runtime.ts +1 -0
  88. package/src/input/commands/operator-runtime.ts +3 -0
  89. package/src/input/commands/personas-runtime.ts +1 -0
  90. package/src/input/commands/platform-access-runtime.ts +40 -17
  91. package/src/input/commands/product-runtime.ts +6 -1
  92. package/src/input/commands/provider-accounts-runtime.ts +3 -2
  93. package/src/input/commands/qrcode-runtime.ts +1 -0
  94. package/src/input/commands/routines-runtime.ts +1 -0
  95. package/src/input/commands/runtime-services.ts +0 -13
  96. package/src/input/commands/security-runtime.ts +1 -0
  97. package/src/input/commands/session-content.ts +1 -0
  98. package/src/input/commands/shell-core.ts +5 -2
  99. package/src/input/commands/subscription-runtime.ts +33 -9
  100. package/src/input/commands/support-bundle-runtime.ts +8 -1
  101. package/src/input/commands/tasks-runtime.ts +1 -0
  102. package/src/input/commands/tts-runtime.ts +1 -0
  103. package/src/input/commands/vibe-runtime.ts +1 -0
  104. package/src/input/commands.ts +4 -0
  105. package/src/input/feed-context-factory.ts +3 -12
  106. package/src/input/handler-command-route.ts +7 -60
  107. package/src/input/handler-feed-routes.ts +0 -194
  108. package/src/input/handler-feed.ts +2 -54
  109. package/src/input/handler-interactions.ts +0 -2
  110. package/src/input/handler-modal-stack.ts +0 -9
  111. package/src/input/handler-picker-routes.ts +24 -1
  112. package/src/input/handler-shortcuts.ts +11 -40
  113. package/src/input/handler-ui-state.ts +13 -0
  114. package/src/input/handler.ts +8 -35
  115. package/src/input/keybindings.ts +40 -17
  116. package/src/input/model-picker-local-fit.ts +130 -0
  117. package/src/input/submission-router.ts +0 -5
  118. package/src/main.ts +111 -89
  119. package/src/renderer/activity-sidebar.ts +186 -0
  120. package/src/renderer/agent-workspace-context-lines.ts +5 -48
  121. package/src/renderer/agent-workspace-style.ts +1 -1
  122. package/src/renderer/agent-workspace.ts +14 -2
  123. package/src/renderer/compositor.ts +37 -125
  124. package/src/renderer/conversation-overlays.ts +3 -3
  125. package/src/renderer/help-overlay.ts +7 -5
  126. package/src/renderer/model-workspace.ts +101 -86
  127. package/src/{panels → renderer}/polish.ts +3 -3
  128. package/src/renderer/shell-surface.ts +4 -5
  129. package/src/renderer/status-token.ts +31 -11
  130. package/src/renderer/tab-strip.ts +1 -1
  131. package/src/renderer/tool-call.ts +7 -8
  132. package/src/renderer/tool-labels.ts +92 -0
  133. package/src/renderer/ui-factory.ts +48 -96
  134. package/src/runtime/bootstrap-command-context.ts +0 -5
  135. package/src/runtime/bootstrap-command-parts.ts +6 -15
  136. package/src/runtime/bootstrap-core.ts +34 -13
  137. package/src/runtime/bootstrap-hook-bridge.ts +3 -1
  138. package/src/runtime/bootstrap-shell.ts +3 -50
  139. package/src/runtime/bootstrap.ts +3 -4
  140. package/src/runtime/context.ts +1 -1
  141. package/src/runtime/diagnostics/panels/index.ts +0 -1
  142. package/src/runtime/diagnostics/panels/policy.ts +1 -1
  143. package/src/runtime/execution-ledger.ts +16 -1
  144. package/src/runtime/index.ts +6 -1
  145. package/src/runtime/onboarding/index.ts +1 -0
  146. package/src/runtime/onboarding/onboarding-state.ts +200 -0
  147. package/src/{panels → runtime}/provider-account-snapshot.ts +5 -2
  148. package/src/runtime/services.ts +16 -4
  149. package/src/runtime/terminal-output-guard.ts +7 -0
  150. package/src/runtime/tool-permission-safety.ts +1 -1
  151. package/src/runtime/ui/model-picker/data-provider.ts +1 -1
  152. package/src/runtime/ui/model-picker/health-enrichment.ts +2 -2
  153. package/src/runtime/ui/model-picker/types.ts +2 -2
  154. package/src/runtime/ui/provider-health/data-provider.ts +3 -3
  155. package/src/runtime/ui/provider-health/types.ts +2 -2
  156. package/src/runtime/ui-services.ts +0 -2
  157. package/src/shell/agent-workspace-fullscreen.ts +0 -1
  158. package/src/shell/autonomy-surfacing.ts +272 -0
  159. package/src/shell/session-continuity-hints.ts +0 -6
  160. package/src/shell/startup-wiring.ts +221 -0
  161. package/src/shell/ui-openers.ts +18 -29
  162. package/src/tools/agent-context-policy.ts +1 -1
  163. package/src/tools/agent-harness-background-processes.ts +4 -4
  164. package/src/tools/agent-harness-browser-cockpit-route.ts +3 -3
  165. package/src/tools/agent-harness-command-runner.ts +6 -1
  166. package/src/tools/agent-harness-document-ops.ts +26 -53
  167. package/src/tools/agent-harness-execution-history.ts +1 -13
  168. package/src/tools/agent-harness-execution-posture.ts +0 -15
  169. package/src/tools/agent-harness-keybinding-metadata.ts +28 -29
  170. package/src/tools/agent-harness-local-model-cookbook.ts +24 -1
  171. package/src/tools/agent-harness-metadata.ts +16 -0
  172. package/src/tools/agent-harness-mode-catalog.ts +2 -9
  173. package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
  174. package/src/tools/agent-harness-setup-posture-utils.ts +1 -1
  175. package/src/tools/agent-harness-tool-schema.ts +13 -14
  176. package/src/tools/agent-harness-tool.ts +27 -23
  177. package/src/tools/agent-harness-ui-surface-metadata.ts +10 -20
  178. package/src/tools/agent-harness-workspace-action-runner.ts +3 -4
  179. package/src/tools/agent-workspace-tool.ts +2 -33
  180. package/src/utils/terminal-width.ts +9 -3
  181. package/src/version.ts +1 -1
  182. package/src/input/agent-workspace-panel-route.ts +0 -44
  183. package/src/input/panel-integration-actions.ts +0 -26
  184. package/src/panels/approval-panel.ts +0 -149
  185. package/src/panels/automation-control-panel.ts +0 -212
  186. package/src/panels/base-panel.ts +0 -254
  187. package/src/panels/builtin/agent.ts +0 -58
  188. package/src/panels/builtin/knowledge.ts +0 -26
  189. package/src/panels/builtin/operations.ts +0 -121
  190. package/src/panels/builtin/session.ts +0 -138
  191. package/src/panels/builtin/shared.ts +0 -275
  192. package/src/panels/builtin/usage.ts +0 -21
  193. package/src/panels/builtin-panels.ts +0 -23
  194. package/src/panels/confirm-state.ts +0 -61
  195. package/src/panels/context-visualizer-panel.ts +0 -204
  196. package/src/panels/cost-tracker-panel.ts +0 -444
  197. package/src/panels/docs-panel.ts +0 -285
  198. package/src/panels/index.ts +0 -25
  199. package/src/panels/knowledge-panel.ts +0 -417
  200. package/src/panels/memory-panel.ts +0 -226
  201. package/src/panels/panel-list-panel.ts +0 -464
  202. package/src/panels/panel-manager.ts +0 -570
  203. package/src/panels/provider-accounts-panel.ts +0 -233
  204. package/src/panels/provider-health-domains.ts +0 -208
  205. package/src/panels/provider-health-panel.ts +0 -720
  206. package/src/panels/provider-health-tracker.ts +0 -115
  207. package/src/panels/provider-stats-panel.ts +0 -366
  208. package/src/panels/qr-panel.ts +0 -207
  209. package/src/panels/schedule-panel.ts +0 -321
  210. package/src/panels/scrollable-list-panel.ts +0 -491
  211. package/src/panels/search-focus.ts +0 -32
  212. package/src/panels/security-panel.ts +0 -295
  213. package/src/panels/session-browser-panel.ts +0 -395
  214. package/src/panels/session-maintenance.ts +0 -125
  215. package/src/panels/subscription-panel.ts +0 -263
  216. package/src/panels/system-messages-panel.ts +0 -230
  217. package/src/panels/tasks-panel.ts +0 -344
  218. package/src/panels/thinking-panel.ts +0 -304
  219. package/src/panels/token-budget-panel.ts +0 -475
  220. package/src/panels/tool-inspector-panel.ts +0 -436
  221. package/src/panels/types.ts +0 -54
  222. package/src/renderer/panel-composite.ts +0 -158
  223. package/src/renderer/panel-tab-bar.ts +0 -69
  224. package/src/renderer/panel-workspace-bar.ts +0 -42
  225. package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
  226. package/src/tools/agent-harness-panel-metadata.ts +0 -211
  227. /package/src/runtime/perf/{panel-health-monitor.ts → component-health.ts} +0 -0
@@ -1,125 +0,0 @@
1
- export type PanelGuidanceMode = 'off' | 'minimal' | 'guided';
2
- export type PanelSessionMaintenanceLevel = 'stable' | 'watch' | 'suggest-compact' | 'compacting' | 'needs-repair' | 'unknown';
3
-
4
- export interface PanelSessionMaintenanceLineageEntry {
5
- readonly branchReason?: string;
6
- }
7
-
8
- export interface PanelSessionMaintenanceSession {
9
- readonly lineage?: readonly PanelSessionMaintenanceLineageEntry[];
10
- readonly lastCompactedAt?: number;
11
- readonly compactionMessageCount?: number;
12
- }
13
-
14
- export interface PanelSessionMaintenanceInput {
15
- readonly currentTokens: number;
16
- readonly contextWindow: number;
17
- readonly messageCount?: number;
18
- readonly sessionMemoryCount?: number;
19
- readonly session?: PanelSessionMaintenanceSession | null;
20
- }
21
-
22
- export interface PanelSessionMaintenanceStatus {
23
- readonly level: PanelSessionMaintenanceLevel;
24
- readonly summary: string;
25
- readonly reasons: readonly string[];
26
- readonly nextSteps: readonly string[];
27
- readonly guidanceMode: PanelGuidanceMode;
28
- readonly usagePct: number;
29
- readonly remainingTokens: number;
30
- readonly thresholdPct: number;
31
- readonly autoCompactEnabled: boolean;
32
- readonly sessionMemoryCount: number;
33
- readonly compactionCount: number;
34
- readonly lastCompactedAt?: number;
35
- readonly compactRecommended: boolean;
36
- }
37
-
38
- export function evaluateSessionMaintenance(input: PanelSessionMaintenanceInput): PanelSessionMaintenanceStatus {
39
- const guidanceMode: PanelGuidanceMode = 'minimal';
40
- const thresholdPct = 80;
41
- const autoCompactEnabled = false;
42
- const usagePct = input.contextWindow > 0 ? Math.min(100, Math.round((Math.max(0, input.currentTokens) / input.contextWindow) * 100)) : 0;
43
- const remainingTokens = Math.max(0, input.contextWindow - input.currentTokens);
44
- const sessionMemoryCount = Math.max(0, input.sessionMemoryCount ?? 0);
45
- const compactionCount = Math.max(0, input.session?.lineage?.filter((entry) => entry.branchReason === 'compaction').length ?? 0);
46
- const lastCompactedAt = input.session?.lastCompactedAt;
47
- const messageCount = Math.max(0, input.messageCount ?? 0);
48
- const staleByMessageGrowth = compactionCount > 0
49
- ? messageCount - (input.session?.compactionMessageCount ?? 0) >= 12
50
- : messageCount >= 24;
51
- if (input.contextWindow <= 0) {
52
- return {
53
- level: 'unknown',
54
- summary: 'Context window unavailable.',
55
- reasons: ['Current model does not expose a known context limit yet.'],
56
- nextSteps: ['/provider', '/context'],
57
- guidanceMode,
58
- usagePct,
59
- remainingTokens,
60
- thresholdPct,
61
- autoCompactEnabled,
62
- sessionMemoryCount,
63
- compactionCount,
64
- lastCompactedAt,
65
- compactRecommended: false,
66
- };
67
- }
68
-
69
- const reasons: string[] = [];
70
- const nextSteps: string[] = [];
71
- let summary = 'Session maintenance is stable.';
72
- let compactRecommended = false;
73
- let level: PanelSessionMaintenanceLevel = 'stable';
74
-
75
- if (usagePct >= 90) {
76
- level = 'needs-repair';
77
- summary = `Compact now to recover context headroom (${usagePct}% used).`;
78
- reasons.push(`Context pressure is high at ${usagePct}% usage.`);
79
- nextSteps.push('/compact', '/context');
80
- compactRecommended = true;
81
- } else if (usagePct >= thresholdPct || remainingTokens <= 15_000) {
82
- level = 'suggest-compact';
83
- summary = `Watch context growth (${usagePct}% used).`;
84
- reasons.push(`Context pressure is climbing at ${usagePct}% usage.`);
85
- nextSteps.push('/context');
86
- compactRecommended = true;
87
- } else if (usagePct >= 70 || staleByMessageGrowth) {
88
- level = 'watch';
89
- summary = staleByMessageGrowth
90
- ? `Conversation has grown ${messageCount.toLocaleString()} messages since the last maintenance checkpoint.`
91
- : `Watch context growth (${usagePct}% used, threshold ${thresholdPct}%).`;
92
- reasons.push(staleByMessageGrowth
93
- ? `Conversation has grown ${messageCount.toLocaleString()} messages since the last maintenance checkpoint.`
94
- : `Context usage is climbing toward the ${thresholdPct}% maintenance threshold.`);
95
- nextSteps.push('/context');
96
- } else {
97
- reasons.push('Context pressure is currently within the stable operating band.');
98
- }
99
-
100
- if (sessionMemoryCount > 0) {
101
- reasons.push(`${sessionMemoryCount} pinned session memor${sessionMemoryCount === 1 ? 'y is' : 'ies are'} preserved during compaction.`);
102
- }
103
- if (compactionCount > 0) {
104
- reasons.push(`Last compaction ran ${lastCompactedAt ? new Date(lastCompactedAt).toISOString() : 'recently'}.`);
105
- }
106
- if (!autoCompactEnabled) {
107
- reasons.push('Auto-compact is currently disabled; use /compact when you need to recover headroom.');
108
- }
109
-
110
- return {
111
- level,
112
- summary,
113
- reasons,
114
- nextSteps,
115
- guidanceMode,
116
- usagePct,
117
- remainingTokens,
118
- thresholdPct,
119
- autoCompactEnabled,
120
- sessionMemoryCount,
121
- compactionCount,
122
- lastCompactedAt,
123
- compactRecommended,
124
- };
125
- }
@@ -1,263 +0,0 @@
1
- import type { Line } from '../types/grid.ts';
2
- import { createEmptyLine } from '../types/grid.ts';
3
- import { ScrollableListPanel } from './scrollable-list-panel.ts';
4
- import type { ProviderSubscription, PendingSubscriptionLogin } from '@pellux/goodvibes-sdk/platform/config';
5
- import { listBuiltinSubscriptionProviders } from '@pellux/goodvibes-sdk/platform/config';
6
- import type { ServiceInspectionQuery, SubscriptionAccessQuery } from '../runtime/ui-service-queries.ts';
7
- import {
8
- buildEmptyState,
9
- buildGuidanceLine,
10
- buildKeyValueLine,
11
- buildPanelListRow,
12
- buildPanelLine,
13
- buildSummaryBlock,
14
- buildPanelWorkspace,
15
- DEFAULT_PANEL_PALETTE,
16
- } from './polish.ts';
17
-
18
- const C = {
19
- ...DEFAULT_PANEL_PALETTE,
20
- header: '#e2e8f0',
21
- headerBg: '#1e293b',
22
- dim: '#64748b',
23
- info: '#38bdf8',
24
- good: '#22c55e',
25
- warn: '#f59e0b',
26
- bad: '#ef4444',
27
- selectedBg: '#0f172a',
28
- } as const;
29
-
30
- interface SubscriptionRow {
31
- readonly provider: string;
32
- readonly hasOAuthConfig: boolean;
33
- readonly subscription: ProviderSubscription | null;
34
- readonly pending: PendingSubscriptionLogin | null;
35
- }
36
-
37
- function statusOf(row: SubscriptionRow): 'active' | 'pending' | 'available' | 'unconfigured' {
38
- if (row.subscription) return 'active';
39
- if (row.pending) return 'pending';
40
- return row.hasOAuthConfig ? 'available' : 'unconfigured';
41
- }
42
-
43
- function statusColor(status: ReturnType<typeof statusOf>): string {
44
- switch (status) {
45
- case 'active':
46
- return C.good;
47
- case 'pending':
48
- return C.warn;
49
- case 'available':
50
- return C.info;
51
- case 'unconfigured':
52
- return C.dim;
53
- }
54
- }
55
-
56
- export class SubscriptionPanel extends ScrollableListPanel<SubscriptionRow> {
57
- private readonly serviceRegistry: Pick<ServiceInspectionQuery, 'getAll'>;
58
- private readonly subscriptionManager: SubscriptionAccessQuery;
59
- private rows: SubscriptionRow[] = [];
60
- private logoutConfirmationTarget: string | null = null;
61
-
62
- public constructor(
63
- serviceRegistry: Pick<ServiceInspectionQuery, 'getAll'>,
64
- subscriptionManager: SubscriptionAccessQuery,
65
- ) {
66
- super('subscription', 'Subscriptions', 'B', 'monitoring');
67
- this.showSelectionGutter = true; // I5: non-color selection affordance
68
- this.serviceRegistry = serviceRegistry;
69
- this.subscriptionManager = subscriptionManager;
70
- }
71
-
72
- public override onActivate(): void {
73
- super.onActivate();
74
- this.refresh();
75
- }
76
-
77
- protected override getPalette() { return C; }
78
- protected override getEmptyStateMessage() { return ' No provider subscriptions are active yet.'; }
79
- protected override getEmptyStateActions() {
80
- return [
81
- { command: '/subscription login openai start --yes', summary: 'start the first-class OpenAI subscription flow' },
82
- { command: '/subscription login <provider> finish <code> --yes', summary: 'finish a pending provider subscription login' },
83
- { command: '/auth review', summary: 'inspect connected-host auth posture without exposing token values' },
84
- ];
85
- }
86
-
87
- protected getItems(): readonly SubscriptionRow[] {
88
- return this.rows;
89
- }
90
-
91
- protected renderItem(row: SubscriptionRow, index: number, selected: boolean, width: number): Line {
92
- const status = statusOf(row);
93
- return buildPanelListRow(width, [
94
- { text: row.provider.padEnd(16).slice(0, 16), fg: C.value },
95
- { text: ` ${status.toUpperCase().padEnd(12)}`, fg: statusColor(status) },
96
- { text: ` oauth=${row.hasOAuthConfig ? 'yes' : 'no'} `, fg: row.hasOAuthConfig ? C.info : C.dim },
97
- { text: ` override=${row.subscription ? 'active' : 'off'}`, fg: row.subscription ? C.good : C.dim },
98
- ], C, { selected, selectedBg: C.selectedBg });
99
- }
100
-
101
- public handleInput(key: string): boolean {
102
- if (this.rows.length === 0) return false;
103
- const selected = this.rows[this.selectedIndex] ?? null;
104
- if (key === 'ArrowUp' || key === 'k') {
105
- this.selectedIndex = Math.max(0, this.selectedIndex - 1);
106
- this.logoutConfirmationTarget = null;
107
- this.markDirty();
108
- return true;
109
- }
110
- if (key === 'ArrowDown' || key === 'j') {
111
- this.selectedIndex = Math.min(this.rows.length - 1, this.selectedIndex + 1);
112
- this.logoutConfirmationTarget = null;
113
- this.markDirty();
114
- return true;
115
- }
116
- if (key === 'enter' || key === 'x') {
117
- if (!selected?.subscription) return false;
118
- if (this.logoutConfirmationTarget === null || this.logoutConfirmationTarget !== selected.provider) {
119
- this.logoutConfirmationTarget = selected.provider;
120
- this.markDirty();
121
- return true;
122
- }
123
- this.subscriptionManager.logout(selected.provider);
124
- this.logoutConfirmationTarget = null;
125
- this.refresh();
126
- this.markDirty();
127
- return true;
128
- }
129
- if ((key === 'n' || key === 'escape') && this.logoutConfirmationTarget) {
130
- this.logoutConfirmationTarget = null;
131
- this.markDirty();
132
- return true;
133
- }
134
- if (key === 'r') {
135
- this.refresh();
136
- this.logoutConfirmationTarget = null;
137
- this.markDirty();
138
- return true;
139
- }
140
- return false;
141
- }
142
-
143
- private refresh(): void {
144
- const manager = this.subscriptionManager;
145
- const services = this.serviceRegistry.getAll();
146
- const builtinProviders = listBuiltinSubscriptionProviders();
147
- const providers = new Set<string>([
148
- ...builtinProviders.map((provider) => provider.provider),
149
- ...Object.values(services)
150
- .filter((service) => service.authType === 'oauth' && service.oauth)
151
- .map((service) => service.providerId ?? service.name),
152
- ...manager.list().map((entry) => entry.provider),
153
- ...manager.listPending().map((entry) => entry.provider),
154
- ]);
155
- this.rows = [...providers]
156
- .sort((a, b) => a.localeCompare(b))
157
- .map((provider) => {
158
- const service = Object.values(services).find((entry) => (
159
- (entry.providerId ?? entry.name) === provider && entry.authType === 'oauth' && entry.oauth
160
- ));
161
- const builtin = builtinProviders.find((entry) => entry.provider === provider);
162
- return {
163
- provider,
164
- hasOAuthConfig: Boolean(service?.oauth) || Boolean(builtin),
165
- subscription: manager.get(provider),
166
- pending: manager.getPending(provider),
167
- } satisfies SubscriptionRow;
168
- });
169
- this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.rows.length - 1));
170
- }
171
-
172
- public render(width: number, height: number): Line[] {
173
- this.refresh();
174
- this.clampSelection();
175
- const intro = 'Review provider login state, subscription-backed routing, and pending browser sign-ins.';
176
-
177
- const activeCount = this.rows.filter((row) => row.subscription).length;
178
- const pendingCount = this.rows.filter((row) => row.pending).length;
179
- const postureLines = [
180
- buildKeyValueLine(width, [
181
- { label: 'configured', value: String(this.rows.filter((row) => row.hasOAuthConfig).length), valueColor: C.info },
182
- { label: 'active', value: String(activeCount), valueColor: activeCount > 0 ? C.good : C.dim },
183
- { label: 'pending', value: String(pendingCount), valueColor: pendingCount > 0 ? C.warn : C.dim },
184
- { label: 'providers', value: String(this.rows.length), valueColor: C.value },
185
- ], C),
186
- buildKeyValueLine(width, [
187
- { label: 'selected', value: (this.rows[this.selectedIndex]?.provider ?? 'none'), valueColor: this.rows[this.selectedIndex] ? C.value : C.dim },
188
- { label: 'status', value: this.rows[this.selectedIndex] ? statusOf(this.rows[this.selectedIndex]!) : 'n/a', valueColor: this.rows[this.selectedIndex] ? statusColor(statusOf(this.rows[this.selectedIndex]!)) : C.dim },
189
- ], C),
190
- buildGuidanceLine(width, '/subscription login <provider> start --yes', 'start browser login, then finish manually with the callback code or URL', C),
191
- ];
192
-
193
- // Empty state: render posture + base empty state
194
- if (this.rows.length === 0) {
195
- const summaryLines = buildSummaryBlock(width, 'Subscription posture', postureLines, C);
196
- const emptyLines = buildEmptyState(
197
- width,
198
- this.getEmptyStateMessage(),
199
- 'Built-in OAuth-capable providers and configured provider routes will appear here once available for browser login or session import.',
200
- this.getEmptyStateActions(),
201
- C,
202
- );
203
- const workspace = buildPanelWorkspace(width, height, {
204
- title: 'Provider Subscriptions',
205
- intro,
206
- sections: [{ lines: [...summaryLines, ...emptyLines] }],
207
- footerLines: [
208
- buildGuidanceLine(width, '/subscription login <provider> start --yes', 'start browser login, then finish explicitly from the Agent command or workspace form', C),
209
- buildPanelLine(width, [[' Up/Down move Enter/X sign out selected provider r refresh', C.dim]]),
210
- ],
211
- palette: C,
212
- });
213
- while (workspace.length < height) workspace.push(createEmptyLine(width));
214
- return workspace.slice(0, height);
215
- }
216
-
217
- const selectedRow = this.rows[this.selectedIndex];
218
- const detailRows: Line[] = [];
219
- if (selectedRow) {
220
- detailRows.push(buildKeyValueLine(width, [
221
- { label: 'provider', value: selectedRow.provider, valueColor: C.value },
222
- { label: 'status', value: statusOf(selectedRow), valueColor: statusColor(statusOf(selectedRow)) },
223
- { label: 'oauth config', value: selectedRow.hasOAuthConfig ? 'present' : 'missing', valueColor: selectedRow.hasOAuthConfig ? C.good : C.bad },
224
- ], C));
225
- if (selectedRow.subscription) {
226
- const expires = selectedRow.subscription.expiresAt
227
- ? new Date(selectedRow.subscription.expiresAt).toISOString()
228
- : 'n/a';
229
- detailRows.push(buildKeyValueLine(width, [
230
- { label: 'token type', value: selectedRow.subscription.tokenType, valueColor: C.info },
231
- { label: 'expires', value: expires, valueColor: C.dim },
232
- ], C));
233
- detailRows.push(buildPanelLine(width, [[
234
- ` ${selectedRow.subscription.overrideAmbientApiKeys
235
- ? 'Provider subscription overrides ambient API-key resolution for this provider.'
236
- : 'Stored for subscription-backed flows. Ambient API-key resolution remains unchanged.'}`,
237
- C.dim,
238
- ]]));
239
- if (this.logoutConfirmationTarget === selectedRow.provider) {
240
- detailRows.push(buildPanelLine(width, [[` Press Enter or X again to sign out ${selectedRow.provider}.`, C.warn]]));
241
- }
242
- } else if (selectedRow.pending) {
243
- detailRows.push(buildPanelLine(width, [[' Login is pending. Finish with /subscription login <provider> finish <code> --yes.', C.warn]]));
244
- } else if (selectedRow.hasOAuthConfig) {
245
- detailRows.push(buildPanelLine(width, [[' Ready for login. Start with /subscription login <provider> start --yes.', C.dim]]));
246
- } else {
247
- detailRows.push(buildPanelLine(width, [[' Add a provider-specific OAuth config or enable a built-in subscription provider to use subscription login.', C.bad]]));
248
- }
249
- }
250
-
251
- const headerLines: Line[] = buildSummaryBlock(width, 'Subscription posture', postureLines, C);
252
-
253
- return this.renderList(width, height, {
254
- title: 'Provider Subscriptions',
255
- header: headerLines,
256
- footer: [
257
- ...detailRows,
258
- buildGuidanceLine(width, '/subscription login <provider> start --yes', 'start browser-based provider login from the packaged subscription flow', C),
259
- buildPanelLine(width, [[' Up/Down move Enter/X sign out selected provider r refresh', C.dim]]),
260
- ],
261
- });
262
- }
263
- }
@@ -1,230 +0,0 @@
1
- /**
2
- * SystemMessagesPanel — displays operational system messages routed away
3
- * from the main conversation.
4
- *
5
- * Migrated (Wave B2): extends ScrollableListPanel<SystemMessageEntry>.
6
- * Navigation (up/down/j/k/pageup/pagedown/g/G) is handled by the base class.
7
- */
8
-
9
- import { ScrollableListPanel } from './scrollable-list-panel.ts';
10
- import type { Line } from '../types/grid.ts';
11
- import type { ComponentHealthMonitor } from '../runtime/perf/panel-health-monitor.ts';
12
- import {
13
- buildBodyText,
14
- buildEmptyState,
15
- buildGuidanceLine,
16
- buildKeyValueLine,
17
- buildPanelListRow,
18
- buildPanelLine,
19
- buildSummaryBlock,
20
- buildPanelWorkspace,
21
- resolvePrimaryScrollableSection,
22
- DEFAULT_PANEL_PALETTE,
23
- extendPalette,
24
- type PanelPalette,
25
- type PanelWorkspaceSection,
26
- } from './polish.ts';
27
- import { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
28
-
29
- const MAX_MESSAGES = 500;
30
-
31
- const C = extendPalette(DEFAULT_PANEL_PALETTE, {
32
- header: '#00ffff',
33
- headerBg: '#0f172a',
34
- high: '#fbbf24',
35
- low: '#9ca3af',
36
- ts: '#6b7280',
37
- } as const);
38
-
39
- export type SystemMessagePriority = 'high' | 'low';
40
-
41
- export interface SystemMessageEntry {
42
- ts: number;
43
- text: string;
44
- priority: SystemMessagePriority;
45
- }
46
-
47
- function fmtTime(ts: number): string {
48
- const d = new Date(ts);
49
- const hh = String(d.getHours()).padStart(2, '0');
50
- const mm = String(d.getMinutes()).padStart(2, '0');
51
- const ss = String(d.getSeconds()).padStart(2, '0');
52
- return `${hh}:${mm}:${ss}`;
53
- }
54
-
55
- export class SystemMessagesPanel extends ScrollableListPanel<SystemMessageEntry> {
56
- private _messages: SystemMessageEntry[] = [];
57
- private readonly configManager: ConfigManager;
58
-
59
- constructor(configManager: ConfigManager, componentHealthMonitor?: ComponentHealthMonitor) {
60
- super('system-messages', 'System Messages', 'J', 'monitoring', componentHealthMonitor);
61
- this.configManager = configManager;
62
- }
63
-
64
- // ---------------------------------------------------------------------------
65
- // ScrollableListPanel contract
66
- // ---------------------------------------------------------------------------
67
-
68
- protected getItems(): readonly SystemMessageEntry[] {
69
- return this._messages;
70
- }
71
-
72
- protected renderItem(
73
- entry: SystemMessageEntry,
74
- index: number,
75
- selected: boolean,
76
- width: number,
77
- ): Line {
78
- const preview = entry.text.replace(/\s+/g, ' ').trim();
79
- return buildPanelListRow(width, [
80
- { text: `${fmtTime(entry.ts)} `, fg: C.ts },
81
- {
82
- text: `${entry.priority === 'high' ? 'HIGH' : 'LOW '.padEnd(4)} `,
83
- fg: entry.priority === 'high' ? C.high : C.low,
84
- bold: entry.priority === 'high',
85
- },
86
- { text: preview, fg: C.value },
87
- ], C, {
88
- selected,
89
- marker: entry.priority === 'high' ? '!' : '\u00b7',
90
- });
91
- }
92
-
93
- protected override getPalette(): PanelPalette {
94
- return C;
95
- }
96
-
97
- protected override getEmptyStateMessage(): string {
98
- return ' No system messages yet.';
99
- }
100
-
101
- // ---------------------------------------------------------------------------
102
- // Public API
103
- // ---------------------------------------------------------------------------
104
-
105
- push(text: string, priority: SystemMessagePriority): void {
106
- this._messages.push({ ts: Date.now(), text, priority });
107
- if (this._messages.length > MAX_MESSAGES) {
108
- this._messages.shift();
109
- if (this.selectedIndex > 0) this.selectedIndex--;
110
- }
111
- // Auto-follow: jump to latest message
112
- this.selectedIndex = Math.max(0, this._messages.length - 1);
113
- this.markDirty();
114
- }
115
-
116
- get count(): number {
117
- return this._messages.length;
118
- }
119
-
120
- getMessages(): readonly SystemMessageEntry[] {
121
- return this._messages;
122
- }
123
-
124
- // ---------------------------------------------------------------------------
125
- // Input — base class handles all navigation; nothing custom here
126
- // ---------------------------------------------------------------------------
127
-
128
- // ---------------------------------------------------------------------------
129
- // Render — multi-section layout (posture + list + detail)
130
- // ---------------------------------------------------------------------------
131
-
132
- override render(width: number, height: number): Line[] {
133
- return this.trackedRender(() => {
134
- const intro = 'Operational system traffic routed out of the main conversation to reduce noise and keep runtime status reviewable.';
135
-
136
- if (this._messages.length === 0) {
137
- this.needsRender = false;
138
- const lines = buildPanelWorkspace(width, height, {
139
- title: 'System Messages',
140
- intro,
141
- sections: [{
142
- lines: buildEmptyState(
143
- width,
144
- this.getEmptyStateMessage(),
145
- 'Model switches, scan notices, provider/system state, and other operational updates will appear here once the runtime starts emitting them.',
146
- [
147
- { command: '/help', summary: 'review commands and operator workflows' },
148
- { command: '/agent', summary: 'open the Agent operator workspace' },
149
- ],
150
- C,
151
- ),
152
- }],
153
- footerLines: [
154
- buildPanelLine(width, [[' j/k or Up/Down scroll g/G jump low-priority system traffic lands here by default', C.dim]]),
155
- ],
156
- palette: C,
157
- });
158
- return lines;
159
- }
160
-
161
- const highCount = this._messages.filter((entry) => entry.priority === 'high').length;
162
- const lowCount = this._messages.length - highCount;
163
- this.selectedIndex = Math.min(this.selectedIndex, this._messages.length - 1);
164
- const ui = this.configManager.getRaw().ui;
165
- const postureLines = [
166
- buildKeyValueLine(width, [
167
- { label: 'messages', value: String(this._messages.length), valueColor: C.value },
168
- { label: 'high', value: String(highCount), valueColor: highCount > 0 ? C.high : C.dim },
169
- { label: 'low', value: String(lowCount), valueColor: lowCount > 0 ? C.low : C.dim },
170
- ], C),
171
- buildKeyValueLine(width, [
172
- { label: 'system route', value: ui.systemMessages, valueColor: C.info },
173
- { label: 'ops route', value: ui.operationalMessages, valueColor: C.info },
174
- { label: 'review route', value: ui.wrfcMessages, valueColor: C.info },
175
- ], C),
176
- buildGuidanceLine(width, '/settings', 'adjust where operational and delegated review/build messages render across panels and conversation', C),
177
- ];
178
-
179
- const selected = this._messages[this.selectedIndex]!;
180
- const messageRows: Line[] = this._messages.map((entry, index) =>
181
- this.renderItem(entry, index, index === this.selectedIndex, width),
182
- );
183
-
184
- const postureSection: PanelWorkspaceSection = { lines: buildSummaryBlock(width, 'System posture', postureLines, C) };
185
- const detailSection: PanelWorkspaceSection = {
186
- title: 'Selected Message',
187
- lines: [
188
- buildPanelLine(width, [
189
- [' Time ', C.label],
190
- [fmtTime(selected.ts), C.value],
191
- [' Priority ', C.label],
192
- [selected.priority, selected.priority === 'high' ? C.high : C.low],
193
- ]),
194
- ...buildBodyText(width, selected.text, C, C.value),
195
- ],
196
- };
197
- const messagesSection = resolvePrimaryScrollableSection(width, height, {
198
- intro,
199
- palette: C,
200
- beforeSections: [postureSection],
201
- section: {
202
- title: 'Timeline',
203
- scrollableLines: messageRows,
204
- selectedIndex: this.selectedIndex,
205
- scrollOffset: this.scrollStart,
206
- minRows: 4,
207
- appendWindowSummary: { dimColor: C.ts },
208
- },
209
- afterSections: [detailSection],
210
- });
211
- this.scrollStart = messagesSection.scrollOffset;
212
- const sections: PanelWorkspaceSection[] = [
213
- postureSection,
214
- messagesSection.section,
215
- detailSection,
216
- ];
217
- this.needsRender = false;
218
- const lines = buildPanelWorkspace(width, height, {
219
- title: 'System Messages',
220
- intro,
221
- sections,
222
- footerLines: [
223
- buildPanelLine(width, [[' j/k or Up/Down scroll PgUp/PgDn page g/G jump', C.dim]]),
224
- ],
225
- palette: C,
226
- });
227
- return lines;
228
- });
229
- }
230
- }