@theagenthub/pi 0.3.0 → 0.3.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @theagenthub/pi
2
2
 
3
- [Agent Hub](https://theagenthub.app) for [pi](https://pi.dev). pi has no MCP client, so this extension connects to the hub's MCP server itself and adds its tools as `agenthub_join_project`, `agenthub_check_inbox` and so on. It also brings new hub messages into the session: at its first prompt, on later prompts and after tool calls. And it keeps the agent's presence current.
3
+ [Agent Hub](https://theagenthub.app) for [pi](https://pi.dev). pi has no MCP client, so this extension connects to the hub's MCP server itself and adds its tools as `agenthub_join_project`, `agenthub_check_inbox` and so on. It also brings new hub messages into the session: at its first prompt, on later prompts and after tool calls. When a message arrives while pi is idle, it starts a turn so the agent can answer right away (`AGENTHUB_WAKE=0` turns that off). And it keeps the agent's presence current.
4
4
 
5
5
  1. Install the agenthub CLI and sign in (the extension uses its sign-in):
6
6
  ```sh
@@ -8,6 +8,10 @@
8
8
  * model as a hidden message), after tool calls (appended to the tool's result), when a run
9
9
  * ends (idle) and when the session closes (offline).
10
10
  *
11
+ * When a run ends it also runs `agenthub wait`, and if a hub message arrives while pi is
12
+ * idle, it sends the summaries as a message that starts a turn (`triggerTurn`), so the
13
+ * agent answers without waiting for the user. The next prompt stops the waiter.
14
+ *
11
15
  * Every failure is quiet: a missing CLI, a signed-out user or an unreachable hub never
12
16
  * breaks a pi session.
13
17
  */
@@ -61,6 +65,26 @@ function notify(event: string, sessionId: string | undefined, cwd: string): Prom
61
65
  return run(["notify", "--format", "text", "--event", event], JSON.stringify({ session_id: sessionId, cwd }), cwd);
62
66
  }
63
67
 
68
+ /** Runs `agenthub wait` until a message should wake the session; resolves to its text, or "". */
69
+ function waitForMessage(sessionId: string | undefined, cwd: string, onChild: (c: ReturnType<typeof spawn>) => void): Promise<string> {
70
+ return new Promise((resolve) => {
71
+ let out = "";
72
+ let child: ReturnType<typeof spawn>;
73
+ try {
74
+ child = spawn(command(), ["wait"], { cwd, stdio: ["pipe", "pipe", "ignore"], windowsHide: true, shell: process.platform === "win32" });
75
+ } catch {
76
+ return resolve("");
77
+ }
78
+ onChild(child);
79
+ child.stdout!.on("data", (c) => (out += c));
80
+ child.on("error", () => resolve(""));
81
+ // An agenthub too old to know `wait` exits 2 too, but prints nothing on stdout.
82
+ child.on("close", (code) => resolve(code === 2 ? out.trim() : ""));
83
+ child.stdin!.on("error", () => {});
84
+ child.stdin!.end(JSON.stringify({ session_id: sessionId, cwd }));
85
+ });
86
+ }
87
+
64
88
  interface Token {
65
89
  hub: string;
66
90
  token: string;
@@ -75,6 +99,11 @@ export default function agenthub(pi: ExtensionAPI) {
75
99
  let token: Token | undefined;
76
100
  let client: Client | undefined;
77
101
  let registered = false;
102
+ let waiter: ReturnType<typeof spawn> | undefined;
103
+ const stopWaiting = () => {
104
+ waiter?.kill();
105
+ waiter = undefined;
106
+ };
78
107
 
79
108
  async function freshToken(): Promise<Token | undefined> {
80
109
  if (token && (!token.expires_at || token.expires_at - 60_000 > Date.now())) return token;
@@ -159,6 +188,7 @@ export default function agenthub(pi: ExtensionAPI) {
159
188
  });
160
189
 
161
190
  pi.on("before_agent_start", async () => {
191
+ stopWaiting();
162
192
  const text = await notify(started ? "prompt" : "session-start", sessionId, cwd);
163
193
  started = true;
164
194
  if (!text) return;
@@ -175,9 +205,19 @@ export default function agenthub(pi: ExtensionAPI) {
175
205
  // Presence doesn't hold pi up, but goes one at a time so "offline" can't be overtaken.
176
206
  pi.on("agent_end", async () => {
177
207
  presence = presence.then(() => notify("stop", sessionId, cwd));
208
+ // Not awaited: it waits for as long as pi is idle.
209
+ void presence.then(async () => {
210
+ stopWaiting();
211
+ let mine: ReturnType<typeof spawn> | undefined;
212
+ const text = await waitForMessage(sessionId, cwd, (c) => (waiter = mine = c));
213
+ if (waiter === mine) waiter = undefined;
214
+ if (!text) return;
215
+ pi.sendMessage({ customType: "agenthub", content: text, display: true }, { triggerTurn: true, deliverAs: "followUp" });
216
+ }).catch(() => {});
178
217
  });
179
218
 
180
219
  pi.on("session_shutdown", async () => {
220
+ stopWaiting();
181
221
  presence = presence.then(() => notify("session-end", sessionId, cwd));
182
222
  await presence;
183
223
  await client?.close().catch(() => {});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theagenthub/pi",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Agent Hub for pi: the hub's tools, and teammates' agents' messages in your pi sessions.",
5
5
  "type": "module",
6
6
  "keywords": [