pi-advisor-flow 0.6.0 → 0.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-advisor-flow",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Advanced Executor/Advisor flow for Pi, fully configurable and extendable.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -8,7 +8,8 @@
8
8
  "pi-coding-agent",
9
9
  "advisor",
10
10
  "pi-advisor",
11
- "herdr"
11
+ "herdr",
12
+ "jev"
12
13
  ],
13
14
  "homepage": "https://github.com/philipbrembeck/pi-advisor",
14
15
  "repository": {
@@ -62,18 +63,19 @@
62
63
  "undici": "8.9.0"
63
64
  },
64
65
  "devDependencies": {
65
- "@biomejs/biome": "2.5.12",
66
+ "@biomejs/biome": "2.5.13",
66
67
  "@earendil-works/pi-ai": "^0.85.1",
67
68
  "@earendil-works/pi-coding-agent": "^0.85.1",
68
69
  "@earendil-works/pi-tui": "^0.85.1",
69
- "@types/node": "^26.4.1",
70
- "bun-types": "1.4.1",
70
+ "@types/node": "^26.6.2",
71
+ "@typesafe-ai/sdk": "^0.6.0",
72
+ "bun-types": "1.4.2",
71
73
  "husky": "^9.1.7",
72
- "knip": "^6.35.1",
73
- "lint-staged": "^17.4.1",
74
- "typebox": "^1.3.25",
74
+ "knip": "^6.37.0",
75
+ "lint-staged": "^17.5.1",
76
+ "typebox": "^1.3.34",
75
77
  "typescript": "^7.0.2",
76
- "ultracite": "7.10.8"
78
+ "ultracite": "7.12.0"
77
79
  },
78
80
  "peerDependencies": {
79
81
  "@earendil-works/pi-ai": "^0.84.1 || ^0.85.1",
@@ -2,6 +2,7 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import { advisorRef } from "../config/state.ts";
3
3
  import type { GitContextLevel } from "../git.ts";
4
4
  import { herdrAdvisorActivity, notifyHerdrAdvisorFailure } from "../herdr.ts";
5
+ import { normalizeScreeningQuestion } from "../tools/jev-filter.ts";
5
6
  import { appendScoutLifecycleEntry } from "../tools/scout-status.ts";
6
7
  import type { ScoutToolDetails } from "../tools/types.ts";
7
8
  import { advisorUsageCost, snapshotAdvisorUsage } from "../usage.ts";
@@ -63,11 +64,12 @@ export const startManualConsultation = (
63
64
  },
64
65
  gitContext
65
66
  )
66
- .then(({ markdown, usage }) => {
67
+ .then(({ adviceId, markdown, usage }) => {
67
68
  if (controller.signal.aborted) {
68
69
  return;
69
70
  }
70
71
  progress.phase = "complete";
72
+ runtime.advisorSessionState.resetTurnsSinceConsultation();
71
73
  runtime.advisorSessionState.recordInvocation({
72
74
  cost: advisorUsageCost(usage),
73
75
  executionEffect: "continued",
@@ -76,11 +78,22 @@ export const startManualConsultation = (
76
78
  trigger: "manual",
77
79
  usage,
78
80
  });
81
+ if (typeof adviceId === "string") {
82
+ // Registering the manual question lets an identical later ask_advisor
83
+ // hit repeat detection instead of re-consulting.
84
+ runtime.advisorSessionState.issueAdvice(
85
+ adviceId,
86
+ markdown,
87
+ "manual",
88
+ false,
89
+ normalizeScreeningQuestion(question)
90
+ );
91
+ }
79
92
  runtime.updateAdvisorUsageStatus(ctx);
80
93
  const normalizedUsage = snapshotAdvisorUsage(usage);
81
94
  runtime.pi.sendMessage(
82
95
  {
83
- content: `Manual Advisor consultation${question ? ` (${question})` : ""}:\n\n${markdown}`,
96
+ content: `Manual Advisor consultation${question ? ` (${question})` : ""} — for your awareness; no action or follow-up consultation is needed unless the user asks:\n\n${markdown}`,
84
97
  customType: "advisor-manual-result",
85
98
  details: {
86
99
  advisor: advisorRef,
@@ -104,6 +117,7 @@ export const startManualConsultation = (
104
117
  }
105
118
  progress.phase = "error";
106
119
  const message = error instanceof Error ? error.message : String(error);
120
+ runtime.advisorSessionState.resetTurnsSinceConsultation();
107
121
  runtime.advisorSessionState.recordInvocation({
108
122
  executionEffect: "continued",
109
123
  failure: "provider-error",
@@ -12,6 +12,17 @@ import {
12
12
  setAdvisorGitContextMaxCharsRef,
13
13
  setAdvisorGitContextRef,
14
14
  setAdvisorHerdrIntegrationRef,
15
+ setAdvisorJevDigestMaxCharsRef,
16
+ setAdvisorJevFilterEnabledRef,
17
+ setAdvisorJevFilterNoulMarginRef,
18
+ setAdvisorJevFilterOverrideWindowRef,
19
+ setAdvisorJevFilterSkipConfidenceRef,
20
+ setAdvisorJevModelRef,
21
+ setAdvisorJevPricePerMtokRef,
22
+ setAdvisorJevTimeoutMsRef,
23
+ setAdvisorJevTransportRef,
24
+ setAdvisorJevTurnGateEveryTurnsRef,
25
+ setAdvisorJevTurnGateNoulThresholdRef,
15
26
  setAdvisorLoopThresholdRef,
16
27
  setAdvisorMaxCallsPerSessionRef,
17
28
  setAdvisorOutcomeLoggingRef,
@@ -31,6 +42,18 @@ import {
31
42
  setSimpleModeRef,
32
43
  } from "../config/state.ts";
33
44
  import { saveConfig, saveGlobalOutcomeLogging } from "../config/storage.ts";
45
+ import {
46
+ DEFAULT_JEV_DIGEST_MAX_CHARS,
47
+ DEFAULT_JEV_FILTER_NOUL_MARGIN,
48
+ DEFAULT_JEV_FILTER_OVERRIDE_WINDOW,
49
+ DEFAULT_JEV_FILTER_SKIP_CONFIDENCE,
50
+ DEFAULT_JEV_MODEL,
51
+ DEFAULT_JEV_PRICE_PER_MTOK,
52
+ DEFAULT_JEV_TIMEOUT_MS,
53
+ DEFAULT_JEV_TRANSPORT,
54
+ DEFAULT_JEV_TURN_GATE_EVERY_TURNS,
55
+ DEFAULT_JEV_TURN_GATE_NOUL_THRESHOLD,
56
+ } from "../config/types.ts";
34
57
  import type { AdvisorSettings } from "../ui/types.ts";
35
58
 
36
59
  // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: one settings form maps every persisted control.
@@ -56,6 +79,31 @@ const applyAdvisorSettings = (settings: AdvisorSettings) => {
56
79
  setAlwaysOnRef(settings.alwaysOn ?? false);
57
80
  setAdvisorFailureModeRef(settings.failureMode ?? "block-session");
58
81
  setAdvisorHerdrIntegrationRef(settings.herdrIntegration ?? true);
82
+ setAdvisorJevFilterEnabledRef(settings.jevFilterEnabled ?? false);
83
+ setAdvisorJevFilterSkipConfidenceRef(
84
+ settings.jevFilterSkipConfidence ?? DEFAULT_JEV_FILTER_SKIP_CONFIDENCE
85
+ );
86
+ setAdvisorJevFilterNoulMarginRef(
87
+ settings.jevFilterNoulMargin ?? DEFAULT_JEV_FILTER_NOUL_MARGIN
88
+ );
89
+ setAdvisorJevFilterOverrideWindowRef(
90
+ settings.jevFilterOverrideWindow ?? DEFAULT_JEV_FILTER_OVERRIDE_WINDOW
91
+ );
92
+ setAdvisorJevModelRef(settings.jevModel ?? DEFAULT_JEV_MODEL);
93
+ setAdvisorJevTimeoutMsRef(settings.jevTimeoutMs ?? DEFAULT_JEV_TIMEOUT_MS);
94
+ setAdvisorJevDigestMaxCharsRef(
95
+ settings.jevDigestMaxChars ?? DEFAULT_JEV_DIGEST_MAX_CHARS
96
+ );
97
+ setAdvisorJevPricePerMtokRef(
98
+ settings.jevPricePerMtok ?? DEFAULT_JEV_PRICE_PER_MTOK
99
+ );
100
+ setAdvisorJevTransportRef(settings.jevTransport ?? DEFAULT_JEV_TRANSPORT);
101
+ setAdvisorJevTurnGateEveryTurnsRef(
102
+ settings.jevTurnGateEveryTurns ?? DEFAULT_JEV_TURN_GATE_EVERY_TURNS
103
+ );
104
+ setAdvisorJevTurnGateNoulThresholdRef(
105
+ settings.jevTurnGateNoulThreshold ?? DEFAULT_JEV_TURN_GATE_NOUL_THRESHOLD
106
+ );
59
107
  setAdvisorToolResultMaxLinesRef(settings.toolResultMaxLines ?? 2000);
60
108
  setAdvisorToolResultMaxBytesRef(settings.toolResultMaxBytes ?? 50 * 1024);
61
109
  setAdvisorRedactSecretsRef(settings.redactSecrets ?? false);
@@ -16,6 +16,7 @@ export type ManualConsult = (
16
16
  onScout?: (event: ScoutLifecycleEvent) => void,
17
17
  gitContext?: GitContextLevel
18
18
  ) => Promise<{
19
+ adviceId?: string;
19
20
  markdown: string;
20
21
  thinkingText: string;
21
22
  draftBytes?: number;
@@ -10,6 +10,17 @@ import {
10
10
  setAdvisorGitContextMaxCharsRef,
11
11
  setAdvisorGitContextRef,
12
12
  setAdvisorHerdrIntegrationRef,
13
+ setAdvisorJevDigestMaxCharsRef,
14
+ setAdvisorJevFilterEnabledRef,
15
+ setAdvisorJevFilterNoulMarginRef,
16
+ setAdvisorJevFilterOverrideWindowRef,
17
+ setAdvisorJevFilterSkipConfidenceRef,
18
+ setAdvisorJevModelRef,
19
+ setAdvisorJevPricePerMtokRef,
20
+ setAdvisorJevTimeoutMsRef,
21
+ setAdvisorJevTransportRef,
22
+ setAdvisorJevTurnGateEveryTurnsRef,
23
+ setAdvisorJevTurnGateNoulThresholdRef,
13
24
  setAdvisorLoopThresholdRef,
14
25
  setAdvisorMaxCallsPerSessionRef,
15
26
  setAdvisorOutcomeLoggingRef,
@@ -38,6 +49,16 @@ import {
38
49
  DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES,
39
50
  DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES,
40
51
  DEFAULT_CONTEXT_MAX_CHARS,
52
+ DEFAULT_JEV_DIGEST_MAX_CHARS,
53
+ DEFAULT_JEV_FILTER_NOUL_MARGIN,
54
+ DEFAULT_JEV_FILTER_OVERRIDE_WINDOW,
55
+ DEFAULT_JEV_FILTER_SKIP_CONFIDENCE,
56
+ DEFAULT_JEV_MODEL,
57
+ DEFAULT_JEV_PRICE_PER_MTOK,
58
+ DEFAULT_JEV_TIMEOUT_MS,
59
+ DEFAULT_JEV_TRANSPORT,
60
+ DEFAULT_JEV_TURN_GATE_EVERY_TURNS,
61
+ DEFAULT_JEV_TURN_GATE_NOUL_THRESHOLD,
41
62
  } from "./types.ts";
42
63
 
43
64
  export const resetDefaults = () => {
@@ -61,6 +82,17 @@ export const resetDefaults = () => {
61
82
  setAlwaysOnRef(false);
62
83
  setAdvisorFailureModeRef("block-session");
63
84
  setAdvisorHerdrIntegrationRef(true);
85
+ setAdvisorJevModelRef(DEFAULT_JEV_MODEL);
86
+ setAdvisorJevFilterEnabledRef(false);
87
+ setAdvisorJevFilterSkipConfidenceRef(DEFAULT_JEV_FILTER_SKIP_CONFIDENCE);
88
+ setAdvisorJevFilterNoulMarginRef(DEFAULT_JEV_FILTER_NOUL_MARGIN);
89
+ setAdvisorJevFilterOverrideWindowRef(DEFAULT_JEV_FILTER_OVERRIDE_WINDOW);
90
+ setAdvisorJevTimeoutMsRef(DEFAULT_JEV_TIMEOUT_MS);
91
+ setAdvisorJevDigestMaxCharsRef(DEFAULT_JEV_DIGEST_MAX_CHARS);
92
+ setAdvisorJevPricePerMtokRef(DEFAULT_JEV_PRICE_PER_MTOK);
93
+ setAdvisorJevTransportRef(DEFAULT_JEV_TRANSPORT);
94
+ setAdvisorJevTurnGateEveryTurnsRef(DEFAULT_JEV_TURN_GATE_EVERY_TURNS);
95
+ setAdvisorJevTurnGateNoulThresholdRef(DEFAULT_JEV_TURN_GATE_NOUL_THRESHOLD);
64
96
  setAdvisorToolResultMaxLinesRef(DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES);
65
97
  setAdvisorToolResultMaxBytesRef(DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES);
66
98
  setAdvisorRedactSecretsRef(false);
@@ -150,6 +182,49 @@ export const applyConfig = (config: AdvisorConfig) => {
150
182
  "advisorHerdrIntegration",
151
183
  setAdvisorHerdrIntegrationRef
152
184
  );
185
+ applyOptionalConfig(
186
+ config,
187
+ "advisorJevFilterEnabled",
188
+ setAdvisorJevFilterEnabledRef
189
+ );
190
+ applyOptionalConfig(
191
+ config,
192
+ "advisorJevFilterSkipConfidence",
193
+ setAdvisorJevFilterSkipConfidenceRef
194
+ );
195
+ applyOptionalConfig(
196
+ config,
197
+ "advisorJevFilterNoulMargin",
198
+ setAdvisorJevFilterNoulMarginRef
199
+ );
200
+ applyOptionalConfig(
201
+ config,
202
+ "advisorJevFilterOverrideWindow",
203
+ setAdvisorJevFilterOverrideWindowRef
204
+ );
205
+ applyNonEmptyStringConfig(config.advisorJevModel, setAdvisorJevModelRef);
206
+ applyOptionalConfig(config, "advisorJevTimeoutMs", setAdvisorJevTimeoutMsRef);
207
+ applyOptionalConfig(
208
+ config,
209
+ "advisorJevDigestMaxChars",
210
+ setAdvisorJevDigestMaxCharsRef
211
+ );
212
+ applyOptionalConfig(
213
+ config,
214
+ "advisorJevPricePerMtok",
215
+ setAdvisorJevPricePerMtokRef
216
+ );
217
+ applyOptionalConfig(config, "advisorJevTransport", setAdvisorJevTransportRef);
218
+ applyOptionalConfig(
219
+ config,
220
+ "advisorJevTurnGateEveryTurns",
221
+ setAdvisorJevTurnGateEveryTurnsRef
222
+ );
223
+ applyOptionalConfig(
224
+ config,
225
+ "advisorJevTurnGateNoulThreshold",
226
+ setAdvisorJevTurnGateNoulThresholdRef
227
+ );
153
228
  applyOptionalConfig(
154
229
  config,
155
230
  "advisorToolResultMaxLines",
@@ -11,6 +11,17 @@ import {
11
11
  advisorGitContextMaxCharsRef,
12
12
  advisorGitContextRef,
13
13
  advisorHerdrIntegrationRef,
14
+ advisorJevDigestMaxCharsRef,
15
+ advisorJevFilterEnabledRef,
16
+ advisorJevFilterNoulMarginRef,
17
+ advisorJevFilterOverrideWindowRef,
18
+ advisorJevFilterSkipConfidenceRef,
19
+ advisorJevModelRef,
20
+ advisorJevPricePerMtokRef,
21
+ advisorJevTimeoutMsRef,
22
+ advisorJevTransportRef,
23
+ advisorJevTurnGateEveryTurnsRef,
24
+ advisorJevTurnGateNoulThresholdRef,
14
25
  advisorLoopThresholdRef,
15
26
  advisorMaxCallsPerSessionRef,
16
27
  advisorOutcomeLoggingRef,
@@ -36,6 +47,8 @@ import {
36
47
  ADVISOR_TOOL_POLICIES,
37
48
  type AdvisorConfig,
38
49
  GATE_FAILURE_MODES,
50
+ JEV_TRANSPORTS,
51
+ type JevTransport,
39
52
  MAX_CONTEXT_MAX_CHARS,
40
53
  } from "./types.ts";
41
54
 
@@ -86,6 +99,30 @@ export const isValidToolResultMaxLines = (value: unknown): value is number =>
86
99
  export const isValidToolResultMaxBytes = (value: unknown): value is number =>
87
100
  nonNegativeSafeInteger(value);
88
101
 
102
+ export const isValidJevTimeoutMs = (value: unknown): value is number =>
103
+ typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
104
+
105
+ export const isValidJevDigestMaxChars = (value: unknown): value is number =>
106
+ nonNegativeSafeInteger(value);
107
+
108
+ export const isValidJevPricePerMtok = (value: unknown): value is number =>
109
+ typeof value === "number" && Number.isFinite(value) && value > 0;
110
+
111
+ export const isValidJevSkipConfidence = (value: unknown): value is number =>
112
+ typeof value === "number" &&
113
+ Number.isFinite(value) &&
114
+ value >= 0.5 &&
115
+ value <= 1;
116
+
117
+ export const isValidJevNoulMargin = (value: unknown): value is number =>
118
+ typeof value === "number" &&
119
+ Number.isFinite(value) &&
120
+ value >= 0 &&
121
+ value <= 0.5;
122
+
123
+ export const isValidJevTransport = (value: unknown): value is JevTransport =>
124
+ typeof value === "string" && JEV_TRANSPORTS.includes(value as JevTransport);
125
+
89
126
  /** One declarative entry per AdvisorConfig key: JSON type, persistence, and
90
127
  * the live runtime value behind it. Validation and storage derive from this
91
128
  * table; adding a setting means adding exactly one entry here. */
@@ -171,6 +208,81 @@ export const CONFIG_SCHEMA = {
171
208
  persisted: true,
172
209
  type: "boolean",
173
210
  },
211
+ advisorJevDigestMaxChars: {
212
+ accepted: "a non-negative safe integer",
213
+ current: () => advisorJevDigestMaxCharsRef,
214
+ persisted: true,
215
+ type: "number",
216
+ validate: isValidJevDigestMaxChars,
217
+ },
218
+ advisorJevFilterEnabled: {
219
+ accepted: "true or false",
220
+ current: () => advisorJevFilterEnabledRef,
221
+ persisted: true,
222
+ type: "boolean",
223
+ },
224
+ advisorJevFilterNoulMargin: {
225
+ accepted: "a number from 0 through 0.5",
226
+ current: () => advisorJevFilterNoulMarginRef,
227
+ persisted: true,
228
+ type: "number",
229
+ validate: isValidJevNoulMargin,
230
+ },
231
+ advisorJevFilterOverrideWindow: {
232
+ accepted: "a non-negative safe integer",
233
+ current: () => advisorJevFilterOverrideWindowRef,
234
+ persisted: true,
235
+ type: "number",
236
+ validate: nonNegativeSafeInteger,
237
+ },
238
+ advisorJevFilterSkipConfidence: {
239
+ accepted: "a number between 0.5 and 1 inclusive",
240
+ current: () => advisorJevFilterSkipConfidenceRef,
241
+ persisted: true,
242
+ type: "number",
243
+ validate: isValidJevSkipConfidence,
244
+ },
245
+ advisorJevModel: {
246
+ accepted: "a non-empty string",
247
+ current: () => advisorJevModelRef,
248
+ persisted: true,
249
+ type: "string",
250
+ },
251
+ advisorJevPricePerMtok: {
252
+ accepted: "a positive number",
253
+ current: () => advisorJevPricePerMtokRef,
254
+ persisted: true,
255
+ type: "number",
256
+ validate: isValidJevPricePerMtok,
257
+ },
258
+ advisorJevTimeoutMs: {
259
+ accepted: "a positive safe integer",
260
+ current: () => advisorJevTimeoutMsRef,
261
+ persisted: true,
262
+ type: "number",
263
+ validate: isValidJevTimeoutMs,
264
+ },
265
+ advisorJevTransport: {
266
+ accepted: JEV_TRANSPORTS.join(", "),
267
+ current: () => advisorJevTransportRef,
268
+ persisted: true,
269
+ type: "enum",
270
+ validate: isValidJevTransport,
271
+ },
272
+ advisorJevTurnGateEveryTurns: {
273
+ accepted: "a non-negative safe integer (0 disables the turn gate)",
274
+ current: () => advisorJevTurnGateEveryTurnsRef,
275
+ persisted: true,
276
+ type: "number",
277
+ validate: nonNegativeSafeInteger,
278
+ },
279
+ advisorJevTurnGateNoulThreshold: {
280
+ accepted: "a number between 0.5 and 1 inclusive",
281
+ current: () => advisorJevTurnGateNoulThresholdRef,
282
+ persisted: true,
283
+ type: "number",
284
+ validate: isValidJevSkipConfidence,
285
+ },
174
286
  advisorLoopThreshold: {
175
287
  accepted: "a safe integer of at least 2",
176
288
  current: () => advisorLoopThresholdRef,
@@ -5,7 +5,18 @@ import {
5
5
  DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES,
6
6
  DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES,
7
7
  DEFAULT_CONTEXT_MAX_CHARS,
8
+ DEFAULT_JEV_DIGEST_MAX_CHARS,
9
+ DEFAULT_JEV_FILTER_NOUL_MARGIN,
10
+ DEFAULT_JEV_FILTER_OVERRIDE_WINDOW,
11
+ DEFAULT_JEV_FILTER_SKIP_CONFIDENCE,
12
+ DEFAULT_JEV_MODEL,
13
+ DEFAULT_JEV_PRICE_PER_MTOK,
14
+ DEFAULT_JEV_TIMEOUT_MS,
15
+ DEFAULT_JEV_TRANSPORT,
16
+ DEFAULT_JEV_TURN_GATE_EVERY_TURNS,
17
+ DEFAULT_JEV_TURN_GATE_NOUL_THRESHOLD,
8
18
  type GateFailureMode,
19
+ type JevTransport,
9
20
  } from "./types.ts";
10
21
 
11
22
  // An empty ref means no model has been selected yet.
@@ -30,6 +41,20 @@ export let simpleModeRef = false;
30
41
  export let alwaysOnRef = false;
31
42
  export let advisorFailureModeRef: GateFailureMode = "block-session";
32
43
  export let advisorHerdrIntegrationRef = true;
44
+ export let advisorJevFilterEnabledRef = false;
45
+ export let advisorJevFilterSkipConfidenceRef =
46
+ DEFAULT_JEV_FILTER_SKIP_CONFIDENCE;
47
+ export let advisorJevFilterNoulMarginRef = DEFAULT_JEV_FILTER_NOUL_MARGIN;
48
+ export let advisorJevFilterOverrideWindowRef =
49
+ DEFAULT_JEV_FILTER_OVERRIDE_WINDOW;
50
+ export let advisorJevModelRef = DEFAULT_JEV_MODEL;
51
+ export let advisorJevTimeoutMsRef = DEFAULT_JEV_TIMEOUT_MS;
52
+ export let advisorJevDigestMaxCharsRef = DEFAULT_JEV_DIGEST_MAX_CHARS;
53
+ export let advisorJevPricePerMtokRef = DEFAULT_JEV_PRICE_PER_MTOK;
54
+ export let advisorJevTransportRef: JevTransport = DEFAULT_JEV_TRANSPORT;
55
+ export let advisorJevTurnGateEveryTurnsRef = DEFAULT_JEV_TURN_GATE_EVERY_TURNS;
56
+ export let advisorJevTurnGateNoulThresholdRef =
57
+ DEFAULT_JEV_TURN_GATE_NOUL_THRESHOLD;
33
58
  export let advisorToolResultMaxLinesRef = DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES;
34
59
  export let advisorToolResultMaxBytesRef = DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES;
35
60
  export let advisorRedactSecretsRef = false;
@@ -117,6 +142,39 @@ export const setAdvisorFailureModeRef = (value: GateFailureMode) => {
117
142
  export const setAdvisorHerdrIntegrationRef = (enabled: boolean) => {
118
143
  advisorHerdrIntegrationRef = enabled;
119
144
  };
145
+ export const setAdvisorJevFilterEnabledRef = (enabled: boolean) => {
146
+ advisorJevFilterEnabledRef = enabled;
147
+ };
148
+ export const setAdvisorJevFilterSkipConfidenceRef = (value: number) => {
149
+ advisorJevFilterSkipConfidenceRef = value;
150
+ };
151
+ export const setAdvisorJevFilterNoulMarginRef = (value: number) => {
152
+ advisorJevFilterNoulMarginRef = value;
153
+ };
154
+ export const setAdvisorJevFilterOverrideWindowRef = (value: number) => {
155
+ advisorJevFilterOverrideWindowRef = value;
156
+ };
157
+ export const setAdvisorJevModelRef = (model: string | undefined) => {
158
+ advisorJevModelRef = model?.trim() || DEFAULT_JEV_MODEL;
159
+ };
160
+ export const setAdvisorJevTimeoutMsRef = (value: number) => {
161
+ advisorJevTimeoutMsRef = value;
162
+ };
163
+ export const setAdvisorJevDigestMaxCharsRef = (value: number) => {
164
+ advisorJevDigestMaxCharsRef = value;
165
+ };
166
+ export const setAdvisorJevPricePerMtokRef = (value: number) => {
167
+ advisorJevPricePerMtokRef = value;
168
+ };
169
+ export const setAdvisorJevTransportRef = (value: JevTransport) => {
170
+ advisorJevTransportRef = value;
171
+ };
172
+ export const setAdvisorJevTurnGateEveryTurnsRef = (value: number) => {
173
+ advisorJevTurnGateEveryTurnsRef = value;
174
+ };
175
+ export const setAdvisorJevTurnGateNoulThresholdRef = (value: number) => {
176
+ advisorJevTurnGateNoulThresholdRef = value;
177
+ };
120
178
  export const setAdvisorToolResultMaxLinesRef = (value: number) => {
121
179
  advisorToolResultMaxLinesRef = value;
122
180
  };
@@ -172,6 +230,17 @@ export const getAdvisorSettings = () => ({
172
230
  gitContext: advisorGitContextRef,
173
231
  gitContextMaxChars: advisorGitContextMaxCharsRef,
174
232
  herdrIntegration: advisorHerdrIntegrationRef,
233
+ jevDigestMaxChars: advisorJevDigestMaxCharsRef,
234
+ jevFilterEnabled: advisorJevFilterEnabledRef,
235
+ jevFilterNoulMargin: advisorJevFilterNoulMarginRef,
236
+ jevFilterOverrideWindow: advisorJevFilterOverrideWindowRef,
237
+ jevFilterSkipConfidence: advisorJevFilterSkipConfidenceRef,
238
+ jevModel: advisorJevModelRef,
239
+ jevPricePerMtok: advisorJevPricePerMtokRef,
240
+ jevTimeoutMs: advisorJevTimeoutMsRef,
241
+ jevTransport: advisorJevTransportRef,
242
+ jevTurnGateEveryTurns: advisorJevTurnGateEveryTurnsRef,
243
+ jevTurnGateNoulThreshold: advisorJevTurnGateNoulThresholdRef,
175
244
  loopThreshold: advisorLoopThresholdRef,
176
245
  maxCallsPerSession: advisorMaxCallsPerSessionRef,
177
246
  outcomeLogging: advisorOutcomeLoggingRef,
@@ -36,7 +36,12 @@ const currentConfigState = (): ConfigState =>
36
36
  const sameConfigValue = <Value>(left: Value, right: Value) =>
37
37
  JSON.stringify(left) === JSON.stringify(right);
38
38
 
39
- const readExistingConfig = (path: string): Record<string, unknown> => {
39
+ /** Keys advisor.json may hold that are not AdvisorConfig settings but are
40
+ * still legitimate (hand-placed secrets). They stay out of CONFIG_SCHEMA, are
41
+ * preserved like any unknown key, and never trigger the typo warning. */
42
+ const RESERVED_ADVISOR_JSON_KEYS = new Set(["typesafe_api_key"]);
43
+
44
+ export const readExistingConfig = (path: string): Record<string, unknown> => {
40
45
  try {
41
46
  const parsed = JSON.parse(readFileSync(path, "utf8"));
42
47
  return parsed && typeof parsed === "object" && !Array.isArray(parsed)
@@ -139,7 +144,7 @@ export const loadConfig = (_ctx: ExtensionContext) => {
139
144
  applyConfig(globalConfig);
140
145
  const unknownKeys = unknownConfigKeys(
141
146
  globalConfig as Record<string, unknown>
142
- );
147
+ ).filter((key) => !RESERVED_ADVISOR_JSON_KEYS.has(key));
143
148
  const warningIdentity = `${global}:${configIdentity(global)}`;
144
149
  if (
145
150
  unknownKeys.length > 0 &&
@@ -9,6 +9,24 @@ export const MAX_CONTEXT_MAX_CHARS = Number.MAX_SAFE_INTEGER;
9
9
  export const DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES = DEFAULT_MAX_LINES;
10
10
  export const DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES = DEFAULT_MAX_BYTES;
11
11
  export const DEFAULT_ADVISOR_GIT_CONTEXT_MAX_CHARS = 20_000;
12
+ export const DEFAULT_JEV_MODEL = "jev-latest";
13
+ // Provisional: the latency benchmark was dropped from scope; revisit if live
14
+ // measurements suggest a different bound.
15
+ export const DEFAULT_JEV_TIMEOUT_MS = 8000;
16
+ export const DEFAULT_JEV_DIGEST_MAX_CHARS = 4000;
17
+ export const DEFAULT_JEV_PRICE_PER_MTOK = 0.042;
18
+ export const DEFAULT_JEV_FILTER_SKIP_CONFIDENCE = 0.85;
19
+ export const DEFAULT_JEV_FILTER_NOUL_MARGIN = 0.35;
20
+ export const DEFAULT_JEV_FILTER_OVERRIDE_WINDOW = 10;
21
+ export const DEFAULT_JEV_TURN_GATE_EVERY_TURNS = 0;
22
+ export const DEFAULT_JEV_TURN_GATE_NOUL_THRESHOLD = 0.8;
23
+ export type JevTransport = "auto" | "typesafe" | "openrouter";
24
+ export const DEFAULT_JEV_TRANSPORT: JevTransport = "auto";
25
+ export const JEV_TRANSPORTS: JevTransport[] = [
26
+ "auto",
27
+ "typesafe",
28
+ "openrouter",
29
+ ];
12
30
 
13
31
  export type AdvisorToolPolicy = "full" | "summary" | "exclude";
14
32
  export type AdvisorToolPolicies = Record<string, AdvisorToolPolicy>;
@@ -40,6 +58,17 @@ export interface AdvisorConfig {
40
58
  advisorGitContext?: GitContextLevel;
41
59
  advisorGitContextMaxChars?: number;
42
60
  advisorHerdrIntegration?: boolean;
61
+ advisorJevDigestMaxChars?: number;
62
+ advisorJevFilterEnabled?: boolean;
63
+ advisorJevFilterNoulMargin?: number;
64
+ advisorJevFilterOverrideWindow?: number;
65
+ advisorJevFilterSkipConfidence?: number;
66
+ advisorJevModel?: string;
67
+ advisorJevPricePerMtok?: number;
68
+ advisorJevTimeoutMs?: number;
69
+ advisorJevTransport?: JevTransport;
70
+ advisorJevTurnGateEveryTurns?: number;
71
+ advisorJevTurnGateNoulThreshold?: number;
43
72
  advisorLoopThreshold?: number;
44
73
  advisorMaxCallsPerSession?: number;
45
74
  advisorOutcomeLogging?: boolean;