import { randomUUID } from "node:crypto"; export const DEFAULT_CODE_AGENT_SESSION_IDLE_TIMEOUT_MS = 30 * 60 * 1000; export const DEFAULT_CODE_AGENT_MAX_SESSIONS = 200; export const DEFAULT_CODE_AGENT_MAX_CONVERSATION_FACTS = 12; export const DEFAULT_CODE_AGENT_MAX_FACT_TOOL_CALLS = 8; export const DEFAULT_CODE_AGENT_MAX_FACT_SKILLS = 24; export const CODE_AGENT_SESSION_STATUSES = Object.freeze([ "creating", "ready", "busy", "idle", "timeout", "error", "canceled", "interrupted", "expired", "failed" ]); export function createCodeAgentSessionRegistry(options = {}) { const idleTimeoutMs = positiveInteger(options.idleTimeoutMs, DEFAULT_CODE_AGENT_SESSION_IDLE_TIMEOUT_MS); const maxSessions = positiveInteger(options.maxSessions, DEFAULT_CODE_AGENT_MAX_SESSIONS); const maxConversationFacts = positiveInteger(options.maxConversationFacts, DEFAULT_CODE_AGENT_MAX_CONVERSATION_FACTS); const maxFactToolCalls = positiveInteger(options.maxFactToolCalls, DEFAULT_CODE_AGENT_MAX_FACT_TOOL_CALLS); const maxFactSkills = positiveInteger(options.maxFactSkills, DEFAULT_CODE_AGENT_MAX_FACT_SKILLS); const idFactory = typeof options.idFactory === "function" ? options.idFactory : () => `ses_${randomUUID()}`; const defaultNow = options.now; const sessions = new Map(); const conversations = new Map(); const conversationFacts = new Map(); function acquire(params = {}) { const timestamp = timestampFor(params.now ?? defaultNow); const timestampMs = Date.parse(timestamp); const conversationId = requiredId(params.conversationId, "cnv"); const requestedSessionId = optionalId(params.sessionId); const mappedSessionId = conversations.get(conversationId) ?? null; if (mappedSessionId && requestedSessionId && mappedSessionId !== requestedSessionId) { const mappedSession = sessions.get(mappedSessionId) ?? null; return blockedAcquire({ code: "session_reuse_conflict", summary: `conversationId ${conversationId} is already bound to sessionId ${mappedSessionId}; requested ${requestedSessionId}.`, session: mappedSession, timestamp, traceId: params.traceId }); } const effectiveSessionId = mappedSessionId || requestedSessionId || requiredId(idFactory(), "ses"); let session = sessions.get(effectiveSessionId) ?? null; const reused = Boolean(session); if (session && sessionExpired(session, timestampMs)) { session.status = "expired"; session.updatedAt = timestamp; session.lastTraceId = optionalId(params.traceId); return blockedAcquire({ code: "session_expired", summary: `Code Agent runner session ${effectiveSessionId} expired after ${session.idleTimeoutMs}ms idle timeout.`, session, timestamp, traceId: params.traceId }); } if (session?.status === "busy") { session.updatedAt = timestamp; session.lastTraceId = optionalId(params.traceId) ?? session.lastTraceId; return blockedAcquire({ code: "session_busy", summary: `Code Agent runner session ${effectiveSessionId} is already busy with traceId ${session.currentTraceId ?? "unknown"}.`, session, timestamp, traceId: params.traceId }); } if (session && ["failed", "interrupted", "timeout", "error", "canceled"].includes(session.status)) { session.updatedAt = timestamp; session.lastTraceId = optionalId(params.traceId) ?? session.lastTraceId; return blockedAcquire({ code: `session_${session.status}`, summary: `Code Agent runner session ${effectiveSessionId} is ${session.status}; create a new session before retrying.`, session, timestamp, traceId: params.traceId }); } if (!session) { session = { sessionId: effectiveSessionId, conversationIds: new Set(), status: "creating", workspace: params.workspace ?? null, sandbox: params.sandbox ?? "read-only", runnerKind: params.runnerKind ?? "hwlab-readonly-runner", sessionMode: params.sessionMode ?? params.implementationType ?? "controlled-readonly-session-registry", capabilityLevel: params.capabilityLevel ?? "read-only-session-tools", implementationType: params.implementationType ?? "controlled-readonly-session-registry", createdAt: timestamp, updatedAt: timestamp, idleTimeoutMs, expiresAt: plusMs(timestampMs, idleTimeoutMs), lastTraceId: optionalId(params.traceId), currentTraceId: null, turn: 0, durable: false, longLivedSession: true, codexStdio: false, readOnly: true, writeCapable: false, secretMaterialStored: false }; sessions.set(effectiveSessionId, session); } session.conversationIds.add(conversationId); session.workspace = params.workspace ?? session.workspace; session.sandbox = params.sandbox ?? session.sandbox; session.runnerKind = params.runnerKind ?? session.runnerKind; session.sessionMode = params.sessionMode ?? session.sessionMode; session.capabilityLevel = params.capabilityLevel ?? session.capabilityLevel; session.implementationType = params.implementationType ?? session.implementationType; session.status = "busy"; session.updatedAt = timestamp; session.expiresAt = plusMs(timestampMs, idleTimeoutMs); session.lastTraceId = optionalId(params.traceId); session.currentTraceId = optionalId(params.traceId); session.turn += 1; conversations.set(conversationId, effectiveSessionId); prune(); return { ok: true, reused, session: publicSession(session, { conversationId, reused, timestamp }) }; } function release(sessionId, params = {}) { const session = sessions.get(requiredId(sessionId, "ses")) ?? null; if (!session) return null; const timestamp = timestampFor(params.now ?? defaultNow); const timestampMs = Date.parse(timestamp); session.status = CODE_AGENT_SESSION_STATUSES.includes(params.status) ? params.status : "idle"; session.updatedAt = timestamp; session.expiresAt = plusMs(timestampMs, session.idleTimeoutMs); session.lastTraceId = optionalId(params.traceId) ?? session.lastTraceId; session.currentTraceId = null; session.statusReason = params.statusReason ?? null; return publicSession(session, { conversationId: params.conversationId, reused: params.reused, timestamp }); } function fail(sessionId, params = {}) { return release(sessionId, { ...params, status: "failed" }); } function interrupt(sessionId, params = {}) { return release(sessionId, { ...params, status: "interrupted" }); } function get(sessionId, params = {}) { const session = sessions.get(requiredId(sessionId, "ses")) ?? null; return session ? publicSession(session, params) : null; } function getByConversation(conversationId, params = {}) { const sessionId = conversations.get(requiredId(conversationId, "cnv")); return sessionId ? get(sessionId, { ...params, conversationId }) : null; } function recordFact(conversationIdValue, fact, params = {}) { const conversationId = requiredId(conversationIdValue, "cnv"); const timestamp = timestampFor(params.now ?? defaultNow); const sanitized = sanitizeConversationFact(fact, { conversationId, timestamp, maxFactToolCalls, maxFactSkills }); if (!sanitized) return getConversationFacts(conversationId); const existing = conversationFacts.get(conversationId) ?? { conversationId, facts: [] }; existing.facts.push(sanitized); existing.facts = existing.facts.slice(-maxConversationFacts); conversationFacts.set(conversationId, existing); return getConversationFacts(conversationId); } function getConversationFacts(conversationIdValue, params = {}) { const conversationId = requiredId(conversationIdValue, "cnv"); const stored = conversationFacts.get(conversationId); const facts = stored?.facts ?? []; const session = getByConversation(conversationId, params); return publicConversationFacts({ conversationId, session, facts, maxFactToolCalls, maxConversationFacts }); } function expireIdle(params = {}) { const timestamp = timestampFor(params.now ?? defaultNow); const timestampMs = Date.parse(timestamp); let expired = 0; for (const session of sessions.values()) { if (!sessionExpired(session, timestampMs)) continue; session.status = "expired"; session.updatedAt = timestamp; session.currentTraceId = null; expired += 1; } return expired; } function describe() { return { kind: "code-agent-session-registry", implementationType: "controlled-readonly-session-registry", status: "available", sessionCount: sessions.size, conversationFactCount: conversationFacts.size, maxSessions, maxConversationFacts, idleTimeoutMs, statuses: [...CODE_AGENT_SESSION_STATUSES], sessionMode: "controlled-readonly-session-registry", recentSessions: [...sessions.values()] .sort((a, b) => String(b.updatedAt).localeCompare(String(a.updatedAt))) .slice(0, 10) .map((session) => publicSession(session)), longLivedCodexStdio: false, longLivedSession: true, durable: false, secretMaterialStored: false, valuesRedacted: true }; } function clear() { sessions.clear(); conversations.clear(); conversationFacts.clear(); } function prune() { if (sessions.size <= maxSessions) return; const sorted = [...sessions.entries()] .sort((a, b) => String(a[1].updatedAt).localeCompare(String(b[1].updatedAt))); for (const [sessionId, session] of sorted.slice(0, sessions.size - maxSessions)) { sessions.delete(sessionId); for (const conversationId of session.conversationIds) { if (conversations.get(conversationId) === sessionId) { conversations.delete(conversationId); } conversationFacts.delete(conversationId); } } } return { acquire, release, fail, interrupt, get, getByConversation, recordFact, getConversationFacts, expireIdle, describe, clear }; } function blockedAcquire({ code, summary, session, timestamp, traceId }) { const publicEvidence = session ? publicSession(session, { timestamp, reused: true }) : { status: "failed", updatedAt: timestamp, lastTraceId: optionalId(traceId), secretMaterialStored: false, valuesRedacted: true }; return { ok: false, code, message: summary, session: publicEvidence, blocker: { code, sourceIssue: "pikasTech/HWLAB#317", summary } }; } function publicSession(session, { conversationId = null, reused = null } = {}) { const conversationIds = [...session.conversationIds]; return { sessionId: session.sessionId, conversationId: conversationId ?? conversationIds[0] ?? null, conversationIds, status: session.status, workspace: session.workspace, sandbox: session.sandbox, runnerKind: session.runnerKind, sessionMode: session.sessionMode, capabilityLevel: session.capabilityLevel, implementationType: session.implementationType, createdAt: session.createdAt, updatedAt: session.updatedAt, idleTimeoutMs: session.idleTimeoutMs, expiresAt: session.expiresAt, lastTraceId: session.lastTraceId, currentTraceId: session.currentTraceId, turn: session.turn, reused: reused === null ? undefined : Boolean(reused), durable: session.durable === true, durableSession: session.durable === true, longLivedSession: session.longLivedSession === true, codexStdio: session.codexStdio === true, readOnly: session.readOnly === true, writeCapable: session.writeCapable === true, secretMaterialStored: false, valuesRedacted: true, ...(session.statusReason ? { statusReason: session.statusReason } : {}) }; } function publicConversationFacts({ conversationId, session, facts, maxFactToolCalls, maxConversationFacts }) { const latest = facts.at(-1) ?? null; const latestOperational = [...facts].reverse().find((fact) => fact.kind !== "session_context") ?? latest; const latestSessionFact = [...facts].reverse().find((fact) => fact.sessionId && fact.sessionMode !== "provider-text-request") ?? latestOperational; const latestSkills = [...facts].reverse().find((fact) => fact.skills?.status && fact.skills.status !== "not_requested")?.skills ?? null; const latestWorkspace = [...facts].reverse().find((fact) => fact.kind !== "session_context" && fact.workspace)?.workspace ?? [...facts].reverse().find((fact) => fact.workspace)?.workspace ?? session?.workspace ?? null; const traceIds = [...new Set(facts.map((fact) => fact.traceId).filter(Boolean))].slice(-6); const recentToolCalls = facts .flatMap((fact) => fact.toolCalls?.items ?? []) .slice(-maxFactToolCalls); return { conversationId, sessionId: session?.sessionId ?? latestSessionFact?.sessionId ?? latest?.sessionId ?? null, sessionStatus: session?.status ?? latestSessionFact?.sessionStatus ?? latest?.sessionStatus ?? null, sessionMode: session?.sessionMode ?? latestSessionFact?.sessionMode ?? latest?.sessionMode ?? null, capabilityLevel: latestOperational?.capabilityLevel ?? session?.capabilityLevel ?? latest?.capabilityLevel ?? null, runnerKind: latestOperational?.runnerKind ?? session?.runnerKind ?? latest?.runnerKind ?? null, latestProvider: latestOperational?.provider ?? latest?.provider ?? null, latestBackend: latestOperational?.backend ?? latest?.backend ?? null, workspace: latestWorkspace, sandbox: latestOperational?.sandbox ?? session?.sandbox ?? latest?.sandbox ?? null, turnCount: facts.length, maxFacts: maxConversationFacts, traceIds, latestTraceId: traceIds.at(-1) ?? session?.lastTraceId ?? null, latestSkills, recentToolCalls, facts: facts.map((fact) => ({ ...fact })), valuesRedacted: true, secretMaterialStored: false, summary: conversationFactsSummary({ facts, session, latestSkills, latestWorkspace, traceIds, recentToolCalls }) }; } function sanitizeConversationFact(fact, { conversationId, timestamp, maxFactToolCalls, maxFactSkills }) { if (!fact || typeof fact !== "object") return null; const session = fact.session && typeof fact.session === "object" ? fact.session : null; const runner = fact.runner && typeof fact.runner === "object" ? fact.runner : null; const runnerTrace = fact.runnerTrace && typeof fact.runnerTrace === "object" ? fact.runnerTrace : null; const toolCalls = sanitizeToolCalls(fact.toolCalls, maxFactToolCalls); const skills = sanitizeSkillsFact(fact.skills, maxFactSkills); return { kind: safeFactText(fact.kind ?? "runner_turn", 80), conversationId, sessionId: safeFactText(session?.sessionId ?? fact.sessionId ?? runner?.sessionId, 120), sessionStatus: safeFactText(session?.status ?? fact.sessionStatus, 40), sessionMode: safeFactText(fact.sessionMode ?? session?.sessionMode ?? runner?.sessionMode ?? runnerTrace?.sessionMode, 120), sessionReused: fact.sessionReuse?.reused === true || session?.reused === true || runner?.sessionReused === true, turn: numberOrNull(session?.turn ?? fact.sessionReuse?.turn ?? runnerTrace?.turn ?? runner?.turn), recordedAt: timestamp, traceId: safeFactText(fact.traceId ?? runnerTrace?.traceId, 120), provider: safeFactText(fact.provider ?? runner?.provider, 120), backend: safeFactText(fact.backend ?? runner?.backend, 160), runnerKind: safeFactText(runner?.kind ?? runnerTrace?.runnerKind ?? fact.runnerKind, 120), workspace: safeFactText(fact.workspace ?? runner?.workspace ?? runnerTrace?.workspace ?? session?.workspace, 240), sandbox: safeFactText(fact.sandbox ?? runner?.sandbox ?? runnerTrace?.sandbox ?? session?.sandbox, 120), capabilityLevel: safeFactText(fact.capabilityLevel ?? runner?.capabilityLevel ?? session?.capabilityLevel, 120), toolCalls, skills, runnerTrace: sanitizeRunnerTrace(runnerTrace), valuesRedacted: true, secretMaterialStored: false }; } function sanitizeToolCalls(toolCalls, limit) { const items = Array.isArray(toolCalls) ? toolCalls.slice(0, limit).map((toolCall) => ({ name: safeFactText(toolCall?.name, 120), type: safeFactText(toolCall?.type, 80), status: safeFactText(toolCall?.status, 40), route: safeFactText(toolCall?.route ?? toolCall?.hwlabApi?.route, 120), method: safeFactText(toolCall?.method, 16), traceId: safeFactText(toolCall?.traceId, 120), outputTruncated: toolCall?.outputTruncated === true })).filter((toolCall) => toolCall.name || toolCall.status || toolCall.route) : []; return { count: Array.isArray(toolCalls) ? toolCalls.length : 0, items, truncated: Array.isArray(toolCalls) && toolCalls.length > items.length }; } function sanitizeSkillsFact(skills, limit) { if (!skills || typeof skills !== "object") { return { status: "not_requested", count: 0, totalCount: 0, names: [], blockers: [], truncated: false }; } const items = Array.isArray(skills.items) ? skills.items : []; const names = items .map((item) => safeFactText(item?.name, 120)) .filter(Boolean) .slice(0, limit); return { status: safeFactText(skills.status ?? "unknown", 40), count: numberOrNull(skills.count ?? items.length) ?? items.length, totalCount: numberOrNull(skills.totalCount ?? skills.count ?? items.length) ?? items.length, names, blockers: Array.isArray(skills.blockers) ? skills.blockers.map((blocker) => safeFactText(blocker?.code, 120)).filter(Boolean).slice(0, 6) : [], truncated: skills.truncated === true || items.length > names.length }; } function sanitizeRunnerTrace(runnerTrace) { if (!runnerTrace || typeof runnerTrace !== "object") return null; return { traceId: safeFactText(runnerTrace.traceId, 120), runnerKind: safeFactText(runnerTrace.runnerKind, 120), sessionMode: safeFactText(runnerTrace.sessionMode, 120), sessionId: safeFactText(runnerTrace.sessionId, 120), turn: numberOrNull(runnerTrace.turn), events: Array.isArray(runnerTrace.events) ? runnerTrace.events.map((event) => safeFactText(event, 120)).filter(Boolean).slice(-8) : [], route: safeFactText(runnerTrace.route, 120), status: safeFactText(runnerTrace.status, 80), outputTruncated: runnerTrace.outputTruncated === true, valuesPrinted: false }; } function conversationFactsSummary({ facts, session, latestSkills, latestWorkspace, traceIds, recentToolCalls }) { const skillNames = latestSkills?.names?.slice(0, 5) ?? []; const toolNames = recentToolCalls.map((toolCall) => [toolCall.name, toolCall.status].filter(Boolean).join(":")).filter(Boolean).slice(-5); return [ `turns=${facts.length}`, `sessionId=${session?.sessionId ?? facts.at(-1)?.sessionId ?? "unknown"}`, `sessionStatus=${session?.status ?? facts.at(-1)?.sessionStatus ?? "unknown"}`, latestWorkspace ? `workspace=${latestWorkspace}` : null, latestSkills ? `skills=${latestSkills.totalCount ?? latestSkills.count ?? skillNames.length}${skillNames.length ? ` (${skillNames.join(",")})` : ""}` : "skills=not_requested", traceIds.length ? `traceIds=${traceIds.join(",")}` : null, toolNames.length ? `tools=${toolNames.join(",")}` : null ].filter(Boolean).join(" / "); } function safeFactText(value, maxLength) { if (value === undefined || value === null || value === "") return null; const normalized = String(value) .replace(/\s+/gu, " ") .replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gu, "Bearer ***") .replace(/sk-[A-Za-z0-9._-]+/gu, "sk-***") .replace(/([A-Za-z0-9_]*(?:TOKEN|KEY|SECRET|PASSWORD)[A-Za-z0-9_]*=)[^\s]+/giu, "$1***") .replace(/\/\/[^/@\s]+@/gu, "//***@") .trim(); if (!normalized) return null; return normalized.length > maxLength ? `${normalized.slice(0, maxLength - 12)}...[redacted]` : normalized; } function numberOrNull(value) { return Number.isFinite(value) ? value : null; } function sessionExpired(session, timestampMs) { if (!Number.isFinite(timestampMs) || session.status === "expired") return session.status === "expired"; return Date.parse(session.expiresAt) <= timestampMs; } function timestampFor(now) { const value = typeof now === "function" ? now() : now; if (typeof value === "string" && !Number.isNaN(Date.parse(value))) return value; if (value instanceof Date && !Number.isNaN(value.valueOf())) return value.toISOString(); return new Date().toISOString(); } function plusMs(timestampMs, offsetMs) { return new Date(timestampMs + offsetMs).toISOString(); } function requiredId(value, fallbackPrefix) { const id = optionalId(value); if (id) return id; return `${fallbackPrefix}_${randomUUID()}`; } function optionalId(value) { return typeof value === "string" && value.trim() ? value.trim() : null; } function positiveInteger(value, fallback) { return Number.isInteger(value) && value > 0 ? value : fallback; }