pi-jev-auto-mode 0.4.1 → 0.5.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/CHANGELOG.md +8 -0
- package/README.md +7 -0
- package/package.json +1 -1
- package/src/extension.ts +21 -2
- package/src/records.ts +68 -2
- package/src/settings.ts +22 -0
- package/src/ui.ts +10 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.0 - 2026-09-24
|
|
4
|
+
|
|
5
|
+
- **Decision records are compact by default.** An approval is drawn as one line — status, tool,
|
|
6
|
+
latency, and the command — and a block as two, keeping the rationale. Each record used to take
|
|
7
|
+
five padded blocks, so a long session was mostly gate records. `/jev-auto-mode display full`
|
|
8
|
+
(or `"display": "full"`) restores the previous view, and the expanded view shows every detail
|
|
9
|
+
in either mode.
|
|
10
|
+
|
|
3
11
|
## 0.4.1 - 2026-09-18
|
|
4
12
|
|
|
5
13
|
- **An unclear answer passes by default.** `uncertain` defaulted to `deny`, which meant a
|
package/README.md
CHANGED
|
@@ -113,8 +113,14 @@ Packages are discovered in the [package gallery](https://pi.dev/packages) throug
|
|
|
113
113
|
/jev-auto-mode uncertain show what the middle band resolves to
|
|
114
114
|
/jev-auto-mode uncertain deny|ask|allow
|
|
115
115
|
/jev-auto-mode scope all|matched how far the semantic layer reaches
|
|
116
|
+
/jev-auto-mode display full|compact how decision records are drawn
|
|
116
117
|
```
|
|
117
118
|
|
|
119
|
+
Each decision leaves a record in the transcript. `display: "compact"` (the default) folds an
|
|
120
|
+
approval into one line and a block into two (the rationale stays, because a block is something
|
|
121
|
+
to act on). `display: "full"` shows the heading, the engine line, the command, the reasons, and
|
|
122
|
+
the rationale. The expanded view shows every detail in either mode.
|
|
123
|
+
|
|
118
124
|
```
|
|
119
125
|
pi --jev-auto-mode start with auto mode enabled
|
|
120
126
|
```
|
|
@@ -195,6 +201,7 @@ Policy notes: `$PI_CODING_AGENT_DIR/jev-auto-mode-policy.md`.
|
|
|
195
201
|
"maxStateCharacters": 120000,
|
|
196
202
|
"uncertain": "deny",
|
|
197
203
|
"gateScope": "all",
|
|
204
|
+
"display": "compact",
|
|
198
205
|
"thresholds": {}
|
|
199
206
|
}
|
|
200
207
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-jev-auto-mode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Jev (TypeSafe System One) backed auto mode for the Pi coding agent: semantically auto-approves bash, write, and edit tool calls and fails closed when a decision cannot be made.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
package/src/extension.ts
CHANGED
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
import {
|
|
56
56
|
DEFAULT_SETTINGS,
|
|
57
57
|
JevAutoModeStore,
|
|
58
|
+
isDisplayMode,
|
|
58
59
|
isGateScope,
|
|
59
60
|
isUncertainAction,
|
|
60
61
|
parseThreshold,
|
|
@@ -64,6 +65,7 @@ import {
|
|
|
64
65
|
import {
|
|
65
66
|
buildConfirmationDialog,
|
|
66
67
|
describeSettings,
|
|
68
|
+
DISPLAY_EXPLANATION,
|
|
67
69
|
GATE_SCOPE_EXPLANATION,
|
|
68
70
|
UNCERTAIN_EXPLANATION,
|
|
69
71
|
formatRuleTable,
|
|
@@ -571,7 +573,7 @@ export function register(pi: ExtensionAPI, options: RegisterOptions = {}): void
|
|
|
571
573
|
default: false,
|
|
572
574
|
});
|
|
573
575
|
|
|
574
|
-
registerDecisionEntryRenderer(pi);
|
|
576
|
+
registerDecisionEntryRenderer(pi, () => state.settings.display);
|
|
575
577
|
|
|
576
578
|
pi.registerCommand(AUTO_MODE_COMMAND, {
|
|
577
579
|
description: "Show or change the Jev auto mode settings",
|
|
@@ -579,7 +581,7 @@ export function register(pi: ExtensionAPI, options: RegisterOptions = {}): void
|
|
|
579
581
|
const value = String(argumentPrefix ?? "");
|
|
580
582
|
const tokens = value.split(/\s+/).filter(Boolean);
|
|
581
583
|
if (tokens.length === 0) {
|
|
582
|
-
return ["status", "on", "off", "policy", "threshold", "scope", "uncertain", "login", "logout"].map((item) => ({
|
|
584
|
+
return ["status", "on", "off", "policy", "threshold", "scope", "uncertain", "display", "login", "logout"].map((item) => ({
|
|
583
585
|
value: item,
|
|
584
586
|
label: item,
|
|
585
587
|
}));
|
|
@@ -756,6 +758,23 @@ export function register(pi: ExtensionAPI, options: RegisterOptions = {}): void
|
|
|
756
758
|
return;
|
|
757
759
|
}
|
|
758
760
|
|
|
761
|
+
if (value.startsWith("display")) {
|
|
762
|
+
const argument = value.slice("display".length).trim();
|
|
763
|
+
if (argument === "") {
|
|
764
|
+
ctx.ui.notify(`display: ${state.settings.display}\n\n${DISPLAY_EXPLANATION}`, "info");
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
if (!isDisplayMode(argument)) {
|
|
768
|
+
ctx.ui.notify(`Expected one of full, compact.\n\n${DISPLAY_EXPLANATION}`, "error");
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
state.settings = { ...state.settings, display: argument };
|
|
773
|
+
await save(gateContext);
|
|
774
|
+
ctx.ui.notify(`decision records now display as: ${argument}`, "info");
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
|
|
759
778
|
if (value.startsWith("uncertain")) {
|
|
760
779
|
const argument = value.slice("uncertain".length).trim();
|
|
761
780
|
if (argument === "") {
|
package/src/records.ts
CHANGED
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
* recorded rationale should not become ammunition for the next tool call.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { Box, Text } from "@earendil-works/pi-tui";
|
|
9
|
+
import { Box, Text, truncateToWidth, type Component } from "@earendil-works/pi-tui";
|
|
10
10
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
11
11
|
import type { ConditionReport, DecisionSource } from "./decide.ts";
|
|
12
12
|
import { formatThreshold } from "./jev/decide.ts";
|
|
13
|
+
import type { DisplayMode } from "./settings.ts";
|
|
13
14
|
|
|
14
15
|
export const DECISION_ENTRY_TYPE = "jev-auto-mode-decision";
|
|
15
16
|
|
|
@@ -85,12 +86,77 @@ export function formatConditionLines(record: DecisionRecord): string[] {
|
|
|
85
86
|
});
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
|
|
89
|
+
/** Collapse whitespace so a multi-line command reads as one line. */
|
|
90
|
+
function oneLine(text: string): string {
|
|
91
|
+
return text.replace(/\s+/g, " ").trim();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface CompactDecisionLines {
|
|
95
|
+
/** `🛡 jev allowed · bash · 299ms · <command>`: everything an approval needs. */
|
|
96
|
+
readonly headline: string;
|
|
97
|
+
/**
|
|
98
|
+
* The rationale, only for a call that did not run. An approval does not need its
|
|
99
|
+
* reasoning on screen; a block does, because the user has to act on it.
|
|
100
|
+
*/
|
|
101
|
+
readonly detail?: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The compact view: one line for an approval, two for a block.
|
|
106
|
+
*
|
|
107
|
+
* Width is applied at render time. This only decides what the lines say.
|
|
108
|
+
*/
|
|
109
|
+
export function formatCompactLines(record: DecisionRecord): CompactDecisionLines {
|
|
110
|
+
const approved = record.status === "allowed" || record.status === "confirmed";
|
|
111
|
+
const parts = [`${approved ? "🛡" : "⛔"} jev ${STATUS_LABEL[record.status]}`, record.tool];
|
|
112
|
+
if (!approved) parts.push(`via ${record.source}`);
|
|
113
|
+
if (typeof record.latencyMs === "number") parts.push(`${Math.round(record.latencyMs)}ms`);
|
|
114
|
+
const summary = oneLine(record.summary);
|
|
115
|
+
if (summary.length > 0) parts.push(summary);
|
|
116
|
+
const headline = parts.join(" · ");
|
|
117
|
+
if (approved) return { headline };
|
|
118
|
+
return { headline, detail: oneLine(record.rationale) };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** A single line clipped to the viewport instead of wrapped. */
|
|
122
|
+
class ClippedLine implements Component {
|
|
123
|
+
private readonly text: string;
|
|
124
|
+
|
|
125
|
+
constructor(text: string) {
|
|
126
|
+
this.text = text;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
render(width: number): string[] {
|
|
130
|
+
return [truncateToWidth(this.text, Math.max(1, width))];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
invalidate(): void {}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export type DisplayModeSource = () => DisplayMode;
|
|
137
|
+
|
|
138
|
+
export function registerDecisionEntryRenderer(
|
|
139
|
+
pi: Pick<ExtensionAPI, "registerEntryRenderer">,
|
|
140
|
+
displayMode: DisplayModeSource = () => "compact",
|
|
141
|
+
): void {
|
|
89
142
|
pi.registerEntryRenderer<DecisionRecord>(DECISION_ENTRY_TYPE, (entry, options, theme) => {
|
|
90
143
|
const record = entry.data;
|
|
91
144
|
if (!record) return undefined;
|
|
92
145
|
|
|
93
146
|
const approved = record.status === "allowed" || record.status === "confirmed";
|
|
147
|
+
|
|
148
|
+
// The expanded view is the place to read a decision in full, so it ignores the
|
|
149
|
+
// compact setting.
|
|
150
|
+
if (displayMode() === "compact" && !options.expanded) {
|
|
151
|
+
const lines = formatCompactLines(record);
|
|
152
|
+
const box = new Box(1, 0, (text) => theme.bg("customMessageBg", text));
|
|
153
|
+
box.addChild(new ClippedLine(theme.fg(approved ? "muted" : "error", lines.headline)));
|
|
154
|
+
if (lines.detail !== undefined) {
|
|
155
|
+
box.addChild(new ClippedLine(theme.fg("dim", `rationale: ${lines.detail}`)));
|
|
156
|
+
}
|
|
157
|
+
return box;
|
|
158
|
+
}
|
|
159
|
+
|
|
94
160
|
const icon = approved ? "🛡" : "⛔";
|
|
95
161
|
const heading = `${icon} ${theme.bold("jev auto mode")} ${theme.fg(
|
|
96
162
|
approved ? "success" : "error",
|
package/src/settings.ts
CHANGED
|
@@ -45,6 +45,23 @@ export interface JevAutoModeSettings {
|
|
|
45
45
|
* visible in `/jev-auto-mode threshold` instead of silently resetting the rule.
|
|
46
46
|
*/
|
|
47
47
|
readonly thresholds: Readonly<Record<string, number>>;
|
|
48
|
+
/**
|
|
49
|
+
* How decision records are drawn in the transcript.
|
|
50
|
+
*
|
|
51
|
+
* `compact` (the default) folds an approval into one line and a block into two, so
|
|
52
|
+
* a long session is not dominated by gate records. `full` shows the heading, the
|
|
53
|
+
* engine line, the command, the reasons, and the rationale. The expanded view (the tool
|
|
54
|
+
* expansion key) always shows every detail, whichever mode is set.
|
|
55
|
+
*/
|
|
56
|
+
readonly display: DisplayMode;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type DisplayMode = "full" | "compact";
|
|
60
|
+
|
|
61
|
+
export const DISPLAY_MODES: readonly DisplayMode[] = ["full", "compact"];
|
|
62
|
+
|
|
63
|
+
export function isDisplayMode(value: unknown): value is DisplayMode {
|
|
64
|
+
return typeof value === "string" && DISPLAY_MODES.includes(value as DisplayMode);
|
|
48
65
|
}
|
|
49
66
|
|
|
50
67
|
export type SettingsScope = "global" | "project";
|
|
@@ -90,6 +107,7 @@ export const DEFAULT_SETTINGS: JevAutoModeSettings = {
|
|
|
90
107
|
uncertain: "allow",
|
|
91
108
|
gateScope: "all",
|
|
92
109
|
thresholds: {},
|
|
110
|
+
display: "compact",
|
|
93
111
|
};
|
|
94
112
|
|
|
95
113
|
const MAX_PATTERN_ENTRIES = 200;
|
|
@@ -193,6 +211,10 @@ export function parseSettingsPatch(value: unknown): SettingsPatch {
|
|
|
193
211
|
patch.gateScope = record.gateScope;
|
|
194
212
|
}
|
|
195
213
|
|
|
214
|
+
if (record.display !== undefined && isDisplayMode(record.display)) {
|
|
215
|
+
patch.display = record.display;
|
|
216
|
+
}
|
|
217
|
+
|
|
196
218
|
const safeCommands = record.safeCommands === undefined ? undefined : readStringArray(record.safeCommands);
|
|
197
219
|
if (safeCommands !== undefined) patch.safeCommands = safeCommands;
|
|
198
220
|
|
package/src/ui.ts
CHANGED
|
@@ -44,6 +44,7 @@ export function describeSettings(settings: JevAutoModeSettings, scope: SettingsS
|
|
|
44
44
|
`max state characters: ${settings.maxStateCharacters}`,
|
|
45
45
|
`uncertain band: ${settings.uncertain}`,
|
|
46
46
|
`gate scope: ${settings.gateScope}`,
|
|
47
|
+
`display: ${settings.display}`,
|
|
47
48
|
].join("\n");
|
|
48
49
|
}
|
|
49
50
|
|
|
@@ -149,6 +150,15 @@ export const USAGE_TEXT = [
|
|
|
149
150
|
" /jev-auto-mode uncertain show what the middle band resolves to",
|
|
150
151
|
" /jev-auto-mode uncertain deny|ask|allow",
|
|
151
152
|
" /jev-auto-mode scope all|matched which calls reach Jev",
|
|
153
|
+
" /jev-auto-mode display full|compact how decision records are drawn",
|
|
154
|
+
].join("\n");
|
|
155
|
+
|
|
156
|
+
export const DISPLAY_EXPLANATION = [
|
|
157
|
+
"How decision records are drawn in the transcript.",
|
|
158
|
+
" compact - one line for an approval, two for a block (with the rationale) (default).",
|
|
159
|
+
" full - heading, engine line, command, reasons, and rationale.",
|
|
160
|
+
"The expanded view always shows every detail. New records use the mode at once;",
|
|
161
|
+
"records already on screen switch when Pi redraws them (e.g. toggling expansion).",
|
|
152
162
|
].join("\n");
|
|
153
163
|
|
|
154
164
|
export const GATE_SCOPE_EXPLANATION = [
|