@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,515 +0,0 @@
1
- /**
2
- * KnowledgeGraphPanel — SDK knowledge graph front-door.
3
- *
4
- * WO-123: Replaces the former static GRAPH_COMMANDS/MEMORY_COMMANDS command
5
- * catalogue with a live console over the injected KnowledgeApi surface:
6
- * - 'browse' mode: header counts + a combined node/source/issue list,
7
- * with '/' search dispatched through graph.items.search, plus a
8
- * schedules summary from jobs.schedules.list.
9
- * - 'review' mode (Tab): the open-issue review queue, with
10
- * accept/reject/resolve/reopen mutations via graph.issues.review,
11
- * ConfirmState-gated — mirrors MemoryPanel's proven review pattern.
12
- *
13
- * 'M' opens the Memory panel directly via panelManager.open (no more dead
14
- * hint text) — the two panels stay distinct: this one is the SDK graph,
15
- * memory-panel.ts is the durable project-memory substrate.
16
- */
17
-
18
- import type { Line } from '../types/grid.ts';
19
- import type { KnowledgeApi } from '@pellux/goodvibes-sdk/platform/knowledge';
20
- import type {
21
- KnowledgeIssueRecord,
22
- KnowledgeNodeRecord,
23
- KnowledgeScheduleRecord,
24
- KnowledgeSearchResult,
25
- KnowledgeSourceRecord,
26
- } from '@pellux/goodvibes-sdk/platform/knowledge';
27
- import { ScrollableListPanel } from './scrollable-list-panel.ts';
28
- import { truncateDisplay } from '../utils/terminal-width.ts';
29
- import { type ConfirmState, handleConfirmInput, renderConfirmLines } from './confirm-state.ts';
30
- import {
31
- buildBodyText,
32
- buildKeyValueLine,
33
- buildPanelLine,
34
- buildPanelWorkspace,
35
- buildSearchInputLine,
36
- DEFAULT_PANEL_PALETTE,
37
- } from './polish.ts';
38
- import {
39
- getPanelSearchFocusTransition,
40
- isPanelSearchBackspace,
41
- isPanelSearchCancel,
42
- isPanelSearchCommit,
43
- isPanelSearchPrintable,
44
- } from './search-focus.ts';
45
- import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
46
-
47
- const C = DEFAULT_PANEL_PALETTE;
48
-
49
- // The 'edit' and 'forget' review actions accept structured data the panel has
50
- // no field to collect, so only the four browsable actions are key-bound here
51
- // (mirrors the reduced key surface the plan calls out: a/x/r/o).
52
- const KNOWLEDGE_REVIEW_ACTIONS = ['accept', 'reject', 'resolve', 'reopen'] as const;
53
- type KnowledgeReviewAction = typeof KNOWLEDGE_REVIEW_ACTIONS[number];
54
-
55
- type BrowseKind = 'node' | 'source' | 'issue' | 'result';
56
-
57
- interface BrowseRow {
58
- readonly kind: BrowseKind;
59
- readonly id: string;
60
- readonly title: string;
61
- readonly tag: string;
62
- readonly detail: string;
63
- }
64
-
65
- type Mode = 'browse' | 'review';
66
-
67
- const LIST_LIMIT = 60;
68
- const SEARCH_LIMIT = 30;
69
-
70
- function cleanInline(value: string | undefined): string {
71
- return (value ?? '').replace(/\s+/g, ' ').trim();
72
- }
73
-
74
- function nodeRow(node: KnowledgeNodeRecord): BrowseRow {
75
- return {
76
- kind: 'node',
77
- id: node.id,
78
- title: cleanInline(node.title) || 'untitled',
79
- tag: `node/${node.kind}`,
80
- detail: cleanInline(node.summary),
81
- };
82
- }
83
-
84
- function sourceRow(source: KnowledgeSourceRecord): BrowseRow {
85
- return {
86
- kind: 'source',
87
- id: source.id,
88
- title: cleanInline(source.title) || cleanInline(source.canonicalUri) || cleanInline(source.sourceUri) || source.id,
89
- tag: `source/${source.status}`,
90
- detail: cleanInline(source.summary),
91
- };
92
- }
93
-
94
- function issueRow(issue: KnowledgeIssueRecord): BrowseRow {
95
- return {
96
- kind: 'issue',
97
- id: issue.id,
98
- title: issue.code,
99
- tag: `issue/${issue.severity}`,
100
- detail: cleanInline(issue.message),
101
- };
102
- }
103
-
104
- function searchResultRow(result: KnowledgeSearchResult): BrowseRow {
105
- const title = result.source?.title
106
- ?? result.source?.canonicalUri
107
- ?? result.node?.title
108
- ?? result.id;
109
- return {
110
- kind: 'result',
111
- id: result.id,
112
- title: cleanInline(title) || result.id,
113
- tag: `${result.kind} score=${result.score}`,
114
- detail: cleanInline(result.reason),
115
- };
116
- }
117
-
118
- function kindColor(kind: BrowseKind): string {
119
- switch (kind) {
120
- case 'node': return C.info;
121
- case 'source': return C.good;
122
- case 'issue': return C.warn;
123
- case 'result': return C.value;
124
- }
125
- }
126
-
127
- function severityColor(severity: KnowledgeIssueRecord['severity']): string {
128
- switch (severity) {
129
- case 'error': return C.bad;
130
- case 'warning': return C.warn;
131
- case 'info':
132
- default: return C.info;
133
- }
134
- }
135
-
136
- export class KnowledgeGraphPanel extends ScrollableListPanel<BrowseRow> {
137
- private readonly knowledge: KnowledgeApi;
138
- private readonly openMemoryPanel?: () => void;
139
-
140
- private mode: Mode = 'browse';
141
-
142
- // Browse-mode data + search state
143
- private browseRows: BrowseRow[] = [];
144
- private searchRows: BrowseRow[] = [];
145
- private searchFocused = false;
146
- private searchQuery = '';
147
- private sourceCount = 0;
148
- private nodeCount = 0;
149
- private issueCount = 0;
150
- private schedules: readonly KnowledgeScheduleRecord[] = [];
151
-
152
- // Review-mode data
153
- private reviewRows: BrowseRow[] = [];
154
- private reviewIssues = new Map<string, KnowledgeIssueRecord>();
155
-
156
- private confirm: ConfirmState<{ issueId: string; action: KnowledgeReviewAction }> | null = null;
157
-
158
- constructor(knowledge: KnowledgeApi, openMemoryPanel?: () => void) {
159
- super('knowledge', 'Knowledge', 'K', 'agent');
160
- this.knowledge = knowledge;
161
- this.openMemoryPanel = openMemoryPanel;
162
- }
163
-
164
- // ---------------------------------------------------------------------------
165
- // Lifecycle
166
- // ---------------------------------------------------------------------------
167
-
168
- override onActivate(): void {
169
- super.onActivate();
170
- this.confirm = null;
171
- this.refreshBrowseRows();
172
- this.refreshReviewRows();
173
- }
174
-
175
- // ---------------------------------------------------------------------------
176
- // Data refresh
177
- // ---------------------------------------------------------------------------
178
-
179
- private refreshBrowseRows(): void {
180
- try {
181
- const nodes = this.knowledge.graph.nodes.list(LIST_LIMIT);
182
- const sources = this.knowledge.sources.list(LIST_LIMIT);
183
- const issues = this.knowledge.graph.issues.list(LIST_LIMIT);
184
- this.sourceCount = sources.length;
185
- this.nodeCount = nodes.length;
186
- this.issueCount = issues.length;
187
- this.browseRows = [
188
- ...sources.map(sourceRow),
189
- ...nodes.map(nodeRow),
190
- ...issues.map(issueRow),
191
- ];
192
- this.schedules = this.knowledge.jobs.schedules.list(20);
193
- } catch (e) {
194
- this.setError(`Knowledge graph load failed: ${summarizeError(e)}`);
195
- }
196
- this.clampSelection();
197
- this.markDirty();
198
- }
199
-
200
- private refreshReviewRows(): void {
201
- try {
202
- const issues = this.knowledge.graph.issues.list(LIST_LIMIT).filter((issue) => issue.status === 'open');
203
- this.reviewIssues = new Map(issues.map((issue) => [issue.id, issue]));
204
- this.reviewRows = issues.map(issueRow);
205
- } catch (e) {
206
- this.setError(`Knowledge review queue load failed: ${summarizeError(e)}`);
207
- }
208
- this.clampSelection();
209
- this.markDirty();
210
- }
211
-
212
- private runSearch(): void {
213
- const query = this.searchQuery.trim();
214
- if (!query) {
215
- this.searchRows = [];
216
- this.markDirty();
217
- return;
218
- }
219
- try {
220
- this.searchRows = this.knowledge.graph.items.search(query, SEARCH_LIMIT).map(searchResultRow);
221
- } catch (e) {
222
- this.setError(`Knowledge search failed: ${summarizeError(e)}`);
223
- this.searchRows = [];
224
- }
225
- this.selectedIndex = 0;
226
- this.markDirty();
227
- }
228
-
229
- // ---------------------------------------------------------------------------
230
- // ScrollableListPanel implementation
231
- // ---------------------------------------------------------------------------
232
-
233
- protected getItems(): readonly BrowseRow[] {
234
- if (this.mode === 'review') return this.reviewRows;
235
- if (this.searchQuery.trim()) return this.searchRows;
236
- return this.browseRows;
237
- }
238
-
239
- protected renderItem(row: BrowseRow, _index: number, selected: boolean, width: number): Line {
240
- const bg = selected ? C.selectBg : undefined;
241
- return buildPanelLine(width, [
242
- [' ', C.label, bg],
243
- [row.tag.padEnd(16), kindColor(row.kind), bg],
244
- [truncateDisplay(row.title, Math.max(0, Math.floor((width - 22) * 0.45))), C.value, bg],
245
- [' ', C.label, bg],
246
- [truncateDisplay(row.detail, Math.max(0, width - 22 - Math.floor((width - 22) * 0.45) - 2)), C.dim, bg],
247
- ]);
248
- }
249
-
250
- protected override getPalette() { return C; }
251
-
252
- protected override getEmptyStateMessage(): string {
253
- if (this.mode === 'review') return 'No issues waiting for review.';
254
- if (this.searchQuery.trim()) return ` No results matching "${this.searchQuery.trim()}"`;
255
- return 'No ingested knowledge yet.';
256
- }
257
-
258
- protected override getEmptyStateActions() {
259
- if (this.mode === 'review' || this.searchQuery.trim()) return [];
260
- return [
261
- { command: '/knowledge ingest-url <url>', summary: 'ingest a URL as the first graph source' },
262
- ];
263
- }
264
-
265
- // ---------------------------------------------------------------------------
266
- // Input
267
- // ---------------------------------------------------------------------------
268
-
269
- /**
270
- * The `/`-to-search buffer wants every character of a burst (paste, or a
271
- * fast-typed query landing in one input.feed() call), same as it always
272
- * has — see the interface doc on `Panel.isCapturingTextBurst`.
273
- */
274
- override isCapturingTextBurst(): boolean {
275
- return this.searchFocused;
276
- }
277
-
278
- handleInput(key: string): boolean {
279
- if (this.lastError !== null) this.clearError();
280
-
281
- if (this.confirm) {
282
- const result = handleConfirmInput(this.confirm, key);
283
- if (result === 'confirmed') {
284
- const { issueId, action } = this.confirm.subject;
285
- this.confirm = null;
286
- void this.applyReview(issueId, action);
287
- return true;
288
- }
289
- if (result === 'cancelled') {
290
- this.confirm = null;
291
- this.markDirty();
292
- return true;
293
- }
294
- if (result === 'absorbed') return true;
295
- }
296
-
297
- if (key === 'tab') {
298
- this.mode = this.mode === 'browse' ? 'review' : 'browse';
299
- this.selectedIndex = 0;
300
- if (this.mode === 'review') this.refreshReviewRows();
301
- else this.refreshBrowseRows();
302
- this.markDirty();
303
- return true;
304
- }
305
-
306
- if (key === 'm' || key === 'M') {
307
- this.openMemoryPanel?.();
308
- return true;
309
- }
310
-
311
- if (this.mode === 'review') {
312
- // getItems() returns reviewRows in review mode, so the shared
313
- // getSelectedItem() accessor resolves the highlighted issue row.
314
- const selected = this.getSelectedItem();
315
- if (key === 'a' && selected) {
316
- this.confirm = { subject: { issueId: selected.id, action: 'accept' }, label: truncateDisplay(selected.detail || selected.title, 40), verb: 'Accept' };
317
- this.markDirty();
318
- return true;
319
- }
320
- if (key === 'x' && selected) {
321
- this.confirm = { subject: { issueId: selected.id, action: 'reject' }, label: truncateDisplay(selected.detail || selected.title, 40), verb: 'Reject' };
322
- this.markDirty();
323
- return true;
324
- }
325
- if (key === 'r' && selected) {
326
- this.confirm = { subject: { issueId: selected.id, action: 'resolve' }, label: truncateDisplay(selected.detail || selected.title, 40), verb: 'Resolve' };
327
- this.markDirty();
328
- return true;
329
- }
330
- if (key === 'o' && selected) {
331
- this.confirm = { subject: { issueId: selected.id, action: 'reopen' }, label: truncateDisplay(selected.detail || selected.title, 40), verb: 'Reopen' };
332
- this.markDirty();
333
- return true;
334
- }
335
- return ScrollableListPanel.prototype.handleInput.call(this, key);
336
- }
337
-
338
- // Browse mode: '/' search focus
339
- if (this.searchFocused) {
340
- const items = this.getItems();
341
- const transition = getPanelSearchFocusTransition(key, { selectedIndex: this.selectedIndex, itemCount: items.length });
342
- if (transition === 'focus-list') {
343
- this.searchFocused = false;
344
- this.markDirty();
345
- return true;
346
- }
347
- if (isPanelSearchCancel(key)) {
348
- this.searchFocused = false;
349
- this.searchQuery = '';
350
- this.searchRows = [];
351
- this.selectedIndex = 0;
352
- this.markDirty();
353
- return true;
354
- }
355
- if (isPanelSearchBackspace(key)) {
356
- this.searchQuery = this.searchQuery.slice(0, -1);
357
- this.runSearch();
358
- return true;
359
- }
360
- if (isPanelSearchCommit(key)) {
361
- this.searchFocused = false;
362
- this.markDirty();
363
- return true;
364
- }
365
- if (isPanelSearchPrintable(key)) {
366
- this.searchQuery += key;
367
- this.runSearch();
368
- return true;
369
- }
370
- return true;
371
- }
372
-
373
- const items = this.getItems();
374
- const transition = getPanelSearchFocusTransition(key, { selectedIndex: this.selectedIndex, itemCount: items.length });
375
- if (transition === 'focus-search') {
376
- this.searchFocused = true;
377
- this.markDirty();
378
- return true;
379
- }
380
-
381
- return ScrollableListPanel.prototype.handleInput.call(this, key);
382
- }
383
-
384
- private async applyReview(issueId: string, action: KnowledgeReviewAction): Promise<void> {
385
- try {
386
- await this.knowledge.graph.issues.review({ issueId, action, reviewer: 'tui' });
387
- } catch (e) {
388
- this.setError(`Review ${action} failed: ${summarizeError(e)}`);
389
- }
390
- this.refreshReviewRows();
391
- this.refreshBrowseRows();
392
- }
393
-
394
- // ---------------------------------------------------------------------------
395
- // Render
396
- // ---------------------------------------------------------------------------
397
-
398
- render(width: number, height: number): Line[] {
399
- if (this.confirm) {
400
- return buildPanelWorkspace(width, height, {
401
- title: 'Knowledge',
402
- intro: '',
403
- sections: [{ title: 'Confirmation', lines: renderConfirmLines(width, this.confirm) }],
404
- footerLines: [buildPanelLine(width, [[' y confirm n / Esc cancel', C.dim]])],
405
- palette: C,
406
- });
407
- }
408
-
409
- // KnowledgeApi has no subscribe/eventing hook (unlike MemoryRegistry), so
410
- // pull fresh data on every render — same fetch-per-call pattern as the
411
- // /knowledge command handlers. These are synchronous in-memory reads.
412
- if (this.mode === 'review') {
413
- this.refreshReviewRows();
414
- this.clampSelection();
415
- return this.renderReviewMode(width, height);
416
- }
417
- this.refreshBrowseRows();
418
- this.clampSelection();
419
- return this.renderBrowseMode(width, height);
420
- }
421
-
422
- private modeToggleLine(width: number): Line {
423
- const label = this.mode === 'review' ? 'Review Queue' : 'Browse';
424
- return buildPanelLine(width, [
425
- [' Mode: ', C.label],
426
- [label, C.info],
427
- [' (Tab to toggle)', C.dim],
428
- ]);
429
- }
430
-
431
- private renderBrowseMode(width: number, height: number): Line[] {
432
- const readySources = this.sourceCount > 0 && this.nodeCount > 0;
433
- const summaryLines: Line[] = [
434
- buildKeyValueLine(width, [
435
- { label: 'sources', value: String(this.sourceCount), valueColor: C.good },
436
- { label: 'nodes', value: String(this.nodeCount), valueColor: C.info },
437
- { label: 'issues', value: String(this.issueCount), valueColor: this.issueCount > 0 ? C.warn : C.good },
438
- { label: 'schedules', value: String(this.schedules.length), valueColor: C.value },
439
- ], C),
440
- this.modeToggleLine(width),
441
- buildSearchInputLine(
442
- width,
443
- this.searchFocused ? '[Search] ' : 'Search: ',
444
- this.searchFocused ? `${this.searchQuery}_` : (this.searchQuery || '(/ to search)'),
445
- C,
446
- this.searchFocused ? { active: false, bg: C.inputBg, valueColor: C.info } : { active: false },
447
- ),
448
- ];
449
-
450
- const selected = this.getSelectedItem();
451
- const selectedLines: Line[] = [];
452
- if (selected) {
453
- selectedLines.push(buildKeyValueLine(width, [
454
- { label: 'kind', value: selected.tag, valueColor: kindColor(selected.kind) },
455
- { label: 'id', value: selected.id.slice(-10), valueColor: C.dim },
456
- ], C));
457
- if (selected.detail) selectedLines.push(...buildBodyText(width, selected.detail, C, C.value));
458
- selectedLines.push(buildPanelLine(width, [
459
- [' Retrieval readiness: ', C.label],
460
- [readySources ? 'ready (sources + nodes indexed)' : 'not ready (needs sources and nodes)', readySources ? C.good : C.warn],
461
- ]));
462
- }
463
-
464
- if (this.schedules.length > 0) {
465
- selectedLines.push(buildPanelLine(width, [[' Schedules', C.label]]));
466
- for (const schedule of this.schedules.slice(0, 4)) {
467
- selectedLines.push(buildPanelLine(width, [
468
- [' ', C.label],
469
- [schedule.enabled ? 'on ' : 'off', schedule.enabled ? C.good : C.dim],
470
- [' ', C.label],
471
- [truncateDisplay(schedule.label, Math.max(0, width - 14)), C.value],
472
- ]));
473
- }
474
- }
475
-
476
- return this.renderList(width, height, {
477
- title: 'Knowledge',
478
- header: summaryLines,
479
- footer: [
480
- ...selectedLines,
481
- buildPanelLine(width, [[' / search j/k or Up/Down move Tab: Review Queue M memory panel Esc clear search', C.dim]]),
482
- ],
483
- });
484
- }
485
-
486
- private renderReviewMode(width: number, height: number): Line[] {
487
- const summaryLines: Line[] = [
488
- buildKeyValueLine(width, [
489
- { label: 'open issues', value: String(this.reviewRows.length), valueColor: this.reviewRows.length > 0 ? C.warn : C.good },
490
- ], C),
491
- this.modeToggleLine(width),
492
- ];
493
-
494
- const selectedRow = this.getSelectedItem();
495
- const selectedIssue = selectedRow ? this.reviewIssues.get(selectedRow.id) : undefined;
496
- const selectedLines: Line[] = [];
497
- if (selectedRow && selectedIssue) {
498
- selectedLines.push(buildKeyValueLine(width, [
499
- { label: 'severity', value: selectedIssue.severity, valueColor: severityColor(selectedIssue.severity) },
500
- { label: 'code', value: selectedIssue.code, valueColor: C.value },
501
- { label: 'status', value: selectedIssue.status, valueColor: C.info },
502
- ], C));
503
- selectedLines.push(...buildBodyText(width, selectedIssue.message, C, C.value));
504
- }
505
-
506
- return this.renderList(width, height, {
507
- title: 'Knowledge',
508
- header: summaryLines,
509
- footer: [
510
- ...selectedLines,
511
- buildPanelLine(width, [[' Up/Down move a accept x reject r resolve o reopen Tab: Browse M memory panel', C.dim]]),
512
- ],
513
- });
514
- }
515
- }