Archived
fix: store session archives under PI_WEB_DATA_DIR
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@jmfederico/pi-web": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Store session archive metadata and archived session files under `PI_WEB_DATA_DIR` when configured.
|
||||||
+12
-1
@@ -109,7 +109,7 @@ Rows with JSON key `—` are runtime-only environment variables, not config-file
|
|||||||
| Project config version | `version` | — | Project | Project-local only; must be `1` when present | Next project-config read |
|
| Project config version | `version` | — | Project | Project-local only; must be `1` when present | Next project-config read |
|
||||||
| **Runtime-only environment variables** | | | | | |
|
| **Runtime-only environment variables** | | | | | |
|
||||||
| Global config file path | — | `PI_WEB_CONFIG` (`XDG_CONFIG_HOME` affects the default path) | Process/env | Selects the global config file; not a project config | Restart services/processes after changing env |
|
| Global config file path | — | `PI_WEB_CONFIG` (`XDG_CONFIG_HOME` affects the default path) | Process/env | Selects the global config file; not a project config | Restart services/processes after changing env |
|
||||||
| Managed data directory | — | `PI_WEB_DATA_DIR` | Process/env | Not supported locally | Restart services before changing; moves managed state location |
|
| Managed data directory | — | `PI_WEB_DATA_DIR` | Process/env | Not supported locally | Restart web/API and session daemon before changing; relocates managed state, including session archives |
|
||||||
| Session daemon socket | — | `PI_WEB_SESSIOND_SOCKET` | Web/API + session daemon env | Not supported locally | Restart daemon and web/API; both must match |
|
| Session daemon socket | — | `PI_WEB_SESSIOND_SOCKET` | Web/API + session daemon env | Not supported locally | Restart daemon and web/API; both must match |
|
||||||
| Session daemon TCP port | — | `PI_WEB_SESSIOND_PORT` | Session daemon env | Not supported locally | Restart session daemon; set `PI_WEB_SESSIOND_URL` for web/API too |
|
| Session daemon TCP port | — | `PI_WEB_SESSIOND_PORT` | Session daemon env | Not supported locally | Restart session daemon; set `PI_WEB_SESSIOND_URL` for web/API too |
|
||||||
| Session daemon TCP host | — | `PI_WEB_SESSIOND_HOST` | Session daemon env | Not supported locally | Restart session daemon |
|
| Session daemon TCP host | — | `PI_WEB_SESSIOND_HOST` | Session daemon env | Not supported locally | Restart session daemon |
|
||||||
@@ -122,6 +122,17 @@ Rows with JSON key `—` are runtime-only environment variables, not config-file
|
|||||||
|
|
||||||
## Key details
|
## Key details
|
||||||
|
|
||||||
|
### Managed data directory
|
||||||
|
|
||||||
|
`PI_WEB_DATA_DIR` selects the machine-global root for PI WEB-managed state. It defaults to `~/.pi-web`. Session archival uses this layout:
|
||||||
|
|
||||||
|
- `$PI_WEB_DATA_DIR/archived-sessions.json` for the archive index.
|
||||||
|
- `$PI_WEB_DATA_DIR/archived-sessions/` for archived session files.
|
||||||
|
|
||||||
|
The session daemon owns both archive locations. Restart the session daemon after setting or changing `PI_WEB_DATA_DIR`; restart the web/API process as well so its other managed-state stores use the same root.
|
||||||
|
|
||||||
|
PI WEB does not automatically migrate existing managed state. Archives created by earlier releases while a custom `PI_WEB_DATA_DIR` was already set may remain under `~/.pi-web` and will not appear automatically after upgrading. Stop the session daemon and back up both locations before reconciling them manually. The archive index stores absolute `archivePath` values, so update those values if archived files are moved to a different path.
|
||||||
|
|
||||||
### External path access
|
### External path access
|
||||||
|
|
||||||
`pathAccess.allowedPaths` grants PI WEB's file explorer and absolute `@` path completions access to specific filesystem roots outside the current workspace.
|
`pathAccess.allowedPaths` grants PI WEB's file explorer and absolute `@` path completions access to specific filesystem roots outside the current workspace.
|
||||||
|
|||||||
@@ -1,17 +1,55 @@
|
|||||||
import { constants } from "node:fs";
|
import { constants } from "node:fs";
|
||||||
import { access, mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
import { access, mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
||||||
import { join, resolve } from "node:path";
|
import { join, resolve } from "node:path";
|
||||||
import { tmpdir } from "node:os";
|
import { homedir, tmpdir } from "node:os";
|
||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
import { SessionArchiveStore } from "./sessionArchiveStore.js";
|
import { defaultSessionArchiveFilePath, SessionArchiveStore } from "./sessionArchiveStore.js";
|
||||||
|
|
||||||
const tempRoots: string[] = [];
|
const tempRoots: string[] = [];
|
||||||
|
|
||||||
|
describe("defaultSessionArchiveFilePath", () => {
|
||||||
|
it("uses PI_WEB_DATA_DIR when configured", () => {
|
||||||
|
expect(defaultSessionArchiveFilePath({ PI_WEB_DATA_DIR: "managed-state" }, "/tmp/pi-web")).toBe(resolve("/tmp/pi-web", "managed-state", "archived-sessions.json"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("preserves the ~/.pi-web default when PI_WEB_DATA_DIR is unset", () => {
|
||||||
|
expect(defaultSessionArchiveFilePath({}, "/tmp/pi-web")).toBe(join(homedir(), ".pi-web", "archived-sessions.json"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("SessionArchiveStore", () => {
|
describe("SessionArchiveStore", () => {
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
|
vi.unstubAllEnvs();
|
||||||
await Promise.all(tempRoots.splice(0).map((path) => rm(path, { recursive: true, force: true })));
|
await Promise.all(tempRoots.splice(0).map((path) => rm(path, { recursive: true, force: true })));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("stores its default index and archived files under PI_WEB_DATA_DIR", async () => {
|
||||||
|
const root = await mkdtemp(join(tmpdir(), "pi-web-archive-data-dir-"));
|
||||||
|
tempRoots.push(root);
|
||||||
|
const dataDir = join(root, "managed-state");
|
||||||
|
const activeDir = join(root, "active");
|
||||||
|
await mkdir(activeDir, { recursive: true });
|
||||||
|
const sourcePath = join(activeDir, "2026-01-01_managed.jsonl");
|
||||||
|
await writeFile(sourcePath, "session contents\n", "utf8");
|
||||||
|
vi.stubEnv("PI_WEB_DATA_DIR", dataDir);
|
||||||
|
|
||||||
|
const store = new SessionArchiveStore();
|
||||||
|
const record = await store.archive({
|
||||||
|
sessionId: "managed",
|
||||||
|
cwd: "/workspace",
|
||||||
|
path: sourcePath,
|
||||||
|
created: "2026-01-01T00:00:00.000Z",
|
||||||
|
modified: "2026-01-01T00:01:00.000Z",
|
||||||
|
messageCount: 1,
|
||||||
|
firstMessage: "hello",
|
||||||
|
});
|
||||||
|
|
||||||
|
const archivePath = join(dataDir, "archived-sessions", "2026-01-01_managed.jsonl");
|
||||||
|
expect(record.archivePath).toBe(archivePath);
|
||||||
|
await expect(readFile(archivePath, "utf8")).resolves.toBe("session contents\n");
|
||||||
|
await expect(readFile(join(dataDir, "archived-sessions.json"), "utf8")).resolves.toContain('"sessionId": "managed"');
|
||||||
|
});
|
||||||
|
|
||||||
it("moves archived session files out of the active session directory and restores them", async () => {
|
it("moves archived session files out of the active session directory and restores them", async () => {
|
||||||
const root = await mkdtemp(join(tmpdir(), "pi-web-archive-"));
|
const root = await mkdtemp(join(tmpdir(), "pi-web-archive-"));
|
||||||
tempRoots.push(root);
|
tempRoots.push(root);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
|
|||||||
import { constants } from "node:fs";
|
import { constants } from "node:fs";
|
||||||
import { access, copyFile, mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
import { access, copyFile, mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
||||||
import { basename, dirname, join } from "node:path";
|
import { basename, dirname, join } from "node:path";
|
||||||
import { homedir } from "node:os";
|
import { piWebDataDir } from "../../config.js";
|
||||||
import { canonicalizeStoredCwd } from "../workingDirectory.js";
|
import { canonicalizeStoredCwd } from "../workingDirectory.js";
|
||||||
|
|
||||||
export interface ArchiveSessionInput {
|
export interface ArchiveSessionInput {
|
||||||
@@ -35,11 +35,15 @@ interface ArchiveFile {
|
|||||||
sessions: ArchivedSessionRecord[];
|
sessions: ArchivedSessionRecord[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function defaultSessionArchiveFilePath(env: NodeJS.ProcessEnv = process.env, cwd = process.cwd()): string {
|
||||||
|
return join(piWebDataDir(env, cwd), "archived-sessions.json");
|
||||||
|
}
|
||||||
|
|
||||||
export class SessionArchiveStore {
|
export class SessionArchiveStore {
|
||||||
private operationQueue: Promise<void> = Promise.resolve();
|
private operationQueue: Promise<void> = Promise.resolve();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly filePath = join(homedir(), ".pi-web", "archived-sessions.json"),
|
private readonly filePath = defaultSessionArchiveFilePath(),
|
||||||
private readonly archiveDir = join(dirname(filePath), "archived-sessions"),
|
private readonly archiveDir = join(dirname(filePath), "archived-sessions"),
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user