mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 18:28:26 +00:00
feat(ui): add Agents dashboard
This commit is contained in:
@@ -6,6 +6,7 @@ import { normalizeAgentId } from "../routing/session-key.js";
|
||||
|
||||
const MAX_ASSISTANT_NAME = 50;
|
||||
const MAX_ASSISTANT_AVATAR = 200;
|
||||
const MAX_ASSISTANT_EMOJI = 16;
|
||||
|
||||
export const DEFAULT_ASSISTANT_IDENTITY: AssistantIdentity = {
|
||||
agentId: "main",
|
||||
@@ -17,6 +18,7 @@ export type AssistantIdentity = {
|
||||
agentId: string;
|
||||
name: string;
|
||||
avatar: string;
|
||||
emoji?: string;
|
||||
};
|
||||
|
||||
function coerceIdentityValue(value: string | undefined, maxLength: number): string | undefined {
|
||||
@@ -64,6 +66,33 @@ function normalizeAvatarValue(value: string | undefined): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function normalizeEmojiValue(value: string | undefined): string | undefined {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
if (trimmed.length > MAX_ASSISTANT_EMOJI) {
|
||||
return undefined;
|
||||
}
|
||||
let hasNonAscii = false;
|
||||
for (let i = 0; i < trimmed.length; i += 1) {
|
||||
if (trimmed.charCodeAt(i) > 127) {
|
||||
hasNonAscii = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasNonAscii) {
|
||||
return undefined;
|
||||
}
|
||||
if (isAvatarUrl(trimmed) || looksLikeAvatarPath(trimmed)) {
|
||||
return undefined;
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
export function resolveAssistantIdentity(params: {
|
||||
cfg: OpenClawConfig;
|
||||
agentId?: string | null;
|
||||
@@ -92,5 +121,13 @@ export function resolveAssistantIdentity(params: {
|
||||
avatarCandidates.map((candidate) => normalizeAvatarValue(candidate)).find(Boolean) ??
|
||||
DEFAULT_ASSISTANT_IDENTITY.avatar;
|
||||
|
||||
return { agentId, name, avatar };
|
||||
const emojiCandidates = [
|
||||
coerceIdentityValue(agentIdentity?.emoji, MAX_ASSISTANT_EMOJI),
|
||||
coerceIdentityValue(fileIdentity?.emoji, MAX_ASSISTANT_EMOJI),
|
||||
coerceIdentityValue(agentIdentity?.avatar, MAX_ASSISTANT_EMOJI),
|
||||
coerceIdentityValue(fileIdentity?.avatar, MAX_ASSISTANT_EMOJI),
|
||||
];
|
||||
const emoji = emojiCandidates.map((candidate) => normalizeEmojiValue(candidate)).find(Boolean);
|
||||
|
||||
return { agentId, name, avatar, emoji };
|
||||
}
|
||||
|
||||
@@ -9,6 +9,20 @@ import {
|
||||
AgentParamsSchema,
|
||||
type AgentSummary,
|
||||
AgentSummarySchema,
|
||||
type AgentsFileEntry,
|
||||
AgentsFileEntrySchema,
|
||||
type AgentsFilesGetParams,
|
||||
AgentsFilesGetParamsSchema,
|
||||
type AgentsFilesGetResult,
|
||||
AgentsFilesGetResultSchema,
|
||||
type AgentsFilesListParams,
|
||||
AgentsFilesListParamsSchema,
|
||||
type AgentsFilesListResult,
|
||||
AgentsFilesListResultSchema,
|
||||
type AgentsFilesSetParams,
|
||||
AgentsFilesSetParamsSchema,
|
||||
type AgentsFilesSetResult,
|
||||
AgentsFilesSetResultSchema,
|
||||
type AgentsListParams,
|
||||
AgentsListParamsSchema,
|
||||
type AgentsListResult,
|
||||
@@ -209,6 +223,15 @@ export const validateAgentIdentityParams =
|
||||
export const validateAgentWaitParams = ajv.compile<AgentWaitParams>(AgentWaitParamsSchema);
|
||||
export const validateWakeParams = ajv.compile<WakeParams>(WakeParamsSchema);
|
||||
export const validateAgentsListParams = ajv.compile<AgentsListParams>(AgentsListParamsSchema);
|
||||
export const validateAgentsFilesListParams = ajv.compile<AgentsFilesListParams>(
|
||||
AgentsFilesListParamsSchema,
|
||||
);
|
||||
export const validateAgentsFilesGetParams = ajv.compile<AgentsFilesGetParams>(
|
||||
AgentsFilesGetParamsSchema,
|
||||
);
|
||||
export const validateAgentsFilesSetParams = ajv.compile<AgentsFilesSetParams>(
|
||||
AgentsFilesSetParamsSchema,
|
||||
);
|
||||
export const validateNodePairRequestParams = ajv.compile<NodePairRequestParams>(
|
||||
NodePairRequestParamsSchema,
|
||||
);
|
||||
@@ -408,6 +431,13 @@ export {
|
||||
WebLoginStartParamsSchema,
|
||||
WebLoginWaitParamsSchema,
|
||||
AgentSummarySchema,
|
||||
AgentsFileEntrySchema,
|
||||
AgentsFilesListParamsSchema,
|
||||
AgentsFilesListResultSchema,
|
||||
AgentsFilesGetParamsSchema,
|
||||
AgentsFilesGetResultSchema,
|
||||
AgentsFilesSetParamsSchema,
|
||||
AgentsFilesSetResultSchema,
|
||||
AgentsListParamsSchema,
|
||||
AgentsListResultSchema,
|
||||
ModelsListParamsSchema,
|
||||
@@ -482,6 +512,13 @@ export type {
|
||||
WebLoginStartParams,
|
||||
WebLoginWaitParams,
|
||||
AgentSummary,
|
||||
AgentsFileEntry,
|
||||
AgentsFilesListParams,
|
||||
AgentsFilesListResult,
|
||||
AgentsFilesGetParams,
|
||||
AgentsFilesGetResult,
|
||||
AgentsFilesSetParams,
|
||||
AgentsFilesSetResult,
|
||||
AgentsListParams,
|
||||
AgentsListResult,
|
||||
SkillsStatusParams,
|
||||
|
||||
@@ -84,6 +84,7 @@ export const AgentIdentityResultSchema = Type.Object(
|
||||
agentId: NonEmptyString,
|
||||
name: Type.Optional(NonEmptyString),
|
||||
avatar: Type.Optional(NonEmptyString),
|
||||
emoji: Type.Optional(NonEmptyString),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
@@ -44,6 +44,70 @@ export const AgentsListResultSchema = Type.Object(
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export const AgentsFileEntrySchema = Type.Object(
|
||||
{
|
||||
name: NonEmptyString,
|
||||
path: NonEmptyString,
|
||||
missing: Type.Boolean(),
|
||||
size: Type.Optional(Type.Integer({ minimum: 0 })),
|
||||
updatedAtMs: Type.Optional(Type.Integer({ minimum: 0 })),
|
||||
content: Type.Optional(Type.String()),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export const AgentsFilesListParamsSchema = Type.Object(
|
||||
{
|
||||
agentId: NonEmptyString,
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export const AgentsFilesListResultSchema = Type.Object(
|
||||
{
|
||||
agentId: NonEmptyString,
|
||||
workspace: NonEmptyString,
|
||||
files: Type.Array(AgentsFileEntrySchema),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export const AgentsFilesGetParamsSchema = Type.Object(
|
||||
{
|
||||
agentId: NonEmptyString,
|
||||
name: NonEmptyString,
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export const AgentsFilesGetResultSchema = Type.Object(
|
||||
{
|
||||
agentId: NonEmptyString,
|
||||
workspace: NonEmptyString,
|
||||
file: AgentsFileEntrySchema,
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export const AgentsFilesSetParamsSchema = Type.Object(
|
||||
{
|
||||
agentId: NonEmptyString,
|
||||
name: NonEmptyString,
|
||||
content: Type.String(),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export const AgentsFilesSetResultSchema = Type.Object(
|
||||
{
|
||||
ok: Type.Literal(true),
|
||||
agentId: NonEmptyString,
|
||||
workspace: NonEmptyString,
|
||||
file: AgentsFileEntrySchema,
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export const ModelsListParamsSchema = Type.Object({}, { additionalProperties: false });
|
||||
|
||||
export const ModelsListResultSchema = Type.Object(
|
||||
@@ -53,7 +117,12 @@ export const ModelsListResultSchema = Type.Object(
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export const SkillsStatusParamsSchema = Type.Object({}, { additionalProperties: false });
|
||||
export const SkillsStatusParamsSchema = Type.Object(
|
||||
{
|
||||
agentId: Type.Optional(NonEmptyString),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export const SkillsBinsParamsSchema = Type.Object({}, { additionalProperties: false });
|
||||
|
||||
|
||||
@@ -11,6 +11,13 @@ import {
|
||||
} from "./agent.js";
|
||||
import {
|
||||
AgentSummarySchema,
|
||||
AgentsFileEntrySchema,
|
||||
AgentsFilesGetParamsSchema,
|
||||
AgentsFilesGetResultSchema,
|
||||
AgentsFilesListParamsSchema,
|
||||
AgentsFilesListResultSchema,
|
||||
AgentsFilesSetParamsSchema,
|
||||
AgentsFilesSetResultSchema,
|
||||
AgentsListParamsSchema,
|
||||
AgentsListResultSchema,
|
||||
ModelChoiceSchema,
|
||||
@@ -182,6 +189,13 @@ export const ProtocolSchemas: Record<string, TSchema> = {
|
||||
WebLoginStartParams: WebLoginStartParamsSchema,
|
||||
WebLoginWaitParams: WebLoginWaitParamsSchema,
|
||||
AgentSummary: AgentSummarySchema,
|
||||
AgentsFileEntry: AgentsFileEntrySchema,
|
||||
AgentsFilesListParams: AgentsFilesListParamsSchema,
|
||||
AgentsFilesListResult: AgentsFilesListResultSchema,
|
||||
AgentsFilesGetParams: AgentsFilesGetParamsSchema,
|
||||
AgentsFilesGetResult: AgentsFilesGetResultSchema,
|
||||
AgentsFilesSetParams: AgentsFilesSetParamsSchema,
|
||||
AgentsFilesSetResult: AgentsFilesSetResultSchema,
|
||||
AgentsListParams: AgentsListParamsSchema,
|
||||
AgentsListResult: AgentsListResultSchema,
|
||||
ModelChoice: ModelChoiceSchema,
|
||||
|
||||
@@ -9,6 +9,13 @@ import type {
|
||||
} from "./agent.js";
|
||||
import type {
|
||||
AgentSummarySchema,
|
||||
AgentsFileEntrySchema,
|
||||
AgentsFilesGetParamsSchema,
|
||||
AgentsFilesGetResultSchema,
|
||||
AgentsFilesListParamsSchema,
|
||||
AgentsFilesListResultSchema,
|
||||
AgentsFilesSetParamsSchema,
|
||||
AgentsFilesSetResultSchema,
|
||||
AgentsListParamsSchema,
|
||||
AgentsListResultSchema,
|
||||
ModelChoiceSchema,
|
||||
@@ -171,6 +178,13 @@ export type ChannelsLogoutParams = Static<typeof ChannelsLogoutParamsSchema>;
|
||||
export type WebLoginStartParams = Static<typeof WebLoginStartParamsSchema>;
|
||||
export type WebLoginWaitParams = Static<typeof WebLoginWaitParamsSchema>;
|
||||
export type AgentSummary = Static<typeof AgentSummarySchema>;
|
||||
export type AgentsFileEntry = Static<typeof AgentsFileEntrySchema>;
|
||||
export type AgentsFilesListParams = Static<typeof AgentsFilesListParamsSchema>;
|
||||
export type AgentsFilesListResult = Static<typeof AgentsFilesListResultSchema>;
|
||||
export type AgentsFilesGetParams = Static<typeof AgentsFilesGetParamsSchema>;
|
||||
export type AgentsFilesGetResult = Static<typeof AgentsFilesGetResultSchema>;
|
||||
export type AgentsFilesSetParams = Static<typeof AgentsFilesSetParamsSchema>;
|
||||
export type AgentsFilesSetResult = Static<typeof AgentsFilesSetResultSchema>;
|
||||
export type AgentsListParams = Static<typeof AgentsListParamsSchema>;
|
||||
export type AgentsListResult = Static<typeof AgentsListResultSchema>;
|
||||
export type ModelChoice = Static<typeof ModelChoiceSchema>;
|
||||
|
||||
@@ -32,6 +32,9 @@ const BASE_METHODS = [
|
||||
"talk.mode",
|
||||
"models.list",
|
||||
"agents.list",
|
||||
"agents.files.list",
|
||||
"agents.files.get",
|
||||
"agents.files.set",
|
||||
"skills.status",
|
||||
"skills.bins",
|
||||
"skills.install",
|
||||
|
||||
@@ -1,13 +1,128 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import type { GatewayRequestHandlers } from "./types.js";
|
||||
import { listAgentIds, resolveAgentWorkspaceDir } from "../../agents/agent-scope.js";
|
||||
import {
|
||||
DEFAULT_AGENTS_FILENAME,
|
||||
DEFAULT_BOOTSTRAP_FILENAME,
|
||||
DEFAULT_HEARTBEAT_FILENAME,
|
||||
DEFAULT_IDENTITY_FILENAME,
|
||||
DEFAULT_MEMORY_ALT_FILENAME,
|
||||
DEFAULT_MEMORY_FILENAME,
|
||||
DEFAULT_SOUL_FILENAME,
|
||||
DEFAULT_TOOLS_FILENAME,
|
||||
DEFAULT_USER_FILENAME,
|
||||
} from "../../agents/workspace.js";
|
||||
import { loadConfig } from "../../config/config.js";
|
||||
import { normalizeAgentId } from "../../routing/session-key.js";
|
||||
import {
|
||||
ErrorCodes,
|
||||
errorShape,
|
||||
formatValidationErrors,
|
||||
validateAgentsFilesGetParams,
|
||||
validateAgentsFilesListParams,
|
||||
validateAgentsFilesSetParams,
|
||||
validateAgentsListParams,
|
||||
} from "../protocol/index.js";
|
||||
import { listAgentsForGateway } from "../session-utils.js";
|
||||
|
||||
const BOOTSTRAP_FILE_NAMES = [
|
||||
DEFAULT_AGENTS_FILENAME,
|
||||
DEFAULT_SOUL_FILENAME,
|
||||
DEFAULT_TOOLS_FILENAME,
|
||||
DEFAULT_IDENTITY_FILENAME,
|
||||
DEFAULT_USER_FILENAME,
|
||||
DEFAULT_HEARTBEAT_FILENAME,
|
||||
DEFAULT_BOOTSTRAP_FILENAME,
|
||||
] as const;
|
||||
|
||||
const MEMORY_FILE_NAMES = [DEFAULT_MEMORY_FILENAME, DEFAULT_MEMORY_ALT_FILENAME] as const;
|
||||
|
||||
const ALLOWED_FILE_NAMES = new Set<string>([...BOOTSTRAP_FILE_NAMES, ...MEMORY_FILE_NAMES]);
|
||||
|
||||
type FileMeta = {
|
||||
size: number;
|
||||
updatedAtMs: number;
|
||||
};
|
||||
|
||||
async function statFile(filePath: string): Promise<FileMeta | null> {
|
||||
try {
|
||||
const stat = await fs.stat(filePath);
|
||||
if (!stat.isFile()) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
size: stat.size,
|
||||
updatedAtMs: Math.floor(stat.mtimeMs),
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function listAgentFiles(workspaceDir: string) {
|
||||
const files: Array<{
|
||||
name: string;
|
||||
path: string;
|
||||
missing: boolean;
|
||||
size?: number;
|
||||
updatedAtMs?: number;
|
||||
}> = [];
|
||||
|
||||
for (const name of BOOTSTRAP_FILE_NAMES) {
|
||||
const filePath = path.join(workspaceDir, name);
|
||||
const meta = await statFile(filePath);
|
||||
if (meta) {
|
||||
files.push({
|
||||
name,
|
||||
path: filePath,
|
||||
missing: false,
|
||||
size: meta.size,
|
||||
updatedAtMs: meta.updatedAtMs,
|
||||
});
|
||||
} else {
|
||||
files.push({ name, path: filePath, missing: true });
|
||||
}
|
||||
}
|
||||
|
||||
const primaryMemoryPath = path.join(workspaceDir, DEFAULT_MEMORY_FILENAME);
|
||||
const primaryMeta = await statFile(primaryMemoryPath);
|
||||
if (primaryMeta) {
|
||||
files.push({
|
||||
name: DEFAULT_MEMORY_FILENAME,
|
||||
path: primaryMemoryPath,
|
||||
missing: false,
|
||||
size: primaryMeta.size,
|
||||
updatedAtMs: primaryMeta.updatedAtMs,
|
||||
});
|
||||
} else {
|
||||
const altMemoryPath = path.join(workspaceDir, DEFAULT_MEMORY_ALT_FILENAME);
|
||||
const altMeta = await statFile(altMemoryPath);
|
||||
if (altMeta) {
|
||||
files.push({
|
||||
name: DEFAULT_MEMORY_ALT_FILENAME,
|
||||
path: altMemoryPath,
|
||||
missing: false,
|
||||
size: altMeta.size,
|
||||
updatedAtMs: altMeta.updatedAtMs,
|
||||
});
|
||||
} else {
|
||||
files.push({ name: DEFAULT_MEMORY_FILENAME, path: primaryMemoryPath, missing: true });
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
function resolveAgentIdOrError(agentIdRaw: string, cfg: ReturnType<typeof loadConfig>) {
|
||||
const agentId = normalizeAgentId(agentIdRaw);
|
||||
const allowed = new Set(listAgentIds(cfg));
|
||||
if (!allowed.has(agentId)) {
|
||||
return null;
|
||||
}
|
||||
return agentId;
|
||||
}
|
||||
|
||||
export const agentsHandlers: GatewayRequestHandlers = {
|
||||
"agents.list": ({ params, respond }) => {
|
||||
if (!validateAgentsListParams(params)) {
|
||||
@@ -26,4 +141,143 @@ export const agentsHandlers: GatewayRequestHandlers = {
|
||||
const result = listAgentsForGateway(cfg);
|
||||
respond(true, result, undefined);
|
||||
},
|
||||
"agents.files.list": async ({ params, respond }) => {
|
||||
if (!validateAgentsFilesListParams(params)) {
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(
|
||||
ErrorCodes.INVALID_REQUEST,
|
||||
`invalid agents.files.list params: ${formatValidationErrors(
|
||||
validateAgentsFilesListParams.errors,
|
||||
)}`,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
const cfg = loadConfig();
|
||||
const agentId = resolveAgentIdOrError(String(params.agentId ?? ""), cfg);
|
||||
if (!agentId) {
|
||||
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "unknown agent id"));
|
||||
return;
|
||||
}
|
||||
const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId);
|
||||
const files = await listAgentFiles(workspaceDir);
|
||||
respond(true, { agentId, workspace: workspaceDir, files }, undefined);
|
||||
},
|
||||
"agents.files.get": async ({ params, respond }) => {
|
||||
if (!validateAgentsFilesGetParams(params)) {
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(
|
||||
ErrorCodes.INVALID_REQUEST,
|
||||
`invalid agents.files.get params: ${formatValidationErrors(
|
||||
validateAgentsFilesGetParams.errors,
|
||||
)}`,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
const cfg = loadConfig();
|
||||
const agentId = resolveAgentIdOrError(String(params.agentId ?? ""), cfg);
|
||||
if (!agentId) {
|
||||
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "unknown agent id"));
|
||||
return;
|
||||
}
|
||||
const name = String(params.name ?? "").trim();
|
||||
if (!ALLOWED_FILE_NAMES.has(name)) {
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(ErrorCodes.INVALID_REQUEST, `unsupported file "${name}"`),
|
||||
);
|
||||
return;
|
||||
}
|
||||
const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId);
|
||||
const filePath = path.join(workspaceDir, name);
|
||||
const meta = await statFile(filePath);
|
||||
if (!meta) {
|
||||
respond(
|
||||
true,
|
||||
{
|
||||
agentId,
|
||||
workspace: workspaceDir,
|
||||
file: { name, path: filePath, missing: true },
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const content = await fs.readFile(filePath, "utf-8");
|
||||
respond(
|
||||
true,
|
||||
{
|
||||
agentId,
|
||||
workspace: workspaceDir,
|
||||
file: {
|
||||
name,
|
||||
path: filePath,
|
||||
missing: false,
|
||||
size: meta.size,
|
||||
updatedAtMs: meta.updatedAtMs,
|
||||
content,
|
||||
},
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
},
|
||||
"agents.files.set": async ({ params, respond }) => {
|
||||
if (!validateAgentsFilesSetParams(params)) {
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(
|
||||
ErrorCodes.INVALID_REQUEST,
|
||||
`invalid agents.files.set params: ${formatValidationErrors(
|
||||
validateAgentsFilesSetParams.errors,
|
||||
)}`,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
const cfg = loadConfig();
|
||||
const agentId = resolveAgentIdOrError(String(params.agentId ?? ""), cfg);
|
||||
if (!agentId) {
|
||||
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "unknown agent id"));
|
||||
return;
|
||||
}
|
||||
const name = String(params.name ?? "").trim();
|
||||
if (!ALLOWED_FILE_NAMES.has(name)) {
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(ErrorCodes.INVALID_REQUEST, `unsupported file "${name}"`),
|
||||
);
|
||||
return;
|
||||
}
|
||||
const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId);
|
||||
await fs.mkdir(workspaceDir, { recursive: true });
|
||||
const filePath = path.join(workspaceDir, name);
|
||||
const content = String(params.content ?? "");
|
||||
await fs.writeFile(filePath, content, "utf-8");
|
||||
const meta = await statFile(filePath);
|
||||
respond(
|
||||
true,
|
||||
{
|
||||
ok: true,
|
||||
agentId,
|
||||
workspace: workspaceDir,
|
||||
file: {
|
||||
name,
|
||||
path: filePath,
|
||||
missing: false,
|
||||
size: meta?.size,
|
||||
updatedAtMs: meta?.updatedAtMs,
|
||||
content,
|
||||
},
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import type { GatewayRequestHandlers } from "./types.js";
|
||||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../../agents/agent-scope.js";
|
||||
import {
|
||||
listAgentIds,
|
||||
resolveAgentWorkspaceDir,
|
||||
resolveDefaultAgentId,
|
||||
} from "../../agents/agent-scope.js";
|
||||
import { installSkill } from "../../agents/skills-install.js";
|
||||
import { buildWorkspaceSkillStatus } from "../../agents/skills-status.js";
|
||||
import { loadWorkspaceSkillEntries, type SkillEntry } from "../../agents/skills.js";
|
||||
import { loadConfig, writeConfigFile } from "../../config/config.js";
|
||||
import { getRemoteSkillEligibility } from "../../infra/skills-remote.js";
|
||||
import { normalizeAgentId } from "../../routing/session-key.js";
|
||||
import {
|
||||
ErrorCodes,
|
||||
errorShape,
|
||||
@@ -75,7 +80,20 @@ export const skillsHandlers: GatewayRequestHandlers = {
|
||||
return;
|
||||
}
|
||||
const cfg = loadConfig();
|
||||
const workspaceDir = resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg));
|
||||
const agentIdRaw = typeof params?.agentId === "string" ? params.agentId.trim() : "";
|
||||
const agentId = agentIdRaw ? normalizeAgentId(agentIdRaw) : resolveDefaultAgentId(cfg);
|
||||
if (agentIdRaw) {
|
||||
const knownAgents = listAgentIds(cfg);
|
||||
if (!knownAgents.includes(agentId)) {
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(ErrorCodes.INVALID_REQUEST, `unknown agent id "${agentIdRaw}"`),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId);
|
||||
const report = buildWorkspaceSkillStatus(workspaceDir, {
|
||||
config: cfg,
|
||||
eligibility: { remote: getRemoteSkillEligibility() },
|
||||
|
||||
Reference in New Issue
Block a user