diff --git a/.gitignore b/.gitignore index 59ae4bf1..90a9d694 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ vendor/opencode/ # OpenCode local deps .opencode/node_modules/ .opencode/bun.lock + +# OpenWork workspace-local artifacts +.opencode/openwork/ diff --git a/packages/server/src/audit.ts b/packages/server/src/audit.ts index aef99a75..33588385 100644 --- a/packages/server/src/audit.ts +++ b/packages/server/src/audit.ts @@ -1,21 +1,55 @@ import { dirname, join } from "node:path"; import { appendFile, readFile } from "node:fs/promises"; +import { homedir } from "node:os"; import type { AuditEntry } from "./types.js"; import { ensureDir, exists } from "./utils.js"; -export function auditLogPath(workspaceRoot: string): string { +function expandHome(value: string): string { + if (value.startsWith("~/")) { + return join(homedir(), value.slice(2)); + } + return value; +} + +function resolveOpenworkDataDir(): string { + const override = process.env.OPENWORK_DATA_DIR?.trim(); + if (override) return expandHome(override); + return join(homedir(), ".openwork", "openwork-server"); +} + +export function auditLogPath(workspaceId: string): string { + return join(resolveOpenworkDataDir(), "audit", `${workspaceId}.jsonl`); +} + +export function legacyAuditLogPath(workspaceRoot: string): string { return join(workspaceRoot, ".opencode", "openwork", "audit.jsonl"); } +async function resolveReadableAuditPath(workspaceRoot: string, workspaceId: string): Promise { + const primary = auditLogPath(workspaceId); + if (await exists(primary)) return primary; + const legacy = legacyAuditLogPath(workspaceRoot); + if (await exists(legacy)) return legacy; + return null; +} + export async function recordAudit(workspaceRoot: string, entry: AuditEntry): Promise { - const path = auditLogPath(workspaceRoot); + const workspaceId = entry.workspaceId?.trim(); + if (!workspaceId) { + const path = legacyAuditLogPath(workspaceRoot); + await ensureDir(dirname(path)); + await appendFile(path, JSON.stringify(entry) + "\n", "utf8"); + return; + } + + const path = auditLogPath(workspaceId); await ensureDir(dirname(path)); await appendFile(path, JSON.stringify(entry) + "\n", "utf8"); } -export async function readLastAudit(workspaceRoot: string): Promise { - const path = auditLogPath(workspaceRoot); - if (!(await exists(path))) return null; +export async function readLastAudit(workspaceRoot: string, workspaceId: string): Promise { + const path = await resolveReadableAuditPath(workspaceRoot, workspaceId); + if (!path) return null; const content = await readFile(path, "utf8"); const lines = content.trim().split("\n"); const last = lines[lines.length - 1]; @@ -27,9 +61,13 @@ export async function readLastAudit(workspaceRoot: string): Promise { - const path = auditLogPath(workspaceRoot); - if (!(await exists(path))) return []; +export async function readAuditEntries( + workspaceRoot: string, + workspaceId: string, + limit = 50, +): Promise { + const path = await resolveReadableAuditPath(workspaceRoot, workspaceId); + if (!path) return []; const content = await readFile(path, "utf8"); const rawLines = content.trim().split("\n").filter(Boolean); if (!rawLines.length) return []; diff --git a/packages/server/src/server.ts b/packages/server/src/server.ts index fde9ab0f..c43c62a8 100644 --- a/packages/server/src/server.ts +++ b/packages/server/src/server.ts @@ -1176,7 +1176,7 @@ function createRoutes(config: ServerConfig, approvals: ApprovalService, tokens: const workspace = await resolveWorkspace(config, ctx.params.id); const opencode = await readOpencodeConfig(workspace.path); const openwork = await readOpenworkConfig(workspace.path); - const lastAudit = await readLastAudit(workspace.path); + const lastAudit = await readLastAudit(workspace.path, workspace.id); return jsonResponse({ opencode, openwork, updatedAt: lastAudit?.timestamp ?? null }); }); @@ -1185,7 +1185,7 @@ function createRoutes(config: ServerConfig, approvals: ApprovalService, tokens: const limitParam = ctx.url.searchParams.get("limit"); const parsed = limitParam ? Number(limitParam) : NaN; const limit = Number.isFinite(parsed) && parsed > 0 ? Math.min(parsed, 200) : 50; - const items = await readAuditEntries(workspace.path, limit); + const items = await readAuditEntries(workspace.path, workspace.id, limit); return jsonResponse({ items }); });