@pi-archimedes/subagent 1.2.1 → 1.3.0
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 +2 -2
- package/src/spawn.ts +77 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-archimedes/subagent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package"
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
],
|
|
12
12
|
"main": "./src/index.ts",
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@pi-archimedes/core": "1.
|
|
14
|
+
"@pi-archimedes/core": "1.3.0"
|
|
15
15
|
},
|
|
16
16
|
"peerDependencies": {
|
|
17
17
|
"@earendil-works/pi-coding-agent": ">=0.1.0",
|
package/src/spawn.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { spawn, type ChildProcess } from "node:child_process";
|
|
2
2
|
import { execSync } from "node:child_process";
|
|
3
3
|
import { existsSync, writeFileSync, unlinkSync, mkdtempSync, rmdirSync, rmSync } from "node:fs";
|
|
4
|
+
import { createServer, type Socket } from "node:net";
|
|
4
5
|
import { tmpdir } from "node:os";
|
|
5
6
|
import { join } from "node:path";
|
|
7
|
+
import { getBus, Events } from "@pi-archimedes/core/bus";
|
|
6
8
|
import type { AgentConfig } from "./agents.js";
|
|
7
9
|
|
|
8
10
|
// Track all temp dirs for cleanup on process exit (graceful shutdown only).
|
|
@@ -87,6 +89,72 @@ export function spawnSubagent(options: SpawnOptions): ChildProcess {
|
|
|
87
89
|
"--no-session",
|
|
88
90
|
];
|
|
89
91
|
|
|
92
|
+
// Create IPC socket for ask tool responses.
|
|
93
|
+
// On Unix: filesystem Unix domain socket. On Windows: named pipe (\\.\pipe\).
|
|
94
|
+
const socketName = `pi-subagent-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
95
|
+
const socketPath = process.platform === "win32"
|
|
96
|
+
? `\\\\.\\pipe\\${socketName}`
|
|
97
|
+
: join(tmpdir(), socketName);
|
|
98
|
+
// Only Unix socket files need pre-unlinking; Windows named pipes auto-clean.
|
|
99
|
+
if (process.platform !== "win32") {
|
|
100
|
+
try { unlinkSync(socketPath); } catch { /* doesn't exist yet */ }
|
|
101
|
+
}
|
|
102
|
+
let clientSocket: Socket | undefined;
|
|
103
|
+
const pendingAsks = new Map<string, Socket>();
|
|
104
|
+
const server = createServer((socket: Socket) => {
|
|
105
|
+
clientSocket = socket;
|
|
106
|
+
(child as ChildProcess & { clientSocket: Socket | undefined }).clientSocket = socket;
|
|
107
|
+
// Parse incoming lines from the child's ask tool (ask_request) and forward to bus
|
|
108
|
+
let buffer = "";
|
|
109
|
+
socket.on("data", (data: Buffer) => {
|
|
110
|
+
buffer += data.toString();
|
|
111
|
+
const lines = buffer.split("\n");
|
|
112
|
+
buffer = lines.pop() ?? "";
|
|
113
|
+
for (const line of lines) {
|
|
114
|
+
try {
|
|
115
|
+
const msg = JSON.parse(line);
|
|
116
|
+
if (msg.type === "ask_request" && msg.requestId) {
|
|
117
|
+
pendingAsks.set(msg.requestId, socket);
|
|
118
|
+
getBus().emit(Events.ASK_REQUEST, {
|
|
119
|
+
source: `subagent:${options.agent?.name ?? "general"}`,
|
|
120
|
+
requestId: msg.requestId,
|
|
121
|
+
questions: msg.questions,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
} catch { /* ignore non-JSON */ }
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
socket.on("close", () => {
|
|
128
|
+
clientSocket = undefined;
|
|
129
|
+
(child as ChildProcess & { clientSocket: Socket | undefined }).clientSocket = undefined;
|
|
130
|
+
// Clean up any pending asks tied to this socket
|
|
131
|
+
for (const [id, s] of pendingAsks) {
|
|
132
|
+
if (s === socket) pendingAsks.delete(id);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
server.listen(socketPath); // async, non-blocking
|
|
137
|
+
|
|
138
|
+
// Write ask responses back to the child over the originating socket
|
|
139
|
+
const unsubAskResponse = getBus().on(Events.ASK_RESPONSE, (payload: unknown) => {
|
|
140
|
+
const data = payload as { requestId: string; cancelled: boolean; results: Array<{ id: string; selectedOptions: string[]; customInput?: string }> };
|
|
141
|
+
const socket = pendingAsks.get(data.requestId);
|
|
142
|
+
if (socket && socket.writable) {
|
|
143
|
+
pendingAsks.delete(data.requestId);
|
|
144
|
+
socket.write(JSON.stringify({
|
|
145
|
+
type: "ask_response",
|
|
146
|
+
requestId: data.requestId,
|
|
147
|
+
cancelled: data.cancelled,
|
|
148
|
+
results: data.results,
|
|
149
|
+
}) + "\n");
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
server.on("close", () => {
|
|
153
|
+
if (process.platform !== "win32") {
|
|
154
|
+
try { unlinkSync(socketPath); } catch { /* ignore */ }
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
90
158
|
// Resolve model with correct priority:
|
|
91
159
|
// 1. frontmatter model (agent.model)
|
|
92
160
|
// 2. explicit tool-call model (options.model)
|
|
@@ -124,7 +192,10 @@ export function spawnSubagent(options: SpawnOptions): ChildProcess {
|
|
|
124
192
|
const child = spawn(command, args, {
|
|
125
193
|
cwd: options.cwd || process.cwd(),
|
|
126
194
|
stdio: ["ignore", "pipe", "pipe"],
|
|
127
|
-
env:
|
|
195
|
+
env: {
|
|
196
|
+
...process.env,
|
|
197
|
+
PI_SUBAGENT_SOCKET: socketPath,
|
|
198
|
+
},
|
|
128
199
|
});
|
|
129
200
|
|
|
130
201
|
// Handle abort signal
|
|
@@ -151,10 +222,15 @@ export function spawnSubagent(options: SpawnOptions): ChildProcess {
|
|
|
151
222
|
if (abortHandler && options.signal) {
|
|
152
223
|
options.signal.removeEventListener("abort", abortHandler);
|
|
153
224
|
}
|
|
225
|
+
server.close();
|
|
226
|
+
unsubAskResponse();
|
|
227
|
+
if (clientSocket) clientSocket.destroy();
|
|
154
228
|
cleanupTempFiles(tmpDir, tmpPath);
|
|
155
229
|
};
|
|
156
230
|
child.on("exit", exitCleanup);
|
|
157
231
|
child.on("error", exitCleanup);
|
|
158
232
|
|
|
233
|
+
// Attach client socket reference to child for streamEvents
|
|
234
|
+
(child as ChildProcess & { clientSocket: Socket | undefined }).clientSocket = clientSocket;
|
|
159
235
|
return child;
|
|
160
236
|
}
|