Status: split lightweight gateway agent list
This commit is contained in:
parent
d47fc009de
commit
c4b18ab3c9
@ -3,7 +3,7 @@ import path from "node:path";
|
||||
import { resolveAgentWorkspaceDir } from "../../agents/agent-scope.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { loadSessionStore, resolveStorePath } from "../../config/sessions.js";
|
||||
import { listAgentsForGateway } from "../../gateway/session-utils.js";
|
||||
import { listGatewayAgentsBasic } from "../../gateway/agent-list.js";
|
||||
|
||||
async function fileExists(p: string): Promise<boolean> {
|
||||
try {
|
||||
@ -15,7 +15,7 @@ async function fileExists(p: string): Promise<boolean> {
|
||||
}
|
||||
|
||||
export async function getAgentLocalStatuses(cfg: OpenClawConfig) {
|
||||
const agentList = listAgentsForGateway(cfg);
|
||||
const agentList = listGatewayAgentsBasic(cfg);
|
||||
const now = Date.now();
|
||||
|
||||
const agents = await Promise.all(
|
||||
|
||||
@ -4,7 +4,7 @@ import { resolveAgentWorkspaceDir } from "../agents/agent-scope.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import { loadSessionStore, resolveStorePath } from "../config/sessions.js";
|
||||
import { listAgentsForGateway } from "../gateway/session-utils.js";
|
||||
import { listGatewayAgentsBasic } from "../gateway/agent-list.js";
|
||||
|
||||
export type AgentLocalStatus = {
|
||||
id: string;
|
||||
@ -36,7 +36,7 @@ async function fileExists(p: string): Promise<boolean> {
|
||||
export async function getAgentLocalStatuses(
|
||||
cfg: OpenClawConfig = loadConfig(),
|
||||
): Promise<AgentLocalStatusesResult> {
|
||||
const agentList = listAgentsForGateway(cfg);
|
||||
const agentList = listGatewayAgentsBasic(cfg);
|
||||
const now = Date.now();
|
||||
|
||||
const statuses: AgentLocalStatus[] = [];
|
||||
|
||||
@ -32,12 +32,15 @@ vi.mock("../config/sessions.js", () => ({
|
||||
resolveStorePath: vi.fn(() => "/tmp/sessions.json"),
|
||||
}));
|
||||
|
||||
vi.mock("../gateway/session-utils.js", () => ({
|
||||
classifySessionKey: vi.fn(() => "direct"),
|
||||
listAgentsForGateway: vi.fn(() => ({
|
||||
vi.mock("../gateway/agent-list.js", () => ({
|
||||
listGatewayAgentsBasic: vi.fn(() => ({
|
||||
defaultId: "main",
|
||||
agents: [{ id: "main" }],
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock("../gateway/session-utils.js", () => ({
|
||||
classifySessionKey: vi.fn(() => "direct"),
|
||||
resolveSessionModelRef: vi.fn(() => ({
|
||||
provider: "openai",
|
||||
model: "gpt-5.2",
|
||||
@ -61,6 +64,8 @@ vi.mock("../infra/system-events.js", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("../routing/session-key.js", () => ({
|
||||
normalizeAgentId: vi.fn((value: string) => value),
|
||||
normalizeMainKey: vi.fn((value?: string) => value ?? "main"),
|
||||
parseAgentSessionKey: vi.fn(() => null),
|
||||
}));
|
||||
|
||||
|
||||
@ -11,11 +11,8 @@ import {
|
||||
resolveStorePath,
|
||||
type SessionEntry,
|
||||
} from "../config/sessions.js";
|
||||
import {
|
||||
classifySessionKey,
|
||||
listAgentsForGateway,
|
||||
resolveSessionModelRef,
|
||||
} from "../gateway/session-utils.js";
|
||||
import { listGatewayAgentsBasic } from "../gateway/agent-list.js";
|
||||
import { classifySessionKey, resolveSessionModelRef } from "../gateway/session-utils.js";
|
||||
import { resolveHeartbeatSummaryForAgent } from "../infra/heartbeat-summary.js";
|
||||
import { peekSystemEvents } from "../infra/system-events.js";
|
||||
import { parseAgentSessionKey } from "../routing/session-key.js";
|
||||
@ -107,7 +104,7 @@ export async function getStatusSummary(
|
||||
resolveLinkChannelContext(cfg),
|
||||
)
|
||||
: null;
|
||||
const agentList = listAgentsForGateway(cfg);
|
||||
const agentList = listGatewayAgentsBasic(cfg);
|
||||
const heartbeatAgents: HeartbeatStatus[] = agentList.agents.map((agent) => {
|
||||
const summary = resolveHeartbeatSummaryForAgent(cfg, agent.id);
|
||||
return {
|
||||
|
||||
@ -168,7 +168,7 @@ const mocks = vi.hoisted(() => ({
|
||||
configSnapshot: null,
|
||||
}),
|
||||
callGateway: vi.fn().mockResolvedValue({}),
|
||||
listAgentsForGateway: vi.fn().mockReturnValue({
|
||||
listGatewayAgentsBasic: vi.fn().mockReturnValue({
|
||||
defaultId: "main",
|
||||
mainKey: "agent:main:main",
|
||||
scope: "per-sender",
|
||||
@ -299,11 +299,18 @@ vi.mock("../gateway/call.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../gateway/call.js")>();
|
||||
return { ...actual, callGateway: mocks.callGateway };
|
||||
});
|
||||
vi.mock("../gateway/agent-list.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../gateway/agent-list.js")>();
|
||||
return {
|
||||
...actual,
|
||||
listGatewayAgentsBasic: mocks.listGatewayAgentsBasic,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../gateway/session-utils.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../gateway/session-utils.js")>();
|
||||
return {
|
||||
...actual,
|
||||
listAgentsForGateway: mocks.listAgentsForGateway,
|
||||
};
|
||||
});
|
||||
vi.mock("../infra/openclaw-root.js", () => ({
|
||||
@ -608,11 +615,11 @@ describe("statusCommand", () => {
|
||||
});
|
||||
|
||||
it("includes sessions across agents in JSON output", async () => {
|
||||
const originalAgents = mocks.listAgentsForGateway.getMockImplementation();
|
||||
const originalAgents = mocks.listGatewayAgentsBasic.getMockImplementation();
|
||||
const originalResolveStorePath = mocks.resolveStorePath.getMockImplementation();
|
||||
const originalLoadSessionStore = mocks.loadSessionStore.getMockImplementation();
|
||||
|
||||
mocks.listAgentsForGateway.mockReturnValue({
|
||||
mocks.listGatewayAgentsBasic.mockReturnValue({
|
||||
defaultId: "main",
|
||||
mainKey: "agent:main:main",
|
||||
scope: "per-sender",
|
||||
@ -651,7 +658,7 @@ describe("statusCommand", () => {
|
||||
).toBe(true);
|
||||
|
||||
if (originalAgents) {
|
||||
mocks.listAgentsForGateway.mockImplementation(originalAgents);
|
||||
mocks.listGatewayAgentsBasic.mockImplementation(originalAgents);
|
||||
}
|
||||
if (originalResolveStorePath) {
|
||||
mocks.resolveStorePath.mockImplementation(originalResolveStorePath);
|
||||
|
||||
88
src/gateway/agent-list.ts
Normal file
88
src/gateway/agent-list.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolveStateDir } from "../config/paths.js";
|
||||
import type { SessionScope } from "../config/sessions.js";
|
||||
import { normalizeAgentId, normalizeMainKey } from "../routing/session-key.js";
|
||||
|
||||
export type GatewayAgentListRow = {
|
||||
id: string;
|
||||
name?: string;
|
||||
};
|
||||
|
||||
function listExistingAgentIdsFromDisk(): string[] {
|
||||
const root = resolveStateDir();
|
||||
const agentsDir = path.join(root, "agents");
|
||||
try {
|
||||
const entries = fs.readdirSync(agentsDir, { withFileTypes: true });
|
||||
return entries
|
||||
.filter((entry) => entry.isDirectory())
|
||||
.map((entry) => normalizeAgentId(entry.name))
|
||||
.filter(Boolean);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function listConfiguredAgentIds(cfg: OpenClawConfig): string[] {
|
||||
const ids = new Set<string>();
|
||||
const defaultId = normalizeAgentId(resolveDefaultAgentId(cfg));
|
||||
ids.add(defaultId);
|
||||
|
||||
for (const entry of cfg.agents?.list ?? []) {
|
||||
if (entry?.id) {
|
||||
ids.add(normalizeAgentId(entry.id));
|
||||
}
|
||||
}
|
||||
|
||||
for (const id of listExistingAgentIdsFromDisk()) {
|
||||
ids.add(id);
|
||||
}
|
||||
|
||||
const sorted = Array.from(ids).filter(Boolean);
|
||||
sorted.sort((a, b) => a.localeCompare(b));
|
||||
return sorted.includes(defaultId)
|
||||
? [defaultId, ...sorted.filter((id) => id !== defaultId)]
|
||||
: sorted;
|
||||
}
|
||||
|
||||
export function listGatewayAgentsBasic(cfg: OpenClawConfig): {
|
||||
defaultId: string;
|
||||
mainKey: string;
|
||||
scope: SessionScope;
|
||||
agents: GatewayAgentListRow[];
|
||||
} {
|
||||
const defaultId = normalizeAgentId(resolveDefaultAgentId(cfg));
|
||||
const mainKey = normalizeMainKey(cfg.session?.mainKey);
|
||||
const scope = cfg.session?.scope ?? "per-sender";
|
||||
const configuredById = new Map<string, { name?: string }>();
|
||||
for (const entry of cfg.agents?.list ?? []) {
|
||||
if (!entry?.id) {
|
||||
continue;
|
||||
}
|
||||
configuredById.set(normalizeAgentId(entry.id), {
|
||||
name: typeof entry.name === "string" && entry.name.trim() ? entry.name.trim() : undefined,
|
||||
});
|
||||
}
|
||||
const explicitIds = new Set(
|
||||
(cfg.agents?.list ?? [])
|
||||
.map((entry) => (entry?.id ? normalizeAgentId(entry.id) : ""))
|
||||
.filter(Boolean),
|
||||
);
|
||||
const allowedIds = explicitIds.size > 0 ? new Set([...explicitIds, defaultId]) : null;
|
||||
let agentIds = listConfiguredAgentIds(cfg).filter((id) =>
|
||||
allowedIds ? allowedIds.has(id) : true,
|
||||
);
|
||||
if (mainKey && !agentIds.includes(mainKey) && (!allowedIds || allowedIds.has(mainKey))) {
|
||||
agentIds = [...agentIds, mainKey];
|
||||
}
|
||||
const agents = agentIds.map((id) => {
|
||||
const meta = configuredById.get(id);
|
||||
return {
|
||||
id,
|
||||
name: meta?.name,
|
||||
};
|
||||
});
|
||||
return { defaultId, mainKey, scope, agents };
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user