pi-warden 0.38.3 → 0.40.1
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 +45 -0
- package/README.md +7 -3
- package/dist/backend.d.ts +4 -7
- package/dist/backend.js +11 -19
- package/dist/config.d.ts +2 -0
- package/dist/config.js +5 -3
- package/dist/conscience.d.ts +49 -3
- package/dist/conscience.js +157 -99
- package/dist/extension.d.ts +3 -2
- package/dist/extension.js +276 -24
- package/dist/extension.js.bak +307 -30
- package/dist/guard.js +9 -5
- package/dist/hashing.d.ts +4 -0
- package/dist/hashing.js +22 -0
- package/dist/index-cmd.d.ts +87 -0
- package/dist/index-cmd.js +330 -0
- package/dist/load.d.ts +12 -3
- package/dist/load.js +20 -11
- package/dist/shape.js +2 -2
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,51 @@ How to keep this current: add the entry in the same pull request as the change,
|
|
|
8
8
|
|
|
9
9
|
<!-- Empty. Next release starts here. -->
|
|
10
10
|
|
|
11
|
+
## 0.40.1
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- An `rm` in a compound command (`rm -rf build && npm test`) no longer makes every word after it a separate target. Target extraction now runs per shell segment, so a compound command stops producing one Jev question per trailing token and no longer trips TypeSafe's 32-question limit on long command lines. A single `rm` naming more targets than the request budget still can; chunking is separate.
|
|
16
|
+
- An `rm` followed by a newline and another command produced no targets at all, so it was never scoped; it now yields the targets of its own line.
|
|
17
|
+
|
|
18
|
+
## 0.40.0
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- `/warden status` and `/warden enable` now describe the key of the configured `typesafeBackend`. With `"openrouter"`, status said "TypeSafe key: missing" on the same line that counted judgments, and enable opened the TypeSafe login prompt, so consent could not be saved. Both call pi-typesafe's backend-aware `authState` and `ensureApiKey`; with no OpenRouter key, enable reports the variable to set instead of prompting (#57).
|
|
23
|
+
- The headless hint and the enable confirmation name the backend's own environment variable.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- `src/backend.ts` reads hosts and key variables from pi-typesafe's `DECISIONS_BACKENDS` instead of keeping its own copy; `keyAvailable` is replaced by `authState({ backend }).usable`, which also honours a rejected key. Requires pi-typesafe 0.7.0.
|
|
28
|
+
|
|
29
|
+
## 0.39.1
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
- Bump `pi-typesafe` from `^0.6.1` to `^0.6.2`. Programmatic API is unchanged; the 0.6.2 tool schema now documents its own payload fields, and no pi-warden text duplicated it.
|
|
33
|
+
|
|
34
|
+
## 0.39.0
|
|
35
|
+
|
|
36
|
+
### Docs
|
|
37
|
+
- README quick start now carries the four setup commands (`/warden enable`, `/warden init`, `/warden index`, `/warden test`) and the beta note; `docs/commands.md` gains rows for `/warden init --force` and `/warden index`.
|
|
38
|
+
- Conscience policy measurement on the per-project corpus with the split disposition gate: beta candidate policy record (89% pooled precision, 74/83; 95% gate not met), comparison against the first measurement and the remeasure, labelled-subset reproduction of the live-run gate numbers.
|
|
39
|
+
- Conscience recommendation remeasurement after index + question changes: before-and-after table, labelled-subset comparison, research-role tool rate, candidate policy update.
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
- `/warden index` command: builds a local capability index with the session model. Entries carry `lead`, `useWhen`, `examples`, and `role` instead of bare names and descriptions. The conscience uses index entries when the source hash matches; bare descriptions are the fallback. Index files live at `~/.pi/agent/pi-warden/index/global.json` and `projects/<hash>.json`. Once-per-session nudge when the index is missing or stale.
|
|
43
|
+
- `conscience.advanceThreshold` config key (default `0.70`): separate gate for the disposition question's P(advance), independent of the usefulness threshold.
|
|
44
|
+
- `CONSCIENCE_BETA_POLICY` in `src/load.ts` (questionHash `fb2d35042f667b3c`, model `jev-1.13.0`, usefulness 0.80, advance 0.70), held in the extension's closure and passed to the activation gate; a test pins the question wording to the policy hash, and `questionHash` is computed over the disposition question plus one canonical candidate question so it no longer moves with batch shape.
|
|
45
|
+
- Capability roles (`research`, `evidence`, `execution`, `delegation`, `review`, `conversation`) in index entries and candidate state; role-based guidance in Score questions.
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
- Conscience defaults: `recommendThreshold` 1.0 → 0.80 (beta policy thresholds); still off by default — `conscience.enabled: true` is the one switch.
|
|
49
|
+
- The replay script omits prompt excerpts from the selected-candidates table unless `PI_WARDEN_OWNER_REPORTS=1`, so committed reports carry no Linear ids or branch names; scrub greps include `CON-[0-9]+`.
|
|
50
|
+
- Replay script `scripts/conscience-replay.mjs` gates delivery on `advanceThreshold` (default 0.70) and reports pi-warden status rows below the gate.
|
|
51
|
+
- Conscience question wording reverted to baseline after four calibration iterations; the disposition gate, not wording, was the lever.
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
- The conscience activation gate now fails closed: with no active policy, or a policy whose hash or model does not match the request, no recommendation is delivered (traced `no_policy`). Previously an absent policy let delivery through, contradicting `docs/configuration.md`. The gate also compares the policy's model against the model that actually answered instead of the literal `"jev-latest"`.
|
|
55
|
+
|
|
11
56
|
## 0.38.3
|
|
12
57
|
|
|
13
58
|
### Fixed
|
package/README.md
CHANGED
|
@@ -14,9 +14,12 @@ Instead of interrupting you for every problem, Warden usually feeds the issue ba
|
|
|
14
14
|
pi install npm:pi-warden
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
1. `/warden enable` to
|
|
18
|
-
2. `/warden
|
|
19
|
-
3.
|
|
17
|
+
1. `/warden enable` to turn judgments on. Paste a key from [console.typesafe.ai](https://console.typesafe.ai) (hidden input, saved owner-only), or skip for offline-only mode.
|
|
18
|
+
2. `/warden init` to write a starter `pi-warden.md`. The rules guard enforces what you put there.
|
|
19
|
+
3. `/warden index` (recommended) so the conscience knows your skills and tools from their real descriptions.
|
|
20
|
+
4. `/warden test` to see one synthetic verdict, then work as usual. The status line shows verdicts; `ctrl+shift+w` opens the trace sidebar.
|
|
21
|
+
|
|
22
|
+
The conscience is beta and off by default. Turn it on with `conscience.enabled: true` in the user config.
|
|
20
23
|
|
|
21
24
|
Works without any key (offline guards: pattern list, runaway stop, sensitive-path notes, credential warnings). Requires Pi 0.85+, Node 22.19+.
|
|
22
25
|
|
|
@@ -32,6 +35,7 @@ Works without any key (offline guards: pattern list, runaway stop, sensitive-pat
|
|
|
32
35
|
| **Security** | code and output | Hardcoded secrets, injection risks |
|
|
33
36
|
| **Runaway** | reply stream | Stops replies that begin repeating themselves |
|
|
34
37
|
| **Subagent triage** | async child reports | Keeps noisy background work from waking the parent |
|
|
38
|
+
| **Conscience** (beta, off) | each operator prompt | Recommends a skill or tool from the capability index before the agent acts; enable with `conscience.enabled: true` |
|
|
35
39
|
|
|
36
40
|
## How intervention works
|
|
37
41
|
|
package/dist/backend.d.ts
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
import type { TypeSafeOptions } from "pi-typesafe";
|
|
2
2
|
/** The judgment backend that receives pi-warden's Jev requests. */
|
|
3
3
|
export type JudgmentBackend = "typesafe" | "openrouter";
|
|
4
|
-
/** The host the consent disclosure names as the destination. */
|
|
4
|
+
/** The host the consent disclosure names as the destination, without scheme: `api.typesafe.ai`, `openrouter.ai`. */
|
|
5
5
|
export declare function backendHost(backend: JudgmentBackend): string;
|
|
6
|
+
/** The environment variable that carries the backend's key, for messages that tell the user what to set. */
|
|
7
|
+
export declare function keyEnvFor(backend: JudgmentBackend): string;
|
|
6
8
|
/** Adapt the consent text to the active backend by substituting the destination host. */
|
|
7
9
|
export declare function disclosureFor(backend: JudgmentBackend, disclosure: string): string;
|
|
8
|
-
/**
|
|
9
|
-
* Whether the right credential exists for the chosen backend.
|
|
10
|
-
* TypeSafe uses the pi-typesafe keystore; other backends use an environment variable.
|
|
11
|
-
*/
|
|
12
|
-
export declare function keyAvailable(backend: JudgmentBackend, env: Record<string, string | undefined>, keystoreUsable: () => boolean): boolean;
|
|
13
10
|
/**
|
|
14
11
|
* Build the options forwarded to `createTypeSafe`.
|
|
15
|
-
* The caller must gate on `
|
|
12
|
+
* The caller must gate on `authState({ backend }).usable` before calling `createTypeSafe`.
|
|
16
13
|
*/
|
|
17
14
|
export declare function judgeOptions(config: {
|
|
18
15
|
maxRequests: number;
|
package/dist/backend.js
CHANGED
|
@@ -1,36 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
typesafe: { host: "api.typesafe.ai" },
|
|
4
|
-
openrouter: { host: "openrouter.ai", keyEnv: "OPENROUTER_API_KEY" },
|
|
5
|
-
};
|
|
6
|
-
/** The host the consent disclosure names as the destination. */
|
|
1
|
+
import { DECISIONS_BACKENDS, DEFAULT_BACKEND } from "pi-typesafe";
|
|
2
|
+
/** The host the consent disclosure names as the destination, without scheme: `api.typesafe.ai`, `openrouter.ai`. */
|
|
7
3
|
export function backendHost(backend) {
|
|
8
|
-
return
|
|
4
|
+
return new URL(DECISIONS_BACKENDS[backend].host).host;
|
|
5
|
+
}
|
|
6
|
+
/** The environment variable that carries the backend's key, for messages that tell the user what to set. */
|
|
7
|
+
export function keyEnvFor(backend) {
|
|
8
|
+
return DECISIONS_BACKENDS[backend].keyEnv ?? DECISIONS_BACKENDS[DEFAULT_BACKEND].keyEnv ?? "TYPESAFE_API_KEY";
|
|
9
9
|
}
|
|
10
10
|
/** Adapt the consent text to the active backend by substituting the destination host. */
|
|
11
11
|
export function disclosureFor(backend, disclosure) {
|
|
12
|
-
return backend ===
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Whether the right credential exists for the chosen backend.
|
|
16
|
-
* TypeSafe uses the pi-typesafe keystore; other backends use an environment variable.
|
|
17
|
-
*/
|
|
18
|
-
export function keyAvailable(backend, env, keystoreUsable) {
|
|
19
|
-
const keyEnv = BACKENDS[backend].keyEnv;
|
|
20
|
-
return keyEnv ? (env[keyEnv]?.trim() ?? "") !== "" : keystoreUsable();
|
|
12
|
+
return backend === DEFAULT_BACKEND ? disclosure : disclosure.replace(backendHost(DEFAULT_BACKEND), backendHost(backend));
|
|
21
13
|
}
|
|
22
14
|
/**
|
|
23
15
|
* Build the options forwarded to `createTypeSafe`.
|
|
24
|
-
* The caller must gate on `
|
|
16
|
+
* The caller must gate on `authState({ backend }).usable` before calling `createTypeSafe`.
|
|
25
17
|
*/
|
|
26
18
|
export function judgeOptions(config) {
|
|
27
19
|
return {
|
|
28
20
|
maxRequests: config.maxRequests,
|
|
29
21
|
timeoutMs: config.timeoutMs,
|
|
30
|
-
...(config.typesafeBackend ===
|
|
22
|
+
...(config.typesafeBackend === DEFAULT_BACKEND ? {} : { backend: config.typesafeBackend }),
|
|
31
23
|
};
|
|
32
24
|
}
|
|
33
25
|
/** Resolve the effective backend from a raw config value; invalid values fall back to "typesafe". */
|
|
34
26
|
export function resolveBackend(raw) {
|
|
35
|
-
return raw === "typesafe" || raw === "openrouter" ? raw :
|
|
27
|
+
return raw === "typesafe" || raw === "openrouter" ? raw : DEFAULT_BACKEND;
|
|
36
28
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -273,6 +273,8 @@ export interface ConscienceConfig {
|
|
|
273
273
|
maxLoadedBytes: number;
|
|
274
274
|
/** P(useful now) must reach this to select a candidate for recommendation. Default 1.0 (trace-only until calibrated per spec §7). */
|
|
275
275
|
recommendThreshold: number;
|
|
276
|
+
/** P(advance) from the disposition question must reach this before a candidate is considered. Default 0.70. */
|
|
277
|
+
advanceThreshold: number;
|
|
276
278
|
/** P(useful now) must reach this to auto-load. Must be >= recommendThreshold. Default 1.0 (trace-only until calibrated). */
|
|
277
279
|
loadThreshold: number;
|
|
278
280
|
}
|
package/dist/config.js
CHANGED
|
@@ -57,13 +57,14 @@ export function defaultConfig() {
|
|
|
57
57
|
enabled: false,
|
|
58
58
|
skills: { mode: "recommend", exclude: [] },
|
|
59
59
|
tools: { enabled: true, exclude: [] },
|
|
60
|
-
timeoutMs:
|
|
60
|
+
timeoutMs: 3000,
|
|
61
61
|
maxAssessments: 3,
|
|
62
62
|
maxNudges: 2,
|
|
63
63
|
maxSkillBytes: 32768,
|
|
64
64
|
maxLoadedBytes: 65536,
|
|
65
|
-
//
|
|
66
|
-
recommendThreshold:
|
|
65
|
+
// Beta policy thresholds (measured 2026-09-22); disabled by default, conscience.enabled is the switch.
|
|
66
|
+
recommendThreshold: 0.80,
|
|
67
|
+
advanceThreshold: 0.70,
|
|
67
68
|
loadThreshold: 1.0,
|
|
68
69
|
},
|
|
69
70
|
};
|
|
@@ -478,6 +479,7 @@ function applyConscience(base, raw) {
|
|
|
478
479
|
maxSkillBytes: Math.max(1024, Math.min(131072, typeof raw.maxSkillBytes === "number" ? raw.maxSkillBytes : base.maxSkillBytes)),
|
|
479
480
|
maxLoadedBytes: Math.max(1024, Math.min(262144, typeof raw.maxLoadedBytes === "number" ? raw.maxLoadedBytes : base.maxLoadedBytes)),
|
|
480
481
|
recommendThreshold: Math.max(0, Math.min(1, typeof raw.recommendThreshold === "number" ? raw.recommendThreshold : base.recommendThreshold)),
|
|
482
|
+
advanceThreshold: Math.max(0, Math.min(1, typeof raw.advanceThreshold === "number" ? raw.advanceThreshold : base.advanceThreshold)),
|
|
481
483
|
loadThreshold: Math.max(0, Math.min(1, typeof raw.loadThreshold === "number" ? raw.loadThreshold : base.loadThreshold)),
|
|
482
484
|
};
|
|
483
485
|
}
|
package/dist/conscience.d.ts
CHANGED
|
@@ -8,6 +8,24 @@ import type { Skill } from "@earendil-works/pi-coding-agent";
|
|
|
8
8
|
import type { Questions } from "pi-typesafe";
|
|
9
9
|
import type { ConscienceConfig } from "./config.js";
|
|
10
10
|
export type Disposition = "advance" | "awaiting_user" | "no_gap" | "unclear";
|
|
11
|
+
/** Capability roles the session model assigns to index entries. */
|
|
12
|
+
export type CapabilityRole = "research" | "evidence" | "execution" | "delegation" | "review" | "conversation";
|
|
13
|
+
/** Minimal index entry shape used by the conscience; full validation lives in index-cmd. */
|
|
14
|
+
export interface IndexEntry {
|
|
15
|
+
kind: "skill" | "tool";
|
|
16
|
+
name: string;
|
|
17
|
+
scope: "global" | "project";
|
|
18
|
+
sourceHash: string;
|
|
19
|
+
role: CapabilityRole;
|
|
20
|
+
lead: string;
|
|
21
|
+
useWhen: string[];
|
|
22
|
+
notWhen: string[];
|
|
23
|
+
inputs: string;
|
|
24
|
+
examples: string[];
|
|
25
|
+
thin: boolean;
|
|
26
|
+
truncated?: boolean;
|
|
27
|
+
_location?: string;
|
|
28
|
+
}
|
|
11
29
|
/** One candidate for assessment: a skill or a tool. */
|
|
12
30
|
export interface Candidate {
|
|
13
31
|
/** "skill" or "tool". */
|
|
@@ -20,6 +38,10 @@ export interface Candidate {
|
|
|
20
38
|
incomplete?: boolean;
|
|
21
39
|
/** Full skill metadata, present for kind=skill. */
|
|
22
40
|
skill?: Skill;
|
|
41
|
+
/** Capability role, present when the candidate has an index entry. */
|
|
42
|
+
role?: CapabilityRole;
|
|
43
|
+
/** Index entry when the candidate has a matching entry; bare description is the fallback. */
|
|
44
|
+
indexEntry?: IndexEntry;
|
|
23
45
|
}
|
|
24
46
|
/** The result of one assessment. */
|
|
25
47
|
export interface AssessmentResult {
|
|
@@ -77,13 +99,29 @@ export declare function buildState(task: string, recentContext: string, activeSk
|
|
|
77
99
|
/**
|
|
78
100
|
* Compute a deterministic hash of the assessment questions for deduplication.
|
|
79
101
|
* Recursively sorts all object keys for canonical form.
|
|
102
|
+
*
|
|
103
|
+
* Opaque candidate ids (c1, c2, …) are positional, not semantic: which batch a prompt happens to
|
|
104
|
+
* produce must not change the hash the activation gate compares. A conscience question set
|
|
105
|
+
* (recognised by the `conscience_disposition` key) is therefore hashed as the disposition question
|
|
106
|
+
* plus one canonical candidate question, keyed `c1` — every candidate question carries the same
|
|
107
|
+
* wording by design (per-candidate state lives in the request state, not the question text), so any
|
|
108
|
+
* one of them is the canonical representative.
|
|
109
|
+
*
|
|
110
|
+
* Why the hash was constant before this change: each assessment issues several requests (one per
|
|
111
|
+
* batch), each with its own question keys, and `assess` kept only the hash of whichever batch wrote
|
|
112
|
+
* `state_.hash` last. Every recorded replay row showed `fb2d35042f667b3c` only because those final
|
|
113
|
+
* batches happened to serialize identically — an accident of batch order, not a guarantee.
|
|
80
114
|
*/
|
|
81
115
|
export declare function questionHash(questions: Questions): string;
|
|
82
|
-
/** Filter candidates by eligibility rules, capped at MAX_ELIGIBLE per category. */
|
|
116
|
+
/** Filter candidates by eligibility rules, capped at MAX_ELIGIBLE per category. Attaches index entries when available. */
|
|
83
117
|
export declare function eligibleCandidates(skills: Skill[], tools: {
|
|
84
118
|
name: string;
|
|
85
119
|
description: string;
|
|
86
|
-
}[], config: ConscienceConfig, activeSkills: string[], suppliedSkills: string[]
|
|
120
|
+
}[], config: ConscienceConfig, activeSkills: string[], suppliedSkills: string[], globalIndex?: {
|
|
121
|
+
entries: IndexEntry[];
|
|
122
|
+
} | undefined, projectIndex?: {
|
|
123
|
+
entries: IndexEntry[];
|
|
124
|
+
} | undefined): {
|
|
87
125
|
candidates: Candidate[];
|
|
88
126
|
skillOverflow: boolean;
|
|
89
127
|
toolOverflow: boolean;
|
|
@@ -105,11 +143,19 @@ export interface ConscienceDeps {
|
|
|
105
143
|
sharedTimeoutMs: number;
|
|
106
144
|
/** Current wall-clock time (ms). Injected for testability. */
|
|
107
145
|
now?: () => number;
|
|
146
|
+
/** Pre-loaded global index, if any. */
|
|
147
|
+
globalIndex?: {
|
|
148
|
+
entries: IndexEntry[];
|
|
149
|
+
} | undefined;
|
|
150
|
+
/** Pre-loaded project index, if any. */
|
|
151
|
+
projectIndex?: {
|
|
152
|
+
entries: IndexEntry[];
|
|
153
|
+
} | undefined;
|
|
108
154
|
}
|
|
109
155
|
/**
|
|
110
156
|
* Run assessment for the given prompt revision.
|
|
111
157
|
* Pre-measurement: returns trace-only results. No steers, no auto-loading.
|
|
112
|
-
* Issues
|
|
158
|
+
* Issues concurrent requests (up to four in flight), skills first, shared deadline.
|
|
113
159
|
*/
|
|
114
160
|
export declare function assess(prompt: string, recentContext: string, skills: Skill[], tools: {
|
|
115
161
|
name: string;
|