@xynogen/pix-runtime 0.6.0 → 0.8.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 +6 -0
- package/package.json +1 -1
- package/src/extension.ts +3 -1
- package/src/herdr-state.ts +31 -1
- package/src/index.ts +6 -0
- package/src/pix-command.ts +82 -8
- package/src/sections/index.ts +1 -0
- package/src/sections/pretty.ts +16 -0
package/README.md
CHANGED
|
@@ -44,6 +44,12 @@ const timeoutMs = ioTimeoutMs(); // shared network timeout
|
|
|
44
44
|
const signal = ioTimeoutSignal(toolSignal); // timeout + cancellation
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
Set `pretty.maxRenderWidth` and `pretty.maxRenderHeight` in `~/.pi/agent/pix.json`,
|
|
48
|
+
or change **Pretty → max modal width/height** with `/pix`. Values accept terminal
|
|
49
|
+
percentages such as `"65%"`/`"80%"` or fixed columns/rows such as `96`/`20`.
|
|
50
|
+
Percentage choices in `/pix` move in 5% steps. Width is the rendered frame width;
|
|
51
|
+
height is the threshold where modal content starts paging.
|
|
52
|
+
|
|
47
53
|
Set `io.timeoutSec` in `~/.pi/agent/pix.json`, or change **Network → timeout (sec)**
|
|
48
54
|
with `/pix`. The default is 30 seconds. It applies to Pix network operations,
|
|
49
55
|
including remote skills, web fetch/search/transcription, MCP requests and
|
package/package.json
CHANGED
package/src/extension.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { bindHerdrNotify } from "./herdr-notify.ts";
|
|
3
|
-
import { bindAgentStateEvents, resetAgentState } from "./herdr-state.ts";
|
|
3
|
+
import { bindAgentStateEvents, resetAgentState, resetUnattendedState } from "./herdr-state.ts";
|
|
4
4
|
import { once } from "./once.ts";
|
|
5
5
|
import { registerPixCommand } from "./pix-command.ts";
|
|
6
6
|
import { pixRuntime } from "./runtime.ts";
|
|
@@ -23,6 +23,7 @@ export default function registerRuntime(pi: ExtensionAPI): void {
|
|
|
23
23
|
|
|
24
24
|
let initialized = false;
|
|
25
25
|
pi.on("session_start", async () => {
|
|
26
|
+
resetUnattendedState(pi.events);
|
|
26
27
|
if (initialized) {
|
|
27
28
|
await runtime.reload({ origin: "reload", source: "session_start" });
|
|
28
29
|
} else {
|
|
@@ -34,6 +35,7 @@ export default function registerRuntime(pi: ExtensionAPI): void {
|
|
|
34
35
|
|
|
35
36
|
pi.on("session_shutdown", async () => {
|
|
36
37
|
resetAgentState(pi.events);
|
|
38
|
+
resetUnattendedState(pi.events);
|
|
37
39
|
unbindHerdrNotify();
|
|
38
40
|
unbindAgentState();
|
|
39
41
|
await runtime.flush();
|
package/src/herdr-state.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { EventBus } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
|
|
3
3
|
export type PixAgentState = "working" | "blocked" | "idle";
|
|
4
|
+
export type UnattendedMode = "off" | "afk" | "yolo";
|
|
4
5
|
export type PixAgentStateEvent = {
|
|
5
6
|
state: PixAgentState;
|
|
6
7
|
message?: string;
|
|
@@ -12,6 +13,8 @@ type Entry = { source: string; message?: string };
|
|
|
12
13
|
type Coordinator = {
|
|
13
14
|
activities: Map<symbol, Entry>;
|
|
14
15
|
blocks: Map<symbol, Entry>;
|
|
16
|
+
unattendedMode: UnattendedMode;
|
|
17
|
+
yoloConsent: boolean;
|
|
15
18
|
};
|
|
16
19
|
|
|
17
20
|
function coordinators(): WeakMap<EventBus, Coordinator> {
|
|
@@ -24,7 +27,12 @@ function coordinator(events: EventBus): Coordinator {
|
|
|
24
27
|
const registry = coordinators();
|
|
25
28
|
let state = registry.get(events);
|
|
26
29
|
if (!state) {
|
|
27
|
-
state = {
|
|
30
|
+
state = {
|
|
31
|
+
activities: new Map(),
|
|
32
|
+
blocks: new Map(),
|
|
33
|
+
unattendedMode: "off",
|
|
34
|
+
yoloConsent: false,
|
|
35
|
+
};
|
|
28
36
|
registry.set(events, state);
|
|
29
37
|
}
|
|
30
38
|
return state;
|
|
@@ -100,6 +108,28 @@ export async function withAgentBlock<T>(
|
|
|
100
108
|
}
|
|
101
109
|
}
|
|
102
110
|
|
|
111
|
+
export function getUnattendedMode(events: EventBus): UnattendedMode {
|
|
112
|
+
return coordinator(events).unattendedMode;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function setUnattendedMode(events: EventBus, mode: UnattendedMode): void {
|
|
116
|
+
coordinator(events).unattendedMode = mode;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function hasYoloConsent(events: EventBus): boolean {
|
|
120
|
+
return coordinator(events).yoloConsent;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function setYoloConsent(events: EventBus, consent: boolean): void {
|
|
124
|
+
coordinator(events).yoloConsent = consent;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function resetUnattendedState(events: EventBus): void {
|
|
128
|
+
const state = coordinator(events);
|
|
129
|
+
state.unattendedMode = "off";
|
|
130
|
+
state.yoloConsent = false;
|
|
131
|
+
}
|
|
132
|
+
|
|
103
133
|
/** Bind state replay/reset to one Pi session lifecycle. */
|
|
104
134
|
export function bindAgentStateEvents(events: EventBus): () => void {
|
|
105
135
|
const off = events.on("pix:agent-state:request", () => publish(events));
|
package/src/index.ts
CHANGED
|
@@ -11,9 +11,15 @@ export { bindHerdrNotify } from "./herdr-notify.ts";
|
|
|
11
11
|
export {
|
|
12
12
|
beginAgentActivity,
|
|
13
13
|
bindAgentStateEvents,
|
|
14
|
+
getUnattendedMode,
|
|
15
|
+
hasYoloConsent,
|
|
14
16
|
type PixAgentState,
|
|
15
17
|
type PixAgentStateEvent,
|
|
16
18
|
resetAgentState,
|
|
19
|
+
resetUnattendedState,
|
|
20
|
+
setUnattendedMode,
|
|
21
|
+
setYoloConsent,
|
|
22
|
+
type UnattendedMode,
|
|
17
23
|
withAgentBlock,
|
|
18
24
|
} from "./herdr-state.ts";
|
|
19
25
|
export { ioTimeoutMs, ioTimeoutSignal } from "./io.ts";
|
package/src/pix-command.ts
CHANGED
|
@@ -21,7 +21,7 @@ import { collapseSection } from "./sections/collapse.ts";
|
|
|
21
21
|
import { compactionSection } from "./sections/compaction.ts";
|
|
22
22
|
import { gateSection } from "./sections/gate.ts";
|
|
23
23
|
import { ioSection } from "./sections/io.ts";
|
|
24
|
-
import { prettySection } from "./sections/pretty.ts";
|
|
24
|
+
import { prettySection, type RenderSize } from "./sections/pretty.ts";
|
|
25
25
|
|
|
26
26
|
interface SettingRow<T> {
|
|
27
27
|
section: string;
|
|
@@ -42,6 +42,15 @@ function formatTokens(tokens: number): string {
|
|
|
42
42
|
return tokens % 1_000_000 === 0 ? `${tokens / 1_000_000}M` : `${tokens / 1000}k`;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
function parseRenderSize(value: string): RenderSize {
|
|
46
|
+
return value.endsWith("%") ? (value as `${number}%`) : Number.parseFloat(value);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function resolveRenderSize(limit: RenderSize, available: number): number {
|
|
50
|
+
if (typeof limit === "number") return Math.floor(limit);
|
|
51
|
+
return Math.floor((available * Number.parseFloat(limit)) / 100);
|
|
52
|
+
}
|
|
53
|
+
|
|
45
54
|
/** Parse a compact token label ("1M", "150k") back to an absolute count. */
|
|
46
55
|
function parseTokens(label: string): number {
|
|
47
56
|
const raw = label.trim();
|
|
@@ -71,6 +80,58 @@ const SETTINGS: SettingRow<unknown>[] = [
|
|
|
71
80
|
read: (v) => v.lsStyle,
|
|
72
81
|
patch: (value) => ({ lsStyle: value as "grid" | "tree" }),
|
|
73
82
|
}),
|
|
83
|
+
row({
|
|
84
|
+
section: "Pretty",
|
|
85
|
+
label: "max modal width",
|
|
86
|
+
handle: prettySection,
|
|
87
|
+
values: [
|
|
88
|
+
"50%",
|
|
89
|
+
"55%",
|
|
90
|
+
"60%",
|
|
91
|
+
"65%",
|
|
92
|
+
"70%",
|
|
93
|
+
"75%",
|
|
94
|
+
"80%",
|
|
95
|
+
"85%",
|
|
96
|
+
"90%",
|
|
97
|
+
"95%",
|
|
98
|
+
"100%",
|
|
99
|
+
"72 cols",
|
|
100
|
+
"80 cols",
|
|
101
|
+
"88 cols",
|
|
102
|
+
"96 cols",
|
|
103
|
+
"104 cols",
|
|
104
|
+
"120 cols",
|
|
105
|
+
],
|
|
106
|
+
read: (v) =>
|
|
107
|
+
typeof v.maxRenderWidth === "number" ? `${v.maxRenderWidth} cols` : v.maxRenderWidth,
|
|
108
|
+
patch: (value) => ({ maxRenderWidth: parseRenderSize(value) }),
|
|
109
|
+
}),
|
|
110
|
+
row({
|
|
111
|
+
section: "Pretty",
|
|
112
|
+
label: "max modal height",
|
|
113
|
+
handle: prettySection,
|
|
114
|
+
values: [
|
|
115
|
+
"50%",
|
|
116
|
+
"55%",
|
|
117
|
+
"60%",
|
|
118
|
+
"65%",
|
|
119
|
+
"70%",
|
|
120
|
+
"75%",
|
|
121
|
+
"80%",
|
|
122
|
+
"85%",
|
|
123
|
+
"90%",
|
|
124
|
+
"95%",
|
|
125
|
+
"100%",
|
|
126
|
+
"12 rows",
|
|
127
|
+
"16 rows",
|
|
128
|
+
"20 rows",
|
|
129
|
+
"24 rows",
|
|
130
|
+
],
|
|
131
|
+
read: (v) =>
|
|
132
|
+
typeof v.maxRenderHeight === "number" ? `${v.maxRenderHeight} rows` : v.maxRenderHeight,
|
|
133
|
+
patch: (value) => ({ maxRenderHeight: parseRenderSize(value) }),
|
|
134
|
+
}),
|
|
74
135
|
row({
|
|
75
136
|
section: "Collapse",
|
|
76
137
|
label: "enabled",
|
|
@@ -154,7 +215,6 @@ export function registerPixCommand(pi: ExtensionAPI, runtime: PixRuntime): void
|
|
|
154
215
|
return;
|
|
155
216
|
}
|
|
156
217
|
|
|
157
|
-
const boxW = 52;
|
|
158
218
|
await ui.custom(
|
|
159
219
|
(
|
|
160
220
|
tui: { requestRender(): void; terminal?: { rows?: number } },
|
|
@@ -208,7 +268,7 @@ export function registerPixCommand(pi: ExtensionAPI, runtime: PixRuntime): void
|
|
|
208
268
|
};
|
|
209
269
|
|
|
210
270
|
return {
|
|
211
|
-
render: () => {
|
|
271
|
+
render: (width: number) => {
|
|
212
272
|
const labelW = Math.max(...SETTINGS.map((r) => r.label.length));
|
|
213
273
|
const body: string[] = [];
|
|
214
274
|
const settingBodyLines: number[] = [];
|
|
@@ -230,8 +290,11 @@ export function registerPixCommand(pi: ExtensionAPI, runtime: PixRuntime): void
|
|
|
230
290
|
body.push(`${cursor} ${label} ${theme.fg(isDefault ? "dim" : "success", value)}`);
|
|
231
291
|
}
|
|
232
292
|
const result = frameModal({
|
|
233
|
-
width
|
|
234
|
-
maxHeight: modalHeight(
|
|
293
|
+
width,
|
|
294
|
+
maxHeight: modalHeight(
|
|
295
|
+
tui.terminal?.rows,
|
|
296
|
+
runtime.get(prettySection).maxRenderHeight,
|
|
297
|
+
),
|
|
235
298
|
header: [theme.fg("accent", theme.bold(" pix settings")), ""],
|
|
236
299
|
body,
|
|
237
300
|
footer: [
|
|
@@ -282,7 +345,18 @@ export function registerPixCommand(pi: ExtensionAPI, runtime: PixRuntime): void
|
|
|
282
345
|
},
|
|
283
346
|
};
|
|
284
347
|
},
|
|
285
|
-
{
|
|
348
|
+
{
|
|
349
|
+
overlay: true,
|
|
350
|
+
overlayOptions: () => {
|
|
351
|
+
const pretty = runtime.get(prettySection);
|
|
352
|
+
return {
|
|
353
|
+
anchor: "center",
|
|
354
|
+
width: pretty.maxRenderWidth,
|
|
355
|
+
maxHeight: pretty.maxRenderHeight,
|
|
356
|
+
margin: 2,
|
|
357
|
+
};
|
|
358
|
+
},
|
|
359
|
+
},
|
|
286
360
|
);
|
|
287
361
|
},
|
|
288
362
|
});
|
|
@@ -309,9 +383,9 @@ interface RuntimeModalResult {
|
|
|
309
383
|
maxBodyOffset: number;
|
|
310
384
|
}
|
|
311
385
|
|
|
312
|
-
function modalHeight(rows = 24): number {
|
|
386
|
+
function modalHeight(rows = 24, limit: RenderSize = "80%"): number {
|
|
313
387
|
const safe = Number.isFinite(rows) ? Math.max(1, Math.floor(rows)) : 24;
|
|
314
|
-
return Math.max(1, Math.min(safe,
|
|
388
|
+
return Math.max(1, Math.min(safe, resolveRenderSize(limit, safe)));
|
|
315
389
|
}
|
|
316
390
|
|
|
317
391
|
function pageBodyOffset(offset: number, size: number, max: number, direction: -1 | 1): number {
|
package/src/sections/index.ts
CHANGED
package/src/sections/pretty.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { defineSection, enumOr, isObj, posNumOr } from "../schema.ts";
|
|
|
2
2
|
|
|
3
3
|
export type IconMode = "nerd" | "unicode" | "ascii";
|
|
4
4
|
export type LsStyle = "grid" | "tree";
|
|
5
|
+
export type RenderSize = number | `${number}%`;
|
|
5
6
|
|
|
6
7
|
export interface DiffConfig {
|
|
7
8
|
splitMinWidth: number;
|
|
@@ -11,6 +12,8 @@ export interface DiffConfig {
|
|
|
11
12
|
export interface PrettyConfig {
|
|
12
13
|
icons: IconMode;
|
|
13
14
|
lsStyle: LsStyle;
|
|
15
|
+
maxRenderWidth: RenderSize;
|
|
16
|
+
maxRenderHeight: RenderSize;
|
|
14
17
|
maxPreviewLines: number;
|
|
15
18
|
maxRenderLines: number;
|
|
16
19
|
maxHighlightChars: number;
|
|
@@ -21,9 +24,20 @@ export interface PrettyConfig {
|
|
|
21
24
|
const ICON_MODES: readonly IconMode[] = ["nerd", "unicode", "ascii"];
|
|
22
25
|
const LS_STYLES: readonly LsStyle[] = ["grid", "tree"];
|
|
23
26
|
|
|
27
|
+
function renderSizeOr(value: unknown, fallback: RenderSize): RenderSize {
|
|
28
|
+
if (typeof value === "number") return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
29
|
+
if (typeof value !== "string") return fallback;
|
|
30
|
+
const match = value.match(/^(\d+(?:\.\d+)?)%$/);
|
|
31
|
+
if (!match) return fallback;
|
|
32
|
+
const percent = Number(match[1]);
|
|
33
|
+
return percent > 0 && percent <= 100 ? (value as `${number}%`) : fallback;
|
|
34
|
+
}
|
|
35
|
+
|
|
24
36
|
const DEFAULTS: Readonly<PrettyConfig> = {
|
|
25
37
|
icons: "nerd",
|
|
26
38
|
lsStyle: "grid",
|
|
39
|
+
maxRenderWidth: "65%",
|
|
40
|
+
maxRenderHeight: "80%",
|
|
27
41
|
maxPreviewLines: 80,
|
|
28
42
|
maxRenderLines: 150,
|
|
29
43
|
maxHighlightChars: 80_000,
|
|
@@ -40,6 +54,8 @@ export const prettySection = defineSection<"pretty", PrettyConfig>({
|
|
|
40
54
|
return {
|
|
41
55
|
icons: enumOr(raw.icons, ICON_MODES, DEFAULTS.icons),
|
|
42
56
|
lsStyle: enumOr(raw.lsStyle, LS_STYLES, DEFAULTS.lsStyle),
|
|
57
|
+
maxRenderWidth: renderSizeOr(raw.maxRenderWidth, DEFAULTS.maxRenderWidth),
|
|
58
|
+
maxRenderHeight: renderSizeOr(raw.maxRenderHeight, DEFAULTS.maxRenderHeight),
|
|
43
59
|
maxPreviewLines: posNumOr(raw.maxPreviewLines, DEFAULTS.maxPreviewLines),
|
|
44
60
|
maxRenderLines: posNumOr(raw.maxRenderLines, DEFAULTS.maxRenderLines),
|
|
45
61
|
maxHighlightChars: posNumOr(raw.maxHighlightChars, DEFAULTS.maxHighlightChars),
|