@pellux/goodvibes-agent 1.4.3 → 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 +24 -0
  2. package/README.md +7 -7
  3. package/dist/package/main.js +7450 -12285
  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 +63 -158
  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,417 +0,0 @@
1
- import type { Line } from '../types/grid.ts';
2
- import { ScrollableListPanel } from './scrollable-list-panel.ts';
3
- import { type ConfirmState, handleConfirmInput, renderConfirmLines } from './confirm-state.ts';
4
- import type { MemoryClass, MemoryRecord, MemoryRegistry, MemoryReviewState } from '@pellux/goodvibes-sdk/platform/state';
5
- import type { KnowledgeStatus } from '@pellux/goodvibes-sdk/platform/knowledge';
6
- import { formatAgentRecordReferences } from '../agent/record-labels.ts';
7
- import {
8
- buildBodyText,
9
- buildGuidanceLine,
10
- buildKeyValueLine,
11
- buildPanelLine,
12
- buildPanelWorkspace,
13
- DEFAULT_PANEL_PALETTE,
14
- } from './polish.ts';
15
-
16
- export interface AgentKnowledgePanelService {
17
- readonly getStatus: () => Promise<KnowledgeStatus & { readonly note?: string }>;
18
- }
19
-
20
- function summarize(records: MemoryRecord[], cls: MemoryClass): MemoryRecord[] {
21
- return records.filter((record) => record.cls === cls).slice(0, 3);
22
- }
23
-
24
- const C = {
25
- ...DEFAULT_PANEL_PALETTE,
26
- header: '#94a3b8',
27
- headerBg: '#1e293b',
28
- } as const;
29
-
30
- function reviewStateColor(state: MemoryReviewState): string {
31
- switch (state) {
32
- case 'reviewed':
33
- return C.good;
34
- case 'stale':
35
- return C.warn;
36
- case 'contradicted':
37
- return C.bad;
38
- case 'fresh':
39
- default:
40
- return C.info;
41
- }
42
- }
43
-
44
- function formatConfidence(confidence: number): string {
45
- return `${confidence.toString().padStart(3, ' ')}%`;
46
- }
47
-
48
- export class KnowledgePanel extends ScrollableListPanel<MemoryRecord> {
49
- private readonly registry: MemoryRegistry;
50
- private readonly agentKnowledgeService: AgentKnowledgePanelService | null;
51
- private unsubscribe?: () => void;
52
- private records: MemoryRecord[] = [];
53
- private agentKnowledgeStatus: (KnowledgeStatus & { readonly note?: string }) | null = null;
54
- private agentKnowledgeError: string | null = null;
55
- private agentKnowledgeLoading = false;
56
- // I1: confirm for destructive review-state mutations
57
- private confirm: ConfirmState<{ id: string; action: 'stale' | 'contradicted' }> | null = null;
58
-
59
- public constructor(registry: MemoryRegistry, agentKnowledgeService: AgentKnowledgePanelService | null = null) {
60
- super('knowledge', 'Knowledge', 'K', 'agent');
61
- this.registry = registry;
62
- this.agentKnowledgeService = agentKnowledgeService;
63
- }
64
-
65
- public override onActivate(): void {
66
- super.onActivate();
67
- this.refresh();
68
- this.refreshAgentKnowledgeStatus();
69
- this.unsubscribe = this.registry.subscribe(() => {
70
- this.refresh();
71
- this.markDirty();
72
- });
73
- }
74
-
75
- public override onDeactivate(): void {
76
- super.onDeactivate();
77
- }
78
-
79
- public override onDestroy(): void {
80
- this.unsubscribe?.();
81
- this.unsubscribe = undefined;
82
- }
83
-
84
- // ---------------------------------------------------------------------------
85
- // ScrollableListPanel implementation
86
- // ---------------------------------------------------------------------------
87
-
88
- protected getItems(): readonly MemoryRecord[] {
89
- return this.records;
90
- }
91
-
92
- protected renderItem(record: MemoryRecord, index: number, selected: boolean, width: number): Line {
93
- const bg = selected ? C.selectBg : undefined;
94
- return buildPanelLine(width, [
95
- [' ', C.label, bg],
96
- [record.reviewState.padEnd(13), reviewStateColor(record.reviewState), bg],
97
- [` ${formatConfidence(record.confidence)} `, C.value, bg],
98
- [record.summary.slice(0, Math.max(0, width - 26)), C.value, bg],
99
- ]);
100
- }
101
-
102
- protected override getPalette() { return C; }
103
- protected override getEmptyStateMessage() { return 'No Agent Knowledge sources or local memory review records'; }
104
- protected override getEmptyStateActions() {
105
- return [
106
- { command: '/knowledge status', summary: 'inspect the isolated Agent Knowledge store' },
107
- { command: '/knowledge ingest-url <url> --yes', summary: 'ingest source-backed material into Agent Knowledge only' },
108
- { command: '/knowledge queue', summary: 'review Agent Knowledge issues' },
109
- { command: '/memory add fact <summary>', summary: 'capture a local non-secret memory record when appropriate' },
110
- ];
111
- }
112
-
113
- // ---------------------------------------------------------------------------
114
- // Input
115
- // ---------------------------------------------------------------------------
116
-
117
- public handleInput(key: string): boolean {
118
- // I1: y/n confirm for stale/contradict
119
- if (this.confirm) {
120
- const result = handleConfirmInput(this.confirm, key);
121
- if (result === 'confirmed') {
122
- const { id, action } = this.confirm.subject;
123
- this.confirm = null;
124
- const selected = this.records.find((r) => r.id === id);
125
- if (selected) {
126
- try {
127
- if (action === 'stale') {
128
- this.registry.review(id, {
129
- state: 'stale',
130
- confidence: Math.min(selected.confidence, 40),
131
- reviewedBy: 'operator',
132
- staleReason: 'marked stale from the knowledge panel',
133
- });
134
- } else {
135
- this.registry.review(id, {
136
- state: 'contradicted',
137
- confidence: 0,
138
- reviewedBy: 'operator',
139
- staleReason: 'marked contradicted from the knowledge panel',
140
- });
141
- }
142
- } catch (e) {
143
- // I2: surface async failure
144
- this.setError(`Review update failed: ${e instanceof Error ? e.message : String(e)}`);
145
- }
146
- }
147
- this.refresh();
148
- this.markDirty();
149
- return true;
150
- }
151
- if (result === 'cancelled') {
152
- this.confirm = null;
153
- this.markDirty();
154
- return true;
155
- }
156
- if (result === 'absorbed') return true;
157
- }
158
-
159
- // I2: auto-clear error on next keypress (inherited via super.handleInput)
160
- if (this.records.length === 0) return super.handleInput(key);
161
-
162
- const selected = this.records[this.selectedIndex];
163
-
164
- if (key === 'Enter' || key === 'return' || key === 'r') {
165
- if (!selected) return false;
166
- this.registry.review(selected.id, {
167
- state: 'reviewed',
168
- confidence: Math.max(selected.confidence, 85),
169
- reviewedBy: 'operator',
170
- });
171
- this.refresh();
172
- this.markDirty();
173
- return true;
174
- }
175
- if (key === 's') {
176
- if (!selected) return false;
177
- // I1: prompt confirm before marking stale
178
- this.confirm = { subject: { id: selected.id, action: 'stale' }, label: selected.summary.slice(0, 40) };
179
- this.markDirty();
180
- return true;
181
- }
182
- if (key === 'c') {
183
- if (!selected) return false;
184
- // I1: prompt confirm before marking contradicted
185
- this.confirm = { subject: { id: selected.id, action: 'contradicted' }, label: selected.summary.slice(0, 40) };
186
- this.markDirty();
187
- return true;
188
- }
189
- if (key === 'f') {
190
- if (!selected) return false;
191
- this.registry.review(selected.id, {
192
- state: 'fresh',
193
- confidence: Math.max(selected.confidence, 60),
194
- reviewedBy: 'operator',
195
- });
196
- this.refresh();
197
- this.markDirty();
198
- return true;
199
- }
200
-
201
- // Normalize arrow keys to base class format
202
- if (key === 'ArrowUp') return super.handleInput('up');
203
- if (key === 'ArrowDown') return super.handleInput('down');
204
- return super.handleInput(key);
205
- }
206
-
207
- private refresh(): void {
208
- const queue = this.registry.reviewQueue(24);
209
- this.records = queue.length > 0 ? queue : this.registry.search({ limit: 24 });
210
- this.clampSelection();
211
- }
212
-
213
- private refreshAgentKnowledgeStatus(): void {
214
- if (!this.agentKnowledgeService || this.agentKnowledgeLoading) return;
215
- this.agentKnowledgeLoading = true;
216
- this.agentKnowledgeError = null;
217
- this.agentKnowledgeService.getStatus()
218
- .then((status) => {
219
- this.agentKnowledgeStatus = status;
220
- this.agentKnowledgeError = null;
221
- })
222
- .catch((error: unknown) => {
223
- this.agentKnowledgeError = error instanceof Error ? error.message : String(error);
224
- })
225
- .finally(() => {
226
- this.agentKnowledgeLoading = false;
227
- this.markDirty();
228
- });
229
- }
230
-
231
- private buildAgentKnowledgeHeader(width: number): Line[] {
232
- const lines: Line[] = [
233
- buildPanelLine(width, [[' Agent Knowledge Segment', C.label]]),
234
- buildPanelLine(width, [
235
- [' route ', C.label],
236
- ['/api/goodvibes-agent/knowledge/*', C.info],
237
- [' isolated: no default knowledge or non-Agent fallback', C.dim],
238
- ]),
239
- ];
240
- if (this.agentKnowledgeLoading && !this.agentKnowledgeStatus) {
241
- lines.push(buildPanelLine(width, [[' loading isolated Agent Knowledge status...', C.dim]]));
242
- } else if (this.agentKnowledgeStatus) {
243
- const status = this.agentKnowledgeStatus;
244
- lines.push(buildKeyValueLine(width, [
245
- { label: 'Ready', value: status.ready ? 'yes' : 'no', valueColor: status.ready ? C.good : C.warn },
246
- { label: 'Sources', value: String(status.sourceCount), valueColor: status.sourceCount > 0 ? C.info : C.dim },
247
- { label: 'Nodes', value: String(status.nodeCount), valueColor: status.nodeCount > 0 ? C.info : C.dim },
248
- { label: 'Issues', value: String(status.issueCount), valueColor: status.issueCount > 0 ? C.warn : C.good },
249
- ], C));
250
- const note = status.note ? ` note: ${status.note}` : '';
251
- lines.push(buildPanelLine(width, [[' storage: ', C.label], [status.storagePath, C.dim], [note, C.dim]]));
252
- } else {
253
- lines.push(buildPanelLine(width, [[' status: not loaded; /knowledge status uses the same isolated route.', C.dim]]));
254
- }
255
- if (this.agentKnowledgeError) {
256
- lines.push(...buildBodyText(width, `Agent Knowledge status warning: ${this.agentKnowledgeError}`, C, C.warn));
257
- }
258
- lines.push(buildPanelLine(width, [
259
- [' actions ', C.label],
260
- ['/knowledge status', C.value],
261
- [' | ', C.dim],
262
- ['/knowledge ingest-url <url> --yes', C.value],
263
- [' | ', C.dim],
264
- ['/knowledge search <query>', C.value],
265
- [' | ', C.dim],
266
- ['/knowledge queue', C.value],
267
- ]));
268
- return lines;
269
- }
270
-
271
- public render(width: number, height: number): Line[] {
272
- this.clampSelection();
273
-
274
- // I1: show confirm dialog in place of normal content
275
- if (this.confirm) {
276
- return buildPanelWorkspace(width, height, {
277
- title: 'Knowledge Control Room',
278
- intro: '',
279
- sections: [{ title: 'Confirmation', lines: renderConfirmLines(width, this.confirm) }],
280
- footerLines: [buildPanelLine(width, [[' y confirm n / Esc cancel', C.dim]])],
281
- palette: C,
282
- });
283
- }
284
-
285
- if (this.records.length === 0) this.refresh();
286
-
287
- const intro = 'Isolated Agent Knowledge plus local non-secret memory review. This surface never falls back to default knowledge or non-Agent knowledge segments.';
288
- const records = this.registry.search({ limit: 200 });
289
- const agentKnowledgeHeader = this.buildAgentKnowledgeHeader(width);
290
-
291
- if (records.length === 0) {
292
- return this.renderList(width, height, {
293
- title: 'Knowledge Control Room',
294
- header: agentKnowledgeHeader,
295
- footer: [buildPanelLine(width, [[' Review keys: Up/Down move r/Enter review s stale c contradicted f fresh', C.dim]])],
296
- });
297
- }
298
-
299
- const queue = this.registry.reviewQueue(24);
300
- const byClass = new Map<MemoryClass, number>();
301
- const byReview = new Map<MemoryReviewState, number>();
302
- const byScope = new Map<string, number>();
303
- for (const record of records) {
304
- byClass.set(record.cls, (byClass.get(record.cls) ?? 0) + 1);
305
- byReview.set(record.reviewState, (byReview.get(record.reviewState) ?? 0) + 1);
306
- byScope.set(record.scope, (byScope.get(record.scope) ?? 0) + 1);
307
- }
308
-
309
- const classLines: Line[] = [
310
- buildPanelLine(width, [
311
- [' facts ', C.label], [String(byClass.get('fact') ?? 0), C.good],
312
- [' risks ', C.label], [String(byClass.get('risk') ?? 0), (byClass.get('risk') ?? 0) > 0 ? C.warn : C.good],
313
- [' runbooks ', C.label], [String(byClass.get('runbook') ?? 0), C.info],
314
- [' architecture ', C.label], [String(byClass.get('architecture') ?? 0), C.info],
315
- [' incidents ', C.label], [String(byClass.get('incident') ?? 0), (byClass.get('incident') ?? 0) > 0 ? C.bad : C.good],
316
- ]),
317
- buildPanelLine(width, [
318
- [' decisions ', C.label], [String(byClass.get('decision') ?? 0), C.value],
319
- [' constraints ', C.label], [String(byClass.get('constraint') ?? 0), C.value],
320
- [' ownership ', C.label], [String(byClass.get('ownership') ?? 0), C.value],
321
- [' patterns ', C.label], [String(byClass.get('pattern') ?? 0), C.value],
322
- [' total ', C.label], [String(records.length), C.value],
323
- ]),
324
- ];
325
-
326
- const reviewLines: Line[] = [
327
- buildPanelLine(width, [
328
- [' reviewed ', C.label], [String(byReview.get('reviewed') ?? 0), C.good],
329
- [' fresh ', C.label], [String(byReview.get('fresh') ?? 0), C.info],
330
- [' stale ', C.label], [String(byReview.get('stale') ?? 0), C.warn],
331
- [' contradicted ', C.label], [String(byReview.get('contradicted') ?? 0), C.bad],
332
- [' Review Queue ', C.label], [String(queue.length), queue.length > 0 ? C.warn : C.good],
333
- ]),
334
- buildPanelLine(width, [
335
- [' session ', C.label], [String(byScope.get('session') ?? 0), C.info],
336
- [' project ', C.label], [String(byScope.get('project') ?? 0), C.value],
337
- [' team ', C.label], [String(byScope.get('team') ?? 0), C.good],
338
- ]),
339
- buildGuidanceLine(width, '/memory review', 'work the stale and contradicted queue from the command workspace', C),
340
- ];
341
-
342
- const recentSummaryLines: Line[] = [];
343
- for (const [title, items, color] of [
344
- ['Recent Risks', summarize(records, 'risk'), C.warn],
345
- ['Runbooks', summarize(records, 'runbook'), C.info],
346
- ['Architecture Notes', summarize(records, 'architecture'), C.info],
347
- ['Recent Incidents', summarize(records, 'incident'), C.bad],
348
- ] as const) {
349
- if (recentSummaryLines.length > 0) {
350
- recentSummaryLines.push(buildPanelLine(width, [['', C.dim]]));
351
- }
352
- recentSummaryLines.push(buildPanelLine(width, [[` ${title}`, C.label]]));
353
- if (items.length === 0) {
354
- recentSummaryLines.push(buildPanelLine(width, [[' none recorded', C.dim]]));
355
- continue;
356
- }
357
- for (const item of items) {
358
- recentSummaryLines.push(buildPanelLine(width, [
359
- [' ', C.label],
360
- [item.summary.slice(0, Math.max(0, width - 2)), color],
361
- ]));
362
- }
363
- }
364
-
365
- const selectedRecord = this.records[this.selectedIndex];
366
- const selectedLines: Line[] = [];
367
- if (selectedRecord) {
368
- selectedLines.push(buildPanelLine(width, [[' Selected', C.label]]));
369
- selectedLines.push(buildKeyValueLine(width, [
370
- { label: 'Class', value: selectedRecord.cls, valueColor: C.value },
371
- { label: 'Scope', value: selectedRecord.scope, valueColor: C.info },
372
- { label: 'Review', value: selectedRecord.reviewState, valueColor: reviewStateColor(selectedRecord.reviewState) },
373
- { label: 'Confidence', value: formatConfidence(selectedRecord.confidence), valueColor: C.value },
374
- ], C));
375
- selectedLines.push(...buildBodyText(width, `Summary: ${selectedRecord.summary}`, C, C.value));
376
- if (selectedRecord.detail) selectedLines.push(...buildBodyText(width, `Detail: ${selectedRecord.detail}`, C, C.dim));
377
- if (selectedRecord.provenance.length) {
378
- selectedLines.push(...buildBodyText(
379
- width,
380
- `Origin: ${formatAgentRecordReferences(selectedRecord.provenance)}`,
381
- C,
382
- C.dim,
383
- ));
384
- }
385
- if (selectedRecord.staleReason) {
386
- selectedLines.push(...buildBodyText(
387
- width,
388
- `Stale reason: ${selectedRecord.staleReason}`,
389
- C,
390
- selectedRecord.reviewState === 'contradicted' ? C.bad : C.warn,
391
- ));
392
- }
393
- if (selectedRecord.reviewedAt) {
394
- selectedLines.push(buildPanelLine(width, [
395
- [' Reviewed: ', C.label],
396
- [new Date(selectedRecord.reviewedAt).toLocaleString(), C.dim],
397
- ]));
398
- if (selectedRecord.reviewedBy) {
399
- selectedLines.push(buildPanelLine(width, [
400
- [' Reviewer: ', C.label],
401
- [selectedRecord.reviewedBy, C.dim],
402
- ]));
403
- }
404
- }
405
- }
406
-
407
- return this.renderList(width, height, {
408
- title: 'Knowledge Control Room',
409
- header: [...agentKnowledgeHeader, ...classLines, ...reviewLines],
410
- footer: [
411
- buildPanelLine(width, [[' Up/Down move r/Enter reviewed s stale c contradicted f fresh', C.dim]]),
412
- ...(selectedLines.length > 0 ? selectedLines : []),
413
- ...recentSummaryLines,
414
- ],
415
- });
416
- }
417
- }
@@ -1,226 +0,0 @@
1
- /**
2
- * MemoryPanel — project memory substrate TUI panel.
3
- *
4
- * Migrated to SearchableListPanel<MemoryRecord> (Wave B1).
5
- */
6
-
7
- import type { Line } from '../types/grid.ts';
8
- import type { MemoryRegistry } from '@pellux/goodvibes-sdk/platform/state';
9
- import type { MemoryRecord, MemoryClass } from '@pellux/goodvibes-sdk/platform/state';
10
- import { formatAgentRecordReferences } from '../agent/record-labels.ts';
11
- import { SearchableListPanel } from './scrollable-list-panel.ts';
12
- import {
13
- buildBodyText,
14
- buildGuidanceLine,
15
- buildKeyValueLine,
16
- buildPanelLine,
17
- extendPalette,
18
- DEFAULT_PANEL_PALETTE,
19
- } from './polish.ts';
20
- import {
21
- getPanelSearchFocusTransition,
22
- isPanelSearchCancel,
23
- } from './search-focus.ts';
24
-
25
- const C = extendPalette(DEFAULT_PANEL_PALETTE, {
26
- header: '#94a3b8',
27
- headerBg: '#1e293b',
28
- decision: '#38bdf8',
29
- constraint: '#f97316',
30
- incident: '#ef4444',
31
- pattern: '#a78bfa',
32
- fact: '#22c55e',
33
- risk: '#f43f5e',
34
- runbook: '#eab308',
35
- architecture: '#60a5fa',
36
- ownership: '#14b8a6',
37
- selected: '#1e3a5f',
38
- searchBg: '#0f172a',
39
- searchFg: '#e2e8f0',
40
- });
41
-
42
- function fmtTime(ts: number): string {
43
- const d = new Date(ts);
44
- return d.toISOString().slice(0, 16).replace('T', ' ');
45
- }
46
-
47
- function classColor(cls: MemoryClass): string {
48
- switch (cls) {
49
- case 'decision': return C.decision;
50
- case 'constraint': return C.constraint;
51
- case 'incident': return C.incident;
52
- case 'pattern': return C.pattern;
53
- case 'fact': return C.fact;
54
- case 'risk': return C.risk;
55
- case 'runbook': return C.runbook;
56
- case 'architecture': return C.architecture;
57
- case 'ownership': return C.ownership;
58
- }
59
- }
60
-
61
- export class MemoryPanel extends SearchableListPanel<MemoryRecord> {
62
- private registry: MemoryRegistry;
63
- private filterFocused = false;
64
- private unsubscribe?: () => void;
65
-
66
- constructor(registry: MemoryRegistry) {
67
- super('memory', 'Memory', 'M', 'agent');
68
- this.registry = registry;
69
- }
70
-
71
- onActivate(): void {
72
- super.onActivate();
73
- this.searchQuery = '';
74
- this.invalidateFilter();
75
- this.filterFocused = false;
76
- this.unsubscribe = this.registry.subscribe(() => {
77
- this.invalidateFilter();
78
- this.markDirty();
79
- });
80
- }
81
-
82
- onDeactivate(): void {
83
- super.onDeactivate();
84
- }
85
-
86
- onDestroy(): void {
87
- this.unsubscribe?.();
88
- this.unsubscribe = undefined;
89
- }
90
-
91
- // ---------------------------------------------------------------------------
92
- // SearchableListPanel implementation
93
- // ---------------------------------------------------------------------------
94
-
95
- protected getAllItems(): readonly MemoryRecord[] {
96
- return this.registry.search({ limit: 100 });
97
- }
98
-
99
- protected matchesSearch(record: MemoryRecord, query: string): boolean {
100
- const q = query.trim().toLowerCase();
101
- if (!q) return true;
102
- const haystack = [
103
- record.summary,
104
- record.detail ?? '',
105
- record.cls,
106
- record.scope,
107
- record.tags.join(' '),
108
- ].join(' ').toLowerCase();
109
- return haystack.includes(q);
110
- }
111
-
112
- protected renderItem(record: MemoryRecord, index: number, selected: boolean, width: number): Line {
113
- const bg = selected ? C.selected : undefined;
114
- return buildPanelLine(width, [
115
- [' ', C.label, bg],
116
- [`[${record.scope.slice(0, 1).toUpperCase()}/${record.cls.slice(0, 3).toUpperCase()}] `, classColor(record.cls), bg],
117
- [record.id.slice(-8), C.dim, bg],
118
- [' ', C.label, bg],
119
- [fmtTime(record.createdAt), C.dim, bg],
120
- [' ', C.label, bg],
121
- [record.summary.slice(0, Math.max(0, width - 33)), C.value, bg],
122
- ]);
123
- }
124
-
125
- protected override getPalette() { return C; }
126
- protected override getEmptyStateMessage() {
127
- return this.searchQuery
128
- ? ` No records matching "${this.searchQuery}"`
129
- : ' No memory records. Use /memory add <class> <summary> to create one.';
130
- }
131
- protected override getEmptyStateActions() {
132
- return [
133
- { command: '/memory add fact <summary>', summary: 'capture a durable fact directly' },
134
- { command: '/memory capture incident latest', summary: 'promote the latest incident into memory' },
135
- ];
136
- }
137
-
138
- handleInput(key: string): boolean {
139
- // Filter-focus mode: typing goes into the search query
140
- if (this.filterFocused) {
141
- const items = this.getItems();
142
- const transition = getPanelSearchFocusTransition(key, { selectedIndex: this.selectedIndex, itemCount: items.length });
143
- if (transition === 'focus-list') {
144
- this.filterFocused = false;
145
- this.markDirty();
146
- return true;
147
- }
148
- if (isPanelSearchCancel(key)) {
149
- this.filterFocused = false;
150
- return super.handleInput(key);
151
- }
152
- return super.handleInput(key);
153
- }
154
-
155
- const items = this.getItems();
156
- const transition = getPanelSearchFocusTransition(key, { selectedIndex: this.selectedIndex, itemCount: items.length });
157
- if (transition === 'focus-search') {
158
- this.filterFocused = true;
159
- this.markDirty();
160
- return true;
161
- }
162
-
163
- if (key === 'r') {
164
- this.invalidateFilter();
165
- this.markDirty();
166
- return true;
167
- }
168
-
169
- return super.handleInput(key);
170
- }
171
-
172
- render(width: number, height: number): Line[] {
173
- this.clampSelection();
174
- const intro = 'Durable project memory across decisions, constraints, incidents, patterns, risks, runbooks, and related origin links.';
175
-
176
- const records = this.getItems();
177
- const byClass = new Map<MemoryClass, number>();
178
- for (const record of records) {
179
- byClass.set(record.cls, (byClass.get(record.cls) ?? 0) + 1);
180
- }
181
-
182
- const filterLine = this.buildFilterInputLine(width, 'Filter', this.filterFocused);
183
-
184
- const summaryLines: Line[] = [
185
- buildKeyValueLine(width, [
186
- { label: 'records', value: String(records.length), valueColor: C.value },
187
- { label: 'facts', value: String(byClass.get('fact') ?? 0), valueColor: C.fact },
188
- { label: 'decisions', value: String(byClass.get('decision') ?? 0), valueColor: C.decision },
189
- { label: 'incidents', value: String(byClass.get('incident') ?? 0), valueColor: C.incident },
190
- { label: 'runbooks', value: String(byClass.get('runbook') ?? 0), valueColor: C.runbook },
191
- ], C),
192
- filterLine,
193
- buildGuidanceLine(width, '/memory review', 'review durable memory and queue posture from the command workspace', C),
194
- ];
195
-
196
- const selected = records[this.selectedIndex];
197
- const selectedLines: Line[] = [];
198
- if (selected) {
199
- selectedLines.push(buildKeyValueLine(width, [
200
- { label: 'scope', value: selected.scope, valueColor: C.info },
201
- { label: 'class', value: selected.cls, valueColor: classColor(selected.cls) },
202
- { label: 'created', value: fmtTime(selected.createdAt), valueColor: C.dim },
203
- ], C));
204
- selectedLines.push(...buildBodyText(width, selected.summary, C, C.value));
205
- if (selected.detail) selectedLines.push(...buildBodyText(width, `Detail: ${selected.detail}`, C, C.dim));
206
- if (selected.tags.length) selectedLines.push(buildPanelLine(width, [[` Tags: ${selected.tags.join(', ')}`, C.good]]));
207
- if (selected.provenance.length) {
208
- selectedLines.push(...buildBodyText(
209
- width,
210
- `Origin: ${formatAgentRecordReferences(selected.provenance)}`,
211
- C,
212
- C.dim,
213
- ));
214
- }
215
- }
216
-
217
- return this.renderList(width, height, {
218
- title: 'Memory',
219
- header: summaryLines,
220
- footer: [
221
- ...selectedLines,
222
- buildPanelLine(width, [[' / search j/k or Up/Down move r reload Esc clear search', C.dim]]),
223
- ],
224
- });
225
- }
226
- }