@pellux/goodvibes-tui 0.18.12 → 0.18.17

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 (196) hide show
  1. package/CHANGELOG.md +172 -0
  2. package/README.md +1 -1
  3. package/docs/foundation-artifacts/operator-contract.json +1 -1
  4. package/package.json +3 -2
  5. package/src/config/index.ts +1 -138
  6. package/src/core/conversation-rendering.ts +3 -3
  7. package/src/core/conversation.ts +176 -423
  8. package/src/core/history.ts +45 -0
  9. package/src/core/orchestrator.ts +3 -735
  10. package/src/core/system-message-router.ts +19 -58
  11. package/src/daemon/cli.ts +82 -6
  12. package/src/input/command-registry.ts +2 -0
  13. package/src/input/commands/control-room-runtime.ts +1 -1
  14. package/src/input/commands/health-runtime.ts +1 -1
  15. package/src/input/commands/local-setup-review.ts +1 -1
  16. package/src/input/commands/platform-access-runtime.ts +1 -1
  17. package/src/input/commands/qrcode-runtime.ts +20 -0
  18. package/src/input/commands/subscription-runtime.ts +1 -1
  19. package/src/input/commands.ts +2 -0
  20. package/src/input/handler-content-actions.ts +2 -2
  21. package/src/input/handler-feed.ts +7 -1
  22. package/src/input/handler-modal-routes.ts +19 -2
  23. package/src/input/handler-modal-token-routes.ts +4 -1
  24. package/src/input/handler-picker-routes.ts +4 -2
  25. package/src/input/handler-ui-state.ts +1 -1
  26. package/src/input/handler.ts +1 -1
  27. package/src/input/model-picker.ts +11 -0
  28. package/src/input/search.ts +1 -1
  29. package/src/input/selection.ts +2 -2
  30. package/src/input/settings-modal.ts +31 -3
  31. package/src/main.ts +1 -1
  32. package/src/panels/agent-inspector-panel.ts +3 -3
  33. package/src/panels/agent-logs-panel.ts +26 -27
  34. package/src/panels/approval-panel.ts +2 -2
  35. package/src/panels/automation-control-panel.ts +3 -3
  36. package/src/panels/base-panel.ts +14 -14
  37. package/src/panels/builtin/operations.ts +1 -1
  38. package/src/panels/builtin/session.ts +67 -1
  39. package/src/panels/builtin/shared.ts +4 -4
  40. package/src/panels/cockpit-panel.ts +2 -2
  41. package/src/panels/communication-panel.ts +3 -3
  42. package/src/panels/context-visualizer-panel.ts +2 -2
  43. package/src/panels/control-plane-panel.ts +3 -3
  44. package/src/panels/cost-tracker-panel.ts +3 -3
  45. package/src/panels/debug-panel.ts +2 -2
  46. package/src/panels/diff-panel.ts +2 -2
  47. package/src/panels/docs-panel.ts +1 -1
  48. package/src/panels/eval-panel.ts +2 -2
  49. package/src/panels/file-explorer-panel.ts +3 -3
  50. package/src/panels/file-preview-panel.ts +3 -3
  51. package/src/panels/forensics-panel.ts +2 -2
  52. package/src/panels/git-panel.ts +1 -1
  53. package/src/panels/hooks-panel.ts +3 -3
  54. package/src/panels/incident-review-panel.ts +1 -1
  55. package/src/panels/intelligence-panel.ts +2 -2
  56. package/src/panels/knowledge-panel.ts +1 -1
  57. package/src/panels/local-auth-panel.ts +2 -2
  58. package/src/panels/marketplace-panel.ts +1 -1
  59. package/src/panels/mcp-panel.ts +3 -3
  60. package/src/panels/memory-panel.ts +1 -1
  61. package/src/panels/ops-control-panel.ts +3 -3
  62. package/src/panels/ops-strategy-panel.ts +2 -2
  63. package/src/panels/orchestration-panel.ts +2 -2
  64. package/src/panels/panel-list-panel.ts +6 -6
  65. package/src/panels/plan-dashboard-panel.ts +1 -1
  66. package/src/panels/plugins-panel.ts +2 -2
  67. package/src/panels/policy-panel.ts +2 -2
  68. package/src/panels/polish.ts +3 -3
  69. package/src/panels/provider-account-snapshot.ts +1 -1
  70. package/src/panels/provider-accounts-panel.ts +25 -29
  71. package/src/panels/provider-health-panel.ts +2 -2
  72. package/src/panels/provider-stats-panel.ts +3 -3
  73. package/src/panels/qr-panel.ts +182 -0
  74. package/src/panels/remote-panel.ts +3 -3
  75. package/src/panels/routes-panel.ts +3 -3
  76. package/src/panels/sandbox-panel.ts +2 -2
  77. package/src/panels/schedule-panel.ts +1 -1
  78. package/src/panels/scrollable-list-panel.ts +407 -0
  79. package/src/panels/security-panel.ts +2 -2
  80. package/src/panels/services-panel.ts +3 -3
  81. package/src/panels/session-browser-panel.ts +2 -2
  82. package/src/panels/settings-sync-panel.ts +2 -2
  83. package/src/panels/skills-panel.ts +6 -6
  84. package/src/panels/subscription-panel.ts +3 -3
  85. package/src/panels/symbol-outline-panel.ts +3 -3
  86. package/src/panels/system-messages-panel.ts +4 -4
  87. package/src/panels/tasks-panel.ts +2 -2
  88. package/src/panels/thinking-panel.ts +3 -3
  89. package/src/panels/token-budget-panel.ts +1 -1
  90. package/src/panels/tool-inspector-panel.ts +3 -3
  91. package/src/panels/types.ts +5 -5
  92. package/src/panels/watchers-panel.ts +3 -3
  93. package/src/panels/welcome-panel.ts +1 -1
  94. package/src/panels/worktree-panel.ts +22 -21
  95. package/src/panels/wrfc-panel.ts +3 -3
  96. package/src/permissions/prompt.ts +3 -22
  97. package/src/plugins/loader.ts +15 -304
  98. package/src/renderer/agent-detail-modal.ts +1 -1
  99. package/src/renderer/autocomplete-overlay.ts +2 -2
  100. package/src/renderer/bookmark-modal.ts +1 -1
  101. package/src/renderer/bottom-bar.ts +2 -2
  102. package/src/renderer/buffer.ts +1 -1
  103. package/src/renderer/code-block.ts +2 -2
  104. package/src/renderer/compositor.ts +2 -2
  105. package/src/renderer/context-inspector.ts +1 -1
  106. package/src/renderer/conversation-layout.ts +2 -2
  107. package/src/renderer/conversation-overlays.ts +1 -1
  108. package/src/renderer/conversation-surface.ts +2 -2
  109. package/src/renderer/diff-view.ts +2 -2
  110. package/src/renderer/diff.ts +1 -1
  111. package/src/renderer/file-picker-overlay.ts +2 -2
  112. package/src/renderer/file-tree.ts +2 -2
  113. package/src/renderer/help-overlay.ts +1 -1
  114. package/src/renderer/history-search-overlay.ts +2 -2
  115. package/src/renderer/live-tail-modal.ts +1 -1
  116. package/src/renderer/markdown.ts +2 -2
  117. package/src/renderer/modal-factory.ts +3 -3
  118. package/src/renderer/model-picker-overlay.ts +2 -2
  119. package/src/renderer/overlay-box.ts +2 -2
  120. package/src/renderer/panel-composite.ts +1 -1
  121. package/src/renderer/panel-picker-overlay.ts +2 -2
  122. package/src/renderer/panel-tab-bar.ts +1 -1
  123. package/src/renderer/panel-workspace-bar.ts +1 -1
  124. package/src/renderer/process-indicator.ts +2 -2
  125. package/src/renderer/process-modal.ts +1 -1
  126. package/src/renderer/profile-picker-modal.ts +2 -2
  127. package/src/renderer/progress.ts +2 -2
  128. package/src/renderer/qr-renderer.ts +117 -0
  129. package/src/renderer/search-overlay.ts +2 -2
  130. package/src/renderer/selection-modal-overlay.ts +2 -2
  131. package/src/renderer/session-picker-modal.ts +2 -2
  132. package/src/renderer/settings-modal-helpers.ts +122 -0
  133. package/src/renderer/settings-modal.ts +149 -113
  134. package/src/renderer/shell-surface.ts +1 -1
  135. package/src/renderer/system-message.ts +1 -1
  136. package/src/renderer/tab-strip.ts +2 -2
  137. package/src/renderer/text-layout.ts +1 -1
  138. package/src/renderer/thinking.ts +1 -1
  139. package/src/renderer/tool-call.ts +2 -2
  140. package/src/renderer/ui-factory.ts +2 -2
  141. package/src/runtime/bootstrap-command-context.ts +5 -6
  142. package/src/runtime/bootstrap-command-parts.ts +32 -18
  143. package/src/runtime/bootstrap-core.ts +3 -2
  144. package/src/runtime/bootstrap-hook-bridge.ts +15 -174
  145. package/src/runtime/bootstrap-shell.ts +4 -4
  146. package/src/runtime/bootstrap.ts +7 -2
  147. package/src/runtime/context.ts +4 -20
  148. package/src/runtime/diagnostics/panels/index.ts +6 -6
  149. package/src/runtime/diagnostics/panels/ops.ts +1 -1
  150. package/src/runtime/diagnostics/panels/panel-resources.ts +118 -0
  151. package/src/runtime/perf/panel-contracts.ts +32 -0
  152. package/src/runtime/perf/panel-health-monitor.ts +18 -0
  153. package/src/runtime/services.ts +5 -5
  154. package/src/runtime/store/domains/domain-read-matrix.ts +0 -2
  155. package/src/runtime/store/selectors/index.ts +11 -6
  156. package/src/runtime/store/state.ts +12 -4
  157. package/src/runtime/ui-events.ts +1 -0
  158. package/src/runtime/ui-read-model-helpers.ts +1 -32
  159. package/src/runtime/ui-read-models-observability-maintenance.ts +1 -81
  160. package/src/runtime/ui-read-models-observability-options.ts +1 -5
  161. package/src/runtime/ui-read-models-observability-remote.ts +1 -73
  162. package/src/runtime/ui-read-models-observability-security.ts +1 -172
  163. package/src/runtime/ui-read-models-observability-system.ts +1 -217
  164. package/src/runtime/ui-read-models-observability.ts +1 -59
  165. package/src/runtime/ui-service-queries.ts +1 -114
  166. package/src/runtime/ui-services.ts +1 -1
  167. package/src/shell/ui-openers.ts +1 -1
  168. package/src/tools/index.ts +1 -186
  169. package/src/types/grid.ts +48 -0
  170. package/src/utils/clipboard.ts +21 -0
  171. package/src/utils/splash-lines.ts +1 -1
  172. package/src/utils/terminal-width.ts +185 -0
  173. package/src/version.ts +1 -1
  174. package/src/config/service-registry.ts +0 -1
  175. package/src/config/subscription-providers.ts +0 -127
  176. package/src/daemon/facade-composition.ts +0 -398
  177. package/src/daemon/facade.ts +0 -638
  178. package/src/daemon/surface-policy.ts +0 -60
  179. package/src/daemon/types.ts +0 -191
  180. package/src/runtime/diagnostics/actions.ts +0 -776
  181. package/src/runtime/diagnostics/index.ts +0 -99
  182. package/src/runtime/diagnostics/panels/agents.ts +0 -252
  183. package/src/runtime/diagnostics/panels/events.ts +0 -188
  184. package/src/runtime/diagnostics/panels/health.ts +0 -242
  185. package/src/runtime/diagnostics/panels/tasks.ts +0 -251
  186. package/src/runtime/diagnostics/panels/tool-calls.ts +0 -267
  187. package/src/runtime/diagnostics/provider.ts +0 -262
  188. package/src/runtime/store/domains/conversation.ts +0 -181
  189. package/src/runtime/store/domains/permissions.ts +0 -143
  190. package/src/runtime/store/helpers/reducers/conversation.ts +0 -228
  191. package/src/runtime/store/helpers/reducers/lifecycle.ts +0 -440
  192. package/src/runtime/store/helpers/reducers/shared.ts +0 -60
  193. package/src/runtime/store/helpers/reducers/sync.ts +0 -555
  194. package/src/runtime/store/helpers/reducers.ts +0 -30
  195. package/src/runtime/ui-read-models-core.ts +0 -95
  196. package/src/runtime/ui-read-models-operations.ts +0 -203
@@ -5,8 +5,8 @@
5
5
 
6
6
  import { join } from 'node:path';
7
7
  import { readFile } from 'node:fs/promises';
8
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
9
- import { createEmptyLine } from '@pellux/goodvibes-sdk/platform/types/grid';
8
+ import type { Line } from '../types/grid.ts';
9
+ import { createEmptyLine } from '../types/grid.ts';
10
10
  import { BasePanel } from './base-panel.ts';
11
11
  import type { AgentManager, AgentRecord } from '@pellux/goodvibes-sdk/platform/tools/agent/index';
12
12
  import type { AgentMessageBus } from '@pellux/goodvibes-sdk/platform/agents/message-bus';
@@ -21,7 +21,7 @@ import {
21
21
  resolveScrollablePanelSection,
22
22
  DEFAULT_PANEL_PALETTE,
23
23
  } from './polish.ts';
24
- import { truncateDisplay } from '@pellux/goodvibes-sdk/platform/utils/terminal-width';
24
+ import { truncateDisplay } from '../utils/terminal-width.ts';
25
25
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils/error-display';
26
26
  import {
27
27
  type AgentDisplayRow as DisplayRow,
@@ -1,10 +1,10 @@
1
1
  import { readFileSync, existsSync, watch, type FSWatcher } from 'fs';
2
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
3
- import { createEmptyLine, createStyledCell } from '@pellux/goodvibes-sdk/platform/types/grid';
4
- import { BasePanel } from './base-panel.ts';
2
+ import type { Line } from '../types/grid.ts';
3
+ import { createEmptyLine, createStyledCell } from '../types/grid.ts';
4
+ import { ScrollableListPanel } from './scrollable-list-panel.ts';
5
5
  import type { AgentManager, AgentRecord } from '@pellux/goodvibes-sdk/platform/tools/agent/index';
6
6
  import type { AgentEvent } from '@pellux/goodvibes-sdk/platform/runtime/events/index';
7
- import type { UiEventFeed } from '@pellux/goodvibes-sdk/platform/runtime/ui-events';
7
+ import type { UiEventFeed } from '../runtime/ui-events.ts';
8
8
  import {
9
9
  buildEmptyState,
10
10
  buildPanelLine,
@@ -38,7 +38,7 @@ export interface AgentLogsPanelDeps {
38
38
  // AgentLogsPanel
39
39
  // ---------------------------------------------------------------------------
40
40
 
41
- export class AgentLogsPanel extends BasePanel {
41
+ export class AgentLogsPanel extends ScrollableListPanel<LogEntry> {
42
42
  // ── Agent state ─────────────────────────────────────────────────────────
43
43
  private agents: AgentRecord[] = [];
44
44
  private selectedAgentIndex = 0;
@@ -47,7 +47,6 @@ export class AgentLogsPanel extends BasePanel {
47
47
  private allEntries: LogEntry[] = []; // raw parsed JSONL for selected agent
48
48
  private filteredEntries: LogEntry[] = []; // after filter applied
49
49
  private lastFileSize = 0;
50
- private scrollOffset = 0;
51
50
 
52
51
  // ── Modes ────────────────────────────────────────────────────────────────
53
52
  private autoFollow = true;
@@ -68,6 +67,16 @@ export class AgentLogsPanel extends BasePanel {
68
67
  this._subscribeEvents();
69
68
  }
70
69
 
70
+ // ── ScrollableListPanel<LogEntry> contract ────────────────────────────────
71
+
72
+ protected getItems(): readonly LogEntry[] {
73
+ return this.filteredEntries;
74
+ }
75
+
76
+ protected renderItem(entry: LogEntry, _index: number, _selected: boolean, width: number): Line {
77
+ return this._renderEntry(entry, width);
78
+ }
79
+
71
80
  // ── Lifecycle ─────────────────────────────────────────────────────────────
72
81
 
73
82
  override onActivate(): void {
@@ -100,7 +109,7 @@ export class AgentLogsPanel extends BasePanel {
100
109
  this._cycleFilter();
101
110
  return true;
102
111
  case 'g': // g — jump to top
103
- this.scrollOffset = 0;
112
+ this.selectedIndex = 0;
104
113
  this.autoFollow = false;
105
114
  this.markDirty();
106
115
  return true;
@@ -109,19 +118,8 @@ export class AgentLogsPanel extends BasePanel {
109
118
  this._clampScroll();
110
119
  this.markDirty();
111
120
  return true;
112
- case 'k': // k / up
113
- case '\x1b[A':
114
- this.autoFollow = false;
115
- this.scrollOffset = Math.max(0, this.scrollOffset - 1);
116
- this.markDirty();
117
- return true;
118
- case 'j': // j / down
119
- case '\x1b[B':
120
- this.scrollOffset++;
121
- this.markDirty();
122
- return true;
123
121
  default:
124
- return false;
122
+ return super.handleInput(key);
125
123
  }
126
124
  }
127
125
 
@@ -205,7 +203,7 @@ export class AgentLogsPanel extends BasePanel {
205
203
 
206
204
  const focusIndex = this.autoFollow
207
205
  ? Math.max(0, this.filteredEntries.length - 1)
208
- : Math.min(this.scrollOffset, Math.max(0, this.filteredEntries.length - 1));
206
+ : Math.min(this.selectedIndex, Math.max(0, this.filteredEntries.length - 1));
209
207
  const summarySection = { title: 'Summary', lines: summaryLines } as const;
210
208
  const agentsSection = { title: 'Agents', lines: [selectorLine] } as const;
211
209
  const logStreamSection = resolveScrollablePanelSection(width, height, {
@@ -217,11 +215,11 @@ export class AgentLogsPanel extends BasePanel {
217
215
  title: 'Log Stream',
218
216
  scrollableLines: this.filteredEntries.map((entry) => this._renderEntry(entry, width)),
219
217
  selectedIndex: focusIndex,
220
- scrollOffset: this.scrollOffset,
218
+ scrollOffset: this.scrollStart,
221
219
  minRows: 8,
222
220
  },
223
221
  });
224
- this.scrollOffset = logStreamSection.scrollOffset;
222
+ this.scrollStart = logStreamSection.scrollOffset;
225
223
 
226
224
  return buildPanelWorkspace(width, height, {
227
225
  title: ' Agents',
@@ -273,7 +271,7 @@ export class AgentLogsPanel extends BasePanel {
273
271
  this.allEntries = parseAgentJsonl(content);
274
272
  this._applyFilter();
275
273
  if (this.autoFollow) {
276
- this.scrollOffset = Math.max(0, this.filteredEntries.length - 1);
274
+ this.selectedIndex = Math.max(0, this.filteredEntries.length - 1);
277
275
  }
278
276
  this.markDirty();
279
277
  } catch {
@@ -373,7 +371,8 @@ export class AgentLogsPanel extends BasePanel {
373
371
  this.allEntries = [];
374
372
  this.filteredEntries = [];
375
373
  this.lastFileSize = 0;
376
- this.scrollOffset = 0;
374
+ this.selectedIndex = 0;
375
+ this.scrollStart = 0;
377
376
  this.autoFollow = true;
378
377
  const agent = this._selectedAgent();
379
378
  if (agent) {
@@ -406,7 +405,7 @@ export class AgentLogsPanel extends BasePanel {
406
405
  this.allEntries = parseAgentJsonl(content);
407
406
  this._applyFilter();
408
407
  if (this.autoFollow) {
409
- this.scrollOffset = Math.max(0, this.filteredEntries.length - 1);
408
+ this.selectedIndex = Math.max(0, this.filteredEntries.length - 1);
410
409
  }
411
410
  } catch {
412
411
  this.allEntries = [];
@@ -438,7 +437,7 @@ export class AgentLogsPanel extends BasePanel {
438
437
  this.filter = FILTER_CYCLE[(idx + 1) % FILTER_CYCLE.length]!;
439
438
  this._applyFilter();
440
439
  if (this.autoFollow) {
441
- this.scrollOffset = Math.max(0, this.filteredEntries.length - 1);
440
+ this.selectedIndex = Math.max(0, this.filteredEntries.length - 1);
442
441
  }
443
442
  this.markDirty();
444
443
  }
@@ -449,7 +448,7 @@ export class AgentLogsPanel extends BasePanel {
449
448
  }
450
449
 
451
450
  private _clampScroll(): void {
452
- this.scrollOffset = Math.min(this.scrollOffset, Math.max(0, this.filteredEntries.length - 1));
451
+ this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredEntries.length - 1));
453
452
  }
454
453
 
455
454
  // ── Private: rendering helpers ─────────────────────────────────────────────
@@ -1,5 +1,5 @@
1
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
2
- import { createEmptyLine } from '@pellux/goodvibes-sdk/platform/types/grid';
1
+ import type { Line } from '../types/grid.ts';
2
+ import { createEmptyLine } from '../types/grid.ts';
3
3
  import { BasePanel } from './base-panel.ts';
4
4
  import {
5
5
  buildGuidanceLine,
@@ -1,8 +1,8 @@
1
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
2
- import { createEmptyLine } from '@pellux/goodvibes-sdk/platform/types/grid';
1
+ import type { Line } from '../types/grid.ts';
2
+ import { createEmptyLine } from '../types/grid.ts';
3
3
  import { BasePanel } from './base-panel.ts';
4
4
  import type { UiAutomationSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
5
- import { truncateDisplay } from '@pellux/goodvibes-sdk/platform/utils/terminal-width';
5
+ import { truncateDisplay } from '../utils/terminal-width.ts';
6
6
  import {
7
7
  buildEmptyState,
8
8
  buildGuidanceLine,
@@ -1,27 +1,27 @@
1
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
1
+ import type { Line } from '../types/grid.ts';
2
2
  import type { Panel, PanelCategory } from './types.ts';
3
- import type { PanelResourceContract, PanelHealthState } from '@pellux/goodvibes-sdk/platform/runtime/perf/panel-contracts';
4
- import type { PanelHealthMonitor } from '@pellux/goodvibes-sdk/platform/runtime/perf/panel-health-monitor';
3
+ import type { ComponentResourceContract, ComponentHealthState } from '../runtime/perf/panel-contracts.ts';
4
+ import type { ComponentHealthMonitor } from '../runtime/perf/panel-health-monitor.ts';
5
5
 
6
6
  export abstract class BasePanel implements Panel {
7
7
  public needsRender = true;
8
8
  public isTransient = false;
9
9
  public isPinned = false;
10
- protected readonly panelHealthMonitor?: PanelHealthMonitor;
10
+ protected readonly componentHealthMonitor?: ComponentHealthMonitor;
11
11
 
12
12
  /**
13
13
  * Optional resource contract for this panel.
14
14
  * Override in subclasses to declare a custom contract; leave undefined
15
- * to use the category default enforced by PanelHealthMonitor.
15
+ * to use the category default enforced by ComponentHealthMonitor.
16
16
  */
17
- public resourceContract: Readonly<PanelResourceContract> | undefined = undefined;
17
+ public resourceContract: Readonly<ComponentResourceContract> | undefined = undefined;
18
18
 
19
19
  /**
20
- * Live health state populated by PanelHealthMonitor.
20
+ * Live health state populated by ComponentHealthMonitor.
21
21
  * Read-only from outside the monitor.
22
22
  */
23
- public get healthState(): Readonly<PanelHealthState> | undefined {
24
- return this.panelHealthMonitor?.getHealth(this.id);
23
+ public get healthState(): Readonly<ComponentHealthState> | undefined {
24
+ return this.componentHealthMonitor?.getHealth(this.id);
25
25
  }
26
26
 
27
27
  constructor(
@@ -29,9 +29,9 @@ export abstract class BasePanel implements Panel {
29
29
  public readonly name: string,
30
30
  public readonly icon: string,
31
31
  public readonly category: PanelCategory,
32
- panelHealthMonitor?: PanelHealthMonitor,
32
+ componentHealthMonitor?: ComponentHealthMonitor,
33
33
  ) {
34
- this.panelHealthMonitor = panelHealthMonitor;
34
+ this.componentHealthMonitor = componentHealthMonitor;
35
35
  }
36
36
 
37
37
  onActivate(): void { this.needsRender = true; }
@@ -45,7 +45,7 @@ export abstract class BasePanel implements Panel {
45
45
  /**
46
46
  * Check whether the panel is currently permitted to render.
47
47
  *
48
- * Consults the shared PanelHealthMonitor. Returns true if not registered
48
+ * Consults the shared ComponentHealthMonitor. Returns true if not registered
49
49
  * (unthrottled) or if the monitor permits a render at this moment.
50
50
  *
51
51
  * Call this inside render() or before invoking render() to skip
@@ -59,7 +59,7 @@ export abstract class BasePanel implements Panel {
59
59
  * ```
60
60
  */
61
61
  protected canRenderNow(now: number = Date.now()): boolean {
62
- return this.panelHealthMonitor?.canRender(this.id, now) ?? true;
62
+ return this.componentHealthMonitor?.canRender(this.id, now) ?? true;
63
63
  }
64
64
 
65
65
  /**
@@ -67,6 +67,6 @@ export abstract class BasePanel implements Panel {
67
67
  * Call this at the end of render() after measuring wall-clock cost.
68
68
  */
69
69
  protected reportRenderDuration(durationMs: number, now: number = Date.now()): void {
70
- this.panelHealthMonitor?.recordRender(this.id, durationMs, now);
70
+ this.componentHealthMonitor?.recordRender(this.id, durationMs, now);
71
71
  }
72
72
  }
@@ -97,7 +97,7 @@ export function registerOperationsPanels(manager: PanelManager, deps: ResolvedBu
97
97
  category: 'monitoring',
98
98
  description: 'Project-local and global skill discovery with origin and dependency details',
99
99
  factory: () => new SkillsPanel({
100
- panelHealthMonitor: deps.panelHealthMonitor,
100
+ componentHealthMonitor: deps.componentHealthMonitor,
101
101
  shellPaths: ui.environment.shellPaths,
102
102
  }),
103
103
  });
@@ -1,12 +1,78 @@
1
+ import { networkInterfaces } from 'node:os';
2
+ import { readFileSync } from 'node:fs';
1
3
  import type { PanelManager } from '../panel-manager.ts';
2
4
  import { SessionBrowserPanel } from '../session-browser-panel.ts';
5
+ import { QrPanel } from '../qr-panel.ts';
3
6
  import { DocsPanel } from '../docs-panel.ts';
4
7
  import { PanelListPanel } from '../panel-list-panel.ts';
5
8
  import { TokenBudgetPanel } from '../token-budget-panel.ts';
6
9
  import type { ResolvedBuiltinPanelDeps } from './shared.ts';
7
10
  import { requireUiServices } from './shared.ts';
11
+ import {
12
+ getOrCreateCompanionToken,
13
+ regenerateCompanionToken,
14
+ buildCompanionConnectionInfo,
15
+ } from '@pellux/goodvibes-sdk/platform/pairing/index';
16
+ import { copyToClipboard } from '../../utils/clipboard.ts';
17
+
18
+ function getLocalNetworkIp(): string {
19
+ const nets = networkInterfaces();
20
+ for (const name of Object.keys(nets)) {
21
+ for (const net of nets[name] ?? []) {
22
+ if (net.family === 'IPv4' && !net.internal) {
23
+ return net.address;
24
+ }
25
+ }
26
+ }
27
+ return 'localhost';
28
+ }
29
+
30
+ function readBootstrapPassword(credentialPath: string): string | undefined {
31
+ try {
32
+ const content = readFileSync(credentialPath, 'utf-8');
33
+ for (const line of content.split('\n')) {
34
+ if (line.startsWith('password=')) {
35
+ return line.slice('password='.length).trim();
36
+ }
37
+ }
38
+ } catch {
39
+ // credential file may not exist yet
40
+ }
41
+ return undefined;
42
+ }
8
43
 
9
44
  export function registerSessionPanels(manager: PanelManager, deps: ResolvedBuiltinPanelDeps): void {
45
+ manager.registerType({
46
+ id: 'qr-code',
47
+ name: 'QR Code',
48
+ icon: 'Q',
49
+ category: 'session',
50
+ description: 'QR code for companion app pairing — scan to connect a mobile or desktop companion',
51
+ factory: () => {
52
+ const tokenRecord = getOrCreateCompanionToken('tui');
53
+ const daemonPort = deps.configManager.get('controlPlane.port');
54
+ const daemonHost = String(process.env['GOODVIBES_DAEMON_HOST'] ?? getLocalNetworkIp());
55
+ const daemonUrl = `http://${daemonHost}:${daemonPort}`;
56
+ const bootstrapPassword = readBootstrapPassword(deps.localUserAuthManager.getBootstrapCredentialPath());
57
+ const connectionInfo = buildCompanionConnectionInfo({
58
+ daemonUrl,
59
+ token: tokenRecord.token,
60
+ password: bootstrapPassword,
61
+ surface: 'tui',
62
+ });
63
+ const regenerate = (): typeof connectionInfo => {
64
+ const newRecord = regenerateCompanionToken('tui');
65
+ return buildCompanionConnectionInfo({
66
+ daemonUrl,
67
+ token: newRecord.token,
68
+ password: bootstrapPassword,
69
+ surface: 'tui',
70
+ });
71
+ };
72
+ return new QrPanel(connectionInfo, regenerate, copyToClipboard);
73
+ },
74
+ });
75
+
10
76
  manager.registerType({
11
77
  id: 'sessions',
12
78
  name: 'Sessions',
@@ -31,7 +97,7 @@ export function registerSessionPanels(manager: PanelManager, deps: ResolvedBuilt
31
97
  icon: 'L',
32
98
  category: 'session',
33
99
  description: 'Browse all registered panels grouped by category, with open/closed status and Enter-to-open',
34
- factory: () => new PanelListPanel(manager, deps.panelHealthMonitor),
100
+ factory: () => new PanelListPanel(manager, deps.componentHealthMonitor),
35
101
  });
36
102
 
37
103
  manager.registerType({
@@ -1,5 +1,5 @@
1
1
  import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config/manager';
2
- import type { ServiceRegistry } from '../../config/service-registry.ts';
2
+ import type { ServiceRegistry } from '@pellux/goodvibes-sdk/platform/config/service-registry';
3
3
  import type { ToolRegistry } from '@pellux/goodvibes-sdk/platform/tools/registry';
4
4
  import type { ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers/registry';
5
5
  import type { Orchestrator } from '../../core/orchestrator';
@@ -21,7 +21,7 @@ import type { SessionMemoryStore } from '@pellux/goodvibes-sdk/platform/core/ses
21
21
  import type { ExecutionPlanManager } from '@pellux/goodvibes-sdk/platform/core/execution-plan';
22
22
  import type { AdaptivePlanner } from '@pellux/goodvibes-sdk/platform/core/adaptive-planner';
23
23
  import type { ApiTokenAuditor } from '@pellux/goodvibes-sdk/platform/security/token-audit';
24
- import type { PanelHealthMonitor } from '@pellux/goodvibes-sdk/platform/runtime/perf/panel-health-monitor';
24
+ import type { ComponentHealthMonitor } from '../../runtime/perf/panel-health-monitor.ts';
25
25
  import type { WorktreeRegistry } from '@pellux/goodvibes-sdk/platform/runtime/worktree/registry';
26
26
  import type { SandboxSessionRegistry } from '@pellux/goodvibes-sdk/platform/runtime/sandbox/session-registry';
27
27
 
@@ -72,8 +72,8 @@ export interface BuiltinPanelDeps {
72
72
  getControlPlaneRecentEvents?: (limit: number) => readonly ControlPlaneRecentEvent[];
73
73
  /** Token auditor for the security control-room panel. */
74
74
  tokenAuditor: ApiTokenAuditor;
75
- /** Shared panel-health monitor for rate-limited panels and diagnostics. */
76
- panelHealthMonitor: PanelHealthMonitor;
75
+ /** Shared component-health monitor for rate-limited panels and diagnostics. */
76
+ componentHealthMonitor: ComponentHealthMonitor;
77
77
  /** Shared worktree registry for worktree surfaces. */
78
78
  worktreeRegistry: WorktreeRegistry;
79
79
  /** Shared sandbox session registry for sandbox surfaces and tools. */
@@ -1,5 +1,5 @@
1
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
2
- import { createEmptyLine } from '@pellux/goodvibes-sdk/platform/types/grid';
1
+ import type { Line } from '../types/grid.ts';
2
+ import { createEmptyLine } from '../types/grid.ts';
3
3
  import { BasePanel } from './base-panel.ts';
4
4
  import type { UiCockpitSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
5
5
  import {
@@ -1,8 +1,8 @@
1
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
2
- import { createEmptyLine } from '@pellux/goodvibes-sdk/platform/types/grid';
1
+ import type { Line } from '../types/grid.ts';
2
+ import { createEmptyLine } from '../types/grid.ts';
3
3
  import { BasePanel } from './base-panel.ts';
4
4
  import type { UiCommunicationSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
5
- import { truncateDisplay } from '@pellux/goodvibes-sdk/platform/utils/terminal-width';
5
+ import { truncateDisplay } from '../utils/terminal-width.ts';
6
6
  import {
7
7
  buildBodyText,
8
8
  buildEmptyState,
@@ -3,11 +3,11 @@
3
3
  // ---------------------------------------------------------------------------
4
4
 
5
5
  import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config/manager';
6
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
6
+ import type { Line } from '../types/grid.ts';
7
7
  import { BasePanel } from './base-panel.ts';
8
8
  import { evaluateSessionMaintenance } from '@pellux/goodvibes-sdk/platform/runtime/session-maintenance';
9
9
  import type { TurnEvent } from '@pellux/goodvibes-sdk/platform/runtime/events/index';
10
- import type { UiEventFeed } from '@pellux/goodvibes-sdk/platform/runtime/ui-events';
10
+ import type { UiEventFeed } from '../runtime/ui-events.ts';
11
11
  import type { UiReadModel, UiSessionSnapshot } from '../runtime/ui-read-models.ts';
12
12
  import type { SessionMemoryQuery } from '../runtime/ui-service-queries.ts';
13
13
  import {
@@ -1,8 +1,8 @@
1
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
2
- import { createEmptyLine } from '@pellux/goodvibes-sdk/platform/types/grid';
1
+ import type { Line } from '../types/grid.ts';
2
+ import { createEmptyLine } from '../types/grid.ts';
3
3
  import { BasePanel } from './base-panel.ts';
4
4
  import type { UiControlPlaneSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
5
- import { truncateDisplay } from '@pellux/goodvibes-sdk/platform/utils/terminal-width';
5
+ import { truncateDisplay } from '../utils/terminal-width.ts';
6
6
  import {
7
7
  buildEmptyState,
8
8
  buildGuidanceLine,
@@ -2,11 +2,11 @@
2
2
  // CostTrackerPanel — per-session / per-agent / per-plan cost estimates
3
3
  // ---------------------------------------------------------------------------
4
4
 
5
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
6
- import { createStyledCell, createEmptyLine } from '@pellux/goodvibes-sdk/platform/types/grid';
5
+ import type { Line } from '../types/grid.ts';
6
+ import { createStyledCell, createEmptyLine } from '../types/grid.ts';
7
7
  import { BasePanel } from './base-panel.ts';
8
8
  import type { AgentEvent, TurnEvent } from '@pellux/goodvibes-sdk/platform/runtime/events/index';
9
- import type { UiEventFeed } from '@pellux/goodvibes-sdk/platform/runtime/ui-events';
9
+ import type { UiEventFeed } from '../runtime/ui-events.ts';
10
10
  import {
11
11
  buildEmptyState,
12
12
  buildPanelLine,
@@ -1,7 +1,7 @@
1
1
  import { BasePanel } from './base-panel.ts';
2
- import { createEmptyLine, createStyledCell, type Line } from '@pellux/goodvibes-sdk/platform/types/grid';
2
+ import { createEmptyLine, createStyledCell, type Line } from '../types/grid.ts';
3
3
  import type { TurnEvent } from '@pellux/goodvibes-sdk/platform/runtime/events/index';
4
- import type { UiEventFeed } from '@pellux/goodvibes-sdk/platform/runtime/ui-events';
4
+ import type { UiEventFeed } from '../runtime/ui-events.ts';
5
5
  import type { Orchestrator } from '../core/orchestrator';
6
6
  import {
7
7
  buildEmptyState,
@@ -2,8 +2,8 @@
2
2
  // DiffPanel — unified diff view of agent file changes
3
3
  // ---------------------------------------------------------------------------
4
4
 
5
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
6
- import { createStyledCell, createEmptyLine } from '@pellux/goodvibes-sdk/platform/types/grid';
5
+ import type { Line } from '../types/grid.ts';
6
+ import { createStyledCell, createEmptyLine } from '../types/grid.ts';
7
7
  import { BasePanel } from './base-panel.ts';
8
8
  import {
9
9
  buildBodyText,
@@ -2,7 +2,7 @@
2
2
  // DocsPanel — tool list, model capabilities, and keyboard shortcut reference.
3
3
  // ---------------------------------------------------------------------------
4
4
 
5
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
5
+ import type { Line } from '../types/grid.ts';
6
6
  import { BasePanel } from './base-panel.ts';
7
7
  import { buildPanelLine, buildPanelWorkspace, buildSearchInputLine, resolveScrollablePanelSection, DEFAULT_PANEL_PALETTE } from './polish.ts';
8
8
  import type { ProviderModelCatalogQuery, ToolCatalogQuery } from '../runtime/ui-service-queries.ts';
@@ -6,8 +6,8 @@
6
6
  */
7
7
 
8
8
  import { BasePanel } from './base-panel.ts';
9
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
10
- import { createEmptyLine } from '@pellux/goodvibes-sdk/platform/types/grid';
9
+ import type { Line } from '../types/grid.ts';
10
+ import { createEmptyLine } from '../types/grid.ts';
11
11
  import {
12
12
  buildEmptyState,
13
13
  buildPanelLine,
@@ -4,8 +4,8 @@
4
4
 
5
5
  import { readdirSync, statSync } from 'node:fs';
6
6
  import { join, relative, basename } from 'node:path';
7
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
8
- import { createEmptyLine } from '@pellux/goodvibes-sdk/platform/types/grid';
7
+ import type { Line } from '../types/grid.ts';
8
+ import { createEmptyLine } from '../types/grid.ts';
9
9
  import { BasePanel } from './base-panel.ts';
10
10
  import {
11
11
  buildEmptyState,
@@ -16,7 +16,7 @@ import {
16
16
  resolveScrollablePanelSection,
17
17
  DEFAULT_PANEL_PALETTE,
18
18
  } from './polish.ts';
19
- import { getDisplayWidth } from '@pellux/goodvibes-sdk/platform/utils/terminal-width';
19
+ import { getDisplayWidth } from '../utils/terminal-width.ts';
20
20
  import {
21
21
  getPanelSearchFocusTransition,
22
22
  isPanelSearchBackspace,
@@ -1,10 +1,10 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
- import type { Line, Cell } from '@pellux/goodvibes-sdk/platform/types/grid';
4
- import { createStyledCell, createEmptyLine } from '@pellux/goodvibes-sdk/platform/types/grid';
3
+ import type { Line, Cell } from '../types/grid.ts';
4
+ import { createStyledCell, createEmptyLine } from '../types/grid.ts';
5
5
  import { BasePanel } from './base-panel.ts';
6
6
  import { SyntaxHighlighter, type SyntaxToken } from '../renderer/syntax-highlighter.ts';
7
- import { getDisplayWidth } from '@pellux/goodvibes-sdk/platform/utils/terminal-width';
7
+ import { getDisplayWidth } from '../utils/terminal-width.ts';
8
8
  import {
9
9
  buildEmptyState,
10
10
  buildPanelLine,
@@ -6,12 +6,12 @@
6
6
  *
7
7
  * Open via /forensics or the panel picker.
8
8
  */
9
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
9
+ import type { Line } from '../types/grid.ts';
10
10
  import type { ForensicsRegistry } from '@pellux/goodvibes-sdk/platform/runtime/forensics/registry';
11
11
  import type { FailureReport, CausalChainEntry, PhaseTimingEntry } from '@pellux/goodvibes-sdk/platform/runtime/forensics/types';
12
12
  import { ForensicsDataPanel } from '@pellux/goodvibes-sdk/platform/runtime/diagnostics/panels/forensics';
13
13
  import { BasePanel } from './base-panel.ts';
14
- import { createEmptyLine } from '@pellux/goodvibes-sdk/platform/types/grid';
14
+ import { createEmptyLine } from '../types/grid.ts';
15
15
  import {
16
16
  buildEmptyState,
17
17
  buildPanelLine,
@@ -1,5 +1,5 @@
1
1
  import { BasePanel } from './base-panel.ts';
2
- import { createEmptyLine, createStyledCell, type Line } from '@pellux/goodvibes-sdk/platform/types/grid';
2
+ import { createEmptyLine, createStyledCell, type Line } from '../types/grid.ts';
3
3
  import { GitService } from '@pellux/goodvibes-sdk/platform/git/service';
4
4
  import { logger } from '@pellux/goodvibes-sdk/platform/utils/logger';
5
5
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils/error-display';
@@ -1,5 +1,5 @@
1
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
2
- import { createEmptyLine } from '@pellux/goodvibes-sdk/platform/types/grid';
1
+ import type { Line } from '../types/grid.ts';
2
+ import { createEmptyLine } from '../types/grid.ts';
3
3
  import { BasePanel } from './base-panel.ts';
4
4
  import { listHookPointContracts } from '@pellux/goodvibes-sdk/platform/hooks/index';
5
5
  import type { HookDispatcher } from '@pellux/goodvibes-sdk/platform/hooks/dispatcher';
@@ -8,7 +8,7 @@ import type { HookActivityRecord, HookActivityTracker } from '@pellux/goodvibes-
8
8
  import type { HookAuthoringAction, HookSimulationResult } from '@pellux/goodvibes-sdk/platform/hooks/workbench';
9
9
  import type { HookChain, HookDefinition } from '@pellux/goodvibes-sdk/platform/hooks/types';
10
10
  import type { HookWorkbench } from '@pellux/goodvibes-sdk/platform/hooks/workbench';
11
- import { truncateDisplay } from '@pellux/goodvibes-sdk/platform/utils/terminal-width';
11
+ import { truncateDisplay } from '../utils/terminal-width.ts';
12
12
  import {
13
13
  buildEmptyState,
14
14
  buildPanelLine,
@@ -1,4 +1,4 @@
1
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
1
+ import type { Line } from '../types/grid.ts';
2
2
  import type { ForensicsRegistry } from '@pellux/goodvibes-sdk/platform/runtime/forensics/registry';
3
3
  import { BasePanel } from './base-panel.ts';
4
4
  import {
@@ -1,5 +1,5 @@
1
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
2
- import { createEmptyLine } from '@pellux/goodvibes-sdk/platform/types/grid';
1
+ import type { Line } from '../types/grid.ts';
2
+ import { createEmptyLine } from '../types/grid.ts';
3
3
  import { BasePanel } from './base-panel.ts';
4
4
  import type { UiIntelligenceSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
5
5
  import {
@@ -1,4 +1,4 @@
1
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
1
+ import type { Line } from '../types/grid.ts';
2
2
  import { BasePanel } from './base-panel.ts';
3
3
  import type { MemoryClass, MemoryRecord, MemoryRegistry, MemoryReviewState } from '@pellux/goodvibes-sdk/platform/state/memory-store';
4
4
  import {
@@ -1,5 +1,5 @@
1
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
2
- import { createEmptyLine } from '@pellux/goodvibes-sdk/platform/types/grid';
1
+ import type { Line } from '../types/grid.ts';
2
+ import { createEmptyLine } from '../types/grid.ts';
3
3
  import { BasePanel } from './base-panel.ts';
4
4
  import {
5
5
  buildDetailBlock,
@@ -1,4 +1,4 @@
1
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
1
+ import type { Line } from '../types/grid.ts';
2
2
  import { BasePanel } from './base-panel.ts';
3
3
  import {
4
4
  buildEmptyState,
@@ -1,9 +1,9 @@
1
- import type { Line } from '@pellux/goodvibes-sdk/platform/types/grid';
2
- import { createEmptyLine } from '@pellux/goodvibes-sdk/platform/types/grid';
1
+ import type { Line } from '../types/grid.ts';
2
+ import { createEmptyLine } from '../types/grid.ts';
3
3
  import { BasePanel } from './base-panel.ts';
4
4
  import type { McpRegistry } from '@pellux/goodvibes-sdk/platform/mcp/registry';
5
5
  import type { McpDecisionRecord } from '@pellux/goodvibes-sdk/platform/runtime/mcp/types';
6
- import { truncateDisplay } from '@pellux/goodvibes-sdk/platform/utils/terminal-width';
6
+ import { truncateDisplay } from '../utils/terminal-width.ts';
7
7
  import {
8
8
  buildEmptyState,
9
9
  buildGuidanceLine,