pi-jev-guard 0.3.0 → 0.3.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/README.md +7 -1
- package/extensions/index.ts +106 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -116,10 +116,16 @@ il guarded nelle sessioni senza uno stato Jev salvato.
|
|
|
116
116
|
Un hold `[JEV — OUTPUT NON PUBBLICATO]` interrompe intenzionalmente il turno:
|
|
117
117
|
controlla `/jev last`. Non viene aggirato con un proseguimento automatico.
|
|
118
118
|
|
|
119
|
-
Verdetto **incerto** (`review`): il gate tenta prima una
|
|
119
|
+
Verdetto **incerto** (`review`): il gate tenta prima una riparazione privata
|
|
120
120
|
(`automatic.repairOnReview`, default `true`), poi applica la policy configurata:
|
|
121
121
|
`policy.onUncertain` e `policy.onUnavailable` (`"hold"` default, oppure `"pass"`).
|
|
122
122
|
Con `"pass"` il testo viene pubblicato con una nota di trasparenza sul verdetto.
|
|
123
|
+
|
|
124
|
+
Se un'altra estensione ri-registra lo stesso provider (caso reale: `pi-accounts`
|
|
125
|
+
al cambio account), l'overlay viene ricostruito sopra il provider live prima di
|
|
126
|
+
ogni turno; in più il gate toglie il suffisso `__jev` da qualunque payload,
|
|
127
|
+
registrando `jev-gate-bypass` e avvisando in TUI, perché quel turno non è
|
|
128
|
+
passato dalla verifica.
|
|
123
129
|
Le conferme per comandi di rete sospetti attendono invece una decisione esplicita;
|
|
124
130
|
la normale sequenza tool → risposta successiva continua senza un nuovo prompt.
|
|
125
131
|
|
package/extensions/index.ts
CHANGED
|
@@ -96,8 +96,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
96
96
|
// Twin attivi, uno o più per provider. Nessun overlay finché la config non
|
|
97
97
|
// li attiva (automatic) o l'utente non usa /jev upstream (opt-in).
|
|
98
98
|
interface OverlayState {
|
|
99
|
+
/** Provider live catturato prima dell'override (auth inclusa). */
|
|
99
100
|
upstream: Provider;
|
|
100
101
|
twinIds: string[];
|
|
102
|
+
/** Il nostro overlay registrato, per accorgersi se viene scalzato. */
|
|
103
|
+
provider: Provider;
|
|
104
|
+
/** Ripiego dopo un tentativo di ri-registrazione non riuscito. */
|
|
105
|
+
reassertFailed?: boolean;
|
|
101
106
|
}
|
|
102
107
|
const overlays = new Map<string, OverlayState>();
|
|
103
108
|
|
|
@@ -208,7 +213,46 @@ export default function (pi: ExtensionAPI) {
|
|
|
208
213
|
onVerdict: recordGateVerdict,
|
|
209
214
|
});
|
|
210
215
|
pi.registerProvider(overlay);
|
|
211
|
-
overlays.set(providerId, {
|
|
216
|
+
overlays.set(providerId, {
|
|
217
|
+
upstream,
|
|
218
|
+
twinIds: [...twinIds],
|
|
219
|
+
provider: overlay,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Un'altra estensione (es. pi-accounts) può ri-registrare lo stesso provider
|
|
225
|
+
* id, sostituendo il nostro overlay con un provider che conserva i modelli
|
|
226
|
+
* (twin compresi) ma non il gate: l'id `__jev` finirebbe all'API.
|
|
227
|
+
* Qui riapplichiamo l'overlay sopra il provider live, preservandone auth e
|
|
228
|
+
* config. Se la ri-registrazione non regge, si smette di ritentare.
|
|
229
|
+
*/
|
|
230
|
+
function reassertOverlays(registry: ModelRegistry): string[] {
|
|
231
|
+
const fixed: string[] = [];
|
|
232
|
+
for (const [providerId, state] of [...overlays]) {
|
|
233
|
+
if (state.reassertFailed) continue;
|
|
234
|
+
let live: Provider | undefined;
|
|
235
|
+
try {
|
|
236
|
+
live = registry.getProvider(providerId);
|
|
237
|
+
} catch {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (!live || live === state.provider) continue;
|
|
241
|
+
try {
|
|
242
|
+
registerOverlay(providerId, live, state.twinIds);
|
|
243
|
+
const applied = overlays.get(providerId);
|
|
244
|
+
if (applied && registry.getProvider(providerId) === applied.provider) {
|
|
245
|
+
fixed.push(providerId);
|
|
246
|
+
} else if (applied) {
|
|
247
|
+
// Il registry restituisce un altro oggetto: non insistere.
|
|
248
|
+
applied.reassertFailed = true;
|
|
249
|
+
}
|
|
250
|
+
} catch {
|
|
251
|
+
const current = overlays.get(providerId);
|
|
252
|
+
if (current) current.reassertFailed = true;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return fixed;
|
|
212
256
|
}
|
|
213
257
|
|
|
214
258
|
/**
|
|
@@ -513,8 +557,62 @@ export default function (pi: ExtensionAPI) {
|
|
|
513
557
|
});
|
|
514
558
|
pi.on("session_tree", async (_event, ctx) => { await startSession(ctx); });
|
|
515
559
|
|
|
516
|
-
//
|
|
517
|
-
//
|
|
560
|
+
// Prima di ogni turno: se un'altra estensione ci ha scalzati dal provider,
|
|
561
|
+
// rimettiamo l'overlay (altrimenti l'id __jev arriverebbe all'API).
|
|
562
|
+
function noteReassert(fixed: string[]): void {
|
|
563
|
+
if (fixed.length === 0) return;
|
|
564
|
+
try {
|
|
565
|
+
pi.appendEntry("jev-overlay-reassert", { providers: fixed });
|
|
566
|
+
} catch {
|
|
567
|
+
// solo diagnostica
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
pi.on("before_agent_start", async (_event, ctx) => {
|
|
572
|
+
try {
|
|
573
|
+
noteReassert(reassertOverlays(ctx.modelRegistry));
|
|
574
|
+
} catch {
|
|
575
|
+
// mai bloccare il turno
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
// Ultima rete: se per qualunque motivo il payload esce con l'id sintetico,
|
|
580
|
+
// togliamo il suffisso (il modello originale è quello giusto) e registriamo
|
|
581
|
+
// che quel turno NON è passato dal gate.
|
|
582
|
+
let lastBypassNoticeAt = 0;
|
|
583
|
+
pi.on("before_provider_request", async (event, ctx) => {
|
|
584
|
+
try {
|
|
585
|
+
const payload = event.payload as { model?: unknown } | undefined;
|
|
586
|
+
if (!payload || typeof payload.model !== "string" || !isTwinId(payload.model)) {
|
|
587
|
+
return undefined;
|
|
588
|
+
}
|
|
589
|
+
const requested = payload.model;
|
|
590
|
+
payload.model = untwinId(requested);
|
|
591
|
+
try {
|
|
592
|
+
pi.appendEntry("jev-gate-bypass", {
|
|
593
|
+
requested,
|
|
594
|
+
sent: payload.model,
|
|
595
|
+
provider: ctx.model?.provider,
|
|
596
|
+
});
|
|
597
|
+
} catch {
|
|
598
|
+
// solo diagnostica
|
|
599
|
+
}
|
|
600
|
+
const now = Date.now();
|
|
601
|
+
if (ctx.hasUI && now - lastBypassNoticeAt >= 60_000) {
|
|
602
|
+
lastBypassNoticeAt = now;
|
|
603
|
+
ctx.ui.notify(
|
|
604
|
+
`Jev: richiesta non passata dal gate (${requested} → ${payload.model}). Verifica disattivata per questo turno; riprova per riattivarla.`,
|
|
605
|
+
"warning",
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
return payload;
|
|
609
|
+
} catch {
|
|
610
|
+
return undefined;
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
// Normalizza gli alias persistiti anche dopo /jev off (overlay rimossi).
|
|
615
|
+
// L'evento context è una copia: la cronologia salvata resta invariata.
|
|
518
616
|
pi.on("context", async (event) => ({
|
|
519
617
|
messages: event.messages.map((message) =>
|
|
520
618
|
message.role === "assistant" && isTwinId(message.model)
|
|
@@ -526,6 +624,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
526
624
|
// su un modello non guarded (revert immediato al twin).
|
|
527
625
|
pi.on("model_select", async (event, ctx) => {
|
|
528
626
|
try {
|
|
627
|
+
// Una selezione account/provider può aver rimpiazzato il nostro overlay.
|
|
628
|
+
noteReassert(reassertOverlays(ctx.modelRegistry));
|
|
529
629
|
if (sessionMode !== "automatic" || !config.automatic.requireGuardedModel) return;
|
|
530
630
|
if (overlays.size === 0) return;
|
|
531
631
|
if (isGuardedModel(event.model.provider, event.model.id)) return;
|
|
@@ -768,6 +868,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
768
868
|
pi.on("message_end", async (event, ctx) => {
|
|
769
869
|
try {
|
|
770
870
|
if (ctx.hasUI) ctx.ui.setStatus("jev", undefined);
|
|
871
|
+
// Dopo la richiesta: se qualcuno ci ha scalzati, rimetti l'overlay
|
|
872
|
+
// per il turno successivo (il payload resta già normalizzato).
|
|
873
|
+
noteReassert(reassertOverlays(ctx.modelRegistry));
|
|
771
874
|
if (sessionMode !== "automatic") return;
|
|
772
875
|
if (event.message.role !== "assistant") return;
|
|
773
876
|
|
package/package.json
CHANGED