@giladbarnea/pi-user-agents 0.0.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/INTERNALS.md +65 -0
- package/PARSER_SPEC.md +264 -0
- package/README.md +127 -0
- package/agent-viewer.ts +653 -0
- package/autocomplete.ts +576 -0
- package/command-line.ts +746 -0
- package/context-meter.ts +41 -0
- package/editor-coloring.ts +160 -0
- package/index.ts +74 -0
- package/package.json +44 -0
- package/runner.ts +591 -0
- package/shared.ts +214 -0
- package/tool-render/FORK.md +108 -0
- package/tool-render/ansi.ts +77 -0
- package/tool-render/constants.ts +48 -0
- package/tool-render/diff.ts +957 -0
- package/tool-render/index.ts +137 -0
- package/tool-render/text.ts +212 -0
- package/tool-render/theme.ts +71 -0
- package/tool-render/tools.ts +502 -0
- package/transcript.ts +303 -0
- package/widget.ts +523 -0
package/shared.ts
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { appendFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import type {
|
|
4
|
+
AgentSession,
|
|
5
|
+
buildSessionContext,
|
|
6
|
+
ExtensionAPI,
|
|
7
|
+
ExtensionCommandContext,
|
|
8
|
+
} from "@earendil-works/pi-coding-agent";
|
|
9
|
+
|
|
10
|
+
export type { AgentSession, ExtensionAPI, ExtensionCommandContext };
|
|
11
|
+
|
|
12
|
+
export type AgentMessage = ReturnType<typeof buildSessionContext>["messages"][number];
|
|
13
|
+
export type Model = NonNullable<ExtensionCommandContext["model"]>;
|
|
14
|
+
export type ThinkingLevel = ReturnType<ExtensionAPI["getThinkingLevel"]>;
|
|
15
|
+
export type Theme = ExtensionCommandContext["ui"]["theme"];
|
|
16
|
+
export type UIContext = ExtensionCommandContext["ui"];
|
|
17
|
+
|
|
18
|
+
export type AgentCommandName = "agent";
|
|
19
|
+
export type MainContextState = "separate" | "will-join" | "joined";
|
|
20
|
+
export type AgentStatus =
|
|
21
|
+
| "starting"
|
|
22
|
+
| "running"
|
|
23
|
+
| "done-waiting-to-post"
|
|
24
|
+
| "idle"
|
|
25
|
+
| "posted"
|
|
26
|
+
| "error";
|
|
27
|
+
|
|
28
|
+
export type ParsedAgentCommand = {
|
|
29
|
+
isolate: boolean;
|
|
30
|
+
context: boolean;
|
|
31
|
+
/** Leading pi CLI tokens (minus the extension's own options) to forward to the child, e.g. ["--thinking", "high"]. */
|
|
32
|
+
forwardedArgs: string[];
|
|
33
|
+
task: string;
|
|
34
|
+
/** Advisory messages surfaced to the user (e.g. a recognized option was typed inside the prose body). */
|
|
35
|
+
warnings: string[];
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type AgentEntryData = {
|
|
39
|
+
content: string;
|
|
40
|
+
details: AgentCommandDetails;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type AgentResultMessage = {
|
|
44
|
+
customType: string;
|
|
45
|
+
content: string;
|
|
46
|
+
display: boolean;
|
|
47
|
+
details: AgentCommandDetails;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type AgentCommandDetails = {
|
|
51
|
+
agentId?: string;
|
|
52
|
+
command: AgentCommandName;
|
|
53
|
+
mainContextState?: MainContextState;
|
|
54
|
+
inheritedContext: boolean;
|
|
55
|
+
model: string;
|
|
56
|
+
modelLabel: string;
|
|
57
|
+
task: string;
|
|
58
|
+
ok: boolean;
|
|
59
|
+
durationMs?: number;
|
|
60
|
+
toolUses?: number;
|
|
61
|
+
turnCount?: number;
|
|
62
|
+
responseText?: string;
|
|
63
|
+
/** Retained for completed entries written before responseText was persisted. */
|
|
64
|
+
responsePreview?: string;
|
|
65
|
+
error?: string;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export type RunningAgent = {
|
|
69
|
+
id: string;
|
|
70
|
+
command: AgentCommandName;
|
|
71
|
+
inheritedContext: boolean;
|
|
72
|
+
model: string;
|
|
73
|
+
modelLabel: string;
|
|
74
|
+
task: string;
|
|
75
|
+
/** The slash command line as the user typed it, e.g. `/agent -j fix the bug`. */
|
|
76
|
+
invocation: string;
|
|
77
|
+
/** -j/--join: post invocation+result to the main agent and trigger its turn, instead of waiting quietly for the next user prompt. */
|
|
78
|
+
notifyMainAgent: boolean;
|
|
79
|
+
mainContextState: MainContextState;
|
|
80
|
+
status: AgentStatus;
|
|
81
|
+
startedAt: number;
|
|
82
|
+
/** Start of the current turn — equals startedAt until the user resumes an idle agent. */
|
|
83
|
+
turnStartedAt: number;
|
|
84
|
+
completedAt?: number;
|
|
85
|
+
activeTools: Map<string, string>;
|
|
86
|
+
toolUses: number;
|
|
87
|
+
turnCount: number;
|
|
88
|
+
responseText: string;
|
|
89
|
+
/** Append-only child conversation, independent from model context compaction. */
|
|
90
|
+
conversationMessages: AgentMessage[];
|
|
91
|
+
latestFinalizedMessage?: AgentMessage;
|
|
92
|
+
error?: string;
|
|
93
|
+
session?: AgentSession;
|
|
94
|
+
/** Latest completed turn's result message, deliverable to the main agent via the overlay's join action. */
|
|
95
|
+
pendingJoinMessage?: AgentResultMessage;
|
|
96
|
+
/** Starts another turn on an idle agent; assigned while the lifecycle loop awaits the next instruction. */
|
|
97
|
+
resume?: (instruction: string) => void;
|
|
98
|
+
/** Ends an idle agent's lifecycle without another turn; assigned alongside resume. */
|
|
99
|
+
retire?: () => void;
|
|
100
|
+
finished: Promise<void>;
|
|
101
|
+
/** Set when the user closes an in-flight agent from the widget — suppresses the error entry and transcript post. */
|
|
102
|
+
aborted?: boolean;
|
|
103
|
+
/** Set when the user interrupts only the active turn, leaving the child session alive. */
|
|
104
|
+
interruptRequested?: boolean;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export type CompletedAgent = {
|
|
108
|
+
id: string;
|
|
109
|
+
command: AgentCommandName;
|
|
110
|
+
modelLabel: string;
|
|
111
|
+
task: string;
|
|
112
|
+
mainContextState: MainContextState;
|
|
113
|
+
pendingJoinMessage?: AgentResultMessage;
|
|
114
|
+
ok: boolean;
|
|
115
|
+
responseText: string;
|
|
116
|
+
messages: AgentMessage[];
|
|
117
|
+
error?: string;
|
|
118
|
+
startedAt: number;
|
|
119
|
+
durationMs: number;
|
|
120
|
+
toolUses: number;
|
|
121
|
+
turnCount: number;
|
|
122
|
+
contextPercent?: number;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export const MESSAGE_TYPE = "pi-user-agents";
|
|
126
|
+
export const WIDGET_KEY = "pi-user-agents";
|
|
127
|
+
export const STEERING_LOG_PATH = `${tmpdir()}/pi-user-agents-steer.log`;
|
|
128
|
+
|
|
129
|
+
export function logSteering(
|
|
130
|
+
agentId: string,
|
|
131
|
+
event: string,
|
|
132
|
+
details: Record<string, unknown> = {},
|
|
133
|
+
): void {
|
|
134
|
+
void appendFile(
|
|
135
|
+
STEERING_LOG_PATH,
|
|
136
|
+
`${new Date().toISOString()} ${JSON.stringify({ agentId, event, ...details })}\n`,
|
|
137
|
+
).catch(() => undefined);
|
|
138
|
+
}
|
|
139
|
+
export const MAX_WIDGET_LINES = 9;
|
|
140
|
+
export const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
141
|
+
|
|
142
|
+
export const TOOL_DISPLAY: Record<string, string> = {
|
|
143
|
+
read: "reading",
|
|
144
|
+
bash: "running command",
|
|
145
|
+
edit: "editing",
|
|
146
|
+
write: "writing",
|
|
147
|
+
grep: "searching",
|
|
148
|
+
find: "finding files",
|
|
149
|
+
ls: "listing",
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export function formatModel(model: Model): string {
|
|
153
|
+
return `${model.provider}/${model.id}${model.name && model.name !== model.id ? ` (${model.name})` : ""}`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function formatModelLabel(model: Model): string {
|
|
157
|
+
return model.name && model.name !== model.id ? model.name : model.id;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function contextLabel(inheritedContext: boolean): string {
|
|
161
|
+
return inheritedContext ? "inherited context" : "isolated";
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function mainContextLabel(state: MainContextState | undefined): string | undefined {
|
|
165
|
+
if (state === "will-join") return "will join context";
|
|
166
|
+
if (state === "joined") return "joined context";
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function formatTurns(turnCount: number): string {
|
|
171
|
+
return `↻${turnCount}`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function formatToolUses(toolUses: number): string {
|
|
175
|
+
return `${toolUses} tool use${toolUses === 1 ? "" : "s"}`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function formatMs(ms: number): string {
|
|
179
|
+
if (!Number.isFinite(ms)) return "0.0s";
|
|
180
|
+
if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`;
|
|
181
|
+
const minutes = Math.floor(ms / 60_000);
|
|
182
|
+
const seconds = Math.round((ms % 60_000) / 1000);
|
|
183
|
+
return `${minutes}m ${seconds}s`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function truncatePlain(text: string, length: number): string {
|
|
187
|
+
const line =
|
|
188
|
+
text
|
|
189
|
+
.split("\n")
|
|
190
|
+
.find((part) => part.trim())
|
|
191
|
+
?.trim() ?? "";
|
|
192
|
+
if (line.length <= length) return line;
|
|
193
|
+
return `${line.slice(0, Math.max(0, length - 1))}…`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function extractTag(
|
|
197
|
+
content: string,
|
|
198
|
+
tag: "task" | "response" | "error" | "duration_ms",
|
|
199
|
+
): string | undefined {
|
|
200
|
+
const match = content.match(new RegExp(`<${tag}>\\n?([\\s\\S]*?)\\n?</${tag}>`));
|
|
201
|
+
return match?.[1]?.trim();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function escapeAttribute(value: string): string {
|
|
205
|
+
return value
|
|
206
|
+
.replace(/&/g, "&")
|
|
207
|
+
.replace(/"/g, """)
|
|
208
|
+
.replace(/</g, "<")
|
|
209
|
+
.replace(/>/g, ">");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function errorMessage(error: unknown): string {
|
|
213
|
+
return error instanceof Error ? error.message : String(error);
|
|
214
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Provenance and boundary for the overlay's vendored historical tool renderer
|
|
3
|
+
last_updated: 2026-07-31
|
|
4
|
+
---
|
|
5
|
+
# The overlay renders tool history itself
|
|
6
|
+
|
|
7
|
+
The `/agent` overlay shows a captured child-session transcript. It is not the live transcript.
|
|
8
|
+
It has no tool registry, no execution state, and no component lifecycle. `AgentViewer` produces
|
|
9
|
+
a `string[]`, and the overlay hard-cuts every line to its own width instead of wrapping it.
|
|
10
|
+
|
|
11
|
+
That is why this module exists. Pi's `ToolExecutionComponent` resolves current tool definitions
|
|
12
|
+
and models live execution, so it cannot render a finished child session whose tools may no
|
|
13
|
+
longer be registered.
|
|
14
|
+
|
|
15
|
+
## Sources
|
|
16
|
+
|
|
17
|
+
- `../rich-tool-diff/` — `read`, `edit`, `write`, `grep`, `find`, `ls`.
|
|
18
|
+
- `/Users/giladbarnea/.pi/agent/extensions/bash-rich-highlight/index.ts` — `bash`.
|
|
19
|
+
|
|
20
|
+
Both are **disabled** in committed `agent/settings.json`, while `pi-user-agents` is enabled.
|
|
21
|
+
Nothing here imports from either at runtime. The code was copied and adapted, by request.
|
|
22
|
+
|
|
23
|
+
1973 lines here, from 2857 lines of source.
|
|
24
|
+
|
|
25
|
+
## The contract this module guarantees
|
|
26
|
+
|
|
27
|
+
1. Pure. No filesystem access, no `process.stdout`, no timers, no tool registry.
|
|
28
|
+
2. Every returned line fits the supplied width and contains no newline.
|
|
29
|
+
3. Calls pair with results by `ToolCall.id` only. Order is never a fallback, because parallel
|
|
30
|
+
tool calls make it ambiguous. An unpaired call genuinely has no stored result.
|
|
31
|
+
4. Rendering is always expanded, and never mentions an expand key. The overlay has no such key.
|
|
32
|
+
5. Every cap counts **rows drawn**, not lines stored. One line of minified output or one long
|
|
33
|
+
command wraps into many rows at overlay width, so a cap on stored lines caps nothing.
|
|
34
|
+
|
|
35
|
+
## What was deliberately left behind
|
|
36
|
+
|
|
37
|
+
### Live-execution machinery, which has no meaning for history
|
|
38
|
+
|
|
39
|
+
Tool registration wrappers, pending and partial rendering paths, the blink timer, the bash
|
|
40
|
+
duration timer, and expand-key hints. `attachDiffDetails`, `readTextForDiff` and
|
|
41
|
+
`existingSmallTextOrUndefined` read the current filesystem, which is wrong for a transcript
|
|
42
|
+
recorded earlier — the file may have changed or been deleted since.
|
|
43
|
+
|
|
44
|
+
### Settings and glyph lookups, replaced by flat constants
|
|
45
|
+
|
|
46
|
+
`readVstackConfig()` walks parent directories, stats files, reads JSON and parses it, on every
|
|
47
|
+
call. `glyphs(cwd)` sits under several per-line helpers. The overlay re-renders about ten times
|
|
48
|
+
a second, so `constants.ts` holds fixed values instead.
|
|
49
|
+
|
|
50
|
+
Dropping the setting lookups also dropped the ASCII glyph mode. The overlay frame already draws
|
|
51
|
+
Unicode box characters unconditionally, so that branch was unreachable — and removing it took
|
|
52
|
+
the `cwd` argument out of roughly thirty signatures.
|
|
53
|
+
|
|
54
|
+
### Terminal-width reads
|
|
55
|
+
|
|
56
|
+
`renderStructuredDiff()` upstream ignores the width its caller passes and reads
|
|
57
|
+
`process.stdout.columns`. Inside a 90%-width overlay that pads rows past the frame, and the
|
|
58
|
+
overlay then silently cuts their right side. Here the width is a required argument.
|
|
59
|
+
|
|
60
|
+
### Path hyperlinks and Nerd Font icons
|
|
61
|
+
|
|
62
|
+
OSC 8 hyperlinks are zero-width escapes threading through padding and truncation arithmetic
|
|
63
|
+
that the overlay performs itself, in a viewport where clicking a path is marginal. The icon
|
|
64
|
+
table needs a Nerd Font and renders as tofu without one. Dropping hyperlinks also removed the
|
|
65
|
+
last need for a `cwd` argument.
|
|
66
|
+
|
|
67
|
+
### highlight.js
|
|
68
|
+
|
|
69
|
+
`bash-rich-highlight/index.ts` imports highlight.js through an absolute path inside the global Pi
|
|
70
|
+
installation, then hand-parses its HTML output into ANSI — about 180 lines. That import breaks
|
|
71
|
+
when the installation prefix or the Pi version changes. Pi's own exported
|
|
72
|
+
`highlightCode(command, "bash")` returns `string[]` directly.
|
|
73
|
+
|
|
74
|
+
### Bash git-diff detection
|
|
75
|
+
|
|
76
|
+
`renderBashDiffOutput` is gated behind `renderBashDiffs`, which defaults to false.
|
|
77
|
+
|
|
78
|
+
### Images
|
|
79
|
+
|
|
80
|
+
`OverlayAwareImage` already suppresses images while a floating overlay is active.
|
|
81
|
+
|
|
82
|
+
## Kept, against expectation
|
|
83
|
+
|
|
84
|
+
**Split diffs.** They only engage at 132 columns or more, which a wide-terminal overlay reaches.
|
|
85
|
+
|
|
86
|
+
**Diff backgrounds.** `applyFullLineBg` re-opens the background after every escape that resets
|
|
87
|
+
it, so a row carrying syntax highlighting, word-diff highlighting and a row tint stays intact.
|
|
88
|
+
`theme.bg` is optional in `RenderTheme`: a theme without it falls back to fixed colours, which
|
|
89
|
+
is also what keeps the module renderable under a test theme.
|
|
90
|
+
|
|
91
|
+
## Behaviour that intentionally differs from the sources
|
|
92
|
+
|
|
93
|
+
1. Unknown tools show `key=value` arguments. Nested containers become `{2 keys}` or
|
|
94
|
+
`[3 items]`, never serialized JSON — a JSON blob is the exact thing this renderer removes.
|
|
95
|
+
2. A bash result whose exit code is `0` renders as success. The sources render `✗ exit 0`.
|
|
96
|
+
3. A call with no stored result says `no result` rather than trailing off after the headline.
|
|
97
|
+
|
|
98
|
+
## Where this is used
|
|
99
|
+
|
|
100
|
+
`widget.ts` calls it from two places, and both must stay in step:
|
|
101
|
+
|
|
102
|
+
- `AgentViewer.messageLines()` — the transcript body.
|
|
103
|
+
- `describeAssistantPart()` — the one-line activity row under the editor.
|
|
104
|
+
|
|
105
|
+
`AgentViewer` memoizes rendered content, because rich diff rendering is far too costly for the
|
|
106
|
+
widget's 100 ms tick. The memo key covers width, message count, the last message's content
|
|
107
|
+
length, agent status, response length and error length. Streamed content only grows, so a
|
|
108
|
+
length is a sufficient change signal for the streaming tail.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {
|
|
2
|
+
truncateToWidth,
|
|
3
|
+
visibleWidth,
|
|
4
|
+
wrapTextWithAnsi,
|
|
5
|
+
} from "@earendil-works/pi-tui";
|
|
6
|
+
|
|
7
|
+
export const ANSI_BG_RESET = "\x1b[49m";
|
|
8
|
+
|
|
9
|
+
/** Private-use character used to locate where a theme's escape codes open and close. */
|
|
10
|
+
export const STYLE_MARKER = "\uE000";
|
|
11
|
+
|
|
12
|
+
const ANSI_RE = /\x1b(?:\[[0-9;:]*m|\]133;[ABC]\x07|\]8;[^\x07\x1b]*(?:\x07|\x1b\\))/g;
|
|
13
|
+
const ANSI_PRESENT_RE = /\x1b\[[0-9;]*m/;
|
|
14
|
+
|
|
15
|
+
export interface AnsiParts {
|
|
16
|
+
close: string;
|
|
17
|
+
open: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function stripAnsi(text: string): string {
|
|
21
|
+
return text.replace(ANSI_RE, "");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function hasAnsi(text: string): boolean {
|
|
25
|
+
return ANSI_PRESENT_RE.test(text);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function visibleLength(text: string): number {
|
|
29
|
+
return visibleWidth(text);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** @example padVisible("ab", 4) // "ab " */
|
|
33
|
+
export function padVisible(text: string, width: number): string {
|
|
34
|
+
const missing = width - visibleLength(text);
|
|
35
|
+
return missing > 0 ? `${text}${" ".repeat(missing)}` : text;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function truncateAnsi(text: string, width: number): string {
|
|
39
|
+
return truncateToWidth(text, Math.max(1, width), "");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Split a styled marker string into the escape codes that open and close it. */
|
|
43
|
+
export function ansiPartsFromStyled(styled: string): AnsiParts {
|
|
44
|
+
const markerIndex = styled.indexOf(STYLE_MARKER);
|
|
45
|
+
if (markerIndex < 0) return { open: "", close: "" };
|
|
46
|
+
return {
|
|
47
|
+
open: styled.slice(0, markerIndex),
|
|
48
|
+
close: styled.slice(markerIndex + STYLE_MARKER.length),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function ansiHasBackground(open: string): boolean {
|
|
53
|
+
for (const match of open.matchAll(/\x1b\[([0-9;:]*)m/g)) {
|
|
54
|
+
const params = match[1] || "0";
|
|
55
|
+
if (/(^|[;:])48([;:]|$)/.test(params)) return true;
|
|
56
|
+
if (/(^|[;:])(?:4[0-7]|10[0-7])([;:]|$)/.test(params)) return true;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function sgrClearsBackground(code: string): boolean {
|
|
62
|
+
const match = code.match(/^\x1b\[([0-9;]*)m$/);
|
|
63
|
+
if (!match) return false;
|
|
64
|
+
const params = match[1] ? match[1].split(";").map((value) => Number.parseInt(value || "0", 10)) : [0];
|
|
65
|
+
return params.some((value) => value === 0 || value === 49);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Track which foreground style is active so word-diff highlighting can restore it. */
|
|
69
|
+
export function updateActiveAnsiStyle(code: string): string {
|
|
70
|
+
const match = code.match(/^\x1b\[([0-9;]*)m$/);
|
|
71
|
+
if (!match) return "";
|
|
72
|
+
const params = match[1] ? match[1].split(";").map((value) => Number.parseInt(value || "0", 10)) : [0];
|
|
73
|
+
if (params.some((value) => value === 0 || value === 39)) return "";
|
|
74
|
+
return code;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export { truncateToWidth, visibleWidth, wrapTextWithAnsi };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flat constants for the overlay's historical tool renderer.
|
|
3
|
+
*
|
|
4
|
+
* The source extensions read these from `.pi/settings.json` on every call, walking parent
|
|
5
|
+
* directories and parsing JSON. The overlay re-renders about ten times per second, so it
|
|
6
|
+
* uses fixed values instead. It also renders only Unicode glyphs, because the overlay frame
|
|
7
|
+
* already draws Unicode box characters unconditionally.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const GLYPHS = {
|
|
11
|
+
bullet: "● ",
|
|
12
|
+
dot: " · ",
|
|
13
|
+
ellipsis: "…",
|
|
14
|
+
fail: "✗",
|
|
15
|
+
line: "─",
|
|
16
|
+
ok: "✓",
|
|
17
|
+
tree: { mid: "├─ ", last: "└─ ", stem: "│ " },
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
/** The overlay is a scrollable inspection surface, so caps are small on purpose. */
|
|
21
|
+
export const CAPS = {
|
|
22
|
+
/** Diff rows per edit or write result. The source extension allows 4000. */
|
|
23
|
+
diffRows: 120,
|
|
24
|
+
/** Preview lines under a `read` result. The source extension allows 80. */
|
|
25
|
+
readPreviewLines: 20,
|
|
26
|
+
/** Preview lines under a `grep`, `find`, or `ls` result. The source extension allows 80. */
|
|
27
|
+
searchPreviewLines: 20,
|
|
28
|
+
/**
|
|
29
|
+
* Rows drawn inside a `bash` result frame. The source extension allows every line.
|
|
30
|
+
*
|
|
31
|
+
* This counts drawn rows, not stored lines. One stored line of minified output wraps into
|
|
32
|
+
* many rows at overlay width, so capping stored lines caps nothing.
|
|
33
|
+
*/
|
|
34
|
+
bashOutputRows: 40,
|
|
35
|
+
/** Rows drawn inside a `bash` command frame, counted the same way. */
|
|
36
|
+
bashCommandRows: 20,
|
|
37
|
+
/** Output lines for a tool with no first-class renderer. */
|
|
38
|
+
genericResultLines: 12,
|
|
39
|
+
/** Characters of a single output line before it is clipped. */
|
|
40
|
+
maxLineWidth: 1000,
|
|
41
|
+
/** Characters of a `bash` command shown in the headline. */
|
|
42
|
+
commandPreviewChars: 96,
|
|
43
|
+
/** Characters of serialized arguments shown by the generic fallback. */
|
|
44
|
+
genericArgumentChars: 160,
|
|
45
|
+
} as const;
|
|
46
|
+
|
|
47
|
+
/** Diff tables narrower than this render unified instead of side by side. */
|
|
48
|
+
export const DIFF_SPLIT_MIN_WIDTH = 132;
|