pi-advisor-flow 0.9.2-dev.5 → 0.10.1-dev.1
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 +10 -0
- package/README.md +40 -5
- package/dist/index.js +464 -86
- package/package.json +2 -2
- package/src/commands/activation-preparation.ts +4 -0
- package/src/commands/manual-consultation.ts +41 -29
- package/src/commands/model-commands.ts +5 -1
- package/src/commands/model-picker.ts +44 -2
- package/src/commands/runtime.ts +9 -1
- package/src/commands/settings-persistence.ts +2 -0
- package/src/commands/types.ts +2 -0
- package/src/config/defaults.ts +6 -0
- package/src/config/schema.ts +7 -0
- package/src/config/state.ts +5 -0
- package/src/config/types.ts +1 -0
- package/src/config.ts +2 -0
- package/src/follow-up.ts +85 -0
- package/src/session-state.ts +33 -0
- package/src/tools/advisor-output.ts +28 -0
- package/src/tools/consultation.ts +311 -41
- package/src/tools/model-access.ts +7 -3
- package/src/tools/register-ask-advisor.ts +133 -31
- package/src/tools/register-lifecycle.ts +11 -0
- package/src/tools/render-advisor-result.ts +3 -0
- package/src/tools/render-common.ts +6 -2
- package/src/tools/types.ts +2 -0
- package/src/ui/settings-items.ts +36 -0
- package/src/ui/settings-mutations.ts +8 -0
- package/src/ui/settings-selector.ts +9 -0
- package/src/ui/types.ts +3 -0
package/dist/index.js
CHANGED
|
@@ -43,6 +43,7 @@ var GATE_FAILURE_MODES = [
|
|
|
43
43
|
// src/config/state.ts
|
|
44
44
|
var executorRef = "";
|
|
45
45
|
var advisorRef = "";
|
|
46
|
+
var advisorFallbackModelRef;
|
|
46
47
|
var advisorAgentsMdContextRef = true;
|
|
47
48
|
var persistedExecutorRef;
|
|
48
49
|
var persistedAdvisorRef;
|
|
@@ -95,6 +96,9 @@ var setExecutorRef = (ref) => {
|
|
|
95
96
|
var setAdvisorRef = (ref) => {
|
|
96
97
|
advisorRef = ref;
|
|
97
98
|
};
|
|
99
|
+
var setAdvisorFallbackModelRef = (ref) => {
|
|
100
|
+
advisorFallbackModelRef = ref?.trim() || undefined;
|
|
101
|
+
};
|
|
98
102
|
var setAdvisorAgentsMdContextRef = (enabled) => {
|
|
99
103
|
advisorAgentsMdContextRef = enabled;
|
|
100
104
|
};
|
|
@@ -251,6 +255,7 @@ var getAdvisorSettings = () => ({
|
|
|
251
255
|
effort: advisorEffortRef,
|
|
252
256
|
failureGate: advisorFailureGateRef,
|
|
253
257
|
failureMode: advisorFailureModeRef,
|
|
258
|
+
fallbackModel: advisorFallbackModelRef,
|
|
254
259
|
gitContext: advisorGitContextRef,
|
|
255
260
|
gitContextMaxChars: advisorGitContextMaxCharsRef,
|
|
256
261
|
herdrIntegration: advisorHerdrIntegrationRef,
|
|
@@ -521,6 +526,12 @@ var CONFIG_SCHEMA = {
|
|
|
521
526
|
persisted: true,
|
|
522
527
|
type: "boolean"
|
|
523
528
|
},
|
|
529
|
+
advisorFallbackModel: {
|
|
530
|
+
accepted: "a provider/model string",
|
|
531
|
+
current: () => configuredModelRef(advisorFallbackModelRef),
|
|
532
|
+
persisted: true,
|
|
533
|
+
type: "string"
|
|
534
|
+
},
|
|
524
535
|
advisorGitContext: {
|
|
525
536
|
accepted: GIT_CONTEXT_LEVELS.join(", "),
|
|
526
537
|
current: () => advisorGitContextRef,
|
|
@@ -868,6 +879,7 @@ import { CONFIG_DIR_NAME, getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
|
868
879
|
var resetDefaults = () => {
|
|
869
880
|
setExecutorRef("");
|
|
870
881
|
setAdvisorRef("");
|
|
882
|
+
setAdvisorFallbackModelRef(undefined);
|
|
871
883
|
setAdvisorAgentsMdContextRef(true);
|
|
872
884
|
setPersistedModelRefs(undefined, undefined);
|
|
873
885
|
setExecutorEffortRef(undefined);
|
|
@@ -928,6 +940,7 @@ var applyNonEmptyStringConfig = (value, apply) => {
|
|
|
928
940
|
var applyConfig = (config) => {
|
|
929
941
|
applyNonEmptyStringConfig(config.executor, setExecutorRef);
|
|
930
942
|
applyNonEmptyStringConfig(config.advisor, setAdvisorRef);
|
|
943
|
+
applyNonEmptyStringConfig(config.advisorFallbackModel, setAdvisorFallbackModelRef);
|
|
931
944
|
applyNonEmptyStringConfig(config.executorEffort, setExecutorEffortRef);
|
|
932
945
|
applyNonEmptyStringConfig(config.advisorEffort, setAdvisorEffortRef);
|
|
933
946
|
applyOptionalConfig(config, "advisorAgentsMdContext", setAdvisorAgentsMdContextRef);
|
|
@@ -1136,14 +1149,16 @@ var advisorModelAccess = (ctx) => {
|
|
|
1136
1149
|
}
|
|
1137
1150
|
return denial;
|
|
1138
1151
|
};
|
|
1139
|
-
var sameModelAdvisorDisabled = (ctx, model = ctx.model) => {
|
|
1140
|
-
|
|
1152
|
+
var sameModelAdvisorDisabled = (ctx, model = ctx.model, advisorModelRef = advisorRef) => {
|
|
1153
|
+
const selectedAdvisorRef = advisorModelRef ?? advisorRef;
|
|
1154
|
+
if (!(advisorDisableSameModelRef && selectedAdvisorRef && model)) {
|
|
1141
1155
|
return false;
|
|
1142
1156
|
}
|
|
1143
|
-
const [provider, id] = splitRef(
|
|
1157
|
+
const [provider, id] = splitRef(selectedAdvisorRef);
|
|
1144
1158
|
return provider === model.provider && id === model.id;
|
|
1145
1159
|
};
|
|
1146
1160
|
var sameModelAdvisorNotice = "Advisor disabled: executor and advisor are the same model.";
|
|
1161
|
+
var fallbackSameModelAdvisorNotice = "Advisor fallback skipped: executor and fallback Advisor are the same model.";
|
|
1147
1162
|
var advisorModelIsAllowed = (ctx) => advisorModelAccess(ctx).allowed && !sameModelAdvisorDisabled(ctx);
|
|
1148
1163
|
var advisorModelAccessReason = (ctx) => {
|
|
1149
1164
|
const access = advisorModelAccess(ctx);
|
|
@@ -1465,6 +1480,9 @@ class SearchableModelSelector extends ModelSelectorAdapter {
|
|
|
1465
1480
|
}
|
|
1466
1481
|
}
|
|
1467
1482
|
|
|
1483
|
+
// src/ui/types.ts
|
|
1484
|
+
var FALLBACK_ADVISOR_MODEL_DISABLED = "Disabled (no fallback)";
|
|
1485
|
+
|
|
1468
1486
|
// src/commands/model-picker.ts
|
|
1469
1487
|
var selectAdvisorModels = async (ctx, options) => {
|
|
1470
1488
|
if (!ctx.hasUI) {
|
|
@@ -1478,7 +1496,13 @@ var selectAdvisorModels = async (ctx, options) => {
|
|
|
1478
1496
|
const allOptions = [
|
|
1479
1497
|
...new Set(refs ?? [options.executor, options.advisor].filter((ref) => Boolean(ref)))
|
|
1480
1498
|
];
|
|
1481
|
-
let {
|
|
1499
|
+
let {
|
|
1500
|
+
advisor,
|
|
1501
|
+
advisorEffort,
|
|
1502
|
+
advisorFallbackModel,
|
|
1503
|
+
executor,
|
|
1504
|
+
executorEffort
|
|
1505
|
+
} = options;
|
|
1482
1506
|
if (options.selectExecutor) {
|
|
1483
1507
|
const selectedExecutor = await ctx.ui.custom((tui, theme, keybindings, done) => new SearchableModelSelector({
|
|
1484
1508
|
allOptions,
|
|
@@ -1521,10 +1545,36 @@ var selectAdvisorModels = async (ctx, options) => {
|
|
|
1521
1545
|
advisor = selectedAdvisor;
|
|
1522
1546
|
advisorEffort = selectedEffort(selectedAdvisorEffort);
|
|
1523
1547
|
}
|
|
1548
|
+
const selectedFallback = await ctx.ui.custom((tui, theme, keybindings, done) => new SearchableModelSelector({
|
|
1549
|
+
allOptions: [
|
|
1550
|
+
FALLBACK_ADVISOR_MODEL_DISABLED,
|
|
1551
|
+
...new Set([
|
|
1552
|
+
...allOptions,
|
|
1553
|
+
...refs || !advisorFallbackModel ? [] : [advisorFallbackModel]
|
|
1554
|
+
])
|
|
1555
|
+
],
|
|
1556
|
+
currentOption: advisorFallbackModel ?? FALLBACK_ADVISOR_MODEL_DISABLED,
|
|
1557
|
+
keybindings,
|
|
1558
|
+
onCancel: () => done(undefined),
|
|
1559
|
+
onSelect: done,
|
|
1560
|
+
theme,
|
|
1561
|
+
title: "Select Fallback Advisor Model (optional)",
|
|
1562
|
+
tui
|
|
1563
|
+
}));
|
|
1564
|
+
if (selectedFallback === undefined) {
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
advisorFallbackModel = selectedFallback === FALLBACK_ADVISOR_MODEL_DISABLED ? undefined : selectedFallback;
|
|
1524
1568
|
if (!(advisor && executor)) {
|
|
1525
1569
|
return;
|
|
1526
1570
|
}
|
|
1527
|
-
return {
|
|
1571
|
+
return {
|
|
1572
|
+
advisor,
|
|
1573
|
+
advisorEffort,
|
|
1574
|
+
advisorFallbackModel,
|
|
1575
|
+
executor,
|
|
1576
|
+
executorEffort
|
|
1577
|
+
};
|
|
1528
1578
|
};
|
|
1529
1579
|
|
|
1530
1580
|
// src/herdr-shared.ts
|
|
@@ -3891,17 +3941,56 @@ var advisorImageNotice = (context) => {
|
|
|
3891
3941
|
|
|
3892
3942
|
Image disclosure: ${disclosure}. Only the images explicitly attached below have pixels available. Other image markers, image paths, and text descriptions are not visual evidence.`;
|
|
3893
3943
|
};
|
|
3894
|
-
var
|
|
3895
|
-
const
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3944
|
+
var advisorPrivacyKey = (ctx) => {
|
|
3945
|
+
const settings = getAdvisorSettings();
|
|
3946
|
+
return JSON.stringify({
|
|
3947
|
+
agentsMdContext: settings.agentsMdContext,
|
|
3948
|
+
contextMaxChars: settings.contextMaxChars,
|
|
3949
|
+
cwd: ctx.cwd,
|
|
3950
|
+
gitContext: settings.gitContext,
|
|
3951
|
+
gitContextMaxChars: settings.gitContextMaxChars,
|
|
3952
|
+
projectTrusted: ctx.isProjectTrusted(),
|
|
3953
|
+
redactSecrets: settings.redactSecrets,
|
|
3954
|
+
scoutEnabled: settings.scoutEnabled,
|
|
3955
|
+
toolPolicies: settings.toolPolicies,
|
|
3956
|
+
toolResultMaxBytes: settings.toolResultMaxBytes,
|
|
3957
|
+
toolResultMaxLines: settings.toolResultMaxLines,
|
|
3958
|
+
trackedFileContent: settings.trackedFileContent,
|
|
3959
|
+
untrackedContent: settings.untrackedContent
|
|
3960
|
+
});
|
|
3961
|
+
};
|
|
3962
|
+
var assertAdvisorFollowUpPrivacy = (payload, ctx) => {
|
|
3963
|
+
if (payload.privacyKey !== advisorPrivacyKey(ctx)) {
|
|
3964
|
+
throw new Error("Follow-up consultation unavailable: privacy or disclosure settings changed after the original consultation. Issue a fresh consultation.");
|
|
3899
3965
|
}
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3966
|
+
};
|
|
3967
|
+
var prepareFollowUp = (followUp, question, ctx) => {
|
|
3968
|
+
if (!question?.trim()) {
|
|
3969
|
+
throw new Error("Follow-up consultations require a non-empty question. Issue a fresh consultation if no question is needed.");
|
|
3903
3970
|
}
|
|
3904
|
-
|
|
3971
|
+
assertAdvisorFollowUpPrivacy(followUp.payload, ctx);
|
|
3972
|
+
const outboundQuestion = advisorRedactSecretsRef || followUp.payload.redacted ? redactSecrets(question) : question;
|
|
3973
|
+
const messages = structuredClone(followUp.payload.messages);
|
|
3974
|
+
messages.push({
|
|
3975
|
+
content: [
|
|
3976
|
+
{
|
|
3977
|
+
text: `Follow-up focus:
|
|
3978
|
+
${outboundQuestion}`,
|
|
3979
|
+
type: "text"
|
|
3980
|
+
}
|
|
3981
|
+
],
|
|
3982
|
+
role: "user",
|
|
3983
|
+
timestamp: Date.now()
|
|
3984
|
+
});
|
|
3985
|
+
return {
|
|
3986
|
+
messages,
|
|
3987
|
+
privacyKey: followUp.payload.privacyKey,
|
|
3988
|
+
redacted: advisorRedactSecretsRef || followUp.payload.redacted,
|
|
3989
|
+
systemPrompt: followUp.payload.systemPrompt
|
|
3990
|
+
};
|
|
3991
|
+
};
|
|
3992
|
+
var prepareFreshRequest = async (options, resolved) => {
|
|
3993
|
+
const { question } = options;
|
|
3905
3994
|
const context = await assembleConsultationContext({
|
|
3906
3995
|
...options,
|
|
3907
3996
|
supportsImages: resolved.model.input.includes("image")
|
|
@@ -3929,42 +4018,136 @@ ${escapeRepositoryText(label)}: attached image pixels (untrusted data).`,
|
|
|
3929
4018
|
timestamp: Date.now()
|
|
3930
4019
|
}
|
|
3931
4020
|
];
|
|
3932
|
-
|
|
4021
|
+
return {
|
|
4022
|
+
context,
|
|
3933
4023
|
messages,
|
|
4024
|
+
privacyKey: advisorPrivacyKey(options.ctx),
|
|
4025
|
+
redacted: advisorRedactSecretsRef,
|
|
4026
|
+
systemPrompt: options.systemPrompt
|
|
4027
|
+
};
|
|
4028
|
+
};
|
|
4029
|
+
var combinedFallbackError = (primaryModel, primaryError, fallbackModel, fallbackError) => new Error(`Advisor model ${primaryModel} failed: ${primaryError.message}. Fallback Advisor model ${fallbackModel} failed: ${fallbackError.message}.`, { cause: fallbackError });
|
|
4030
|
+
var advisorContextDetails = (context) => ({
|
|
4031
|
+
agentRulesBytes: context?.projectRules?.bytes || undefined,
|
|
4032
|
+
draftBytes: context?.draftText ? Buffer.byteLength(context.draftText, "utf-8") : undefined,
|
|
4033
|
+
imageBytes: context?.images.reduce((sum, item) => sum + Buffer.from(item.image.data, "base64").length, 0) || undefined,
|
|
4034
|
+
imageCount: context?.images.length,
|
|
4035
|
+
imageOmissions: context?.imageOmissions || undefined,
|
|
4036
|
+
imagePartsSeen: context?.imagePartsSeen || undefined,
|
|
4037
|
+
preferenceBytes: context?.preferences?.bytes,
|
|
4038
|
+
scout: context?.scout,
|
|
4039
|
+
trackedBytes: context?.tracked.reduce((sum, item) => sum + item.bytes, 0) || undefined,
|
|
4040
|
+
untrackedBytes: context?.untracked.reduce((sum, item) => sum + item.bytes, 0) || undefined
|
|
4041
|
+
});
|
|
4042
|
+
var runAdvisorAttempt = async (options, prepared, resolved, modelRef) => {
|
|
4043
|
+
const streamed = await collectTextStream(resolved, {
|
|
4044
|
+
messages: structuredClone(prepared.messages),
|
|
3934
4045
|
onChunk: options.onChunk,
|
|
3935
4046
|
reasoning: advisorEffortRef,
|
|
3936
|
-
signal,
|
|
3937
|
-
systemPrompt
|
|
4047
|
+
signal: options.signal,
|
|
4048
|
+
systemPrompt: prepared.systemPrompt
|
|
3938
4049
|
});
|
|
3939
|
-
const markdown = streamed
|
|
4050
|
+
const { text: markdown } = streamed;
|
|
3940
4051
|
if (!markdown.trim()) {
|
|
3941
4052
|
throw new AdvisorNoAdviceError;
|
|
3942
4053
|
}
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
4054
|
+
return {
|
|
4055
|
+
...advisorContextDetails(prepared.context),
|
|
4056
|
+
followUp: Boolean(options.followUp),
|
|
4057
|
+
followUpPayload: {
|
|
4058
|
+
depth: options.followUp ? options.followUp.payload.depth + 1 : 0,
|
|
4059
|
+
messages: structuredClone(prepared.messages),
|
|
4060
|
+
model: modelRef,
|
|
4061
|
+
privacyKey: prepared.privacyKey,
|
|
4062
|
+
redacted: prepared.redacted,
|
|
4063
|
+
systemPrompt: prepared.systemPrompt
|
|
4064
|
+
},
|
|
3950
4065
|
markdown,
|
|
3951
|
-
model:
|
|
3952
|
-
preferenceBytes: context.preferences?.bytes,
|
|
4066
|
+
model: modelRef,
|
|
3953
4067
|
thinkingText: streamed.thinking,
|
|
3954
|
-
trackedBytes: context.tracked.reduce((sum, item) => sum + item.bytes, 0) || undefined,
|
|
3955
|
-
untrackedBytes: context.untracked.reduce((sum, item) => sum + item.bytes, 0) || undefined,
|
|
3956
4068
|
usage: streamed.usage
|
|
3957
4069
|
};
|
|
3958
|
-
|
|
3959
|
-
|
|
4070
|
+
};
|
|
4071
|
+
var runFallbackAdvisorAttempt = async (options, primaryModel, primaryError, prepared) => {
|
|
4072
|
+
const { ctx } = options;
|
|
4073
|
+
let request = prepared;
|
|
4074
|
+
if (!advisorFallbackModelRef) {
|
|
4075
|
+
throw primaryError;
|
|
4076
|
+
}
|
|
4077
|
+
const fallbackModel = advisorFallbackModelRef;
|
|
4078
|
+
if (fallbackModel === primaryModel) {
|
|
4079
|
+
throw combinedFallbackError(primaryModel, primaryError, fallbackModel, new Error("fallback is the same model as the primary Advisor"));
|
|
4080
|
+
}
|
|
4081
|
+
if (sameModelAdvisorDisabled(ctx, ctx.model, fallbackModel)) {
|
|
4082
|
+
throw combinedFallbackError(primaryModel, primaryError, fallbackModel, new Error(fallbackSameModelAdvisorNotice));
|
|
4083
|
+
}
|
|
4084
|
+
const fallbackAccessReason = advisorModelAccessReason(ctx);
|
|
4085
|
+
if (fallbackAccessReason) {
|
|
4086
|
+
throw combinedFallbackError(primaryModel, primaryError, fallbackModel, new Error(fallbackAccessReason));
|
|
4087
|
+
}
|
|
4088
|
+
let resolvedFallback;
|
|
4089
|
+
try {
|
|
4090
|
+
resolvedFallback = await resolveConfiguredModel(ctx, fallbackModel, "Advisor fallback");
|
|
4091
|
+
} catch (error) {
|
|
4092
|
+
throw combinedFallbackError(primaryModel, primaryError, fallbackModel, error instanceof Error ? error : new Error(String(error)));
|
|
4093
|
+
}
|
|
4094
|
+
if (!request) {
|
|
4095
|
+
try {
|
|
4096
|
+
request = options.followUp ? prepareFollowUp(options.followUp, options.question, options.ctx) : await prepareFreshRequest(options, resolvedFallback);
|
|
4097
|
+
} catch (error) {
|
|
4098
|
+
throw combinedFallbackError(primaryModel, primaryError, fallbackModel, error instanceof Error ? error : new Error(String(error)));
|
|
4099
|
+
}
|
|
4100
|
+
}
|
|
4101
|
+
try {
|
|
4102
|
+
return await runAdvisorAttempt(options, request, resolvedFallback, fallbackModel);
|
|
4103
|
+
} catch (error) {
|
|
4104
|
+
if (options.signal?.aborted) {
|
|
4105
|
+
throw error;
|
|
4106
|
+
}
|
|
4107
|
+
throw combinedFallbackError(primaryModel, primaryError, fallbackModel, error instanceof Error ? error : new Error(String(error)));
|
|
3960
4108
|
}
|
|
3961
|
-
return response;
|
|
3962
4109
|
};
|
|
3963
|
-
var
|
|
4110
|
+
var collectAdvisorResponse = async (options) => {
|
|
4111
|
+
const { ctx, signal } = options;
|
|
4112
|
+
loadConfig(ctx);
|
|
4113
|
+
const primaryModel = options.followUp?.payload.model ?? advisorRef;
|
|
4114
|
+
if (sameModelAdvisorDisabled(ctx, ctx.model, primaryModel)) {
|
|
4115
|
+
throw new Error(primaryModel === advisorRef ? sameModelAdvisorNotice : fallbackSameModelAdvisorNotice);
|
|
4116
|
+
}
|
|
4117
|
+
const accessReason = advisorModelAccessReason(ctx);
|
|
4118
|
+
if (accessReason) {
|
|
4119
|
+
throw new Error(accessReason);
|
|
4120
|
+
}
|
|
4121
|
+
let primaryError;
|
|
4122
|
+
let prepared;
|
|
4123
|
+
let resolvedPrimary;
|
|
4124
|
+
try {
|
|
4125
|
+
resolvedPrimary = await resolveConfiguredModel(ctx, primaryModel, "Advisor");
|
|
4126
|
+
} catch (error) {
|
|
4127
|
+
primaryError = error instanceof Error ? error : new Error(String(error));
|
|
4128
|
+
}
|
|
4129
|
+
if (resolvedPrimary) {
|
|
4130
|
+
prepared = options.followUp ? prepareFollowUp(options.followUp, options.question, options.ctx) : await prepareFreshRequest(options, resolvedPrimary);
|
|
4131
|
+
try {
|
|
4132
|
+
return await runAdvisorAttempt(options, prepared, resolvedPrimary, primaryModel);
|
|
4133
|
+
} catch (error) {
|
|
4134
|
+
primaryError = error instanceof Error ? error : new Error(String(error));
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4137
|
+
if (!primaryError) {
|
|
4138
|
+
throw new Error("Advisor request failed before an error was recorded.");
|
|
4139
|
+
}
|
|
4140
|
+
if (signal?.aborted) {
|
|
4141
|
+
throw primaryError;
|
|
4142
|
+
}
|
|
4143
|
+
return runFallbackAdvisorAttempt(options, primaryModel, primaryError, prepared);
|
|
4144
|
+
};
|
|
4145
|
+
var consultAdvisor = async (ctx, question, signal, onChunk, trigger = "executor-requested", gitContext, draft, includeUntracked, includeTracked, onScout, currentInvocationId, followUp, onFollowUpPayload) => {
|
|
3964
4146
|
const result = await collectAdvisorResponse({
|
|
3965
4147
|
ctx,
|
|
3966
4148
|
currentInvocationId,
|
|
3967
4149
|
draft,
|
|
4150
|
+
followUp,
|
|
3968
4151
|
gitContext,
|
|
3969
4152
|
includeTracked,
|
|
3970
4153
|
includeUntracked,
|
|
@@ -3974,7 +4157,10 @@ var consultAdvisor = async (ctx, question, signal, onChunk, trigger = "executor-
|
|
|
3974
4157
|
signal,
|
|
3975
4158
|
systemPrompt: ADVISOR_SYSTEM
|
|
3976
4159
|
});
|
|
3977
|
-
|
|
4160
|
+
const adviceId = randomUUID2();
|
|
4161
|
+
const { followUpPayload, ...publicResult } = result;
|
|
4162
|
+
onFollowUpPayload?.(adviceId, followUpPayload, followUp?.adviceId);
|
|
4163
|
+
return { ...publicResult, adviceId, trigger };
|
|
3978
4164
|
};
|
|
3979
4165
|
var runAdvisorGate = async (ctx, question, trigger = "repeated-tool-call", signal, onChunk, onScout, currentInvocationId) => {
|
|
3980
4166
|
try {
|
|
@@ -4135,10 +4321,10 @@ var hiddenThinkingLabel = (theme) => {
|
|
|
4135
4321
|
};
|
|
4136
4322
|
var renderThinkingMarkdown = (thinking, theme) => piHideThinkingEnabled() ? hiddenThinkingLabel(theme) : new ThinkingMarkdown(thinking, theme);
|
|
4137
4323
|
var resolveAdvisorRequest = (question) => question?.trim() || undefined;
|
|
4138
|
-
var renderAdvisorCallBox = (question, theme) => {
|
|
4324
|
+
var renderAdvisorCallBox = (question, theme, followUp = false) => {
|
|
4139
4325
|
const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
4140
4326
|
const label = theme.fg("customMessageLabel", theme.bold("[advisor]"));
|
|
4141
|
-
const title = theme.fg("customMessageText", "Executor → Advisor");
|
|
4327
|
+
const title = theme.fg("customMessageText", followUp ? "Executor → Advisor · Follow-up" : "Executor → Advisor");
|
|
4142
4328
|
box.addChild(new Text(question ? `${label} ${title}
|
|
4143
4329
|
${theme.fg("dim", ` ${question}`)}` : `${label} ${title}`, 0, 0));
|
|
4144
4330
|
return box;
|
|
@@ -4324,6 +4510,59 @@ var renderScoutDetails = (box, scout, expanded, theme) => {
|
|
|
4324
4510
|
}
|
|
4325
4511
|
};
|
|
4326
4512
|
|
|
4513
|
+
// src/follow-up.ts
|
|
4514
|
+
var FOLLOW_UP_TTL_MS = 5 * 60 * 1000;
|
|
4515
|
+
var FOLLOW_UP_MAX_DEPTH = 3;
|
|
4516
|
+
var FOLLOW_UP_MAX_TOOL_CALLS = 3;
|
|
4517
|
+
var copy = (value) => structuredClone(value);
|
|
4518
|
+
|
|
4519
|
+
class AdvisorFollowUpCache {
|
|
4520
|
+
#entries = new Map;
|
|
4521
|
+
#now;
|
|
4522
|
+
constructor(now = Date.now) {
|
|
4523
|
+
this.#now = now;
|
|
4524
|
+
}
|
|
4525
|
+
clear() {
|
|
4526
|
+
this.#entries.clear();
|
|
4527
|
+
}
|
|
4528
|
+
advanceToolCall() {
|
|
4529
|
+
const now = this.#now();
|
|
4530
|
+
for (const [adviceId, entry] of this.#entries) {
|
|
4531
|
+
if (now >= entry.expiresAt || entry.toolCallsSince + 1 >= FOLLOW_UP_MAX_TOOL_CALLS) {
|
|
4532
|
+
this.#entries.delete(adviceId);
|
|
4533
|
+
continue;
|
|
4534
|
+
}
|
|
4535
|
+
entry.toolCallsSince += 1;
|
|
4536
|
+
}
|
|
4537
|
+
}
|
|
4538
|
+
capture(adviceId, payload, replacesAdviceId) {
|
|
4539
|
+
if (replacesAdviceId) {
|
|
4540
|
+
this.#entries.delete(replacesAdviceId);
|
|
4541
|
+
}
|
|
4542
|
+
this.#entries.set(adviceId, {
|
|
4543
|
+
...copy(payload),
|
|
4544
|
+
expiresAt: this.#now() + FOLLOW_UP_TTL_MS,
|
|
4545
|
+
toolCallsSince: 0
|
|
4546
|
+
});
|
|
4547
|
+
}
|
|
4548
|
+
get(adviceId) {
|
|
4549
|
+
const entry = this.#entries.get(adviceId);
|
|
4550
|
+
if (!entry) {
|
|
4551
|
+
return { reason: "the adviceId is unknown or no longer cached" };
|
|
4552
|
+
}
|
|
4553
|
+
if (this.#now() >= entry.expiresAt) {
|
|
4554
|
+
this.#entries.delete(adviceId);
|
|
4555
|
+
return { reason: "the cached payload has expired" };
|
|
4556
|
+
}
|
|
4557
|
+
if (entry.depth >= FOLLOW_UP_MAX_DEPTH) {
|
|
4558
|
+
return {
|
|
4559
|
+
reason: `the follow-up chain is limited to ${FOLLOW_UP_MAX_DEPTH} follow-ups`
|
|
4560
|
+
};
|
|
4561
|
+
}
|
|
4562
|
+
return { payload: copy(entry) };
|
|
4563
|
+
}
|
|
4564
|
+
}
|
|
4565
|
+
|
|
4327
4566
|
// src/jev/ledger.ts
|
|
4328
4567
|
var freshJevUsage = () => ({
|
|
4329
4568
|
cost: 0,
|
|
@@ -4545,8 +4784,12 @@ class AdvisorSessionState {
|
|
|
4545
4784
|
#consumedCalls = 0;
|
|
4546
4785
|
#callReservations = new Set;
|
|
4547
4786
|
#jev = new AdvisorJevLedgerState;
|
|
4787
|
+
#followUps;
|
|
4548
4788
|
#sessionTurnOrdinal = 0;
|
|
4549
4789
|
#turnsSinceConsultation = 0;
|
|
4790
|
+
constructor(now) {
|
|
4791
|
+
this.#followUps = new AdvisorFollowUpCache(now);
|
|
4792
|
+
}
|
|
4550
4793
|
resetTask() {
|
|
4551
4794
|
this.#repetition = freshRepetition();
|
|
4552
4795
|
this.#blockedReason = undefined;
|
|
@@ -4555,6 +4798,7 @@ class AdvisorSessionState {
|
|
|
4555
4798
|
this.#consumedCalls = 0;
|
|
4556
4799
|
this.#callReservations.clear();
|
|
4557
4800
|
this.#jev.reset();
|
|
4801
|
+
this.#followUps.clear();
|
|
4558
4802
|
this.#sessionTurnOrdinal = 0;
|
|
4559
4803
|
this.#turnsSinceConsultation = 0;
|
|
4560
4804
|
}
|
|
@@ -4619,6 +4863,18 @@ class AdvisorSessionState {
|
|
|
4619
4863
|
get consumedCalls() {
|
|
4620
4864
|
return this.#consumedCalls;
|
|
4621
4865
|
}
|
|
4866
|
+
advanceFollowUpToolCall() {
|
|
4867
|
+
this.#followUps.advanceToolCall();
|
|
4868
|
+
}
|
|
4869
|
+
clearFollowUps() {
|
|
4870
|
+
this.#followUps.clear();
|
|
4871
|
+
}
|
|
4872
|
+
followUpFor(adviceId) {
|
|
4873
|
+
return this.#followUps.get(adviceId);
|
|
4874
|
+
}
|
|
4875
|
+
captureFollowUp(adviceId, payload, replacesAdviceId) {
|
|
4876
|
+
this.#followUps.capture(adviceId, payload, replacesAdviceId);
|
|
4877
|
+
}
|
|
4622
4878
|
recordCompletedTurn() {
|
|
4623
4879
|
this.#sessionTurnOrdinal += 1;
|
|
4624
4880
|
this.#turnsSinceConsultation += 1;
|
|
@@ -4777,6 +5033,7 @@ class AdvisorSessionState {
|
|
|
4777
5033
|
`Budget: ${budget}`,
|
|
4778
5034
|
`Usage: ${formatAdvisorUsageTotals(totals)}`,
|
|
4779
5035
|
`Markdown advice: ${markdown.length} responses (${this.#ledger.draftConsultations} with drafts)`,
|
|
5036
|
+
`Follow-ups: ${markdown.filter((item) => item.followUp).length}`,
|
|
4780
5037
|
`Outcome reports: ${this.#ledger.outcomes}`,
|
|
4781
5038
|
`Gate decisions: ${this.#decisionsLine()}`,
|
|
4782
5039
|
`Loop matching: normalized tool signatures; ${this.#repetition.interventions} gate intervention${this.#repetition.interventions === 1 ? "" : "s"}`,
|
|
@@ -4834,7 +5091,7 @@ class CommandRuntime {
|
|
|
4834
5091
|
this.advisorSessionState = dependencies.sessionState ?? sessionStateFor(pi);
|
|
4835
5092
|
this.herdrActivity = dependencies.herdrActivity ?? herdrAdvisorActivity;
|
|
4836
5093
|
this.scoutStatus = dependencies.statusManager ?? new ScoutStatusManager(false);
|
|
4837
|
-
this.requestAdvisor = dependencies.consult ?? ((ctx, question, signal, onChunk, onScout, gitContext) => consultAdvisor(ctx, question, signal, onChunk, "manual", gitContext, undefined, undefined, undefined, onScout));
|
|
5094
|
+
this.requestAdvisor = dependencies.consult ?? ((ctx, question, signal, onChunk, onScout, gitContext) => consultAdvisor(ctx, question, signal, onChunk, "manual", gitContext, undefined, undefined, undefined, onScout, undefined, undefined, (adviceId, payload, replacesAdviceId) => this.advisorSessionState.captureFollowUp(adviceId, payload, replacesAdviceId)));
|
|
4838
5095
|
}
|
|
4839
5096
|
flowEnabled() {
|
|
4840
5097
|
return this.pi.getActiveTools().includes("ask_advisor");
|
|
@@ -4904,6 +5161,7 @@ var prepareActivationModels = async (runtime, ctx, announce, executorOverride, a
|
|
|
4904
5161
|
const selection = await selectAdvisorModels(ctx, {
|
|
4905
5162
|
advisor: advisorOverride || persisted.advisor ? advisorRef : "",
|
|
4906
5163
|
advisorEffort: advisorEffortRef,
|
|
5164
|
+
advisorFallbackModel: advisorFallbackModelRef,
|
|
4907
5165
|
executor: executorOverride || plan.pendingExecutor || persisted.executor ? executorRef : "",
|
|
4908
5166
|
executorEffort: executorEffortRef,
|
|
4909
5167
|
selectAdvisor: plan.selectAdvisor,
|
|
@@ -4913,6 +5171,7 @@ var prepareActivationModels = async (runtime, ctx, announce, executorOverride, a
|
|
|
4913
5171
|
return;
|
|
4914
5172
|
}
|
|
4915
5173
|
setAdvisorRef(selection.advisor);
|
|
5174
|
+
setAdvisorFallbackModelRef(selection.advisorFallbackModel);
|
|
4916
5175
|
setAdvisorEffortRef(selection.advisorEffort);
|
|
4917
5176
|
setExecutorRef(selection.executor);
|
|
4918
5177
|
setExecutorEffortRef(selection.executorEffort);
|
|
@@ -6030,7 +6289,15 @@ var startManualConsultation = async (runtime, ctx, question, controller, scoutSt
|
|
|
6030
6289
|
const finishHerdrActivity = runtime.herdrActivity.start();
|
|
6031
6290
|
let scoutDetails;
|
|
6032
6291
|
try {
|
|
6033
|
-
const {
|
|
6292
|
+
const {
|
|
6293
|
+
adviceId,
|
|
6294
|
+
agentRulesBytes,
|
|
6295
|
+
followUp,
|
|
6296
|
+
markdown,
|
|
6297
|
+
model,
|
|
6298
|
+
preferenceBytes,
|
|
6299
|
+
usage
|
|
6300
|
+
} = await runtime.requestAdvisor(ctx, question, controller.signal, (thinking, text) => {
|
|
6034
6301
|
if (controller.signal.aborted) {
|
|
6035
6302
|
return;
|
|
6036
6303
|
}
|
|
@@ -6055,8 +6322,9 @@ var startManualConsultation = async (runtime, ctx, question, controller, scoutSt
|
|
|
6055
6322
|
runtime.advisorSessionState.recordInvocation({
|
|
6056
6323
|
cost: advisorUsageCost(usage),
|
|
6057
6324
|
executionEffect: "continued",
|
|
6325
|
+
followUp,
|
|
6058
6326
|
kind: "markdown",
|
|
6059
|
-
model: advisorRef,
|
|
6327
|
+
model: model ?? advisorRef,
|
|
6060
6328
|
trigger: "manual",
|
|
6061
6329
|
usage
|
|
6062
6330
|
});
|
|
@@ -6065,8 +6333,10 @@ var startManualConsultation = async (runtime, ctx, question, controller, scoutSt
|
|
|
6065
6333
|
}
|
|
6066
6334
|
runtime.updateAdvisorUsageStatus(ctx);
|
|
6067
6335
|
const details = {
|
|
6068
|
-
|
|
6336
|
+
adviceId,
|
|
6337
|
+
advisor: model ?? advisorRef,
|
|
6069
6338
|
agentRulesBytes,
|
|
6339
|
+
followUp,
|
|
6070
6340
|
preferenceBytes,
|
|
6071
6341
|
question,
|
|
6072
6342
|
text: markdown
|
|
@@ -6223,7 +6493,7 @@ var registerModelCommands = (runtime) => {
|
|
|
6223
6493
|
handler: (args, ctx) => activateAdvisor(runtime, args, ctx)
|
|
6224
6494
|
});
|
|
6225
6495
|
runtime.pi.registerCommand("advisor-models", {
|
|
6226
|
-
description: "Select and persist the Executor and
|
|
6496
|
+
description: "Select and persist the Executor, Advisor, and optional fallback models with reasoning levels",
|
|
6227
6497
|
handler: async (_args, ctx) => {
|
|
6228
6498
|
if (!(loadCommandConfig(ctx) && ctx.hasUI)) {
|
|
6229
6499
|
return;
|
|
@@ -6232,6 +6502,7 @@ var registerModelCommands = (runtime) => {
|
|
|
6232
6502
|
const selection = await selectAdvisorModels(ctx, {
|
|
6233
6503
|
advisor: persisted.advisor ? advisorRef : "",
|
|
6234
6504
|
advisorEffort: advisorEffortRef,
|
|
6505
|
+
advisorFallbackModel: advisorFallbackModelRef,
|
|
6235
6506
|
executor: runtime.pendingExecutorModelRef ?? (persisted.executor ? executorRef : ""),
|
|
6236
6507
|
executorEffort: executorEffortRef,
|
|
6237
6508
|
selectAdvisor: true,
|
|
@@ -6242,6 +6513,7 @@ var registerModelCommands = (runtime) => {
|
|
|
6242
6513
|
}
|
|
6243
6514
|
setExecutorRef(selection.executor);
|
|
6244
6515
|
setAdvisorRef(selection.advisor);
|
|
6516
|
+
setAdvisorFallbackModelRef(selection.advisorFallbackModel);
|
|
6245
6517
|
setExecutorEffortRef(selection.executorEffort);
|
|
6246
6518
|
setAdvisorEffortRef(selection.advisorEffort);
|
|
6247
6519
|
const path = saveConfig(ctx, {
|
|
@@ -7288,6 +7560,25 @@ var jevItems = (settings, theme, tui) => [
|
|
|
7288
7560
|
])
|
|
7289
7561
|
}
|
|
7290
7562
|
];
|
|
7563
|
+
var advisorFallbackModelItem = (settings, modelRefs, keybindings, theme, tui) => ({
|
|
7564
|
+
currentValue: settings.fallbackModel ?? FALLBACK_ADVISOR_MODEL_DISABLED,
|
|
7565
|
+
description: "Retry a failed Advisor request once with this optional model; the primary model remains unchanged.",
|
|
7566
|
+
id: "fallbackModel",
|
|
7567
|
+
label: "Fallback Advisor model",
|
|
7568
|
+
submenu: (_currentValue, done) => new SearchableModelSelector({
|
|
7569
|
+
allOptions: [
|
|
7570
|
+
FALLBACK_ADVISOR_MODEL_DISABLED,
|
|
7571
|
+
...new Set(modelRefs ?? (settings.fallbackModel ? [settings.fallbackModel] : []))
|
|
7572
|
+
],
|
|
7573
|
+
currentOption: settings.fallbackModel ?? FALLBACK_ADVISOR_MODEL_DISABLED,
|
|
7574
|
+
keybindings: keybindings ?? getKeybindings(),
|
|
7575
|
+
onCancel: done,
|
|
7576
|
+
onSelect: (value) => done(value === FALLBACK_ADVISOR_MODEL_DISABLED ? "" : value),
|
|
7577
|
+
theme,
|
|
7578
|
+
title: "Fallback Advisor model",
|
|
7579
|
+
tui
|
|
7580
|
+
})
|
|
7581
|
+
});
|
|
7291
7582
|
var advisorModelWhitelistItem = (settings, modelRefs, keybindings, theme, tui) => ({
|
|
7292
7583
|
currentValue: settings.modelWhitelist?.length ? settings.modelWhitelist.join(", ") : "Any model",
|
|
7293
7584
|
description: "Only the exact provider/model references listed here may call the Advisor; an empty list allows every model.",
|
|
@@ -7309,6 +7600,7 @@ var advisorModelWhitelistItem = (settings, modelRefs, keybindings, theme, tui) =
|
|
|
7309
7600
|
});
|
|
7310
7601
|
var createSettingsItems = ({
|
|
7311
7602
|
effortLevels,
|
|
7603
|
+
fallbackModel,
|
|
7312
7604
|
modelWhitelist,
|
|
7313
7605
|
presets,
|
|
7314
7606
|
settings,
|
|
@@ -7336,7 +7628,8 @@ var createSettingsItems = ({
|
|
|
7336
7628
|
id: "alwaysOn",
|
|
7337
7629
|
label: "Always on",
|
|
7338
7630
|
values: TOGGLE_VALUES
|
|
7339
|
-
}
|
|
7631
|
+
},
|
|
7632
|
+
fallbackModel
|
|
7340
7633
|
];
|
|
7341
7634
|
if (settings.simpleMode) {
|
|
7342
7635
|
items.push(modelWhitelist, toggle("disableSameModel", "Disable same-model Advisor", "Skip advice when the active Executor and Advisor use the same provider/model; turn off to allow higher-effort same-model reviews.", settings.disableSameModel, true));
|
|
@@ -7558,6 +7851,10 @@ var applyCoreMutation = (settings, id, value, presets) => {
|
|
|
7558
7851
|
settings.alwaysOn = value === "On";
|
|
7559
7852
|
return true;
|
|
7560
7853
|
}
|
|
7854
|
+
case "fallbackModel": {
|
|
7855
|
+
settings.fallbackModel = value === FALLBACK_ADVISOR_MODEL_DISABLED ? undefined : value.trim() || undefined;
|
|
7856
|
+
return true;
|
|
7857
|
+
}
|
|
7561
7858
|
case "effort": {
|
|
7562
7859
|
settings.effort = value;
|
|
7563
7860
|
return true;
|
|
@@ -7742,9 +8039,11 @@ class AdvisorSettingsSelector {
|
|
|
7742
8039
|
}
|
|
7743
8040
|
return defaultLabel(text, selected);
|
|
7744
8041
|
};
|
|
8042
|
+
const fallbackModel = advisorFallbackModelItem(this.settings, this.options.modelRefs, this.options.keybindings, this.options.theme, this.options.tui);
|
|
7745
8043
|
const modelWhitelist = advisorModelWhitelistItem(this.settings, this.options.modelRefs, this.options.keybindings, this.options.theme, this.options.tui);
|
|
7746
8044
|
const items = createSettingsItems({
|
|
7747
8045
|
effortLevels: this.options.effortLevels,
|
|
8046
|
+
fallbackModel,
|
|
7748
8047
|
modelWhitelist,
|
|
7749
8048
|
presets: this.presets,
|
|
7750
8049
|
settings: this.settings,
|
|
@@ -7798,6 +8097,7 @@ var applySessionSettings = (settings) => {
|
|
|
7798
8097
|
setAdvisorEffortRef(settings.effort === "Default (Model Default)" ? undefined : settings.effort);
|
|
7799
8098
|
setContextMaxCharsRef(settings.contextMaxChars);
|
|
7800
8099
|
setAdvisorPlanGateRef(settings.planGate);
|
|
8100
|
+
setAdvisorFallbackModelRef(settings.fallbackModel);
|
|
7801
8101
|
setAdvisorFailureGateRef(settings.failureGate);
|
|
7802
8102
|
setAdvisorCompletionGateRef(settings.completionGate);
|
|
7803
8103
|
setAdvisorDisableSameModelRef(settings.disableSameModel ?? true);
|
|
@@ -8099,7 +8399,32 @@ var registerJevTurnGate = (on, registration) => {
|
|
|
8099
8399
|
};
|
|
8100
8400
|
|
|
8101
8401
|
// src/tools/register-ask-advisor.ts
|
|
8402
|
+
import { Type as Type2 } from "typebox";
|
|
8403
|
+
|
|
8404
|
+
// src/tools/advisor-output.ts
|
|
8102
8405
|
import { Type } from "typebox";
|
|
8406
|
+
var advisorOutputSchema = Type.Object({
|
|
8407
|
+
adviceId: Type.Optional(Type.String()),
|
|
8408
|
+
advisor: Type.Optional(Type.String()),
|
|
8409
|
+
followUp: Type.Optional(Type.Boolean()),
|
|
8410
|
+
jev: Type.Optional(Type.Object({
|
|
8411
|
+
kind: Type.Union([Type.Literal("screened"), Type.Literal("repeat")]),
|
|
8412
|
+
reason: Type.String(),
|
|
8413
|
+
skipped: Type.Boolean()
|
|
8414
|
+
})),
|
|
8415
|
+
skipReason: Type.Optional(Type.String()),
|
|
8416
|
+
text: Type.String({
|
|
8417
|
+
description: "Advisor Markdown or consultation skip notice."
|
|
8418
|
+
}),
|
|
8419
|
+
usage: Type.Optional(Type.Object({
|
|
8420
|
+
cacheRead: Type.Optional(Type.Number()),
|
|
8421
|
+
cacheWrite: Type.Optional(Type.Number()),
|
|
8422
|
+
cost: Type.Optional(Type.Number()),
|
|
8423
|
+
input: Type.Optional(Type.Number()),
|
|
8424
|
+
output: Type.Optional(Type.Number()),
|
|
8425
|
+
totalTokens: Type.Optional(Type.Number())
|
|
8426
|
+
}))
|
|
8427
|
+
});
|
|
8103
8428
|
|
|
8104
8429
|
// src/tools/render-advisor-result.ts
|
|
8105
8430
|
import { getMarkdownTheme as getMarkdownTheme4 } from "@earendil-works/pi-coding-agent";
|
|
@@ -8188,6 +8513,9 @@ var renderPartialAdvisorResult = (box, result, expanded, theme, context) => {
|
|
|
8188
8513
|
var thinkingPreview = (details) => details?.thinking?.trim() ? `${details.thinking.slice(0, 300)}${details.thinking.length > 300 ? "…" : ""}` : "";
|
|
8189
8514
|
var finalResultLines = (details, advice, theme) => {
|
|
8190
8515
|
const lines = [renderAdvisorResponseHeader(hasSoundVerdict(advice), theme)];
|
|
8516
|
+
if (details?.followUp) {
|
|
8517
|
+
lines.push(theme.fg("dim", " Follow-up consultation"));
|
|
8518
|
+
}
|
|
8191
8519
|
if (details?.advisor) {
|
|
8192
8520
|
lines.push(theme.fg("dim", ` ${details.advisor}`));
|
|
8193
8521
|
}
|
|
@@ -8255,6 +8583,32 @@ var assertAdvisorModelAccess = (ctx) => {
|
|
|
8255
8583
|
throw new Error(accessReason);
|
|
8256
8584
|
}
|
|
8257
8585
|
};
|
|
8586
|
+
var resolveFollowUp = (params, session) => {
|
|
8587
|
+
if (!params.followUpTo) {
|
|
8588
|
+
return;
|
|
8589
|
+
}
|
|
8590
|
+
if (!params.question?.trim() || params.draft !== undefined || params.gitContext !== undefined || params.includeTrackedFiles?.length || params.includeUntracked?.length) {
|
|
8591
|
+
throw new Error("Follow-up consultations accept only followUpTo and a non-empty question. Issue a fresh consultation for new context or attachments.");
|
|
8592
|
+
}
|
|
8593
|
+
const cached = session.followUpFor(params.followUpTo);
|
|
8594
|
+
if (!cached.payload) {
|
|
8595
|
+
throw new Error(`Follow-up unavailable for adviceId ${params.followUpTo}: ${cached.reason ?? "the cached payload is unavailable"}. Issue a fresh consultation.`);
|
|
8596
|
+
}
|
|
8597
|
+
return { adviceId: params.followUpTo, payload: cached.payload };
|
|
8598
|
+
};
|
|
8599
|
+
var sameModelNoticeFor = (modelRef) => (modelRef ?? advisorRef) === advisorRef ? sameModelAdvisorNotice : fallbackSameModelAdvisorNotice;
|
|
8600
|
+
var skippedSameModelResult = (id, notice, reservedCalls, session) => {
|
|
8601
|
+
session.releaseCall(id);
|
|
8602
|
+
reservedCalls.delete(id);
|
|
8603
|
+
return {
|
|
8604
|
+
content: [{ text: notice, type: "text" }],
|
|
8605
|
+
details: {
|
|
8606
|
+
skipReason: notice,
|
|
8607
|
+
text: notice
|
|
8608
|
+
},
|
|
8609
|
+
structuredContent: { skipReason: notice, text: notice }
|
|
8610
|
+
};
|
|
8611
|
+
};
|
|
8258
8612
|
var claimTrackedHandoff = (session, includeTrackedFiles) => {
|
|
8259
8613
|
if (!includeTrackedFiles?.length) {
|
|
8260
8614
|
return;
|
|
@@ -8275,23 +8629,18 @@ var registerAskAdvisorTool = ({
|
|
|
8275
8629
|
session
|
|
8276
8630
|
}) => {
|
|
8277
8631
|
pi.registerTool({
|
|
8278
|
-
description: "Consult the on-demand Advisor model for strategic guidance. Call with an empty object for a contextual review; attach an optional draft for concrete plan or completion review. If the Advisor explicitly names a missing file, you may make a sequential
|
|
8632
|
+
description: "Consult the on-demand Advisor model for strategic guidance. Call with an empty object for a contextual review; attach an optional draft for concrete plan or completion review. Use followUpTo with a new question to continue one cached consultation without rebuilding its redacted context. If the Advisor explicitly names a missing file, you may make a sequential fresh consultation with includeTrackedFiles when enabled and relevant. In Pi Codemode, gather and filter deterministic tool results first, then call tools.ask_advisor. Nested results are not automatically reconstructed: pass only permitted, concise summaries in draft (untrusted, capped at 8 KiB, not independently verified). Draft does not inherit source-tool disclosure policies; use gitContext for patches within the user's allowance.",
|
|
8279
8633
|
async execute(_id, params, signal, onUpdate, ctx) {
|
|
8280
|
-
|
|
8281
|
-
session.releaseCall(_id);
|
|
8282
|
-
reservedCalls.delete(_id);
|
|
8283
|
-
return {
|
|
8284
|
-
content: [{ text: sameModelAdvisorNotice, type: "text" }],
|
|
8285
|
-
details: {
|
|
8286
|
-
skipReason: sameModelAdvisorNotice,
|
|
8287
|
-
text: sameModelAdvisorNotice
|
|
8288
|
-
}
|
|
8289
|
-
};
|
|
8290
|
-
};
|
|
8634
|
+
let followUp;
|
|
8291
8635
|
try {
|
|
8292
8636
|
loadConfig(ctx);
|
|
8293
|
-
|
|
8294
|
-
|
|
8637
|
+
followUp = resolveFollowUp(params, session);
|
|
8638
|
+
if (followUp) {
|
|
8639
|
+
assertAdvisorFollowUpPrivacy(followUp.payload, ctx);
|
|
8640
|
+
}
|
|
8641
|
+
const modelRef = followUp?.payload.model;
|
|
8642
|
+
if (sameModelAdvisorDisabled(ctx, ctx.model, modelRef)) {
|
|
8643
|
+
return skippedSameModelResult(_id, sameModelNoticeFor(modelRef), reservedCalls, session);
|
|
8295
8644
|
}
|
|
8296
8645
|
assertAdvisorModelAccess(ctx);
|
|
8297
8646
|
} catch (error) {
|
|
@@ -8325,20 +8674,22 @@ var registerAskAdvisorTool = ({
|
|
|
8325
8674
|
const skipText = screeningSkipText(screening);
|
|
8326
8675
|
session.releaseCall(_id);
|
|
8327
8676
|
reservedCalls.delete(_id);
|
|
8677
|
+
const skipped = {
|
|
8678
|
+
jev: {
|
|
8679
|
+
kind: screening.kind,
|
|
8680
|
+
reason: screening.reason,
|
|
8681
|
+
skipped: true
|
|
8682
|
+
},
|
|
8683
|
+
text: skipText
|
|
8684
|
+
};
|
|
8328
8685
|
return {
|
|
8329
8686
|
content: [{ text: skipText, type: "text" }],
|
|
8330
|
-
details:
|
|
8331
|
-
|
|
8332
|
-
kind: screening.kind,
|
|
8333
|
-
reason: screening.reason,
|
|
8334
|
-
skipped: true
|
|
8335
|
-
},
|
|
8336
|
-
text: skipText
|
|
8337
|
-
}
|
|
8687
|
+
details: skipped,
|
|
8688
|
+
structuredContent: skipped
|
|
8338
8689
|
};
|
|
8339
8690
|
}
|
|
8340
|
-
if (sameModelAdvisorDisabled(ctx)) {
|
|
8341
|
-
return
|
|
8691
|
+
if (sameModelAdvisorDisabled(ctx, ctx.model, followUp?.payload.model)) {
|
|
8692
|
+
return skippedSameModelResult(_id, sameModelNoticeFor(followUp?.payload.model), reservedCalls, session);
|
|
8342
8693
|
}
|
|
8343
8694
|
if (!simpleMode && !session.canConsult(getAdvisorMaxCallsPerSession(), _id)) {
|
|
8344
8695
|
throw new Error("Advisor call budget exhausted for this session.");
|
|
@@ -8368,7 +8719,8 @@ var registerAskAdvisorTool = ({
|
|
|
8368
8719
|
const result = await requestAdvisor(ctx, resolveAdvisorRequest(params.question), signal, (t, tx) => coalescedUpdate.update({
|
|
8369
8720
|
content: [{ text: tx, type: "text" }],
|
|
8370
8721
|
details: {
|
|
8371
|
-
advisor: advisorRef,
|
|
8722
|
+
advisor: followUp?.payload.model ?? advisorRef,
|
|
8723
|
+
followUp: Boolean(followUp),
|
|
8372
8724
|
question: resolveAdvisorRequest(params.question),
|
|
8373
8725
|
scout: scoutDetails,
|
|
8374
8726
|
text: tx,
|
|
@@ -8379,17 +8731,19 @@ var registerAskAdvisorTool = ({
|
|
|
8379
8731
|
coalescedUpdate.update({
|
|
8380
8732
|
content: [{ text: scoutDetails.text ?? "", type: "text" }],
|
|
8381
8733
|
details: {
|
|
8382
|
-
advisor: advisorRef,
|
|
8734
|
+
advisor: followUp?.payload.model ?? advisorRef,
|
|
8735
|
+
followUp: Boolean(followUp),
|
|
8383
8736
|
question: resolveAdvisorRequest(params.question),
|
|
8384
8737
|
scout: scoutDetails
|
|
8385
8738
|
}
|
|
8386
8739
|
});
|
|
8387
|
-
}, _id);
|
|
8740
|
+
}, _id, followUp, (adviceId, payload, replacesAdviceId) => session.captureFollowUp(adviceId, payload, replacesAdviceId));
|
|
8388
8741
|
flushUpdate();
|
|
8389
8742
|
session.issueAdvice(result.adviceId, result.markdown, result.trigger, Boolean(result.draftBytes), normalizedQuestion);
|
|
8390
8743
|
session.recordInvocation({
|
|
8391
8744
|
cost: advisorUsageCost(result.usage),
|
|
8392
8745
|
executionEffect: "continued",
|
|
8746
|
+
followUp: result.followUp,
|
|
8393
8747
|
kind: "markdown",
|
|
8394
8748
|
model: result.model,
|
|
8395
8749
|
trigger: "executor-requested",
|
|
@@ -8403,6 +8757,7 @@ var registerAskAdvisorTool = ({
|
|
|
8403
8757
|
advisor: result.model,
|
|
8404
8758
|
agentRulesBytes: result.agentRulesBytes,
|
|
8405
8759
|
draftBytes: result.draftBytes,
|
|
8760
|
+
followUp: result.followUp,
|
|
8406
8761
|
imageBytes: result.imageBytes,
|
|
8407
8762
|
imageCount: result.imageCount,
|
|
8408
8763
|
imageOmissions: result.imageOmissions,
|
|
@@ -8418,16 +8773,26 @@ var registerAskAdvisorTool = ({
|
|
|
8418
8773
|
if (usage) {
|
|
8419
8774
|
details.usage = usage;
|
|
8420
8775
|
}
|
|
8776
|
+
const structuredContent = {
|
|
8777
|
+
adviceId: result.adviceId,
|
|
8778
|
+
advisor: result.model,
|
|
8779
|
+
followUp: Boolean(result.followUp),
|
|
8780
|
+
text: result.markdown
|
|
8781
|
+
};
|
|
8782
|
+
if (usage) {
|
|
8783
|
+
structuredContent.usage = usage;
|
|
8784
|
+
}
|
|
8421
8785
|
const response = {
|
|
8422
8786
|
content: [
|
|
8423
8787
|
{
|
|
8424
|
-
text:
|
|
8788
|
+
text: `${result.followUp ? "Advisor follow-up" : "Advisor"} (${result.model})
|
|
8425
8789
|
|
|
8426
8790
|
${result.markdown}`,
|
|
8427
8791
|
type: "text"
|
|
8428
8792
|
}
|
|
8429
8793
|
],
|
|
8430
|
-
details
|
|
8794
|
+
details,
|
|
8795
|
+
structuredContent
|
|
8431
8796
|
};
|
|
8432
8797
|
if (piUsage) {
|
|
8433
8798
|
response.usage = piUsage;
|
|
@@ -8440,7 +8805,7 @@ ${result.markdown}`,
|
|
|
8440
8805
|
executionEffect: "continued",
|
|
8441
8806
|
failure: "provider-error",
|
|
8442
8807
|
kind: "markdown",
|
|
8443
|
-
model: advisorRef,
|
|
8808
|
+
model: followUp?.payload.model ?? advisorRef,
|
|
8444
8809
|
trigger: "executor-requested"
|
|
8445
8810
|
});
|
|
8446
8811
|
updateAdvisorUsageStatus(ctx, session);
|
|
@@ -8456,32 +8821,36 @@ ${result.markdown}`,
|
|
|
8456
8821
|
},
|
|
8457
8822
|
label: "Ask Advisor",
|
|
8458
8823
|
name: "ask_advisor",
|
|
8459
|
-
|
|
8460
|
-
|
|
8824
|
+
outputSchema: advisorOutputSchema,
|
|
8825
|
+
parameters: Type2.Object({
|
|
8826
|
+
draft: Type2.Optional(Type2.String({
|
|
8461
8827
|
description: "Concise untrusted draft for plan or completion review; claims are not verification evidence."
|
|
8462
8828
|
})),
|
|
8463
|
-
|
|
8829
|
+
followUpTo: Type2.Optional(Type2.String({
|
|
8830
|
+
description: "Opaque adviceId from a prior successful consultation. Requires a new non-empty question and reuses that consultation's redacted payload; do not combine with draft, Git context, or file attachments."
|
|
8831
|
+
})),
|
|
8832
|
+
force: Type2.Optional(Type2.Boolean({
|
|
8464
8833
|
description: "Set true only when you judge a decision genuinely material after a consultation was screened out; bypasses screening."
|
|
8465
8834
|
})),
|
|
8466
|
-
gitContext:
|
|
8835
|
+
gitContext: Type2.Optional(Type2.Union([Type2.Literal("none"), Type2.Literal("summary"), Type2.Literal("full")], {
|
|
8467
8836
|
description: "How much of the working tree to include. Use full when the review depends on the exact code changes, such as a completion review. Use summary for changed file names only, or none when the question is not about the current changes. The user's configured allowance is the ceiling and a larger request is narrowed to it."
|
|
8468
8837
|
})),
|
|
8469
|
-
includeTrackedFiles:
|
|
8838
|
+
includeTrackedFiles: Type2.Optional(Type2.Array(Type2.String({
|
|
8470
8839
|
description: "Exact tracked repository-relative files to attach after the Advisor explicitly names a file it cannot review. Requires global advisorTrackedFileContent consent; current working-tree contents are sent as untrusted data."
|
|
8471
8840
|
}))),
|
|
8472
|
-
includeUntracked:
|
|
8841
|
+
includeUntracked: Type2.Optional(Type2.Array(Type2.String({
|
|
8473
8842
|
description: "Exact new repository-relative files to include only when user configuration allows it."
|
|
8474
8843
|
}))),
|
|
8475
|
-
question:
|
|
8844
|
+
question: Type2.Optional(Type2.String({
|
|
8476
8845
|
description: "The specific question or decision to get advice on. Omit this for normal reviews: the Advisor already has the conversation context."
|
|
8477
8846
|
}))
|
|
8478
8847
|
}),
|
|
8479
8848
|
promptGuidelines: [
|
|
8480
|
-
"Call ask_advisor with an empty object for general consultation. For a plan or completion review, include a concise draft naming work, validation, and remaining risks; its claims are not evidence. If the Advisor explicitly says it cannot review a specifically named file, you may make a sequential follow-up call with includeTrackedFiles when the file is relevant, permitted, and worth the shared call budget; do not infer paths or retry automatically."
|
|
8849
|
+
"Call ask_advisor with an empty object for general consultation. For a plan or completion review, include a concise draft naming work, validation, and remaining risks; its claims are not evidence. To drill into an existing answer, pass its adviceId as followUpTo with a new question and no draft, Git context, or attachments. If the Advisor explicitly says it cannot review a specifically named file, you may make a sequential follow-up call with includeTrackedFiles when the file is relevant, permitted, and worth the shared call budget; do not infer paths or retry automatically."
|
|
8481
8850
|
],
|
|
8482
8851
|
promptSnippet: "Consult the Advisor using its existing context; attach a draft for plan or completion review",
|
|
8483
8852
|
renderCall(args, theme) {
|
|
8484
|
-
return renderAdvisorCallBox(args.question?.trim(), theme);
|
|
8853
|
+
return renderAdvisorCallBox(args.question?.trim(), theme, Boolean(args.followUpTo));
|
|
8485
8854
|
},
|
|
8486
8855
|
renderResult(result, options, theme, context) {
|
|
8487
8856
|
return renderAdvisorResult(result, options, theme, context);
|
|
@@ -8678,6 +9047,14 @@ ${guidelines.map((rule) => `- ${rule}`).join(`
|
|
|
8678
9047
|
`)}`
|
|
8679
9048
|
} : undefined;
|
|
8680
9049
|
});
|
|
9050
|
+
pi.on("input", () => {
|
|
9051
|
+
session.clearFollowUps();
|
|
9052
|
+
});
|
|
9053
|
+
pi.on("tool_result", (event) => {
|
|
9054
|
+
if (event.toolName !== "ask_advisor") {
|
|
9055
|
+
session.advanceFollowUpToolCall();
|
|
9056
|
+
}
|
|
9057
|
+
});
|
|
8681
9058
|
pi.on("tool_call", (event, ctx) => {
|
|
8682
9059
|
if (session.blocked) {
|
|
8683
9060
|
return {
|
|
@@ -8717,6 +9094,7 @@ ${guidelines.map((rule) => `- ${rule}`).join(`
|
|
|
8717
9094
|
});
|
|
8718
9095
|
pi.on("session_shutdown", (_event, ctx) => {
|
|
8719
9096
|
reservedCalls.clear();
|
|
9097
|
+
session.clearFollowUps();
|
|
8720
9098
|
session.clearCallReservations();
|
|
8721
9099
|
scoutStatus.clear(ctx);
|
|
8722
9100
|
herdrBlock.clear();
|
|
@@ -8728,7 +9106,7 @@ ${guidelines.map((rule) => `- ${rule}`).join(`
|
|
|
8728
9106
|
|
|
8729
9107
|
// src/tools/register-outcome.ts
|
|
8730
9108
|
import { Text as Text6 } from "@earendil-works/pi-tui";
|
|
8731
|
-
import { Type as
|
|
9109
|
+
import { Type as Type3 } from "typebox";
|
|
8732
9110
|
var registerOutcomeTool = ({
|
|
8733
9111
|
appendOutcome: appendAdvisorOutcome,
|
|
8734
9112
|
pi,
|
|
@@ -8782,10 +9160,10 @@ var registerOutcomeTool = ({
|
|
|
8782
9160
|
},
|
|
8783
9161
|
label: "Record Advisor Outcome",
|
|
8784
9162
|
name: "record_advisor_outcome",
|
|
8785
|
-
parameters:
|
|
8786
|
-
adoption:
|
|
8787
|
-
adviceId:
|
|
8788
|
-
validationStatus:
|
|
9163
|
+
parameters: Type3.Object({
|
|
9164
|
+
adoption: Type3.String({ enum: ADOPTIONS }),
|
|
9165
|
+
adviceId: Type3.String(),
|
|
9166
|
+
validationStatus: Type3.String({ enum: VALIDATIONS })
|
|
8789
9167
|
}),
|
|
8790
9168
|
renderCall: () => new Text6("[advisor] Record outcome", 0, 0),
|
|
8791
9169
|
renderResult: (result) => new Text6(textFrom(result.content), 0, 0)
|