pi-advisor-flow 0.1.6 → 0.1.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-advisor-flow",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
package/src/commands.ts CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  contextMaxCharsRef, loadConfig, saveConfig, parseArgs, splitRef,
9
9
  } from "./config.js";
10
10
  import { AdvisorSettingsSelector, SearchableModelSelector, type AdvisorSettings, type ContextPreset } from "./ui.js";
11
+ import { herdrAdvisorActivity } from "./herdr.js";
11
12
  import { adviceForDisplay, consult, renderAdvisorCallBox, resolveAdvisorRequest } from "./tools.js";
12
13
 
13
14
  const EFFORT_LEVELS = ["Default (Model Default)", "off", "minimal", "low", "medium", "high", "xhigh", "max"];
@@ -44,6 +45,7 @@ export const registerCommands = (pi: ExtensionAPI, dependencies: { consult?: typ
44
45
  pi.on("session_shutdown", () => {
45
46
  for (const controller of manualConsultations) controller.abort();
46
47
  manualConsultations.clear();
48
+ herdrAdvisorActivity.clear();
47
49
  });
48
50
 
49
51
  pi.registerCommand("advisor-manual", {
@@ -58,6 +60,7 @@ export const registerCommands = (pi: ExtensionAPI, dependencies: { consult?: typ
58
60
  manualConsultations.add(controller);
59
61
  pi.appendEntry?.("advisor-manual-call", { question });
60
62
 
63
+ herdrAdvisorActivity.start();
61
64
  void requestAdvisor(ctx, question, controller.signal)
62
65
  .then(({ advice }) => {
63
66
  if (controller.signal.aborted) return;
@@ -78,7 +81,10 @@ export const registerCommands = (pi: ExtensionAPI, dependencies: { consult?: typ
78
81
  const message = error instanceof Error ? error.message : String(error);
79
82
  if (ctx.hasUI) ctx.ui.notify(`Advisor consultation failed: ${message}`, "error");
80
83
  })
81
- .finally(() => manualConsultations.delete(controller));
84
+ .finally(() => {
85
+ manualConsultations.delete(controller);
86
+ herdrAdvisorActivity.finish();
87
+ });
82
88
  },
83
89
  });
84
90
 
package/src/herdr.ts ADDED
@@ -0,0 +1,87 @@
1
+ import net from "node:net";
2
+
3
+ const SOURCE = "pi-advisor:advisor-activity";
4
+ const HERDR_PI_SOURCE = "herdr:pi";
5
+ let sequence = Date.now() * 1_000;
6
+
7
+ const nextSequence = () => ++sequence;
8
+
9
+ type HerdrRequest = {
10
+ id: string;
11
+ method: "pane.report_metadata";
12
+ params: {
13
+ pane_id: string;
14
+ source: string;
15
+ agent: "pi";
16
+ applies_to_source: string;
17
+ state_labels?: { working: string };
18
+ clear_state_labels?: true;
19
+ seq: number;
20
+ };
21
+ };
22
+
23
+ type Report = (request: HerdrRequest) => void;
24
+
25
+ const sendToHerdr: Report = (request) => {
26
+ if (process.env.HERDR_ENV !== "1") return;
27
+ const paneId = process.env.HERDR_PANE_ID;
28
+ const socketPath = process.env.HERDR_SOCKET_PATH;
29
+ if (!paneId || !socketPath) return;
30
+
31
+ const endpoint = process.platform === "win32" ? `\\\\.\\pipe\\${socketPath}` : socketPath;
32
+ const socket = net.createConnection(endpoint);
33
+ const timeout = setTimeout(() => socket.destroy(), 500);
34
+ timeout.unref?.();
35
+ socket.once("connect", () => socket.write(`${JSON.stringify(request)}\n`));
36
+ socket.once("data", () => socket.destroy());
37
+ socket.once("error", () => socket.destroy());
38
+ socket.once("close", () => clearTimeout(timeout));
39
+ };
40
+
41
+ export class HerdrAdvisorActivity {
42
+ #activeConsultations = 0;
43
+
44
+ constructor(private readonly report: Report = sendToHerdr) {}
45
+
46
+ start() {
47
+ this.#activeConsultations += 1;
48
+ if (this.#activeConsultations === 1) this.safeReport(false);
49
+ }
50
+
51
+ finish() {
52
+ if (this.#activeConsultations === 0) return;
53
+ this.#activeConsultations -= 1;
54
+ if (this.#activeConsultations === 0) this.safeReport(true);
55
+ }
56
+
57
+ clear() {
58
+ if (this.#activeConsultations === 0) return;
59
+ this.#activeConsultations = 0;
60
+ this.safeReport(true);
61
+ }
62
+
63
+ private safeReport(clear: boolean) {
64
+ try {
65
+ this.report(this.request(clear));
66
+ } catch {
67
+ // Herdr is optional; an unavailable integration must never affect advice.
68
+ }
69
+ }
70
+
71
+ private request(clear: boolean): HerdrRequest {
72
+ return {
73
+ id: `${SOURCE}:${nextSequence()}`,
74
+ method: "pane.report_metadata",
75
+ params: {
76
+ pane_id: process.env.HERDR_PANE_ID ?? "",
77
+ source: SOURCE,
78
+ agent: "pi",
79
+ applies_to_source: HERDR_PI_SOURCE,
80
+ ...(clear ? { clear_state_labels: true } : { state_labels: { working: "seeking advice" } }),
81
+ seq: nextSequence(),
82
+ },
83
+ };
84
+ }
85
+ }
86
+
87
+ export const herdrAdvisorActivity = new HerdrAdvisorActivity();
package/src/tools.ts CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  advisorRef, advisorEffortRef, contextMaxCharsRef, loadConfig, splitRef,
8
8
  } from "./config.js";
9
9
  import { recentConversation, textFrom } from "./conversation.js";
10
+ import { herdrAdvisorActivity } from "./herdr.js";
10
11
 
11
12
  export const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
12
13
  export const resolveAdvisorRequest = (question?: string) => question?.trim() || undefined;
@@ -159,16 +160,21 @@ export const registerAdvisorTool = (pi: ExtensionAPI) => {
159
160
  },
160
161
  async execute(_id, params, signal, onUpdate, ctx) {
161
162
  const question = resolveAdvisorRequest(params.question);
162
- const { advice, thinkingText } = await consult(ctx, question, signal, (t, tx) => {
163
- onUpdate?.({
164
- content: [{ type: "text", text: tx }],
165
- details: { thinking: t, text: tx, advisor: advisorRef, question },
163
+ herdrAdvisorActivity.start();
164
+ try {
165
+ const { advice, thinkingText } = await consult(ctx, question, signal, (t, tx) => {
166
+ onUpdate?.({
167
+ content: [{ type: "text", text: tx }],
168
+ details: { thinking: t, text: tx, advisor: advisorRef, question },
169
+ });
166
170
  });
167
- });
168
- return {
169
- content: [{ type: "text", text: `Advisor (${advisorRef})\n\n${advice}` }],
170
- details: { thinking: thinkingText, text: advice, advisor: advisorRef, question },
171
- };
171
+ return {
172
+ content: [{ type: "text", text: `Advisor (${advisorRef})\n\n${advice}` }],
173
+ details: { thinking: thinkingText, text: advice, advisor: advisorRef, question },
174
+ };
175
+ } finally {
176
+ herdrAdvisorActivity.finish();
177
+ }
172
178
  },
173
179
  });
174
180
  };