(req: http.IncomingMessage, res: http.ServerResponse)
| 3113 | } |
| 3114 | |
| 3115 | private handleSaveConfig(req: http.IncomingMessage, res: http.ServerResponse): void { |
| 3116 | this.readBody(req, async (body) => { |
| 3117 | try { |
| 3118 | const newCfg = JSON.parse(body); |
| 3119 | const cfgPath = this.getOpenClawConfigPath(); |
| 3120 | let raw: Record<string, unknown> = {}; |
| 3121 | if (fs.existsSync(cfgPath)) { |
| 3122 | raw = JSON.parse(fs.readFileSync(cfgPath, "utf-8")); |
| 3123 | } |
| 3124 | |
| 3125 | if (!raw.plugins) raw.plugins = {}; |
| 3126 | const plugins = raw.plugins as Record<string, unknown>; |
| 3127 | if (!plugins.entries) plugins.entries = {}; |
| 3128 | const entries = plugins.entries as Record<string, unknown>; |
| 3129 | const entryKey = entries["memos-local-openclaw-plugin"] ? "memos-local-openclaw-plugin" |
| 3130 | : entries["memos-local"] ? "memos-local" |
| 3131 | : entries["memos-lite-openclaw-plugin"] ? "memos-lite-openclaw-plugin" |
| 3132 | : entries["memos-lite"] ? "memos-lite" |
| 3133 | : "memos-local-openclaw-plugin"; |
| 3134 | if (!entries[entryKey]) entries[entryKey] = { enabled: true }; |
| 3135 | const entry = entries[entryKey] as Record<string, unknown>; |
| 3136 | if (!entry.config) entry.config = {}; |
| 3137 | const config = entry.config as Record<string, unknown>; |
| 3138 | |
| 3139 | const oldSharing = config.sharing as Record<string, unknown> | undefined; |
| 3140 | const oldSharingRole = oldSharing?.role as string | undefined; |
| 3141 | const oldSharingEnabled = Boolean(oldSharing?.enabled); |
| 3142 | const oldClientHubAddress = String((oldSharing?.client as Record<string, unknown>)?.hubAddress || ""); |
| 3143 | |
| 3144 | if (newCfg.embedding) config.embedding = newCfg.embedding; |
| 3145 | if (newCfg.summarizer) config.summarizer = newCfg.summarizer; |
| 3146 | if (newCfg.skillEvolution) config.skillEvolution = newCfg.skillEvolution; |
| 3147 | if (newCfg.viewerPort) config.viewerPort = newCfg.viewerPort; |
| 3148 | if (newCfg.taskAutoFinalizeHours !== undefined) config.taskAutoFinalizeHours = newCfg.taskAutoFinalizeHours; |
| 3149 | if (newCfg.telemetry !== undefined) config.telemetry = newCfg.telemetry; |
| 3150 | if (newCfg.sharing !== undefined) { |
| 3151 | const existing = (config.sharing as Record<string, unknown>) || {}; |
| 3152 | const merged = { ...existing, ...newCfg.sharing }; |
| 3153 | if (newCfg.sharing.capabilities && existing.capabilities) { |
| 3154 | merged.capabilities = { ...(existing.capabilities as Record<string, unknown>), ...newCfg.sharing.capabilities }; |
| 3155 | } |
| 3156 | if (merged.role === "client" && merged.client) { |
| 3157 | const clientCfg = merged.client as Record<string, unknown>; |
| 3158 | const addr = String(clientCfg.hubAddress || ""); |
| 3159 | if (addr && oldSharingRole === "hub" && oldSharingEnabled) { |
| 3160 | const selfHubPort = (oldSharing?.hub as Record<string, unknown>)?.port ?? 18800; |
| 3161 | const localIPs = this.getLocalIPs(); |
| 3162 | localIPs.push("127.0.0.1", "localhost", "0.0.0.0"); |
| 3163 | try { |
| 3164 | const u = new URL(addr.startsWith("http") ? addr : `http://${addr}`); |
| 3165 | const targetPort = u.port || (u.protocol === "https:" ? "443" : "80"); |
| 3166 | if (localIPs.includes(u.hostname) && targetPort === String(selfHubPort)) { |
| 3167 | res.writeHead(400, { "Content-Type": "application/json" }); |
| 3168 | res.end(JSON.stringify({ error: "cannot_join_self" })); |
| 3169 | return; |
| 3170 | } |
| 3171 | } catch {} |
| 3172 | } |
no test coverage detected