mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 01:13:29 +00:00
fix: add agents identity helper
This commit is contained in:
209
src/commands/agents.commands.identity.ts
Normal file
209
src/commands/agents.commands.identity.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import { DEFAULT_IDENTITY_FILENAME } from "../agents/workspace.js";
|
||||
import { CONFIG_PATH_CLAWDBOT, writeConfigFile } from "../config/config.js";
|
||||
import type { IdentityConfig } from "../config/types.js";
|
||||
import { normalizeAgentId } from "../routing/session-key.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
import { requireValidConfig } from "./agents.command-shared.js";
|
||||
import {
|
||||
type AgentIdentity,
|
||||
findAgentEntryIndex,
|
||||
listAgentEntries,
|
||||
loadAgentIdentity,
|
||||
parseIdentityMarkdown,
|
||||
} from "./agents.config.js";
|
||||
|
||||
type AgentsSetIdentityOptions = {
|
||||
agent?: string;
|
||||
workspace?: string;
|
||||
identityFile?: string;
|
||||
name?: string;
|
||||
emoji?: string;
|
||||
theme?: string;
|
||||
fromIdentity?: boolean;
|
||||
json?: boolean;
|
||||
};
|
||||
|
||||
const normalizeWorkspacePath = (input: string) => path.resolve(resolveUserPath(input));
|
||||
|
||||
const coerceTrimmed = (value?: string) => {
|
||||
const trimmed = value?.trim();
|
||||
return trimmed ? trimmed : undefined;
|
||||
};
|
||||
|
||||
async function loadIdentityFromFile(filePath: string): Promise<AgentIdentity | null> {
|
||||
try {
|
||||
const content = await fs.readFile(filePath, "utf-8");
|
||||
const parsed = parseIdentityMarkdown(content);
|
||||
if (!parsed.name && !parsed.emoji && !parsed.theme && !parsed.creature && !parsed.vibe) {
|
||||
return null;
|
||||
}
|
||||
return parsed;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveAgentIdByWorkspace(
|
||||
cfg: Parameters<typeof resolveAgentWorkspaceDir>[0],
|
||||
workspaceDir: string,
|
||||
): string[] {
|
||||
const list = listAgentEntries(cfg);
|
||||
const ids =
|
||||
list.length > 0 ? list.map((entry) => normalizeAgentId(entry.id)) : [resolveDefaultAgentId(cfg)];
|
||||
const normalizedTarget = normalizeWorkspacePath(workspaceDir);
|
||||
return ids.filter(
|
||||
(id) => normalizeWorkspacePath(resolveAgentWorkspaceDir(cfg, id)) === normalizedTarget,
|
||||
);
|
||||
}
|
||||
|
||||
export async function agentsSetIdentityCommand(
|
||||
opts: AgentsSetIdentityOptions,
|
||||
runtime: RuntimeEnv = defaultRuntime,
|
||||
) {
|
||||
const cfg = await requireValidConfig(runtime);
|
||||
if (!cfg) return;
|
||||
|
||||
const agentRaw = coerceTrimmed(opts.agent);
|
||||
const nameRaw = coerceTrimmed(opts.name);
|
||||
const emojiRaw = coerceTrimmed(opts.emoji);
|
||||
const themeRaw = coerceTrimmed(opts.theme);
|
||||
const hasExplicitIdentity = Boolean(nameRaw || emojiRaw || themeRaw);
|
||||
|
||||
const identityFileRaw = coerceTrimmed(opts.identityFile);
|
||||
const workspaceRaw = coerceTrimmed(opts.workspace);
|
||||
const wantsIdentityFile = Boolean(opts.fromIdentity || identityFileRaw || !hasExplicitIdentity);
|
||||
|
||||
let identityFilePath: string | undefined;
|
||||
let workspaceDir: string | undefined;
|
||||
|
||||
if (identityFileRaw) {
|
||||
identityFilePath = normalizeWorkspacePath(identityFileRaw);
|
||||
workspaceDir = path.dirname(identityFilePath);
|
||||
} else if (workspaceRaw) {
|
||||
workspaceDir = normalizeWorkspacePath(workspaceRaw);
|
||||
} else if (wantsIdentityFile || !agentRaw) {
|
||||
workspaceDir = path.resolve(process.cwd());
|
||||
}
|
||||
|
||||
let agentId = agentRaw ? normalizeAgentId(agentRaw) : undefined;
|
||||
if (!agentId) {
|
||||
if (!workspaceDir) {
|
||||
runtime.error("Select an agent with --agent or provide a workspace via --workspace.");
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
const matches = resolveAgentIdByWorkspace(cfg, workspaceDir);
|
||||
if (matches.length === 0) {
|
||||
runtime.error(
|
||||
`No agent workspace matches ${workspaceDir}. Pass --agent to target a specific agent.`,
|
||||
);
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
if (matches.length > 1) {
|
||||
runtime.error(
|
||||
`Multiple agents match ${workspaceDir}: ${matches.join(", ")}. Pass --agent to choose one.`,
|
||||
);
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
agentId = matches[0];
|
||||
}
|
||||
|
||||
let identityFromFile: AgentIdentity | null = null;
|
||||
if (wantsIdentityFile) {
|
||||
if (identityFilePath) {
|
||||
identityFromFile = await loadIdentityFromFile(identityFilePath);
|
||||
} else if (workspaceDir) {
|
||||
identityFromFile = loadAgentIdentity(workspaceDir);
|
||||
}
|
||||
if (!identityFromFile) {
|
||||
const targetPath =
|
||||
identityFilePath ??
|
||||
(workspaceDir ? path.join(workspaceDir, DEFAULT_IDENTITY_FILENAME) : "IDENTITY.md");
|
||||
runtime.error(`No identity data found in ${targetPath}.`);
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const fileTheme =
|
||||
identityFromFile?.theme ??
|
||||
identityFromFile?.creature ??
|
||||
identityFromFile?.vibe ??
|
||||
undefined;
|
||||
const incomingIdentity: IdentityConfig = {
|
||||
...(nameRaw || identityFromFile?.name ? { name: nameRaw ?? identityFromFile?.name } : {}),
|
||||
...(emojiRaw || identityFromFile?.emoji ? { emoji: emojiRaw ?? identityFromFile?.emoji } : {}),
|
||||
...(themeRaw || fileTheme ? { theme: themeRaw ?? fileTheme } : {}),
|
||||
};
|
||||
|
||||
if (!incomingIdentity.name && !incomingIdentity.emoji && !incomingIdentity.theme) {
|
||||
runtime.error("No identity fields provided. Use --name/--emoji/--theme or --from-identity.");
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
const list = listAgentEntries(cfg);
|
||||
const index = findAgentEntryIndex(list, agentId);
|
||||
const base = index >= 0 ? list[index] : { id: agentId };
|
||||
const nextIdentity: IdentityConfig = {
|
||||
...base.identity,
|
||||
...incomingIdentity,
|
||||
};
|
||||
|
||||
const nextEntry = {
|
||||
...base,
|
||||
identity: nextIdentity,
|
||||
};
|
||||
|
||||
const nextList = [...list];
|
||||
if (index >= 0) {
|
||||
nextList[index] = nextEntry;
|
||||
} else {
|
||||
const defaultId = normalizeAgentId(resolveDefaultAgentId(cfg));
|
||||
if (nextList.length === 0 && agentId !== defaultId) {
|
||||
nextList.push({ id: defaultId });
|
||||
}
|
||||
nextList.push(nextEntry);
|
||||
}
|
||||
|
||||
const nextConfig = {
|
||||
...cfg,
|
||||
agents: {
|
||||
...cfg.agents,
|
||||
list: nextList,
|
||||
},
|
||||
};
|
||||
|
||||
await writeConfigFile(nextConfig);
|
||||
|
||||
if (opts.json) {
|
||||
runtime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
agentId,
|
||||
identity: nextIdentity,
|
||||
workspace: workspaceDir ?? null,
|
||||
identityFile: identityFilePath ?? null,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
runtime.log(`Updated ${CONFIG_PATH_CLAWDBOT}`);
|
||||
runtime.log(`Agent: ${agentId}`);
|
||||
if (nextIdentity.name) runtime.log(`Name: ${nextIdentity.name}`);
|
||||
if (nextIdentity.theme) runtime.log(`Theme: ${nextIdentity.theme}`);
|
||||
if (nextIdentity.emoji) runtime.log(`Emoji: ${nextIdentity.emoji}`);
|
||||
if (workspaceDir) runtime.log(`Workspace: ${workspaceDir}`);
|
||||
}
|
||||
Reference in New Issue
Block a user