pi-advisor-flow 0.2.0 → 0.2.1
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 +123 -97
- package/src/config.ts +233 -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.1",
|
|
28
36
|
"license": "MIT",
|
|
29
37
|
"repository": {
|
|
30
38
|
"type": "git",
|
package/src/commands.ts
CHANGED
|
@@ -114,6 +114,22 @@ type ManualConsult = (
|
|
|
114
114
|
question?: string,
|
|
115
115
|
signal?: AbortSignal
|
|
116
116
|
) => Promise<{ markdown: string; thinkingText: string }>;
|
|
117
|
+
type ThinkingLevel = Parameters<ExtensionAPI["setThinkingLevel"]>[0];
|
|
118
|
+
|
|
119
|
+
const notify = (
|
|
120
|
+
ctx: ExtensionContext,
|
|
121
|
+
message: string,
|
|
122
|
+
level: "error" | "info" | "warning"
|
|
123
|
+
) => {
|
|
124
|
+
if (ctx.hasUI) {
|
|
125
|
+
ctx.ui.notify(message, level);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const findConfiguredModel = (ctx: ExtensionContext, ref: string) => {
|
|
130
|
+
const [provider, modelId] = splitRef(ref);
|
|
131
|
+
return ctx.modelRegistry.find(provider, modelId);
|
|
132
|
+
};
|
|
117
133
|
|
|
118
134
|
export const registerCommands = (
|
|
119
135
|
pi: ExtensionAPI,
|
|
@@ -126,6 +142,104 @@ export const registerCommands = (
|
|
|
126
142
|
consultAdvisor(ctx, question, signal, undefined, "manual"));
|
|
127
143
|
const manualConsultations = new Set<AbortController>();
|
|
128
144
|
|
|
145
|
+
const startManualConsultation = (
|
|
146
|
+
ctx: ExtensionContext,
|
|
147
|
+
question: string | undefined,
|
|
148
|
+
controller: AbortController
|
|
149
|
+
) => {
|
|
150
|
+
herdrAdvisorActivity.start();
|
|
151
|
+
return requestAdvisor(ctx, question, controller.signal)
|
|
152
|
+
.then(({ markdown }) => {
|
|
153
|
+
advisorSessionState.recordInvocation({
|
|
154
|
+
executionEffect: "continued",
|
|
155
|
+
kind: "markdown",
|
|
156
|
+
model: advisorRef,
|
|
157
|
+
trigger: "manual",
|
|
158
|
+
});
|
|
159
|
+
if (controller.signal.aborted) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
pi.sendMessage(
|
|
163
|
+
{
|
|
164
|
+
content: `Manual Advisor consultation${question ? ` (${question})` : ""}:\n\n${markdown}`,
|
|
165
|
+
customType: "advisor-manual-result",
|
|
166
|
+
details: { advisor: advisorRef, question, text: markdown },
|
|
167
|
+
display: true,
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
// Steer lets the current turn finish its active work; the Executor sees
|
|
171
|
+
// the result before its next model call rather than being interrupted.
|
|
172
|
+
deliverAs: "steer",
|
|
173
|
+
triggerTurn: true,
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
})
|
|
177
|
+
.catch((error: unknown) => {
|
|
178
|
+
if (controller.signal.aborted) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
182
|
+
advisorSessionState.recordInvocation({
|
|
183
|
+
executionEffect: "continued",
|
|
184
|
+
failure: "provider-error",
|
|
185
|
+
kind: "markdown",
|
|
186
|
+
model: advisorRef,
|
|
187
|
+
trigger: "manual",
|
|
188
|
+
});
|
|
189
|
+
pi.sendMessage(
|
|
190
|
+
{
|
|
191
|
+
content: `Manual Advisor consultation failed: ${message}`,
|
|
192
|
+
customType: "advisor-manual-result",
|
|
193
|
+
details: {
|
|
194
|
+
advisor: advisorRef,
|
|
195
|
+
text: `**Advisor consultation failed:** ${message}`,
|
|
196
|
+
},
|
|
197
|
+
display: true,
|
|
198
|
+
},
|
|
199
|
+
{ deliverAs: "steer", triggerTurn: true }
|
|
200
|
+
);
|
|
201
|
+
notify(ctx, `Advisor consultation failed: ${message}`, "error");
|
|
202
|
+
notifyHerdrAdvisorFailure("Advisor consultation failed", message);
|
|
203
|
+
})
|
|
204
|
+
.finally(() => {
|
|
205
|
+
manualConsultations.delete(controller);
|
|
206
|
+
herdrAdvisorActivity.finish();
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const activateAdvisor = async (args: string, ctx: ExtensionContext) => {
|
|
211
|
+
loadConfig(ctx);
|
|
212
|
+
const argumentError = parseArgs(args);
|
|
213
|
+
if (argumentError) {
|
|
214
|
+
notify(ctx, argumentError, "error");
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const executor = findConfiguredModel(ctx, executorRef);
|
|
218
|
+
if (!executor) {
|
|
219
|
+
notify(ctx, `Executor model not found: ${executorRef}`, "error");
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (!findConfiguredModel(ctx, advisorRef)) {
|
|
223
|
+
notify(ctx, `Advisor model not found: ${advisorRef}`, "error");
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (!(await pi.setModel(executor))) {
|
|
227
|
+
notify(ctx, `No API key for Executor ${executorRef}`, "error");
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (executorEffortRef) {
|
|
231
|
+
pi.setThinkingLevel(executorEffortRef as ThinkingLevel);
|
|
232
|
+
}
|
|
233
|
+
if (!flowEnabled()) {
|
|
234
|
+
pi.setActiveTools([...pi.getActiveTools(), "ask_advisor"]);
|
|
235
|
+
}
|
|
236
|
+
notify(
|
|
237
|
+
ctx,
|
|
238
|
+
`Advisor flow ready — Executor: ${executorRef} (thinking: ${executorEffortRef || "default"}) · Advisor: ${advisorRef} (thinking: ${advisorEffortRef || "default"})`,
|
|
239
|
+
"info"
|
|
240
|
+
);
|
|
241
|
+
};
|
|
242
|
+
|
|
129
243
|
pi.registerEntryRenderer?.(
|
|
130
244
|
"advisor-manual-call",
|
|
131
245
|
(entry, _options, theme) => {
|
|
@@ -175,15 +289,13 @@ export const registerCommands = (
|
|
|
175
289
|
pi.registerCommand("advisor-manual", {
|
|
176
290
|
description:
|
|
177
291
|
"Consult the Advisor in parallel; accepts an optional focused question and fans its response out to the Executor",
|
|
178
|
-
handler:
|
|
292
|
+
handler: (args, ctx) => {
|
|
179
293
|
loadConfig(ctx);
|
|
180
294
|
if (!advisorSessionState.canConsult(advisorMaxCallsPerSessionRef)) {
|
|
181
295
|
const message = "Advisor call budget exhausted for this session.";
|
|
182
|
-
|
|
183
|
-
ctx.ui.notify(message, "warning");
|
|
184
|
-
}
|
|
296
|
+
notify(ctx, message, "warning");
|
|
185
297
|
notifyHerdrAdvisorFailure("Advisor budget exhausted", message);
|
|
186
|
-
return;
|
|
298
|
+
return Promise.resolve();
|
|
187
299
|
}
|
|
188
300
|
advisorSessionState.consumeCall();
|
|
189
301
|
const question = resolveAdvisorRequest(args);
|
|
@@ -196,109 +308,22 @@ export const registerCommands = (
|
|
|
196
308
|
const controller = new AbortController();
|
|
197
309
|
manualConsultations.add(controller);
|
|
198
310
|
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
|
-
});
|
|
311
|
+
startManualConsultation(ctx, question, controller);
|
|
312
|
+
return Promise.resolve();
|
|
250
313
|
},
|
|
251
314
|
});
|
|
252
315
|
|
|
253
316
|
pi.registerCommand("advisor", {
|
|
254
317
|
description:
|
|
255
318
|
"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
|
-
},
|
|
319
|
+
handler: (args, ctx) => activateAdvisor(args, ctx),
|
|
296
320
|
});
|
|
297
321
|
|
|
298
322
|
pi.registerCommand("advisor-models", {
|
|
299
323
|
description:
|
|
300
324
|
"Select and persist the Executor and Advisor models with reasoning levels",
|
|
301
325
|
handler: async (_args, ctx) => {
|
|
326
|
+
loadConfig(ctx);
|
|
302
327
|
if (!ctx.hasUI) {
|
|
303
328
|
return;
|
|
304
329
|
}
|
|
@@ -442,7 +467,7 @@ export const registerCommands = (
|
|
|
442
467
|
|
|
443
468
|
pi.registerCommand("advisor-off", {
|
|
444
469
|
description: "Disable on-demand Advisor calls; keep the current model",
|
|
445
|
-
handler:
|
|
470
|
+
handler: (_args, ctx) => {
|
|
446
471
|
pi.setActiveTools(
|
|
447
472
|
pi.getActiveTools().filter((name) => name !== "ask_advisor")
|
|
448
473
|
);
|
|
@@ -452,6 +477,7 @@ export const registerCommands = (
|
|
|
452
477
|
"info"
|
|
453
478
|
);
|
|
454
479
|
}
|
|
480
|
+
return Promise.resolve();
|
|
455
481
|
},
|
|
456
482
|
});
|
|
457
483
|
};
|