diff --git a/.changeset/session-runtime-reload-command.md b/.changeset/session-runtime-reload-command.md
new file mode 100644
index 0000000..c352987
--- /dev/null
+++ b/.changeset/session-runtime-reload-command.md
@@ -0,0 +1,5 @@
+---
+"@jmfederico/pi-web": patch
+---
+
+Add `/reload` support for PI WEB sessions so installed Pi package resources can be refreshed in existing sessions without restarting the session daemon, while keeping browser plugin reload guidance separate.
diff --git a/README.md b/README.md
index b63902a..7f301b9 100644
--- a/README.md
+++ b/README.md
@@ -107,6 +107,8 @@ PI WEB supports trusted browser-side PI WEB plugins that can add actions, worksp
Pi packages are managed separately through Pi's package manager or **Settings → Pi packages**. Use **Settings → PI WEB plugins** only to enable or disable discovered browser plugins.
+After installing, updating, or removing a Pi package, type `/reload` in each idle PI WEB session to refresh Pi runtime resources such as extensions, skills, prompt templates, themes, and context/system prompt files. Reload the browser page separately for newly discovered or changed PI WEB plugins.
+
Read more: [Plugin API](https://pi-web.dev/plugins)
## Configuration
diff --git a/docs/config.html b/docs/config.html
index ffdb75c..a610588 100644
--- a/docs/config.html
+++ b/docs/config.html
@@ -143,7 +143,7 @@
spawnSessions / subsessions: restart the session daemon.
pathAccess: applies on the next request; existing file views may need a browser refresh.
plugins: reload the browser tab after changing PI WEB plugin enablement.
-
Pi package install/remove/update: not a PI WEB config key; after a mutation, reload the browser page for newly discovered PI WEB browser plugins and reload existing Pi sessions (or use /reload in Pi) for session-runtime resources. A routine session daemon restart is not required.
+
Pi package install/remove/update: not a PI WEB config key; after a mutation, type /reload in each idle PI WEB session to refresh Pi runtime resources such as extensions, skills, prompt templates, themes, and context/system prompt files as supported by Pi. Reload the browser page separately for PI WEB browser plugin changes. A routine session daemon restart is not required.
shortcuts: saved settings apply in the browser after config refresh/save.
diff --git a/docs/config.md b/docs/config.md
index 8e7e468..ef02890 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -37,7 +37,7 @@ Process restarts depend on the key:
- `pathAccess`: applies on the next request; existing file views may need a browser refresh.
- `uploads.defaultFolder`: applies to newly opened Files upload dialogs and new direct drag/drop batches after config/workspace refresh.
- `plugins`: reload the browser tab after changing PI WEB plugin enablement.
-- Pi package install/remove/update: not a PI WEB config key; after a mutation, reload the browser page for newly discovered PI WEB browser plugins and reload existing Pi sessions (or use `/reload` in Pi) for session-runtime resources. A routine session daemon restart is not required.
+- Pi package install/remove/update: not a PI WEB config key; after a mutation, type `/reload` in each idle PI WEB session to refresh Pi runtime resources such as extensions, skills, prompt templates, themes, and context/system prompt files as supported by Pi. Reload the browser page separately for PI WEB browser plugin changes. A routine session daemon restart is not required.
- `shortcuts`: saved settings apply in the browser after config refresh/save.
## Global config example
diff --git a/docs/plugins.html b/docs/plugins.html
index f4b8e2e..278ba9b 100644
--- a/docs/plugins.html
+++ b/docs/plugins.html
@@ -130,8 +130,8 @@
Pi packages vs PI WEB plugins
Pi packages are packages managed by Pi (pi install, pi remove,
- pi update). A Pi package can provide extensions, skills, prompt templates, themes, and/or
- PI WEB browser plugins. Many Pi packages do not include a PI WEB plugin.
+ pi update). A Pi package can provide extensions, skills, prompt templates, themes,
+ context/system prompt files, and/or PI WEB browser plugins. Many Pi packages do not include a PI WEB plugin.
PI WEB plugins are browser-side UI modules discovered from bundled, local, dev, and
@@ -146,10 +146,11 @@
Use Settings → PI WEB plugins to enable or disable discovered PI WEB browser plugins
- before the browser imports them. After installing, removing, or updating a Pi package, 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. A
- routine session daemon restart is not required.
+ before the browser imports them. After installing, removing, or updating a Pi package, type
+ /reload in each idle PI WEB session to refresh Pi runtime resources such as extensions,
+ skills, prompt templates, themes, and context/system prompt files as supported by Pi. Reload the browser
+ page separately for newly discovered or changed PI WEB browser plugins. A routine session daemon restart
+ is not required.
diff --git a/docs/plugins.md b/docs/plugins.md
index 0cd9e5c..085049e 100644
--- a/docs/plugins.md
+++ b/docs/plugins.md
@@ -15,13 +15,13 @@ They do **not** run in the session daemon, do not get a server-side hook API, an
## Pi packages vs PI WEB plugins
-**Pi packages** are packages managed by Pi (`pi install`, `pi remove`, `pi update`). A Pi package can provide extensions, skills, prompt templates, themes, and/or PI WEB browser plugins. Many Pi packages do not include a PI WEB plugin.
+**Pi packages** are packages managed by Pi (`pi install`, `pi remove`, `pi update`). A Pi package can provide extensions, skills, prompt templates, themes, context/system prompt files, and/or PI WEB browser plugins. Many Pi packages do not include a PI WEB plugin.
**PI WEB plugins** are browser-side PI WEB UI modules discovered from bundled, local, dev, and installed Pi-package sources. Enabling or disabling a PI WEB plugin is a PI WEB config task; installing, removing, or updating a Pi package is a Pi package-manager task.
Use **Settings → Pi packages** to view configured Pi packages or install/remove/update a package. Enter only the package source, such as `npm:@scope/package`, a git/URL source, or a local path. PI WEB uses Pi's default package location, equivalent to `pi install `, and does not ask for an install location.
-Use **Settings → PI WEB plugins** to enable or disable discovered PI WEB browser plugins before the browser imports them. After installing, removing, or updating a Pi package, reload the browser page to import newly discovered PI WEB browser plugins. Reload existing Pi sessions, or use `/reload` in Pi, so session-runtime resources such as extensions, skills, prompt templates, and themes are rediscovered. A routine session daemon restart is not required.
+Use **Settings → PI WEB plugins** to enable or disable discovered PI WEB browser plugins before the browser imports them. After installing, removing, or updating a Pi package, type `/reload` in each idle PI WEB session to refresh Pi runtime resources such as extensions, skills, prompt templates, themes, and context/system prompt files as supported by Pi. Reload the browser page separately for newly discovered or changed PI WEB browser plugins. A routine session daemon restart is not required.
## Trust model
diff --git a/src/client/src/components/SessionList.ts b/src/client/src/components/SessionList.ts
index 99ca0b7..c1c34e9 100644
--- a/src/client/src/components/SessionList.ts
+++ b/src/client/src/components/SessionList.ts
@@ -240,7 +240,7 @@ export class SessionList extends LitElement implements KeyboardNavigableSection
${descendantCount > 0 ? html`` : null}
${session.parentSessionPath !== undefined ? html`` : null}
- ${this.canReload ? html`` : null}
+ ${this.canReload ? html`` : null}
`}
` : null}
diff --git a/src/client/src/components/settings/SettingsPackagesPanel.ts b/src/client/src/components/settings/SettingsPackagesPanel.ts
index efe9794..5ed9577 100644
--- a/src/client/src/components/settings/SettingsPackagesPanel.ts
+++ b/src/client/src/components/settings/SettingsPackagesPanel.ts
@@ -23,7 +23,7 @@ export class SettingsPackagesPanel extends LitElement {
Pi packages
-
Install, remove, and update packages managed by Pi. Pi packages can provide extensions, skills, prompt templates, themes, and PI WEB browser plugins.
+
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.
diff --git a/src/client/src/components/settings/piPackageSettings.test.ts b/src/client/src/components/settings/piPackageSettings.test.ts
index f95189f..b699910 100644
--- a/src/client/src/components/settings/piPackageSettings.test.ts
+++ b/src/client/src/components/settings/piPackageSettings.test.ts
@@ -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");
});
diff --git a/src/client/src/components/settings/piPackageSettings.ts b/src/client/src/components/settings/piPackageSettings.ts
index 7bcad1d..3bb0dc4 100644
--- a/src/client/src/components/settings/piPackageSettings.ts
+++ b/src/client/src/components/settings/piPackageSettings.ts
@@ -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.`;
}
diff --git a/src/client/src/controllers/sessionController.test.ts b/src/client/src/controllers/sessionController.test.ts
index f189a2b..e2beef9 100644
--- a/src/client/src/controllers/sessionController.test.ts
+++ b/src/client/src/controllers/sessionController.test.ts
@@ -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 () => {
diff --git a/src/client/src/controllers/sessionController.ts b/src/client/src/controllers/sessionController.ts
index 93533b6..b891227 100644
--- a/src/client/src/controllers/sessionController.ts
+++ b/src/client/src/controllers/sessionController.ts
@@ -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 {
diff --git a/src/client/src/plugins/core/actions.ts b/src/client/src/plugins/core/actions.ts
index e36edb7..6bd7f1a 100644
--- a/src/client/src/plugins/core/actions.ts
+++ b/src/client/src/plugins/core/actions.ts
@@ -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 {
diff --git a/src/client/src/plugins/registry.test.ts b/src/client/src/plugins/registry.test.ts
index bc40b38..ce3dbfc 100644
--- a/src/client/src/plugins/registry.test.ts
+++ b/src/client/src/plugins/registry.test.ts
@@ -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);
diff --git a/src/server/sessions/builtinCommands.ts b/src/server/sessions/builtinCommands.ts
index 0a740d0..a56e375 100644
--- a/src/server/sessions/builtinCommands.ts
+++ b/src/server/sessions/builtinCommands.ts
@@ -20,7 +20,7 @@ export const BUILTIN_COMMANDS: ClientCommand[] = [
{ name: "new", description: "Start a new session", source: "builtin" },
{ name: "compact", description: "Manually compact session context", source: "builtin" },
{ name: "resume", description: "Resume a different session", source: "builtin" },
- { name: "reload", description: "Reload keybindings, extensions, skills, prompts, and themes", source: "builtin" },
+ { name: "reload", description: "Reload Pi runtime resources for this session", source: "builtin" },
{ name: "quit", description: "Quit pi", source: "builtin" },
];
diff --git a/src/server/sessions/piSessionService.test.ts b/src/server/sessions/piSessionService.test.ts
index b99cbab..3206b6b 100644
--- a/src/server/sessions/piSessionService.test.ts
+++ b/src/server/sessions/piSessionService.test.ts
@@ -63,7 +63,7 @@ function fakeRuntime(sessionId = "session-1", patch: Partial = {})
const customMessageCalls: { message: { customType: string; content: string; display: boolean; details?: unknown }; options: unknown }[] = [];
const bindExtensionCalls: unknown[] = [];
const listeners: ((event: unknown) => void)[] = [];
- const calls = { abort: 0, bindExtensions: bindExtensionCalls, clearQueue: 0, dispose: 0, prompt: promptCalls, sendCustomMessage: customMessageCalls };
+ const calls = { abort: 0, bindExtensions: bindExtensionCalls, clearQueue: 0, dispose: 0, prompt: promptCalls, reload: 0, sendCustomMessage: customMessageCalls };
const session: TestSession = {
sessionId,
sessionFile: `/tmp/${sessionId}.jsonl`,
@@ -94,6 +94,10 @@ function fakeRuntime(sessionId = "session-1", patch: Partial = {})
},
getSessionStats: () => ({ sessionId, totalMessages: 0, userMessages: 0, assistantMessages: 0, toolCalls: 0, tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, cost: 0 }),
getContextUsage: () => undefined,
+ reload: () => {
+ calls.reload += 1;
+ return Promise.resolve();
+ },
prompt: (text: string, options: unknown) => {
calls.prompt.push({ text, options });
return Promise.resolve();
@@ -738,6 +742,29 @@ describe("PiSessionService", () => {
await service.dispose();
});
+ it("runs /reload by refreshing the active runtime resources in place", async () => {
+ const hub = new CapturingSessionEventHub();
+ const fake = fakeRuntime("runtime-reload-session");
+ const service = new PiSessionService(hub, {
+ createAgentRuntime: runtimeCreator(fake.runtime),
+ sessionManager: sessionGateway([sessionRecord("runtime-reload-session")]),
+ heartbeatIntervalMs: 60_000,
+ });
+
+ await expect(service.runCommand(sessionRef("runtime-reload-session"), "/reload")).resolves.toEqual({
+ type: "done",
+ message: "Session runtime resources reloaded. Extensions, skills, prompt templates, themes, and context/system prompt files are refreshed for this session. Reload the browser page separately for PI WEB browser plugin changes.",
+ });
+
+ expect(fake.calls.reload).toBe(1);
+ expect(fake.calls.abort).toBe(0);
+ expect(fake.calls.dispose).toBe(0);
+ expect(hub.globalEvents.some((event) => event.type === "activity.update" && event.activity.sessionId === "runtime-reload-session" && event.activity.label === "resources reloaded")).toBe(true);
+ expect(hub.globalEvents.some((event) => event.type === "status.update" && event.status.sessionId === "runtime-reload-session")).toBe(true);
+
+ await service.dispose();
+ });
+
it("reloads a session by closing the active runtime and re-opening it from disk", async () => {
const first = fakeRuntime("reload-session");
const second = fakeRuntime("reload-session");
diff --git a/src/server/sessions/piSessionService.ts b/src/server/sessions/piSessionService.ts
index 391e6ae..dc507f6 100644
--- a/src/server/sessions/piSessionService.ts
+++ b/src/server/sessions/piSessionService.ts
@@ -217,6 +217,7 @@ export interface PiAgentSession {
compact(instructions?: string): Promise<{ summary: string; tokensBefore: number }>;
getUserMessagesForForking(): readonly { entryId: string; text: string }[];
getSessionStats(): { sessionId: string; totalMessages: number; userMessages: number; assistantMessages: number; toolCalls: number; tokens: ClientSessionStatus["tokens"]; cost: number };
+ reload(): Promise;
getContextUsage(): ClientSessionStatus["contextUsage"] | undefined;
prompt(text: string, options?: { streamingBehavior?: "steer" | "followUp"; images?: ImageContent[] }): Promise;
sendCustomMessage(message: { customType: string; content: string; display: boolean; details?: unknown }, options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" }): Promise;
@@ -423,6 +424,7 @@ export class PiSessionService {
this.publishActivity(session, result === "success" ? "compaction complete" : "compaction failed", result === "success" ? "idle" : "error", detail);
this.publishStatus(session);
},
+ reloadSession: (session) => this.reloadSessionRuntime(session),
},
{ listSessionNames: (cwd) => this.listSessionNames(cwd) },
);
@@ -1118,6 +1120,22 @@ export class PiSessionService {
return this.commandService.respond(active.runtime.session.sessionId, requestId, value);
}
+ private async reloadSessionRuntime(session: PiAgentSession): Promise {
+ if (this.hasActiveWork(session)) throw new Error("Stop current session activity before reloading");
+ this.publishActivity(session, "reloading resources", "active");
+ try {
+ await session.reload();
+ this.publishActivity(session, "resources reloaded", "idle");
+ this.publishStatus(session);
+ } catch (error: unknown) {
+ const message = error instanceof Error ? error.message : String(error);
+ this.publishActivity(session, "reload failed", "error", message);
+ this.events.publish(session.sessionId, { type: "session.error", message });
+ this.publishStatus(session);
+ throw error;
+ }
+ }
+
async archive(ref: PiSessionLookup): Promise {
const session = await this.getOrOpen(ref);
if (this.hasActiveWork(session)) throw new Error("Stop current session activity before archiving");
diff --git a/src/server/sessions/sessionCommandService.test.ts b/src/server/sessions/sessionCommandService.test.ts
index f3a3cb1..7ca503c 100644
--- a/src/server/sessions/sessionCommandService.test.ts
+++ b/src/server/sessions/sessionCommandService.test.ts
@@ -106,6 +106,30 @@ describe("SessionCommandService", () => {
expect(active.runtime.session.compact).toHaveBeenCalledWith("focus on tests");
});
+ it("reloads runtime resources through the injected lifecycle callback", async () => {
+ const active = activeSession();
+ const reloadSession = vi.fn(async () => { await Promise.resolve(); });
+ const service = new SessionCommandService(() => getActive(active), vi.fn(), eventPublisher(), { reloadSession });
+
+ await expect(service.run("s1", "/reload")).resolves.toEqual({
+ type: "done",
+ message: "Session runtime resources reloaded. Extensions, skills, prompt templates, themes, and context/system prompt files are refreshed for this session. Reload the browser page separately for PI WEB browser plugin changes.",
+ });
+ expect(reloadSession).toHaveBeenCalledWith(active.runtime.session);
+ });
+
+ it("rejects runtime reload while the session has active work", async () => {
+ const active = activeSession({ isBashRunning: true });
+ const reloadSession = vi.fn(async () => { await Promise.resolve(); });
+ const service = new SessionCommandService(() => getActive(active), vi.fn(), eventPublisher(), { reloadSession });
+
+ await expect(service.run("s1", "/reload")).resolves.toEqual({
+ type: "unsupported",
+ message: "Cannot reload while the session is active. Stop current activity before reloading.",
+ });
+ expect(reloadSession).not.toHaveBeenCalled();
+ });
+
it("creates fork selection requests from newest message to oldest and responds with selected entry", async () => {
const active = activeSession({
getUserMessagesForForking: vi.fn(() => [
diff --git a/src/server/sessions/sessionCommandService.ts b/src/server/sessions/sessionCommandService.ts
index 2c1f7b6..ad90f51 100644
--- a/src/server/sessions/sessionCommandService.ts
+++ b/src/server/sessions/sessionCommandService.ts
@@ -47,6 +47,12 @@ export interface CommandEventPublisher {
publishGlobal?(event: Extract): void;
}
+export interface SessionCommandLifecycle {
+ onCompactionStart?: (session: TSession) => void;
+ onCompactionEnd?: (session: TSession, result: "success" | "error", detail?: string) => void;
+ reloadSession?: (session: TSession) => Promise;
+}
+
export interface SessionCommandNaming {
listSessionNames?: (cwd: string) => Promise;
}
@@ -65,10 +71,7 @@ export class SessionCommandService,
private readonly prompt: (sessionId: string, text: string) => Promise,
private readonly events: CommandEventPublisher,
- private readonly lifecycle: {
- onCompactionStart?: (session: TSession) => void;
- onCompactionEnd?: (session: TSession, result: "success" | "error", detail?: string) => void;
- } = {},
+ private readonly lifecycle: SessionCommandLifecycle = {},
private readonly naming: SessionCommandNaming = {},
) {}
@@ -93,6 +96,7 @@ export class SessionCommandService {
+ if (sessionHasActiveWork(session)) return { type: "unsupported", message: "Cannot reload while the session is active. Stop current activity before reloading." };
+ if (this.lifecycle.reloadSession === undefined) return { type: "unsupported", message: "/reload is not available for this session runtime." };
+
+ try {
+ await this.lifecycle.reloadSession(session);
+ } catch (error: unknown) {
+ const message = error instanceof Error ? error.message : String(error);
+ return { type: "unsupported", message: `Reload failed: ${message}` };
+ }
+ return { type: "done", message: "Session runtime resources reloaded. Extensions, skills, prompt templates, themes, and context/system prompt files are refreshed for this session. Reload the browser page separately for PI WEB browser plugin changes." };
+ }
+
private async clone(active: CommandActiveSession): Promise {
if (sessionHasActiveWork(active.runtime.session)) return forkActiveUnsupported("clone");
const leafId = active.runtime.session.sessionManager.getLeafId();