pi-advisor-flow 0.3.6 → 0.5.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 +38 -0
- package/README.md +11 -5
- package/extensions/index.ts +0 -10
- package/package.json +11 -13
- package/src/commands.ts +355 -118
- package/src/config.ts +29 -3
- package/src/model-stream.ts +123 -0
- package/src/scout-context.ts +34 -8
- package/src/scout.ts +7 -34
- package/src/session-state.ts +48 -2
- package/src/tools.ts +208 -121
- package/src/ui.ts +1060 -600
- package/src/usage.ts +199 -0
- package/src/telemetry.ts +0 -270
package/src/tools.ts
CHANGED
|
@@ -24,7 +24,6 @@ import {
|
|
|
24
24
|
advisorGitContextMaxCharsRef,
|
|
25
25
|
advisorGitContextRef,
|
|
26
26
|
advisorLoopThresholdRef,
|
|
27
|
-
advisorMaxCallsPerSessionRef,
|
|
28
27
|
advisorOutcomeLoggingRef,
|
|
29
28
|
advisorPlanGateRef,
|
|
30
29
|
advisorRedactSecretsRef,
|
|
@@ -35,6 +34,8 @@ import {
|
|
|
35
34
|
advisorUntrackedContentRef,
|
|
36
35
|
contextMaxCharsRef,
|
|
37
36
|
executorRef,
|
|
37
|
+
getAdvisorMaxCallsPerSession,
|
|
38
|
+
getAdvisorSettings,
|
|
38
39
|
isSimpleMode,
|
|
39
40
|
loadConfig,
|
|
40
41
|
} from "./config.js";
|
|
@@ -57,7 +58,12 @@ import {
|
|
|
57
58
|
herdrAdvisorBlock,
|
|
58
59
|
notifyHerdrAdvisorFailure,
|
|
59
60
|
} from "./herdr.js";
|
|
60
|
-
import {
|
|
61
|
+
import {
|
|
62
|
+
ADVISOR_STREAM_UPDATE_INTERVAL_MS,
|
|
63
|
+
collectTextStream,
|
|
64
|
+
createCoalescedUpdate,
|
|
65
|
+
resolveConfiguredModel,
|
|
66
|
+
} from "./model-stream.js";
|
|
61
67
|
import { ADOPTIONS, appendOutcome, VALIDATIONS } from "./outcomes.js";
|
|
62
68
|
import { readProjectPreferences } from "./preferences.js";
|
|
63
69
|
import {
|
|
@@ -68,6 +74,7 @@ import {
|
|
|
68
74
|
import {
|
|
69
75
|
buildScoutManifest,
|
|
70
76
|
reconstructScoutConversation,
|
|
77
|
+
SCOUT_MANIFEST_MAX_BYTES,
|
|
71
78
|
} from "./scout-context.js";
|
|
72
79
|
import {
|
|
73
80
|
AdvisorSessionState,
|
|
@@ -75,8 +82,13 @@ import {
|
|
|
75
82
|
type GateDecision,
|
|
76
83
|
type GateTrigger,
|
|
77
84
|
} from "./session-state.js";
|
|
78
|
-
import type { BenchmarkTelemetry } from "./telemetry.js";
|
|
79
85
|
import { readTrackedFiles, readUntrackedFiles } from "./untracked.js";
|
|
86
|
+
import {
|
|
87
|
+
advisorUsageCost,
|
|
88
|
+
advisorUsageForPi,
|
|
89
|
+
formatAdvisorUsage,
|
|
90
|
+
snapshotAdvisorUsage,
|
|
91
|
+
} from "./usage.js";
|
|
80
92
|
|
|
81
93
|
export type {
|
|
82
94
|
AdvisorInvocationRecord,
|
|
@@ -302,6 +314,7 @@ export interface AdvisorGateFailure {
|
|
|
302
314
|
markdown?: string;
|
|
303
315
|
message: string;
|
|
304
316
|
ok: false;
|
|
317
|
+
usage?: unknown;
|
|
305
318
|
}
|
|
306
319
|
export interface AdvisorConsultationResult {
|
|
307
320
|
adviceId: string;
|
|
@@ -327,14 +340,6 @@ export interface AdvisorGateResult {
|
|
|
327
340
|
}
|
|
328
341
|
export type AdvisorGateOutcome = AdvisorGateResult | AdvisorGateFailure;
|
|
329
342
|
|
|
330
|
-
export const advisorUsageCost = (usage: unknown): number | undefined => {
|
|
331
|
-
const value = usage as
|
|
332
|
-
| { cost?: { total?: unknown }; totalCost?: unknown }
|
|
333
|
-
| undefined;
|
|
334
|
-
const cost = value?.cost?.total ?? value?.totalCost;
|
|
335
|
-
return typeof cost === "number" ? cost : undefined;
|
|
336
|
-
};
|
|
337
|
-
|
|
338
343
|
const DECISION_LINE = /^Decision\s*:\s*(proceed|revise|blocked)\s*$/i;
|
|
339
344
|
const CODE_FENCE = /^(?:```|~~~)/;
|
|
340
345
|
const LINE_BREAK = /\r?\n/;
|
|
@@ -431,8 +436,7 @@ export const curateAdvisorConversation = async (
|
|
|
431
436
|
enabled = advisorScoutEnabledRef,
|
|
432
437
|
runScout: typeof runAdvisorScout = runAdvisorScout,
|
|
433
438
|
currentInvocationId?: string,
|
|
434
|
-
maxChars?: number
|
|
435
|
-
telemetry?: BenchmarkTelemetry
|
|
439
|
+
maxChars?: number
|
|
436
440
|
): Promise<{
|
|
437
441
|
conversation: string;
|
|
438
442
|
scout?: Exclude<ScoutOutcome, { cancelled: true }>;
|
|
@@ -445,7 +449,8 @@ export const curateAdvisorConversation = async (
|
|
|
445
449
|
}
|
|
446
450
|
const built = buildScoutManifest(ctx, {
|
|
447
451
|
currentInvocationId,
|
|
448
|
-
|
|
452
|
+
maxConversationChars: maxChars,
|
|
453
|
+
maxManifestBytes: SCOUT_MANIFEST_MAX_BYTES,
|
|
449
454
|
});
|
|
450
455
|
if (!built.ok) {
|
|
451
456
|
const scout: Exclude<ScoutOutcome, { cancelled: true }> = {
|
|
@@ -470,8 +475,7 @@ export const curateAdvisorConversation = async (
|
|
|
470
475
|
signal,
|
|
471
476
|
onScout,
|
|
472
477
|
undefined,
|
|
473
|
-
undefined
|
|
474
|
-
telemetry
|
|
478
|
+
undefined
|
|
475
479
|
);
|
|
476
480
|
if (!outcome.ok && outcome.cancelled) {
|
|
477
481
|
throw signal?.reason instanceof Error
|
|
@@ -504,8 +508,7 @@ const collectAdvisorResponse = async (
|
|
|
504
508
|
includeUntracked?: string[],
|
|
505
509
|
includeTracked?: string[],
|
|
506
510
|
onScout?: (event: ScoutLifecycleEvent) => void,
|
|
507
|
-
currentInvocationId?: string
|
|
508
|
-
telemetry?: BenchmarkTelemetry
|
|
511
|
+
currentInvocationId?: string
|
|
509
512
|
) => {
|
|
510
513
|
loadConfig(ctx);
|
|
511
514
|
const resolved = await resolveConfiguredModel(ctx, advisorRef, "Advisor");
|
|
@@ -551,8 +554,7 @@ const collectAdvisorResponse = async (
|
|
|
551
554
|
advisorScoutEnabledRef,
|
|
552
555
|
runAdvisorScout,
|
|
553
556
|
currentInvocationId,
|
|
554
|
-
conversationBudget
|
|
555
|
-
telemetry
|
|
557
|
+
conversationBudget
|
|
556
558
|
);
|
|
557
559
|
const { conversation, scout } = curated;
|
|
558
560
|
const preferences = await readProjectPreferences(
|
|
@@ -643,43 +645,22 @@ export const consultAdvisor = async (
|
|
|
643
645
|
includeUntracked?: string[],
|
|
644
646
|
includeTracked?: string[],
|
|
645
647
|
onScout?: (event: ScoutLifecycleEvent) => void,
|
|
646
|
-
currentInvocationId?: string
|
|
647
|
-
telemetry?: BenchmarkTelemetry
|
|
648
|
+
currentInvocationId?: string
|
|
648
649
|
): Promise<AdvisorConsultationResult> => {
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
telemetry
|
|
664
|
-
);
|
|
665
|
-
telemetry?.advisorEnd({
|
|
666
|
-
model: result.model,
|
|
667
|
-
outcome: "completed",
|
|
668
|
-
question,
|
|
669
|
-
response: result.markdown,
|
|
670
|
-
trigger,
|
|
671
|
-
usage: result.usage,
|
|
672
|
-
});
|
|
673
|
-
return { ...result, adviceId: randomUUID(), trigger };
|
|
674
|
-
} catch (error) {
|
|
675
|
-
telemetry?.advisorError({
|
|
676
|
-
category: signal?.aborted ? "cancelled" : "provider-error",
|
|
677
|
-
model: advisorRef,
|
|
678
|
-
question,
|
|
679
|
-
trigger,
|
|
680
|
-
});
|
|
681
|
-
throw error;
|
|
682
|
-
}
|
|
650
|
+
const result = await collectAdvisorResponse(
|
|
651
|
+
ctx,
|
|
652
|
+
ADVISOR_SYSTEM,
|
|
653
|
+
question,
|
|
654
|
+
signal,
|
|
655
|
+
onChunk,
|
|
656
|
+
gitContext,
|
|
657
|
+
draft,
|
|
658
|
+
includeUntracked,
|
|
659
|
+
includeTracked,
|
|
660
|
+
onScout,
|
|
661
|
+
currentInvocationId
|
|
662
|
+
);
|
|
663
|
+
return { ...result, adviceId: randomUUID(), trigger };
|
|
683
664
|
};
|
|
684
665
|
|
|
685
666
|
export const runAdvisorGate = async (
|
|
@@ -689,10 +670,8 @@ export const runAdvisorGate = async (
|
|
|
689
670
|
signal?: AbortSignal,
|
|
690
671
|
onChunk?: (thinking: string, text: string) => void,
|
|
691
672
|
onScout?: (event: ScoutLifecycleEvent) => void,
|
|
692
|
-
currentInvocationId?: string
|
|
693
|
-
telemetry?: BenchmarkTelemetry
|
|
673
|
+
currentInvocationId?: string
|
|
694
674
|
): Promise<AdvisorGateOutcome> => {
|
|
695
|
-
telemetry?.advisorStart({ model: advisorRef, question, trigger });
|
|
696
675
|
try {
|
|
697
676
|
const result = await collectAdvisorResponse(
|
|
698
677
|
ctx,
|
|
@@ -705,20 +684,11 @@ export const runAdvisorGate = async (
|
|
|
705
684
|
undefined,
|
|
706
685
|
undefined,
|
|
707
686
|
onScout,
|
|
708
|
-
currentInvocationId
|
|
709
|
-
telemetry
|
|
687
|
+
currentInvocationId
|
|
710
688
|
);
|
|
711
689
|
const parsed = parseAutomaticDecision(result.markdown);
|
|
712
|
-
telemetry?.advisorEnd({
|
|
713
|
-
model: result.model,
|
|
714
|
-
outcome: parsed.ok ? `decision:${parsed.decision}` : parsed.category,
|
|
715
|
-
question,
|
|
716
|
-
response: result.markdown,
|
|
717
|
-
trigger,
|
|
718
|
-
usage: result.usage,
|
|
719
|
-
});
|
|
720
690
|
if (!parsed.ok) {
|
|
721
|
-
return parsed;
|
|
691
|
+
return { ...parsed, usage: result.usage };
|
|
722
692
|
}
|
|
723
693
|
return {
|
|
724
694
|
...parsed,
|
|
@@ -728,12 +698,6 @@ export const runAdvisorGate = async (
|
|
|
728
698
|
usage: result.usage,
|
|
729
699
|
};
|
|
730
700
|
} catch (error) {
|
|
731
|
-
telemetry?.advisorError({
|
|
732
|
-
category: signal?.aborted ? "cancelled" : "provider-error",
|
|
733
|
-
model: advisorRef,
|
|
734
|
-
question,
|
|
735
|
-
trigger,
|
|
736
|
-
});
|
|
737
701
|
if (signal?.aborted) {
|
|
738
702
|
throw error;
|
|
739
703
|
}
|
|
@@ -749,6 +713,18 @@ export const runAdvisorGate = async (
|
|
|
749
713
|
}
|
|
750
714
|
};
|
|
751
715
|
|
|
716
|
+
const updateAdvisorUsageStatus = (
|
|
717
|
+
ctx: ExtensionContext,
|
|
718
|
+
session: AdvisorSessionState
|
|
719
|
+
) => {
|
|
720
|
+
if (ctx.hasUI) {
|
|
721
|
+
ctx.ui.setStatus(
|
|
722
|
+
"advisor-usage",
|
|
723
|
+
getAdvisorSettings().showUsageFooter ? session.usageStatus() : undefined
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
|
|
752
728
|
const notifyLocalFailure = (
|
|
753
729
|
ctx: ExtensionContext,
|
|
754
730
|
message: string,
|
|
@@ -773,12 +749,15 @@ export const gateFailureEffectForMode = (
|
|
|
773
749
|
: ("session-blocked" as const);
|
|
774
750
|
};
|
|
775
751
|
|
|
776
|
-
const gateDecisionEffect = (
|
|
752
|
+
const gateDecisionEffect = (
|
|
753
|
+
decision: GateDecision,
|
|
754
|
+
failureMode: "block-session" | "block-tool" | "warn-and-continue"
|
|
755
|
+
) => {
|
|
777
756
|
if (decision === "proceed") {
|
|
778
757
|
return "continued" as const;
|
|
779
758
|
}
|
|
780
759
|
return decision === "blocked"
|
|
781
|
-
? (
|
|
760
|
+
? gateFailureEffectForMode(failureMode)
|
|
782
761
|
: ("tool-blocked" as const);
|
|
783
762
|
};
|
|
784
763
|
|
|
@@ -786,15 +765,42 @@ const failureEffect = (
|
|
|
786
765
|
category: GateFailureCategory,
|
|
787
766
|
message: string,
|
|
788
767
|
ctx: ExtensionContext,
|
|
789
|
-
session: AdvisorSessionState
|
|
768
|
+
session: AdvisorSessionState,
|
|
769
|
+
failureMode: "block-session" | "block-tool" | "warn-and-continue"
|
|
790
770
|
) => {
|
|
791
771
|
const reason = `Advisor gate ${category}: ${message}`;
|
|
792
|
-
notifyLocalFailure(ctx, message,
|
|
772
|
+
notifyLocalFailure(ctx, message, failureMode === "block-session");
|
|
793
773
|
notifyHerdrAdvisorFailure("Advisor gate failure", reason);
|
|
794
|
-
if (
|
|
774
|
+
if (failureMode === "warn-and-continue") {
|
|
775
|
+
return { block: false, effect: "continued" as const, reason };
|
|
776
|
+
}
|
|
777
|
+
if (failureMode === "block-tool") {
|
|
778
|
+
return { block: true, effect: "tool-blocked" as const, reason };
|
|
779
|
+
}
|
|
780
|
+
session.block(reason);
|
|
781
|
+
herdrAdvisorBlock.set(reason);
|
|
782
|
+
if (advisorBlockOnBlockedRef) {
|
|
783
|
+
ctx.abort();
|
|
784
|
+
}
|
|
785
|
+
return { block: true, effect: "session-blocked" as const, reason };
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
const blockedDecisionEffect = (
|
|
789
|
+
reason: string,
|
|
790
|
+
ctx: ExtensionContext,
|
|
791
|
+
session: AdvisorSessionState,
|
|
792
|
+
failureMode: "block-session" | "block-tool" | "warn-and-continue"
|
|
793
|
+
) => {
|
|
794
|
+
if (failureMode === "warn-and-continue") {
|
|
795
|
+
if (ctx.hasUI) {
|
|
796
|
+
ctx.ui.notify(
|
|
797
|
+
"Advisor gate returned blocked; continuing by configuration.",
|
|
798
|
+
"warning"
|
|
799
|
+
);
|
|
800
|
+
}
|
|
795
801
|
return { block: false, effect: "continued" as const, reason };
|
|
796
802
|
}
|
|
797
|
-
if (
|
|
803
|
+
if (failureMode === "block-tool") {
|
|
798
804
|
return { block: true, effect: "tool-blocked" as const, reason };
|
|
799
805
|
}
|
|
800
806
|
session.block(reason);
|
|
@@ -814,7 +820,7 @@ const reserveAdvisorCall = (
|
|
|
814
820
|
if (event.toolName !== "ask_advisor" || isSimpleMode()) {
|
|
815
821
|
return;
|
|
816
822
|
}
|
|
817
|
-
if (!session.canConsult(
|
|
823
|
+
if (!session.canConsult(getAdvisorMaxCallsPerSession())) {
|
|
818
824
|
const message = "Advisor call budget exhausted for this session.";
|
|
819
825
|
if (ctx.hasUI) {
|
|
820
826
|
ctx.ui.notify(message, "warning");
|
|
@@ -840,12 +846,20 @@ const sendAutomaticGateCall = (pi: ExtensionAPI, event: ToolCallEvent) => {
|
|
|
840
846
|
);
|
|
841
847
|
};
|
|
842
848
|
|
|
843
|
-
const sendAutomaticGateFailure = (
|
|
849
|
+
const sendAutomaticGateFailure = (
|
|
850
|
+
pi: ExtensionAPI,
|
|
851
|
+
markdown: string,
|
|
852
|
+
usage?: unknown
|
|
853
|
+
) => {
|
|
854
|
+
const normalizedUsage = snapshotAdvisorUsage(usage);
|
|
844
855
|
pi.sendMessage(
|
|
845
856
|
{
|
|
846
857
|
content: markdown,
|
|
847
858
|
customType: "advisor-loop-result",
|
|
848
|
-
details: {
|
|
859
|
+
details: {
|
|
860
|
+
text: markdown,
|
|
861
|
+
...(normalizedUsage ? { usage: normalizedUsage } : {}),
|
|
862
|
+
},
|
|
849
863
|
display: true,
|
|
850
864
|
},
|
|
851
865
|
{ deliverAs: "steer" }
|
|
@@ -864,6 +878,9 @@ const sendAutomaticGateResult = (
|
|
|
864
878
|
advisor: result.model,
|
|
865
879
|
decision: result.decision,
|
|
866
880
|
text: result.markdown,
|
|
881
|
+
...(snapshotAdvisorUsage(result.usage)
|
|
882
|
+
? { usage: snapshotAdvisorUsage(result.usage) }
|
|
883
|
+
: {}),
|
|
867
884
|
},
|
|
868
885
|
display: true,
|
|
869
886
|
},
|
|
@@ -877,8 +894,7 @@ const handleAutomaticGate = async (
|
|
|
877
894
|
ctx: ExtensionContext,
|
|
878
895
|
session: AdvisorSessionState,
|
|
879
896
|
runGate: typeof runAdvisorGate,
|
|
880
|
-
scoutStatus: ScoutStatusManager
|
|
881
|
-
telemetry?: BenchmarkTelemetry
|
|
897
|
+
scoutStatus: ScoutStatusManager
|
|
882
898
|
): Promise<ToolCallEventResult | undefined> => {
|
|
883
899
|
if (
|
|
884
900
|
isSimpleMode() ||
|
|
@@ -893,12 +909,14 @@ const handleAutomaticGate = async (
|
|
|
893
909
|
return;
|
|
894
910
|
}
|
|
895
911
|
const reason = `Advisor loop gate: normalized signature for ${event.toolName} repeated ${advisorLoopThresholdRef} times without a materially different tool action.`;
|
|
896
|
-
|
|
912
|
+
const failureMode = advisorFailureModeRef;
|
|
913
|
+
if (!session.canConsult(getAdvisorMaxCallsPerSession())) {
|
|
897
914
|
const failure = failureEffect(
|
|
898
915
|
"budget-exhausted",
|
|
899
916
|
"Advisor gate call budget is exhausted.",
|
|
900
917
|
ctx,
|
|
901
|
-
session
|
|
918
|
+
session,
|
|
919
|
+
failureMode
|
|
902
920
|
);
|
|
903
921
|
return failure.block ? { block: true, reason: failure.reason } : undefined;
|
|
904
922
|
}
|
|
@@ -931,27 +949,30 @@ const handleAutomaticGate = async (
|
|
|
931
949
|
ensureGateCall();
|
|
932
950
|
}
|
|
933
951
|
},
|
|
934
|
-
event.toolCallId
|
|
935
|
-
telemetry
|
|
952
|
+
event.toolCallId
|
|
936
953
|
);
|
|
937
954
|
ensureGateCall();
|
|
938
955
|
if (!result.ok) {
|
|
939
956
|
session.recordInvocation({
|
|
940
|
-
executionEffect: gateFailureEffectForMode(
|
|
957
|
+
executionEffect: gateFailureEffectForMode(failureMode),
|
|
941
958
|
failure: result.category,
|
|
942
959
|
kind: "gate",
|
|
943
960
|
model: advisorRef,
|
|
944
961
|
trigger: "repeated-tool-call",
|
|
962
|
+
usage: result.usage,
|
|
945
963
|
});
|
|
964
|
+
updateAdvisorUsageStatus(ctx, session);
|
|
946
965
|
const failure = failureEffect(
|
|
947
966
|
result.category,
|
|
948
967
|
result.message,
|
|
949
968
|
ctx,
|
|
950
|
-
session
|
|
969
|
+
session,
|
|
970
|
+
failureMode
|
|
951
971
|
);
|
|
952
972
|
sendAutomaticGateFailure(
|
|
953
973
|
pi,
|
|
954
|
-
`**Advisor gate failure (${result.category}):** ${result.message}
|
|
974
|
+
`**Advisor gate failure (${result.category}):** ${result.message}`,
|
|
975
|
+
result.usage
|
|
955
976
|
);
|
|
956
977
|
return failure.block
|
|
957
978
|
? { block: true, reason: `${reason}\n${failure.reason}` }
|
|
@@ -960,12 +981,13 @@ const handleAutomaticGate = async (
|
|
|
960
981
|
session.recordInvocation({
|
|
961
982
|
cost: advisorUsageCost(result.usage),
|
|
962
983
|
decision: result.decision,
|
|
963
|
-
executionEffect: gateDecisionEffect(result.decision),
|
|
984
|
+
executionEffect: gateDecisionEffect(result.decision, failureMode),
|
|
964
985
|
kind: "gate",
|
|
965
986
|
model: result.model,
|
|
966
987
|
trigger: result.trigger,
|
|
967
988
|
usage: result.usage,
|
|
968
989
|
});
|
|
990
|
+
updateAdvisorUsageStatus(ctx, session);
|
|
969
991
|
sendAutomaticGateResult(pi, result);
|
|
970
992
|
if (result.decision === "proceed") {
|
|
971
993
|
session.resetRepetition();
|
|
@@ -973,11 +995,13 @@ const handleAutomaticGate = async (
|
|
|
973
995
|
}
|
|
974
996
|
const gateReason = `Advisor loop review: ${result.markdown}`;
|
|
975
997
|
if (result.decision === "blocked") {
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
998
|
+
const effect = blockedDecisionEffect(
|
|
999
|
+
gateReason,
|
|
1000
|
+
ctx,
|
|
1001
|
+
session,
|
|
1002
|
+
failureMode
|
|
1003
|
+
);
|
|
1004
|
+
return effect.block ? { block: true, reason: effect.reason } : undefined;
|
|
981
1005
|
}
|
|
982
1006
|
return { block: true, reason: gateReason };
|
|
983
1007
|
} finally {
|
|
@@ -1011,6 +1035,7 @@ interface AdvisorToolDetails {
|
|
|
1011
1035
|
thinking?: string;
|
|
1012
1036
|
trackedBytes?: number;
|
|
1013
1037
|
untrackedBytes?: number;
|
|
1038
|
+
usage?: unknown;
|
|
1014
1039
|
}
|
|
1015
1040
|
interface AdvisorRenderState {
|
|
1016
1041
|
phase?: string;
|
|
@@ -1059,7 +1084,7 @@ export const scoutDetailsFromEvent = (
|
|
|
1059
1084
|
selectedLabels: outcome.selectedLabels,
|
|
1060
1085
|
status: "curated",
|
|
1061
1086
|
synthesis: outcome.selection.synthesis,
|
|
1062
|
-
usage: outcome.metrics.usage,
|
|
1087
|
+
usage: snapshotAdvisorUsage(outcome.metrics.usage),
|
|
1063
1088
|
}
|
|
1064
1089
|
: {
|
|
1065
1090
|
availableCount: outcome.metrics.availableCount,
|
|
@@ -1069,7 +1094,7 @@ export const scoutDetailsFromEvent = (
|
|
|
1069
1094
|
omittedBeforeScout: outcome.metrics.omittedBeforeScout,
|
|
1070
1095
|
selectedCount: 0,
|
|
1071
1096
|
status: "fallback",
|
|
1072
|
-
usage: outcome.metrics.usage,
|
|
1097
|
+
usage: snapshotAdvisorUsage(outcome.metrics.usage),
|
|
1073
1098
|
};
|
|
1074
1099
|
};
|
|
1075
1100
|
|
|
@@ -1093,6 +1118,11 @@ export class ScoutStatusManager {
|
|
|
1093
1118
|
readonly #active = new Set<symbol>();
|
|
1094
1119
|
readonly #known = new Set<symbol>();
|
|
1095
1120
|
readonly #retired = new Set<symbol>();
|
|
1121
|
+
private readonly showStatus: boolean;
|
|
1122
|
+
|
|
1123
|
+
constructor(showStatus = true) {
|
|
1124
|
+
this.showStatus = showStatus;
|
|
1125
|
+
}
|
|
1096
1126
|
|
|
1097
1127
|
register(token: symbol) {
|
|
1098
1128
|
if (!this.#retired.has(token)) {
|
|
@@ -1107,7 +1137,9 @@ export class ScoutStatusManager {
|
|
|
1107
1137
|
this.#known.add(token);
|
|
1108
1138
|
if (event.type === "call" || event.type === "chunk") {
|
|
1109
1139
|
this.#active.add(token);
|
|
1110
|
-
|
|
1140
|
+
if (this.showStatus) {
|
|
1141
|
+
ctx.ui.setStatus("advisor-scout", "Scout curating…");
|
|
1142
|
+
}
|
|
1111
1143
|
return;
|
|
1112
1144
|
}
|
|
1113
1145
|
this.release(ctx, token);
|
|
@@ -1117,7 +1149,7 @@ export class ScoutStatusManager {
|
|
|
1117
1149
|
this.#active.delete(token);
|
|
1118
1150
|
this.#known.delete(token);
|
|
1119
1151
|
this.#retired.add(token);
|
|
1120
|
-
if (!ctx.hasUI) {
|
|
1152
|
+
if (!(ctx.hasUI && this.showStatus)) {
|
|
1121
1153
|
return;
|
|
1122
1154
|
}
|
|
1123
1155
|
ctx.ui.setStatus(
|
|
@@ -1132,7 +1164,7 @@ export class ScoutStatusManager {
|
|
|
1132
1164
|
}
|
|
1133
1165
|
this.#known.clear();
|
|
1134
1166
|
this.#active.clear();
|
|
1135
|
-
if (ctx.hasUI) {
|
|
1167
|
+
if (ctx.hasUI && this.showStatus) {
|
|
1136
1168
|
ctx.ui.setStatus("advisor-scout", undefined);
|
|
1137
1169
|
}
|
|
1138
1170
|
}
|
|
@@ -1173,6 +1205,12 @@ export const renderScoutDetails = (
|
|
|
1173
1205
|
` ${scout.model}${scout.selectedCount === undefined ? "" : ` · ${scout.selectedCount} kept / ${Math.max(0, (scout.availableCount ?? 0) - scout.selectedCount)} omitted`}${scout.latencyMs === undefined ? "" : ` · ${(scout.latencyMs / 1000).toFixed(1)}s`}`
|
|
1174
1206
|
),
|
|
1175
1207
|
];
|
|
1208
|
+
if (getAdvisorSettings().showUsageDetails) {
|
|
1209
|
+
const usage = formatAdvisorUsage(scout.usage);
|
|
1210
|
+
if (usage) {
|
|
1211
|
+
lines.push(theme.fg("dim", ` Usage: ${usage}`));
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1176
1214
|
if (scout.fallbackReason) {
|
|
1177
1215
|
lines.push(theme.fg("warning", ` ${scout.fallbackReason}`));
|
|
1178
1216
|
}
|
|
@@ -1293,6 +1331,12 @@ const renderFinalAdvisorResult = (
|
|
|
1293
1331
|
if (details?.advisor) {
|
|
1294
1332
|
lines.push(theme.fg("dim", ` ${details.advisor}`));
|
|
1295
1333
|
}
|
|
1334
|
+
if (getAdvisorSettings().showUsageDetails) {
|
|
1335
|
+
const usage = formatAdvisorUsage(details?.usage);
|
|
1336
|
+
if (usage) {
|
|
1337
|
+
lines.push(theme.fg("dim", ` Usage: ${usage}`));
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1296
1340
|
const attachments = [
|
|
1297
1341
|
details?.draftBytes
|
|
1298
1342
|
? `Draft attached · ${details.draftBytes} B`
|
|
@@ -1355,11 +1399,14 @@ export const registerAdvisorTool = (
|
|
|
1355
1399
|
pi: ExtensionAPI,
|
|
1356
1400
|
session: AdvisorSessionState = advisorSessionState,
|
|
1357
1401
|
dependencies: {
|
|
1402
|
+
appendOutcome?: typeof appendOutcome;
|
|
1403
|
+
consult?: typeof consultAdvisor;
|
|
1358
1404
|
runGate?: typeof runAdvisorGate;
|
|
1359
1405
|
statusManager?: ScoutStatusManager;
|
|
1360
|
-
telemetry?: BenchmarkTelemetry;
|
|
1361
1406
|
} = {}
|
|
1362
1407
|
) => {
|
|
1408
|
+
const appendAdvisorOutcome = dependencies.appendOutcome ?? appendOutcome;
|
|
1409
|
+
const requestAdvisor = dependencies.consult ?? consultAdvisor;
|
|
1363
1410
|
const reservedCalls = new Set<string>();
|
|
1364
1411
|
const scoutStatus = dependencies.statusManager ?? new ScoutStatusManager();
|
|
1365
1412
|
|
|
@@ -1385,7 +1432,12 @@ export const registerAdvisorTool = (
|
|
|
1385
1432
|
"advisor-loop-result",
|
|
1386
1433
|
(message, { expanded }, theme) => {
|
|
1387
1434
|
const details = message.details as
|
|
1388
|
-
| {
|
|
1435
|
+
| {
|
|
1436
|
+
advisor?: string;
|
|
1437
|
+
decision?: GateDecision;
|
|
1438
|
+
text?: string;
|
|
1439
|
+
usage?: unknown;
|
|
1440
|
+
}
|
|
1389
1441
|
| undefined;
|
|
1390
1442
|
const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
1391
1443
|
box.addChild(
|
|
@@ -1401,6 +1453,12 @@ export const registerAdvisorTool = (
|
|
|
1401
1453
|
if (details?.advisor) {
|
|
1402
1454
|
box.addChild(new Text(theme.fg("dim", ` ${details.advisor}`), 0, 0));
|
|
1403
1455
|
}
|
|
1456
|
+
if (getAdvisorSettings().showUsageDetails) {
|
|
1457
|
+
const usage = formatAdvisorUsage(details?.usage);
|
|
1458
|
+
if (usage) {
|
|
1459
|
+
box.addChild(new Text(theme.fg("dim", ` Usage: ${usage}`), 0, 0));
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1404
1462
|
if (details?.text) {
|
|
1405
1463
|
box.addChild(
|
|
1406
1464
|
new Markdown(
|
|
@@ -1428,10 +1486,13 @@ export const registerAdvisorTool = (
|
|
|
1428
1486
|
}
|
|
1429
1487
|
);
|
|
1430
1488
|
|
|
1431
|
-
pi.on("session_start", () => {
|
|
1489
|
+
pi.on("session_start", (_event, ctx) => {
|
|
1432
1490
|
session.resetTask();
|
|
1433
1491
|
reservedCalls.clear();
|
|
1434
1492
|
herdrAdvisorBlock.clear();
|
|
1493
|
+
if (ctx?.hasUI) {
|
|
1494
|
+
ctx.ui.setStatus("advisor-usage", undefined);
|
|
1495
|
+
}
|
|
1435
1496
|
});
|
|
1436
1497
|
|
|
1437
1498
|
pi.on("before_agent_start", (_event, ctx) => {
|
|
@@ -1442,7 +1503,7 @@ export const registerAdvisorTool = (
|
|
|
1442
1503
|
const guidelines = advisorInvocationGuidelines();
|
|
1443
1504
|
const budget = isSimpleMode()
|
|
1444
1505
|
? undefined
|
|
1445
|
-
: session.remainingCalls(
|
|
1506
|
+
: session.remainingCalls(getAdvisorMaxCallsPerSession());
|
|
1446
1507
|
if (budget !== undefined) {
|
|
1447
1508
|
guidelines.push(
|
|
1448
1509
|
`Advisor calls remaining this session: ${budget}.\nReserve calls for material decisions, repeated failures, or final review.`
|
|
@@ -1476,8 +1537,7 @@ export const registerAdvisorTool = (
|
|
|
1476
1537
|
ctx,
|
|
1477
1538
|
session,
|
|
1478
1539
|
dependencies.runGate ?? runAdvisorGate,
|
|
1479
|
-
scoutStatus
|
|
1480
|
-
dependencies.telemetry
|
|
1540
|
+
scoutStatus
|
|
1481
1541
|
);
|
|
1482
1542
|
});
|
|
1483
1543
|
|
|
@@ -1488,7 +1548,7 @@ export const registerAdvisorTool = (
|
|
|
1488
1548
|
if (isSimpleMode() || session.blocked || !advisorSessionSummaryRef) {
|
|
1489
1549
|
return;
|
|
1490
1550
|
}
|
|
1491
|
-
const summary = session.summary(
|
|
1551
|
+
const summary = session.summary(getAdvisorMaxCallsPerSession());
|
|
1492
1552
|
if (summary && ctx.hasUI) {
|
|
1493
1553
|
ctx.ui.notify(summary, "info");
|
|
1494
1554
|
}
|
|
@@ -1498,6 +1558,9 @@ export const registerAdvisorTool = (
|
|
|
1498
1558
|
reservedCalls.clear();
|
|
1499
1559
|
scoutStatus.clear(ctx);
|
|
1500
1560
|
herdrAdvisorBlock.clear();
|
|
1561
|
+
if (ctx?.hasUI) {
|
|
1562
|
+
ctx.ui.setStatus("advisor-usage", undefined);
|
|
1563
|
+
}
|
|
1501
1564
|
});
|
|
1502
1565
|
|
|
1503
1566
|
pi.registerTool({
|
|
@@ -1514,20 +1577,31 @@ export const registerAdvisorTool = (
|
|
|
1514
1577
|
);
|
|
1515
1578
|
}
|
|
1516
1579
|
if (!isSimpleMode()) {
|
|
1517
|
-
if (!session.canConsult(
|
|
1580
|
+
if (!session.canConsult(getAdvisorMaxCallsPerSession())) {
|
|
1518
1581
|
throw new Error("Advisor call budget exhausted for this session.");
|
|
1519
1582
|
}
|
|
1520
1583
|
session.consumeCall();
|
|
1521
1584
|
}
|
|
1522
1585
|
herdrAdvisorActivity.start();
|
|
1523
1586
|
let scoutDetails: ScoutToolDetails | undefined;
|
|
1587
|
+
const coalescedUpdate = createCoalescedUpdate(
|
|
1588
|
+
(update: Parameters<NonNullable<typeof onUpdate>>[0]) =>
|
|
1589
|
+
onUpdate?.(update),
|
|
1590
|
+
ADVISOR_STREAM_UPDATE_INTERVAL_MS
|
|
1591
|
+
);
|
|
1592
|
+
const flushUpdate = () => {
|
|
1593
|
+
const result = coalescedUpdate.flush();
|
|
1594
|
+
if (result.failed) {
|
|
1595
|
+
throw result.error;
|
|
1596
|
+
}
|
|
1597
|
+
};
|
|
1524
1598
|
try {
|
|
1525
|
-
const result = await
|
|
1599
|
+
const result = await requestAdvisor(
|
|
1526
1600
|
ctx,
|
|
1527
1601
|
resolveAdvisorRequest(params.question),
|
|
1528
1602
|
signal,
|
|
1529
1603
|
(t, tx) =>
|
|
1530
|
-
|
|
1604
|
+
coalescedUpdate.update({
|
|
1531
1605
|
content: [{ text: tx, type: "text" }],
|
|
1532
1606
|
details: {
|
|
1533
1607
|
advisor: advisorRef,
|
|
@@ -1545,7 +1619,7 @@ export const registerAdvisorTool = (
|
|
|
1545
1619
|
params.includeTrackedFiles,
|
|
1546
1620
|
(event) => {
|
|
1547
1621
|
scoutDetails = scoutDetailsFromEvent(event, scoutDetails);
|
|
1548
|
-
|
|
1622
|
+
coalescedUpdate.update({
|
|
1549
1623
|
content: [{ text: scoutDetails.text ?? "", type: "text" }],
|
|
1550
1624
|
details: {
|
|
1551
1625
|
advisor: advisorRef,
|
|
@@ -1554,9 +1628,9 @@ export const registerAdvisorTool = (
|
|
|
1554
1628
|
},
|
|
1555
1629
|
});
|
|
1556
1630
|
},
|
|
1557
|
-
_id
|
|
1558
|
-
dependencies.telemetry
|
|
1631
|
+
_id
|
|
1559
1632
|
);
|
|
1633
|
+
flushUpdate();
|
|
1560
1634
|
session.issueAdvice(
|
|
1561
1635
|
result.adviceId,
|
|
1562
1636
|
result.markdown,
|
|
@@ -1571,6 +1645,9 @@ export const registerAdvisorTool = (
|
|
|
1571
1645
|
trigger: "executor-requested",
|
|
1572
1646
|
usage: result.usage,
|
|
1573
1647
|
});
|
|
1648
|
+
const usage = snapshotAdvisorUsage(result.usage);
|
|
1649
|
+
const piUsage = advisorUsageForPi(result.usage);
|
|
1650
|
+
updateAdvisorUsageStatus(ctx, session);
|
|
1574
1651
|
return {
|
|
1575
1652
|
content: [
|
|
1576
1653
|
{
|
|
@@ -1589,9 +1666,15 @@ export const registerAdvisorTool = (
|
|
|
1589
1666
|
thinking: result.thinkingText,
|
|
1590
1667
|
trackedBytes: result.trackedBytes,
|
|
1591
1668
|
untrackedBytes: result.untrackedBytes,
|
|
1669
|
+
...(usage ? { usage } : {}),
|
|
1592
1670
|
},
|
|
1671
|
+
...(piUsage ? { usage: piUsage } : {}),
|
|
1593
1672
|
};
|
|
1594
1673
|
} catch (error) {
|
|
1674
|
+
// Publish the latest partial state before surfacing a provider or
|
|
1675
|
+
// execution error. A failure from the UI sink must not replace the
|
|
1676
|
+
// original error because this path is also used for provider failures.
|
|
1677
|
+
coalescedUpdate.flush();
|
|
1595
1678
|
const message = error instanceof Error ? error.message : String(error);
|
|
1596
1679
|
session.recordInvocation({
|
|
1597
1680
|
executionEffect: "continued",
|
|
@@ -1600,10 +1683,12 @@ export const registerAdvisorTool = (
|
|
|
1600
1683
|
model: advisorRef,
|
|
1601
1684
|
trigger: "executor-requested",
|
|
1602
1685
|
});
|
|
1686
|
+
updateAdvisorUsageStatus(ctx, session);
|
|
1603
1687
|
notifyLocalFailure(ctx, message);
|
|
1604
1688
|
notifyHerdrAdvisorFailure("Advisor consultation failed", message);
|
|
1605
1689
|
throw error;
|
|
1606
1690
|
} finally {
|
|
1691
|
+
coalescedUpdate.cancel();
|
|
1607
1692
|
herdrAdvisorActivity.finish();
|
|
1608
1693
|
}
|
|
1609
1694
|
},
|
|
@@ -1680,18 +1765,19 @@ export const registerAdvisorTool = (
|
|
|
1680
1765
|
details: { recorded: false },
|
|
1681
1766
|
};
|
|
1682
1767
|
}
|
|
1683
|
-
const advice = session.
|
|
1768
|
+
const advice = session.reserveAdvice(params.adviceId);
|
|
1684
1769
|
if (!advice) {
|
|
1685
|
-
throw new Error("Unknown
|
|
1770
|
+
throw new Error("Unknown, already recorded, or pending adviceId.");
|
|
1686
1771
|
}
|
|
1687
1772
|
try {
|
|
1688
|
-
await
|
|
1773
|
+
await appendAdvisorOutcome({
|
|
1689
1774
|
adoption: params.adoption as (typeof ADOPTIONS)[number],
|
|
1690
1775
|
advice: advice.advice,
|
|
1691
1776
|
trigger: advice.trigger,
|
|
1692
1777
|
validationStatus:
|
|
1693
1778
|
params.validationStatus as (typeof VALIDATIONS)[number],
|
|
1694
1779
|
});
|
|
1780
|
+
session.commitAdvice(params.adviceId);
|
|
1695
1781
|
return {
|
|
1696
1782
|
content: [
|
|
1697
1783
|
{ text: "Advisor outcome recorded locally.", type: "text" },
|
|
@@ -1699,6 +1785,7 @@ export const registerAdvisorTool = (
|
|
|
1699
1785
|
details: { recorded: true },
|
|
1700
1786
|
};
|
|
1701
1787
|
} catch {
|
|
1788
|
+
session.releaseAdvice(params.adviceId);
|
|
1702
1789
|
if (ctx.hasUI) {
|
|
1703
1790
|
ctx.ui.notify(
|
|
1704
1791
|
"Advisor outcome could not be recorded locally.",
|