@pellux/goodvibes-agent 1.4.4 → 1.5.1

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 (230) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +52 -7
  3. package/dist/package/main.js +7491 -12253
  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/live-verification/live-verification.json +13 -13
  11. package/release/live-verification/live-verification.md +15 -15
  12. package/release/performance-snapshot.json +2 -2
  13. package/release/release-notes.md +5 -7
  14. package/release/release-readiness.json +6 -6
  15. package/src/agent/behavior-discovery-summary.ts +4 -18
  16. package/src/agent/calendar-registry.ts +322 -0
  17. package/src/agent/competitive-feature-inventory.ts +268 -130
  18. package/src/agent/document-registry.ts +5 -1
  19. package/src/agent/email/email-service.ts +350 -0
  20. package/src/agent/email/imap-client.ts +596 -0
  21. package/src/agent/email/smtp-client.ts +453 -0
  22. package/src/agent/ics-calendar.ts +662 -0
  23. package/src/agent/ics-timezone.ts +172 -0
  24. package/src/agent/markdown-frontmatter.ts +31 -0
  25. package/src/agent/memory-safety.ts +1 -1
  26. package/src/agent/note-registry.ts +3 -9
  27. package/src/agent/persona-discovery.ts +3 -15
  28. package/src/agent/persona-registry.ts +1 -10
  29. package/src/agent/research-source-registry.ts +5 -1
  30. package/src/agent/routine-discovery.ts +3 -15
  31. package/src/agent/runtime-profile.ts +3 -0
  32. package/src/agent/skill-discovery.ts +3 -15
  33. package/src/agent/skill-draft-proposer.ts +203 -0
  34. package/src/agent/skill-draft-runner.ts +201 -0
  35. package/src/agent/skill-registry.ts +36 -1
  36. package/src/agent/skill-standard.ts +99 -0
  37. package/src/agent/vibe-file.ts +7 -22
  38. package/src/cli/completion.ts +1 -1
  39. package/src/cli/config-overrides.ts +10 -1
  40. package/src/cli/redaction.ts +17 -5
  41. package/src/config/provider-model.ts +2 -1
  42. package/src/config/secret-config.ts +13 -6
  43. package/src/core/activity-feed.ts +97 -0
  44. package/src/core/away-digest.ts +161 -0
  45. package/src/core/conversation-rendering.ts +7 -3
  46. package/src/core/conversation.ts +22 -15
  47. package/src/core/hardware-profile.ts +362 -0
  48. package/src/core/last-seen-store.ts +78 -0
  49. package/src/core/plain-language.ts +52 -0
  50. package/src/core/setup-incomplete-hint.ts +90 -0
  51. package/src/core/system-message-router.ts +38 -87
  52. package/src/input/agent-workspace-basic-command-editor-submission.ts +60 -220
  53. package/src/input/agent-workspace-basic-command-editors.ts +39 -141
  54. package/src/input/agent-workspace-categories.ts +40 -125
  55. package/src/input/agent-workspace-command-editor.ts +4 -0
  56. package/src/input/agent-workspace-host-category.ts +0 -5
  57. package/src/input/agent-workspace-local-editor-submission.ts +3 -1
  58. package/src/input/agent-workspace-navigation.ts +10 -3
  59. package/src/input/agent-workspace-onboarding-actions.ts +132 -0
  60. package/src/input/agent-workspace-onboarding-categories.ts +29 -26
  61. package/src/input/agent-workspace-onboarding-finish.ts +11 -4
  62. package/src/input/agent-workspace-onboarding-state.ts +111 -0
  63. package/src/input/agent-workspace-profile-editor-submission.ts +260 -0
  64. package/src/input/agent-workspace-profile-editors.ts +155 -0
  65. package/src/input/agent-workspace-subscription-editor.ts +7 -0
  66. package/src/input/agent-workspace-types.ts +5 -1
  67. package/src/input/agent-workspace.ts +65 -39
  68. package/src/input/command-registry.ts +18 -5
  69. package/src/input/commands/agent-runtime-profile-runtime.ts +2 -1
  70. package/src/input/commands/agent-skills-runtime.ts +60 -3
  71. package/src/input/commands/calendar-runtime.ts +209 -0
  72. package/src/input/commands/channels-runtime.ts +1 -0
  73. package/src/input/commands/command-error.ts +9 -0
  74. package/src/input/commands/compat-runtime.ts +1 -0
  75. package/src/input/commands/config.ts +1 -0
  76. package/src/input/commands/conversation-runtime.ts +1 -1
  77. package/src/input/commands/delegation-runtime.ts +5 -6
  78. package/src/input/commands/email-runtime.ts +387 -0
  79. package/src/input/commands/experience-runtime.ts +17 -4
  80. package/src/input/commands/guidance-runtime.ts +1 -1
  81. package/src/input/commands/health-runtime.ts +6 -1
  82. package/src/input/commands/knowledge-format.ts +73 -0
  83. package/src/input/commands/knowledge.ts +9 -74
  84. package/src/input/commands/local-provider-runtime.ts +2 -1
  85. package/src/input/commands/local-runtime.ts +10 -4
  86. package/src/input/commands/mcp-runtime.ts +7 -0
  87. package/src/input/commands/notify-runtime.ts +17 -1
  88. package/src/input/commands/onboarding-runtime.ts +1 -0
  89. package/src/input/commands/operator-actions-runtime.ts +1 -0
  90. package/src/input/commands/operator-runtime.ts +3 -0
  91. package/src/input/commands/personas-runtime.ts +1 -0
  92. package/src/input/commands/platform-access-runtime.ts +40 -17
  93. package/src/input/commands/product-runtime.ts +6 -1
  94. package/src/input/commands/provider-accounts-runtime.ts +3 -2
  95. package/src/input/commands/qrcode-runtime.ts +1 -0
  96. package/src/input/commands/routines-runtime.ts +1 -0
  97. package/src/input/commands/runtime-services.ts +0 -13
  98. package/src/input/commands/security-runtime.ts +1 -0
  99. package/src/input/commands/session-content.ts +1 -0
  100. package/src/input/commands/shell-core.ts +5 -2
  101. package/src/input/commands/subscription-runtime.ts +33 -9
  102. package/src/input/commands/support-bundle-runtime.ts +8 -1
  103. package/src/input/commands/tasks-runtime.ts +1 -0
  104. package/src/input/commands/tts-runtime.ts +1 -0
  105. package/src/input/commands/vibe-runtime.ts +1 -0
  106. package/src/input/commands.ts +4 -0
  107. package/src/input/feed-context-factory.ts +3 -12
  108. package/src/input/handler-command-route.ts +7 -60
  109. package/src/input/handler-feed-routes.ts +0 -194
  110. package/src/input/handler-feed.ts +2 -54
  111. package/src/input/handler-interactions.ts +0 -2
  112. package/src/input/handler-modal-stack.ts +0 -9
  113. package/src/input/handler-picker-routes.ts +24 -1
  114. package/src/input/handler-shortcuts.ts +11 -40
  115. package/src/input/handler-ui-state.ts +13 -0
  116. package/src/input/handler.ts +8 -35
  117. package/src/input/keybindings.ts +40 -17
  118. package/src/input/model-picker-local-fit.ts +130 -0
  119. package/src/input/shell-passthrough.ts +58 -0
  120. package/src/input/submission-router.ts +0 -5
  121. package/src/main.ts +129 -90
  122. package/src/renderer/activity-sidebar.ts +186 -0
  123. package/src/renderer/agent-workspace-context-lines.ts +5 -48
  124. package/src/renderer/agent-workspace-style.ts +1 -1
  125. package/src/renderer/agent-workspace.ts +14 -2
  126. package/src/renderer/compositor.ts +37 -125
  127. package/src/renderer/conversation-overlays.ts +3 -3
  128. package/src/renderer/help-overlay.ts +7 -5
  129. package/src/renderer/model-workspace.ts +101 -86
  130. package/src/{panels → renderer}/polish.ts +3 -3
  131. package/src/renderer/shell-surface.ts +4 -5
  132. package/src/renderer/status-token.ts +31 -11
  133. package/src/renderer/tab-strip.ts +1 -1
  134. package/src/renderer/tool-call.ts +7 -8
  135. package/src/renderer/tool-labels.ts +92 -0
  136. package/src/renderer/ui-factory.ts +48 -96
  137. package/src/runtime/bootstrap-command-context.ts +0 -5
  138. package/src/runtime/bootstrap-command-parts.ts +6 -15
  139. package/src/runtime/bootstrap-core.ts +34 -13
  140. package/src/runtime/bootstrap-hook-bridge.ts +3 -1
  141. package/src/runtime/bootstrap-shell.ts +3 -50
  142. package/src/runtime/bootstrap.ts +3 -4
  143. package/src/runtime/context.ts +1 -1
  144. package/src/runtime/diagnostics/panels/index.ts +0 -1
  145. package/src/runtime/diagnostics/panels/policy.ts +1 -1
  146. package/src/runtime/execution-ledger.ts +16 -1
  147. package/src/runtime/index.ts +6 -1
  148. package/src/runtime/onboarding/index.ts +1 -0
  149. package/src/runtime/onboarding/onboarding-state.ts +200 -0
  150. package/src/{panels → runtime}/provider-account-snapshot.ts +5 -2
  151. package/src/runtime/services.ts +16 -4
  152. package/src/runtime/terminal-output-guard.ts +7 -0
  153. package/src/runtime/tool-permission-safety.ts +1 -1
  154. package/src/runtime/ui/model-picker/data-provider.ts +1 -1
  155. package/src/runtime/ui/model-picker/health-enrichment.ts +2 -2
  156. package/src/runtime/ui/model-picker/types.ts +2 -2
  157. package/src/runtime/ui/provider-health/data-provider.ts +3 -3
  158. package/src/runtime/ui/provider-health/types.ts +2 -2
  159. package/src/runtime/ui-services.ts +0 -2
  160. package/src/shell/agent-workspace-fullscreen.ts +0 -1
  161. package/src/shell/autonomy-surfacing.ts +272 -0
  162. package/src/shell/session-continuity-hints.ts +0 -6
  163. package/src/shell/startup-wiring.ts +221 -0
  164. package/src/shell/ui-openers.ts +18 -29
  165. package/src/tools/agent-context-policy.ts +1 -1
  166. package/src/tools/agent-harness-background-processes.ts +4 -4
  167. package/src/tools/agent-harness-browser-cockpit-route.ts +3 -3
  168. package/src/tools/agent-harness-command-runner.ts +6 -1
  169. package/src/tools/agent-harness-document-ops.ts +26 -53
  170. package/src/tools/agent-harness-execution-history.ts +1 -13
  171. package/src/tools/agent-harness-execution-posture.ts +0 -15
  172. package/src/tools/agent-harness-keybinding-metadata.ts +28 -29
  173. package/src/tools/agent-harness-local-model-cookbook.ts +24 -1
  174. package/src/tools/agent-harness-metadata.ts +16 -0
  175. package/src/tools/agent-harness-mode-catalog.ts +2 -9
  176. package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
  177. package/src/tools/agent-harness-setup-posture-utils.ts +1 -1
  178. package/src/tools/agent-harness-tool-schema.ts +13 -14
  179. package/src/tools/agent-harness-tool.ts +27 -23
  180. package/src/tools/agent-harness-ui-surface-metadata.ts +10 -20
  181. package/src/tools/agent-harness-workspace-action-runner.ts +3 -4
  182. package/src/tools/agent-workspace-tool.ts +2 -33
  183. package/src/utils/terminal-width.ts +9 -3
  184. package/src/version.ts +1 -1
  185. package/src/input/agent-workspace-panel-route.ts +0 -44
  186. package/src/input/panel-integration-actions.ts +0 -26
  187. package/src/panels/approval-panel.ts +0 -149
  188. package/src/panels/automation-control-panel.ts +0 -212
  189. package/src/panels/base-panel.ts +0 -254
  190. package/src/panels/builtin/agent.ts +0 -58
  191. package/src/panels/builtin/knowledge.ts +0 -26
  192. package/src/panels/builtin/operations.ts +0 -121
  193. package/src/panels/builtin/session.ts +0 -138
  194. package/src/panels/builtin/shared.ts +0 -275
  195. package/src/panels/builtin/usage.ts +0 -21
  196. package/src/panels/builtin-panels.ts +0 -23
  197. package/src/panels/confirm-state.ts +0 -61
  198. package/src/panels/context-visualizer-panel.ts +0 -204
  199. package/src/panels/cost-tracker-panel.ts +0 -444
  200. package/src/panels/docs-panel.ts +0 -285
  201. package/src/panels/index.ts +0 -25
  202. package/src/panels/knowledge-panel.ts +0 -417
  203. package/src/panels/memory-panel.ts +0 -226
  204. package/src/panels/panel-list-panel.ts +0 -464
  205. package/src/panels/panel-manager.ts +0 -570
  206. package/src/panels/provider-accounts-panel.ts +0 -233
  207. package/src/panels/provider-health-domains.ts +0 -208
  208. package/src/panels/provider-health-panel.ts +0 -720
  209. package/src/panels/provider-health-tracker.ts +0 -115
  210. package/src/panels/provider-stats-panel.ts +0 -366
  211. package/src/panels/qr-panel.ts +0 -207
  212. package/src/panels/schedule-panel.ts +0 -321
  213. package/src/panels/scrollable-list-panel.ts +0 -491
  214. package/src/panels/search-focus.ts +0 -32
  215. package/src/panels/security-panel.ts +0 -295
  216. package/src/panels/session-browser-panel.ts +0 -395
  217. package/src/panels/session-maintenance.ts +0 -125
  218. package/src/panels/subscription-panel.ts +0 -263
  219. package/src/panels/system-messages-panel.ts +0 -230
  220. package/src/panels/tasks-panel.ts +0 -344
  221. package/src/panels/thinking-panel.ts +0 -304
  222. package/src/panels/token-budget-panel.ts +0 -475
  223. package/src/panels/tool-inspector-panel.ts +0 -436
  224. package/src/panels/types.ts +0 -54
  225. package/src/renderer/panel-composite.ts +0 -158
  226. package/src/renderer/panel-tab-bar.ts +0 -69
  227. package/src/renderer/panel-workspace-bar.ts +0 -42
  228. package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
  229. package/src/tools/agent-harness-panel-metadata.ts +0 -211
  230. /package/src/runtime/perf/{panel-health-monitor.ts → component-health.ts} +0 -0
@@ -1,69 +0,0 @@
1
- import { type Line } from '../types/grid.ts';
2
- import type { Panel } from '../panels/types.ts';
3
- import { renderTabStrip } from './tab-strip.ts';
4
-
5
- const ACTIVE_FG = '#e2e8f0';
6
- const ACTIVE_FG_UNFOCUSED = '#94a3b8';
7
- const ACTIVE_BG = '#1e293b';
8
- const INACTIVE_FG = '244';
9
- const SEPARATOR_FG = '238';
10
- const CLOSE_FG = '238';
11
- const LABEL_FG = '#cbd5e1';
12
- const LABEL_BG = '#0f172a';
13
-
14
- /**
15
- * Render the panel tab bar.
16
- *
17
- * Shows open panel tabs with the active one highlighted.
18
- * Format: │ icon Name │ icon Name │ ...
19
- *
20
- * Active tab: slate-white, bold, bg #1e293b
21
- * Inactive tab: grey (244), no background
22
- * Separators: │ in dim grey (238)
23
- * Overflow: > right scroll indicator (stateless - shows when tabs extend beyond width)
24
- * Close button: x at far right for active tab
25
- */
26
- export function renderPanelTabBar(
27
- panels: Panel[],
28
- activeIndex: number,
29
- width: number,
30
- focused: boolean = true,
31
- paneLabel?: string,
32
- ): Line {
33
- if (panels.length === 0) return renderTabStrip({ width, tabs: [], style: {
34
- activeFg: ACTIVE_FG,
35
- activeBg: ACTIVE_BG,
36
- activeBold: focused,
37
- inactiveFg: INACTIVE_FG,
38
- separatorFg: SEPARATOR_FG,
39
- labelFg: LABEL_FG,
40
- labelBg: focused ? LABEL_BG : '',
41
- labelBold: focused,
42
- overflowFg: SEPARATOR_FG,
43
- trailingFg: focused ? ACTIVE_FG_UNFOCUSED : INACTIVE_FG,
44
- } });
45
-
46
- const tabs = panels.map((panel, index) => ({
47
- label: `${panel.icon} ${panel.name}`,
48
- active: index === activeIndex,
49
- }));
50
-
51
- return renderTabStrip({
52
- width,
53
- tabs,
54
- prefixLabel: paneLabel ? ` ${paneLabel.toUpperCase()} ` : undefined,
55
- suffixLabel: ` ${panels.length} x `,
56
- style: {
57
- activeFg: focused ? ACTIVE_FG : ACTIVE_FG_UNFOCUSED,
58
- activeBg: ACTIVE_BG,
59
- activeBold: focused,
60
- inactiveFg: INACTIVE_FG,
61
- separatorFg: SEPARATOR_FG,
62
- labelFg: LABEL_FG,
63
- labelBg: focused ? LABEL_BG : '',
64
- labelBold: focused,
65
- overflowFg: SEPARATOR_FG,
66
- trailingFg: CLOSE_FG,
67
- },
68
- });
69
- }
@@ -1,42 +0,0 @@
1
- import type { Line } from '../types/grid.ts';
2
- import type { WorkspaceTab } from '../panels/panel-manager.ts';
3
- import { renderTabStrip } from './tab-strip.ts';
4
-
5
- const ACTIVE_FG = '#e2e8f0';
6
- const ACTIVE_BG = '#0f172a';
7
- const INACTIVE_FG = '#94a3b8';
8
- const SEPARATOR_FG = '238';
9
- const LABEL_FG = '#cbd5e1';
10
- const LABEL_BG = '#1e293b';
11
-
12
- export function renderPanelWorkspaceBar(
13
- tabs: readonly WorkspaceTab[],
14
- width: number,
15
- focused: boolean,
16
- ): Line {
17
- return renderTabStrip({
18
- width,
19
- tabs: tabs.map((tab) => ({
20
- // tab.active = selected in its own pane (drives highlighted background).
21
- // tab.focused = has keyboard focus (drives brighter text / focus indicator).
22
- // A tab can be active-but-not-focused (selected in the unfocused pane) or
23
- // active-and-focused (selected in the focused pane).
24
- label: `${tab.pane === 'bottom' ? 'v' : '^'} ${tab.icon} ${tab.name}${tab.focused ? ' ▸' : ''}`,
25
- active: tab.active,
26
- })),
27
- prefixLabel: ' PANELS ',
28
- suffixLabel: ` ${tabs.length} `,
29
- style: {
30
- activeFg: ACTIVE_FG,
31
- activeBg: ACTIVE_BG,
32
- activeBold: focused,
33
- inactiveFg: INACTIVE_FG,
34
- separatorFg: SEPARATOR_FG,
35
- labelFg: LABEL_FG,
36
- labelBg: focused ? LABEL_BG : '',
37
- labelBold: focused,
38
- overflowFg: SEPARATOR_FG,
39
- trailingFg: INACTIVE_FG,
40
- },
41
- });
42
- }
@@ -1,118 +0,0 @@
1
- /**
2
- * Panel resource diagnostics panel data provider.
3
- *
4
- * Polls the shared shell ComponentHealthMonitor and produces PanelResourceSnapshot
5
- * values for the diagnostics panel to render.
6
- */
7
- import type { ComponentHealthMonitor } from '../../perf/panel-health-monitor.ts';
8
- import type {
9
- ComponentResourceEntry,
10
- ComponentResourceSnapshot,
11
- } from '@/runtime/index.ts';
12
-
13
- const DEFAULT_POLL_INTERVAL_MS = 500;
14
-
15
- const HEALTH_ORDER: Record<string, number> = {
16
- overloaded: 0,
17
- warning: 1,
18
- healthy: 2,
19
- };
20
-
21
- export class PanelResourcesPanel {
22
- private readonly _pollIntervalMs: number;
23
- private readonly _monitor: ComponentHealthMonitor;
24
- private _current: ComponentResourceSnapshot;
25
- private _timerId: ReturnType<typeof setInterval> | null = null;
26
- private readonly _subscribers = new Set<() => void>();
27
-
28
- constructor(monitor: ComponentHealthMonitor, pollIntervalMs: number = DEFAULT_POLL_INTERVAL_MS) {
29
- this._monitor = monitor;
30
- this._pollIntervalMs = pollIntervalMs;
31
- this._current = this._buildSnapshot(Date.now());
32
- }
33
-
34
- start(): void {
35
- if (this._timerId !== null) return;
36
- this._timerId = setInterval(() => {
37
- this._current = this._buildSnapshot(Date.now());
38
- this._notify();
39
- }, this._pollIntervalMs);
40
- }
41
-
42
- stop(): void {
43
- if (this._timerId !== null) {
44
- clearInterval(this._timerId);
45
- this._timerId = null;
46
- }
47
- }
48
-
49
- getSnapshot(): ComponentResourceSnapshot {
50
- return this._current;
51
- }
52
-
53
- refresh(now: number = Date.now()): ComponentResourceSnapshot {
54
- this._current = this._buildSnapshot(now);
55
- return this._current;
56
- }
57
-
58
- subscribe(callback: () => void): () => void {
59
- this._subscribers.add(callback);
60
- return () => this._subscribers.delete(callback);
61
- }
62
-
63
- dispose(): void {
64
- this.stop();
65
- this._subscribers.clear();
66
- }
67
-
68
- private _buildSnapshot(capturedAt: number): ComponentResourceSnapshot {
69
- const healthStates = this._monitor.getAllHealth();
70
-
71
- const panels: ComponentResourceEntry[] = healthStates.map((health) => {
72
- const contract = this._monitor.getContract(health.componentId);
73
- return {
74
- componentId: health.componentId,
75
- throttleStatus: health.throttleStatus,
76
- healthStatus: health.healthStatus,
77
- renderP95Ms: health.renderP95Ms,
78
- maxRenderMs: contract?.maxRenderMs ?? 0,
79
- rendersInWindow: health.rendersInWindow,
80
- maxUpdatesPerSecond: contract?.maxUpdatesPerSecond ?? 0,
81
- consecutiveViolations: health.consecutiveViolations,
82
- totalSuppressed: health.totalSuppressed,
83
- totalPermitted: health.totalPermitted,
84
- lastRenderAt: health.lastRenderAt,
85
- nextAllowedAt: health.nextAllowedAt,
86
- };
87
- });
88
-
89
- panels.sort((left, right) => {
90
- const diff = (HEALTH_ORDER[left.healthStatus] ?? 2) - (HEALTH_ORDER[right.healthStatus] ?? 2);
91
- return diff !== 0 ? diff : left.componentId.localeCompare(right.componentId);
92
- });
93
-
94
- const overloadedCount = panels.filter((panel) => panel.healthStatus === 'overloaded').length;
95
- const warningCount = panels.filter((panel) => panel.healthStatus === 'warning').length;
96
- const healthyCount = panels.filter((panel) => panel.healthStatus === 'healthy').length;
97
- const totalSuppressed = panels.reduce((sum, panel) => sum + panel.totalSuppressed, 0);
98
-
99
- return {
100
- panels,
101
- overloadedCount,
102
- warningCount,
103
- healthyCount,
104
- totalSuppressed,
105
- capturedAt,
106
- };
107
- }
108
-
109
- private _notify(): void {
110
- for (const callback of this._subscribers) {
111
- try {
112
- callback();
113
- } catch {
114
- // Subscriber failures must not take down diagnostics polling.
115
- }
116
- }
117
- }
118
- }
@@ -1,211 +0,0 @@
1
- import type { CommandContext } from '../input/command-registry.ts';
2
- import { agentWorkspaceCategoryForPanel, agentWorkspaceCommandForPanel } from '../input/agent-workspace-panel-route.ts';
3
- import type { PanelRegistration } from '../panels/types.ts';
4
-
5
- export interface AgentHarnessPanelArgs {
6
- readonly query?: unknown;
7
- readonly panelId?: unknown;
8
- readonly target?: unknown;
9
- readonly category?: unknown;
10
- readonly includeParameters?: unknown;
11
- readonly limit?: unknown;
12
- readonly pane?: unknown;
13
- }
14
-
15
- interface PanelLookup {
16
- readonly source: 'panelId' | 'target' | 'query';
17
- readonly input: string;
18
- readonly resolvedBy: 'id' | 'case-insensitive-id' | 'name' | 'case-insensitive-name' | 'search';
19
- }
20
-
21
- type PanelResolution =
22
- | {
23
- readonly status: 'found';
24
- readonly registration: PanelRegistration;
25
- readonly lookup: PanelLookup;
26
- }
27
- | {
28
- readonly status: 'ambiguous';
29
- readonly input: string;
30
- readonly candidates: readonly Record<string, unknown>[];
31
- };
32
-
33
- function readString(value: unknown): string {
34
- return typeof value === 'string' ? value.trim() : '';
35
- }
36
-
37
- function readLimit(value: unknown, fallback: number): number {
38
- const parsed = typeof value === 'string' && value.trim() ? Number(value) : value;
39
- if (typeof parsed !== 'number' || !Number.isFinite(parsed)) return fallback;
40
- return Math.max(1, Math.min(500, Math.trunc(parsed)));
41
- }
42
-
43
- function previewText(value: string, maxLength = 56): string {
44
- const normalized = value.replace(/\s+/g, ' ').trim();
45
- return normalized.length <= maxLength ? normalized : `${normalized.slice(0, maxLength - 3).trimEnd()}...`;
46
- }
47
-
48
- function panelManager(context: CommandContext) {
49
- return context.workspace.panelManager ?? null;
50
- }
51
-
52
- function panelMatches(panel: Record<string, unknown>, query: string): boolean {
53
- if (!query) return true;
54
- return [
55
- panel.id,
56
- panel.name,
57
- panel.category,
58
- panel.description,
59
- panel.summary,
60
- panel.workspaceRoute,
61
- ].map((value) => typeof value === 'object' ? JSON.stringify(value) : String(value ?? '')).join('\n').toLowerCase().includes(query.toLowerCase());
62
- }
63
-
64
- function panelLookupFromArgs(args: AgentHarnessPanelArgs): { readonly source: PanelLookup['source']; readonly input: string } | null {
65
- const panelId = readString(args.panelId);
66
- if (panelId) return { source: 'panelId', input: panelId };
67
- const target = readString(args.target);
68
- if (target) return { source: 'target', input: target };
69
- const query = readString(args.query);
70
- if (query) return { source: 'query', input: query };
71
- return null;
72
- }
73
-
74
- function panelCandidate(registration: PanelRegistration): Record<string, unknown> {
75
- return {
76
- id: registration.id,
77
- name: registration.name,
78
- category: registration.category,
79
- summary: previewText(registration.description),
80
- modelRoute: panelModelRoute(),
81
- workspaceRoute: {
82
- categoryId: agentWorkspaceCategoryForPanel(registration.id),
83
- command: agentWorkspaceCommandForPanel(registration.id),
84
- },
85
- };
86
- }
87
-
88
- function panelModelRoute(): string {
89
- return 'agent_harness mode:"open_panel" or mode:"workspace_actions"';
90
- }
91
-
92
- function resolveHarnessPanel(context: CommandContext, args: AgentHarnessPanelArgs): PanelResolution | null {
93
- const manager = panelManager(context);
94
- const lookup = panelLookupFromArgs(args);
95
- if (!manager || !lookup) return null;
96
- const registrations = manager.getRegisteredTypes();
97
- const exactId = registrations.find((panel) => panel.id === lookup.input);
98
- if (exactId) return { status: 'found', registration: exactId, lookup: { ...lookup, resolvedBy: 'id' } };
99
- const inputLower = lookup.input.toLowerCase();
100
- const exactName = registrations.find((panel) => panel.name === lookup.input);
101
- if (exactName) return { status: 'found', registration: exactName, lookup: { ...lookup, resolvedBy: 'name' } };
102
- const ciId = registrations.filter((panel) => panel.id.toLowerCase() === inputLower);
103
- if (ciId.length === 1) return { status: 'found', registration: ciId[0]!, lookup: { ...lookup, resolvedBy: 'case-insensitive-id' } };
104
- if (ciId.length > 1) return { status: 'ambiguous', input: lookup.input, candidates: ciId.map(panelCandidate).slice(0, 8) };
105
- const ciName = registrations.filter((panel) => panel.name.toLowerCase() === inputLower);
106
- if (ciName.length === 1) return { status: 'found', registration: ciName[0]!, lookup: { ...lookup, resolvedBy: 'case-insensitive-name' } };
107
- if (ciName.length > 1) return { status: 'ambiguous', input: lookup.input, candidates: ciName.map(panelCandidate).slice(0, 8) };
108
- const category = readString(args.category);
109
- const search = registrations.filter((registration) => {
110
- if (category && registration.category !== category) return false;
111
- return panelMatches(panelCandidate(registration), lookup.input);
112
- });
113
- if (search.length === 1) return { status: 'found', registration: search[0]!, lookup: { ...lookup, resolvedBy: 'search' } };
114
- if (search.length > 1) return { status: 'ambiguous', input: lookup.input, candidates: search.map(panelCandidate).slice(0, 8) };
115
- return null;
116
- }
117
-
118
- function describePanelRegistration(
119
- context: CommandContext,
120
- registration: PanelRegistration,
121
- options: { readonly includeParameters?: boolean; readonly lookup?: PanelLookup } = {},
122
- ): Record<string, unknown> {
123
- const manager = panelManager(context);
124
- const openPanel = manager?.getPanel(registration.id) ?? null;
125
- const pane = manager?.getPaneOf(registration.id) ?? null;
126
- const activePanel = manager?.getActivePanel() ?? null;
127
- return {
128
- id: registration.id,
129
- name: registration.name,
130
- icon: registration.icon,
131
- category: registration.category,
132
- ...(options.includeParameters ? { description: registration.description } : { summary: previewText(registration.description) }),
133
- ...(options.lookup ? { lookup: options.lookup } : {}),
134
- preload: registration.preload === true,
135
- open: openPanel !== null,
136
- pane,
137
- active: activePanel?.id === registration.id,
138
- focused: activePanel?.id === registration.id,
139
- modelRoute: panelModelRoute(),
140
- workspaceRoute: {
141
- categoryId: agentWorkspaceCategoryForPanel(registration.id),
142
- command: agentWorkspaceCommandForPanel(registration.id),
143
- },
144
- ...(options.includeParameters ? {
145
- policy: {
146
- effect: 'ui-navigation',
147
- confirmation: 'agent_harness mode:"open_panel" requires confirm:true and explicitUserRequest.',
148
- boundary: 'Panels are Agent/TUI operator views. The model can inspect panel catalog/open state; panel routing uses the existing Agent workspace route and does not mutate connected-host lifecycle.',
149
- },
150
- } : {}),
151
- };
152
- }
153
-
154
- export function totalHarnessPanels(context: CommandContext): number {
155
- return panelManager(context)?.getRegisteredTypes().length ?? 0;
156
- }
157
-
158
- export function listHarnessPanels(context: CommandContext, args: AgentHarnessPanelArgs): readonly Record<string, unknown>[] {
159
- const manager = panelManager(context);
160
- if (!manager) return [];
161
- const query = readString(args.query);
162
- const category = readString(args.category);
163
- const limit = readLimit(args.limit, 200);
164
- const includeParameters = args.includeParameters === true;
165
- return manager.getRegisteredTypes()
166
- .map((registration) => describePanelRegistration(context, registration, { includeParameters }))
167
- .filter((panel) => !category || panel.category === category)
168
- .filter((panel) => panelMatches(panel, query))
169
- .sort((a, b) => String(a.id).localeCompare(String(b.id)))
170
- .slice(0, limit);
171
- }
172
-
173
- export function describeHarnessPanel(context: CommandContext, args: AgentHarnessPanelArgs): Record<string, unknown> | null {
174
- const resolved = resolveHarnessPanel(context, args);
175
- if (resolved?.status === 'found') return describePanelRegistration(context, resolved.registration, { includeParameters: true, lookup: resolved.lookup });
176
- if (resolved?.status === 'ambiguous') {
177
- return { status: 'ambiguous', input: resolved.input, candidates: resolved.candidates };
178
- }
179
- return null;
180
- }
181
-
182
- export function openHarnessPanel(context: CommandContext, args: AgentHarnessPanelArgs): Record<string, unknown> {
183
- const resolved = resolveHarnessPanel(context, args);
184
- if (resolved?.status === 'ambiguous') {
185
- return { status: 'ambiguous_panel', input: resolved.input, candidates: resolved.candidates };
186
- }
187
- if (!resolved) {
188
- return {
189
- status: 'unknown_panel',
190
- panelId: readString(args.panelId || args.target || args.query) || '<missing>',
191
- availablePanels: listHarnessPanels(context, { limit: 50 }).map((entry) => entry.id),
192
- };
193
- }
194
- const panel = describePanelRegistration(context, resolved.registration, { includeParameters: true, lookup: resolved.lookup });
195
- const requestedPane = readString(args.pane);
196
- const pane = requestedPane === 'bottom' || requestedPane === 'top' ? requestedPane : undefined;
197
- if (!context.showPanel) {
198
- return {
199
- status: 'route_unavailable',
200
- panel,
201
- note: 'The current runtime did not provide showPanel. Use the returned workspaceRoute from the TUI.',
202
- };
203
- }
204
- context.showPanel(String(panel.id), pane);
205
- return {
206
- status: 'routed',
207
- panel,
208
- pane: pane ?? 'default',
209
- note: 'Panel routing was handed to the current Agent operator surface.',
210
- };
211
- }