pi-advisor-flow 0.9.2-dev.3 → 0.9.2-dev.5
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 +12 -0
- package/README.md +7 -2
- package/dist/index.js +580 -201
- package/package.json +1 -1
- package/src/commands/manual-consultation.ts +31 -26
- package/src/commands/outcome-stats.ts +98 -0
- package/src/commands/registration.ts +2 -0
- package/src/commands/renderers.ts +51 -0
- package/src/commands/runtime.ts +2 -3
- package/src/commands/settings-persistence.ts +16 -7
- package/src/commands/types.ts +3 -2
- package/src/config/args.ts +3 -1
- package/src/config/defaults.ts +7 -0
- package/src/config/schema.ts +7 -0
- package/src/config/state.ts +5 -0
- package/src/config/storage.ts +23 -2
- package/src/config/types.ts +1 -0
- package/src/config.ts +2 -0
- package/src/image-attachments.ts +7 -3
- package/src/outcome-stats.ts +154 -0
- package/src/outcomes.ts +8 -1
- package/src/preferences.ts +103 -13
- package/src/redaction.ts +1 -1
- package/src/tools/consult-context.ts +12 -1
- package/src/tools/consultation.ts +34 -7
- package/src/tools/jev-filter.ts +2 -1
- package/src/tools/jev-turn-gate.ts +2 -0
- package/src/tools/loop-gate.ts +2 -0
- package/src/tools/prompts.ts +28 -2
- package/src/tools/register-ask-advisor.ts +1 -0
- package/src/tools/register-renderers.ts +40 -6
- package/src/tools/registration.ts +2 -2
- package/src/tools/render-advisor-result.ts +21 -11
- package/src/tools/session.ts +14 -0
- package/src/tools/types.ts +3 -0
- package/src/ui/manual-dialog.ts +10 -3
- package/src/ui/settings-items.ts +7 -0
- package/src/ui/settings-mutations.ts +1 -0
- package/src/ui/types.ts +1 -0
- package/src/usage.ts +34 -6
package/package.json
CHANGED
|
@@ -15,6 +15,8 @@ import type { CommandRuntime, ManualAdvisorProgressState } from "./types.ts";
|
|
|
15
15
|
|
|
16
16
|
interface ManualResultDetails {
|
|
17
17
|
advisor: string;
|
|
18
|
+
agentRulesBytes?: number;
|
|
19
|
+
preferenceBytes?: number;
|
|
18
20
|
question?: string;
|
|
19
21
|
text: string;
|
|
20
22
|
usage?: AdvisorUsageSnapshot;
|
|
@@ -48,34 +50,35 @@ export const startManualConsultation = async (
|
|
|
48
50
|
const finishHerdrActivity = runtime.herdrActivity.start();
|
|
49
51
|
let scoutDetails: ScoutToolDetails | undefined;
|
|
50
52
|
try {
|
|
51
|
-
const { adviceId, markdown, usage } =
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
progress.thinking = thinking;
|
|
61
|
-
progress.text = text;
|
|
62
|
-
runtime.requestManualRender(ctx);
|
|
63
|
-
},
|
|
64
|
-
(event) => {
|
|
65
|
-
if (!controller.signal.aborted) {
|
|
66
|
-
runtime.scoutStatus.update(ctx, scoutStatusToken, event);
|
|
67
|
-
scoutDetails = appendScoutLifecycleEntry(
|
|
68
|
-
runtime.pi,
|
|
69
|
-
event,
|
|
70
|
-
scoutDetails
|
|
71
|
-
);
|
|
72
|
-
progress.scout = scoutDetails;
|
|
53
|
+
const { adviceId, agentRulesBytes, markdown, preferenceBytes, usage } =
|
|
54
|
+
await runtime.requestAdvisor(
|
|
55
|
+
ctx,
|
|
56
|
+
question,
|
|
57
|
+
controller.signal,
|
|
58
|
+
(thinking, text) => {
|
|
59
|
+
if (controller.signal.aborted) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
73
62
|
progress.phase = "active";
|
|
63
|
+
progress.thinking = thinking;
|
|
64
|
+
progress.text = text;
|
|
74
65
|
runtime.requestManualRender(ctx);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
66
|
+
},
|
|
67
|
+
(event) => {
|
|
68
|
+
if (!controller.signal.aborted) {
|
|
69
|
+
runtime.scoutStatus.update(ctx, scoutStatusToken, event);
|
|
70
|
+
scoutDetails = appendScoutLifecycleEntry(
|
|
71
|
+
runtime.pi,
|
|
72
|
+
event,
|
|
73
|
+
scoutDetails
|
|
74
|
+
);
|
|
75
|
+
progress.scout = scoutDetails;
|
|
76
|
+
progress.phase = "active";
|
|
77
|
+
runtime.requestManualRender(ctx);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
gitContext
|
|
81
|
+
);
|
|
79
82
|
if (controller.signal.aborted) {
|
|
80
83
|
return;
|
|
81
84
|
}
|
|
@@ -103,6 +106,8 @@ export const startManualConsultation = async (
|
|
|
103
106
|
runtime.updateAdvisorUsageStatus(ctx);
|
|
104
107
|
const details: ManualResultDetails = {
|
|
105
108
|
advisor: advisorRef,
|
|
109
|
+
agentRulesBytes,
|
|
110
|
+
preferenceBytes,
|
|
106
111
|
question,
|
|
107
112
|
text: markdown,
|
|
108
113
|
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { aggregateOutcomeRecords, readOutcomeLog } from "../outcome-stats.ts";
|
|
2
|
+
import type { OutcomeStats } from "../outcome-stats.ts";
|
|
3
|
+
import { OUTCOME_LOG_MAX_BYTES } from "../outcomes.ts";
|
|
4
|
+
import type { CommandRuntime } from "./types.ts";
|
|
5
|
+
|
|
6
|
+
export interface AdvisorStatsDetails {
|
|
7
|
+
error?: string;
|
|
8
|
+
malformedLines: number;
|
|
9
|
+
stats?: OutcomeStats;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const percent = (rate: number | undefined) =>
|
|
13
|
+
rate === undefined ? "n/a" : `${(rate * 100).toFixed(1)}%`;
|
|
14
|
+
|
|
15
|
+
const validatedCount = (value: { failed: number; passed: number }) =>
|
|
16
|
+
value.failed + value.passed;
|
|
17
|
+
|
|
18
|
+
const timestamp = (value: string | undefined) =>
|
|
19
|
+
value ? new Date(value).toLocaleString() : "n/a";
|
|
20
|
+
|
|
21
|
+
export const formatAdvisorStats = ({
|
|
22
|
+
error,
|
|
23
|
+
malformedLines,
|
|
24
|
+
stats,
|
|
25
|
+
}: AdvisorStatsDetails): string => {
|
|
26
|
+
if (error) {
|
|
27
|
+
return `**Could not read Advisor outcomes.**\n\n${error}`;
|
|
28
|
+
}
|
|
29
|
+
if (!stats || stats.total === 0) {
|
|
30
|
+
const ignored = malformedLines
|
|
31
|
+
? `\n\nIgnored ${malformedLines} malformed ledger line${malformedLines === 1 ? "" : "s"}.`
|
|
32
|
+
: "";
|
|
33
|
+
return `**No Advisor outcomes recorded yet.**\n\nEnable outcome logging and record an outcome after an Advisor response to build this report.${ignored}`;
|
|
34
|
+
}
|
|
35
|
+
const { validationByAdoption } = stats;
|
|
36
|
+
const { followed } = validationByAdoption;
|
|
37
|
+
const notFollowed = validationByAdoption["not-followed"];
|
|
38
|
+
const triggerLines = Object.entries(stats.byTrigger).map(
|
|
39
|
+
([trigger, count]) => `- ${trigger}: ${count}`
|
|
40
|
+
);
|
|
41
|
+
const adoptionLines = Object.entries(stats.adoption).map(
|
|
42
|
+
([adoption, count]) => `- ${adoption}: ${count}`
|
|
43
|
+
);
|
|
44
|
+
const validationLines = [
|
|
45
|
+
`- followed: ${percent(followed.passRate)} passed (${followed.passed}/${validatedCount(followed)})`,
|
|
46
|
+
`- not-followed: ${percent(notFollowed.passRate)} passed (${notFollowed.passed}/${validatedCount(notFollowed)})`,
|
|
47
|
+
];
|
|
48
|
+
const ignored = malformedLines
|
|
49
|
+
? `\n\nIgnored ${malformedLines} malformed ledger line${malformedLines === 1 ? "" : "s"}.`
|
|
50
|
+
: "";
|
|
51
|
+
return [
|
|
52
|
+
`**${stats.total} Advisor outcome${stats.total === 1 ? "" : "s"}**`,
|
|
53
|
+
"",
|
|
54
|
+
"**By trigger**",
|
|
55
|
+
...triggerLines,
|
|
56
|
+
"",
|
|
57
|
+
"**Adoption**",
|
|
58
|
+
...adoptionLines,
|
|
59
|
+
"",
|
|
60
|
+
"**Validation passed rate (passed / passed + failed)**",
|
|
61
|
+
...validationLines,
|
|
62
|
+
"",
|
|
63
|
+
`Distinct advices seen: ${stats.distinctAdvices}`,
|
|
64
|
+
`Time window: ${timestamp(stats.firstTimestamp)} → ${timestamp(stats.lastTimestamp)}`,
|
|
65
|
+
`Ledger window: capped at ${OUTCOME_LOG_MAX_BYTES / (1024 * 1024)} MiB and rewritten on overflow; this report covers only retained records.${ignored}`,
|
|
66
|
+
].join("\n");
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const readAdvisorStats = async (): Promise<AdvisorStatsDetails> => {
|
|
70
|
+
try {
|
|
71
|
+
const log = await readOutcomeLog();
|
|
72
|
+
const stats = aggregateOutcomeRecords(log.records, log.malformedLines);
|
|
73
|
+
return { malformedLines: log.malformedLines, stats };
|
|
74
|
+
} catch (error) {
|
|
75
|
+
return {
|
|
76
|
+
error: error instanceof Error ? error.message : String(error),
|
|
77
|
+
malformedLines: 0,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const registerOutcomeStatsCommand = (runtime: CommandRuntime) => {
|
|
83
|
+
runtime.pi.registerCommand("advisor-stats", {
|
|
84
|
+
description: "Show retained Advisor outcome adoption and validation stats",
|
|
85
|
+
handler: async (_args, _ctx) => {
|
|
86
|
+
const details = await readAdvisorStats();
|
|
87
|
+
runtime.pi.sendMessage(
|
|
88
|
+
{
|
|
89
|
+
content: [],
|
|
90
|
+
customType: "advisor-stats-result",
|
|
91
|
+
details,
|
|
92
|
+
display: true,
|
|
93
|
+
},
|
|
94
|
+
{ deliverAs: "steer", triggerTurn: false }
|
|
95
|
+
);
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
};
|
|
@@ -7,6 +7,7 @@ import { activateAdvisor } from "./activation.ts";
|
|
|
7
7
|
import { registerCommandLifecycle } from "./lifecycle.ts";
|
|
8
8
|
import { registerManualCommand } from "./manual-command.ts";
|
|
9
9
|
import { registerModelCommands } from "./model-commands.ts";
|
|
10
|
+
import { registerOutcomeStatsCommand } from "./outcome-stats.ts";
|
|
10
11
|
import { registerCommandRenderers } from "./renderers.ts";
|
|
11
12
|
import { createCommandRuntime } from "./runtime.ts";
|
|
12
13
|
import { registerSettingsCommands } from "./settings-commands.ts";
|
|
@@ -24,5 +25,6 @@ export const registerCommands = (
|
|
|
24
25
|
registerCommandLifecycle(runtime, activate);
|
|
25
26
|
registerManualCommand(runtime);
|
|
26
27
|
registerModelCommands(runtime);
|
|
28
|
+
registerOutcomeStatsCommand(runtime);
|
|
27
29
|
registerSettingsCommands(runtime);
|
|
28
30
|
};
|
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
} from "../tools/render-common.ts";
|
|
16
16
|
import { formatAdvisorUsage } from "../usage.ts";
|
|
17
17
|
import { ManualAdvisorProgressComponent } from "./manual-progress.ts";
|
|
18
|
+
import { formatAdvisorStats } from "./outcome-stats.ts";
|
|
19
|
+
import type { AdvisorStatsDetails } from "./outcome-stats.ts";
|
|
18
20
|
import type { CommandRuntime } from "./types.ts";
|
|
19
21
|
|
|
20
22
|
interface ManualCallEntryDetails {
|
|
@@ -24,10 +26,22 @@ interface ManualCallEntryDetails {
|
|
|
24
26
|
|
|
25
27
|
interface ManualResultDetails {
|
|
26
28
|
advisor?: string;
|
|
29
|
+
agentRulesBytes?: number;
|
|
30
|
+
preferenceBytes?: number;
|
|
27
31
|
text?: string;
|
|
28
32
|
usage?: unknown;
|
|
29
33
|
}
|
|
30
34
|
|
|
35
|
+
const formatByteSize = (bytes: number) => {
|
|
36
|
+
if (bytes >= 1024 * 1024) {
|
|
37
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
|
|
38
|
+
}
|
|
39
|
+
if (bytes >= 1024) {
|
|
40
|
+
return `${Math.round(bytes / 1024)} KiB`;
|
|
41
|
+
}
|
|
42
|
+
return `${bytes} B`;
|
|
43
|
+
};
|
|
44
|
+
|
|
31
45
|
const manualCallRenderer =
|
|
32
46
|
(runtime: CommandRuntime): EntryRenderer<ManualCallEntryDetails> =>
|
|
33
47
|
(entry, { expanded }, theme) => {
|
|
@@ -70,12 +84,45 @@ const manualResultRenderer: MessageRenderer<ManualResultDetails> = (
|
|
|
70
84
|
box.addChild(new Text(theme.fg("dim", ` Usage: ${usage}`), 0, 0));
|
|
71
85
|
}
|
|
72
86
|
}
|
|
87
|
+
const attachments = [
|
|
88
|
+
details?.agentRulesBytes
|
|
89
|
+
? `AGENTS.md rules attached · ${formatByteSize(details.agentRulesBytes)}`
|
|
90
|
+
: undefined,
|
|
91
|
+
details?.preferenceBytes
|
|
92
|
+
? `Project preferences attached · ${formatByteSize(details.preferenceBytes)}`
|
|
93
|
+
: undefined,
|
|
94
|
+
].filter((value): value is string => value !== undefined);
|
|
95
|
+
if (attachments.length) {
|
|
96
|
+
box.addChild(
|
|
97
|
+
new Text(theme.fg("dim", ` ${attachments.join(" · ")}`), 0, 0)
|
|
98
|
+
);
|
|
99
|
+
}
|
|
73
100
|
box.addChild(
|
|
74
101
|
new Markdown(adviceForDisplay(advice, expanded), 0, 0, getMarkdownTheme())
|
|
75
102
|
);
|
|
76
103
|
return box;
|
|
77
104
|
};
|
|
78
105
|
|
|
106
|
+
const outcomeStatsRenderer: MessageRenderer<AdvisorStatsDetails> = (
|
|
107
|
+
message,
|
|
108
|
+
_options,
|
|
109
|
+
theme
|
|
110
|
+
) => {
|
|
111
|
+
const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
112
|
+
box.addChild(
|
|
113
|
+
new Text(theme.fg("accent", theme.bold("◆ ADVISOR · OUTCOME STATS")), 0, 0)
|
|
114
|
+
);
|
|
115
|
+
box.addChild(
|
|
116
|
+
new Markdown(
|
|
117
|
+
formatAdvisorStats(message.details ?? { malformedLines: 0 }),
|
|
118
|
+
0,
|
|
119
|
+
0,
|
|
120
|
+
getMarkdownTheme()
|
|
121
|
+
)
|
|
122
|
+
);
|
|
123
|
+
return box;
|
|
124
|
+
};
|
|
125
|
+
|
|
79
126
|
export const registerCommandRenderers = (runtime: CommandRuntime) => {
|
|
80
127
|
runtime.pi.registerEntryRenderer?.(
|
|
81
128
|
"advisor-manual-call",
|
|
@@ -85,4 +132,8 @@ export const registerCommandRenderers = (runtime: CommandRuntime) => {
|
|
|
85
132
|
"advisor-manual-result",
|
|
86
133
|
manualResultRenderer
|
|
87
134
|
);
|
|
135
|
+
runtime.pi.registerMessageRenderer?.(
|
|
136
|
+
"advisor-stats-result",
|
|
137
|
+
outcomeStatsRenderer
|
|
138
|
+
);
|
|
88
139
|
};
|
package/src/commands/runtime.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
sameModelAdvisorNotice,
|
|
13
13
|
} from "../tools/model-access.ts";
|
|
14
14
|
import { ScoutStatusManager } from "../tools/scout-status.ts";
|
|
15
|
-
import {
|
|
15
|
+
import { sessionStateFor } from "../tools/session.ts";
|
|
16
16
|
import { uiAction } from "../ui-guard.ts";
|
|
17
17
|
import type {
|
|
18
18
|
CommandDependencies,
|
|
@@ -60,8 +60,7 @@ class CommandRuntime implements CommandRuntimeContract {
|
|
|
60
60
|
|
|
61
61
|
constructor(pi: ExtensionAPI, dependencies: CommandDependencies = {}) {
|
|
62
62
|
this.pi = pi;
|
|
63
|
-
this.advisorSessionState =
|
|
64
|
-
dependencies.sessionState ?? defaultAdvisorSessionState;
|
|
63
|
+
this.advisorSessionState = dependencies.sessionState ?? sessionStateFor(pi);
|
|
65
64
|
this.herdrActivity = dependencies.herdrActivity ?? herdrAdvisorActivity;
|
|
66
65
|
this.scoutStatus =
|
|
67
66
|
dependencies.statusManager ?? new ScoutStatusManager(false);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
getAdvisorSettings,
|
|
4
5
|
getPersistedModelRefs,
|
|
6
|
+
setAdvisorAgentsMdContextRef,
|
|
5
7
|
setAdvisorAutoLoopGateRef,
|
|
6
8
|
setAdvisorBlockOnBlockedRef,
|
|
7
9
|
setAdvisorCollapseResponsesRef,
|
|
@@ -125,6 +127,7 @@ const applyDisclosureSettings = (settings: AdvisorSettings) => {
|
|
|
125
127
|
setAdvisorGitContextRef(settings.gitContext ?? "summary");
|
|
126
128
|
setAdvisorGitContextMaxCharsRef(settings.gitContextMaxChars ?? 20_000);
|
|
127
129
|
setAdvisorToolPoliciesRef(settings.toolPolicies ?? {});
|
|
130
|
+
setAdvisorAgentsMdContextRef(settings.agentsMdContext ?? true);
|
|
128
131
|
setAdvisorTrackedFileContentRef(settings.trackedFileContent ?? false);
|
|
129
132
|
setAdvisorUntrackedContentRef(settings.untrackedContent ?? false);
|
|
130
133
|
setAdvisorOutcomeLoggingRef(settings.outcomeLogging ?? false);
|
|
@@ -140,11 +143,17 @@ export const saveAdvisorSettings = (
|
|
|
140
143
|
ctx: ExtensionContext,
|
|
141
144
|
settings: AdvisorSettings
|
|
142
145
|
) => {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
146
|
+
const previous = getAdvisorSettings();
|
|
147
|
+
try {
|
|
148
|
+
applyAdvisorSettings(settings);
|
|
149
|
+
const persisted = getPersistedModelRefs();
|
|
150
|
+
saveConfig(ctx, {
|
|
151
|
+
persistAdvisor: Boolean(persisted.advisor),
|
|
152
|
+
persistExecutor: Boolean(persisted.executor),
|
|
153
|
+
});
|
|
154
|
+
saveGlobalOutcomeLogging(settings.outcomeLogging ?? false);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
applyAdvisorSettings(previous);
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
150
159
|
};
|
package/src/commands/types.ts
CHANGED
|
@@ -19,10 +19,11 @@ export type ManualConsult = (
|
|
|
19
19
|
gitContext?: GitContextLevel
|
|
20
20
|
) => Promise<{
|
|
21
21
|
adviceId?: string;
|
|
22
|
-
|
|
23
|
-
thinkingText: string;
|
|
22
|
+
agentRulesBytes?: number;
|
|
24
23
|
draftBytes?: number;
|
|
24
|
+
markdown: string;
|
|
25
25
|
preferenceBytes?: number;
|
|
26
|
+
thinkingText: string;
|
|
26
27
|
usage?: unknown;
|
|
27
28
|
}>;
|
|
28
29
|
|
package/src/config/args.ts
CHANGED
|
@@ -16,7 +16,9 @@ export const parseArgs = (args: string): string | undefined => {
|
|
|
16
16
|
let nextAdvisor = advisorRef;
|
|
17
17
|
let nextContextMaxChars = contextMaxCharsRef;
|
|
18
18
|
for (const token of args.trim().split(ARGUMENT_WHITESPACE).filter(Boolean)) {
|
|
19
|
-
const
|
|
19
|
+
const separator = token.indexOf("=");
|
|
20
|
+
const key = separator === -1 ? token : token.slice(0, separator);
|
|
21
|
+
const value = separator === -1 ? undefined : token.slice(separator + 1);
|
|
20
22
|
if (key === "executor" && value) {
|
|
21
23
|
nextExecutor = value;
|
|
22
24
|
}
|
package/src/config/defaults.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
setAdvisorAgentsMdContextRef,
|
|
2
3
|
setAdvisorAutoLoopGateRef,
|
|
3
4
|
setAdvisorBlockOnBlockedRef,
|
|
4
5
|
setAdvisorCollapseResponsesRef,
|
|
@@ -68,6 +69,7 @@ import {
|
|
|
68
69
|
export const resetDefaults = () => {
|
|
69
70
|
setExecutorRef("");
|
|
70
71
|
setAdvisorRef("");
|
|
72
|
+
setAdvisorAgentsMdContextRef(true);
|
|
71
73
|
setPersistedModelRefs(undefined, undefined);
|
|
72
74
|
setExecutorEffortRef(undefined);
|
|
73
75
|
setAdvisorEffortRef(undefined);
|
|
@@ -139,6 +141,11 @@ export const applyConfig = (config: AdvisorConfig) => {
|
|
|
139
141
|
applyNonEmptyStringConfig(config.advisor, setAdvisorRef);
|
|
140
142
|
applyNonEmptyStringConfig(config.executorEffort, setExecutorEffortRef);
|
|
141
143
|
applyNonEmptyStringConfig(config.advisorEffort, setAdvisorEffortRef);
|
|
144
|
+
applyOptionalConfig(
|
|
145
|
+
config,
|
|
146
|
+
"advisorAgentsMdContext",
|
|
147
|
+
setAdvisorAgentsMdContextRef
|
|
148
|
+
);
|
|
142
149
|
applyOptionalConfig(config, "contextMaxChars", setContextMaxCharsRef);
|
|
143
150
|
applyOptionalConfig(config, "advisorPlanGate", setAdvisorPlanGateRef);
|
|
144
151
|
applyOptionalConfig(config, "advisorFailureGate", setAdvisorFailureGateRef);
|
package/src/config/schema.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { isString } from "../content-utils.ts";
|
|
|
2
2
|
import type { JsonValue } from "../content-utils.ts";
|
|
3
3
|
import { GIT_CONTEXT_LEVELS, isValidGitContextLevel } from "../git.ts";
|
|
4
4
|
import {
|
|
5
|
+
advisorAgentsMdContextRef,
|
|
5
6
|
advisorAutoLoopGateRef,
|
|
6
7
|
advisorBlockOnBlockedRef,
|
|
7
8
|
advisorCollapseResponsesRef,
|
|
@@ -171,6 +172,12 @@ export const CONFIG_SCHEMA = {
|
|
|
171
172
|
persisted: true,
|
|
172
173
|
type: "string",
|
|
173
174
|
},
|
|
175
|
+
advisorAgentsMdContext: {
|
|
176
|
+
accepted: "true or false",
|
|
177
|
+
current: () => advisorAgentsMdContextRef,
|
|
178
|
+
persisted: true,
|
|
179
|
+
type: "boolean",
|
|
180
|
+
},
|
|
174
181
|
advisorAutoLoopGate: {
|
|
175
182
|
accepted: "true or false",
|
|
176
183
|
current: () => advisorAutoLoopGateRef,
|
package/src/config/state.ts
CHANGED
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
// An empty ref means no model has been selected yet.
|
|
26
26
|
export let executorRef = "";
|
|
27
27
|
export let advisorRef = "";
|
|
28
|
+
export let advisorAgentsMdContextRef = true;
|
|
28
29
|
let persistedExecutorRef: string | undefined;
|
|
29
30
|
let persistedAdvisorRef: string | undefined;
|
|
30
31
|
export let executorEffortRef: string | undefined;
|
|
@@ -80,6 +81,9 @@ export const setExecutorRef = (ref: string) => {
|
|
|
80
81
|
export const setAdvisorRef = (ref: string) => {
|
|
81
82
|
advisorRef = ref;
|
|
82
83
|
};
|
|
84
|
+
export const setAdvisorAgentsMdContextRef = (enabled: boolean) => {
|
|
85
|
+
advisorAgentsMdContextRef = enabled;
|
|
86
|
+
};
|
|
83
87
|
/** Returns model refs explicitly persisted in the global Advisor config. */
|
|
84
88
|
export const getPersistedModelRefs = () => ({
|
|
85
89
|
advisor: persistedAdvisorRef,
|
|
@@ -234,6 +238,7 @@ export const setShowUsageFooterRef = (enabled: boolean) => {
|
|
|
234
238
|
* imported mutable bindings, which can be snapshotted by extension loaders.
|
|
235
239
|
*/
|
|
236
240
|
export const getAdvisorSettings = () => ({
|
|
241
|
+
agentsMdContext: advisorAgentsMdContextRef,
|
|
237
242
|
alwaysOn: alwaysOnRef,
|
|
238
243
|
autoLoopGate: advisorAutoLoopGateRef,
|
|
239
244
|
blockOnBlocked: advisorBlockOnBlockedRef,
|
package/src/config/storage.ts
CHANGED
|
@@ -52,6 +52,27 @@ export const readExistingConfig = (path: string): RecordValue => {
|
|
|
52
52
|
}
|
|
53
53
|
};
|
|
54
54
|
|
|
55
|
+
const readConfigForSave = (path: string): RecordValue => {
|
|
56
|
+
if (!existsSync(path)) {
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
let parsed: unknown;
|
|
60
|
+
try {
|
|
61
|
+
parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
62
|
+
} catch (error) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Cannot save Advisor configuration at ${path}: existing JSON is malformed; fix advisor.json before saving.`,
|
|
65
|
+
{ cause: error }
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
if (!isRecord(parsed)) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`Cannot save Advisor configuration at ${path}: configuration must be a JSON object.`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
return parsed;
|
|
74
|
+
};
|
|
75
|
+
|
|
55
76
|
const shouldPersistConfigKey = (
|
|
56
77
|
key: SavedConfigKey,
|
|
57
78
|
persistAdvisor: boolean,
|
|
@@ -197,7 +218,6 @@ export const saveConfig = (
|
|
|
197
218
|
const path = join(getAgentDir(), "advisor.json");
|
|
198
219
|
const persistAdvisor = options.persistAdvisor ?? true;
|
|
199
220
|
const persistExecutor = options.persistExecutor ?? true;
|
|
200
|
-
const existing = readExistingConfig(path);
|
|
201
221
|
const current = currentConfigState();
|
|
202
222
|
const baseline = loadedConfigPath === path ? loadedConfigState : undefined;
|
|
203
223
|
const changedKeys = baseline
|
|
@@ -208,6 +228,7 @@ export const saveConfig = (
|
|
|
208
228
|
if (changedKeys.length === 0) {
|
|
209
229
|
return path;
|
|
210
230
|
}
|
|
231
|
+
const existing = readConfigForSave(path);
|
|
211
232
|
const data = applyChangedConfigValues(
|
|
212
233
|
existing,
|
|
213
234
|
current,
|
|
@@ -240,7 +261,7 @@ export const saveConfig = (
|
|
|
240
261
|
/** Outcome logging is deliberately written only to the global Pi configuration. */
|
|
241
262
|
export const saveGlobalOutcomeLogging = (enabled: boolean) => {
|
|
242
263
|
const path = join(getAgentDir(), "advisor.json");
|
|
243
|
-
const existing =
|
|
264
|
+
const existing = readConfigForSave(path);
|
|
244
265
|
writeFileSync(
|
|
245
266
|
path,
|
|
246
267
|
`${JSON.stringify({ ...existing, advisorOutcomeLogging: enabled }, null, 2)}\n`
|
package/src/config/types.ts
CHANGED
package/src/config.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// biome-ignore lint/performance/noBarrelFile: this facade intentionally preserves the public config module contract.
|
|
2
2
|
export { parseArgs } from "./config/args.ts";
|
|
3
3
|
export {
|
|
4
|
+
advisorAgentsMdContextRef,
|
|
4
5
|
advisorAutoLoopGateRef,
|
|
5
6
|
advisorBlockOnBlockedRef,
|
|
6
7
|
advisorCollapseResponsesRef,
|
|
@@ -34,6 +35,7 @@ export {
|
|
|
34
35
|
getAdvisorSettings,
|
|
35
36
|
getPersistedModelRefs,
|
|
36
37
|
isSimpleMode,
|
|
38
|
+
setAdvisorAgentsMdContextRef,
|
|
37
39
|
setAdvisorAutoLoopGateRef,
|
|
38
40
|
setAdvisorBlockOnBlockedRef,
|
|
39
41
|
setAdvisorCollapseResponsesRef,
|
package/src/image-attachments.ts
CHANGED
|
@@ -72,9 +72,13 @@ export const readImageFiles = async (
|
|
|
72
72
|
remainingBytes: number,
|
|
73
73
|
remainingCount: number
|
|
74
74
|
): Promise<{ images: ImageAttachment[]; omitted: number }> => {
|
|
75
|
-
const names =
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
const names = [
|
|
76
|
+
...new Set(
|
|
77
|
+
Array.isArray(requested)
|
|
78
|
+
? requested.filter((path) => isString(path) && imageFileCandidate(path))
|
|
79
|
+
: []
|
|
80
|
+
),
|
|
81
|
+
];
|
|
78
82
|
if (!enabled || !names.length) {
|
|
79
83
|
return { images: [], omitted: names.length };
|
|
80
84
|
}
|