Archived
Show session lineage and support detaching forks
This commit is contained in:
@@ -31,6 +31,7 @@ export function registerSessionProxyRoutes(app: FastifyInstance, daemon = new Se
|
||||
app.post<{ Params: { sessionId: string } }>("/api/sessions/:sessionId/stop", (request, reply) => proxy(request, reply));
|
||||
app.post<{ Params: { sessionId: string } }>("/api/sessions/:sessionId/archive", (request, reply) => proxy(request, reply));
|
||||
app.post<{ Params: { sessionId: string } }>("/api/sessions/:sessionId/restore", (request, reply) => proxy(request, reply));
|
||||
app.post<{ Params: { sessionId: string } }>("/api/sessions/:sessionId/detach-parent", (request, reply) => proxy(request, reply));
|
||||
|
||||
app.get<{ Params: { sessionId: string } }>("/api/sessions/:sessionId/events", { websocket: true }, (socket, request) => {
|
||||
bridgeSockets(socket, daemon.connectWebSocket(`/sessions/${request.params.sessionId}/events`));
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import {
|
||||
AuthStorage,
|
||||
createAgentSessionFromServices,
|
||||
@@ -115,6 +116,7 @@ export class PiSessionService {
|
||||
modified: s.modified.toISOString(),
|
||||
messageCount: s.messageCount,
|
||||
firstMessage: s.firstMessage,
|
||||
...(s.parentSessionPath === undefined ? {} : { parentSessionPath: s.parentSessionPath }),
|
||||
...(archived === undefined ? {} : { archived: true, archivedAt: archived.archivedAt }),
|
||||
};
|
||||
});
|
||||
@@ -234,6 +236,13 @@ export class PiSessionService {
|
||||
await this.archiveStore.restore(sessionId);
|
||||
}
|
||||
|
||||
async detachParent(sessionId: string): Promise<void> {
|
||||
const session = await this.getOrOpen(sessionId);
|
||||
const sessionFile = session.sessionFile;
|
||||
if (sessionFile === undefined || sessionFile === "") throw new Error("Session is not persisted");
|
||||
await clearParentSession(sessionFile);
|
||||
}
|
||||
|
||||
async abort(sessionId: string): Promise<void> {
|
||||
const active = this.active.get(sessionId);
|
||||
if (!active) return;
|
||||
@@ -414,6 +423,18 @@ export class PiSessionService {
|
||||
}
|
||||
}
|
||||
|
||||
async function clearParentSession(sessionFile: string): Promise<void> {
|
||||
const content = await readFile(sessionFile, "utf8");
|
||||
const newlineIndex = content.indexOf("\n");
|
||||
const firstLine = newlineIndex === -1 ? content : content.slice(0, newlineIndex);
|
||||
const rest = newlineIndex === -1 ? "" : content.slice(newlineIndex);
|
||||
const header: unknown = JSON.parse(firstLine);
|
||||
if (!isRecord(header) || header["type"] !== "session") throw new Error("Invalid session file header");
|
||||
if (header["parentSession"] === undefined) return;
|
||||
delete header["parentSession"];
|
||||
await writeFile(sessionFile, `${JSON.stringify(header)}${rest}`, "utf8");
|
||||
}
|
||||
|
||||
function userTextMessage(text: string): { role: "user"; content: string } {
|
||||
return { role: "user", content: text };
|
||||
}
|
||||
|
||||
@@ -113,6 +113,7 @@ export class SessionCommandService {
|
||||
|
||||
function clientSessionFromRuntime(runtime: AgentSessionRuntime): ClientSession {
|
||||
const session = runtime.session;
|
||||
const parentSessionPath = typeof session.sessionManager.getHeader === "function" ? session.sessionManager.getHeader()?.parentSession : undefined;
|
||||
return {
|
||||
id: session.sessionId,
|
||||
path: session.sessionFile ?? "",
|
||||
@@ -122,6 +123,7 @@ function clientSessionFromRuntime(runtime: AgentSessionRuntime): ClientSession {
|
||||
modified: new Date().toISOString(),
|
||||
messageCount: session.messages.length,
|
||||
firstMessage: "",
|
||||
...(parentSessionPath === undefined ? {} : { parentSessionPath }),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -103,6 +103,15 @@ export function registerSessionRoutes(app: FastifyInstance, sessions: PiSessionS
|
||||
}
|
||||
});
|
||||
|
||||
app.post<{ Params: { sessionId: string } }>(`${prefix}/sessions/:sessionId/detach-parent`, async (request, reply) => {
|
||||
try {
|
||||
await sessions.detachParent(request.params.sessionId);
|
||||
return { detached: true };
|
||||
} catch (error) {
|
||||
return reply.code(400).send({ error: error instanceof Error ? error.message : String(error) });
|
||||
}
|
||||
});
|
||||
|
||||
app.get<{ Params: { sessionId: string } }>(`${prefix}/sessions/:sessionId/events`, { websocket: true }, (socket, request) => {
|
||||
eventHub.add(request.params.sessionId, socket);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user