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/commands.ts
CHANGED
|
@@ -2,16 +2,17 @@ import {
|
|
|
2
2
|
type ExtensionAPI,
|
|
3
3
|
type ExtensionContext,
|
|
4
4
|
getMarkdownTheme,
|
|
5
|
+
type Theme,
|
|
5
6
|
} from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import { Box, Markdown, Text } from "@earendil-works/pi-tui";
|
|
7
|
+
import { Box, type Component, Markdown, Text } from "@earendil-works/pi-tui";
|
|
7
8
|
import {
|
|
8
9
|
advisorEffortRef,
|
|
9
|
-
advisorMaxCallsPerSessionRef,
|
|
10
10
|
advisorRef,
|
|
11
11
|
alwaysOnRef,
|
|
12
12
|
contextMaxCharsRef,
|
|
13
13
|
executorEffortRef,
|
|
14
14
|
executorRef,
|
|
15
|
+
getAdvisorMaxCallsPerSession,
|
|
15
16
|
getAdvisorSettings,
|
|
16
17
|
isSimpleMode,
|
|
17
18
|
loadConfig,
|
|
@@ -46,13 +47,15 @@ import {
|
|
|
46
47
|
setContextMaxCharsRef,
|
|
47
48
|
setExecutorEffortRef,
|
|
48
49
|
setExecutorRef,
|
|
50
|
+
setShowUsageDetailsRef,
|
|
51
|
+
setShowUsageFooterRef,
|
|
49
52
|
setSimpleModeRef,
|
|
50
53
|
splitRef,
|
|
51
54
|
} from "./config.js";
|
|
55
|
+
import type { GitContextLevel } from "./git.js";
|
|
52
56
|
import { herdrAdvisorActivity, notifyHerdrAdvisorFailure } from "./herdr.js";
|
|
53
57
|
import type { ScoutLifecycleEvent } from "./scout.js";
|
|
54
58
|
import type { AdvisorSessionState } from "./session-state.js";
|
|
55
|
-
import type { BenchmarkTelemetry } from "./telemetry.js";
|
|
56
59
|
import {
|
|
57
60
|
adviceForDisplay,
|
|
58
61
|
appendScoutLifecycleEntry,
|
|
@@ -61,15 +64,25 @@ import {
|
|
|
61
64
|
hasSoundVerdict,
|
|
62
65
|
renderAdvisorCallBox,
|
|
63
66
|
renderAdvisorResponseHeader,
|
|
67
|
+
renderScoutDetails,
|
|
64
68
|
resolveAdvisorRequest,
|
|
65
69
|
ScoutStatusManager,
|
|
70
|
+
type ScoutToolDetails,
|
|
71
|
+
SPINNER_FRAMES,
|
|
66
72
|
} from "./tools.js";
|
|
67
73
|
import {
|
|
68
74
|
type AdvisorSettings,
|
|
69
75
|
AdvisorSettingsSelector,
|
|
70
76
|
type ContextPreset,
|
|
77
|
+
ManualAdvisorDialog,
|
|
78
|
+
type ManualAdvisorRequest,
|
|
71
79
|
SearchableModelSelector,
|
|
72
80
|
} from "./ui.js";
|
|
81
|
+
import {
|
|
82
|
+
advisorUsageCost,
|
|
83
|
+
formatAdvisorUsage,
|
|
84
|
+
snapshotAdvisorUsage,
|
|
85
|
+
} from "./usage.js";
|
|
73
86
|
|
|
74
87
|
const DEFAULT_EFFORT_LEVEL = "Default (Model Default)";
|
|
75
88
|
const SELECTED_PREFIX = "✓ ";
|
|
@@ -139,12 +152,14 @@ type ManualConsult = (
|
|
|
139
152
|
question?: string,
|
|
140
153
|
signal?: AbortSignal,
|
|
141
154
|
onChunk?: (thinking: string, text: string) => void,
|
|
142
|
-
onScout?: (event: ScoutLifecycleEvent) => void
|
|
155
|
+
onScout?: (event: ScoutLifecycleEvent) => void,
|
|
156
|
+
gitContext?: GitContextLevel
|
|
143
157
|
) => Promise<{
|
|
144
158
|
markdown: string;
|
|
145
159
|
thinkingText: string;
|
|
146
160
|
draftBytes?: number;
|
|
147
161
|
preferenceBytes?: number;
|
|
162
|
+
usage?: unknown;
|
|
148
163
|
}>;
|
|
149
164
|
type ThinkingLevel = Parameters<ExtensionAPI["setThinkingLevel"]>[0];
|
|
150
165
|
|
|
@@ -158,54 +173,215 @@ const notify = (
|
|
|
158
173
|
}
|
|
159
174
|
};
|
|
160
175
|
|
|
176
|
+
type ManualAdvisorProgressPhase =
|
|
177
|
+
| "preparing"
|
|
178
|
+
| "active"
|
|
179
|
+
| "complete"
|
|
180
|
+
| "cancelled"
|
|
181
|
+
| "error";
|
|
182
|
+
interface ManualAdvisorProgressState {
|
|
183
|
+
phase: ManualAdvisorProgressPhase;
|
|
184
|
+
scout?: ScoutToolDetails;
|
|
185
|
+
text?: string;
|
|
186
|
+
thinking?: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
class ManualAdvisorProgressComponent implements Component {
|
|
190
|
+
private readonly question: string | undefined;
|
|
191
|
+
private readonly state: ManualAdvisorProgressState;
|
|
192
|
+
private readonly expanded: boolean;
|
|
193
|
+
private readonly theme: Theme;
|
|
194
|
+
|
|
195
|
+
constructor(
|
|
196
|
+
question: string | undefined,
|
|
197
|
+
state: ManualAdvisorProgressState,
|
|
198
|
+
expanded: boolean,
|
|
199
|
+
theme: Theme
|
|
200
|
+
) {
|
|
201
|
+
this.question = question;
|
|
202
|
+
this.state = state;
|
|
203
|
+
this.expanded = expanded;
|
|
204
|
+
this.theme = theme;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
render(width: number): string[] {
|
|
208
|
+
const box = renderAdvisorCallBox(this.question, this.theme);
|
|
209
|
+
if (this.state.phase === "complete" || this.state.phase === "cancelled") {
|
|
210
|
+
return box.render(width);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const { scout } = this.state;
|
|
214
|
+
const scoutActive =
|
|
215
|
+
scout?.status === "calling" || scout?.status === "streaming";
|
|
216
|
+
if (scoutActive) {
|
|
217
|
+
renderScoutDetails(box, scout, this.expanded, this.theme);
|
|
218
|
+
return box.render(width);
|
|
219
|
+
}
|
|
220
|
+
if (scout?.status === "cancelled") {
|
|
221
|
+
return box.render(width);
|
|
222
|
+
}
|
|
223
|
+
if (this.state.phase === "error") {
|
|
224
|
+
box.addChild(
|
|
225
|
+
new Text(
|
|
226
|
+
this.theme.fg("error", this.theme.bold("◆ ADVISOR · FAILED")),
|
|
227
|
+
0,
|
|
228
|
+
0
|
|
229
|
+
)
|
|
230
|
+
);
|
|
231
|
+
return box.render(width);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const frame =
|
|
235
|
+
SPINNER_FRAMES[Math.floor(Date.now() / 80) % SPINNER_FRAMES.length];
|
|
236
|
+
let status = "Working…";
|
|
237
|
+
if (this.state.phase === "preparing") {
|
|
238
|
+
status = "Preparing…";
|
|
239
|
+
} else if (this.state.text?.trim()) {
|
|
240
|
+
status = "Responding…";
|
|
241
|
+
}
|
|
242
|
+
box.addChild(
|
|
243
|
+
new Text(
|
|
244
|
+
`${this.theme.fg("warning", this.theme.bold(`◆ ADVISOR ${frame}`))} ${this.theme.fg("dim", `· ${status}`)}`,
|
|
245
|
+
0,
|
|
246
|
+
0
|
|
247
|
+
)
|
|
248
|
+
);
|
|
249
|
+
if (this.state.thinking) {
|
|
250
|
+
box.addChild(
|
|
251
|
+
new Text(
|
|
252
|
+
this.theme.fg(
|
|
253
|
+
"thinkingText",
|
|
254
|
+
` 💭 ${this.state.thinking.replace(/\n/g, " ").slice(-200)}`
|
|
255
|
+
),
|
|
256
|
+
0,
|
|
257
|
+
0
|
|
258
|
+
)
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
if (this.state.text) {
|
|
262
|
+
box.addChild(
|
|
263
|
+
new Markdown(
|
|
264
|
+
adviceForDisplay(this.state.text, this.expanded),
|
|
265
|
+
0,
|
|
266
|
+
0,
|
|
267
|
+
getMarkdownTheme()
|
|
268
|
+
)
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
return box.render(width);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
invalidate(): void {
|
|
275
|
+
// The live progress state is read during each render.
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
161
279
|
const findConfiguredModel = (ctx: ExtensionContext, ref: string) => {
|
|
162
280
|
const [provider, modelId] = splitRef(ref);
|
|
163
281
|
return ctx.modelRegistry.find(provider, modelId);
|
|
164
282
|
};
|
|
165
283
|
|
|
284
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: one settings form maps every persisted control.
|
|
285
|
+
const applyAdvisorSettings = (settings: AdvisorSettings) => {
|
|
286
|
+
setAdvisorEffortRef(
|
|
287
|
+
settings.effort === "Default (Model Default)" ? undefined : settings.effort
|
|
288
|
+
);
|
|
289
|
+
setContextMaxCharsRef(settings.contextMaxChars);
|
|
290
|
+
setAdvisorPlanGateRef(settings.planGate);
|
|
291
|
+
setAdvisorFailureGateRef(settings.failureGate);
|
|
292
|
+
setAdvisorCompletionGateRef(settings.completionGate);
|
|
293
|
+
setAdvisorCollapseResponsesRef(settings.collapseResponses);
|
|
294
|
+
setAdvisorCustomInvocationRef(settings.customRule);
|
|
295
|
+
setAdvisorBlockOnBlockedRef(settings.blockOnBlocked ?? true);
|
|
296
|
+
setAdvisorAutoLoopGateRef(settings.autoLoopGate ?? true);
|
|
297
|
+
setAdvisorLoopThresholdRef(settings.loopThreshold ?? 3);
|
|
298
|
+
setAdvisorMaxCallsPerSessionRef(settings.maxCallsPerSession);
|
|
299
|
+
setAdvisorSessionSummaryRef(settings.sessionSummary ?? false);
|
|
300
|
+
setAdvisorScoutEnabledRef(settings.scoutEnabled ?? false);
|
|
301
|
+
setShowUsageDetailsRef(settings.showUsageDetails ?? true);
|
|
302
|
+
setShowUsageFooterRef(settings.showUsageFooter ?? false);
|
|
303
|
+
setSimpleModeRef(settings.simpleMode ?? false);
|
|
304
|
+
setAlwaysOnRef(settings.alwaysOn ?? false);
|
|
305
|
+
setAdvisorFailureModeRef(settings.failureMode ?? "block-session");
|
|
306
|
+
setAdvisorHerdrIntegrationRef(settings.herdrIntegration ?? true);
|
|
307
|
+
setAdvisorToolResultMaxLinesRef(settings.toolResultMaxLines ?? 2000);
|
|
308
|
+
setAdvisorToolResultMaxBytesRef(settings.toolResultMaxBytes ?? 50 * 1024);
|
|
309
|
+
setAdvisorRedactSecretsRef(settings.redactSecrets ?? false);
|
|
310
|
+
setAdvisorGitContextRef(settings.gitContext ?? "summary");
|
|
311
|
+
setAdvisorGitContextMaxCharsRef(settings.gitContextMaxChars ?? 20_000);
|
|
312
|
+
setAdvisorToolPoliciesRef(settings.toolPolicies ?? {});
|
|
313
|
+
setAdvisorTrackedFileContentRef(settings.trackedFileContent ?? false);
|
|
314
|
+
setAdvisorUntrackedContentRef(settings.untrackedContent ?? false);
|
|
315
|
+
setAdvisorOutcomeLoggingRef(settings.outcomeLogging ?? false);
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const saveAdvisorSettings = (
|
|
319
|
+
ctx: ExtensionContext,
|
|
320
|
+
settings: AdvisorSettings
|
|
321
|
+
) => {
|
|
322
|
+
applyAdvisorSettings(settings);
|
|
323
|
+
saveConfig(ctx);
|
|
324
|
+
saveGlobalOutcomeLogging(settings.outcomeLogging ?? false);
|
|
325
|
+
};
|
|
326
|
+
|
|
166
327
|
export const registerCommands = (
|
|
167
328
|
pi: ExtensionAPI,
|
|
168
329
|
dependencies: {
|
|
169
330
|
consult?: ManualConsult;
|
|
170
331
|
sessionState?: AdvisorSessionState;
|
|
171
332
|
statusManager?: ScoutStatusManager;
|
|
172
|
-
telemetry?: BenchmarkTelemetry;
|
|
173
333
|
} = {}
|
|
174
334
|
) => {
|
|
175
335
|
const advisorSessionState =
|
|
176
336
|
dependencies.sessionState ?? defaultAdvisorSessionState;
|
|
177
|
-
const scoutStatus =
|
|
337
|
+
const scoutStatus =
|
|
338
|
+
dependencies.statusManager ?? new ScoutStatusManager(false);
|
|
178
339
|
const flowEnabled = () => pi.getActiveTools().includes("ask_advisor");
|
|
179
340
|
const requestAdvisor =
|
|
180
341
|
dependencies.consult ??
|
|
181
|
-
((ctx, question, signal, onChunk, onScout) =>
|
|
342
|
+
((ctx, question, signal, onChunk, onScout, gitContext) =>
|
|
182
343
|
consultAdvisor(
|
|
183
344
|
ctx,
|
|
184
345
|
question,
|
|
185
346
|
signal,
|
|
186
347
|
onChunk,
|
|
187
348
|
"manual",
|
|
188
|
-
|
|
349
|
+
gitContext,
|
|
189
350
|
undefined,
|
|
190
351
|
undefined,
|
|
191
352
|
undefined,
|
|
192
353
|
onScout,
|
|
193
|
-
undefined
|
|
194
|
-
dependencies.telemetry
|
|
354
|
+
undefined
|
|
195
355
|
));
|
|
196
356
|
const manualConsultations = new Map<AbortController, symbol>();
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
357
|
+
const manualProgressTimers = new Map<
|
|
358
|
+
AbortController,
|
|
359
|
+
ReturnType<typeof setInterval>
|
|
360
|
+
>();
|
|
361
|
+
const manualProgress = new Map<string, ManualAdvisorProgressState>();
|
|
362
|
+
let manualProgressSequence = 0;
|
|
363
|
+
const updateAdvisorUsageStatus = (ctx: ExtensionContext) => {
|
|
364
|
+
if (ctx.hasUI) {
|
|
365
|
+
ctx.ui.setStatus(
|
|
366
|
+
"advisor-usage",
|
|
367
|
+
getAdvisorSettings().showUsageFooter
|
|
368
|
+
? advisorSessionState.usageStatus()
|
|
369
|
+
: undefined
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
const reportManualBudgetExhausted = (ctx: ExtensionContext) => {
|
|
374
|
+
const message = "Advisor call budget exhausted for this session.";
|
|
375
|
+
notify(ctx, message, "warning");
|
|
376
|
+
notifyHerdrAdvisorFailure("Advisor budget exhausted", message);
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
// A status update with an undefined value is an invisible render pulse. The
|
|
380
|
+
// transcript owns manual progress now, so no consultation text is kept in the
|
|
381
|
+
// footer while this pulse still drives the live spinner.
|
|
382
|
+
const requestManualRender = (ctx: ExtensionContext) => {
|
|
383
|
+
if (ctx.hasUI) {
|
|
384
|
+
ctx.ui.setStatus("advisor-manual", undefined);
|
|
209
385
|
}
|
|
210
386
|
};
|
|
211
387
|
|
|
@@ -213,10 +389,26 @@ export const registerCommands = (
|
|
|
213
389
|
ctx: ExtensionContext,
|
|
214
390
|
question: string | undefined,
|
|
215
391
|
controller: AbortController,
|
|
216
|
-
scoutStatusToken: symbol
|
|
392
|
+
scoutStatusToken: symbol,
|
|
393
|
+
progress: ManualAdvisorProgressState,
|
|
394
|
+
gitContext?: GitContextLevel
|
|
217
395
|
) => {
|
|
218
396
|
herdrAdvisorActivity.start();
|
|
219
|
-
|
|
397
|
+
progress.phase = "preparing";
|
|
398
|
+
requestManualRender(ctx);
|
|
399
|
+
if (ctx.hasUI) {
|
|
400
|
+
const timer = setInterval(() => {
|
|
401
|
+
if (
|
|
402
|
+
controller.signal.aborted ||
|
|
403
|
+
manualConsultations.get(controller) !== scoutStatusToken
|
|
404
|
+
) {
|
|
405
|
+
clearInterval(timer);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
requestManualRender(ctx);
|
|
409
|
+
}, 80);
|
|
410
|
+
manualProgressTimers.set(controller, timer);
|
|
411
|
+
}
|
|
220
412
|
let scoutDetails: Parameters<typeof appendScoutLifecycleEntry>[2];
|
|
221
413
|
return requestAdvisor(
|
|
222
414
|
ctx,
|
|
@@ -226,52 +418,47 @@ export const registerCommands = (
|
|
|
226
418
|
if (controller.signal.aborted) {
|
|
227
419
|
return;
|
|
228
420
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
if (text.trim()) {
|
|
234
|
-
status = "Advisor responding…";
|
|
235
|
-
}
|
|
236
|
-
setManualStatus(ctx, controller, scoutStatusToken, status);
|
|
421
|
+
progress.phase = "active";
|
|
422
|
+
progress.thinking = thinking;
|
|
423
|
+
progress.text = text;
|
|
424
|
+
requestManualRender(ctx);
|
|
237
425
|
},
|
|
238
426
|
(event) => {
|
|
239
427
|
if (!controller.signal.aborted) {
|
|
240
428
|
scoutStatus.update(ctx, scoutStatusToken, event);
|
|
241
|
-
if (event.type === "call" || event.type === "chunk") {
|
|
242
|
-
setManualStatus(
|
|
243
|
-
ctx,
|
|
244
|
-
controller,
|
|
245
|
-
scoutStatusToken,
|
|
246
|
-
"Advisor Scout curating…"
|
|
247
|
-
);
|
|
248
|
-
} else if (event.type === "success" || event.type === "fallback") {
|
|
249
|
-
setManualStatus(
|
|
250
|
-
ctx,
|
|
251
|
-
controller,
|
|
252
|
-
scoutStatusToken,
|
|
253
|
-
"Advisor working…"
|
|
254
|
-
);
|
|
255
|
-
}
|
|
256
429
|
scoutDetails = appendScoutLifecycleEntry(pi, event, scoutDetails);
|
|
430
|
+
progress.scout = scoutDetails;
|
|
431
|
+
progress.phase = "active";
|
|
432
|
+
requestManualRender(ctx);
|
|
257
433
|
}
|
|
258
|
-
}
|
|
434
|
+
},
|
|
435
|
+
gitContext
|
|
259
436
|
)
|
|
260
|
-
.then(({ markdown }) => {
|
|
437
|
+
.then(({ markdown, usage }) => {
|
|
261
438
|
if (controller.signal.aborted) {
|
|
262
439
|
return;
|
|
263
440
|
}
|
|
441
|
+
progress.phase = "complete";
|
|
264
442
|
advisorSessionState.recordInvocation({
|
|
443
|
+
cost: advisorUsageCost(usage),
|
|
265
444
|
executionEffect: "continued",
|
|
266
445
|
kind: "markdown",
|
|
267
446
|
model: advisorRef,
|
|
268
447
|
trigger: "manual",
|
|
448
|
+
usage,
|
|
269
449
|
});
|
|
450
|
+
updateAdvisorUsageStatus(ctx);
|
|
451
|
+
const normalizedUsage = snapshotAdvisorUsage(usage);
|
|
270
452
|
pi.sendMessage(
|
|
271
453
|
{
|
|
272
454
|
content: `Manual Advisor consultation${question ? ` (${question})` : ""}:\n\n${markdown}`,
|
|
273
455
|
customType: "advisor-manual-result",
|
|
274
|
-
details: {
|
|
456
|
+
details: {
|
|
457
|
+
advisor: advisorRef,
|
|
458
|
+
question,
|
|
459
|
+
text: markdown,
|
|
460
|
+
...(normalizedUsage ? { usage: normalizedUsage } : {}),
|
|
461
|
+
},
|
|
275
462
|
display: true,
|
|
276
463
|
},
|
|
277
464
|
{
|
|
@@ -286,6 +473,7 @@ export const registerCommands = (
|
|
|
286
473
|
if (controller.signal.aborted) {
|
|
287
474
|
return;
|
|
288
475
|
}
|
|
476
|
+
progress.phase = "error";
|
|
289
477
|
const message = error instanceof Error ? error.message : String(error);
|
|
290
478
|
advisorSessionState.recordInvocation({
|
|
291
479
|
executionEffect: "continued",
|
|
@@ -294,6 +482,7 @@ export const registerCommands = (
|
|
|
294
482
|
model: advisorRef,
|
|
295
483
|
trigger: "manual",
|
|
296
484
|
});
|
|
485
|
+
updateAdvisorUsageStatus(ctx);
|
|
297
486
|
pi.sendMessage(
|
|
298
487
|
{
|
|
299
488
|
content: `Manual Advisor consultation failed: ${message}`,
|
|
@@ -310,12 +499,15 @@ export const registerCommands = (
|
|
|
310
499
|
notifyHerdrAdvisorFailure("Advisor consultation failed", message);
|
|
311
500
|
})
|
|
312
501
|
.finally(() => {
|
|
313
|
-
if (
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
)
|
|
317
|
-
|
|
502
|
+
if (controller.signal.aborted) {
|
|
503
|
+
progress.phase = "cancelled";
|
|
504
|
+
}
|
|
505
|
+
const timer = manualProgressTimers.get(controller);
|
|
506
|
+
if (timer) {
|
|
507
|
+
clearInterval(timer);
|
|
508
|
+
manualProgressTimers.delete(controller);
|
|
318
509
|
}
|
|
510
|
+
requestManualRender(ctx);
|
|
319
511
|
scoutStatus.release(ctx, scoutStatusToken);
|
|
320
512
|
manualConsultations.delete(controller);
|
|
321
513
|
herdrAdvisorActivity.finish();
|
|
@@ -414,9 +606,20 @@ export const registerCommands = (
|
|
|
414
606
|
|
|
415
607
|
pi.registerEntryRenderer?.(
|
|
416
608
|
"advisor-manual-call",
|
|
417
|
-
(entry,
|
|
418
|
-
const { question } = (entry.data ?? {}) as {
|
|
419
|
-
|
|
609
|
+
(entry, { expanded }, theme) => {
|
|
610
|
+
const { progressId, question } = (entry.data ?? {}) as {
|
|
611
|
+
progressId?: string;
|
|
612
|
+
question?: string;
|
|
613
|
+
};
|
|
614
|
+
const progress = progressId ? manualProgress.get(progressId) : undefined;
|
|
615
|
+
return progress
|
|
616
|
+
? new ManualAdvisorProgressComponent(
|
|
617
|
+
question,
|
|
618
|
+
progress,
|
|
619
|
+
Boolean(expanded),
|
|
620
|
+
theme
|
|
621
|
+
)
|
|
622
|
+
: renderAdvisorCallBox(question, theme);
|
|
420
623
|
}
|
|
421
624
|
);
|
|
422
625
|
|
|
@@ -424,7 +627,7 @@ export const registerCommands = (
|
|
|
424
627
|
"advisor-manual-result",
|
|
425
628
|
(message, { expanded }, theme) => {
|
|
426
629
|
const details = message.details as
|
|
427
|
-
| { advisor?: string; text?: string }
|
|
630
|
+
| { advisor?: string; text?: string; usage?: unknown }
|
|
428
631
|
| undefined;
|
|
429
632
|
const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
430
633
|
const advice =
|
|
@@ -443,6 +646,12 @@ export const registerCommands = (
|
|
|
443
646
|
if (details?.advisor) {
|
|
444
647
|
box.addChild(new Text(theme.fg("dim", ` ${details.advisor}`), 0, 0));
|
|
445
648
|
}
|
|
649
|
+
if (getAdvisorSettings().showUsageDetails) {
|
|
650
|
+
const usage = formatAdvisorUsage(details?.usage);
|
|
651
|
+
if (usage) {
|
|
652
|
+
box.addChild(new Text(theme.fg("dim", ` Usage: ${usage}`), 0, 0));
|
|
653
|
+
}
|
|
654
|
+
}
|
|
446
655
|
box.addChild(
|
|
447
656
|
new Markdown(
|
|
448
657
|
adviceForDisplay(advice, expanded),
|
|
@@ -485,39 +694,88 @@ export const registerCommands = (
|
|
|
485
694
|
|
|
486
695
|
pi.on("session_shutdown", (_event, ctx) => {
|
|
487
696
|
if (ctx.hasUI) {
|
|
488
|
-
ctx.ui.setStatus("advisor-
|
|
697
|
+
ctx.ui.setStatus("advisor-usage", undefined);
|
|
489
698
|
}
|
|
490
699
|
for (const [controller, token] of manualConsultations) {
|
|
491
700
|
controller.abort();
|
|
701
|
+
const timer = manualProgressTimers.get(controller);
|
|
702
|
+
if (timer) {
|
|
703
|
+
clearInterval(timer);
|
|
704
|
+
manualProgressTimers.delete(controller);
|
|
705
|
+
}
|
|
492
706
|
scoutStatus.release(ctx, token);
|
|
493
707
|
}
|
|
494
708
|
scoutStatus.clear(ctx);
|
|
495
709
|
manualConsultations.clear();
|
|
710
|
+
manualProgressTimers.clear();
|
|
711
|
+
manualProgress.clear();
|
|
496
712
|
herdrAdvisorActivity.clear();
|
|
497
713
|
});
|
|
498
714
|
|
|
499
715
|
pi.registerCommand("advisor-manual", {
|
|
500
716
|
description:
|
|
501
717
|
"Consult the Advisor in parallel; accepts an optional focused question and fans its response out to the Executor",
|
|
502
|
-
handler: (args, ctx) => {
|
|
718
|
+
handler: async (args, ctx) => {
|
|
503
719
|
if (!loadCommandConfig(ctx)) {
|
|
504
|
-
return
|
|
720
|
+
return;
|
|
505
721
|
}
|
|
506
722
|
if (
|
|
507
723
|
!(
|
|
508
724
|
isSimpleMode() ||
|
|
509
|
-
advisorSessionState.canConsult(
|
|
725
|
+
advisorSessionState.canConsult(getAdvisorMaxCallsPerSession())
|
|
510
726
|
)
|
|
511
727
|
) {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
728
|
+
reportManualBudgetExhausted(ctx);
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
let gitContext: GitContextLevel | undefined;
|
|
733
|
+
let question: string | undefined;
|
|
734
|
+
if (ctx.mode === "tui") {
|
|
735
|
+
const request = await ctx.ui.custom<ManualAdvisorRequest | undefined>(
|
|
736
|
+
(tui, theme, keybindings, done) =>
|
|
737
|
+
new ManualAdvisorDialog({
|
|
738
|
+
gitContext: getAdvisorSettings().gitContext,
|
|
739
|
+
initialMessage: args,
|
|
740
|
+
keybindings,
|
|
741
|
+
onCancel: () => done(undefined),
|
|
742
|
+
onSubmit: done,
|
|
743
|
+
theme,
|
|
744
|
+
tui,
|
|
745
|
+
}),
|
|
746
|
+
{
|
|
747
|
+
overlay: true,
|
|
748
|
+
overlayOptions: {
|
|
749
|
+
anchor: "center",
|
|
750
|
+
margin: 2,
|
|
751
|
+
maxHeight: "80%",
|
|
752
|
+
minWidth: 56,
|
|
753
|
+
width: 76,
|
|
754
|
+
},
|
|
755
|
+
}
|
|
756
|
+
);
|
|
757
|
+
if (!request) {
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
if (
|
|
761
|
+
!(
|
|
762
|
+
isSimpleMode() ||
|
|
763
|
+
advisorSessionState.canConsult(getAdvisorMaxCallsPerSession())
|
|
764
|
+
)
|
|
765
|
+
) {
|
|
766
|
+
reportManualBudgetExhausted(ctx);
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
const { gitContext: selectedGitContext, message } = request;
|
|
770
|
+
gitContext = selectedGitContext;
|
|
771
|
+
question = resolveAdvisorRequest(message);
|
|
772
|
+
} else {
|
|
773
|
+
question = resolveAdvisorRequest(args);
|
|
516
774
|
}
|
|
775
|
+
|
|
517
776
|
if (!isSimpleMode()) {
|
|
518
777
|
advisorSessionState.consumeCall();
|
|
519
778
|
}
|
|
520
|
-
const question = resolveAdvisorRequest(args);
|
|
521
779
|
// A single visible progress surface avoids competing consultations overwriting
|
|
522
780
|
// each other's streamed state. A newer manual request replaces the previous one.
|
|
523
781
|
for (const [pending, token] of manualConsultations) {
|
|
@@ -527,11 +785,21 @@ export const registerCommands = (
|
|
|
527
785
|
manualConsultations.clear();
|
|
528
786
|
const controller = new AbortController();
|
|
529
787
|
const scoutStatusToken = Symbol("manual-scout");
|
|
788
|
+
manualProgressSequence += 1;
|
|
789
|
+
const progressId = `manual-${manualProgressSequence}`;
|
|
790
|
+
const progress: ManualAdvisorProgressState = { phase: "preparing" };
|
|
791
|
+
manualProgress.set(progressId, progress);
|
|
530
792
|
scoutStatus.register(scoutStatusToken);
|
|
531
793
|
manualConsultations.set(controller, scoutStatusToken);
|
|
532
|
-
pi.appendEntry?.("advisor-manual-call", { question });
|
|
533
|
-
startManualConsultation(
|
|
534
|
-
|
|
794
|
+
pi.appendEntry?.("advisor-manual-call", { progressId, question });
|
|
795
|
+
startManualConsultation(
|
|
796
|
+
ctx,
|
|
797
|
+
question,
|
|
798
|
+
controller,
|
|
799
|
+
scoutStatusToken,
|
|
800
|
+
progress,
|
|
801
|
+
gitContext
|
|
802
|
+
);
|
|
535
803
|
},
|
|
536
804
|
});
|
|
537
805
|
|
|
@@ -616,68 +884,37 @@ export const registerCommands = (
|
|
|
616
884
|
});
|
|
617
885
|
|
|
618
886
|
pi.registerCommand("advisor-settings", {
|
|
619
|
-
description: "Configure Advisor
|
|
620
|
-
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: one settings form maps every persisted control.
|
|
887
|
+
description: "Configure Advisor settings",
|
|
621
888
|
handler: async (_args, ctx) => {
|
|
622
889
|
if (!(loadCommandConfig(ctx) && ctx.hasUI)) {
|
|
623
890
|
return;
|
|
624
891
|
}
|
|
625
892
|
|
|
626
893
|
const initial: AdvisorSettings = getAdvisorSettings();
|
|
627
|
-
|
|
894
|
+
await ctx.ui.custom<void>(
|
|
628
895
|
(tui, theme, _keybindings, done) =>
|
|
629
896
|
new AdvisorSettingsSelector({
|
|
630
897
|
effortLevels: EFFORT_LEVELS,
|
|
631
898
|
initial,
|
|
632
|
-
onCancel: () => done(
|
|
633
|
-
|
|
899
|
+
onCancel: () => done(),
|
|
900
|
+
onChange: (settings) => {
|
|
901
|
+
try {
|
|
902
|
+
saveAdvisorSettings(ctx, settings);
|
|
903
|
+
updateAdvisorUsageStatus(ctx);
|
|
904
|
+
} catch (error) {
|
|
905
|
+
const message =
|
|
906
|
+
error instanceof Error ? error.message : String(error);
|
|
907
|
+
ctx.ui.notify(
|
|
908
|
+
`Could not save Advisor settings: ${message}`,
|
|
909
|
+
"error"
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
},
|
|
634
913
|
presets: CONTEXT_PRESETS,
|
|
635
914
|
theme,
|
|
636
915
|
tui,
|
|
637
916
|
})
|
|
638
917
|
);
|
|
639
|
-
if (!settings) {
|
|
640
|
-
return;
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
setAdvisorEffortRef(
|
|
644
|
-
settings.effort === "Default (Model Default)"
|
|
645
|
-
? undefined
|
|
646
|
-
: settings.effort
|
|
647
|
-
);
|
|
648
|
-
setContextMaxCharsRef(settings.contextMaxChars);
|
|
649
|
-
setAdvisorPlanGateRef(settings.planGate);
|
|
650
|
-
setAdvisorFailureGateRef(settings.failureGate);
|
|
651
|
-
setAdvisorCompletionGateRef(settings.completionGate);
|
|
652
|
-
setAdvisorCollapseResponsesRef(settings.collapseResponses);
|
|
653
|
-
setAdvisorCustomInvocationRef(settings.customRule);
|
|
654
|
-
setAdvisorBlockOnBlockedRef(settings.blockOnBlocked ?? true);
|
|
655
|
-
setAdvisorAutoLoopGateRef(settings.autoLoopGate ?? true);
|
|
656
|
-
setAdvisorLoopThresholdRef(settings.loopThreshold ?? 3);
|
|
657
|
-
setAdvisorMaxCallsPerSessionRef(settings.maxCallsPerSession);
|
|
658
|
-
setAdvisorSessionSummaryRef(settings.sessionSummary ?? false);
|
|
659
|
-
setAdvisorScoutEnabledRef(settings.scoutEnabled ?? false);
|
|
660
|
-
setSimpleModeRef(settings.simpleMode ?? false);
|
|
661
|
-
setAlwaysOnRef(settings.alwaysOn ?? false);
|
|
662
|
-
setAdvisorFailureModeRef(settings.failureMode ?? "block-session");
|
|
663
|
-
setAdvisorHerdrIntegrationRef(settings.herdrIntegration ?? true);
|
|
664
|
-
setAdvisorToolResultMaxLinesRef(settings.toolResultMaxLines ?? 2000);
|
|
665
|
-
setAdvisorToolResultMaxBytesRef(settings.toolResultMaxBytes ?? 50 * 1024);
|
|
666
|
-
setAdvisorRedactSecretsRef(settings.redactSecrets ?? false);
|
|
667
|
-
setAdvisorGitContextRef(settings.gitContext ?? "summary");
|
|
668
|
-
setAdvisorGitContextMaxCharsRef(settings.gitContextMaxChars ?? 20_000);
|
|
669
|
-
setAdvisorToolPoliciesRef(settings.toolPolicies ?? {});
|
|
670
|
-
setAdvisorTrackedFileContentRef(settings.trackedFileContent ?? false);
|
|
671
|
-
setAdvisorUntrackedContentRef(settings.untrackedContent ?? false);
|
|
672
|
-
setAdvisorOutcomeLoggingRef(settings.outcomeLogging ?? false);
|
|
673
|
-
const path = saveConfig(ctx);
|
|
674
|
-
const globalPath = saveGlobalOutcomeLogging(
|
|
675
|
-
settings.outcomeLogging ?? false
|
|
676
|
-
);
|
|
677
|
-
ctx.ui.notify(
|
|
678
|
-
`Saved Advisor settings to ${path}; outcome logging globally to ${globalPath}`,
|
|
679
|
-
"info"
|
|
680
|
-
);
|
|
681
918
|
},
|
|
682
919
|
});
|
|
683
920
|
|