pi-advisor-flow 0.5.0 → 0.5.2

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.
Files changed (59) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +37 -71
  3. package/extensions/index.ts +7 -7
  4. package/package.json +19 -6
  5. package/src/commands/activation-preparation.ts +117 -0
  6. package/src/commands/activation.ts +135 -0
  7. package/src/commands/lifecycle.ts +83 -0
  8. package/src/commands/manual-command.ts +107 -0
  9. package/src/commands/manual-consultation.ts +144 -0
  10. package/src/commands/manual-progress.ts +93 -0
  11. package/src/commands/model-commands.ts +66 -0
  12. package/src/commands/model-options.ts +181 -0
  13. package/src/commands/model-picker.ts +110 -0
  14. package/src/commands/registration.ts +27 -0
  15. package/src/commands/renderers.ts +76 -0
  16. package/src/commands/runtime.ts +114 -0
  17. package/src/commands/settings-commands.ts +80 -0
  18. package/src/commands/settings-persistence.ts +81 -0
  19. package/src/commands/types.ts +78 -0
  20. package/src/commands.ts +9 -946
  21. package/src/config/args.ts +37 -0
  22. package/src/config/defaults.ts +190 -0
  23. package/src/config/state.ts +199 -0
  24. package/src/config/storage.ts +224 -0
  25. package/src/config/types.ts +63 -0
  26. package/src/config/validation.ts +234 -0
  27. package/src/config.ts +104 -791
  28. package/src/conversation.ts +4 -2
  29. package/src/herdr.ts +1 -1
  30. package/src/model-stream.ts +25 -2
  31. package/src/scout-context.ts +2 -2
  32. package/src/scout.ts +1 -1
  33. package/src/tools/consultation.ts +337 -0
  34. package/src/tools/gate-policy.ts +132 -0
  35. package/src/tools/gate-protocol.ts +127 -0
  36. package/src/tools/loop-gate.ts +210 -0
  37. package/src/tools/prompts.ts +159 -0
  38. package/src/tools/register-ask-advisor.ts +225 -0
  39. package/src/tools/register-lifecycle.ts +89 -0
  40. package/src/tools/register-outcome.ts +75 -0
  41. package/src/tools/register-renderers.ts +88 -0
  42. package/src/tools/registration.ts +35 -0
  43. package/src/tools/render-advisor-result.ts +168 -0
  44. package/src/tools/render-common.ts +120 -0
  45. package/src/tools/scout-status.ts +206 -0
  46. package/src/tools/session.ts +3 -0
  47. package/src/tools/types.ts +119 -0
  48. package/src/tools.ts +42 -1811
  49. package/src/ui/manual-dialog-render.ts +150 -0
  50. package/src/ui/manual-dialog.ts +305 -0
  51. package/src/ui/model-selector.ts +166 -0
  52. package/src/ui/settings-formatting.ts +133 -0
  53. package/src/ui/settings-items.ts +323 -0
  54. package/src/ui/settings-list-adapter.ts +103 -0
  55. package/src/ui/settings-mutations.ts +80 -0
  56. package/src/ui/settings-selector.ts +161 -0
  57. package/src/ui/text-setting-submenu.ts +66 -0
  58. package/src/ui/types.ts +111 -0
  59. package/src/ui.ts +9 -1418
package/src/config.ts CHANGED
@@ -1,791 +1,104 @@
1
- import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
2
- import { join } from "node:path";
3
- import {
4
- CONFIG_DIR_NAME,
5
- DEFAULT_MAX_BYTES,
6
- DEFAULT_MAX_LINES,
7
- type ExtensionContext,
8
- getAgentDir,
9
- } from "@earendil-works/pi-coding-agent";
10
- import {
11
- GIT_CONTEXT_LEVELS,
12
- type GitContextLevel,
13
- isValidGitContextLevel,
14
- } from "./git.js";
15
-
16
- export const FALLBACK_EXECUTOR = "openai-codex/gpt-5.6-luna";
17
- export const FALLBACK_ADVISOR = "openai-codex/gpt-5.6-sol";
18
- export const DEFAULT_CONTEXT_MAX_CHARS = 15_000;
19
- export const MAX_CONTEXT_MAX_CHARS = Number.MAX_SAFE_INTEGER;
20
- export const DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES = DEFAULT_MAX_LINES;
21
- export const DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES = DEFAULT_MAX_BYTES;
22
- export const DEFAULT_ADVISOR_GIT_CONTEXT_MAX_CHARS = 20_000;
23
- export type AdvisorToolPolicy = "full" | "summary" | "exclude";
24
- export type AdvisorToolPolicies = Record<string, AdvisorToolPolicy>;
25
- export const ADVISOR_TOOL_POLICIES: AdvisorToolPolicy[] = [
26
- "full",
27
- "summary",
28
- "exclude",
29
- ];
30
- export type GateFailureMode =
31
- | "block-session"
32
- | "block-tool"
33
- | "warn-and-continue";
34
- export const GATE_FAILURE_MODES: GateFailureMode[] = [
35
- "block-session",
36
- "block-tool",
37
- "warn-and-continue",
38
- ];
39
-
40
- export let executorRef = FALLBACK_EXECUTOR;
41
- export let advisorRef = FALLBACK_ADVISOR;
42
- export let executorEffortRef: string | undefined;
43
- export let advisorEffortRef: string | undefined;
44
- export let contextMaxCharsRef = DEFAULT_CONTEXT_MAX_CHARS;
45
- export let advisorPlanGateRef = true;
46
- export let advisorFailureGateRef = true;
47
- export let advisorCompletionGateRef = true;
48
- export let advisorCustomInvocationRef: string | undefined;
49
- export let advisorCollapseResponsesRef = false;
50
- export let advisorBlockOnBlockedRef = true;
51
- export let advisorAutoLoopGateRef = true;
52
- export let advisorLoopThresholdRef = 3;
53
- export let advisorMaxCallsPerSessionRef: number | undefined;
54
- export let advisorSessionSummaryRef = false;
55
- export let simpleModeRef = false;
56
- export let alwaysOnRef = false;
57
- export let advisorFailureModeRef: GateFailureMode = "block-session";
58
- export let advisorHerdrIntegrationRef = true;
59
- export let advisorToolResultMaxLinesRef = DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES;
60
- export let advisorToolResultMaxBytesRef = DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES;
61
- export let advisorRedactSecretsRef = false;
62
- export let advisorGitContextRef: GitContextLevel = "summary";
63
- export let advisorGitContextMaxCharsRef = DEFAULT_ADVISOR_GIT_CONTEXT_MAX_CHARS;
64
- export let advisorToolPoliciesRef: AdvisorToolPolicies = {};
65
- export let advisorOutcomeLoggingRef = false;
66
- export let advisorUntrackedContentRef = false;
67
- export let advisorTrackedFileContentRef = false;
68
- export let advisorScoutEnabledRef = false;
69
- export let showUsageDetailsRef = true;
70
- export let showUsageFooterRef = false;
71
-
72
- export const setExecutorRef = (ref: string) => {
73
- executorRef = ref;
74
- };
75
- export const setAdvisorRef = (ref: string) => {
76
- advisorRef = ref;
77
- };
78
- export const setExecutorEffortRef = (effort: string | undefined) => {
79
- executorEffortRef = effort;
80
- };
81
- export const setAdvisorEffortRef = (effort: string | undefined) => {
82
- advisorEffortRef = effort;
83
- };
84
- export const isValidContextMaxChars = (value: unknown): value is number =>
85
- typeof value === "number" &&
86
- Number.isSafeInteger(value) &&
87
- value >= 0 &&
88
- value <= MAX_CONTEXT_MAX_CHARS;
89
- export const setContextMaxCharsRef = (value: number) => {
90
- contextMaxCharsRef = value;
91
- };
92
- export const setAdvisorPlanGateRef = (enabled: boolean) => {
93
- advisorPlanGateRef = enabled;
94
- };
95
- export const setAdvisorFailureGateRef = (enabled: boolean) => {
96
- advisorFailureGateRef = enabled;
97
- };
98
- export const setAdvisorCompletionGateRef = (enabled: boolean) => {
99
- advisorCompletionGateRef = enabled;
100
- };
101
- export const setAdvisorCustomInvocationRef = (rule: string | undefined) => {
102
- advisorCustomInvocationRef = rule?.trim() || undefined;
103
- };
104
- export const setAdvisorCollapseResponsesRef = (enabled: boolean) => {
105
- advisorCollapseResponsesRef = enabled;
106
- };
107
- export const setAdvisorBlockOnBlockedRef = (enabled: boolean) => {
108
- advisorBlockOnBlockedRef = enabled;
109
- };
110
- export const setAdvisorAutoLoopGateRef = (enabled: boolean) => {
111
- advisorAutoLoopGateRef = enabled;
112
- };
113
- export const isValidLoopThreshold = (value: unknown): value is number =>
114
- typeof value === "number" && Number.isSafeInteger(value) && value >= 2;
115
- export const setAdvisorLoopThresholdRef = (value: number) => {
116
- advisorLoopThresholdRef = value;
117
- };
118
- export const isValidMaxCallsPerSession = (value: unknown): value is number =>
119
- typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
120
- export const setAdvisorMaxCallsPerSessionRef = (value: number | undefined) => {
121
- advisorMaxCallsPerSessionRef = value;
122
- };
123
- export const setAdvisorSessionSummaryRef = (enabled: boolean) => {
124
- advisorSessionSummaryRef = enabled;
125
- };
126
- export const setSimpleModeRef = (enabled: boolean) => {
127
- simpleModeRef = enabled;
128
- };
129
- export const setAlwaysOnRef = (enabled: boolean) => {
130
- alwaysOnRef = enabled;
131
- };
132
- /** Whether advanced Advisor automation is suppressed for this session. */
133
- export const isSimpleMode = () => simpleModeRef;
134
- export const isValidGateFailureMode = (
135
- value: unknown
136
- ): value is GateFailureMode =>
137
- typeof value === "string" &&
138
- GATE_FAILURE_MODES.includes(value as GateFailureMode);
139
- export const setAdvisorFailureModeRef = (value: GateFailureMode) => {
140
- advisorFailureModeRef = value;
141
- };
142
- export const setAdvisorHerdrIntegrationRef = (enabled: boolean) => {
143
- advisorHerdrIntegrationRef = enabled;
144
- };
145
- export const isValidToolResultMaxLines = (value: unknown): value is number =>
146
- typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
147
- export const isValidToolResultMaxBytes = (value: unknown): value is number =>
148
- typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
149
- export const setAdvisorToolResultMaxLinesRef = (value: number) => {
150
- advisorToolResultMaxLinesRef = value;
151
- };
152
- export const setAdvisorToolResultMaxBytesRef = (value: number) => {
153
- advisorToolResultMaxBytesRef = value;
154
- };
155
- export const setAdvisorRedactSecretsRef = (enabled: boolean) => {
156
- advisorRedactSecretsRef = enabled;
157
- };
158
- export const setAdvisorGitContextRef = (level: GitContextLevel) => {
159
- advisorGitContextRef = level;
160
- };
161
- export const setAdvisorGitContextMaxCharsRef = (value: number) => {
162
- advisorGitContextMaxCharsRef = value;
163
- };
164
- export const setAdvisorToolPoliciesRef = (policies: AdvisorToolPolicies) => {
165
- advisorToolPoliciesRef = { ...policies };
166
- };
167
- export const setAdvisorOutcomeLoggingRef = (enabled: boolean) => {
168
- advisorOutcomeLoggingRef = enabled;
169
- };
170
- export const setAdvisorUntrackedContentRef = (enabled: boolean) => {
171
- advisorUntrackedContentRef = enabled;
172
- };
173
- export const setAdvisorTrackedFileContentRef = (enabled: boolean) => {
174
- advisorTrackedFileContentRef = enabled;
175
- };
176
- export const setAdvisorScoutEnabledRef = (enabled: boolean) => {
177
- advisorScoutEnabledRef = enabled;
178
- };
179
- export const setShowUsageDetailsRef = (enabled: boolean) => {
180
- showUsageDetailsRef = enabled;
181
- };
182
- export const setShowUsageFooterRef = (enabled: boolean) => {
183
- showUsageFooterRef = enabled;
184
- };
185
-
186
- /**
187
- * Returns the current live settings state. Use this at UI boundaries instead of
188
- * imported mutable bindings, which can be snapshotted by extension loaders.
189
- */
190
- export const getAdvisorSettings = () => ({
191
- alwaysOn: alwaysOnRef,
192
- autoLoopGate: advisorAutoLoopGateRef,
193
- blockOnBlocked: advisorBlockOnBlockedRef,
194
- collapseResponses: advisorCollapseResponsesRef,
195
- completionGate: advisorCompletionGateRef,
196
- contextMaxChars: contextMaxCharsRef,
197
- customRule: advisorCustomInvocationRef,
198
- effort: advisorEffortRef,
199
- failureGate: advisorFailureGateRef,
200
- failureMode: advisorFailureModeRef,
201
- gitContext: advisorGitContextRef,
202
- gitContextMaxChars: advisorGitContextMaxCharsRef,
203
- herdrIntegration: advisorHerdrIntegrationRef,
204
- loopThreshold: advisorLoopThresholdRef,
205
- maxCallsPerSession: advisorMaxCallsPerSessionRef,
206
- outcomeLogging: advisorOutcomeLoggingRef,
207
- planGate: advisorPlanGateRef,
208
- redactSecrets: advisorRedactSecretsRef,
209
- scoutEnabled: advisorScoutEnabledRef,
210
- sessionSummary: advisorSessionSummaryRef,
211
- showUsageDetails: showUsageDetailsRef,
212
- showUsageFooter: showUsageFooterRef,
213
- simpleMode: simpleModeRef,
214
- toolPolicies: { ...advisorToolPoliciesRef },
215
- toolResultMaxBytes: advisorToolResultMaxBytesRef,
216
- toolResultMaxLines: advisorToolResultMaxLinesRef,
217
- trackedFileContent: advisorTrackedFileContentRef,
218
- untrackedContent: advisorUntrackedContentRef,
219
- });
220
-
221
- /** Reads the live session budget at each enforcement boundary. */
222
- export const getAdvisorMaxCallsPerSession = () =>
223
- getAdvisorSettings().maxCallsPerSession;
224
-
225
- export const splitRef = (ref: string): [string, string] => {
226
- const i = ref.indexOf("/");
227
- return i === -1 ? ["openai-codex", ref] : [ref.slice(0, i), ref.slice(i + 1)];
228
- };
229
-
230
- export const configPaths = (ctx: ExtensionContext) => [
231
- ctx.isProjectTrusted()
232
- ? join(ctx.cwd, CONFIG_DIR_NAME, "advisor.json")
233
- : null,
234
- join(getAgentDir(), "advisor.json"),
235
- ];
236
-
237
- export interface AdvisorConfig {
238
- advisor?: string;
239
- advisorAutoLoopGate?: boolean;
240
- advisorBlockOnBlocked?: boolean;
241
- advisorCollapseResponses?: boolean;
242
- advisorCompletionGate?: boolean;
243
- advisorCustomInvocation?: string;
244
- advisorEffort?: string;
245
- advisorFailureGate?: boolean;
246
- advisorGitContext?: GitContextLevel;
247
- advisorGitContextMaxChars?: number;
248
- advisorHerdrIntegration?: boolean;
249
- advisorLoopThreshold?: number;
250
- advisorMaxCallsPerSession?: number;
251
- advisorOutcomeLogging?: boolean;
252
- advisorPlanGate?: boolean;
253
- advisorRedactSecrets?: boolean;
254
- advisorScoutEnabled?: boolean;
255
- advisorSessionSummary?: boolean;
256
- advisorToolPolicies?: AdvisorToolPolicies;
257
- advisorToolResultMaxBytes?: number;
258
- advisorToolResultMaxLines?: number;
259
- advisorTrackedFileContent?: boolean;
260
- advisorUntrackedContent?: boolean;
261
- alwaysOn?: boolean;
262
- contextMaxChars?: number;
263
- executor?: string;
264
- executorEffort?: string;
265
- gateFailureMode?: GateFailureMode;
266
- showUsageDetails?: boolean;
267
- showUsageFooter?: boolean;
268
- simpleMode?: boolean;
269
- }
270
-
271
- const CONFIG_KEYS = new Set<keyof AdvisorConfig>([
272
- "advisor",
273
- "advisorAutoLoopGate",
274
- "advisorBlockOnBlocked",
275
- "advisorCollapseResponses",
276
- "advisorCompletionGate",
277
- "advisorCustomInvocation",
278
- "advisorEffort",
279
- "advisorFailureGate",
280
- "advisorGitContext",
281
- "advisorGitContextMaxChars",
282
- "advisorHerdrIntegration",
283
- "advisorLoopThreshold",
284
- "advisorMaxCallsPerSession",
285
- "advisorPlanGate",
286
- "advisorSessionSummary",
287
- "advisorScoutEnabled",
288
- "showUsageDetails",
289
- "simpleMode",
290
- "alwaysOn",
291
- "advisorToolResultMaxBytes",
292
- "advisorToolResultMaxLines",
293
- "advisorTrackedFileContent",
294
- "advisorRedactSecrets",
295
- "advisorUntrackedContent",
296
- "advisorOutcomeLogging",
297
- "advisorToolPolicies",
298
- "contextMaxChars",
299
- "executor",
300
- "executorEffort",
301
- "gateFailureMode",
302
- "showUsageFooter",
303
- ]);
304
- const BOOLEAN_CONFIG_KEYS = [
305
- "advisorPlanGate",
306
- "advisorFailureGate",
307
- "advisorCompletionGate",
308
- "advisorCollapseResponses",
309
- "advisorBlockOnBlocked",
310
- "advisorAutoLoopGate",
311
- "advisorSessionSummary",
312
- "advisorScoutEnabled",
313
- "showUsageDetails",
314
- "showUsageFooter",
315
- "simpleMode",
316
- "alwaysOn",
317
- "advisorHerdrIntegration",
318
- "advisorTrackedFileContent",
319
- "advisorRedactSecrets",
320
- "advisorUntrackedContent",
321
- "advisorOutcomeLogging",
322
- ] as const;
323
- const STRING_CONFIG_KEYS = [
324
- "executor",
325
- "advisor",
326
- "executorEffort",
327
- "advisorEffort",
328
- "advisorCustomInvocation",
329
- ] as const;
330
- const ARGUMENT_WHITESPACE = /\s+/;
331
-
332
- type ConfigRecord = Record<string, unknown>;
333
-
334
- const invalidConfigValue = (
335
- path: string,
336
- key: string,
337
- accepted: string
338
- ): never => {
339
- throw new TypeError(
340
- `Invalid advisor configuration at ${path}, key ${JSON.stringify(key)}: expected ${accepted}.`
341
- );
342
- };
343
-
344
- const unknownConfigKeys = (config: ConfigRecord) =>
345
- Object.keys(config).filter(
346
- (key) => !CONFIG_KEYS.has(key as keyof AdvisorConfig)
347
- );
348
-
349
- const validateStringValues = (config: ConfigRecord, path: string) => {
350
- for (const key of STRING_CONFIG_KEYS) {
351
- if (config[key] !== undefined && typeof config[key] !== "string") {
352
- invalidConfigValue(
353
- path,
354
- key,
355
- key === "executor" || key === "advisor"
356
- ? "a provider/model string"
357
- : "a string"
358
- );
359
- }
360
- }
361
- };
362
-
363
- const validateBooleanValues = (config: ConfigRecord, path: string) => {
364
- for (const key of BOOLEAN_CONFIG_KEYS) {
365
- if (config[key] !== undefined && typeof config[key] !== "boolean") {
366
- invalidConfigValue(path, key, "true or false");
367
- }
368
- }
369
- };
370
-
371
- export const isValidAdvisorToolPolicies = (
372
- value: unknown
373
- ): value is AdvisorToolPolicies => {
374
- if (!value || typeof value !== "object" || Array.isArray(value)) {
375
- return false;
376
- }
377
- return Object.entries(value).every(
378
- ([toolName, policy]) =>
379
- toolName.trim().length > 0 &&
380
- typeof policy === "string" &&
381
- ADVISOR_TOOL_POLICIES.includes(policy as AdvisorToolPolicy)
382
- );
383
- };
384
-
385
- const validateNumericValues = (config: ConfigRecord, path: string) => {
386
- const numericRules: [
387
- keyof AdvisorConfig,
388
- (value: unknown) => boolean,
389
- string,
390
- ][] = [
391
- [
392
- "contextMaxChars",
393
- isValidContextMaxChars,
394
- `a safe integer from 0 through ${MAX_CONTEXT_MAX_CHARS}`,
395
- ],
396
- [
397
- "advisorLoopThreshold",
398
- isValidLoopThreshold,
399
- "a safe integer of at least 2",
400
- ],
401
- [
402
- "advisorMaxCallsPerSession",
403
- isValidMaxCallsPerSession,
404
- "a non-negative safe integer",
405
- ],
406
- [
407
- "advisorToolResultMaxLines",
408
- isValidToolResultMaxLines,
409
- "a non-negative safe integer",
410
- ],
411
- [
412
- "advisorToolResultMaxBytes",
413
- isValidToolResultMaxBytes,
414
- "a non-negative safe integer",
415
- ],
416
- [
417
- "advisorGitContextMaxChars",
418
- isValidToolResultMaxBytes,
419
- "a non-negative safe integer",
420
- ],
421
- ];
422
- for (const [key, isValid, description] of numericRules) {
423
- if (config[key] !== undefined && !isValid(config[key])) {
424
- invalidConfigValue(path, key, description);
425
- }
426
- }
427
- };
428
-
429
- export const validateConfig = (
430
- value: unknown,
431
- path = "advisor.json"
432
- ): value is AdvisorConfig => {
433
- if (!value || typeof value !== "object" || Array.isArray(value)) {
434
- throw new TypeError(
435
- `Invalid advisor configuration at ${path}: expected a JSON object.`
436
- );
437
- }
438
- const config = value as ConfigRecord;
439
- validateStringValues(config, path);
440
- validateBooleanValues(config, path);
441
- validateNumericValues(config, path);
442
- if (
443
- config.advisorToolPolicies !== undefined &&
444
- !isValidAdvisorToolPolicies(config.advisorToolPolicies)
445
- ) {
446
- invalidConfigValue(
447
- path,
448
- "advisorToolPolicies",
449
- "a JSON object with non-empty tool names and full, summary, or exclude values"
450
- );
451
- }
452
- if (
453
- config.gateFailureMode !== undefined &&
454
- !isValidGateFailureMode(config.gateFailureMode)
455
- ) {
456
- invalidConfigValue(path, "gateFailureMode", GATE_FAILURE_MODES.join(", "));
457
- }
458
- if (
459
- config.advisorGitContext !== undefined &&
460
- !isValidGitContextLevel(config.advisorGitContext)
461
- ) {
462
- invalidConfigValue(
463
- path,
464
- "advisorGitContext",
465
- GIT_CONTEXT_LEVELS.join(", ")
466
- );
467
- }
468
- return true;
469
- };
470
-
471
- const resetDefaults = () => {
472
- executorRef = FALLBACK_EXECUTOR;
473
- advisorRef = FALLBACK_ADVISOR;
474
- executorEffortRef = undefined;
475
- advisorEffortRef = undefined;
476
- contextMaxCharsRef = DEFAULT_CONTEXT_MAX_CHARS;
477
- advisorPlanGateRef = true;
478
- advisorFailureGateRef = true;
479
- advisorCompletionGateRef = true;
480
- advisorCustomInvocationRef = undefined;
481
- advisorCollapseResponsesRef = false;
482
- advisorBlockOnBlockedRef = true;
483
- advisorAutoLoopGateRef = true;
484
- advisorLoopThresholdRef = 3;
485
- advisorMaxCallsPerSessionRef = undefined;
486
- advisorSessionSummaryRef = false;
487
- simpleModeRef = false;
488
- alwaysOnRef = false;
489
- advisorFailureModeRef = "block-session";
490
- advisorHerdrIntegrationRef = true;
491
- advisorToolResultMaxLinesRef = DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES;
492
- advisorToolResultMaxBytesRef = DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES;
493
- advisorRedactSecretsRef = false;
494
- advisorGitContextRef = "summary";
495
- advisorGitContextMaxCharsRef = DEFAULT_ADVISOR_GIT_CONTEXT_MAX_CHARS;
496
- advisorToolPoliciesRef = {};
497
- advisorOutcomeLoggingRef = false;
498
- advisorUntrackedContentRef = false;
499
- advisorTrackedFileContentRef = false;
500
- advisorScoutEnabledRef = false;
501
- showUsageDetailsRef = true;
502
- showUsageFooterRef = false;
503
- };
504
-
505
- const applyOptionalConfig = <Key extends keyof AdvisorConfig>(
506
- config: AdvisorConfig,
507
- key: Key,
508
- apply: (value: NonNullable<AdvisorConfig[Key]>) => void
509
- ) => {
510
- const value = config[key];
511
- if (value !== undefined) {
512
- apply(value as NonNullable<AdvisorConfig[Key]>);
513
- }
514
- };
515
-
516
- const applyNonEmptyStringConfig = (
517
- value: string | undefined,
518
- apply: (value: string) => void
519
- ) => {
520
- if (value) {
521
- apply(value);
522
- }
523
- };
524
-
525
- const applyConfig = (config: AdvisorConfig) => {
526
- applyNonEmptyStringConfig(config.executor, setExecutorRef);
527
- applyNonEmptyStringConfig(config.advisor, setAdvisorRef);
528
- applyNonEmptyStringConfig(config.executorEffort, setExecutorEffortRef);
529
- applyNonEmptyStringConfig(config.advisorEffort, setAdvisorEffortRef);
530
- applyOptionalConfig(config, "contextMaxChars", setContextMaxCharsRef);
531
- applyOptionalConfig(config, "advisorPlanGate", setAdvisorPlanGateRef);
532
- applyOptionalConfig(config, "advisorFailureGate", setAdvisorFailureGateRef);
533
- applyOptionalConfig(
534
- config,
535
- "advisorCompletionGate",
536
- setAdvisorCompletionGateRef
537
- );
538
- applyOptionalConfig(
539
- config,
540
- "advisorCustomInvocation",
541
- setAdvisorCustomInvocationRef
542
- );
543
- applyOptionalConfig(
544
- config,
545
- "advisorCollapseResponses",
546
- setAdvisorCollapseResponsesRef
547
- );
548
- applyOptionalConfig(
549
- config,
550
- "advisorBlockOnBlocked",
551
- setAdvisorBlockOnBlockedRef
552
- );
553
- applyOptionalConfig(config, "advisorAutoLoopGate", setAdvisorAutoLoopGateRef);
554
- applyOptionalConfig(
555
- config,
556
- "advisorLoopThreshold",
557
- setAdvisorLoopThresholdRef
558
- );
559
- applyOptionalConfig(
560
- config,
561
- "advisorMaxCallsPerSession",
562
- setAdvisorMaxCallsPerSessionRef
563
- );
564
- applyOptionalConfig(
565
- config,
566
- "advisorSessionSummary",
567
- setAdvisorSessionSummaryRef
568
- );
569
- applyOptionalConfig(config, "advisorScoutEnabled", setAdvisorScoutEnabledRef);
570
- applyOptionalConfig(config, "showUsageDetails", setShowUsageDetailsRef);
571
- applyOptionalConfig(config, "showUsageFooter", setShowUsageFooterRef);
572
- applyOptionalConfig(config, "simpleMode", setSimpleModeRef);
573
- applyOptionalConfig(config, "alwaysOn", setAlwaysOnRef);
574
- applyOptionalConfig(config, "gateFailureMode", setAdvisorFailureModeRef);
575
- applyOptionalConfig(
576
- config,
577
- "advisorHerdrIntegration",
578
- setAdvisorHerdrIntegrationRef
579
- );
580
- applyOptionalConfig(
581
- config,
582
- "advisorToolResultMaxLines",
583
- setAdvisorToolResultMaxLinesRef
584
- );
585
- applyOptionalConfig(
586
- config,
587
- "advisorToolResultMaxBytes",
588
- setAdvisorToolResultMaxBytesRef
589
- );
590
- applyOptionalConfig(
591
- config,
592
- "advisorRedactSecrets",
593
- setAdvisorRedactSecretsRef
594
- );
595
- applyOptionalConfig(config, "advisorGitContext", setAdvisorGitContextRef);
596
- applyOptionalConfig(
597
- config,
598
- "advisorGitContextMaxChars",
599
- setAdvisorGitContextMaxCharsRef
600
- );
601
- applyOptionalConfig(config, "advisorToolPolicies", setAdvisorToolPoliciesRef);
602
- applyOptionalConfig(
603
- config,
604
- "advisorUntrackedContent",
605
- setAdvisorUntrackedContentRef
606
- );
607
- applyOptionalConfig(
608
- config,
609
- "advisorTrackedFileContent",
610
- setAdvisorTrackedFileContentRef
611
- );
612
- };
613
-
614
- const readConfig = (path: string): AdvisorConfig => {
615
- try {
616
- const config = JSON.parse(readFileSync(path, "utf8"));
617
- validateConfig(config, path);
618
- return config;
619
- } catch (error) {
620
- throw error instanceof Error ? error : new Error(String(error));
621
- }
622
- };
623
-
624
- // loadConfig runs on every tool call and every consultation. Caching the parsed
625
- // file by path and stat identity removes that read and parse from the hot path
626
- // while still applying the full reset-then-apply sequence on each call.
627
- const configCache = new Map<
628
- string,
629
- { config: AdvisorConfig; identity: string }
630
- >();
631
- const warnedUnknownConfigIdentities = new Set<string>();
632
-
633
- /** Drops the parsed-configuration cache; the next load re-reads from disk. */
634
- export const resetConfigCache = () => {
635
- configCache.clear();
636
- warnedUnknownConfigIdentities.clear();
637
- };
638
-
639
- /**
640
- * Nanosecond timestamps plus size and inode so a same-millisecond or
641
- * same-size external rewrite cannot be served from the cache. An unstattable
642
- * path yields a unique value, which always forces a re-read.
643
- */
644
- const configIdentity = (path: string): string => {
645
- try {
646
- const stats = statSync(path, { bigint: true });
647
- return `${stats.mtimeNs}:${stats.ctimeNs}:${stats.size}:${stats.ino}:${stats.dev}`;
648
- } catch {
649
- return `unstattable:${process.hrtime.bigint()}`;
650
- }
651
- };
652
-
653
- const readConfigCached = (path: string): AdvisorConfig => {
654
- const identity = configIdentity(path);
655
- const cached = configCache.get(path);
656
- if (cached?.identity === identity) {
657
- return cached.config;
658
- }
659
- const config = readConfig(path);
660
- configCache.set(path, { config, identity });
661
- return config;
662
- };
663
-
664
- export const loadConfig = (_ctx: ExtensionContext) => {
665
- resetDefaults();
666
- const global = join(getAgentDir(), "advisor.json");
667
- const globalConfig = existsSync(global)
668
- ? readConfigCached(global)
669
- : undefined;
670
- if (globalConfig) {
671
- applyConfig(globalConfig);
672
- const unknownKeys = unknownConfigKeys(globalConfig as ConfigRecord);
673
- const warningIdentity = `${global}:${configIdentity(global)}`;
674
- if (
675
- unknownKeys.length > 0 &&
676
- _ctx.hasUI &&
677
- !warnedUnknownConfigIdentities.has(warningIdentity)
678
- ) {
679
- _ctx.ui.notify(
680
- `Advisor configuration at ${global} contains unrecognized key(s) ${unknownKeys.map((key) => JSON.stringify(key)).join(", ")}. They were preserved but ignored; check for typos or upgrade pi-advisor.`,
681
- "warning"
682
- );
683
- warnedUnknownConfigIdentities.add(warningIdentity);
684
- }
685
- }
686
- // Repository-controlled project configuration is never applied. Models,
687
- // prompts, gates, budgets, disclosure, redaction, integrations, and consent
688
- // remain under the user's global Pi configuration.
689
- advisorOutcomeLoggingRef = globalConfig?.advisorOutcomeLogging === true;
690
- return existsSync(global) ? global : null;
691
- };
692
-
693
- /** Saves user-controlled configuration globally without outcome consent. */
694
- export const saveConfig = (_ctx: ExtensionContext) => {
695
- const path = join(getAgentDir(), "advisor.json");
696
- let existing: Record<string, unknown> = {};
697
- try {
698
- const parsed = JSON.parse(readFileSync(path, "utf8"));
699
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
700
- existing = parsed as Record<string, unknown>;
701
- }
702
- } catch {
703
- /* replace a missing or malformed file */
704
- }
705
- if (advisorMaxCallsPerSessionRef === undefined) {
706
- existing.advisorMaxCallsPerSession = undefined;
707
- }
708
- const data = {
709
- ...existing,
710
- advisor: advisorRef,
711
- advisorAutoLoopGate: advisorAutoLoopGateRef,
712
- advisorBlockOnBlocked: advisorBlockOnBlockedRef,
713
- advisorCollapseResponses: advisorCollapseResponsesRef,
714
- advisorCompletionGate: advisorCompletionGateRef,
715
- advisorCustomInvocation: advisorCustomInvocationRef,
716
- advisorEffort: advisorEffortRef,
717
- advisorFailureGate: advisorFailureGateRef,
718
- advisorLoopThreshold: advisorLoopThresholdRef,
719
- advisorPlanGate: advisorPlanGateRef,
720
- contextMaxChars: contextMaxCharsRef,
721
- executor: executorRef,
722
- executorEffort: executorEffortRef,
723
- ...(advisorMaxCallsPerSessionRef === undefined
724
- ? {}
725
- : { advisorMaxCallsPerSession: advisorMaxCallsPerSessionRef }),
726
- advisorGitContext: advisorGitContextRef,
727
- advisorGitContextMaxChars: advisorGitContextMaxCharsRef,
728
- advisorHerdrIntegration: advisorHerdrIntegrationRef,
729
- advisorRedactSecrets: advisorRedactSecretsRef,
730
- advisorScoutEnabled: advisorScoutEnabledRef,
731
- advisorSessionSummary: advisorSessionSummaryRef,
732
- advisorToolPolicies: advisorToolPoliciesRef,
733
- advisorToolResultMaxBytes: advisorToolResultMaxBytesRef,
734
- advisorToolResultMaxLines: advisorToolResultMaxLinesRef,
735
- advisorTrackedFileContent: advisorTrackedFileContentRef,
736
- advisorUntrackedContent: advisorUntrackedContentRef,
737
- alwaysOn: alwaysOnRef,
738
- gateFailureMode: advisorFailureModeRef,
739
- showUsageDetails: showUsageDetailsRef,
740
- showUsageFooter: showUsageFooterRef,
741
- simpleMode: simpleModeRef,
742
- };
743
- writeFileSync(path, `${JSON.stringify(data, null, 2)}\n`);
744
- resetConfigCache();
745
- return path;
746
- };
747
-
748
- /** Outcome logging is deliberately written only to the global Pi configuration. */
749
- export const saveGlobalOutcomeLogging = (enabled: boolean) => {
750
- const path = join(getAgentDir(), "advisor.json");
751
- let existing: Record<string, unknown> = {};
752
- try {
753
- const parsed = JSON.parse(readFileSync(path, "utf8"));
754
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
755
- existing = parsed as Record<string, unknown>;
756
- }
757
- } catch {
758
- /* create it */
759
- }
760
- writeFileSync(
761
- path,
762
- `${JSON.stringify({ ...existing, advisorOutcomeLogging: enabled }, null, 2)}\n`
763
- );
764
- resetConfigCache();
765
- return path;
766
- };
767
-
768
- export const parseArgs = (args: string): string | undefined => {
769
- let nextExecutor = executorRef;
770
- let nextAdvisor = advisorRef;
771
- let nextContextMaxChars = contextMaxCharsRef;
772
- for (const token of args.trim().split(ARGUMENT_WHITESPACE).filter(Boolean)) {
773
- const [key, value] = token.split("=");
774
- if (key === "executor" && value) {
775
- nextExecutor = value;
776
- }
777
- if (key === "advisor" && value) {
778
- nextAdvisor = value;
779
- }
780
- if (key === "contextMaxChars") {
781
- const parsed = Number(value);
782
- if (!isValidContextMaxChars(parsed)) {
783
- return `contextMaxChars must be a non-negative integer no greater than ${MAX_CONTEXT_MAX_CHARS}.`;
784
- }
785
- nextContextMaxChars = parsed;
786
- }
787
- }
788
- executorRef = nextExecutor;
789
- advisorRef = nextAdvisor;
790
- contextMaxCharsRef = nextContextMaxChars;
791
- };
1
+ // biome-ignore lint/performance/noBarrelFile: this facade intentionally preserves the public config module contract.
2
+ export { parseArgs } from "./config/args.js";
3
+ export {
4
+ advisorAutoLoopGateRef,
5
+ advisorBlockOnBlockedRef,
6
+ advisorCollapseResponsesRef,
7
+ advisorCompletionGateRef,
8
+ advisorCustomInvocationRef,
9
+ advisorEffortRef,
10
+ advisorFailureGateRef,
11
+ advisorFailureModeRef,
12
+ advisorGitContextMaxCharsRef,
13
+ advisorGitContextRef,
14
+ advisorHerdrIntegrationRef,
15
+ advisorLoopThresholdRef,
16
+ advisorMaxCallsPerSessionRef,
17
+ advisorOutcomeLoggingRef,
18
+ advisorPlanGateRef,
19
+ advisorRedactSecretsRef,
20
+ advisorRef,
21
+ advisorScoutEnabledRef,
22
+ advisorSessionSummaryRef,
23
+ advisorToolPoliciesRef,
24
+ advisorToolResultMaxBytesRef,
25
+ advisorToolResultMaxLinesRef,
26
+ advisorTrackedFileContentRef,
27
+ advisorUntrackedContentRef,
28
+ alwaysOnRef,
29
+ contextMaxCharsRef,
30
+ executorEffortRef,
31
+ executorRef,
32
+ getAdvisorMaxCallsPerSession,
33
+ getAdvisorSettings,
34
+ getPersistedModelRefs,
35
+ isSimpleMode,
36
+ setAdvisorAutoLoopGateRef,
37
+ setAdvisorBlockOnBlockedRef,
38
+ setAdvisorCollapseResponsesRef,
39
+ setAdvisorCompletionGateRef,
40
+ setAdvisorCustomInvocationRef,
41
+ setAdvisorEffortRef,
42
+ setAdvisorFailureGateRef,
43
+ setAdvisorFailureModeRef,
44
+ setAdvisorGitContextMaxCharsRef,
45
+ setAdvisorGitContextRef,
46
+ setAdvisorHerdrIntegrationRef,
47
+ setAdvisorLoopThresholdRef,
48
+ setAdvisorMaxCallsPerSessionRef,
49
+ setAdvisorOutcomeLoggingRef,
50
+ setAdvisorPlanGateRef,
51
+ setAdvisorRedactSecretsRef,
52
+ setAdvisorRef,
53
+ setAdvisorScoutEnabledRef,
54
+ setAdvisorSessionSummaryRef,
55
+ setAdvisorToolPoliciesRef,
56
+ setAdvisorToolResultMaxBytesRef,
57
+ setAdvisorToolResultMaxLinesRef,
58
+ setAdvisorTrackedFileContentRef,
59
+ setAdvisorUntrackedContentRef,
60
+ setAlwaysOnRef,
61
+ setContextMaxCharsRef,
62
+ setExecutorEffortRef,
63
+ setExecutorRef,
64
+ setShowUsageDetailsRef,
65
+ setShowUsageFooterRef,
66
+ setSimpleModeRef,
67
+ showUsageDetailsRef,
68
+ showUsageFooterRef,
69
+ simpleModeRef,
70
+ splitRef,
71
+ } from "./config/state.js";
72
+ export type { SaveConfigOptions } from "./config/storage.js";
73
+ export {
74
+ configPaths,
75
+ loadConfig,
76
+ resetConfigCache,
77
+ saveConfig,
78
+ saveGlobalOutcomeLogging,
79
+ } from "./config/storage.js";
80
+ export type {
81
+ AdvisorConfig,
82
+ AdvisorToolPolicies,
83
+ AdvisorToolPolicy,
84
+ GateFailureMode,
85
+ } from "./config/types.js";
86
+ export {
87
+ ADVISOR_TOOL_POLICIES,
88
+ DEFAULT_ADVISOR_GIT_CONTEXT_MAX_CHARS,
89
+ DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES,
90
+ DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES,
91
+ DEFAULT_CONTEXT_MAX_CHARS,
92
+ GATE_FAILURE_MODES,
93
+ MAX_CONTEXT_MAX_CHARS,
94
+ } from "./config/types.js";
95
+ export {
96
+ isValidAdvisorToolPolicies,
97
+ isValidContextMaxChars,
98
+ isValidGateFailureMode,
99
+ isValidLoopThreshold,
100
+ isValidMaxCallsPerSession,
101
+ isValidToolResultMaxBytes,
102
+ isValidToolResultMaxLines,
103
+ validateConfig,
104
+ } from "./config/validation.js";