Archived
fix: require explicit alternate agent state
This commit is contained in:
+18
-17
@@ -130,8 +130,8 @@
|
|||||||
<p>
|
<p>
|
||||||
Environment overrides include <code>PI_WEB_HOST</code>, <code>PI_WEB_PORT</code> / <code>PORT</code>,
|
Environment overrides include <code>PI_WEB_HOST</code>, <code>PI_WEB_PORT</code> / <code>PORT</code>,
|
||||||
<code>PI_WEB_ALLOWED_HOSTS</code>, <code>PI_WEB_MAX_UPLOAD_BYTES</code>, <code>PI_WEB_AGENT_COMMAND</code>,
|
<code>PI_WEB_ALLOWED_HOSTS</code>, <code>PI_WEB_MAX_UPLOAD_BYTES</code>, <code>PI_WEB_AGENT_COMMAND</code>,
|
||||||
<code>PI_WEB_AGENT_DIR</code>, <code>PI_WEB_AGENT_SESSION_DIR</code>, <code>PI_CODING_AGENT_DIR</code>,
|
<code>PI_WEB_AGENT_DIR</code>, <code>PI_WEB_AGENT_SESSION_DIR</code>, <code>PI_CODING_AGENT_DIR</code> /
|
||||||
<code>PI_CODING_AGENT_SESSION_DIR</code>, <code>PI_WEB_SPAWN_SESSIONS</code>, and
|
<code>PI_CODING_AGENT_SESSION_DIR</code> for Pi compatibility, <code>PI_WEB_SPAWN_SESSIONS</code>, and
|
||||||
<code>PI_WEB_SUBSESSIONS</code>.
|
<code>PI_WEB_SUBSESSIONS</code>.
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -280,7 +280,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Agent state directory</td>
|
<td>Agent state directory</td>
|
||||||
<td><code>agent.dir</code></td>
|
<td><code>agent.dir</code></td>
|
||||||
<td><code>PI_WEB_AGENT_DIR</code>, <code>PI_CODING_AGENT_DIR</code></td>
|
<td><code>PI_WEB_AGENT_DIR</code> (<code>PI_CODING_AGENT_DIR</code> for Pi compatibility)</td>
|
||||||
<td>Global/session daemon</td>
|
<td>Global/session daemon</td>
|
||||||
<td>Not supported locally</td>
|
<td>Not supported locally</td>
|
||||||
<td>Restart session daemon; affects auth, models, settings, and sessions</td>
|
<td>Restart session daemon; affects auth, models, settings, and sessions</td>
|
||||||
@@ -393,7 +393,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Agent session storage directory</td>
|
<td>Agent session storage directory</td>
|
||||||
<td>—</td>
|
<td>—</td>
|
||||||
<td><code>PI_WEB_AGENT_SESSION_DIR</code>, <code>PI_CODING_AGENT_SESSION_DIR</code></td>
|
<td><code>PI_WEB_AGENT_SESSION_DIR</code> (<code>PI_CODING_AGENT_SESSION_DIR</code> for Pi compatibility)</td>
|
||||||
<td>Session daemon env</td>
|
<td>Session daemon env</td>
|
||||||
<td>Not supported locally</td>
|
<td>Not supported locally</td>
|
||||||
<td>Restart session daemon; env-only session storage override</td>
|
<td>Restart session daemon; env-only session storage override</td>
|
||||||
@@ -401,7 +401,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Agent config directory</td>
|
<td>Agent config directory</td>
|
||||||
<td>—</td>
|
<td>—</td>
|
||||||
<td><code>PI_WEB_AGENT_DIR</code>, <code>PI_CODING_AGENT_DIR</code></td>
|
<td><code>PI_WEB_AGENT_DIR</code> (<code>PI_CODING_AGENT_DIR</code> for Pi compatibility)</td>
|
||||||
<td>Web/API + session daemon env</td>
|
<td>Web/API + session daemon env</td>
|
||||||
<td>Not supported locally</td>
|
<td>Not supported locally</td>
|
||||||
<td>Restart services</td>
|
<td>Restart services</td>
|
||||||
@@ -452,31 +452,32 @@
|
|||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<code>agent.dir</code> controls which compatible agent state directory PI WEB reads for auth providers,
|
<code>agent.dir</code> controls which compatible agent state directory PI WEB reads for auth providers,
|
||||||
model settings, settings, and session metadata. It defaults to <code>~/.pi/agent</code>. Set it to another
|
model settings, settings, and session metadata. It defaults to <code>~/.pi/agent</code> only for the default
|
||||||
Pi-compatible state directory when you want an isolated profile or an alternate compatible agent's data.
|
<code>pi</code> command. Set it explicitly when you want an isolated Pi profile or when
|
||||||
|
<code>agent.command</code> points at another compatible CLI.
|
||||||
</p>
|
</p>
|
||||||
<div class="code-card">
|
<div class="code-card">
|
||||||
<pre><code>{
|
<pre><code>{
|
||||||
"agent": {
|
"agent": {
|
||||||
"command": "pi",
|
"command": "my-pi-fork",
|
||||||
"dir": "~/agent-profiles/research"
|
"dir": "/opt/my-pi-fork/agent"
|
||||||
}
|
}
|
||||||
}</code></pre>
|
}</code></pre>
|
||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
For example, an Oh My Pi profile can set <code>agent.command</code> to <code>omp</code> and
|
For example, a fork profile can set <code>agent.command</code> to <code>my-pi-fork</code> and
|
||||||
<code>agent.dir</code> to <code>~/.omp/agent</code>.
|
<code>agent.dir</code> to <code>/opt/my-pi-fork/agent</code>.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Environment variables take precedence over the config file. <code>PI_WEB_AGENT_COMMAND</code> selects the
|
Environment variables take precedence over the config file. <code>PI_WEB_AGENT_COMMAND</code> selects the
|
||||||
command, <code>PI_WEB_AGENT_DIR</code> sets the state directory for any command, and
|
command, <code>PI_WEB_AGENT_DIR</code> sets the state directory, and <code>PI_WEB_AGENT_SESSION_DIR</code>
|
||||||
<code>PI_WEB_AGENT_SESSION_DIR</code> overrides session storage separately from <code>agent.dir</code>.
|
overrides session storage separately from <code>agent.dir</code>. Existing Pi Coding Agent env names
|
||||||
Existing Pi Coding Agent env names (<code>PI_CODING_AGENT_DIR</code> and
|
(<code>PI_CODING_AGENT_DIR</code> and <code>PI_CODING_AGENT_SESSION_DIR</code>) remain supported only for
|
||||||
<code>PI_CODING_AGENT_SESSION_DIR</code>) remain supported for compatibility.
|
Pi compatibility; alternate commands should use the explicit PI WEB variables or config keys.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Session directory overrides are environment-only; use <code>PI_WEB_AGENT_SESSION_DIR</code> unless you need
|
Session directory overrides are environment-only; use <code>PI_WEB_AGENT_SESSION_DIR</code> unless you are
|
||||||
the legacy Pi-compatible <code>PI_CODING_AGENT_SESSION_DIR</code> name.
|
intentionally using the legacy Pi-compatible <code>PI_CODING_AGENT_SESSION_DIR</code> name.
|
||||||
</p>
|
</p>
|
||||||
<div class="callout warning">
|
<div class="callout warning">
|
||||||
Restart the session daemon after changing agent settings. The web/API process can display the new config
|
Restart the session daemon after changing agent settings. The web/API process can display the new config
|
||||||
|
|||||||
+10
-10
@@ -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, `<project>/.pi-web/config.json` overrides the global value.
|
Supported project-local settings are then applied for that project's workspaces. For upload defaults, `<project>/.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`, `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` for Pi compatibility, `PI_WEB_SPAWN_SESSIONS`, and `PI_WEB_SUBSESSIONS`.
|
||||||
|
|
||||||
Process restarts depend on the key:
|
Process restarts depend on the key:
|
||||||
|
|
||||||
@@ -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 |
|
| 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 |
|
| 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 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` | 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` for Pi compatibility) | 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 |
|
| 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 |
|
| Tracked subsessions (beta) | `subsessions` | `PI_WEB_SUBSESSIONS` | Global/session daemon | Not supported locally; also requires `spawnSessions` | Restart session daemon |
|
||||||
| Plugin enablement/settings | `plugins.<id>.enabled`, `plugins.<id>.settings` | — | Global | Not core local config; plugins may read their own project files | Reload browser tab |
|
| Plugin enablement/settings | `plugins.<id>.enabled`, `plugins.<id>.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 |
|
| 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 |
|
| 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 |
|
| 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` | Session daemon env | Not supported locally | Restart session daemon; env-only session storage override |
|
| Agent session storage directory | — | `PI_WEB_AGENT_SESSION_DIR` (`PI_CODING_AGENT_SESSION_DIR` for Pi compatibility) | 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 |
|
| Agent config directory | — | `PI_WEB_AGENT_DIR` (`PI_CODING_AGENT_DIR` for Pi compatibility) | 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 |
|
| 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
|
## Key details
|
||||||
@@ -170,22 +170,22 @@ The per-request size limit is still controlled by `maxUploadBytes` / `PI_WEB_MAX
|
|||||||
|
|
||||||
`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.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 `~/.pi/agent`. Set it to another Pi-compatible state directory when you want an isolated profile or an alternate compatible agent's data.
|
`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` only for the default `pi` command. Set it explicitly when you want an isolated Pi profile or when `agent.command` points at another compatible CLI.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"agent": {
|
"agent": {
|
||||||
"command": "pi",
|
"command": "my-pi-fork",
|
||||||
"dir": "~/agent-profiles/research"
|
"dir": "/opt/my-pi-fork/agent"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For example, an Oh My Pi profile can set `agent.command` to `omp` and `agent.dir` to `~/.omp/agent`.
|
For example, a fork profile can set `agent.command` to `my-pi-fork` and `agent.dir` to `/opt/my-pi-fork/agent`.
|
||||||
|
|
||||||
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.
|
Environment variables take precedence over the config file. `PI_WEB_AGENT_COMMAND` selects the command, `PI_WEB_AGENT_DIR` sets the state directory, 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 only for Pi compatibility; alternate commands should use the explicit PI WEB variables or config keys.
|
||||||
|
|
||||||
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.
|
Session directory overrides are environment-only; use `PI_WEB_AGENT_SESSION_DIR` unless you are intentionally using 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.
|
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.
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -46,7 +46,7 @@ describe("commandWithVersionCheck", () => {
|
|||||||
|
|
||||||
it("shell-quotes command words", () => {
|
it("shell-quotes command words", () => {
|
||||||
process.env["SHELL"] = "/bin/bash";
|
process.env["SHELL"] = "/bin/bash";
|
||||||
expect(commandWithVersionCheck("/tmp/agent's/omp")).toBe("command -v '/tmp/agent'\\''s/omp' && ('/tmp/agent'\\''s/omp' --version 2>&1 || true)");
|
expect(commandWithVersionCheck("/tmp/agent's/acme-agent")).toBe("command -v '/tmp/agent'\\''s/acme-agent' && ('/tmp/agent'\\''s/acme-agent' --version 2>&1 || true)");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -55,11 +55,11 @@ describe("agentCommandForChecks", () => {
|
|||||||
const dir = mkdtempSync(join(tmpdir(), "pi-web-cli-test-"));
|
const dir = mkdtempSync(join(tmpdir(), "pi-web-cli-test-"));
|
||||||
try {
|
try {
|
||||||
const configPath = join(dir, "config.json");
|
const configPath = join(dir, "config.json");
|
||||||
writeFileSync(configPath, `${JSON.stringify({ agent: { command: "omp" } })}\n`);
|
writeFileSync(configPath, `${JSON.stringify({ agent: { command: "acme-agent", dir: "/opt/acme-agent/state" } })}\n`);
|
||||||
process.env["PI_WEB_CONFIG"] = configPath;
|
process.env["PI_WEB_CONFIG"] = configPath;
|
||||||
delete process.env["PI_WEB_AGENT_COMMAND"];
|
delete process.env["PI_WEB_AGENT_COMMAND"];
|
||||||
|
|
||||||
expect(agentCommandForChecks()).toBe("omp");
|
expect(agentCommandForChecks()).toBe("acme-agent");
|
||||||
} finally {
|
} finally {
|
||||||
rmSync(dir, { recursive: true, force: true });
|
rmSync(dir, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export class SettingsSessiondPanel extends LitElement {
|
|||||||
?disabled=${this.loading || this.saving || agentDirOverridden}
|
?disabled=${this.loading || this.saving || agentDirOverridden}
|
||||||
@change=${(event: Event) => { void this.saveAgentField("dir", event); }}
|
@change=${(event: Event) => { void this.saveAgentField("dir", event); }}
|
||||||
>
|
>
|
||||||
<small>Choose which compatible auth, models, settings, and sessions PI WEB reads. Set a separate directory for isolated agent profiles, then restart the session daemon.</small>
|
<small>Choose which compatible auth, models, settings, and sessions PI WEB reads. Non-<code>pi</code> commands require an explicit state directory, then a session daemon restart.</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<span class="field-heading">
|
<span class="field-heading">
|
||||||
@@ -110,7 +110,7 @@ export class SettingsSessiondPanel extends LitElement {
|
|||||||
<h3>Effective after environment overrides</h3>
|
<h3>Effective after environment overrides</h3>
|
||||||
<dl>
|
<dl>
|
||||||
<div><dt>Agent command</dt><dd>${effectiveAgent?.command ?? html`<span class="muted">pi default</span>`}</dd></div>
|
<div><dt>Agent command</dt><dd>${effectiveAgent?.command ?? html`<span class="muted">pi default</span>`}</dd></div>
|
||||||
<div><dt>Agent state</dt><dd>${effectiveAgent?.dir ?? html`<span class="muted">Pi default</span>`}</dd></div>
|
<div><dt>Agent state</dt><dd>${effectiveAgent?.dir ?? html`<span class="muted">~/.pi/agent default</span>`}</dd></div>
|
||||||
<div><dt>Spawn sessions</dt><dd>${effectiveSpawn ? "Enabled" : html`<span class="muted">Disabled</span>`}</dd></div>
|
<div><dt>Spawn sessions</dt><dd>${effectiveSpawn ? "Enabled" : html`<span class="muted">Disabled</span>`}</dd></div>
|
||||||
<div><dt>Subsessions</dt><dd>${effectiveSubsessions ? "Enabled" : html`<span class="muted">Disabled</span>`}</dd></div>
|
<div><dt>Subsessions</dt><dd>${effectiveSubsessions ? "Enabled" : html`<span class="muted">Disabled</span>`}</dd></div>
|
||||||
</dl>
|
</dl>
|
||||||
|
|||||||
+30
-21
@@ -50,23 +50,28 @@ describe("PI WEB config persistence", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("persists and reads custom agent runtime settings", () => {
|
it("persists and reads custom agent runtime settings", () => {
|
||||||
savePiWebConfig({ agent: { command: "omp", dir: "~/.omp/agent" } }, testOptions());
|
savePiWebConfig({ agent: { command: "acme-agent", dir: "/opt/acme-agent/state" } }, testOptions());
|
||||||
|
|
||||||
expect(loadPiWebConfig(testOptions()).config.agent).toEqual({ command: "omp", dir: "~/.omp/agent" });
|
expect(loadPiWebConfig(testOptions()).config.agent).toEqual({ command: "acme-agent", dir: "/opt/acme-agent/state" });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps the Pi agent directory default for alternate commands", () => {
|
it("defaults to the Pi agent directory only for Pi commands and launchers", () => {
|
||||||
expect(effectiveAgentConfig({ HOME: join(tempDir, ".home") }, { agent: { command: "omp" } })).toMatchObject({
|
expect(effectiveAgentConfig({ HOME: join(tempDir, ".home") }, { agent: { command: "/tmp/pi.cmd" } })).toMatchObject({
|
||||||
command: "omp",
|
command: "/tmp/pi.cmd",
|
||||||
dir: join(tempDir, ".home", ".pi", "agent"),
|
dir: join(tempDir, ".home", ".pi", "agent"),
|
||||||
sessionDirEnvKeys: ["PI_WEB_AGENT_SESSION_DIR", "PI_CODING_AGENT_SESSION_DIR"],
|
sessionDirEnvKeys: ["PI_WEB_AGENT_SESSION_DIR", "PI_CODING_AGENT_SESSION_DIR"],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("requires an explicit agent directory for non-Pi commands", () => {
|
||||||
|
expect(() => effectiveAgentConfig({}, { agent: { command: "acme-agent" } })).toThrow('PI WEB config agent.dir or PI_WEB_AGENT_DIR is required when agent.command is "acme-agent"');
|
||||||
|
expect(() => savePiWebConfig({ agent: { command: "acme-agent" } }, testOptions())).toThrow('PI WEB config agent.dir or PI_WEB_AGENT_DIR is required when agent.command is "acme-agent"');
|
||||||
|
});
|
||||||
|
|
||||||
it("resolves explicit alternate agent command and state directory settings", () => {
|
it("resolves explicit alternate agent command and state directory settings", () => {
|
||||||
expect(effectiveAgentConfig({ HOME: join(tempDir, ".home") }, { agent: { command: "omp", dir: "~/.omp/agent" } })).toMatchObject({
|
expect(effectiveAgentConfig({ HOME: join(tempDir, ".home") }, { agent: { command: "acme-agent", dir: "~/agent-profiles/acme" } })).toMatchObject({
|
||||||
command: "omp",
|
command: "acme-agent",
|
||||||
dir: join(tempDir, ".home", ".omp", "agent"),
|
dir: join(tempDir, ".home", "agent-profiles", "acme"),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -80,36 +85,40 @@ describe("PI WEB config persistence", () => {
|
|||||||
PI_CODING_AGENT_SESSION_DIR: "",
|
PI_CODING_AGENT_SESSION_DIR: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(effectiveAgentConfig(env, { agent: { command: "omp", dir: "~/.omp/agent" } })).toMatchObject({
|
expect(effectiveAgentConfig(env, { agent: { command: "acme-agent", dir: "~/agent-profiles/acme" } })).toMatchObject({
|
||||||
command: "omp",
|
command: "acme-agent",
|
||||||
dir: join(tempDir, ".home", ".omp", "agent"),
|
dir: join(tempDir, ".home", "agent-profiles", "acme"),
|
||||||
});
|
});
|
||||||
expect(hasAgentDirEnvOverride(env)).toBe(false);
|
expect(hasAgentDirEnvOverride(env, "acme-agent")).toBe(false);
|
||||||
expect(hasAgentSessionDirEnvOverride(env)).toBe(false);
|
expect(hasAgentSessionDirEnvOverride(env, "acme-agent")).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses generic agent directory env precedence and Pi compatibility fallback", () => {
|
it("uses explicit PI WEB agent directory env precedence", () => {
|
||||||
expect(effectiveAgentConfig({
|
expect(effectiveAgentConfig({
|
||||||
PI_WEB_AGENT_COMMAND: "omp",
|
PI_WEB_AGENT_COMMAND: "acme-agent",
|
||||||
PI_WEB_AGENT_DIR: join(tempDir, "web-env-agent"),
|
PI_WEB_AGENT_DIR: join(tempDir, "web-env-agent"),
|
||||||
PI_CODING_AGENT_DIR: join(tempDir, "pi-env-agent"),
|
PI_CODING_AGENT_DIR: join(tempDir, "pi-env-agent"),
|
||||||
}, { agent: { command: "pi", dir: join(tempDir, "config-agent") } })).toMatchObject({
|
}, { agent: { command: "pi", dir: join(tempDir, "config-agent") } })).toMatchObject({
|
||||||
command: "omp",
|
command: "acme-agent",
|
||||||
dir: join(tempDir, "web-env-agent"),
|
dir: join(tempDir, "web-env-agent"),
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps legacy Pi env directory overrides scoped to Pi commands", () => {
|
||||||
expect(effectiveAgentConfig({
|
expect(effectiveAgentConfig({
|
||||||
PI_CODING_AGENT_DIR: join(tempDir, "pi-env-agent"),
|
PI_CODING_AGENT_DIR: join(tempDir, "pi-env-agent"),
|
||||||
}, { agent: { dir: join(tempDir, "config-agent") } })).toMatchObject({
|
}, { agent: { dir: join(tempDir, "config-agent") } })).toMatchObject({
|
||||||
dir: join(tempDir, "pi-env-agent"),
|
dir: join(tempDir, "pi-env-agent"),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
expect(() => effectiveAgentConfig({
|
||||||
|
PI_CODING_AGENT_DIR: join(tempDir, "pi-env-agent"),
|
||||||
|
}, { agent: { command: "acme-agent" } })).toThrow('PI WEB config agent.dir or PI_WEB_AGENT_DIR is required when agent.command is "acme-agent"');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not generate command-specific session directory env keys", () => {
|
it("uses only explicit session directory env keys", () => {
|
||||||
const keys = ["PI_WEB_AGENT_SESSION_DIR", "PI_CODING_AGENT_SESSION_DIR"];
|
expect(agentSessionDirEnvKeys()).toEqual(["PI_WEB_AGENT_SESSION_DIR", "PI_CODING_AGENT_SESSION_DIR"]);
|
||||||
|
expect(effectiveAgentConfig({ HOME: join(tempDir, ".home"), PI_WEB_AGENT_COMMAND: "acme-agent", PI_WEB_AGENT_DIR: join(tempDir, "agent") }).sessionDirEnvKeys).toEqual(["PI_WEB_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", () => {
|
it("exposes the default upload folder in the effective config", () => {
|
||||||
|
|||||||
+20
-10
@@ -50,24 +50,27 @@ export interface EffectivePiWebAgentConfig {
|
|||||||
|
|
||||||
export function effectiveAgentConfig(env: NodeJS.ProcessEnv = process.env, config: Pick<PiWebConfig, "agent"> = {}, cwd = process.cwd()): EffectivePiWebAgentConfig {
|
export function effectiveAgentConfig(env: NodeJS.ProcessEnv = process.env, config: Pick<PiWebConfig, "agent"> = {}, cwd = process.cwd()): EffectivePiWebAgentConfig {
|
||||||
const command = parseAgentCommand(envValue(env, PI_WEB_AGENT_COMMAND_ENV) ?? config.agent?.command ?? DEFAULT_AGENT_COMMAND, "agent.command", "environment");
|
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);
|
const configuredDir = envValue(env, PI_WEB_AGENT_DIR_ENV) ?? (isPiCommand(command) ? envValue(env, PI_CODING_AGENT_DIR_ENV) : undefined) ?? config.agent?.dir ?? defaultAgentDirForCommand(command, env);
|
||||||
return {
|
return {
|
||||||
command,
|
command,
|
||||||
dir: resolveAgentDirPath(configuredDir, env, cwd, "agent.dir", "environment"),
|
dir: resolveAgentDirPath(configuredDir, env, cwd, "agent.dir", "environment"),
|
||||||
sessionDirEnvKeys: agentSessionDirEnvKeys(),
|
sessionDirEnvKeys: agentSessionDirEnvKeys(command),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function agentSessionDirEnvKeys(): string[] {
|
export function agentSessionDirEnvKeys(command = DEFAULT_AGENT_COMMAND): string[] {
|
||||||
return uniqueStrings([PI_WEB_AGENT_SESSION_DIR_ENV, PI_CODING_AGENT_SESSION_DIR_ENV]);
|
return uniqueStrings([
|
||||||
|
PI_WEB_AGENT_SESSION_DIR_ENV,
|
||||||
|
...(isPiCommand(command) ? [PI_CODING_AGENT_SESSION_DIR_ENV] : []),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasAgentDirEnvOverride(env: NodeJS.ProcessEnv): boolean {
|
export function hasAgentDirEnvOverride(env: NodeJS.ProcessEnv, command = DEFAULT_AGENT_COMMAND): boolean {
|
||||||
return isEnvSet(env[PI_WEB_AGENT_DIR_ENV]) || isEnvSet(env[PI_CODING_AGENT_DIR_ENV]);
|
return isEnvSet(env[PI_WEB_AGENT_DIR_ENV]) || (isPiCommand(command) && isEnvSet(env[PI_CODING_AGENT_DIR_ENV]));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasAgentSessionDirEnvOverride(env: NodeJS.ProcessEnv): boolean {
|
export function hasAgentSessionDirEnvOverride(env: NodeJS.ProcessEnv, command = DEFAULT_AGENT_COMMAND): boolean {
|
||||||
return agentSessionDirEnvKeys().some((key) => isEnvSet(env[key]));
|
return agentSessionDirEnvKeys(command).some((key) => isEnvSet(env[key]));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function effectiveUploadsConfig(config: Pick<PiWebConfig, "uploads"> = {}): NonNullable<PiWebConfig["uploads"]> {
|
export function effectiveUploadsConfig(config: Pick<PiWebConfig, "uploads"> = {}): NonNullable<PiWebConfig["uploads"]> {
|
||||||
@@ -141,6 +144,7 @@ export function savePiWebConfig(config: PiWebConfig, options: LoadOptions = {}):
|
|||||||
const env = options.env ?? process.env;
|
const env = options.env ?? process.env;
|
||||||
const path = piWebConfigPath(env, options.cwd ?? process.cwd());
|
const path = piWebConfigPath(env, options.cwd ?? process.cwd());
|
||||||
const normalized = parsePiWebConfig(piWebConfigRecord(config), path);
|
const normalized = parsePiWebConfig(piWebConfigRecord(config), path);
|
||||||
|
effectiveAgentConfig(env, normalized, options.cwd ?? process.cwd());
|
||||||
const existing = readExistingConfigObject(path);
|
const existing = readExistingConfigObject(path);
|
||||||
delete existing["host"];
|
delete existing["host"];
|
||||||
delete existing["port"];
|
delete existing["port"];
|
||||||
@@ -335,8 +339,14 @@ function expandHomePath(value: string, env: NodeJS.ProcessEnv): string {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function defaultAgentDir(env: NodeJS.ProcessEnv): string {
|
function defaultAgentDirForCommand(command: string, env: NodeJS.ProcessEnv): string {
|
||||||
return expandHomePath("~/.pi/agent", env);
|
if (isPiCommand(command)) return expandHomePath("~/.pi/agent", env);
|
||||||
|
throw new Error(`PI WEB config agent.dir or ${PI_WEB_AGENT_DIR_ENV} is required when agent.command is ${JSON.stringify(command)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPiCommand(command: string): boolean {
|
||||||
|
const name = command.split(/[\\/]/u).at(-1)?.toLowerCase() ?? command.toLowerCase();
|
||||||
|
return name.replace(/(?:\.[cm]?js|\.exe|\.cmd)$/iu, "") === DEFAULT_AGENT_COMMAND;
|
||||||
}
|
}
|
||||||
|
|
||||||
function envValue(env: NodeJS.ProcessEnv, key: string): string | undefined {
|
function envValue(env: NodeJS.ProcessEnv, key: string): string | undefined {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export function currentPiWebConfigResponse(options: LoadOptions = {}): PiWebConf
|
|||||||
exists: loaded.exists,
|
exists: loaded.exists,
|
||||||
config: loaded.config,
|
config: loaded.config,
|
||||||
effectiveConfig: effective.config,
|
effectiveConfig: effective.config,
|
||||||
envOverrides: piWebConfigEnvOverrides(env),
|
envOverrides: piWebConfigEnvOverrides(env, effective.config),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +167,8 @@ function parsePluginsRequest(value: unknown): NonNullable<PiWebConfig["plugins"]
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function piWebConfigEnvOverrides(env: NodeJS.ProcessEnv): PiWebConfigEnvOverrides {
|
function piWebConfigEnvOverrides(env: NodeJS.ProcessEnv, config: PiWebConfig = {}): PiWebConfigEnvOverrides {
|
||||||
|
const command = config.agent?.command;
|
||||||
return {
|
return {
|
||||||
host: isEnvSet(env["PI_WEB_HOST"]),
|
host: isEnvSet(env["PI_WEB_HOST"]),
|
||||||
port: isEnvSet(env["PI_WEB_PORT"]) || isEnvSet(env["PORT"]),
|
port: isEnvSet(env["PI_WEB_PORT"]) || isEnvSet(env["PORT"]),
|
||||||
@@ -175,8 +176,8 @@ function piWebConfigEnvOverrides(env: NodeJS.ProcessEnv): PiWebConfigEnvOverride
|
|||||||
spawnSessions: isEnvSet(env["PI_WEB_SPAWN_SESSIONS"]),
|
spawnSessions: isEnvSet(env["PI_WEB_SPAWN_SESSIONS"]),
|
||||||
subsessions: isEnvSet(env["PI_WEB_SUBSESSIONS"]),
|
subsessions: isEnvSet(env["PI_WEB_SUBSESSIONS"]),
|
||||||
agentCommand: isEnvSet(env["PI_WEB_AGENT_COMMAND"]),
|
agentCommand: isEnvSet(env["PI_WEB_AGENT_COMMAND"]),
|
||||||
agentDir: hasAgentDirEnvOverride(env),
|
agentDir: hasAgentDirEnvOverride(env, command),
|
||||||
agentSessionDir: hasAgentSessionDirEnvOverride(env),
|
agentSessionDir: hasAgentSessionDirEnvOverride(env, command),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user