From 66a36a6548c39c1e815f25c6e26b93a904db7b0a Mon Sep 17 00:00:00 2001 From: Mevy Assistant Date: Thu, 23 Apr 2026 10:11:40 +0200 Subject: [PATCH] refactor(multitenant): centralize controlplane session paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce a shared controlplane paths helper and use it in runtime plus operator tooling. This removes another tenant-derived path assumption and aligns controlplane session logs with the actual tmp-based layout used by the platform. --- Build: pass | Tests: pass — 105 passed (7 files) --- docs/internal/MULTITENANT-HANDOFF.md | 10 ++++++++-- scripts/agent-logs.ts | 22 +++++++++++++++------- src/controlplane-paths.test.ts | 20 ++++++++++++++++++++ src/controlplane-paths.ts | 9 +++++++++ src/controlplane-runner.ts | 3 ++- src/index.ts | 3 ++- 6 files changed, 56 insertions(+), 11 deletions(-) create mode 100644 src/controlplane-paths.test.ts create mode 100644 src/controlplane-paths.ts diff --git a/docs/internal/MULTITENANT-HANDOFF.md b/docs/internal/MULTITENANT-HANDOFF.md index c8c6e14..de35ae7 100644 --- a/docs/internal/MULTITENANT-HANDOFF.md +++ b/docs/internal/MULTITENANT-HANDOFF.md @@ -89,6 +89,11 @@ Also updated: - `scripts/agent-status.ts`, `scripts/agent-task.ts`, and `scripts/agent-task-status.ts` now use platform-owned wording for operator errors +- `src/controlplane-paths.ts` now centralizes controlplane session directory + layout under project `tmp/` +- `scripts/agent-logs.ts` now reads the real controlplane session paths + (`tmp/sessions` and `tmp/sessions/pi`) instead of guessing a tenant-named + home config path ## Recommended first code tasks @@ -118,8 +123,9 @@ Likely next targets: - `src/controlplane-heartbeat.ts` - shared tmp socket/pid/session names -- `scripts/agent-logs.ts` and any remaining operator tooling still keyed to - tenant-derived runtime paths +- any remaining operator tooling still keyed to tenant-derived runtime paths +- broader ownership audit for remaining `AGENT_NAME` references in shared + runtime code ### 3. Build a dedicated platform audit command diff --git a/scripts/agent-logs.ts b/scripts/agent-logs.ts index ca59cb5..705d908 100644 --- a/scripts/agent-logs.ts +++ b/scripts/agent-logs.ts @@ -1,17 +1,25 @@ import fs from 'fs'; import path from 'path'; -import os from 'os'; + +import { TMP_DIR } from '../src/config.js'; +import { + getControlplanePiSessionDir, + getControlplaneSessionDir, +} from '../src/controlplane-paths.js'; const agentId = process.argv[2] ?? ''; -const agentName = (process.env.AGENT_NAME || 'clawdie').toLowerCase(); -const SESSIONS_DIR = path.join(os.homedir(), '.config', agentName, 'sessions'); +const SESSIONS_DIR = getControlplaneSessionDir(TMP_DIR); +const PI_SESSIONS_DIR = getControlplanePiSessionDir(SESSIONS_DIR); function findSessionFiles(): string[] { - if (!fs.existsSync(SESSIONS_DIR)) return []; const files: string[] = []; - for (const entry of fs.readdirSync(SESSIONS_DIR, { withFileTypes: true })) { - if (entry.name.endsWith('.jsonl')) { - files.push(path.join(SESSIONS_DIR, entry.name)); + const roots = [SESSIONS_DIR, PI_SESSIONS_DIR]; + for (const root of roots) { + if (!fs.existsSync(root)) continue; + for (const entry of fs.readdirSync(root, { withFileTypes: true })) { + if (entry.name.endsWith('.jsonl')) { + files.push(path.join(root, entry.name)); + } } } return files.sort().reverse(); diff --git a/src/controlplane-paths.test.ts b/src/controlplane-paths.test.ts new file mode 100644 index 0000000..5d627be --- /dev/null +++ b/src/controlplane-paths.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from 'vitest'; + +import { + getControlplanePiSessionDir, + getControlplaneSessionDir, +} from './controlplane-paths.js'; + +describe('controlplane-paths', () => { + it('derives the controlplane session dir from tmp', () => { + expect(getControlplaneSessionDir('/srv/clawdie/tmp')).toBe( + '/srv/clawdie/tmp/sessions', + ); + }); + + it('derives the nested pi session dir from the controlplane session dir', () => { + expect(getControlplanePiSessionDir('/srv/clawdie/tmp/sessions')).toBe( + '/srv/clawdie/tmp/sessions/pi', + ); + }); +}); diff --git a/src/controlplane-paths.ts b/src/controlplane-paths.ts new file mode 100644 index 0000000..ebea8c2 --- /dev/null +++ b/src/controlplane-paths.ts @@ -0,0 +1,9 @@ +import path from 'path'; + +export function getControlplaneSessionDir(tmpDir: string): string { + return path.join(tmpDir, 'sessions'); +} + +export function getControlplanePiSessionDir(sessionDir: string): string { + return path.join(sessionDir, 'pi'); +} diff --git a/src/controlplane-runner.ts b/src/controlplane-runner.ts index 4be0db1..86a6185 100644 --- a/src/controlplane-runner.ts +++ b/src/controlplane-runner.ts @@ -11,6 +11,7 @@ import fs from 'fs'; import path from 'path'; +import { getControlplanePiSessionDir } from './controlplane-paths.js'; import { getAgentSkillIndex } from './skill-library.js'; import { CONTROLPLANE_JAIL_ISOLATION, @@ -109,7 +110,7 @@ export function buildControlplaneRunCommand( // Pi writes its own native JSONL format — keep it in a 'pi/' subdirectory // so it doesn't collide with the controlplane SessionEntry files in the parent. - const piSessionDir = path.join(absSessionCwd, 'pi'); + const piSessionDir = getControlplanePiSessionDir(absSessionCwd); fs.mkdirSync(piSessionDir, { recursive: true }); const sessionFile = path.join(piSessionDir, `${agentId}.jsonl`); const identityFile = diff --git a/src/index.ts b/src/index.ts index 5113e09..9829e1e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -37,6 +37,7 @@ import { TELEGRAM_OPS_CHAT_ID, WATCHDOG_MODE, } from './config.js'; +import { getControlplaneSessionDir } from './controlplane-paths.js'; import { incCounter, registerGauge, startMetricsServer } from './metrics.js'; import { buildStartupReport, @@ -970,7 +971,7 @@ async function main(): Promise { // Controlplane uses the memory pool — ensure schema + agents + budgets exist. await runSchemaMigration(getMemoryPool()); - const sessionDir = path.join(TMP_DIR, 'sessions'); + const sessionDir = getControlplaneSessionDir(TMP_DIR); fs.mkdirSync(sessionDir, { recursive: true }); let controlplaneServer: import('http').Server | undefined;