pi-cockpit 0.14.0 → 0.15.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 +4 -1
- package/package.json +3 -3
- package/src/agent-overlay.ts +12 -2
- package/src/agents-store.ts +11 -1
- package/src/bash-bg-overlay.ts +3 -0
- package/src/config.ts +4 -1
- package/src/index.ts +167 -15
- package/src/render.ts +9 -2
- package/src/settings/cockpit-provider.ts +7 -0
- package/src/settings-view.ts +13 -0
- package/src/sidebar-render.ts +5 -2
- package/src/tui-i18n.ts +110 -2
- package/src/types.ts +18 -0
- package/src/viewport-stability.ts +173 -99
- package/src/zen-browse.ts +178 -0
- package/src/zen-render.ts +499 -0
- package/src/zen-sheet.ts +254 -0
- package/themes/cockpit-zen.json +24 -0
package/README.md
CHANGED
|
@@ -66,6 +66,7 @@ Cockpit has no package dependency on `pi-maestro-flow`. It observes optional pro
|
|
|
66
66
|
"agentsMode": "list",
|
|
67
67
|
"todoMode": "list",
|
|
68
68
|
"todoExpanded": false,
|
|
69
|
+
"stackStyle": "classic",
|
|
69
70
|
"hideNativeAgents": true,
|
|
70
71
|
"icons": { "mode": "auto" },
|
|
71
72
|
"sidebar": {
|
|
@@ -91,6 +92,7 @@ Cockpit has no package dependency on `pi-maestro-flow`. It observes optional pro
|
|
|
91
92
|
- `quietSymbols`: `"check"` (✓/✗/⋯) or `"dot"` (●/○/◌) lifecycle glyphs for quiet tool rows.
|
|
92
93
|
- `agentsMode` / `todoMode`: `"list"` or `"compact"`.
|
|
93
94
|
- `todoExpanded`: when `true`, expands the todo widget by default.
|
|
95
|
+
- `stackStyle`: `"classic"` keeps the separate Todo/Agent widgets; `"zen"` projects MISSION, WORK, and ACTORS into one borderless stack. The default remains `"classic"`.
|
|
94
96
|
- `hideNativeAgents`: when `true`, clears the teammate extension's own `teammate-agents` widget (it draws a similar list *below* the editor) so the two don't duplicate. On by default.
|
|
95
97
|
- `sidebar.mode`: `"auto"` or `"on"` enables the dock when at least 72 main columns plus 32 sidebar columns fit; `"off"` always uses widgets.
|
|
96
98
|
- `sidebar.width`: persisted dock width, rounded and clamped to `32..56`; default `40`.
|
|
@@ -114,11 +116,12 @@ The tab title is `frame + pi - <session> - <working state>`. The session part fo
|
|
|
114
116
|
- `/cockpit` — opens the settings overlay.
|
|
115
117
|
- `/maestro-settings` — opens the unified settings shell (Cockpit, Flow, Teammate and integrations).
|
|
116
118
|
- `Alt+J` — opens the background Bash jobs overlay (live status, command, cwd, duration, output tail).
|
|
119
|
+
- `Alt+L` — browses the visible Cockpit surface. In Zen widget mode, arrows or `j/k` select rows, first Enter expands inline details, second Enter opens the entity sheet/overlay, and Esc steps back. In the Agent overlay, `m` makes the selected agent the editor input target.
|
|
117
120
|
- `/cockpit sidebar` — reports the current sidebar mode, width, and density.
|
|
118
121
|
- `/cockpit sidebar auto|on|off` — selects dock behavior.
|
|
119
122
|
- `/cockpit sidebar resize` or `Ctrl+Shift+R` — enters temporary Resize mode. Left/Right adjusts one column, Shift+Left/Shift+Right adjusts four, Enter accepts, and Escape rolls back. Mouse reporting is active only during Resize mode.
|
|
120
123
|
- `/cockpit quiet` — toggles quiet mode; `/cockpit bg` shows background jobs.
|
|
121
|
-
- `/theme` — switch theme with live preview; `/theme <name>` applies directly. Pi ships no standalone theme command; cockpit provides one.
|
|
124
|
+
- `/theme` — switch theme with live preview; `/theme <name>` applies directly. `cockpit-zen` is the restrained warm-gold theme designed for the Zen stack; selecting it does not change `stackStyle`. Pi ships no standalone theme command; cockpit provides one.
|
|
122
125
|
|
|
123
126
|
## Sidebar compatibility
|
|
124
127
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-cockpit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Responsive Maestro operations sidebar, fallback status widgets, and footer for Pi.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"README.md"
|
|
19
19
|
],
|
|
20
20
|
"scripts": {
|
|
21
|
-
"test": "node --test --experimental-transform-types tests/config.test.ts tests/public-events.test.ts tests/maestro-store.test.ts tests/split-pane.test.ts tests/editor-bottom.test.ts tests/sidebar-render.test.ts tests/sidebar-controller.test.ts tests/viewport-stability.test.ts tests/quiet-tools.test.ts tests/edit-guard.test.ts tests/agents-store.test.ts tests/endpoint-store.test.ts tests/session-ui-state.test.ts tests/session-tabs.test.ts tests/agent-bar.test.ts tests/supervision-store.test.ts tests/bash-bg-store.test.ts tests/bash-bg-widget.test.ts tests/bash-bg-overlay.test.ts tests/agent-overlay.test.ts tests/todo-store.test.ts tests/render.test.ts tests/agent-scroll.test.ts tests/session-bar.test.ts tests/session-detail.test.ts tests/footer.test.ts tests/extension-status.test.ts tests/input-routing.test.ts tests/integration-contract.test.ts tests/stack-widget.test.ts tests/settings-view.test.ts tests/thinking-fold.test.ts tests/thinking-timer.test.ts tests/tick-policy.test.ts tests/ambient.test.ts tests/viewport.test.ts tests/theme-picker.test.ts tests/model-picker.test.ts tests/title-gen.test.ts tests/title-llm.test.ts tests/settings-registry.test.ts tests/settings-coordinator.test.ts tests/settings-locale.test.ts tests/tui-i18n.test.ts tests/settings-shell.test.ts tests/settings-cockpit-provider.test.ts tests/settings-cross-package.test.ts tests/ui-primitives.test.ts tests/settings-shell-secret.test.ts tests/settings-shell-listcrud.test.ts tests/capturing-overlay.test.ts tests/claude-editor.test.ts tests/fullscreen-controller.test.ts tests/transcript-selection.test.ts tests/terminal-cleanup.test.ts tests/terminal-capability.test.ts",
|
|
21
|
+
"test": "node --test --experimental-transform-types tests/config.test.ts tests/public-events.test.ts tests/maestro-store.test.ts tests/split-pane.test.ts tests/editor-bottom.test.ts tests/sidebar-render.test.ts tests/sidebar-controller.test.ts tests/viewport-stability.test.ts tests/quiet-tools.test.ts tests/edit-guard.test.ts tests/agents-store.test.ts tests/endpoint-store.test.ts tests/session-ui-state.test.ts tests/session-tabs.test.ts tests/agent-bar.test.ts tests/supervision-store.test.ts tests/bash-bg-store.test.ts tests/bash-bg-widget.test.ts tests/bash-bg-overlay.test.ts tests/agent-overlay.test.ts tests/todo-store.test.ts tests/render.test.ts tests/agent-scroll.test.ts tests/session-bar.test.ts tests/session-detail.test.ts tests/footer.test.ts tests/extension-status.test.ts tests/input-routing.test.ts tests/integration-contract.test.ts tests/stack-widget.test.ts tests/zen-render.test.ts tests/zen-browse.test.ts tests/zen-sheet.test.ts tests/settings-view.test.ts tests/thinking-fold.test.ts tests/thinking-timer.test.ts tests/tick-policy.test.ts tests/ambient.test.ts tests/viewport.test.ts tests/theme-picker.test.ts tests/model-picker.test.ts tests/title-gen.test.ts tests/title-llm.test.ts tests/settings-registry.test.ts tests/settings-coordinator.test.ts tests/settings-locale.test.ts tests/tui-i18n.test.ts tests/settings-shell.test.ts tests/settings-cockpit-provider.test.ts tests/settings-cross-package.test.ts tests/ui-primitives.test.ts tests/settings-shell-secret.test.ts tests/settings-shell-listcrud.test.ts tests/capturing-overlay.test.ts tests/claude-editor.test.ts tests/fullscreen-controller.test.ts tests/transcript-selection.test.ts tests/terminal-cleanup.test.ts tests/terminal-capability.test.ts",
|
|
22
22
|
"typecheck": "tsc --noEmit",
|
|
23
23
|
"prepack": "node ../../scripts/bundle-settings-core.mjs",
|
|
24
24
|
"postpack": "node ../../scripts/bundle-settings-core.mjs --clean"
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"@earendil-works/pi-coding-agent": "0.83.0",
|
|
73
73
|
"@earendil-works/pi-tui": "0.83.0",
|
|
74
74
|
"@types/node": "^22.0.0",
|
|
75
|
-
"pi-maestro-teammate": "1.
|
|
75
|
+
"pi-maestro-teammate": "1.13.0",
|
|
76
76
|
"typebox": "1.3.7",
|
|
77
77
|
"typescript": "^5.5.0"
|
|
78
78
|
},
|
package/src/agent-overlay.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
truncateToWidth,
|
|
10
10
|
visibleWidth,
|
|
11
11
|
} from "@earendil-works/pi-tui";
|
|
12
|
-
import { effectiveAgentStatus, type AgentDisplayStatus } from "./agents-store.ts";
|
|
12
|
+
import { effectiveAgentStatus, isExpertLeader, type AgentDisplayStatus } from "./agents-store.ts";
|
|
13
13
|
import { scrollBy, type AgentScrollState } from "./agent-scroll.ts";
|
|
14
14
|
import type { IconGlyphs } from "./icons.ts";
|
|
15
15
|
import { visibleStart } from "./layout.ts";
|
|
@@ -28,6 +28,8 @@ export interface AgentOverlayParams {
|
|
|
28
28
|
getAgents: () => readonly AgentRow[];
|
|
29
29
|
getViewingId: () => string | undefined;
|
|
30
30
|
onSelect: (correlationId: string) => void;
|
|
31
|
+
/** Set the selected agent as the editor input target without closing the overlay. */
|
|
32
|
+
onTarget?: (correlationId: string) => void;
|
|
31
33
|
onCommand: (correlationId: string, action: "interrupt" | "steer", message?: string) => void;
|
|
32
34
|
requestRender: () => void;
|
|
33
35
|
close: () => void;
|
|
@@ -289,6 +291,11 @@ export class AgentOverlay implements Component, Focusable {
|
|
|
289
291
|
this.scrollOutput(Math.max(1, this.detailRows - 2));
|
|
290
292
|
return;
|
|
291
293
|
}
|
|
294
|
+
if (commandInput === "m" && this.selectedId && this.params.onTarget) {
|
|
295
|
+
this.params.onTarget(this.selectedId);
|
|
296
|
+
this.ack(tuiT("overlay.agents.targetAck", { agent: this.agentName(this.selectedId) }));
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
292
299
|
if (commandInput === "i" && this.selectedId) {
|
|
293
300
|
// Interrupt (打断): abort the current turn/tool; the agent stays alive
|
|
294
301
|
// and is told to report and continue.
|
|
@@ -444,8 +451,11 @@ export class AgentOverlay implements Component, Focusable {
|
|
|
444
451
|
const visual = agentVisual(status, this.params.glyphs);
|
|
445
452
|
const marker = row.correlationId === this.selectedId ? this.params.glyphs.selectMarker : " ";
|
|
446
453
|
const task = row.task ? ` · ${row.task}` : "";
|
|
454
|
+
const expertTag = isExpertLeader(row)
|
|
455
|
+
? `${this.params.theme.fg("accent", tuiT("widget.agent.expert"))} `
|
|
456
|
+
: "";
|
|
447
457
|
return fit(
|
|
448
|
-
`${prefix}${marker} ${this.params.theme.fg(visual.color, visual.glyph)} ${this.params.theme.bold(agentLabel(row))}${task}`,
|
|
458
|
+
`${prefix}${marker} ${this.params.theme.fg(visual.color, visual.glyph)} ${expertTag}${this.params.theme.bold(agentLabel(row))}${task}`,
|
|
449
459
|
width,
|
|
450
460
|
);
|
|
451
461
|
});
|
package/src/agents-store.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type {
|
|
|
5
5
|
} from "pi-maestro-teammate/v1/events";
|
|
6
6
|
import type { AgentProgressSnapshot } from "pi-maestro-teammate/v1/types";
|
|
7
7
|
import { sanitizeExtensionStatusText } from "./extension-status.ts";
|
|
8
|
-
import type
|
|
8
|
+
import { EXPERT_LEADER_NAME, type AgentRow, type AgentStatus } from "./types.ts";
|
|
9
9
|
|
|
10
10
|
const STATUS_TEXT_MAX = 48;
|
|
11
11
|
const TEAMMATE_STALL_TIMEOUT_MS = 30_000;
|
|
@@ -99,6 +99,16 @@ function deriveRole(agent: string | undefined, name: string | undefined): string
|
|
|
99
99
|
return clean(name) || "agent";
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
/**
|
|
103
|
+
* True when the row is the workflow Leader of an expert-mode dispatch
|
|
104
|
+
* (pi-maestro-teammate EXPERT_MODE_LEADER_NAME). The Leader keeps the same
|
|
105
|
+
* tree + dependency-arrow presentation as parallel/DAG runs; this only adds
|
|
106
|
+
* its strategy marker.
|
|
107
|
+
*/
|
|
108
|
+
export function isExpertLeader(row: { name: string | undefined }): boolean {
|
|
109
|
+
return row.name === EXPERT_LEADER_NAME;
|
|
110
|
+
}
|
|
111
|
+
|
|
102
112
|
export function mapAgentStatus(status: unknown): AgentStatus {
|
|
103
113
|
switch (status) {
|
|
104
114
|
case "pending":
|
package/src/bash-bg-overlay.ts
CHANGED
|
@@ -25,6 +25,8 @@ export interface BashBgOverlayParams {
|
|
|
25
25
|
close: () => void;
|
|
26
26
|
theme: Theme;
|
|
27
27
|
glyphs: IconGlyphs;
|
|
28
|
+
/** Select this job when the overlay first renders. */
|
|
29
|
+
initialJobId?: string;
|
|
28
30
|
/** Wall-clock snapshot supplied by the owner and advanced through tick(). */
|
|
29
31
|
now: number;
|
|
30
32
|
/** Live terminal height, so the card can use the space it already reserves. */
|
|
@@ -57,6 +59,7 @@ export class BashBgOverlay implements Component, Focusable {
|
|
|
57
59
|
constructor(private readonly params: BashBgOverlayParams) {
|
|
58
60
|
this.now = params.now;
|
|
59
61
|
this.liveTickKey = this.tickKey(params.now);
|
|
62
|
+
this.selectedId = params.initialJobId;
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
invalidate(): void {}
|
package/src/config.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import { type CockpitConfig, type CurrencyMode, DEFAULT_CONFIG } from "./types.ts";
|
|
4
|
+
import { type CockpitConfig, type CurrencyMode, DEFAULT_CONFIG, type StackStyle } from "./types.ts";
|
|
5
5
|
|
|
6
6
|
const SIDEBAR_MIN_WIDTH = 32;
|
|
7
7
|
const SIDEBAR_MAX_WIDTH = 56;
|
|
@@ -19,6 +19,7 @@ export function mergeConfig(base: CockpitConfig, over: unknown): CockpitConfig {
|
|
|
19
19
|
const isToolPaletteMode = (v: unknown): v is "classic" | "family" | "readwrite" | "search" | "mono" =>
|
|
20
20
|
v === "classic" || v === "family" || v === "readwrite" || v === "search" || v === "mono";
|
|
21
21
|
const isIconMode = (v: unknown): v is "auto" | "nerd" | "ascii" => v === "auto" || v === "nerd" || v === "ascii";
|
|
22
|
+
const isStackStyle = (v: unknown): v is StackStyle => v === "classic" || v === "zen";
|
|
22
23
|
const isCurrencyMode = (v: unknown): v is CurrencyMode => v === "usd" || v === "cny";
|
|
23
24
|
const isSidebarMode = (v: unknown): v is "auto" | "on" | "off" => v === "auto" || v === "on" || v === "off";
|
|
24
25
|
const isSidebarDensity = (v: unknown): v is "comfortable" | "compact" => v === "comfortable" || v === "compact";
|
|
@@ -47,6 +48,7 @@ const isCurrencyMode = (v: unknown): v is CurrencyMode => v === "usd" || v === "
|
|
|
47
48
|
agentsMode: isMode(o.agentsMode) ? o.agentsMode : base.agentsMode,
|
|
48
49
|
todoMode: isMode(o.todoMode) ? o.todoMode : base.todoMode,
|
|
49
50
|
todoExpanded: typeof o.todoExpanded === "boolean" ? o.todoExpanded : base.todoExpanded,
|
|
51
|
+
stackStyle: isStackStyle(o.stackStyle) ? o.stackStyle : base.stackStyle,
|
|
50
52
|
hideNativeAgents: typeof o.hideNativeAgents === "boolean" ? o.hideNativeAgents : base.hideNativeAgents,
|
|
51
53
|
pinEditorBottom: typeof o.pinEditorBottom === "boolean" ? o.pinEditorBottom : base.pinEditorBottom,
|
|
52
54
|
doubleEscapeClearInput: typeof o.doubleEscapeClearInput === "boolean" ? o.doubleEscapeClearInput : base.doubleEscapeClearInput,
|
|
@@ -133,6 +135,7 @@ export function mergeConfigDocument(raw: unknown, config: CockpitConfig): Record
|
|
|
133
135
|
agentsMode: config.agentsMode,
|
|
134
136
|
todoMode: config.todoMode,
|
|
135
137
|
todoExpanded: config.todoExpanded,
|
|
138
|
+
stackStyle: config.stackStyle,
|
|
136
139
|
hideNativeAgents: config.hideNativeAgents,
|
|
137
140
|
pinEditorBottom: config.pinEditorBottom,
|
|
138
141
|
doubleEscapeClearInput: config.doubleEscapeClearInput,
|
package/src/index.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { SupervisionStore } from "./supervision-store.ts";
|
|
|
13
13
|
import { MaestroStore } from "./maestro-store.ts";
|
|
14
14
|
import { createSidebarController, type SidebarController } from "./sidebar-controller.ts";
|
|
15
15
|
import { COCKPIT_SPLIT_PANE_MARKER } from "./split-pane.ts";
|
|
16
|
-
import { attachViewportStability } from "./viewport-stability.ts";
|
|
16
|
+
import { attachViewportStability, type ViewportStabilityPatch } from "./viewport-stability.ts";
|
|
17
17
|
import {
|
|
18
18
|
COCKPIT_EDITOR_BOTTOM_MARKER,
|
|
19
19
|
EDITOR_BOTTOM_WIDGET_KEY,
|
|
@@ -54,7 +54,17 @@ import { SessionUiState } from "./session-ui-state.ts";
|
|
|
54
54
|
import { nextSessionTabId } from "./session-tabs.ts";
|
|
55
55
|
import { renderWindowBar, windowSessionColor } from "./window-bar.ts";
|
|
56
56
|
import { makeWindowThreadWidget } from "./window-thread-view.ts";
|
|
57
|
-
import { agentPanelRows } from "./viewport.ts";
|
|
57
|
+
import { agentPanelRows, panelRows } from "./viewport.ts";
|
|
58
|
+
import { createZenBrowseController, type ZenBrowseController } from "./zen-browse.ts";
|
|
59
|
+
import { enumerateZenNavRows, renderZenStack } from "./zen-render.ts";
|
|
60
|
+
import {
|
|
61
|
+
ZenSheet,
|
|
62
|
+
buildZenMissionSheet,
|
|
63
|
+
buildZenRunSheet,
|
|
64
|
+
buildZenSwarmSheet,
|
|
65
|
+
buildZenTaskSheet,
|
|
66
|
+
type ZenSheetDocument,
|
|
67
|
+
} from "./zen-sheet.ts";
|
|
58
68
|
import { routeAgentInput } from "./input-routing.ts";
|
|
59
69
|
import { activeThemeName, ThemePicker } from "./theme-picker.ts";
|
|
60
70
|
import { ModelPicker, type ModelPickerEntry } from "./model-picker.ts";
|
|
@@ -323,6 +333,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
323
333
|
// Independent of the split-pane attach: the sidebar can be off while the
|
|
324
334
|
// thinking label / teammate tree still stream above the visible viewport.
|
|
325
335
|
let stabilityTui: TUI | undefined;
|
|
336
|
+
let viewportStabilityPatch: ViewportStabilityPatch | undefined;
|
|
326
337
|
// True while Cockpit owns a capturing overlay (bash jobs, theme picker,
|
|
327
338
|
// settings panel). Split-pane resize must yield to the overlay's focus and
|
|
328
339
|
// refuse to start while one is open; otherwise the resize listener — a
|
|
@@ -331,6 +342,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
331
342
|
/** Extra repaint target while the Agent modal is consuming the live store. */
|
|
332
343
|
let activeAgentOverlayRender: (() => void) | undefined;
|
|
333
344
|
let activeAgentOverlay: { finalize(): void } | undefined;
|
|
345
|
+
let activeZenSheet: { finalize(): void } | undefined;
|
|
334
346
|
let activeBashBgOverlay: BashBgOverlay | undefined;
|
|
335
347
|
/** Agent activity temporarily wins vertical space without rewriting Todo preference. */
|
|
336
348
|
let agentPriorityActive = false;
|
|
@@ -340,6 +352,8 @@ export default function (pi: ExtensionAPI): void {
|
|
|
340
352
|
// component.dispose() (MW-2).
|
|
341
353
|
let activeSettingsOverlay: { finalize(): void } | undefined;
|
|
342
354
|
let sidebarController: SidebarController | undefined;
|
|
355
|
+
let zenBrowseController: ZenBrowseController | undefined;
|
|
356
|
+
let zenNavRows: string[] = [];
|
|
343
357
|
let editorBottomController: EditorBottomController | undefined;
|
|
344
358
|
// Whether Cockpit installed its custom editor this session (reload-gated).
|
|
345
359
|
// Tracks ownership so teardown restores the default editor only when ours.
|
|
@@ -676,14 +690,17 @@ export default function (pi: ExtensionAPI): void {
|
|
|
676
690
|
|
|
677
691
|
const ensureViewportStability = (tui: TUI): void => {
|
|
678
692
|
if (stabilityTui === tui) return;
|
|
693
|
+
viewportStabilityPatch?.detach();
|
|
694
|
+
viewportStabilityPatch = attachViewportStability(tui);
|
|
679
695
|
stabilityTui = tui;
|
|
680
|
-
attachViewportStability(tui);
|
|
681
696
|
};
|
|
682
697
|
|
|
683
698
|
const sessionListOverlayActive = (): boolean =>
|
|
684
699
|
capturingOverlayActive || ambientKeysShouldYield(capturedTui);
|
|
685
700
|
|
|
686
701
|
const clearWidgets = (ctx: ExtensionContext): void => {
|
|
702
|
+
zenBrowseController?.end();
|
|
703
|
+
zenNavRows = [];
|
|
687
704
|
ctx.ui.setWidget(STACK_WIDGET_KEY, undefined);
|
|
688
705
|
ctx.ui.setWidget(AGENT_WIDGET_KEY, undefined);
|
|
689
706
|
};
|
|
@@ -926,7 +943,10 @@ export default function (pi: ExtensionAPI): void {
|
|
|
926
943
|
capturedTui = tui;
|
|
927
944
|
ensureViewportStability(tui);
|
|
928
945
|
return makeAgentWidget({
|
|
929
|
-
|
|
946
|
+
// The Zen stack absorbs the roster into its ACTORS section; an empty
|
|
947
|
+
// roster makes makeAgentWidget render zero rows, so the widget slot
|
|
948
|
+
// stays registered (placement bookkeeping) without duplicating rows.
|
|
949
|
+
getAgents: () => config.stackStyle === "zen" || sessionUi.mode === "window" ? [] : agents.snapshot(),
|
|
930
950
|
getConfig: () => config,
|
|
931
951
|
isRunning: () => running,
|
|
932
952
|
isAnimating,
|
|
@@ -1019,18 +1039,63 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1019
1039
|
);
|
|
1020
1040
|
};
|
|
1021
1041
|
|
|
1042
|
+
const ensureZenBrowseController = (ctx: ExtensionContext): ZenBrowseController => {
|
|
1043
|
+
if (zenBrowseController) return zenBrowseController;
|
|
1044
|
+
zenBrowseController = createZenBrowseController({
|
|
1045
|
+
getNavRows: () => zenNavRows,
|
|
1046
|
+
subscribeInput: (handler) => ctx.ui.onTerminalInput(handler),
|
|
1047
|
+
requestRender: () => req(),
|
|
1048
|
+
onActivate: (id) => {
|
|
1049
|
+
void activateZenRow(ctx, id).catch((error) => {
|
|
1050
|
+
ctx.ui.notify(error instanceof Error ? error.message : String(error), "warning");
|
|
1051
|
+
});
|
|
1052
|
+
},
|
|
1053
|
+
shouldYield: () => capturingOverlayActive || ambientKeysShouldYield(capturedTui),
|
|
1054
|
+
onWarning: (message) => ctx.ui.notify(message, "warning"),
|
|
1055
|
+
emptyNotice: () => tuiT("notice.sidebarNothing"),
|
|
1056
|
+
});
|
|
1057
|
+
return zenBrowseController;
|
|
1058
|
+
};
|
|
1059
|
+
|
|
1022
1060
|
const installWidgets = (ctx: ExtensionContext): void => {
|
|
1061
|
+
const browseController = ensureZenBrowseController(ctx);
|
|
1023
1062
|
ctx.ui.setWidget(
|
|
1024
1063
|
STACK_WIDGET_KEY,
|
|
1025
1064
|
(tui, theme) => {
|
|
1026
1065
|
capturedTui = tui;
|
|
1027
1066
|
ensureViewportStability(tui);
|
|
1028
|
-
|
|
1067
|
+
const todoWidget = makeTodoWidget({
|
|
1029
1068
|
getTodos: () => todos.snapshot(),
|
|
1030
1069
|
getConfig: () => config,
|
|
1031
1070
|
getExpanded: effectiveTodoExpanded,
|
|
1032
1071
|
isAnimating,
|
|
1033
1072
|
})(tui, theme);
|
|
1073
|
+
// stackStyle is dispatched per render, not per install, so a settings
|
|
1074
|
+
// toggle switches the projection live without re-registering widgets.
|
|
1075
|
+
return {
|
|
1076
|
+
render(width: number): string[] {
|
|
1077
|
+
if (config.stackStyle !== "zen") {
|
|
1078
|
+
zenNavRows = [];
|
|
1079
|
+
return todoWidget.render(width);
|
|
1080
|
+
}
|
|
1081
|
+
const input = {
|
|
1082
|
+
maestro: maestro.snapshot(),
|
|
1083
|
+
todos: todos.snapshot(),
|
|
1084
|
+
agents: sessionUi.mode === "window" ? [] : visibleAgentRows(agents.snapshot()),
|
|
1085
|
+
jobs: bashBg.snapshot(),
|
|
1086
|
+
config: { ...config, todoExpanded: effectiveTodoExpanded() },
|
|
1087
|
+
width,
|
|
1088
|
+
theme,
|
|
1089
|
+
now: Date.now(),
|
|
1090
|
+
maxRows: panelRows(terminalRows(tui)),
|
|
1091
|
+
browse: browseController.state(),
|
|
1092
|
+
};
|
|
1093
|
+
zenNavRows = enumerateZenNavRows(input);
|
|
1094
|
+
return renderZenStack(input);
|
|
1095
|
+
},
|
|
1096
|
+
invalidate(): void { todoWidget.invalidate(); },
|
|
1097
|
+
dispose(): void { todoWidget.dispose(); },
|
|
1098
|
+
};
|
|
1034
1099
|
},
|
|
1035
1100
|
{ placement: "aboveEditor" },
|
|
1036
1101
|
);
|
|
@@ -1135,6 +1200,9 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1135
1200
|
sidebarController?.dispose();
|
|
1136
1201
|
editorBottomController?.dispose();
|
|
1137
1202
|
}
|
|
1203
|
+
zenBrowseController?.dispose();
|
|
1204
|
+
zenBrowseController = undefined;
|
|
1205
|
+
zenNavRows = [];
|
|
1138
1206
|
sidebarController = undefined;
|
|
1139
1207
|
editorBottomController = undefined;
|
|
1140
1208
|
fullscreenController = undefined;
|
|
@@ -1148,6 +1216,12 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1148
1216
|
}
|
|
1149
1217
|
activeAgentOverlay = undefined;
|
|
1150
1218
|
activeAgentOverlayRender = undefined;
|
|
1219
|
+
try {
|
|
1220
|
+
activeZenSheet?.finalize();
|
|
1221
|
+
} catch {
|
|
1222
|
+
// best effort
|
|
1223
|
+
}
|
|
1224
|
+
activeZenSheet = undefined;
|
|
1151
1225
|
dockEffectiveVisible = false;
|
|
1152
1226
|
clearClaudeEditor(ctx);
|
|
1153
1227
|
clearFullscreen(ctx);
|
|
@@ -1172,6 +1246,9 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1172
1246
|
// ambient surfaces are best-effort
|
|
1173
1247
|
}
|
|
1174
1248
|
stopTick();
|
|
1249
|
+
viewportStabilityPatch?.detach();
|
|
1250
|
+
viewportStabilityPatch = undefined;
|
|
1251
|
+
stabilityTui = undefined;
|
|
1175
1252
|
lastPublishedInputTarget = "@main";
|
|
1176
1253
|
pi.events.emit(COCKPIT_INPUT_TARGET_EVENT, { version: 1 } satisfies CockpitInputTargetV1);
|
|
1177
1254
|
capturedTui = undefined;
|
|
@@ -1500,15 +1577,14 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1500
1577
|
}),
|
|
1501
1578
|
pi.events.on(COCKPIT_PREEMPT_RESIZE_EVENT, () => {
|
|
1502
1579
|
// A capturing overlay opened by another extension (teammate attach,
|
|
1503
|
-
// control center, model mapping) must preempt
|
|
1504
|
-
// or browse mode: both are global terminal-input hooks that would
|
|
1505
|
-
// otherwise swallow the overlay's first arrow/Enter/Esc.
|
|
1580
|
+
// control center, model mapping) must preempt every global input hook.
|
|
1506
1581
|
try {
|
|
1507
1582
|
sidebarController?.cancelResize();
|
|
1508
1583
|
sidebarController?.endFocus();
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1584
|
+
zenBrowseController?.end();
|
|
1585
|
+
} catch {
|
|
1586
|
+
// best effort
|
|
1587
|
+
}
|
|
1512
1588
|
}),
|
|
1513
1589
|
);
|
|
1514
1590
|
};
|
|
@@ -1832,6 +1908,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1832
1908
|
|
|
1833
1909
|
const enterCapturingOverlay = (): void => {
|
|
1834
1910
|
capturingOverlayActive = true;
|
|
1911
|
+
zenBrowseController?.end();
|
|
1835
1912
|
req();
|
|
1836
1913
|
try {
|
|
1837
1914
|
sidebarController?.cancelResize();
|
|
@@ -1844,7 +1921,71 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1844
1921
|
req();
|
|
1845
1922
|
};
|
|
1846
1923
|
|
|
1847
|
-
const
|
|
1924
|
+
const openZenSheet = async (
|
|
1925
|
+
ctx: ExtensionContext,
|
|
1926
|
+
getDocument: () => ZenSheetDocument | undefined,
|
|
1927
|
+
): Promise<void> => {
|
|
1928
|
+
if (!ctx.hasUI || capturingOverlayActive) return;
|
|
1929
|
+
let ownedSheet: { finalize(): void } | undefined;
|
|
1930
|
+
enterCapturingOverlay();
|
|
1931
|
+
try {
|
|
1932
|
+
await ctx.ui.custom<void>((tui, theme, _kb, done) => {
|
|
1933
|
+
let settled = false;
|
|
1934
|
+
const finalize = (): void => {
|
|
1935
|
+
if (settled) return;
|
|
1936
|
+
settled = true;
|
|
1937
|
+
done(undefined);
|
|
1938
|
+
};
|
|
1939
|
+
ownedSheet = { finalize };
|
|
1940
|
+
activeZenSheet = ownedSheet;
|
|
1941
|
+
return new ZenSheet({
|
|
1942
|
+
getDocument,
|
|
1943
|
+
requestRender: () => tui.requestRender(),
|
|
1944
|
+
close: finalize,
|
|
1945
|
+
theme,
|
|
1946
|
+
glyphs: resolveGlyphs(config.icons.mode),
|
|
1947
|
+
getTerminalRows: () => terminalRows(tui),
|
|
1948
|
+
});
|
|
1949
|
+
}, {
|
|
1950
|
+
overlay: true,
|
|
1951
|
+
overlayOptions: { anchor: "center", width: "72%", maxHeight: "85%" },
|
|
1952
|
+
});
|
|
1953
|
+
} finally {
|
|
1954
|
+
if (activeZenSheet === ownedSheet) activeZenSheet = undefined;
|
|
1955
|
+
exitCapturingOverlay();
|
|
1956
|
+
}
|
|
1957
|
+
};
|
|
1958
|
+
|
|
1959
|
+
const activateZenRow = async (ctx: ExtensionContext, id: string): Promise<void> => {
|
|
1960
|
+
if (id === "mission") {
|
|
1961
|
+
await openZenSheet(ctx, () => buildZenMissionSheet(maestro.snapshot()));
|
|
1962
|
+
return;
|
|
1963
|
+
}
|
|
1964
|
+
if (id === "run") {
|
|
1965
|
+
await openZenSheet(ctx, () => buildZenRunSheet(maestro.snapshot()));
|
|
1966
|
+
return;
|
|
1967
|
+
}
|
|
1968
|
+
if (id === "swarm") {
|
|
1969
|
+
await openZenSheet(ctx, () => buildZenSwarmSheet(maestro.snapshot()));
|
|
1970
|
+
return;
|
|
1971
|
+
}
|
|
1972
|
+
if (id.startsWith("task:")) {
|
|
1973
|
+
const taskId = id.slice("task:".length);
|
|
1974
|
+
await openZenSheet(ctx, () => buildZenTaskSheet(todos.snapshot().find((item) => item.id === taskId)));
|
|
1975
|
+
return;
|
|
1976
|
+
}
|
|
1977
|
+
if (id.startsWith("agent:")) {
|
|
1978
|
+
const correlationId = id.slice("agent:".length);
|
|
1979
|
+
selectAgent(correlationId);
|
|
1980
|
+
await openAgentOverlay(ctx, correlationId);
|
|
1981
|
+
return;
|
|
1982
|
+
}
|
|
1983
|
+
if (id.startsWith("job:")) {
|
|
1984
|
+
await openBashBgOverlay(ctx, id.slice("job:".length));
|
|
1985
|
+
}
|
|
1986
|
+
};
|
|
1987
|
+
|
|
1988
|
+
const openAgentOverlay = async (ctx: ExtensionContext, initialCorrelationId?: string): Promise<void> => {
|
|
1848
1989
|
if (!ctx.hasUI) return;
|
|
1849
1990
|
if (capturingOverlayActive) {
|
|
1850
1991
|
ctx.ui.notify(tuiT("notice.closeOverlayAgents"), "warning");
|
|
@@ -1854,6 +1995,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1854
1995
|
ctx.ui.notify(tuiT("notice.noAgents"), "info");
|
|
1855
1996
|
return;
|
|
1856
1997
|
}
|
|
1998
|
+
if (initialCorrelationId) selectAgent(initialCorrelationId);
|
|
1857
1999
|
let ownedOverlay: { finalize(): void } | undefined;
|
|
1858
2000
|
let ownedRender: (() => void) | undefined;
|
|
1859
2001
|
enterCapturingOverlay();
|
|
@@ -1871,8 +2013,9 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1871
2013
|
activeAgentOverlayRender = ownedRender;
|
|
1872
2014
|
return new AgentOverlay({
|
|
1873
2015
|
getAgents: () => agents.snapshot(),
|
|
1874
|
-
getViewingId: selectedAgentCorrelationId,
|
|
2016
|
+
getViewingId: () => initialCorrelationId ?? selectedAgentCorrelationId(),
|
|
1875
2017
|
onSelect: (correlationId) => selectAgent(correlationId),
|
|
2018
|
+
onTarget: (correlationId) => selectAgent(correlationId),
|
|
1876
2019
|
onCommand: (correlationId, action, message) => {
|
|
1877
2020
|
pi.events.emit(TEAMMATE_AGENT_COMMAND_EVENT, { correlationId, action, ...(message !== undefined ? { message } : {}) });
|
|
1878
2021
|
},
|
|
@@ -1955,7 +2098,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1955
2098
|
});
|
|
1956
2099
|
});
|
|
1957
2100
|
|
|
1958
|
-
const openBashBgOverlay = async (ctx: ExtensionContext): Promise<void> => {
|
|
2101
|
+
const openBashBgOverlay = async (ctx: ExtensionContext, initialJobId?: string): Promise<void> => {
|
|
1959
2102
|
if (!ctx.hasUI) return;
|
|
1960
2103
|
let ownedOverlay: BashBgOverlay | undefined;
|
|
1961
2104
|
enterCapturingOverlay();
|
|
@@ -1970,6 +2113,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1970
2113
|
glyphs: resolveGlyphs(config.icons.mode),
|
|
1971
2114
|
now: Date.now(),
|
|
1972
2115
|
hideLiveDuration: config.staticMode,
|
|
2116
|
+
...(initialJobId ? { initialJobId } : {}),
|
|
1973
2117
|
getTerminalRows: () => terminalRows(tui),
|
|
1974
2118
|
});
|
|
1975
2119
|
activeBashBgOverlay = ownedOverlay;
|
|
@@ -2145,6 +2289,10 @@ export default function (pi: ExtensionAPI): void {
|
|
|
2145
2289
|
else clearEditorBottom(ctx);
|
|
2146
2290
|
}
|
|
2147
2291
|
if (changedKeys.some((key) => key.startsWith("sidebar."))) syncSidebarMode(ctx);
|
|
2292
|
+
if (previous.stackStyle !== nextConfig.stackStyle && nextConfig.stackStyle !== "zen") {
|
|
2293
|
+
zenBrowseController?.end();
|
|
2294
|
+
zenNavRows = [];
|
|
2295
|
+
}
|
|
2148
2296
|
publishUiOwnership();
|
|
2149
2297
|
req();
|
|
2150
2298
|
},
|
|
@@ -2231,7 +2379,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
2231
2379
|
});
|
|
2232
2380
|
|
|
2233
2381
|
pi.registerShortcut(SIDEBAR_FOCUS_KEY, {
|
|
2234
|
-
description: "Browse the Cockpit
|
|
2382
|
+
description: "Browse the visible Cockpit surface with the keyboard (↑↓/j/k/Home/End, Enter, Esc)",
|
|
2235
2383
|
handler(ctx) {
|
|
2236
2384
|
if (!config.enabled) {
|
|
2237
2385
|
ctx.ui.notify(tuiT("notice.cockpitDisabled"), "warning");
|
|
@@ -2241,6 +2389,10 @@ export default function (pi: ExtensionAPI): void {
|
|
|
2241
2389
|
ctx.ui.notify(tuiT("notice.closeOverlayBrowse"), "warning");
|
|
2242
2390
|
return;
|
|
2243
2391
|
}
|
|
2392
|
+
if (config.stackStyle === "zen" && surfaceState === "widgets") {
|
|
2393
|
+
ensureZenBrowseController(ctx).begin();
|
|
2394
|
+
return;
|
|
2395
|
+
}
|
|
2244
2396
|
sidebarController?.beginFocus();
|
|
2245
2397
|
},
|
|
2246
2398
|
});
|
package/src/render.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Theme, ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { effectiveAgentStatus, type AgentDisplayStatus } from "./agents-store.ts";
|
|
2
|
+
import { effectiveAgentStatus, isExpertLeader, type AgentDisplayStatus } from "./agents-store.ts";
|
|
3
3
|
import type { AgentRow, TodoItem, ViewMode } from "./types.ts";
|
|
4
4
|
import type { IconGlyphs } from "./icons.ts";
|
|
5
5
|
import { composeByPriority, fitLineByPriority, type PriorityGroup, type PrioritizedSegment, type WidthUtils } from "./layout.ts";
|
|
@@ -237,7 +237,9 @@ export function renderAgents(
|
|
|
237
237
|
+ (stalled ? theme.fg("error", `${g.separator}${tuiT("common.stalled", { count: stalled })}`) : "");
|
|
238
238
|
const tails = rows
|
|
239
239
|
.filter((r) => r.tail)
|
|
240
|
-
.map((r) =>
|
|
240
|
+
.map((r) => isExpertLeader(r)
|
|
241
|
+
? `${theme.fg("accent", tuiT("widget.agent.expert"))}${theme.fg("dim", ":")} ${theme.fg("dim", r.tail)}`
|
|
242
|
+
: `${theme.fg(roleColor(r.role), r.role)}${theme.fg("dim", ":")} ${theme.fg("dim", r.tail)}`);
|
|
241
243
|
const parts = opts.withHead === false ? tails : [head, ...tails];
|
|
242
244
|
if (parts.length === 0) return [];
|
|
243
245
|
return [utils.clip(parts.join(theme.fg("dim", g.separator)), width, ell)];
|
|
@@ -292,6 +294,11 @@ export function renderAgents(
|
|
|
292
294
|
{ text: theme.fg(status.color, status.glyph), priority: 99, clippable: false },
|
|
293
295
|
];
|
|
294
296
|
if (status.label) segs.push({ text: theme.fg(status.color, status.label), priority: 95, clippable: false });
|
|
297
|
+
// Expert mode marks its workflow Leader row; the row keeps the same
|
|
298
|
+
// tree/dependency presentation as parallel and DAG dispatches.
|
|
299
|
+
if (isExpertLeader(r)) {
|
|
300
|
+
segs.push({ text: theme.fg("accent", tuiT("widget.agent.expert")), priority: 93, clippable: false });
|
|
301
|
+
}
|
|
295
302
|
if (r.phase && !(r.phase === "tool-execution" && r.activeTool)) {
|
|
296
303
|
segs.push({ text: theme.fg("dim", r.phase), priority: 92, minWidth: 6 });
|
|
297
304
|
}
|
|
@@ -67,6 +67,7 @@ const CONFIG_KEYS = [
|
|
|
67
67
|
"agentsMode",
|
|
68
68
|
"todoMode",
|
|
69
69
|
"todoExpanded",
|
|
70
|
+
"stackStyle",
|
|
70
71
|
"hideNativeAgents",
|
|
71
72
|
"toolPalette",
|
|
72
73
|
"icons.mode",
|
|
@@ -155,6 +156,7 @@ const CATALOGS = {
|
|
|
155
156
|
"cockpit.agentsMode": "Agent display",
|
|
156
157
|
"cockpit.todoMode": "Todo display",
|
|
157
158
|
"cockpit.todoExpanded": "Todo expanded",
|
|
159
|
+
"cockpit.stackStyle": "Stack projection",
|
|
158
160
|
"cockpit.hideNativeAgents": "Hide native agent widget",
|
|
159
161
|
"cockpit.icons.mode": "Icon mode",
|
|
160
162
|
"cockpit.sidebar.mode": "Sidebar mode",
|
|
@@ -175,6 +177,7 @@ const CATALOGS = {
|
|
|
175
177
|
"cockpit.title.maxLength": "Title max length",
|
|
176
178
|
"cockpit.title.maxLength.description": "Hard cap on the composed tab title (the middle is ellided).",
|
|
177
179
|
"cockpit.option.classic": "Classic",
|
|
180
|
+
"cockpit.option.zen": "Zen",
|
|
178
181
|
"cockpit.option.family": "Family",
|
|
179
182
|
"cockpit.option.readwrite": "Read/Write",
|
|
180
183
|
"cockpit.option.search": "Search",
|
|
@@ -225,6 +228,7 @@ const CATALOGS = {
|
|
|
225
228
|
"cockpit.agentsMode": "Agent 显示",
|
|
226
229
|
"cockpit.todoMode": "Todo 显示",
|
|
227
230
|
"cockpit.todoExpanded": "展开 Todo",
|
|
231
|
+
"cockpit.stackStyle": "栈投影",
|
|
228
232
|
"cockpit.hideNativeAgents": "隐藏原生 Agent 面板",
|
|
229
233
|
"cockpit.icons.mode": "图标模式",
|
|
230
234
|
"cockpit.sidebar.mode": "侧边栏模式",
|
|
@@ -245,6 +249,7 @@ const CATALOGS = {
|
|
|
245
249
|
"cockpit.title.maxLength": "标题最大长度",
|
|
246
250
|
"cockpit.title.maxLength.description": "组合标签页标题的硬上限(中间省略)。",
|
|
247
251
|
"cockpit.option.classic": "经典",
|
|
252
|
+
"cockpit.option.zen": "Zen",
|
|
248
253
|
"cockpit.option.family": "族",
|
|
249
254
|
"cockpit.option.readwrite": "读写",
|
|
250
255
|
"cockpit.option.search": "搜索",
|
|
@@ -322,6 +327,7 @@ const DEFINITIONS: readonly SettingDefinition[] = [
|
|
|
322
327
|
enumDefinition("agentsMode", "cockpit.group.panels", 0, "cockpit.agentsMode", ["list", "compact"], "live"),
|
|
323
328
|
enumDefinition("todoMode", "cockpit.group.panels", 1, "cockpit.todoMode", ["list", "compact"], "live"),
|
|
324
329
|
booleanDefinition("todoExpanded", "cockpit.group.panels", 2, "cockpit.todoExpanded", "live"),
|
|
330
|
+
enumDefinition("stackStyle", "cockpit.group.layout", 4, "cockpit.stackStyle", ["classic", "zen"], "live"),
|
|
325
331
|
booleanDefinition("hideNativeAgents", "cockpit.group.panels", 3, "cockpit.hideNativeAgents", "live"),
|
|
326
332
|
enumDefinition("sidebar.mode", "cockpit.group.sidebar", 0, "cockpit.sidebar.mode", ["auto", "on", "off"], "live"),
|
|
327
333
|
{
|
|
@@ -688,6 +694,7 @@ function validValue(key: CockpitSettingKey, value: JsonValue): boolean {
|
|
|
688
694
|
if (key === "title.generationModel") return typeof value === "string";
|
|
689
695
|
if (key.startsWith("title.")) return typeof value === "boolean";
|
|
690
696
|
if (key === "quietSymbols") return value === "check" || value === "dot";
|
|
697
|
+
if (key === "stackStyle") return value === "classic" || value === "zen";
|
|
691
698
|
if (key === "toolPalette") return ["classic", "family", "readwrite", "search", "mono"].includes(String(value));
|
|
692
699
|
if (key === "agentsMode" || key === "todoMode") return value === "list" || value === "compact";
|
|
693
700
|
if (key === "icons.mode") return value === "auto" || value === "nerd" || value === "ascii";
|
package/src/settings-view.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
QuietSymbolMode,
|
|
13
13
|
SidebarDensity,
|
|
14
14
|
SidebarMode,
|
|
15
|
+
StackStyle,
|
|
15
16
|
ViewMode,
|
|
16
17
|
} from "./types.ts";
|
|
17
18
|
import { tuiT } from "./tui-i18n.ts";
|
|
@@ -39,6 +40,7 @@ const QUIET_SYMBOL_MODES: QuietSymbolMode[] = ["check", "dot"];
|
|
|
39
40
|
const ICON_MODES: IconMode[] = ["auto", "nerd", "ascii"];
|
|
40
41
|
const SIDEBAR_MODES: SidebarMode[] = ["auto", "on", "off"];
|
|
41
42
|
const SIDEBAR_DENSITIES: SidebarDensity[] = ["comfortable", "compact"];
|
|
43
|
+
const STACK_STYLES: StackStyle[] = ["classic", "zen"];
|
|
42
44
|
|
|
43
45
|
function cycle<T>(values: readonly T[], current: T): T {
|
|
44
46
|
const index = values.indexOf(current);
|
|
@@ -65,6 +67,8 @@ const LEGACY_VALUE_KEYS: Readonly<Record<string, string>> = {
|
|
|
65
67
|
compact: "common.value.compact",
|
|
66
68
|
auto: "common.value.auto",
|
|
67
69
|
comfortable: "common.value.comfortable",
|
|
70
|
+
classic: "common.value.classic",
|
|
71
|
+
zen: "common.value.zen",
|
|
68
72
|
"picker…": "common.value.picker",
|
|
69
73
|
[NO_THEME_LABEL]: "legacy.piSettings",
|
|
70
74
|
"(rule-based)": "common.ruleBased",
|
|
@@ -170,6 +174,13 @@ export function buildRows(config: CockpitConfig, live?: LiveRowState): SettingsR
|
|
|
170
174
|
value: config.todoExpanded ? "yes" : "no",
|
|
171
175
|
next: config.todoExpanded ? "no" : "yes",
|
|
172
176
|
},
|
|
177
|
+
{
|
|
178
|
+
key: "stackStyle",
|
|
179
|
+
accel: "p",
|
|
180
|
+
label: "stack style",
|
|
181
|
+
value: config.stackStyle,
|
|
182
|
+
next: cycle(STACK_STYLES, config.stackStyle),
|
|
183
|
+
},
|
|
173
184
|
{
|
|
174
185
|
key: "hideNativeAgents",
|
|
175
186
|
accel: "n",
|
|
@@ -314,6 +325,8 @@ export function applyRow(config: CockpitConfig, key: string, textValue?: string)
|
|
|
314
325
|
return { ...config, todoMode: cycle(VIEW_MODES, config.todoMode) };
|
|
315
326
|
case "todoExpanded":
|
|
316
327
|
return { ...config, todoExpanded: !config.todoExpanded };
|
|
328
|
+
case "stackStyle":
|
|
329
|
+
return { ...config, stackStyle: cycle(STACK_STYLES, config.stackStyle) };
|
|
317
330
|
case "hideNativeAgents":
|
|
318
331
|
return { ...config, hideNativeAgents: !config.hideNativeAgents };
|
|
319
332
|
case "sidebarMode":
|