pi-warden 0.7.0 → 0.9.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 +168 -100
- package/dist/config.d.ts +44 -1
- package/dist/config.js +53 -4
- package/dist/extension.d.ts +1 -1
- package/dist/extension.js +126 -8
- package/dist/guard.d.ts +13 -0
- package/dist/guard.js +122 -6
- package/dist/index.d.ts +11 -5
- package/dist/index.js +6 -3
- package/dist/notify.d.ts +42 -0
- package/dist/notify.js +99 -0
- package/dist/panel.js +1 -1
- package/dist/rules.d.ts +164 -0
- package/dist/rules.js +479 -0
- package/dist/runaway.d.ts +66 -0
- package/dist/runaway.js +115 -0
- package/dist/shape.d.ts +1 -1
- package/dist/shape.js +11 -2
- package/dist/trace.d.ts +5 -1
- package/dist/trace.js +24 -0
- package/dist/widget.d.ts +10 -0
- package/dist/widget.js +32 -0
- package/package.json +2 -2
package/dist/shape.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isMode } from "./config.js";
|
|
2
|
+
import { DEFAULT_TEMPLATES } from "./widget.js";
|
|
2
3
|
/** The config layout this extension build expects; compared with the loaded config module's CONFIG_SCHEMA. */
|
|
3
|
-
export const EXPECTED_SCHEMA =
|
|
4
|
+
export const EXPECTED_SCHEMA = 5;
|
|
4
5
|
const off = { enabled: false };
|
|
5
6
|
const proseOff = () => ({ ...off, audience: "technical", threshold: 1, trend: 3, minChars: 1 });
|
|
6
7
|
/**
|
|
@@ -30,8 +31,11 @@ export function completeConfig(loaded) {
|
|
|
30
31
|
done: section("done", { ...off, claimsDone: 1, nudge: false }),
|
|
31
32
|
slop: section("slop", { ...off, threshold: 1, prose: proseOff() }),
|
|
32
33
|
security: section("security", { ...off, threshold: 1 }),
|
|
34
|
+
rules: section("rules", { ...off, threshold: 1, files: [], fallback: false, maxChars: 500, exclude: [], skip: [], sensitivePaths: {} }),
|
|
33
35
|
context: section("context", { ...off, tailMinChars: 1, confidence: 1, duplicateMinChars: Number.MAX_SAFE_INTEGER, recallTool: "none", formatConfidence: 1 }),
|
|
34
|
-
|
|
36
|
+
runaway: section("runaway", { ...off, repeats: Number.MAX_SAFE_INTEGER, thinkingRepeats: Number.MAX_SAFE_INTEGER, minChars: Number.MAX_SAFE_INTEGER, recover: false }),
|
|
37
|
+
notify: section("notify", { ...off, cooldownMs: 0, command: [] }),
|
|
38
|
+
widget: section("widget", { ...off, placement: "aboveEditor", shortcut: "", panelWidth: "40%", action: "", stuck: "", done: "", prose: "", security: "", context: "", runaway: "", rules: "" }),
|
|
35
39
|
};
|
|
36
40
|
if (typeof config.slop.prose !== "object" || config.slop.prose === null) {
|
|
37
41
|
missing.push("slop.prose");
|
|
@@ -44,6 +48,11 @@ export function completeConfig(loaded) {
|
|
|
44
48
|
}
|
|
45
49
|
if (typeof config.widget.panelWidth !== "string" && typeof config.widget.panelWidth !== "number")
|
|
46
50
|
config.widget = { ...config.widget, panelWidth: "40%" };
|
|
51
|
+
// The runaway guard added its template later than the other sections; an older widget section renders the default line.
|
|
52
|
+
if (typeof config.widget.runaway !== "string")
|
|
53
|
+
config.widget = { ...config.widget, runaway: DEFAULT_TEMPLATES.runaway };
|
|
54
|
+
if (typeof config.widget.rules !== "string")
|
|
55
|
+
config.widget = { ...config.widget, rules: DEFAULT_TEMPLATES.rules };
|
|
47
56
|
return { config, missing };
|
|
48
57
|
}
|
|
49
58
|
export function shapeWarning(missing, loadedSchema) {
|
package/dist/trace.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { DoneVerdict } from "./done.js";
|
|
2
2
|
import type { Verdict } from "./guard.js";
|
|
3
3
|
import type { ProseVerdict } from "./prose.js";
|
|
4
|
+
import type { RulesVerdict } from "./rules.js";
|
|
5
|
+
import type { RunawayVerdict } from "./runaway.js";
|
|
4
6
|
import type { Attempt, StuckVerdict } from "./stuck.js";
|
|
5
|
-
export type GuardName = "action" | "stuck" | "done" | "prose" | "security" | "context";
|
|
7
|
+
export type GuardName = "action" | "stuck" | "done" | "prose" | "security" | "context" | "runaway" | "rules";
|
|
6
8
|
export interface TraceEntry {
|
|
7
9
|
at: number;
|
|
8
10
|
guard: GuardName;
|
|
@@ -28,4 +30,6 @@ export declare function actionDetails(verdict: Verdict, extra?: {
|
|
|
28
30
|
}): string[];
|
|
29
31
|
export declare function stuckDetails(verdict: StuckVerdict, attempts: readonly Attempt[], told?: string): string[];
|
|
30
32
|
export declare function doneDetails(verdict: DoneVerdict, finalMessage: string, told?: string): string[];
|
|
33
|
+
export declare function runawayDetails(verdict: RunawayVerdict, told: string | undefined, recovering: boolean): string[];
|
|
31
34
|
export declare function proseDetails(verdict: ProseVerdict, reply: string, audience: string, told?: string): string[];
|
|
35
|
+
export declare function rulesDetails(verdict: RulesVerdict, told?: string): string[];
|
package/dist/trace.js
CHANGED
|
@@ -81,6 +81,16 @@ export function doneDetails(verdict, finalMessage, told) {
|
|
|
81
81
|
lines.push(`agent told: ${clip(told, 400)}`);
|
|
82
82
|
return lines;
|
|
83
83
|
}
|
|
84
|
+
export function runawayDetails(verdict, told, recovering) {
|
|
85
|
+
const lines = [
|
|
86
|
+
`repeated: ${clip(verdict.block, 200)}`,
|
|
87
|
+
`${verdict.count} times in ${verdict.chars} chars of ${verdict.kind}; signal: ${verdict.signal === "block" ? "identical paragraph" : "recurring trailing phrase"}`,
|
|
88
|
+
recovering ? "run stopped; one follow-up turn started" : "run stopped; not restarted",
|
|
89
|
+
];
|
|
90
|
+
if (told)
|
|
91
|
+
lines.push(`agent told: ${clip(told, 400)}`);
|
|
92
|
+
return lines;
|
|
93
|
+
}
|
|
84
94
|
export function proseDetails(verdict, reply, audience, told) {
|
|
85
95
|
const lines = [`reply: ${clip(reply.replace(/\s+/g, " "), 300)}`, `audience: ${audience}`];
|
|
86
96
|
if (verdict.scores)
|
|
@@ -93,3 +103,17 @@ export function proseDetails(verdict, reply, audience, told) {
|
|
|
93
103
|
lines.push(`agent told: ${clip(told, 400)}`);
|
|
94
104
|
return lines;
|
|
95
105
|
}
|
|
106
|
+
export function rulesDetails(verdict, told) {
|
|
107
|
+
const lines = [`${verdict.tool} ${verdict.path}; rules from ${verdict.sources.join(", ") || "nowhere"}${verdict.aggregate ? " (judged as one document)" : ""}; ${verdict.asked} question${verdict.asked === 1 ? "" : "s"}`];
|
|
108
|
+
if (verdict.scores?.length)
|
|
109
|
+
lines.push(`jev: ${verdict.scores.map(score => `${score.name} ${score.outcome.replace(/_/g, " ")} ${percent(score.violation)}`).join(" · ")}${verdict.model ? ` · ${verdict.model} · ${verdict.elapsedMs} ms` : ""}`);
|
|
110
|
+
if (verdict.findings.length)
|
|
111
|
+
lines.push(`violations: ${verdict.findings.map(finding => `${finding.name} (${percent(finding.violation)})`).join("; ")}${verdict.editId ? ` in ${verdict.editId.replace("_", " ")}` : ""}`);
|
|
112
|
+
if (verdict.skippedReason)
|
|
113
|
+
lines.push(`skipped: ${verdict.skippedReason}`);
|
|
114
|
+
if (verdict.error)
|
|
115
|
+
lines.push(`typesafe: ${verdict.error}`);
|
|
116
|
+
if (told)
|
|
117
|
+
lines.push(`agent told: ${clip(told, 400)}`);
|
|
118
|
+
return lines;
|
|
119
|
+
}
|
package/dist/widget.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { DoneVerdict } from "./done.js";
|
|
2
2
|
import type { Verdict } from "./guard.js";
|
|
3
3
|
import type { ProseVerdict } from "./prose.js";
|
|
4
|
+
import type { RulesVerdict } from "./rules.js";
|
|
5
|
+
import type { RunawayVerdict } from "./runaway.js";
|
|
4
6
|
import type { StuckVerdict } from "./stuck.js";
|
|
5
7
|
export type WidgetPlacement = "aboveEditor" | "belowEditor";
|
|
6
8
|
export interface WidgetConfig {
|
|
@@ -17,6 +19,8 @@ export interface WidgetConfig {
|
|
|
17
19
|
prose: string;
|
|
18
20
|
security: string;
|
|
19
21
|
context: string;
|
|
22
|
+
runaway: string;
|
|
23
|
+
rules: string;
|
|
20
24
|
}
|
|
21
25
|
export declare const DEFAULT_TEMPLATES: {
|
|
22
26
|
readonly action: "warden · {tool} · irreversible {irreversible} · off-task {offTask} · {scope} · slop: {slop} · patterns: {patterns} · {flags} · {level}";
|
|
@@ -25,6 +29,8 @@ export declare const DEFAULT_TEMPLATES: {
|
|
|
25
29
|
readonly prose: "warden · prose · wordy {wordy} · clichés {cliches} · jargon {jargon} · {flags} · {status}";
|
|
26
30
|
readonly security: "warden · security · {tool} · injection {injection} · exfiltration {exfiltration} · {status}";
|
|
27
31
|
readonly context: "warden · context · {tool} · {retention} · saved {bytesSaved} bytes";
|
|
32
|
+
readonly runaway: "warden · runaway · {kind} · {count}× repeated · {chars} chars · {signal} · {status}";
|
|
33
|
+
readonly rules: "warden · rules · {tool} {path} · {asked} rules · {violations} · {status}";
|
|
28
34
|
};
|
|
29
35
|
export declare function defaultWidgetConfig(): WidgetConfig;
|
|
30
36
|
export type Tokens = Record<string, string | undefined>;
|
|
@@ -35,12 +41,16 @@ export type Tokens = Record<string, string | undefined>;
|
|
|
35
41
|
export declare function renderTemplate(template: string, tokens: Tokens): string;
|
|
36
42
|
export declare function actionTokens(verdict: Verdict, at?: number): Tokens;
|
|
37
43
|
export declare function stuckTokens(verdict: StuckVerdict, at?: number): Tokens;
|
|
44
|
+
export declare function runawayTokens(verdict: RunawayVerdict, recovering: boolean, at?: number): Tokens;
|
|
38
45
|
export declare function doneTokens(verdict: DoneVerdict, at?: number): Tokens;
|
|
39
46
|
export declare function proseTokens(verdict: ProseVerdict, at?: number): Tokens;
|
|
47
|
+
export declare function rulesTokens(verdict: RulesVerdict, at?: number): Tokens;
|
|
40
48
|
/** Token names users can put in templates, for /warden status and the README. */
|
|
41
49
|
export declare const TOKEN_NAMES: {
|
|
42
50
|
readonly security: readonly ["tool", "injection", "exfiltration", "status"];
|
|
43
51
|
readonly context: readonly ["tool", "retention", "bytesSaved"];
|
|
52
|
+
readonly runaway: readonly ["kind", "count", "chars", "signal", "block", "status", "time", "guard"];
|
|
53
|
+
readonly rules: readonly ["tool", "path", "asked", "violations", "status", "source", "reasons", "model", "ms", "flags", "time", "guard"];
|
|
44
54
|
readonly action: readonly ["tool", "level", "source", "irreversible", "offTask", "scope", "approved", "slop", "slopStub", "slopComments", "slopDead", "slopHedging", "patterns", "reasons", "path", "model", "ms", "flags", "time", "guard"];
|
|
45
55
|
readonly prose: readonly ["wordy", "cliches", "jargon", "status", "reasons", "model", "ms", "flags", "time", "guard"];
|
|
46
56
|
readonly stuck: readonly ["failures", "sameStrategy", "approachChange", "progress", "status", "source", "reasons", "model", "ms", "flags", "time", "guard"];
|
package/dist/widget.js
CHANGED
|
@@ -5,6 +5,8 @@ export const DEFAULT_TEMPLATES = {
|
|
|
5
5
|
prose: "warden · prose · wordy {wordy} · clichés {cliches} · jargon {jargon} · {flags} · {status}",
|
|
6
6
|
security: "warden · security · {tool} · injection {injection} · exfiltration {exfiltration} · {status}",
|
|
7
7
|
context: "warden · context · {tool} · {retention} · saved {bytesSaved} bytes",
|
|
8
|
+
runaway: "warden · runaway · {kind} · {count}× repeated · {chars} chars · {signal} · {status}",
|
|
9
|
+
rules: "warden · rules · {tool} {path} · {asked} rules · {violations} · {status}",
|
|
8
10
|
};
|
|
9
11
|
export function defaultWidgetConfig() {
|
|
10
12
|
return { enabled: true, placement: "aboveEditor", shortcut: "ctrl+shift+w", panelWidth: "40%", ...DEFAULT_TEMPLATES };
|
|
@@ -81,6 +83,18 @@ export function stuckTokens(verdict, at = Date.now()) {
|
|
|
81
83
|
flags: flags || undefined,
|
|
82
84
|
};
|
|
83
85
|
}
|
|
86
|
+
export function runawayTokens(verdict, recovering, at = Date.now()) {
|
|
87
|
+
return {
|
|
88
|
+
guard: "runaway",
|
|
89
|
+
time: time(at),
|
|
90
|
+
kind: verdict.kind,
|
|
91
|
+
count: String(verdict.count),
|
|
92
|
+
chars: String(verdict.chars),
|
|
93
|
+
signal: verdict.signal,
|
|
94
|
+
block: verdict.block,
|
|
95
|
+
status: recovering ? "stopped, recovering" : "stopped",
|
|
96
|
+
};
|
|
97
|
+
}
|
|
84
98
|
export function doneTokens(verdict, at = Date.now()) {
|
|
85
99
|
return {
|
|
86
100
|
guard: "done",
|
|
@@ -113,10 +127,28 @@ export function proseTokens(verdict, at = Date.now()) {
|
|
|
113
127
|
flags: verdict.error ? "typesafe error" : undefined,
|
|
114
128
|
};
|
|
115
129
|
}
|
|
130
|
+
export function rulesTokens(verdict, at = Date.now()) {
|
|
131
|
+
return {
|
|
132
|
+
guard: "rules",
|
|
133
|
+
time: time(at),
|
|
134
|
+
tool: verdict.tool,
|
|
135
|
+
path: verdict.path,
|
|
136
|
+
asked: verdict.source === "skipped" ? undefined : String(verdict.asked),
|
|
137
|
+
violations: verdict.findings.length ? verdict.findings.map(finding => `${finding.name} ${finding.violation.toFixed(2)}`).join(", ") : verdict.scores ? "none" : undefined,
|
|
138
|
+
status: verdict.source === "error" ? "typesafe error" : verdict.source === "skipped" ? "skipped" : verdict.findings.length ? "violation" : "ok",
|
|
139
|
+
source: verdict.source,
|
|
140
|
+
reasons: verdict.skippedReason ?? (verdict.findings.length ? verdict.findings.map(finding => finding.name).join("; ") : undefined),
|
|
141
|
+
model: verdict.model,
|
|
142
|
+
ms: verdict.elapsedMs === undefined ? undefined : String(verdict.elapsedMs),
|
|
143
|
+
flags: verdict.error ? "typesafe error" : undefined,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
116
146
|
/** Token names users can put in templates, for /warden status and the README. */
|
|
117
147
|
export const TOKEN_NAMES = {
|
|
118
148
|
security: ["tool", "injection", "exfiltration", "status"],
|
|
119
149
|
context: ["tool", "retention", "bytesSaved"],
|
|
150
|
+
runaway: ["kind", "count", "chars", "signal", "block", "status", "time", "guard"],
|
|
151
|
+
rules: ["tool", "path", "asked", "violations", "status", "source", "reasons", "model", "ms", "flags", "time", "guard"],
|
|
120
152
|
action: ["tool", "level", "source", "irreversible", "offTask", "scope", "approved", "slop", "slopStub", "slopComments", "slopDead", "slopHedging", "patterns", "reasons", "path", "model", "ms", "flags", "time", "guard"],
|
|
121
153
|
prose: ["wordy", "cliches", "jargon", "status", "reasons", "model", "ms", "flags", "time", "guard"],
|
|
122
154
|
stuck: ["failures", "sameStrategy", "approachChange", "progress", "status", "source", "reasons", "model", "ms", "flags", "time", "guard"],
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-warden",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Guardrails for Pi built on pi-typesafe that steer the agent instead of interrupting you: Jev judges irreversible and off-task tool calls before they run, detects stuck loops, checks unverified done claims, and flags slop.",
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "Guardrails for Pi built on pi-typesafe that steer the agent instead of interrupting you: Jev judges irreversible and off-task tool calls before they run, checks written code against your project rules, detects stuck loops, checks unverified done claims, and flags slop.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Ryan Gapac",
|