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.
- package/CHANGELOG.md +54 -0
- package/README.md +37 -71
- package/extensions/index.ts +7 -7
- package/package.json +19 -6
- package/src/commands/activation-preparation.ts +117 -0
- package/src/commands/activation.ts +135 -0
- package/src/commands/lifecycle.ts +83 -0
- package/src/commands/manual-command.ts +107 -0
- package/src/commands/manual-consultation.ts +144 -0
- package/src/commands/manual-progress.ts +93 -0
- package/src/commands/model-commands.ts +66 -0
- package/src/commands/model-options.ts +181 -0
- package/src/commands/model-picker.ts +110 -0
- package/src/commands/registration.ts +27 -0
- package/src/commands/renderers.ts +76 -0
- package/src/commands/runtime.ts +114 -0
- package/src/commands/settings-commands.ts +80 -0
- package/src/commands/settings-persistence.ts +81 -0
- package/src/commands/types.ts +78 -0
- package/src/commands.ts +9 -946
- package/src/config/args.ts +37 -0
- package/src/config/defaults.ts +190 -0
- package/src/config/state.ts +199 -0
- package/src/config/storage.ts +224 -0
- package/src/config/types.ts +63 -0
- package/src/config/validation.ts +234 -0
- package/src/config.ts +104 -791
- package/src/conversation.ts +4 -2
- package/src/herdr.ts +1 -1
- package/src/model-stream.ts +25 -2
- package/src/scout-context.ts +2 -2
- package/src/scout.ts +1 -1
- package/src/tools/consultation.ts +337 -0
- package/src/tools/gate-policy.ts +132 -0
- package/src/tools/gate-protocol.ts +127 -0
- package/src/tools/loop-gate.ts +210 -0
- package/src/tools/prompts.ts +159 -0
- package/src/tools/register-ask-advisor.ts +225 -0
- package/src/tools/register-lifecycle.ts +89 -0
- package/src/tools/register-outcome.ts +75 -0
- package/src/tools/register-renderers.ts +88 -0
- package/src/tools/registration.ts +35 -0
- package/src/tools/render-advisor-result.ts +168 -0
- package/src/tools/render-common.ts +120 -0
- package/src/tools/scout-status.ts +206 -0
- package/src/tools/session.ts +3 -0
- package/src/tools/types.ts +119 -0
- package/src/tools.ts +42 -1811
- package/src/ui/manual-dialog-render.ts +150 -0
- package/src/ui/manual-dialog.ts +305 -0
- package/src/ui/model-selector.ts +166 -0
- package/src/ui/settings-formatting.ts +133 -0
- package/src/ui/settings-items.ts +323 -0
- package/src/ui/settings-list-adapter.ts +103 -0
- package/src/ui/settings-mutations.ts +80 -0
- package/src/ui/settings-selector.ts +161 -0
- package/src/ui/text-setting-submenu.ts +66 -0
- package/src/ui/types.ts +111 -0
- package/src/ui.ts +9 -1418
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {
|
|
2
|
+
advisorRef,
|
|
3
|
+
contextMaxCharsRef,
|
|
4
|
+
executorRef,
|
|
5
|
+
setAdvisorRef,
|
|
6
|
+
setContextMaxCharsRef,
|
|
7
|
+
setExecutorRef,
|
|
8
|
+
} from "./state.js";
|
|
9
|
+
import { MAX_CONTEXT_MAX_CHARS } from "./types.js";
|
|
10
|
+
import { isValidContextMaxChars } from "./validation.js";
|
|
11
|
+
|
|
12
|
+
const ARGUMENT_WHITESPACE = /\s+/;
|
|
13
|
+
|
|
14
|
+
export const parseArgs = (args: string): string | undefined => {
|
|
15
|
+
let nextExecutor = executorRef;
|
|
16
|
+
let nextAdvisor = advisorRef;
|
|
17
|
+
let nextContextMaxChars = contextMaxCharsRef;
|
|
18
|
+
for (const token of args.trim().split(ARGUMENT_WHITESPACE).filter(Boolean)) {
|
|
19
|
+
const [key, value] = token.split("=");
|
|
20
|
+
if (key === "executor" && value) {
|
|
21
|
+
nextExecutor = value;
|
|
22
|
+
}
|
|
23
|
+
if (key === "advisor" && value) {
|
|
24
|
+
nextAdvisor = value;
|
|
25
|
+
}
|
|
26
|
+
if (key === "contextMaxChars") {
|
|
27
|
+
const parsed = Number(value);
|
|
28
|
+
if (!isValidContextMaxChars(parsed)) {
|
|
29
|
+
return `contextMaxChars must be a non-negative integer no greater than ${MAX_CONTEXT_MAX_CHARS}.`;
|
|
30
|
+
}
|
|
31
|
+
nextContextMaxChars = parsed;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
setExecutorRef(nextExecutor);
|
|
35
|
+
setAdvisorRef(nextAdvisor);
|
|
36
|
+
setContextMaxCharsRef(nextContextMaxChars);
|
|
37
|
+
};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import {
|
|
2
|
+
setAdvisorAutoLoopGateRef,
|
|
3
|
+
setAdvisorBlockOnBlockedRef,
|
|
4
|
+
setAdvisorCollapseResponsesRef,
|
|
5
|
+
setAdvisorCompletionGateRef,
|
|
6
|
+
setAdvisorCustomInvocationRef,
|
|
7
|
+
setAdvisorEffortRef,
|
|
8
|
+
setAdvisorFailureGateRef,
|
|
9
|
+
setAdvisorFailureModeRef,
|
|
10
|
+
setAdvisorGitContextMaxCharsRef,
|
|
11
|
+
setAdvisorGitContextRef,
|
|
12
|
+
setAdvisorHerdrIntegrationRef,
|
|
13
|
+
setAdvisorLoopThresholdRef,
|
|
14
|
+
setAdvisorMaxCallsPerSessionRef,
|
|
15
|
+
setAdvisorOutcomeLoggingRef,
|
|
16
|
+
setAdvisorPlanGateRef,
|
|
17
|
+
setAdvisorRedactSecretsRef,
|
|
18
|
+
setAdvisorRef,
|
|
19
|
+
setAdvisorScoutEnabledRef,
|
|
20
|
+
setAdvisorSessionSummaryRef,
|
|
21
|
+
setAdvisorToolPoliciesRef,
|
|
22
|
+
setAdvisorToolResultMaxBytesRef,
|
|
23
|
+
setAdvisorToolResultMaxLinesRef,
|
|
24
|
+
setAdvisorTrackedFileContentRef,
|
|
25
|
+
setAdvisorUntrackedContentRef,
|
|
26
|
+
setAlwaysOnRef,
|
|
27
|
+
setContextMaxCharsRef,
|
|
28
|
+
setExecutorEffortRef,
|
|
29
|
+
setExecutorRef,
|
|
30
|
+
setPersistedModelRefs,
|
|
31
|
+
setShowUsageDetailsRef,
|
|
32
|
+
setShowUsageFooterRef,
|
|
33
|
+
setSimpleModeRef,
|
|
34
|
+
} from "./state.js";
|
|
35
|
+
import type { AdvisorConfig } from "./types.js";
|
|
36
|
+
import {
|
|
37
|
+
DEFAULT_ADVISOR_GIT_CONTEXT_MAX_CHARS,
|
|
38
|
+
DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES,
|
|
39
|
+
DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES,
|
|
40
|
+
DEFAULT_CONTEXT_MAX_CHARS,
|
|
41
|
+
} from "./types.js";
|
|
42
|
+
|
|
43
|
+
export const resetDefaults = () => {
|
|
44
|
+
setExecutorRef("");
|
|
45
|
+
setAdvisorRef("");
|
|
46
|
+
setPersistedModelRefs(undefined, undefined);
|
|
47
|
+
setExecutorEffortRef(undefined);
|
|
48
|
+
setAdvisorEffortRef(undefined);
|
|
49
|
+
setContextMaxCharsRef(DEFAULT_CONTEXT_MAX_CHARS);
|
|
50
|
+
setAdvisorPlanGateRef(true);
|
|
51
|
+
setAdvisorFailureGateRef(true);
|
|
52
|
+
setAdvisorCompletionGateRef(true);
|
|
53
|
+
setAdvisorCustomInvocationRef(undefined);
|
|
54
|
+
setAdvisorCollapseResponsesRef(false);
|
|
55
|
+
setAdvisorBlockOnBlockedRef(true);
|
|
56
|
+
setAdvisorAutoLoopGateRef(true);
|
|
57
|
+
setAdvisorLoopThresholdRef(3);
|
|
58
|
+
setAdvisorMaxCallsPerSessionRef(undefined);
|
|
59
|
+
setAdvisorSessionSummaryRef(false);
|
|
60
|
+
setSimpleModeRef(false);
|
|
61
|
+
setAlwaysOnRef(false);
|
|
62
|
+
setAdvisorFailureModeRef("block-session");
|
|
63
|
+
setAdvisorHerdrIntegrationRef(true);
|
|
64
|
+
setAdvisorToolResultMaxLinesRef(DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES);
|
|
65
|
+
setAdvisorToolResultMaxBytesRef(DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES);
|
|
66
|
+
setAdvisorRedactSecretsRef(false);
|
|
67
|
+
setAdvisorGitContextRef("summary");
|
|
68
|
+
setAdvisorGitContextMaxCharsRef(DEFAULT_ADVISOR_GIT_CONTEXT_MAX_CHARS);
|
|
69
|
+
setAdvisorToolPoliciesRef({});
|
|
70
|
+
setAdvisorOutcomeLoggingRef(false);
|
|
71
|
+
setAdvisorUntrackedContentRef(false);
|
|
72
|
+
setAdvisorTrackedFileContentRef(false);
|
|
73
|
+
setAdvisorScoutEnabledRef(false);
|
|
74
|
+
setShowUsageDetailsRef(true);
|
|
75
|
+
setShowUsageFooterRef(false);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const applyOptionalConfig = <Key extends keyof AdvisorConfig>(
|
|
79
|
+
config: AdvisorConfig,
|
|
80
|
+
key: Key,
|
|
81
|
+
apply: (value: NonNullable<AdvisorConfig[Key]>) => void
|
|
82
|
+
) => {
|
|
83
|
+
const value = config[key];
|
|
84
|
+
if (value !== undefined) {
|
|
85
|
+
apply(value as NonNullable<AdvisorConfig[Key]>);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const applyNonEmptyStringConfig = (
|
|
90
|
+
value: string | undefined,
|
|
91
|
+
apply: (value: string) => void
|
|
92
|
+
) => {
|
|
93
|
+
if (value) {
|
|
94
|
+
apply(value);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const applyConfig = (config: AdvisorConfig) => {
|
|
99
|
+
applyNonEmptyStringConfig(config.executor, setExecutorRef);
|
|
100
|
+
applyNonEmptyStringConfig(config.advisor, setAdvisorRef);
|
|
101
|
+
applyNonEmptyStringConfig(config.executorEffort, setExecutorEffortRef);
|
|
102
|
+
applyNonEmptyStringConfig(config.advisorEffort, setAdvisorEffortRef);
|
|
103
|
+
applyOptionalConfig(config, "contextMaxChars", setContextMaxCharsRef);
|
|
104
|
+
applyOptionalConfig(config, "advisorPlanGate", setAdvisorPlanGateRef);
|
|
105
|
+
applyOptionalConfig(config, "advisorFailureGate", setAdvisorFailureGateRef);
|
|
106
|
+
applyOptionalConfig(
|
|
107
|
+
config,
|
|
108
|
+
"advisorCompletionGate",
|
|
109
|
+
setAdvisorCompletionGateRef
|
|
110
|
+
);
|
|
111
|
+
applyOptionalConfig(
|
|
112
|
+
config,
|
|
113
|
+
"advisorCustomInvocation",
|
|
114
|
+
setAdvisorCustomInvocationRef
|
|
115
|
+
);
|
|
116
|
+
applyOptionalConfig(
|
|
117
|
+
config,
|
|
118
|
+
"advisorCollapseResponses",
|
|
119
|
+
setAdvisorCollapseResponsesRef
|
|
120
|
+
);
|
|
121
|
+
applyOptionalConfig(
|
|
122
|
+
config,
|
|
123
|
+
"advisorBlockOnBlocked",
|
|
124
|
+
setAdvisorBlockOnBlockedRef
|
|
125
|
+
);
|
|
126
|
+
applyOptionalConfig(config, "advisorAutoLoopGate", setAdvisorAutoLoopGateRef);
|
|
127
|
+
applyOptionalConfig(
|
|
128
|
+
config,
|
|
129
|
+
"advisorLoopThreshold",
|
|
130
|
+
setAdvisorLoopThresholdRef
|
|
131
|
+
);
|
|
132
|
+
applyOptionalConfig(
|
|
133
|
+
config,
|
|
134
|
+
"advisorMaxCallsPerSession",
|
|
135
|
+
setAdvisorMaxCallsPerSessionRef
|
|
136
|
+
);
|
|
137
|
+
applyOptionalConfig(
|
|
138
|
+
config,
|
|
139
|
+
"advisorSessionSummary",
|
|
140
|
+
setAdvisorSessionSummaryRef
|
|
141
|
+
);
|
|
142
|
+
applyOptionalConfig(config, "advisorScoutEnabled", setAdvisorScoutEnabledRef);
|
|
143
|
+
applyOptionalConfig(config, "showUsageDetails", setShowUsageDetailsRef);
|
|
144
|
+
applyOptionalConfig(config, "showUsageFooter", setShowUsageFooterRef);
|
|
145
|
+
applyOptionalConfig(config, "simpleMode", setSimpleModeRef);
|
|
146
|
+
applyOptionalConfig(config, "alwaysOn", setAlwaysOnRef);
|
|
147
|
+
applyOptionalConfig(config, "gateFailureMode", setAdvisorFailureModeRef);
|
|
148
|
+
applyOptionalConfig(
|
|
149
|
+
config,
|
|
150
|
+
"advisorHerdrIntegration",
|
|
151
|
+
setAdvisorHerdrIntegrationRef
|
|
152
|
+
);
|
|
153
|
+
applyOptionalConfig(
|
|
154
|
+
config,
|
|
155
|
+
"advisorToolResultMaxLines",
|
|
156
|
+
setAdvisorToolResultMaxLinesRef
|
|
157
|
+
);
|
|
158
|
+
applyOptionalConfig(
|
|
159
|
+
config,
|
|
160
|
+
"advisorToolResultMaxBytes",
|
|
161
|
+
setAdvisorToolResultMaxBytesRef
|
|
162
|
+
);
|
|
163
|
+
applyOptionalConfig(
|
|
164
|
+
config,
|
|
165
|
+
"advisorRedactSecrets",
|
|
166
|
+
setAdvisorRedactSecretsRef
|
|
167
|
+
);
|
|
168
|
+
applyOptionalConfig(config, "advisorGitContext", setAdvisorGitContextRef);
|
|
169
|
+
applyOptionalConfig(
|
|
170
|
+
config,
|
|
171
|
+
"advisorGitContextMaxChars",
|
|
172
|
+
setAdvisorGitContextMaxCharsRef
|
|
173
|
+
);
|
|
174
|
+
applyOptionalConfig(config, "advisorToolPolicies", setAdvisorToolPoliciesRef);
|
|
175
|
+
applyOptionalConfig(
|
|
176
|
+
config,
|
|
177
|
+
"advisorUntrackedContent",
|
|
178
|
+
setAdvisorUntrackedContentRef
|
|
179
|
+
);
|
|
180
|
+
applyOptionalConfig(
|
|
181
|
+
config,
|
|
182
|
+
"advisorTrackedFileContent",
|
|
183
|
+
setAdvisorTrackedFileContentRef
|
|
184
|
+
);
|
|
185
|
+
applyOptionalConfig(
|
|
186
|
+
config,
|
|
187
|
+
"advisorOutcomeLogging",
|
|
188
|
+
setAdvisorOutcomeLoggingRef
|
|
189
|
+
);
|
|
190
|
+
};
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import type { GitContextLevel } from "../git.js";
|
|
2
|
+
import {
|
|
3
|
+
type AdvisorToolPolicies,
|
|
4
|
+
DEFAULT_ADVISOR_GIT_CONTEXT_MAX_CHARS,
|
|
5
|
+
DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES,
|
|
6
|
+
DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES,
|
|
7
|
+
DEFAULT_CONTEXT_MAX_CHARS,
|
|
8
|
+
type GateFailureMode,
|
|
9
|
+
} from "./types.js";
|
|
10
|
+
|
|
11
|
+
// An empty ref means no model has been selected yet.
|
|
12
|
+
export let executorRef = "";
|
|
13
|
+
export let advisorRef = "";
|
|
14
|
+
let persistedExecutorRef: string | undefined;
|
|
15
|
+
let persistedAdvisorRef: string | undefined;
|
|
16
|
+
export let executorEffortRef: string | undefined;
|
|
17
|
+
export let advisorEffortRef: string | undefined;
|
|
18
|
+
export let contextMaxCharsRef = DEFAULT_CONTEXT_MAX_CHARS;
|
|
19
|
+
export let advisorPlanGateRef = true;
|
|
20
|
+
export let advisorFailureGateRef = true;
|
|
21
|
+
export let advisorCompletionGateRef = true;
|
|
22
|
+
export let advisorCustomInvocationRef: string | undefined;
|
|
23
|
+
export let advisorCollapseResponsesRef = false;
|
|
24
|
+
export let advisorBlockOnBlockedRef = true;
|
|
25
|
+
export let advisorAutoLoopGateRef = true;
|
|
26
|
+
export let advisorLoopThresholdRef = 3;
|
|
27
|
+
export let advisorMaxCallsPerSessionRef: number | undefined;
|
|
28
|
+
export let advisorSessionSummaryRef = false;
|
|
29
|
+
export let simpleModeRef = false;
|
|
30
|
+
export let alwaysOnRef = false;
|
|
31
|
+
export let advisorFailureModeRef: GateFailureMode = "block-session";
|
|
32
|
+
export let advisorHerdrIntegrationRef = true;
|
|
33
|
+
export let advisorToolResultMaxLinesRef = DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES;
|
|
34
|
+
export let advisorToolResultMaxBytesRef = DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES;
|
|
35
|
+
export let advisorRedactSecretsRef = false;
|
|
36
|
+
export let advisorGitContextRef: GitContextLevel = "summary";
|
|
37
|
+
export let advisorGitContextMaxCharsRef = DEFAULT_ADVISOR_GIT_CONTEXT_MAX_CHARS;
|
|
38
|
+
export let advisorToolPoliciesRef: AdvisorToolPolicies = {};
|
|
39
|
+
export let advisorOutcomeLoggingRef = false;
|
|
40
|
+
export let advisorUntrackedContentRef = false;
|
|
41
|
+
export let advisorTrackedFileContentRef = false;
|
|
42
|
+
export let advisorScoutEnabledRef = false;
|
|
43
|
+
export let showUsageDetailsRef = true;
|
|
44
|
+
export let showUsageFooterRef = false;
|
|
45
|
+
|
|
46
|
+
export const setExecutorRef = (ref: string) => {
|
|
47
|
+
executorRef = ref;
|
|
48
|
+
};
|
|
49
|
+
export const setAdvisorRef = (ref: string) => {
|
|
50
|
+
advisorRef = ref;
|
|
51
|
+
};
|
|
52
|
+
/** Returns model refs explicitly persisted in the global Advisor config. */
|
|
53
|
+
export const getPersistedModelRefs = () => ({
|
|
54
|
+
advisor: persistedAdvisorRef,
|
|
55
|
+
executor: persistedExecutorRef,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
/** Updates persisted model refs without exposing the mutation in the facade. */
|
|
59
|
+
export const setPersistedModelRefs = (
|
|
60
|
+
advisor: string | undefined,
|
|
61
|
+
executor: string | undefined
|
|
62
|
+
) => {
|
|
63
|
+
persistedAdvisorRef = advisor;
|
|
64
|
+
persistedExecutorRef = executor;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const setExecutorEffortRef = (effort: string | undefined) => {
|
|
68
|
+
executorEffortRef = effort;
|
|
69
|
+
};
|
|
70
|
+
export const setAdvisorEffortRef = (effort: string | undefined) => {
|
|
71
|
+
advisorEffortRef = effort;
|
|
72
|
+
};
|
|
73
|
+
export const setContextMaxCharsRef = (value: number) => {
|
|
74
|
+
contextMaxCharsRef = value;
|
|
75
|
+
};
|
|
76
|
+
export const setAdvisorPlanGateRef = (enabled: boolean) => {
|
|
77
|
+
advisorPlanGateRef = enabled;
|
|
78
|
+
};
|
|
79
|
+
export const setAdvisorFailureGateRef = (enabled: boolean) => {
|
|
80
|
+
advisorFailureGateRef = enabled;
|
|
81
|
+
};
|
|
82
|
+
export const setAdvisorCompletionGateRef = (enabled: boolean) => {
|
|
83
|
+
advisorCompletionGateRef = enabled;
|
|
84
|
+
};
|
|
85
|
+
export const setAdvisorCustomInvocationRef = (rule: string | undefined) => {
|
|
86
|
+
advisorCustomInvocationRef = rule?.trim() || undefined;
|
|
87
|
+
};
|
|
88
|
+
export const setAdvisorCollapseResponsesRef = (enabled: boolean) => {
|
|
89
|
+
advisorCollapseResponsesRef = enabled;
|
|
90
|
+
};
|
|
91
|
+
export const setAdvisorBlockOnBlockedRef = (enabled: boolean) => {
|
|
92
|
+
advisorBlockOnBlockedRef = enabled;
|
|
93
|
+
};
|
|
94
|
+
export const setAdvisorAutoLoopGateRef = (enabled: boolean) => {
|
|
95
|
+
advisorAutoLoopGateRef = enabled;
|
|
96
|
+
};
|
|
97
|
+
export const setAdvisorLoopThresholdRef = (value: number) => {
|
|
98
|
+
advisorLoopThresholdRef = value;
|
|
99
|
+
};
|
|
100
|
+
export const setAdvisorMaxCallsPerSessionRef = (value: number | undefined) => {
|
|
101
|
+
advisorMaxCallsPerSessionRef = value;
|
|
102
|
+
};
|
|
103
|
+
export const setAdvisorSessionSummaryRef = (enabled: boolean) => {
|
|
104
|
+
advisorSessionSummaryRef = enabled;
|
|
105
|
+
};
|
|
106
|
+
export const setSimpleModeRef = (enabled: boolean) => {
|
|
107
|
+
simpleModeRef = enabled;
|
|
108
|
+
};
|
|
109
|
+
export const setAlwaysOnRef = (enabled: boolean) => {
|
|
110
|
+
alwaysOnRef = enabled;
|
|
111
|
+
};
|
|
112
|
+
/** Whether advanced Advisor automation is suppressed for this session. */
|
|
113
|
+
export const isSimpleMode = () => simpleModeRef;
|
|
114
|
+
export const setAdvisorFailureModeRef = (value: GateFailureMode) => {
|
|
115
|
+
advisorFailureModeRef = value;
|
|
116
|
+
};
|
|
117
|
+
export const setAdvisorHerdrIntegrationRef = (enabled: boolean) => {
|
|
118
|
+
advisorHerdrIntegrationRef = enabled;
|
|
119
|
+
};
|
|
120
|
+
export const setAdvisorToolResultMaxLinesRef = (value: number) => {
|
|
121
|
+
advisorToolResultMaxLinesRef = value;
|
|
122
|
+
};
|
|
123
|
+
export const setAdvisorToolResultMaxBytesRef = (value: number) => {
|
|
124
|
+
advisorToolResultMaxBytesRef = value;
|
|
125
|
+
};
|
|
126
|
+
export const setAdvisorRedactSecretsRef = (enabled: boolean) => {
|
|
127
|
+
advisorRedactSecretsRef = enabled;
|
|
128
|
+
};
|
|
129
|
+
export const setAdvisorGitContextRef = (level: GitContextLevel) => {
|
|
130
|
+
advisorGitContextRef = level;
|
|
131
|
+
};
|
|
132
|
+
export const setAdvisorGitContextMaxCharsRef = (value: number) => {
|
|
133
|
+
advisorGitContextMaxCharsRef = value;
|
|
134
|
+
};
|
|
135
|
+
export const setAdvisorToolPoliciesRef = (policies: AdvisorToolPolicies) => {
|
|
136
|
+
advisorToolPoliciesRef = { ...policies };
|
|
137
|
+
};
|
|
138
|
+
export const setAdvisorOutcomeLoggingRef = (enabled: boolean) => {
|
|
139
|
+
advisorOutcomeLoggingRef = enabled;
|
|
140
|
+
};
|
|
141
|
+
export const setAdvisorUntrackedContentRef = (enabled: boolean) => {
|
|
142
|
+
advisorUntrackedContentRef = enabled;
|
|
143
|
+
};
|
|
144
|
+
export const setAdvisorTrackedFileContentRef = (enabled: boolean) => {
|
|
145
|
+
advisorTrackedFileContentRef = enabled;
|
|
146
|
+
};
|
|
147
|
+
export const setAdvisorScoutEnabledRef = (enabled: boolean) => {
|
|
148
|
+
advisorScoutEnabledRef = enabled;
|
|
149
|
+
};
|
|
150
|
+
export const setShowUsageDetailsRef = (enabled: boolean) => {
|
|
151
|
+
showUsageDetailsRef = enabled;
|
|
152
|
+
};
|
|
153
|
+
export const setShowUsageFooterRef = (enabled: boolean) => {
|
|
154
|
+
showUsageFooterRef = enabled;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Returns the current live settings state. Use this at UI boundaries instead of
|
|
159
|
+
* imported mutable bindings, which can be snapshotted by extension loaders.
|
|
160
|
+
*/
|
|
161
|
+
export const getAdvisorSettings = () => ({
|
|
162
|
+
alwaysOn: alwaysOnRef,
|
|
163
|
+
autoLoopGate: advisorAutoLoopGateRef,
|
|
164
|
+
blockOnBlocked: advisorBlockOnBlockedRef,
|
|
165
|
+
collapseResponses: advisorCollapseResponsesRef,
|
|
166
|
+
completionGate: advisorCompletionGateRef,
|
|
167
|
+
contextMaxChars: contextMaxCharsRef,
|
|
168
|
+
customRule: advisorCustomInvocationRef,
|
|
169
|
+
effort: advisorEffortRef,
|
|
170
|
+
failureGate: advisorFailureGateRef,
|
|
171
|
+
failureMode: advisorFailureModeRef,
|
|
172
|
+
gitContext: advisorGitContextRef,
|
|
173
|
+
gitContextMaxChars: advisorGitContextMaxCharsRef,
|
|
174
|
+
herdrIntegration: advisorHerdrIntegrationRef,
|
|
175
|
+
loopThreshold: advisorLoopThresholdRef,
|
|
176
|
+
maxCallsPerSession: advisorMaxCallsPerSessionRef,
|
|
177
|
+
outcomeLogging: advisorOutcomeLoggingRef,
|
|
178
|
+
planGate: advisorPlanGateRef,
|
|
179
|
+
redactSecrets: advisorRedactSecretsRef,
|
|
180
|
+
scoutEnabled: advisorScoutEnabledRef,
|
|
181
|
+
sessionSummary: advisorSessionSummaryRef,
|
|
182
|
+
showUsageDetails: showUsageDetailsRef,
|
|
183
|
+
showUsageFooter: showUsageFooterRef,
|
|
184
|
+
simpleMode: simpleModeRef,
|
|
185
|
+
toolPolicies: { ...advisorToolPoliciesRef },
|
|
186
|
+
toolResultMaxBytes: advisorToolResultMaxBytesRef,
|
|
187
|
+
toolResultMaxLines: advisorToolResultMaxLinesRef,
|
|
188
|
+
trackedFileContent: advisorTrackedFileContentRef,
|
|
189
|
+
untrackedContent: advisorUntrackedContentRef,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
/** Reads the live session budget at each enforcement boundary. */
|
|
193
|
+
export const getAdvisorMaxCallsPerSession = () =>
|
|
194
|
+
getAdvisorSettings().maxCallsPerSession;
|
|
195
|
+
|
|
196
|
+
export const splitRef = (ref: string): [string, string] => {
|
|
197
|
+
const i = ref.indexOf("/");
|
|
198
|
+
return i === -1 ? ["openai-codex", ref] : [ref.slice(0, i), ref.slice(i + 1)];
|
|
199
|
+
};
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
CONFIG_DIR_NAME,
|
|
5
|
+
type ExtensionContext,
|
|
6
|
+
getAgentDir,
|
|
7
|
+
} from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import { applyConfig, resetDefaults } from "./defaults.js";
|
|
9
|
+
import {
|
|
10
|
+
advisorAutoLoopGateRef,
|
|
11
|
+
advisorBlockOnBlockedRef,
|
|
12
|
+
advisorCollapseResponsesRef,
|
|
13
|
+
advisorCompletionGateRef,
|
|
14
|
+
advisorCustomInvocationRef,
|
|
15
|
+
advisorEffortRef,
|
|
16
|
+
advisorFailureGateRef,
|
|
17
|
+
advisorFailureModeRef,
|
|
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";
|
|
45
|
+
|
|
46
|
+
export const configPaths = (ctx: ExtensionContext) => [
|
|
47
|
+
ctx.isProjectTrusted()
|
|
48
|
+
? join(ctx.cwd, CONFIG_DIR_NAME, "advisor.json")
|
|
49
|
+
: null,
|
|
50
|
+
join(getAgentDir(), "advisor.json"),
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
const configuredModelRef = (value: string | undefined): string | undefined =>
|
|
54
|
+
value?.trim() || undefined;
|
|
55
|
+
|
|
56
|
+
const readConfig = (path: string): AdvisorConfig => {
|
|
57
|
+
try {
|
|
58
|
+
const config = JSON.parse(readFileSync(path, "utf8"));
|
|
59
|
+
validateConfig(config, path);
|
|
60
|
+
return config;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// loadConfig runs on every tool call and every consultation. Caching the parsed
|
|
67
|
+
// file by path and stat identity removes that read and parse from the hot path
|
|
68
|
+
// while still applying the full reset-then-apply sequence on each call.
|
|
69
|
+
const configCache = new Map<
|
|
70
|
+
string,
|
|
71
|
+
{ config: AdvisorConfig; identity: string }
|
|
72
|
+
>();
|
|
73
|
+
const warnedUnknownConfigIdentities = new Set<string>();
|
|
74
|
+
|
|
75
|
+
/** Drops the parsed-configuration cache; the next load re-reads from disk. */
|
|
76
|
+
export const resetConfigCache = () => {
|
|
77
|
+
configCache.clear();
|
|
78
|
+
warnedUnknownConfigIdentities.clear();
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Nanosecond timestamps plus size and inode so a same-millisecond or
|
|
83
|
+
* same-size external rewrite cannot be served from the cache. An unstattable
|
|
84
|
+
* path yields a unique value, which always forces a re-read.
|
|
85
|
+
*/
|
|
86
|
+
const configIdentity = (path: string): string => {
|
|
87
|
+
try {
|
|
88
|
+
const stats = statSync(path, { bigint: true });
|
|
89
|
+
return `${stats.mtimeNs}:${stats.ctimeNs}:${stats.size}:${stats.ino}:${stats.dev}`;
|
|
90
|
+
} catch {
|
|
91
|
+
return `unstattable:${process.hrtime.bigint()}`;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const readConfigCached = (path: string): AdvisorConfig => {
|
|
96
|
+
const identity = configIdentity(path);
|
|
97
|
+
const cached = configCache.get(path);
|
|
98
|
+
if (cached?.identity === identity) {
|
|
99
|
+
return cached.config;
|
|
100
|
+
}
|
|
101
|
+
const config = readConfig(path);
|
|
102
|
+
configCache.set(path, { config, identity });
|
|
103
|
+
return config;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const loadConfig = (_ctx: ExtensionContext) => {
|
|
107
|
+
resetDefaults();
|
|
108
|
+
const global = join(getAgentDir(), "advisor.json");
|
|
109
|
+
const globalConfig = existsSync(global)
|
|
110
|
+
? readConfigCached(global)
|
|
111
|
+
: undefined;
|
|
112
|
+
setPersistedModelRefs(
|
|
113
|
+
configuredModelRef(globalConfig?.advisor),
|
|
114
|
+
configuredModelRef(globalConfig?.executor)
|
|
115
|
+
);
|
|
116
|
+
if (globalConfig) {
|
|
117
|
+
applyConfig(globalConfig);
|
|
118
|
+
const unknownKeys = unknownConfigKeys(
|
|
119
|
+
globalConfig as Record<string, unknown>
|
|
120
|
+
);
|
|
121
|
+
const warningIdentity = `${global}:${configIdentity(global)}`;
|
|
122
|
+
if (
|
|
123
|
+
unknownKeys.length > 0 &&
|
|
124
|
+
_ctx.hasUI &&
|
|
125
|
+
!warnedUnknownConfigIdentities.has(warningIdentity)
|
|
126
|
+
) {
|
|
127
|
+
_ctx.ui.notify(
|
|
128
|
+
`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.`,
|
|
129
|
+
"warning"
|
|
130
|
+
);
|
|
131
|
+
warnedUnknownConfigIdentities.add(warningIdentity);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Repository-controlled project configuration is never applied. Models,
|
|
135
|
+
// prompts, gates, budgets, disclosure, redaction, integrations, and consent
|
|
136
|
+
// remain under the user's global Pi configuration.
|
|
137
|
+
setAdvisorOutcomeLoggingRef(globalConfig?.advisorOutcomeLogging === true);
|
|
138
|
+
return existsSync(global) ? global : null;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/** Saves user-controlled configuration globally without outcome consent. */
|
|
142
|
+
export interface SaveConfigOptions {
|
|
143
|
+
persistAdvisor?: boolean;
|
|
144
|
+
persistExecutor?: boolean;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export const saveConfig = (
|
|
148
|
+
_ctx: ExtensionContext,
|
|
149
|
+
options: SaveConfigOptions = {}
|
|
150
|
+
) => {
|
|
151
|
+
const path = join(getAgentDir(), "advisor.json");
|
|
152
|
+
const persistAdvisor = options.persistAdvisor ?? true;
|
|
153
|
+
const persistExecutor = options.persistExecutor ?? true;
|
|
154
|
+
let existing: Record<string, unknown> = {};
|
|
155
|
+
try {
|
|
156
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
157
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
158
|
+
existing = parsed as Record<string, unknown>;
|
|
159
|
+
}
|
|
160
|
+
} catch {
|
|
161
|
+
/* replace a missing or malformed file */
|
|
162
|
+
}
|
|
163
|
+
if (advisorMaxCallsPerSessionRef === undefined) {
|
|
164
|
+
existing.advisorMaxCallsPerSession = undefined;
|
|
165
|
+
}
|
|
166
|
+
const data = {
|
|
167
|
+
...existing,
|
|
168
|
+
...(persistAdvisor && advisorRef ? { advisor: advisorRef } : {}),
|
|
169
|
+
advisorAutoLoopGate: advisorAutoLoopGateRef,
|
|
170
|
+
advisorBlockOnBlocked: advisorBlockOnBlockedRef,
|
|
171
|
+
advisorCollapseResponses: advisorCollapseResponsesRef,
|
|
172
|
+
advisorCompletionGate: advisorCompletionGateRef,
|
|
173
|
+
advisorCustomInvocation: advisorCustomInvocationRef,
|
|
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
|
+
};
|
|
201
|
+
writeFileSync(path, `${JSON.stringify(data, null, 2)}\n`);
|
|
202
|
+
resetConfigCache();
|
|
203
|
+
return path;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/** Outcome logging is deliberately written only to the global Pi configuration. */
|
|
207
|
+
export const saveGlobalOutcomeLogging = (enabled: boolean) => {
|
|
208
|
+
const path = join(getAgentDir(), "advisor.json");
|
|
209
|
+
let existing: Record<string, unknown> = {};
|
|
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
|
+
}
|
|
218
|
+
writeFileSync(
|
|
219
|
+
path,
|
|
220
|
+
`${JSON.stringify({ ...existing, advisorOutcomeLogging: enabled }, null, 2)}\n`
|
|
221
|
+
);
|
|
222
|
+
resetConfigCache();
|
|
223
|
+
return path;
|
|
224
|
+
};
|