feat(ai-judge): show dialog advice widget with verdict and focus

- add AdvicePresenter rendering a pi-native setWidget panel while a
  permission dialog is up: judgment verdict with reason, high-risk skip
  with category, or an unavailable cause
- highlight the decision-relevant command fragment via a focus cascade:
  high-risk match, triggering unit, then executed unit
- clear the widget when permissions:decision resolves the same request
- add dialogAdvice config key (default true, invalid falls back with a
  diagnostic) and cover formatting, lifecycle, and config in tests
This commit is contained in:
2026-09-17 23:42:57 +08:00
parent 77ef5483ed
commit 7190bffd86
7 changed files with 592 additions and 7 deletions
@@ -25,6 +25,7 @@ describe("loadJudgeConfig — missing and malformed", () => {
timeoutMs: 15_000,
timeoutCohort: "default",
judgeModel: undefined,
dialogAdvice: true,
diagnostics: [
expect.objectContaining({ key: "file", fallback: "all defaults" }),
],
@@ -47,6 +48,42 @@ describe("loadJudgeConfig — missing and malformed", () => {
});
});
describe("loadJudgeConfig — dialogAdvice", () => {
it("defaults to true when absent", () => {
const config = loadJudgeConfig(
deps({ [CONFIG_PATH]: JSON.stringify({ version: 2 }) }),
);
expect(config.dialogAdvice).toBe(true);
expect(config.diagnostics).toEqual([]);
});
it("accepts an explicit false", () => {
const config = loadJudgeConfig(
deps({
[CONFIG_PATH]: JSON.stringify({ version: 2, dialogAdvice: false }),
}),
);
expect(config.dialogAdvice).toBe(false);
expect(config.diagnostics).toEqual([]);
});
it("falls back to true with a diagnostic on a non-boolean", () => {
const config = loadJudgeConfig(
deps({
[CONFIG_PATH]: JSON.stringify({ version: 2, dialogAdvice: "yes" }),
}),
);
expect(config.dialogAdvice).toBe(true);
expect(config.diagnostics).toEqual([
{
key: "dialogAdvice",
problem: 'not a boolean: "yes"',
fallback: "true",
},
]);
});
});
describe("loadJudgeConfig — version selection", () => {
it("treats a missing version as v1", () => {
const config = loadJudgeConfig(deps({ [CONFIG_PATH]: '{"mode":"shadow"}' }));
@@ -246,6 +283,7 @@ describe("loadJudgeConfig — snapshot immutability", () => {
timeoutMs: 20_000,
timeoutCohort: 20_000,
judgeModel: { provider: "p", id: "m" },
dialogAdvice: true,
diagnostics: [],
});
});