pi-advisor-flow 0.9.2-dev.5 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +9 -5
- package/dist/index.js +404 -65
- 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/consultation.ts +311 -41
- package/src/tools/model-access.ts +7 -3
- package/src/tools/register-ask-advisor.ts +109 -23
- 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)));
|
|
4108
|
+
}
|
|
4109
|
+
};
|
|
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));
|
|
3960
4128
|
}
|
|
3961
|
-
|
|
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);
|
|
3962
4144
|
};
|
|
3963
|
-
var consultAdvisor = async (ctx, question, signal, onChunk, trigger = "executor-requested", gitContext, draft, includeUntracked, includeTracked, onScout, currentInvocationId) => {
|
|
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);
|
|
@@ -8188,6 +8488,9 @@ var renderPartialAdvisorResult = (box, result, expanded, theme, context) => {
|
|
|
8188
8488
|
var thinkingPreview = (details) => details?.thinking?.trim() ? `${details.thinking.slice(0, 300)}${details.thinking.length > 300 ? "…" : ""}` : "";
|
|
8189
8489
|
var finalResultLines = (details, advice, theme) => {
|
|
8190
8490
|
const lines = [renderAdvisorResponseHeader(hasSoundVerdict(advice), theme)];
|
|
8491
|
+
if (details?.followUp) {
|
|
8492
|
+
lines.push(theme.fg("dim", " Follow-up consultation"));
|
|
8493
|
+
}
|
|
8191
8494
|
if (details?.advisor) {
|
|
8192
8495
|
lines.push(theme.fg("dim", ` ${details.advisor}`));
|
|
8193
8496
|
}
|
|
@@ -8255,6 +8558,31 @@ var assertAdvisorModelAccess = (ctx) => {
|
|
|
8255
8558
|
throw new Error(accessReason);
|
|
8256
8559
|
}
|
|
8257
8560
|
};
|
|
8561
|
+
var resolveFollowUp = (params, session) => {
|
|
8562
|
+
if (!params.followUpTo) {
|
|
8563
|
+
return;
|
|
8564
|
+
}
|
|
8565
|
+
if (!params.question?.trim() || params.draft !== undefined || params.gitContext !== undefined || params.includeTrackedFiles?.length || params.includeUntracked?.length) {
|
|
8566
|
+
throw new Error("Follow-up consultations accept only followUpTo and a non-empty question. Issue a fresh consultation for new context or attachments.");
|
|
8567
|
+
}
|
|
8568
|
+
const cached = session.followUpFor(params.followUpTo);
|
|
8569
|
+
if (!cached.payload) {
|
|
8570
|
+
throw new Error(`Follow-up unavailable for adviceId ${params.followUpTo}: ${cached.reason ?? "the cached payload is unavailable"}. Issue a fresh consultation.`);
|
|
8571
|
+
}
|
|
8572
|
+
return { adviceId: params.followUpTo, payload: cached.payload };
|
|
8573
|
+
};
|
|
8574
|
+
var sameModelNoticeFor = (modelRef) => (modelRef ?? advisorRef) === advisorRef ? sameModelAdvisorNotice : fallbackSameModelAdvisorNotice;
|
|
8575
|
+
var skippedSameModelResult = (id, notice, reservedCalls, session) => {
|
|
8576
|
+
session.releaseCall(id);
|
|
8577
|
+
reservedCalls.delete(id);
|
|
8578
|
+
return {
|
|
8579
|
+
content: [{ text: notice, type: "text" }],
|
|
8580
|
+
details: {
|
|
8581
|
+
skipReason: notice,
|
|
8582
|
+
text: notice
|
|
8583
|
+
}
|
|
8584
|
+
};
|
|
8585
|
+
};
|
|
8258
8586
|
var claimTrackedHandoff = (session, includeTrackedFiles) => {
|
|
8259
8587
|
if (!includeTrackedFiles?.length) {
|
|
8260
8588
|
return;
|
|
@@ -8275,23 +8603,18 @@ var registerAskAdvisorTool = ({
|
|
|
8275
8603
|
session
|
|
8276
8604
|
}) => {
|
|
8277
8605
|
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
|
|
8606
|
+
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.",
|
|
8279
8607
|
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
|
-
};
|
|
8608
|
+
let followUp;
|
|
8291
8609
|
try {
|
|
8292
8610
|
loadConfig(ctx);
|
|
8293
|
-
|
|
8294
|
-
|
|
8611
|
+
followUp = resolveFollowUp(params, session);
|
|
8612
|
+
if (followUp) {
|
|
8613
|
+
assertAdvisorFollowUpPrivacy(followUp.payload, ctx);
|
|
8614
|
+
}
|
|
8615
|
+
const modelRef = followUp?.payload.model;
|
|
8616
|
+
if (sameModelAdvisorDisabled(ctx, ctx.model, modelRef)) {
|
|
8617
|
+
return skippedSameModelResult(_id, sameModelNoticeFor(modelRef), reservedCalls, session);
|
|
8295
8618
|
}
|
|
8296
8619
|
assertAdvisorModelAccess(ctx);
|
|
8297
8620
|
} catch (error) {
|
|
@@ -8337,8 +8660,8 @@ var registerAskAdvisorTool = ({
|
|
|
8337
8660
|
}
|
|
8338
8661
|
};
|
|
8339
8662
|
}
|
|
8340
|
-
if (sameModelAdvisorDisabled(ctx)) {
|
|
8341
|
-
return
|
|
8663
|
+
if (sameModelAdvisorDisabled(ctx, ctx.model, followUp?.payload.model)) {
|
|
8664
|
+
return skippedSameModelResult(_id, sameModelNoticeFor(followUp?.payload.model), reservedCalls, session);
|
|
8342
8665
|
}
|
|
8343
8666
|
if (!simpleMode && !session.canConsult(getAdvisorMaxCallsPerSession(), _id)) {
|
|
8344
8667
|
throw new Error("Advisor call budget exhausted for this session.");
|
|
@@ -8368,7 +8691,8 @@ var registerAskAdvisorTool = ({
|
|
|
8368
8691
|
const result = await requestAdvisor(ctx, resolveAdvisorRequest(params.question), signal, (t, tx) => coalescedUpdate.update({
|
|
8369
8692
|
content: [{ text: tx, type: "text" }],
|
|
8370
8693
|
details: {
|
|
8371
|
-
advisor: advisorRef,
|
|
8694
|
+
advisor: followUp?.payload.model ?? advisorRef,
|
|
8695
|
+
followUp: Boolean(followUp),
|
|
8372
8696
|
question: resolveAdvisorRequest(params.question),
|
|
8373
8697
|
scout: scoutDetails,
|
|
8374
8698
|
text: tx,
|
|
@@ -8379,17 +8703,19 @@ var registerAskAdvisorTool = ({
|
|
|
8379
8703
|
coalescedUpdate.update({
|
|
8380
8704
|
content: [{ text: scoutDetails.text ?? "", type: "text" }],
|
|
8381
8705
|
details: {
|
|
8382
|
-
advisor: advisorRef,
|
|
8706
|
+
advisor: followUp?.payload.model ?? advisorRef,
|
|
8707
|
+
followUp: Boolean(followUp),
|
|
8383
8708
|
question: resolveAdvisorRequest(params.question),
|
|
8384
8709
|
scout: scoutDetails
|
|
8385
8710
|
}
|
|
8386
8711
|
});
|
|
8387
|
-
}, _id);
|
|
8712
|
+
}, _id, followUp, (adviceId, payload, replacesAdviceId) => session.captureFollowUp(adviceId, payload, replacesAdviceId));
|
|
8388
8713
|
flushUpdate();
|
|
8389
8714
|
session.issueAdvice(result.adviceId, result.markdown, result.trigger, Boolean(result.draftBytes), normalizedQuestion);
|
|
8390
8715
|
session.recordInvocation({
|
|
8391
8716
|
cost: advisorUsageCost(result.usage),
|
|
8392
8717
|
executionEffect: "continued",
|
|
8718
|
+
followUp: result.followUp,
|
|
8393
8719
|
kind: "markdown",
|
|
8394
8720
|
model: result.model,
|
|
8395
8721
|
trigger: "executor-requested",
|
|
@@ -8403,6 +8729,7 @@ var registerAskAdvisorTool = ({
|
|
|
8403
8729
|
advisor: result.model,
|
|
8404
8730
|
agentRulesBytes: result.agentRulesBytes,
|
|
8405
8731
|
draftBytes: result.draftBytes,
|
|
8732
|
+
followUp: result.followUp,
|
|
8406
8733
|
imageBytes: result.imageBytes,
|
|
8407
8734
|
imageCount: result.imageCount,
|
|
8408
8735
|
imageOmissions: result.imageOmissions,
|
|
@@ -8421,7 +8748,7 @@ var registerAskAdvisorTool = ({
|
|
|
8421
8748
|
const response = {
|
|
8422
8749
|
content: [
|
|
8423
8750
|
{
|
|
8424
|
-
text:
|
|
8751
|
+
text: `${result.followUp ? "Advisor follow-up" : "Advisor"} (${result.model})
|
|
8425
8752
|
|
|
8426
8753
|
${result.markdown}`,
|
|
8427
8754
|
type: "text"
|
|
@@ -8440,7 +8767,7 @@ ${result.markdown}`,
|
|
|
8440
8767
|
executionEffect: "continued",
|
|
8441
8768
|
failure: "provider-error",
|
|
8442
8769
|
kind: "markdown",
|
|
8443
|
-
model: advisorRef,
|
|
8770
|
+
model: followUp?.payload.model ?? advisorRef,
|
|
8444
8771
|
trigger: "executor-requested"
|
|
8445
8772
|
});
|
|
8446
8773
|
updateAdvisorUsageStatus(ctx, session);
|
|
@@ -8460,6 +8787,9 @@ ${result.markdown}`,
|
|
|
8460
8787
|
draft: Type.Optional(Type.String({
|
|
8461
8788
|
description: "Concise untrusted draft for plan or completion review; claims are not verification evidence."
|
|
8462
8789
|
})),
|
|
8790
|
+
followUpTo: Type.Optional(Type.String({
|
|
8791
|
+
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."
|
|
8792
|
+
})),
|
|
8463
8793
|
force: Type.Optional(Type.Boolean({
|
|
8464
8794
|
description: "Set true only when you judge a decision genuinely material after a consultation was screened out; bypasses screening."
|
|
8465
8795
|
})),
|
|
@@ -8477,11 +8807,11 @@ ${result.markdown}`,
|
|
|
8477
8807
|
}))
|
|
8478
8808
|
}),
|
|
8479
8809
|
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."
|
|
8810
|
+
"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
8811
|
],
|
|
8482
8812
|
promptSnippet: "Consult the Advisor using its existing context; attach a draft for plan or completion review",
|
|
8483
8813
|
renderCall(args, theme) {
|
|
8484
|
-
return renderAdvisorCallBox(args.question?.trim(), theme);
|
|
8814
|
+
return renderAdvisorCallBox(args.question?.trim(), theme, Boolean(args.followUpTo));
|
|
8485
8815
|
},
|
|
8486
8816
|
renderResult(result, options, theme, context) {
|
|
8487
8817
|
return renderAdvisorResult(result, options, theme, context);
|
|
@@ -8678,6 +9008,14 @@ ${guidelines.map((rule) => `- ${rule}`).join(`
|
|
|
8678
9008
|
`)}`
|
|
8679
9009
|
} : undefined;
|
|
8680
9010
|
});
|
|
9011
|
+
pi.on("input", () => {
|
|
9012
|
+
session.clearFollowUps();
|
|
9013
|
+
});
|
|
9014
|
+
pi.on("tool_result", (event) => {
|
|
9015
|
+
if (event.toolName !== "ask_advisor") {
|
|
9016
|
+
session.advanceFollowUpToolCall();
|
|
9017
|
+
}
|
|
9018
|
+
});
|
|
8681
9019
|
pi.on("tool_call", (event, ctx) => {
|
|
8682
9020
|
if (session.blocked) {
|
|
8683
9021
|
return {
|
|
@@ -8717,6 +9055,7 @@ ${guidelines.map((rule) => `- ${rule}`).join(`
|
|
|
8717
9055
|
});
|
|
8718
9056
|
pi.on("session_shutdown", (_event, ctx) => {
|
|
8719
9057
|
reservedCalls.clear();
|
|
9058
|
+
session.clearFollowUps();
|
|
8720
9059
|
session.clearCallReservations();
|
|
8721
9060
|
scoutStatus.clear(ctx);
|
|
8722
9061
|
herdrBlock.clear();
|