import fs from "node:fs"; import path from "node:path"; import type { ClawdbotConfig } from "../config/config.js"; import { formatUserTime, resolveUserTimeFormat, resolveUserTimezone, type ResolvedTimeFormat, } from "./date-time.js"; export type RuntimeInfoInput = { agentId?: string; host: string; os: string; arch: string; node: string; model: string; defaultModel?: string; channel?: string; capabilities?: string[]; /** Supported message actions for the current channel (e.g., react, edit, unsend) */ channelActions?: string[]; repoRoot?: string; }; export type SystemPromptRuntimeParams = { runtimeInfo: RuntimeInfoInput; userTimezone: string; userTime?: string; userTimeFormat?: ResolvedTimeFormat; }; export function buildSystemPromptParams(params: { config?: ClawdbotConfig; agentId?: string; runtime: Omit; workspaceDir?: string; cwd?: string; }): SystemPromptRuntimeParams { const repoRoot = resolveRepoRoot({ config: params.config, workspaceDir: params.workspaceDir, cwd: params.cwd, }); const userTimezone = resolveUserTimezone(params.config?.agents?.defaults?.userTimezone); const userTimeFormat = resolveUserTimeFormat(params.config?.agents?.defaults?.timeFormat); const userTime = formatUserTime(new Date(), userTimezone, userTimeFormat); return { runtimeInfo: { agentId: params.agentId, ...params.runtime, repoRoot, }, userTimezone, userTime, userTimeFormat, }; } function resolveRepoRoot(params: { config?: ClawdbotConfig; workspaceDir?: string; cwd?: string; }): string | undefined { const configured = params.config?.agents?.defaults?.repoRoot?.trim(); if (configured) { try { const resolved = path.resolve(configured); const stat = fs.statSync(resolved); if (stat.isDirectory()) return resolved; } catch { // ignore invalid config path } } const candidates = [params.workspaceDir, params.cwd] .map((value) => value?.trim()) .filter(Boolean) as string[]; const seen = new Set(); for (const candidate of candidates) { const resolved = path.resolve(candidate); if (seen.has(resolved)) continue; seen.add(resolved); const root = findGitRoot(resolved); if (root) return root; } return undefined; } function findGitRoot(startDir: string): string | null { let current = path.resolve(startDir); for (let i = 0; i < 12; i += 1) { const gitPath = path.join(current, ".git"); try { const stat = fs.statSync(gitPath); if (stat.isDirectory() || stat.isFile()) return current; } catch { // ignore missing .git at this level } const parent = path.dirname(current); if (parent === current) break; current = parent; } return null; }