@pellux/goodvibes-tui 1.1.0 → 1.9.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 (267) hide show
  1. package/CHANGELOG.md +85 -21
  2. package/README.md +28 -18
  3. package/docs/foundation-artifacts/operator-contract.json +4045 -1763
  4. package/package.json +2 -2
  5. package/src/audio/spoken-turn-controller.ts +12 -2
  6. package/src/audio/spoken-turn-wiring.ts +2 -1
  7. package/src/cli/help.ts +8 -1
  8. package/src/cli/service-posture.ts +2 -1
  9. package/src/cli/status.ts +2 -1
  10. package/src/cli/surface-command.ts +2 -2
  11. package/src/core/alert-gating.ts +67 -0
  12. package/src/core/approval-alert.ts +72 -0
  13. package/src/core/budget-breach-notifier.ts +106 -0
  14. package/src/core/composer-state.ts +11 -3
  15. package/src/core/conversation-line-cache.ts +27 -3
  16. package/src/core/conversation-rendering.ts +90 -14
  17. package/src/core/conversation.ts +18 -0
  18. package/src/core/focus-tracker.ts +41 -0
  19. package/src/core/long-task-notifier.ts +33 -6
  20. package/src/core/stream-event-wiring.ts +20 -1
  21. package/src/core/system-message-noise.ts +87 -0
  22. package/src/core/system-message-router.ts +104 -51
  23. package/src/core/turn-cancellation.ts +25 -0
  24. package/src/core/turn-event-wiring.ts +72 -3
  25. package/src/daemon/cli.ts +29 -2
  26. package/src/daemon/handlers/register.ts +8 -1
  27. package/src/daemon/service-commands.ts +329 -0
  28. package/src/export/cost-utils.ts +36 -0
  29. package/src/input/autocomplete.ts +27 -1
  30. package/src/input/command-registry.ts +84 -5
  31. package/src/input/commands/checkpoint-runtime.ts +280 -0
  32. package/src/input/commands/codebase-runtime.ts +232 -0
  33. package/src/input/commands/config.ts +43 -3
  34. package/src/input/commands/eval.ts +1 -1
  35. package/src/input/commands/health-runtime.ts +10 -2
  36. package/src/input/commands/image-runtime.ts +112 -0
  37. package/src/input/commands/intelligence-runtime.ts +11 -1
  38. package/src/input/commands/local-auth-runtime.ts +4 -1
  39. package/src/input/commands/local-runtime.ts +9 -3
  40. package/src/input/commands/marketplace-runtime.ts +2 -2
  41. package/src/input/commands/memory.ts +73 -35
  42. package/src/input/commands/operator-panel-runtime.ts +71 -31
  43. package/src/input/commands/planning-runtime.ts +116 -4
  44. package/src/input/commands/platform-sandbox-runtime.ts +1 -6
  45. package/src/input/commands/plugin-runtime.ts +2 -2
  46. package/src/input/commands/policy-dispatch.ts +21 -0
  47. package/src/input/commands/provider-accounts-runtime.ts +1 -1
  48. package/src/input/commands/qrcode-runtime.ts +26 -6
  49. package/src/input/commands/recall-review.ts +35 -0
  50. package/src/input/commands/remote-runtime-setup.ts +5 -3
  51. package/src/input/commands/remote-runtime.ts +3 -3
  52. package/src/input/commands/runtime-services.ts +54 -13
  53. package/src/input/commands/services-runtime.ts +1 -1
  54. package/src/input/commands/session-content.ts +20 -9
  55. package/src/input/commands/session-workflow.ts +16 -1
  56. package/src/input/commands/settings-sync-runtime.ts +16 -4
  57. package/src/input/commands/shell-core.ts +25 -8
  58. package/src/input/commands/skills-runtime.ts +2 -8
  59. package/src/input/commands/subscription-runtime.ts +2 -2
  60. package/src/input/commands/test-runtime.ts +277 -0
  61. package/src/input/commands/websearch-runtime.ts +101 -0
  62. package/src/input/commands/work-plan-runtime.ts +36 -10
  63. package/src/input/commands/workstream-runtime.ts +488 -0
  64. package/src/input/commands.ts +12 -0
  65. package/src/input/config-modal-types.ts +175 -0
  66. package/src/input/config-modal.ts +592 -0
  67. package/src/input/feed-context-factory.ts +10 -8
  68. package/src/input/handler-command-route.ts +37 -20
  69. package/src/input/handler-content-actions.ts +17 -2
  70. package/src/input/handler-feed-routes.ts +74 -114
  71. package/src/input/handler-feed.ts +79 -27
  72. package/src/input/handler-interactions.ts +1 -3
  73. package/src/input/handler-modal-routes.ts +136 -6
  74. package/src/input/handler-modal-stack.ts +14 -7
  75. package/src/input/handler-modal-token-routes.ts +16 -49
  76. package/src/input/handler-onboarding-daemon-adopt.ts +149 -0
  77. package/src/input/handler-onboarding.ts +71 -59
  78. package/src/input/handler-picker-routes.ts +26 -102
  79. package/src/input/handler-shortcuts.ts +74 -14
  80. package/src/input/handler-types.ts +2 -6
  81. package/src/input/handler-ui-state.ts +10 -22
  82. package/src/input/handler.ts +12 -29
  83. package/src/input/keybindings.ts +23 -8
  84. package/src/input/model-picker-types.ts +24 -6
  85. package/src/input/model-picker.ts +77 -2
  86. package/src/input/onboarding/onboarding-runtime-status.ts +10 -1
  87. package/src/input/onboarding/onboarding-wizard-apply.ts +8 -1
  88. package/src/input/onboarding/onboarding-wizard-constants.ts +4 -1
  89. package/src/input/onboarding/onboarding-wizard-network-adopt.ts +136 -0
  90. package/src/input/onboarding/onboarding-wizard-steps.ts +9 -6
  91. package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
  92. package/src/input/panel-integration-actions.ts +9 -170
  93. package/src/input/panel-mouse-geometry.ts +97 -0
  94. package/src/input/panel-paste-flood-guard.ts +86 -0
  95. package/src/input/selection-modal.ts +11 -0
  96. package/src/input/session-picker-modal.ts +44 -4
  97. package/src/input/settings-modal-data.ts +146 -2
  98. package/src/input/settings-modal-mutations.ts +6 -4
  99. package/src/input/settings-modal-types.ts +4 -2
  100. package/src/main.ts +49 -51
  101. package/src/panels/agent-inspector-shared.ts +2 -1
  102. package/src/panels/base-panel.ts +22 -1
  103. package/src/panels/builtin/agent.ts +21 -107
  104. package/src/panels/builtin/development.ts +15 -61
  105. package/src/panels/builtin/knowledge.ts +8 -32
  106. package/src/panels/builtin/operations.ts +84 -428
  107. package/src/panels/builtin/session.ts +21 -112
  108. package/src/panels/builtin/shared.ts +18 -46
  109. package/src/panels/builtin-modals.ts +218 -0
  110. package/src/panels/builtin-panels.ts +5 -0
  111. package/src/panels/cost-tracker-panel.ts +36 -10
  112. package/src/panels/diff-panel.ts +12 -43
  113. package/src/panels/eval-registry.ts +60 -0
  114. package/src/panels/fleet-deep-link.ts +31 -0
  115. package/src/panels/fleet-panel-format.ts +62 -0
  116. package/src/panels/fleet-panel-worktree-detail.ts +48 -0
  117. package/src/panels/fleet-panel.ts +758 -0
  118. package/src/panels/fleet-read-model.ts +520 -0
  119. package/src/panels/fleet-steer.ts +125 -0
  120. package/src/panels/fleet-stop.ts +153 -0
  121. package/src/panels/fleet-tabs.ts +230 -0
  122. package/src/panels/fleet-transcript.ts +356 -0
  123. package/src/panels/index.ts +7 -31
  124. package/src/panels/modals/hooks-modal.ts +187 -0
  125. package/src/panels/modals/keybindings-modal.ts +166 -0
  126. package/src/panels/modals/knowledge-modal.ts +158 -0
  127. package/src/panels/modals/local-auth-modal.ts +132 -0
  128. package/src/panels/modals/marketplace-modal.ts +218 -0
  129. package/src/panels/modals/memory-modal.ts +180 -0
  130. package/src/panels/modals/modal-surface-helpers.ts +54 -0
  131. package/src/panels/modals/modal-theme.ts +42 -0
  132. package/src/panels/modals/pairing-modal.ts +163 -0
  133. package/src/panels/modals/planning-modal.ts +304 -0
  134. package/src/panels/modals/plugins-modal.ts +174 -0
  135. package/src/panels/modals/policy-modal.ts +256 -0
  136. package/src/panels/modals/provider-health-modal.ts +136 -0
  137. package/src/panels/modals/remote-modal.ts +115 -0
  138. package/src/panels/modals/sandbox-modal.ts +150 -0
  139. package/src/panels/modals/security-modal.ts +217 -0
  140. package/src/panels/modals/services-modal.ts +179 -0
  141. package/src/panels/modals/settings-sync-modal.ts +142 -0
  142. package/src/panels/modals/skills-modal.ts +189 -0
  143. package/src/panels/modals/subscription-modal.ts +172 -0
  144. package/src/panels/modals/work-plan-modal.ts +177 -0
  145. package/src/panels/panel-confirm-overlay.ts +72 -0
  146. package/src/panels/panel-manager.ts +123 -9
  147. package/src/panels/polish-core.ts +38 -25
  148. package/src/panels/project-planning-answer-actions.ts +8 -11
  149. package/src/panels/skills-panel.ts +7 -51
  150. package/src/panels/types.ts +37 -0
  151. package/src/permissions/hunk-selection.ts +179 -0
  152. package/src/permissions/prompt.ts +216 -13
  153. package/src/renderer/autocomplete-overlay.ts +28 -2
  154. package/src/renderer/compaction-history-modal.ts +19 -3
  155. package/src/renderer/compaction-preview.ts +13 -2
  156. package/src/renderer/compaction-quality.ts +100 -0
  157. package/src/renderer/compositor.ts +2 -3
  158. package/src/renderer/config-modal.ts +95 -0
  159. package/src/renderer/conversation-overlays.ts +10 -17
  160. package/src/renderer/fleet-tab-strip.ts +56 -0
  161. package/src/renderer/footer-tips.ts +15 -3
  162. package/src/renderer/fullscreen-primitives.ts +32 -22
  163. package/src/renderer/git-status.ts +43 -1
  164. package/src/renderer/help-overlay.ts +2 -2
  165. package/src/renderer/layout.ts +0 -4
  166. package/src/renderer/markdown.ts +7 -3
  167. package/src/renderer/modal-factory.ts +25 -20
  168. package/src/renderer/model-workspace.ts +62 -4
  169. package/src/renderer/overlay-box.ts +21 -17
  170. package/src/renderer/panel-workspace-bar.ts +8 -2
  171. package/src/renderer/process-indicator.ts +14 -3
  172. package/src/renderer/selection-modal-overlay.ts +6 -1
  173. package/src/renderer/session-picker-modal.ts +196 -3
  174. package/src/renderer/settings-modal-helpers.ts +2 -0
  175. package/src/renderer/settings-modal.ts +7 -0
  176. package/src/renderer/shell-surface.ts +8 -1
  177. package/src/renderer/status-glyphs.ts +14 -15
  178. package/src/renderer/system-message.ts +15 -3
  179. package/src/renderer/terminal-bg-probe.ts +339 -0
  180. package/src/renderer/terminal-escapes.ts +20 -0
  181. package/src/renderer/theme-mode-config.ts +67 -0
  182. package/src/renderer/theme.ts +91 -1
  183. package/src/renderer/thinking.ts +11 -3
  184. package/src/renderer/tool-call.ts +15 -9
  185. package/src/renderer/tool-result-summary.ts +148 -0
  186. package/src/renderer/turn-injection.ts +133 -0
  187. package/src/renderer/ui-factory.ts +154 -85
  188. package/src/renderer/ui-primitives.ts +30 -129
  189. package/src/runtime/bootstrap-command-context.ts +27 -1
  190. package/src/runtime/bootstrap-command-parts.ts +41 -10
  191. package/src/runtime/bootstrap-core.ts +47 -15
  192. package/src/runtime/bootstrap-hook-bridge.ts +7 -0
  193. package/src/runtime/bootstrap-shell.ts +48 -17
  194. package/src/runtime/bootstrap.ts +129 -22
  195. package/src/runtime/code-index-services.ts +135 -0
  196. package/src/runtime/legacy-daemon-migration.ts +516 -0
  197. package/src/runtime/memory-fold.ts +26 -0
  198. package/src/runtime/onboarding/derivation.ts +7 -2
  199. package/src/runtime/onboarding/snapshot.ts +27 -1
  200. package/src/runtime/onboarding/types.ts +31 -1
  201. package/src/runtime/operator-token-cleanup.ts +82 -1
  202. package/src/runtime/orchestrator-core-services.ts +59 -0
  203. package/src/runtime/process-lifecycle.ts +3 -1
  204. package/src/runtime/resume-notice.ts +209 -0
  205. package/src/runtime/services.ts +98 -46
  206. package/src/runtime/session-inbound-inputs.ts +252 -0
  207. package/src/runtime/session-spine-transport.ts +64 -0
  208. package/src/runtime/terminal-output-guard.ts +15 -8
  209. package/src/runtime/ui-services.ts +27 -3
  210. package/src/runtime/workstream-services.ts +327 -0
  211. package/src/runtime/wrfc-persistence.ts +124 -17
  212. package/src/shell/blocking-input.ts +68 -4
  213. package/src/shell/recovery-input-helpers.ts +170 -1
  214. package/src/shell/ui-openers.ts +171 -26
  215. package/src/utils/format-duration.ts +8 -18
  216. package/src/utils/splash-lines.ts +1 -1
  217. package/src/utils/terminal-width.ts +52 -0
  218. package/src/version.ts +1 -1
  219. package/src/panels/agent-inspector-panel.ts +0 -786
  220. package/src/panels/approval-panel.ts +0 -252
  221. package/src/panels/automation-control-panel.ts +0 -479
  222. package/src/panels/cockpit-panel.ts +0 -481
  223. package/src/panels/cockpit-read-model.ts +0 -238
  224. package/src/panels/communication-panel.ts +0 -256
  225. package/src/panels/control-plane-panel.ts +0 -470
  226. package/src/panels/debug-panel.ts +0 -615
  227. package/src/panels/docs-panel.ts +0 -384
  228. package/src/panels/eval-panel.ts +0 -627
  229. package/src/panels/file-explorer-panel.ts +0 -673
  230. package/src/panels/file-preview-panel.ts +0 -517
  231. package/src/panels/hooks-panel.ts +0 -401
  232. package/src/panels/incident-review-panel.ts +0 -406
  233. package/src/panels/intelligence-panel.ts +0 -383
  234. package/src/panels/knowledge-graph-panel.ts +0 -515
  235. package/src/panels/marketplace-panel.ts +0 -399
  236. package/src/panels/memory-panel.ts +0 -558
  237. package/src/panels/ops-control-panel.ts +0 -329
  238. package/src/panels/ops-strategy-panel.ts +0 -321
  239. package/src/panels/orchestration-panel.ts +0 -465
  240. package/src/panels/panel-list-panel.ts +0 -566
  241. package/src/panels/plan-dashboard-panel.ts +0 -707
  242. package/src/panels/policy-panel.ts +0 -517
  243. package/src/panels/project-planning-panel.ts +0 -731
  244. package/src/panels/provider-health-panel.ts +0 -678
  245. package/src/panels/provider-health-tracker.ts +0 -310
  246. package/src/panels/provider-health-views.ts +0 -567
  247. package/src/panels/qr-panel.ts +0 -280
  248. package/src/panels/remote-panel.ts +0 -534
  249. package/src/panels/routes-panel.ts +0 -241
  250. package/src/panels/sandbox-panel.ts +0 -456
  251. package/src/panels/security-panel.ts +0 -447
  252. package/src/panels/services-panel.ts +0 -329
  253. package/src/panels/session-browser-panel.ts +0 -496
  254. package/src/panels/settings-sync-panel.ts +0 -398
  255. package/src/panels/subscription-panel.ts +0 -342
  256. package/src/panels/symbol-outline-panel.ts +0 -619
  257. package/src/panels/system-messages-panel.ts +0 -364
  258. package/src/panels/tasks-panel.ts +0 -608
  259. package/src/panels/thinking-panel.ts +0 -333
  260. package/src/panels/tool-inspector-panel.ts +0 -537
  261. package/src/panels/work-plan-panel.ts +0 -540
  262. package/src/panels/worktree-panel.ts +0 -360
  263. package/src/panels/wrfc-panel.ts +0 -790
  264. package/src/renderer/agent-detail-modal.ts +0 -466
  265. package/src/renderer/live-tail-modal.ts +0 -156
  266. package/src/renderer/process-modal.ts +0 -671
  267. package/src/renderer/qr-renderer.ts +0 -120
@@ -1,558 +0,0 @@
1
- /**
2
- * MemoryPanel — merged project memory substrate panel.
3
- *
4
- * TASK-040: Merged from the former separate MemoryPanel + KnowledgePanel.
5
- * One panel, two filter modes toggled with Tab:
6
- * - 'all' : full record list with search (former MemoryPanel behaviour)
7
- * - 'review' : review-queue view with r/s/c/f actions (former KnowledgePanel behaviour)
8
- *
9
- * Panel id stays 'memory'; the 'knowledge' id is repointed to the graph view.
10
- * No capability removed from either previous surface.
11
- */
12
-
13
- import type { Line } from '../types/grid.ts';
14
- import type { MemoryRegistry } from '@pellux/goodvibes-sdk/platform/state';
15
- import type { MemoryClass, MemoryRecord, MemoryReviewState } from '@pellux/goodvibes-sdk/platform/state';
16
- import { ScrollableListPanel } from './scrollable-list-panel.ts';
17
- import { truncateDisplay } from '../utils/terminal-width.ts';
18
- import { type ConfirmState, handleConfirmInput, renderConfirmLines } from './confirm-state.ts';
19
- import {
20
- buildBodyText,
21
- buildKeyValueLine,
22
- buildPanelLine,
23
- buildPanelWorkspace,
24
- extendPalette,
25
- DEFAULT_PANEL_PALETTE,
26
- } from './polish.ts';
27
- import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
28
-
29
- // ---------------------------------------------------------------------------
30
- // Colour palette
31
- // ---------------------------------------------------------------------------
32
-
33
- // Domain accents only — one hue per memory class (base chrome: header/
34
- // headerBg/selectBg come from DEFAULT_PANEL_PALETTE).
35
- const C = extendPalette(DEFAULT_PANEL_PALETTE, {
36
- decision: '#38bdf8',
37
- constraint: '#f97316',
38
- incident: '#ef4444',
39
- pattern: '#a78bfa',
40
- fact: '#22c55e',
41
- risk: '#f43f5e',
42
- runbook: '#eab308',
43
- architecture: '#60a5fa',
44
- ownership: '#14b8a6',
45
- });
46
-
47
- // ---------------------------------------------------------------------------
48
- // Filter modes
49
- // ---------------------------------------------------------------------------
50
-
51
- type FilterMode = 'all' | 'review';
52
-
53
- const FILTER_LABELS: Record<FilterMode, string> = {
54
- all: 'All Records',
55
- review: 'Review Queue',
56
- };
57
-
58
- // ---------------------------------------------------------------------------
59
- // Helpers
60
- // ---------------------------------------------------------------------------
61
-
62
- function fmtTime(ts: number): string {
63
- const d = new Date(ts);
64
- return d.toISOString().slice(0, 16).replace('T', ' ');
65
- }
66
-
67
- function classColor(cls: MemoryClass): string {
68
- switch (cls) {
69
- case 'decision': return C.decision;
70
- case 'constraint': return C.constraint;
71
- case 'incident': return C.incident;
72
- case 'pattern': return C.pattern;
73
- case 'fact': return C.fact;
74
- case 'risk': return C.risk;
75
- case 'runbook': return C.runbook;
76
- case 'architecture': return C.architecture;
77
- case 'ownership': return C.ownership;
78
- }
79
- }
80
-
81
- function reviewStateColor(state: MemoryReviewState): string {
82
- switch (state) {
83
- case 'reviewed': return C.good;
84
- case 'stale': return C.warn;
85
- case 'contradicted': return C.bad;
86
- case 'fresh':
87
- default: return C.info;
88
- }
89
- }
90
-
91
- function formatConfidence(confidence: number): string {
92
- return `${confidence.toString().padStart(3, ' ')}%`;
93
- }
94
-
95
- // ---------------------------------------------------------------------------
96
- // MemoryPanel
97
- // ---------------------------------------------------------------------------
98
-
99
- export class MemoryPanel extends ScrollableListPanel<MemoryRecord> {
100
- private readonly registry: MemoryRegistry;
101
- private filterMode: FilterMode = 'all';
102
- private unsubscribe?: () => void;
103
-
104
- // Confirm state (for destructive stale/contradict/delete actions)
105
- private confirm: ConfirmState<{ id: string; action: 'stale' | 'contradicted' | 'delete' }> | null = null;
106
-
107
- // Cached records for review-mode (reviewQueue-first, same as former KnowledgePanel)
108
- private reviewRecords: MemoryRecord[] = [];
109
-
110
- constructor(registry: MemoryRegistry) {
111
- super('memory', 'Memory', 'M', 'agent');
112
- this.registry = registry;
113
- // WO-153: converged modal '/' filter — only active in 'all' mode; review
114
- // mode uses r/s/c/f/d single-letter actions instead of text filtering.
115
- this.filterEnabled = true;
116
- this.filterLabel = 'Search';
117
- }
118
-
119
- // ---------------------------------------------------------------------------
120
- // Lifecycle
121
- // ---------------------------------------------------------------------------
122
-
123
- override onActivate(): void {
124
- super.onActivate();
125
- this.filterQuery = '';
126
- this.filterActive = false;
127
- this.confirm = null;
128
- this.refreshReviewRecords();
129
- this.unsubscribe = this.registry.subscribe(() => {
130
- this.refreshReviewRecords();
131
- this.markDirty();
132
- });
133
- }
134
-
135
- override onDeactivate(): void {
136
- super.onDeactivate();
137
- }
138
-
139
- override onDestroy(): void {
140
- this.unsubscribe?.();
141
- this.unsubscribe = undefined;
142
- }
143
-
144
- // ---------------------------------------------------------------------------
145
- // ScrollableListPanel implementation (filter is only enabled in 'all' mode)
146
- // ---------------------------------------------------------------------------
147
-
148
- /** `q` arrives already trimmed + lower-cased from ScrollableListPanel.getVisibleItems(). */
149
- protected override filterMatches(record: MemoryRecord, q: string): boolean {
150
- const haystack = [
151
- record.summary,
152
- record.detail ?? '',
153
- record.cls,
154
- record.scope,
155
- record.tags.join(' '),
156
- ].join(' ').toLowerCase();
157
- return haystack.includes(q);
158
- }
159
-
160
- protected renderItem(record: MemoryRecord, index: number, selected: boolean, width: number): Line {
161
- if (this.filterMode === 'review') {
162
- // Review-mode row: reviewState + confidence (matches former KnowledgePanel row)
163
- const bg = selected ? C.selectBg : undefined;
164
- return buildPanelLine(width, [
165
- [' ', C.label, bg],
166
- [record.reviewState.padEnd(13), reviewStateColor(record.reviewState), bg],
167
- [` ${formatConfidence(record.confidence)} `, C.value, bg],
168
- [truncateDisplay(record.summary, Math.max(0, width - 26)), C.value, bg],
169
- ]);
170
- }
171
- // All-mode row: scope/class + id + time + summary (matches former MemoryPanel row)
172
- const bg = selected ? C.selectBg : undefined;
173
- return buildPanelLine(width, [
174
- [' ', C.label, bg],
175
- [`[${record.scope.slice(0, 1).toUpperCase()}/${record.cls.slice(0, 3).toUpperCase()}] `, classColor(record.cls), bg],
176
- [record.id.slice(-8), C.dim, bg],
177
- [' ', C.label, bg],
178
- [fmtTime(record.createdAt), C.dim, bg],
179
- [' ', C.label, bg],
180
- [truncateDisplay(record.summary, Math.max(0, width - 33)), C.value, bg],
181
- ]);
182
- }
183
-
184
- protected override getPalette() { return C; }
185
-
186
- protected override getEmptyStateMessage() {
187
- if (this.filterMode === 'review') return 'No records in the review queue.';
188
- // The filter-narrowed-to-empty case is handled by ScrollableListPanel's
189
- // own 'No matches for "..."' message (renderList) before this is reached.
190
- return ' No memory records. Use /recall add <class> <summary> to create one.';
191
- }
192
-
193
- protected override getEmptyStateActions() {
194
- return [
195
- { command: '/recall add fact <summary>', summary: 'capture a durable fact directly' },
196
- { command: '/recall capture incident latest', summary: 'promote the latest incident into memory' },
197
- { command: '/recall capture policy', summary: 'store the current policy posture as durable evidence' },
198
- ];
199
- }
200
-
201
- // ---------------------------------------------------------------------------
202
- // Internal helpers
203
- // ---------------------------------------------------------------------------
204
-
205
- /**
206
- * getItems() so that ScrollableListPanel infrastructure (renderList,
207
- * clampSelection, navigation bounds, and — in 'all' mode — the modal '/'
208
- * filter) all operate on the right data set per mode — preventing the
209
- * render/action index desync where rendered list index N did not
210
- * correspond to reviewRecords[N] when the two lists differ.
211
- */
212
- protected getItems(): readonly MemoryRecord[] {
213
- if (this.filterMode === 'review') return this.reviewRecords;
214
- return this.registry.search({ limit: 100 });
215
- }
216
-
217
- private refreshReviewRecords(): void {
218
- this.reviewRecords = this.registry.reviewQueue(24);
219
- this.clampSelection();
220
- }
221
-
222
- /** Count of records beyond the 100-record fetch cap in getItems(). */
223
- private getHiddenCount(): number {
224
- const total = this.registry.search({}).length;
225
- return Math.max(0, total - 100);
226
- }
227
-
228
- private cycleFilter(): void {
229
- const modes: FilterMode[] = ['all', 'review'];
230
- const next = modes[(modes.indexOf(this.filterMode) + 1) % modes.length];
231
- this.filterMode = next;
232
- // The modal '/' filter only applies in 'all' mode — review mode uses
233
- // r/s/c/f/d single-letter actions instead of text filtering.
234
- this.filterEnabled = next === 'all';
235
- this.refreshReviewRecords();
236
- this.markDirty();
237
- }
238
-
239
- // ---------------------------------------------------------------------------
240
- // Input
241
- // ---------------------------------------------------------------------------
242
-
243
- handleInput(key: string): boolean {
244
- // Review confirm dialog intercepts all input
245
- if (this.confirm) {
246
- const result = handleConfirmInput(this.confirm, key);
247
- if (result === 'confirmed') {
248
- const { id, action } = this.confirm.subject;
249
- this.confirm = null;
250
- if (action === 'delete') {
251
- try {
252
- this.registry.delete(id);
253
- } catch (e) {
254
- this.setError(`Delete failed: ${summarizeError(e)}`);
255
- }
256
- this.refreshReviewRecords();
257
- this.markDirty();
258
- return true;
259
- }
260
- const record = this.reviewRecords.find((r) => r.id === id);
261
- if (record) {
262
- try {
263
- if (action === 'stale') {
264
- this.registry.review(id, {
265
- state: 'stale',
266
- confidence: Math.min(record.confidence, 40),
267
- reviewedBy: 'operator',
268
- staleReason: 'marked stale from the memory panel',
269
- });
270
- } else {
271
- this.registry.review(id, {
272
- state: 'contradicted',
273
- confidence: 0,
274
- reviewedBy: 'operator',
275
- staleReason: 'marked contradicted from the memory panel',
276
- });
277
- }
278
- } catch (e) {
279
- this.setError(`Review update failed: ${summarizeError(e)}`);
280
- }
281
- }
282
- this.refreshReviewRecords();
283
- this.markDirty();
284
- return true;
285
- }
286
- if (result === 'cancelled') {
287
- this.confirm = null;
288
- this.markDirty();
289
- return true;
290
- }
291
- if (result === 'absorbed') return true;
292
- }
293
-
294
- // Tab cycles filter modes
295
- if (key === 'tab') {
296
- this.cycleFilter();
297
- return true;
298
- }
299
-
300
- // Review-mode specific actions (r/s/c/f)
301
- if (this.filterMode === 'review') {
302
- // In review mode getItems() returns reviewRecords and filterEnabled is
303
- // false, so getSelectedItem() resolves the highlighted review record.
304
- const selected = this.getSelectedItem();
305
-
306
- if (key === 'enter' || key === 'return' || key === 'r') {
307
- if (!selected) return false;
308
- this.registry.review(selected.id, {
309
- state: 'reviewed',
310
- confidence: Math.max(selected.confidence, 85),
311
- reviewedBy: 'operator',
312
- });
313
- this.refreshReviewRecords();
314
- this.markDirty();
315
- return true;
316
- }
317
- if (key === 's') {
318
- if (!selected) return false;
319
- this.confirm = { subject: { id: selected.id, action: 'stale' }, label: truncateDisplay(selected.summary, 40) };
320
- this.markDirty();
321
- return true;
322
- }
323
- if (key === 'c') {
324
- if (!selected) return false;
325
- this.confirm = { subject: { id: selected.id, action: 'contradicted' }, label: truncateDisplay(selected.summary, 40) };
326
- this.markDirty();
327
- return true;
328
- }
329
- if (key === 'f') {
330
- if (!selected) return false;
331
- this.registry.review(selected.id, {
332
- state: 'fresh',
333
- confidence: Math.max(selected.confidence, 60),
334
- reviewedBy: 'operator',
335
- });
336
- this.refreshReviewRecords();
337
- this.markDirty();
338
- return true;
339
- }
340
- if (key === 'd') {
341
- if (!selected) return false;
342
- this.confirm = { subject: { id: selected.id, action: 'delete' }, label: truncateDisplay(selected.summary, 40) };
343
- this.markDirty();
344
- return true;
345
- }
346
- }
347
-
348
- // All-mode: 'r' reload and 'd' delete are single-letter actions that
349
- // coexist with the modal '/' filter — they only fire outside filter
350
- // mode, so they remain typeable into the filter query while it's active.
351
- if (this.filterMode === 'all' && !this.filterActive) {
352
- if (key === 'r') {
353
- this.markDirty();
354
- return true;
355
- }
356
-
357
- if (key === 'd') {
358
- const selected = this.getSelectedItem();
359
- if (!selected) return false;
360
- this.confirm = { subject: { id: selected.id, action: 'delete' }, label: truncateDisplay(selected.summary, 40) };
361
- this.markDirty();
362
- return true;
363
- }
364
- }
365
-
366
- // Navigation + (in 'all' mode) the modal '/' filter: delegate to
367
- // ScrollableListPanel. filterEnabled is false in review mode, so '/' is
368
- // inert there and single-char keys like 'j'/'k' navigate normally.
369
- return super.handleInput(key);
370
- }
371
-
372
- // ---------------------------------------------------------------------------
373
- // Render
374
- // ---------------------------------------------------------------------------
375
-
376
- render(width: number, height: number): Line[] {
377
- this.clampSelection();
378
-
379
- // Review confirm dialog takes over the full panel
380
- if (this.confirm) {
381
- return buildPanelWorkspace(width, height, {
382
- title: 'Memory',
383
- intro: '',
384
- sections: [{ title: 'Confirmation', lines: renderConfirmLines(width, this.confirm) }],
385
- footerLines: [buildPanelLine(width, [[' y confirm n / Esc cancel', C.dim]])],
386
- palette: C,
387
- });
388
- }
389
-
390
- const filterLabel = FILTER_LABELS[this.filterMode];
391
- const filterToggleLine = buildPanelLine(width, [
392
- [' Filter: ', C.label],
393
- [filterLabel, C.info],
394
- [' (Tab to toggle)', C.dim],
395
- ]);
396
-
397
- if (this.filterMode === 'review') {
398
- return this.renderReviewMode(width, height, filterToggleLine);
399
- }
400
- return this.renderAllMode(width, height, filterToggleLine);
401
- }
402
-
403
- private renderAllMode(width: number, height: number, filterToggleLine: Line): Line[] {
404
- // getVisibleItems() applies the modal '/' filter (getItems() is the
405
- // unfiltered 100-cap fetch); the filter input line itself is
406
- // auto-injected by renderList() below (filterEnabled=true in 'all' mode).
407
- const records = this.getVisibleItems();
408
- const byClass = new Map<MemoryClass, number>();
409
- for (const record of records) {
410
- byClass.set(record.cls, (byClass.get(record.cls) ?? 0) + 1);
411
- }
412
-
413
- const hiddenCount = this.getHiddenCount();
414
-
415
- const summaryLines: Line[] = [
416
- buildKeyValueLine(width, [
417
- { label: 'records', value: String(records.length), valueColor: C.value },
418
- { label: 'facts', value: String(byClass.get('fact') ?? 0), valueColor: C.fact },
419
- { label: 'decisions', value: String(byClass.get('decision') ?? 0), valueColor: C.decision },
420
- { label: 'incidents', value: String(byClass.get('incident') ?? 0), valueColor: C.incident },
421
- { label: 'runbooks', value: String(byClass.get('runbook') ?? 0), valueColor: C.runbook },
422
- ], C),
423
- filterToggleLine,
424
- ...(hiddenCount > 0
425
- ? [buildPanelLine(width, [[` +${hiddenCount} hidden (100-record cap)`, C.dim]])]
426
- : []),
427
- ];
428
-
429
- const selected = this.getSelectedItem();
430
- const selectedLines: Line[] = [];
431
- if (selected) {
432
- selectedLines.push(buildKeyValueLine(width, [
433
- { label: 'scope', value: selected.scope, valueColor: C.info },
434
- { label: 'class', value: selected.cls, valueColor: classColor(selected.cls) },
435
- { label: 'created', value: fmtTime(selected.createdAt), valueColor: C.dim },
436
- ], C));
437
- selectedLines.push(...buildBodyText(width, selected.summary, C, C.value));
438
- if (selected.detail) selectedLines.push(...buildBodyText(width, `Detail: ${selected.detail}`, C, C.dim));
439
- if (selected.tags.length) selectedLines.push(buildPanelLine(width, [[` Tags: ${selected.tags.join(', ')}`, C.good]]));
440
- if (selected.provenance.length) {
441
- selectedLines.push(...buildBodyText(
442
- width,
443
- `Provenance: ${selected.provenance.map((p) => `${p.kind}:${p.ref}`).join(' ')}`,
444
- C,
445
- C.dim,
446
- ));
447
- }
448
- }
449
-
450
- return this.renderList(width, height, {
451
- title: 'Memory',
452
- header: summaryLines,
453
- footer: [
454
- ...selectedLines,
455
- buildPanelLine(width, [[' / search j/k or Up/Down move r reload d delete Tab: Review Queue Esc clear search', C.dim]]),
456
- ],
457
- });
458
- }
459
-
460
- private renderReviewMode(width: number, height: number, filterToggleLine: Line): Line[] {
461
- if (this.reviewRecords.length === 0) this.refreshReviewRecords();
462
-
463
- const allRecords = this.registry.search({ limit: 200 });
464
- const queue = this.registry.reviewQueue(24);
465
- const byClass = new Map<MemoryClass, number>();
466
- const byReview = new Map<MemoryReviewState, number>();
467
- const byScope = new Map<string, number>();
468
- for (const record of allRecords) {
469
- byClass.set(record.cls, (byClass.get(record.cls) ?? 0) + 1);
470
- byReview.set(record.reviewState, (byReview.get(record.reviewState) ?? 0) + 1);
471
- byScope.set(record.scope, (byScope.get(record.scope) ?? 0) + 1);
472
- }
473
-
474
- const classLines: Line[] = [
475
- buildPanelLine(width, [
476
- [' facts ', C.label], [String(byClass.get('fact') ?? 0), C.good],
477
- [' risks ', C.label], [String(byClass.get('risk') ?? 0), (byClass.get('risk') ?? 0) > 0 ? C.warn : C.good],
478
- [' runbooks ', C.label], [String(byClass.get('runbook') ?? 0), C.info],
479
- [' architecture ', C.label], [String(byClass.get('architecture') ?? 0), C.info],
480
- [' incidents ', C.label], [String(byClass.get('incident') ?? 0), (byClass.get('incident') ?? 0) > 0 ? C.bad : C.good],
481
- ]),
482
- buildPanelLine(width, [
483
- [' decisions ', C.label], [String(byClass.get('decision') ?? 0), C.value],
484
- [' constraints ', C.label], [String(byClass.get('constraint') ?? 0), C.value],
485
- [' ownership ', C.label], [String(byClass.get('ownership') ?? 0), C.value],
486
- [' patterns ', C.label], [String(byClass.get('pattern') ?? 0), C.value],
487
- [' total ', C.label], [String(allRecords.length), C.value],
488
- ]),
489
- ];
490
-
491
- const reviewLines: Line[] = [
492
- buildPanelLine(width, [
493
- [' reviewed ', C.label], [String(byReview.get('reviewed') ?? 0), C.good],
494
- [' fresh ', C.label], [String(byReview.get('fresh') ?? 0), C.info],
495
- [' stale ', C.label], [String(byReview.get('stale') ?? 0), C.warn],
496
- [' contradicted ', C.label], [String(byReview.get('contradicted') ?? 0), C.bad],
497
- [' Queue ', C.label], [String(queue.length), queue.length > 0 ? C.warn : C.good],
498
- ]),
499
- buildPanelLine(width, [
500
- [' session ', C.label], [String(byScope.get('session') ?? 0), C.info],
501
- [' project ', C.label], [String(byScope.get('project') ?? 0), C.value],
502
- [' team ', C.label], [String(byScope.get('team') ?? 0), C.good],
503
- ]),
504
- filterToggleLine,
505
- ];
506
-
507
- const selectedRecord = this.getSelectedItem();
508
- const selectedLines: Line[] = [];
509
- if (selectedRecord) {
510
- selectedLines.push(buildPanelLine(width, [[' Selected', C.label]]));
511
- selectedLines.push(buildKeyValueLine(width, [
512
- { label: 'Class', value: selectedRecord.cls, valueColor: C.value },
513
- { label: 'Scope', value: selectedRecord.scope, valueColor: C.info },
514
- { label: 'Review', value: selectedRecord.reviewState, valueColor: reviewStateColor(selectedRecord.reviewState) },
515
- { label: 'Confidence', value: formatConfidence(selectedRecord.confidence), valueColor: C.value },
516
- ], C));
517
- selectedLines.push(...buildBodyText(width, `Summary: ${selectedRecord.summary}`, C, C.value));
518
- if (selectedRecord.detail) selectedLines.push(...buildBodyText(width, `Detail: ${selectedRecord.detail}`, C, C.dim));
519
- if (selectedRecord.provenance.length) {
520
- selectedLines.push(...buildBodyText(
521
- width,
522
- `Provenance: ${selectedRecord.provenance.map((p) => `${p.kind}:${p.ref}`).join(', ')}`,
523
- C,
524
- C.dim,
525
- ));
526
- }
527
- if (selectedRecord.staleReason) {
528
- selectedLines.push(...buildBodyText(
529
- width,
530
- `Stale reason: ${selectedRecord.staleReason}`,
531
- C,
532
- selectedRecord.reviewState === 'contradicted' ? C.bad : C.warn,
533
- ));
534
- }
535
- if (selectedRecord.reviewedAt) {
536
- selectedLines.push(buildPanelLine(width, [
537
- [' Reviewed: ', C.label],
538
- [new Date(selectedRecord.reviewedAt).toLocaleString(), C.dim],
539
- ]));
540
- if (selectedRecord.reviewedBy) {
541
- selectedLines.push(buildPanelLine(width, [
542
- [' Reviewer: ', C.label],
543
- [selectedRecord.reviewedBy, C.dim],
544
- ]));
545
- }
546
- }
547
- }
548
-
549
- return this.renderList(width, height, {
550
- title: 'Memory',
551
- header: [...classLines, ...reviewLines],
552
- footer: [
553
- ...(selectedLines.length > 0 ? selectedLines : []),
554
- buildPanelLine(width, [[' Tab: All Records Up/Down move r/Enter reviewed s stale c contradicted f fresh d delete', C.dim]]),
555
- ],
556
- });
557
- }
558
- }