pi-advisor-flow 0.3.6 → 0.4.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 +15 -0
- package/README.md +5 -2
- package/extensions/index.ts +0 -10
- package/package.json +1 -3
- package/src/commands.ts +32 -9
- package/src/config.ts +7 -3
- package/src/scout-context.ts +34 -8
- package/src/scout.ts +7 -34
- package/src/session-state.ts +22 -0
- package/src/tools.ts +108 -96
- package/src/usage.ts +199 -0
- package/src/telemetry.ts +0 -270
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,21 @@ All notable changes to this project are documented here.
|
|
|
4
4
|
|
|
5
5
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## 0.4.0
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Exposed provider-reported Advisor and Scout token/cache/cost details per result, tracked cumulative direct Advisor usage in the session footer and optional summary, and attached normalized `ask_advisor` usage to Pi's built-in cost totals without double-counting manual consultations or automatic gates.
|
|
12
|
+
|
|
13
|
+
### Removed
|
|
14
|
+
|
|
15
|
+
- Removed the repository-only Benchmark suite and its benchmark-specific telemetry instrumentation.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Kept Scout's serialized manifest limit separate from the Advisor conversation budget so required context does not fall back solely because of manifest metadata overhead.
|
|
20
|
+
- Made unlimited Advisor call budgets live at every enforcement boundary, preventing stale finite limits from triggering false Herdr budget notifications after switching to unlimited.
|
|
21
|
+
|
|
7
22
|
## 0.3.6
|
|
8
23
|
|
|
9
24
|
### Fixed
|
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@ The idea is simple: keep implementation on a fast model and borrow frontier reas
|
|
|
21
21
|
- **Configurable review gates** before plans, after repeated failures, and before declaring completion.
|
|
22
22
|
- **Automatic loop detection** for repeated tool calls, with explicit proceed, revise, or blocked decisions.
|
|
23
23
|
- **Separate model and reasoning controls** for the Executor and Advisor.
|
|
24
|
+
- **Advisor usage accounting** with per-response token/cost details and cumulative direct usage in the Pi footer and session summary.
|
|
24
25
|
- **Privacy controls** for conversation history, repository context, explicit tracked/untracked file handoff, tool results, secret redaction, and outcome logging.
|
|
25
26
|
- **Optional persistent activation, Simple mode, session summaries, and Herdr integration.**
|
|
26
27
|
- **EXPERIMENTAL Advisor Scout** that uses the configured Executor model to curate conversation evidence before every Advisor call.
|
|
@@ -55,7 +56,7 @@ Unknown fields in `advisor.json` are preserved for forward compatibility and rep
|
|
|
55
56
|
You can also enable the flow and select both models at once:
|
|
56
57
|
|
|
57
58
|
```text
|
|
58
|
-
/advisor executor=
|
|
59
|
+
/advisor executor=openai-codex/gpt-5.6-luna advisor=openai-codex/gpt-5.6-sol
|
|
59
60
|
```
|
|
60
61
|
|
|
61
62
|
## How it works
|
|
@@ -68,6 +69,8 @@ You can also enable the flow and select both models at once:
|
|
|
68
69
|
|
|
69
70
|
A normal consultation never blocks execution. The optional automatic loop gate is different: it evaluates repeated tool calls and applies the configured failure policy when the Advisor says to revise, reports a block, is unavailable, or returns an invalid decision.
|
|
70
71
|
|
|
72
|
+
Advisor responses show provider-reported input, output, cache, and cost details when available. Successful `ask_advisor` tool results also carry normalized usage into Pi's built-in `Tools/summaries` and `/cost` totals. Manual consultations and automatic gates remain in the separate session-local direct Advisor accounting because they are custom messages, so they are not double-counted in Pi's Executor totals. Missing or partial provider usage is shown as unavailable rather than fabricated as zero usage.
|
|
73
|
+
|
|
71
74
|
Successful calls return an opaque `adviceId`. If global outcome logging is enabled, the Executor can call `record_advisor_outcome` once to record whether the advice was adopted and whether final validation passed.
|
|
72
75
|
|
|
73
76
|
### Experimental Advisor Scout
|
|
@@ -76,7 +79,7 @@ Experimental Advisor Scout is off by default. Enable `Experimental Advisor Scout
|
|
|
76
79
|
|
|
77
80
|
Scout runs before `ask_advisor`, `/advisor-manual`, and automatic Advisor gates. It uses the configured Executor model and Executor reasoning effort in a separate model call. This adds cost and latency, but can reduce cost in the Advisor call. The compact result shows the model, selection counts, and elapsed time; `Ctrl+O` shows bounded selected labels and the synthesis.
|
|
78
81
|
|
|
79
|
-
Scout receives a bounded manifest of conversation and tool-history groups after the normal tool disclosure, result-cap, and redaction policies are applied. The manifest
|
|
82
|
+
Scout receives a bounded manifest of conversation and tool-history groups after the normal tool disclosure, result-cap, and redaction policies are applied. The Scout manifest has its own fixed transport limit, while the reconstructed conversation remains bounded by the Advisor's remaining context budget after repository context; manifest metadata no longer consumes that Advisor conversation budget. A zero remaining budget produces no history groups. For a pending `ask_advisor` call, Scout receives only the allowlisted question and Git-context preference, never the draft or explicit attachment paths. Scout does not receive the deterministic Git context, draft, project preferences, or explicit tracked and untracked attachments. Those regions are appended later through their existing consent and cap rules.
|
|
80
83
|
|
|
81
84
|
This experiment adapts the context-boundary idea from Zhang et al., ["FastContext: Training Efficient Repository Explorer for Coding Agents"](https://arxiv.org/html/2606.14066v1). It is not a reproduction of FastContext. pi-advisor Scout curates conversation history only.
|
|
82
85
|
|
package/extensions/index.ts
CHANGED
|
@@ -2,7 +2,6 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import { registerCommands } from "../src/commands.js";
|
|
3
3
|
import { setHerdrBlockedEmitter } from "../src/herdr.js";
|
|
4
4
|
import { AdvisorSessionState } from "../src/session-state.js";
|
|
5
|
-
import { createBenchmarkTelemetry } from "../src/telemetry.js";
|
|
6
5
|
import {
|
|
7
6
|
consultAdvisor as consultAdvisorImplementation,
|
|
8
7
|
parseAutomaticDecision as parseAutomaticDecisionImplementation,
|
|
@@ -37,20 +36,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
37
36
|
setHerdrBlockedEmitter((active, label) =>
|
|
38
37
|
pi.events.emit("herdr:blocked", { active, label })
|
|
39
38
|
);
|
|
40
|
-
const benchmarkTelemetry = createBenchmarkTelemetry(pi.events);
|
|
41
|
-
if (benchmarkTelemetry) {
|
|
42
|
-
// Diagnostics only: never rewrite provider payloads or affect production sessions.
|
|
43
|
-
pi.on("before_provider_request", (event) => {
|
|
44
|
-
benchmarkTelemetry.providerRequest(event.payload);
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
39
|
registerAdvisorTool(pi, sessionState, {
|
|
48
40
|
statusManager: scoutStatus,
|
|
49
|
-
telemetry: benchmarkTelemetry,
|
|
50
41
|
});
|
|
51
42
|
registerCommands(pi, {
|
|
52
43
|
sessionState,
|
|
53
44
|
statusManager: scoutStatus,
|
|
54
|
-
telemetry: benchmarkTelemetry,
|
|
55
45
|
});
|
|
56
46
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-advisor-flow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Advanced Executor/Advisor flow for Pi, fully configurable and extendable.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -28,8 +28,6 @@
|
|
|
28
28
|
"README.md"
|
|
29
29
|
],
|
|
30
30
|
"scripts": {
|
|
31
|
-
"benchmark": "bun benchmarks/src/cli.ts",
|
|
32
|
-
"benchmark:report": "bun benchmarks/src/cli.ts report",
|
|
33
31
|
"format": "bunx ultracite fix --linter-enabled=false",
|
|
34
32
|
"lint": "bunx ultracite check",
|
|
35
33
|
"lint:fix": "bunx ultracite fix",
|
package/src/commands.ts
CHANGED
|
@@ -6,12 +6,12 @@ import {
|
|
|
6
6
|
import { Box, Markdown, Text } from "@earendil-works/pi-tui";
|
|
7
7
|
import {
|
|
8
8
|
advisorEffortRef,
|
|
9
|
-
advisorMaxCallsPerSessionRef,
|
|
10
9
|
advisorRef,
|
|
11
10
|
alwaysOnRef,
|
|
12
11
|
contextMaxCharsRef,
|
|
13
12
|
executorEffortRef,
|
|
14
13
|
executorRef,
|
|
14
|
+
getAdvisorMaxCallsPerSession,
|
|
15
15
|
getAdvisorSettings,
|
|
16
16
|
isSimpleMode,
|
|
17
17
|
loadConfig,
|
|
@@ -52,7 +52,6 @@ import {
|
|
|
52
52
|
import { herdrAdvisorActivity, notifyHerdrAdvisorFailure } from "./herdr.js";
|
|
53
53
|
import type { ScoutLifecycleEvent } from "./scout.js";
|
|
54
54
|
import type { AdvisorSessionState } from "./session-state.js";
|
|
55
|
-
import type { BenchmarkTelemetry } from "./telemetry.js";
|
|
56
55
|
import {
|
|
57
56
|
adviceForDisplay,
|
|
58
57
|
appendScoutLifecycleEntry,
|
|
@@ -70,6 +69,11 @@ import {
|
|
|
70
69
|
type ContextPreset,
|
|
71
70
|
SearchableModelSelector,
|
|
72
71
|
} from "./ui.js";
|
|
72
|
+
import {
|
|
73
|
+
advisorUsageCost,
|
|
74
|
+
formatAdvisorUsage,
|
|
75
|
+
snapshotAdvisorUsage,
|
|
76
|
+
} from "./usage.js";
|
|
73
77
|
|
|
74
78
|
const DEFAULT_EFFORT_LEVEL = "Default (Model Default)";
|
|
75
79
|
const SELECTED_PREFIX = "✓ ";
|
|
@@ -145,6 +149,7 @@ type ManualConsult = (
|
|
|
145
149
|
thinkingText: string;
|
|
146
150
|
draftBytes?: number;
|
|
147
151
|
preferenceBytes?: number;
|
|
152
|
+
usage?: unknown;
|
|
148
153
|
}>;
|
|
149
154
|
type ThinkingLevel = Parameters<ExtensionAPI["setThinkingLevel"]>[0];
|
|
150
155
|
|
|
@@ -169,7 +174,6 @@ export const registerCommands = (
|
|
|
169
174
|
consult?: ManualConsult;
|
|
170
175
|
sessionState?: AdvisorSessionState;
|
|
171
176
|
statusManager?: ScoutStatusManager;
|
|
172
|
-
telemetry?: BenchmarkTelemetry;
|
|
173
177
|
} = {}
|
|
174
178
|
) => {
|
|
175
179
|
const advisorSessionState =
|
|
@@ -190,10 +194,14 @@ export const registerCommands = (
|
|
|
190
194
|
undefined,
|
|
191
195
|
undefined,
|
|
192
196
|
onScout,
|
|
193
|
-
undefined
|
|
194
|
-
dependencies.telemetry
|
|
197
|
+
undefined
|
|
195
198
|
));
|
|
196
199
|
const manualConsultations = new Map<AbortController, symbol>();
|
|
200
|
+
const updateAdvisorUsageStatus = (ctx: ExtensionContext) => {
|
|
201
|
+
if (ctx.hasUI) {
|
|
202
|
+
ctx.ui.setStatus("advisor-usage", advisorSessionState.usageStatus());
|
|
203
|
+
}
|
|
204
|
+
};
|
|
197
205
|
const setManualStatus = (
|
|
198
206
|
ctx: ExtensionContext,
|
|
199
207
|
controller: AbortController,
|
|
@@ -257,21 +265,30 @@ export const registerCommands = (
|
|
|
257
265
|
}
|
|
258
266
|
}
|
|
259
267
|
)
|
|
260
|
-
.then(({ markdown }) => {
|
|
268
|
+
.then(({ markdown, usage }) => {
|
|
261
269
|
if (controller.signal.aborted) {
|
|
262
270
|
return;
|
|
263
271
|
}
|
|
264
272
|
advisorSessionState.recordInvocation({
|
|
273
|
+
cost: advisorUsageCost(usage),
|
|
265
274
|
executionEffect: "continued",
|
|
266
275
|
kind: "markdown",
|
|
267
276
|
model: advisorRef,
|
|
268
277
|
trigger: "manual",
|
|
278
|
+
usage,
|
|
269
279
|
});
|
|
280
|
+
updateAdvisorUsageStatus(ctx);
|
|
281
|
+
const normalizedUsage = snapshotAdvisorUsage(usage);
|
|
270
282
|
pi.sendMessage(
|
|
271
283
|
{
|
|
272
284
|
content: `Manual Advisor consultation${question ? ` (${question})` : ""}:\n\n${markdown}`,
|
|
273
285
|
customType: "advisor-manual-result",
|
|
274
|
-
details: {
|
|
286
|
+
details: {
|
|
287
|
+
advisor: advisorRef,
|
|
288
|
+
question,
|
|
289
|
+
text: markdown,
|
|
290
|
+
...(normalizedUsage ? { usage: normalizedUsage } : {}),
|
|
291
|
+
},
|
|
275
292
|
display: true,
|
|
276
293
|
},
|
|
277
294
|
{
|
|
@@ -294,6 +311,7 @@ export const registerCommands = (
|
|
|
294
311
|
model: advisorRef,
|
|
295
312
|
trigger: "manual",
|
|
296
313
|
});
|
|
314
|
+
updateAdvisorUsageStatus(ctx);
|
|
297
315
|
pi.sendMessage(
|
|
298
316
|
{
|
|
299
317
|
content: `Manual Advisor consultation failed: ${message}`,
|
|
@@ -424,7 +442,7 @@ export const registerCommands = (
|
|
|
424
442
|
"advisor-manual-result",
|
|
425
443
|
(message, { expanded }, theme) => {
|
|
426
444
|
const details = message.details as
|
|
427
|
-
| { advisor?: string; text?: string }
|
|
445
|
+
| { advisor?: string; text?: string; usage?: unknown }
|
|
428
446
|
| undefined;
|
|
429
447
|
const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
430
448
|
const advice =
|
|
@@ -443,6 +461,10 @@ export const registerCommands = (
|
|
|
443
461
|
if (details?.advisor) {
|
|
444
462
|
box.addChild(new Text(theme.fg("dim", ` ${details.advisor}`), 0, 0));
|
|
445
463
|
}
|
|
464
|
+
const usage = formatAdvisorUsage(details?.usage);
|
|
465
|
+
if (usage) {
|
|
466
|
+
box.addChild(new Text(theme.fg("dim", ` Usage: ${usage}`), 0, 0));
|
|
467
|
+
}
|
|
446
468
|
box.addChild(
|
|
447
469
|
new Markdown(
|
|
448
470
|
adviceForDisplay(advice, expanded),
|
|
@@ -486,6 +508,7 @@ export const registerCommands = (
|
|
|
486
508
|
pi.on("session_shutdown", (_event, ctx) => {
|
|
487
509
|
if (ctx.hasUI) {
|
|
488
510
|
ctx.ui.setStatus("advisor-manual", undefined);
|
|
511
|
+
ctx.ui.setStatus("advisor-usage", undefined);
|
|
489
512
|
}
|
|
490
513
|
for (const [controller, token] of manualConsultations) {
|
|
491
514
|
controller.abort();
|
|
@@ -506,7 +529,7 @@ export const registerCommands = (
|
|
|
506
529
|
if (
|
|
507
530
|
!(
|
|
508
531
|
isSimpleMode() ||
|
|
509
|
-
advisorSessionState.canConsult(
|
|
532
|
+
advisorSessionState.canConsult(getAdvisorMaxCallsPerSession())
|
|
510
533
|
)
|
|
511
534
|
) {
|
|
512
535
|
const message = "Advisor call budget exhausted for this session.";
|
package/src/config.ts
CHANGED
|
@@ -13,8 +13,8 @@ import {
|
|
|
13
13
|
isValidGitContextLevel,
|
|
14
14
|
} from "./git.js";
|
|
15
15
|
|
|
16
|
-
export const FALLBACK_EXECUTOR = "
|
|
17
|
-
export const FALLBACK_ADVISOR = "
|
|
16
|
+
export const FALLBACK_EXECUTOR = "openai-codex/gpt-5.6-luna";
|
|
17
|
+
export const FALLBACK_ADVISOR = "openai-codex/gpt-5.6-sol";
|
|
18
18
|
export const DEFAULT_CONTEXT_MAX_CHARS = 15_000;
|
|
19
19
|
export const MAX_CONTEXT_MAX_CHARS = Number.MAX_SAFE_INTEGER;
|
|
20
20
|
export const DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES = DEFAULT_MAX_LINES;
|
|
@@ -208,9 +208,13 @@ export const getAdvisorSettings = () => ({
|
|
|
208
208
|
untrackedContent: advisorUntrackedContentRef,
|
|
209
209
|
});
|
|
210
210
|
|
|
211
|
+
/** Reads the live session budget at each enforcement boundary. */
|
|
212
|
+
export const getAdvisorMaxCallsPerSession = () =>
|
|
213
|
+
getAdvisorSettings().maxCallsPerSession;
|
|
214
|
+
|
|
211
215
|
export const splitRef = (ref: string): [string, string] => {
|
|
212
216
|
const i = ref.indexOf("/");
|
|
213
|
-
return i === -1 ? ["
|
|
217
|
+
return i === -1 ? ["openai-codex", ref] : [ref.slice(0, i), ref.slice(i + 1)];
|
|
214
218
|
};
|
|
215
219
|
|
|
216
220
|
export const configPaths = (ctx: ExtensionContext) => [
|
package/src/scout-context.ts
CHANGED
|
@@ -169,9 +169,14 @@ const pendingInvocationDisclosure = (
|
|
|
169
169
|
|
|
170
170
|
export interface BuildScoutManifestOptions {
|
|
171
171
|
currentInvocationId?: string;
|
|
172
|
+
/** @deprecated Use maxManifestBytes for the Scout transport budget. */
|
|
172
173
|
maxBytes?: number;
|
|
174
|
+
/** Maximum reconstructed Advisor conversation characters. */
|
|
175
|
+
maxConversationChars?: number;
|
|
173
176
|
maxGroupBytes?: number;
|
|
174
177
|
maxGroups?: number;
|
|
178
|
+
/** Maximum serialized group-manifest bytes sent to Scout. */
|
|
179
|
+
maxManifestBytes?: number;
|
|
175
180
|
policies?: AdvisorToolPolicies;
|
|
176
181
|
redact?: boolean;
|
|
177
182
|
toolResultMaxBytes?: number;
|
|
@@ -191,9 +196,13 @@ export const buildScoutManifest = (
|
|
|
191
196
|
options.toolResultMaxLines ?? advisorToolResultMaxLinesRef;
|
|
192
197
|
const toolResultMaxBytes =
|
|
193
198
|
options.toolResultMaxBytes ?? advisorToolResultMaxBytesRef;
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
199
|
+
const {
|
|
200
|
+
maxBytes,
|
|
201
|
+
maxConversationChars,
|
|
202
|
+
maxGroupBytes = SCOUT_GROUP_MAX_BYTES,
|
|
203
|
+
maxGroups = SCOUT_MANIFEST_MAX_GROUPS,
|
|
204
|
+
maxManifestBytes = maxBytes ?? SCOUT_MANIFEST_MAX_BYTES,
|
|
205
|
+
} = options;
|
|
197
206
|
|
|
198
207
|
let latestUserIndex = -1;
|
|
199
208
|
const callOwners = new Map<string, { index: number; name: string }>();
|
|
@@ -423,7 +432,7 @@ export const buildScoutManifest = (
|
|
|
423
432
|
const availableBytes =
|
|
424
433
|
groups.reduce((sum, group) => sum + groupWireBytes(group), 0) +
|
|
425
434
|
protocolOmittedBytes;
|
|
426
|
-
if (
|
|
435
|
+
if (maxManifestBytes <= 0) {
|
|
427
436
|
return {
|
|
428
437
|
manifest: {
|
|
429
438
|
availableBytes: 0,
|
|
@@ -439,22 +448,39 @@ export const buildScoutManifest = (
|
|
|
439
448
|
if (
|
|
440
449
|
required.some((group) => group.bytes > maxGroupBytes) ||
|
|
441
450
|
required.length > maxGroups ||
|
|
442
|
-
required.reduce((sum, group) => sum + groupWireBytes(group), 0) >
|
|
451
|
+
required.reduce((sum, group) => sum + groupWireBytes(group), 0) >
|
|
452
|
+
maxManifestBytes
|
|
443
453
|
) {
|
|
444
454
|
return {
|
|
445
455
|
message:
|
|
446
|
-
"Required Scout context exceeds
|
|
456
|
+
"Required Scout context exceeds the Scout manifest transport limit.",
|
|
457
|
+
ok: false,
|
|
458
|
+
reason: "required-group-overflow",
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
const contentChars = (items: ScoutContextGroup[]) =>
|
|
462
|
+
items.reduce((sum, group) => sum + group.content.length, 0) +
|
|
463
|
+
Math.max(0, items.length - 1) * 2;
|
|
464
|
+
if (
|
|
465
|
+
maxConversationChars !== undefined &&
|
|
466
|
+
contentChars(required) > maxConversationChars
|
|
467
|
+
) {
|
|
468
|
+
return {
|
|
469
|
+
message:
|
|
470
|
+
"Required Scout context exceeds the Advisor conversation budget.",
|
|
447
471
|
ok: false,
|
|
448
472
|
reason: "required-group-overflow",
|
|
449
473
|
};
|
|
450
474
|
}
|
|
451
|
-
|
|
452
475
|
const selected = groups.filter(
|
|
453
476
|
(group) => group.required || group.bytes <= maxGroupBytes
|
|
454
477
|
);
|
|
455
478
|
const fits = () =>
|
|
456
479
|
selected.length <= maxGroups &&
|
|
457
|
-
selected.reduce((sum, group) => sum + groupWireBytes(group), 0) <=
|
|
480
|
+
selected.reduce((sum, group) => sum + groupWireBytes(group), 0) <=
|
|
481
|
+
maxManifestBytes &&
|
|
482
|
+
(maxConversationChars === undefined ||
|
|
483
|
+
contentChars(selected) <= maxConversationChars);
|
|
458
484
|
while (!fits()) {
|
|
459
485
|
const optionalIndex = selected.findIndex((group) => !group.required);
|
|
460
486
|
if (optionalIndex < 0) {
|
package/src/scout.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
SCOUT_SYNTHESIS_MAX_BYTES,
|
|
14
14
|
type ScoutManifest,
|
|
15
15
|
} from "./scout-context.js";
|
|
16
|
-
import
|
|
16
|
+
import { snapshotAdvisorUsage } from "./usage.js";
|
|
17
17
|
|
|
18
18
|
export const SCOUT_TIMEOUT_MS = 30_000;
|
|
19
19
|
|
|
@@ -224,42 +224,12 @@ export const runAdvisorScout = async (
|
|
|
224
224
|
parentSignal?: AbortSignal,
|
|
225
225
|
onEvent?: (event: ScoutLifecycleEvent) => void,
|
|
226
226
|
timeoutMs = SCOUT_TIMEOUT_MS,
|
|
227
|
-
dependencies: ScoutDependencies = defaultDependencies
|
|
228
|
-
telemetry?: BenchmarkTelemetry
|
|
227
|
+
dependencies: ScoutDependencies = defaultDependencies
|
|
229
228
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: cancellation, timeout, provider, and schema outcomes remain explicitly distinct.
|
|
230
229
|
): Promise<ScoutOutcome> => {
|
|
231
230
|
const startedAt = Date.now();
|
|
232
231
|
const publish = (event: ScoutLifecycleEvent) => {
|
|
233
232
|
onEvent?.(event);
|
|
234
|
-
if (event.type === "chunk") {
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
if (event.type === "call" || event.type === "cancelled") {
|
|
238
|
-
telemetry?.scout(event);
|
|
239
|
-
} else if (event.type === "success") {
|
|
240
|
-
telemetry?.scout({
|
|
241
|
-
availableCount: event.outcome.metrics.availableCount,
|
|
242
|
-
latencyMs: event.outcome.metrics.latencyMs,
|
|
243
|
-
model: event.outcome.model,
|
|
244
|
-
omittedBeforeScout: event.outcome.metrics.omittedBeforeScout,
|
|
245
|
-
selectedCount: event.outcome.metrics.selectedCount,
|
|
246
|
-
selectedLabels: event.outcome.selectedLabels,
|
|
247
|
-
synthesis: event.outcome.selection.synthesis,
|
|
248
|
-
type: "success",
|
|
249
|
-
usage: event.outcome.metrics.usage,
|
|
250
|
-
});
|
|
251
|
-
} else {
|
|
252
|
-
telemetry?.scout({
|
|
253
|
-
availableCount: event.outcome.metrics.availableCount,
|
|
254
|
-
fallback: `${event.outcome.category}: ${event.outcome.message}`,
|
|
255
|
-
latencyMs: event.outcome.metrics.latencyMs,
|
|
256
|
-
model: event.outcome.model,
|
|
257
|
-
omittedBeforeScout: event.outcome.metrics.omittedBeforeScout,
|
|
258
|
-
selectedCount: event.outcome.metrics.selectedCount,
|
|
259
|
-
type: "fallback",
|
|
260
|
-
usage: event.outcome.metrics.usage,
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
233
|
};
|
|
264
234
|
if (parentSignal?.aborted) {
|
|
265
235
|
publish({ type: "cancelled" });
|
|
@@ -361,7 +331,10 @@ export const runAdvisorScout = async (
|
|
|
361
331
|
? ("invalid-selection" as const)
|
|
362
332
|
: ("empty-response" as const),
|
|
363
333
|
message,
|
|
364
|
-
metrics: {
|
|
334
|
+
metrics: {
|
|
335
|
+
...baseMetrics(manifest, startedAt),
|
|
336
|
+
usage: snapshotAdvisorUsage(streamed.usage),
|
|
337
|
+
},
|
|
365
338
|
model: executorRef,
|
|
366
339
|
ok: false as const,
|
|
367
340
|
};
|
|
@@ -383,7 +356,7 @@ export const runAdvisorScout = async (
|
|
|
383
356
|
.filter((group) => group.required)
|
|
384
357
|
.map((group) => group.id),
|
|
385
358
|
]).size,
|
|
386
|
-
usage: streamed.usage,
|
|
359
|
+
usage: snapshotAdvisorUsage(streamed.usage),
|
|
387
360
|
},
|
|
388
361
|
model: executorRef,
|
|
389
362
|
ok: true as const,
|
package/src/session-state.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AdvisorUsageTotals,
|
|
3
|
+
addAdvisorUsage,
|
|
4
|
+
emptyAdvisorUsageTotals,
|
|
5
|
+
formatAdvisorUsageStatus,
|
|
6
|
+
formatAdvisorUsageTotals,
|
|
7
|
+
} from "./usage.js";
|
|
8
|
+
|
|
1
9
|
export type GateDecision = "proceed" | "revise" | "blocked";
|
|
2
10
|
export type ConsultationTrigger = "manual" | "executor-requested";
|
|
3
11
|
export type GateTrigger =
|
|
@@ -120,6 +128,7 @@ export class AdvisorSessionState {
|
|
|
120
128
|
#draftConsultations = 0;
|
|
121
129
|
#outcomes = 0;
|
|
122
130
|
#lastAdvice?: string;
|
|
131
|
+
#usage = emptyAdvisorUsageTotals();
|
|
123
132
|
|
|
124
133
|
resetTask() {
|
|
125
134
|
this.#previousSignature = undefined;
|
|
@@ -133,6 +142,7 @@ export class AdvisorSessionState {
|
|
|
133
142
|
this.#draftConsultations = 0;
|
|
134
143
|
this.#outcomes = 0;
|
|
135
144
|
this.#lastAdvice = undefined;
|
|
145
|
+
this.#usage = emptyAdvisorUsageTotals();
|
|
136
146
|
}
|
|
137
147
|
|
|
138
148
|
clearBlocked() {
|
|
@@ -182,8 +192,19 @@ export class AdvisorSessionState {
|
|
|
182
192
|
return this.#consumedCalls;
|
|
183
193
|
}
|
|
184
194
|
|
|
195
|
+
/** Returns a copy of cumulative direct Advisor usage for this session. */
|
|
196
|
+
get usageTotals(): AdvisorUsageTotals {
|
|
197
|
+
return { ...this.#usage };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Returns the footer-ready direct Advisor usage status for this session. */
|
|
201
|
+
usageStatus() {
|
|
202
|
+
return formatAdvisorUsageStatus(this.#usage);
|
|
203
|
+
}
|
|
204
|
+
|
|
185
205
|
recordInvocation(record: AdvisorInvocationRecord) {
|
|
186
206
|
this.#invocations.push(record);
|
|
207
|
+
addAdvisorUsage(this.#usage, record.usage);
|
|
187
208
|
}
|
|
188
209
|
issueAdvice(
|
|
189
210
|
id: string,
|
|
@@ -271,6 +292,7 @@ export class AdvisorSessionState {
|
|
|
271
292
|
`Triggers: ${["manual", "executor-requested", "repeated-tool-call", "completion-review", "custom-rule"].filter((trigger) => countTrigger(trigger as AdvisorTrigger) > 0).join(", ") || "none"}`,
|
|
272
293
|
`Models: ${models}`,
|
|
273
294
|
`Budget: ${budget}`,
|
|
295
|
+
`Usage: ${formatAdvisorUsageTotals(this.#usage)}`,
|
|
274
296
|
`Markdown advice: ${markdown.length} responses (${this.#draftConsultations} with drafts)`,
|
|
275
297
|
`Outcome reports: ${this.#outcomes}`,
|
|
276
298
|
`Gate decisions: ${decisions}`,
|