@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.
- package/CHANGELOG.md +85 -21
- package/README.md +28 -18
- package/docs/foundation-artifacts/operator-contract.json +4045 -1763
- package/package.json +2 -2
- package/src/audio/spoken-turn-controller.ts +12 -2
- package/src/audio/spoken-turn-wiring.ts +2 -1
- package/src/cli/help.ts +8 -1
- package/src/cli/service-posture.ts +2 -1
- package/src/cli/status.ts +2 -1
- package/src/cli/surface-command.ts +2 -2
- package/src/core/alert-gating.ts +67 -0
- package/src/core/approval-alert.ts +72 -0
- package/src/core/budget-breach-notifier.ts +106 -0
- package/src/core/composer-state.ts +11 -3
- package/src/core/conversation-line-cache.ts +27 -3
- package/src/core/conversation-rendering.ts +90 -14
- package/src/core/conversation.ts +18 -0
- package/src/core/focus-tracker.ts +41 -0
- package/src/core/long-task-notifier.ts +33 -6
- package/src/core/stream-event-wiring.ts +20 -1
- package/src/core/system-message-noise.ts +87 -0
- package/src/core/system-message-router.ts +104 -51
- package/src/core/turn-cancellation.ts +25 -0
- package/src/core/turn-event-wiring.ts +72 -3
- package/src/daemon/cli.ts +29 -2
- package/src/daemon/handlers/register.ts +8 -1
- package/src/daemon/service-commands.ts +329 -0
- package/src/export/cost-utils.ts +36 -0
- package/src/input/autocomplete.ts +27 -1
- package/src/input/command-registry.ts +84 -5
- package/src/input/commands/checkpoint-runtime.ts +280 -0
- package/src/input/commands/codebase-runtime.ts +232 -0
- package/src/input/commands/config.ts +43 -3
- package/src/input/commands/eval.ts +1 -1
- package/src/input/commands/health-runtime.ts +10 -2
- package/src/input/commands/image-runtime.ts +112 -0
- package/src/input/commands/intelligence-runtime.ts +11 -1
- package/src/input/commands/local-auth-runtime.ts +4 -1
- package/src/input/commands/local-runtime.ts +9 -3
- package/src/input/commands/marketplace-runtime.ts +2 -2
- package/src/input/commands/memory.ts +73 -35
- package/src/input/commands/operator-panel-runtime.ts +71 -31
- package/src/input/commands/planning-runtime.ts +116 -4
- package/src/input/commands/platform-sandbox-runtime.ts +1 -6
- package/src/input/commands/plugin-runtime.ts +2 -2
- package/src/input/commands/policy-dispatch.ts +21 -0
- package/src/input/commands/provider-accounts-runtime.ts +1 -1
- package/src/input/commands/qrcode-runtime.ts +26 -6
- package/src/input/commands/recall-review.ts +35 -0
- package/src/input/commands/remote-runtime-setup.ts +5 -3
- package/src/input/commands/remote-runtime.ts +3 -3
- package/src/input/commands/runtime-services.ts +54 -13
- package/src/input/commands/services-runtime.ts +1 -1
- package/src/input/commands/session-content.ts +20 -9
- package/src/input/commands/session-workflow.ts +16 -1
- package/src/input/commands/settings-sync-runtime.ts +16 -4
- package/src/input/commands/shell-core.ts +25 -8
- package/src/input/commands/skills-runtime.ts +2 -8
- package/src/input/commands/subscription-runtime.ts +2 -2
- package/src/input/commands/test-runtime.ts +277 -0
- package/src/input/commands/websearch-runtime.ts +101 -0
- package/src/input/commands/work-plan-runtime.ts +36 -10
- package/src/input/commands/workstream-runtime.ts +488 -0
- package/src/input/commands.ts +12 -0
- package/src/input/config-modal-types.ts +175 -0
- package/src/input/config-modal.ts +592 -0
- package/src/input/feed-context-factory.ts +10 -8
- package/src/input/handler-command-route.ts +37 -20
- package/src/input/handler-content-actions.ts +17 -2
- package/src/input/handler-feed-routes.ts +74 -114
- package/src/input/handler-feed.ts +79 -27
- package/src/input/handler-interactions.ts +1 -3
- package/src/input/handler-modal-routes.ts +136 -6
- package/src/input/handler-modal-stack.ts +14 -7
- package/src/input/handler-modal-token-routes.ts +16 -49
- package/src/input/handler-onboarding-daemon-adopt.ts +149 -0
- package/src/input/handler-onboarding.ts +71 -59
- package/src/input/handler-picker-routes.ts +26 -102
- package/src/input/handler-shortcuts.ts +74 -14
- package/src/input/handler-types.ts +2 -6
- package/src/input/handler-ui-state.ts +10 -22
- package/src/input/handler.ts +12 -29
- package/src/input/keybindings.ts +23 -8
- package/src/input/model-picker-types.ts +24 -6
- package/src/input/model-picker.ts +77 -2
- package/src/input/onboarding/onboarding-runtime-status.ts +10 -1
- package/src/input/onboarding/onboarding-wizard-apply.ts +8 -1
- package/src/input/onboarding/onboarding-wizard-constants.ts +4 -1
- package/src/input/onboarding/onboarding-wizard-network-adopt.ts +136 -0
- package/src/input/onboarding/onboarding-wizard-steps.ts +9 -6
- package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
- package/src/input/panel-integration-actions.ts +9 -170
- package/src/input/panel-mouse-geometry.ts +97 -0
- package/src/input/panel-paste-flood-guard.ts +86 -0
- package/src/input/selection-modal.ts +11 -0
- package/src/input/session-picker-modal.ts +44 -4
- package/src/input/settings-modal-data.ts +146 -2
- package/src/input/settings-modal-mutations.ts +6 -4
- package/src/input/settings-modal-types.ts +4 -2
- package/src/main.ts +49 -51
- package/src/panels/agent-inspector-shared.ts +2 -1
- package/src/panels/base-panel.ts +22 -1
- package/src/panels/builtin/agent.ts +21 -107
- package/src/panels/builtin/development.ts +15 -61
- package/src/panels/builtin/knowledge.ts +8 -32
- package/src/panels/builtin/operations.ts +84 -428
- package/src/panels/builtin/session.ts +21 -112
- package/src/panels/builtin/shared.ts +18 -46
- package/src/panels/builtin-modals.ts +218 -0
- package/src/panels/builtin-panels.ts +5 -0
- package/src/panels/cost-tracker-panel.ts +36 -10
- package/src/panels/diff-panel.ts +12 -43
- package/src/panels/eval-registry.ts +60 -0
- package/src/panels/fleet-deep-link.ts +31 -0
- package/src/panels/fleet-panel-format.ts +62 -0
- package/src/panels/fleet-panel-worktree-detail.ts +48 -0
- package/src/panels/fleet-panel.ts +758 -0
- package/src/panels/fleet-read-model.ts +520 -0
- package/src/panels/fleet-steer.ts +125 -0
- package/src/panels/fleet-stop.ts +153 -0
- package/src/panels/fleet-tabs.ts +230 -0
- package/src/panels/fleet-transcript.ts +356 -0
- package/src/panels/index.ts +7 -31
- package/src/panels/modals/hooks-modal.ts +187 -0
- package/src/panels/modals/keybindings-modal.ts +166 -0
- package/src/panels/modals/knowledge-modal.ts +158 -0
- package/src/panels/modals/local-auth-modal.ts +132 -0
- package/src/panels/modals/marketplace-modal.ts +218 -0
- package/src/panels/modals/memory-modal.ts +180 -0
- package/src/panels/modals/modal-surface-helpers.ts +54 -0
- package/src/panels/modals/modal-theme.ts +42 -0
- package/src/panels/modals/pairing-modal.ts +163 -0
- package/src/panels/modals/planning-modal.ts +304 -0
- package/src/panels/modals/plugins-modal.ts +174 -0
- package/src/panels/modals/policy-modal.ts +256 -0
- package/src/panels/modals/provider-health-modal.ts +136 -0
- package/src/panels/modals/remote-modal.ts +115 -0
- package/src/panels/modals/sandbox-modal.ts +150 -0
- package/src/panels/modals/security-modal.ts +217 -0
- package/src/panels/modals/services-modal.ts +179 -0
- package/src/panels/modals/settings-sync-modal.ts +142 -0
- package/src/panels/modals/skills-modal.ts +189 -0
- package/src/panels/modals/subscription-modal.ts +172 -0
- package/src/panels/modals/work-plan-modal.ts +177 -0
- package/src/panels/panel-confirm-overlay.ts +72 -0
- package/src/panels/panel-manager.ts +123 -9
- package/src/panels/polish-core.ts +38 -25
- package/src/panels/project-planning-answer-actions.ts +8 -11
- package/src/panels/skills-panel.ts +7 -51
- package/src/panels/types.ts +37 -0
- package/src/permissions/hunk-selection.ts +179 -0
- package/src/permissions/prompt.ts +216 -13
- package/src/renderer/autocomplete-overlay.ts +28 -2
- package/src/renderer/compaction-history-modal.ts +19 -3
- package/src/renderer/compaction-preview.ts +13 -2
- package/src/renderer/compaction-quality.ts +100 -0
- package/src/renderer/compositor.ts +2 -3
- package/src/renderer/config-modal.ts +95 -0
- package/src/renderer/conversation-overlays.ts +10 -17
- package/src/renderer/fleet-tab-strip.ts +56 -0
- package/src/renderer/footer-tips.ts +15 -3
- package/src/renderer/fullscreen-primitives.ts +32 -22
- package/src/renderer/git-status.ts +43 -1
- package/src/renderer/help-overlay.ts +2 -2
- package/src/renderer/layout.ts +0 -4
- package/src/renderer/markdown.ts +7 -3
- package/src/renderer/modal-factory.ts +25 -20
- package/src/renderer/model-workspace.ts +62 -4
- package/src/renderer/overlay-box.ts +21 -17
- package/src/renderer/panel-workspace-bar.ts +8 -2
- package/src/renderer/process-indicator.ts +14 -3
- package/src/renderer/selection-modal-overlay.ts +6 -1
- package/src/renderer/session-picker-modal.ts +196 -3
- package/src/renderer/settings-modal-helpers.ts +2 -0
- package/src/renderer/settings-modal.ts +7 -0
- package/src/renderer/shell-surface.ts +8 -1
- package/src/renderer/status-glyphs.ts +14 -15
- package/src/renderer/system-message.ts +15 -3
- package/src/renderer/terminal-bg-probe.ts +339 -0
- package/src/renderer/terminal-escapes.ts +20 -0
- package/src/renderer/theme-mode-config.ts +67 -0
- package/src/renderer/theme.ts +91 -1
- package/src/renderer/thinking.ts +11 -3
- package/src/renderer/tool-call.ts +15 -9
- package/src/renderer/tool-result-summary.ts +148 -0
- package/src/renderer/turn-injection.ts +133 -0
- package/src/renderer/ui-factory.ts +154 -85
- package/src/renderer/ui-primitives.ts +30 -129
- package/src/runtime/bootstrap-command-context.ts +27 -1
- package/src/runtime/bootstrap-command-parts.ts +41 -10
- package/src/runtime/bootstrap-core.ts +47 -15
- package/src/runtime/bootstrap-hook-bridge.ts +7 -0
- package/src/runtime/bootstrap-shell.ts +48 -17
- package/src/runtime/bootstrap.ts +129 -22
- package/src/runtime/code-index-services.ts +135 -0
- package/src/runtime/legacy-daemon-migration.ts +516 -0
- package/src/runtime/memory-fold.ts +26 -0
- package/src/runtime/onboarding/derivation.ts +7 -2
- package/src/runtime/onboarding/snapshot.ts +27 -1
- package/src/runtime/onboarding/types.ts +31 -1
- package/src/runtime/operator-token-cleanup.ts +82 -1
- package/src/runtime/orchestrator-core-services.ts +59 -0
- package/src/runtime/process-lifecycle.ts +3 -1
- package/src/runtime/resume-notice.ts +209 -0
- package/src/runtime/services.ts +98 -46
- package/src/runtime/session-inbound-inputs.ts +252 -0
- package/src/runtime/session-spine-transport.ts +64 -0
- package/src/runtime/terminal-output-guard.ts +15 -8
- package/src/runtime/ui-services.ts +27 -3
- package/src/runtime/workstream-services.ts +327 -0
- package/src/runtime/wrfc-persistence.ts +124 -17
- package/src/shell/blocking-input.ts +68 -4
- package/src/shell/recovery-input-helpers.ts +170 -1
- package/src/shell/ui-openers.ts +171 -26
- package/src/utils/format-duration.ts +8 -18
- package/src/utils/splash-lines.ts +1 -1
- package/src/utils/terminal-width.ts +52 -0
- package/src/version.ts +1 -1
- package/src/panels/agent-inspector-panel.ts +0 -786
- package/src/panels/approval-panel.ts +0 -252
- package/src/panels/automation-control-panel.ts +0 -479
- package/src/panels/cockpit-panel.ts +0 -481
- package/src/panels/cockpit-read-model.ts +0 -238
- package/src/panels/communication-panel.ts +0 -256
- package/src/panels/control-plane-panel.ts +0 -470
- package/src/panels/debug-panel.ts +0 -615
- package/src/panels/docs-panel.ts +0 -384
- package/src/panels/eval-panel.ts +0 -627
- package/src/panels/file-explorer-panel.ts +0 -673
- package/src/panels/file-preview-panel.ts +0 -517
- package/src/panels/hooks-panel.ts +0 -401
- package/src/panels/incident-review-panel.ts +0 -406
- package/src/panels/intelligence-panel.ts +0 -383
- package/src/panels/knowledge-graph-panel.ts +0 -515
- package/src/panels/marketplace-panel.ts +0 -399
- package/src/panels/memory-panel.ts +0 -558
- package/src/panels/ops-control-panel.ts +0 -329
- package/src/panels/ops-strategy-panel.ts +0 -321
- package/src/panels/orchestration-panel.ts +0 -465
- package/src/panels/panel-list-panel.ts +0 -566
- package/src/panels/plan-dashboard-panel.ts +0 -707
- package/src/panels/policy-panel.ts +0 -517
- package/src/panels/project-planning-panel.ts +0 -731
- package/src/panels/provider-health-panel.ts +0 -678
- package/src/panels/provider-health-tracker.ts +0 -310
- package/src/panels/provider-health-views.ts +0 -567
- package/src/panels/qr-panel.ts +0 -280
- package/src/panels/remote-panel.ts +0 -534
- package/src/panels/routes-panel.ts +0 -241
- package/src/panels/sandbox-panel.ts +0 -456
- package/src/panels/security-panel.ts +0 -447
- package/src/panels/services-panel.ts +0 -329
- package/src/panels/session-browser-panel.ts +0 -496
- package/src/panels/settings-sync-panel.ts +0 -398
- package/src/panels/subscription-panel.ts +0 -342
- package/src/panels/symbol-outline-panel.ts +0 -619
- package/src/panels/system-messages-panel.ts +0 -364
- package/src/panels/tasks-panel.ts +0 -608
- package/src/panels/thinking-panel.ts +0 -333
- package/src/panels/tool-inspector-panel.ts +0 -537
- package/src/panels/work-plan-panel.ts +0 -540
- package/src/panels/worktree-panel.ts +0 -360
- package/src/panels/wrfc-panel.ts +0 -790
- package/src/renderer/agent-detail-modal.ts +0 -466
- package/src/renderer/live-tail-modal.ts +0 -156
- package/src/renderer/process-modal.ts +0 -671
- 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
|
-
}
|