@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 +1 -1
- package/extensions/agenthub.ts +40 -0
- package/package.json +1 -1
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
|
package/extensions/agenthub.ts
CHANGED
|
@@ -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(() => {});
|