feat: add session runtime reload command

This commit is contained in:
Federico Jaramillo Martinez
2026-07-01 19:32:52 +02:00
parent 8ade238228
commit 889672ff53
19 changed files with 129 additions and 31 deletions
+1 -1
View File
@@ -240,7 +240,7 @@ export class SessionList extends LitElement implements KeyboardNavigableSection
<button title="Archive session" @click=${() => { this.openMenuSessionId = undefined; this.onArchive?.(session); }}>Archive</button>
${descendantCount > 0 ? html`<button title="Archive this session and its descendants" @click=${() => { this.openMenuSessionId = undefined; this.confirmArchiveWithDescendants(session, descendantCount); }}>Archive with descendants (${descendantCount})</button>` : null}
${session.parentSessionPath !== undefined ? html`<button title="Detach from parent" @click=${() => { this.openMenuSessionId = undefined; this.onDetachParent?.(session); }}>Detach from parent</button>` : null}
${this.canReload ? html`<button title=${isSessionActive(this.statuses[session.id], this.activities[session.id]) ? "Stop current session activity before reloading" : "Reload session from disk"} ?disabled=${isSessionActive(this.statuses[session.id], this.activities[session.id])} @click=${() => { this.openMenuSessionId = undefined; this.onReload?.(session); }}>Reload</button>` : null}
${this.canReload ? html`<button title=${isSessionActive(this.statuses[session.id], this.activities[session.id]) ? "Stop current session activity before reloading from disk" : "Reload session from disk without refreshing Pi runtime resources"} ?disabled=${isSessionActive(this.statuses[session.id], this.activities[session.id])} @click=${() => { this.openMenuSessionId = undefined; this.onReload?.(session); }}>Reload from disk</button>` : null}
`}
</div>
` : null}
@@ -23,7 +23,7 @@ export class SettingsPackagesPanel extends LitElement {
<div class="section-heading">
<div>
<h2>Pi packages</h2>
<p>Install, remove, and update packages managed by Pi. Pi packages can provide extensions, skills, prompt templates, themes, and PI WEB browser plugins.</p>
<p>Install, remove, and update packages managed by Pi. Pi packages can provide extensions, skills, prompt templates, themes, context/system prompt files, and PI WEB browser plugins.</p>
</div>
<button class="secondary" ?disabled=${this.loading || this.isOperating} @click=${() => { void this.onReload?.(); }}>Reload</button>
</div>
@@ -37,8 +37,9 @@ describe("Pi package settings helpers", () => {
it("describes the browser and session reload follow-up without requiring sessiond restarts", () => {
const message = piPackageMutationFollowUpMessage("install");
expect(message).toContain("Reload the browser page");
expect(message).toContain("Reload existing Pi sessions");
expect(message).toContain("Type /reload in each idle PI WEB session");
expect(message).toContain("extensions, skills, prompt templates, themes, and context/system prompt files");
expect(message).toContain("Reload the browser page separately for PI WEB browser plugin changes");
expect(message).not.toContain("session daemon");
expect(message).not.toContain("sessiond");
});
@@ -54,5 +54,5 @@ export function isPiPackageOperationPending(operation: PiPackageOperationState |
export function piPackageMutationFollowUpMessage(action: PiPackageMutationAction): string {
const verb = action === "install" ? "installed" : action === "remove" ? "removed" : "updated";
return `Pi package ${verb}. Reload the browser page to import newly discovered PI WEB browser plugins. Reload existing Pi sessions, or use /reload in Pi, so extensions, skills, prompt templates, and themes are rediscovered.`;
return `Pi package ${verb}. Type /reload in each idle PI WEB session to rediscover Pi runtime resources: extensions, skills, prompt templates, themes, and context/system prompt files. Reload the browser page separately for PI WEB browser plugin changes.`;
}
@@ -879,7 +879,7 @@ describe("SessionController", () => {
expect(state.error).toContain("requires an updated Pi-Web runtime");
});
it("reloads the selected session, discards the cached transcript, and re-fetches history", async () => {
it("reloads the selected session from disk, discards the cached transcript, and re-fetches history", async () => {
Object.defineProperty(globalThis, "localStorage", { value: new MemoryStorage(), configurable: true });
const reloadCalls: string[] = [];
const messageCalls: string[] = [];
@@ -917,7 +917,7 @@ describe("SessionController", () => {
expect(state.error).toBe("");
});
it("does not reload sessions when the selected machine runtime does not support it", async () => {
it("does not reload sessions from disk when the selected machine runtime does not support it", async () => {
const reloadCalls: string[] = [];
let state: AppState = {
...initialAppState(),
@@ -943,7 +943,7 @@ describe("SessionController", () => {
await controller.reloadSession(oldSession);
expect(reloadCalls).toEqual([]);
expect(state.error).toContain("requires an updated Pi-Web runtime");
expect(state.error).toContain("Reloading sessions from disk requires an updated Pi-Web runtime");
});
it("forgets archived selections when the archived section collapse clears selection", async () => {
@@ -484,7 +484,7 @@ export class SessionController {
const machineId = selectedMachineId(this.getState());
const runtime = this.getState().machineRuntimes[machineId];
if (runtime?.ok !== true || !supportsPiWebCapability(runtime, PI_WEB_CAPABILITIES.sessionsReload)) {
this.setState({ error: "Reloading sessions requires an updated Pi-Web runtime on this machine." });
this.setState({ error: "Reloading sessions from disk requires an updated Pi-Web runtime on this machine." });
return;
}
try {
+3 -3
View File
@@ -177,8 +177,8 @@ export function createCoreActions(): PluginAction[] {
},
{
id: "session.reload",
title: "Reload Session",
description: "Re-read the selected session from disk to pick up entries written by another process",
title: "Reload Session from Disk",
description: "Close and re-open the selected session from its session file. Use /reload in the prompt for Pi runtime resources.",
group: "Session",
enabled: hasReloadableSession,
disabledReason: reloadSessionDisabledReason,
@@ -236,7 +236,7 @@ function reloadSessionDisabledReason(context: { state: AppState }): string | und
const session = context.state.selectedSession;
if (session === undefined || session.archived === true || isCachedNewSessionInfo(session)) return undefined;
if (isSessionActive(context.state.status, context.state.activity)) return undefined;
return missingCapabilityReason(context.state, PI_WEB_CAPABILITIES.sessionsReload, "reload sessions");
return missingCapabilityReason(context.state, PI_WEB_CAPABILITIES.sessionsReload, "reload sessions from disk");
}
function missingCapabilityReason(state: AppState, capability: PiWebCapability, action: string): string | undefined {
+6 -3
View File
@@ -187,18 +187,21 @@ describe("PluginRegistry", () => {
expect(archivedActions.find((action) => action.id === "core:session.delete")?.enabled).toBe(false);
});
it("enables session reload only for a writable session on a capable, idle runtime", () => {
it("enables session disk reload only for a writable session on a capable, idle runtime", () => {
const registry = new PluginRegistry();
registry.register({ id: "core", plugin: corePlugin });
const reloadRuntime = { local: { machineId: "local", ok: true as const, checkedAt: "now", capabilities: [PI_WEB_CAPABILITIES.sessionsReload] } };
const reloadable = registry.getActions(createContext({ selectedSession: testSession(), machineRuntimes: reloadRuntime }).context);
expect(reloadable.find((action) => action.id === "core:session.reload")?.enabled).toBe(true);
const reloadableAction = reloadable.find((action) => action.id === "core:session.reload");
expect(reloadableAction?.enabled).toBe(true);
expect(reloadableAction?.title).toBe("Reload Session from Disk");
expect(reloadableAction?.description).toContain("Use /reload in the prompt for Pi runtime resources");
const noCapability = registry.getActions(createContext({ selectedSession: testSession() }).context);
const noCapabilityReload = noCapability.find((action) => action.id === "core:session.reload");
expect(noCapabilityReload?.enabled).toBe(false);
expect(noCapabilityReload?.disabledReason).toBe("Update and restart Pi-Web on this machine to reload sessions.");
expect(noCapabilityReload?.disabledReason).toBe("Update and restart Pi-Web on this machine to reload sessions from disk.");
const archived = registry.getActions(createContext({ selectedSession: { ...testSession(), archived: true, archivedAt: "2026-05-20T00:00:00.000Z" }, machineRuntimes: reloadRuntime }).context);
expect(archived.find((action) => action.id === "core:session.reload")?.enabled).toBe(false);