mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-13 20:26:08 +00:00
* refactor: share talk event metric extraction * refactor: reuse shared coercion helpers * refactor: reuse shared primitive guards * refactor: reuse shared record guard * refactor: reuse shared primitive helpers * refactor: reuse shared string guards * refactor: reuse shared non-empty string guard * refactor: share plugin primitive coercion helpers * refactor: reuse plugin coercion helpers * refactor: reuse plugin coercion helpers in more plugins * refactor: reuse channel coercion helpers * refactor: reuse monitor coercion helpers * refactor: reuse provider coercion helpers * refactor: reuse core coercion helpers * refactor: reuse runtime coercion helpers * refactor: reuse helper coercion in codex paths * refactor: reuse helper coercion in runtime paths * refactor: reuse codex app-server coercion helpers * refactor: reuse codex record helpers * refactor: reuse migration and qa record helpers * refactor: reuse feishu and core helper guards * refactor: reuse browser and policy coercion helpers * refactor: reuse memory wiki record helper * refactor: share boolean coercion helpers * refactor: reuse finite number coercion * refactor: reuse trimmed string list helpers * refactor: reuse string list normalization * refactor: reuse remaining string list helpers * refactor: reuse string entry normalizer * refactor: share sorted string helpers * refactor: share string list normalization * test: preserve command registry browser imports * refactor: reuse trimmed list helpers * refactor: reuse string dedupe helpers * refactor: reuse local dedupe helpers * refactor: reuse more string dedupe helpers * refactor: reuse command string dedupe helpers * refactor: dedupe memory path lists with helper * refactor: expose string dedupe helpers to plugins * refactor: reuse core string dedupe helpers * refactor: reuse shared unique value helpers * refactor: reuse unique helpers in agent utilities * refactor: reuse unique helpers in config plumbing * refactor: reuse unique helpers in extensions * refactor: reuse unique helpers in core utilities * refactor: reuse unique helpers in qa plugins * refactor: reuse unique helpers in memory plugins * refactor: reuse unique helpers in channel plugins * refactor: reuse unique helpers in core tails * refactor: reuse unique helper in comfy workflow * refactor: reuse unique helpers in test utilities * refactor: expose unique value helper to plugins * refactor: reuse unique helpers for numeric lists * refactor: replace index dedupe filters * refactor: reuse string entry normalization * refactor: reuse string normalization in plugin helpers * refactor: reuse string normalization in extension helpers * refactor: reuse string normalization in channel parsers * refactor: reuse string normalization in memory search * refactor: reuse string normalization in provider parsers * refactor: reuse string normalization in qa helpers * refactor: reuse string normalization in infra parsers * refactor: reuse string normalization in messaging parsers * refactor: reuse string normalization in core parsers * refactor: reuse string normalization in extension parsers * refactor: reuse string normalization in remaining parsers * refactor: reuse string normalization in final parser spots * refactor: reuse string normalization in qa media helpers * refactor: reuse normalization in provider and media lists * refactor: reuse normalization for remaining set filters * refactor: reuse normalization in policy allowlists * refactor: reuse normalization in session and owner lists * refactor: centralize primitive string lists * refactor: reuse lowercase entry helpers * refactor: reuse sorted string helpers * refactor: reuse unique trimmed helpers * refactor: reuse string normalization helpers * refactor: reuse catalog string helpers * refactor: reuse remaining string helpers * refactor: simplify remaining list normalization * refactor: reuse codex auth order normalization * chore: refresh plugin sdk api baseline * fix: make shared string sorting deterministic * chore: refresh plugin sdk api baseline * fix: align host env security ordering
147 lines
5.0 KiB
TypeScript
147 lines
5.0 KiB
TypeScript
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
|
import { buildRealtimeVoiceAgentConsultPolicyInstructions } from "openclaw/plugin-sdk/realtime-voice";
|
|
import { root } from "openclaw/plugin-sdk/security-runtime";
|
|
import { normalizeOptionalString as normalizeString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
import type { VoiceCallConfig } from "./config.js";
|
|
import type { CoreAgentDeps, CoreConfig } from "./core-bridge.js";
|
|
|
|
type AgentEntryLike = {
|
|
id?: unknown;
|
|
systemPromptOverride?: unknown;
|
|
};
|
|
|
|
type VoiceIdentityLike = {
|
|
name?: unknown;
|
|
emoji?: unknown;
|
|
theme?: unknown;
|
|
creature?: unknown;
|
|
vibe?: unknown;
|
|
};
|
|
|
|
function readAgentEntries(cfg: CoreConfig): AgentEntryLike[] {
|
|
const agents = (cfg as { agents?: { list?: unknown } }).agents;
|
|
return Array.isArray(agents?.list)
|
|
? agents.list.filter((entry): entry is AgentEntryLike =>
|
|
Boolean(entry && typeof entry === "object"),
|
|
)
|
|
: [];
|
|
}
|
|
|
|
function resolveAgentSystemPromptOverride(cfg: CoreConfig, agentId: string): string | undefined {
|
|
const entries = readAgentEntries(cfg);
|
|
const entry = entries.find((candidate) => normalizeString(candidate.id) === agentId);
|
|
return (
|
|
normalizeString(entry?.systemPromptOverride) ??
|
|
normalizeString(
|
|
(cfg as { agents?: { defaults?: { systemPromptOverride?: unknown } } }).agents?.defaults
|
|
?.systemPromptOverride,
|
|
)
|
|
);
|
|
}
|
|
|
|
function limitText(text: string, maxChars: number): string {
|
|
if (text.length <= maxChars) {
|
|
return text;
|
|
}
|
|
return `${text.slice(0, Math.max(0, maxChars - 32)).trimEnd()}\n[truncated]`;
|
|
}
|
|
|
|
async function readWorkspaceVoiceContextFiles(params: {
|
|
workspaceDir: string;
|
|
files: readonly string[];
|
|
maxChars: number;
|
|
}): Promise<string[]> {
|
|
const sections: string[] = [];
|
|
let remaining = params.maxChars;
|
|
const workspaceRoot = await root(params.workspaceDir).catch(() => null);
|
|
if (!workspaceRoot) {
|
|
return sections;
|
|
}
|
|
for (const file of params.files) {
|
|
if (remaining <= 0) {
|
|
continue;
|
|
}
|
|
const content = await workspaceRoot.readText(file).catch(() => undefined);
|
|
const trimmed = content?.trim();
|
|
if (!trimmed) {
|
|
continue;
|
|
}
|
|
const body = limitText(trimmed, Math.max(0, remaining - file.length - 16));
|
|
const section = `### ${file}\n${body}`;
|
|
sections.push(section);
|
|
remaining -= section.length;
|
|
}
|
|
return sections;
|
|
}
|
|
|
|
export async function buildRealtimeVoiceInstructions(params: {
|
|
baseInstructions: string;
|
|
config: VoiceCallConfig;
|
|
coreConfig: CoreConfig;
|
|
agentRuntime: CoreAgentDeps;
|
|
}): Promise<string> {
|
|
const { config } = params;
|
|
const sections: string[] = [params.baseInstructions];
|
|
const consultGuidance = buildRealtimeVoiceAgentConsultPolicyInstructions(config.realtime);
|
|
if (consultGuidance) {
|
|
sections.push(consultGuidance);
|
|
}
|
|
|
|
const contextConfig = config.realtime.agentContext;
|
|
if (!contextConfig.enabled) {
|
|
return sections.filter(Boolean).join("\n\n");
|
|
}
|
|
|
|
const agentId = config.agentId ?? "main";
|
|
const capsule: string[] = [
|
|
"OpenClaw agent voice context:",
|
|
`- Agent id: ${agentId}`,
|
|
"- Use this context to match the OpenClaw agent's personality and standing preferences on fast voice turns.",
|
|
"- Treat this as compact context only; call openclaw_agent_consult when the caller needs the full agent brain, tools, memory, or workspace state.",
|
|
];
|
|
|
|
if (contextConfig.includeIdentity) {
|
|
const identity = params.agentRuntime.resolveAgentIdentity(
|
|
params.coreConfig as OpenClawConfig,
|
|
agentId,
|
|
) as VoiceIdentityLike | undefined;
|
|
const identityLines = [
|
|
normalizeString(identity?.name) ? `- Name: ${normalizeString(identity?.name)}` : undefined,
|
|
normalizeString(identity?.emoji) ? `- Emoji: ${normalizeString(identity?.emoji)}` : undefined,
|
|
normalizeString(identity?.vibe) ? `- Vibe: ${normalizeString(identity?.vibe)}` : undefined,
|
|
normalizeString(identity?.theme) ? `- Theme: ${normalizeString(identity?.theme)}` : undefined,
|
|
normalizeString(identity?.creature)
|
|
? `- Creature/persona: ${normalizeString(identity?.creature)}`
|
|
: undefined,
|
|
].filter(Boolean);
|
|
if (identityLines.length > 0) {
|
|
capsule.push(`Configured identity:\n${identityLines.join("\n")}`);
|
|
}
|
|
}
|
|
|
|
if (contextConfig.includeSystemPrompt) {
|
|
const systemPrompt = resolveAgentSystemPromptOverride(params.coreConfig, agentId);
|
|
if (systemPrompt) {
|
|
capsule.push(`Configured system prompt override:\n${systemPrompt}`);
|
|
}
|
|
}
|
|
|
|
if (contextConfig.includeWorkspaceFiles) {
|
|
const workspaceDir = params.agentRuntime.resolveAgentWorkspaceDir(
|
|
params.coreConfig as OpenClawConfig,
|
|
agentId,
|
|
);
|
|
const fileSections = await readWorkspaceVoiceContextFiles({
|
|
workspaceDir,
|
|
files: contextConfig.files,
|
|
maxChars: contextConfig.maxChars,
|
|
});
|
|
if (fileSections.length > 0) {
|
|
capsule.push(`Workspace voice context:\n${fileSections.join("\n\n")}`);
|
|
}
|
|
}
|
|
|
|
sections.push(limitText(capsule.join("\n\n"), contextConfig.maxChars));
|
|
return sections.filter(Boolean).join("\n\n");
|
|
}
|