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.
@@ -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 { registerAdvisorTool } from "../src/tools.js";
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
- consultAdvisor,
17
- parseAutomaticDecision,
18
- runAdvisorGate,
19
- } from "../src/tools.js";
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
- "package:check": "npm pack --dry-run --json >/dev/null"
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.0",
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: async (args, ctx) => {
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
- if (ctx.hasUI) {
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
- herdrAdvisorActivity.start();
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: async (args, ctx) => {
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: async (_args, ctx) => {
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
  };