pi-advisor-flow 0.2.0 → 0.2.2
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/extensions/index.ts +15 -6
- package/package.json +12 -4
- package/src/commands.ts +125 -129
- package/src/config.ts +256 -178
- package/src/conversation.ts +43 -24
- package/src/herdr.ts +3 -6
- package/src/session-state.ts +14 -6
- package/src/tools.ts +282 -185
- package/src/ui.ts +107 -73
package/extensions/index.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { registerCommands } from "../src/commands.js";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
consultAdvisor as consultAdvisorImplementation,
|
|
5
|
+
parseAutomaticDecision as parseAutomaticDecisionImplementation,
|
|
6
|
+
registerAdvisorTool,
|
|
7
|
+
runAdvisorGate as runAdvisorGateImplementation,
|
|
8
|
+
} from "../src/tools.js";
|
|
4
9
|
|
|
5
10
|
export type { AdvisorConfig, GateFailureMode } from "../src/config.js";
|
|
6
11
|
export type {
|
|
@@ -12,11 +17,15 @@ export type {
|
|
|
12
17
|
GateDecision,
|
|
13
18
|
GateTrigger,
|
|
14
19
|
} from "../src/tools.js";
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
export const consultAdvisor = (
|
|
21
|
+
...args: Parameters<typeof consultAdvisorImplementation>
|
|
22
|
+
) => consultAdvisorImplementation(...args);
|
|
23
|
+
export const parseAutomaticDecision = (
|
|
24
|
+
...args: Parameters<typeof parseAutomaticDecisionImplementation>
|
|
25
|
+
) => parseAutomaticDecisionImplementation(...args);
|
|
26
|
+
export const runAdvisorGate = (
|
|
27
|
+
...args: Parameters<typeof runAdvisorGateImplementation>
|
|
28
|
+
) => runAdvisorGateImplementation(...args);
|
|
20
29
|
|
|
21
30
|
export default function (pi: ExtensionAPI) {
|
|
22
31
|
registerAdvisorTool(pi);
|
package/package.json
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"devDependencies": {
|
|
3
|
+
"@biomejs/biome": "2.5.3",
|
|
3
4
|
"@earendil-works/pi-ai": "^0.80.7",
|
|
4
5
|
"@earendil-works/pi-coding-agent": "^0.80.7",
|
|
5
6
|
"@earendil-works/pi-tui": "^0.80.7",
|
|
6
7
|
"@types/node": "^20.11.0",
|
|
7
8
|
"bun-types": "^1.0.0",
|
|
9
|
+
"husky": "^9.1.7",
|
|
10
|
+
"lint-staged": "^17.1.0",
|
|
8
11
|
"typebox": "^1.1.38",
|
|
9
12
|
"typescript": "^5.3.3",
|
|
10
|
-
"ultracite": "7.9.4"
|
|
11
|
-
"@biomejs/biome": "2.5.3"
|
|
13
|
+
"ultracite": "7.9.4"
|
|
12
14
|
},
|
|
13
15
|
"name": "pi-advisor-flow",
|
|
14
16
|
"peerDependencies": {
|
|
@@ -21,10 +23,16 @@
|
|
|
21
23
|
"test": "bun test",
|
|
22
24
|
"typecheck": "tsc --noEmit",
|
|
23
25
|
"format": "bunx ultracite fix --linter-enabled=false",
|
|
24
|
-
"
|
|
26
|
+
"lint": "bunx ultracite check",
|
|
27
|
+
"lint:fix": "bunx ultracite fix",
|
|
28
|
+
"package:check": "npm pack --dry-run --json >/dev/null",
|
|
29
|
+
"prepare": "husky"
|
|
30
|
+
},
|
|
31
|
+
"lint-staged": {
|
|
32
|
+
"*.{json,jsonc,ts}": "bun run lint:fix --"
|
|
25
33
|
},
|
|
26
34
|
"type": "module",
|
|
27
|
-
"version": "0.2.
|
|
35
|
+
"version": "0.2.2",
|
|
28
36
|
"license": "MIT",
|
|
29
37
|
"repository": {
|
|
30
38
|
"type": "git",
|
package/src/commands.ts
CHANGED
|
@@ -5,25 +5,12 @@ import {
|
|
|
5
5
|
} from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { Box, Markdown, Text } from "@earendil-works/pi-tui";
|
|
7
7
|
import {
|
|
8
|
-
advisorAutoLoopGateRef,
|
|
9
|
-
advisorBlockOnBlockedRef,
|
|
10
|
-
advisorCollapseResponsesRef,
|
|
11
|
-
advisorCompletionGateRef,
|
|
12
|
-
advisorCustomInvocationRef,
|
|
13
8
|
advisorEffortRef,
|
|
14
|
-
advisorFailureGateRef,
|
|
15
|
-
advisorFailureModeRef,
|
|
16
|
-
advisorHerdrIntegrationRef,
|
|
17
|
-
advisorLoopThresholdRef,
|
|
18
9
|
advisorMaxCallsPerSessionRef,
|
|
19
|
-
advisorPlanGateRef,
|
|
20
10
|
advisorRef,
|
|
21
|
-
advisorSessionSummaryRef,
|
|
22
|
-
advisorToolResultMaxBytesRef,
|
|
23
|
-
advisorToolResultMaxLinesRef,
|
|
24
|
-
contextMaxCharsRef,
|
|
25
11
|
executorEffortRef,
|
|
26
12
|
executorRef,
|
|
13
|
+
getAdvisorSettings,
|
|
27
14
|
loadConfig,
|
|
28
15
|
parseArgs,
|
|
29
16
|
saveConfig,
|
|
@@ -114,6 +101,22 @@ type ManualConsult = (
|
|
|
114
101
|
question?: string,
|
|
115
102
|
signal?: AbortSignal
|
|
116
103
|
) => Promise<{ markdown: string; thinkingText: string }>;
|
|
104
|
+
type ThinkingLevel = Parameters<ExtensionAPI["setThinkingLevel"]>[0];
|
|
105
|
+
|
|
106
|
+
const notify = (
|
|
107
|
+
ctx: ExtensionContext,
|
|
108
|
+
message: string,
|
|
109
|
+
level: "error" | "info" | "warning"
|
|
110
|
+
) => {
|
|
111
|
+
if (ctx.hasUI) {
|
|
112
|
+
ctx.ui.notify(message, level);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const findConfiguredModel = (ctx: ExtensionContext, ref: string) => {
|
|
117
|
+
const [provider, modelId] = splitRef(ref);
|
|
118
|
+
return ctx.modelRegistry.find(provider, modelId);
|
|
119
|
+
};
|
|
117
120
|
|
|
118
121
|
export const registerCommands = (
|
|
119
122
|
pi: ExtensionAPI,
|
|
@@ -126,6 +129,104 @@ export const registerCommands = (
|
|
|
126
129
|
consultAdvisor(ctx, question, signal, undefined, "manual"));
|
|
127
130
|
const manualConsultations = new Set<AbortController>();
|
|
128
131
|
|
|
132
|
+
const startManualConsultation = (
|
|
133
|
+
ctx: ExtensionContext,
|
|
134
|
+
question: string | undefined,
|
|
135
|
+
controller: AbortController
|
|
136
|
+
) => {
|
|
137
|
+
herdrAdvisorActivity.start();
|
|
138
|
+
return requestAdvisor(ctx, question, controller.signal)
|
|
139
|
+
.then(({ markdown }) => {
|
|
140
|
+
advisorSessionState.recordInvocation({
|
|
141
|
+
executionEffect: "continued",
|
|
142
|
+
kind: "markdown",
|
|
143
|
+
model: advisorRef,
|
|
144
|
+
trigger: "manual",
|
|
145
|
+
});
|
|
146
|
+
if (controller.signal.aborted) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
pi.sendMessage(
|
|
150
|
+
{
|
|
151
|
+
content: `Manual Advisor consultation${question ? ` (${question})` : ""}:\n\n${markdown}`,
|
|
152
|
+
customType: "advisor-manual-result",
|
|
153
|
+
details: { advisor: advisorRef, question, text: markdown },
|
|
154
|
+
display: true,
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
// Steer lets the current turn finish its active work; the Executor sees
|
|
158
|
+
// the result before its next model call rather than being interrupted.
|
|
159
|
+
deliverAs: "steer",
|
|
160
|
+
triggerTurn: true,
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
})
|
|
164
|
+
.catch((error: unknown) => {
|
|
165
|
+
if (controller.signal.aborted) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
169
|
+
advisorSessionState.recordInvocation({
|
|
170
|
+
executionEffect: "continued",
|
|
171
|
+
failure: "provider-error",
|
|
172
|
+
kind: "markdown",
|
|
173
|
+
model: advisorRef,
|
|
174
|
+
trigger: "manual",
|
|
175
|
+
});
|
|
176
|
+
pi.sendMessage(
|
|
177
|
+
{
|
|
178
|
+
content: `Manual Advisor consultation failed: ${message}`,
|
|
179
|
+
customType: "advisor-manual-result",
|
|
180
|
+
details: {
|
|
181
|
+
advisor: advisorRef,
|
|
182
|
+
text: `**Advisor consultation failed:** ${message}`,
|
|
183
|
+
},
|
|
184
|
+
display: true,
|
|
185
|
+
},
|
|
186
|
+
{ deliverAs: "steer", triggerTurn: true }
|
|
187
|
+
);
|
|
188
|
+
notify(ctx, `Advisor consultation failed: ${message}`, "error");
|
|
189
|
+
notifyHerdrAdvisorFailure("Advisor consultation failed", message);
|
|
190
|
+
})
|
|
191
|
+
.finally(() => {
|
|
192
|
+
manualConsultations.delete(controller);
|
|
193
|
+
herdrAdvisorActivity.finish();
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const activateAdvisor = async (args: string, ctx: ExtensionContext) => {
|
|
198
|
+
loadConfig(ctx);
|
|
199
|
+
const argumentError = parseArgs(args);
|
|
200
|
+
if (argumentError) {
|
|
201
|
+
notify(ctx, argumentError, "error");
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const executor = findConfiguredModel(ctx, executorRef);
|
|
205
|
+
if (!executor) {
|
|
206
|
+
notify(ctx, `Executor model not found: ${executorRef}`, "error");
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (!findConfiguredModel(ctx, advisorRef)) {
|
|
210
|
+
notify(ctx, `Advisor model not found: ${advisorRef}`, "error");
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (!(await pi.setModel(executor))) {
|
|
214
|
+
notify(ctx, `No API key for Executor ${executorRef}`, "error");
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (executorEffortRef) {
|
|
218
|
+
pi.setThinkingLevel(executorEffortRef as ThinkingLevel);
|
|
219
|
+
}
|
|
220
|
+
if (!flowEnabled()) {
|
|
221
|
+
pi.setActiveTools([...pi.getActiveTools(), "ask_advisor"]);
|
|
222
|
+
}
|
|
223
|
+
notify(
|
|
224
|
+
ctx,
|
|
225
|
+
`Advisor flow ready — Executor: ${executorRef} (thinking: ${executorEffortRef || "default"}) · Advisor: ${advisorRef} (thinking: ${advisorEffortRef || "default"})`,
|
|
226
|
+
"info"
|
|
227
|
+
);
|
|
228
|
+
};
|
|
229
|
+
|
|
129
230
|
pi.registerEntryRenderer?.(
|
|
130
231
|
"advisor-manual-call",
|
|
131
232
|
(entry, _options, theme) => {
|
|
@@ -175,15 +276,13 @@ export const registerCommands = (
|
|
|
175
276
|
pi.registerCommand("advisor-manual", {
|
|
176
277
|
description:
|
|
177
278
|
"Consult the Advisor in parallel; accepts an optional focused question and fans its response out to the Executor",
|
|
178
|
-
handler:
|
|
279
|
+
handler: (args, ctx) => {
|
|
179
280
|
loadConfig(ctx);
|
|
180
281
|
if (!advisorSessionState.canConsult(advisorMaxCallsPerSessionRef)) {
|
|
181
282
|
const message = "Advisor call budget exhausted for this session.";
|
|
182
|
-
|
|
183
|
-
ctx.ui.notify(message, "warning");
|
|
184
|
-
}
|
|
283
|
+
notify(ctx, message, "warning");
|
|
185
284
|
notifyHerdrAdvisorFailure("Advisor budget exhausted", message);
|
|
186
|
-
return;
|
|
285
|
+
return Promise.resolve();
|
|
187
286
|
}
|
|
188
287
|
advisorSessionState.consumeCall();
|
|
189
288
|
const question = resolveAdvisorRequest(args);
|
|
@@ -196,109 +295,22 @@ export const registerCommands = (
|
|
|
196
295
|
const controller = new AbortController();
|
|
197
296
|
manualConsultations.add(controller);
|
|
198
297
|
pi.appendEntry?.("advisor-manual-call", { question });
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
void requestAdvisor(ctx, question, controller.signal)
|
|
202
|
-
.then(({ markdown }) => {
|
|
203
|
-
advisorSessionState.recordInvocation({
|
|
204
|
-
executionEffect: "continued",
|
|
205
|
-
kind: "markdown",
|
|
206
|
-
model: advisorRef,
|
|
207
|
-
trigger: "manual",
|
|
208
|
-
});
|
|
209
|
-
const advice = markdown;
|
|
210
|
-
if (controller.signal.aborted) {
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
pi.sendMessage(
|
|
214
|
-
{
|
|
215
|
-
content: `Manual Advisor consultation${question ? ` (${question})` : ""}:\n\n${advice}`,
|
|
216
|
-
customType: "advisor-manual-result",
|
|
217
|
-
details: { advisor: advisorRef, question, text: advice },
|
|
218
|
-
display: true,
|
|
219
|
-
},
|
|
220
|
-
{
|
|
221
|
-
// Steer lets the current turn finish its active work; the Executor sees
|
|
222
|
-
// the result before its next model call rather than being interrupted.
|
|
223
|
-
deliverAs: "steer",
|
|
224
|
-
triggerTurn: true,
|
|
225
|
-
}
|
|
226
|
-
);
|
|
227
|
-
})
|
|
228
|
-
.catch((error: unknown) => {
|
|
229
|
-
if (controller.signal.aborted) {
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
const message =
|
|
233
|
-
error instanceof Error ? error.message : String(error);
|
|
234
|
-
advisorSessionState.recordInvocation({
|
|
235
|
-
executionEffect: "continued",
|
|
236
|
-
failure: "provider-error",
|
|
237
|
-
kind: "markdown",
|
|
238
|
-
model: advisorRef,
|
|
239
|
-
trigger: "manual",
|
|
240
|
-
});
|
|
241
|
-
if (ctx.hasUI) {
|
|
242
|
-
ctx.ui.notify(`Advisor consultation failed: ${message}`, "error");
|
|
243
|
-
}
|
|
244
|
-
notifyHerdrAdvisorFailure("Advisor consultation failed", message);
|
|
245
|
-
})
|
|
246
|
-
.finally(() => {
|
|
247
|
-
manualConsultations.delete(controller);
|
|
248
|
-
herdrAdvisorActivity.finish();
|
|
249
|
-
});
|
|
298
|
+
startManualConsultation(ctx, question, controller);
|
|
299
|
+
return Promise.resolve();
|
|
250
300
|
},
|
|
251
301
|
});
|
|
252
302
|
|
|
253
303
|
pi.registerCommand("advisor", {
|
|
254
304
|
description:
|
|
255
305
|
"Enable the Executor/Advisor flow and switch to the configured Executor model; accepts contextMaxChars=N",
|
|
256
|
-
handler:
|
|
257
|
-
loadConfig(ctx);
|
|
258
|
-
const argumentError = parseArgs(args);
|
|
259
|
-
if (argumentError) {
|
|
260
|
-
if (ctx.hasUI) {
|
|
261
|
-
ctx.ui.notify(argumentError, "error");
|
|
262
|
-
}
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
const [provider, modelId] = splitRef(executorRef);
|
|
266
|
-
const executor = ctx.modelRegistry.find(provider, modelId);
|
|
267
|
-
if (!executor) {
|
|
268
|
-
return ctx.hasUI
|
|
269
|
-
? ctx.ui.notify(`Executor model not found: ${executorRef}`, "error")
|
|
270
|
-
: undefined;
|
|
271
|
-
}
|
|
272
|
-
const [ap, am] = splitRef(advisorRef);
|
|
273
|
-
if (!ctx.modelRegistry.find(ap, am)) {
|
|
274
|
-
return ctx.hasUI
|
|
275
|
-
? ctx.ui.notify(`Advisor model not found: ${advisorRef}`, "error")
|
|
276
|
-
: undefined;
|
|
277
|
-
}
|
|
278
|
-
if (!(await pi.setModel(executor))) {
|
|
279
|
-
return ctx.hasUI
|
|
280
|
-
? ctx.ui.notify(`No API key for Executor ${executorRef}`, "error")
|
|
281
|
-
: undefined;
|
|
282
|
-
}
|
|
283
|
-
if (executorEffortRef) {
|
|
284
|
-
pi.setThinkingLevel(executorEffortRef as any);
|
|
285
|
-
}
|
|
286
|
-
if (!flowEnabled()) {
|
|
287
|
-
pi.setActiveTools([...pi.getActiveTools(), "ask_advisor"]);
|
|
288
|
-
}
|
|
289
|
-
if (ctx.hasUI) {
|
|
290
|
-
ctx.ui.notify(
|
|
291
|
-
`Advisor flow ready — Executor: ${executorRef} (thinking: ${executorEffortRef || "default"}) · Advisor: ${advisorRef} (thinking: ${advisorEffortRef || "default"})`,
|
|
292
|
-
"info"
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
},
|
|
306
|
+
handler: (args, ctx) => activateAdvisor(args, ctx),
|
|
296
307
|
});
|
|
297
308
|
|
|
298
309
|
pi.registerCommand("advisor-models", {
|
|
299
310
|
description:
|
|
300
311
|
"Select and persist the Executor and Advisor models with reasoning levels",
|
|
301
312
|
handler: async (_args, ctx) => {
|
|
313
|
+
loadConfig(ctx);
|
|
302
314
|
if (!ctx.hasUI) {
|
|
303
315
|
return;
|
|
304
316
|
}
|
|
@@ -381,24 +393,7 @@ export const registerCommands = (
|
|
|
381
393
|
return;
|
|
382
394
|
}
|
|
383
395
|
|
|
384
|
-
const initial: AdvisorSettings =
|
|
385
|
-
autoLoopGate: advisorAutoLoopGateRef,
|
|
386
|
-
blockOnBlocked: advisorBlockOnBlockedRef,
|
|
387
|
-
collapseResponses: advisorCollapseResponsesRef,
|
|
388
|
-
completionGate: advisorCompletionGateRef,
|
|
389
|
-
contextMaxChars: contextMaxCharsRef,
|
|
390
|
-
customRule: advisorCustomInvocationRef,
|
|
391
|
-
effort: advisorEffortRef,
|
|
392
|
-
failureGate: advisorFailureGateRef,
|
|
393
|
-
failureMode: advisorFailureModeRef,
|
|
394
|
-
herdrIntegration: advisorHerdrIntegrationRef,
|
|
395
|
-
loopThreshold: advisorLoopThresholdRef,
|
|
396
|
-
maxCallsPerSession: advisorMaxCallsPerSessionRef,
|
|
397
|
-
planGate: advisorPlanGateRef,
|
|
398
|
-
sessionSummary: advisorSessionSummaryRef,
|
|
399
|
-
toolResultMaxBytes: advisorToolResultMaxBytesRef,
|
|
400
|
-
toolResultMaxLines: advisorToolResultMaxLinesRef,
|
|
401
|
-
};
|
|
396
|
+
const initial: AdvisorSettings = getAdvisorSettings();
|
|
402
397
|
const settings = await ctx.ui.custom<AdvisorSettings | undefined>(
|
|
403
398
|
(tui, theme, _keybindings, done) =>
|
|
404
399
|
new AdvisorSettingsSelector({
|
|
@@ -442,7 +437,7 @@ export const registerCommands = (
|
|
|
442
437
|
|
|
443
438
|
pi.registerCommand("advisor-off", {
|
|
444
439
|
description: "Disable on-demand Advisor calls; keep the current model",
|
|
445
|
-
handler:
|
|
440
|
+
handler: (_args, ctx) => {
|
|
446
441
|
pi.setActiveTools(
|
|
447
442
|
pi.getActiveTools().filter((name) => name !== "ask_advisor")
|
|
448
443
|
);
|
|
@@ -452,6 +447,7 @@ export const registerCommands = (
|
|
|
452
447
|
"info"
|
|
453
448
|
);
|
|
454
449
|
}
|
|
450
|
+
return Promise.resolve();
|
|
455
451
|
},
|
|
456
452
|
});
|
|
457
453
|
};
|