import { createHash } from "node:crypto"; import type { AgentKind, RuntimeNamespace, ShareScope, } from "../../agent-contract/dto.js"; export const DEFAULT_PROFILE_ID = "default"; export interface OwnerFields { ownerAgentKind: AgentKind; ownerProfileId: string; ownerWorkspaceId: string | null; } export interface VisibilityWhere { sql: string; params: Record; } export function normalizeNamespace( input: Partial | null | undefined, fallbackAgent: AgentKind = "unknown", ): RuntimeNamespace { const agentKind = cleanId(input?.agentKind) || String(fallbackAgent || "unknown"); const profileId = cleanId(input?.profileId) || DEFAULT_PROFILE_ID; const workspacePath = cleanPath(input?.workspacePath); const workspaceId = cleanId(input?.workspaceId) || (workspacePath ? hashWorkspace(workspacePath) : undefined); const sessionKey = cleanText(input?.sessionKey); const profileLabel = cleanText(input?.profileLabel); return { agentKind, profileId, ...(profileLabel ? { profileLabel } : {}), ...(workspaceId ? { workspaceId } : {}), ...(workspacePath ? { workspacePath } : {}), ...(sessionKey ? { sessionKey } : {}), }; } export function namespaceFromHints( agent: AgentKind, hints?: Record | null, fallback?: RuntimeNamespace, ): RuntimeNamespace { const embedded = asNamespace(hints?.namespace); const agentIdentity = cleanId(hints?.agentIdentity); const profileId = embedded?.profileId || agentIdentity || deriveHermesProfileId(cleanText(hints?.hermesHome)) || cleanId(hints?.profileId) || fallback?.profileId; return normalizeNamespace( { agentKind: embedded?.agentKind || cleanId(hints?.agentKind) || fallback?.agentKind || agent, profileId, profileLabel: embedded?.profileLabel || cleanText(hints?.profileLabel) || cleanText(hints?.agentIdentity) || fallback?.profileLabel, workspaceId: embedded?.workspaceId || cleanId(hints?.workspaceId) || fallback?.workspaceId, workspacePath: embedded?.workspacePath || cleanPath(hints?.workspaceDir) || cleanPath(hints?.agentDir) || cleanPath(hints?.workspacePath) || fallback?.workspacePath, sessionKey: embedded?.sessionKey || cleanText(hints?.sessionKey) || fallback?.sessionKey, }, agent, ); } export function ownerFromNamespace(ns: RuntimeNamespace): OwnerFields { const normalized = normalizeNamespace(ns, ns.agentKind); return { ownerAgentKind: normalized.agentKind, ownerProfileId: normalized.profileId, ownerWorkspaceId: normalized.workspaceId ?? null, }; } export function ownerParams(ns: RuntimeNamespace, prefix = "owner"): Record { const owner = ownerFromNamespace(ns); return { [`${prefix}_agent_kind`]: owner.ownerAgentKind, [`${prefix}_profile_id`]: owner.ownerProfileId, [`${prefix}_workspace_id`]: owner.ownerWorkspaceId, }; } export function normalizeShareScope(scope: unknown): ShareScope { if (scope === "local") return "public"; if (scope === "public" || scope === "hub") return scope; return "private"; } export function visibilityWhere( ns: RuntimeNamespace | null | undefined, alias = "", ): VisibilityWhere { const col = (name: string) => `${alias ? `${alias}.` : ""}${name}`; const normalized = normalizeNamespace(ns, ns?.agentKind ?? "unknown"); const ownerKind = col("owner_agent_kind"); const ownerProfile = col("owner_profile_id"); const shareScope = `COALESCE(${col("share_scope")}, 'private')`; return { sql: `((` + `${ownerKind} = @vis_owner_agent_kind AND ` + `COALESCE(${ownerProfile}, @vis_default_profile_id) = @vis_owner_profile_id` + `) OR ${ownerKind} IS NULL` + ` OR ${ownerKind} = 'unknown'` + ` OR (${shareScope} IN ('local', 'public') AND ${ownerKind} = @vis_owner_agent_kind)` + ` OR ${shareScope} = 'hub')`, params: { vis_owner_agent_kind: normalized.agentKind, vis_owner_profile_id: normalized.profileId, vis_default_profile_id: DEFAULT_PROFILE_ID, }, }; } export function ownerWhere( ns: RuntimeNamespace | null | undefined, alias = "", ): VisibilityWhere { const col = (name: string) => `${alias ? `${alias}.` : ""}${name}`; const normalized = normalizeNamespace(ns, ns?.agentKind ?? "unknown"); return { sql: `${col("owner_agent_kind")} = @owner_agent_kind AND ` + `${col("owner_profile_id")} = @owner_profile_id`, params: { owner_agent_kind: normalized.agentKind, owner_profile_id: normalized.profileId, }, }; } export function isVisibleTo( row: { ownerAgentKind?: AgentKind; ownerProfileId?: string; share?: { scope?: ShareScope | string | null } | null; }, ns: RuntimeNamespace, ): boolean { const scope = normalizeShareScope(row.share?.scope); if (!row.ownerAgentKind || row.ownerAgentKind === "unknown") { return true; } const normalized = normalizeNamespace(ns, ns.agentKind); const sameAgentFramework = row.ownerAgentKind === normalized.agentKind; const sameAgent = sameAgentFramework && (row.ownerProfileId ?? DEFAULT_PROFILE_ID) === normalized.profileId; if (sameAgent) return true; if (scope === "public") return sameAgentFramework; if (scope === "hub") return true; return false; } export function namespaceMeta(ns: RuntimeNamespace): Record { const normalized = normalizeNamespace(ns, ns.agentKind); return { namespace: normalized, ownerAgentKind: normalized.agentKind, ownerProfileId: normalized.profileId, ownerWorkspaceId: normalized.workspaceId ?? null, }; } function asNamespace(value: unknown): RuntimeNamespace | null { if (!value || typeof value !== "object" || Array.isArray(value)) return null; const record = value as Record; const agentKind = cleanId(record.agentKind); const profileId = cleanId(record.profileId); if (!agentKind && !profileId) return null; return normalizeNamespace({ agentKind: agentKind || "unknown", profileId: profileId || DEFAULT_PROFILE_ID, profileLabel: cleanText(record.profileLabel), workspaceId: cleanId(record.workspaceId), workspacePath: cleanPath(record.workspacePath), sessionKey: cleanText(record.sessionKey), }); } function deriveHermesProfileId(hermesHome: string | undefined): string | undefined { if (!hermesHome) return undefined; const normalized = hermesHome.replace(/\\/g, "/").replace(/\/+$/, ""); const match = /\/profiles\/([^/]+)$/.exec(normalized); if (match?.[1]) return cleanId(match[1]); if (normalized.endsWith("/.hermes")) return DEFAULT_PROFILE_ID; return undefined; } function cleanText(value: unknown): string | undefined { if (typeof value !== "string") return undefined; const trimmed = value.trim(); return trimmed || undefined; } function cleanPath(value: unknown): string | undefined { return cleanText(value); } function cleanId(value: unknown): string | undefined { const trimmed = cleanText(value); if (!trimmed) return undefined; return trimmed.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || undefined; } function hashWorkspace(workspacePath: string): string { return createHash("sha256").update(workspacePath).digest("hex").slice(0, 16); }