@herbertgao/pi-extensions 2026.9.7 → 2026.9.9
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 +8 -4
- package/THIRD_PARTY_NOTICES.md +52 -0
- package/node_modules/@herbertgao/pi-bark/package.json +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +4 -4
- package/node_modules/@herbertgao/resume-from/package.json +2 -2
- package/node_modules/@narumitw/pi-btw/README.md +6 -1
- package/node_modules/@narumitw/pi-btw/dist/index.ts +209 -23
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +4 -4
- package/node_modules/@narumitw/pi-btw/package.json +1 -1
- package/node_modules/@narumitw/pi-btw/src/btw.ts +1 -12
- package/node_modules/@narumitw/pi-btw/src/menu.ts +244 -15
- package/node_modules/@narumitw/pi-btw/src/settings.ts +5 -0
- package/node_modules/grok-mermaid/CHANGELOG.md +46 -0
- package/node_modules/grok-mermaid/LICENSE +205 -0
- package/node_modules/grok-mermaid/README.md +191 -0
- package/node_modules/grok-mermaid/dist/ansi.d.ts +16 -0
- package/node_modules/grok-mermaid/dist/ansi.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/ansi.js +23 -0
- package/node_modules/grok-mermaid/dist/ansi.js.map +1 -0
- package/node_modules/grok-mermaid/dist/canvas.d.ts +87 -0
- package/node_modules/grok-mermaid/dist/canvas.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/canvas.js +366 -0
- package/node_modules/grok-mermaid/dist/canvas.js.map +1 -0
- package/node_modules/grok-mermaid/dist/graph.d.ts +74 -0
- package/node_modules/grok-mermaid/dist/graph.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/graph.js +91 -0
- package/node_modules/grok-mermaid/dist/graph.js.map +1 -0
- package/node_modules/grok-mermaid/dist/index.d.ts +32 -0
- package/node_modules/grok-mermaid/dist/index.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/index.js +100 -0
- package/node_modules/grok-mermaid/dist/index.js.map +1 -0
- package/node_modules/grok-mermaid/dist/labels.d.ts +62 -0
- package/node_modules/grok-mermaid/dist/labels.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/labels.js +324 -0
- package/node_modules/grok-mermaid/dist/labels.js.map +1 -0
- package/node_modules/grok-mermaid/dist/layout-seq.d.ts +12 -0
- package/node_modules/grok-mermaid/dist/layout-seq.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/layout-seq.js +194 -0
- package/node_modules/grok-mermaid/dist/layout-seq.js.map +1 -0
- package/node_modules/grok-mermaid/dist/layout.d.ts +87 -0
- package/node_modules/grok-mermaid/dist/layout.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/layout.js +881 -0
- package/node_modules/grok-mermaid/dist/layout.js.map +1 -0
- package/node_modules/grok-mermaid/dist/parse.d.ts +83 -0
- package/node_modules/grok-mermaid/dist/parse.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/parse.js +1151 -0
- package/node_modules/grok-mermaid/dist/parse.js.map +1 -0
- package/node_modules/grok-mermaid/dist/source-box.d.ts +18 -0
- package/node_modules/grok-mermaid/dist/source-box.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/source-box.js +78 -0
- package/node_modules/grok-mermaid/dist/source-box.js.map +1 -0
- package/node_modules/grok-mermaid/dist/types.d.ts +42 -0
- package/node_modules/grok-mermaid/dist/types.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/types.js +1 -0
- package/node_modules/grok-mermaid/dist/types.js.map +1 -0
- package/node_modules/grok-mermaid/dist/width-data.d.ts +2 -0
- package/node_modules/grok-mermaid/dist/width-data.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/width-data.js +994 -0
- package/node_modules/grok-mermaid/dist/width-data.js.map +1 -0
- package/node_modules/grok-mermaid/dist/width.d.ts +18 -0
- package/node_modules/grok-mermaid/dist/width.d.ts.map +1 -0
- package/node_modules/grok-mermaid/dist/width.js +76 -0
- package/node_modules/grok-mermaid/dist/width.js.map +1 -0
- package/node_modules/grok-mermaid/package.json +49 -0
- package/node_modules/grok-mermaid/src/ansi.ts +34 -0
- package/node_modules/grok-mermaid/src/canvas.ts +373 -0
- package/node_modules/grok-mermaid/src/graph.ts +142 -0
- package/node_modules/grok-mermaid/src/index.ts +104 -0
- package/node_modules/grok-mermaid/src/labels.ts +326 -0
- package/node_modules/grok-mermaid/src/layout-seq.ts +203 -0
- package/node_modules/grok-mermaid/src/layout.ts +1015 -0
- package/node_modules/grok-mermaid/src/parse.ts +1189 -0
- package/node_modules/grok-mermaid/src/source-box.ts +89 -0
- package/node_modules/grok-mermaid/src/types.ts +43 -0
- package/node_modules/grok-mermaid/src/width-data.ts +993 -0
- package/node_modules/grok-mermaid/src/width.ts +74 -0
- package/node_modules/pi-jev-auto-mode/CHANGELOG.md +110 -0
- package/node_modules/pi-jev-auto-mode/LICENSE +21 -0
- package/node_modules/pi-jev-auto-mode/README.md +285 -0
- package/node_modules/pi-jev-auto-mode/SECURITY.md +32 -0
- package/node_modules/pi-jev-auto-mode/docs/calibration.md +154 -0
- package/node_modules/pi-jev-auto-mode/docs/design.md +164 -0
- package/node_modules/pi-jev-auto-mode/docs/security.md +124 -0
- package/node_modules/pi-jev-auto-mode/index.ts +1 -0
- package/node_modules/pi-jev-auto-mode/package.json +72 -0
- package/node_modules/pi-jev-auto-mode/src/call.ts +180 -0
- package/node_modules/pi-jev-auto-mode/src/decide.ts +86 -0
- package/node_modules/pi-jev-auto-mode/src/extension.ts +862 -0
- package/node_modules/pi-jev-auto-mode/src/intent.ts +71 -0
- package/node_modules/pi-jev-auto-mode/src/jev/availability.ts +53 -0
- package/node_modules/pi-jev-auto-mode/src/jev/criteria.ts +19 -0
- package/node_modules/pi-jev-auto-mode/src/jev/decide.ts +187 -0
- package/node_modules/pi-jev-auto-mode/src/jev/engine.ts +165 -0
- package/node_modules/pi-jev-auto-mode/src/jev/index.ts +20 -0
- package/node_modules/pi-jev-auto-mode/src/jev/questions.ts +273 -0
- package/node_modules/pi-jev-auto-mode/src/jev/response.ts +64 -0
- package/node_modules/pi-jev-auto-mode/src/jev/state.ts +20 -0
- package/node_modules/pi-jev-auto-mode/src/jev/transport.ts +117 -0
- package/node_modules/pi-jev-auto-mode/src/jev/types.ts +46 -0
- package/node_modules/pi-jev-auto-mode/src/policy.ts +558 -0
- package/node_modules/pi-jev-auto-mode/src/records.ts +118 -0
- package/node_modules/pi-jev-auto-mode/src/settings.ts +327 -0
- package/node_modules/pi-jev-auto-mode/src/ui.ts +233 -0
- package/node_modules/pi-lens/CHANGELOG.md +45 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +21 -9
- package/node_modules/pi-lens/dist/clients/cascade-format.js +24 -0
- package/node_modules/pi-lens/dist/clients/extension-log.js +48 -1
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +11 -12
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +3 -0
- package/node_modules/pi-lens/dist/clients/lsp/index.js +41 -7
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +9 -2
- package/node_modules/pi-lens/dist/clients/path-utils.js +51 -9
- package/node_modules/pi-lens/dist/clients/persistent-reverify.js +271 -0
- package/node_modules/pi-lens/dist/clients/pipeline.js +15 -1
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +124 -7
- package/node_modules/pi-lens/dist/clients/skills-resolver.js +38 -2
- package/node_modules/pi-lens/dist/clients/widget-state.js +152 -6
- package/node_modules/pi-lens/dist/index.js +729 -344
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +14 -2
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +84 -17
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +32 -5
- package/node_modules/pi-lens/package.json +1 -1
- package/node_modules/pi-typesafe/LICENSE +21 -0
- package/node_modules/pi-typesafe/README.md +153 -0
- package/node_modules/pi-typesafe/dist/ask.d.ts +32 -0
- package/node_modules/pi-typesafe/dist/ask.js +25 -0
- package/node_modules/pi-typesafe/dist/auth.d.ts +56 -0
- package/node_modules/pi-typesafe/dist/auth.js +114 -0
- package/node_modules/pi-typesafe/dist/batch.d.ts +74 -0
- package/node_modules/pi-typesafe/dist/batch.js +116 -0
- package/node_modules/pi-typesafe/dist/calibrate.d.ts +106 -0
- package/node_modules/pi-typesafe/dist/calibrate.js +157 -0
- package/node_modules/pi-typesafe/dist/client.d.ts +69 -0
- package/node_modules/pi-typesafe/dist/client.js +188 -0
- package/node_modules/pi-typesafe/dist/credentials.d.ts +46 -0
- package/node_modules/pi-typesafe/dist/credentials.js +112 -0
- package/node_modules/pi-typesafe/dist/errors.d.ts +9 -0
- package/node_modules/pi-typesafe/dist/errors.js +31 -0
- package/node_modules/pi-typesafe/dist/extension.d.ts +3 -0
- package/node_modules/pi-typesafe/dist/extension.js +222 -0
- package/node_modules/pi-typesafe/dist/index.d.ts +18 -0
- package/node_modules/pi-typesafe/dist/index.js +9 -0
- package/node_modules/pi-typesafe/dist/key-prompt.d.ts +3 -0
- package/node_modules/pi-typesafe/dist/key-prompt.js +43 -0
- package/node_modules/pi-typesafe/dist/login.d.ts +27 -0
- package/node_modules/pi-typesafe/dist/login.js +38 -0
- package/node_modules/pi-typesafe/dist/schema.d.ts +43 -0
- package/node_modules/pi-typesafe/dist/schema.js +152 -0
- package/node_modules/pi-typesafe/dist/ui.d.ts +3 -0
- package/node_modules/pi-typesafe/dist/ui.js +3 -0
- package/node_modules/pi-typesafe/dist/usage.d.ts +73 -0
- package/node_modules/pi-typesafe/dist/usage.js +182 -0
- package/node_modules/pi-typesafe/examples/decision-extension.ts +41 -0
- package/node_modules/pi-typesafe/extensions/index.js +2 -0
- package/node_modules/pi-typesafe/package.json +89 -0
- package/package.json +16 -8
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { WIDTHS } from './width-data.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Display width, measured in grapheme clusters.
|
|
5
|
+
*
|
|
6
|
+
* A cluster is the unit both of measuring and of painting, so a box is always
|
|
7
|
+
* sized for exactly what gets drawn into it. Splitting those two — sizing by
|
|
8
|
+
* cluster but painting by code point — is what makes `👨👩👧` overflow its
|
|
9
|
+
* border in the Rust original.
|
|
10
|
+
*
|
|
11
|
+
* Clustering comes from `Intl.Segmenter` (UAX #29), which already handles ZWJ
|
|
12
|
+
* sequences, skin-tone modifiers, variation selectors, keycaps, flags and
|
|
13
|
+
* Hangul. Per-code-point widths are generated from the `unicode-width` crate.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' })
|
|
17
|
+
|
|
18
|
+
const VS16 = 0xfe0f
|
|
19
|
+
const isRegionalIndicator = (cp: number): boolean => cp >= 0x1f1e6 && cp <= 0x1f1ff
|
|
20
|
+
|
|
21
|
+
/** Width of one code point; the table covers the whole code point space. */
|
|
22
|
+
function codePointWidth(cp: number): number {
|
|
23
|
+
let lo = 0
|
|
24
|
+
let hi = WIDTHS.length - 1
|
|
25
|
+
while (lo <= hi) {
|
|
26
|
+
const mid = (lo + hi) >> 1
|
|
27
|
+
const run = WIDTHS[mid]
|
|
28
|
+
if (cp < run[0]) hi = mid - 1
|
|
29
|
+
else if (cp > run[1]) lo = mid + 1
|
|
30
|
+
else return run[2]
|
|
31
|
+
}
|
|
32
|
+
return 1
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Columns occupied by one grapheme cluster.
|
|
37
|
+
*
|
|
38
|
+
* The widest code point wins, so a base plus its combining marks measures as
|
|
39
|
+
* the base. Two adjustments: a variation selector requesting emoji
|
|
40
|
+
* presentation forces two columns, as does a regional indicator pair (a flag).
|
|
41
|
+
*
|
|
42
|
+
* Zero is a real answer — a soft hyphen or zero-width space occupies nothing,
|
|
43
|
+
* and callers skip painting such a cluster rather than reserving a cell.
|
|
44
|
+
*/
|
|
45
|
+
export function clusterWidth(cluster: string): number {
|
|
46
|
+
let w = 0
|
|
47
|
+
let vs16 = false
|
|
48
|
+
let regional = 0
|
|
49
|
+
for (const ch of cluster) {
|
|
50
|
+
const cp = ch.codePointAt(0) as number
|
|
51
|
+
if (cp === VS16) vs16 = true
|
|
52
|
+
if (isRegionalIndicator(cp)) regional++
|
|
53
|
+
const cw = codePointWidth(cp)
|
|
54
|
+
if (cw > w) w = cw
|
|
55
|
+
}
|
|
56
|
+
return vs16 || regional >= 2 ? 2 : w
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Iterate grapheme clusters, so no loop can split one. */
|
|
60
|
+
export function* clusters(s: string): Generator<string> {
|
|
61
|
+
for (const { segment } of segmenter.segment(s)) yield segment
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Iterate clusters paired with their display width. */
|
|
65
|
+
export function* measured(s: string): Generator<[string, number]> {
|
|
66
|
+
for (const { segment } of segmenter.segment(s)) yield [segment, clusterWidth(segment)]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Display columns of a string. */
|
|
70
|
+
export function stringWidth(s: string): number {
|
|
71
|
+
let w = 0
|
|
72
|
+
for (const { segment } of segmenter.segment(s)) w += clusterWidth(segment)
|
|
73
|
+
return w
|
|
74
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.4.1 - 2026-09-18
|
|
4
|
+
|
|
5
|
+
- **An unclear answer passes by default.** `uncertain` defaulted to `deny`, which meant a
|
|
6
|
+
judgment the model was unsure about stopped the call. That is the interruption an auto mode
|
|
7
|
+
exists to remove; clear rejections still block. `uncertain deny` and `uncertain ask` remain
|
|
8
|
+
available for anyone who wants the stricter behaviour.
|
|
9
|
+
- **Without a key the gate says so and stops**, instead of inventing a verdict and blocking with
|
|
10
|
+
an unexplained reason. The message names the fix: `/jev-auto-mode login`, or
|
|
11
|
+
`/jev-auto-mode off`. The footer reads `🛡 jev no key` in that state, and the session start
|
|
12
|
+
warns once.
|
|
13
|
+
- **A chain of read-only commands is read-only.** `cd src && ls -la && git log -3` was judged as
|
|
14
|
+
a whole because the allowlist rejected any command containing shell control syntax, so agents
|
|
15
|
+
paid a judgment round trip for their most common line. Each segment is now checked on its own,
|
|
16
|
+
and `cd` is allowed. A chain containing anything else (`curl … | sh`) is still judged.
|
|
17
|
+
- **The docs match the no-key behaviour.** The README and a comment still described a fallback
|
|
18
|
+
that confirms in a UI, which the gate no longer does: without a key it stops the calls it cannot
|
|
19
|
+
judge and says it is not connected to Jev.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## 0.4.0
|
|
23
|
+
|
|
24
|
+
**The semantic layer now sees everything the deterministic layer cannot vouch for, and it does
|
|
25
|
+
not stop ordinary work.**
|
|
26
|
+
|
|
27
|
+
- `gateScope` (default `all`) replaces the denylist as the way calls are selected. A dangerous
|
|
28
|
+
pattern can only recognise a shape someone wrote down first: a request that uploads a file
|
|
29
|
+
(`curl -d @...`) once ran with no judgment at all because no pattern described it, and adding
|
|
30
|
+
patterns to a denylist is a race that never ends. Under `all`, the deterministic layer names
|
|
31
|
+
what it can vouch for and everything else is judged. `matched` keeps the old behaviour.
|
|
32
|
+
`/jev-auto-mode scope all|matched` switches between them.
|
|
33
|
+
- **The intent question is asked only about commands the deterministic layer recognised as a
|
|
34
|
+
dangerous shape, and only a clear "this was not requested" blocks.** Asked about every command,
|
|
35
|
+
it blocked ordinary work the request never mentioned — an unrequested `mv`, `cp`, `tar`,
|
|
36
|
+
`chmod +x`, or `node -e`. An auto mode that stops for those has no reason to exist. Measured
|
|
37
|
+
after the change: those run, while an unrequested `git reset --hard`, `npm publish`, `rm -rf`,
|
|
38
|
+
or `sudo` still blocks (p = 0.04–0.11).
|
|
39
|
+
- Read-only inspection is now a real fast path, because under `all` it carries the load the
|
|
40
|
+
denylist used to carry: `cat`, `head`, `tail`, `wc`, `find`, `jq`, `diff`, `sort`, `stat`,
|
|
41
|
+
version probes, and read-only git subcommands. Destructive variants (`find -delete`,
|
|
42
|
+
`git tag -d`, `push --force`) still match dangerous patterns and are judged.
|
|
43
|
+
- The user's `safeCommands` outranks a dangerous-pattern match; the built-in read-only list does
|
|
44
|
+
not, so `grep secret ~/.ssh/id_ed25519` is judged even though `grep` is read-only.
|
|
45
|
+
- `Escalated:` replaces `Matched:` in the confirmation dialog, because under `all` the reasons
|
|
46
|
+
are not all pattern matches.
|
|
47
|
+
|
|
48
|
+
## 0.3.0
|
|
49
|
+
|
|
50
|
+
The 0.2.0 default resolved the middle band as a block, but two conditions were still
|
|
51
|
+
`required`, which made the gate strict for structurally wrong reasons rather than measured
|
|
52
|
+
ones. Corrected:
|
|
53
|
+
|
|
54
|
+
- `intent_coverage` 0.80 → **0.60**. Measured answers are 0.77–0.98 when the user asked and
|
|
55
|
+
0.06–0.15 when they did not, so 0.80 sat on top of the "asked" cluster instead of inside the
|
|
56
|
+
empty band between the two. The middle band is now (0.40, 0.60).
|
|
57
|
+
- `policy_compliance` required → **hazard**. It measured 0.66–0.85 on calls where nothing was
|
|
58
|
+
wrong, so as a requirement it blocked every gated call the moment a policy was configured.
|
|
59
|
+
Now only a clear violation stops a call.
|
|
60
|
+
- `path_not_protected` required → **hazard**. An unclear answer no longer blocks on its own; the
|
|
61
|
+
user's request decides. A target the model clearly identifies as a credential store still
|
|
62
|
+
blocks (`.env` measured p = 0.02, `~/.ssh` p = 0.03).
|
|
63
|
+
- New `no_fetched_code_execution`, required, asked only for commands the deterministic layer
|
|
64
|
+
already recognised as downloaded-script execution. `curl | bash` stays blocked (p = 0.02)
|
|
65
|
+
without making every other call strict.
|
|
66
|
+
- `.env.example`, `.env.sample`, `.env.template`, and `.env.dist` are no longer treated as
|
|
67
|
+
credential stores: templates belong in the repository.
|
|
68
|
+
- The intent window widened from 8 messages / 4000 characters to 12 / 6000, so an ongoing task
|
|
69
|
+
does not lose the request that justifies it and look unrequested as a result.
|
|
70
|
+
|
|
71
|
+
## 0.2.0
|
|
72
|
+
|
|
73
|
+
- The confirmation dialog is bounded: it showed the whole command, and since Pi's dialogs do not
|
|
74
|
+
clip their content a long command produced a dialog taller than the terminal. It now shows a
|
|
75
|
+
short preview and says what was hidden.
|
|
76
|
+
- **The middle band no longer asks the user by default.** An auto mode that stops to ask has
|
|
77
|
+
handed the decision back to a human, and the agent can always ask in conversation if it needs
|
|
78
|
+
guidance. A judgment that is neither satisfied nor rejected now blocks, so the gate never
|
|
79
|
+
takes over the screen.
|
|
80
|
+
- `uncertain` setting and `/jev-auto-mode uncertain deny|ask|allow` control it. `deny` is the
|
|
81
|
+
default; `ask` restores the confirmation dialog; `allow` trusts the band.
|
|
82
|
+
- `/jev-auto-mode threshold edit` picks a rule and prompts for a value, showing each rule's
|
|
83
|
+
current threshold next to the last probability the model returned for it.
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
## 0.1.1
|
|
87
|
+
|
|
88
|
+
- Correct the product name. It is **Jev** — TypeSafe's System One model, spelled with a
|
|
89
|
+
capital J and lowercase `ev`, as in their announcement, FAQ, and the model id
|
|
90
|
+
`typesafe-ai/jev` — not "JEV". Fixed in the README, docs, source comments, and the package
|
|
91
|
+
description. The `0.1.0` metadata cannot be edited, so this release exists to carry the
|
|
92
|
+
corrected name. No behavioral change.
|
|
93
|
+
|
|
94
|
+
## 0.1.0
|
|
95
|
+
|
|
96
|
+
Initial release.
|
|
97
|
+
|
|
98
|
+
- Two-layer gate: a deterministic policy envelope (hard-deny, allow/deny patterns,
|
|
99
|
+
dangerous-command detection, protected paths) and a Jev semantic layer that only sees
|
|
100
|
+
what the first layer escalated.
|
|
101
|
+
- Conditions carry a `mode` (`required` / `hazard`) and a `severity` (`hazard` / `soft`),
|
|
102
|
+
calibrated against measured probabilities from the real API (`docs/calibration.md`).
|
|
103
|
+
- Fail-closed everywhere: no key, timeout, malformed response, cancellation, missing
|
|
104
|
+
answer, or oversized request all resolve to a block rather than an approval.
|
|
105
|
+
- `/jev-auto-mode` command surface: `on|off`, `login|logout`, `policy`, `threshold`.
|
|
106
|
+
- Decision records via `pi.appendEntry`, kept out of the LLM context, expandable to a
|
|
107
|
+
per-condition tuning sheet.
|
|
108
|
+
- Network uploads of local data and reads of credential material are gated: a `curl -d @file`
|
|
109
|
+
that matched nothing ran with no judgment before this.
|
|
110
|
+
- `pi install` from npm or git; tagged `pi-package` for the package gallery.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 jomatsu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# pi-jev-auto-mode
|
|
2
|
+
|
|
3
|
+
Auto mode for the [Pi coding agent](https://github.com/earendil-works/pi-mono) backed by
|
|
4
|
+
**Jev** (TypeSafe System One, a decision-only model). Pi has no built-in permission system,
|
|
5
|
+
so a gate either exists as an extension or it does not exist at all. This one judges
|
|
6
|
+
`bash`, `write`, and `edit` tool calls semantically and **fails closed** whenever a decision
|
|
7
|
+
cannot be made.
|
|
8
|
+
|
|
9
|
+
> **Status: milestones 1–3 are complete.** The deterministic envelope, the Jev engine,
|
|
10
|
+
> real-API calibration, settings, policy notes, per-rule threshold tuning, and decision
|
|
11
|
+
> records are implemented and tested (171 tests, no network). See [`docs/design.md`](./docs/design.md) for
|
|
12
|
+
> the roadmap and [`docs/calibration.md`](./docs/calibration.md) for the measured
|
|
13
|
+
> probabilities behind every threshold.
|
|
14
|
+
|
|
15
|
+
## What it does
|
|
16
|
+
|
|
17
|
+
The gate has two layers, in this order:
|
|
18
|
+
|
|
19
|
+
1. **Deterministic policy** — hard-deny commands, your allow/deny patterns, dangerous-command
|
|
20
|
+
detection, protected paths. Hard-deny is never handed to the semantic layer.
|
|
21
|
+
2. **Semantic judgment (Jev)** — only the calls the first layer escalated.
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
hard-deny → block (never reaches Jev)
|
|
25
|
+
your deny pattern → block
|
|
26
|
+
your allow pattern → allow (recorded)
|
|
27
|
+
your safeCommands → run, no record
|
|
28
|
+
dangerous pattern match → Jev (even when the command looks read-only)
|
|
29
|
+
read-only builtin → run, no record
|
|
30
|
+
in-project write/edit → run, no record
|
|
31
|
+
everything else → Jev: allow · block · block-if-undecidable
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**`gateScope` decides how far the semantic layer reaches, and the default is `all`.** A denylist
|
|
35
|
+
can only recognise the shapes someone wrote a pattern for first: a command that uploaded a file
|
|
36
|
+
(`curl -d @~/.ssh/id_ed25519 ...`) matched nothing and ran with no judgment at all. Under `all`
|
|
37
|
+
the deterministic layer names what it can vouch for — read-only inspection, your declared safe
|
|
38
|
+
commands, a write inside the project to an unprotected path — and everything else is judged.
|
|
39
|
+
`matched` restores the old pattern-only behaviour. `/jev-auto-mode scope all|matched` changes it.
|
|
40
|
+
|
|
41
|
+
The trade is latency: a judged call costs roughly half a second (measured 193–642 ms across
|
|
42
|
+
eleven ordinary commands), while a fast-path call costs nothing. Read-only inspection is
|
|
43
|
+
therefore a real allowlist rather than a convenience.
|
|
44
|
+
|
|
45
|
+
An auto mode that stops for ordinary work has no reason to exist, so **the intent question is
|
|
46
|
+
asked only about commands the deterministic layer recognised as a dangerous shape, and only a
|
|
47
|
+
clear "this was not requested" blocks**. Measured: an unrequested `mv`, `cp`, `tar`, `chmod +x`,
|
|
48
|
+
or `node -e` is judged and allowed, while an unrequested `git reset --hard`, `npm publish`,
|
|
49
|
+
`rm -rf`, or `sudo` is blocked.
|
|
50
|
+
|
|
51
|
+
`rm -rf build` inside the repository is recognized as a scoped local deletion. A write to
|
|
52
|
+
`.env`, `.git/`, `~/.ssh`, `.pi/`, `.github/workflows/`, or `AGENTS.md` is escalated even when
|
|
53
|
+
the path is inside the working directory.
|
|
54
|
+
|
|
55
|
+
### How Jev decides
|
|
56
|
+
|
|
57
|
+
Conditions are phrased so the safe state is "yes", and each one is classified by
|
|
58
|
+
`mode` and `severity`:
|
|
59
|
+
|
|
60
|
+
| | meaning |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `mode: required` | must be satisfied; the middle band escalates to a confirmation |
|
|
63
|
+
| `mode: hazard` | only a clear negative matters; the middle band is ignored |
|
|
64
|
+
| `severity: hazard` | a clear rejection always blocks |
|
|
65
|
+
| `severity: soft` | a clear rejection is cleared when the user's own request covers the call |
|
|
66
|
+
|
|
67
|
+
So `intent_coverage` ("is this what the user asked for?") is the permission question, and
|
|
68
|
+
questions like "is a secret being sent to a network endpoint" are hazard detectors that only
|
|
69
|
+
block when they are sure. Posting a private key is never cleared by intent; force-pushing a
|
|
70
|
+
feature branch the user asked for is.
|
|
71
|
+
|
|
72
|
+
**Nothing is delegated to the user by default.** The middle band — where Jev is neither
|
|
73
|
+
satisfied nor rejecting — resolves to a block, so Jev's probability is the whole answer and the
|
|
74
|
+
gate never takes over the screen. `/jev-auto-mode uncertain ask` restores the confirmation
|
|
75
|
+
dialog if you want it; `allow` trusts the band. Everything else that cannot be decided — no
|
|
76
|
+
engine, timeout, malformed response, cancellation — also blocks.
|
|
77
|
+
|
|
78
|
+
## Install
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
pi install npm:pi-jev-auto-mode
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Or straight from the repository, which needs no npm account:
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
pi install git:github.com/jomatsu/pi-jev-auto-mode
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Try it without installing:
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
pi -e npm:pi-jev-auto-mode
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Packages are discovered in the [package gallery](https://pi.dev/packages) through the
|
|
97
|
+
`pi-package` keyword.
|
|
98
|
+
|
|
99
|
+
## Usage
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
/jev-auto-mode show status (settings + where the API key comes from)
|
|
103
|
+
/jev-auto-mode on|off toggle auto mode
|
|
104
|
+
/jev-auto-mode login store a TypeSafe API key (verified, then saved 0600)
|
|
105
|
+
/jev-auto-mode logout remove the stored key
|
|
106
|
+
/jev-auto-mode policy list the policy notes
|
|
107
|
+
/jev-auto-mode policy edit
|
|
108
|
+
/jev-auto-mode policy clear
|
|
109
|
+
/jev-auto-mode threshold show thresholds and the last observed probability per rule
|
|
110
|
+
/jev-auto-mode threshold <rule> <0.5-1> set one threshold
|
|
111
|
+
/jev-auto-mode threshold edit pick a rule and type a value
|
|
112
|
+
/jev-auto-mode threshold reset [rule] restore the calibrated default
|
|
113
|
+
/jev-auto-mode uncertain show what the middle band resolves to
|
|
114
|
+
/jev-auto-mode uncertain deny|ask|allow
|
|
115
|
+
/jev-auto-mode scope all|matched how far the semantic layer reaches
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
pi --jev-auto-mode start with auto mode enabled
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The semantic layer needs a [TypeSafe](https://typesafe.ai/) API key. Jev is early access, so an
|
|
123
|
+
account may be waitlisted; **the gate still works without one**. Its own rules keep running —
|
|
124
|
+
read-only and user-declared safe commands pass, hard-deny shapes are blocked — but a call
|
|
125
|
+
nothing vouches for is blocked with "Not connected to Jev" instead of being judged.
|
|
126
|
+
|
|
127
|
+
`/jev-auto-mode login` asks for the key, verifies it against the API (`GET /v1/models`), and
|
|
128
|
+
stores it as an owner-only file at
|
|
129
|
+
`$PI_CODING_AGENT_DIR/secrets/jev-auto-mode-typesafe-api-key` (mode `0600`) — the same place Pi
|
|
130
|
+
keeps its own credentials, so it is never committed with a project. `TYPESAFE_API_KEY` takes
|
|
131
|
+
precedence when set, so a one-off or CI override needs no login. `TYPESAFE_DEFAULT_MODEL`
|
|
132
|
+
selects the model (default `jev-latest`).
|
|
133
|
+
|
|
134
|
+
A key is only stored after the API accepts it: a typo that got saved would turn into a gate
|
|
135
|
+
that silently blocks every escalated call. If the API cannot be reached the key is not stored
|
|
136
|
+
either, and the command says so rather than claiming success.
|
|
137
|
+
|
|
138
|
+
Without a key the gate does not disable itself and does not allow everything: it stops the calls
|
|
139
|
+
it cannot judge and says it is not connected to Jev. The footer shows `🛡 jev (<scope>)` while the
|
|
140
|
+
semantic layer is active and `🛡 jev no key (<scope>)` when it is not.
|
|
141
|
+
|
|
142
|
+
## Tuning
|
|
143
|
+
|
|
144
|
+
The thresholds are a starting point measured on twelve fixtures, not a truth
|
|
145
|
+
([`docs/calibration.md`](./docs/calibration.md)). To retune them from your own work:
|
|
146
|
+
|
|
147
|
+
1. Run the thing you care about. The gate records every judgment; expand the record in the
|
|
148
|
+
transcript and read the per-condition table:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
intent_coverage p=0.97 pass (t=0.80, >= 0.80)
|
|
152
|
+
no_outward_effect p=0.06 reject (t=0.90, <= 0.10) <- decided (cleared by the user's request)
|
|
153
|
+
local_scope p=0.81 pass (t=0.90, >= 0.90)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
2. If a condition that should have passed lands in the middle band, lower its threshold. If
|
|
157
|
+
something got through that should not have, raise it. `/jev-auto-mode threshold` shows the
|
|
158
|
+
current value next to the last probability the model returned for that rule.
|
|
159
|
+
|
|
160
|
+
3. `/jev-auto-mode threshold <rule> <value>` writes the override. It takes effect immediately
|
|
161
|
+
and persists in the global settings file.
|
|
162
|
+
|
|
163
|
+
A threshold must leave a middle band on both sides (`0.5 < t <= 1`): `t` is the probability
|
|
164
|
+
required to count as satisfied, and `1 - t` is the probability at or below which the condition
|
|
165
|
+
counts as violated. Values that close one side are rejected.
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"thresholds": {
|
|
170
|
+
"intent_coverage": 0.6,
|
|
171
|
+
"no_secret_egress": 0.995
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The right fix is usually to phrase the condition better, not to move the threshold. If "should
|
|
177
|
+
pass" and "should reject" answers overlap, the question is ambiguous.
|
|
178
|
+
|
|
179
|
+
## Configuration
|
|
180
|
+
|
|
181
|
+
Global settings: `$PI_CODING_AGENT_DIR/jev-auto-mode.json` (default `~/.pi/agent/`).
|
|
182
|
+
Project override: `<cwd>/.pi/jev-auto-mode.json`, honored **only for a trusted project** —
|
|
183
|
+
an untrusted checkout must not be able to loosen the gate that is judging it.
|
|
184
|
+
Policy notes: `$PI_CODING_AGENT_DIR/jev-auto-mode-policy.md`.
|
|
185
|
+
|
|
186
|
+
```json
|
|
187
|
+
{
|
|
188
|
+
"enabled": true,
|
|
189
|
+
"timeoutMs": 4000,
|
|
190
|
+
"maxRetries": 1,
|
|
191
|
+
"safeCommands": ["uv run pytest*", "pnpm run typecheck*"],
|
|
192
|
+
"allowedCommands": ["rm -rf build*"],
|
|
193
|
+
"disallowedCommands": ["npm publish*"],
|
|
194
|
+
"extraProtectedPaths": [],
|
|
195
|
+
"maxStateCharacters": 120000,
|
|
196
|
+
"uncertain": "deny",
|
|
197
|
+
"gateScope": "all",
|
|
198
|
+
"thresholds": {}
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
- Allow patterns never match a command containing shell control syntax (`;`, `&&`, `|`,
|
|
203
|
+
redirection, substitution), so `ls*` cannot approve `ls && rm -rf /`.
|
|
204
|
+
- Malformed values are dropped rather than defaulted, so a broken project file cannot pin a
|
|
205
|
+
value that overrides the global layer.
|
|
206
|
+
- There are two ways to widen the fast path, with different meanings:
|
|
207
|
+
|
|
208
|
+
| Setting | Effect |
|
|
209
|
+
|---|---|
|
|
210
|
+
| `safeCommands` | Run without a decision record. For commands that are safe *on your machine*: `uv run pytest*`, `npm run test*`, `cargo test*`, `go test ./...*` |
|
|
211
|
+
| `allowedCommands` | Override a dangerous-pattern match. The override is recorded, so approving `rm -rf build` by rule is visible |
|
|
212
|
+
|
|
213
|
+
The built-in safe list is not configurable and holds read-only inspection only (`git
|
|
214
|
+
status`/`diff`/`log`/`show`/`branch`, `ls`, `pwd`, `rg`, `grep`). It deliberately contains no
|
|
215
|
+
command that executes project code: a test runner runs repository code, so declaring it safe
|
|
216
|
+
is a decision for the machine that owns it, not a default shipped to everyone. Allow patterns
|
|
217
|
+
never match a command containing shell control syntax, so `ls*` cannot approve `ls && rm -rf /`.
|
|
218
|
+
|
|
219
|
+
## What leaves the machine
|
|
220
|
+
|
|
221
|
+
An escalated call sends the following to TypeSafe's API (`api.typesafe.ai`):
|
|
222
|
+
|
|
223
|
+
- the tool name and the bash command text (truncated),
|
|
224
|
+
- for `write` / `edit`: the target **path** — never the file contents or the diff,
|
|
225
|
+
- the working directory, the matched policy reason names,
|
|
226
|
+
- recent **user** messages (bounded), and your policy notes.
|
|
227
|
+
|
|
228
|
+
Obvious credentials (`*_KEY=…`, `Bearer …`, JWTs, `sk-…`, `ghp_…`, PEM private keys) are
|
|
229
|
+
redacted on the way out. Assistant output, tool output, and file contents are never sent.
|
|
230
|
+
Details and the failure-mode table: [`docs/security.md`](./docs/security.md).
|
|
231
|
+
|
|
232
|
+
## Releasing
|
|
233
|
+
|
|
234
|
+
A version, a tag, and a release are cut **once, when the version is published**, so the tag list
|
|
235
|
+
matches what people can install. Unfinished work accumulates under `## Unreleased` in
|
|
236
|
+
`CHANGELOG.md` and is renamed to the version at release time:
|
|
237
|
+
|
|
238
|
+
1. `npm run check`
|
|
239
|
+
2. rename `## Unreleased` to `## <version>` in `CHANGELOG.md`, bump `version` in `package.json`
|
|
240
|
+
3. commit, `git tag -a v<version>`, `git push --follow-tags`
|
|
241
|
+
4. `gh release create v<version> --notes-file <(the changelog section)`
|
|
242
|
+
5. `npm publish`
|
|
243
|
+
|
|
244
|
+
## Development
|
|
245
|
+
|
|
246
|
+
```sh
|
|
247
|
+
npm install
|
|
248
|
+
npm test # node:test, no network
|
|
249
|
+
npm run typecheck
|
|
250
|
+
node --experimental-strip-types scripts/calibrate.ts # real API, needs TYPESAFE_API_KEY
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Layout:
|
|
254
|
+
|
|
255
|
+
| Path | Responsibility |
|
|
256
|
+
|---|---|
|
|
257
|
+
| `src/policy.ts` | hard-deny, user rules, dangerous patterns, protected paths, safe commands |
|
|
258
|
+
| `src/call.ts` | `tool_call` → judgment state (redaction, truncation, path classification) |
|
|
259
|
+
| `src/intent.ts` | recent user-authored intent only |
|
|
260
|
+
| `src/decide.ts` | the decision-engine seam (`DecisionEngine`) |
|
|
261
|
+
| `src/jev/questions.ts` | the condition set, modes, severities, thresholds |
|
|
262
|
+
| `src/jev/availability.ts` | where the API key comes from (env or stored secret) |
|
|
263
|
+
| `src/jev/decide.ts` | probability → condition verdict → decision |
|
|
264
|
+
| `src/jev/engine.ts` | one request per call, budget guard, calibration hook |
|
|
265
|
+
| `src/jev/transport.ts` | the SDK, wrapped so failures become decisions |
|
|
266
|
+
| `src/jev/response.ts` | response re-validation (a 200 is not an answer) |
|
|
267
|
+
| `src/settings.ts` | global/project settings, policy notes, and the stored API key |
|
|
268
|
+
| `src/records.ts` | `appendEntry` records and their renderer |
|
|
269
|
+
| `src/ui.ts` | footer status and user-facing text |
|
|
270
|
+
| `src/extension.ts` | `tool_call` orchestration and command wiring |
|
|
271
|
+
|
|
272
|
+
## Acknowledgements
|
|
273
|
+
|
|
274
|
+
The deterministic pattern catalogue is adapted from
|
|
275
|
+
[`@nilskluewer/pi-auto-permission-gate`](https://github.com/nilskluewer/pi-auto-permission-gate)
|
|
276
|
+
(MIT), and the three-layer structure (fast paths → hard rules → classifier) follows the same
|
|
277
|
+
extension and Qwen Code's Auto Mode. The Jev design constraints (fail closed, two-sided
|
|
278
|
+
thresholds that keep the middle band meaningful, one request per judgment) come from measuring
|
|
279
|
+
the API directly — [`docs/calibration.md`](./docs/calibration.md) records the measurements and
|
|
280
|
+
the reasoning. Nothing here depends on a wrapper library: the Jev layer is written against the
|
|
281
|
+
official SDK.
|
|
282
|
+
|
|
283
|
+
## License
|
|
284
|
+
|
|
285
|
+
MIT
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
This extension stands between a coding agent and your shell, so its own failure modes matter.
|
|
4
|
+
|
|
5
|
+
## Reporting
|
|
6
|
+
|
|
7
|
+
Open a private security advisory on the repository, or an issue if the report does not need to
|
|
8
|
+
stay private. Please include the tool call, the recorded decision (`/jev-auto-mode` records
|
|
9
|
+
are expandable in the transcript), and the Jev probabilities if you have them.
|
|
10
|
+
|
|
11
|
+
## What is in scope
|
|
12
|
+
|
|
13
|
+
- A call that should have been blocked and was not.
|
|
14
|
+
- A call that was approved without the semantic layer being consulted.
|
|
15
|
+
- Credential or file content leaving the machine inside a judgment request.
|
|
16
|
+
- A stored API key readable by another user, or written somewhere other than
|
|
17
|
+
`<agentDir>/secrets/` with mode `0600`.
|
|
18
|
+
|
|
19
|
+
## Design summary
|
|
20
|
+
|
|
21
|
+
The gate is two layers and the order is the enforcement:
|
|
22
|
+
|
|
23
|
+
1. A deterministic envelope (hard-deny, user rules, dangerous-command patterns, protected
|
|
24
|
+
paths). Hard-deny returns before the semantic layer is constructed, so a probabilistic
|
|
25
|
+
verdict can never resurrect it.
|
|
26
|
+
2. Jev, which only sees calls the first layer escalated.
|
|
27
|
+
|
|
28
|
+
Anything that cannot be decided — no key, timeout, malformed response, a response missing an
|
|
29
|
+
answer, cancellation, an oversized request — blocks. See
|
|
30
|
+
[docs/security.md](./docs/security.md) for the failure-mode table, what is sent to the API, and
|
|
31
|
+
the known limits (including the `soft`-severity clearing, which is the mechanism to remove first
|
|
32
|
+
if a wrong approval ever appears).
|