@pellux/goodvibes-tui 0.20.3 → 0.22.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 (142) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +23 -2
  3. package/docs/foundation-artifacts/operator-contract.json +78 -1
  4. package/package.json +4 -2
  5. package/src/audio/spoken-turn-controller.ts +31 -1
  6. package/src/audio/spoken-turn-wiring.ts +26 -4
  7. package/src/cli/bundle-command.ts +1 -1
  8. package/src/cli/completions/generate.ts +658 -0
  9. package/src/cli/config-overrides.ts +68 -0
  10. package/src/cli/entrypoint.ts +6 -0
  11. package/src/cli/help.ts +4 -2
  12. package/src/cli/management-commands.ts +1 -1
  13. package/src/cli/management.ts +1 -8
  14. package/src/cli/parser.ts +31 -18
  15. package/src/cli/service-command.ts +1 -1
  16. package/src/cli/surface-command.ts +1 -1
  17. package/src/cli/tui-startup.ts +72 -10
  18. package/src/cli/types.ts +14 -3
  19. package/src/cli-flags.ts +1 -0
  20. package/src/config/atomic-write.ts +70 -0
  21. package/src/config/goodvibes-home-audit.ts +2 -0
  22. package/src/config/read-versioned.ts +115 -0
  23. package/src/core/context-auto-compact.ts +77 -0
  24. package/src/core/conversation-rendering.ts +49 -15
  25. package/src/core/conversation.ts +101 -16
  26. package/src/core/format-user-error.ts +192 -0
  27. package/src/core/stream-event-wiring.ts +144 -0
  28. package/src/core/stream-stall-watchdog.ts +103 -0
  29. package/src/core/system-message-router.ts +5 -1
  30. package/src/core/turn-event-wiring.ts +124 -0
  31. package/src/daemon/cli.ts +5 -0
  32. package/src/export/cost-utils.ts +71 -0
  33. package/src/export/gist-uploader.ts +136 -0
  34. package/src/input/command-registry.ts +32 -1
  35. package/src/input/commands/control-room-runtime.ts +10 -10
  36. package/src/input/commands/experience-runtime.ts +5 -4
  37. package/src/input/commands/knowledge.ts +1 -1
  38. package/src/input/commands/local-auth-runtime.ts +27 -5
  39. package/src/input/commands/local-setup.ts +4 -6
  40. package/src/input/commands/memory-product-runtime.ts +8 -6
  41. package/src/input/commands/operator-panel-runtime.ts +1 -1
  42. package/src/input/commands/operator-runtime.ts +3 -10
  43. package/src/input/commands/{integration-runtime.ts → plugin-runtime.ts} +1 -1
  44. package/src/input/commands/provider.ts +57 -3
  45. package/src/input/commands/recall-review.ts +26 -2
  46. package/src/input/commands/services-runtime.ts +2 -2
  47. package/src/input/commands/session-workflow.ts +8 -16
  48. package/src/input/commands/session.ts +70 -20
  49. package/src/input/commands/share-runtime.ts +99 -12
  50. package/src/input/commands/tts-runtime.ts +30 -4
  51. package/src/input/commands.ts +2 -4
  52. package/src/input/delete-key-policy.ts +46 -0
  53. package/src/input/feed-context-factory.ts +2 -0
  54. package/src/input/handler-feed.ts +3 -0
  55. package/src/input/handler-interactions.ts +2 -15
  56. package/src/input/handler-modal-routes.ts +128 -12
  57. package/src/input/handler-modal-token-routes.ts +22 -5
  58. package/src/input/handler-onboarding-cloudflare.ts +1 -1
  59. package/src/input/handler-onboarding.ts +73 -69
  60. package/src/input/handler-types.ts +163 -0
  61. package/src/input/handler.ts +6 -2
  62. package/src/input/input-history.ts +76 -6
  63. package/src/input/model-picker-filter.ts +265 -0
  64. package/src/input/model-picker-items.ts +208 -0
  65. package/src/input/model-picker.ts +92 -325
  66. package/src/input/onboarding/handler-onboarding-routes.ts +7 -2
  67. package/src/input/onboarding/onboarding-verification-helpers.ts +76 -0
  68. package/src/input/onboarding/onboarding-wizard-apply.ts +14 -4
  69. package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +16 -2
  70. package/src/input/onboarding/onboarding-wizard-cloudflare.ts +8 -8
  71. package/src/input/onboarding/onboarding-wizard-external-surface-extra-specs.ts +1 -1
  72. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +2 -29
  73. package/src/input/onboarding/onboarding-wizard-rules.ts +28 -28
  74. package/src/input/onboarding/onboarding-wizard-state.ts +20 -20
  75. package/src/input/onboarding/onboarding-wizard-steps.ts +24 -25
  76. package/src/input/onboarding/onboarding-wizard-types.ts +145 -3
  77. package/src/input/onboarding/onboarding-wizard-validation.ts +77 -0
  78. package/src/input/onboarding/onboarding-wizard.ts +3 -3
  79. package/src/input/settings-modal-behavior.ts +5 -0
  80. package/src/input/settings-modal-data.ts +378 -0
  81. package/src/input/settings-modal-mutations.ts +157 -0
  82. package/src/input/settings-modal-reset.ts +154 -0
  83. package/src/input/settings-modal.ts +236 -232
  84. package/src/main.ts +93 -85
  85. package/src/panels/agent-inspector-panel.ts +120 -18
  86. package/src/panels/agent-inspector-shared.ts +29 -0
  87. package/src/panels/builtin/agent.ts +4 -1
  88. package/src/panels/builtin/development.ts +5 -1
  89. package/src/panels/builtin/knowledge.ts +14 -13
  90. package/src/panels/builtin/operations.ts +22 -1
  91. package/src/panels/builtin/shared.ts +7 -0
  92. package/src/panels/cockpit-panel.ts +123 -3
  93. package/src/panels/cockpit-read-model.ts +232 -0
  94. package/src/panels/confirm-state.ts +27 -12
  95. package/src/panels/cost-tracker-panel.ts +23 -67
  96. package/src/panels/eval-panel.ts +10 -9
  97. package/src/panels/index.ts +1 -1
  98. package/src/panels/knowledge-graph-panel.ts +84 -0
  99. package/src/panels/local-auth-panel.ts +124 -4
  100. package/src/panels/memory-panel.ts +370 -40
  101. package/src/panels/project-planning-panel.ts +42 -4
  102. package/src/panels/search-focus.ts +11 -5
  103. package/src/panels/session-maintenance.ts +66 -15
  104. package/src/panels/subscription-panel.ts +33 -25
  105. package/src/panels/types.ts +28 -1
  106. package/src/panels/wrfc-panel.ts +224 -41
  107. package/src/renderer/agent-detail-modal.ts +118 -13
  108. package/src/renderer/code-block.ts +10 -2
  109. package/src/renderer/compositor.ts +18 -4
  110. package/src/renderer/context-inspector.ts +1 -5
  111. package/src/renderer/context-status-hint.ts +54 -0
  112. package/src/renderer/diff.ts +94 -21
  113. package/src/renderer/markdown.ts +29 -13
  114. package/src/renderer/settings-modal-helpers.ts +1 -1
  115. package/src/renderer/settings-modal.ts +90 -10
  116. package/src/renderer/shell-surface.ts +10 -0
  117. package/src/renderer/syntax-highlighter.ts +10 -3
  118. package/src/renderer/term-caps.ts +318 -0
  119. package/src/renderer/theme.ts +158 -0
  120. package/src/renderer/tool-call.ts +12 -2
  121. package/src/renderer/ui-factory.ts +50 -6
  122. package/src/runtime/bootstrap-command-context.ts +1 -0
  123. package/src/runtime/bootstrap-command-parts.ts +18 -0
  124. package/src/runtime/bootstrap-core.ts +145 -13
  125. package/src/runtime/bootstrap-shell.ts +11 -0
  126. package/src/runtime/bootstrap.ts +9 -0
  127. package/src/runtime/onboarding/apply.ts +4 -6
  128. package/src/runtime/onboarding/index.ts +1 -0
  129. package/src/runtime/onboarding/markers.ts +42 -49
  130. package/src/runtime/onboarding/progress.ts +148 -0
  131. package/src/runtime/onboarding/state.ts +133 -55
  132. package/src/runtime/onboarding/types.ts +20 -0
  133. package/src/runtime/services.ts +27 -1
  134. package/src/runtime/wrfc-persistence.ts +237 -0
  135. package/src/shell/blocking-input.ts +20 -5
  136. package/src/tools/wrfc-agent-guard.ts +64 -3
  137. package/src/utils/format-elapsed.ts +30 -0
  138. package/src/utils/terminal-width.ts +45 -0
  139. package/src/version.ts +1 -1
  140. package/src/work-plans/work-plan-store.ts +4 -6
  141. package/src/panels/knowledge-panel.ts +0 -345
  142. package/src/planning/project-planning-coordinator.ts +0 -543
@@ -1,3 +1,16 @@
1
+ /**
2
+ * Session maintenance types and local evaluator.
3
+ *
4
+ * The canonical evaluator is the SDK version exported from @/runtime/index.ts
5
+ * (via operations.evaluateSessionMaintenance), which reads from configManager.
6
+ *
7
+ * This module provides:
8
+ * - The shared type surface used across TUI panels.
9
+ * - A thin local evaluator kept in sync with the SDK signature so panel code
10
+ * that passes configManager has a coherent call site.
11
+ */
12
+ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
13
+
1
14
  export type PanelGuidanceMode = 'off' | 'minimal' | 'guided';
2
15
  export type PanelSessionMaintenanceLevel = 'stable' | 'watch' | 'suggest-compact' | 'compacting' | 'needs-repair' | 'unknown';
3
16
 
@@ -9,9 +22,12 @@ export interface PanelSessionMaintenanceSession {
9
22
  readonly lineage?: readonly PanelSessionMaintenanceLineageEntry[];
10
23
  readonly lastCompactedAt?: number;
11
24
  readonly compactionMessageCount?: number;
25
+ readonly compactionState?: string;
12
26
  }
13
27
 
14
28
  export interface PanelSessionMaintenanceInput {
29
+ /** ConfigManager used to read behavior.autoCompactThreshold and related keys. */
30
+ readonly configManager: Pick<ConfigManager, 'get'>;
15
31
  readonly currentTokens: number;
16
32
  readonly contextWindow: number;
17
33
  readonly messageCount?: number;
@@ -27,7 +43,15 @@ export interface PanelSessionMaintenanceStatus {
27
43
  readonly guidanceMode: PanelGuidanceMode;
28
44
  readonly usagePct: number;
29
45
  readonly remainingTokens: number;
46
+ /**
47
+ * Compact threshold as a percent integer.
48
+ * SDK schema range is [10, 100]; default is 80.
49
+ */
30
50
  readonly thresholdPct: number;
51
+ /**
52
+ * True when autoCompactThreshold is in its valid range (>0).
53
+ * With SDK default (80), auto-compact is active unless explicitly lowered below 10.
54
+ */
31
55
  readonly autoCompactEnabled: boolean;
32
56
  readonly sessionMemoryCount: number;
33
57
  readonly compactionCount: number;
@@ -35,19 +59,33 @@ export interface PanelSessionMaintenanceStatus {
35
59
  readonly compactRecommended: boolean;
36
60
  }
37
61
 
62
+ /**
63
+ * Evaluate session maintenance from config-driven thresholds.
64
+ *
65
+ * behavior.autoCompactThreshold (percent integer, SDK schema range [10, 100], default 80):
66
+ * - [10, 100] → threshold at that percent; autoCompactEnabled = true.
67
+ * - 0 (defensive fallback for null/missing config only; not a valid schema value).
68
+ *
69
+ * NOTE: The SDK's evaluateSessionMaintenance (from @/runtime/index.ts) is the
70
+ * canonical implementation used in production. This local version exists so
71
+ * panel tests can import types without crossing the SDK boundary.
72
+ */
38
73
  export function evaluateSessionMaintenance(input: PanelSessionMaintenanceInput): PanelSessionMaintenanceStatus {
39
- const guidanceMode: PanelGuidanceMode = 'minimal';
40
- const thresholdPct = 80;
41
- const autoCompactEnabled = false;
74
+ const guidanceMode: PanelGuidanceMode = (input.configManager.get('behavior.guidanceMode') as PanelGuidanceMode | undefined) ?? 'minimal';
75
+ const rawThreshold = Number(input.configManager.get('behavior.autoCompactThreshold') ?? 0);
76
+ const thresholdPct = Math.max(0, Number.isFinite(rawThreshold) ? rawThreshold : 0);
77
+ const autoCompactEnabled = thresholdPct > 0;
78
+
42
79
  const usagePct = input.contextWindow > 0 ? Math.min(100, Math.round((Math.max(0, input.currentTokens) / input.contextWindow) * 100)) : 0;
43
80
  const remainingTokens = Math.max(0, input.contextWindow - input.currentTokens);
44
81
  const sessionMemoryCount = Math.max(0, input.sessionMemoryCount ?? 0);
45
82
  const compactionCount = Math.max(0, input.session?.lineage?.filter((entry) => entry.branchReason === 'compaction').length ?? 0);
46
83
  const lastCompactedAt = input.session?.lastCompactedAt;
47
84
  const messageCount = Math.max(0, input.messageCount ?? 0);
48
- const staleByMessageGrowth = compactionCount > 0
85
+ const staleByMessageGrowth = (input.session?.compactionMessageCount ?? 0) > 0
49
86
  ? messageCount - (input.session?.compactionMessageCount ?? 0) >= 12
50
87
  : messageCount >= 24;
88
+
51
89
  if (input.contextWindow <= 0) {
52
90
  return {
53
91
  level: 'unknown',
@@ -66,32 +104,45 @@ export function evaluateSessionMaintenance(input: PanelSessionMaintenanceInput):
66
104
  };
67
105
  }
68
106
 
107
+ if (input.session?.compactionState === 'failed') {
108
+ return {
109
+ level: 'needs-repair',
110
+ summary: 'Compaction needs operator repair.',
111
+ reasons: ['Compaction failed and the session may need manual recovery.'],
112
+ nextSteps: ['/compact', '/health review'],
113
+ guidanceMode,
114
+ usagePct,
115
+ remainingTokens,
116
+ thresholdPct,
117
+ autoCompactEnabled,
118
+ sessionMemoryCount,
119
+ compactionCount,
120
+ lastCompactedAt,
121
+ compactRecommended: true,
122
+ };
123
+ }
124
+
69
125
  const reasons: string[] = [];
70
126
  const nextSteps: string[] = [];
71
127
  let summary = 'Session maintenance is stable.';
72
128
  let compactRecommended = false;
73
129
  let level: PanelSessionMaintenanceLevel = 'stable';
74
130
 
75
- if (usagePct >= 90) {
76
- level = 'needs-repair';
131
+ const atThreshold = autoCompactEnabled ? usagePct >= thresholdPct : usagePct >= 80;
132
+ if (atThreshold || remainingTokens <= 15_000) {
133
+ level = 'suggest-compact';
77
134
  summary = `Compact now to recover context headroom (${usagePct}% used).`;
78
135
  reasons.push(`Context pressure is high at ${usagePct}% usage.`);
79
136
  nextSteps.push('/compact', '/panel tokens');
80
137
  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('/panel tokens');
86
- compactRecommended = true;
87
- } else if (usagePct >= 70 || staleByMessageGrowth) {
138
+ } else if (usagePct >= Math.max(70, autoCompactEnabled ? thresholdPct - 10 : 70) || staleByMessageGrowth) {
88
139
  level = 'watch';
89
140
  summary = staleByMessageGrowth
90
141
  ? `Conversation has grown ${messageCount.toLocaleString()} messages since the last maintenance checkpoint.`
91
142
  : `Watch context growth (${usagePct}% used, threshold ${thresholdPct}%).`;
92
143
  reasons.push(staleByMessageGrowth
93
144
  ? `Conversation has grown ${messageCount.toLocaleString()} messages since the last maintenance checkpoint.`
94
- : `Context usage is climbing toward the ${thresholdPct}% maintenance threshold.`);
145
+ : `Context usage is climbing toward the ${thresholdPct > 0 ? `${thresholdPct}% auto-compact threshold` : 'maintenance band'}.`);
95
146
  nextSteps.push('/panel tokens');
96
147
  } else {
97
148
  reasons.push('Context pressure is currently within the stable operating band.');
@@ -104,7 +155,7 @@ export function evaluateSessionMaintenance(input: PanelSessionMaintenanceInput):
104
155
  reasons.push(`Last compaction ran ${lastCompactedAt ? new Date(lastCompactedAt).toISOString() : 'recently'}.`);
105
156
  }
106
157
  if (!autoCompactEnabled) {
107
- reasons.push('Auto-compact is currently disabled; use /compact when you need to recover headroom.');
158
+ reasons.push('Auto-compaction is disabled; maintenance stays fully manual.');
108
159
  }
109
160
 
110
161
  return {
@@ -1,8 +1,10 @@
1
1
  import type { Line } from '../types/grid.ts';
2
2
  import { createEmptyLine } from '../types/grid.ts';
3
3
  import { ScrollableListPanel } from './scrollable-list-panel.ts';
4
+ import type { KeyName } from './types.ts';
4
5
  import type { ProviderSubscription, PendingSubscriptionLogin } from '@pellux/goodvibes-sdk/platform/config';
5
6
  import { listBuiltinSubscriptionProviders } from '@pellux/goodvibes-sdk/platform/config';
7
+ import { type ConfirmState, handleConfirmInput } from './confirm-state.ts';
6
8
  import type { ServiceInspectionQuery, SubscriptionAccessQuery } from '../runtime/ui-service-queries.ts';
7
9
  import {
8
10
  buildEmptyState,
@@ -57,7 +59,8 @@ export class SubscriptionPanel extends ScrollableListPanel<SubscriptionRow> {
57
59
  private readonly serviceRegistry: Pick<ServiceInspectionQuery, 'getAll'>;
58
60
  private readonly subscriptionManager: SubscriptionAccessQuery;
59
61
  private rows: SubscriptionRow[] = [];
60
- private logoutConfirmationTarget: string | null = null;
62
+ /** Pending logout confirmation uses project-standard ConfirmState contract. */
63
+ private confirm: ConfirmState<string> | null = null;
61
64
 
62
65
  public constructor(
63
66
  serviceRegistry: Pick<ServiceInspectionQuery, 'getAll'>,
@@ -98,42 +101,47 @@ export class SubscriptionPanel extends ScrollableListPanel<SubscriptionRow> {
98
101
  ], C, { selected, selectedBg: C.selectedBg });
99
102
  }
100
103
 
101
- public handleInput(key: string): boolean {
104
+ public handleInput(key: KeyName): boolean {
105
+ // Project-standard confirm contract: Enter/y confirm; n/Esc cancel; other absorbed.
106
+ const confirmResult = handleConfirmInput(this.confirm, key);
107
+ if (confirmResult === 'confirmed') {
108
+ const provider = this.confirm!.subject;
109
+ this.confirm = null;
110
+ this.subscriptionManager.logout(provider);
111
+ this.refresh();
112
+ this.markDirty();
113
+ return true;
114
+ }
115
+ if (confirmResult === 'cancelled') {
116
+ this.confirm = null;
117
+ this.markDirty();
118
+ return true;
119
+ }
120
+ if (confirmResult === 'absorbed') return true;
121
+
102
122
  if (this.rows.length === 0) return false;
103
123
  const selected = this.rows[this.selectedIndex] ?? null;
104
- if (key === 'ArrowUp' || key === 'k') {
124
+ if (key === 'up' || key === 'k') {
105
125
  this.selectedIndex = Math.max(0, this.selectedIndex - 1);
106
- this.logoutConfirmationTarget = null;
126
+ this.confirm = null;
107
127
  this.markDirty();
108
128
  return true;
109
129
  }
110
- if (key === 'ArrowDown' || key === 'j') {
130
+ if (key === 'down' || key === 'j') {
111
131
  this.selectedIndex = Math.min(this.rows.length - 1, this.selectedIndex + 1);
112
- this.logoutConfirmationTarget = null;
132
+ this.confirm = null;
113
133
  this.markDirty();
114
134
  return true;
115
135
  }
116
- if (key === 'enter' || key === 'x') {
136
+ if (key === 'enter' || key === 'return') {
117
137
  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;
138
+ this.confirm = { subject: selected.provider, label: selected.provider };
131
139
  this.markDirty();
132
140
  return true;
133
141
  }
134
142
  if (key === 'r') {
135
143
  this.refresh();
136
- this.logoutConfirmationTarget = null;
144
+ this.confirm = null;
137
145
  this.markDirty();
138
146
  return true;
139
147
  }
@@ -206,7 +214,7 @@ export class SubscriptionPanel extends ScrollableListPanel<SubscriptionRow> {
206
214
  sections: [{ lines: [...summaryLines, ...emptyLines] }],
207
215
  footerLines: [
208
216
  buildGuidanceLine(width, '/subscription login <provider> start', 'start browser-based provider login from the packaged subscription surface', C),
209
- buildPanelLine(width, [[' Up/Down move Enter/X sign out selected provider r refresh', C.dim]]),
217
+ buildPanelLine(width, [[' Up/Down move Enter sign out selected provider y/Esc confirm/cancel r refresh', C.dim]]),
210
218
  ],
211
219
  palette: C,
212
220
  });
@@ -236,8 +244,8 @@ export class SubscriptionPanel extends ScrollableListPanel<SubscriptionRow> {
236
244
  : 'Stored for subscription-backed flows. Ambient API-key resolution remains unchanged.'}`,
237
245
  C.dim,
238
246
  ]]));
239
- if (this.logoutConfirmationTarget === selectedRow.provider) {
240
- detailRows.push(buildPanelLine(width, [[` Press Enter or X again to sign out ${selectedRow.provider}.`, C.warn]]));
247
+ if (this.confirm?.subject === selectedRow.provider) {
248
+ detailRows.push(buildPanelLine(width, [[` Sign out ${selectedRow.provider}? Press y or Enter to confirm, n or Esc to cancel.`, C.warn]]));
241
249
  }
242
250
  } else if (selectedRow.pending) {
243
251
  detailRows.push(buildPanelLine(width, [[' Login is pending. Finish with /subscription login <provider> finish <code>.', C.warn]]));
@@ -256,7 +264,7 @@ export class SubscriptionPanel extends ScrollableListPanel<SubscriptionRow> {
256
264
  footer: [
257
265
  ...detailRows,
258
266
  buildGuidanceLine(width, '/subscription login <provider> start', 'start browser-based provider login from the packaged subscription surface', C),
259
- buildPanelLine(width, [[' Up/Down move Enter/X sign out selected provider r refresh', C.dim]]),
267
+ buildPanelLine(width, [[' Up/Down move Enter sign out selected provider y/Esc confirm/cancel r refresh', C.dim]]),
260
268
  ],
261
269
  });
262
270
  }
@@ -1,6 +1,33 @@
1
1
  import type { Line } from '../types/grid.ts';
2
2
  import type { ComponentResourceContract, ComponentHealthState } from '../runtime/perf/panel-contracts.ts';
3
3
 
4
+ /**
5
+ * Named logical key identifiers emitted by the input tokenizer.
6
+ * These are the ONLY key names that will appear in `handleInput` calls;
7
+ * the tokenizer never emits DOM/browser-style names like 'ArrowUp' or 'Enter'.
8
+ *
9
+ * Printable single-character input is passed through as-is; the `string & {}`
10
+ * fallback preserves that handling while making named-key completions discoverable
11
+ * in editors and keeping non-null single-character values compatible.
12
+ */
13
+ export type NamedKey =
14
+ | 'up' | 'down' | 'left' | 'right'
15
+ | 'home' | 'end' | 'pageup' | 'pagedown'
16
+ | 'insert' | 'delete' | 'backspace'
17
+ | 'enter' | 'return' | 'escape' | 'space' | 'tab'
18
+ | 'f1' | 'f2' | 'f3' | 'f4' | 'f5' | 'f6'
19
+ | 'f7' | 'f8' | 'f9' | 'f10' | 'f11' | 'f12';
20
+
21
+ /**
22
+ * The full key type accepted by `Panel.handleInput`.
23
+ *
24
+ * Named keys (arrow keys, modifiers, function keys) are represented by
25
+ * lowercase `NamedKey` members. Single printable characters are passed
26
+ * verbatim via the `string & {}` escape hatch so panels can handle 'j', 'k',
27
+ * 'r', etc. without losing type safety on the named members.
28
+ */
29
+ export type KeyName = NamedKey | (string & {});
30
+
4
31
  export type PanelCategory = 'development' | 'agent' | 'monitoring' | 'session' | 'ai';
5
32
 
6
33
  export interface Panel {
@@ -35,7 +62,7 @@ export interface Panel {
35
62
  healthState?: Readonly<ComponentHealthState>;
36
63
 
37
64
  // Input (optional)
38
- handleInput?(key: string): boolean;
65
+ handleInput?(key: KeyName): boolean;
39
66
 
40
67
  // Scroll input (optional)
41
68
  // Positive delta scrolls down; negative delta scrolls up.