First Release of Claw3D (#11)
Co-authored-by: iamlukethedev <iamlukethedev@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user