@pellux/goodvibes-tui 0.27.0 → 0.29.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 +124 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/core/context-usage.ts +53 -0
- package/src/core/conversation.ts +4 -6
- package/src/core/session-recovery.ts +2 -0
- package/src/core/turn-event-wiring.ts +1 -0
- package/src/input/commands/cost-runtime.ts +49 -0
- package/src/input/commands/eval.ts +4 -3
- package/src/input/commands/operator-runtime.ts +5 -1
- package/src/input/commands.ts +2 -0
- package/src/input/handler-feed-routes.ts +60 -26
- package/src/input/handler-feed.ts +17 -8
- package/src/input/handler-picker-routes.ts +18 -13
- package/src/input/handler-shortcuts.ts +51 -0
- package/src/input/handler-ui-state.ts +4 -0
- package/src/input/handler.ts +43 -6
- package/src/input/keybindings.ts +53 -8
- package/src/input/model-picker.ts +5 -0
- package/src/input/panel-integration-actions.ts +117 -1
- package/src/main.ts +5 -2
- package/src/panels/agent-inspector-panel.ts +139 -45
- package/src/panels/agent-inspector-shared.ts +147 -3
- package/src/panels/approval-panel.ts +204 -102
- package/src/panels/automation-control-panel.ts +370 -103
- package/src/panels/base-panel.ts +26 -2
- package/src/panels/builtin/agent.ts +28 -37
- package/src/panels/builtin/development.ts +40 -32
- package/src/panels/builtin/knowledge.ts +15 -6
- package/src/panels/builtin/operations.ts +178 -117
- package/src/panels/builtin/session.ts +35 -9
- package/src/panels/builtin/shared.ts +98 -6
- package/src/panels/cockpit-panel.ts +253 -75
- package/src/panels/cockpit-read-model.ts +2 -1
- package/src/panels/communication-panel.ts +159 -56
- package/src/panels/confirm-state.ts +8 -1
- package/src/panels/control-plane-panel.ts +354 -95
- package/src/panels/cost-tracker-panel.ts +243 -57
- package/src/panels/debug-panel.ts +347 -159
- package/src/panels/diff-panel.ts +257 -82
- package/src/panels/docs-panel.ts +166 -64
- package/src/panels/eval-panel.ts +336 -109
- package/src/panels/expandable-list-panel.ts +189 -0
- package/src/panels/file-explorer-panel.ts +238 -159
- package/src/panels/file-preview-panel.ts +141 -59
- package/src/panels/git-panel.ts +360 -191
- package/src/panels/hooks-panel.ts +181 -19
- package/src/panels/incident-review-panel.ts +252 -43
- package/src/panels/index.ts +0 -4
- package/src/panels/intelligence-panel.ts +310 -103
- package/src/panels/knowledge-graph-panel.ts +480 -58
- package/src/panels/local-auth-panel.ts +251 -33
- package/src/panels/marketplace-panel.ts +219 -33
- package/src/panels/memory-panel.ts +83 -81
- package/src/panels/ops-control-panel.ts +211 -32
- package/src/panels/ops-strategy-panel.ts +131 -45
- package/src/panels/orchestration-panel.ts +259 -67
- package/src/panels/panel-list-panel.ts +184 -136
- package/src/panels/panel-manager.ts +120 -13
- package/src/panels/plan-dashboard-panel.ts +507 -74
- package/src/panels/plugins-panel.ts +227 -34
- package/src/panels/policy-panel.ts +264 -55
- package/src/panels/polish-core.ts +162 -0
- package/src/panels/polish-tables.ts +258 -0
- package/src/panels/polish.ts +67 -149
- package/src/panels/project-planning-answer-actions.ts +134 -0
- package/src/panels/project-planning-panel.ts +84 -113
- package/src/panels/provider-health-panel.ts +438 -480
- package/src/panels/provider-health-routes.ts +203 -0
- package/src/panels/provider-health-tracker.ts +194 -6
- package/src/panels/provider-health-views.ts +560 -0
- package/src/panels/qr-panel.ts +136 -38
- package/src/panels/remote-panel.ts +123 -38
- package/src/panels/routes-panel.ts +87 -24
- package/src/panels/sandbox-panel.ts +232 -65
- package/src/panels/scrollable-list-panel.ts +143 -145
- package/src/panels/security-panel.ts +204 -52
- package/src/panels/services-panel.ts +174 -76
- package/src/panels/session-browser-panel.ts +101 -6
- package/src/panels/session-maintenance.ts +4 -122
- package/src/panels/settings-sync-panel.ts +346 -68
- package/src/panels/skills-panel.ts +157 -89
- package/src/panels/subscription-panel.ts +105 -34
- package/src/panels/symbol-outline-panel.ts +287 -154
- package/src/panels/system-messages-panel.ts +172 -38
- package/src/panels/tasks-panel.ts +348 -134
- package/src/panels/thinking-panel.ts +66 -32
- package/src/panels/token-budget-panel.ts +233 -46
- package/src/panels/tool-inspector-panel.ts +170 -54
- package/src/panels/types.ts +65 -4
- package/src/panels/work-plan-panel.ts +393 -39
- package/src/panels/worktree-panel.ts +239 -61
- package/src/panels/wrfc-panel-format.ts +133 -0
- package/src/panels/wrfc-panel.ts +146 -150
- package/src/renderer/compaction-preview.ts +2 -1
- package/src/renderer/compositor.ts +46 -43
- package/src/renderer/conversation-overlays.ts +25 -11
- package/src/renderer/footer-tips.ts +41 -0
- package/src/renderer/fullscreen-primitives.ts +22 -16
- package/src/renderer/help-overlay.ts +91 -14
- package/src/renderer/hint-grammar.ts +52 -0
- package/src/renderer/layout.ts +7 -7
- package/src/renderer/modal-factory.ts +23 -15
- package/src/renderer/model-picker-overlay.ts +30 -11
- package/src/renderer/model-workspace.ts +2 -3
- package/src/renderer/overlay-box.ts +16 -10
- package/src/renderer/panel-composite.ts +7 -20
- package/src/renderer/panel-workspace-bar.ts +14 -8
- package/src/renderer/process-indicator.ts +3 -1
- package/src/renderer/progress.ts +8 -6
- package/src/renderer/search-overlay.ts +27 -6
- package/src/renderer/session-picker-modal.ts +6 -4
- package/src/renderer/settings-modal.ts +70 -10
- package/src/renderer/shell-surface.ts +41 -15
- package/src/renderer/status-glyphs.ts +11 -9
- package/src/renderer/tab-strip.ts +148 -34
- package/src/renderer/theme.ts +60 -3
- package/src/renderer/ui-factory.ts +45 -36
- package/src/renderer/ui-primitives.ts +23 -2
- package/src/runtime/bootstrap-shell.ts +35 -18
- package/src/runtime/diagnostics/panels/index.ts +0 -3
- package/src/runtime/ui/model-picker/health-enrichment.ts +3 -0
- package/src/runtime/ui/model-picker/types.ts +4 -0
- package/src/shell/ui-openers.ts +14 -22
- package/src/utils/format-duration.ts +55 -0
- package/src/utils/format-number.ts +71 -0
- package/src/utils/splash-lines.ts +44 -3
- package/src/version.ts +1 -1
- package/src/work-plans/work-plan-store.ts +13 -0
- package/src/panels/agent-logs-panel.ts +0 -666
- package/src/panels/agent-logs-shared.ts +0 -129
- package/src/panels/context-visualizer-panel.ts +0 -214
- package/src/panels/forensics-panel.ts +0 -364
- package/src/panels/panel-picker.ts +0 -106
- package/src/panels/provider-account-snapshot.ts +0 -259
- package/src/panels/provider-accounts-panel.ts +0 -218
- package/src/panels/provider-stats-panel.ts +0 -366
- package/src/panels/schedule-panel.ts +0 -342
- package/src/panels/watchers-panel.ts +0 -193
- package/src/renderer/panel-picker-overlay.ts +0 -202
- package/src/renderer/panel-tab-bar.ts +0 -69
- package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
- package/src/runtime/diagnostics/panels/policy.ts +0 -177
package/src/panels/wrfc-panel.ts
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import type { Line } from '../types/grid.ts';
|
|
2
2
|
import type { WrfcChain, WrfcState, QualityGateResult } from '@pellux/goodvibes-sdk/platform/agents';
|
|
3
|
-
import type { Constraint, ConstraintFinding } from '@pellux/goodvibes-sdk/platform/agents';
|
|
4
3
|
import type { WrfcController } from '@pellux/goodvibes-sdk/platform/agents';
|
|
5
4
|
import { BasePanel } from './base-panel.ts';
|
|
6
5
|
import type { WorkflowEvent } from '@/runtime/index.ts';
|
|
7
6
|
import type { UiEventFeed } from '../runtime/ui-events.ts';
|
|
8
7
|
import {
|
|
8
|
+
buildMeterLine,
|
|
9
9
|
buildPanelLine,
|
|
10
10
|
buildPanelWorkspace,
|
|
11
11
|
resolveScrollablePanelSection,
|
|
12
12
|
DEFAULT_PANEL_PALETTE,
|
|
13
|
-
extendPalette,
|
|
14
13
|
type PanelWorkspaceSection,
|
|
15
14
|
buildSelectablePanelLine,
|
|
16
15
|
buildStyledPanelLine,
|
|
@@ -22,6 +21,9 @@ import {
|
|
|
22
21
|
handleConfirmInput,
|
|
23
22
|
} from './confirm-state.ts';
|
|
24
23
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
24
|
+
import { formatDuration } from '../utils/format-duration.ts';
|
|
25
|
+
import { AgentInspectorPanel } from './agent-inspector-panel.ts';
|
|
26
|
+
import type { PanelIntegrationContext } from './types.ts';
|
|
25
27
|
|
|
26
28
|
// ---------------------------------------------------------------------------
|
|
27
29
|
// Constants
|
|
@@ -45,131 +47,21 @@ const TERMINAL_STATES: readonly WrfcState[] = ['passed', 'failed'];
|
|
|
45
47
|
const RESUMABLE_STATES: readonly WrfcState[] = ['pending', 'reviewing', 'fixing', 'awaiting_gates'];
|
|
46
48
|
|
|
47
49
|
// ---------------------------------------------------------------------------
|
|
48
|
-
// Colour palette
|
|
50
|
+
// Colour palette + formatting helpers live in ./wrfc-panel-format.ts (extracted
|
|
51
|
+
// to keep this module under the architecture line-count cap). Re-exported here
|
|
52
|
+
// so ./wrfc-panel.ts remains their public import site.
|
|
49
53
|
// ---------------------------------------------------------------------------
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
// Issue-severity ramp (domain -- no shared equivalent)
|
|
63
|
-
issueCrit: '#ef4444',
|
|
64
|
-
issueMaj: '#f97316',
|
|
65
|
-
issueMin: '#eab308',
|
|
66
|
-
issueSug: '#6b7280',
|
|
67
|
-
|
|
68
|
-
// Selection + divider chrome with no shared equivalent
|
|
69
|
-
selected: '#1e40af', // selection bg
|
|
70
|
-
selectedFg: '#f8fafc',
|
|
71
|
-
border: '#334155',
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// ---------------------------------------------------------------------------
|
|
75
|
-
// Helpers
|
|
76
|
-
// ---------------------------------------------------------------------------
|
|
77
|
-
const SPARKLINE_CHARS = '._-:=+*#';
|
|
78
|
-
|
|
79
|
-
export function sparkline(scores: number[], maxScore = 10): string {
|
|
80
|
-
if (scores.length === 0) return '';
|
|
81
|
-
return scores
|
|
82
|
-
.map(s => {
|
|
83
|
-
const ratio = Math.max(0, Math.min(1, s / maxScore));
|
|
84
|
-
const idx = Math.round(ratio * (SPARKLINE_CHARS.length - 1));
|
|
85
|
-
return SPARKLINE_CHARS[idx];
|
|
86
|
-
})
|
|
87
|
-
.join('');
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function stateColor(state: WrfcState): string {
|
|
91
|
-
switch (state) {
|
|
92
|
-
case 'passed': return C.passed;
|
|
93
|
-
case 'failed': return C.failed;
|
|
94
|
-
case 'reviewing': return C.reviewing;
|
|
95
|
-
case 'engineering': return C.engineering;
|
|
96
|
-
case 'fixing': return C.fixing;
|
|
97
|
-
case 'gating':
|
|
98
|
-
case 'awaiting_gates': return C.gating;
|
|
99
|
-
case 'committing': return C.committing;
|
|
100
|
-
case 'integrating': return C.integrating;
|
|
101
|
-
default: return C.pending;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export function stateLabel(state: WrfcState): string {
|
|
106
|
-
switch (state) {
|
|
107
|
-
case 'engineering': return 'ENG';
|
|
108
|
-
case 'reviewing': return 'REV';
|
|
109
|
-
case 'fixing': return 'FIX';
|
|
110
|
-
case 'gating': return 'GATE';
|
|
111
|
-
case 'awaiting_gates': return 'WAIT';
|
|
112
|
-
case 'committing': return 'COMMIT';
|
|
113
|
-
case 'integrating': return 'INTG';
|
|
114
|
-
case 'passed': return 'PASS';
|
|
115
|
-
case 'failed': return 'FAIL';
|
|
116
|
-
default: return 'PEND';
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function issueColor(severity: string): string {
|
|
121
|
-
switch (severity) {
|
|
122
|
-
case 'critical': return C.issueCrit;
|
|
123
|
-
case 'major': return C.issueMaj;
|
|
124
|
-
case 'minor': return C.issueMin;
|
|
125
|
-
default: return C.issueSug;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function issuePrefix(severity: string): string {
|
|
130
|
-
switch (severity) {
|
|
131
|
-
case 'critical': return '[CRIT] ';
|
|
132
|
-
case 'major': return '[MAJR] ';
|
|
133
|
-
case 'minor': return '[MINR] ';
|
|
134
|
-
default: return '[SUGG] ';
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export function truncate(s: string, max: number): string {
|
|
139
|
-
if (s.length <= max) return s;
|
|
140
|
-
return s.slice(0, max - 3) + '...';
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// ---------------------------------------------------------------------------
|
|
144
|
-
// Constraint helpers
|
|
145
|
-
// ---------------------------------------------------------------------------
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Returns display tag, foreground colour, and dim flag for a single constraint
|
|
149
|
-
* based on whether a reviewer finding exists for it.
|
|
150
|
-
*/
|
|
151
|
-
export function constraintStatusMarker(
|
|
152
|
-
constraint: Constraint,
|
|
153
|
-
findings: ConstraintFinding[] | undefined,
|
|
154
|
-
): { tag: string; fg: string; dim: boolean } {
|
|
155
|
-
const finding = findings?.find(f => f.constraintId === constraint.id);
|
|
156
|
-
if (!finding) {
|
|
157
|
-
return { tag: '[UNV]', fg: C.dim, dim: true };
|
|
158
|
-
}
|
|
159
|
-
if (finding.satisfied) {
|
|
160
|
-
return { tag: '[SAT]', fg: C.good, dim: false };
|
|
161
|
-
}
|
|
162
|
-
// Unsatisfied — use severity to pick colour and tag text
|
|
163
|
-
const sev = finding.severity ?? 'major';
|
|
164
|
-
let sevTag: string;
|
|
165
|
-
let fg: string;
|
|
166
|
-
switch (sev) {
|
|
167
|
-
case 'critical': sevTag = '[UNS CRIT]'; fg = C.issueCrit; break;
|
|
168
|
-
case 'minor': sevTag = '[UNS MINOR]'; fg = C.issueMin; break;
|
|
169
|
-
default: sevTag = '[UNS MAJOR]'; fg = C.issueMaj; break;
|
|
170
|
-
}
|
|
171
|
-
return { tag: sevTag, fg, dim: false };
|
|
172
|
-
}
|
|
54
|
+
import {
|
|
55
|
+
C,
|
|
56
|
+
sparkline,
|
|
57
|
+
stateColor,
|
|
58
|
+
stateLabel,
|
|
59
|
+
issueColor,
|
|
60
|
+
issuePrefix,
|
|
61
|
+
truncate,
|
|
62
|
+
constraintStatusMarker,
|
|
63
|
+
} from './wrfc-panel-format.ts';
|
|
64
|
+
export { sparkline, stateColor, stateLabel, truncate, constraintStatusMarker } from './wrfc-panel-format.ts';
|
|
173
65
|
|
|
174
66
|
// ---------------------------------------------------------------------------
|
|
175
67
|
// Panel
|
|
@@ -184,6 +76,17 @@ export class WrfcPanel extends BasePanel {
|
|
|
184
76
|
private chains: WrfcChain[] = [];
|
|
185
77
|
private selectedIndex = 0;
|
|
186
78
|
private scrollOffset = 0;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The chain under the cursor. This panel owns its own selection state
|
|
82
|
+
* (`selectedIndex` navigates `this.chains` directly), so every selected-row
|
|
83
|
+
* read routes through this one accessor — indexing `this.chains` by the
|
|
84
|
+
* cursor directly is banned by the no-raw-selectedindex-read architecture rule.
|
|
85
|
+
*/
|
|
86
|
+
private selectedChain(): WrfcChain | undefined {
|
|
87
|
+
return this.chains.at(this.selectedIndex);
|
|
88
|
+
}
|
|
89
|
+
|
|
187
90
|
private expandedChainIds = new Set<string>();
|
|
188
91
|
private unsubscribers: Array<() => void> = [];
|
|
189
92
|
/** Last event timestamp per chain id, for stall detection. */
|
|
@@ -248,10 +151,64 @@ export class WrfcPanel extends BasePanel {
|
|
|
248
151
|
case 'enter': this.toggleExpanded(); return true;
|
|
249
152
|
case 'c': this.beginCancelConfirm(); return true;
|
|
250
153
|
case 'r': this.doResume(); return true;
|
|
154
|
+
// 'a' jumps to the selected chain's owner agent in the Inspector. The
|
|
155
|
+
// actual navigation happens in handlePanelIntegrationAction (it needs
|
|
156
|
+
// the PanelManager); consuming the key here just requires a selected
|
|
157
|
+
// chain to exist so the router below fires next.
|
|
158
|
+
case 'a': return this.selectedChain() !== undefined;
|
|
251
159
|
default: return false;
|
|
252
160
|
}
|
|
253
161
|
}
|
|
254
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Cross-panel jump: 'a' on a selected chain opens the Inspector focused on
|
|
165
|
+
* that chain's owner agent (inspectAgent, reverse of the forward jump
|
|
166
|
+
* agent-detail-modal.ts makes from an agent record's wrfcId to its chain).
|
|
167
|
+
*/
|
|
168
|
+
handlePanelIntegrationAction(key: string, ctx: PanelIntegrationContext): boolean {
|
|
169
|
+
if (key !== 'a') return false;
|
|
170
|
+
const chain = this.selectedChain();
|
|
171
|
+
if (!chain) return false;
|
|
172
|
+
const inspector = ctx.panelManager.open('inspector');
|
|
173
|
+
if (inspector instanceof AgentInspectorPanel) {
|
|
174
|
+
inspector.inspectAgent(chain.ownerAgentId);
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Mouse-wheel scroll: pan the chain list viewport without moving the
|
|
182
|
+
* selection cursor (BasePanel's default forwards wheel deltas into
|
|
183
|
+
* handleInput('up'/'down'), which would also move the selected row).
|
|
184
|
+
* scrollOffset is a hint consumed by resolveScrollablePanelSection's
|
|
185
|
+
* tracked window on the next render, which clamps it into range and keeps
|
|
186
|
+
* the selected row inside the guard band — so an over-large delta here is
|
|
187
|
+
* always safe.
|
|
188
|
+
*/
|
|
189
|
+
override handleScroll(deltaRows: number): boolean {
|
|
190
|
+
if (this.confirmCancel) return false;
|
|
191
|
+
const rows = Math.trunc(deltaRows);
|
|
192
|
+
if (rows === 0) return false;
|
|
193
|
+
const next = Math.max(0, this.scrollOffset + rows);
|
|
194
|
+
if (next === this.scrollOffset) return false;
|
|
195
|
+
this.scrollOffset = next;
|
|
196
|
+
this.markDirty();
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Cross-panel jump target: select the chain by id and expand it. Used by
|
|
202
|
+
* WorkPlanPanel's 'w' jump key when an item links a wrfcId.
|
|
203
|
+
*/
|
|
204
|
+
selectChain(chainId: string): void {
|
|
205
|
+
const index = this.chains.findIndex((chain) => chain.id === chainId);
|
|
206
|
+
if (index < 0) return;
|
|
207
|
+
this.selectedIndex = index;
|
|
208
|
+
this.expandedChainIds.add(chainId);
|
|
209
|
+
this.markDirty();
|
|
210
|
+
}
|
|
211
|
+
|
|
255
212
|
// -------------------------------------------------------------------------
|
|
256
213
|
// Render
|
|
257
214
|
// -------------------------------------------------------------------------
|
|
@@ -269,7 +226,10 @@ export class WrfcPanel extends BasePanel {
|
|
|
269
226
|
width,
|
|
270
227
|
' No WRFC chains yet',
|
|
271
228
|
'WRFC chains appear here as review/fix cycles execute. Expanded rows show scores, gates, issues, and failure detail.',
|
|
272
|
-
[
|
|
229
|
+
[
|
|
230
|
+
{ command: '/teamwork create-mode local-engineer <task>', summary: 'run an engineer task through the write-review-fix-commit loop (reviewMode: wrfc)' },
|
|
231
|
+
{ command: '/teamwork modes', summary: 'list every teamwork mode and see which ones use reviewMode wrfc' },
|
|
232
|
+
],
|
|
273
233
|
DEFAULT_PANEL_PALETTE,
|
|
274
234
|
),
|
|
275
235
|
},
|
|
@@ -303,21 +263,25 @@ export class WrfcPanel extends BasePanel {
|
|
|
303
263
|
const stalled = this.isStalled(chain, now);
|
|
304
264
|
|
|
305
265
|
if (isSelected) selectedLineIndex = chainLines.length;
|
|
306
|
-
chainLines.push(...this.renderChainRow(chain, width, isSelected, isExpanded, rowBg, rowFg, stalled));
|
|
266
|
+
chainLines.push(...this.renderChainRow(chain, width, isSelected, isExpanded, rowBg, rowFg, stalled, now));
|
|
307
267
|
|
|
308
268
|
if (isExpanded) {
|
|
309
269
|
chainLines.push(...this.renderChainDetail(chain, width, 12));
|
|
310
270
|
}
|
|
311
271
|
}
|
|
312
272
|
|
|
313
|
-
const selectedChain = this.
|
|
273
|
+
const selectedChain = this.selectedChain();
|
|
314
274
|
const selectedLines: Line[] = selectedChain
|
|
315
275
|
? [
|
|
316
276
|
buildPanelLine(width, [
|
|
317
277
|
[' State ', DEFAULT_PANEL_PALETTE.label],
|
|
318
278
|
[stateLabel(selectedChain.state), stateColor(selectedChain.state)],
|
|
319
|
-
['
|
|
320
|
-
[
|
|
279
|
+
[TERMINAL_STATES.includes(selectedChain.state) ? ' Duration ' : ' Elapsed ', DEFAULT_PANEL_PALETTE.label],
|
|
280
|
+
[this.durationLabel(selectedChain, now), DEFAULT_PANEL_PALETTE.info],
|
|
281
|
+
]),
|
|
282
|
+
buildPanelLine(width, [
|
|
283
|
+
[' Task ', DEFAULT_PANEL_PALETTE.label],
|
|
284
|
+
[truncate(selectedChain.task, Math.max(8, width - 8)), DEFAULT_PANEL_PALETTE.value],
|
|
321
285
|
]),
|
|
322
286
|
buildPanelLine(width, [
|
|
323
287
|
[' Reviews ', DEFAULT_PANEL_PALETTE.label],
|
|
@@ -344,6 +308,10 @@ export class WrfcPanel extends BasePanel {
|
|
|
344
308
|
]
|
|
345
309
|
: [];
|
|
346
310
|
|
|
311
|
+
const totalChains = this.chains.length;
|
|
312
|
+
const stalledCount = this.chains.filter((c) => this.isStalled(c, now)).length;
|
|
313
|
+
const completedChains = passedCount + failedCount;
|
|
314
|
+
const meterWidth = Math.max(10, Math.min(24, width - 34));
|
|
347
315
|
const summarySection: PanelWorkspaceSection = {
|
|
348
316
|
title: 'Summary',
|
|
349
317
|
lines: [
|
|
@@ -354,7 +322,16 @@ export class WrfcPanel extends BasePanel {
|
|
|
354
322
|
[String(passedCount), DEFAULT_PANEL_PALETTE.good],
|
|
355
323
|
[' Failed ', DEFAULT_PANEL_PALETTE.label],
|
|
356
324
|
[String(failedCount), failedCount > 0 ? DEFAULT_PANEL_PALETTE.bad : DEFAULT_PANEL_PALETTE.dim],
|
|
325
|
+
[' Stalled ', DEFAULT_PANEL_PALETTE.label],
|
|
326
|
+
[String(stalledCount), stalledCount > 0 ? C.warn : DEFAULT_PANEL_PALETTE.dim],
|
|
357
327
|
]),
|
|
328
|
+
buildMeterLine(
|
|
329
|
+
width,
|
|
330
|
+
totalChains > 0 ? Math.round((completedChains / totalChains) * meterWidth) : 0,
|
|
331
|
+
meterWidth,
|
|
332
|
+
{ filled: failedCount > 0 ? DEFAULT_PANEL_PALETTE.bad : DEFAULT_PANEL_PALETTE.good, empty: C.border, label: DEFAULT_PANEL_PALETTE.label },
|
|
333
|
+
{ prefix: ' Resolved ', suffix: ` ${completedChains}/${totalChains} ` },
|
|
334
|
+
),
|
|
358
335
|
],
|
|
359
336
|
};
|
|
360
337
|
const selectedSection: PanelWorkspaceSection = {
|
|
@@ -379,7 +356,7 @@ export class WrfcPanel extends BasePanel {
|
|
|
379
356
|
} : null;
|
|
380
357
|
|
|
381
358
|
// Footer: show resume-disabled reason for the selected chain.
|
|
382
|
-
const selectedForFooter = this.
|
|
359
|
+
const selectedForFooter = this.selectedChain();
|
|
383
360
|
const resumeReason = selectedForFooter ? this.resumeDisabledReason(selectedForFooter) : null;
|
|
384
361
|
const footerLines: Line[] = [
|
|
385
362
|
buildPanelLine(width, [
|
|
@@ -388,6 +365,7 @@ export class WrfcPanel extends BasePanel {
|
|
|
388
365
|
[' c', DEFAULT_PANEL_PALETTE.info], [' cancel', DEFAULT_PANEL_PALETTE.dim],
|
|
389
366
|
[' r', resumeReason ? DEFAULT_PANEL_PALETTE.dim : DEFAULT_PANEL_PALETTE.info],
|
|
390
367
|
[resumeReason ? ` resume (${resumeReason})` : ' resume', DEFAULT_PANEL_PALETTE.dim],
|
|
368
|
+
[' a', DEFAULT_PANEL_PALETTE.info], [' jump to agent', DEFAULT_PANEL_PALETTE.dim],
|
|
391
369
|
]),
|
|
392
370
|
];
|
|
393
371
|
|
|
@@ -449,6 +427,7 @@ export class WrfcPanel extends BasePanel {
|
|
|
449
427
|
bg: string,
|
|
450
428
|
fg: string,
|
|
451
429
|
stalled = false,
|
|
430
|
+
now: number = Date.now(),
|
|
452
431
|
): Line[] {
|
|
453
432
|
const stateCol = stalled ? C.warn : stateColor(chain.state);
|
|
454
433
|
const stateTag = stalled
|
|
@@ -463,6 +442,7 @@ export class WrfcPanel extends BasePanel {
|
|
|
463
442
|
? ` ${chain.reviewScores[chain.reviewScores.length - 1].toFixed(1)}/10`
|
|
464
443
|
: '';
|
|
465
444
|
const stalledBadge = stalled ? ' [STALLED]' : '';
|
|
445
|
+
const durationTag = ` ${this.durationLabel(chain, now)}`;
|
|
466
446
|
// Constraint badge: c:sat/total — only when constraints exist
|
|
467
447
|
let constraintBadge = '';
|
|
468
448
|
if (chain.constraints.length > 0) {
|
|
@@ -471,7 +451,7 @@ export class WrfcPanel extends BasePanel {
|
|
|
471
451
|
const satisfied = findings ? findings.filter(f => f.satisfied).length : 0;
|
|
472
452
|
constraintBadge = ` c:${satisfied}/${total}`;
|
|
473
453
|
}
|
|
474
|
-
const rightInfo = `${stalledBadge}${latestScore}${fixes}${cycles}${constraintBadge} `;
|
|
454
|
+
const rightInfo = `${stalledBadge}${durationTag}${latestScore}${fixes}${cycles}${constraintBadge} `;
|
|
475
455
|
|
|
476
456
|
// Compute how much space the task text can use, then check if rightInfo fits.
|
|
477
457
|
// If the terminal is narrow and rightInfo would overflow, omit it entirely
|
|
@@ -529,6 +509,11 @@ export class WrfcPanel extends BasePanel {
|
|
|
529
509
|
}
|
|
530
510
|
|
|
531
511
|
private renderChainDetail(chain: WrfcChain, width: number, maxLines: number): Line[] {
|
|
512
|
+
// Build the FULL detail content first (no per-section maxLines guards
|
|
513
|
+
// below), then apply one cap at the end so a truncation always ends with
|
|
514
|
+
// a visible "+N more" indicator instead of silently dropping content —
|
|
515
|
+
// the constraint sub-list keeps its own independent (+N more) cap since
|
|
516
|
+
// that is documented, unrelated badge/marker behavior (PRESERVE).
|
|
532
517
|
const lines: Line[] = [];
|
|
533
518
|
const indent = ' ';
|
|
534
519
|
|
|
@@ -555,13 +540,12 @@ export class WrfcPanel extends BasePanel {
|
|
|
555
540
|
}
|
|
556
541
|
|
|
557
542
|
// Constraints section (between Cycles and Gates)
|
|
558
|
-
if (chain.constraints.length > 0
|
|
543
|
+
if (chain.constraints.length > 0) {
|
|
559
544
|
lines.push(buildStyledPanelLine(width, [{ text: `${indent}Constraints`, fg: C.label }]));
|
|
560
545
|
const MAX_CONSTRAINTS = 10;
|
|
561
546
|
const findings = chain.reviewerReport?.constraintFindings;
|
|
562
547
|
const displayed = chain.constraints.slice(0, MAX_CONSTRAINTS);
|
|
563
548
|
for (const constraint of displayed) {
|
|
564
|
-
if (lines.length >= maxLines) break;
|
|
565
549
|
const marker = constraintStatusMarker(constraint, findings);
|
|
566
550
|
const statusTag = marker.tag;
|
|
567
551
|
const rowPrefix = `${indent} ${statusTag} `;
|
|
@@ -575,7 +559,7 @@ export class WrfcPanel extends BasePanel {
|
|
|
575
559
|
]));
|
|
576
560
|
}
|
|
577
561
|
const remaining = chain.constraints.length - MAX_CONSTRAINTS;
|
|
578
|
-
if (remaining > 0
|
|
562
|
+
if (remaining > 0) {
|
|
579
563
|
lines.push(buildStyledPanelLine(width, [
|
|
580
564
|
{ text: `${indent} (+${remaining} more)`, fg: C.dim, dim: true },
|
|
581
565
|
]));
|
|
@@ -586,7 +570,6 @@ export class WrfcPanel extends BasePanel {
|
|
|
586
570
|
if (chain.gateResults && chain.gateResults.length > 0) {
|
|
587
571
|
lines.push(buildStyledPanelLine(width, [{ text: `${indent}Gates`, fg: C.label }]));
|
|
588
572
|
for (const gate of chain.gateResults) {
|
|
589
|
-
if (lines.length >= maxLines) break;
|
|
590
573
|
lines.push(this.renderGateResult(gate, width, indent + ' '));
|
|
591
574
|
}
|
|
592
575
|
}
|
|
@@ -599,10 +582,9 @@ export class WrfcPanel extends BasePanel {
|
|
|
599
582
|
}
|
|
600
583
|
|
|
601
584
|
// Synthetic issues injected by controller (continuity violations)
|
|
602
|
-
if (chain.syntheticIssues && chain.syntheticIssues.length > 0
|
|
585
|
+
if (chain.syntheticIssues && chain.syntheticIssues.length > 0) {
|
|
603
586
|
lines.push(buildStyledPanelLine(width, [{ text: `${indent}Controller flags`, fg: C.issueCrit, bold: true }]));
|
|
604
587
|
for (const synthetic of chain.syntheticIssues) {
|
|
605
|
-
if (lines.length >= maxLines) break;
|
|
606
588
|
const prefix = `${indent} [CRIT] `;
|
|
607
589
|
const descMax = Math.max(8, width - prefix.length);
|
|
608
590
|
const desc = truncate(synthetic.description, descMax);
|
|
@@ -615,10 +597,9 @@ export class WrfcPanel extends BasePanel {
|
|
|
615
597
|
|
|
616
598
|
// Issues from reviewer
|
|
617
599
|
const issues = chain.reviewerReport?.issues ?? [];
|
|
618
|
-
if (issues.length > 0
|
|
600
|
+
if (issues.length > 0) {
|
|
619
601
|
lines.push(buildStyledPanelLine(width, [{ text: `${indent}Issues`, fg: C.label }]));
|
|
620
602
|
for (const issue of issues) {
|
|
621
|
-
if (lines.length >= maxLines) break;
|
|
622
603
|
const prefix = `${indent} ${issuePrefix(issue.severity)}`;
|
|
623
604
|
const descMax = width - prefix.length;
|
|
624
605
|
const desc = truncate(issue.description, Math.max(8, descMax));
|
|
@@ -630,7 +611,7 @@ export class WrfcPanel extends BasePanel {
|
|
|
630
611
|
}
|
|
631
612
|
|
|
632
613
|
// Error
|
|
633
|
-
if (chain.error
|
|
614
|
+
if (chain.error) {
|
|
634
615
|
const errPrefix = `${indent}Error `;
|
|
635
616
|
lines.push(buildStyledPanelLine(width, [
|
|
636
617
|
{ text: errPrefix, fg: C.failed, bold: true },
|
|
@@ -639,11 +620,15 @@ export class WrfcPanel extends BasePanel {
|
|
|
639
620
|
}
|
|
640
621
|
|
|
641
622
|
// Divider after expanded section
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
623
|
+
lines.push(buildStyledPanelLine(width, [{ text: ' ' + '-'.repeat(Math.max(0, width - 4)) + ' ', fg: C.border, dim: true }]));
|
|
624
|
+
|
|
625
|
+
if (lines.length <= maxLines) return lines;
|
|
626
|
+
const visible = lines.slice(0, Math.max(0, maxLines - 1));
|
|
627
|
+
const hidden = lines.length - visible.length;
|
|
628
|
+
visible.push(buildStyledPanelLine(width, [
|
|
629
|
+
{ text: `${indent}+${hidden} more (press a to inspect the owner agent for full detail)`, fg: C.dim, dim: true },
|
|
630
|
+
]));
|
|
631
|
+
return visible;
|
|
647
632
|
}
|
|
648
633
|
|
|
649
634
|
private renderGateResult(gate: QualityGateResult, width: number, indent: string): Line {
|
|
@@ -671,7 +656,7 @@ export class WrfcPanel extends BasePanel {
|
|
|
671
656
|
}
|
|
672
657
|
|
|
673
658
|
private toggleExpanded(): void {
|
|
674
|
-
const chain = this.
|
|
659
|
+
const chain = this.selectedChain();
|
|
675
660
|
if (!chain) return;
|
|
676
661
|
if (this.expandedChainIds.has(chain.id)) {
|
|
677
662
|
this.expandedChainIds.delete(chain.id);
|
|
@@ -754,7 +739,7 @@ export class WrfcPanel extends BasePanel {
|
|
|
754
739
|
|
|
755
740
|
/** Initiate cancel-confirm flow for the selected chain (noop if terminal). */
|
|
756
741
|
private beginCancelConfirm(): void {
|
|
757
|
-
const chain = this.
|
|
742
|
+
const chain = this.selectedChain();
|
|
758
743
|
if (!chain || TERMINAL_STATES.includes(chain.state)) return;
|
|
759
744
|
this.confirmCancel = {
|
|
760
745
|
subject: chain.id,
|
|
@@ -769,7 +754,7 @@ export class WrfcPanel extends BasePanel {
|
|
|
769
754
|
* Emits a visible noop reason when the chain is not resumable.
|
|
770
755
|
*/
|
|
771
756
|
private doResume(): void {
|
|
772
|
-
const chain = this.
|
|
757
|
+
const chain = this.selectedChain();
|
|
773
758
|
if (!chain) return;
|
|
774
759
|
if (!RESUMABLE_STATES.includes(chain.state)) return;
|
|
775
760
|
this.deps.controller.resumeChain(chain.id);
|
|
@@ -791,4 +776,15 @@ export class WrfcPanel extends BasePanel {
|
|
|
791
776
|
const last = this.lastEventAt.get(chain.id) ?? chain.createdAt;
|
|
792
777
|
return (now - last) >= STALL_THRESHOLD_MS;
|
|
793
778
|
}
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Elapsed time since createdAt for an active chain, or total duration from
|
|
782
|
+
* createdAt to completedAt for a terminal one. createdAt/completedAt are
|
|
783
|
+
* already consumed for chain sort order (syncFromController) — this just
|
|
784
|
+
* surfaces the same fields as a human-readable label.
|
|
785
|
+
*/
|
|
786
|
+
private durationLabel(chain: WrfcChain, now: number): string {
|
|
787
|
+
const end = chain.completedAt ?? now;
|
|
788
|
+
return formatDuration(Math.max(0, end - chain.createdAt));
|
|
789
|
+
}
|
|
794
790
|
}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { estimateConversationTokens } from '@pellux/goodvibes-sdk/platform/core';
|
|
20
|
+
import { computeContextUsage } from '../core/context-usage.ts';
|
|
20
21
|
import type { CompactionEvent } from '@pellux/goodvibes-sdk/platform/core';
|
|
21
22
|
import type { ProviderMessage } from '@pellux/goodvibes-sdk/platform/providers';
|
|
22
23
|
|
|
@@ -58,7 +59,7 @@ export function buildCompactionPreview(opts: CompactionPreviewOptions): string {
|
|
|
58
59
|
const tokensAfterEstimate = COMPACTION_OUTPUT_CEILING_ESTIMATE;
|
|
59
60
|
|
|
60
61
|
const contextStr = contextWindow > 0
|
|
61
|
-
? ` (${Math.round((tokensBefore
|
|
62
|
+
? ` (${Math.round(computeContextUsage(tokensBefore, contextWindow).rawRatio * 100)}% of ${fmtN(contextWindow)} context window)`
|
|
62
63
|
: '';
|
|
63
64
|
|
|
64
65
|
const pinStr = pinnedMemoryCount > 0
|