pi-advisor-flow 0.5.1 → 0.5.3
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 +21 -0
- package/README.md +3 -2
- package/extensions/index.ts +7 -7
- package/package.json +2 -1
- package/src/commands/activation-preparation.ts +117 -0
- package/src/commands/activation.ts +135 -0
- package/src/commands/lifecycle.ts +83 -0
- package/src/commands/manual-command.ts +107 -0
- package/src/commands/manual-consultation.ts +144 -0
- package/src/commands/manual-progress.ts +93 -0
- package/src/commands/model-commands.ts +66 -0
- package/src/commands/model-options.ts +181 -0
- package/src/commands/model-picker.ts +110 -0
- package/src/commands/registration.ts +27 -0
- package/src/commands/renderers.ts +76 -0
- package/src/commands/runtime.ts +114 -0
- package/src/commands/settings-commands.ts +80 -0
- package/src/commands/settings-persistence.ts +81 -0
- package/src/commands/types.ts +78 -0
- package/src/commands.ts +9 -1259
- package/src/config/args.ts +37 -0
- package/src/config/defaults.ts +190 -0
- package/src/config/state.ts +199 -0
- package/src/config/storage.ts +224 -0
- package/src/config/types.ts +63 -0
- package/src/config/validation.ts +234 -0
- package/src/config.ts +104 -814
- package/src/conversation.ts +4 -2
- package/src/herdr.ts +1 -1
- package/src/model-stream.ts +15 -1
- package/src/scout-context.ts +2 -2
- package/src/scout.ts +1 -1
- package/src/tools/consultation.ts +337 -0
- package/src/tools/gate-policy.ts +132 -0
- package/src/tools/gate-protocol.ts +127 -0
- package/src/tools/loop-gate.ts +210 -0
- package/src/tools/prompts.ts +159 -0
- package/src/tools/register-ask-advisor.ts +247 -0
- package/src/tools/register-lifecycle.ts +119 -0
- package/src/tools/register-outcome.ts +75 -0
- package/src/tools/register-renderers.ts +88 -0
- package/src/tools/registration.ts +35 -0
- package/src/tools/render-advisor-result.ts +168 -0
- package/src/tools/render-common.ts +120 -0
- package/src/tools/scout-status.ts +206 -0
- package/src/tools/session.ts +3 -0
- package/src/tools/types.ts +119 -0
- package/src/tools.ts +42 -1865
- package/src/ui/manual-dialog-render.ts +150 -0
- package/src/ui/manual-dialog.ts +305 -0
- package/src/ui/model-selector.ts +166 -0
- package/src/ui/settings-formatting.ts +133 -0
- package/src/ui/settings-items.ts +323 -0
- package/src/ui/settings-list-adapter.ts +103 -0
- package/src/ui/settings-mutations.ts +80 -0
- package/src/ui/settings-selector.ts +161 -0
- package/src/ui/text-setting-submenu.ts +66 -0
- package/src/ui/types.ts +111 -0
- package/src/ui.ts +9 -1418
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { getMarkdownTheme, type Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { type Component, Markdown, Text } from "@earendil-works/pi-tui";
|
|
3
|
+
import {
|
|
4
|
+
adviceForDisplay,
|
|
5
|
+
renderAdvisorCallBox,
|
|
6
|
+
renderThinkingMarkdown,
|
|
7
|
+
SPINNER_FRAMES,
|
|
8
|
+
} from "../tools/render-common.js";
|
|
9
|
+
import { renderScoutDetails } from "../tools/scout-status.js";
|
|
10
|
+
import type { ManualAdvisorProgressState } from "./types.js";
|
|
11
|
+
|
|
12
|
+
export class ManualAdvisorProgressComponent implements Component {
|
|
13
|
+
private readonly question: string | undefined;
|
|
14
|
+
private readonly state: ManualAdvisorProgressState;
|
|
15
|
+
private readonly expanded: boolean;
|
|
16
|
+
private readonly theme: Theme;
|
|
17
|
+
|
|
18
|
+
constructor(
|
|
19
|
+
question: string | undefined,
|
|
20
|
+
state: ManualAdvisorProgressState,
|
|
21
|
+
expanded: boolean,
|
|
22
|
+
theme: Theme
|
|
23
|
+
) {
|
|
24
|
+
this.question = question;
|
|
25
|
+
this.state = state;
|
|
26
|
+
this.expanded = expanded;
|
|
27
|
+
this.theme = theme;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
render(width: number): string[] {
|
|
31
|
+
const box = renderAdvisorCallBox(this.question, this.theme);
|
|
32
|
+
if (this.state.phase === "complete" || this.state.phase === "cancelled") {
|
|
33
|
+
return box.render(width);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { scout } = this.state;
|
|
37
|
+
const scoutActive =
|
|
38
|
+
scout?.status === "calling" || scout?.status === "streaming";
|
|
39
|
+
if (scoutActive) {
|
|
40
|
+
renderScoutDetails(box, scout, this.expanded, this.theme);
|
|
41
|
+
return box.render(width);
|
|
42
|
+
}
|
|
43
|
+
if (scout?.status === "cancelled") {
|
|
44
|
+
return box.render(width);
|
|
45
|
+
}
|
|
46
|
+
if (this.state.phase === "error") {
|
|
47
|
+
box.addChild(
|
|
48
|
+
new Text(
|
|
49
|
+
this.theme.fg("error", this.theme.bold("◆ ADVISOR · FAILED")),
|
|
50
|
+
0,
|
|
51
|
+
0
|
|
52
|
+
)
|
|
53
|
+
);
|
|
54
|
+
return box.render(width);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const frame =
|
|
58
|
+
SPINNER_FRAMES[Math.floor(Date.now() / 80) % SPINNER_FRAMES.length];
|
|
59
|
+
let status = "Working…";
|
|
60
|
+
if (this.state.phase === "preparing") {
|
|
61
|
+
status = "Preparing…";
|
|
62
|
+
} else if (this.state.text?.trim()) {
|
|
63
|
+
status = "Responding…";
|
|
64
|
+
}
|
|
65
|
+
box.addChild(
|
|
66
|
+
new Text(
|
|
67
|
+
`${this.theme.fg("warning", this.theme.bold(`◆ ADVISOR ${frame}`))} ${this.theme.fg("dim", `· ${status}`)}`,
|
|
68
|
+
0,
|
|
69
|
+
0
|
|
70
|
+
)
|
|
71
|
+
);
|
|
72
|
+
if (this.state.thinking?.trim()) {
|
|
73
|
+
box.addChild(
|
|
74
|
+
renderThinkingMarkdown(this.state.thinking.slice(-200), this.theme)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
if (this.state.text) {
|
|
78
|
+
box.addChild(
|
|
79
|
+
new Markdown(
|
|
80
|
+
adviceForDisplay(this.state.text, this.expanded),
|
|
81
|
+
0,
|
|
82
|
+
0,
|
|
83
|
+
getMarkdownTheme()
|
|
84
|
+
)
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
return box.render(width);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
invalidate(): void {
|
|
91
|
+
// The live progress state is read during each render.
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
advisorEffortRef,
|
|
3
|
+
advisorRef,
|
|
4
|
+
executorEffortRef,
|
|
5
|
+
executorRef,
|
|
6
|
+
getPersistedModelRefs,
|
|
7
|
+
setAdvisorEffortRef,
|
|
8
|
+
setAdvisorRef,
|
|
9
|
+
setExecutorEffortRef,
|
|
10
|
+
setExecutorRef,
|
|
11
|
+
} from "../config/state.js";
|
|
12
|
+
import { saveConfig } from "../config/storage.js";
|
|
13
|
+
import { activateAdvisor } from "./activation.js";
|
|
14
|
+
import { loadCommandConfig } from "./activation-preparation.js";
|
|
15
|
+
import { selectAdvisorModels } from "./model-picker.js";
|
|
16
|
+
import type { CommandRuntime } from "./types.js";
|
|
17
|
+
|
|
18
|
+
export const registerModelCommands = (runtime: CommandRuntime) => {
|
|
19
|
+
runtime.pi.registerCommand("advisor", {
|
|
20
|
+
description:
|
|
21
|
+
"Enable the Executor/Advisor flow and switch to the configured or explicitly selected Executor model; accepts contextMaxChars=N",
|
|
22
|
+
handler: (args, ctx) => activateAdvisor(runtime, args, ctx),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
runtime.pi.registerCommand("advisor-models", {
|
|
26
|
+
description:
|
|
27
|
+
"Select and persist the Executor and Advisor models with reasoning levels",
|
|
28
|
+
handler: async (_args, ctx) => {
|
|
29
|
+
if (!(loadCommandConfig(ctx) && ctx.hasUI)) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
// When `/model` was used before activation, show that session choice as
|
|
33
|
+
// the Executor's current option instead of making the persisted Executor
|
|
34
|
+
// look like the active selection.
|
|
35
|
+
const persisted = getPersistedModelRefs();
|
|
36
|
+
const selection = await selectAdvisorModels(ctx, {
|
|
37
|
+
advisor: persisted.advisor ? advisorRef : "",
|
|
38
|
+
advisorEffort: advisorEffortRef,
|
|
39
|
+
executor:
|
|
40
|
+
runtime.pendingExecutorModelRef ??
|
|
41
|
+
(persisted.executor ? executorRef : ""),
|
|
42
|
+
executorEffort: executorEffortRef,
|
|
43
|
+
selectAdvisor: true,
|
|
44
|
+
selectExecutor: true,
|
|
45
|
+
});
|
|
46
|
+
if (!selection) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
setExecutorRef(selection.executor);
|
|
51
|
+
setAdvisorRef(selection.advisor);
|
|
52
|
+
setExecutorEffortRef(selection.executorEffort);
|
|
53
|
+
setAdvisorEffortRef(selection.advisorEffort);
|
|
54
|
+
|
|
55
|
+
const path = saveConfig(ctx, {
|
|
56
|
+
persistAdvisor: true,
|
|
57
|
+
persistExecutor: true,
|
|
58
|
+
});
|
|
59
|
+
runtime.pendingExecutorModelRef = undefined;
|
|
60
|
+
ctx.ui.notify(
|
|
61
|
+
`Saved Executor + Advisor configurations to ${path}`,
|
|
62
|
+
"info"
|
|
63
|
+
);
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { type getPersistedModelRefs, splitRef } from "../config/state.js";
|
|
3
|
+
import type { ContextPreset } from "../ui/types.js";
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_EFFORT_LEVEL = "Default (Model Default)";
|
|
6
|
+
export const SELECTED_PREFIX = "✓ ";
|
|
7
|
+
export const EFFORT_LEVELS = [
|
|
8
|
+
DEFAULT_EFFORT_LEVEL,
|
|
9
|
+
"off",
|
|
10
|
+
"minimal",
|
|
11
|
+
"low",
|
|
12
|
+
"medium",
|
|
13
|
+
"high",
|
|
14
|
+
"xhigh",
|
|
15
|
+
"max",
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export const effortChoices = (configured: string | undefined): string[] => {
|
|
19
|
+
const current = configured ?? DEFAULT_EFFORT_LEVEL;
|
|
20
|
+
return [
|
|
21
|
+
`${SELECTED_PREFIX}${current}`,
|
|
22
|
+
...EFFORT_LEVELS.filter((level) => level !== current),
|
|
23
|
+
];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const selectedEffort = (choice: string): string | undefined => {
|
|
27
|
+
const effort = choice.startsWith(SELECTED_PREFIX)
|
|
28
|
+
? choice.slice(SELECTED_PREFIX.length)
|
|
29
|
+
: choice;
|
|
30
|
+
return effort === DEFAULT_EFFORT_LEVEL ? undefined : effort;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const ADVISOR_ACTIVATION_EXPLANATION =
|
|
34
|
+
"The Advisor is a second-opinion model that reviews the Executor's context and returns risks, alternatives, and verification steps without changing files or running tools.";
|
|
35
|
+
|
|
36
|
+
const ARGUMENT_WHITESPACE = /\s+/;
|
|
37
|
+
const hasModelOverride = (args: string, key: "advisor" | "executor") =>
|
|
38
|
+
args
|
|
39
|
+
.trim()
|
|
40
|
+
.split(ARGUMENT_WHITESPACE)
|
|
41
|
+
.some((token) => {
|
|
42
|
+
const [tokenKey, value] = token.split("=");
|
|
43
|
+
return tokenKey === key && Boolean(value);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export const hasExecutorOverride = (args: string) =>
|
|
47
|
+
hasModelOverride(args, "executor");
|
|
48
|
+
export const hasAdvisorOverride = (args: string) =>
|
|
49
|
+
hasModelOverride(args, "advisor");
|
|
50
|
+
|
|
51
|
+
export const CONTEXT_PRESETS: ContextPreset[] = [
|
|
52
|
+
{
|
|
53
|
+
description:
|
|
54
|
+
"No conversation history. The Advisor receives only its standing instructions.",
|
|
55
|
+
label: "0",
|
|
56
|
+
value: 0,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
description: "The most recent 10,000 characters of the current branch.",
|
|
60
|
+
label: "10k",
|
|
61
|
+
value: 10_000,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
description: "The most recent 25,000 characters of the current branch.",
|
|
65
|
+
label: "25k",
|
|
66
|
+
value: 25_000,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
description: "The most recent 100,000 characters of the current branch.",
|
|
70
|
+
label: "100k",
|
|
71
|
+
value: 100_000,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
description: "The most recent 200,000 characters of the current branch.",
|
|
75
|
+
label: "200k",
|
|
76
|
+
value: 200_000,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
description:
|
|
80
|
+
"The complete reconstructed conversation branch. Cost and model context limits apply.",
|
|
81
|
+
label: "ALL",
|
|
82
|
+
value: Number.MAX_SAFE_INTEGER,
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
export const findConfiguredModel = (
|
|
87
|
+
ctx: ExtensionContext,
|
|
88
|
+
ref: string | undefined
|
|
89
|
+
) => {
|
|
90
|
+
if (!ref) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const [provider, modelId] = splitRef(ref);
|
|
94
|
+
return ctx.modelRegistry.find(provider, modelId);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const getAvailableModelRefs = (
|
|
98
|
+
ctx: ExtensionContext
|
|
99
|
+
): string[] | undefined => {
|
|
100
|
+
if (typeof ctx.modelRegistry.getAvailable !== "function") {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
return ctx.modelRegistry
|
|
104
|
+
.getAvailable()
|
|
105
|
+
.map((model) => `${model.provider}/${model.id}`);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const isSelectableModel = (
|
|
109
|
+
ctx: ExtensionContext,
|
|
110
|
+
ref: string | undefined,
|
|
111
|
+
availableRefs: Set<string> | undefined
|
|
112
|
+
) => {
|
|
113
|
+
if (!ref) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
if (availableRefs) {
|
|
117
|
+
const [provider, modelId] = splitRef(ref);
|
|
118
|
+
return availableRefs.has(`${provider}/${modelId}`);
|
|
119
|
+
}
|
|
120
|
+
return Boolean(findConfiguredModel(ctx, ref));
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export const getExplicitModelError = (
|
|
124
|
+
ctx: ExtensionContext,
|
|
125
|
+
ref: string | undefined,
|
|
126
|
+
label: "Advisor" | "Executor",
|
|
127
|
+
overridden: boolean,
|
|
128
|
+
availableRefs: Set<string> | undefined
|
|
129
|
+
) => {
|
|
130
|
+
if (overridden) {
|
|
131
|
+
if (!ref) {
|
|
132
|
+
return `${label} model not configured`;
|
|
133
|
+
}
|
|
134
|
+
if (!findConfiguredModel(ctx, ref)) {
|
|
135
|
+
return `${label} model not found: ${ref}`;
|
|
136
|
+
}
|
|
137
|
+
if (availableRefs && !isSelectableModel(ctx, ref, availableRefs)) {
|
|
138
|
+
return `${label} model unavailable: ${ref}`;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export interface ActivationModelPlan {
|
|
144
|
+
pendingExecutor: string | undefined;
|
|
145
|
+
selectAdvisor: boolean;
|
|
146
|
+
selectExecutor: boolean;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export const planActivationModels = (
|
|
150
|
+
ctx: ExtensionContext,
|
|
151
|
+
executor: string | undefined,
|
|
152
|
+
advisor: string | undefined,
|
|
153
|
+
pendingExecutorRef: string | undefined,
|
|
154
|
+
persisted: ReturnType<typeof getPersistedModelRefs>,
|
|
155
|
+
executorOverride: boolean,
|
|
156
|
+
advisorOverride: boolean,
|
|
157
|
+
availableRefs: Set<string> | undefined
|
|
158
|
+
): ActivationModelPlan => {
|
|
159
|
+
const pendingExecutor =
|
|
160
|
+
!executorOverride &&
|
|
161
|
+
pendingExecutorRef &&
|
|
162
|
+
isSelectableModel(ctx, pendingExecutorRef, availableRefs)
|
|
163
|
+
? pendingExecutorRef
|
|
164
|
+
: undefined;
|
|
165
|
+
const executorConfigured =
|
|
166
|
+
executorOverride ||
|
|
167
|
+
Boolean(pendingExecutor) ||
|
|
168
|
+
Boolean(
|
|
169
|
+
persisted.executor && isSelectableModel(ctx, executor, availableRefs)
|
|
170
|
+
);
|
|
171
|
+
const advisorConfigured =
|
|
172
|
+
advisorOverride ||
|
|
173
|
+
Boolean(
|
|
174
|
+
persisted.advisor && isSelectableModel(ctx, advisor, availableRefs)
|
|
175
|
+
);
|
|
176
|
+
return {
|
|
177
|
+
pendingExecutor,
|
|
178
|
+
selectAdvisor: !advisorConfigured,
|
|
179
|
+
selectExecutor: !executorConfigured,
|
|
180
|
+
};
|
|
181
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { SearchableModelSelector } from "../ui/model-selector.js";
|
|
3
|
+
import {
|
|
4
|
+
effortChoices,
|
|
5
|
+
getAvailableModelRefs,
|
|
6
|
+
selectedEffort,
|
|
7
|
+
} from "./model-options.js";
|
|
8
|
+
|
|
9
|
+
export interface AdvisorModelSelection {
|
|
10
|
+
advisor: string;
|
|
11
|
+
advisorEffort: string | undefined;
|
|
12
|
+
executor: string;
|
|
13
|
+
executorEffort: string | undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface AdvisorModelPickerOptions {
|
|
17
|
+
advisor: string | undefined;
|
|
18
|
+
advisorEffort: string | undefined;
|
|
19
|
+
executor: string | undefined;
|
|
20
|
+
executorEffort: string | undefined;
|
|
21
|
+
selectAdvisor: boolean;
|
|
22
|
+
selectExecutor: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const selectAdvisorModels = async (
|
|
26
|
+
ctx: ExtensionContext,
|
|
27
|
+
options: AdvisorModelPickerOptions
|
|
28
|
+
): Promise<AdvisorModelSelection | undefined> => {
|
|
29
|
+
if (!ctx.hasUI) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
const refs = getAvailableModelRefs(ctx);
|
|
33
|
+
if (refs && refs.length === 0) {
|
|
34
|
+
ctx.ui.notify(
|
|
35
|
+
"No selectable models are available. Configure a provider with /login or models.json, then retry.",
|
|
36
|
+
"error"
|
|
37
|
+
);
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
const allOptions = [
|
|
41
|
+
...new Set(
|
|
42
|
+
refs ??
|
|
43
|
+
[options.executor, options.advisor].filter((ref): ref is string =>
|
|
44
|
+
Boolean(ref)
|
|
45
|
+
)
|
|
46
|
+
),
|
|
47
|
+
];
|
|
48
|
+
let { advisor, advisorEffort, executor, executorEffort } = options;
|
|
49
|
+
|
|
50
|
+
if (options.selectExecutor) {
|
|
51
|
+
const selectedExecutor = await ctx.ui.custom<string | undefined>(
|
|
52
|
+
(tui, theme, keybindings, done) =>
|
|
53
|
+
new SearchableModelSelector({
|
|
54
|
+
allOptions,
|
|
55
|
+
currentOption: executor || undefined,
|
|
56
|
+
keybindings,
|
|
57
|
+
onCancel: () => done(undefined),
|
|
58
|
+
onSelect: done,
|
|
59
|
+
theme,
|
|
60
|
+
title: "Select Executor Model",
|
|
61
|
+
tui,
|
|
62
|
+
})
|
|
63
|
+
);
|
|
64
|
+
if (!selectedExecutor) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
const selectedExecutorEffort = await ctx.ui.select(
|
|
68
|
+
"Select Executor Reasoning/Thinking Level",
|
|
69
|
+
effortChoices(executorEffort)
|
|
70
|
+
);
|
|
71
|
+
if (!selectedExecutorEffort) {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
executor = selectedExecutor;
|
|
75
|
+
executorEffort = selectedEffort(selectedExecutorEffort);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (options.selectAdvisor) {
|
|
79
|
+
const selectedAdvisor = await ctx.ui.custom<string | undefined>(
|
|
80
|
+
(tui, theme, keybindings, done) =>
|
|
81
|
+
new SearchableModelSelector({
|
|
82
|
+
allOptions,
|
|
83
|
+
currentOption: advisor || undefined,
|
|
84
|
+
keybindings,
|
|
85
|
+
onCancel: () => done(undefined),
|
|
86
|
+
onSelect: done,
|
|
87
|
+
theme,
|
|
88
|
+
title: "Select Advisor Model",
|
|
89
|
+
tui,
|
|
90
|
+
})
|
|
91
|
+
);
|
|
92
|
+
if (!selectedAdvisor) {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
const selectedAdvisorEffort = await ctx.ui.select(
|
|
96
|
+
"Select Advisor Reasoning/Thinking Level",
|
|
97
|
+
effortChoices(advisorEffort)
|
|
98
|
+
);
|
|
99
|
+
if (!selectedAdvisorEffort) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
advisor = selectedAdvisor;
|
|
103
|
+
advisorEffort = selectedEffort(selectedAdvisorEffort);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!(advisor && executor)) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
return { advisor, advisorEffort, executor, executorEffort };
|
|
110
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ExtensionAPI,
|
|
3
|
+
ExtensionContext,
|
|
4
|
+
} from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { activateAdvisor } from "./activation.js";
|
|
6
|
+
import { registerCommandLifecycle } from "./lifecycle.js";
|
|
7
|
+
import { registerManualCommand } from "./manual-command.js";
|
|
8
|
+
import { registerModelCommands } from "./model-commands.js";
|
|
9
|
+
import { registerCommandRenderers } from "./renderers.js";
|
|
10
|
+
import { createCommandRuntime } from "./runtime.js";
|
|
11
|
+
import { registerSettingsCommands } from "./settings-commands.js";
|
|
12
|
+
import type { CommandDependencies } from "./types.js";
|
|
13
|
+
|
|
14
|
+
export const registerCommands = (
|
|
15
|
+
pi: ExtensionAPI,
|
|
16
|
+
dependencies: CommandDependencies = {}
|
|
17
|
+
) => {
|
|
18
|
+
const runtime = createCommandRuntime(pi, dependencies);
|
|
19
|
+
const activate = (args: string, ctx: ExtensionContext, announce = true) =>
|
|
20
|
+
activateAdvisor(runtime, args, ctx, announce);
|
|
21
|
+
|
|
22
|
+
registerCommandRenderers(runtime);
|
|
23
|
+
registerCommandLifecycle(runtime, activate);
|
|
24
|
+
registerManualCommand(runtime);
|
|
25
|
+
registerModelCommands(runtime);
|
|
26
|
+
registerSettingsCommands(runtime);
|
|
27
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Box, Markdown, Text } from "@earendil-works/pi-tui";
|
|
3
|
+
import { getAdvisorSettings } from "../config/state.js";
|
|
4
|
+
import {
|
|
5
|
+
adviceForDisplay,
|
|
6
|
+
hasSoundVerdict,
|
|
7
|
+
renderAdvisorCallBox,
|
|
8
|
+
renderAdvisorResponseHeader,
|
|
9
|
+
} from "../tools/render-common.js";
|
|
10
|
+
import { formatAdvisorUsage } from "../usage.js";
|
|
11
|
+
import { ManualAdvisorProgressComponent } from "./manual-progress.js";
|
|
12
|
+
import type { CommandRuntime } from "./types.js";
|
|
13
|
+
|
|
14
|
+
export const registerCommandRenderers = (runtime: CommandRuntime) => {
|
|
15
|
+
runtime.pi.registerEntryRenderer?.(
|
|
16
|
+
"advisor-manual-call",
|
|
17
|
+
(entry, { expanded }, theme) => {
|
|
18
|
+
const { progressId, question } = (entry.data ?? {}) as {
|
|
19
|
+
progressId?: string;
|
|
20
|
+
question?: string;
|
|
21
|
+
};
|
|
22
|
+
const progress = progressId
|
|
23
|
+
? runtime.manualProgress.get(progressId)
|
|
24
|
+
: undefined;
|
|
25
|
+
return progress
|
|
26
|
+
? new ManualAdvisorProgressComponent(
|
|
27
|
+
question,
|
|
28
|
+
progress,
|
|
29
|
+
Boolean(expanded),
|
|
30
|
+
theme
|
|
31
|
+
)
|
|
32
|
+
: renderAdvisorCallBox(question, theme);
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
runtime.pi.registerMessageRenderer?.(
|
|
37
|
+
"advisor-manual-result",
|
|
38
|
+
(message, { expanded }, theme) => {
|
|
39
|
+
const details = message.details as
|
|
40
|
+
| { advisor?: string; text?: string; usage?: unknown }
|
|
41
|
+
| undefined;
|
|
42
|
+
const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
43
|
+
const advice =
|
|
44
|
+
details?.text ??
|
|
45
|
+
(typeof message.content === "string"
|
|
46
|
+
? message.content
|
|
47
|
+
: "(Advisor returned no advice.)");
|
|
48
|
+
// Manual consultations must render exactly like an Executor ask_advisor call.
|
|
49
|
+
box.addChild(
|
|
50
|
+
new Text(
|
|
51
|
+
renderAdvisorResponseHeader(hasSoundVerdict(advice), theme),
|
|
52
|
+
0,
|
|
53
|
+
0
|
|
54
|
+
)
|
|
55
|
+
);
|
|
56
|
+
if (details?.advisor) {
|
|
57
|
+
box.addChild(new Text(theme.fg("dim", ` ${details.advisor}`), 0, 0));
|
|
58
|
+
}
|
|
59
|
+
if (getAdvisorSettings().showUsageDetails) {
|
|
60
|
+
const usage = formatAdvisorUsage(details?.usage);
|
|
61
|
+
if (usage) {
|
|
62
|
+
box.addChild(new Text(theme.fg("dim", ` Usage: ${usage}`), 0, 0));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
box.addChild(
|
|
66
|
+
new Markdown(
|
|
67
|
+
adviceForDisplay(advice, expanded),
|
|
68
|
+
0,
|
|
69
|
+
0,
|
|
70
|
+
getMarkdownTheme()
|
|
71
|
+
)
|
|
72
|
+
);
|
|
73
|
+
return box;
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ExtensionAPI,
|
|
3
|
+
ExtensionContext,
|
|
4
|
+
} from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { getAdvisorSettings } from "../config/state.js";
|
|
6
|
+
import { notifyHerdrAdvisorFailure } from "../herdr.js";
|
|
7
|
+
import type { AdvisorSessionState } from "../session-state.js";
|
|
8
|
+
import { consultAdvisor } from "../tools/consultation.js";
|
|
9
|
+
import { ScoutStatusManager } from "../tools/scout-status.js";
|
|
10
|
+
import { advisorSessionState as defaultAdvisorSessionState } from "../tools/session.js";
|
|
11
|
+
import type {
|
|
12
|
+
CommandDependencies,
|
|
13
|
+
CommandRuntime as CommandRuntimeContract,
|
|
14
|
+
ManualAdvisorProgressState,
|
|
15
|
+
ManualConsult,
|
|
16
|
+
} from "./types.js";
|
|
17
|
+
|
|
18
|
+
export const notify = (
|
|
19
|
+
ctx: ExtensionContext,
|
|
20
|
+
message: string,
|
|
21
|
+
level: "error" | "info" | "warning"
|
|
22
|
+
) => {
|
|
23
|
+
if (ctx.hasUI) {
|
|
24
|
+
ctx.ui.notify(message, level);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export class CommandRuntime implements CommandRuntimeContract {
|
|
29
|
+
readonly advisorSessionState: AdvisorSessionState;
|
|
30
|
+
readonly manualConsultations = new Map<AbortController, symbol>();
|
|
31
|
+
readonly manualProgress = new Map<string, ManualAdvisorProgressState>();
|
|
32
|
+
readonly manualProgressTimers = new Map<
|
|
33
|
+
AbortController,
|
|
34
|
+
ReturnType<typeof setInterval>
|
|
35
|
+
>();
|
|
36
|
+
readonly pi: ExtensionAPI;
|
|
37
|
+
readonly requestAdvisor: ManualConsult;
|
|
38
|
+
readonly scoutStatus: ScoutStatusManager;
|
|
39
|
+
manualProgressSequence = 0;
|
|
40
|
+
pendingExecutorModelRef: string | undefined;
|
|
41
|
+
suppressModelSelectionSync = false;
|
|
42
|
+
|
|
43
|
+
constructor(pi: ExtensionAPI, dependencies: CommandDependencies = {}) {
|
|
44
|
+
this.pi = pi;
|
|
45
|
+
this.advisorSessionState =
|
|
46
|
+
dependencies.sessionState ?? defaultAdvisorSessionState;
|
|
47
|
+
this.scoutStatus =
|
|
48
|
+
dependencies.statusManager ?? new ScoutStatusManager(false);
|
|
49
|
+
this.requestAdvisor =
|
|
50
|
+
dependencies.consult ??
|
|
51
|
+
((ctx, question, signal, onChunk, onScout, gitContext) =>
|
|
52
|
+
consultAdvisor(
|
|
53
|
+
ctx,
|
|
54
|
+
question,
|
|
55
|
+
signal,
|
|
56
|
+
onChunk,
|
|
57
|
+
"manual",
|
|
58
|
+
gitContext,
|
|
59
|
+
undefined,
|
|
60
|
+
undefined,
|
|
61
|
+
undefined,
|
|
62
|
+
onScout,
|
|
63
|
+
undefined
|
|
64
|
+
));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
flowEnabled() {
|
|
68
|
+
return this.pi.getActiveTools().includes("ask_advisor");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
nextManualProgressId() {
|
|
72
|
+
this.manualProgressSequence += 1;
|
|
73
|
+
return `manual-${this.manualProgressSequence}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async setExecutorModel(
|
|
77
|
+
model: Parameters<ExtensionAPI["setModel"]>[0]
|
|
78
|
+
): Promise<boolean> {
|
|
79
|
+
this.suppressModelSelectionSync = true;
|
|
80
|
+
try {
|
|
81
|
+
return await this.pi.setModel(model);
|
|
82
|
+
} finally {
|
|
83
|
+
this.suppressModelSelectionSync = false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
updateAdvisorUsageStatus(ctx: ExtensionContext) {
|
|
88
|
+
if (ctx.hasUI) {
|
|
89
|
+
ctx.ui.setStatus(
|
|
90
|
+
"advisor-usage",
|
|
91
|
+
getAdvisorSettings().showUsageFooter
|
|
92
|
+
? this.advisorSessionState.usageStatus()
|
|
93
|
+
: undefined
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
reportManualBudgetExhausted(ctx: ExtensionContext) {
|
|
99
|
+
const message = "Advisor call budget exhausted for this session.";
|
|
100
|
+
notify(ctx, message, "warning");
|
|
101
|
+
notifyHerdrAdvisorFailure("Advisor budget exhausted", message);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
requestManualRender(ctx: ExtensionContext) {
|
|
105
|
+
if (ctx.hasUI) {
|
|
106
|
+
ctx.ui.setStatus("advisor-manual", undefined);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export const createCommandRuntime = (
|
|
112
|
+
pi: ExtensionAPI,
|
|
113
|
+
dependencies: CommandDependencies = {}
|
|
114
|
+
) => new CommandRuntime(pi, dependencies);
|