@@ -0,0 +1,131 @@+import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk";+import fs from "node:fs";+import os from "node:os";+import path from "node:path";+import type { DynamicAgentCreationConfig } from "./types.js";++export type MaybeCreateDynamicAgentResult = {+ created: boolean;+ updatedCfg: OpenClawConfig;+ agentId?: string;
Show 3 hidden lines
+};++/**
+ * Check if a dynamic agent should be created for a DM user and create it if needed.+ * This creates a unique agent instance with its own workspace for each DM user.+ */+export async function maybeCreateDynamicAgent(params: {+ cfg: OpenClawConfig;+ runtime: PluginRuntime;+ senderOpenId: string;+ dynamicCfg: DynamicAgentCreationConfig;+ log: (msg: string) => void;+}): Promise<MaybeCreateDynamicAgentResult> {+ const { cfg, runtime, senderOpenId, dynamicCfg, log } = params;++ // Check if there's already a binding for this user
Show 63 hidden lines
+ const existingBindings = cfg.bindings ?? [];+ const hasBinding = existingBindings.some(+ (b) =>+ b.match?.channel === "feishu" &&+ b.match?.peer?.kind === "dm" &&+ b.match?.peer?.id === senderOpenId,+ );++ if (hasBinding) {+ return { created: false, updatedCfg: cfg };+ }++ // Check maxAgents limit if configured+ if (dynamicCfg.maxAgents !== undefined) {+ const feishuAgentCount = (cfg.agents?.list ?? []).filter((a) =>+ a.id.startsWith("feishu-"),+ ).length;+ if (feishuAgentCount >= dynamicCfg.maxAgents) {+ log(+ `feishu: maxAgents limit (${dynamicCfg.maxAgents}) reached, not creating agent for ${senderOpenId}`,+ );+ return { created: false, updatedCfg: cfg };+ }+ }++ // Use full OpenID as agent ID suffix (OpenID format: ou_xxx is already filesystem-safe)+ const agentId = `feishu-${senderOpenId}`;++ // Check if agent already exists (but binding was missing)+ const existingAgent = (cfg.agents?.list ?? []).find((a) => a.id === agentId);+ if (existingAgent) {+ // Agent exists but binding doesn't - just add the binding+ log(`feishu: agent "${agentId}" exists, adding missing binding for ${senderOpenId}`);++ const updatedCfg: OpenClawConfig = {+ ...cfg,+ bindings: [+ ...existingBindings,+ {+ agentId,+ match: {+ channel: "feishu",+ peer: { kind: "dm", id: senderOpenId },+ },+ },+ ],+ };++ await runtime.config.writeConfigFile(updatedCfg);+ return { created: true, updatedCfg, agentId };+ }++ // Resolve path templates with substitutions+ const workspaceTemplate = dynamicCfg.workspaceTemplate ?? "~/.openclaw/workspace-{agentId}";+ const agentDirTemplate = dynamicCfg.agentDirTemplate ?? "~/.openclaw/agents/{agentId}/agent";++ const workspace = resolveUserPath(+ workspaceTemplate.replace("{userId}", senderOpenId).replace("{agentId}", agentId),+ );+ const agentDir = resolveUserPath(+ agentDirTemplate.replace("{userId}", senderOpenId).replace("{agentId}", agentId),+ );+
maybeCreateDynamicAgent adds an agent entry, a sender binding and workspace directories, then persists them through the config writer without checking the channel's configWrites policy.