@thurstonsand/pi-agent-switchboard 0.0.0 → 0.2.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/index.js +73 -16
- package/package.json +3 -2
package/index.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// packages/pi/src/index.ts
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
|
|
1
5
|
// packages/shared/src/index.ts
|
|
2
6
|
import { execFileSync } from "node:child_process";
|
|
3
7
|
import { readFileSync } from "node:fs";
|
|
@@ -97,18 +101,49 @@ function branch(cwd) {
|
|
|
97
101
|
}
|
|
98
102
|
|
|
99
103
|
// packages/pi/src/index.ts
|
|
104
|
+
import { Type } from "typebox";
|
|
100
105
|
var KEY = Symbol.for("agent-switchboard.recorder");
|
|
101
106
|
var store = globalThis;
|
|
102
107
|
function claim() {
|
|
103
108
|
const env = process.env;
|
|
104
109
|
if (env.SWB_MANAGED === "1" && env.SWB_DB && env.SWB_TMUX_SESSION) {
|
|
105
|
-
store[KEY] = {
|
|
110
|
+
store[KEY] = {
|
|
111
|
+
launch: Object.freeze({ db: env.SWB_DB, tmuxSession: env.SWB_TMUX_SESSION, bin: env.SWB_BIN ?? "" }),
|
|
112
|
+
attention: new Set,
|
|
113
|
+
...env.SWB_BIN ? {} : { failure: "started by an swb older than this recorder; upgrade swb" }
|
|
114
|
+
};
|
|
106
115
|
}
|
|
107
116
|
delete env.SWB_MANAGED;
|
|
108
117
|
delete env.SWB_DB;
|
|
109
118
|
delete env.SWB_TMUX_SESSION;
|
|
119
|
+
delete env.SWB_BIN;
|
|
110
120
|
return store[KEY];
|
|
111
121
|
}
|
|
122
|
+
var run = promisify(execFile);
|
|
123
|
+
function host(bin) {
|
|
124
|
+
const swb = async (...args) => {
|
|
125
|
+
try {
|
|
126
|
+
return (await run(bin, args, { timeout: 15000 })).stdout;
|
|
127
|
+
} catch (error) {
|
|
128
|
+
const { stderr, message } = error;
|
|
129
|
+
throw new Error(stderr?.trim() || message);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
return {
|
|
133
|
+
name: "swb",
|
|
134
|
+
async launch(input) {
|
|
135
|
+
await swb("launch", "--cwd", input.cwd, "--session-id", input.sessionId, "--model", input.model);
|
|
136
|
+
return { success: true };
|
|
137
|
+
},
|
|
138
|
+
async listSessions() {
|
|
139
|
+
const sessions = JSON.parse(await swb("ls", "--json"));
|
|
140
|
+
return sessions.filter((s) => s.open).map((s) => ({ sessionId: s.id, cwd: s.cwd, title: s.title ?? s.id }));
|
|
141
|
+
},
|
|
142
|
+
async wake(sessionId) {
|
|
143
|
+
await swb("wake", sessionId);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
112
147
|
function liveHost(row) {
|
|
113
148
|
if (row && row.boot_id === bootId() && pidAlive(row.pid))
|
|
114
149
|
return row.tmux_session;
|
|
@@ -126,6 +161,8 @@ function src_default(pi) {
|
|
|
126
161
|
return;
|
|
127
162
|
const proc = claimed;
|
|
128
163
|
let ui;
|
|
164
|
+
if (proc.launch.bin)
|
|
165
|
+
pi.events.on("pi-sessions:hosts:v1", (request) => request.register(host(proc.launch.bin)));
|
|
129
166
|
const showFailure = () => ui?.setStatus("swb", ui.theme.fg("error", `swb: not recording: ${proc.failure}`));
|
|
130
167
|
const connect = () => {
|
|
131
168
|
proc.db ??= new Db(proc.launch.db, { create: false });
|
|
@@ -191,9 +228,7 @@ function src_default(pi) {
|
|
|
191
228
|
db.run("UPDATE sessions SET phase = 'idle', cwd = ?, project_root = ?, branch = ?, transcript = ? WHERE session_id = ?", ctx.cwd, projectRoot(ctx.cwd), branch(ctx.cwd), transcript, id);
|
|
192
229
|
});
|
|
193
230
|
});
|
|
194
|
-
pi.on("
|
|
195
|
-
if (event.message.role !== "user")
|
|
196
|
-
return;
|
|
231
|
+
pi.on("agent_start", (_event, ctx) => {
|
|
197
232
|
writeFor(ctx, (db, id) => {
|
|
198
233
|
const transcript = ctx.sessionManager.getSessionFile();
|
|
199
234
|
if (!transcript)
|
|
@@ -201,12 +236,10 @@ function src_default(pi) {
|
|
|
201
236
|
const now = Date.now();
|
|
202
237
|
db.run(`INSERT INTO sessions (session_id, tool, cwd, project_root, transcript, title, branch, phase, created_at, last_prompt_at)
|
|
203
238
|
VALUES (?, 'pi', ?, ?, ?, ?, ?, 'working', ?, ?)
|
|
204
|
-
ON CONFLICT (session_id) DO UPDATE SET phase = 'working',
|
|
239
|
+
ON CONFLICT (session_id) DO UPDATE SET phase = 'working',
|
|
240
|
+
last_prompt_at = CASE WHEN sessions.phase = 'idle' THEN excluded.last_prompt_at ELSE sessions.last_prompt_at END`, id, ctx.cwd, projectRoot(ctx.cwd), transcript, ctx.sessionManager.getSessionName() ?? null, branch(ctx.cwd), now, now);
|
|
205
241
|
});
|
|
206
242
|
});
|
|
207
|
-
pi.on("agent_start", (_event, ctx) => {
|
|
208
|
-
writeFor(ctx, (db, id) => db.run("UPDATE sessions SET phase = 'working' WHERE session_id = ?", id));
|
|
209
|
-
});
|
|
210
243
|
const attention = (open) => (data) => {
|
|
211
244
|
const attentionId = data.attentionId;
|
|
212
245
|
if (typeof attentionId !== "string" || !current)
|
|
@@ -230,6 +263,16 @@ function src_default(pi) {
|
|
|
230
263
|
pi.on("session_info_changed", (event, ctx) => {
|
|
231
264
|
writeFor(ctx, (db, id) => db.run("UPDATE sessions SET title = ? WHERE session_id = ?", event.name ?? null, id));
|
|
232
265
|
});
|
|
266
|
+
const markArchived = (ctx) => {
|
|
267
|
+
let archived = false;
|
|
268
|
+
writeFor(ctx, (db, id) => {
|
|
269
|
+
if (!db.get("SELECT 1 FROM sessions WHERE session_id = ?", id))
|
|
270
|
+
return;
|
|
271
|
+
db.run("INSERT INTO marks (session_id, archived_at) VALUES (?, ?) ON CONFLICT (session_id) DO UPDATE SET archived_at = excluded.archived_at", id, Date.now());
|
|
272
|
+
archived = true;
|
|
273
|
+
});
|
|
274
|
+
return archived;
|
|
275
|
+
};
|
|
233
276
|
pi.registerCommand("archive", {
|
|
234
277
|
description: "Archive this session in swb and quit",
|
|
235
278
|
handler: async (_args, ctx) => {
|
|
@@ -237,19 +280,33 @@ function src_default(pi) {
|
|
|
237
280
|
ctx.ui.notify("swb: turn running: wait for it to complete", "error");
|
|
238
281
|
return;
|
|
239
282
|
}
|
|
240
|
-
|
|
241
|
-
writeFor(ctx, (db, id) => {
|
|
242
|
-
if (!db.get("SELECT 1 FROM sessions WHERE session_id = ?", id))
|
|
243
|
-
return;
|
|
244
|
-
db.run("INSERT INTO marks (session_id, archived_at) VALUES (?, ?) ON CONFLICT (session_id) DO UPDATE SET archived_at = excluded.archived_at", id, Date.now());
|
|
245
|
-
archived = true;
|
|
246
|
-
});
|
|
247
|
-
if (archived)
|
|
283
|
+
if (markArchived(ctx))
|
|
248
284
|
ctx.shutdown();
|
|
249
285
|
else if (!proc.failure)
|
|
250
286
|
ctx.ui.notify("swb: nothing to archive before the first prompt", "error");
|
|
251
287
|
}
|
|
252
288
|
});
|
|
289
|
+
pi.registerTool({
|
|
290
|
+
name: "swb_archive",
|
|
291
|
+
label: "Archive session",
|
|
292
|
+
description: "Archive this session in Agent Switchboard. It leaves the user's roster of open sessions, and pi quits as soon as this turn ends. The user can restore it with `swb unarchive <id>`.",
|
|
293
|
+
promptSnippet: "Archive this session when the user asks",
|
|
294
|
+
promptGuidelines: [
|
|
295
|
+
"Use swb_archive only when the user asks to archive this session; never archive on your own initiative.",
|
|
296
|
+
"Call swb_archive as the last action of the turn, then end with a brief final message: pi quits once the turn ends."
|
|
297
|
+
],
|
|
298
|
+
parameters: Type.Object({}),
|
|
299
|
+
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
300
|
+
if (!markArchived(ctx)) {
|
|
301
|
+
throw new Error(proc.failure ? `swb: not recording: ${proc.failure}` : "swb: no session recorded yet");
|
|
302
|
+
}
|
|
303
|
+
ctx.shutdown();
|
|
304
|
+
return {
|
|
305
|
+
content: [{ type: "text", text: "Archived. pi quits when this turn ends." }],
|
|
306
|
+
details: undefined
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
});
|
|
253
310
|
pi.on("session_shutdown", (event, ctx) => {
|
|
254
311
|
if (event.reason !== "quit")
|
|
255
312
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thurstonsand/pi-agent-switchboard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Records managed pi sessions for Agent Switchboard (swb)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
]
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
|
-
"@earendil-works/pi-coding-agent": "*"
|
|
20
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
21
|
+
"typebox": "*"
|
|
21
22
|
}
|
|
22
23
|
}
|