@pellux/goodvibes-tui 1.7.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/README.md +10 -9
- 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/composer-state.ts +11 -3
- package/src/core/conversation-line-cache.ts +27 -3
- package/src/core/conversation-rendering.ts +71 -14
- 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 +68 -1
- package/src/core/turn-cancellation.ts +7 -2
- package/src/core/turn-event-wiring.ts +10 -2
- 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/input/autocomplete.ts +27 -1
- package/src/input/command-registry.ts +46 -4
- package/src/input/commands/codebase-runtime.ts +46 -6
- package/src/input/commands/config.ts +43 -3
- package/src/input/commands/health-runtime.ts +9 -1
- package/src/input/commands/memory.ts +68 -35
- package/src/input/commands/operator-panel-runtime.ts +31 -7
- package/src/input/commands/planning-runtime.ts +95 -6
- package/src/input/commands/qrcode-runtime.ts +25 -5
- package/src/input/commands/remote-runtime-setup.ts +5 -3
- package/src/input/commands/session-content.ts +20 -9
- package/src/input/commands/settings-sync-runtime.ts +15 -3
- package/src/input/commands/shell-core.ts +10 -1
- package/src/input/commands/workstream-runtime.ts +168 -18
- package/src/input/config-modal-types.ts +15 -1
- package/src/input/config-modal.ts +227 -12
- package/src/input/feed-context-factory.ts +3 -0
- package/src/input/handler-command-route.ts +10 -3
- package/src/input/handler-content-actions.ts +17 -2
- package/src/input/handler-feed-routes.ts +43 -121
- package/src/input/handler-feed.ts +32 -4
- package/src/input/handler-modal-routes.ts +78 -13
- package/src/input/handler-modal-stack.ts +11 -2
- 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 +15 -8
- package/src/input/handler-shortcuts.ts +59 -9
- package/src/input/handler.ts +6 -1
- package/src/input/keybindings.ts +6 -5
- package/src/input/model-picker.ts +19 -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-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 +51 -2
- package/src/input/settings-modal-types.ts +4 -2
- package/src/main.ts +28 -27
- package/src/panels/builtin/shared.ts +9 -3
- 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 +89 -131
- package/src/panels/fleet-read-model.ts +43 -0
- package/src/panels/fleet-steer.ts +35 -2
- package/src/panels/fleet-stop.ts +29 -1
- package/src/panels/modals/keybindings-modal.ts +16 -1
- package/src/panels/modals/modal-theme.ts +35 -29
- package/src/panels/modals/pairing-modal.ts +25 -6
- package/src/panels/modals/planning-modal.ts +43 -21
- package/src/panels/modals/work-plan-modal.ts +28 -0
- package/src/panels/panel-manager.ts +15 -4
- package/src/panels/polish-core.ts +38 -25
- package/src/panels/project-planning-answer-actions.ts +8 -13
- package/src/panels/types.ts +24 -0
- package/src/permissions/prompt.ts +160 -13
- package/src/renderer/autocomplete-overlay.ts +28 -2
- package/src/renderer/compositor.ts +2 -3
- package/src/renderer/config-modal.ts +19 -5
- package/src/renderer/footer-tips.ts +5 -1
- package/src/renderer/fullscreen-primitives.ts +32 -22
- package/src/renderer/git-status.ts +3 -1
- 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 +18 -3
- package/src/renderer/overlay-box.ts +21 -17
- 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 +22 -3
- package/src/renderer/ui-factory.ts +154 -85
- package/src/renderer/ui-primitives.ts +30 -129
- package/src/runtime/bootstrap-command-context.ts +6 -0
- package/src/runtime/bootstrap-command-parts.ts +8 -4
- package/src/runtime/bootstrap-core.ts +33 -11
- package/src/runtime/bootstrap-hook-bridge.ts +7 -0
- package/src/runtime/bootstrap-shell.ts +19 -1
- package/src/runtime/bootstrap.ts +118 -5
- package/src/runtime/code-index-services.ts +25 -2
- 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 +10 -0
- package/src/runtime/resume-notice.ts +209 -0
- package/src/runtime/services.ts +12 -8
- 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 +19 -3
- package/src/runtime/workstream-services.ts +160 -28
- package/src/runtime/wrfc-persistence.ts +124 -17
- package/src/shell/blocking-input.ts +46 -3
- package/src/shell/recovery-input-helpers.ts +170 -1
- package/src/shell/ui-openers.ts +42 -9
- package/src/utils/terminal-width.ts +52 -0
- package/src/version.ts +1 -1
|
@@ -5,15 +5,135 @@
|
|
|
5
5
|
* Shows a list of saved sessions with:
|
|
6
6
|
* - name, timestamp (formatted), message count
|
|
7
7
|
* Footer hints: [Enter] Load [d] Delete [Esc] Close
|
|
8
|
+
*
|
|
9
|
+
* W3-T2: when the modal was wired with a cross-surface session union
|
|
10
|
+
* (`modal.crossSurfaceView.mode !== 'local'`), an additional read-only
|
|
11
|
+
* "Cross-surface sessions" section is appended, badged kind/status/project
|
|
12
|
+
* (parity with the webui SessionsView) with one of three honest states —
|
|
13
|
+
* true-empty, offline, or stale — never a silently-collapsed list. In
|
|
14
|
+
* 'local' mode (no sessionBroker wired, e.g. every pre-existing caller/test)
|
|
15
|
+
* this section is entirely absent and the box sizing is unchanged.
|
|
8
16
|
*/
|
|
9
17
|
|
|
10
18
|
import type { Line } from '../types/grid.ts';
|
|
19
|
+
import type { SharedSessionRecord } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
11
20
|
import { ModalFactory } from './modal-factory.ts';
|
|
21
|
+
import { UI_TONES } from './ui-primitives.ts';
|
|
12
22
|
import type { SessionPickerModal } from '../input/session-picker-modal.ts';
|
|
13
23
|
import { formatTimestamp } from './modal-utils.ts';
|
|
14
24
|
import { fitDisplay } from '../utils/terminal-width.ts';
|
|
15
25
|
import { getOverlaySurfaceMetrics, getStableOverlayContentRows } from './overlay-viewport.ts';
|
|
16
26
|
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Cross-surface badge helpers (parity with webui's src/lib/sessions-union.ts)
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
/** Verbatim for unknown/future kinds — never dropped, never guessed. */
|
|
32
|
+
function kindLabel(kind: string): string {
|
|
33
|
+
return kind.trim() || 'unknown';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function projectLabel(project: string): string {
|
|
37
|
+
return project.trim() || 'unknown';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isClosedStatus(status: string): boolean {
|
|
41
|
+
return status.trim().toLowerCase() === 'closed';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* D-TUI/#A2: closed sessions carry an optional, honest reason for WHY they
|
|
46
|
+
* closed under `metadata.closeReason` (SDK's `SharedSessionCloseReason`,
|
|
47
|
+
* 'closeReason' key — see `@pellux/goodvibes-sdk` platform/control-plane
|
|
48
|
+
* session-broker-sessions.ts's `readSessionCloseReason`). `metadata` is an
|
|
49
|
+
* open record so old readers and records from a build that predates this
|
|
50
|
+
* field ignore it safely — read it duck-typed here rather than importing the
|
|
51
|
+
* SDK's helper, and tolerate `metadata` itself being absent or malformed.
|
|
52
|
+
*/
|
|
53
|
+
function readCloseReason(record: SharedSessionRecord): string | undefined {
|
|
54
|
+
const raw = (record.metadata as Record<string, unknown> | undefined)?.['closeReason'];
|
|
55
|
+
return typeof raw === 'string' ? raw : undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A GC sweep closing an idle session ('idle-reaped') auto-reopens on the next
|
|
60
|
+
* heartbeat — it is NOT the same event as a deliberate user/surface close, so
|
|
61
|
+
* it must never render under the same "closed" badge (W4/#A2). Tolerant of
|
|
62
|
+
* records without the field (pre-feature builds, or a deliberate close).
|
|
63
|
+
*/
|
|
64
|
+
function isReapedRecord(record: SharedSessionRecord): boolean {
|
|
65
|
+
return isClosedStatus(record.status) && readCloseReason(record) === 'idle-reaped';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Wave-4 UX-lens note: 'reaped' names a mechanism (the idle-session sweep),
|
|
70
|
+
* not a state a first-time reader can guess — the webui pairs its own
|
|
71
|
+
* 'reaped' badge with a tooltip explaining it
|
|
72
|
+
* (SessionsView.tsx: "Closed by the idle-session sweep — reopens
|
|
73
|
+
* automatically on the next activity"). The TUI has no hover/tooltip
|
|
74
|
+
* surface, so the plain-language explanation is rendered as its own short
|
|
75
|
+
* line under the cross-surface list instead — shown ONLY when at least one
|
|
76
|
+
* visible row actually carries the badge, so it never adds noise to a list
|
|
77
|
+
* with no reaped rows.
|
|
78
|
+
*/
|
|
79
|
+
const REAPED_BADGE_HINT = 'reaped = closed by the idle sweep — reopens on next activity';
|
|
80
|
+
|
|
81
|
+
/** True when at least one of the currently-rendered (post-truncation) cross-surface rows carries the 'reaped' badge. */
|
|
82
|
+
function hasVisibleReapedRow(modal: SessionPickerModal): boolean {
|
|
83
|
+
if (modal.crossSurfaceView.mode === 'local') return false;
|
|
84
|
+
return modal.crossSurfaceSessions.slice(0, MAX_CROSS_SURFACE_ROWS).some(isReapedRecord);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function statusLabel(record: SharedSessionRecord): string {
|
|
88
|
+
const trimmed = record.status.trim();
|
|
89
|
+
if (!trimmed) return 'active';
|
|
90
|
+
if (!isClosedStatus(trimmed)) return trimmed;
|
|
91
|
+
return isReapedRecord(record) ? 'reaped' : 'closed';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const MAX_CROSS_SURFACE_ROWS = 5;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The exact honest note for the current state, or null when the union view
|
|
98
|
+
* needs no caveat (fresh/embedded with rows). Precedence: offline > stale >
|
|
99
|
+
* true-empty — the three states from the W3-T2 brief, never collapsed into
|
|
100
|
+
* each other (an offline view never silently renders as "no sessions yet").
|
|
101
|
+
*/
|
|
102
|
+
function crossSurfaceNote(view: SessionPickerModal['crossSurfaceView'], rowCount: number): string | null {
|
|
103
|
+
if (view.mode === 'local') return null;
|
|
104
|
+
if (view.offlineNote) return `${view.offlineNote} — showing local sessions only`;
|
|
105
|
+
if (view.stale) {
|
|
106
|
+
if (view.lastSyncAt === null) return 'Union view may be stale.';
|
|
107
|
+
const ageSeconds = Math.max(0, Math.round((Date.now() - view.lastSyncAt) / 1000));
|
|
108
|
+
return `Union view may be stale, last synced ${ageSeconds}s ago`;
|
|
109
|
+
}
|
|
110
|
+
if (rowCount === 0) return 'No sessions yet.';
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function crossSurfaceRowCount(modal: SessionPickerModal): number {
|
|
115
|
+
return modal.crossSurfaceView.mode === 'local' ? 0 : Math.min(MAX_CROSS_SURFACE_ROWS, modal.crossSurfaceSessions.length);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Extra content rows the cross-surface section needs: separator + header
|
|
120
|
+
* (+note) + rows (+the "[showing N of M]" overflow line when the union has
|
|
121
|
+
* more records than MAX_CROSS_SURFACE_ROWS — previously uncounted here, which
|
|
122
|
+
* let modal-factory's tail-clip silently eat that trailing line even though
|
|
123
|
+
* the rest of the budget accounting held; see W3 Finding 1).
|
|
124
|
+
*/
|
|
125
|
+
function crossSurfaceExtraRows(modal: SessionPickerModal): number {
|
|
126
|
+
if (modal.crossSurfaceView.mode === 'local') return 0;
|
|
127
|
+
const rows = crossSurfaceRowCount(modal);
|
|
128
|
+
const noteRow = crossSurfaceNote(modal.crossSurfaceView, rows) ? 1 : 0;
|
|
129
|
+
const overflowRow = modal.crossSurfaceSessions.length > MAX_CROSS_SURFACE_ROWS ? 1 : 0;
|
|
130
|
+
// W4 UX-lens: the plain-language 'reaped' explainer is its own row —
|
|
131
|
+
// uncounted here it would be silently eaten by modal-factory's tail-clip
|
|
132
|
+
// exactly the way W3 Finding 1 describes above.
|
|
133
|
+
const reapedHintRow = hasVisibleReapedRow(modal) ? 1 : 0;
|
|
134
|
+
return 2 + noteRow + overflowRow + reapedHintRow + Math.max(rows, noteRow === 1 && rows === 0 ? 0 : rows);
|
|
135
|
+
}
|
|
136
|
+
|
|
17
137
|
// ---------------------------------------------------------------------------
|
|
18
138
|
// Renderer
|
|
19
139
|
// ---------------------------------------------------------------------------
|
|
@@ -29,16 +149,37 @@ export function renderSessionPickerModal(
|
|
|
29
149
|
width: number,
|
|
30
150
|
viewportHeight = 24,
|
|
31
151
|
): Line[] {
|
|
152
|
+
const extraRows = crossSurfaceExtraRows(modal);
|
|
32
153
|
const metrics = getOverlaySurfaceMetrics(width, viewportHeight, {
|
|
33
154
|
chromeRows: 6,
|
|
34
155
|
minContentRows: 5,
|
|
35
|
-
maxContentRows: 9,
|
|
156
|
+
maxContentRows: 9 + extraRows,
|
|
36
157
|
});
|
|
37
158
|
const boxMargin = metrics.margin;
|
|
38
159
|
const boxW = metrics.boxWidth;
|
|
39
160
|
const contentW = metrics.contentWidth;
|
|
40
|
-
const
|
|
41
|
-
|
|
161
|
+
const targetContentRows = getStableOverlayContentRows(metrics.contentRows, 8 + extraRows);
|
|
162
|
+
|
|
163
|
+
// W3 Finding 1: when a cross-surface union section is present, cap the
|
|
164
|
+
// LOCAL list's window to what's left after reserving extraRows for the
|
|
165
|
+
// union — otherwise the local list expands to fill metrics.contentRows
|
|
166
|
+
// (which already includes the union's reservation) and modal-factory's
|
|
167
|
+
// single tail-clip (createModal, targetContentRows) silently drops the
|
|
168
|
+
// union section, or its trailing rows, once the box fills up. In local
|
|
169
|
+
// mode (extraRows === 0, every pre-existing caller/test) this reduces to
|
|
170
|
+
// the exact pre-T2 formula, so local-only output is byte-for-byte
|
|
171
|
+
// unaffected.
|
|
172
|
+
let visibleRows: number;
|
|
173
|
+
if (extraRows === 0) {
|
|
174
|
+
visibleRows = metrics.contentRows;
|
|
175
|
+
} else {
|
|
176
|
+
const localBudget = Math.max(0, targetContentRows - extraRows);
|
|
177
|
+
const overhead = 2; // header + separator row, present whenever sessions.length > 0
|
|
178
|
+
const remaining = Math.max(0, localBudget - overhead);
|
|
179
|
+
visibleRows = modal.sessions.length > remaining
|
|
180
|
+
? Math.max(3, remaining - 2) // reserve 2 more rows for the local "[x-y of N]" overflow line
|
|
181
|
+
: Math.max(3, remaining);
|
|
182
|
+
}
|
|
42
183
|
modal.setVisibleRows(visibleRows);
|
|
43
184
|
|
|
44
185
|
const sections: import('./modal-factory.ts').ModalSection[] = [];
|
|
@@ -97,6 +238,58 @@ export function renderSessionPickerModal(
|
|
|
97
238
|
}
|
|
98
239
|
}
|
|
99
240
|
|
|
241
|
+
// W3-T2: cross-surface session union — visible only when a sessionBroker
|
|
242
|
+
// was wired (mode !== 'local'); absent entirely otherwise, so the box size
|
|
243
|
+
// and content of every pre-existing (local-only) caller is unaffected.
|
|
244
|
+
if (modal.crossSurfaceView.mode !== 'local') {
|
|
245
|
+
sections.push({ type: 'separator' });
|
|
246
|
+
sections.push({
|
|
247
|
+
type: 'text',
|
|
248
|
+
content: `Cross-surface sessions (${modal.crossSurfaceView.mode})`,
|
|
249
|
+
style: { fg: '240', dim: true, bold: true },
|
|
250
|
+
});
|
|
251
|
+
const note = crossSurfaceNote(modal.crossSurfaceView, modal.crossSurfaceSessions.length);
|
|
252
|
+
if (note) {
|
|
253
|
+
sections.push({
|
|
254
|
+
type: 'text',
|
|
255
|
+
content: note,
|
|
256
|
+
style: { fg: modal.crossSurfaceView.offlineNote ? UI_TONES.state.warn : '244', dim: !modal.crossSurfaceView.offlineNote },
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
if (modal.crossSurfaceSessions.length > 0) {
|
|
260
|
+
const rows = modal.crossSurfaceSessions.slice(0, MAX_CROSS_SURFACE_ROWS);
|
|
261
|
+
const listItems: import('./modal-factory.ts').ModalListItem[] = rows.map((record) => {
|
|
262
|
+
const reaped = isReapedRecord(record);
|
|
263
|
+
const closed = isClosedStatus(record.status);
|
|
264
|
+
const label = fitDisplay(
|
|
265
|
+
`${record.title || record.id} · ${kindLabel(record.kind)} · ${statusLabel(record)} · ${projectLabel(record.project)}`,
|
|
266
|
+
contentW,
|
|
267
|
+
);
|
|
268
|
+
// Reaped rows get their own tone (not plain dim-closed): the session
|
|
269
|
+
// will auto-reopen on the next heartbeat, unlike a deliberate close.
|
|
270
|
+
const style = reaped ? { fg: UI_TONES.state.info } : closed ? { fg: '244', dim: true } : undefined;
|
|
271
|
+
return { label, style };
|
|
272
|
+
});
|
|
273
|
+
sections.push({ type: 'list', items: listItems });
|
|
274
|
+
if (modal.crossSurfaceSessions.length > MAX_CROSS_SURFACE_ROWS) {
|
|
275
|
+
sections.push({
|
|
276
|
+
type: 'text',
|
|
277
|
+
content: `[showing ${MAX_CROSS_SURFACE_ROWS} of ${modal.crossSurfaceSessions.length}]`,
|
|
278
|
+
style: { fg: '244', dim: true },
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
// W4 UX-lens: only ever rendered when a visible row actually carries
|
|
282
|
+
// the 'reaped' badge — never speculative noise.
|
|
283
|
+
if (hasVisibleReapedRow(modal)) {
|
|
284
|
+
sections.push({
|
|
285
|
+
type: 'text',
|
|
286
|
+
content: REAPED_BADGE_HINT,
|
|
287
|
+
style: { fg: UI_TONES.state.info, dim: true },
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
100
293
|
// Status message if present
|
|
101
294
|
if (modal.statusMessage) {
|
|
102
295
|
sections.push({ type: 'separator' });
|
|
@@ -59,10 +59,12 @@ export const CATEGORY_LABELS: Record<(typeof SETTINGS_CATEGORIES)[number], strin
|
|
|
59
59
|
storage: 'Storage',
|
|
60
60
|
permissions: 'Permissions',
|
|
61
61
|
orchestration: 'Orchestration',
|
|
62
|
+
planner: 'Planner',
|
|
62
63
|
wrfc: 'WRFC',
|
|
63
64
|
helper: 'Helper',
|
|
64
65
|
tts: 'TTS',
|
|
65
66
|
service: 'Service',
|
|
67
|
+
daemon: 'Daemon',
|
|
66
68
|
controlPlane: 'Control Plane',
|
|
67
69
|
httpListener: 'HTTP Listener',
|
|
68
70
|
web: 'Web',
|
|
@@ -32,10 +32,12 @@ const CATEGORY_INFO: Record<SettingsCategory, string> = {
|
|
|
32
32
|
storage: 'Local storage posture, including secret storage policy and maximum artifact size for knowledge/home graph/document ingestion.',
|
|
33
33
|
permissions: 'Permission mode and tool-class policy. These settings decide whether the shell prompts before read/write/exec/network/agent actions.',
|
|
34
34
|
orchestration: 'Agent orchestration limits and recursion controls.',
|
|
35
|
+
planner: 'How /workstream decomposes a goal into work items: agent-driven decomposition (with heuristic fallback) or the forced heuristic path, plus the planning agent\'s turn, token, and wall-clock bounds.',
|
|
35
36
|
wrfc: 'Work-review-fix-cycle thresholds, retry limits, and automatic commit behavior.',
|
|
36
37
|
helper: 'Helper model defaults used by helper subsystems when they do not use the main chat route.',
|
|
37
38
|
tts: 'Text-to-speech provider, voice, and optional spoken-turn LLM overrides.',
|
|
38
39
|
service: 'Background service posture: enabled state, autostart, restart behavior, service name, platform, and logs.',
|
|
40
|
+
daemon: 'Local session daemon. It hosts the shared session broker and companion chat so a session started here is visible and steerable from other surfaces. On by default, bound to loopback (127.0.0.1) only.',
|
|
39
41
|
controlPlane: 'Daemon control-plane settings for local admin/API access.',
|
|
40
42
|
httpListener: 'HTTP listener settings for webhook and integration ingress.',
|
|
41
43
|
web: 'Browser surface settings for the local or network web UI.',
|
|
@@ -57,6 +59,11 @@ const CATEGORY_INFO: Record<SettingsCategory, string> = {
|
|
|
57
59
|
};
|
|
58
60
|
|
|
59
61
|
const ENUM_VALUE_DESCRIPTIONS: Record<string, Record<string, string>> = {
|
|
62
|
+
'display.themeMode': {
|
|
63
|
+
auto: 'Probe the terminal background colour (OSC 11) once at startup and pick light or dark. Falls back to dark on unreadable/unsupported terminals. Only evaluated at startup — selecting auto takes effect next launch.',
|
|
64
|
+
dark: 'Force the dark theme regardless of terminal background. Applies immediately.',
|
|
65
|
+
light: 'Force the light theme regardless of terminal background. Applies immediately.',
|
|
66
|
+
},
|
|
60
67
|
'behavior.hitlMode': {
|
|
61
68
|
quiet: 'Minimize operational interruptions and surface fewer Human-in-the-Loop prompts.',
|
|
62
69
|
balanced: 'Show important Human-in-the-Loop prompts without turning routine work into noise.',
|
|
@@ -15,7 +15,7 @@ export interface ShellFooterBuildOptions {
|
|
|
15
15
|
* themselves (main.ts does) do not need to also pass this.
|
|
16
16
|
*/
|
|
17
17
|
readonly panelFocused?: boolean;
|
|
18
|
-
readonly usage: { up: number; down: number };
|
|
18
|
+
readonly usage: { up: number; down: number; fleetCostUsd?: number | null };
|
|
19
19
|
readonly showExitNotice: boolean;
|
|
20
20
|
readonly lastCopyTime: number;
|
|
21
21
|
readonly model?: string;
|
|
@@ -47,6 +47,12 @@ export interface ShellFooterBuildOptions {
|
|
|
47
47
|
* the process indicator, context bar, context-info line and posture block.
|
|
48
48
|
*/
|
|
49
49
|
readonly compact?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* S3d: cross-surface session-spine posture for the context-info segment.
|
|
52
|
+
* Set ONLY in adopted-daemon mode ('online'/'offline'); left undefined in
|
|
53
|
+
* embedded/local mode so no segment renders.
|
|
54
|
+
*/
|
|
55
|
+
readonly sessionSpineStatus?: 'online' | 'offline';
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
export interface ShellFooterBuildResult {
|
|
@@ -120,6 +126,7 @@ export function buildShellFooter(
|
|
|
120
126
|
options.composerFlags,
|
|
121
127
|
options.composerPendingRisk,
|
|
122
128
|
options.compact ?? false,
|
|
129
|
+
options.sessionSpineStatus,
|
|
123
130
|
);
|
|
124
131
|
// Compact posture drops the process indicator and context hint entirely so
|
|
125
132
|
// the footer fits within ~5 rows on short terminals.
|
|
@@ -4,20 +4,19 @@
|
|
|
4
4
|
// Extracted as a neutral module so both status-token.ts and polish.ts can
|
|
5
5
|
// import from here without creating a circular ESM dependency.
|
|
6
6
|
//
|
|
7
|
-
// STATE_GLYPHS is
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
7
|
+
// W6-P1: STATE_GLYPHS is no longer hardcoded here. It is the SDK presentation
|
|
8
|
+
// contract (@pellux/goodvibes-sdk/platform/presentation, landed by W4-S1 and
|
|
9
|
+
// already adopted by the agent in W4-R4), aliased to GLYPHS.status so the
|
|
10
|
+
// four semantic glyphs are spelled out in exactly one place and can never
|
|
11
|
+
// drift from the registry again. Re-exported under the historical name so
|
|
12
|
+
// status-token.ts and polish.ts import unchanged.
|
|
13
|
+
//
|
|
14
|
+
// Glyphs (unchanged — this module's historical values, the TUI reference the
|
|
15
|
+
// contract was extracted from):
|
|
16
|
+
// good ✓ (CHECK MARK U+2713) — GLYPHS.status.success
|
|
17
|
+
// warn ⚠ (WARNING SIGN U+26A0) — GLYPHS.status.warn
|
|
18
|
+
// bad ✕ (MULTIPLICATION X U+2715) — GLYPHS.status.failure
|
|
19
|
+
// info ○ (WHITE CIRCLE U+25CB) — GLYPHS.status.info
|
|
12
20
|
// ---------------------------------------------------------------------------
|
|
13
21
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export type StatusState = 'good' | 'warn' | 'bad' | 'info';
|
|
17
|
-
|
|
18
|
-
export const STATE_GLYPHS: Record<StatusState, string> = {
|
|
19
|
-
good: GLYPHS.status.success, // ✓
|
|
20
|
-
warn: GLYPHS.status.warn, // ⚠
|
|
21
|
-
bad: GLYPHS.status.failure, // ✕
|
|
22
|
-
info: GLYPHS.status.info, // ○
|
|
23
|
-
};
|
|
22
|
+
export { STATE_GLYPHS, type StatusState } from '@pellux/goodvibes-sdk/platform/presentation';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type Line } from '../types/grid.ts';
|
|
2
|
-
import { BORDERS
|
|
2
|
+
import { BORDERS } from './layout.ts';
|
|
3
3
|
import { renderConversationNotice } from './conversation-surface.ts';
|
|
4
|
+
import { activeUiTones } from './theme.ts';
|
|
4
5
|
|
|
5
6
|
/** Exported for use by typeOverride callers and tests. */
|
|
6
7
|
export type SystemMessageType = 'error' | 'warning' | 'info';
|
|
@@ -71,12 +72,23 @@ export function renderSystemMessage(
|
|
|
71
72
|
typeOverride?: SystemMessageType,
|
|
72
73
|
): Line[] {
|
|
73
74
|
const msgType = typeOverride ?? classifySystemMessage(content);
|
|
75
|
+
// System-message notices paint the ▌ marker and body text on the TRANSPARENT
|
|
76
|
+
// terminal background (renderConversationNotice passes no bodyBg), so the
|
|
77
|
+
// accent and dim text resolve per-render through activeUiTones() to stay
|
|
78
|
+
// legible on a light terminal. Dark values are byte-identical to the prior
|
|
79
|
+
// static reads (chrome.bad == state.bad, chrome.warn == state.warn,
|
|
80
|
+
// state.info unchanged, chrome.faint == fg.dim). The ▌ marker glyph is still
|
|
81
|
+
// sourced from the BORDERS table.
|
|
82
|
+
const t = activeUiTones();
|
|
74
83
|
const border = msgType === 'error' ? BORDERS.ERROR
|
|
75
84
|
: msgType === 'warning' ? BORDERS.WARNING
|
|
76
85
|
: BORDERS.INFO;
|
|
77
|
-
const
|
|
86
|
+
const accent = msgType === 'error' ? t.chrome.bad
|
|
87
|
+
: msgType === 'warning' ? t.chrome.warn
|
|
88
|
+
: t.state.info;
|
|
89
|
+
const textColor = msgType === 'info' ? t.chrome.faint : accent;
|
|
78
90
|
return renderConversationNotice(content, width, {
|
|
79
|
-
accent
|
|
91
|
+
accent,
|
|
80
92
|
text: textColor,
|
|
81
93
|
dim: msgType === 'info',
|
|
82
94
|
}, border.char);
|