| Agent state directory |
agent.dir |
- PI_WEB_AGENT_DIR, PI_CODING_AGENT_DIR, OMP_CODING_AGENT_DIR |
+ PI_WEB_AGENT_DIR, PI_CODING_AGENT_DIR |
Global/session daemon |
Not supported locally |
Restart session daemon; affects auth, models, settings, and sessions |
@@ -394,7 +393,7 @@
| Agent session storage directory |
— |
- PI_WEB_AGENT_SESSION_DIR, PI_CODING_AGENT_SESSION_DIR, OMP_CODING_AGENT_SESSION_DIR |
+ PI_WEB_AGENT_SESSION_DIR, PI_CODING_AGENT_SESSION_DIR |
Session daemon env |
Not supported locally |
Restart session daemon; env-only session storage override |
@@ -402,7 +401,7 @@
| Agent config directory |
— |
- PI_WEB_AGENT_DIR, PI_CODING_AGENT_DIR, OMP_CODING_AGENT_DIR |
+ PI_WEB_AGENT_DIR, PI_CODING_AGENT_DIR |
Web/API + session daemon env |
Not supported locally |
Restart services |
@@ -448,34 +447,41 @@
Agent runtime
agent.command controls which Pi-compatible CLI PI WEB checks in doctor/status/update flows.
- It defaults to pi; set it to omp when this machine should use Oh My Pi.
+ It defaults to pi. Set it only when diagnostics and package-managed update checks should target
+ another compatible command; the embedded session runtime still uses PI WEB's SDK integration.
agent.dir controls which compatible agent state directory PI WEB reads for auth providers,
- model settings, settings, and session metadata. It defaults to the selected agent's conventional
- directory (~/.pi/agent for pi, ~/.omp/agent for omp).
+ model settings, settings, and session metadata. It defaults to ~/.pi/agent. Set it to another
+ Pi-compatible state directory when you want an isolated profile or an alternate compatible agent's data.
{
"agent": {
- "command": "omp",
- "dir": "~/.omp/agent"
+ "command": "pi",
+ "dir": "~/agent-profiles/research"
}
}
- Environment variables take precedence over the config file. PI_WEB_AGENT_COMMAND selects the
- command, PI_WEB_AGENT_DIR sets the state directory for any command, and command-specific
- variables such as OMP_CODING_AGENT_DIR are honored when the selected command is omp.
+ For example, an Oh My Pi profile can set agent.command to omp and
+ agent.dir to ~/.omp/agent.
- Session directory overrides are environment-only. Set PI_WEB_AGENT_SESSION_DIR or the selected
- command's session variable (for example OMP_CODING_AGENT_SESSION_DIR) when you need to override
- session storage separately from agent.dir.
+ Environment variables take precedence over the config file. PI_WEB_AGENT_COMMAND selects the
+ command, PI_WEB_AGENT_DIR sets the state directory for any command, and
+ PI_WEB_AGENT_SESSION_DIR overrides session storage separately from agent.dir.
+ Existing Pi Coding Agent env names (PI_CODING_AGENT_DIR and
+ PI_CODING_AGENT_SESSION_DIR) remain supported for compatibility.
+
+
+ Session directory overrides are environment-only; use PI_WEB_AGENT_SESSION_DIR unless you need
+ the legacy Pi-compatible PI_CODING_AGENT_SESSION_DIR name.
Restart the session daemon after changing agent settings. The web/API process can display the new config
- immediately, but active session runtime ownership is intentionally long-lived.
+ immediately, and status/plugin discovery may re-read it on later requests, but active session runtime
+ ownership is intentionally long-lived.
diff --git a/docs/config.md b/docs/config.md
index fe9ba72..22429fc 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -25,7 +25,7 @@ defaults → global config file → environment overrides
Supported project-local settings are then applied for that project's workspaces. For upload defaults, `/.pi-web/config.json` overrides the global value.
-Environment overrides include `PI_WEB_HOST`, `PI_WEB_PORT` / `PORT`, `PI_WEB_ALLOWED_HOSTS`, `PI_WEB_MAX_UPLOAD_BYTES`, `PI_WEB_AGENT_COMMAND`, `PI_WEB_AGENT_DIR`, `PI_WEB_AGENT_SESSION_DIR`, `PI_CODING_AGENT_DIR`, `PI_CODING_AGENT_SESSION_DIR`, `OMP_CODING_AGENT_DIR`, `OMP_CODING_AGENT_SESSION_DIR`, `PI_WEB_SPAWN_SESSIONS`, and `PI_WEB_SUBSESSIONS`.
+Environment overrides include `PI_WEB_HOST`, `PI_WEB_PORT` / `PORT`, `PI_WEB_ALLOWED_HOSTS`, `PI_WEB_MAX_UPLOAD_BYTES`, `PI_WEB_AGENT_COMMAND`, `PI_WEB_AGENT_DIR`, `PI_WEB_AGENT_SESSION_DIR`, `PI_CODING_AGENT_DIR`, `PI_CODING_AGENT_SESSION_DIR`, `PI_WEB_SPAWN_SESSIONS`, and `PI_WEB_SUBSESSIONS`.
Process restarts depend on the key:
@@ -51,8 +51,8 @@ Process restarts depend on the key:
},
"maxUploadBytes": 67108864,
"agent": {
- "command": "omp",
- "dir": "~/.omp/agent"
+ "command": "pi",
+ "dir": "~/agent-profiles/research"
},
"spawnSessions": true,
"subsessions": false,
@@ -104,7 +104,7 @@ Rows with JSON key `—` are runtime-only environment variables, not config-file
| Manual file upload default folder | `uploads.defaultFolder` | — | Global + project | **Overrides**: project value wins for workspaces in that project; otherwise global/default applies | New Upload dialogs and direct drag/drop batches after config/workspace refresh |
| Upload/body limit | `maxUploadBytes` | `PI_WEB_MAX_UPLOAD_BYTES` | Global | Not supported locally | Restart web/API and session daemon |
| Agent CLI command | `agent.command` | `PI_WEB_AGENT_COMMAND` | Global/session daemon | Not supported locally | Restart session daemon; affects doctor/status/update checks |
-| Agent state directory | `agent.dir` | `PI_WEB_AGENT_DIR`, `PI_CODING_AGENT_DIR`, `OMP_CODING_AGENT_DIR` | Global/session daemon | Not supported locally | Restart session daemon; affects auth, models, settings, and sessions |
+| Agent state directory | `agent.dir` | `PI_WEB_AGENT_DIR`, `PI_CODING_AGENT_DIR` | Global/session daemon | Not supported locally | Restart session daemon; affects auth, models, settings, and sessions |
| Agent can spawn sessions | `spawnSessions` | `PI_WEB_SPAWN_SESSIONS` | Global/session daemon | Not supported locally | Restart session daemon |
| Tracked subsessions (beta) | `subsessions` | `PI_WEB_SUBSESSIONS` | Global/session daemon | Not supported locally; also requires `spawnSessions` | Restart session daemon |
| Plugin enablement/settings | `plugins..enabled`, `plugins..settings` | — | Global | Not core local config; plugins may read their own project files | Reload browser tab |
@@ -119,8 +119,8 @@ Rows with JSON key `—` are runtime-only environment variables, not config-file
| Web-to-daemon URL | — | `PI_WEB_SESSIOND_URL` | Web/API env | Not supported locally | Restart web/API |
| Projects storage file | — | `PI_WEB_PROJECTS_FILE` | Web/API + session daemon env | Not supported locally | Restart services; advanced state override |
| Remote machines storage file | — | `PI_WEB_MACHINES_FILE` | Web/API env | Not supported locally | Restart web/API; advanced state override |
-| Agent session storage directory | — | `PI_WEB_AGENT_SESSION_DIR`, `PI_CODING_AGENT_SESSION_DIR`, `OMP_CODING_AGENT_SESSION_DIR` | Session daemon env | Not supported locally | Restart session daemon; env-only session storage override |
-| Agent config directory | — | `PI_WEB_AGENT_DIR`, `PI_CODING_AGENT_DIR`, `OMP_CODING_AGENT_DIR` | Web/API + session daemon env | Not supported locally | Restart services |
+| Agent session storage directory | — | `PI_WEB_AGENT_SESSION_DIR`, `PI_CODING_AGENT_SESSION_DIR` | Session daemon env | Not supported locally | Restart session daemon; env-only session storage override |
+| Agent config directory | — | `PI_WEB_AGENT_DIR`, `PI_CODING_AGENT_DIR` | Web/API + session daemon env | Not supported locally | Restart services |
| Skip update checks | — | `PI_WEB_SKIP_VERSION_CHECK`, `PI_WEB_OFFLINE`, `PI_SKIP_VERSION_CHECK`, `PI_OFFLINE` | Web/API env | Not supported locally | Restart web/API after env changes |
## Key details
@@ -168,24 +168,26 @@ The per-request size limit is still controlled by `maxUploadBytes` / `PI_WEB_MAX
### Agent runtime selection
-`agent.command` controls which Pi-compatible CLI PI WEB checks in doctor/status/update flows. It defaults to `pi`; set it to `omp` when this machine should use Oh My Pi.
+`agent.command` controls which Pi-compatible CLI PI WEB checks in doctor/status/update flows. It defaults to `pi`. Set it only when diagnostics and package-managed update checks should target another compatible command; the embedded session runtime still uses PI WEB's SDK integration.
-`agent.dir` controls which compatible agent state directory PI WEB reads for auth providers, model settings, settings, and session metadata. It defaults to the selected agent's conventional directory (`~/.pi/agent` for `pi`, `~/.omp/agent` for `omp`).
+`agent.dir` controls which compatible agent state directory PI WEB reads for auth providers, model settings, settings, and session metadata. It defaults to `~/.pi/agent`. Set it to another Pi-compatible state directory when you want an isolated profile or an alternate compatible agent's data.
```json
{
"agent": {
- "command": "omp",
- "dir": "~/.omp/agent"
+ "command": "pi",
+ "dir": "~/agent-profiles/research"
}
}
```
-Environment variables take precedence over the config file. `PI_WEB_AGENT_COMMAND` selects the command, `PI_WEB_AGENT_DIR` sets the state directory for any command, and command-specific variables such as `OMP_CODING_AGENT_DIR` are honored when the selected command is `omp`.
+For example, an Oh My Pi profile can set `agent.command` to `omp` and `agent.dir` to `~/.omp/agent`.
-Session directory overrides are environment-only. Set `PI_WEB_AGENT_SESSION_DIR` or the selected command's session variable (for example `OMP_CODING_AGENT_SESSION_DIR`) when you need to override session storage separately from `agent.dir`.
+Environment variables take precedence over the config file. `PI_WEB_AGENT_COMMAND` selects the command, `PI_WEB_AGENT_DIR` sets the state directory for any command, and `PI_WEB_AGENT_SESSION_DIR` overrides session storage separately from `agent.dir`. Existing Pi Coding Agent env names (`PI_CODING_AGENT_DIR` and `PI_CODING_AGENT_SESSION_DIR`) remain supported for compatibility.
-Restart the session daemon after changing agent settings. The web/API process can display the new config immediately, but active session runtime ownership is intentionally long-lived.
+Session directory overrides are environment-only; use `PI_WEB_AGENT_SESSION_DIR` unless you need the legacy Pi-compatible `PI_CODING_AGENT_SESSION_DIR` name.
+
+Restart the session daemon after changing agent settings. The web/API process can display the new config immediately, and status/plugin discovery may re-read it on later requests, but active session runtime ownership is intentionally long-lived.
### Session daemon tools
diff --git a/src/client/src/api/parsers.test.ts b/src/client/src/api/parsers.test.ts
index 038d476..64273ff 100644
--- a/src/client/src/api/parsers.test.ts
+++ b/src/client/src/api/parsers.test.ts
@@ -7,14 +7,14 @@ describe("API parsers", () => {
expect(parsePiWebConfigResponse({
path: "/tmp/config.json",
exists: true,
- config: { host: "0.0.0.0", port: 8504, allowedHosts: ["example.local"], shortcuts: { "core:view.chat": "mod+1", "core:session.stop": null }, plugins: { info: { enabled: false, settings: { compact: true } } }, pathAccess: { allowedPaths: ["/tmp"] }, uploads: { defaultFolder: "manual/uploads" }, maxUploadBytes: 1234, agent: { command: "omp", dir: "~/.omp/agent" } },
- effectiveConfig: { host: "127.0.0.1", port: 8504, allowedHosts: true, pathAccess: { allowedPaths: ["/tmp"] }, uploads: { defaultFolder: ".pi-web/uploads" }, agent: { command: "omp", dir: "/Users/dev/.omp/agent" } },
+ config: { host: "0.0.0.0", port: 8504, allowedHosts: ["example.local"], shortcuts: { "core:view.chat": "mod+1", "core:session.stop": null }, plugins: { info: { enabled: false, settings: { compact: true } } }, pathAccess: { allowedPaths: ["/tmp"] }, uploads: { defaultFolder: "manual/uploads" }, maxUploadBytes: 1234, agent: { command: "agent-lab", dir: "~/agent-profiles/lab" } },
+ effectiveConfig: { host: "127.0.0.1", port: 8504, allowedHosts: true, pathAccess: { allowedPaths: ["/tmp"] }, uploads: { defaultFolder: ".pi-web/uploads" }, agent: { command: "agent-lab", dir: "/Users/dev/agent-profiles/lab" } },
envOverrides: { host: true, port: false, allowedHosts: false, spawnSessions: false, subsessions: false, agentCommand: false, agentDir: true, agentSessionDir: false },
})).toEqual({
path: "/tmp/config.json",
exists: true,
- config: { host: "0.0.0.0", port: 8504, allowedHosts: ["example.local"], shortcuts: { "core:view.chat": "mod+1", "core:session.stop": null }, plugins: { info: { enabled: false, settings: { compact: true } } }, pathAccess: { allowedPaths: ["/tmp"] }, uploads: { defaultFolder: "manual/uploads" }, maxUploadBytes: 1234, agent: { command: "omp", dir: "~/.omp/agent" } },
- effectiveConfig: { host: "127.0.0.1", port: 8504, allowedHosts: true, pathAccess: { allowedPaths: ["/tmp"] }, uploads: { defaultFolder: ".pi-web/uploads" }, agent: { command: "omp", dir: "/Users/dev/.omp/agent" } },
+ config: { host: "0.0.0.0", port: 8504, allowedHosts: ["example.local"], shortcuts: { "core:view.chat": "mod+1", "core:session.stop": null }, plugins: { info: { enabled: false, settings: { compact: true } } }, pathAccess: { allowedPaths: ["/tmp"] }, uploads: { defaultFolder: "manual/uploads" }, maxUploadBytes: 1234, agent: { command: "agent-lab", dir: "~/agent-profiles/lab" } },
+ effectiveConfig: { host: "127.0.0.1", port: 8504, allowedHosts: true, pathAccess: { allowedPaths: ["/tmp"] }, uploads: { defaultFolder: ".pi-web/uploads" }, agent: { command: "agent-lab", dir: "/Users/dev/agent-profiles/lab" } },
envOverrides: { host: true, port: false, allowedHosts: false, spawnSessions: false, subsessions: false, agentCommand: false, agentDir: true, agentSessionDir: false },
});
});
diff --git a/src/client/src/components/settings/SettingsSessiondPanel.ts b/src/client/src/components/settings/SettingsSessiondPanel.ts
index 98824c0..4ac2e31 100644
--- a/src/client/src/components/settings/SettingsSessiondPanel.ts
+++ b/src/client/src/components/settings/SettingsSessiondPanel.ts
@@ -54,7 +54,7 @@ export class SettingsSessiondPanel extends LitElement {
?disabled=${this.loading || this.saving || agentCommandOverridden}
@change=${(event: Event) => { void this.saveAgentField("command", event); }}
>
- Use omp to make doctor/update checks target Oh My Pi. The embedded session runtime remains PI WEB's SDK path, so this does not dynamically load a different agent implementation.
+ Set an alternate Pi-compatible CLI when doctor/update checks should target a different command. The embedded session runtime remains PI WEB's SDK path, so this does not dynamically load a different agent implementation.
@@ -67,11 +67,11 @@ export class SettingsSessiondPanel extends LitElement {
autocomplete="off"
spellcheck="false"
.value=${config?.config.agent?.dir ?? ""}
- placeholder="~/.pi/agent or ~/.omp/agent"
+ placeholder="~/.pi/agent or ~/agent-profiles/work"
?disabled=${this.loading || this.saving || agentDirOverridden}
@change=${(event: Event) => { void this.saveAgentField("dir", event); }}
>
- Choose which compatible auth, models, settings, and sessions PI WEB reads. For OMP, set this to ~/.omp/agent, then restart the session daemon.
+ Choose which compatible auth, models, settings, and sessions PI WEB reads. Set a separate directory for isolated agent profiles, then restart the session daemon.
diff --git a/src/client/src/components/settings/settingsConfigDraft.test.ts b/src/client/src/components/settings/settingsConfigDraft.test.ts
index d177add..0b05679 100644
--- a/src/client/src/components/settings/settingsConfigDraft.test.ts
+++ b/src/client/src/components/settings/settingsConfigDraft.test.ts
@@ -20,7 +20,7 @@ describe("settings config drafts", () => {
allowedHostsMode: "list",
allowedHostsText: "example.local, 192.168.1.20\n",
allowedPathsText: "/tmp\n~/SDKs\n",
- }, { shortcuts: { "core:view.chat": "mod+1", "core:session.stop": null }, plugins: { info: { enabled: false } }, pathAccess: { allowedPaths: ["/old"] }, uploads: { defaultFolder: "manual/uploads" }, maxUploadBytes: 1234, agent: { command: "omp", dir: "~/.omp/agent" } })).toEqual({
+ }, { shortcuts: { "core:view.chat": "mod+1", "core:session.stop": null }, plugins: { info: { enabled: false } }, pathAccess: { allowedPaths: ["/old"] }, uploads: { defaultFolder: "manual/uploads" }, maxUploadBytes: 1234, agent: { command: "agent-lab", dir: "~/agent-profiles/lab" } })).toEqual({
host: "127.0.0.1",
port: 9000,
allowedHosts: ["example.local", "192.168.1.20"],
@@ -29,7 +29,7 @@ describe("settings config drafts", () => {
pathAccess: { allowedPaths: ["/tmp", "~/SDKs"] },
uploads: { defaultFolder: "manual/uploads" },
maxUploadBytes: 1234,
- agent: { command: "omp", dir: "~/.omp/agent" },
+ agent: { command: "agent-lab", dir: "~/agent-profiles/lab" },
});
});
diff --git a/src/config.test.ts b/src/config.test.ts
index 8cf115d..a1352b7 100644
--- a/src/config.test.ts
+++ b/src/config.test.ts
@@ -2,7 +2,7 @@ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
-import { DEFAULT_MAX_UPLOAD_BYTES, DEFAULT_UPLOADS_FOLDER, effectiveAgentConfig, effectivePiWebConfig, loadPiWebConfig, maxUploadBytes, savePiWebConfig, spawnSessionsEnabled, subsessionsEnabled } from "./config.js";
+import { DEFAULT_MAX_UPLOAD_BYTES, DEFAULT_UPLOADS_FOLDER, agentSessionDirEnvKeys, effectiveAgentConfig, effectivePiWebConfig, hasAgentDirEnvOverride, hasAgentSessionDirEnvOverride, loadPiWebConfig, maxUploadBytes, savePiWebConfig, spawnSessionsEnabled, subsessionsEnabled } from "./config.js";
let tempDir: string;
let configPath: string;
@@ -55,43 +55,61 @@ describe("PI WEB config persistence", () => {
expect(loadPiWebConfig(testOptions()).config.agent).toEqual({ command: "omp", dir: "~/.omp/agent" });
});
- it("resolves OMP agent defaults from the configured command", () => {
+ it("keeps the Pi agent directory default for alternate commands", () => {
expect(effectiveAgentConfig({ HOME: join(tempDir, ".home") }, { agent: { command: "omp" } })).toMatchObject({
command: "omp",
- dir: join(tempDir, ".home", ".omp", "agent"),
- sessionDirEnvKeys: ["PI_WEB_AGENT_SESSION_DIR", "OMP_CODING_AGENT_SESSION_DIR", "PI_CODING_AGENT_SESSION_DIR"],
+ dir: join(tempDir, ".home", ".pi", "agent"),
+ sessionDirEnvKeys: ["PI_WEB_AGENT_SESSION_DIR", "PI_CODING_AGENT_SESSION_DIR"],
});
});
- it("lets PI WEB agent environment overrides take precedence", () => {
+ it("resolves explicit alternate agent command and state directory settings", () => {
+ expect(effectiveAgentConfig({ HOME: join(tempDir, ".home") }, { agent: { command: "omp", dir: "~/.omp/agent" } })).toMatchObject({
+ command: "omp",
+ dir: join(tempDir, ".home", ".omp", "agent"),
+ });
+ });
+
+ it("ignores empty agent environment overrides", () => {
+ const env = {
+ HOME: join(tempDir, ".home"),
+ PI_WEB_AGENT_COMMAND: "",
+ PI_WEB_AGENT_DIR: "",
+ PI_WEB_AGENT_SESSION_DIR: "",
+ PI_CODING_AGENT_DIR: "",
+ PI_CODING_AGENT_SESSION_DIR: "",
+ };
+
+ expect(effectiveAgentConfig(env, { agent: { command: "omp", dir: "~/.omp/agent" } })).toMatchObject({
+ command: "omp",
+ dir: join(tempDir, ".home", ".omp", "agent"),
+ });
+ expect(hasAgentDirEnvOverride(env)).toBe(false);
+ expect(hasAgentSessionDirEnvOverride(env)).toBe(false);
+ });
+
+ it("uses generic agent directory env precedence and Pi compatibility fallback", () => {
expect(effectiveAgentConfig({
PI_WEB_AGENT_COMMAND: "omp",
- PI_WEB_AGENT_DIR: join(tempDir, "env-agent"),
+ PI_WEB_AGENT_DIR: join(tempDir, "web-env-agent"),
+ PI_CODING_AGENT_DIR: join(tempDir, "pi-env-agent"),
}, { agent: { command: "pi", dir: join(tempDir, "config-agent") } })).toMatchObject({
command: "omp",
- dir: join(tempDir, "env-agent"),
+ dir: join(tempDir, "web-env-agent"),
+ });
+
+ expect(effectiveAgentConfig({
+ PI_CODING_AGENT_DIR: join(tempDir, "pi-env-agent"),
+ }, { agent: { dir: join(tempDir, "config-agent") } })).toMatchObject({
+ dir: join(tempDir, "pi-env-agent"),
});
});
- it("lets command-specific agent environment directories override config", () => {
- expect(effectiveAgentConfig({
- HOME: join(tempDir, ".home"),
- OMP_CODING_AGENT_DIR: join(tempDir, "omp-env-agent"),
- }, { agent: { command: "omp", dir: join(tempDir, "config-agent") } })).toMatchObject({
- command: "omp",
- dir: join(tempDir, "omp-env-agent"),
- });
- });
+ it("does not generate command-specific session directory env keys", () => {
+ const keys = ["PI_WEB_AGENT_SESSION_DIR", "PI_CODING_AGENT_SESSION_DIR"];
- it("normalizes omp.exe to OMP environment keys", () => {
- expect(effectiveAgentConfig({
- HOME: join(tempDir, ".home"),
- OMP_CODING_AGENT_DIR: join(tempDir, "omp-exe-env-agent"),
- }, { agent: { command: "omp.exe", dir: join(tempDir, "config-agent") } })).toMatchObject({
- command: "omp.exe",
- dir: join(tempDir, "omp-exe-env-agent"),
- sessionDirEnvKeys: ["PI_WEB_AGENT_SESSION_DIR", "OMP_CODING_AGENT_SESSION_DIR", "PI_CODING_AGENT_SESSION_DIR"],
- });
+ expect(agentSessionDirEnvKeys()).toEqual(keys);
+ expect(effectiveAgentConfig({ HOME: join(tempDir, ".home"), PI_WEB_AGENT_COMMAND: "omp" }).sessionDirEnvKeys).toEqual(keys);
});
it("exposes the default upload folder in the effective config", () => {
diff --git a/src/config.ts b/src/config.ts
index 8754c01..b775191 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -49,26 +49,25 @@ export interface EffectivePiWebAgentConfig {
}
export function effectiveAgentConfig(env: NodeJS.ProcessEnv = process.env, config: Pick = {}, cwd = process.cwd()): EffectivePiWebAgentConfig {
- const command = parseAgentCommand(env[PI_WEB_AGENT_COMMAND_ENV] ?? config.agent?.command ?? DEFAULT_AGENT_COMMAND, "agent.command", "environment");
- const commandDirEnv = commandAgentDirEnv(command);
- const configuredDir = env[PI_WEB_AGENT_DIR_ENV] ?? env[commandDirEnv] ?? config.agent?.dir ?? defaultAgentDirForCommand(command, env);
+ const command = parseAgentCommand(envValue(env, PI_WEB_AGENT_COMMAND_ENV) ?? config.agent?.command ?? DEFAULT_AGENT_COMMAND, "agent.command", "environment");
+ const configuredDir = envValue(env, PI_WEB_AGENT_DIR_ENV) ?? envValue(env, PI_CODING_AGENT_DIR_ENV) ?? config.agent?.dir ?? defaultAgentDir(env);
return {
command,
dir: resolveAgentDirPath(configuredDir, env, cwd, "agent.dir", "environment"),
- sessionDirEnvKeys: agentSessionDirEnvKeys(command),
+ sessionDirEnvKeys: agentSessionDirEnvKeys(),
};
}
-export function agentSessionDirEnvKeys(command = DEFAULT_AGENT_COMMAND): string[] {
- return uniqueStrings([PI_WEB_AGENT_SESSION_DIR_ENV, commandSessionDirEnv(command), PI_CODING_AGENT_SESSION_DIR_ENV]);
+export function agentSessionDirEnvKeys(): string[] {
+ return uniqueStrings([PI_WEB_AGENT_SESSION_DIR_ENV, PI_CODING_AGENT_SESSION_DIR_ENV]);
}
-export function hasAgentDirEnvOverride(env: NodeJS.ProcessEnv, command = DEFAULT_AGENT_COMMAND): boolean {
- return isEnvSet(env[PI_WEB_AGENT_DIR_ENV]) || isEnvSet(env[commandAgentDirEnv(command)]);
+export function hasAgentDirEnvOverride(env: NodeJS.ProcessEnv): boolean {
+ return isEnvSet(env[PI_WEB_AGENT_DIR_ENV]) || isEnvSet(env[PI_CODING_AGENT_DIR_ENV]);
}
-export function hasAgentSessionDirEnvOverride(env: NodeJS.ProcessEnv, command = DEFAULT_AGENT_COMMAND): boolean {
- return agentSessionDirEnvKeys(command).some((key) => isEnvSet(env[key]));
+export function hasAgentSessionDirEnvOverride(env: NodeJS.ProcessEnv): boolean {
+ return agentSessionDirEnvKeys().some((key) => isEnvSet(env[key]));
}
export function effectiveUploadsConfig(config: Pick = {}): NonNullable {
@@ -109,7 +108,10 @@ export function loadPiWebConfig(options: LoadOptions = {}): LoadedPiWebConfig {
}
export function effectivePiWebConfig(options: LoadOptions = {}): LoadedPiWebConfig {
- const loaded = loadPiWebConfig(options);
+ return resolveEffectivePiWebConfig(loadPiWebConfig(options), options);
+}
+
+export function resolveEffectivePiWebConfig(loaded: LoadedPiWebConfig, options: LoadOptions = {}): LoadedPiWebConfig {
const env = options.env ?? process.env;
const host = env["PI_WEB_HOST"];
const port = env["PI_WEB_PORT"] ?? env["PORT"];
@@ -333,28 +335,13 @@ function expandHomePath(value: string, env: NodeJS.ProcessEnv): string {
return value;
}
-function defaultAgentDirForCommand(command: string, env: NodeJS.ProcessEnv): string {
- return expandHomePath(isOmpCommand(command) ? "~/.omp/agent" : "~/.pi/agent", env);
+function defaultAgentDir(env: NodeJS.ProcessEnv): string {
+ return expandHomePath("~/.pi/agent", env);
}
-function commandAgentDirEnv(command: string): string {
- const prefix = agentEnvPrefix(command);
- return prefix === "PI" ? PI_CODING_AGENT_DIR_ENV : `${prefix}_CODING_AGENT_DIR`;
-}
-
-function commandSessionDirEnv(command: string): string {
- return `${agentEnvPrefix(command)}_CODING_AGENT_SESSION_DIR`;
-}
-
-function agentEnvPrefix(command: string): string {
- const name = command.split(/[\\/]/u).at(-1) ?? command;
- const normalized = name.replace(/(?:\.[cm]?js|\.exe)$/iu, "").replace(/[^A-Za-z0-9]+/gu, "_").replace(/^_+|_+$/gu, "").toUpperCase();
- return normalized === "" ? "PI" : normalized;
-}
-
-function isOmpCommand(command: string): boolean {
- const name = command.split(/[\\/]/u).at(-1)?.toLowerCase();
- return name === "omp" || name === "omp.exe";
+function envValue(env: NodeJS.ProcessEnv, key: string): string | undefined {
+ const value = env[key];
+ return value !== undefined && value !== "" ? value : undefined;
}
function isEnvSet(value: string | undefined): boolean {
diff --git a/src/server/app.test.ts b/src/server/app.test.ts
index 808b6fb..abbb728 100644
--- a/src/server/app.test.ts
+++ b/src/server/app.test.ts
@@ -15,7 +15,7 @@ import type { SessionProxyDaemon } from "./sessiond/sessionProxyRoutes.js";
import { PI_WEB_CAPABILITIES } from "../shared/capabilities.js";
import { machineScopedPluginId } from "../shared/machinePluginIds.js";
import { MAX_IMAGE_PREVIEW_BYTES } from "../shared/workspaceFiles.js";
-import type { PiWebConfigResponse, PiWebConfigValues } from "../shared/apiTypes.js";
+import type { PiWebConfigResponse, PiWebConfigValues, PiWebStatusResponse } from "../shared/apiTypes.js";
import type { Project, Workspace } from "./types.js";
let app: FastifyInstance;
@@ -556,6 +556,35 @@ describe("buildApp", () => {
]);
});
+ it("uses the latest configured agent dir for PI WEB status after config writes", async () => {
+ const originalSkipVersionCheck = process.env["PI_WEB_SKIP_VERSION_CHECK"];
+ process.env["PI_WEB_SKIP_VERSION_CHECK"] = "1";
+ try {
+ const initialAgentDir = join(tempDir, "initial-agent");
+ const updatedAgentDir = join(tempDir, "updated-agent");
+ piWebConfig = { agent: { command: "pi", dir: initialAgentDir } };
+ await mkdir(initialAgentDir, { recursive: true });
+ await installConfiguredPiWebPackage(updatedAgentDir);
+
+ const initialStatus = await app.inject({ method: "GET", url: "/api/pi-web/status" });
+ expect(initialStatus.statusCode).toBe(200);
+
+ const updateResponse = await app.inject({
+ method: "PUT",
+ url: "/api/config",
+ payload: { config: { agent: { command: "pi", dir: updatedAgentDir } } },
+ });
+ expect(updateResponse.statusCode).toBe(200);
+
+ const refreshedStatus = await app.inject({ method: "GET", url: "/api/pi-web/status" });
+
+ expect(refreshedStatus.statusCode).toBe(200);
+ expect(refreshedStatus.json().components.web.installation).toMatchObject({ kind: "pi-package", source: process.cwd(), scope: "user" });
+ } finally {
+ restoreEnv("PI_WEB_SKIP_VERSION_CHECK", originalSkipVersionCheck);
+ }
+ });
+
it("serves supported workspace images as previews", async () => {
const addResponse = await app.inject({
method: "POST",
@@ -920,6 +949,16 @@ function fakeSessionDaemon(): SessionProxyDaemon {
};
}
+async function installConfiguredPiWebPackage(agentDir: string): Promise {
+ await mkdir(agentDir, { recursive: true });
+ await writeFile(join(agentDir, "settings.json"), `${JSON.stringify({ packages: [process.cwd()] }, null, 2)}\n`, "utf8");
+}
+
+function restoreEnv(key: string, value: string | undefined): void {
+ if (value === undefined) Reflect.deleteProperty(process.env, key);
+ else process.env[key] = value;
+}
+
function fakeRemoteClient(overrides: Partial): MachineClient {
return {
request: () => Promise.resolve({ statusCode: 200, headers: {}, body: Readable.from([]) }),
diff --git a/src/server/app.ts b/src/server/app.ts
index 9860f71..1b81d2b 100644
--- a/src/server/app.ts
+++ b/src/server/app.ts
@@ -20,9 +20,9 @@ import { registerTerminalProxyRoutes } from "./terminalProxyRoutes.js";
import { registerWorkspaceDeletionRoutes } from "./workspaces/workspaceDeletionRoutes.js";
import { createFilePiWebConfigService, registerConfigRoutes, type PiWebConfigService } from "./configRoutes.js";
import { PiWebPluginService } from "./piWebPluginService.js";
-import { createPiWebStatusCache } from "./piWebStatusCache.js";
+import { createPiWebStatusCache, type PiWebStatusCache } from "./piWebStatusCache.js";
import { getPiWebRuntime, getPiWebStatus, getPiWebVersionStatus } from "./piWebStatus.js";
-import { effectiveAgentConfig, effectivePiWebConfig } from "../config.js";
+import { effectiveAgentConfig, type EffectivePiWebAgentConfig } from "../config.js";
import { MachineService } from "./machines/machineService.js";
import { registerMachineRoutes } from "./machines/machineRoutes.js";
import { registerMachineProxyRoutes } from "./machines/machineProxyRoutes.js";
@@ -116,17 +116,42 @@ function registerLocalFileSuggestionRoutes(app: FastifyInstance, projects: Proje
});
}
+async function readEffectiveConfig(config: Pick) {
+ return (await config.read()).effectiveConfig;
+}
+
+async function readEffectiveAgentConfig(config: Pick): Promise {
+ return effectiveAgentConfig(process.env, await readEffectiveConfig(config));
+}
+
+function invalidatePiWebStatusOnWrite(config: PiWebConfigService, statusCache: Pick): PiWebConfigService {
+ return {
+ read: () => config.read(),
+ write: async (nextConfig) => {
+ const response = await config.write(nextConfig);
+ statusCache.invalidate();
+ return response;
+ },
+ };
+}
+
export async function buildApp(deps: AppDependencies = {}): Promise {
const app = Fastify({ logger: deps.logger ?? true, ...(deps.bodyLimit === undefined ? {} : { bodyLimit: deps.bodyLimit }) });
await app.register(fastifyWebsocket);
const projects = deps.projects ?? new ProjectService(new ProjectStore());
const workspaces = deps.workspaces ?? new WorkspaceService();
- const agent = effectiveAgentConfig(process.env, effectivePiWebConfig().config);
- const piWebPlugins = deps.piWebPlugins ?? new PiWebPluginService({ agentDir: agent.dir });
const configService = deps.config ?? createFilePiWebConfigService();
+ const readConfig = () => readEffectiveConfig(configService);
+ const readAgentConfig = () => readEffectiveAgentConfig(configService);
+ const piWebPlugins = deps.piWebPlugins ?? new PiWebPluginService({
+ configProvider: readConfig,
+ });
const sessionDaemon = deps.sessionDaemon ?? new SessionDaemonClient();
- const piWebStatusCache = createPiWebStatusCache(() => getPiWebStatus(sessionDaemon, { agentCommand: agent.command, agentDir: agent.dir }), {
+ const piWebStatusCache = createPiWebStatusCache(async () => {
+ const agent = await readAgentConfig();
+ return getPiWebStatus(sessionDaemon, { agentCommand: agent.command, agentDir: agent.dir });
+ }, {
onError: (error) => { app.log.warn({ err: error }, "failed to refresh PI WEB status cache"); },
});
const machines = deps.machines ?? new MachineService(undefined, {
@@ -144,10 +169,13 @@ export async function buildApp(deps: AppDependencies = {}): Promise piWebStatusCache.get());
- app.get("/api/pi-web/version", async () => getPiWebVersionStatus(sessionDaemon, { agentCommand: agent.command, agentDir: agent.dir }));
+ app.get("/api/pi-web/version", async () => {
+ const agent = await readAgentConfig();
+ return getPiWebVersionStatus(sessionDaemon, { agentCommand: agent.command, agentDir: agent.dir });
+ });
app.get("/api/pi-web/runtime", async () => getPiWebRuntime(sessionDaemon));
app.get("/api/plugins", async () => piWebPlugins.plugins());
- registerConfigRoutes(app, configService);
+ registerConfigRoutes(app, invalidatePiWebStatusOnWrite(configService, piWebStatusCache));
registerMachineRoutes(app, machines);
registerMachinePluginProxyRoutes(app, machines);
diff --git a/src/server/configRoutes.test.ts b/src/server/configRoutes.test.ts
index 264805f..5b729c0 100644
--- a/src/server/configRoutes.test.ts
+++ b/src/server/configRoutes.test.ts
@@ -37,11 +37,11 @@ describe("config routes", () => {
const response = await app.inject({
method: "PUT",
url: "/api/config",
- payload: { config: { host: "0.0.0.0", port: 9000, allowedHosts: true, spawnSessions: true, subsessions: true, agent: { command: "omp", dir: "~/.omp/agent" }, shortcuts: { "core:view.chat": "mod+1", "core:session.stop": null }, plugins: { info: { enabled: false, settings: { note: "hidden" } } }, pathAccess: { allowedPaths: ["/tmp"] }, uploads: { defaultFolder: "uploads\\manual" }, maxUploadBytes: 1234 } },
+ payload: { config: { host: "0.0.0.0", port: 9000, allowedHosts: true, spawnSessions: true, subsessions: true, agent: { command: "agent-lab", dir: "~/agent-profiles/lab" }, shortcuts: { "core:view.chat": "mod+1", "core:session.stop": null }, plugins: { info: { enabled: false, settings: { note: "hidden" } } }, pathAccess: { allowedPaths: ["/tmp"] }, uploads: { defaultFolder: "uploads\\manual" }, maxUploadBytes: 1234 } },
});
expect(response.statusCode).toBe(200);
- expect(savedConfig).toEqual({ host: "0.0.0.0", port: 9000, allowedHosts: true, spawnSessions: true, subsessions: true, agent: { command: "omp", dir: "~/.omp/agent" }, shortcuts: { "core:view.chat": "mod+1", "core:session.stop": null }, plugins: { info: { enabled: false, settings: { note: "hidden" } } }, pathAccess: { allowedPaths: ["/tmp"] }, uploads: { defaultFolder: "uploads/manual" }, maxUploadBytes: 1234 });
+ expect(savedConfig).toEqual({ host: "0.0.0.0", port: 9000, allowedHosts: true, spawnSessions: true, subsessions: true, agent: { command: "agent-lab", dir: "~/agent-profiles/lab" }, shortcuts: { "core:view.chat": "mod+1", "core:session.stop": null }, plugins: { info: { enabled: false, settings: { note: "hidden" } } }, pathAccess: { allowedPaths: ["/tmp"] }, uploads: { defaultFolder: "uploads/manual" }, maxUploadBytes: 1234 });
expect(response.json().config).toEqual(savedConfig);
});
diff --git a/src/server/configRoutes.ts b/src/server/configRoutes.ts
index 89ef3f4..838e6ee 100644
--- a/src/server/configRoutes.ts
+++ b/src/server/configRoutes.ts
@@ -1,5 +1,5 @@
import type { FastifyInstance } from "fastify";
-import { effectiveAgentConfig, effectivePiWebConfig, hasAgentDirEnvOverride, hasAgentSessionDirEnvOverride, loadPiWebConfig, parseUploadsConfig, savePiWebConfig, type LoadOptions, type PiWebConfig } from "../config.js";
+import { hasAgentDirEnvOverride, hasAgentSessionDirEnvOverride, loadPiWebConfig, parseUploadsConfig, resolveEffectivePiWebConfig, savePiWebConfig, type LoadOptions, type PiWebConfig } from "../config.js";
import type { PiWebConfigEnvOverrides, PiWebConfigResponse, PiWebConfigValues } from "../shared/apiTypes.js";
import { isPiWebPluginId } from "../shared/pluginIds.js";
@@ -20,14 +20,14 @@ export function createFilePiWebConfigService(options: LoadOptions = {}): PiWebCo
export function currentPiWebConfigResponse(options: LoadOptions = {}): PiWebConfigResponse {
const loaded = loadPiWebConfig(options);
- const effective = effectivePiWebConfig(options);
+ const effective = resolveEffectivePiWebConfig(loaded, options);
const env = options.env ?? process.env;
return {
path: loaded.path,
exists: loaded.exists,
config: loaded.config,
effectiveConfig: effective.config,
- envOverrides: piWebConfigEnvOverrides(env, loaded.config),
+ envOverrides: piWebConfigEnvOverrides(env),
};
}
@@ -167,8 +167,7 @@ function parsePluginsRequest(value: unknown): NonNullable {
expect(manifest.plugins[0]?.module).toMatch(/^\/pi-web-plugins\/review\/dist\/review\.js\?v=\d+$/u);
});
+ it("uses the current config provider for Pi package plugin discovery", async () => {
+ const packageDir = join(tempDir, "pkg");
+ const initialAgentDir = join(tempDir, "initial-agent");
+ const updatedAgentDir = join(tempDir, "updated-agent");
+ let currentConfig = { agent: { dir: initialAgentDir } };
+ await writePlugin(packageDir, {
+ packageJson: { piWeb: { plugins: [{ id: "agent-package", module: "dist/plugin.js" }] } },
+ files: { "dist/plugin.js": "export default {};" },
+ });
+ await mkdir(initialAgentDir, { recursive: true });
+ await mkdir(updatedAgentDir, { recursive: true });
+ await writeFile(join(updatedAgentDir, "settings.json"), `${JSON.stringify({ packages: [packageDir] }, null, 2)}\n`, "utf8");
+ const service = new PiWebPluginService({ roots: [], cwd: tempDir, configProvider: () => currentConfig });
+
+ await expect(service.manifest()).resolves.toEqual({ plugins: [] });
+
+ currentConfig = { agent: { dir: updatedAgentDir } };
+
+ await expect(service.manifest()).resolves.toMatchObject({ plugins: [{ id: "agent-package", source: packageDir, scope: "user" }] });
+ });
+
it("discovers source checkout plugin packages without symlinks", async () => {
await mkdir(join(tempDir, "src", "server"), { recursive: true });
await writeFile(join(tempDir, "src", "server", "index.ts"), "export {};\n");
diff --git a/src/server/piWebPluginService.ts b/src/server/piWebPluginService.ts
index b952d53..17a8c6e 100644
--- a/src/server/piWebPluginService.ts
+++ b/src/server/piWebPluginService.ts
@@ -2,8 +2,8 @@ import { existsSync } from "node:fs";
import { readdir, readFile, realpath, stat } from "node:fs/promises";
import { dirname, join, relative, resolve, sep } from "node:path";
import { fileURLToPath } from "node:url";
-import { DefaultPackageManager, getAgentDir, SettingsManager } from "@earendil-works/pi-coding-agent";
-import { loadPiWebConfig, piWebDataDir, type PiWebConfig } from "../config.js";
+import { DefaultPackageManager, SettingsManager } from "@earendil-works/pi-coding-agent";
+import { effectiveAgentConfig, loadPiWebConfig, piWebDataDir, type PiWebConfig } from "../config.js";
import type { PiWebPluginInfo, PiWebPluginsResponse, PiWebPluginScope } from "../shared/apiTypes.js";
import { isPiWebPluginId } from "../shared/pluginIds.js";
@@ -46,8 +46,9 @@ interface PiWebPluginServiceOptions {
roots?: LocalPluginRoot[];
cwd?: string;
agentDir?: string;
+ agentDirProvider?: () => string | Promise;
packageProvider?: PiPackageProvider | false;
- configProvider?: () => PiWebConfig;
+ configProvider?: () => PiWebConfig | Promise;
}
interface LocalPluginRoot {
@@ -71,11 +72,12 @@ type ArraylessPluginRecord = Omit;
export class DefaultPiPackageProvider implements PiPackageProvider {
private readonly packageManager: DefaultPackageManager;
- constructor(cwd = process.cwd(), agentDir = getAgentDir()) {
+ constructor(cwd = process.cwd(), agentDir?: string) {
+ const resolvedAgentDir = agentDir ?? defaultAgentDirForCwd(cwd);
this.packageManager = new DefaultPackageManager({
cwd,
- agentDir,
- settingsManager: SettingsManager.create(cwd, agentDir),
+ agentDir: resolvedAgentDir,
+ settingsManager: SettingsManager.create(cwd, resolvedAgentDir),
});
}
@@ -88,16 +90,32 @@ export class DefaultPiPackageProvider implements PiPackageProvider {
}
}
+function defaultAgentDirForCwd(cwd: string): string {
+ return effectiveAgentConfig(process.env, loadPiWebConfig({ cwd }).config, cwd).dir;
+}
+
export class PiWebPluginService {
+ private readonly cwd: string;
private readonly roots: LocalPluginRoot[];
- private readonly packageProvider: PiPackageProvider | undefined;
- private readonly configProvider: () => PiWebConfig;
+ private readonly agentDir: string | undefined;
+ private readonly agentDirProvider: (() => string | Promise) | undefined;
+ private readonly packageProviderForAgentDir: ((agentDir: string) => PiPackageProvider) | undefined;
+ private readonly configProvider: () => PiWebConfig | Promise;
constructor(options: PiWebPluginServiceOptions = {}) {
const cwd = options.cwd ?? process.cwd();
- const agentDir = options.agentDir ?? getAgentDir();
+ this.cwd = cwd;
this.roots = options.roots ?? defaultPluginRoots(cwd);
- this.packageProvider = options.packageProvider === false ? undefined : options.packageProvider ?? new DefaultPiPackageProvider(cwd, agentDir);
+ this.agentDir = options.agentDir;
+ this.agentDirProvider = options.agentDirProvider;
+ const packageProvider = options.packageProvider;
+ if (packageProvider === false) {
+ this.packageProviderForAgentDir = undefined;
+ } else if (packageProvider !== undefined) {
+ this.packageProviderForAgentDir = () => packageProvider;
+ } else {
+ this.packageProviderForAgentDir = (agentDir) => new DefaultPiPackageProvider(cwd, agentDir);
+ }
this.configProvider = options.configProvider ?? (() => loadPiWebConfig({ cwd }).config);
}
@@ -110,7 +128,8 @@ export class PiWebPluginService {
}
async plugins(): Promise {
- const [plugins, config] = await Promise.all([this.discoverPlugins(), Promise.resolve(this.configProvider())]);
+ const config = await this.configProvider();
+ const plugins = await this.discoverPlugins(config);
return { plugins: plugins.map((plugin) => this.pluginInfo(plugin, config)) };
}
@@ -143,15 +162,28 @@ export class PiWebPluginService {
};
}
- private async discoverPlugins(): Promise {
+ private async discoverPlugins(config?: PiWebConfig): Promise {
const records = new Map();
for (const plugin of await this.discoverLocalPlugins()) addUnique(records, plugin);
- if (this.packageProvider !== undefined) {
- for (const plugin of await this.discoverPiPackagePlugins(this.packageProvider)) addUnique(records, plugin);
+ const packageProvider = await this.packageProvider(config);
+ if (packageProvider !== undefined) {
+ for (const plugin of await this.discoverPiPackagePlugins(packageProvider)) addUnique(records, plugin);
}
return [...records.values()].sort((left, right) => left.id.localeCompare(right.id));
}
+ private async packageProvider(config?: PiWebConfig): Promise {
+ if (this.packageProviderForAgentDir === undefined) return undefined;
+ return this.packageProviderForAgentDir(await this.currentAgentDir(config));
+ }
+
+ private async currentAgentDir(config?: PiWebConfig): Promise {
+ if (this.agentDirProvider !== undefined) return await this.agentDirProvider();
+ if (this.agentDir !== undefined) return this.agentDir;
+ const currentConfig = config ?? await this.configProvider();
+ return effectiveAgentConfig(process.env, currentConfig, this.cwd).dir;
+ }
+
private async discoverLocalPlugins(): Promise {
const plugins: PluginRecord[] = [];
for (const root of this.roots) plugins.push(...await discoverLocalRoot(root));
diff --git a/src/server/piWebStatus.test.ts b/src/server/piWebStatus.test.ts
index 94bf1eb..3e546fc 100644
--- a/src/server/piWebStatus.test.ts
+++ b/src/server/piWebStatus.test.ts
@@ -52,7 +52,7 @@ describe("PI WEB status", () => {
capabilities: [],
});
- const status = await getPiWebVersionStatus(daemon, { agentCommand: "omp", agentDir });
+ const status = await getPiWebVersionStatus(daemon, { agentCommand: "alt-agent", agentDir });
expect(status.components.sessiond.installation).toMatchObject({ kind: "pi-package", source: process.cwd(), scope: "user" });
} finally {
@@ -85,12 +85,12 @@ describe("PI WEB status", () => {
{ kind: "pi-package", source: "npm:@jmfederico/pi-web", scope: "user", path: "/tmp/pi-web" },
"pi-web restart",
{
- agentCommand: "/tmp/agent's/omp",
- hasCommand: (command) => Promise.resolve(command === "/tmp/agent's/omp"),
+ agentCommand: "/tmp/agent's/alt-agent",
+ hasCommand: (command) => Promise.resolve(command === "/tmp/agent's/alt-agent"),
},
);
- expect(updateCommand).toBe("'/tmp/agent'\\''s/omp' update 'npm:@jmfederico/pi-web' && pi-web restart");
+ expect(updateCommand).toBe("'/tmp/agent'\\''s/alt-agent' update 'npm:@jmfederico/pi-web' && pi-web restart");
});
it("suggests native systemd commands for local development services", async () => {
diff --git a/src/server/piWebStatusCache.ts b/src/server/piWebStatusCache.ts
index ec8813b..3f56c09 100644
--- a/src/server/piWebStatusCache.ts
+++ b/src/server/piWebStatusCache.ts
@@ -11,6 +11,7 @@ export interface PiWebStatusCacheOptions {
export interface PiWebStatusCache {
get(): Promise;
refresh(): Promise;
+ invalidate(): void;
}
export function createPiWebStatusCache(load: () => Promise, options: PiWebStatusCacheOptions = {}): PiWebStatusCache {
@@ -42,5 +43,8 @@ export function createPiWebStatusCache(load: () => Promise,
return refresh();
},
refresh,
+ invalidate(): void {
+ cached = undefined;
+ },
};
}
diff --git a/src/server/sessions/authService.test.ts b/src/server/sessions/authService.test.ts
index 04c3e4b..15dbe38 100644
--- a/src/server/sessions/authService.test.ts
+++ b/src/server/sessions/authService.test.ts
@@ -44,9 +44,9 @@ describe("AuthService", () => {
const agentDir = await tempAgentDir();
const auth = new AuthService({ agentDir });
- auth.saveApiKey("anthropic", "sk-omp");
+ auth.saveApiKey("anthropic", "sk-test");
- await expect(readFile(join(agentDir, "auth.json"), "utf8")).resolves.toContain("sk-omp");
+ await expect(readFile(join(agentDir, "auth.json"), "utf8")).resolves.toContain("sk-test");
auth.dispose();
});
});
diff --git a/src/server/sessions/piSessionManagerGateway.test.ts b/src/server/sessions/piSessionManagerGateway.test.ts
index eda4d73..144df97 100644
--- a/src/server/sessions/piSessionManagerGateway.test.ts
+++ b/src/server/sessions/piSessionManagerGateway.test.ts
@@ -60,12 +60,12 @@ describe("SessionDirResolver", () => {
expect(resolver.resolve(cwd)).toMatchObject({ source: "env", sessionDir: envDir, usesConfiguredSessionDir: true });
});
- it("uses OMP sessionDir environment overrides before settings", async () => {
- const envDir = join(tempDir, "omp-env-sessions");
+ it("uses PI WEB sessionDir environment overrides before settings", async () => {
+ const envDir = join(tempDir, "pi-web-env-sessions");
await mkdir(agentDir, { recursive: true });
await writeFile(join(agentDir, "settings.json"), `${JSON.stringify({ sessionDir: join(tempDir, "settings-sessions") }, null, 2)}\n`, "utf8");
- const resolver = new SessionDirResolver({ agentDir, env: { OMP_CODING_AGENT_SESSION_DIR: envDir }, sessionDirEnvKeys: ["PI_WEB_AGENT_SESSION_DIR", "OMP_CODING_AGENT_SESSION_DIR", "PI_CODING_AGENT_SESSION_DIR"] });
+ const resolver = new SessionDirResolver({ agentDir, env: { PI_WEB_AGENT_SESSION_DIR: envDir } });
expect(resolver.resolve(cwd)).toMatchObject({ source: "env", sessionDir: envDir, usesConfiguredSessionDir: true });
});
@@ -92,14 +92,13 @@ describe("Pi session manager gateway", () => {
await expect(gateway.listAll()).resolves.toEqual(expect.arrayContaining([expect.objectContaining({ id: "default-session", cwd }), expect.objectContaining({ id: "env-session", cwd })]));
});
- it("includes command-specific env session directories in global listing", async () => {
- for (const envKey of ["PI_WEB_AGENT_SESSION_DIR", "OMP_CODING_AGENT_SESSION_DIR"]) {
+ it("includes generic env session directories in global listing", async () => {
+ for (const envKey of ["PI_WEB_AGENT_SESSION_DIR", "PI_CODING_AGENT_SESSION_DIR"]) {
const envSessionDir = join(tempDir, `${envKey.toLowerCase()}-sessions`);
await writeSessionFile(envSessionDir, `${envKey.toLowerCase()}-session`, cwd);
const gateway = createPiSessionManagerGateway({
agentDir,
env: { [envKey]: envSessionDir },
- sessionDirEnvKeys: ["PI_WEB_AGENT_SESSION_DIR", "OMP_CODING_AGENT_SESSION_DIR", "PI_CODING_AGENT_SESSION_DIR"],
});
if (gateway.listAll === undefined) throw new Error("Expected legacy listing support");
diff --git a/src/server/sessions/piSessionManagerGateway.ts b/src/server/sessions/piSessionManagerGateway.ts
index 7b06eaf..d13ea10 100644
--- a/src/server/sessions/piSessionManagerGateway.ts
+++ b/src/server/sessions/piSessionManagerGateway.ts
@@ -2,12 +2,11 @@ import type { Dirent } from "node:fs";
import { readdir } from "node:fs/promises";
import { homedir } from "node:os";
import { dirname, isAbsolute, join, resolve } from "node:path";
-import { getAgentDir, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
+import { SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
+import { agentSessionDirEnvKeys, effectiveAgentConfig } from "../../config.js";
import { canonicalizeStoredCwd, cwdPathsEqual } from "../workingDirectory.js";
import type { PiSessionListEntry, PiSessionManager, PiSessionManagerGateway } from "./piSessionService.js";
-export const PI_SESSION_DIR_ENV = "PI_CODING_AGENT_SESSION_DIR";
-
type SessionDirSource = "env" | "settings" | "pi-default";
export interface SessionDirResolution {
@@ -28,9 +27,9 @@ export class SessionDirResolver {
private readonly sessionDirEnvKeys: readonly string[];
constructor(options: SessionDirResolverOptions = {}) {
- this.agentDir = options.agentDir ?? getAgentDir();
+ this.agentDir = options.agentDir ?? effectiveAgentConfig().dir;
this.env = options.env ?? process.env;
- this.sessionDirEnvKeys = options.sessionDirEnvKeys ?? [PI_SESSION_DIR_ENV];
+ this.sessionDirEnvKeys = options.sessionDirEnvKeys ?? agentSessionDirEnvKeys();
}
defaultSessionsRoot(): string {
@@ -131,11 +130,11 @@ function uniqueSessionsByPath(sessions: readonly PiSessionListEntry[]): PiSessio
return [...byPath.values()].sort((a, b) => b.modified.getTime() - a.modified.getTime());
}
-export function defaultPiSessionsRoot(agentDir = getAgentDir()): string {
+export function defaultPiSessionsRoot(agentDir = effectiveAgentConfig().dir): string {
return join(agentDir, "sessions");
}
-export function defaultPiSessionDir(cwd: string, agentDir = getAgentDir()): string {
+export function defaultPiSessionDir(cwd: string, agentDir = effectiveAgentConfig().dir): string {
return sessionDirInDefaultPiStore(defaultPiSessionsRoot(agentDir), cwd);
}
diff --git a/src/server/sessions/piSessionService.ts b/src/server/sessions/piSessionService.ts
index bf0d12a..95cc5d3 100644
--- a/src/server/sessions/piSessionService.ts
+++ b/src/server/sessions/piSessionService.ts
@@ -7,7 +7,6 @@ import {
createAgentSessionServices,
createEditToolDefinition,
defineTool,
- getAgentDir,
ModelRegistry,
SessionManager,
type CreateAgentSessionRuntimeFactory,
@@ -25,6 +24,7 @@ import { createModelRegistryForAgentDir, type AuthChange } from "./authService.j
import { fallbackSessionName, generateShortSessionName } from "./sessionNameGenerator.js";
import { computeEditPreview, type EditPreviewResult } from "./editPreview.js";
import { createPiSessionManagerGateway } from "./piSessionManagerGateway.js";
+import { effectiveAgentConfig } from "../../config.js";
import { attachmentsToInlineImages, saveAttachmentsToWorkspace } from "./attachmentService.js";
import { parsePromptAttachments } from "../../shared/promptAttachments.js";
import type { SavedPromptAttachment } from "../../shared/apiTypes.js";
@@ -338,7 +338,7 @@ export class PiSessionService implements SessionRouteService {
constructor(private readonly events: SessionEventHub, deps: PiSessionServiceDependencies = {}) {
this.archiveStore = deps.archiveStore ?? new SessionArchiveStore();
- this.agentDir = deps.agentDir ?? getAgentDir();
+ this.agentDir = deps.agentDir ?? effectiveAgentConfig().dir;
this.sessionManager = deps.sessionManager ?? createPiSessionManagerGateway({ agentDir: this.agentDir });
this.modelRegistry = deps.modelRegistry ?? createModelRegistryForAgentDir(this.agentDir);
this.spawnTargets = deps.spawnTargets;
diff --git a/src/shared/apiTypes.ts b/src/shared/apiTypes.ts
index b225908..41292d8 100644
--- a/src/shared/apiTypes.ts
+++ b/src/shared/apiTypes.ts
@@ -89,7 +89,7 @@ export interface PiWebConfigValues {
* while the capability stabilizes. Requires spawnSessions to be enabled.
*/
subsessions?: boolean;
- /** Agent runtime state used by the session daemon (Pi by default; OMP compatible). */
+ /** Agent runtime command/state used by PI WEB and the session daemon (Pi by default). */
agent?: PiWebAgentConfig;
}