@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,436 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // ToolInspectorPanel — chronological list of tool calls with args/results.
3
- // ---------------------------------------------------------------------------
4
-
5
- import type { Line } from '../types/grid.ts';
6
- import { BasePanel } from './base-panel.ts';
7
- import type { ToolEvent, TurnEvent } from '@/runtime/index.ts';
8
- import type { UiEventFeed } from '../runtime/ui-events.ts';
9
- import {
10
- buildEmptyState,
11
- buildPanelLine,
12
- buildStyledPanelLine,
13
- buildPanelWorkspace,
14
- resolveScrollablePanelSection,
15
- DEFAULT_PANEL_PALETTE,
16
- type PanelWorkspaceSection,
17
- } from './polish.ts';
18
- import { truncateDisplay } from '../utils/terminal-width.ts';
19
-
20
- const C = {
21
- headerBg: '#1a1a2e',
22
- headerFg: '#ffffff',
23
- statusBar: '#222233',
24
- statusFg: '#aaaaaa',
25
- toolFg: '#00ccff',
26
- resultFg: '#66ddff',
27
- errorFg: '#ff6666',
28
- durationFg: '#aaaa66',
29
- argsFg: '#aaaaaa',
30
- dimFg: '#555566',
31
- selected: '#00ffff',
32
- selectedBg: '#1a2a3a',
33
- labelFg: '#8888bb',
34
- filterFg: '#ffcc44',
35
- } as const;
36
-
37
- const MAX_ENTRIES = 500;
38
-
39
- interface ToolCallRecord {
40
- callId: string;
41
- tool: string;
42
- args: Record<string, unknown>;
43
- startMs: number;
44
- endMs?: number;
45
- result?: unknown;
46
- error?: string;
47
- expanded: boolean;
48
- approved?: boolean;
49
- outputClass?: string;
50
- policyAction?: string;
51
- spillBackend?: string;
52
- resultSummary?: string;
53
- }
54
-
55
- type FilterMode = 'all' | string;
56
-
57
- type FlatRow =
58
- | { kind: 'call'; recordIndex: number; text: string }
59
- | { kind: 'detail'; text: string; isError: boolean };
60
-
61
- function shortTime(ms: number): string {
62
- const d = new Date(ms);
63
- return `${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}:${String(d.getSeconds()).padStart(2,'0')}`;
64
- }
65
-
66
- function formatMs(ms: number): string {
67
- if (ms < 1000) return `${ms}ms`;
68
- if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
69
- return `${Math.floor(ms / 60000)}m${Math.floor((ms % 60000) / 1000)}s`;
70
- }
71
-
72
- function truncateJson(val: unknown, maxLen = 120): string {
73
- const s = JSON.stringify(val) ?? 'null';
74
- return s.length > maxLen ? s.slice(0, maxLen - 1) + '\u2026' : s;
75
- }
76
-
77
- function isResultSummaryRecord(value: unknown): value is {
78
- readonly preview?: unknown;
79
- readonly kind?: unknown;
80
- readonly byteSize?: unknown;
81
- } {
82
- return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
83
- }
84
-
85
- function summarizeResult(result: unknown): string | undefined {
86
- // SDK OBS-05 (0.21.31+): TOOL_SUCCEEDED/TOOL_FAILED.result is a ToolResultSummary
87
- // { kind: 'text' | 'json' | 'binary' | 'error' | 'empty'; byteSize: number; preview?: string }.
88
- if (!isResultSummaryRecord(result)) return undefined;
89
- if (typeof result.preview === 'string' && result.preview.trim()) {
90
- const compact = result.preview.replace(/\s+/g, ' ').trim();
91
- return compact.length > 72 ? `${compact.slice(0, 69)}\u2026` : compact;
92
- }
93
- if (typeof result.kind === 'string' && typeof result.byteSize === 'number') {
94
- return `${result.kind} (${result.byteSize}B)`;
95
- }
96
- return undefined;
97
- }
98
-
99
- function detectOutputClass(tool: string, args: Record<string, unknown>): string {
100
- const name = tool.toLowerCase();
101
- if (name.includes('read') || name.includes('find') || name.includes('inspect') || name.includes('grep')) return 'read';
102
- if (name.includes('write') || name.includes('edit') || name.includes('patch')) return 'write';
103
- if (name.includes('exec') || name.includes('shell') || name.includes('bash') || typeof args.command === 'string') return 'execute';
104
- if (name.includes('fetch') || name.includes('http') || name.includes('remote')) return 'network';
105
- return 'analyze';
106
- }
107
-
108
- export class ToolInspectorPanel extends BasePanel {
109
- private records: ToolCallRecord[] = [];
110
- private filterMode: FilterMode = 'all';
111
- private unsubs: Array<() => void> = [];
112
- private cursorIndex = 0;
113
- private scrollOffset = 0;
114
- private autoScroll = true;
115
- private _flatCache: FlatRow[] | null = null;
116
-
117
- constructor(
118
- private readonly toolEvents: UiEventFeed<ToolEvent>,
119
- private readonly turnEvents: UiEventFeed<TurnEvent>,
120
- ) {
121
- super('tools', 'Tools', 'X', 'ai');
122
- this._attachBus();
123
- }
124
-
125
- override onActivate(): void {
126
- this.needsRender = true;
127
- }
128
-
129
- override onDeactivate(): void {
130
- super.onDeactivate();
131
- }
132
-
133
- override onDestroy(): void {
134
- this._detachBus();
135
- }
136
-
137
- override markDirty(): void {
138
- this._flatCache = null;
139
- super.markDirty();
140
- }
141
-
142
- handleInput(key: string): boolean {
143
- switch (key) {
144
- case 'up': this._move(-1); this.autoScroll = false; return true;
145
- case 'down': this._move(1); return true;
146
- case 'pageup': this._move(-10); this.autoScroll = false; return true;
147
- case 'pagedown': this._move(10); return true;
148
- case 'return': this._toggleExpand(); return true;
149
- case 'c': this.records = []; this.markDirty(); return true;
150
- case 'g': this.autoScroll = true; this.markDirty(); return true;
151
- case 'f': this._cycleFilter(); return true;
152
- default: return false;
153
- }
154
- }
155
-
156
- render(width: number, height: number): Line[] {
157
- if (height <= 0 || width <= 0) return [];
158
-
159
- const running = this.records.filter(r => r.endMs === undefined).length;
160
- const filterLabel = this.filterMode === 'all' ? '' : ` [${this.filterMode}]`;
161
- const title = ` Tools [${this.records.length} calls${running > 0 ? `, ${running} running` : ''}]${filterLabel}`;
162
- const footerLines = [
163
- buildPanelLine(width, [
164
- [' Up/Down', DEFAULT_PANEL_PALETTE.info],
165
- [' scroll', DEFAULT_PANEL_PALETTE.dim],
166
- [' Enter', DEFAULT_PANEL_PALETTE.info],
167
- [' expand', DEFAULT_PANEL_PALETTE.dim],
168
- [' f', DEFAULT_PANEL_PALETTE.info],
169
- [' filter', DEFAULT_PANEL_PALETTE.dim],
170
- [' c', DEFAULT_PANEL_PALETTE.info],
171
- [' clear', DEFAULT_PANEL_PALETTE.dim],
172
- [' g', DEFAULT_PANEL_PALETTE.info],
173
- [' end', DEFAULT_PANEL_PALETTE.dim],
174
- ]),
175
- ];
176
-
177
- const flat = this._getFlat();
178
-
179
- if (this.autoScroll) {
180
- this.cursorIndex = Math.max(0, flat.length - 1);
181
- }
182
-
183
- if (flat.length === 0) {
184
- return buildPanelWorkspace(width, height, {
185
- title,
186
- intro: 'Inspect chronological tool activity, arguments, results, errors, and running calls.',
187
- sections: [
188
- {
189
- title: 'Calls',
190
- lines: buildEmptyState(
191
- width,
192
- ' No tool calls yet',
193
- 'Tool executions appear here as the agent works. Expand a call to inspect its arguments and result payload.',
194
- [],
195
- DEFAULT_PANEL_PALETTE,
196
- ),
197
- },
198
- ],
199
- footerLines,
200
- palette: DEFAULT_PANEL_PALETTE,
201
- });
202
- }
203
-
204
- this.cursorIndex = Math.max(0, Math.min(this.cursorIndex, Math.max(0, flat.length - 1)));
205
- const summary: PanelWorkspaceSection = {
206
- title: 'Summary',
207
- lines: [
208
- buildPanelLine(width, [
209
- [' Calls ', DEFAULT_PANEL_PALETTE.label],
210
- [String(this.records.length), DEFAULT_PANEL_PALETTE.value],
211
- [' Running ', DEFAULT_PANEL_PALETTE.label],
212
- [String(running), running > 0 ? DEFAULT_PANEL_PALETTE.warn : DEFAULT_PANEL_PALETTE.dim],
213
- [' Filter ', DEFAULT_PANEL_PALETTE.label],
214
- [this.filterMode === 'all' ? 'all' : this.filterMode, this.filterMode === 'all' ? DEFAULT_PANEL_PALETTE.dim : DEFAULT_PANEL_PALETTE.info],
215
- ]),
216
- ],
217
- };
218
-
219
- const selected = flat[this.cursorIndex];
220
- const detailLines: Line[] = [];
221
- if (selected?.kind === 'call') {
222
- const filtered = this.filterMode === 'all'
223
- ? this.records
224
- : this.records.filter(r => r.tool === this.filterMode);
225
- const rec = filtered[selected.recordIndex];
226
- if (rec) {
227
- detailLines.push(buildPanelLine(width, [[' Tool ', DEFAULT_PANEL_PALETTE.label], [rec.tool, DEFAULT_PANEL_PALETTE.info], [' Started ', DEFAULT_PANEL_PALETTE.label], [shortTime(rec.startMs), DEFAULT_PANEL_PALETTE.value]]));
228
- detailLines.push(buildPanelLine(width, [[' Status ', DEFAULT_PANEL_PALETTE.label], [rec.endMs === undefined ? 'running' : rec.error ? 'error' : 'completed', rec.endMs === undefined ? DEFAULT_PANEL_PALETTE.warn : rec.error ? DEFAULT_PANEL_PALETTE.bad : DEFAULT_PANEL_PALETTE.good]]));
229
- detailLines.push(buildPanelLine(width, [
230
- [' Risk ', DEFAULT_PANEL_PALETTE.label],
231
- [rec.outputClass ?? detectOutputClass(rec.tool, rec.args), DEFAULT_PANEL_PALETTE.warn],
232
- [' Approved ', DEFAULT_PANEL_PALETTE.label],
233
- [rec.approved === undefined ? 'unknown' : rec.approved ? 'yes' : 'no', rec.approved === false ? DEFAULT_PANEL_PALETTE.bad : DEFAULT_PANEL_PALETTE.value],
234
- ]));
235
- if (rec.policyAction || rec.spillBackend) {
236
- detailLines.push(buildPanelLine(width, [
237
- [' Output ', DEFAULT_PANEL_PALETTE.label],
238
- [rec.policyAction ?? 'none', rec.policyAction && rec.policyAction !== 'none' ? DEFAULT_PANEL_PALETTE.warn : DEFAULT_PANEL_PALETTE.value],
239
- [' Spill ', DEFAULT_PANEL_PALETTE.label],
240
- [rec.spillBackend ?? 'none', rec.spillBackend ? DEFAULT_PANEL_PALETTE.info : DEFAULT_PANEL_PALETTE.dim],
241
- ]));
242
- }
243
- if (rec.resultSummary) {
244
- detailLines.push(buildPanelLine(width, [[' Summary ', DEFAULT_PANEL_PALETTE.label], [rec.resultSummary, DEFAULT_PANEL_PALETTE.value]]));
245
- }
246
- if (rec.error) detailLines.push(buildPanelLine(width, [[' Error ', DEFAULT_PANEL_PALETTE.bad], [rec.error, DEFAULT_PANEL_PALETTE.value]]));
247
- }
248
- }
249
-
250
- const selectedSection: PanelWorkspaceSection = { title: 'Selected', lines: detailLines };
251
- const callsSection = resolveScrollablePanelSection(width, height, {
252
- intro: 'Inspect chronological tool activity, arguments, results, errors, and running calls.',
253
- footerLines,
254
- palette: DEFAULT_PANEL_PALETTE,
255
- beforeSections: [summary],
256
- section: {
257
- title: 'Calls',
258
- scrollableLines: flat.map((row, index) => this._renderRow(width, row, index === this.cursorIndex)),
259
- selectedIndex: this.cursorIndex,
260
- scrollOffset: this.scrollOffset,
261
- minRows: 8,
262
- },
263
- afterSections: [selectedSection],
264
- });
265
- this.scrollOffset = callsSection.scrollOffset;
266
-
267
- return buildPanelWorkspace(width, height, {
268
- title,
269
- intro: 'Inspect chronological tool activity, arguments, results, errors, and running calls.',
270
- sections: [
271
- summary,
272
- callsSection.section,
273
- selectedSection,
274
- ],
275
- footerLines,
276
- palette: DEFAULT_PANEL_PALETTE,
277
- });
278
- }
279
-
280
- private _renderRow(width: number, row: FlatRow, isCursor: boolean): Line {
281
- const bg = isCursor ? C.selectedBg : '';
282
- if (row.kind === 'call') {
283
- return buildStyledPanelLine(width, [
284
- { text: isCursor ? '▸' : ' ', fg: C.selected, bg, bold: isCursor },
285
- { text: truncateDisplay(row.text, Math.max(0, width - 1)), fg: isCursor ? C.selected : C.toolFg, bg, bold: isCursor },
286
- ]);
287
- }
288
- return buildStyledPanelLine(width, [
289
- { text: ' ', fg: C.argsFg, bg },
290
- { text: truncateDisplay(row.text, Math.max(0, width - 2)), fg: row.isError ? C.errorFg : C.argsFg, bg },
291
- ]);
292
- }
293
-
294
- private _getFlat(): FlatRow[] {
295
- if (this._flatCache) return this._flatCache;
296
- const flat: FlatRow[] = [];
297
- const filtered = this.filterMode === 'all'
298
- ? this.records
299
- : this.records.filter(r => r.tool === this.filterMode);
300
-
301
- for (let i = 0; i < filtered.length; i++) {
302
- const rec = filtered[i]!;
303
- const dur = rec.endMs !== undefined ? ` (${formatMs(rec.endMs - rec.startMs)})` : ' (running)';
304
- const expand = rec.expanded ? ' ▾' : ' ▸';
305
- const ts = shortTime(rec.startMs);
306
- const risk = rec.outputClass ?? detectOutputClass(rec.tool, rec.args);
307
- const action = rec.policyAction && rec.policyAction !== 'none' ? ` ${rec.policyAction}` : '';
308
- const callText = `${ts} ${rec.tool} [${risk}]${action}${dur}${expand}`;
309
- flat.push({ kind: 'call', recordIndex: i, text: callText });
310
-
311
- if (rec.expanded) {
312
- const argsStr = truncateJson(rec.args, 200);
313
- flat.push({ kind: 'detail', text: `Args ${argsStr}`, isError: false });
314
- flat.push({ kind: 'detail', text: `Risk ${risk}${rec.approved === undefined ? '' : ` Approved ${rec.approved ? 'yes' : 'no'}`}`, isError: false });
315
- if (rec.policyAction || rec.spillBackend) {
316
- flat.push({
317
- kind: 'detail',
318
- text: `Output policy ${rec.policyAction ?? 'none'}${rec.spillBackend ? ` Spill ${rec.spillBackend}` : ''}`,
319
- isError: false,
320
- });
321
- }
322
- if (rec.resultSummary) {
323
- flat.push({ kind: 'detail', text: `Summary ${rec.resultSummary}`, isError: false });
324
- }
325
- if (rec.result !== undefined) {
326
- flat.push({ kind: 'detail', text: `Result ${truncateJson(rec.result, 200)}`, isError: false });
327
- }
328
- if (rec.error) {
329
- flat.push({ kind: 'detail', text: `Error ${rec.error}`, isError: true });
330
- }
331
- }
332
- }
333
- this._flatCache = flat;
334
- return flat;
335
- }
336
-
337
- private _toggleExpand(): void {
338
- const flat = this._getFlat();
339
- const row = flat[this.cursorIndex];
340
- if (!row || row.kind !== 'call') return;
341
- const rec = this.records.filter(r =>
342
- this.filterMode === 'all' ? true : r.tool === this.filterMode
343
- )[row.recordIndex];
344
- if (rec) {
345
- rec.expanded = !rec.expanded;
346
- this.markDirty();
347
- }
348
- }
349
-
350
- private _cycleFilter(): void {
351
- const tools = [...new Set(this.records.map(r => r.tool))];
352
- if (tools.length === 0) return;
353
- if (this.filterMode === 'all') {
354
- this.filterMode = tools[0]!;
355
- } else {
356
- const idx = tools.indexOf(this.filterMode);
357
- this.filterMode = idx >= tools.length - 1 ? 'all' : tools[idx + 1]!;
358
- }
359
- this.markDirty();
360
- }
361
-
362
- private _move(delta: number): void {
363
- const flat = this._getFlat();
364
- if (flat.length === 0) return;
365
- this.cursorIndex = Math.max(0, Math.min(flat.length - 1, this.cursorIndex + delta));
366
- this.markDirty();
367
- }
368
-
369
- private _attachBus(): void {
370
- if (this.unsubs.length > 0) return;
371
-
372
- this.unsubs.push(this.toolEvents.on('TOOL_RECEIVED', (data) => {
373
- if (this.records.length >= MAX_ENTRIES) {
374
- this.records.shift();
375
- }
376
- this.records.push({
377
- callId: data.callId,
378
- tool: data.tool,
379
- args: data.args,
380
- startMs: Date.now(),
381
- expanded: false,
382
- outputClass: detectOutputClass(data.tool, data.args),
383
- });
384
- this.autoScroll = true;
385
- this.markDirty();
386
- }));
387
-
388
- this.unsubs.push(this.toolEvents.on('TOOL_PERMISSIONED', (data) => {
389
- const rec = this.records.findLast(r => r.callId === data.callId);
390
- if (rec) rec.approved = data.approved;
391
- this.markDirty();
392
- }));
393
-
394
- // NOTE: After SDK OBS-05 (0.21.31), TOOL_SUCCEEDED/TOOL_FAILED.result is a ToolResultSummary
395
- // ({ kind, byteSize, preview? }) rather than the raw ToolResult object. The previous
396
- // `_policyAudit` extraction is no longer reachable via this event — policy audit metadata
397
- // must be sourced from a different channel (approval broker / tool result store) if the
398
- // Tool Inspector is to display it in future.
399
- this.unsubs.push(this.toolEvents.on('TOOL_SUCCEEDED', (data) => {
400
- const rec = this.records.findLast(r => r.callId === data.callId);
401
- if (rec) {
402
- rec.endMs = Date.now();
403
- rec.result = data.result;
404
- rec.resultSummary = summarizeResult(data.result);
405
- }
406
- this.markDirty();
407
- }));
408
-
409
- this.unsubs.push(this.toolEvents.on('TOOL_FAILED', (data) => {
410
- const rec = this.records.findLast(r => r.callId === data.callId);
411
- if (rec) {
412
- rec.endMs = Date.now();
413
- rec.result = data.result;
414
- rec.error = data.error;
415
- rec.resultSummary = summarizeResult(data.result) ?? data.error;
416
- }
417
- this.markDirty();
418
- }));
419
-
420
- this.unsubs.push(this.turnEvents.on('TURN_ERROR', (data) => {
421
- // Mark any running calls as errored
422
- for (const rec of this.records) {
423
- if (rec.endMs === undefined) {
424
- rec.endMs = Date.now();
425
- rec.error = data.error ?? 'unknown error';
426
- }
427
- }
428
- this.markDirty();
429
- }));
430
- }
431
-
432
- private _detachBus(): void {
433
- for (const unsub of this.unsubs) unsub();
434
- this.unsubs = [];
435
- }
436
- }
@@ -1,54 +0,0 @@
1
- import type { Line } from '../types/grid.ts';
2
- import type { ComponentResourceContract, ComponentHealthState } from '../runtime/perf/panel-contracts.ts';
3
-
4
- export type PanelCategory = 'agent' | 'monitoring' | 'session' | 'ai';
5
-
6
- export interface Panel {
7
- id: string;
8
- name: string;
9
- icon: string; // single char for tab bar
10
- category: PanelCategory;
11
-
12
- // Lifecycle
13
- onActivate(): void;
14
- onDeactivate(): void;
15
- onDestroy(): void;
16
-
17
- // Rendering
18
- render(width: number, height: number): Line[];
19
-
20
- // State
21
- isTransient: boolean;
22
- isPinned: boolean;
23
- needsRender: boolean;
24
-
25
- // Dirty-flag contract (R2: activated panel render skipping)
26
- /** Mark this panel as needing a re-render on the next frame. */
27
- invalidate(): void;
28
- /** Called by the compositor after a successful render to clear the dirty flag. */
29
- markRendered(): void;
30
-
31
- // Resource contract (optional — panels may declare resource requirements)
32
- resourceContract?: Readonly<ComponentResourceContract>;
33
-
34
- // Health state (optional — set by ComponentHealthMonitor when panel is registered)
35
- healthState?: Readonly<ComponentHealthState>;
36
-
37
- // Input (optional)
38
- handleInput?(key: string): boolean;
39
-
40
- // Scroll input (optional)
41
- // Positive delta scrolls down; negative delta scrolls up.
42
- handleScroll?(deltaRows: number): boolean;
43
- }
44
-
45
- export interface PanelRegistration extends Pick<Panel, 'id' | 'name' | 'icon' | 'category'> {
46
- factory: () => Panel;
47
- description: string;
48
- /**
49
- * Instantiate this panel during bootstrap and retain the instance when it is
50
- * closed so its background data continues to accumulate before the user
51
- * actively opens the workspace.
52
- */
53
- preload?: boolean;
54
- }
@@ -1,158 +0,0 @@
1
- import type { Line } from '../types/grid.ts';
2
- import type { Panel } from '../panels/types.ts';
3
- import type { InputHandler } from '../input/handler.ts';
4
- import type { PanelManager } from '../panels/panel-manager.ts';
5
- import type { PanelCompositeData } from './compositor.ts';
6
- import { createSplitPaneLayout } from './layout-engine.ts';
7
- import { renderPanelTabBar } from './panel-tab-bar.ts';
8
- import { renderPanelWorkspaceBar } from './panel-workspace-bar.ts';
9
-
10
- /**
11
- * R2: Per-panel render cache for dirty-flag skipping.
12
- *
13
- * Maintainability hazard — mid-render invalidation race:
14
- *
15
- * If an event listener (e.g. a runtime-bus subscriber) fires DURING a panel's
16
- * `render()` call and mutates state such that `needsRender = true`, the
17
- * trailing `panel.markRendered()` below will clobber that invalidation back
18
- * to `false`, causing the next frame to serve stale cached output until
19
- * something else invalidates the panel.
20
- *
21
- * In practice this is rare because `render()` is synchronous and short, and
22
- * runtime-bus listeners that mutate panel state typically run outside the
23
- * render pass. Panels that DO expect to mutate from within their own render
24
- * path (e.g. deferred lazy-load) should call `this.invalidate()` AFTER the
25
- * trailing work so the next frame picks it up.
26
- *
27
- * The fix (now applied): snapshot `needsRender` before calling `render()` and
28
- * only call `markRendered()` when no concurrent invalidation occurred during
29
- * the render pass — preserving any mid-render invalidation.
30
- */
31
- interface PanelRenderCache {
32
- lines: Line[];
33
- width: number;
34
- height: number;
35
- }
36
- const panelRenderCache = new WeakMap<Panel, PanelRenderCache>();
37
- /**
38
- * Per-panel render-generation counter. Incremented whenever invalidate() fires
39
- * on the panel (tracked here externally since Panel does not expose a version).
40
- * We monkey-patch each panel the first time it enters renderPanel() by wrapping
41
- * its invalidate() to bump the counter stored in this map.
42
- *
43
- * Race-guard: snapshot the generation before render(), compare after. If it
44
- * changed, a mid-render invalidation occurred — leave needsRender=true.
45
- */
46
- const panelRenderGen = new WeakMap<Panel, { gen: number }>();
47
-
48
- function getRenderGenState(panel: Panel): { gen: number } {
49
- let state = panelRenderGen.get(panel);
50
- if (!state) {
51
- state = { gen: 0 };
52
- panelRenderGen.set(panel, state);
53
- // Wrap invalidate() to bump generation counter.
54
- const origInvalidate = panel.invalidate.bind(panel);
55
- panel.invalidate = () => {
56
- state!.gen++;
57
- origInvalidate();
58
- };
59
- }
60
- return state;
61
- }
62
-
63
- /** R2: Render a panel, skipping if nothing changed. Returns cached lines on a skip. */
64
- export function renderPanel(panel: Panel, width: number, height: number): Line[] {
65
- const cached = panelRenderCache.get(panel);
66
- if (cached && !panel.needsRender && cached.width === width && cached.height === height) {
67
- return cached.lines;
68
- }
69
- // Snapshot render-generation counter BEFORE calling render(). If an event
70
- // listener fires during render() and calls panel.invalidate(), the generation
71
- // counter will be bumped. We compare after render to detect mid-render races.
72
- const genState = getRenderGenState(panel);
73
- const genBefore = genState.gen;
74
- const lines = panel.render(width, height);
75
- // Only call markRendered() when no mid-render invalidation occurred.
76
- // If the generation changed during render(), a concurrent invalidate() fired —
77
- // leave needsRender=true so the next frame re-renders with the new state.
78
- if (genState.gen === genBefore) {
79
- panel.markRendered();
80
- }
81
- // If gen changed, needsRender is already true (invalidate() set it); do not
82
- // call markRendered() — the next frame will pick it up.
83
- panelRenderCache.set(panel, { lines, width, height });
84
- return lines;
85
- }
86
-
87
- export interface PanelCompositeBuildResult {
88
- readonly panelData?: PanelCompositeData;
89
- readonly panelWidth?: number;
90
- }
91
-
92
- export function buildPanelCompositeData(
93
- panelManager: PanelManager,
94
- input: InputHandler,
95
- panelWidth: number,
96
- panelHeight: number,
97
- ): PanelCompositeBuildResult {
98
- if (!panelManager.isVisible() || panelManager.getAllOpen().length === 0 || panelWidth <= 0) {
99
- return {};
100
- }
101
-
102
- const topPane = panelManager.getTopPane();
103
- const bottomPane = panelManager.getBottomPane();
104
- const focusedPane = panelManager.getFocusedPane();
105
- const workspaceTabs = panelManager.getWorkspaceTabs();
106
- const verticalSplitRatio = panelManager.getVerticalSplitRatio();
107
- const hasBottom = panelManager.isBottomPaneVisible() && bottomPane.panels.length > 0;
108
- const workspaceBar = renderPanelWorkspaceBar(workspaceTabs, panelWidth, input.panelFocused);
109
-
110
- let topContent: Line[];
111
- let topTabBar: Line | undefined;
112
- let bottomTabBar: Line | undefined;
113
- let bottomContent: Line[] | undefined;
114
-
115
- const topActivePanel = topPane.panels[topPane.activeIndex] ?? null;
116
-
117
- if (hasBottom) {
118
- topTabBar = renderPanelTabBar(
119
- topPane.panels,
120
- topPane.activeIndex,
121
- panelWidth,
122
- input.panelFocused && focusedPane === 'top',
123
- 'top',
124
- );
125
- const paneLayout = createSplitPaneLayout(Math.max(0, panelHeight - 1), verticalSplitRatio);
126
- const topH = paneLayout.topContentRows;
127
- const bottomH = paneLayout.bottomContentRows;
128
- topContent = topActivePanel ? renderPanel(topActivePanel, panelWidth, topH) : [];
129
-
130
- const bottomActivePanel = bottomPane.panels[bottomPane.activeIndex] ?? null;
131
- bottomTabBar = renderPanelTabBar(
132
- bottomPane.panels,
133
- bottomPane.activeIndex,
134
- panelWidth,
135
- input.panelFocused && focusedPane === 'bottom',
136
- 'bottom',
137
- );
138
- bottomContent = bottomActivePanel ? renderPanel(bottomActivePanel, panelWidth, bottomH) : [];
139
- } else {
140
- const topH = Math.max(0, panelHeight - 1);
141
- topContent = topActivePanel ? renderPanel(topActivePanel, panelWidth, topH) : [];
142
- }
143
-
144
- return {
145
- panelData: {
146
- workspaceBar,
147
- topTabBar,
148
- topContent,
149
- topFocused: input.panelFocused && focusedPane === 'top',
150
- bottomTabBar,
151
- bottomContent,
152
- bottomFocused: input.panelFocused && focusedPane === 'bottom',
153
- separator: true,
154
- verticalSplitRatio,
155
- },
156
- panelWidth,
157
- };
158
- }