pi-advisor-flow 0.5.5 → 0.6.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/CHANGELOG.md +28 -1
- package/README.md +1 -1
- package/dist/index.js +5786 -0
- package/extensions/index.ts +9 -9
- package/package.json +16 -10
- package/src/{untracked.ts → attachments.ts} +2 -5
- package/src/commands/activation-preparation.ts +6 -6
- package/src/commands/activation.ts +7 -7
- package/src/commands/lifecycle.ts +5 -5
- package/src/commands/manual-command.ts +13 -9
- package/src/commands/manual-consultation.ts +8 -8
- package/src/commands/manual-progress.ts +3 -3
- package/src/commands/model-commands.ts +6 -6
- package/src/commands/model-options.ts +4 -4
- package/src/commands/model-picker.ts +2 -2
- package/src/commands/registration.ts +8 -8
- package/src/commands/renderers.ts +5 -5
- package/src/commands/runtime.ts +8 -8
- package/src/commands/settings-commands.ts +8 -8
- package/src/commands/settings-persistence.ts +4 -4
- package/src/commands/types.ts +5 -13
- package/src/commands.ts +2 -2
- package/src/config/args.ts +3 -3
- package/src/config/defaults.ts +3 -3
- package/src/config/schema.ts +322 -0
- package/src/config/state.ts +2 -2
- package/src/config/storage.ts +113 -103
- package/src/config/types.ts +1 -1
- package/src/config/validation.ts +61 -181
- package/src/config.ts +7 -7
- package/src/content-utils.ts +30 -0
- package/src/conversation.ts +13 -168
- package/src/git.ts +4 -18
- package/src/herdr.ts +6 -8
- package/src/model-stream.ts +5 -8
- package/src/outcomes.ts +2 -5
- package/src/pi-settings.ts +59 -0
- package/src/preferences.ts +2 -2
- package/src/redaction.ts +50 -0
- package/src/scout-context.ts +121 -485
- package/src/scout-curation.ts +84 -0
- package/src/scout-groups.ts +485 -0
- package/src/scout-protocol.ts +122 -0
- package/src/scout-reconstruct.ts +44 -0
- package/src/scout-types.ts +66 -0
- package/src/scout.ts +53 -63
- package/src/session-state.ts +119 -76
- package/src/tool-result-cap.ts +88 -0
- package/src/tools/consult-context.ts +155 -0
- package/src/tools/consultation.ts +71 -220
- package/src/tools/gate-policy.ts +5 -5
- package/src/tools/gate-protocol.ts +2 -2
- package/src/tools/loop-gate.ts +70 -61
- package/src/tools/prompts.ts +4 -7
- package/src/tools/register-ask-advisor.ts +9 -14
- package/src/tools/register-lifecycle.ts +7 -7
- package/src/tools/register-outcome.ts +5 -5
- package/src/tools/register-renderers.ts +5 -5
- package/src/tools/registration.ts +10 -10
- package/src/tools/render-advisor-result.ts +11 -8
- package/src/tools/render-common.ts +25 -13
- package/src/tools/scout-status.ts +5 -5
- package/src/tools/session.ts +1 -1
- package/src/tools/types.ts +7 -7
- package/src/tools.ts +9 -9
- package/src/ui/manual-dialog-render.ts +10 -3
- package/src/ui/manual-dialog.ts +4 -9
- package/src/ui/model-selector.ts +7 -6
- package/src/ui/settings-formatting.ts +2 -2
- package/src/ui/settings-items.ts +4 -4
- package/src/ui/settings-mutations.ts +2 -2
- package/src/ui/settings-selector.ts +5 -5
- package/src/ui/text-setting-submenu.ts +6 -1
- package/src/ui/types.ts +2 -2
- package/src/ui.ts +4 -4
- package/src/usage.ts +1 -6
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { GIT_CONTEXT_LEVELS, isValidGitContextLevel } from "../git.ts";
|
|
2
|
+
import {
|
|
3
|
+
advisorAutoLoopGateRef,
|
|
4
|
+
advisorBlockOnBlockedRef,
|
|
5
|
+
advisorCollapseResponsesRef,
|
|
6
|
+
advisorCompletionGateRef,
|
|
7
|
+
advisorCustomInvocationRef,
|
|
8
|
+
advisorEffortRef,
|
|
9
|
+
advisorFailureGateRef,
|
|
10
|
+
advisorFailureModeRef,
|
|
11
|
+
advisorGitContextMaxCharsRef,
|
|
12
|
+
advisorGitContextRef,
|
|
13
|
+
advisorHerdrIntegrationRef,
|
|
14
|
+
advisorLoopThresholdRef,
|
|
15
|
+
advisorMaxCallsPerSessionRef,
|
|
16
|
+
advisorOutcomeLoggingRef,
|
|
17
|
+
advisorPlanGateRef,
|
|
18
|
+
advisorRedactSecretsRef,
|
|
19
|
+
advisorRef,
|
|
20
|
+
advisorScoutEnabledRef,
|
|
21
|
+
advisorSessionSummaryRef,
|
|
22
|
+
advisorToolPoliciesRef,
|
|
23
|
+
advisorToolResultMaxBytesRef,
|
|
24
|
+
advisorToolResultMaxLinesRef,
|
|
25
|
+
advisorTrackedFileContentRef,
|
|
26
|
+
advisorUntrackedContentRef,
|
|
27
|
+
alwaysOnRef,
|
|
28
|
+
contextMaxCharsRef,
|
|
29
|
+
executorEffortRef,
|
|
30
|
+
executorRef,
|
|
31
|
+
showUsageDetailsRef,
|
|
32
|
+
showUsageFooterRef,
|
|
33
|
+
simpleModeRef,
|
|
34
|
+
} from "./state.ts";
|
|
35
|
+
import {
|
|
36
|
+
ADVISOR_TOOL_POLICIES,
|
|
37
|
+
type AdvisorConfig,
|
|
38
|
+
GATE_FAILURE_MODES,
|
|
39
|
+
MAX_CONTEXT_MAX_CHARS,
|
|
40
|
+
} from "./types.ts";
|
|
41
|
+
|
|
42
|
+
/** An empty ref means no model has been selected yet. */
|
|
43
|
+
const configuredModelRef = (value: string | undefined): string | undefined =>
|
|
44
|
+
value?.trim() || undefined;
|
|
45
|
+
|
|
46
|
+
export const isValidAdvisorToolPolicies = (
|
|
47
|
+
value: unknown
|
|
48
|
+
): value is Record<string, (typeof ADVISOR_TOOL_POLICIES)[number]> => {
|
|
49
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return Object.entries(value).every(
|
|
53
|
+
([toolName, policy]) =>
|
|
54
|
+
toolName.trim().length > 0 &&
|
|
55
|
+
typeof policy === "string" &&
|
|
56
|
+
ADVISOR_TOOL_POLICIES.includes(
|
|
57
|
+
policy as (typeof ADVISOR_TOOL_POLICIES)[number]
|
|
58
|
+
)
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const nonNegativeSafeInteger = (value: unknown): value is number =>
|
|
63
|
+
typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
64
|
+
|
|
65
|
+
export const isValidContextMaxChars = (value: unknown): value is number =>
|
|
66
|
+
typeof value === "number" &&
|
|
67
|
+
Number.isSafeInteger(value) &&
|
|
68
|
+
value >= 0 &&
|
|
69
|
+
value <= MAX_CONTEXT_MAX_CHARS;
|
|
70
|
+
|
|
71
|
+
export const isValidLoopThreshold = (value: unknown): value is number =>
|
|
72
|
+
typeof value === "number" && Number.isSafeInteger(value) && value >= 2;
|
|
73
|
+
|
|
74
|
+
export const isValidMaxCallsPerSession = (value: unknown): value is number =>
|
|
75
|
+
nonNegativeSafeInteger(value);
|
|
76
|
+
|
|
77
|
+
export const isValidGateFailureMode = (
|
|
78
|
+
value: unknown
|
|
79
|
+
): value is (typeof GATE_FAILURE_MODES)[number] =>
|
|
80
|
+
typeof value === "string" &&
|
|
81
|
+
GATE_FAILURE_MODES.includes(value as (typeof GATE_FAILURE_MODES)[number]);
|
|
82
|
+
|
|
83
|
+
export const isValidToolResultMaxLines = (value: unknown): value is number =>
|
|
84
|
+
nonNegativeSafeInteger(value);
|
|
85
|
+
|
|
86
|
+
export const isValidToolResultMaxBytes = (value: unknown): value is number =>
|
|
87
|
+
nonNegativeSafeInteger(value);
|
|
88
|
+
|
|
89
|
+
/** One declarative entry per AdvisorConfig key: JSON type, persistence, and
|
|
90
|
+
* the live runtime value behind it. Validation and storage derive from this
|
|
91
|
+
* table; adding a setting means adding exactly one entry here. */
|
|
92
|
+
export interface ConfigKeySchema {
|
|
93
|
+
/** Human-readable accepted-value phrase for error messages. */
|
|
94
|
+
accepted: string;
|
|
95
|
+
/** Live runtime value captured by saveConfig. */
|
|
96
|
+
current: () => unknown;
|
|
97
|
+
/** Included in the state captured and diffed by saveConfig. */
|
|
98
|
+
persisted: boolean;
|
|
99
|
+
/** JSON value type used for the base type check. */
|
|
100
|
+
type: "string" | "boolean" | "number" | "enum" | "object";
|
|
101
|
+
/** Type beyond the JSON type, for enum and object keys. */
|
|
102
|
+
validate?: (value: unknown) => boolean;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const CONFIG_SCHEMA = {
|
|
106
|
+
advisor: {
|
|
107
|
+
accepted: "a provider/model string",
|
|
108
|
+
current: () => configuredModelRef(advisorRef),
|
|
109
|
+
persisted: true,
|
|
110
|
+
type: "string",
|
|
111
|
+
},
|
|
112
|
+
advisorAutoLoopGate: {
|
|
113
|
+
accepted: "true or false",
|
|
114
|
+
current: () => advisorAutoLoopGateRef,
|
|
115
|
+
persisted: true,
|
|
116
|
+
type: "boolean",
|
|
117
|
+
},
|
|
118
|
+
advisorBlockOnBlocked: {
|
|
119
|
+
accepted: "true or false",
|
|
120
|
+
current: () => advisorBlockOnBlockedRef,
|
|
121
|
+
persisted: true,
|
|
122
|
+
type: "boolean",
|
|
123
|
+
},
|
|
124
|
+
advisorCollapseResponses: {
|
|
125
|
+
accepted: "true or false",
|
|
126
|
+
current: () => advisorCollapseResponsesRef,
|
|
127
|
+
persisted: true,
|
|
128
|
+
type: "boolean",
|
|
129
|
+
},
|
|
130
|
+
advisorCompletionGate: {
|
|
131
|
+
accepted: "true or false",
|
|
132
|
+
current: () => advisorCompletionGateRef,
|
|
133
|
+
persisted: true,
|
|
134
|
+
type: "boolean",
|
|
135
|
+
},
|
|
136
|
+
advisorCustomInvocation: {
|
|
137
|
+
accepted: "a string",
|
|
138
|
+
current: () => advisorCustomInvocationRef,
|
|
139
|
+
persisted: true,
|
|
140
|
+
type: "string",
|
|
141
|
+
},
|
|
142
|
+
advisorEffort: {
|
|
143
|
+
accepted: "a string",
|
|
144
|
+
current: () => advisorEffortRef,
|
|
145
|
+
persisted: true,
|
|
146
|
+
type: "string",
|
|
147
|
+
},
|
|
148
|
+
advisorFailureGate: {
|
|
149
|
+
accepted: "true or false",
|
|
150
|
+
current: () => advisorFailureGateRef,
|
|
151
|
+
persisted: true,
|
|
152
|
+
type: "boolean",
|
|
153
|
+
},
|
|
154
|
+
advisorGitContext: {
|
|
155
|
+
accepted: GIT_CONTEXT_LEVELS.join(", "),
|
|
156
|
+
current: () => advisorGitContextRef,
|
|
157
|
+
persisted: true,
|
|
158
|
+
type: "enum",
|
|
159
|
+
validate: isValidGitContextLevel,
|
|
160
|
+
},
|
|
161
|
+
advisorGitContextMaxChars: {
|
|
162
|
+
accepted: "a non-negative safe integer",
|
|
163
|
+
current: () => advisorGitContextMaxCharsRef,
|
|
164
|
+
persisted: true,
|
|
165
|
+
type: "number",
|
|
166
|
+
validate: nonNegativeSafeInteger,
|
|
167
|
+
},
|
|
168
|
+
advisorHerdrIntegration: {
|
|
169
|
+
accepted: "true or false",
|
|
170
|
+
current: () => advisorHerdrIntegrationRef,
|
|
171
|
+
persisted: true,
|
|
172
|
+
type: "boolean",
|
|
173
|
+
},
|
|
174
|
+
advisorLoopThreshold: {
|
|
175
|
+
accepted: "a safe integer of at least 2",
|
|
176
|
+
current: () => advisorLoopThresholdRef,
|
|
177
|
+
persisted: true,
|
|
178
|
+
type: "number",
|
|
179
|
+
validate: isValidLoopThreshold,
|
|
180
|
+
},
|
|
181
|
+
advisorMaxCallsPerSession: {
|
|
182
|
+
accepted: "a non-negative safe integer",
|
|
183
|
+
current: () => advisorMaxCallsPerSessionRef,
|
|
184
|
+
persisted: true,
|
|
185
|
+
type: "number",
|
|
186
|
+
validate: isValidMaxCallsPerSession,
|
|
187
|
+
},
|
|
188
|
+
advisorOutcomeLogging: {
|
|
189
|
+
accepted: "true or false",
|
|
190
|
+
current: () => advisorOutcomeLoggingRef,
|
|
191
|
+
persisted: false,
|
|
192
|
+
type: "boolean",
|
|
193
|
+
},
|
|
194
|
+
advisorPlanGate: {
|
|
195
|
+
accepted: "true or false",
|
|
196
|
+
current: () => advisorPlanGateRef,
|
|
197
|
+
persisted: true,
|
|
198
|
+
type: "boolean",
|
|
199
|
+
},
|
|
200
|
+
advisorRedactSecrets: {
|
|
201
|
+
accepted: "true or false",
|
|
202
|
+
current: () => advisorRedactSecretsRef,
|
|
203
|
+
persisted: true,
|
|
204
|
+
type: "boolean",
|
|
205
|
+
},
|
|
206
|
+
advisorScoutEnabled: {
|
|
207
|
+
accepted: "true or false",
|
|
208
|
+
current: () => advisorScoutEnabledRef,
|
|
209
|
+
persisted: true,
|
|
210
|
+
type: "boolean",
|
|
211
|
+
},
|
|
212
|
+
advisorSessionSummary: {
|
|
213
|
+
accepted: "true or false",
|
|
214
|
+
current: () => advisorSessionSummaryRef,
|
|
215
|
+
persisted: true,
|
|
216
|
+
type: "boolean",
|
|
217
|
+
},
|
|
218
|
+
advisorToolPolicies: {
|
|
219
|
+
accepted:
|
|
220
|
+
"a JSON object with non-empty tool names and full, summary, or exclude values",
|
|
221
|
+
current: () => ({ ...advisorToolPoliciesRef }),
|
|
222
|
+
persisted: true,
|
|
223
|
+
type: "object",
|
|
224
|
+
validate: isValidAdvisorToolPolicies,
|
|
225
|
+
},
|
|
226
|
+
advisorToolResultMaxBytes: {
|
|
227
|
+
accepted: "a non-negative safe integer",
|
|
228
|
+
current: () => advisorToolResultMaxBytesRef,
|
|
229
|
+
persisted: true,
|
|
230
|
+
type: "number",
|
|
231
|
+
validate: isValidToolResultMaxBytes,
|
|
232
|
+
},
|
|
233
|
+
advisorToolResultMaxLines: {
|
|
234
|
+
accepted: "a non-negative safe integer",
|
|
235
|
+
current: () => advisorToolResultMaxLinesRef,
|
|
236
|
+
persisted: true,
|
|
237
|
+
type: "number",
|
|
238
|
+
validate: isValidToolResultMaxLines,
|
|
239
|
+
},
|
|
240
|
+
advisorTrackedFileContent: {
|
|
241
|
+
accepted: "true or false",
|
|
242
|
+
current: () => advisorTrackedFileContentRef,
|
|
243
|
+
persisted: true,
|
|
244
|
+
type: "boolean",
|
|
245
|
+
},
|
|
246
|
+
advisorUntrackedContent: {
|
|
247
|
+
accepted: "true or false",
|
|
248
|
+
current: () => advisorUntrackedContentRef,
|
|
249
|
+
persisted: true,
|
|
250
|
+
type: "boolean",
|
|
251
|
+
},
|
|
252
|
+
alwaysOn: {
|
|
253
|
+
accepted: "true or false",
|
|
254
|
+
current: () => alwaysOnRef,
|
|
255
|
+
persisted: true,
|
|
256
|
+
type: "boolean",
|
|
257
|
+
},
|
|
258
|
+
contextMaxChars: {
|
|
259
|
+
accepted: `a safe integer from 0 through ${MAX_CONTEXT_MAX_CHARS}`,
|
|
260
|
+
current: () => contextMaxCharsRef,
|
|
261
|
+
persisted: true,
|
|
262
|
+
type: "number",
|
|
263
|
+
validate: isValidContextMaxChars,
|
|
264
|
+
},
|
|
265
|
+
executor: {
|
|
266
|
+
accepted: "a provider/model string",
|
|
267
|
+
current: () => configuredModelRef(executorRef),
|
|
268
|
+
persisted: true,
|
|
269
|
+
type: "string",
|
|
270
|
+
},
|
|
271
|
+
executorEffort: {
|
|
272
|
+
accepted: "a string",
|
|
273
|
+
current: () => executorEffortRef,
|
|
274
|
+
persisted: true,
|
|
275
|
+
type: "string",
|
|
276
|
+
},
|
|
277
|
+
gateFailureMode: {
|
|
278
|
+
accepted: GATE_FAILURE_MODES.join(", "),
|
|
279
|
+
current: () => advisorFailureModeRef,
|
|
280
|
+
persisted: true,
|
|
281
|
+
type: "enum",
|
|
282
|
+
validate: isValidGateFailureMode,
|
|
283
|
+
},
|
|
284
|
+
showUsageDetails: {
|
|
285
|
+
accepted: "true or false",
|
|
286
|
+
current: () => showUsageDetailsRef,
|
|
287
|
+
persisted: true,
|
|
288
|
+
type: "boolean",
|
|
289
|
+
},
|
|
290
|
+
showUsageFooter: {
|
|
291
|
+
accepted: "true or false",
|
|
292
|
+
current: () => showUsageFooterRef,
|
|
293
|
+
persisted: true,
|
|
294
|
+
type: "boolean",
|
|
295
|
+
},
|
|
296
|
+
simpleMode: {
|
|
297
|
+
accepted: "true or false",
|
|
298
|
+
current: () => simpleModeRef,
|
|
299
|
+
persisted: true,
|
|
300
|
+
type: "boolean",
|
|
301
|
+
},
|
|
302
|
+
} as const satisfies Record<keyof AdvisorConfig, ConfigKeySchema>;
|
|
303
|
+
|
|
304
|
+
export type ConfigKey = keyof typeof CONFIG_SCHEMA;
|
|
305
|
+
|
|
306
|
+
/** Keys saveConfig captures, diffs, and writes to advisor.json. */
|
|
307
|
+
export type PersistedConfigKey = {
|
|
308
|
+
[Key in ConfigKey]: (typeof CONFIG_SCHEMA)[Key]["persisted"] extends true
|
|
309
|
+
? Key
|
|
310
|
+
: never;
|
|
311
|
+
}[ConfigKey];
|
|
312
|
+
|
|
313
|
+
export const SAVED_CONFIG_KEYS = (
|
|
314
|
+
Object.keys(CONFIG_SCHEMA) as ConfigKey[]
|
|
315
|
+
).filter(
|
|
316
|
+
(key) => CONFIG_SCHEMA[key].persisted
|
|
317
|
+
) as readonly PersistedConfigKey[];
|
|
318
|
+
|
|
319
|
+
/** Widened per-key view for consumers that index by dynamic key. */
|
|
320
|
+
const SCHEMA_BY_KEY: Record<ConfigKey, ConfigKeySchema> = CONFIG_SCHEMA;
|
|
321
|
+
|
|
322
|
+
export { configuredModelRef, SCHEMA_BY_KEY };
|
package/src/config/state.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { GitContextLevel } from "../git.
|
|
1
|
+
import type { GitContextLevel } from "../git.ts";
|
|
2
2
|
import {
|
|
3
3
|
type AdvisorToolPolicies,
|
|
4
4
|
DEFAULT_ADVISOR_GIT_CONTEXT_MAX_CHARS,
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES,
|
|
7
7
|
DEFAULT_CONTEXT_MAX_CHARS,
|
|
8
8
|
type GateFailureMode,
|
|
9
|
-
} from "./types.
|
|
9
|
+
} from "./types.ts";
|
|
10
10
|
|
|
11
11
|
// An empty ref means no model has been selected yet.
|
|
12
12
|
export let executorRef = "";
|
package/src/config/storage.ts
CHANGED
|
@@ -5,43 +5,16 @@ import {
|
|
|
5
5
|
type ExtensionContext,
|
|
6
6
|
getAgentDir,
|
|
7
7
|
} from "@earendil-works/pi-coding-agent";
|
|
8
|
-
import { applyConfig, resetDefaults } from "./defaults.
|
|
8
|
+
import { applyConfig, resetDefaults } from "./defaults.ts";
|
|
9
9
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
advisorGitContextMaxCharsRef,
|
|
19
|
-
advisorGitContextRef,
|
|
20
|
-
advisorHerdrIntegrationRef,
|
|
21
|
-
advisorLoopThresholdRef,
|
|
22
|
-
advisorMaxCallsPerSessionRef,
|
|
23
|
-
advisorPlanGateRef,
|
|
24
|
-
advisorRedactSecretsRef,
|
|
25
|
-
advisorRef,
|
|
26
|
-
advisorScoutEnabledRef,
|
|
27
|
-
advisorSessionSummaryRef,
|
|
28
|
-
advisorToolPoliciesRef,
|
|
29
|
-
advisorToolResultMaxBytesRef,
|
|
30
|
-
advisorToolResultMaxLinesRef,
|
|
31
|
-
advisorTrackedFileContentRef,
|
|
32
|
-
advisorUntrackedContentRef,
|
|
33
|
-
alwaysOnRef,
|
|
34
|
-
contextMaxCharsRef,
|
|
35
|
-
executorEffortRef,
|
|
36
|
-
executorRef,
|
|
37
|
-
setAdvisorOutcomeLoggingRef,
|
|
38
|
-
setPersistedModelRefs,
|
|
39
|
-
showUsageDetailsRef,
|
|
40
|
-
showUsageFooterRef,
|
|
41
|
-
simpleModeRef,
|
|
42
|
-
} from "./state.js";
|
|
43
|
-
import type { AdvisorConfig } from "./types.js";
|
|
44
|
-
import { unknownConfigKeys, validateConfig } from "./validation.js";
|
|
10
|
+
CONFIG_SCHEMA,
|
|
11
|
+
configuredModelRef,
|
|
12
|
+
type PersistedConfigKey,
|
|
13
|
+
SAVED_CONFIG_KEYS,
|
|
14
|
+
} from "./schema.ts";
|
|
15
|
+
import { setAdvisorOutcomeLoggingRef, setPersistedModelRefs } from "./state.ts";
|
|
16
|
+
import type { AdvisorConfig } from "./types.ts";
|
|
17
|
+
import { unknownConfigKeys, validateConfig } from "./validation.ts";
|
|
45
18
|
|
|
46
19
|
export const configPaths = (ctx: ExtensionContext) => [
|
|
47
20
|
ctx.isProjectTrusted()
|
|
@@ -50,22 +23,71 @@ export const configPaths = (ctx: ExtensionContext) => [
|
|
|
50
23
|
join(getAgentDir(), "advisor.json"),
|
|
51
24
|
];
|
|
52
25
|
|
|
53
|
-
|
|
54
|
-
|
|
26
|
+
type SavedConfigKey = PersistedConfigKey;
|
|
27
|
+
type ConfigState = {
|
|
28
|
+
[Key in SavedConfigKey]: AdvisorConfig[Key];
|
|
29
|
+
};
|
|
55
30
|
|
|
56
|
-
const
|
|
31
|
+
const currentConfigState = (): ConfigState =>
|
|
32
|
+
Object.fromEntries(
|
|
33
|
+
SAVED_CONFIG_KEYS.map((key) => [key, CONFIG_SCHEMA[key].current()])
|
|
34
|
+
) as ConfigState;
|
|
35
|
+
|
|
36
|
+
const sameConfigValue = <Value>(left: Value, right: Value) =>
|
|
37
|
+
JSON.stringify(left) === JSON.stringify(right);
|
|
38
|
+
|
|
39
|
+
const readExistingConfig = (path: string): Record<string, unknown> => {
|
|
57
40
|
try {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
41
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
42
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
43
|
+
? (parsed as Record<string, unknown>)
|
|
44
|
+
: {};
|
|
45
|
+
} catch {
|
|
46
|
+
return {};
|
|
63
47
|
}
|
|
64
48
|
};
|
|
65
49
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
50
|
+
const shouldPersistConfigKey = (
|
|
51
|
+
key: SavedConfigKey,
|
|
52
|
+
persistAdvisor: boolean,
|
|
53
|
+
persistExecutor: boolean
|
|
54
|
+
) =>
|
|
55
|
+
(key !== "advisor" || persistAdvisor) &&
|
|
56
|
+
(key !== "executor" || persistExecutor);
|
|
57
|
+
|
|
58
|
+
const applyChangedConfigValues = (
|
|
59
|
+
data: Record<string, unknown>,
|
|
60
|
+
current: ConfigState,
|
|
61
|
+
changedKeys: readonly SavedConfigKey[],
|
|
62
|
+
persistAdvisor: boolean,
|
|
63
|
+
persistExecutor: boolean
|
|
64
|
+
) => {
|
|
65
|
+
for (const key of changedKeys) {
|
|
66
|
+
if (!shouldPersistConfigKey(key, persistAdvisor, persistExecutor)) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const value = current[key];
|
|
70
|
+
const isEmptyModelRef = (key === "advisor" || key === "executor") && !value;
|
|
71
|
+
if (value === undefined || isEmptyModelRef) {
|
|
72
|
+
delete data[key];
|
|
73
|
+
} else {
|
|
74
|
+
data[key] = value;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Tracks the last load/save state so a later save merges fresh disk contents instead of overwriting an external edit.
|
|
80
|
+
let loadedConfigState: ConfigState | undefined;
|
|
81
|
+
let loadedConfigPath: string | undefined;
|
|
82
|
+
|
|
83
|
+
const readConfig = (path: string): AdvisorConfig => {
|
|
84
|
+
const config = JSON.parse(readFileSync(path, "utf8"));
|
|
85
|
+
validateConfig(config, path);
|
|
86
|
+
return config;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// loadConfig runs per tool call and consultation; caching by path and stat
|
|
90
|
+
// identity keeps the parse off the hot path while still reset-applying each call.
|
|
69
91
|
const configCache = new Map<
|
|
70
92
|
string,
|
|
71
93
|
{ config: AdvisorConfig; identity: string }
|
|
@@ -135,10 +157,17 @@ export const loadConfig = (_ctx: ExtensionContext) => {
|
|
|
135
157
|
// prompts, gates, budgets, disclosure, redaction, integrations, and consent
|
|
136
158
|
// remain under the user's global Pi configuration.
|
|
137
159
|
setAdvisorOutcomeLoggingRef(globalConfig?.advisorOutcomeLogging === true);
|
|
160
|
+
loadedConfigState = currentConfigState();
|
|
161
|
+
loadedConfigPath = global;
|
|
138
162
|
return existsSync(global) ? global : null;
|
|
139
163
|
};
|
|
140
164
|
|
|
141
|
-
/**
|
|
165
|
+
/**
|
|
166
|
+
* Saves user-controlled configuration globally without outcome consent.
|
|
167
|
+
*
|
|
168
|
+
* Only values changed since the last load/save are written. This lets an
|
|
169
|
+
* external edit to advisor.json coexist with a later settings or model save.
|
|
170
|
+
*/
|
|
142
171
|
export interface SaveConfigOptions {
|
|
143
172
|
persistAdvisor?: boolean;
|
|
144
173
|
persistExecutor?: boolean;
|
|
@@ -151,70 +180,51 @@ export const saveConfig = (
|
|
|
151
180
|
const path = join(getAgentDir(), "advisor.json");
|
|
152
181
|
const persistAdvisor = options.persistAdvisor ?? true;
|
|
153
182
|
const persistExecutor = options.persistExecutor ?? true;
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
existing.advisorMaxCallsPerSession = undefined;
|
|
183
|
+
const existing = readExistingConfig(path);
|
|
184
|
+
const current = currentConfigState();
|
|
185
|
+
const baseline = loadedConfigPath === path ? loadedConfigState : undefined;
|
|
186
|
+
const changedKeys = baseline
|
|
187
|
+
? SAVED_CONFIG_KEYS.filter(
|
|
188
|
+
(key) => !sameConfigValue(current[key], baseline[key])
|
|
189
|
+
)
|
|
190
|
+
: [...SAVED_CONFIG_KEYS];
|
|
191
|
+
if (changedKeys.length === 0) {
|
|
192
|
+
return path;
|
|
165
193
|
}
|
|
166
|
-
const data = {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
advisorEffort: advisorEffortRef,
|
|
175
|
-
advisorFailureGate: advisorFailureGateRef,
|
|
176
|
-
advisorLoopThreshold: advisorLoopThresholdRef,
|
|
177
|
-
advisorPlanGate: advisorPlanGateRef,
|
|
178
|
-
contextMaxChars: contextMaxCharsRef,
|
|
179
|
-
...(persistExecutor && executorRef ? { executor: executorRef } : {}),
|
|
180
|
-
executorEffort: executorEffortRef,
|
|
181
|
-
...(advisorMaxCallsPerSessionRef === undefined
|
|
182
|
-
? {}
|
|
183
|
-
: { advisorMaxCallsPerSession: advisorMaxCallsPerSessionRef }),
|
|
184
|
-
advisorGitContext: advisorGitContextRef,
|
|
185
|
-
advisorGitContextMaxChars: advisorGitContextMaxCharsRef,
|
|
186
|
-
advisorHerdrIntegration: advisorHerdrIntegrationRef,
|
|
187
|
-
advisorRedactSecrets: advisorRedactSecretsRef,
|
|
188
|
-
advisorScoutEnabled: advisorScoutEnabledRef,
|
|
189
|
-
advisorSessionSummary: advisorSessionSummaryRef,
|
|
190
|
-
advisorToolPolicies: advisorToolPoliciesRef,
|
|
191
|
-
advisorToolResultMaxBytes: advisorToolResultMaxBytesRef,
|
|
192
|
-
advisorToolResultMaxLines: advisorToolResultMaxLinesRef,
|
|
193
|
-
advisorTrackedFileContent: advisorTrackedFileContentRef,
|
|
194
|
-
advisorUntrackedContent: advisorUntrackedContentRef,
|
|
195
|
-
alwaysOn: alwaysOnRef,
|
|
196
|
-
gateFailureMode: advisorFailureModeRef,
|
|
197
|
-
showUsageDetails: showUsageDetailsRef,
|
|
198
|
-
showUsageFooter: showUsageFooterRef,
|
|
199
|
-
simpleMode: simpleModeRef,
|
|
200
|
-
};
|
|
194
|
+
const data = { ...existing };
|
|
195
|
+
applyChangedConfigValues(
|
|
196
|
+
data,
|
|
197
|
+
current,
|
|
198
|
+
changedKeys,
|
|
199
|
+
persistAdvisor,
|
|
200
|
+
persistExecutor
|
|
201
|
+
);
|
|
201
202
|
writeFileSync(path, `${JSON.stringify(data, null, 2)}\n`);
|
|
202
203
|
resetConfigCache();
|
|
204
|
+
const nextLoadedState = { ...current };
|
|
205
|
+
if (!persistAdvisor) {
|
|
206
|
+
nextLoadedState.advisor = baseline
|
|
207
|
+
? baseline.advisor
|
|
208
|
+
: configuredModelRef(
|
|
209
|
+
typeof existing.advisor === "string" ? existing.advisor : undefined
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
if (!persistExecutor) {
|
|
213
|
+
nextLoadedState.executor = baseline
|
|
214
|
+
? baseline.executor
|
|
215
|
+
: configuredModelRef(
|
|
216
|
+
typeof existing.executor === "string" ? existing.executor : undefined
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
loadedConfigState = nextLoadedState;
|
|
220
|
+
loadedConfigPath = path;
|
|
203
221
|
return path;
|
|
204
222
|
};
|
|
205
223
|
|
|
206
224
|
/** Outcome logging is deliberately written only to the global Pi configuration. */
|
|
207
225
|
export const saveGlobalOutcomeLogging = (enabled: boolean) => {
|
|
208
226
|
const path = join(getAgentDir(), "advisor.json");
|
|
209
|
-
|
|
210
|
-
try {
|
|
211
|
-
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
212
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
213
|
-
existing = parsed as Record<string, unknown>;
|
|
214
|
-
}
|
|
215
|
-
} catch {
|
|
216
|
-
/* create it */
|
|
217
|
-
}
|
|
227
|
+
const existing = readExistingConfig(path);
|
|
218
228
|
writeFileSync(
|
|
219
229
|
path,
|
|
220
230
|
`${JSON.stringify({ ...existing, advisorOutcomeLogging: enabled }, null, 2)}\n`
|
package/src/config/types.ts
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
DEFAULT_MAX_BYTES,
|
|
3
3
|
DEFAULT_MAX_LINES,
|
|
4
4
|
} from "@earendil-works/pi-coding-agent";
|
|
5
|
-
import type { GitContextLevel } from "../git.
|
|
5
|
+
import type { GitContextLevel } from "../git.ts";
|
|
6
6
|
|
|
7
7
|
export const DEFAULT_CONTEXT_MAX_CHARS = 15_000;
|
|
8
8
|
export const MAX_CONTEXT_MAX_CHARS = Number.MAX_SAFE_INTEGER;
|