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.
@@ -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.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: async (args, ctx) => {
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
- if (ctx.hasUI) {
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
- 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
- });
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: 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
- },
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: async (_args, ctx) => {
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
  };