Files
claw3d/src/features/agents/operations/agentFleetHydration.ts
T
Luke The Dev 4fa4f13558 First Release of Claw3D (#11)
Co-authored-by: iamlukethedev <iamlukethedev@users.noreply.github.com>
2026-03-19 23:14:04 -05:00

228 lines
7.4 KiB
TypeScript

import { buildAgentMainSessionKey, isSameSessionKey } from "@/lib/gateway/GatewayClient";
import { type GatewayModelPolicySnapshot } from "@/lib/gateway/models";
import { type StudioSettings, type StudioSettingsPublic } from "@/lib/studio/settings";
import {
type SummaryPreviewSnapshot,
type SummarySnapshotPatch,
type SummaryStatusSnapshot,
} from "@/features/agents/state/runtimeEventBridge";
import type { AgentStoreSeed } from "@/features/agents/state/store";
import { deriveHydrateAgentFleetResult } from "@/features/agents/operations/agentFleetHydrationDerivation";
type GatewayClientLike = {
call: (method: string, params: unknown) => Promise<unknown>;
getLastHello?: () => { snapshot?: unknown } | null;
};
type AgentsListResult = {
defaultId: string;
mainKey: string;
scope?: string;
agents: Array<{
id: string;
name?: string;
identity?: {
name?: string;
theme?: string;
emoji?: string;
avatar?: string;
avatarUrl?: string;
};
}>;
};
type SessionsListEntry = {
key: string;
updatedAt?: number | null;
displayName?: string;
origin?: { label?: string | null; provider?: string | null } | null;
thinkingLevel?: string;
modelProvider?: string;
model?: string;
execHost?: string | null;
execSecurity?: string | null;
execAsk?: string | null;
};
type SessionsListResult = {
sessions?: SessionsListEntry[];
};
type ExecApprovalsSnapshot = {
file?: {
agents?: Record<string, { security?: string | null; ask?: string | null }>;
};
};
const isRecord = (value: unknown): value is Record<string, unknown> =>
Boolean(value && typeof value === "object" && !Array.isArray(value));
const resolveAgentsListFromHelloSnapshot = (snapshot: unknown): AgentsListResult | null => {
if (!isRecord(snapshot)) return null;
const health = isRecord(snapshot.health) ? snapshot.health : null;
const sessionDefaults = isRecord(snapshot.sessionDefaults) ? snapshot.sessionDefaults : null;
const rawAgents = Array.isArray(health?.agents) ? health.agents : [];
const agents = rawAgents.flatMap((entry) => {
if (!isRecord(entry)) return [];
const id = typeof entry.agentId === "string" ? entry.agentId.trim() : "";
if (!id) return [];
const name = typeof entry.name === "string" ? entry.name.trim() : "";
return [
{
id,
...(name ? { name } : {}),
},
];
});
if (agents.length === 0) return null;
const defaultId =
typeof health?.defaultAgentId === "string"
? health.defaultAgentId.trim()
: agents.find((entry, index) => {
const raw = rawAgents[index];
return isRecord(raw) && raw.isDefault === true;
})?.id ?? agents[0]?.id ?? "main";
const mainKey =
typeof sessionDefaults?.mainKey === "string" ? sessionDefaults.mainKey.trim() || "main" : "main";
const scope =
typeof sessionDefaults?.scope === "string" ? sessionDefaults.scope.trim() || undefined : undefined;
return {
defaultId,
mainKey,
...(scope ? { scope } : {}),
agents,
};
};
export type HydrateAgentFleetResult = {
seeds: AgentStoreSeed[];
sessionCreatedAgentIds: string[];
sessionSettingsSyncedAgentIds: string[];
summaryPatches: SummarySnapshotPatch[];
suggestedSelectedAgentId: string | null;
configSnapshot: GatewayModelPolicySnapshot | null;
};
export async function hydrateAgentFleetFromGateway(params: {
client: GatewayClientLike;
gatewayUrl: string;
cachedConfigSnapshot: GatewayModelPolicySnapshot | null;
loadStudioSettings: () => Promise<StudioSettings | StudioSettingsPublic | null>;
isDisconnectLikeError: (err: unknown) => boolean;
logError?: (message: string, error: unknown) => void;
}): Promise<HydrateAgentFleetResult> {
const logError = params.logError ?? ((message, error) => console.error(message, error));
let configSnapshot = params.cachedConfigSnapshot;
if (!configSnapshot) {
try {
configSnapshot = (await params.client.call(
"config.get",
{}
)) as GatewayModelPolicySnapshot;
} catch (err) {
if (!params.isDisconnectLikeError(err)) {
logError("Failed to load gateway config while loading agents.", err);
}
}
}
const gatewayKey = params.gatewayUrl.trim();
let settings: StudioSettings | StudioSettingsPublic | null = null;
if (gatewayKey) {
try {
settings = await params.loadStudioSettings();
} catch (err) {
logError("Failed to load studio settings while loading agents.", err);
}
}
let execApprovalsSnapshot: ExecApprovalsSnapshot | null = null;
try {
execApprovalsSnapshot = (await params.client.call(
"exec.approvals.get",
{}
)) as ExecApprovalsSnapshot;
} catch (err) {
if (!params.isDisconnectLikeError(err)) {
logError("Failed to load exec approvals while loading agents.", err);
}
}
let agentsResult = (await params.client.call("agents.list", {})) as AgentsListResult;
if (!Array.isArray(agentsResult?.agents) || agentsResult.agents.length === 0) {
const fallback = resolveAgentsListFromHelloSnapshot(params.client.getLastHello?.()?.snapshot);
if (fallback) {
agentsResult = fallback;
}
}
const mainKey = agentsResult.mainKey?.trim() || "main";
const mainSessionKeyByAgent = new Map<string, SessionsListEntry | null>();
await Promise.all(
agentsResult.agents.map(async (agent) => {
try {
const expectedMainKey = buildAgentMainSessionKey(agent.id, mainKey);
const sessions = (await params.client.call("sessions.list", {
agentId: agent.id,
includeGlobal: false,
includeUnknown: false,
search: expectedMainKey,
limit: 4,
})) as SessionsListResult;
const entries = Array.isArray(sessions.sessions) ? sessions.sessions : [];
const mainEntry =
entries.find((entry) => isSameSessionKey(entry.key ?? "", expectedMainKey)) ?? null;
mainSessionKeyByAgent.set(agent.id, mainEntry);
} catch (err) {
if (!params.isDisconnectLikeError(err)) {
logError("Failed to list sessions while resolving agent session.", err);
}
mainSessionKeyByAgent.set(agent.id, null);
}
})
);
let statusSummary: SummaryStatusSnapshot | null = null;
let previewResult: SummaryPreviewSnapshot | null = null;
try {
const sessionKeys = Array.from(
new Set(
agentsResult.agents
.filter((agent) => Boolean(mainSessionKeyByAgent.get(agent.id)))
.map((agent) => buildAgentMainSessionKey(agent.id, mainKey))
.filter((key) => key.trim().length > 0)
)
).slice(0, 64);
if (sessionKeys.length > 0) {
const snapshot = await Promise.all([
params.client.call("status", {}) as Promise<SummaryStatusSnapshot>,
params.client.call("sessions.preview", {
keys: sessionKeys,
limit: 8,
maxChars: 240,
}) as Promise<SummaryPreviewSnapshot>,
]);
statusSummary = snapshot[0] ?? null;
previewResult = snapshot[1] ?? null;
}
} catch (err) {
if (!params.isDisconnectLikeError(err)) {
logError("Failed to load initial summary snapshot.", err);
}
}
const derived = deriveHydrateAgentFleetResult({
gatewayUrl: params.gatewayUrl,
configSnapshot: configSnapshot ?? null,
settings,
execApprovalsSnapshot,
agentsResult,
mainSessionByAgentId: mainSessionKeyByAgent,
statusSummary,
previewResult,
});
return derived;
}