pi-advisor-flow 0.9.2-dev.3 → 0.9.2-dev.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +7 -2
- package/dist/index.js +580 -201
- package/package.json +1 -1
- package/src/commands/manual-consultation.ts +31 -26
- package/src/commands/outcome-stats.ts +98 -0
- package/src/commands/registration.ts +2 -0
- package/src/commands/renderers.ts +51 -0
- package/src/commands/runtime.ts +2 -3
- package/src/commands/settings-persistence.ts +16 -7
- package/src/commands/types.ts +3 -2
- package/src/config/args.ts +3 -1
- package/src/config/defaults.ts +7 -0
- package/src/config/schema.ts +7 -0
- package/src/config/state.ts +5 -0
- package/src/config/storage.ts +23 -2
- package/src/config/types.ts +1 -0
- package/src/config.ts +2 -0
- package/src/image-attachments.ts +7 -3
- package/src/outcome-stats.ts +154 -0
- package/src/outcomes.ts +8 -1
- package/src/preferences.ts +103 -13
- package/src/redaction.ts +1 -1
- package/src/tools/consult-context.ts +12 -1
- package/src/tools/consultation.ts +34 -7
- package/src/tools/jev-filter.ts +2 -1
- package/src/tools/jev-turn-gate.ts +2 -0
- package/src/tools/loop-gate.ts +2 -0
- package/src/tools/prompts.ts +28 -2
- package/src/tools/register-ask-advisor.ts +1 -0
- package/src/tools/register-renderers.ts +40 -6
- package/src/tools/registration.ts +2 -2
- package/src/tools/render-advisor-result.ts +21 -11
- package/src/tools/session.ts +14 -0
- package/src/tools/types.ts +3 -0
- package/src/ui/manual-dialog.ts +10 -3
- package/src/ui/settings-items.ts +7 -0
- package/src/ui/settings-mutations.ts +1 -0
- package/src/ui/types.ts +1 -0
- package/src/usage.ts +34 -6
|
@@ -24,12 +24,27 @@ const advisorResultDetails = (result: AgentToolResult<AdvisorToolDetails>) =>
|
|
|
24
24
|
|
|
25
25
|
const syncRenderPhase = (context: AdvisorToolContext, phase: string) => {
|
|
26
26
|
if (context.state.phase !== phase && context.state.timerId) {
|
|
27
|
-
|
|
27
|
+
clearTimeout(context.state.timerId);
|
|
28
28
|
context.state.timerId = undefined;
|
|
29
29
|
}
|
|
30
30
|
context.state.phase = phase;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
+
const scheduleRender = (context: AdvisorToolContext) => {
|
|
34
|
+
const timer = setTimeout(() => {
|
|
35
|
+
if (context.state.timerId !== timer) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
context.state.timerId = undefined;
|
|
39
|
+
try {
|
|
40
|
+
context.invalidate();
|
|
41
|
+
} catch {
|
|
42
|
+
// The render slot may have been discarded before the repaint fired.
|
|
43
|
+
}
|
|
44
|
+
}, 80);
|
|
45
|
+
context.state.timerId = timer;
|
|
46
|
+
};
|
|
47
|
+
|
|
33
48
|
const formatByteSize = (bytes: number): string => {
|
|
34
49
|
if (bytes >= 1024 ** 3) {
|
|
35
50
|
return `${(bytes / 1024 ** 3).toFixed(1)} GiB`;
|
|
@@ -45,6 +60,9 @@ const formatByteSize = (bytes: number): string => {
|
|
|
45
60
|
|
|
46
61
|
const attachmentLabels = (details: AdvisorToolDetails | undefined) =>
|
|
47
62
|
[
|
|
63
|
+
details?.agentRulesBytes
|
|
64
|
+
? `AGENTS.md rules attached · ${formatByteSize(details.agentRulesBytes)}`
|
|
65
|
+
: undefined,
|
|
48
66
|
details?.draftBytes
|
|
49
67
|
? `Draft attached · ${formatByteSize(details.draftBytes)}`
|
|
50
68
|
: undefined,
|
|
@@ -100,15 +118,7 @@ const renderPartialAdvisorResult = (
|
|
|
100
118
|
scout?.status === "calling" || scout?.status === "streaming";
|
|
101
119
|
syncRenderPhase(context, scoutActive ? "scout" : "advisor");
|
|
102
120
|
if (!context.state.timerId) {
|
|
103
|
-
context
|
|
104
|
-
try {
|
|
105
|
-
context.invalidate();
|
|
106
|
-
} catch {
|
|
107
|
-
// Renderer context is gone; stop repainting and drop the timer.
|
|
108
|
-
clearInterval(context.state.timerId);
|
|
109
|
-
context.state.timerId = undefined;
|
|
110
|
-
}
|
|
111
|
-
}, 80);
|
|
121
|
+
scheduleRender(context);
|
|
112
122
|
}
|
|
113
123
|
if (scout) {
|
|
114
124
|
renderScoutDetails(box, scout, expanded, theme);
|
|
@@ -177,7 +187,7 @@ const renderFinalAdvisorResult = (
|
|
|
177
187
|
) => {
|
|
178
188
|
syncRenderPhase(context, "final");
|
|
179
189
|
if (context.state.timerId) {
|
|
180
|
-
|
|
190
|
+
clearTimeout(context.state.timerId);
|
|
181
191
|
context.state.timerId = undefined;
|
|
182
192
|
}
|
|
183
193
|
const details = advisorResultDetails(result);
|
package/src/tools/session.ts
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
1
3
|
import { AdvisorSessionState } from "../session-state.ts";
|
|
2
4
|
|
|
3
5
|
export const advisorSessionState = new AdvisorSessionState();
|
|
6
|
+
|
|
7
|
+
const sessionStates = new WeakMap<object, AdvisorSessionState>();
|
|
8
|
+
|
|
9
|
+
export const sessionStateFor = (owner: ExtensionAPI): AdvisorSessionState => {
|
|
10
|
+
const existing = sessionStates.get(owner);
|
|
11
|
+
if (existing) {
|
|
12
|
+
return existing;
|
|
13
|
+
}
|
|
14
|
+
const state = new AdvisorSessionState();
|
|
15
|
+
sessionStates.set(owner, state);
|
|
16
|
+
return state;
|
|
17
|
+
};
|
package/src/tools/types.ts
CHANGED
|
@@ -46,6 +46,7 @@ export interface AdvisorGateFailure {
|
|
|
46
46
|
|
|
47
47
|
export interface AdvisorConsultationResult {
|
|
48
48
|
adviceId: string;
|
|
49
|
+
agentRulesBytes?: number;
|
|
49
50
|
draftBytes?: number;
|
|
50
51
|
imageBytes?: number;
|
|
51
52
|
imageCount?: number;
|
|
@@ -63,6 +64,7 @@ export interface AdvisorConsultationResult {
|
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
export interface AdvisorGateResult {
|
|
67
|
+
agentRulesBytes?: number;
|
|
66
68
|
decision: GateDecision;
|
|
67
69
|
markdown: string;
|
|
68
70
|
model: string;
|
|
@@ -86,6 +88,7 @@ export type GateFailureCategory =
|
|
|
86
88
|
export interface AdvisorToolDetails {
|
|
87
89
|
adviceId?: string;
|
|
88
90
|
advisor?: string;
|
|
91
|
+
agentRulesBytes?: number;
|
|
89
92
|
draftBytes?: number;
|
|
90
93
|
imageBytes?: number;
|
|
91
94
|
imageCount?: number;
|
package/src/ui/manual-dialog.ts
CHANGED
|
@@ -24,6 +24,7 @@ const TUI_INPUT_TAB: keyof Keybindings = ["tui", "input", "tab"].join(
|
|
|
24
24
|
interface ManualDialogState {
|
|
25
25
|
completed: boolean;
|
|
26
26
|
focused: boolean;
|
|
27
|
+
submitting: boolean;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
const isShiftTab = (keyData: string): boolean =>
|
|
@@ -40,6 +41,7 @@ export class ManualAdvisorDialog implements Component, Focusable {
|
|
|
40
41
|
private readonly state: ManualDialogState = {
|
|
41
42
|
completed: false,
|
|
42
43
|
focused: false,
|
|
44
|
+
submitting: false,
|
|
43
45
|
};
|
|
44
46
|
|
|
45
47
|
get focused(): boolean {
|
|
@@ -273,17 +275,22 @@ export class ManualAdvisorDialog implements Component, Focusable {
|
|
|
273
275
|
}
|
|
274
276
|
|
|
275
277
|
private submit(message = this.editor.getText()): void {
|
|
276
|
-
if (this.state.completed) {
|
|
278
|
+
if (this.state.completed || this.state.submitting) {
|
|
277
279
|
return;
|
|
278
280
|
}
|
|
279
|
-
this.state.
|
|
281
|
+
this.state.submitting = true;
|
|
280
282
|
const request: ManualAdvisorRequest = {
|
|
281
283
|
gitContext: this.gitLevels[this.gitIndex] ?? "off",
|
|
282
284
|
};
|
|
283
285
|
if (message) {
|
|
284
286
|
request.message = message;
|
|
285
287
|
}
|
|
286
|
-
|
|
288
|
+
try {
|
|
289
|
+
this.options.onSubmit(request);
|
|
290
|
+
this.state.completed = true;
|
|
291
|
+
} finally {
|
|
292
|
+
this.state.submitting = false;
|
|
293
|
+
}
|
|
287
294
|
}
|
|
288
295
|
|
|
289
296
|
private cancel(): void {
|
package/src/ui/settings-items.ts
CHANGED
|
@@ -472,6 +472,13 @@ export const createSettingsItems = ({
|
|
|
472
472
|
settings.redactSecrets,
|
|
473
473
|
false
|
|
474
474
|
),
|
|
475
|
+
toggle(
|
|
476
|
+
"agentsMdContext",
|
|
477
|
+
"AGENTS.md context",
|
|
478
|
+
"Include trusted project and global AGENTS.md rules in Advisor calls.",
|
|
479
|
+
settings.agentsMdContext,
|
|
480
|
+
true
|
|
481
|
+
),
|
|
475
482
|
{
|
|
476
483
|
currentValue: settings.gitContext ?? "summary",
|
|
477
484
|
description: "How much repository context is shared with the Advisor.",
|
package/src/ui/types.ts
CHANGED
package/src/usage.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Usage } from "@earendil-works/pi-ai/compat";
|
|
2
2
|
|
|
3
3
|
import { isNumber, isRecordOf } from "./content-utils.ts";
|
|
4
|
-
import type { JsonValue } from "./content-utils.ts";
|
|
4
|
+
import type { JsonValue, RecordValue } from "./content-utils.ts";
|
|
5
5
|
|
|
6
6
|
/** Normalized usage returned by an Advisor or Scout provider response. */
|
|
7
7
|
export interface AdvisorUsageSnapshot {
|
|
@@ -33,6 +33,18 @@ const add = (left: number | undefined, right: number | undefined) =>
|
|
|
33
33
|
|
|
34
34
|
const costFields = ["input", "output", "cacheRead", "cacheWrite", "total"];
|
|
35
35
|
|
|
36
|
+
const hasPartialProviderCost = (
|
|
37
|
+
snapshot: AdvisorUsageSnapshot,
|
|
38
|
+
usage: RecordValue
|
|
39
|
+
) => {
|
|
40
|
+
const cost = isRecordOf(usage.cost) ? usage.cost : undefined;
|
|
41
|
+
return (
|
|
42
|
+
cost !== undefined &&
|
|
43
|
+
snapshot.cost === undefined &&
|
|
44
|
+
costFields.some((field) => finite(cost[field]) !== undefined)
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
36
48
|
/** Extracts provider-agnostic usage fields without trusting provider metadata. */
|
|
37
49
|
export const snapshotAdvisorUsage = <Input>(
|
|
38
50
|
usage: Input
|
|
@@ -62,23 +74,39 @@ export const snapshotAdvisorUsage = <Input>(
|
|
|
62
74
|
export const advisorUsageCost = <Input>(usage: Input): number | undefined =>
|
|
63
75
|
snapshotAdvisorUsage(usage)?.cost;
|
|
64
76
|
|
|
77
|
+
const completeUsageSnapshot = <Input>(
|
|
78
|
+
usage: Input
|
|
79
|
+
): { snapshot: AdvisorUsageSnapshot; usage: RecordValue } | undefined => {
|
|
80
|
+
const snapshot = snapshotAdvisorUsage(usage);
|
|
81
|
+
if (!(snapshot && isRecordOf(usage))) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (hasPartialProviderCost(snapshot, usage)) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
return { snapshot, usage };
|
|
88
|
+
};
|
|
89
|
+
|
|
65
90
|
/**
|
|
66
91
|
* Converts supported provider usage to Pi's complete nested-tool usage shape.
|
|
67
92
|
* Missing fields become zero only at this Pi API boundary; absent usage remains
|
|
68
93
|
* undefined so an unavailable request is never presented as a zero-cost call.
|
|
69
94
|
*/
|
|
70
95
|
export const advisorUsageForPi = <Input>(usage: Input): Usage | undefined => {
|
|
71
|
-
const
|
|
72
|
-
if (!
|
|
96
|
+
const complete = completeUsageSnapshot(usage);
|
|
97
|
+
if (!complete) {
|
|
73
98
|
return undefined;
|
|
74
99
|
}
|
|
75
|
-
const
|
|
100
|
+
const { snapshot } = complete;
|
|
101
|
+
const cost = isRecordOf(complete.usage.cost)
|
|
102
|
+
? complete.usage.cost
|
|
103
|
+
: undefined;
|
|
76
104
|
const input = snapshot.input ?? 0;
|
|
77
105
|
const output = snapshot.output ?? 0;
|
|
78
106
|
const cacheRead = snapshot.cacheRead ?? 0;
|
|
79
107
|
const cacheWrite = snapshot.cacheWrite ?? 0;
|
|
80
|
-
const cacheWrite1h = finite(usage.cacheWrite1h);
|
|
81
|
-
const reasoning = finite(usage.reasoning);
|
|
108
|
+
const cacheWrite1h = finite(complete.usage.cacheWrite1h);
|
|
109
|
+
const reasoning = finite(complete.usage.reasoning);
|
|
82
110
|
const piUsage: Usage = {
|
|
83
111
|
cacheRead,
|
|
84
112
|
cacheWrite,
|