refactor(multitenant): centralize controlplane session paths

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)
This commit is contained in:
Mevy Assistant 2026-04-23 10:11:40 +02:00
parent c8cfa898de
commit 66a36a6548
6 changed files with 56 additions and 11 deletions

View file

@ -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

View file

@ -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();

View file

@ -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',
);
});
});

View file

@ -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');
}

View file

@ -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 =

View file

@ -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<void> {
// 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;