@pellux/goodvibes-agent 1.4.4 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +7 -7
  3. package/dist/package/main.js +7424 -12241
  4. package/docs/README.md +2 -2
  5. package/docs/channels-remote-and-api.md +1 -1
  6. package/docs/getting-started.md +2 -2
  7. package/docs/release-and-publishing.md +1 -1
  8. package/docs/tools-and-commands.md +5 -5
  9. package/package.json +4 -2
  10. package/release/performance-snapshot.json +2 -2
  11. package/release/release-notes.md +11 -7
  12. package/release/release-readiness.json +6 -6
  13. package/src/agent/behavior-discovery-summary.ts +4 -18
  14. package/src/agent/calendar-registry.ts +322 -0
  15. package/src/agent/competitive-feature-inventory.ts +268 -130
  16. package/src/agent/document-registry.ts +5 -1
  17. package/src/agent/email/email-service.ts +350 -0
  18. package/src/agent/email/imap-client.ts +596 -0
  19. package/src/agent/email/smtp-client.ts +453 -0
  20. package/src/agent/ics-calendar.ts +662 -0
  21. package/src/agent/ics-timezone.ts +172 -0
  22. package/src/agent/markdown-frontmatter.ts +31 -0
  23. package/src/agent/memory-safety.ts +1 -1
  24. package/src/agent/note-registry.ts +3 -9
  25. package/src/agent/persona-discovery.ts +3 -15
  26. package/src/agent/persona-registry.ts +1 -10
  27. package/src/agent/research-source-registry.ts +5 -1
  28. package/src/agent/routine-discovery.ts +3 -15
  29. package/src/agent/runtime-profile.ts +3 -0
  30. package/src/agent/skill-discovery.ts +3 -15
  31. package/src/agent/skill-draft-proposer.ts +203 -0
  32. package/src/agent/skill-draft-runner.ts +201 -0
  33. package/src/agent/skill-registry.ts +36 -1
  34. package/src/agent/skill-standard.ts +99 -0
  35. package/src/agent/vibe-file.ts +7 -22
  36. package/src/cli/completion.ts +1 -1
  37. package/src/cli/config-overrides.ts +10 -1
  38. package/src/cli/redaction.ts +17 -5
  39. package/src/config/provider-model.ts +2 -1
  40. package/src/config/secret-config.ts +13 -6
  41. package/src/core/activity-feed.ts +97 -0
  42. package/src/core/away-digest.ts +161 -0
  43. package/src/core/conversation-rendering.ts +7 -3
  44. package/src/core/conversation.ts +22 -15
  45. package/src/core/hardware-profile.ts +362 -0
  46. package/src/core/last-seen-store.ts +78 -0
  47. package/src/core/plain-language.ts +52 -0
  48. package/src/core/setup-incomplete-hint.ts +90 -0
  49. package/src/core/system-message-router.ts +38 -87
  50. package/src/input/agent-workspace-basic-command-editor-submission.ts +60 -220
  51. package/src/input/agent-workspace-basic-command-editors.ts +39 -141
  52. package/src/input/agent-workspace-categories.ts +40 -125
  53. package/src/input/agent-workspace-command-editor.ts +4 -0
  54. package/src/input/agent-workspace-host-category.ts +0 -5
  55. package/src/input/agent-workspace-local-editor-submission.ts +3 -1
  56. package/src/input/agent-workspace-navigation.ts +10 -3
  57. package/src/input/agent-workspace-onboarding-actions.ts +132 -0
  58. package/src/input/agent-workspace-onboarding-categories.ts +29 -26
  59. package/src/input/agent-workspace-onboarding-finish.ts +11 -4
  60. package/src/input/agent-workspace-onboarding-state.ts +111 -0
  61. package/src/input/agent-workspace-profile-editor-submission.ts +260 -0
  62. package/src/input/agent-workspace-profile-editors.ts +155 -0
  63. package/src/input/agent-workspace-subscription-editor.ts +7 -0
  64. package/src/input/agent-workspace-types.ts +5 -1
  65. package/src/input/agent-workspace.ts +65 -39
  66. package/src/input/command-registry.ts +18 -5
  67. package/src/input/commands/agent-runtime-profile-runtime.ts +2 -1
  68. package/src/input/commands/agent-skills-runtime.ts +60 -3
  69. package/src/input/commands/calendar-runtime.ts +209 -0
  70. package/src/input/commands/channels-runtime.ts +1 -0
  71. package/src/input/commands/command-error.ts +9 -0
  72. package/src/input/commands/compat-runtime.ts +1 -0
  73. package/src/input/commands/config.ts +1 -0
  74. package/src/input/commands/conversation-runtime.ts +1 -1
  75. package/src/input/commands/delegation-runtime.ts +5 -6
  76. package/src/input/commands/email-runtime.ts +387 -0
  77. package/src/input/commands/experience-runtime.ts +17 -4
  78. package/src/input/commands/guidance-runtime.ts +1 -1
  79. package/src/input/commands/health-runtime.ts +6 -1
  80. package/src/input/commands/knowledge-format.ts +73 -0
  81. package/src/input/commands/knowledge.ts +9 -74
  82. package/src/input/commands/local-provider-runtime.ts +2 -1
  83. package/src/input/commands/local-runtime.ts +10 -4
  84. package/src/input/commands/mcp-runtime.ts +7 -0
  85. package/src/input/commands/notify-runtime.ts +17 -1
  86. package/src/input/commands/onboarding-runtime.ts +1 -0
  87. package/src/input/commands/operator-actions-runtime.ts +1 -0
  88. package/src/input/commands/operator-runtime.ts +3 -0
  89. package/src/input/commands/personas-runtime.ts +1 -0
  90. package/src/input/commands/platform-access-runtime.ts +40 -17
  91. package/src/input/commands/product-runtime.ts +6 -1
  92. package/src/input/commands/provider-accounts-runtime.ts +3 -2
  93. package/src/input/commands/qrcode-runtime.ts +1 -0
  94. package/src/input/commands/routines-runtime.ts +1 -0
  95. package/src/input/commands/runtime-services.ts +0 -13
  96. package/src/input/commands/security-runtime.ts +1 -0
  97. package/src/input/commands/session-content.ts +1 -0
  98. package/src/input/commands/shell-core.ts +5 -2
  99. package/src/input/commands/subscription-runtime.ts +33 -9
  100. package/src/input/commands/support-bundle-runtime.ts +8 -1
  101. package/src/input/commands/tasks-runtime.ts +1 -0
  102. package/src/input/commands/tts-runtime.ts +1 -0
  103. package/src/input/commands/vibe-runtime.ts +1 -0
  104. package/src/input/commands.ts +4 -0
  105. package/src/input/feed-context-factory.ts +3 -12
  106. package/src/input/handler-command-route.ts +7 -60
  107. package/src/input/handler-feed-routes.ts +0 -194
  108. package/src/input/handler-feed.ts +2 -54
  109. package/src/input/handler-interactions.ts +0 -2
  110. package/src/input/handler-modal-stack.ts +0 -9
  111. package/src/input/handler-picker-routes.ts +24 -1
  112. package/src/input/handler-shortcuts.ts +11 -40
  113. package/src/input/handler-ui-state.ts +13 -0
  114. package/src/input/handler.ts +8 -35
  115. package/src/input/keybindings.ts +40 -17
  116. package/src/input/model-picker-local-fit.ts +130 -0
  117. package/src/input/submission-router.ts +0 -5
  118. package/src/main.ts +111 -89
  119. package/src/renderer/activity-sidebar.ts +186 -0
  120. package/src/renderer/agent-workspace-context-lines.ts +5 -48
  121. package/src/renderer/agent-workspace-style.ts +1 -1
  122. package/src/renderer/agent-workspace.ts +14 -2
  123. package/src/renderer/compositor.ts +37 -125
  124. package/src/renderer/conversation-overlays.ts +3 -3
  125. package/src/renderer/help-overlay.ts +7 -5
  126. package/src/renderer/model-workspace.ts +101 -86
  127. package/src/{panels → renderer}/polish.ts +3 -3
  128. package/src/renderer/shell-surface.ts +4 -5
  129. package/src/renderer/status-token.ts +31 -11
  130. package/src/renderer/tab-strip.ts +1 -1
  131. package/src/renderer/tool-call.ts +7 -8
  132. package/src/renderer/tool-labels.ts +92 -0
  133. package/src/renderer/ui-factory.ts +48 -96
  134. package/src/runtime/bootstrap-command-context.ts +0 -5
  135. package/src/runtime/bootstrap-command-parts.ts +6 -15
  136. package/src/runtime/bootstrap-core.ts +34 -13
  137. package/src/runtime/bootstrap-hook-bridge.ts +3 -1
  138. package/src/runtime/bootstrap-shell.ts +3 -50
  139. package/src/runtime/bootstrap.ts +3 -4
  140. package/src/runtime/context.ts +1 -1
  141. package/src/runtime/diagnostics/panels/index.ts +0 -1
  142. package/src/runtime/diagnostics/panels/policy.ts +1 -1
  143. package/src/runtime/execution-ledger.ts +16 -1
  144. package/src/runtime/index.ts +6 -1
  145. package/src/runtime/onboarding/index.ts +1 -0
  146. package/src/runtime/onboarding/onboarding-state.ts +200 -0
  147. package/src/{panels → runtime}/provider-account-snapshot.ts +5 -2
  148. package/src/runtime/services.ts +16 -4
  149. package/src/runtime/terminal-output-guard.ts +7 -0
  150. package/src/runtime/tool-permission-safety.ts +1 -1
  151. package/src/runtime/ui/model-picker/data-provider.ts +1 -1
  152. package/src/runtime/ui/model-picker/health-enrichment.ts +2 -2
  153. package/src/runtime/ui/model-picker/types.ts +2 -2
  154. package/src/runtime/ui/provider-health/data-provider.ts +3 -3
  155. package/src/runtime/ui/provider-health/types.ts +2 -2
  156. package/src/runtime/ui-services.ts +0 -2
  157. package/src/shell/agent-workspace-fullscreen.ts +0 -1
  158. package/src/shell/autonomy-surfacing.ts +272 -0
  159. package/src/shell/session-continuity-hints.ts +0 -6
  160. package/src/shell/startup-wiring.ts +221 -0
  161. package/src/shell/ui-openers.ts +18 -29
  162. package/src/tools/agent-context-policy.ts +1 -1
  163. package/src/tools/agent-harness-background-processes.ts +4 -4
  164. package/src/tools/agent-harness-browser-cockpit-route.ts +3 -3
  165. package/src/tools/agent-harness-command-runner.ts +6 -1
  166. package/src/tools/agent-harness-document-ops.ts +26 -53
  167. package/src/tools/agent-harness-execution-history.ts +1 -13
  168. package/src/tools/agent-harness-execution-posture.ts +0 -15
  169. package/src/tools/agent-harness-keybinding-metadata.ts +28 -29
  170. package/src/tools/agent-harness-local-model-cookbook.ts +24 -1
  171. package/src/tools/agent-harness-metadata.ts +16 -0
  172. package/src/tools/agent-harness-mode-catalog.ts +2 -9
  173. package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
  174. package/src/tools/agent-harness-setup-posture-utils.ts +1 -1
  175. package/src/tools/agent-harness-tool-schema.ts +13 -14
  176. package/src/tools/agent-harness-tool.ts +27 -23
  177. package/src/tools/agent-harness-ui-surface-metadata.ts +10 -20
  178. package/src/tools/agent-harness-workspace-action-runner.ts +3 -4
  179. package/src/tools/agent-workspace-tool.ts +2 -33
  180. package/src/utils/terminal-width.ts +9 -3
  181. package/src/version.ts +1 -1
  182. package/src/input/agent-workspace-panel-route.ts +0 -44
  183. package/src/input/panel-integration-actions.ts +0 -26
  184. package/src/panels/approval-panel.ts +0 -149
  185. package/src/panels/automation-control-panel.ts +0 -212
  186. package/src/panels/base-panel.ts +0 -254
  187. package/src/panels/builtin/agent.ts +0 -58
  188. package/src/panels/builtin/knowledge.ts +0 -26
  189. package/src/panels/builtin/operations.ts +0 -121
  190. package/src/panels/builtin/session.ts +0 -138
  191. package/src/panels/builtin/shared.ts +0 -275
  192. package/src/panels/builtin/usage.ts +0 -21
  193. package/src/panels/builtin-panels.ts +0 -23
  194. package/src/panels/confirm-state.ts +0 -61
  195. package/src/panels/context-visualizer-panel.ts +0 -204
  196. package/src/panels/cost-tracker-panel.ts +0 -444
  197. package/src/panels/docs-panel.ts +0 -285
  198. package/src/panels/index.ts +0 -25
  199. package/src/panels/knowledge-panel.ts +0 -417
  200. package/src/panels/memory-panel.ts +0 -226
  201. package/src/panels/panel-list-panel.ts +0 -464
  202. package/src/panels/panel-manager.ts +0 -570
  203. package/src/panels/provider-accounts-panel.ts +0 -233
  204. package/src/panels/provider-health-domains.ts +0 -208
  205. package/src/panels/provider-health-panel.ts +0 -720
  206. package/src/panels/provider-health-tracker.ts +0 -115
  207. package/src/panels/provider-stats-panel.ts +0 -366
  208. package/src/panels/qr-panel.ts +0 -207
  209. package/src/panels/schedule-panel.ts +0 -321
  210. package/src/panels/scrollable-list-panel.ts +0 -491
  211. package/src/panels/search-focus.ts +0 -32
  212. package/src/panels/security-panel.ts +0 -295
  213. package/src/panels/session-browser-panel.ts +0 -395
  214. package/src/panels/session-maintenance.ts +0 -125
  215. package/src/panels/subscription-panel.ts +0 -263
  216. package/src/panels/system-messages-panel.ts +0 -230
  217. package/src/panels/tasks-panel.ts +0 -344
  218. package/src/panels/thinking-panel.ts +0 -304
  219. package/src/panels/token-budget-panel.ts +0 -475
  220. package/src/panels/tool-inspector-panel.ts +0 -436
  221. package/src/panels/types.ts +0 -54
  222. package/src/renderer/panel-composite.ts +0 -158
  223. package/src/renderer/panel-tab-bar.ts +0 -69
  224. package/src/renderer/panel-workspace-bar.ts +0 -42
  225. package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
  226. package/src/tools/agent-harness-panel-metadata.ts +0 -211
  227. /package/src/runtime/perf/{panel-health-monitor.ts → component-health.ts} +0 -0
@@ -1,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
- }