pi-cockpit 0.22.1 → 0.23.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 +15 -15
- package/package.json +3 -3
- package/src/compaction-style.ts +233 -0
- package/src/footer.ts +5 -2
- package/src/index.ts +87 -6
- package/src/input-routing.ts +6 -2
- package/src/session-detail.ts +1 -1
- package/src/stack-widget.ts +4 -1
- package/src/target-integration.ts +305 -0
- package/src/target-routing.ts +667 -0
- package/src/types.ts +10 -10
- package/src/window-thread-view.ts +8 -1
- package/src/zen-render.ts +5 -2
package/README.md
CHANGED
|
@@ -62,8 +62,8 @@ Cockpit has no package dependency on `pi-maestro-flow`. It observes optional pro
|
|
|
62
62
|
```json
|
|
63
63
|
{
|
|
64
64
|
"enabled": true,
|
|
65
|
-
"quietMode":
|
|
66
|
-
"quietSymbols": "
|
|
65
|
+
"quietMode": true,
|
|
66
|
+
"quietSymbols": "dot",
|
|
67
67
|
"agentsMode": "list",
|
|
68
68
|
"todoMode": "list",
|
|
69
69
|
"todoExpanded": false,
|
|
@@ -71,21 +71,21 @@ Cockpit has no package dependency on `pi-maestro-flow`. It observes optional pro
|
|
|
71
71
|
"hideNativeAgents": true,
|
|
72
72
|
"icons": { "mode": "auto" },
|
|
73
73
|
"sidebar": {
|
|
74
|
-
"mode": "
|
|
74
|
+
"mode": "off",
|
|
75
75
|
"width": 40,
|
|
76
76
|
"density": "comfortable"
|
|
77
77
|
},
|
|
78
78
|
"title": {
|
|
79
79
|
"enabled": true,
|
|
80
80
|
"showSession": true,
|
|
81
|
-
"showCwd":
|
|
82
|
-
"showModel":
|
|
83
|
-
"showThinking":
|
|
84
|
-
"showGit":
|
|
85
|
-
"showMaestro":
|
|
81
|
+
"showCwd": true,
|
|
82
|
+
"showModel": true,
|
|
83
|
+
"showThinking": true,
|
|
84
|
+
"showGit": true,
|
|
85
|
+
"showMaestro": true,
|
|
86
86
|
"maxLength": 80
|
|
87
87
|
},
|
|
88
|
-
"theme": "",
|
|
88
|
+
"theme": "cockpit-zen",
|
|
89
89
|
"usage": {
|
|
90
90
|
"enabled": true,
|
|
91
91
|
"footer": true,
|
|
@@ -108,16 +108,16 @@ Cockpit has no package dependency on `pi-maestro-flow`. It observes optional pro
|
|
|
108
108
|
- `icons.mode`: `"auto"` (detect Nerd Font), `"nerd"`, or `"ascii"`.
|
|
109
109
|
- `title.enabled`: master switch for the terminal tab title (session summary + working state + opt-in tags). On by default.
|
|
110
110
|
- `title.showSession`: include the session summary (the `session_info` name, else a short session id) right after `pi`. On by default.
|
|
111
|
-
- `title.showCwd`: include the working directory after the session.
|
|
112
|
-
- `title.showModel`: include the active model tag (`m:gpt-5.6-sol`).
|
|
113
|
-
- `title.showThinking`: include the thinking level tag (`t:high`); skipped while off.
|
|
114
|
-
- `title.showGit`: include the git branch tag (`git:main`, `git:detached`); read synchronously from `.git/HEAD`, so it costs no process spawn.
|
|
115
|
-
- `title.showMaestro`: include the Maestro workflow status tag (`wf:running`, `wf:done`).
|
|
111
|
+
- `title.showCwd`: include the working directory after the session. On by default.
|
|
112
|
+
- `title.showModel`: include the active model tag (`m:gpt-5.6-sol`). On by default.
|
|
113
|
+
- `title.showThinking`: include the thinking level tag (`t:high`); skipped while off. On by default.
|
|
114
|
+
- `title.showGit`: include the git branch tag (`git:main`, `git:detached`); read synchronously from `.git/HEAD`, so it costs no process spawn. On by default.
|
|
115
|
+
- `title.showMaestro`: include the Maestro workflow status tag (`wf:running`, `wf:done`). On by default.
|
|
116
116
|
- `title.maxLength`: hard cap on the composed title; the middle is ellided to keep the head and the working-state tail. Clamped to `20..200`; default `80`.
|
|
117
117
|
- `title.generationModel`: `"provider/model"` (e.g. `"maestro-qwen/qwen3.8-max"`) of a model registered with the `/api-manager` command (or any other provider pi resolves). When set, the session title is generated by that model over the first completed turn (user prompt + assistant reply) through its OpenAI-compatible endpoint, with a 10s timeout. Empty (default) uses the offline rule-based extractor instead. Generation is best-effort — on any failure the title silently falls back to the rule-based one. Editable from the `/cockpit` settings overlay (`z` or cursor to the `title gen model` row, Enter, type, Enter to save; empty clears back to rule-based).
|
|
118
118
|
|
|
119
119
|
The tab title is `frame + pi - <session> - <working state>`. The session part follows Claude Code's chain (`sessionTitle ?? agentTitle ?? haikuTitle ?? default`): the `/session name` title wins, otherwise the generated title, otherwise a short session id. The frame is Claude Code's title chrome: `⠂`/`⠐` braille spinner while a turn runs, static `✳` when idle; failure replaces it with `✗`. On quit, Cockpit clears the title so no stale tab label lingers (Claude Code's `CLEAR_TERMINAL_TITLE`).
|
|
120
|
-
- `theme`: named theme override; empty string follows the Pi session theme.
|
|
120
|
+
- `theme`: named theme override; defaults to `"cockpit-zen"`. An empty string follows the Pi session theme.
|
|
121
121
|
- `usage.enabled`: master switch for the usage bars (quota, balance, spend). On by default; off hides the footer segment and disables the `/usage` command. Ports the [`hknet/pi-usage-bars`](https://github.com/hknet/pi-usage-bars) extension.
|
|
122
122
|
- `usage.footer`: when `true`, render the live quota bar on a dedicated footer line. The `/usage` command works regardless of this toggle. On by default.
|
|
123
123
|
- `usage.pollIntervalMs`: how often to refresh usage data, in milliseconds. Clamped to `30000`..`1800000` (30s..30min); `0` switches to manual refresh — no background polling and no footer bar; the `/usage` overlay fetches on open and re-fetches with `r`. Default `120000` (2min). Lower values hit provider APIs more often.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-cockpit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.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/todo-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/teammate-message.test.ts tests/input-routing.test.ts tests/input-history-store.test.ts tests/unified-editor.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 tests/usage-core.test.ts tests/usage-extension.test.ts tests/usage-history.test.ts tests/usage-sparkline.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/compaction-style.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/todo-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/teammate-message.test.ts tests/input-routing.test.ts tests/input-history-store.test.ts tests/unified-editor.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 tests/usage-core.test.ts tests/usage-extension.test.ts tests/usage-history.test.ts tests/usage-sparkline.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.84.4",
|
|
73
73
|
"@earendil-works/pi-tui": "0.84.4",
|
|
74
74
|
"@types/node": "^22.0.0",
|
|
75
|
-
"pi-maestro-teammate": "2.
|
|
75
|
+
"pi-maestro-teammate": "2.6.0",
|
|
76
76
|
"typebox": "1.3.7",
|
|
77
77
|
"typescript": "^5.5.0"
|
|
78
78
|
},
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CompactionSummaryMessageComponent,
|
|
3
|
+
keyText,
|
|
4
|
+
} from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import {
|
|
6
|
+
truncateToWidth,
|
|
7
|
+
type Component,
|
|
8
|
+
visibleWidth,
|
|
9
|
+
wrapTextWithAnsi,
|
|
10
|
+
} from "@earendil-works/pi-tui";
|
|
11
|
+
import type { IconGlyphs } from "./icons.ts";
|
|
12
|
+
|
|
13
|
+
const COMPACTION_STYLE_MARKER = Symbol.for("pi-cockpit.compaction-style");
|
|
14
|
+
const NEW_CONTEXT_CAPSULE = "Maestro New Context Recovery Capsule";
|
|
15
|
+
|
|
16
|
+
interface CompactionSummaryLike {
|
|
17
|
+
summary: string;
|
|
18
|
+
tokensBefore: number;
|
|
19
|
+
timestamp: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface CompactionComponentInternals {
|
|
23
|
+
expanded: boolean;
|
|
24
|
+
message: CompactionSummaryLike;
|
|
25
|
+
clear(): void;
|
|
26
|
+
addChild(component: Component): void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface CompactionTheme {
|
|
30
|
+
fg(role: string, text: string): string;
|
|
31
|
+
bold(text: string): string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface StyleProvider {
|
|
35
|
+
isEnabled(): boolean;
|
|
36
|
+
getTheme(): CompactionTheme | undefined;
|
|
37
|
+
getGlyphs(): IconGlyphs;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const PLAIN_THEME: CompactionTheme = {
|
|
41
|
+
fg: (_role, text) => text,
|
|
42
|
+
bold: (text) => text,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
type UpdateDisplay = (this: CompactionComponentInternals) => void;
|
|
46
|
+
|
|
47
|
+
interface CompactionStyleMarker {
|
|
48
|
+
original: UpdateDisplay;
|
|
49
|
+
retain(provider: StyleProvider): () => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface CompactionStylePatch {
|
|
53
|
+
active: boolean;
|
|
54
|
+
detach(): void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface CompactionSummaryDetails {
|
|
58
|
+
kind: "new-context" | "compaction";
|
|
59
|
+
checkpoint?: string;
|
|
60
|
+
todo?: string;
|
|
61
|
+
nextTask?: string;
|
|
62
|
+
lineCount: number;
|
|
63
|
+
byteCount: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function once(action: () => void): () => void {
|
|
67
|
+
let called = false;
|
|
68
|
+
return () => {
|
|
69
|
+
if (called) return;
|
|
70
|
+
called = true;
|
|
71
|
+
action();
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function clean(value: string, max = 180): string {
|
|
76
|
+
const safe = value
|
|
77
|
+
.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
|
|
78
|
+
.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F\u200B-\u200F\u202A-\u202E\u2060-\u206F\uFEFF]/g, " ")
|
|
79
|
+
.replace(/\s+/g, " ")
|
|
80
|
+
.trim();
|
|
81
|
+
return safe.length > max ? `${safe.slice(0, max - 1)}…` : safe;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function field(summary: string, label: string): string | undefined {
|
|
85
|
+
const line = summary.split("\n").find((candidate) => candidate.trimStart().startsWith(`- ${label}:`));
|
|
86
|
+
if (!line) return undefined;
|
|
87
|
+
const value = clean(line.slice(line.indexOf(":") + 1));
|
|
88
|
+
return value || undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function nextTask(summary: string): string | undefined {
|
|
92
|
+
const section = summary.match(/## (?:Active Todo Tasks|Runnable Pending Frontier)\s*\n- \[#([^\]]+)\] ([^\n]+)/);
|
|
93
|
+
if (!section) return undefined;
|
|
94
|
+
const subject = clean(section[2].replace(/ \((?:in_progress|pending|blocked|completed|deleted); assignee=.*$/, ""));
|
|
95
|
+
return clean(`#${section[1]} ${subject}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function compactionSummaryDetails(summary: string): CompactionSummaryDetails {
|
|
99
|
+
const normalized = typeof summary === "string" ? summary : "";
|
|
100
|
+
return {
|
|
101
|
+
kind: normalized.includes(NEW_CONTEXT_CAPSULE) || normalized.startsWith("<recovery_capsule")
|
|
102
|
+
? "new-context"
|
|
103
|
+
: "compaction",
|
|
104
|
+
checkpoint: field(normalized, "Checkpoint ID"),
|
|
105
|
+
todo: field(normalized, "Todo"),
|
|
106
|
+
nextTask: nextTask(normalized),
|
|
107
|
+
lineCount: normalized === "" ? 0 : normalized.split("\n").length,
|
|
108
|
+
byteCount: Buffer.byteLength(normalized, "utf8"),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function formatBytes(bytes: number): string {
|
|
113
|
+
if (bytes < 1_024) return `${bytes}B`;
|
|
114
|
+
const kib = bytes / 1_024;
|
|
115
|
+
return `${kib >= 10 ? Math.round(kib) : kib.toFixed(1)}KB`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function card(message: CompactionSummaryLike, provider: StyleProvider): Component {
|
|
119
|
+
return {
|
|
120
|
+
render(width: number): string[] {
|
|
121
|
+
const safeWidth = Math.max(1, width);
|
|
122
|
+
if (safeWidth <= 1) return [];
|
|
123
|
+
const theme = provider.getTheme() ?? PLAIN_THEME;
|
|
124
|
+
const glyphs = provider.getGlyphs();
|
|
125
|
+
const expandHint = keyText("app.tools.expand") || "ctrl+o";
|
|
126
|
+
const tokenText = message.tokensBefore.toLocaleString("en-US");
|
|
127
|
+
if (!provider.isEnabled()) {
|
|
128
|
+
return [
|
|
129
|
+
truncateToWidth(theme.fg("customMessageLabel", theme.bold("[compaction]")), safeWidth, glyphs.ellipsis),
|
|
130
|
+
"",
|
|
131
|
+
truncateToWidth(
|
|
132
|
+
theme.fg("customMessageText", `Compacted from ${tokenText} tokens (`)
|
|
133
|
+
+ theme.fg("dim", expandHint)
|
|
134
|
+
+ theme.fg("customMessageText", " to expand)"),
|
|
135
|
+
safeWidth,
|
|
136
|
+
glyphs.ellipsis,
|
|
137
|
+
),
|
|
138
|
+
];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const details = compactionSummaryDetails(message.summary);
|
|
142
|
+
const title = details.kind === "new-context" ? "new context" : "compaction";
|
|
143
|
+
const summaryKind = details.kind === "new-context" ? "deterministic recovery capsule" : "model summary";
|
|
144
|
+
const rows = [`${theme.fg("success", `${glyphs.check} complete`)} ${summaryKind}`];
|
|
145
|
+
if (details.todo) rows.push(`${theme.fg("accent", `${glyphs.pending} todo`)} ${details.todo}`);
|
|
146
|
+
if (details.nextTask) rows.push(`${theme.fg("warning", `${glyphs.arrow} next`)} ${details.nextTask}`);
|
|
147
|
+
rows.push(theme.fg("dim", [
|
|
148
|
+
details.checkpoint ? `checkpoint ${details.checkpoint}` : "summary",
|
|
149
|
+
`${details.lineCount} lines`,
|
|
150
|
+
formatBytes(details.byteCount),
|
|
151
|
+
`${expandHint} expand`,
|
|
152
|
+
].join(" · ")));
|
|
153
|
+
|
|
154
|
+
const cardWidth = safeWidth - 1;
|
|
155
|
+
const label = `${theme.fg("success", glyphs.check)} ${theme.fg("toolTitle", theme.bold(title))} ${theme.fg("dim", `· ${tokenText} tokens`)}`;
|
|
156
|
+
if (cardWidth < 6) return [truncateToWidth(label, cardWidth, glyphs.ellipsis)];
|
|
157
|
+
|
|
158
|
+
const innerWidth = cardWidth - 2;
|
|
159
|
+
const contentWidth = Math.max(1, innerWidth - 2);
|
|
160
|
+
const fit = (text: string, target: number): string => {
|
|
161
|
+
const clipped = truncateToWidth(text, target, glyphs.ellipsis);
|
|
162
|
+
return `${clipped}${" ".repeat(Math.max(0, target - visibleWidth(clipped)))}`;
|
|
163
|
+
};
|
|
164
|
+
const header = truncateToWidth(` ${label} `, innerWidth, glyphs.ellipsis);
|
|
165
|
+
const body = rows.flatMap((row) => wrapTextWithAnsi(row, contentWidth));
|
|
166
|
+
return [
|
|
167
|
+
`${theme.fg("dim", glyphs.box.topLeft)}${header}${theme.fg("dim", `${glyphs.box.horizontal.repeat(Math.max(0, innerWidth - visibleWidth(header)))}${glyphs.box.topRight}`)}`,
|
|
168
|
+
...body.map((row) => `${theme.fg("dim", glyphs.box.vertical)} ${fit(row, contentWidth)} ${theme.fg("dim", glyphs.box.vertical)}`),
|
|
169
|
+
theme.fg("dim", `${glyphs.box.bottomLeft}${glyphs.box.horizontal.repeat(innerWidth)}${glyphs.box.bottomRight}`),
|
|
170
|
+
];
|
|
171
|
+
},
|
|
172
|
+
invalidate(): void {},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function markerOf(value: unknown): CompactionStyleMarker | undefined {
|
|
177
|
+
if (typeof value !== "function") return undefined;
|
|
178
|
+
return (value as UpdateDisplay & Record<symbol, CompactionStyleMarker | undefined>)[COMPACTION_STYLE_MARKER];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Pi does not expose a renderer hook for its built-in compaction message. Patch
|
|
183
|
+
* the exported component's stable method while Cockpit is active, and retain the
|
|
184
|
+
* native expanded Markdown view and a reversible fallback for incompatible hosts.
|
|
185
|
+
*/
|
|
186
|
+
export function attachCompactionStyle(provider: StyleProvider): CompactionStylePatch {
|
|
187
|
+
try {
|
|
188
|
+
const prototype = CompactionSummaryMessageComponent.prototype as unknown as object;
|
|
189
|
+
const descriptor = Object.getOwnPropertyDescriptor(prototype, "updateDisplay");
|
|
190
|
+
if (!descriptor || typeof descriptor.value !== "function" || descriptor.writable !== true) {
|
|
191
|
+
return { active: false, detach() {} };
|
|
192
|
+
}
|
|
193
|
+
const existing = markerOf(descriptor.value);
|
|
194
|
+
if (existing) return { active: true, detach: existing.retain(provider) };
|
|
195
|
+
|
|
196
|
+
const original = descriptor.value as UpdateDisplay;
|
|
197
|
+
const providers: StyleProvider[] = [provider];
|
|
198
|
+
const wrapped: UpdateDisplay = function () {
|
|
199
|
+
const active = providers.at(-1);
|
|
200
|
+
if (!active || this.expanded || typeof this.message?.summary !== "string") {
|
|
201
|
+
original.call(this);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
this.clear();
|
|
206
|
+
this.addChild(card(this.message, active));
|
|
207
|
+
} catch {
|
|
208
|
+
original.call(this);
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
const release = (retained: StyleProvider): void => {
|
|
212
|
+
const index = providers.lastIndexOf(retained);
|
|
213
|
+
if (index >= 0) providers.splice(index, 1);
|
|
214
|
+
if (providers.length === 0 && Object.getOwnPropertyDescriptor(prototype, "updateDisplay")?.value === wrapped) {
|
|
215
|
+
Object.defineProperty(prototype, "updateDisplay", descriptor);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
const retain = (retained: StyleProvider): (() => void) => {
|
|
219
|
+
providers.push(retained);
|
|
220
|
+
return once(() => release(retained));
|
|
221
|
+
};
|
|
222
|
+
Object.defineProperty(wrapped, COMPACTION_STYLE_MARKER, {
|
|
223
|
+
value: { original, retain } satisfies CompactionStyleMarker,
|
|
224
|
+
});
|
|
225
|
+
Object.defineProperty(prototype, "updateDisplay", { ...descriptor, value: wrapped });
|
|
226
|
+
if (Object.getOwnPropertyDescriptor(prototype, "updateDisplay")?.value !== wrapped) {
|
|
227
|
+
return { active: false, detach() {} };
|
|
228
|
+
}
|
|
229
|
+
return { active: true, detach: once(() => release(provider)) };
|
|
230
|
+
} catch {
|
|
231
|
+
return { active: false, detach() {} };
|
|
232
|
+
}
|
|
233
|
+
}
|
package/src/footer.ts
CHANGED
|
@@ -238,8 +238,11 @@ function alignRight(left: string, right: string, width: number, measure: WidthUt
|
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
export function renderFooter(p: FooterParts): string[] {
|
|
241
|
-
const { width, theme, glyphs: g, utils } = p;
|
|
242
|
-
if (
|
|
241
|
+
const { width: viewportWidth, theme, glyphs: g, utils } = p;
|
|
242
|
+
if (viewportWidth <= 0) return [""];
|
|
243
|
+
// Footer values repaint on usage, Git, workflow and background-job updates.
|
|
244
|
+
// Reserve the last terminal column so those rewrites cannot trigger auto-wrap.
|
|
245
|
+
const width = Math.max(1, viewportWidth - 1);
|
|
243
246
|
const ell = theme.fg("dim", g.ellipsis);
|
|
244
247
|
const sep = theme.fg("dim", g.separator.trim());
|
|
245
248
|
|
package/src/index.ts
CHANGED
|
@@ -25,6 +25,7 @@ import { MaestroStore } from "./maestro-store.ts";
|
|
|
25
25
|
import { createSidebarController, type SidebarController } from "./sidebar-controller.ts";
|
|
26
26
|
import { COCKPIT_SPLIT_PANE_MARKER } from "./split-pane.ts";
|
|
27
27
|
import { attachViewportStability, type ViewportStabilityPatch } from "./viewport-stability.ts";
|
|
28
|
+
import { attachCompactionStyle, type CompactionStylePatch } from "./compaction-style.ts";
|
|
28
29
|
import {
|
|
29
30
|
COCKPIT_EDITOR_BOTTOM_MARKER,
|
|
30
31
|
EDITOR_BOTTOM_WIDGET_KEY,
|
|
@@ -86,7 +87,7 @@ import {
|
|
|
86
87
|
buildZenTaskSheet,
|
|
87
88
|
type ZenSheetDocument,
|
|
88
89
|
} from "./zen-sheet.ts";
|
|
89
|
-
import { isLegacyTodoOverlayInput, routeAgentInput } from "./input-routing.ts";
|
|
90
|
+
import { isLegacyTodoOverlayInput, isLocalInputText, routeAgentInput } from "./input-routing.ts";
|
|
90
91
|
import { activeThemeName, ThemePicker } from "./theme-picker.ts";
|
|
91
92
|
import { ModelPicker, type ModelPickerEntry } from "./model-picker.ts";
|
|
92
93
|
import { getUsageTotals, invalidateUsageCache, renderFooter, setUsageThrottle, type PaintTheme, type WidthUtils } from "./footer.ts";
|
|
@@ -131,6 +132,13 @@ import {
|
|
|
131
132
|
type CockpitConfig,
|
|
132
133
|
} from "./types.ts";
|
|
133
134
|
import type { MailboxHostRegistry } from "pi-maestro-teammate/v1/mailbox";
|
|
135
|
+
import {
|
|
136
|
+
buildCockpitTargetCatalogue,
|
|
137
|
+
createTargetAutocompleteProvider,
|
|
138
|
+
formatPendingTargetContext,
|
|
139
|
+
routeCanonicalTargetInput,
|
|
140
|
+
type PendingTargetReference,
|
|
141
|
+
} from "./target-integration.ts";
|
|
134
142
|
|
|
135
143
|
export {
|
|
136
144
|
EndpointStore,
|
|
@@ -153,6 +161,16 @@ export { makeWindowThreadWidget, renderWindowThreadView, windowThreadBody } from
|
|
|
153
161
|
|
|
154
162
|
const MAILBOX_REGISTRY_KEY = Symbol.for("pi-maestro-teammate.mailbox-registry");
|
|
155
163
|
|
|
164
|
+
async function listSshHostPickerEntries() {
|
|
165
|
+
const sshHosts = await import("pi-maestro-teammate/v1/ssh-hosts");
|
|
166
|
+
return sshHosts.listSshHostPickerEntries();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function activateSshHost(hostId: string): Promise<void> {
|
|
170
|
+
const sshHosts = await import("pi-maestro-teammate/v1/ssh-hosts");
|
|
171
|
+
await sshHosts.activateSshHost(hostId);
|
|
172
|
+
}
|
|
173
|
+
|
|
156
174
|
const FOOTER_UTILS: WidthUtils = { measure: visibleWidth, clip: truncateToWidth };
|
|
157
175
|
const BASH_BG_OVERLAY_KEY = "alt+j";
|
|
158
176
|
const TODO_OVERLAY_KEY = "alt+shift+t";
|
|
@@ -361,6 +379,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
361
379
|
// thinking label / teammate tree still stream above the visible viewport.
|
|
362
380
|
let stabilityTui: TUI | undefined;
|
|
363
381
|
let viewportStabilityPatch: ViewportStabilityPatch | undefined;
|
|
382
|
+
let compactionStylePatch: CompactionStylePatch | undefined;
|
|
364
383
|
const ensureViewportStability = (tui: TUI): void => {
|
|
365
384
|
// Native fullscreen owns a fixed application viewport and has no main-screen
|
|
366
385
|
// applyLineResets hook. Do not cache that expected miss: the same dynamic TUI
|
|
@@ -433,6 +452,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
433
452
|
// stand-in for Claude Code's Haiku title). Cleared per session.
|
|
434
453
|
let aiTitle: string | undefined;
|
|
435
454
|
let firstUserText: string | undefined;
|
|
455
|
+
let pendingTargetReference: PendingTargetReference | undefined;
|
|
436
456
|
let titleRequested = false;
|
|
437
457
|
let titleFrameIndex = 0;
|
|
438
458
|
let mainOutputRevision = 0;
|
|
@@ -465,6 +485,11 @@ export default function (pi: ExtensionAPI): void {
|
|
|
465
485
|
// AgentSession construction — before renderInitialMessages runs.
|
|
466
486
|
ensureConfigExists();
|
|
467
487
|
config = loadConfig();
|
|
488
|
+
compactionStylePatch = attachCompactionStyle({
|
|
489
|
+
isEnabled: () => config.enabled,
|
|
490
|
+
getTheme: () => lastCtx?.ui.theme,
|
|
491
|
+
getGlyphs: () => resolveGlyphs(config.icons.mode),
|
|
492
|
+
});
|
|
468
493
|
// Guarded edit replaces the built-in edit (same name, same execution, plus a
|
|
469
494
|
// UTF-8 gate): editing a non-UTF-8 file would otherwise corrupt its bytes.
|
|
470
495
|
registerGuardedEditTool(pi);
|
|
@@ -912,6 +937,13 @@ export default function (pi: ExtensionAPI): void {
|
|
|
912
937
|
current: tuiT("window.autocompleteCurrent"),
|
|
913
938
|
peer: tuiT("window.autocompletePeer"),
|
|
914
939
|
});
|
|
940
|
+
const targetCatalogue = async () => {
|
|
941
|
+
try {
|
|
942
|
+
return buildCockpitTargetCatalogue(endpoints.snapshot(), await listSshHostPickerEntries(), true);
|
|
943
|
+
} catch {
|
|
944
|
+
return buildCockpitTargetCatalogue(endpoints.snapshot(), [], false);
|
|
945
|
+
}
|
|
946
|
+
};
|
|
915
947
|
|
|
916
948
|
const publishInputTarget = (force = false): void => {
|
|
917
949
|
const target = sessionUi.mode === "window" ? selectedWindowInputTarget() : selectedAgentTarget();
|
|
@@ -1841,6 +1873,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1841
1873
|
maestro.clear();
|
|
1842
1874
|
aiTitle = undefined;
|
|
1843
1875
|
firstUserText = undefined;
|
|
1876
|
+
pendingTargetReference = undefined;
|
|
1844
1877
|
titleRequested = false;
|
|
1845
1878
|
titleFrameIndex = 0;
|
|
1846
1879
|
titleGeneration += 1;
|
|
@@ -1855,10 +1888,13 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1855
1888
|
settingsLocale.reload();
|
|
1856
1889
|
settingsRegistry.emitLocale(settingsLocale.locale);
|
|
1857
1890
|
selectedHashWindowTargets.clear();
|
|
1858
|
-
ctx.ui.addAutocompleteProvider((current) =>
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1891
|
+
ctx.ui.addAutocompleteProvider((current) => createTargetAutocompleteProvider(
|
|
1892
|
+
createWindowAutocompleteProvider(
|
|
1893
|
+
current,
|
|
1894
|
+
() => config.enabled ? windowAutocompleteTargets() : [],
|
|
1895
|
+
(target) => selectedHashWindowTargets.set(target.token.toLocaleLowerCase("en"), target),
|
|
1896
|
+
),
|
|
1897
|
+
async () => config.enabled ? targetCatalogue() : buildCockpitTargetCatalogue(endpoints.snapshot(), [], false),
|
|
1862
1898
|
));
|
|
1863
1899
|
invalidateUsageCache();
|
|
1864
1900
|
// Quiet mode: register compact tool renderers and fold thinking blocks.
|
|
@@ -1952,7 +1988,10 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1952
1988
|
}
|
|
1953
1989
|
settingsProviderDisposer = undefined;
|
|
1954
1990
|
endpoints.disconnect();
|
|
1991
|
+
compactionStylePatch?.detach();
|
|
1992
|
+
compactionStylePatch = undefined;
|
|
1955
1993
|
lastCtx = undefined;
|
|
1994
|
+
pendingTargetReference = undefined;
|
|
1956
1995
|
running = false;
|
|
1957
1996
|
runningStartedAt = undefined;
|
|
1958
1997
|
uiPromptDepth = 0;
|
|
@@ -1971,6 +2010,13 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1971
2010
|
usageSubsystem?.stop(ctx);
|
|
1972
2011
|
});
|
|
1973
2012
|
|
|
2013
|
+
pi.on("before_agent_start", async (event) => {
|
|
2014
|
+
const reference = pendingTargetReference;
|
|
2015
|
+
pendingTargetReference = undefined;
|
|
2016
|
+
if (!reference) return undefined;
|
|
2017
|
+
const context = formatPendingTargetContext(reference, await targetCatalogue());
|
|
2018
|
+
return context ? { systemPrompt: `${event.systemPrompt}\n\n${context}` } : undefined;
|
|
2019
|
+
});
|
|
1974
2020
|
pi.on("agent_start", () => {
|
|
1975
2021
|
running = true;
|
|
1976
2022
|
runningStartedAt = Date.now();
|
|
@@ -1978,6 +2024,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1978
2024
|
req();
|
|
1979
2025
|
});
|
|
1980
2026
|
pi.on("agent_end", () => {
|
|
2027
|
+
pendingTargetReference = undefined;
|
|
1981
2028
|
running = false;
|
|
1982
2029
|
runningStartedAt = undefined;
|
|
1983
2030
|
activeTools.clear();
|
|
@@ -2038,7 +2085,41 @@ export default function (pi: ExtensionAPI): void {
|
|
|
2038
2085
|
const mailboxRegistry = globals[MAILBOX_REGISTRY_KEY] as MailboxHostRegistry | undefined;
|
|
2039
2086
|
const hasImages = (e.images?.length ?? 0) > 0;
|
|
2040
2087
|
const interactiveText = e.source === "interactive" && e.text.trim().length > 0;
|
|
2041
|
-
|
|
2088
|
+
|
|
2089
|
+
if (config.enabled && e.source === "interactive") {
|
|
2090
|
+
pendingTargetReference = undefined;
|
|
2091
|
+
const canonical = await routeCanonicalTargetInput({
|
|
2092
|
+
text: e.text,
|
|
2093
|
+
hasImages,
|
|
2094
|
+
getCatalogue: targetCatalogue,
|
|
2095
|
+
activateSsh: activateSshHost,
|
|
2096
|
+
send: async (request) => {
|
|
2097
|
+
const liveRegistry = sessionRegistry() ?? endpoints.registry;
|
|
2098
|
+
const delivery = await (liveRegistry?.send?.({
|
|
2099
|
+
...request,
|
|
2100
|
+
source: "user",
|
|
2101
|
+
}) ?? liveRegistry?.router?.route({
|
|
2102
|
+
...request,
|
|
2103
|
+
source: "user",
|
|
2104
|
+
}));
|
|
2105
|
+
return delivery ?? { delivered: false, error: tuiT("notice.deliveryRegistryUnavailable") };
|
|
2106
|
+
},
|
|
2107
|
+
notify: (message, type) => ctx.ui.notify(message, type),
|
|
2108
|
+
restore: (text) => {
|
|
2109
|
+
const selectedId = sessionUi.selectedId(sessionUi.mode);
|
|
2110
|
+
if (selectedId) sessionUi.setDraft(selectedId, text);
|
|
2111
|
+
ctx.ui.setEditorText(text);
|
|
2112
|
+
},
|
|
2113
|
+
});
|
|
2114
|
+
if (canonical?.action === "handled") return { action: "handled" as const };
|
|
2115
|
+
if (canonical?.action === "transform") {
|
|
2116
|
+
pendingTargetReference = canonical.reference;
|
|
2117
|
+
if (!firstUserText) firstUserText = canonical.text;
|
|
2118
|
+
return { action: "transform" as const, text: canonical.text };
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
const isSynthetic = isLocalInputText(e.text);
|
|
2042
2123
|
const hashWindowRoute = config.enabled && e.source === "interactive" && !isSynthetic
|
|
2043
2124
|
? resolveWindowRouteInput(e.text, windowAutocompleteTargets(), selectedHashWindowTargets)
|
|
2044
2125
|
: undefined;
|
package/src/input-routing.ts
CHANGED
|
@@ -38,6 +38,11 @@ export function isLegacyTodoOverlayInput(data: string): boolean {
|
|
|
38
38
|
return data === "\x1bT";
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/** Inputs owned by the local Pi host rather than the selected Agent/Window route. */
|
|
42
|
+
export function isLocalInputText(text: string): boolean {
|
|
43
|
+
return text.startsWith("/") || text.startsWith("!") || text.trim().toLocaleLowerCase("en") === "#ssh";
|
|
44
|
+
}
|
|
45
|
+
|
|
41
46
|
function inputRegistries(provider: AgentInputRegistryProvider | undefined): AgentInputRegistries {
|
|
42
47
|
if (provider && "deliverAgentMessage" in provider) return { mailbox: provider };
|
|
43
48
|
return provider ?? {};
|
|
@@ -56,8 +61,7 @@ export async function routeAgentInput(
|
|
|
56
61
|
): Promise<"continue" | "handled"> {
|
|
57
62
|
const hasImages = (event.images?.length ?? 0) > 0;
|
|
58
63
|
const interactive = event.source === "interactive" && (event.text.trim().length > 0 || hasImages);
|
|
59
|
-
|
|
60
|
-
if (!interactive || synthetic || !target) return "continue";
|
|
64
|
+
if (!interactive || isLocalInputText(event.text) || !target) return "continue";
|
|
61
65
|
|
|
62
66
|
if (hasImages) {
|
|
63
67
|
ui.notify(tuiT("notice.agentImage", { label: target.label }), "warning");
|
package/src/session-detail.ts
CHANGED
package/src/stack-widget.ts
CHANGED
|
@@ -72,6 +72,9 @@ export function makeTodoWidget(deps: TodoWidgetDeps) {
|
|
|
72
72
|
const cfg = deps.getConfig();
|
|
73
73
|
const todos = deps.getTodos();
|
|
74
74
|
if (todos.length === 0) return [];
|
|
75
|
+
// Todo rows repaint as task state changes. Keep the terminal's final
|
|
76
|
+
// column empty so auto-wrap cannot desynchronize pi-tui's cursor model.
|
|
77
|
+
const liveWidth = Math.max(1, width - 1);
|
|
75
78
|
const expanded = deps.getExpanded?.() ?? cfg.todoExpanded;
|
|
76
79
|
const g = resolveGlyphs(cfg.icons.mode);
|
|
77
80
|
// Todo row glyphs come from the glyph table (a hollow rectangle for
|
|
@@ -81,7 +84,7 @@ export function makeTodoWidget(deps: TodoWidgetDeps) {
|
|
|
81
84
|
expanded,
|
|
82
85
|
maxRows: panelRows(terminalRows(tui)),
|
|
83
86
|
};
|
|
84
|
-
return renderTodos(todos, expanded ? "list" : cfg.todoMode,
|
|
87
|
+
return renderTodos(todos, expanded ? "list" : cfg.todoMode, liveWidth, paint, UTILS, opts);
|
|
85
88
|
},
|
|
86
89
|
invalidate(): void {},
|
|
87
90
|
dispose(): void {},
|