From 1c9e0d6020545a828527adabc82472e9530b750f Mon Sep 17 00:00:00 2001 From: Grant Doyle Date: Tue, 9 Jun 2026 15:53:03 -0500 Subject: [PATCH 1/8] fix(chat): cross-model compatibility + add model-sweep regression suite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three model-family bugs were live before this: • Qwen/Llama/gpt-oss 400 with "unknown field stream_options". Mason added stream_options.include_usage on every streaming request to surface Anthropic cache stats, but those providers reject the field. • Gemini 400 with "Gemini models only support one system prompt". Mason builds up to three system messages (skills manifest + user prompt + tool-aware nudge); Gemini permits exactly one. Other models tolerated multiple so we never noticed. • Llama / Qwen 3 next 400 with "max_tokens cannot exceed N". The 16384 cap was for Opus extended thinking; Llama caps at 8192 and Qwen 3 next at 10000. Fixes: • src/chat-shared.ts (new): extracted flattenContent, applyAnthropicCaching, plus three new helpers — supportsStreamOptions (allowlist Claude / GPT / Codex / o-series; explicitly exclude gpt-oss), maxTokensFor (16384 for Claude, 10000 for Qwen 3 next, 8192 default), consolidateSystemMessages (collapse multiple role:"system" into one with \n\n separator; universally compatible and preserves cache_control behavior). • src/main.ts: import from chat-shared, gate body.stream_options on supportsStreamOptions, run consolidateSystemMessages before applyAnthropicCaching, and use maxTokensFor for both chat- completions branches. Regression coverage: • scripts/test-models.js (new): standalone Node sweep that mints OAuth via the local databricks CLI, discovers every chat model via /api/2.0/serving-endpoints, and runs three scenarios (hello-no-tools, hello-with-tools, multi-system) against each. Mirrors Mason's chat-handler logic by reusing the same helpers from build/ts/chat-shared.js. Mirrors the chatLoop "promote to responses when tools + responses-supported" rule so gpt-5-5 tool tests are skipped (Mason never sends them via chat completions). • npm run test:models — runs the sweep. --filter scopes to a model subset; --profile picks a non-DEFAULT databrickscfg profile. End-to-end result: 81/81 model+scenario combinations green on the user's workspace (Claude, GPT, GPT-OSS, Gemini, Llama 3/4, Qwen 3 next + 3.5, Gemma). Co-authored-by: Isaac --- .release-please-manifest.json | 2 +- package-lock.json | 4 +- package.json | 5 +- scripts/test-models.js | 301 ++++++++++++++++++++++++++++++++++ src/chat-shared.ts | 105 ++++++++++++ src/main.ts | 93 +++-------- 6 files changed, 439 insertions(+), 71 deletions(-) create mode 100644 scripts/test-models.js create mode 100644 src/chat-shared.ts diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 866e93f..50f0c45 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.4.3" + ".": "1.4.4" } diff --git a/package-lock.json b/package-lock.json index 4bec4f0..46a835a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mason", - "version": "1.4.3", + "version": "1.4.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mason", - "version": "1.4.3", + "version": "1.4.4", "hasInstallScript": true, "dependencies": { "electron-window-state": "5.0.3", diff --git a/package.json b/package.json index 48cef63..f2ceff0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mason", - "version": "1.4.3", + "version": "1.4.4", "description": "Desktop chat app for Databricks AI Gateway with MCP tool calling", "author": "Databricks", "main": "build/ts/main.js", @@ -14,7 +14,8 @@ "build:mac": "npm run build:ts && electron-builder --mac", "build:win": "npm run build:ts && electron-builder --win", "build:linux": "npm run build:ts && electron-builder --linux", - "dist": "npm run build:ts && electron-builder --publish never" + "dist": "npm run build:ts && electron-builder --publish never", + "test:models": "npm run build:ts && node scripts/test-models.js" }, "devDependencies": { "electron": "33.4.11", diff --git a/scripts/test-models.js b/scripts/test-models.js new file mode 100644 index 0000000..a578bb1 --- /dev/null +++ b/scripts/test-models.js @@ -0,0 +1,301 @@ +#!/usr/bin/env node +// +// Cross-model regression sweep. Hits every model in the discovered list with +// three canned scenarios via the same Databricks AI Gateway endpoints Mason +// uses. Catches model-family compatibility regressions before they ship. +// +// Usage: +// npm run test:models # all models +// npm run test:models -- --filter claude # subset +// npm run test:models -- --profile prod # different profile +// +// Reads profile from ~/.databrickscfg, mints OAuth via the local databricks +// CLI, discovers models via /api/2.0/serving-endpoints, sends each scenario. + +"use strict"; + +const { execSync } = require("child_process"); +const fs = require("fs"); +const os = require("os"); +const path = require("path"); + +const { + applyAnthropicCaching, + consolidateSystemMessages, + maxTokensFor, + supportsStreamOptions, +} = require("../build/ts/chat-shared"); + +// ---------- args ---------- +function arg(name, fallback) { + const i = process.argv.indexOf(name); + if (i === -1) return fallback; + return process.argv[i + 1] ?? fallback; +} +const PROFILE = arg("--profile", "DEFAULT"); +const FILTER = arg("--filter", ""); +const TIMEOUT_MS = 30_000; + +// ---------- profile + token ---------- +function parseHost(profileName) { + const cfgPath = path.join(os.homedir(), ".databrickscfg"); + if (!fs.existsSync(cfgPath)) die(`~/.databrickscfg not found`); + const text = fs.readFileSync(cfgPath, "utf-8"); + let inSection = false; + for (const line of text.split("\n")) { + const sec = line.match(/^\[(.+)\]$/); + if (sec) { + inSection = sec[1] === profileName; + continue; + } + if (!inSection) continue; + const kv = line.match(/^host\s*=\s*(.+)$/); + if (kv) return kv[1].trim().replace(/\/+$/, ""); + } + die(`Profile [${profileName}] has no host in ~/.databrickscfg`); +} + +function mintToken(profileName) { + try { + const out = execSync(`databricks auth token --profile ${profileName}`, { + encoding: "utf-8", + timeout: 10_000, + }).trim(); + return JSON.parse(out).access_token; + } catch (e) { + die(`Failed to mint OAuth token for [${profileName}]: ${e.message}`); + } +} + +// ---------- discovery ---------- +async function discoverModels(host, token) { + const res = await fetchWithTimeout(`${host}/api/2.0/serving-endpoints`, { + headers: { Authorization: `Bearer ${token}` }, + }); + if (!res.ok) die(`Model discovery failed: HTTP ${res.status}`); + const data = await res.json(); + return (data.endpoints || []) + .filter( + (e) => + e.endpoint_type === "FOUNDATION_MODEL_API" && + e.task && + e.task.includes("chat") && + e.state?.ready === "READY" + ) + .map((e) => { + const fm = e.config?.served_entities?.[0]?.foundation_model || {}; + const apiTypes = fm.api_types || []; + const supportsChat = apiTypes.includes("mlflow/v1/chat/completions"); + const supportsResponses = apiTypes.includes("openai/v1/responses"); + let format = null; + if (supportsChat) format = "chat"; + else if (supportsResponses) format = "responses"; + return format ? { value: e.name, format, apiTypes } : null; + }) + .filter(Boolean); +} + +// ---------- scenarios ---------- +const STUB_TOOL = { + type: "function", + function: { + name: "echo", + description: "Echo back a message. Used only for regression testing.", + parameters: { + type: "object", + properties: { message: { type: "string" } }, + required: ["message"], + }, + }, +}; + +const SCENARIOS = [ + { + name: "hello-no-tools", + build: () => ({ + messages: [{ role: "user", content: "Reply with the single word: hello" }], + tools: null, + }), + }, + { + name: "hello-with-tools", + build: () => ({ + messages: [{ role: "user", content: "Reply with the single word: hello" }], + tools: [STUB_TOOL], + }), + }, + { + name: "multi-system", + build: () => ({ + messages: [ + { role: "system", content: "You are a helpful assistant." }, + { role: "system", content: "Reply concisely." }, + { role: "system", content: "Skills available: none. Do not invoke load_skill." }, + { role: "user", content: "Reply with the single word: hello" }, + ], + tools: null, + }), + }, +]; + +// ---------- request builder (mirrors main.ts chat handler) ---------- +function buildBody(model, scenario, format) { + const { messages, tools } = scenario.build(); + const isResponses = format === "responses"; + // Skip Responses for sweep — its message shape differs and the bugs we're + // guarding against are chat-completions specific. + if (isResponses) return null; + // Mirror Mason's chatLoop: when tools are present AND the model supports + // Responses, the renderer promotes to Responses (works around GPT-5.5's + // server-side reasoning_effort injection that conflicts with tools in + // chat completions). Since the sweep only exercises chat-completions, skip + // tool scenarios on these models — they'd 400 in this path but Mason never + // sends them this way. + const supportsResponses = (model.apiTypes || []).includes("openai/v1/responses"); + if (supportsResponses && tools && tools.length > 0) return null; + + let body = { + model: model.value, + max_tokens: maxTokensFor(model.value), + messages: messages.slice(), + }; + if (tools && tools.length > 0) { + body.tools = tools; + body.tool_choice = "auto"; + } + body.stream = true; + if (supportsStreamOptions(model.value)) { + body.stream_options = { include_usage: true }; + } + body.messages = consolidateSystemMessages(body.messages); + applyAnthropicCaching(body, model.value); + return body; +} + +// ---------- runner ---------- +async function runOne(host, token, model, scenario) { + const body = buildBody(model, scenario, model.format); + if (!body) { + const reason = + model.format === "responses" + ? "responses-only" + : "promotes-to-responses-with-tools"; + return { model: model.value, scenario: scenario.name, ok: true, skip: reason }; + } + const url = `${host}/ai-gateway/mlflow/v1/chat/completions`; + try { + const res = await fetchWithTimeout(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify(body), + }); + if (!res.ok) { + const text = await res.text(); + return { + model: model.value, + scenario: scenario.name, + ok: false, + error: `HTTP ${res.status}: ${text.slice(0, 200)}`, + }; + } + // Drain the stream so the test is realistic. We don't render anything; + // just confirm the stream completes without an error frame. + const reader = res.body.getReader(); + const decoder = new TextDecoder(); + let buf = ""; + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buf += decoder.decode(value, { stream: true }); + // Look for an obvious error in the stream + if (buf.includes('"error"')) { + return { + model: model.value, + scenario: scenario.name, + ok: false, + error: `Stream contained error: ${buf.slice(0, 200)}`, + }; + } + } + return { model: model.value, scenario: scenario.name, ok: true }; + } catch (e) { + return { + model: model.value, + scenario: scenario.name, + ok: false, + error: e.message, + }; + } +} + +// ---------- output ---------- +function printReport(results) { + const byModel = new Map(); + for (const r of results) { + if (!byModel.has(r.model)) byModel.set(r.model, []); + byModel.get(r.model).push(r); + } + let totalOk = 0; + let totalFail = 0; + for (const [model, rs] of byModel) { + const allOk = rs.every((r) => r.ok); + const symbol = allOk ? "✓" : "✗"; + console.log(`${symbol} ${model}`); + for (const r of rs) { + if (r.skip) { + console.log(` · ${r.scenario.padEnd(20)} skipped (${r.skip})`); + } else if (r.ok) { + totalOk++; + console.log(` ✓ ${r.scenario}`); + } else { + totalFail++; + console.log(` ✗ ${r.scenario.padEnd(20)} ${r.error}`); + } + } + } + console.log(""); + console.log(`Total: ${totalOk} passed, ${totalFail} failed`); +} + +function fetchWithTimeout(url, opts = {}) { + const ctrl = new AbortController(); + const t = setTimeout(() => ctrl.abort(), TIMEOUT_MS); + return fetch(url, { ...opts, signal: ctrl.signal }).finally(() => + clearTimeout(t) + ); +} + +function die(msg) { + console.error(`ERROR: ${msg}`); + process.exit(2); +} + +// ---------- main ---------- +async function main() { + console.log(`Profile: ${PROFILE}`); + const host = parseHost(PROFILE); + console.log(`Host: ${host}`); + const token = mintToken(PROFILE); + console.log(`Discovering models...`); + const models = await discoverModels(host, token); + const targets = FILTER + ? models.filter((m) => m.value.toLowerCase().includes(FILTER.toLowerCase())) + : models; + console.log(`Testing ${targets.length} model(s) × ${SCENARIOS.length} scenario(s)...\n`); + + const results = []; + for (const m of targets) { + for (const s of SCENARIOS) { + const r = await runOne(host, token, m, s); + results.push(r); + } + } + + printReport(results); + process.exit(results.some((r) => !r.ok && !r.skip) ? 1 : 0); +} + +main().catch((e) => die(e.stack || e.message)); diff --git a/src/chat-shared.ts b/src/chat-shared.ts new file mode 100644 index 0000000..f5036c1 --- /dev/null +++ b/src/chat-shared.ts @@ -0,0 +1,105 @@ +// Shared chat-handler helpers used by both src/main.ts (Electron main process) +// and scripts/test-models.js (CLI regression sweep). Kept dependency-free so the +// compiled build/ts/chat-shared.js can be required from a plain Node script +// without dragging Electron in. + +// Flatten a content field that might be a string, null, or an array of parts +// (Gemini, some Anthropic responses, etc. return `content: [{type:"text", text:"..."}]`). +// Without this, the renderer feeds an array to marked() and gets a confusing +// "input parameter is of type [object Array], string expected" error. +export function flattenContent(c: any): string { + if (c == null) return ""; + if (typeof c === "string") return c; + if (Array.isArray(c)) { + return c + .map((p) => { + if (typeof p === "string") return p; + if (p == null) return ""; + if (typeof p.text === "string") return p.text; + if (typeof p.content === "string") return p.content; + return ""; + }) + .join(""); + } + return String(c); +} + +// Anthropic prompt-caching helper. Sets cache_control: {type: "ephemeral"} +// breakpoints on the heaviest static portions of the prompt (tools + last +// system message) so repeated turns within a 5-minute window read at ~10% of +// the input cost. No-op for non-Claude models. +export function applyAnthropicCaching(body: any, model: string): void { + if (typeof model !== "string") return; + if (!model.toLowerCase().includes("claude")) return; + + if (Array.isArray(body.tools) && body.tools.length > 0) { + const lastIdx = body.tools.length - 1; + body.tools[lastIdx] = { + ...body.tools[lastIdx], + cache_control: { type: "ephemeral" }, + }; + } + + if (Array.isArray(body.messages)) { + let lastSystemIdx = -1; + for (let i = 0; i < body.messages.length; i++) { + if (body.messages[i]?.role === "system") lastSystemIdx = i; + } + if (lastSystemIdx >= 0) { + body.messages[lastSystemIdx] = { + ...body.messages[lastSystemIdx], + cache_control: { type: "ephemeral" }, + }; + } + } +} + +// Per-family max_tokens cap. The 16K default was chosen for Opus extended +// thinking; Llama caps at 8192 and Qwen 3 next caps at 10000, so we'd 400 +// those models with "max_tokens cannot exceed N". Conservative default, +// bump only for Claude where we actually need the headroom. +export function maxTokensFor(model: string): number { + if (typeof model !== "string") return 8192; + const m = model.toLowerCase(); + if (m.includes("claude")) return 16384; + if (m.includes("qwen3-next") || m.includes("qwen3-80b")) return 10000; + return 8192; +} + +// stream_options.include_usage is supported by Anthropic and OpenAI-family +// models on the Databricks Gateway. Qwen / Llama / gpt-oss (Databricks' open- +// weights GPT proxy) 400 with "unknown field"; for those we skip it (we'd +// just lose the usage log line for non-Claude turns, which already don't +// carry cache_read anyway). +export function supportsStreamOptions(model: string): boolean { + if (typeof model !== "string") return false; + const m = model.toLowerCase(); + // gpt-oss-* matches the "gpt" substring but the upstream rejects the flag. + if (m.includes("gpt-oss")) return false; + return ( + m.includes("claude") || + m.includes("gpt") || + m.includes("codex") || + /\bo[1-9]\b/.test(m) + ); +} + +// Some providers (Gemini) reject multiple system messages. Mason builds up to +// three (skills manifest + user systemPrompt + tool-aware nudge). Collapse to +// one combined system message immediately before send. cache_control on "the +// last system message" (see applyAnthropicCaching) still works — it's now the +// only one. +export function consolidateSystemMessages(messages: any[]): any[] { + const systems: string[] = []; + const rest: any[] = []; + for (const m of messages) { + if (m?.role === "system") { + const content = typeof m.content === "string" ? m.content : flattenContent(m.content); + if (content) systems.push(content); + } else { + rest.push(m); + } + } + if (systems.length === 0) return rest; + return [{ role: "system", content: systems.join("\n\n") }, ...rest]; +} diff --git a/src/main.ts b/src/main.ts index 1d41d97..20498f8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,6 +3,13 @@ import { execSync, spawn, ChildProcess } from "child_process"; import * as path from "path"; import * as fs from "fs"; import * as os from "os"; +import { + applyAnthropicCaching, + consolidateSystemMessages, + flattenContent, + maxTokensFor, + supportsStreamOptions, +} from "./chat-shared"; try { // electron-reloader watches main.js paths; in dev mode it picks up our @@ -127,46 +134,6 @@ function chatFetch( }); } -// Flatten a content field that might be a string, null, or an array of parts -// (Gemini, some Anthropic responses, etc. return `content: [{type:"text", text:"..."}]`). -// Without this, the renderer feeds an array to marked() and gets a confusing -// "input parameter is of type [object Array], string expected" error. -// Anthropic prompt-caching helper. Sets cache_control: {type: "ephemeral"} -// breakpoints on the heaviest static portions of the prompt (tools + last -// system message) so repeated turns within a 5-minute window read at ~10% of -// the input cost. No-op for non-Claude models. Databricks AI Gateway passes -// cache_control through to Anthropic; the response's usage.cache_read_input_tokens -// surfaces whether a cache hit occurred (logged below). -function applyAnthropicCaching(body: any, model: string): void { - if (typeof model !== "string") return; - if (!model.toLowerCase().includes("claude")) return; - - // Mark the last tool. Anthropic caches everything up to and including the - // marked element, so a single breakpoint at the end covers all tools. - if (Array.isArray(body.tools) && body.tools.length > 0) { - const lastIdx = body.tools.length - 1; - body.tools[lastIdx] = { - ...body.tools[lastIdx], - cache_control: { type: "ephemeral" }, - }; - } - - // Mark the last system message. Covers skills manifest + user system prompt - // + tool-aware nudge — everything stable before the dynamic chat history. - if (Array.isArray(body.messages)) { - let lastSystemIdx = -1; - for (let i = 0; i < body.messages.length; i++) { - if (body.messages[i]?.role === "system") lastSystemIdx = i; - } - if (lastSystemIdx >= 0) { - body.messages[lastSystemIdx] = { - ...body.messages[lastSystemIdx], - cache_control: { type: "ephemeral" }, - }; - } - } -} - // Sanitize tool_calls before returning to the renderer. Two failure modes: // • Empty arguments: providers stream tools that take no params as // function.arguments="" — the Databricks AI Gateway rejects that on the @@ -199,23 +166,6 @@ function sanitizeToolCalls(toolCalls: any[]): any[] { }); } -function flattenContent(c: any): string { - if (c == null) return ""; - if (typeof c === "string") return c; - if (Array.isArray(c)) { - return c - .map((p) => { - if (typeof p === "string") return p; - if (p == null) return ""; - if (typeof p.text === "string") return p.text; - if (typeof p.content === "string") return p.content; - return ""; - }) - .join(""); - } - return String(c); -} - function sanitizeLog(str: string): string { return str .replace(/Bearer [^\s"]+/g, "Bearer ****") @@ -2043,31 +1993,42 @@ ipcMain.handle( const hasSystem = messages.some((m: any) => m.role === "system"); body = { model, - max_tokens: 16384, + max_tokens: maxTokensFor(String(model)), messages: hasSystem ? messages : [systemMsg, ...messages], tools, tool_choice: "auto", }; console.log(`[CHAT] Sending ${tools.length} tools: ${toolNames}`); } else { - body = { model, max_tokens: 16384, messages }; + body = { model, max_tokens: maxTokensFor(String(model)), messages }; } } if (shouldStream) { body.stream = true; - // Ask the upstream to include a final usage chunk in the SSE stream so - // we can log cache hits and total token counts. Without this, the - // streaming path never sees usage and we can't verify caching is - // actually engaging. - body.stream_options = { include_usage: true }; + // Only ask for usage chunks from providers that accept the flag. Qwen + // and Llama 400 with "unknown field 'stream_options'" if we send it. + // Non-Claude streams don't carry cache_read anyway, so dropping the + // flag there costs nothing. + if (supportsStreamOptions(String(model))) { + body.stream_options = { include_usage: true }; + } + } + + // Gemini rejects more than one role:"system" message. Mason builds up to + // three (skills manifest + user systemPrompt + tool-aware nudge), so + // collapse to a single combined system message before send. Universally + // compatible; cache_control on "the last system message" still works + // since it's now the only one. + if (Array.isArray(body.messages)) { + body.messages = consolidateSystemMessages(body.messages); } // Anthropic prompt caching. Tool schemas dominate every turn (~16K tokens // for ~80 tools at ~200 tokens each). With cache_control on the last tool, // Anthropic caches the entire tools prefix for 5 minutes — subsequent - // turns within the cache window read at 10% of input cost. Also mark the - // last system message so the static instruction prefix gets cached too. + // turns within the cache window read at 10% of input cost. Also marks the + // (now consolidated) system message so the static prefix gets cached too. // OpenAI prefixes >1024 tokens cache automatically (no opt-in needed). // Gemini/Meta/Qwen have no standard caching API — leave untouched. applyAnthropicCaching(body, String(model)); From b7bbf4eeedb3303f5817cb4a3039fa5ad84c0f5a Mon Sep 17 00:00:00 2001 From: Grant Doyle Date: Tue, 9 Jun 2026 15:53:04 -0500 Subject: [PATCH 2/8] chore(release): 1.4.4 Co-authored-by: Isaac From 4082ec8998413e4daaea2cd2e9897ebc1aa164ee Mon Sep 17 00:00:00 2001 From: Grant Doyle Date: Thu, 11 Jun 2026 11:28:51 -0500 Subject: [PATCH 3/8] fix(chat): flatten array-shaped delta.content in SSE stream Qwen 3.5 122B, Gemini 2.5, and gpt-oss stream delta.content as an array of content parts; appending it to a string yielded literal "[object Object]" in the chat window. Coerce through flattenContent before accumulating/emitting. Sweep now parses SSE deltas the same way and fails on "[object Object]" in assembled output. Co-authored-by: Isaac --- scripts/test-models.js | 46 ++++++++++++++++++++++++++++++++++++------ src/main.ts | 14 +++++++++++-- 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/scripts/test-models.js b/scripts/test-models.js index a578bb1..d7f7006 100644 --- a/scripts/test-models.js +++ b/scripts/test-models.js @@ -22,6 +22,7 @@ const path = require("path"); const { applyAnthropicCaching, consolidateSystemMessages, + flattenContent, maxTokensFor, supportsStreamOptions, } = require("../build/ts/chat-shared"); @@ -201,17 +202,21 @@ async function runOne(host, token, model, scenario) { error: `HTTP ${res.status}: ${text.slice(0, 200)}`, }; } - // Drain the stream so the test is realistic. We don't render anything; - // just confirm the stream completes without an error frame. + // Drain the stream. Parse SSE chunks and accumulate content the way + // main.ts does (via flattenContent) so we catch shape regressions — + // notably Qwen 3.5 122B which streams `delta.content` as an array of + // content parts. Without flattening, JS coerces it to "[object Object]" + // in the chat window. const reader = res.body.getReader(); const decoder = new TextDecoder(); let buf = ""; + let assembled = ""; + let sawArrayContent = false; while (true) { const { done, value } = await reader.read(); if (done) break; buf += decoder.decode(value, { stream: true }); - // Look for an obvious error in the stream - if (buf.includes('"error"')) { + if (buf.includes('"error"') && !buf.includes('"error_code":null')) { return { model: model.value, scenario: scenario.name, @@ -219,8 +224,36 @@ async function runOne(host, token, model, scenario) { error: `Stream contained error: ${buf.slice(0, 200)}`, }; } + let nl; + while ((nl = buf.indexOf("\n")) !== -1) { + const line = buf.slice(0, nl).trim(); + buf = buf.slice(nl + 1); + if (!line.startsWith("data: ")) continue; + const data = line.slice(6); + if (data === "[DONE]") break; + try { + const chunk = JSON.parse(data); + const delta = chunk.choices?.[0]?.delta; + if (delta?.content != null) { + if (Array.isArray(delta.content)) sawArrayContent = true; + const piece = + typeof delta.content === "string" + ? delta.content + : flattenContent(delta.content); + assembled += piece; + } + } catch (_) {} + } + } + if (assembled.includes("[object Object]")) { + return { + model: model.value, + scenario: scenario.name, + ok: false, + error: `Assembled content contains "[object Object]" — main.ts is not flattening delta.content (sawArrayContent=${sawArrayContent}).`, + }; } - return { model: model.value, scenario: scenario.name, ok: true }; + return { model: model.value, scenario: scenario.name, ok: true, sawArrayContent }; } catch (e) { return { model: model.value, @@ -249,7 +282,8 @@ function printReport(results) { console.log(` · ${r.scenario.padEnd(20)} skipped (${r.skip})`); } else if (r.ok) { totalOk++; - console.log(` ✓ ${r.scenario}`); + const note = r.sawArrayContent ? " (array-shaped delta.content)" : ""; + console.log(` ✓ ${r.scenario}${note}`); } else { totalFail++; console.log(` ✗ ${r.scenario.padEnd(20)} ${r.error}`); diff --git a/src/main.ts b/src/main.ts index 20498f8..55bcc6a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2090,8 +2090,18 @@ ipcMain.handle( const chunk = JSON.parse(data); const delta = chunk.choices?.[0]?.delta; if (delta?.content) { - fullContent += delta.content; - if (win) win.webContents.send("chat-chunk", delta.content); + // Some providers (Qwen 3.5 122B on Databricks Gateway) stream + // delta.content as an array of content parts instead of a + // string. Coerce via flattenContent so we never `+=` an array + // onto a string (that yields literal "[object Object]"). + const piece = + typeof delta.content === "string" + ? delta.content + : flattenContent(delta.content); + if (piece) { + fullContent += piece; + if (win) win.webContents.send("chat-chunk", piece); + } } if (Array.isArray(delta?.tool_calls)) { for (const tc of delta.tool_calls) { From d9406fdcd7bc319af4e5c62c0f7f6e9808d69738 Mon Sep 17 00:00:00 2001 From: Grant Doyle Date: Thu, 11 Jun 2026 11:34:08 -0500 Subject: [PATCH 4/8] refactor(chat): extract shared agent-loop primitives into agent-runner.ts resolveModelRouting (gateway/format resolution incl. tools->Responses promotion), executeToolCore (headless load_skill/builtin/MCP dispatch), and capToolResult move to src/agent-runner.ts so the upcoming workflow engine can reuse them. getAllToolDefs gains an optional allowlist param (narrowing only) for per-cell tool selection; renderQuestionCard gains an optional container. Chat behavior unchanged. Co-authored-by: Isaac --- docs/specs/agentic-workflow-designer.md | 460 ++++++++++++++++++++++++ index.html | 1 + src/agent-runner.ts | 146 ++++++++ src/chat.ts | 163 ++------- src/messages.ts | 10 +- src/tools.ts | 13 +- 6 files changed, 646 insertions(+), 147 deletions(-) create mode 100644 docs/specs/agentic-workflow-designer.md create mode 100644 src/agent-runner.ts diff --git a/docs/specs/agentic-workflow-designer.md b/docs/specs/agentic-workflow-designer.md new file mode 100644 index 0000000..5b31884 --- /dev/null +++ b/docs/specs/agentic-workflow-designer.md @@ -0,0 +1,460 @@ +# Engineering Specification — Agentic Workflow Designer + +**Status:** Draft for review +**Author:** Mason core +**Target:** Mason v1.5.x (phased; see Rollout) +**Last updated:** 2026-06-11 + +--- + +## 1. Summary + +Add a visual, node-based **Workflow Designer** to Mason — a drag-and-drop canvas (in the spirit of n8n / ComfyUI) where users compose multi-model agentic pipelines out of **Cells**. Each Cell selects a model, a subset of available tools (built-in + MCP + UC MCP), and a prompt. Cells are wired together with edges: an edge from Cell A to Cell B means *A runs first, and A's output is injected into B's context*. Edges can also express **feedback loops** (B sends results back to A for revision) and **review gates** (a cell decides whether the workflow ends or routes work back for another pass). + +The designer is opened from a new button in the sidebar, directly **above the Profile section**. It replaces the chat pane with a full-pane canvas view, following the same view-swapping pattern as Dashboards/Settings/Onboarding. + +Everything executes through the existing Databricks AI Gateway plumbing — per-model format routing, OAuth, streaming, MCP tool dispatch, Anthropic prompt caching — none of which changes. The workflow engine is a thin orchestrator that runs the existing per-turn agent loop once per cell, in graph order. + +### Motivating user story (acceptance scenario) + +> I click **Workflow Designer**. A designer pane opens in the chat window. I create a cell, select **Fable 5**, pick a couple of MCP tools, and write a prompt with the high-level goals and specs of the project. I create a second cell with **Opus 4.8**, a different toolset, and an additional prompt. I drag a line from the Fable cell to the Opus cell — meaning the Fable cell runs first and its output feeds the Opus cell. I create a third cell named **"unit tests"** with a Sonnet model. The Fable cell feeds its unit-test specs to the unit-tests cell via a second line, and the unit-tests cell *also* receives a line from the Opus cell (two inputs) so it can run Opus's work against the spec sheet. The unit-tests cell has a **feedback** line back to the Opus cell: it reports which tests passed and what gaps remain, and Opus iterates until they're closed. When the unit-tests cell deems the work complete, it hands off to the Fable cell for **final review**. Fable either ends the session or passes the work back to Opus for another round. + +Section 12 walks this scenario through the spec end-to-end as the primary acceptance test. + +--- + +## 2. Current-state evaluation (what we're building on) + +A short audit of the parts of Mason this feature touches, and the constraints they impose. + +### 2.1 Architecture facts that shape this design + +| Fact | Where | Consequence for the designer | +|---|---|---| +| Renderer is **script-mode TypeScript** — no bundler, no imports; modules share one global scope and load via ` + diff --git a/src/agent-runner.ts b/src/agent-runner.ts new file mode 100644 index 0000000..e8afa03 --- /dev/null +++ b/src/agent-runner.ts @@ -0,0 +1,146 @@ +// Shared agent-loop primitives used by both the chat view (chat.ts) and the +// workflow engine (workflow-engine.ts). Script-mode global like every other +// renderer module — loaded before chat.js in index.html. +// +// What lives here is the *headless* core of a tool-bearing agent turn: +// • resolveModelRouting — per-model gateway/format resolution, including the +// tools→Responses promotion for models like GPT-5.5 +// • executeToolCore — execute one tool call (load_skill / builtin IPC / +// HTTP MCP / stdio MCP) and return its result content + a short preview +// • capToolResult — bound tool results so a single call can't blow context +// +// Deliberately *not* here: streaming/typewriter UI, history mutation, and +// ask_user (all UI-bound — each caller owns its own presentation). + +declare function getGatewayUrl(): string | null; + +const MAX_TOOL_RESULT_CHARS = 256 * 1024; +function capToolResult(text: string, toolName: string): string { + if (text.length <= MAX_TOOL_RESULT_CHARS) return text; + return ( + text.slice(0, MAX_TOOL_RESULT_CHARS) + + `\n\n[Truncated: ${toolName} returned ${text.length} chars, only first ${MAX_TOOL_RESULT_CHARS} kept. Ask for a more specific query or read in chunks.]` + ); +} + +interface ModelRouting { + model: string; + gateway: string; + format: "chat" | "responses"; +} + +// Resolve which model id / gateway / API format a chat request should use. +// `sel` is a model picker value: either a discovered model id or +// "custom:" for user-configured endpoints. When tools are attached +// and the model also supports the Responses API, promote to Responses — this +// works around GPT-5.5's server-side reasoning_effort injection that +// conflicts with tools in mlflow/v1/chat/completions. +function resolveModelRouting(sel: string, hasTools: boolean): ModelRouting { + let gateway = getGatewayUrl() || ""; + let model = sel; + let format: "chat" | "responses" | null = null; + + if (sel.startsWith("custom:")) { + model = sel.replace("custom:", ""); + const ep = mason.customEndpoints.find((e) => e.modelId === model); + if (ep) { + if (ep.gatewayUrl) gateway = ep.gatewayUrl; + format = ep.format || null; + } + } else { + for (const g of mason.discoveredModels) { + const m = g.models.find((x) => x.value === sel); + if (m) { + format = m.format || null; + const supportsResponses = m.apiTypes && m.apiTypes.includes("openai/v1/responses"); + if (hasTools && supportsResponses) { + format = "responses"; + } + break; + } + } + } + + return { model, gateway, format: format || "chat" }; +} + +interface ToolExecResult { + ok: boolean; + content: string; // goes into the role:"tool" message (already capped) + preview: string; // short human-readable line for UI +} + +// Execute one tool call, headlessly. Covers every dispatch path except +// ask_user (UI-bound; callers handle it themselves). Never throws — errors +// come back as { ok: false } with an "Error: …" content the model can read. +async function executeToolCore( + toolName: string, + args: Record +): Promise { + try { + if (toolName === "load_skill") { + const slug = String(args.slug || ""); + if (!slug) throw new Error("slug is required"); + const skill = (await window.api.skillsLoad(slug)) as + | { slug: string; name: string; description: string; body: string } + | null; + if (!skill) { + return { + ok: false, + content: `Error: skill "${slug}" not found.`, + preview: `skill "${slug}" not found`, + }; + } + const content = `# ${skill.name}\n\n${skill.body}`; + return { + ok: true, + content: capToolResult(content, toolName), + preview: `Loaded skill: ${slug}`, + }; + } + + if (BUILTIN_TOOL_NAMES.has(toolName)) { + const toolResult = (await window.api.builtinToolCall({ toolName, args })) as any; + const resultText = capToolResult(JSON.stringify(toolResult), toolName); + const preview = + toolResult?.message || + (typeof toolResult?.content === "string" && toolResult.content.slice(0, 200)) || + resultText; + return { ok: true, content: resultText, preview: String(preview) }; + } + + const server = findMcpServerForTool(toolName); + if (!server) { + return { + ok: false, + content: "Error: no MCP server found for this tool", + preview: "no MCP server found for this tool", + }; + } + + let toolResult: any; + if (server.type === "stdio") { + toolResult = await window.api.mcpStdioCallTool({ key: server.key!, toolName, args }); + } else { + const mcpToken = await getAuthToken(); + toolResult = await window.api.mcpCallTool({ + serverUrl: server.url!, + token: mcpToken, + toolName, + args, + }); + } + const rawText = toolResult?.content + ? toolResult.content.map((c: any) => c.text || JSON.stringify(c)).join("\n") + : JSON.stringify(toolResult); + const resultText = capToolResult(rawText, toolName); + return { + ok: true, + content: resultText, + preview: resultText.slice(0, 200) + (resultText.length > 200 ? "..." : ""), + }; + } catch (e) { + const msg = (e as Error).message; + return { ok: false, content: `Error: ${msg}`, preview: msg }; + } +} diff --git a/src/chat.ts b/src/chat.ts index f5fd44c..df3e4d5 100644 --- a/src/chat.ts +++ b/src/chat.ts @@ -161,15 +161,6 @@ function safeInlinePos(line: string): number { return lastSafe; } -const MAX_TOOL_RESULT_CHARS = 256 * 1024; -function capToolResult(text: string, toolName: string): string { - if (text.length <= MAX_TOOL_RESULT_CHARS) return text; - return ( - text.slice(0, MAX_TOOL_RESULT_CHARS) + - `\n\n[Truncated: ${toolName} returned ${text.length} chars, only first ${MAX_TOOL_RESULT_CHARS} kept. Ask for a more specific query or read in chunks.]` - ); -} - function setGenerating(active: boolean): void { mason.generating = active; const sendBtn = mason.el.send as HTMLButtonElement | null; @@ -279,31 +270,10 @@ async function chatLoop(_profile: { host?: string }): Promise { while (maxIterations-- > 0) { iterationsUsed += 1; const chatToken = await getAuthToken(); - const sel = modelEl.value; - let chatGateway = getGatewayUrl(); - let chatModel = sel; - let chatFormat: "chat" | "responses" | null = null; - - if (sel.startsWith("custom:")) { - chatModel = sel.replace("custom:", ""); - const ep = mason.customEndpoints.find((e) => e.modelId === chatModel); - if (ep) { - if (ep.gatewayUrl) chatGateway = ep.gatewayUrl; - chatFormat = ep.format || null; - } - } else { - for (const g of mason.discoveredModels) { - const m = g.models.find((x) => x.value === sel); - if (m) { - chatFormat = m.format || null; - const supportsResponses = m.apiTypes && m.apiTypes.includes("openai/v1/responses"); - if (toolsForApi && toolsForApi.length > 0 && supportsResponses) { - chatFormat = "responses"; - } - break; - } - } - } + const routing = resolveModelRouting(modelEl.value, !!(toolsForApi && toolsForApi.length > 0)); + const chatGateway: string | null = routing.gateway || null; + const chatModel = routing.model; + const chatFormat: "chat" | "responses" = routing.format; // Stream chat completions regardless of tools — main.ts accumulates // tool_calls deltas now. Responses API stream format differs; keep it @@ -495,37 +465,14 @@ async function chatLoop(_profile: { host?: string }): Promise { // also skip the "Calling tool: …" announcement since they render their // own UI inline. if (toolName === "load_skill") { - try { - const slug = String(args.slug || ""); - if (!slug) throw new Error("slug is required"); - addMessageEl("tool-call", `Loading skill: ${slug}`); - const skill = (await window.api.skillsLoad(slug)) as - | { slug: string; name: string; description: string; body: string } - | null; - if (!skill) { - (mason.history as any[]).push({ - role: "tool", - tool_call_id: tc.id, - name: toolName, - content: `Error: skill "${slug}" not found.`, - }); - } else { - const content = `# ${skill.name}\n\n${skill.body}`; - (mason.history as any[]).push({ - role: "tool", - tool_call_id: tc.id, - name: toolName, - content: capToolResult(content, toolName), - }); - } - } catch (e) { - (mason.history as any[]).push({ - role: "tool", - tool_call_id: tc.id, - name: toolName, - content: `Error: ${(e as Error).message}`, - }); - } + addMessageEl("tool-call", `Loading skill: ${String(args.slug || "")}`); + const r = await executeToolCore(toolName, args); + (mason.history as any[]).push({ + role: "tool", + tool_call_id: tc.id, + name: toolName, + content: r.content, + }); continue; } @@ -574,83 +521,21 @@ async function chatLoop(_profile: { host?: string }): Promise { // stuck. showThinking(); - if (BUILTIN_TOOL_NAMES.has(toolName)) { - try { - const toolResult = (await window.api.builtinToolCall({ toolName, args })) as any; - const resultText = capToolResult(JSON.stringify(toolResult), toolName); - (mason.history as any[]).push({ - role: "tool", - tool_call_id: tc.id, - name: toolName, - content: resultText, - }); - const preview = - toolResult?.message || - (typeof toolResult?.content === "string" && toolResult.content.slice(0, 200)) || - resultText; - addMessageEl("tool-call", `${toolName}: ${preview}`); - } catch (e) { - (mason.history as any[]).push({ - role: "tool", - tool_call_id: tc.id, - name: toolName, - content: `Error: ${(e as Error).message}`, - }); - addMessageEl("error", `Tool error (${toolName}): ${(e as Error).message}`); - } - continue; - } - - const server = findMcpServerForTool(toolName); - if (!server) { - (mason.history as any[]).push({ - role: "tool", - tool_call_id: tc.id, - name: toolName, - content: "Error: no MCP server found for this tool", - }); - continue; - } - - try { - let toolResult: any; - if (server.type === "stdio") { - toolResult = await window.api.mcpStdioCallTool({ - key: server.key!, - toolName, - args, - }); - } else { - const mcpToken = await getAuthToken(); - toolResult = await window.api.mcpCallTool({ - serverUrl: server.url!, - token: mcpToken, - toolName, - args, - }); - } - const rawText = toolResult?.content - ? toolResult.content.map((c: any) => c.text || JSON.stringify(c)).join("\n") - : JSON.stringify(toolResult); - const resultText = capToolResult(rawText, toolName); - (mason.history as any[]).push({ - role: "tool", - tool_call_id: tc.id, - name: toolName, - content: resultText, - }); + const isBuiltin = BUILTIN_TOOL_NAMES.has(toolName); + const r = await executeToolCore(toolName, args); + (mason.history as any[]).push({ + role: "tool", + tool_call_id: tc.id, + name: toolName, + content: r.content, + }); + if (r.ok) { addMessageEl( "tool-call", - `${toolName} result: ${resultText.slice(0, 200)}${resultText.length > 200 ? "..." : ""}` + isBuiltin ? `${toolName}: ${r.preview}` : `${toolName} result: ${r.preview}` ); - } catch (e) { - (mason.history as any[]).push({ - role: "tool", - tool_call_id: tc.id, - name: toolName, - content: `Error: ${(e as Error).message}`, - }); - addMessageEl("error", `Tool error (${toolName}): ${(e as Error).message}`); + } else if (r.content !== "Error: no MCP server found for this tool") { + addMessageEl("error", `Tool error (${toolName}): ${r.preview}`); } } diff --git a/src/messages.ts b/src/messages.ts index 07d5c93..14be21b 100644 --- a/src/messages.ts +++ b/src/messages.ts @@ -121,12 +121,16 @@ interface AskUserQuestion { // questions in sequence (single chat bubble — no round-trip to the model // between questions). Resolves with a JSON-stringified record of // { question: answer } pairs, or the literal "user_cancelled" if the user -// cancels at any step. -function renderQuestionCard(questions: AskUserQuestion[]): Promise { +// cancels at any step. `container` defaults to the chat messages pane; the +// workflow designer passes its transcript drawer instead. +function renderQuestionCard( + questions: AskUserQuestion[], + container?: HTMLElement +): Promise { return new Promise((resolve) => { removeThinking(); clearWelcome(); - const messagesEl = mason.el.messages as HTMLElement | null; + const messagesEl = container || (mason.el.messages as HTMLElement | null); const list = (questions || []).slice(0, 4); if (!messagesEl || list.length === 0) { resolve("user_cancelled"); diff --git a/src/tools.ts b/src/tools.ts index 7e5c72b..49d9bec 100644 --- a/src/tools.ts +++ b/src/tools.ts @@ -108,14 +108,17 @@ const BUILTIN_TOOL_NAMES = new Set(BUILTIN_TOOLS.map((t) => t.function.name)); // these inline so they can render UI and await user input. const RENDERER_BUILTIN_TOOL_NAMES = new Set(["ask_user", "load_skill"]); -function getAllToolDefs(): ToolDef[] { - const tools: ToolDef[] = BUILTIN_TOOLS.filter( - (t) => !mason.disabledTools.has(t.function.name) - ); +// Without an allowlist: every tool the user hasn't globally disabled (chat +// behavior). With an allowlist (workflow cells): only tools that are both +// available and named in the set — per-cell narrowing, never widening. +function getAllToolDefs(allowlist?: Set): ToolDef[] { + const allowed = (name: string): boolean => + allowlist ? allowlist.has(name) : !mason.disabledTools.has(name); + const tools: ToolDef[] = BUILTIN_TOOLS.filter((t) => allowed(t.function.name)); for (const server of mason.mcpServers) { const serverTools = (server.tools || []) as McpToolDescriptor[]; for (const tool of serverTools) { - if (mason.disabledTools.has(tool.name)) continue; + if (!allowed(tool.name)) continue; tools.push({ type: "function", function: { From 3d7c44192edff88c23f6b1f04fd303533f2456b3 Mon Sep 17 00:00:00 2001 From: Grant Doyle Date: Thu, 11 Jun 2026 11:50:59 -0500 Subject: [PATCH 5/8] =?UTF-8?q?feat(designer):=20agentic=20workflow=20desi?= =?UTF-8?q?gner=20=E2=80=94=20canvas,=20cells,=20edges,=20engine?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Visual node-based workflow designer (sidebar button above Profile, Cmd+D). Cells select a model + per-cell tool subset + prompt; flow edges pipe outputs downstream with labeled multi-input joins; dashed feedback edges create bounded revision loops driven by a forced route_output verdict tool on gate cells. Sequential execution through the existing chat IPC; budgets (40 inner / 5 feedback / 25 global) guard against runaway loops. Workflows persist to ~/.mason/workflows. Includes the Spec → Implement → Test → Review template. Co-authored-by: Isaac --- css/app.css | 355 +++++++++++++++ index.html | 48 ++ src/app.ts | 13 + src/chat.ts | 8 + src/dashboards.ts | 4 + src/designer.ts | 966 ++++++++++++++++++++++++++++++++++++++++ src/history.ts | 4 + src/main.ts | 55 +++ src/preload.ts | 5 + src/shared/api.ts | 12 + src/state.ts | 6 + src/types/state.d.ts | 14 +- src/types/workflow.d.ts | 68 +++ src/workflow-engine.ts | 638 ++++++++++++++++++++++++++ 14 files changed, 2195 insertions(+), 1 deletion(-) create mode 100644 src/designer.ts create mode 100644 src/types/workflow.d.ts create mode 100644 src/workflow-engine.ts diff --git a/css/app.css b/css/app.css index f4b07f2..7da9c59 100644 --- a/css/app.css +++ b/css/app.css @@ -1066,3 +1066,358 @@ /* Notes block in update modal */ body.dark #updateNotes { background: rgba(255,255,255,0.05); } + + /* ============================================================ + Workflow Designer + ============================================================ */ + + .sidebar-designer-btn { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 10px; + border: 1px solid #ccc; + border-radius: 8px; + background: none; + color: inherit; + font-size: 0.82rem; + cursor: pointer; + width: 100%; + transition: background 0.2s, border-color 0.2s; + } + .sidebar-designer-btn:hover { background: rgba(0,0,0,0.04); border-color: #aaa; } + .sidebar-designer-btn.active { border-color: #ff3621; color: #ff3621; } + body.dark .sidebar-designer-btn { border-color: #444; } + body.dark .sidebar-designer-btn:hover { background: rgba(255,255,255,0.04); border-color: #666; } + body.dark .sidebar-designer-btn.active { border-color: #ff3621; color: #ff6a5b; } + + .designer-view { + display: none; + flex: 1; + flex-direction: column; + width: 100%; + min-height: 0; + position: relative; + } + .designer-view.visible { display: flex; } + + .designer-toolbar { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 14px; + border-bottom: 1px solid #e8e8e8; + flex-wrap: wrap; + } + body.dark .designer-toolbar { border-color: #2a2a2a; } + .designer-toolbar select, + .wf-name-input { + padding: 6px 10px; + border: 1px solid #ddd; + border-radius: 8px; + font-size: 0.82rem; + background: #fff; + color: #333; + outline: none; + } + .wf-name-input { flex: 0 1 220px; font-weight: 600; } + .wf-name-input:focus { border-color: #007aff; } + body.dark .designer-toolbar select, + body.dark .wf-name-input { background: #222; border-color: #3a3a3a; color: #ccc; } + + .wf-btn { + padding: 6px 12px; + border: 1px solid #ccc; + border-radius: 8px; + background: none; + color: inherit; + font-size: 0.82rem; + cursor: pointer; + transition: background 0.2s, border-color 0.2s; + white-space: nowrap; + } + .wf-btn:hover { background: rgba(0,0,0,0.04); border-color: #aaa; } + body.dark .wf-btn { border-color: #444; } + body.dark .wf-btn:hover { background: rgba(255,255,255,0.05); } + .wf-btn.primary { background: #ff3621; border-color: #ff3621; color: #fff; font-weight: 600; } + .wf-btn.primary:hover { background: #e62f1c; } + .wf-btn.primary.running { background: #555; border-color: #555; } + .wf-btn.danger:hover { border-color: #c00; color: #c00; } + .wf-btn.dirty::after { content: " •"; color: #ff3621; } + + .wf-status { font-size: 0.78rem; opacity: 0.6; margin-left: auto; max-width: 46%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + .wf-status.error { color: #c00; opacity: 1; } + body.dark .wf-status.error { color: #ff6a5b; } + + .designer-canvas-wrap { + flex: 1; + position: relative; + overflow: hidden; + min-height: 0; + cursor: grab; + background-color: #fafafa; + background-image: radial-gradient(circle, rgba(0,0,0,0.13) 1px, transparent 1px); + background-size: 22px 22px; + } + .designer-canvas-wrap:active { cursor: grabbing; } + body.dark .designer-canvas-wrap { + background-color: #161616; + background-image: radial-gradient(circle, rgba(255,255,255,0.09) 1px, transparent 1px); + } + .designer-canvas { + position: absolute; + top: 0; left: 0; + width: 0; height: 0; + transform-origin: 0 0; + } + .designer-edges { + position: absolute; + left: -10000px; + top: -10000px; + width: 20000px; + height: 20000px; + overflow: visible; + pointer-events: none; + } + .wf-edge { + fill: none; + stroke: #9aa3ad; + stroke-width: 2; + pointer-events: none; + } + .wf-edge.selected { stroke: #007aff; stroke-width: 3; } + .wf-edge-feedback { stroke: #ff3621; stroke-dasharray: 7 5; } + .wf-edge-feedback.selected { stroke: #ff3621; stroke-width: 3.5; } + .wf-edge-ghost { stroke: #007aff; stroke-dasharray: 4 4; opacity: 0.7; } + .wf-edge-hit { + fill: none; + stroke: transparent; + stroke-width: 16; + pointer-events: stroke; + cursor: pointer; + } + .wf-edge-label { + font-size: 12px; + fill: #667; + text-anchor: middle; + pointer-events: none; + font-family: -apple-system, BlinkMacSystemFont, sans-serif; + } + body.dark .wf-edge-label { fill: #99a; } + body.dark .wf-edge { stroke: #5a626c; } + + .wf-edge-label-input { + position: absolute; + transform: translate(-50%, -50%); + z-index: 30; + padding: 4px 8px; + border: 1px solid #007aff; + border-radius: 6px; + font-size: 0.78rem; + background: #fff; + color: #333; + outline: none; + width: 170px; + } + body.dark .wf-edge-label-input { background: #2a2a2a; color: #ddd; } + + .wf-cell { + position: absolute; + width: 240px; + background: #fff; + border: 1.5px solid #d4d8dd; + border-radius: 12px; + box-shadow: 0 2px 10px rgba(0,0,0,0.07); + padding: 8px 10px 10px; + display: flex; + flex-direction: column; + gap: 6px; + cursor: default; + box-sizing: border-box; + } + .wf-cell.selected { border-color: #007aff; box-shadow: 0 3px 14px rgba(0,122,255,0.22); } + body.dark .wf-cell { background: #242424; border-color: #3c4046; } + body.dark .wf-cell.selected { border-color: #4a9bff; } + + .wf-cell-header { + display: flex; + align-items: center; + gap: 6px; + cursor: move; + margin: -8px -10px 0; + padding: 8px 10px 2px; + } + .wf-cell-name { + flex: 1; + min-width: 0; + border: none; + background: none; + font-weight: 700; + font-size: 0.86rem; + color: inherit; + outline: none; + cursor: text; + } + .wf-cell-delete { + border: none; + background: none; + color: inherit; + opacity: 0.4; + font-size: 1rem; + cursor: pointer; + padding: 0 2px; + } + .wf-cell-delete:hover { opacity: 1; color: #c00; } + + .wf-cell-model { + width: 100%; + padding: 4px 6px; + border: 1px solid #ddd; + border-radius: 6px; + font-size: 0.76rem; + background: #fff; + color: #333; + outline: none; + } + body.dark .wf-cell-model { background: #1d1d1d; border-color: #3a3a3a; color: #ccc; } + + .wf-cell-toolsrow { display: flex; align-items: center; justify-content: space-between; gap: 6px; } + .wf-cell-tools-btn { + border: 1px solid #ddd; + border-radius: 6px; + background: none; + color: inherit; + font-size: 0.72rem; + padding: 3px 8px; + cursor: pointer; + } + .wf-cell-tools-btn:hover { border-color: #aaa; background: rgba(0,0,0,0.03); } + body.dark .wf-cell-tools-btn { border-color: #3a3a3a; } + + .wf-cell-status { + font-size: 0.7rem; + padding: 2px 8px; + border-radius: 999px; + background: rgba(0,0,0,0.06); + opacity: 0.75; + } + body.dark .wf-cell-status { background: rgba(255,255,255,0.08); } + .wf-status-running { background: #ff3621 !important; color: #fff; opacity: 1; animation: wfPulse 1.2s ease-in-out infinite; } + .wf-status-queued { background: rgba(255,54,33,0.15) !important; color: #ff3621; opacity: 1; } + .wf-status-done { background: #1d9b4e !important; color: #fff; opacity: 1; } + .wf-status-failed { background: #c00 !important; color: #fff; opacity: 1; } + .wf-status-skipped { opacity: 0.4; } + @keyframes wfPulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.55; } } + + .wf-cell-prompt { + width: 100%; + resize: vertical; + min-height: 54px; + max-height: 220px; + border: 1px solid #ddd; + border-radius: 6px; + font-size: 0.76rem; + font-family: inherit; + padding: 6px 8px; + background: #fff; + color: #333; + outline: none; + box-sizing: border-box; + } + .wf-cell-prompt:focus { border-color: #007aff; } + body.dark .wf-cell-prompt { background: #1d1d1d; border-color: #3a3a3a; color: #ccc; } + + .wf-port { + position: absolute; + width: 14px; + height: 14px; + border-radius: 50%; + background: #fff; + border: 2.5px solid #9aa3ad; + z-index: 2; + } + body.dark .wf-port { background: #242424; } + .wf-port-in { left: -8px; top: 50%; transform: translateY(-50%); } + .wf-port-out { right: -8px; top: 50%; transform: translateY(-50%); cursor: crosshair; } + .wf-port-out:hover { border-color: #007aff; background: #007aff; } + .wf-port-feedback { + left: 50%; top: -8px; transform: translateX(-50%); + border-style: dashed; + border-color: #ff3621; + } + + .designer-drawer { + border-top: 1px solid #e8e8e8; + height: 240px; + display: flex; + flex-direction: column; + flex-shrink: 0; + background: #fff; + } + body.dark .designer-drawer { border-color: #2a2a2a; background: #1c1c1c; } + .designer-drawer.collapsed { height: 38px; } + .designer-drawer.collapsed .designer-drawer-body { display: none; } + .designer-drawer-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 6px 14px; + font-size: 0.82rem; + font-weight: 600; + flex-shrink: 0; + } + .designer-drawer-header .wf-cell-status { margin-left: 8px; } + .designer-drawer-body { + flex: 1; + overflow-y: auto; + padding: 4px 16px 14px; + font-size: 0.84rem; + } + .wf-drawer-hint { opacity: 0.5; font-size: 0.8rem; padding: 8px 0; } + .wf-tr { margin: 6px 0; } + .wf-tr-tool-call, .wf-tr-tool-result, .wf-tr-info { + font-family: ui-monospace, Menlo, monospace; + font-size: 0.74rem; + color: #3568a8; + background: rgba(0,122,255,0.07); + border-radius: 6px; + padding: 4px 8px; + white-space: pre-wrap; + word-break: break-word; + } + body.dark .wf-tr-tool-call, body.dark .wf-tr-tool-result, body.dark .wf-tr-info { color: #7fb0e8; } + .wf-tr-error { color: #c00; font-size: 0.78rem; } + body.dark .wf-tr-error { color: #ff6a5b; } + .wf-tr-verdict { + border-left: 3px solid #ff3621; + padding: 4px 10px; + font-size: 0.8rem; + font-weight: 600; + background: rgba(255,54,33,0.06); + border-radius: 0 6px 6px 0; + white-space: pre-wrap; + } + .wf-tr-output { border-top: 1px dashed #ddd; padding-top: 8px; } + body.dark .wf-tr-output { border-color: #3a3a3a; } + + .wf-tools-group { + font-size: 0.72rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.04em; + opacity: 0.5; + margin: 12px 0 4px; + } + .wf-tools-row { + display: flex; + align-items: baseline; + gap: 8px; + padding: 3px 2px; + font-size: 0.8rem; + cursor: pointer; + border-radius: 6px; + } + .wf-tools-row:hover { background: rgba(0,0,0,0.03); } + body.dark .wf-tools-row:hover { background: rgba(255,255,255,0.04); } + .wf-tools-name { font-family: ui-monospace, Menlo, monospace; font-size: 0.74rem; } + .wf-tools-desc { opacity: 0.45; font-size: 0.72rem; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } diff --git a/index.html b/index.html index 11602e2..a1baf06 100644 --- a/index.html +++ b/index.html @@ -32,6 +32,15 @@
+ +
+
+ + + + + + + + + +
+
+
+ +
+
+
+
+ Transcript + +
+
+
+ + +
+
@@ -429,6 +475,8 @@

MCP Servers

+ + diff --git a/src/app.ts b/src/app.ts index a2bb2a8..81702d0 100644 --- a/src/app.ts +++ b/src/app.ts @@ -879,6 +879,12 @@ function initEventListeners(): void { (el.settingsBtn as HTMLElement | null)?.addEventListener("click", () => switchToSettingsView()); (el.settingsViewClose as HTMLElement | null)?.addEventListener("click", () => switchToChatsTab()); + // Workflow designer (toggles back to chat when already open) + document.getElementById("designerBtn")?.addEventListener("click", () => { + if (mason.currentView === "designer") switchToChatsTab(); + else switchToDesignerView(); + }); + // Update modal const updateModal = el.updateModal as HTMLElement | null; const updateLatest = el.updateLatest as HTMLElement | null; @@ -1258,6 +1264,8 @@ function initEventListeners(): void { mason.autoConnectDone = false; await autoConnectMcp(); if (mason.currentView === "dashboards") loadDashboards(); + // Re-validate workflow cell models against the new workspace's model list. + designerOnProfileSwitch(); }); // Chat send @@ -1289,6 +1297,11 @@ function initEventListeners(): void { e.preventDefault(); (el.sidebar as HTMLElement | null)?.classList.toggle("hidden"); } + if (mod && e.key === "d") { + e.preventDefault(); + if (mason.currentView === "designer") switchToChatsTab(); + else switchToDesignerView(); + } if (e.key === "Escape") { popup?.classList.remove("open"); modelMenu?.classList.remove("open"); diff --git a/src/chat.ts b/src/chat.ts index df3e4d5..0a7ac39 100644 --- a/src/chat.ts +++ b/src/chat.ts @@ -187,6 +187,14 @@ async function send(): Promise { return; } + if (mason.workflowRun?.running) { + addMessageEl( + "error", + "A workflow is currently running — stop it in the Workflow Designer before chatting." + ); + return; + } + const profile = getSelectedProfile(); if (!profile) { addMessageEl("error", "Select a Databricks profile in the sidebar."); diff --git a/src/dashboards.ts b/src/dashboards.ts index 4270b77..3041d1e 100644 --- a/src/dashboards.ts +++ b/src/dashboards.ts @@ -11,6 +11,7 @@ declare function renderProfilesList(): void; declare function refreshSkillsState(): Promise; declare function renderSkillsSettingsList(): void; declare function updateSkillsAutoLoadVisual(): void; +declare function hideDesignerView(): void; function elAs(key: string): T | null { return mason.el[key] as T | null; @@ -39,6 +40,7 @@ function switchToChatsTab(): void { const settingsClose = elAs("settingsViewClose"); if (settingsClose) settingsClose.style.display = "none"; elAs("onboardingView")?.classList.remove("visible"); + hideDesignerView(); } function switchToDashboardsTab(): void { @@ -63,6 +65,7 @@ function switchToDashboardsTab(): void { elAs("settingsView")?.classList.remove("visible"); const settingsClose = elAs("settingsViewClose"); if (settingsClose) settingsClose.style.display = "none"; + hideDesignerView(); loadDashboards(); } @@ -82,6 +85,7 @@ function switchToSettingsView(): void { const settingsClose = elAs("settingsViewClose"); if (settingsClose) settingsClose.style.display = "inline-block"; elAs("onboardingView")?.classList.remove("visible"); + hideDesignerView(); if (typeof renderProfilesList === "function") renderProfilesList(); if (typeof refreshSkillsState === "function") { refreshSkillsState().then(() => { diff --git a/src/designer.ts b/src/designer.ts new file mode 100644 index 0000000..785d6cc --- /dev/null +++ b/src/designer.ts @@ -0,0 +1,966 @@ +// Agentic Workflow Designer — canvas UI. +// See docs/specs/agentic-workflow-designer.md (Section 4) for the UX spec. +// +// The canvas is a hand-rolled DOM + SVG implementation: cells are absolutely +// positioned cards inside a single transformed container, edges are cubic +// bezier paths in an SVG underlay that shares the same transform, so pan and +// zoom move everything together. + +declare function switchToChatsTab(): void; +declare function renderMarkdown(text: string): string; +declare function renderQuestionCard( + questions: Array<{ question: string; options: string[]; multiSelect?: boolean }>, + container?: HTMLElement +): Promise; + +const CELL_WIDTH = 240; + +// --- module state --- +let dsgInited = false; +let dsgView = { x: 60, y: 40, scale: 1 }; +let dsgSelectedCellId: string | null = null; +let dsgSelectedEdgeId: string | null = null; +let dsgCellEls = new Map(); +let dsgListLoaded = false; + +function dsgEl(id: string): T { + return document.getElementById(id) as T; +} + +function dsgWf(): MasonWorkflow | null { + return mason.currentWorkflow; +} + +function dsgRunning(): boolean { + return !!mason.workflowRun?.running; +} + +function dsgMarkDirty(): void { + mason.workflowDirty = true; + const save = dsgEl("wfSave"); + if (save) save.classList.add("dirty"); +} + +function dsgClearDirty(): void { + mason.workflowDirty = false; + const save = dsgEl("wfSave"); + if (save) save.classList.remove("dirty"); +} + +// --- view switching --- + +function switchToDesignerView(): void { + mason.currentView = "designer"; + const main = document.querySelector(".main") as HTMLElement | null; + if (main) main.style.display = "none"; + document.getElementById("dashboardView")?.classList.remove("visible"); + const webview = document.getElementById("dashboardWebview"); + if (webview) webview.style.display = "none"; + document.getElementById("settingsView")?.classList.remove("visible"); + const settingsClose = document.getElementById("settingsViewClose"); + if (settingsClose) settingsClose.style.display = "none"; + document.getElementById("onboardingView")?.classList.remove("visible"); + document.getElementById("designerView")?.classList.add("visible"); + document.getElementById("designerBtn")?.classList.add("active"); + + initDesigner(); + if (!dsgListLoaded) { + dsgListLoaded = true; + refreshWorkflowList().then(() => { + if (!dsgWf()) { + if (mason.workflows.length > 0) { + openWorkflow(mason.workflows[0].id); + } else { + newWorkflow(); + } + } + }); + } +} + +// Called by every other switchTo* via hideDesignerView(). +function hideDesignerView(): void { + document.getElementById("designerView")?.classList.remove("visible"); + document.getElementById("designerBtn")?.classList.remove("active"); +} + +// --- workflow CRUD --- + +async function refreshWorkflowList(): Promise { + mason.workflows = await window.api.workflowList(); + const sel = dsgEl("wfSelect"); + if (!sel) return; + sel.innerHTML = ""; + for (const w of mason.workflows) { + const opt = document.createElement("option"); + opt.value = w.id; + opt.textContent = w.name; + if (dsgWf()?.id === w.id) opt.selected = true; + sel.appendChild(opt); + } + const newOpt = document.createElement("option"); + newOpt.value = "__new__"; + newOpt.textContent = "+ New workflow"; + sel.appendChild(newOpt); + if (dsgWf()) sel.value = dsgWf()!.id; +} + +function newWorkflow(): void { + mason.currentWorkflow = { + id: genId(), + name: "Untitled workflow", + version: 1, + cells: [], + edges: [], + createdAt: Date.now(), + updatedAt: Date.now(), + }; + mason.workflowRun = null; + dsgSelectedCellId = null; + dsgSelectedEdgeId = null; + dsgView = { x: 60, y: 40, scale: 1 }; + dsgClearDirty(); + renderDesigner(); + refreshWorkflowList(); +} + +async function openWorkflow(id: string): Promise { + const wf = (await window.api.workflowLoad(id)) as MasonWorkflow | null; + if (!wf) return; + mason.currentWorkflow = wf; + mason.workflowRun = null; + dsgSelectedCellId = null; + dsgSelectedEdgeId = null; + dsgClearDirty(); + renderDesigner(); + await refreshWorkflowList(); +} + +async function saveWorkflow(): Promise { + const wf = dsgWf(); + if (!wf) return; + wf.name = dsgEl("wfName")?.value.trim() || "Untitled workflow"; + wf.updatedAt = Date.now(); + const result = await window.api.workflowSave(wf); + if (result.ok) { + dsgClearDirty(); + await refreshWorkflowList(); + dsgSetStatus("Saved.", false); + } else { + dsgSetStatus(result.error || "Save failed.", true); + } +} + +async function deleteWorkflow(): Promise { + const wf = dsgWf(); + if (!wf) return; + if (!confirm(`Delete workflow "${wf.name}"? This cannot be undone.`)) return; + await window.api.workflowDelete(wf.id); + mason.currentWorkflow = null; + await refreshWorkflowList(); + if (mason.workflows.length > 0) { + await openWorkflow(mason.workflows[0].id); + } else { + newWorkflow(); + } +} + +// --- coordinate helpers --- + +function dsgApplyTransform(): void { + const canvas = dsgEl("wfCanvas"); + if (!canvas) return; + canvas.style.transform = `translate(${dsgView.x}px, ${dsgView.y}px) scale(${dsgView.scale})`; +} + +function dsgScreenToCanvas(clientX: number, clientY: number): { x: number; y: number } { + const wrap = dsgEl("wfCanvasWrap"); + const rect = wrap.getBoundingClientRect(); + return { + x: (clientX - rect.left - dsgView.x) / dsgView.scale, + y: (clientY - rect.top - dsgView.y) / dsgView.scale, + }; +} + +// --- rendering --- + +function renderDesigner(): void { + const wf = dsgWf(); + const nameInput = dsgEl("wfName"); + if (nameInput && wf) nameInput.value = wf.name; + const canvas = dsgEl("wfCanvas"); + if (!canvas || !wf) return; + + // Remove cell elements for deleted cells; (re)create the rest. + for (const [id, el] of dsgCellEls) { + if (!wf.cells.some((c) => c.id === id)) { + el.remove(); + dsgCellEls.delete(id); + } + } + for (const cell of wf.cells) renderCell(cell); + dsgApplyTransform(); + redrawEdges(); + renderDrawer(); + dsgUpdateRunButton(); +} + +function dsgModelOptions(selected: string): string { + let html = ""; + for (const g of mason.discoveredModels) { + html += ``; + for (const m of g.models) { + html += ``; + } + html += ""; + } + if (mason.customEndpoints.length > 0) { + html += ''; + for (const ep of mason.customEndpoints) { + const val = `custom:${ep.modelId}`; + html += ``; + } + html += ""; + } + if (selected && !workflowModelAvailable(selected)) { + html += ``; + } + return html; +} + +function dsgStatusInfo(cellId: string): { label: string; cls: string } { + const rec = mason.workflowRun?.cells[cellId]; + const status: CellRunStatus = rec?.status || "idle"; + return { label: status, cls: `wf-status-${status}` }; +} + +function renderCell(cell: WorkflowCellConfig): void { + const canvas = dsgEl("wfCanvas"); + let el = dsgCellEls.get(cell.id); + if (!el) { + el = document.createElement("div"); + el.className = "wf-cell"; + el.dataset.id = cell.id; + el.innerHTML = ` +
+
+ + +
+ +
+ + +
+ +
+
+ `; + canvas.appendChild(el); + dsgCellEls.set(cell.id, el); + dsgWireCell(el, cell.id); + } + el.style.left = `${cell.position.x}px`; + el.style.top = `${cell.position.y}px`; + el.classList.toggle("selected", dsgSelectedCellId === cell.id); + + const nameEl = el.querySelector(".wf-cell-name") as HTMLInputElement; + if (document.activeElement !== nameEl) nameEl.value = cell.name; + const modelEl2 = el.querySelector(".wf-cell-model") as HTMLSelectElement; + modelEl2.innerHTML = dsgModelOptions(cell.model.value); + modelEl2.value = cell.model.value; + const toolsBtn = el.querySelector(".wf-cell-tools-btn") as HTMLButtonElement; + toolsBtn.textContent = `⚒ ${cell.enabledTools.length} tool${cell.enabledTools.length === 1 ? "" : "s"}`; + const promptEl = el.querySelector(".wf-cell-prompt") as HTMLTextAreaElement; + if (document.activeElement !== promptEl) promptEl.value = cell.prompt; + const statusEl = el.querySelector(".wf-cell-status") as HTMLElement; + const st = dsgStatusInfo(cell.id); + statusEl.textContent = st.label; + statusEl.className = `wf-cell-status ${st.cls}`; +} + +function dsgWireCell(el: HTMLElement, cellId: string): void { + const cellOf = (): WorkflowCellConfig | undefined => dsgWf()?.cells.find((c) => c.id === cellId); + + // Select on any mousedown inside the cell. + el.addEventListener("mousedown", () => { + if (dsgSelectedCellId !== cellId) { + dsgSelectedCellId = cellId; + dsgSelectedEdgeId = null; + for (const [id, cellEl] of dsgCellEls) cellEl.classList.toggle("selected", id === cellId); + redrawEdges(); + renderDrawer(); + } + }); + + // Drag by header (but not from the name input or delete button). + const header = el.querySelector(".wf-cell-header") as HTMLElement; + header.addEventListener("mousedown", (e) => { + const t = e.target as HTMLElement; + if (t.closest("input") || t.closest("button")) return; + e.preventDefault(); + const cell = cellOf(); + if (!cell) return; + const start = dsgScreenToCanvas(e.clientX, e.clientY); + const orig = { ...cell.position }; + const onMove = (me: MouseEvent): void => { + const now = dsgScreenToCanvas(me.clientX, me.clientY); + cell.position = { + x: Math.round(orig.x + (now.x - start.x)), + y: Math.round(orig.y + (now.y - start.y)), + }; + el.style.left = `${cell.position.x}px`; + el.style.top = `${cell.position.y}px`; + redrawEdges(); + }; + const onUp = (): void => { + document.removeEventListener("mousemove", onMove); + document.removeEventListener("mouseup", onUp); + dsgMarkDirty(); + }; + document.addEventListener("mousemove", onMove); + document.addEventListener("mouseup", onUp); + }); + + (el.querySelector(".wf-cell-name") as HTMLInputElement).addEventListener("input", (e) => { + const cell = cellOf(); + if (cell) { + cell.name = (e.target as HTMLInputElement).value; + dsgMarkDirty(); + } + }); + + (el.querySelector(".wf-cell-delete") as HTMLButtonElement).addEventListener("click", () => { + const wf = dsgWf(); + if (!wf || dsgRunning()) return; + wf.cells = wf.cells.filter((c) => c.id !== cellId); + wf.edges = wf.edges.filter((e) => e.from !== cellId && e.to !== cellId); + if (dsgSelectedCellId === cellId) dsgSelectedCellId = null; + dsgMarkDirty(); + renderDesigner(); + }); + + (el.querySelector(".wf-cell-model") as HTMLSelectElement).addEventListener("change", (e) => { + const cell = cellOf(); + if (!cell) return; + const sel = e.target as HTMLSelectElement; + cell.model = { + value: sel.value, + label: sel.options[sel.selectedIndex]?.textContent || sel.value, + }; + dsgMarkDirty(); + }); + + (el.querySelector(".wf-cell-prompt") as HTMLTextAreaElement).addEventListener("input", (e) => { + const cell = cellOf(); + if (cell) { + cell.prompt = (e.target as HTMLTextAreaElement).value; + dsgMarkDirty(); + } + }); + + (el.querySelector(".wf-cell-tools-btn") as HTMLButtonElement).addEventListener("click", () => + openCellToolsModal(cellId) + ); + + // Edge creation from the output port. + const outPort = el.querySelector(".wf-port-out") as HTMLElement; + outPort.addEventListener("mousedown", (e) => { + e.preventDefault(); + e.stopPropagation(); + dsgStartEdgeDrag(cellId, e); + }); +} + +// --- edges --- + +function dsgPortPos( + cell: WorkflowCellConfig, + port: "in" | "out" | "feedback" +): { x: number; y: number } { + const el = dsgCellEls.get(cell.id); + const h = el ? el.offsetHeight : 140; + if (port === "out") return { x: cell.position.x + CELL_WIDTH, y: cell.position.y + h / 2 }; + if (port === "in") return { x: cell.position.x, y: cell.position.y + h / 2 }; + return { x: cell.position.x + CELL_WIDTH / 2, y: cell.position.y }; +} + +function dsgEdgePath(edge: WorkflowEdge): string { + const wf = dsgWf()!; + const from = wf.cells.find((c) => c.id === edge.from); + const to = wf.cells.find((c) => c.id === edge.to); + if (!from || !to) return ""; + const p1 = dsgPortPos(from, "out"); + if (edge.kind === "feedback") { + const p2 = dsgPortPos(to, "feedback"); + const dx = Math.max(50, Math.abs(p2.x - p1.x) / 2); + return `M ${p1.x} ${p1.y} C ${p1.x + dx} ${p1.y}, ${p2.x} ${p2.y - 70}, ${p2.x} ${p2.y}`; + } + const p2 = dsgPortPos(to, "in"); + const dx = Math.max(40, Math.abs(p2.x - p1.x) / 2); + return `M ${p1.x} ${p1.y} C ${p1.x + dx} ${p1.y}, ${p2.x - dx} ${p2.y}, ${p2.x} ${p2.y}`; +} + +function dsgEdgeMidpoint(edge: WorkflowEdge): { x: number; y: number } { + const wf = dsgWf()!; + const from = wf.cells.find((c) => c.id === edge.from); + const to = wf.cells.find((c) => c.id === edge.to); + if (!from || !to) return { x: 0, y: 0 }; + const p1 = dsgPortPos(from, "out"); + const p2 = dsgPortPos(to, edge.kind === "feedback" ? "feedback" : "in"); + return { x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2 }; +} + +function redrawEdges(): void { + const svg = dsgEl("wfEdgeSvg"); + const wf = dsgWf(); + if (!svg || !wf) return; + const NS = "http://www.w3.org/2000/svg"; + svg.innerHTML = ""; + for (const edge of wf.edges) { + const d = dsgEdgePath(edge); + if (!d) continue; + + const path = document.createElementNS(NS, "path"); + path.setAttribute("d", d); + path.setAttribute( + "class", + `wf-edge wf-edge-${edge.kind}${dsgSelectedEdgeId === edge.id ? " selected" : ""}` + ); + svg.appendChild(path); + + // Fat invisible twin for clicks. + const hit = document.createElementNS(NS, "path"); + hit.setAttribute("d", d); + hit.setAttribute("class", "wf-edge-hit"); + hit.addEventListener("mousedown", (e) => { + e.stopPropagation(); + dsgSelectedEdgeId = edge.id; + dsgSelectedCellId = null; + for (const el of dsgCellEls.values()) el.classList.remove("selected"); + redrawEdges(); + renderDrawer(); + }); + hit.addEventListener("dblclick", (e) => { + e.stopPropagation(); + dsgEditEdgeLabel(edge.id); + }); + svg.appendChild(hit); + + if (edge.label) { + const mid = dsgEdgeMidpoint(edge); + const text = document.createElementNS(NS, "text"); + text.setAttribute("x", String(mid.x)); + text.setAttribute("y", String(mid.y - 6)); + text.setAttribute("class", "wf-edge-label"); + text.textContent = edge.label; + svg.appendChild(text); + } + } +} + +function dsgStartEdgeDrag(fromCellId: string, e: MouseEvent): void { + const svg = dsgEl("wfEdgeSvg"); + const wf = dsgWf(); + if (!svg || !wf || dsgRunning()) return; + const fromCell = wf.cells.find((c) => c.id === fromCellId)!; + const p1 = dsgPortPos(fromCell, "out"); + const NS = "http://www.w3.org/2000/svg"; + const ghost = document.createElementNS(NS, "path"); + ghost.setAttribute("class", "wf-edge wf-edge-ghost"); + svg.appendChild(ghost); + + const onMove = (me: MouseEvent): void => { + const p2 = dsgScreenToCanvas(me.clientX, me.clientY); + const dx = Math.max(40, Math.abs(p2.x - p1.x) / 2); + ghost.setAttribute( + "d", + `M ${p1.x} ${p1.y} C ${p1.x + dx} ${p1.y}, ${p2.x - dx} ${p2.y}, ${p2.x} ${p2.y}` + ); + }; + const onUp = (me: MouseEvent): void => { + document.removeEventListener("mousemove", onMove); + document.removeEventListener("mouseup", onUp); + ghost.remove(); + const target = document.elementFromPoint(me.clientX, me.clientY) as HTMLElement | null; + const cellEl = target?.closest(".wf-cell") as HTMLElement | null; + if (!cellEl || !cellEl.dataset.id || cellEl.dataset.id === fromCellId) return; + const kind: WorkflowEdgeKind = + target?.closest(".wf-port-feedback") != null ? "feedback" : "flow"; + const to = cellEl.dataset.id; + if (wf.edges.some((ed) => ed.from === fromCellId && ed.to === to && ed.kind === kind)) return; + wf.edges.push({ id: genId(), from: fromCellId, to, kind }); + dsgMarkDirty(); + redrawEdges(); + }; + document.addEventListener("mousemove", onMove); + document.addEventListener("mouseup", onUp); + onMove(e); +} + +function dsgDeleteSelectedEdge(): void { + const wf = dsgWf(); + if (!wf || !dsgSelectedEdgeId || dsgRunning()) return; + wf.edges = wf.edges.filter((e) => e.id !== dsgSelectedEdgeId); + dsgSelectedEdgeId = null; + dsgMarkDirty(); + redrawEdges(); +} + +// Floating inline input for edge labels (window.prompt doesn't exist in +// Electron renderers). +function dsgEditEdgeLabel(edgeId: string): void { + const wf = dsgWf(); + const wrap = dsgEl("wfCanvasWrap"); + if (!wf || !wrap) return; + const edge = wf.edges.find((e) => e.id === edgeId); + if (!edge) return; + const mid = dsgEdgeMidpoint(edge); + const input = document.createElement("input"); + input.className = "wf-edge-label-input"; + input.value = edge.label || ""; + input.placeholder = "Edge label (e.g. spec)"; + input.style.left = `${mid.x * dsgView.scale + dsgView.x}px`; + input.style.top = `${mid.y * dsgView.scale + dsgView.y}px`; + wrap.appendChild(input); + input.focus(); + input.select(); + const commit = (): void => { + edge.label = input.value.trim() || undefined; + input.remove(); + dsgMarkDirty(); + redrawEdges(); + }; + input.addEventListener("blur", commit); + input.addEventListener("keydown", (e) => { + if (e.key === "Enter") input.blur(); + if (e.key === "Escape") { + input.removeEventListener("blur", commit); + input.remove(); + } + e.stopPropagation(); + }); +} + +// --- cells --- + +function addCell(at?: { x: number; y: number }): void { + const wf = dsgWf(); + if (!wf || dsgRunning()) return; + const wrap = dsgEl("wfCanvasWrap"); + const rect = wrap.getBoundingClientRect(); + const center = + at || + dsgScreenToCanvas(rect.left + rect.width / 2 - 120, rect.top + rect.height / 2 - 90); + const fallback = mason.defaultModel || { + value: mason.selectedModelValue, + label: mason.selectedModelLabel, + }; + wf.cells.push({ + id: genId(), + name: `Cell ${wf.cells.length + 1}`, + model: { ...fallback }, + enabledTools: [], + prompt: "", + position: { x: Math.round(center.x), y: Math.round(center.y) }, + }); + dsgMarkDirty(); + renderDesigner(); +} + +// --- per-cell tools modal --- + +let dsgToolsCellId: string | null = null; + +function openCellToolsModal(cellId: string): void { + const wf = dsgWf(); + const cell = wf?.cells.find((c) => c.id === cellId); + if (!cell) return; + dsgToolsCellId = cellId; + const modal = dsgEl("wfToolsModal"); + const list = dsgEl("wfToolsList"); + const title = dsgEl("wfToolsTitle"); + title.textContent = `Tools for "${cell.name}"`; + list.innerHTML = ""; + + const enabled = new Set(cell.enabledTools); + const all = getAllToolDefsUnfiltered(); + const bySource = new Map(); + for (const t of all) { + const src = t._source || "Other"; + if (!bySource.has(src)) bySource.set(src, []); + bySource.get(src)!.push(t); + } + for (const [source, tools] of bySource) { + const header = document.createElement("div"); + header.className = "wf-tools-group"; + header.textContent = source; + list.appendChild(header); + for (const t of tools) { + const name = t.function.name; + const row = document.createElement("label"); + row.className = "wf-tools-row"; + row.innerHTML = `${escapeHtml(name)}${escapeHtml((t.function.description || "").slice(0, 90))}`; + (row.querySelector("input") as HTMLInputElement).addEventListener("change", (e) => { + const on = (e.target as HTMLInputElement).checked; + if (on) enabled.add(name); + else enabled.delete(name); + cell.enabledTools = [...enabled]; + dsgMarkDirty(); + renderCell(cell); + }); + list.appendChild(row); + } + } + modal.classList.add("open"); +} + +// --- run / stop --- + +function dsgSetStatus(text: string, isError: boolean): void { + const el = dsgEl("wfStatus"); + if (!el) return; + el.textContent = text; + el.classList.toggle("error", isError); +} + +function dsgUpdateRunButton(): void { + const btn = dsgEl("wfRun"); + if (!btn) return; + const running = dsgRunning(); + btn.textContent = running ? "■ Stop" : "▶ Run"; + btn.classList.toggle("running", running); +} + +async function runCurrentWorkflow(): Promise { + const wf = dsgWf(); + if (!wf) return; + if (dsgRunning()) { + // Stop. + if (mason.workflowRun) mason.workflowRun.aborted = true; + window.api.abortChat(); + return; + } + if (mason.generating) { + dsgSetStatus("A chat response is still streaming — stop it first.", true); + return; + } + const errors = validateWorkflow(wf); + if (errors.length > 0) { + dsgSetStatus(errors[0] + (errors.length > 1 ? ` (+${errors.length - 1} more)` : ""), true); + return; + } + dsgSetStatus("", false); + + const callbacks: EngineCallbacks = { + onCellStatus: (cellId) => { + const cell = wf.cells.find((c) => c.id === cellId); + if (cell) renderCell(cell); + if (cellId === dsgSelectedCellId) renderDrawer(); + // Auto-follow the running cell so the drawer shows live output. + if (mason.workflowRun?.cells[cellId]?.status === "running") { + dsgSelectedCellId = cellId; + dsgSelectedEdgeId = null; + for (const [id, el] of dsgCellEls) el.classList.toggle("selected", id === cellId); + renderDrawer(); + } + dsgUpdateRunButton(); + }, + onCellTranscript: (cellId, entry) => { + mason.workflowRun?.cells[cellId]?.transcript.push(entry); + if (cellId === dsgSelectedCellId) renderDrawer(); + }, + onStreamText: (cellId, text) => { + if (cellId !== dsgSelectedCellId) return; + const live = document.getElementById("wfDrawerLive"); + if (live) { + live.innerHTML = renderMarkdown(text); + const body = dsgEl("wfDrawerBody"); + if (body) body.scrollTop = body.scrollHeight; + } + }, + askUser: (questions) => { + const body = dsgEl("wfDrawerBody"); + dsgEl("wfDrawer")?.classList.remove("collapsed"); + return renderQuestionCard(questions, body || undefined); + }, + }; + + dsgUpdateRunButton(); + try { + const state = await runWorkflow(wf, callbacks); + if (state.aborted) { + dsgSetStatus("Stopped.", false); + } else if (state.error) { + dsgSetStatus(state.error, true); + } else { + dsgSetStatus(`Done — ${state.totalSteps} cell run${state.totalSteps === 1 ? "" : "s"}.`, false); + } + } catch (e) { + dsgSetStatus((e as Error).message, true); + } finally { + dsgUpdateRunButton(); + renderDesigner(); + } +} + +// --- transcript drawer --- + +function renderDrawer(): void { + const title = dsgEl("wfDrawerTitle"); + const body = dsgEl("wfDrawerBody"); + if (!title || !body) return; + const wf = dsgWf(); + + if (dsgSelectedEdgeId && wf) { + const edge = wf.edges.find((e) => e.id === dsgSelectedEdgeId); + if (edge) { + const from = wf.cells.find((c) => c.id === edge.from)?.name || "?"; + const to = wf.cells.find((c) => c.id === edge.to)?.name || "?"; + title.textContent = `${edge.kind === "feedback" ? "Feedback edge" : "Edge"}: ${from} → ${to}`; + body.innerHTML = `
${edge.label ? `Label: ${escapeHtml(edge.label)}. ` : ""}Double-click the edge to ${edge.label ? "edit" : "add"} a label — labels become input headers downstream and route names for gates. Press Delete to remove the edge.
`; + return; + } + } + + const cell = wf?.cells.find((c) => c.id === dsgSelectedCellId); + if (!cell) { + title.textContent = "Transcript"; + body.innerHTML = + '
Select a cell to see its run transcript. Drag from a cell\'s right port onto another cell to connect them; drop on the top port for a feedback edge.
'; + return; + } + const rec = mason.workflowRun?.cells[cell.id]; + const st = dsgStatusInfo(cell.id); + title.innerHTML = `${escapeHtml(cell.name)} ${st.label}`; + body.innerHTML = ""; + if (!rec || (rec.transcript.length === 0 && !rec.output && rec.status !== "running")) { + body.innerHTML = '
No runs yet.
'; + return; + } + for (const entry of rec.transcript) { + const div = document.createElement("div"); + div.className = `wf-tr wf-tr-${entry.kind}`; + if (entry.kind === "assistant") { + div.innerHTML = renderMarkdown(entry.text); + } else { + div.textContent = entry.text; + } + body.appendChild(div); + } + if (rec.status === "running") { + const live = document.createElement("div"); + live.id = "wfDrawerLive"; + live.className = "wf-tr wf-tr-assistant"; + body.appendChild(live); + } else if (rec.output) { + const out = document.createElement("div"); + out.className = "wf-tr wf-tr-assistant wf-tr-output"; + out.innerHTML = renderMarkdown(rec.output); + body.appendChild(out); + } + if (rec.error) { + const err = document.createElement("div"); + err.className = "wf-tr wf-tr-error"; + err.textContent = rec.error; + body.appendChild(err); + } + body.scrollTop = body.scrollHeight; +} + +// --- template (spec section 12) --- + +function dsgFindModel(needle: string): { value: string; label: string } | null { + for (const g of mason.discoveredModels) { + for (const m of g.models) { + if (m.value.toLowerCase().includes(needle)) return { value: m.value, label: m.label }; + } + } + return null; +} + +function insertTemplateWorkflow(): void { + const fallback = mason.defaultModel || { + value: mason.selectedModelValue, + label: mason.selectedModelLabel, + }; + const fable = dsgFindModel("fable") || fallback; + const opus = dsgFindModel("opus") || fallback; + const sonnet = dsgFindModel("sonnet") || fallback; + + const spec: WorkflowCellConfig = { + id: genId(), + name: "Spec", + model: { ...fable }, + enabledTools: [], + prompt: + "You are the architect. Turn the high-level goals below into (1) a precise project specification and (2) a unit-test spec sheet listing concrete, verifiable acceptance tests. Be exhaustive but unambiguous.", + position: { x: 40, y: 160 }, + }; + const impl: WorkflowCellConfig = { + id: genId(), + name: "Implement", + model: { ...opus }, + enabledTools: ["write_file", "read_file"], + prompt: + "You are the implementer. Build exactly what the specification asks for. When you receive feedback about failing tests, close every gap it names.", + position: { x: 380, y: 40 }, + maxLoopIterations: 5, + }; + const tests: WorkflowCellConfig = { + id: genId(), + name: "Unit tests", + model: { ...sonnet }, + enabledTools: ["read_file"], + prompt: + "You are the test runner. Evaluate the implementation against the unit-test spec sheet, test by test. Report which pass and which fail with specifics. If any fail, route feedback to the implementer; when all pass, hand off for final review.", + position: { x: 720, y: 160 }, + }; + const review: WorkflowCellConfig = { + id: genId(), + name: "Final review", + model: { ...fable }, + enabledTools: [], + prompt: + "You are the original architect reviewing the finished work against your specification. If it fully satisfies the spec, end the workflow with a final summary. If gaps remain, route feedback to the implementer describing exactly what to fix.", + position: { x: 1060, y: 40 }, + }; + mason.currentWorkflow = { + id: genId(), + name: "Spec → Implement → Test → Review", + version: 1, + cells: [spec, impl, tests, review], + edges: [ + { id: genId(), from: spec.id, to: impl.id, kind: "flow", label: "goals" }, + { id: genId(), from: spec.id, to: tests.id, kind: "flow", label: "spec" }, + { id: genId(), from: impl.id, to: tests.id, kind: "flow", label: "implementation" }, + { id: genId(), from: tests.id, to: impl.id, kind: "feedback", label: "test failures" }, + { id: genId(), from: tests.id, to: review.id, kind: "flow", label: "passing work" }, + { id: genId(), from: review.id, to: impl.id, kind: "feedback", label: "review feedback" }, + ], + createdAt: Date.now(), + updatedAt: Date.now(), + }; + mason.workflowRun = null; + dsgSelectedCellId = null; + dsgSelectedEdgeId = null; + dsgView = { x: 30, y: 30, scale: 0.85 }; + dsgMarkDirty(); + renderDesigner(); + refreshWorkflowList(); +} + +// --- profile switch revalidation --- + +function designerOnProfileSwitch(): void { + if (mason.currentView === "designer") renderDesigner(); +} + +// --- init / event wiring --- + +function initDesigner(): void { + if (dsgInited) return; + dsgInited = true; + + dsgEl("wfAddCell")?.addEventListener("click", () => addCell()); + dsgEl("wfRun")?.addEventListener("click", () => runCurrentWorkflow()); + dsgEl("wfSave")?.addEventListener("click", () => saveWorkflow()); + dsgEl("wfNew")?.addEventListener("click", () => newWorkflow()); + dsgEl("wfTemplate")?.addEventListener("click", () => insertTemplateWorkflow()); + dsgEl("wfDeleteBtn")?.addEventListener("click", () => deleteWorkflow()); + dsgEl("wfName")?.addEventListener("input", () => { + const wf = dsgWf(); + if (wf) { + wf.name = dsgEl("wfName").value; + dsgMarkDirty(); + } + }); + dsgEl("wfSelect")?.addEventListener("change", (e) => { + const val = (e.target as HTMLSelectElement).value; + if (val === "__new__") newWorkflow(); + else openWorkflow(val); + }); + + const toolsModal = dsgEl("wfToolsModal"); + dsgEl("wfToolsClose")?.addEventListener("click", () => + toolsModal.classList.remove("open") + ); + toolsModal?.addEventListener("click", (e) => { + if (e.target === toolsModal) toolsModal.classList.remove("open"); + }); + + dsgEl("wfDrawerToggle")?.addEventListener("click", () => + dsgEl("wfDrawer")?.classList.toggle("collapsed") + ); + + // Canvas pan (drag empty space) + zoom (pinch / ctrl-wheel) + scroll-pan. + const wrap = dsgEl("wfCanvasWrap"); + wrap?.addEventListener("mousedown", (e) => { + const t = e.target as HTMLElement; + if (t.closest(".wf-cell") || t.closest(".wf-edge-label-input")) return; + // Clicking empty canvas clears selection. + dsgSelectedCellId = null; + dsgSelectedEdgeId = null; + for (const el of dsgCellEls.values()) el.classList.remove("selected"); + redrawEdges(); + renderDrawer(); + e.preventDefault(); + const start = { x: e.clientX, y: e.clientY }; + const orig = { ...dsgView }; + const onMove = (me: MouseEvent): void => { + dsgView.x = orig.x + (me.clientX - start.x); + dsgView.y = orig.y + (me.clientY - start.y); + dsgApplyTransform(); + }; + const onUp = (): void => { + document.removeEventListener("mousemove", onMove); + document.removeEventListener("mouseup", onUp); + }; + document.addEventListener("mousemove", onMove); + document.addEventListener("mouseup", onUp); + }); + wrap?.addEventListener( + "wheel", + (e) => { + e.preventDefault(); + if (e.ctrlKey || e.metaKey) { + // Pinch / ctrl-wheel zoom around the cursor. + const before = dsgScreenToCanvas(e.clientX, e.clientY); + const factor = Math.exp(-e.deltaY * 0.01); + dsgView.scale = Math.min(2, Math.max(0.25, dsgView.scale * factor)); + const rect = wrap.getBoundingClientRect(); + dsgView.x = e.clientX - rect.left - before.x * dsgView.scale; + dsgView.y = e.clientY - rect.top - before.y * dsgView.scale; + } else { + dsgView.x -= e.deltaX; + dsgView.y -= e.deltaY; + } + dsgApplyTransform(); + }, + { passive: false } + ); + wrap?.addEventListener("dblclick", (e) => { + const t = e.target as HTMLElement; + if (t.closest(".wf-cell")) return; + const pos = dsgScreenToCanvas(e.clientX, e.clientY); + addCell({ x: pos.x - 120, y: pos.y - 60 }); + }); + + // Delete selected edge (not while typing in a field). + document.addEventListener("keydown", (e) => { + if (mason.currentView !== "designer") return; + const tag = (document.activeElement?.tagName || "").toLowerCase(); + if (tag === "input" || tag === "textarea" || tag === "select") return; + if ((e.key === "Delete" || e.key === "Backspace") && dsgSelectedEdgeId) { + e.preventDefault(); + dsgDeleteSelectedEdge(); + } + }); + + // Autosave every 10s while the designer is open (mirrors chat autosave). + setInterval(() => { + if (mason.currentView === "designer" && mason.workflowDirty && !dsgRunning() && dsgWf()) { + saveWorkflow(); + } + }, 10_000); +} diff --git a/src/history.ts b/src/history.ts index b54c4bf..f047dac 100644 --- a/src/history.ts +++ b/src/history.ts @@ -5,6 +5,7 @@ declare function selectModelByValue(value: string): void; declare function renderMessages(): void; declare function newChat(): void; declare function genId(): string; +declare function switchToChatsTab(): void; interface HistoryListItem { id: string; @@ -39,6 +40,9 @@ async function loadChat(id: string): Promise { | { id: string; title?: string; model?: string; messages: unknown[] } | null; if (!data) return; + // Loading a chat while the designer is open should land the user back in + // the chat pane, like newChat() does. + if (mason.currentView === "designer") switchToChatsTab(); mason.currentChatId = id; mason.history = data.messages; // Only restore the saved model if this workspace actually has it. diff --git a/src/main.ts b/src/main.ts index 55bcc6a..2015063 100644 --- a/src/main.ts +++ b/src/main.ts @@ -23,6 +23,7 @@ try { const MASON_HOME = path.join(os.homedir(), ".mason"); const HISTORY_DIR = path.join(MASON_HOME, "chat_history"); +const WORKFLOWS_DIR = path.join(MASON_HOME, "workflows"); const CONFIG_DIR = path.join(MASON_HOME, "config"); const BIN_DIR = path.join(MASON_HOME, "bin"); const WORKSPACES_FILE = path.join(CONFIG_DIR, "workspaces.json"); @@ -245,6 +246,60 @@ ipcMain.handle("history-delete", (_event: IpcMainInvokeEvent, id: string) => { if (fs.existsSync(filePath)) fs.unlinkSync(filePath); }); +// --- Workflow designer persistence (~/.mason/workflows/.json) --- + +function ensureWorkflowsDir(): void { + if (!fs.existsSync(WORKFLOWS_DIR)) fs.mkdirSync(WORKFLOWS_DIR); +} + +// Workflow ids come from genId() in the renderer, but never trust a +// renderer-supplied string as a path segment. +function workflowFilePath(id: string): string | null { + if (!/^[a-zA-Z0-9_-]{1,64}$/.test(id)) return null; + return path.join(WORKFLOWS_DIR, `${id}.json`); +} + +ipcMain.handle("workflow-list", () => { + ensureWorkflowsDir(); + const files = fs.readdirSync(WORKFLOWS_DIR).filter((f) => f.endsWith(".json")); + const out: Array<{ id: string; name: string; updatedAt: number }> = []; + for (const f of files) { + try { + const data = JSON.parse(fs.readFileSync(path.join(WORKFLOWS_DIR, f), "utf-8")); + out.push({ + id: f.replace(/\.json$/, ""), + name: data.name || "Untitled workflow", + updatedAt: data.updatedAt || 0, + }); + } catch (_) {} + } + return out.sort((a, b) => b.updatedAt - a.updatedAt); +}); + +ipcMain.handle("workflow-load", (_event: IpcMainInvokeEvent, id: string) => { + const filePath = workflowFilePath(id); + if (!filePath || !fs.existsSync(filePath)) return null; + return JSON.parse(fs.readFileSync(filePath, "utf-8")); +}); + +ipcMain.handle("workflow-save", (_event: IpcMainInvokeEvent, wf: any) => { + ensureWorkflowsDir(); + const filePath = workflowFilePath(String(wf?.id || "")); + if (!filePath) return { ok: false, error: "Invalid workflow id" }; + // Atomic write: temp file + rename, so a crash mid-write can't corrupt + // the saved workflow. + const tmp = `${filePath}.tmp`; + fs.writeFileSync(tmp, JSON.stringify(wf, null, 2)); + fs.renameSync(tmp, filePath); + return { ok: true }; +}); + +ipcMain.handle("workflow-delete", (_event: IpcMainInvokeEvent, id: string) => { + const filePath = workflowFilePath(id); + if (filePath && fs.existsSync(filePath)) fs.unlinkSync(filePath); + return { ok: true }; +}); + // --- OAuth via Databricks CLI --- interface TokenCacheEntry { diff --git a/src/preload.ts b/src/preload.ts index 180eb9e..f7becc9 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -70,6 +70,11 @@ const api: MasonApi = { settingsLoad: () => ipcRenderer.invoke("settings-load"), settingsSave: (partial) => ipcRenderer.invoke("settings-save", partial), + workflowList: () => ipcRenderer.invoke("workflow-list"), + workflowLoad: (id) => ipcRenderer.invoke("workflow-load", id), + workflowSave: (wf) => ipcRenderer.invoke("workflow-save", wf), + workflowDelete: (id) => ipcRenderer.invoke("workflow-delete", id), + skillsList: () => ipcRenderer.invoke("skills-list"), skillsLoad: (slug) => ipcRenderer.invoke("skills-load", slug), skillsSave: (params) => ipcRenderer.invoke("skills-save", params), diff --git a/src/shared/api.ts b/src/shared/api.ts index 11d5c41..4d1b39e 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -202,6 +202,12 @@ export interface MasonSkillsConfig { autoLoadSkills: boolean; } +export interface WorkflowSummary { + id: string; + name: string; + updatedAt: number; +} + export interface MasonApi { // Streaming chat chunk listener onChatChunk(callback: (chunk: ChatChunk) => void): void; @@ -288,6 +294,12 @@ export interface MasonApi { onDevkitInstallProgress(callback: (payload: DevkitInstallProgress) => void): void; removeDevkitInstallListeners(): void; + // Workflow designer + workflowList(): Promise; + workflowLoad(id: string): Promise; + workflowSave(wf: unknown): Promise<{ ok: boolean; error?: string }>; + workflowDelete(id: string): Promise<{ ok: boolean }>; + // Skills skillsList(): Promise; skillsLoad(slug: string): Promise<{ slug: string; name: string; description: string; body: string } | null>; diff --git a/src/state.ts b/src/state.ts index b72b7c6..42e643f 100644 --- a/src/state.ts +++ b/src/state.ts @@ -38,6 +38,12 @@ window.mason = { disabledSkills: new Set(), autoLoadSkills: true, + // Workflow designer + workflows: [], + currentWorkflow: null, + workflowRun: null, + workflowDirty: false, + // DOM refs (populated on init) el: {}, }; diff --git a/src/types/state.d.ts b/src/types/state.d.ts index 1668cf2..3327bcb 100644 --- a/src/types/state.d.ts +++ b/src/types/state.d.ts @@ -97,7 +97,13 @@ declare global { settings: MasonSettings; systemPrompt: string; - currentView: "chat" | "dashboards" | "dashboard-detail" | "settings" | "onboarding"; + currentView: + | "chat" + | "dashboards" + | "dashboard-detail" + | "settings" + | "onboarding" + | "designer"; dashboardsList: MasonDashboard[]; dashboardsLoading?: boolean; autoConnectDone: boolean; @@ -106,6 +112,12 @@ declare global { disabledSkills: Set; autoLoadSkills: boolean; + // Workflow designer + workflows: MasonWorkflowSummary[]; + currentWorkflow: MasonWorkflow | null; + workflowRun: WorkflowRunState | null; + workflowDirty: boolean; + defaultModel?: { value: string; label: string } | null; el: Record; diff --git a/src/types/workflow.d.ts b/src/types/workflow.d.ts new file mode 100644 index 0000000..a718f54 --- /dev/null +++ b/src/types/workflow.d.ts @@ -0,0 +1,68 @@ +// Ambient types for the Agentic Workflow Designer. +// See docs/specs/agentic-workflow-designer.md for semantics. + +declare global { + interface WorkflowCellConfig { + id: string; + name: string; + model: { value: string; label: string }; + enabledTools: string[]; + prompt: string; + position: { x: number; y: number }; + maxLoopIterations?: number; // feedback re-entry cap, default 5 + } + + type WorkflowEdgeKind = "flow" | "feedback"; + + interface WorkflowEdge { + id: string; + from: string; // cell id + to: string; // cell id + kind: WorkflowEdgeKind; + label?: string; // input header downstream / route name for gates + } + + interface MasonWorkflow { + id: string; + name: string; + version: 1; + cells: WorkflowCellConfig[]; + edges: WorkflowEdge[]; + createdAt: number; + updatedAt: number; + } + + interface MasonWorkflowSummary { + id: string; + name: string; + updatedAt: number; + } + + type CellRunStatus = "idle" | "queued" | "running" | "done" | "failed" | "skipped"; + + interface CellTranscriptEntry { + kind: "assistant" | "tool-call" | "tool-result" | "error" | "verdict" | "info"; + text: string; + } + + interface CellRunRecord { + status: CellRunStatus; + transcript: CellTranscriptEntry[]; + output: string; // final assistant text of the most recent run + verdict?: { route: string; notes: string }; + iterations: number; // feedback re-entries (1 = first run) + error?: string; + } + + interface WorkflowRunState { + workflowId: string; + running: boolean; + aborted: boolean; + cells: Record; + startedAt: number; + totalSteps: number; // cell runs consumed from the global budget + error?: string; + } +} + +export {}; diff --git a/src/workflow-engine.ts b/src/workflow-engine.ts new file mode 100644 index 0000000..7757559 --- /dev/null +++ b/src/workflow-engine.ts @@ -0,0 +1,638 @@ +// Workflow execution engine for the Agentic Workflow Designer. +// See docs/specs/agentic-workflow-designer.md (Section 6) for semantics. +// +// A workflow run is a sequence of cell runs; each cell run is one bounded +// headless agent loop (reusing resolveModelRouting / executeToolCore from +// agent-runner.ts); edges define order and context. Cells run one at a time — +// main.ts has a single in-flight chat controller, so sequential execution +// keeps Stop and chunk routing correct (spec 7.6 defers parallelism). + +// --- budgets (spec 6.4) --- +const WORKFLOW_GLOBAL_BUDGET = 25; // cell runs per workflow run +const WORKFLOW_INNER_BUDGET = 40; // agent-loop iterations within one cell run +const WORKFLOW_DEFAULT_LOOP_CAP = 5; // feedback re-entries per cell +const ROUTE_TOOL_NAME = "route_output"; + +interface EngineCallbacks { + onCellStatus(cellId: string, status: CellRunStatus): void; + onCellTranscript(cellId: string, entry: CellTranscriptEntry): void; + // Streamed text of the in-progress assistant turn (full text so far). + onStreamText(cellId: string, text: string): void; + askUser( + questions: Array<{ question: string; options: string[]; multiSelect?: boolean }> + ): Promise; +} + +// --- graph helpers --- + +function wfFlowEdges(wf: MasonWorkflow): WorkflowEdge[] { + return wf.edges.filter((e) => e.kind === "flow"); +} + +function wfCellById(wf: MasonWorkflow, id: string): WorkflowCellConfig | undefined { + return wf.cells.find((c) => c.id === id); +} + +// Route key shown to the model for an outgoing edge: explicit label first, +// target cell name fallback. +function wfRouteKey(wf: MasonWorkflow, edge: WorkflowEdge): string { + if (edge.label && edge.label.trim()) return edge.label.trim(); + return wfCellById(wf, edge.to)?.name || edge.to; +} + +// Input section header the downstream model sees for an inbound edge. +function wfInputLabel(wf: MasonWorkflow, edge: WorkflowEdge): string { + if (edge.label && edge.label.trim()) return edge.label.trim(); + return wfCellById(wf, edge.from)?.name || edge.from; +} + +function wfIsGate(wf: MasonWorkflow, cellId: string): boolean { + return wf.edges.some((e) => e.from === cellId && e.kind === "feedback"); +} + +// Flow-descendants of a cell (feedback edges don't propagate invalidation). +function wfFlowDescendants(wf: MasonWorkflow, startId: string): Set { + const out = new Set(); + const queue = [startId]; + while (queue.length > 0) { + const id = queue.shift()!; + for (const e of wfFlowEdges(wf)) { + if (e.from === id && !out.has(e.to)) { + out.add(e.to); + queue.push(e.to); + } + } + } + return out; +} + +// Validation per spec 4.3. Returns a list of human-readable errors; empty +// means runnable. +function validateWorkflow(wf: MasonWorkflow): string[] { + const errors: string[] = []; + if (wf.cells.length === 0) { + errors.push("Add at least one cell."); + return errors; + } + const ids = new Set(wf.cells.map((c) => c.id)); + for (const c of wf.cells) { + if (!c.model?.value) errors.push(`Cell "${c.name}" has no model selected.`); + } + for (const e of wf.edges) { + if (!ids.has(e.from) || !ids.has(e.to)) errors.push("An edge points at a deleted cell."); + if (e.from === e.to) errors.push("A cell cannot connect to itself."); + } + + // Flow-edge-only cycles are illegal (cycles must include a feedback edge). + // Kahn's algorithm: if we can't consume every cell, there's a flow cycle. + const indeg = new Map(); + for (const c of wf.cells) indeg.set(c.id, 0); + for (const e of wfFlowEdges(wf)) { + if (ids.has(e.to)) indeg.set(e.to, (indeg.get(e.to) || 0) + 1); + } + const queue = wf.cells.filter((c) => (indeg.get(c.id) || 0) === 0).map((c) => c.id); + let consumed = 0; + while (queue.length > 0) { + const id = queue.shift()!; + consumed += 1; + for (const e of wfFlowEdges(wf)) { + if (e.from !== id) continue; + const d = (indeg.get(e.to) || 0) - 1; + indeg.set(e.to, d); + if (d === 0) queue.push(e.to); + } + } + if (consumed < wf.cells.length) { + errors.push( + "Cells are connected in a loop of solid (flow) edges. Loops are only allowed through dashed feedback edges." + ); + } + + // Model availability in the current profile. + for (const c of wf.cells) { + if (!c.model?.value) continue; + if (!workflowModelAvailable(c.model.value)) { + errors.push( + `Cell "${c.name}" uses model "${c.model.label || c.model.value}", which isn't available in this workspace.` + ); + } + } + return errors; +} + +function workflowModelAvailable(value: string): boolean { + if (value.startsWith("custom:")) { + const id = value.replace("custom:", ""); + return mason.customEndpoints.some((e) => e.modelId === id); + } + return mason.discoveredModels.some((g) => g.models.some((m) => m.value === value)); +} + +// --- context assembly (spec 6.2) --- + +interface FeedbackPayload { + fromCell: string; + notes: string; + prevOutput: string; + iteration: number; +} + +function buildCellPreamble( + wf: MasonWorkflow, + cell: WorkflowCellConfig, + routes: Array<{ key: string; edge: WorkflowEdge }> | null +): string { + const lines: string[] = [ + `You are the cell "${cell.name}" in the agentic workflow "${wf.name}".`, + `Workflow cells run in sequence; your final message becomes the input of downstream cells.`, + ]; + const downstream = wfFlowEdges(wf) + .filter((e) => e.from === cell.id) + .map((e) => wfCellById(wf, e.to)?.name || e.to); + if (downstream.length > 0 && !routes) { + lines.push(`Your output will be passed to: ${downstream.join(", ")}.`); + } + if (routes) { + lines.push( + "", + `When your work is complete you MUST call the "${ROUTE_TOOL_NAME}" tool exactly once to decide where the workflow goes next. Available routes:` + ); + for (const r of routes) { + const target = wfCellById(wf, r.edge.to)?.name || r.edge.to; + const kind = r.edge.kind === "feedback" ? "send feedback back to" : "hand off to"; + lines.push(` - "${r.key}": ${kind} the "${target}" cell`); + } + lines.push(` - "end": the workflow is complete; provide a final summary in notes`); + lines.push(`Do not end your turn without calling ${ROUTE_TOOL_NAME}.`); + } + return lines.join("\n"); +} + +function buildCellMessages( + wf: MasonWorkflow, + cell: WorkflowCellConfig, + delivered: Map, + feedback: FeedbackPayload | null, + routes: Array<{ key: string; edge: WorkflowEdge }> | null +): any[] { + const messages: any[] = []; + if (cell.prompt.trim()) messages.push({ role: "system", content: cell.prompt.trim() }); + messages.push({ role: "system", content: buildCellPreamble(wf, cell, routes) }); + + const sections: string[] = []; + for (const e of wfFlowEdges(wf)) { + if (e.to !== cell.id) continue; + const payload = delivered.get(e.id); + if (payload === undefined) continue; + sections.push(`## Input from "${wfInputLabel(wf, e)}"\n\n${payload}`); + } + if (feedback) { + sections.push( + `## Feedback from "${feedback.fromCell}" (iteration ${feedback.iteration})\n\n${feedback.notes}` + ); + if (feedback.prevOutput) { + sections.push(`## Your previous output\n\n${feedback.prevOutput}`); + } + } + messages.push({ + role: "user", + content: sections.length > 0 ? sections.join("\n\n") : "Begin. Follow your instructions.", + }); + return messages; +} + +function buildRouteTool(routes: Array<{ key: string; edge: WorkflowEdge }>): ToolDef { + return { + type: "function", + function: { + name: ROUTE_TOOL_NAME, + description: + "REQUIRED final action: choose where this workflow goes next. Call exactly once, when your work in this cell is complete.", + parameters: { + type: "object", + properties: { + route: { + type: "string", + enum: [...routes.map((r) => r.key), "end"], + description: "The route to take next, or 'end' to complete the workflow.", + }, + notes: { + type: "string", + description: + "Feedback or instructions for the target cell — or the final summary if ending.", + }, + }, + required: ["route", "notes"], + }, + }, + }; +} + +// --- cell run (headless agent loop) --- + +interface CellRunOutcome { + outcome: "text" | "verdict" | "failed" | "aborted"; + finalText: string; + verdict?: { route: string; notes: string }; + error?: string; +} + +// Called when a gate proposes a verdict. Return null to accept, or an error +// string to reject (sent back as the tool result; the loop continues). +type VerdictGatekeeper = (verdict: { route: string; notes: string }) => string | null; + +async function runCellLoop( + wf: MasonWorkflow, + cell: WorkflowCellConfig, + initialMessages: any[], + routes: Array<{ key: string; edge: WorkflowEdge }> | null, + gatekeeper: VerdictGatekeeper | null, + runState: WorkflowRunState, + cb: EngineCallbacks +): Promise { + const transcript: any[] = [...initialMessages]; + + const allowlist = new Set(cell.enabledTools); + const toolDefs = getAllToolDefs(allowlist); + const missing = cell.enabledTools.filter( + (n) => !toolDefs.some((t) => t.function.name === n) + ); + if (missing.length > 0) { + console.warn(`[WORKFLOW] Cell "${cell.name}": tools unavailable, dropped: ${missing.join(", ")}`); + } + const toolsForApi: any[] = toolDefs.map(({ type, function: fn }) => ({ type, function: fn })); + if (routes) toolsForApi.push(buildRouteTool(routes)); + + let routeReminders = 0; + let budget = WORKFLOW_INNER_BUDGET; + + while (budget-- > 0) { + if (runState.aborted) return { outcome: "aborted", finalText: "" }; + + const token = await getAuthToken(); + const routing = resolveModelRouting(cell.model.value, toolsForApi.length > 0); + const canStream = routing.format !== "responses"; + + let streamedText = ""; + if (canStream) { + window.api.onChatChunk((chunk: any) => { + streamedText += chunk; + cb.onStreamText(cell.id, streamedText); + }); + } + + let result: any; + try { + result = await window.api.chat({ + token, + model: routing.model, + messages: transcript, + tools: toolsForApi.length > 0 ? toolsForApi : undefined, + gateway: routing.gateway, + format: routing.format, + stream: canStream, + }); + } catch (e) { + if (canStream) window.api.removeChatChunkListeners(); + if (runState.aborted) return { outcome: "aborted", finalText: streamedText }; + return { outcome: "failed", finalText: "", error: (e as Error).message }; + } + if (canStream) window.api.removeChatChunkListeners(); + if (runState.aborted) return { outcome: "aborted", finalText: streamedText }; + + if (result.type === "text") { + const content = (result.content || "").trim(); + if (!content) { + return { + outcome: "failed", + finalText: "", + error: + "Model returned an empty response — likely hit its token budget mid-thinking. Try a smaller prompt or a different model.", + }; + } + if (routes) { + // Gates must route. Remind twice, then fail (spec 6.3 fallback). + if (routeReminders < 2) { + routeReminders += 1; + transcript.push({ role: "assistant", content }); + cb.onCellTranscript(cell.id, { kind: "assistant", text: content }); + transcript.push({ + role: "user", + content: `You must now call the ${ROUTE_TOOL_NAME} tool to choose the next route. Do not reply with text.`, + }); + continue; + } + return { + outcome: "failed", + finalText: content, + error: `Cell "${cell.name}" never called ${ROUTE_TOOL_NAME}. Use a tool-capable model for cells with feedback edges.`, + }; + } + cb.onCellTranscript(cell.id, { kind: "assistant", text: content }); + return { outcome: "text", finalText: content }; + } + + if (result.type === "tool_calls") { + transcript.push({ + role: "assistant", + content: result.content || null, + tool_calls: result.tool_calls, + }); + if (result.content) { + cb.onCellTranscript(cell.id, { kind: "assistant", text: result.content }); + } + + for (const tc of result.tool_calls || []) { + const toolName = tc.function.name; + let args: Record = {}; + try { + args = JSON.parse(tc.function.arguments) as Record; + } catch (_) {} + + if (toolName === ROUTE_TOOL_NAME && routes) { + const verdict = { + route: String(args.route || ""), + notes: String(args.notes || ""), + }; + const rejection = gatekeeper ? gatekeeper(verdict) : null; + const known = + verdict.route === "end" || routes.some((r) => r.key === verdict.route); + if (!known) { + transcript.push({ + role: "tool", + tool_call_id: tc.id, + name: toolName, + content: `Error: "${verdict.route}" is not a valid route. Choose one of: ${[...routes.map((r) => r.key), "end"].join(", ")}.`, + }); + continue; + } + if (rejection) { + cb.onCellTranscript(cell.id, { kind: "info", text: rejection }); + transcript.push({ + role: "tool", + tool_call_id: tc.id, + name: toolName, + content: `Error: ${rejection}`, + }); + continue; + } + // Accepted — the verdict ends the cell run. The latest assistant + // text (preamble of this turn, or the previous turn's output) + // stands as the cell's output. + const lastText = + result.content || + [...transcript].reverse().find((m: any) => m.role === "assistant" && typeof m.content === "string" && m.content)?.content || + ""; + return { outcome: "verdict", finalText: lastText, verdict }; + } + + if (toolName === "ask_user") { + let questions: Array<{ question: string; options: string[]; multiSelect?: boolean }>; + if (Array.isArray(args.questions)) { + questions = args.questions as any[]; + } else if (typeof args.question === "string") { + questions = [ + { + question: args.question as string, + options: (args.options as string[]) || [], + multiSelect: Boolean(args.multiSelect), + }, + ]; + } else { + questions = []; + } + const answer = await cb.askUser(questions); + transcript.push({ + role: "tool", + tool_call_id: tc.id, + name: toolName, + content: answer, + }); + continue; + } + + cb.onCellTranscript(cell.id, { kind: "tool-call", text: `Calling tool: ${toolName}` }); + const r = await executeToolCore(toolName, args); + transcript.push({ + role: "tool", + tool_call_id: tc.id, + name: toolName, + content: r.content, + }); + cb.onCellTranscript(cell.id, { + kind: r.ok ? "tool-result" : "error", + text: r.ok ? `${toolName}: ${r.preview}` : `Tool error (${toolName}): ${r.preview}`, + }); + if (runState.aborted) return { outcome: "aborted", finalText: "" }; + } + continue; + } + + return { outcome: "failed", finalText: "", error: "Unexpected response type from gateway." }; + } + + return { + outcome: "failed", + finalText: "", + error: `Cell "${cell.name}" hit the ${WORKFLOW_INNER_BUDGET}-step agent-loop budget without finishing.`, + }; +} + +// --- workflow run (scheduler) --- + +async function runWorkflow(wf: MasonWorkflow, cb: EngineCallbacks): Promise { + const state: WorkflowRunState = { + workflowId: wf.id, + running: true, + aborted: false, + cells: {}, + startedAt: Date.now(), + totalSteps: 0, + }; + for (const c of wf.cells) { + state.cells[c.id] = { status: "idle", transcript: [], output: "", iterations: 0 }; + cb.onCellStatus(c.id, "idle"); + } + mason.workflowRun = state; + + // Payload delivered along each flow edge (edge id → text). + const delivered = new Map(); + const resolved = new Set(); // done or skipped + const pendingFeedback = new Map(); + + console.log(`[WORKFLOW] Run started: "${wf.name}" (${wf.cells.length} cells, ${wf.edges.length} edges)`); + + const inboundFlow = (cellId: string): WorkflowEdge[] => + wfFlowEdges(wf).filter((e) => e.to === cellId); + + // Stable order: topological-ish by canvas position (top-to-bottom, + // left-to-right) so execution order is visually predictable among ties. + const cellOrder = [...wf.cells].sort( + (a, b) => a.position.y - b.position.y || a.position.x - b.position.x + ); + + try { + while (!state.aborted) { + // Resolve skippable cells first: all inbound resolved, none delivered. + let movedSomething = true; + while (movedSomething) { + movedSomething = false; + for (const cell of cellOrder) { + if (resolved.has(cell.id)) continue; + const inbound = inboundFlow(cell.id); + if (inbound.length === 0) continue; + const allResolved = inbound.every((e) => resolved.has(e.from)); + const anyDelivered = inbound.some((e) => delivered.has(e.id)); + if (allResolved && !anyDelivered) { + resolved.add(cell.id); + state.cells[cell.id].status = "skipped"; + cb.onCellStatus(cell.id, "skipped"); + movedSomething = true; + } + } + } + + // Pick the next eligible cell. + const next = cellOrder.find((cell) => { + if (resolved.has(cell.id)) return false; + const inbound = inboundFlow(cell.id); + if (!inbound.every((e) => resolved.has(e.from))) return false; + return inbound.length === 0 || inbound.some((e) => delivered.has(e.id)); + }); + if (!next) break; // nothing left to run — workflow complete + + if (state.totalSteps >= WORKFLOW_GLOBAL_BUDGET) { + state.error = `Workflow hit the global budget of ${WORKFLOW_GLOBAL_BUDGET} cell runs. Check for runaway feedback loops.`; + break; + } + state.totalSteps += 1; + + const rec = state.cells[next.id]; + rec.status = "running"; + rec.iterations += 1; + rec.error = undefined; + rec.verdict = undefined; + rec.transcript = []; + cb.onCellStatus(next.id, "running"); + + const outgoing = wf.edges.filter((e) => e.from === next.id); + const isGate = outgoing.some((e) => e.kind === "feedback"); + // Route keys must be unique; disambiguate duplicates with a suffix. + let routes: Array<{ key: string; edge: WorkflowEdge }> | null = null; + if (isGate) { + const seen = new Set(); + routes = outgoing.map((edge) => { + let key = wfRouteKey(wf, edge); + while (seen.has(key) || key === "end") key = `${key}-2`; + seen.add(key); + return { key, edge }; + }); + } + + const feedback = pendingFeedback.get(next.id) || null; + pendingFeedback.delete(next.id); + const messages = buildCellMessages(wf, next, delivered, feedback, routes); + + console.log( + `[WORKFLOW] Cell "${next.name}" running (model ${next.model.value}, ${next.enabledTools.length} tools${isGate ? ", gate" : ""}${feedback ? `, iteration ${rec.iterations}` : ""})` + ); + + // Gatekeeper: reject feedback routes whose target is out of loop budget + // (spec 6.4 — force the gate to choose another route). + const gatekeeper: VerdictGatekeeper | null = routes + ? (verdict) => { + if (verdict.route === "end") return null; + const r = routes!.find((x) => x.key === verdict.route); + if (!r || r.edge.kind !== "feedback") return null; + const target = wfCellById(wf, r.edge.to)!; + const cap = target.maxLoopIterations || WORKFLOW_DEFAULT_LOOP_CAP; + if (state.cells[target.id].iterations >= cap) { + return `Loop budget exhausted for "${target.name}" (${cap} iterations). Choose a different route or "end".`; + } + return null; + } + : null; + + const out = await runCellLoop(wf, next, messages, routes, gatekeeper, state, cb); + + if (out.outcome === "aborted") { + rec.status = "failed"; + rec.error = "Stopped by user."; + cb.onCellStatus(next.id, "failed"); + break; + } + if (out.outcome === "failed") { + rec.status = "failed"; + rec.error = out.error; + cb.onCellTranscript(next.id, { kind: "error", text: out.error || "Cell failed." }); + cb.onCellStatus(next.id, "failed"); + state.error = out.error; + break; + } + + rec.output = out.finalText; + rec.status = "done"; + resolved.add(next.id); + cb.onCellStatus(next.id, "done"); + + if (out.outcome === "text") { + // Non-gate: deliver output on every outgoing flow edge (fan-out). + for (const e of outgoing) { + if (e.kind === "flow") delivered.set(e.id, out.finalText); + } + continue; + } + + // Gate verdict handling. + const verdict = out.verdict!; + rec.verdict = verdict; + cb.onCellTranscript(next.id, { + kind: "verdict", + text: `Route: ${verdict.route} — ${verdict.notes}`, + }); + console.log( + `[WORKFLOW] Cell "${next.name}" verdict: route="${verdict.route}" (iteration ${rec.iterations})` + ); + + if (verdict.route === "end") continue; // delivers nothing; downstream skips + + const chosen = routes!.find((r) => r.key === verdict.route)!; + if (chosen.edge.kind === "flow") { + const payload = out.finalText + ? `${out.finalText}\n\n## Notes from "${next.name}"\n\n${verdict.notes}` + : verdict.notes; + delivered.set(chosen.edge.id, payload); + continue; + } + + // Feedback route: re-open the target and everything downstream of it. + const target = wfCellById(wf, chosen.edge.to)!; + pendingFeedback.set(target.id, { + fromCell: next.name, + notes: verdict.notes, + prevOutput: state.cells[target.id].output, + iteration: state.cells[target.id].iterations + 1, + }); + const reopen = wfFlowDescendants(wf, target.id); + reopen.add(target.id); + for (const id of reopen) { + resolved.delete(id); + // Outputs from re-opened cells are stale — clear their deliveries. + for (const e of wfFlowEdges(wf)) { + if (e.from === id) delivered.delete(e.id); + } + if (state.cells[id].status !== "running") { + state.cells[id].status = "queued"; + cb.onCellStatus(id, "queued"); + } + } + } + } finally { + state.running = false; + const secs = Math.round((Date.now() - state.startedAt) / 1000); + console.log( + `[WORKFLOW] Run ${state.aborted ? "stopped" : state.error ? "failed" : "complete"}: ${state.totalSteps} cell runs, ${secs}s` + ); + } + return state; +} From dab9a1adcd67f538a4b98a681d94c5a3d00e2356 Mon Sep 17 00:00:00 2001 From: Grant Doyle Date: Thu, 11 Jun 2026 12:40:52 -0500 Subject: [PATCH 6/8] feat(designer): visible, configurable feedback-loop caps + convergence pressure Loops were already bounded (5 revisions per cell, 25 cell runs global, gatekeeper forces a terminal route on exhaustion) but invisibly so. Now: feedback-target cells show an editable revision cap (1-20) on the card; status pills show loop progress (running 3/5); gates see 'revision N of M' annotations on revised inputs plus per-route budget state and an explicit don't-chase-perfection instruction. Verified against a critic prompted to never be satisfied: forced to route end after the cap, 4 cell runs. Co-authored-by: Isaac --- css/app.css | 22 +++++++++++++++++++++ src/designer.ts | 44 +++++++++++++++++++++++++++++++++++++++++- src/workflow-engine.ts | 39 ++++++++++++++++++++++++++++--------- 3 files changed, 95 insertions(+), 10 deletions(-) diff --git a/css/app.css b/css/app.css index 7da9c59..02c4989 100644 --- a/css/app.css +++ b/css/app.css @@ -1421,3 +1421,25 @@ body.dark .wf-tools-row:hover { background: rgba(255,255,255,0.04); } .wf-tools-name { font-family: ui-monospace, Menlo, monospace; font-size: 0.74rem; } .wf-tools-desc { opacity: 0.45; font-size: 0.72rem; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + + /* Per-cell feedback loop cap */ + .wf-cell-loopcap { + display: flex; + align-items: center; + gap: 3px; + font-size: 0.74rem; + opacity: 0.75; + cursor: help; + } + .wf-cell-loopcap input { + width: 34px; + padding: 2px 4px; + border: 1px solid #ddd; + border-radius: 6px; + font-size: 0.72rem; + background: #fff; + color: #333; + outline: none; + text-align: center; + } + body.dark .wf-cell-loopcap input { background: #1d1d1d; border-color: #3a3a3a; color: #ccc; } diff --git a/src/designer.ts b/src/designer.ts index 785d6cc..629ae02 100644 --- a/src/designer.ts +++ b/src/designer.ts @@ -231,7 +231,15 @@ function dsgModelOptions(selected: string): string { function dsgStatusInfo(cellId: string): { label: string; cls: string } { const rec = mason.workflowRun?.cells[cellId]; const status: CellRunStatus = rec?.status || "idle"; - return { label: status, cls: `wf-status-${status}` }; + // Surface feedback-loop progress ("running 3/5") once a cell is on its + // second iteration so loops are visibly bounded. + let label: string = status; + if (rec && rec.iterations > 1) { + const cell = dsgWf()?.cells.find((c) => c.id === cellId); + const cap = cell?.maxLoopIterations || 5; + label = `${status} ${Math.min(rec.iterations, cap)}/${cap}`; + } + return { label, cls: `wf-status-${status}` }; } function renderCell(cell: WorkflowCellConfig): void { @@ -250,6 +258,10 @@ function renderCell(cell: WorkflowCellConfig): void {
+
@@ -271,6 +283,17 @@ function renderCell(cell: WorkflowCellConfig): void { modelEl2.value = cell.model.value; const toolsBtn = el.querySelector(".wf-cell-tools-btn") as HTMLButtonElement; toolsBtn.textContent = `⚒ ${cell.enabledTools.length} tool${cell.enabledTools.length === 1 ? "" : "s"}`; + // The loop cap only matters when something feeds back into this cell — + // hide the control otherwise to keep cards quiet. + const loopWrap = el.querySelector(".wf-cell-loopcap") as HTMLElement; + const isFeedbackTarget = !!dsgWf()?.edges.some( + (e) => e.to === cell.id && e.kind === "feedback" + ); + loopWrap.style.display = isFeedbackTarget ? "" : "none"; + const loopInput = loopWrap.querySelector("input") as HTMLInputElement; + if (document.activeElement !== loopInput) { + loopInput.value = String(cell.maxLoopIterations || 5); + } const promptEl = el.querySelector(".wf-cell-prompt") as HTMLTextAreaElement; if (document.activeElement !== promptEl) promptEl.value = cell.prompt; const statusEl = el.querySelector(".wf-cell-status") as HTMLElement; @@ -363,6 +386,18 @@ function dsgWireCell(el: HTMLElement, cellId: string): void { openCellToolsModal(cellId) ); + (el.querySelector(".wf-cell-loopcap input") as HTMLInputElement).addEventListener( + "change", + (e) => { + const cell = cellOf(); + if (!cell) return; + const v = parseInt((e.target as HTMLInputElement).value, 10); + cell.maxLoopIterations = Number.isFinite(v) ? Math.min(20, Math.max(1, v)) : 5; + (e.target as HTMLInputElement).value = String(cell.maxLoopIterations); + dsgMarkDirty(); + } + ); + // Edge creation from the output port. const outPort = el.querySelector(".wf-port-out") as HTMLElement; outPort.addEventListener("mousedown", (e) => { @@ -492,6 +527,9 @@ function dsgStartEdgeDrag(fromCellId: string, e: MouseEvent): void { wf.edges.push({ id: genId(), from: fromCellId, to, kind }); dsgMarkDirty(); redrawEdges(); + // A new feedback edge reveals the target's loop-cap control. + const toCell = wf.cells.find((c) => c.id === to); + if (toCell) renderCell(toCell); }; document.addEventListener("mousemove", onMove); document.addEventListener("mouseup", onUp); @@ -501,10 +539,14 @@ function dsgStartEdgeDrag(fromCellId: string, e: MouseEvent): void { function dsgDeleteSelectedEdge(): void { const wf = dsgWf(); if (!wf || !dsgSelectedEdgeId || dsgRunning()) return; + const removed = wf.edges.find((e) => e.id === dsgSelectedEdgeId); wf.edges = wf.edges.filter((e) => e.id !== dsgSelectedEdgeId); dsgSelectedEdgeId = null; dsgMarkDirty(); redrawEdges(); + // Removing a feedback edge may hide the target's loop-cap control. + const toCell = removed && wf.cells.find((c) => c.id === removed.to); + if (toCell) renderCell(toCell); } // Floating inline input for edge labels (window.prompt doesn't exist in diff --git a/src/workflow-engine.ts b/src/workflow-engine.ts index 7757559..fef38b7 100644 --- a/src/workflow-engine.ts +++ b/src/workflow-engine.ts @@ -140,7 +140,8 @@ interface FeedbackPayload { function buildCellPreamble( wf: MasonWorkflow, cell: WorkflowCellConfig, - routes: Array<{ key: string; edge: WorkflowEdge }> | null + routes: Array<{ key: string; edge: WorkflowEdge }> | null, + runState: WorkflowRunState | null ): string { const lines: string[] = [ `You are the cell "${cell.name}" in the agentic workflow "${wf.name}".`, @@ -158,11 +159,22 @@ function buildCellPreamble( `When your work is complete you MUST call the "${ROUTE_TOOL_NAME}" tool exactly once to decide where the workflow goes next. Available routes:` ); for (const r of routes) { - const target = wfCellById(wf, r.edge.to)?.name || r.edge.to; - const kind = r.edge.kind === "feedback" ? "send feedback back to" : "hand off to"; - lines.push(` - "${r.key}": ${kind} the "${target}" cell`); + const target = wfCellById(wf, r.edge.to); + const targetName = target?.name || r.edge.to; + if (r.edge.kind === "feedback" && target) { + const cap = target.maxLoopIterations || WORKFLOW_DEFAULT_LOOP_CAP; + const used = runState?.cells[target.id]?.iterations || 0; + lines.push( + ` - "${r.key}": send feedback back to the "${targetName}" cell (revision ${used} of ${cap} used — after ${cap} this route closes)` + ); + } else { + lines.push(` - "${r.key}": hand off to the "${targetName}" cell`); + } } lines.push(` - "end": the workflow is complete; provide a final summary in notes`); + lines.push( + `Feedback loops are bounded — do not chase perfection. Judge against a concrete bar: once the work meets it, move forward or end. Reserve feedback routes for specific, fixable problems, and make each piece of feedback materially different from the last.` + ); lines.push(`Do not end your turn without calling ${ROUTE_TOOL_NAME}.`); } return lines.join("\n"); @@ -173,22 +185,31 @@ function buildCellMessages( cell: WorkflowCellConfig, delivered: Map, feedback: FeedbackPayload | null, - routes: Array<{ key: string; edge: WorkflowEdge }> | null + routes: Array<{ key: string; edge: WorkflowEdge }> | null, + runState: WorkflowRunState | null ): any[] { const messages: any[] = []; if (cell.prompt.trim()) messages.push({ role: "system", content: cell.prompt.trim() }); - messages.push({ role: "system", content: buildCellPreamble(wf, cell, routes) }); + messages.push({ role: "system", content: buildCellPreamble(wf, cell, routes, runState) }); const sections: string[] = []; for (const e of wfFlowEdges(wf)) { if (e.to !== cell.id) continue; const payload = delivered.get(e.id); if (payload === undefined) continue; - sections.push(`## Input from "${wfInputLabel(wf, e)}"\n\n${payload}`); + // Annotate revised work with its revision count so gates feel the + // convergence pressure ("revision 4 of 5" reads very differently from a + // first draft). + const source = wfCellById(wf, e.from); + const srcIters = (source && runState?.cells[source.id]?.iterations) || 0; + const srcCap = source?.maxLoopIterations || WORKFLOW_DEFAULT_LOOP_CAP; + const revNote = srcIters > 1 ? ` (revision ${srcIters} of ${srcCap})` : ""; + sections.push(`## Input from "${wfInputLabel(wf, e)}"${revNote}\n\n${payload}`); } if (feedback) { + const cap = cell.maxLoopIterations || WORKFLOW_DEFAULT_LOOP_CAP; sections.push( - `## Feedback from "${feedback.fromCell}" (iteration ${feedback.iteration})\n\n${feedback.notes}` + `## Feedback from "${feedback.fromCell}" (revision ${feedback.iteration} of ${cap})\n\n${feedback.notes}` ); if (feedback.prevOutput) { sections.push(`## Your previous output\n\n${feedback.prevOutput}`); @@ -531,7 +552,7 @@ async function runWorkflow(wf: MasonWorkflow, cb: EngineCallbacks): Promise Date: Thu, 11 Jun 2026 12:51:19 -0500 Subject: [PATCH 7/8] docs: add Workflow Designer section + screenshot to README Co-authored-by: Isaac --- README.md | 8 ++++++++ docs/designer.png | Bin 0 -> 182903 bytes 2 files changed, 8 insertions(+) create mode 100644 docs/designer.png diff --git a/README.md b/README.md index 71a1d42..bddd271 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,14 @@ Mason is a desktop application that leverages Databricks Unity AI Gateway and Un Switching between Claude, GPT, Gemini, and Llama models in Mason

+## Agentic Workflow Designer + +Build multi-model agentic pipelines on a drag-and-drop canvas. Each **cell** picks a model, a subset of your connected tools, and a prompt; wire cells together with **flow** edges to pipe one cell's output into the next, or **feedback** edges to create bounded revision loops where a reviewer routes work back until it passes. Mix providers freely — a Fable cell can feed an Opus cell whose work a Sonnet cell validates — all through the same governed gateway. + +

+ Mason's Agentic Workflow Designer: cells wired into a multi-model pipeline with feedback loops +

+ ## Installation ### macOS (Apple Silicon) — one-line install diff --git a/docs/designer.png b/docs/designer.png new file mode 100644 index 0000000000000000000000000000000000000000..0768dca34f68a77394c5d3fd86a2c0a783144642 GIT binary patch literal 182903 zcmeFZby!txw?0Y1_70lkdQ@pcXxMppNa4H zetZAUZ@=IE|6Ipwu8ZlMPmVdCXWV0qd(1FZWf^P?G7JO+1Z+83$(INSsN4t$$W|a! z;L6j?tWpF7L@7%N2~{}>32Id*doxQLQv?L&7@uer1tc+Wo1gZVcmgT0#DH29eBzv; zS*YxQSV70{!u-PbQ{{GAA$4N|ogRS2l_HZ=| z;+~*9%jrVQczHTw<2l%4Yx5;j<_a{d5|Lp#{)#j14KZf=?X}g6;fAkZ_WYI4-98yJ zk#)rh?{`b>YD%zr)92rL+*F{b-<nuDp%2*c37inrt9tZ8e&%S&< zySXIqF@KjNrb5M{F|P3yS3a`4p785ubKSEbljBO>!K2&>DaX)m9vlX!Vr&+37t#B~ zZ$?}SulU+hMgsaS%-tw@`2(s}41)xxK2|+XH@JXjE3FxrU=?k5ITaR5MoMQ?JdXOs zza)OpdY;Zi?ea)z{9IqfyWRt}7T3vrpCZNJjcjw8m3xuW=cv3Pl0m~0%C|I z0t#@32>i%^A0YnNU<5SaF9GnA%s~44E-H5h^55skRu2Wm)FtHPfWPX-PNt@I&KCAA zZ@b5URuRW7Uun5$DJcpV+uJ@jG_f}_eGapAc&LH^g$V$swx%wI)G%8cJ7)oy5bd85 z0>JshWe6?xpCT^SLbO^+s?-wpPNvj6&)J`|(+XoyQ&U5oOw0scN=pB$I`BUsS_>B! z2LTAg-QE4UJJ)l2Cvyk~KR-W&ofE>z$p(~QbM~-vF@&+%In({s$lvWqnmQXhSvt5_ z+S^e-v}GNd5ej*j`hOF}UvmC)6%e#A1{Ct|hbD}1SsEyTfFOz>Cn@#{hPac8 zmh_}`FnBx5N%RdfZ5Bv;B_&&`uW7=B9W_qrB9dd`9S2V*4-c4~)CP+djHd=-7o#pk z2>bd>^|!k8P^;gZ@TsMpz5Vd+tnI~?hnu@wZeHGXYFgUoA4OXMYT44Ga~|udVti0RV| z5YWzlIDjtP7^KdU(<3YN7!ovoS`21Qi5^7-SUp3cT>y1RNMMVhNQ(Z$K|PF~;@a$^ z!E7v@o6eY^@(;!Su6~r<@L6WZ`2A(1=r59gn909q*x`btZO<)>8mTBE_cZ-KP2tb% zIx{k*sgY>^^AUZCLge)HxIado`F|4qz@O!azaJ`zPOn{Y0ORhXWhcd6VH>G9XiWxdxzDBv66}&|! z=elM{hF@qW&L8Y;PpcuHSE>h7TIR^DCB8C^*Fuq`rbG8WpLN$zQ!|{cad%Je5dV~r zFj;EaT_6#HGgfIo-_d(^dp=L@QBXPOIhHLGB|ZJ7#AWkkvY>kr+uQPv->%zcw^yg( zp3jD2&H7UKD?CrF$k`3voo)`dV_=d`eewB>hD$kPplv#UR%U;FwmZwwp+ftQ52{)| zBLfbH^CWbQeqt*h)zmZ8CGIKf)BT;4@M+AHkQpNpt*gPb_WEs&bx~PBS}j3Wo!css zw(uS8whq_uZ?|oO)*RQ0Pd`39)&6ag!v=X`lY0Jp5<}obnYn&nlkMjq;qy|tN6tOJ z-Kkn2=yGZqbHpb&H`$L5)! z)`VUhB=*Zx^>5NTl@IDH+QjpQ<~~452K2 zk4ZB1yk^H}+O|^H$1{Q^-hCiL%xy$XX0&)b;2&Se6!C*IC5 zPPWEvtlCGA6{CW!dP!5`YJ3_=CGthvM@G=nXMc`t?M9pJu{`bQ`6;Y$1%(2c^p~tp zlWi6BY6k?UU{Q~WTKjPOvCMmzzgUlznFrn!l&8MgKjZmsQ$%sz_x|jN^8Br!hZVP@ zS@~O(x1SmmAhjm;b(dx%fiTD;qU&*eKc5lq?*-48nH_3+Q}~C8A$l_n&Pe;15hzNX zCoj@0Y?y3U?qG^Al%y=^!n@iQ%h<@Tsb$K^Is=~(4dfj6$Mzl5%L%KDQ0Njf@W z=n8cQ+9V(0h-cRrm%}&#-0R%Bv#KAK_`-_SUQI%gL{?LUWS*G?DJ7bv|8F zwk|d!-Zx#>X3#h5pBE!Z)sD*2W_QaT%}?HnO>vEfr>6I7)&r}1lfj{jB@dil5zC^U zJMmF)Ps{HDc=83l{8LRUopG~PaC%1N6bpU7TUX>L^nKfsz2DnYYsvO|Lq^do=uP`2 zwyRwUeHs4d7^n$-se;psAyj)!+A&G&8epkh(tmz81pI^jrw9vdPcJmLniOAf5(ROBD)2viC_nsav=#omC;qr6^b^xIQZcr; zS`1GbRG3v(B8demWV9kfmdK+QJoOxjc7PToi8tVq@y5I}mVx;4O`W$#_><(zr^2@< z>iOTlJ6;kZVbC=-_zyPQ-ySsM^4Lz5H*DtT*_QS4*rh}2F0#ZXlM;~)`!Jy@YyIi_ zM@%Nfi&@@amN7c8P*Q#~<{X{2?XQIEa5UMks%97PyQ}S(L&9Ec3T6qi5U)8!?>r6j4@`<>5a2EV_%ZbKs#_O9Xz zfCUDhuQc!xu=r*<9(|0zJ)7=N^S!b(hmi`nSk(H{bEe*f|C8uwochVrp zV-{Zu(=Z}&%}BBrU6j5ThF-vW+sz2X`Jk9}|Bt#Wx(wxIDNa1fqPorG%jRa9Tof7| z`2e++!>?8Cj2gc@KXM?#M)1^3R1GvF+@7BpvjvM>F5tQ z!rs@*8;OX^;Q@qFj(`ZKzbJoi@MOZNn{78vMYv>hIIo8Px!atF`EguuNO#N1{9OK0 zD+zgUZ8K(|!^_26b~pBZ)bz)!8dc~G=rpY}U@&*m2stvkhu!bZ+(ilgC|nJ8Tp8|0 zSn2g%%`~|^+)zdM(xjaJEz>`0Bz`$+hCpdR_f8yzQ2p%NH73XU1kpBoAJUeF_vA;8 z#Cc`T{LQTAiQ$Nofqy8sxPdpws2ieNIwa3Kk*;gh^`LUk&2O6v+^?y1hc73G9=nZX zTwJz7lXyP71ASFQFh-!n(X%>idQ0a^y4*k$@?=Rf@07$C2{jVX8o#~Ku^%gW^F6Jg zfGzl+%7NazXYb^7wm0}{;2R!8CqveT|5ZItx$m977&0A3fEe=PM6yk3*y;ZK{H^{k ztJk+TU?oy+*$yZ<*f8sTjuYvo!auL5Xi<68$fZD>idBEw;&1R7ffEp8qi%j+fdmnl zTH_O>f6)#fuB1ZeT)X6{RenZ7?r-PtR;uKx`;!6#ft?xX!cl_I4p`A zD`PZi7wS}(syW`Q%(qjx>(n0lOMY#A=dXqui0Xldu~mCM#|0NS$fbsk#xT5zOH4fJHp7@G+^)jJ= z>seCBvMB591~aSd(1j}gihKVF$Js;y$xb)Ju#PXeyLSIaQkRTWffHs{B~Fn<|2j$P zc2Im`!gPZevU7oktv*GQ3>#)d4a}$9Uge@D_q_*C5*F0vz(U|x#Xnj+han|FjYRecj{*0OCupPQD-f&l3B$B%`Sw z`i(j+cnd7?f9x8#C8|LfEk>8Gl8yoSho|s&o0npMNpi#2|0Cx=HV9Z6|NkKW^V9kN znk4;~3ZDB!G$ke^yz%g;#wO$Ln5wkbPj?#q@%q84OBHY}&C1HUI(=(Fio~i}WE1UJ zOxUGh-g%BV9NUZc|~k&ESTk& z+&^lK2R{O}8WO+xKoYlQtS$i0d3>%;JW=yf-M*yy-CZhQ-X_oAw556N@q3+3n%mZ% zj^(Swbu_h8`PmfLUH#B?8I%g6*ZlIX!8l6DjQOH@C`W;0+Pb*5KqiWEaa4{CqEj`{ z_L0JRyP)hNh1-N-|mecHeeUP0@pWdyuPDVe^^m>7vx&&`WLY>7fwSyZ_W9HBzA{bsm&^4}Pp1 zf`y9#=fr~|`d&}ZPo2;E?cZG&cg8VI1M1D1Riof%Gp@*t+q5lj<>s)8h^rnLuor$| zKj9m|5!j$6=P(MI1*Bjnxcy*ot>+x>R?bfWGBcy{KJa(lpPL2DQ`u>t`Yp`~Iqk#9 zM?x+;?|8x{Oo5y0Ma@?m(z-DCGQ-LzRzjGVK|3LI9r9q z!kfUxC7|VsYUlO0<3+l*l5DG2Kb__@@x3r^!y*a53dCIR z3!zDVVf{()xhWl#RF5(ULKi>jwO?!g)m*=;eBSqZvfOfb2QXXQSIxDxmPTn(G`jz@ z$Oh3@0C*6s+VM>-d&{~8K+uBr@2(zO6F;wBA(%ExwoTz!dQYPFGf~g8GwFEYWLU+W zg=f-)&}$l@tc$gH0+6n4zsNlt zn-a=*4=~^8OcF@2 zaKJL`HqUf_kF-USuScl*3Q9Beu^wA`pA5?Efyr&lV`_=PS>9TlNBX5EVTLVy06>}L z%xuM5TWHW~Lc^uNG5|AQP=mRd$ALF`^N!10dLlf8U-HWjiDJFl8R08}f5Iw%u7|2n z=V6gC7vsk-9 z;Da1fN!oD)cMQ&FoTHM{5m(QmX(UoeKy#$bm7jUV2$B(kF;RR+M@Rdtk+P8~@$BJ; z!CaK=GSclIDN-|$=n#)nFT!RxEDYX>CK=oS(&WNIQ-K~z-g;JYTr@eO;y#%|4Q7Vg zu=5%AG(?M$Q{QtKxA8TDj_vTsFjYR*`CJRw-lU)Ax#{?Hn_DwurfiC#tu$ectrmGrmKXoU@KHq^j%$?aE;IP*;uu+x%tIo@c(S~ zTVNycn+|CC-JYf{eTlheZpL4;Oky_-Q0LC}J}kXlORj01tM#hUCn`~V8ELruN!#u_ zm4ClIqY@YhD`BV8`G~O;g$=qlcL@thSpYzBQ z#t+89!ce@5q(R)r2=Zy7LvW~FbU5hJ}EYu$KXw09+bS?VHf@~}JUI*XXsaTWF2kS8JFrWJ#4#OqrOlGbDg z&C}l=sz-fRN--dYS!&xVIhS@9_+SQnCkk|@nzob47q|iE&v{FhbtT%`GR@jD3~#5~ zpW12|D?6E&L}EXi^){kX(w6$;>pgles!YnA1WJjyh^P?Rhab;u_%w;?x{kiCIPMdi z)^(qK?Wd|paG(kiq^UD+byac+BtP3ju;C~ z=Ob4)NMXTDv)6}>eF*d<-YDt&od%2zm@z_LfMfCknr4Jes!I({V2b^a|-H@8&&01!oR2{u{co7smfekZgpzOX|3r{RYMaGXI z?VU{NJHEgUZ<7~lezf(IBy{?X{zn8yTdR39E9Fn_KyO@0%DZZa}^!$NpA&( zK$udXr$d|`eAA!115}y9FfNBtlde$!pTsxyhD`6gCxBJ_RnQzv!;GB9>sv>3XY3%h z7#uxUKXI);W?!`!%;R-t!yAR#Zd~dB+58sR&#LaXh^(>(jRC;!3JKzsc#+SQ*;RlE z^o0qix40r}ssanKNGwe)+1h}fo~d6OIv*n~uu}MA>%+d6`_T;7UODilgMO0zW6ARvS3Hb8LvKx^u z82iA6rhGig_k_+|)BuOz&RDUDuqotSlR9!B(R!rM#F`9(vvTdhJ0b#LJc1 z&D`YQD=F4+4sc`ph7r*b@ZuMHAyV=?edTg;fQswlf58wlBvDGb4qP_)*?8R&!jWwg zHbV0@ZL;*E1JO?@OKN;X{0@Dj6U388gW}xM*#ta6ZQJeHyTz`6?uwdfA&$5oe{{Id-+K?X%3i zTy94^J;A5GN+_sl`Q9;GQi{r2O@*UoO z;zzagaQw*F`(2{^X6RQDykG_uhb-x(n+`6F!7x7d;nm?q+Eb7_!J<$^vH(H9z}H^4{)pVIc2C%XM=y1pMSsAe&uj`QNm@dUVQy1` ztrb7)tnl~h$0}#56{Bgn+`<*{+vCDIr5Bf+8F-x-^k+?1!D{;{~Ci&31qBe z)Jk)WdZ=;NBthiQyz7|tlIUoh$h&k{Orbd=zWn-1q+A^wuZ>Z!o$9`L`mZJpMv(5b z3x|<$$*NF%;WYbM%kOG%uNa?;CG}ZLBLRTR8K&q1=0i*^gb_7lD0oq^72%>=C@{uV zvgNnoOjwEy2Ql6KB_ojnj7z_k7!jCmUJ5Iy`nA*G1nSrbc9tjTeDkXtY1oig?;{38S4SgAl^-+1ViRD2nK z$PM2@*k2MmqWcq@God-}edy;@M1t@Th{8A_ul#T)_V0u zy87ymu-6pv^I?gHBKRQsh(lv&xG8CDN&MFcWk(uh zL3~g}XWehc;?6fHnaA{q{wRDaR?~9jg)8z8(l7Mz3AeSG3Ofo*4X^miEM8%Kh@PGH z7(=Qv;3Wv~rd$gXR{Xqavr~v@LQb z+EA03(J;usWx3aR@0bKYNoZW!4ZK_Xj ze`e(@mnXv}2?&1yPPY2*^u2FI%zD7dy!LuNls(nM#rh)CM&WEWx2NO9`srOw0My-I z^Ph4Z*Hxbs$~(*Sfu9U1*u9*82=bgw+uM8XRp)W|y{ldCDV!c>}5vEm=l?}O>8Ml48y&@=65w`i&Y_{!Rm7C_d#WJ4foK6Y&wJ981 z@Rj;36AMoxH0;tmn3TSlh+I$hlLgtNa+-dq=}!e<(V$y*m+MT;{mr@v@0&B*%Gt3E zMljKPoF8oHL?zgbSD9)n`fTB+E|L82dpz?x`QI3$e%wJ=R>F0lsill)IBW5&lO{ zFyte^%x-swQGOEuE?Fh=#U?O6NC!sMUU#t+Ue1coVUh8qpD3^mFcgi(b(|x20RU?$ zLay(7Y3h^q5QCzT2p*}AE(ARKr_-@-E`X73GX@B)r#Qz>vMrY11KK0i$u`-x!tuI5 ztE6eK{@!<)1X<~+G}!N;a^813K^GOfO#gOElgG^_#gL`WPRI4_i{VEfGJ6y%4F*C@@q|uC)YW+`9_$FW*2kKBtILKV z6rc98YHn2f`ja^5tmb&?-W{2VLkbwL4eLHpn&wK9nY0IS0gNwFi$9H~{lc)8K z<_AQpn3_e+*q^Pg1W%XVP~A2T%23%3=PH#6olP{?N)C%S>_3cc_{b@_Gn7#=X(7#- zm6dfjD?GrhRQe%K1%y$;S7XHjw|$RRe(j#GChAw5j%oK}>t#(9a=^lbQ_O!!Vvzy1 zVddaDPdmb1zq$2|(Ljd2$vYHeclHkN<6d63)dbBcpyJ-1_F-2{xA)Sh?(_o~rKUn> zpI87y`3ne2cfU7UeM9Ectmn0Biq&U>z372JK_bww_5q8mm*LwjQ2r%t14u}k@Oa>M znd*LGwK4p;{ILwBM+=K!6R8)b6D(EV%!HFLLwY-i)K==*p~dvg9owk_1;3q~T$msV zgeszLm5?Pq&SkLT{czh4)=gUehX9wFjL^|iv&D1$iUSUBVRmfoo#r`E%x3a2mGq*T zx8QDhTqLE?lrrC95b7k)Ycstsi%p=w1CKVrZ1?>+>?7H8Drpiu&&{0E$)R9imvYEU zLMP|;CIy@`Z_|0kkb<*i#T*sLw;xs6#d>Xy>98gAYv3N~s0EP+_C@IdLi?$xU>GAfOTcAgAOl-UVf^JbU%huI zOXN>QFDrS=WDAew(PU#O&hQ%*GWr;JmGY*ukG^#BZNJ~$uo2~pCz$puL27+qYSpBB zU@f_9d!FrV`YtD=3aFb+y(6JKGw)B^y|37SbSXxRryK(=of!_V1%EU$F!$1ErWEFc zP5)6tHJm11s=z{$tcy zq4Swx4`zd%)23hV&I$A~7rkoc=SCp6(0(2zp?U6K`qoAF+xqub*SnSJUYSv@Iz{h0 z%#*b?xh%*o*{b)b3@yTN6m?%JmzTzB`c2m|{y;EqV;2)yP9 zlwR-oyqq@ZF@Dy+G1>B+JsBgv6wrbzIEeQ~0o|mylOTu)d?m9-TGiKJ=b}@^cZn!5 zTRO57h=o_%dUFg#l88&Ie)vk@DKH{?o2qmO|=`9+4L`3cuWTn+7%*oyiGYDas~JdB!t3Dq$D~_1uOFU2DPE8+q8a#5b zXFRtHi*{f)2He(cnb^1#0wXL8w;XC?mz-JU9>DUQWhCbHL6+iVwBx~<1;8(y7k?&%Eqf+xe^05C2R@;PWk+Y`})th9%FJr&pfx_b~t zG**)8HWnM8hDSqBaVk04-+kT7JFg>hb+DP2?sjSxUtEuB(LA>uLgG#IL)eAX(qMIU zIZDKBQQ1IBAJHqRGB^d$vqd}QnthO4gf#0D)0;fp#^F!_hd%!K^>n{FL8dx51b!es zlGXNu*Od_x;dnw)u5$HqiV{QWZdgO){>C}cy#Nl-6a*7VTo)zgn~)CyN8R?>sSnzE zw6V1M2gyRw09G{@JHR;FJPW9V+p+_oH zFMDkzXSl*?%Gfgka6YzhRwdM=j_f;~=#zPa?XbE^P_pTLq%Wku8kZnvW>%F)+ES%<@u5 z-N&WT`FzvsA~npLF!o|X>+MS-WEiDYibPP5(U8eN5?x$Gx2WTEZ7<`O<`kmJX9Bg_ zxp1y207^=q>O+{D9~X4*Eit8tf8{DnqVMe(M>uM%m=h&K6Y2lynysG)+OOl($#DAG ze^`zw_0>HUZq$LZ)NL@9PN-|$!PuhCOA##nh!qn_mRr!h+5%x6PUG-D?Kr7-Y%}! zqdE%VLE{>8*7INqg6twc;&g1O9ZI{!iwDDYF3$YTk)~mTEAY&W+f|64B{C|OgX8UO zAYx`e-aQo~{AZrPI{G*smKu044gRx7pg8_;TYHH_Dly#}DUjUf(XQuUmXxwbBZp9) z-|e>k-uqOiOy8>I>Ysu@e7F2gWTnELDr-U6yr2L*j<?Yk`?q;71oxhym6ZVF`)6wCeVsHjnYJoWR2lhGzi*U0xX%u3YU6GadN0?G?N@FcV#OqFU*_V9-vj`7;Y)vR;+IVi3rZVl9 zn>pJg?nO$qW;AbnpibnsgdgPg=p~lxZ6YSZ8M(x1euR8-wnnL^KokVwkoo<{kCf zr;J}Z45C6(?H@rhwId?@W;7JV`~rD#eX!4e&)=-^7Ey^0b{QZ9t>ZumH`Ff-HZpA4 zOMh_NZS4N;hnlB#49z4MeZPT@VX)*f$qWwAqVdw{qw`@czJ# z5adqRbme4=SC+E=O;)&MD@DQk36akqG@nMW@FpD58m^sbyp)Q!9GvbIW{%;4J|j~# zo~2+MjnMyQ#`@NaSNa2>oLXujhm=0^KIhf$wrR&5$^N9E{n-c4wb$7CWsqw8SCzyT zZ2lKVnu8KtU-J>V@@&z)Z?1pGOxkK=PzdRxXUrJ2FhW@2LF^b5i;tovy7W}}mdHMG z#>P~@LofQ`FP0*3Okc5j9RB>c6o%$ObP8}oO1gtX4qp+~L@?50fgpNtqKgBDIvb_i z*tA>FcQG_SfHfn4$#;2(^jKp^6*6>W7{mjG#hf;ElM@scfnD(8gVRVMaCpWgw1bdA zBwKH>Nr4K&2_MN&6gS+e1@pB%IFS;WOxtAxF4%>@DKFJzU-H_e!HNuCHD)A)Iy8sB zHj^gI=vZe+B+>umQ-v6h8pLp5sA;hK2RWyGXI+mx{y>7r(NWukT+BFyK8`R4)!|~U z2w-`w_m(*DCi5Oa~dX)9IL)sJm10 zeTMd8`h38R3_=ROBczkmG8#=Du21J8zwN8bC6C>6jDY(ys#om*XH zlGlDqO-3Gqkg;)_7LQlmTS{!5N~3kRgw^8u^vhynm+# zLay+!B*SJz`1%bq(@*BuVq8Ia*5RgmOu{3S>q#DLaGJEcMlXYxJo~ka)7I*%IUgNl zN_tP5Z_#iCuS#A;$SG0eDUIQh8J!&vnw`&*S}58_>IG#Kmw6%Y_llB|>L~h8i|dk? zGa)ad4%O+-+7INWT+AX!7-+LXVHJI%^{ksbVm4B)3vny*YPW zzVkh}iUzDuV7Y6Qoi%j(O&b*Kdh7$S=GgDM;{R64dTsQLgCSw zCrts};Y?kw_M=jZYmbMRhvcHNhN9Td!{4g#iRw*ahwxX+A3YIuD-uuKf|A)FHzj>)0{w!J zffSNHCfBBo_gSr&7>4jiAjs-_5+Ibf=BfqL>BOQ)7mzP~^;zH79u{RC2QpV?2xoEp>hi_*R6mFQ;)TJqkQ%UJf{I}?u4*2j!rOiUrMaP5B~c)KpfG{nUz zm3+5;8M%(H=X5!sc~lyXpLWfJthUh)BbMA$(qBr?W9Ea*mSe(+HmEGMnJa5UXr*oE zabqYY*vEhDFKZ>T1Q?~5%VY7~{m!$w^K1{Ke#T6oDxb|RiVu+J9wCim_oIyvF3VQk zE#7ijlF|tcja(X$_xH~6b=7`3?&A_(x|Tj?y2#Rn9U>E0SQ}=}9(scWQRwiO=9y;F zU+7QHQ60{7-lpEgnadWD=-dAQDZtsAV--{5#KR$MZvlOQMCTbk=G?u^x7+i6@{P@a-& zHQ(GvvPP`BfZ&0Tp8H;(ceDK=ML*J~LF3C6rwts*k7{fi7PyINRZ5n)FvM zCR@`6R9y1{Coa)2op%$~Y21=bqUWGS+e#>4LzEY!fB4}y>^g%;@s;Yy#l#XA4PH99 zPgNvgqU-ZT^5c59@&!`-5D3yYV*w8sDLcMoJPJSJ!~e*P?Py5vBAG>y-;-iZM8!`3vPUnStc8hP8D)L2q=hpyBV zNR*AGR?hWunB2-+MD%SD1>^(*8pjzY{(vnVctG72E=>5kxxapas|&@SjS@2j7b*ZY zTzZe+>TQIM8yJDpibt-|(>4`i8-wvp)>H}RcZEqQsy1?opJXWURfRl%PPnd$x9Vzp zjvVGqdB>}v$E<)iAM$hOI9zAV?`t`|=Eui?CZdWzX4W7@k6b4rbmCef4-77r;eN0A z`x-yK46FrqjfjcI3o2JD+U^jL0iNQ;c+YeFh1 z0z;{2Izq)!gMy}06yj<0p_K>F3iAQ9_v;?VxBSSYH(Usmq<6ypgGi5Fj6c#jz*@bl za#(EgR9VORZWPxlyW)Kqk@&5l9kX8s)c*!50c%WZ%m7sljhEuiVJsp_3)>dlb>wx9 z=x;^%hp*F%$Oc$#U^t-UXc5SPPFHOAxN3 z=YUL4APzU2PJ=3;l+T^vN4cJJ=#QjGRXGo0dqPdHjF|OR(~N8kCHWwupbzq}4AIsk z5|*E>;mD?sg7GE@2}0EhNBl{ArSmv7G2|u27JvC?Mn`LG5@C@RTJ~h@^rScbiddp# zzzLS3(m;|b*8C7n%z$d>2^yD77u9Y*4)EZ`!?J3*Ye`01#>3V0eZ}O1D{pePF3FMa&1GY*A0fXo8t+J*V*aDMoQ7y_F8iG`6-&t$#b=(m`?ZzTNUjt@X#*L(D7M}g z8k5!gc=?nP%(%zU8M-;=Rv<|F4Z6*q4>MKCp`wXM!F?>F-FiX%Cd7es%9GTXb#cxJ zJ@d3`?m(2z1rLK)T!COXmEB}!|2ywo7&v}|wHm?!YdlXVIqL7Q74xb*?km7NC+bgH zWIb3lDbq)t^BzXZmK->rzSpI|%F$c)dGlH~@rS}J$W_}tV?KXzze~VgA^%NW-QpO} zeY;OsBV!Oj7TduyaV@C`l5(#i(x61!EB7h+l^g9D!b7|)5%U0J6go~+@2SU&cqUiD2mD5wa!(5<_dwAeuXbee~M>H9j+wWh0T;?DN5RA*}BS z#Uf&u&9Pp^R}~GixhW^Sw&;c8 z8#H>-8;TyJnG&;H>In`-DtUb&ski$D8B{L1G!~S79N3U331{2yGmxD15 zNsx2Hh9s1aJw7%qW(npt@X;Xk!@xXD<;$zH^Yz(E*8A=8I40bbavzac3gHuc;X)C+ z0?%viJ6@;xgF{(DR!F`5im^$M~r|8=Py+~*f7z>l;&P_cJPXRhp3-r2Et*n$P&`n zG*35)CZ^VVOO*5CmaoaToOBOY2V#>qtLt+*YUjo1BXyPleqF1HzS`pTZoN;2%4cMD z9DO}FmP`+HD}-Tp%Dvy3U30p$^K4ZCI;X zF|cnOlrtZYcaR%qZtmT=-U%nxQ*qv)v~Jv7g9Y?J6c~^3JNkaG zVWVK6dEtSm{#;^Jp40sbMY4aJu!xH&OoH=U9AY|r8n%pn$?32fh${@DqW!)akiqUB zRrbKW(npU^d_?yd?FGjW1c%6I`%>$dV{F?@glm%46my{3h{s|MNe+cHrk-k3c8L#} zbYod$Vs%F%$57KNOn2<^9%fru)T1p z)Woj`9`Y{pYq@~YulMq>A*=wG4$*=H4z0{#f9qed(HxTVl6Xt};>D7M`42oy3R8t= za=+#?cJZykLVh0n>I=1_j8+Xcf`5f%7KgrqwF#o_piOIy8x(l{I^n_Mgyk(X23Mlr zQhvCAjT^x|nbh~zRvzic%fyUZaJJhkPr*#hmC3H6JC6HbgqXi_E&_bcf^d7dK+te$ zO2=u3LXbN})J#3YojRJINYt3yq_Xy*q5IIAV9Bj1yYa$U5&Ag$dVF|9t?=`opY)V> zezzP?I2f171fVaX^Z)YcVCTNdGb0)A#Wo$+H9hMLX-pxDLi=*<=4vT|E8fb!-sWD` zi!(iM)1y_^DA9Z8lsy?J#@ySC6jiUUZ(LzeH~CG75Te4oy2UGkacd1v0`<3f73wY* z7h2rD=zm{MSX9$d=WUBKm$HbkoG>CnP{y^~`FY{x>__)J(DS$r`pV$W(tAm%D1h#lJ%6={C9N~JMQ-!-8 z-%~P_z@zaw*$R2k?%U>570$~8ritA4+L)fzptOBD9m8FE)I-ZC3R7^n3B?OkiQ-s} zYb4m7%7Uo;qM$#5CNyeog|{8s{=nMe>I``Ak_x%{n0s}|9;|iiVPY{=uoKmkfX$Yj zBrnPTIKQvDhbc%t`~jjxAEU4vai2c#JeHw750{mN*1X8cH_+xbM)mk=)nlMf%AN6t z@g$2BL=I9}N{xAMBb^C5YSE zPqjr#N&=nU$~>yT@s}(vanX+|RIgcyie-*kamG0VNvGzR^vJFn^c2U?#OB*F4OYbR z*4$M7{Vf32)BdxoJ<=ep4=)yg?BPTk^XZ`-g4N-n)xVkDlX3tdW(R8j^cTnk#s8D< zXF6f_be+f2-x&q`q>4CIYn?(wvmm)aCyf*@bc3Nr;>Vq?jm`d-ujpx`L5M{q9S3g) z&z1!gTMMCY^*+Nt3NVhW6A_`(q#_|p)nPtak}ax8m7JC@8WEY^-d-mT8`haNajqz{ zwX_I3vfOlH_WO$iv_p?~Z}~!SJLly?rW_Jtagxn@iA_8}x}?g~YLA{4eFHe;9S2k2 zV6@S*C{=A=oUU=kBk*Wj26|mHKgKM+%o^iYXnG~h<|UxX_Y)vHF z4&?k>a3O^~RsD<>D94XpEiJ+bu-IfFh-tl_SD+dNFh5DCAHWmXTKm;8x{XhH!VeHW zjz{gT+-oR$^uDb)5)@>lB^egF6tK-P7gW7jlu8u0$@t?&l3Eab zfQsRVXe&aONAHoBjYe zUqAlle91oK;6v?tn4w&%pRDLjg?aEt`IA8~DQV6$vQ^Vn9*2Pjv)9f5tE4E(Al8~x zAo=}E!EjLx{DoE`V^oY+P~NzN($Z1`>Jxbq-w%UDM>mR}GKSU#5sMmsRx+lMc7nN@ zZonMf4IuH?i-V$Coi;Uj&4FQN-_ZJ+nc!_- z+XDdFDf4;9{A{()YXq!ryJN*Zlo=I>|cwlk^Ag5c|czZUpFDqpxnKbiH!k)CHwuU>U;9 z3INKl(qd?h@bnZw`o(bIti8L`n6JX-i9~PV;puTX4)fvu`?VlJa>OQMCWVnw7eqe5 zjbn5aJl35O56lhlrIYJNQPaWswQhw?*2&^W0T6*C{roC_}<>b zsLYO;R$Ne#n}aiYbxDj86XH>i$%tUy7a`fK_{&$!Rr3Gf$Xm!#?|of}Xi{VV?l*4$ zU*Zt8#&1CAT!GutM0~K>+Zd=|uKdXIUpZ+pD5^&agH&=-_ka1X{`K{c3|sV;R3iBA z-&3vScGE^%8^8t>MnH`!G+qeI}{NE=|VWK*Ls3p$m5Sj;SD-M!Q@g(uFfMCau z_y+~?4~MNBwOz;eR~Wni7bY1Xny~>{7pT}|CW1RZ15bCSc@qj=&aEG|Q&pzAO?9GT zfuo%$r_}z_Fdha*%>`;X9gwX^0%+msho3o&gC7$U|H-8R(klyn03Khb*=WJZ11D^< z%$%1McNh4G%0CS7U)Q5aQHHiM8~-2X-a4+zt?L>V+`y(=x*MdVyF--jkd*Ea>5xWB zKmln%>2B#3N$KuT8fmF_ZO?tq{XFmc+&1NJWlx&teMeqOILh*b>o z&JBE0&UA6d{zlqAB!8l`wd&48@Ly}c3A5U08M7QzX$>Q zizd9ex%p&)dR}m07?S#w!!ulkBI!R}`VlSI{g$A^;9tH+0_c&bB0SckwBFzf&u=BF zD^~|I#>re^0g$WH|^e=)vps0G;`;#R(mS#5)8-slU==JnJyA>!G8i99$6)4?Y zF4<`H$9<>wR$LFA7?3SLE&%?YJE&XC3horIF^`t$^UZ;u>;bi)vn;AGGlziz(3hOz z-|SWm7yIAc{#I-OfrzPPrf=2Bx)ex#Dphb4x)c`W=oVv1zIrtRy z`deEk`N~-c|1J3a<3{xFKLi%jsw5Ao0WsaCY5AlPAj?RWj_^eOr6yzB4`_?-Ju4{_Ma9st#r;(z<3_uBAaYH@TM*+2;87eSoRS$cG06!BtXHJeFHf%@>3RSrC1;&1 z^{YID(D2e{jr8#b*3y()$XK_mszC;xxOl)lLIBAHC6adb#E54Q`W~NojQA^-QJOrTV|wO8+U&uH%5@ zCptbJhi}$R749@|5{Re>~XGm(w8aqVlmX5XlE$!N}lY zo$s&Wt>O*E1&=Y6yR5UUGc0=W_>_Gsu_v)d^;8@@Xy$%}f$WNT3P|ttYi!;QrVD9j z5U*5~m}7szg7>g+0{N>;v%(**$rKbXF)3+^Uaw%IKdBM9V@lVOYzHLUB*=y;DskWU z6@VK@oGd&cXY#!?0WK(4h@~uZPZZ2FjoU+o_{pc@Kfd}6Br>57$J`nO@n-m%&!6?k zN&4<-=wZv#z(qxXJ2({g?cJ4tVC=x=vBIY8yk!6X& zLR(K=&#Gf9Ex78cKizFwamtSZ+$y=I1K)u$nD8TYpw;Y{Q6K-~fLOuHn}Tbnrxv zjRDoThhgD^&rWz`5PxtPYYh_k3gdtS5A;(^8}MZU4<5wZ6yxOMQiUD^z z_w9nbS&+=`0@J2SSORA;obk-R7{2rsRlVit8&s*G9O>L9 zkPw{#mLz{r4Vkx9dOj=0HxUkQ{+S?=-HQZmNUbc> zU%WfMfR&bt#*J=&63jXGlI?*UtOm3lrf*CQ`1kAA>~f-c3;E;?K%-@{)!!eM&XKf% zJBt}*M&a^>3Vjl2@q5lzU$cMqy$*$4^dbKC3Qi3J*Fhfl^*3^}tKF~n0Pb)`j1Ihy z^qCt7omdH=*Fs}vKaDeBERg;-Ilx}_|wN?X%j&@J0W#kH&*8^RW!K$Q$37Y3x zG@rpF%(zZyA2f=d2Uq<9LbWt)gwS~TcKOOa`c!FvX*iXF8KCUIas(#of#Ib&*I|)` zCK6g7u%e?z^gJAlAyi#>*_F%XV@g6ZfIDYQ$UjYQCaW>7_g_jC|3fUr!U9X^Z7SSX zU;yR65F?*SKQZ1 zPL@;xjtDev6JOu5q^p`;UmQ93HZ@2YQ2!>t{vkVPBZfy2nE0x=@;xV(SM&=^CFAi7EUK?F^067pZOl-J)t_$$6zQt|S;{^JNvL%=)2461-0Z}R{07#am zR+)|Gt7@sMuQdrAjxPHHl)rs`j7oZcO`aQ86(|NW9+-x|aj@Czv`~36TPp0Ac@%4K z(<(t(3;?gSeyI=sbPB_&D5~&}tY{UOrAD#6A~`gJ6Ml~7DO1#aF#uK>SWTQ9PB8mW zHf}tVJm^kW6X8ILL~X5lS zMLZs<4A0u#QE-ZKX1hV*(AwqwxA#X6dq@>hQTwvCwz)F~bau>@G}o zd(kTavlWiSG|J25R0YEC#o2gX^n71I-``{-8j& zv^ma@bY?&FLf25sAqcUTw|E1G1t66FxV%829wgMD;iCBNGhH;So}eqN1XfEYEWdhhPk&jp zO%q!)yXfm$vn`;_KJ<~c4%jo()D7j?(EJB@7zYR8Fb)f=zwg^zN-;f^4EVmrf}q>iD0NyxEw0k!ocR(UUsLFz-U^Yykko8_2Yz<1;eSPJZTYq@TZPEbMW0W-({y z=)Mqbv2`@QOA2C)W7|6`FOu$C&ogAJwb~JyH&?!~nNx4$nHPf9#XDCRazfYE=4yJJRdYydFP9Q@n z<$W#w1UGB!S+&(0bV81-kC+=jK{4j@6~H<9SBg_fKTBwsFjlczKEaaDzthjr88N(K z9g|wGxA?_|_O73lYC686=<|6Dm63Bk@{zAI5O$XI)k7U6fLtB*=lx7xE2%Z>gnJ-E z;ctwR$a8C_kX_v&5Ome`iRC4IAu+Tk|FL6{R!VWVmN|(5XF=tDASif+V1PSfivk&*~ zCdjVy*CZ`-Bf8s?139eux9VBH&Pn&-=%tr!bUUq-J$^?cklSC5o#H6TKo=cctNoQ;MqbaC;gAC^mqQU|z$2`Ga7F_pQ|D5_K89)Y|D+8thb zo%1~7`C!3L87uv7?8B&E8~Lj(_Ds9=DavlDEUXXh`Eio8{3rmA_Xk`vwmqF*nSO}Lrsgs!pR+qf# z64$J-f8!t9rFLk8LZa9pL z?Cg%axJhl;7K3T#-R-Cnt$sIdA8?mH0$`~G?^aI8RroKE`t@^Danp<^chSAse_#tG zbKq#}f?nS8IbtMTVC51KGyMJTH8|TH*?Qx;m&xen1QWGCwVh1h{N$(yeuhyJa*{-AAOKNI=3)@8y*pX{MF z(s^%UqVpy6Gyab7&va)ktnd1}QB|xe?meO|%M&(2vDLF5sIM;ed(HADCbbyaW>`>$ z6dOlzc1Bv#U;G$vR|qoiB6)c2=#l;eOJVJ9hp!5nYd4Zb%%~Bu)T+e69I3hr0_|xu zbyZ=(Xt?+p?y^KUa$CbDl&hO3OroNJI!M+P+2*hIJm_=Bh)X!Jv%mk@(4ZP1!KjTY zdtYlFi;$f<_ax4D;A{V6=^kFNp=84%ol3sZIYW`|%jH@7cC{a^6x^s$D2(&AK^zq| z&$bf}l#nS*?5xVo-rX|Ye!pLZ-)~U=kO01)NF1I<9;^5BYM0c z=yNS&j)JIFPMm&mya{8Mi`ziByq<@~jvk%-Qu5`bgi_2y4U`6Wbp|KhmhA%Dm76{Y zKmJul(3NQ;VPUK`in;ENQ?2z#DY-EDSTfdJ^kQ$6Nu{(FlN9lFu2_Fz71_sf;jpwz z^(qsRWRYJ>jL6l6tiXdtOG^_;wo9|tZ3acZ+N0pb_H?_K`@v|g3C+8}9M+u=W_z;k{~%fT9BicDUjZmi_w@Lc|g)WNG0B@r=|wXP%PO}-9l1;537Ks%+hxAM(>g!3gefk1iG!F&Bcz6gHoxkgPUKU z0KURxlmymXJg&6=N?;pJBvEWm@@V{3eXN#{v5Ij$R2VEH zm+@?D+mkvTWLgp}6!zFx#u!*=ltWlJXup^#pXw}|kT4;%XK~CuuQG1IjiPNgU|@Nd z%4(zqkOJkS2K;h>fCodAp^kJOVM7L5nyXKQVZT$z5K?3XBvB z_!hy&lKJURvex#BN(g>|uj*E2LT4TS3G@AA& z9=Ui}T03WAjT3h0CWe z#s0#Vrw|ptpSn$M4O|v><%`BF=$jdDpA!WVjSal&Ygq_uXOttiti6QNKhICqHk;L` z(s#%&8aLPe9EhN+)!D?xhA3M)==f={x}yVrXORc`gE+j2v$9B+TWDRwT~qrbaAFFl z&5BchBmIvBIu3)%qcdw351S}~gV)=IpUGasZ^)QrPt3ceYT8%%1`Q~%w~pA2_P%g^ zn8k=8`FH|jX~2eeX1tJZI$uKX20PFLf^t7cR$>`ht+_oEJ^lg2q(Wi;QZ5|{n(%cE z;T0b=leMlga>vm+uI2gs6RpG4CnUv1cdpA+TFx6hr~bN(X`&O6V#zuLw~-t=?Q$6u z=vY5M?M6HXr_Fe!o84Oqu@(QVCa>9=zsk3MkXX&^P?lAls5>@Ft$R3`Vd~8Ov z`@`FHnyS>Yx(*)wZ4q&=bDlX@ilX}EQN*idPBs3$xzByuDb*R5lC-d;5O*Kaq}r+d zQ8?M7-e}1Xg=-`5`3`$%B$+#>_bzr--6F?Y${>QDfqFLA(9q=jf_Yo$azT5|cIk|9 z3<P;*={E%;*6TKIRl?H43cGhhjnYSJ{*j2^p^#qvPq#$#>&mL&vIxv+DP! z@Q$xvcufv!28`CK=jGS1K{w?YX`56$8}OAb=XYN}4h-vkUi5jdRJP~!sH*pSgkI-t z4Bn+to7(Bo!l0$up%?o{s+d`CsYf~>k?>Lm-%y7qLYnokg4?OD@q4hI->GUjPaN#L zoAM#>y$r!m|Fwp=^APIT4f$Ss>aOMdqm0C^b0^nWHD%&#&oOxGch_H>9t%pse*LKR zR$W!qx~j#haEK~mh#FpT>j=GUj~jpsQuF-U+w z;*0#X$w>kkJDHG~CS#n%l#q_%C+hGAX;(RQ3aZk9AWs<#Jabxv<2gnz~@ z7ia`%%k}9YNZxF&F7v!qvGwpPzRqSF4?{i29$5P3|EvlcB>Ut39a_h$>;7UR4kqv6 zHZvPV#?sm4I>F>GH5Marzlv*qcQFU)uv+TMcg2ddv~^lVqdQM{&OK(q^oFbrsN3FX z-{ufzlVoR5$Jf@^gc**-walM=C4fl-3mI0fs#B!f1zkddPB0~1;=1|Rc zbeR6>;UKX_!$}Wi7d~#iY{Hno1Ag9har?XYDWe*2wvwVs>gf(sJ|)edXwQSC2m1kt z)_>K4DY5+AkNdAf#l|cKYZQ42iKnf3+;vK1A(bKG=H_n|FD*Wo@a1Q686HM`7aOO# z9w0OvdiQI?Mqr|Ub2~+|`Jc*%-rE5GhG=0c^&OhAAO3SVj8ME!bnUdgN*y_^cbUz{ z_hLV6FJ7c79EhAOBIGj5$f%T4QoMpP=7%HPh+peh*h!iS5)q zJZ>eDpyx9DESoJc{vO3u7F*aPlSJ$VODm5Ssrt+nzwqPUGB>!TaGO_ywiDJYTW2^V(Dn2rlh>*At=?areqb3b=RwliRnIrgU#4Dh z>ap>|zsdAPqoth=?x{ZujUwsrO&_{%GbIyzG%vr*>1WzUKipdoIQ4$g>CydaixJ*c zIU1XXhR~!wqvgw#D1)P_$Mptyj}_m~{A@q#y^$*zdwFiZ%UHc~ItHbn&wa~9go(AwDG6=n6@F-XskU50faT}qU3h`5j@~EbC?Of!KT3+- z;!O}gc<-@`C+@8RJ@}Z3kc*oY(HW)psAN%6ZLemXixU&PxqbdFD&laak)u?+X8M?56C3dWfEYffsZ0 zNh0SVgv_l64*W>B*87)SYPGp{wq0n%ND$#N=U*KNb#&i?H&=pp92KmCw_>Z2*;AWH zZLx;xmWKDxw3ASuR+`gHLJE%L3S67#{jIts_tf%4zyYdDs8T#$rlXXg@hB_t0vGppn4$(9tuzUh zExi4m8D007v3W6Qu;Xh*Yx$}NGq!O@4$!T8o>I84n8gP*`)E}b9e&xn;2D<^`S7Uc zK9-I_WtUZJg_C$IKchb2w6Cgc(&;NA&R+CB9SMRX=lLB zu!z@*c!AnSaZWQpBMXm+kN|GozOx>S$6lf*Olp}L^b(y6gqr`9%f%V753f0QTJ|3b z9ba5PNJac`814i@)Gbmlm#ouMq$1Mp&T2N$>nNkh(PsK0-e6(QyO71xP?z8C5#7!e z6zlQ2K{&?o6mG;nu_YhsTYP^}<#{E1ZxDkx)mEKR5Jd)sKr+Hq2Fw1PbxirnHOGeU z_Cwd!Ey21z={AzU=QMh>mdhLMY2+LUidHT5ER4ya_DhJ#4MkqjCljpC;IB=7E~fD^ z5IzmoRVEiAN8Wnr>OSMSKH3cx^1@?VJ6b8!B+J_aV?ArSM}tdp8bnUoqf6WdM}!y$wK zhLyf5ZH+i0YMxSh2XF(on`=Mlez7x|4IH%dH41Rt_va%3S7q*?f3ieB$7Z6i_8o=) z^*7O}4BxXEMy-b%cO#z^Q|&IdCyF|&ZiK(*UoO>vA!%LR-PxYUn^vQ=8{Kg*wp|!s zk_f|aE3kOm1w79aEfW$@2@8g`W7~HCd4VO!(8`q&D=X~eaw{_yJaYxKNM!&Q^boj| z&kTEqVUW2*iw_4h$ZAs}DU2{;?FttBU8EN;$8z#(^BQ7$PRj3w*Q=Z+oia+NT3{-` zRj)^=`Ucw?Q6(rus_i^Sw+A_F>~wEf(_LL>aMtQ=!rG{2F1n{gz`3cUX*@ReUHhAy z-ijAnhIlM`kPU?-d3@fqW0w(WF=H~~_gC)&5=MjzbT@+>AJ<(IVC(bVzdDDbFK9rw z?w%*Nn9=;^q2{S`@suW)%@B%0p4L|G%wq$mSw!`tP(t1`UmykGg*L3^ezyHZTx{9I zJjIkp2#(FCS~gz_uxVtMLD#mH8VLchS7)=bpRLMN4m4fMZKv2QlTP<9u8)V+LxRT1 z$Wig+ew`mU_EQMC5A6H?>|-cN<^U8p?vJpUS_aPD_#VJ3V=wh5;OzFc1>Uz$fi+P= zYkmRKz9Y~9%(E8jjwver%&d47icS*ROYV&ZBbmMm!Nu4C{gA3tm>Vdeh!;2Dj>dp? zCEh%n1kKc>z-9X8DU*3EGNTTX!K%X(!U}Mh%-*15Ym-b z6a4-Au51*7E@=9-2em=@gCtW@r^yP8njiQ-xLT)Rp&7t(y zui0A&@fa8%KA+NQ0Zl_fQ6GNBm?+S>Bme3{2y=*k1}xGVoi7ee(8z_SOO?|FCx&Jm zl+Y3;09~wc@Micvrm%5$6x@BpfZmbg=c`>2lU%ug23*TH=e67hr;o|UZ*R#@MSMkz z3x~s~kss$=taQ}&aVpe%nim_z5B}!}^wzeR{9iSp0O&<{K%IoLCCOIWYLs@*w>5^5 zEC)qAR<&t4OD&#D$z|>1+BN+$IC=qIM2sW9ud$0YL7~fY3BE`k>^UWVG6C>%=4(`% zmC~GcGqqz)2s@IxYb6q{=OfEE{V(2MRW4+SQ7_9uR4;LDr>agI>+VpDC zPMojW*YZ4=u0WsuS?3D9u%OB7D#+}VIl$n1opD1HpMy#@vwq^lnG{yXq3f^#Vtcky z05AYE9msBP*X@nuz*MG(Q6zNOql1GX1Aw_2>S%fdR4BU2=%G2V_IUY=9W8xSDcOxK z;1WrcaGOt zObWu;rCDV$X!q>(1J({;qUj)_%4jg2f8=N-n!&)!`vA;;sNGtvc!bUGvJM57g|?_4 z%=_P6fO!vg7}2lUJ~IwBC#tJ?-S#t8ykcRO$XTA=8Te<3OGpM_wx(U2@pVUe)!0t+ zbp_>mXOBfs4+cCwv(Kxs4G_J73ysY7Xb;>UwBR@l$y4{3zrs?Xqj#HzNGLOL{9K%` zpFz704a;`xTdmcO3CD&!(;$#c3@CEJcaoetGeo8(bc`)u5Gs+AO-45BMm`yuwv^QK z3@566m6R!!{Mbs#lbt75Dk5IITR}dVgQ%}t9U@W2FeVGyOQg787{g>`@ip^R+V@S9 z$=>6ZMs~nAQ!q-ZSUG(?+B@APj?D>>5b`+8O0Q}G)bSxUA^)@88MCDribv0(uu&x} z7EQn_*PX9Z!IKy%i>wh?n5D@Gy>u}jFV@8!=x^s8`A+x9qyr*+Jiz6iZb+)c&(4_h0k3oEBLiBorYp@~+bfjl)%eWP zPNcq4C+%v#=^5Q>zZ1pWcU%(akBMvd^pT`KBj*ne?coYqad-|VzbuyLA0Wnqm@Fh@ zZpycq3}5YlG+ugt-c4wLouQ41Bwzt;gZ_B2SW^UXhp$@?llk0x;%IU?HH%yH;6iP8 z8)b8P-A>H9=VMPCFSPF&I8ESyxgXnh7_+@=gVvrm8OvQLY62P{Z~05=EQ$mb}eWbM6eX~nCaffO$L7o<(^BJLr4 zuCZzV3iAu!OPlo>tO-kD=e{}`);g@CN2)r@A-S|nLh&^ZKbPy0X9=mYh$16jphO=t$Q)x1v_ACXmb^1^OGG*1{ zbSA42#we$__p8u^v@zRUU+)7$s~<|a=n~2SnL^4r#`LIU@HpI(TY5!#)J{E+3-LDu zpS;xb0SB&r$>^8x#ne{&v!+9XO)noG_?XNKq!%_?uB}me7#i@Lns6B3J&DFq$Jq)N ztdTVyN%j=BuI@IIkfq0~jAC;U{_M~_`}igMZii_~oD@DmYC?<2(I&4s;b{vIZX~nG z{yEc1ag?0gg`t;QvXd4)DLT%o$m_gfu`6jCj9A2ZweIWT64kM%HCX<=B8#zoD_Xuo zY5@^ai>}M(knSKZ&MhH^96@0zS%ICQd4U+o_SqWSlKQqdVKAJwJNI#dnkz6yc6ns^ zg(1{WuiQ8=ozZFJey-h&zY#Pmy!}}9J52mCrg>1EfZPLIckPe;Msk9d>VVyDx$j&C zQjOKOj>!uDJ3qHwD?>KUo-`=&PyOU>XP5d@h5XU~GH!Z(EKlG!mora#?pTDr%qot8pWkJJChB6mw zw`BbjDOn=l#auxTu9HY*bzYYq>wF^)K6fZmoE!_vwPOCkkjpWe>#;z>TlIU+#cKFn z{=8GVRmX?vbJ(71JmN2CJT$l>UPT|iF(?r<&>Yk#!iZPi!jgB5(~_pdgz^u++IQU& zh+CpFSj1~YEqTv35LC5QtgxAD{GH4Q<=#oI{L`l=kL4*gMLEpgq5YLOWr%w0LGK=%)ALNYDjnC2Z}8bw9goh$QV za5)pfs@u3PHuNDp-4ndd{br=y=H2S~%l5BVv%2nQ)?jL=(>;dwz%ok;~(iJgY{j&+7#IJEB!^8cYQYWUr0oSqP53 zzDNTM%iq`LJ7dqFTF<_nJ3`IbNkFR^1`TF|LnhAl-kWg!o39aQhvh`T`H0!?Yj7I# zhdODlj+{N}U5};QTm~11zKTC#8V7TiXto6?ofsYP=M2k0?+WZ!G z|L$8GVJ{n7nyTSY)12yTXcNdrV+TI`U8`Ver$$woWrat0+NRln!k?`;i5!@qI9*u2 zF9L@`J{NV*<(jbEuOz+WX+ETSi@ZBqgIc0r zSBqBbxa5!!aYg(Em|Am!)6>Zh$m^~?|Kci*eM~3z>pM-g-3$(3NKO(><)KK1wcXxa zmGI#uqzl%1pF42W*0Ha73%#%fs|srx68|Jg0w>R6vDG%-%OEtInG;;7T06w#{vh>v zKU%q53bu%A<41=-dYM$VQ`d(z3*mWeVL+rp5vg7$>7S+or808vUFgu*Sb@vmO9&1d z`V>CH(P^ue;4yudq9F;weg9f8KB_gD$M%8f?Zrt}oEV>${V@sriJe}(6O{+%>Fx|J zfOn8?*rXb;`4OKrx}RF>dk*)jcjn1!ra&*q0&~6S{lij(qYcUW=oGvhc-crTpdWRP z5>>QFCgH$^@9H!gLneOH?GnjvR>^z&128~brC5j4?Z+sBOe9`A z3>^FG4g(sbeFF&LLK8M@@I*Mq;k95q0tJv?ck(Uy++U=*ZUR#-#}_Gx$dkJeUaC<2y&hDvI!C;A9fnLcU(hedgN*VMng z06^QW+PYvT$;eLtrrO$_sY1HBIVQu%wVA2)WY3Bx)1?4l3pwY zgI6NcHgFEXC)A;cjg5|0WbLe5U=rn@tIxdO_fg@kq{XRf01>a9&ga{5e}GqcU_{qq zy_&BXTLpO;mDEFEZdbgQAuFJ+Ll~RDC^s@dq%FbcLLp#_0!jIoNQ)G*JYU#uTYduH zCz~w4nkd0oH11mCkW~4FL#qN*9si0B-?zuYtm)Ve2IO}kO%LU+e|Z=*=~Fko|?N@90T=Cf2l$%U-Z9t>`+2fXKW zdMh^g8ETb$6)I^NnVHIy+3;{BerH9$nsgo7MP$@|I-1&c z7tNbpN}TI>*m2pmm3gmBIPB2-Kzu#xj`FkF6Y2A22zHUWA)>s0+}~qF#jUVG56bG9 zTf=&kUtU6Q$3D zQjblST7170isr%7Uz0z29{$X{{$XeaFzd$x0#RWSi^wfY!XmKJ1j4{o(8b9Tms6%7 z7BfRj>LDdQ8O<7bqpv<)FdskW4Az4HRa@=~ud_$$nIWd@f*{VyKz~J$NpX}mQl4** znc0+5!WfOOPT%)8e0d&z8u`#Jjr&ihS+w^)J56w^YaWCB-WbM;d3zbBDd;?{5e`oLrNW zU^ir0cLo%m97Gr|-U$xsyV!f_jC>C~0)E-EkktV(l}}HJxb0{0b|%4Cyl7AF#s%aZ zGt{)(1WjKwO_^h@r60^{A;cI@`=u!xAs(?D1H@uU*TQ3ro2pi>(sjF&A6;O(6-yps z@M7E3-l3z{h|=y(G`M?DE*P7oyc<-?LTA3`J632Ho#K zC1881En0-$l0SSj4Tj~xyYEg1g29r!MPoLtP#yS*qCCv(RmCs3Y_J!GB7oQ&uL$o< zkF9Lu>^}L7Epb&QB8Q$~YFkW=-c^Gz_S%MOLAoK)=3A9$NWX_$ebgZ+-ZH#seBdJ$ z!0x^~-+=2A5n@<19(9-n(s6ZHE&g7?4JJ}F3Q^LNjAiM-H4x^j969Xg zELPnt$I@yov@+G-s=wP&5=!%J_CD_cAwB|D&JFz(pS2SAt%(v@`r$2kUzDv;wUq>~ z)E0~c6DUlBb!YgMOX?zH0lM_ywUd`^G68D2t5E!tw@UttmgLIlCgv}-O5rt%tPv6~ z8fEj?%UA`d#W|%M6Str#D6a#w;jb4|$Aw#+KlI`rl)UE|ykpiK!GUrS2ntwv=f!~2 z>iJ{JeepEUY=)b;7+v!G-z^I)YRE+o=AiG8xl+`R;mc3c&#WezOj@vJKEGfAfx=j7 zA58V6;LSr=+I4Lre`E_ewxXq{|KMiL9`qoEEV;dftKi_`Xl;3Y9iwjYyZu~xDUT_XP$}#~D-;t{!-DD5j>Q3D0}D29t@VSqNz-JCVL7`1I63 zOoZw))<_}GYG$T!?H5w`fs}R*^+#ZNFX0+H)By1eQ)cMFw_l3UINaBpYWY;K>+;uk z91mQN_Wn#&o*%?AT7gcQat;#zzE1q-mJ!Xb#!KkccsNnfQ@hJdf64lk4`tt9Lt-VG z?X-(u{f-CHkmNMs9LPrB##gG#YzC~_h2To=m16NSjL>R9KeQfvDzcF97yeYMLn};u zeMKqZa1)9yF1NSe7Wo-%+;@K6M9QNOA8dHWObSF(qO`ltU+ZlTDB z_ANjZf?zT{PKV9t0gwEwk!H8!HJ0_FWDG|Jak3a@Y0NW}789Gm-zTa-;5rW7n~}9j zw-PiisvE+VuVWo`4saM`Pg8DntY32FKY=hD`}5tVB<=BTDJMUEPVT-6F?h=?lnU=P zq@t-rm>RGjPRtJPCm(SB4wrriGc}L?G9UngP9d&=PmRS4skNIiZPY8$V$F2dJ^i$d zhve+kT5;kD9T*O};u)_5g%V?&<-4WwaK`b^2iU<&yeNHiPXdGN_8;8*dLl1a#}%)a zw$~ysW`1L&+)jhBy6l(p{TS;yGYpKQA|x;U)SH0c8m!=wZ*QnWl(7> zwRDzd(+e-Jz`QTZFR}28Nxd0S{!u?0*YH3N4ZD~nDR+r#j_l_%n;`HGtZ={d-wDx@ zY_GY1s>#)@+QC0?@2|t;5v4e1=NL7w{XJ0+B(cBdY4%0$BVsNZibkxv;=oy=JqWx*2rRl#kE!nH^-nM3o* z=KWq^(|ADG@u=d&9VF|S=XNItTG_rc~40}4>KbplZ242Hd2<-P_2u5Mgq;Xq8$ z3(DS8R)cyhFyXNsNN0j%TetqE#8zoFA;6kehB-{Jd5fKr z#E{f)zLjeFGF$UF)G&4m6kg%(=*nFt1jkEsyF+iFd_lHos>@I9YH%^SQe5>fCRxLM zc6gQ>9UI=8K4o0oQJ_6(CI+itNdI36=^k*&ZaFJ=$>HW)e#WNm~;dq0}Y@0XSRV9`<9z88d6&A?-j<6aT=FhV_R!$T>HnpMrHn93;gFq|@gFjSMRB%9uP`-!+q#73hGCKFsB~6=oj(`yv&PUw@`Ul8f9zMV$ z2i7ep)Kc7;sRY<^L~1>_&&&nbsH_%O4Jo)Pl_nE>D&gP&6Y);=7v}c!tfb&R0m-!o zc%m~9V43tp5n0Vt(zV>(dMC0QkrNm=1>)mW+AOiRUcUnRz?n#DJf`ibvdmKS`EuiS zcraTHzB>~C_;V%+lVKW|CSG9L3p%-WB1T>(9OOxlT4~FDE?us4e@^nFcmai3QQfoI z2f2`rplYM-cYk+A|IYW;i2^}Vh&3!xp}TbNT-0v0flplWATjWhuuuW>Uqsh04+1Z2 zOXaS$QQsWy;m|gpipEnAA*ndEe8-{Xc_Kzganx-IZ9Tw{He5jc_H2&k*CtiC-=?#) z|1Zvt^ZmX_f#X7~PO)!njSuZdUtoMuCuP(fUB`FkiX_DoW3+?SLBKqu`;3Sj12~=X zsvb^JJB=?7dQ@pPLA#6)G;82R9)N2}o(JW%pABXyYZ{G&c%SW7WRRRV9ai($PEIRk z?Bz~%0vDf1m@UxogN%fZ0U*~aZgT<9YZY99cFl?#xhZ19Jq6 zFQg7oQACM|I$yv%jkHEQLHDQ)OOsWv`b&O&Edd7{$^(F4YZ5Hg>(j3;iQa@@03u@W zP>q)I{}ugw6e&Mrtt!^zh6+Enq~|6>>ULTTk^er1*%x)k_E%xz3m<%+%xkJXT8gd2 z0v>Bb!eJp2SPjenHseuBP=_LUOfCxk7%BN_Cw%>f4{tQ`$PGM6ZJa_G!2EhMlihwJ7*#Y#4_X5n+B2+tVU>~vu~ z_U4m#5px)%2b$XUvD83AQaj7sBdwEa5EfT~P+Doh2Y_2$xDaw$P?ADCb{J8op~=Zt ztFll$*#?xX4da&A-@kw7?q+fq2GfS>fsXeZ7+KDJC9M6ONDaVHlK4n?OoW*0fuoff z@)Ap>fJFkl4Mshcwq$PWc#(kqm@Pz+z~|XglsEn$h=u2HadFM&9-Fim^cDntkN_w>O|#l| zYKV<1TcA_Ue!iYlT>{qk=8fQM2FrM4tyBG8b{$HybTV4n-bWoA5)#zVt)BzY?FKX= z-e+A4FPH2)t)2lpVqV&J9S=SlQn%`7g|9#f)5b_SJF)q%OTXEuWB{7d8%rzAivPY# z#Tjws1shpA4w@)9sGeZiBDRR8y~>YOT#P19qCT35M%s6;sdYpajEYrra}8oAwah`S+8iKH76 z8e^J)N{?lUfl815GX8y)j*m|h>!1Nq%v&aPdPR7rbKe0O7@LFzuUy#Gixd!&(!(Gj z_lb{Rg81Iec8oc90wxg7(5?*0hNikiArCF|d)pd;5lBbCJ#6K0rIYs#K?w=*L1$-9fKhM>0r(LN>te0%|RKD{MpgBIkSZN|yY-*6$PGUr8r6-0g_zfN9H*&08t1oS(Cx92)mdTt_Dvok;bAG&0F1 z_1@wuqbF=?kR$CgqyfCk{zst1Js5RzeQ)5B^qT5{|6LBkl6=iyvPVkL%aEHl&s^9& zo~tc^&ZRv{&RC1AbiUV&8j48~1G@Q`z_+McF@LcA6R1+81O#~BB^q0-x8-bLMOX(W8=5fRWMAp4b)WkHbEIwALROpA4Gt% zpO=$=$K(tByF9QAzJ?mDRy>W{3l3ft4K@2ffPm{c#wZ#^#MQG?#Ug_mn))G}6BOh0 zGqyZI8o0;B?&>uH5*3?fd5>GL#D#fCHD&j5!YjZk)o^~b_pvP=*K7%7HI|wGhpn%G zs%q=Pr8}iN1*AKq8)>Dbr37h|?v`#)kPrmvl9D>oUDDkk(%rmuy!F2~-f$@6GL*Z| z-g~Vz=U0;eDt7@KE&L~8w^}JQ#26^c&p1(N3;QT8?z#Wr-TJtxT*KL6`blC+Aqglt zuB1RiN?tVeHIrj&3_2%Wh8hR(n0P?e;_?|?bSr%Tds=&t%(Bso-2H4yps#uXl>i@B zoNn1&gsaxo)$UKDSsg)B5U=EB4%WJpjzLdtnY(l=5{(V{DK#4PTezV-)of# zgNZLef89%OMaZ2{9D(p~V1o~(xe;1uP+#=(4i3F4^Y0=A`|pl6NYL(Cx2!RKb=(py zo^wqh#jd#&6^zmac6-ik>81R}!0vQZ#kg80M#PN3J*{0<62<48zo<5_+{-OMW^1&o z2;sSsJ__}CCNdY-OEXJAjo|DYdo^)l;(y4tZP_QCn_ccd^e15PPu=wbL2#Rtf?_Xq z#Yt*M>T9*6yuU-Sb4ADnaVq>EZ7MtL{Y$%ouTLD{@5X*B_rmq zQyqRLoWQOE27S(}Ldjun<$!;#D9Fm5Ssz_R_|} zGp)ZuaKM;e6DDcKD6N*PCK$CIUmjywxkVDhY2Hsy!%+?w`%)cqBs*9~zSBz5jg+O$ z{+ro_GlFpHPcf3J7JcCY_k&wvy+{lBC^klVwY%D%rCiSYAW5dgH^zAI?;3*vHq`zV zB{~!eK0pOO_L)2HQk?|fCLf>#Jydkh{>AzJLi@rc*iq_V717)e-HcI?xL)FX4l@YZ zp5cK_glPTM+2H6Rq@f4)Hd}zl-ogm+>YuvO_Fht)aAyE_Cj(WK;@>L;D?`ErC@ld# zjz1){MQ-4>@D7^K5f>L{GAsfz2Ai&TipAb^f;H|m$TziK)beNCbLA&MAVBNasP-@w zc7N}0K%h3qUt-X}{j++1p^*q+4HG6(`AOe?8l%b#ro-DBV2hj)fBs-))}2PRtIeHW%7jheauE({w1)p$PT-FAR+kaihHOJ zcXU@C@LPz^yw(XmwkoU8vaqxDXk*uy*WVkQ-Gt55^Qk+r!iy{HJkaLGv_mSQc zd>5O`tH&=+;DTWeC>Et}RXV&PE>c+QQDua&5=e1RUFd)zqiQ+gv2xmzJ`wXyn)XK z@=j}LXaVCkjq_>;Ds*(Hw(c_xNC}X0n$Fid5%XALcwX$=xe3@#cSx z!E~lJuuqRvStxmf(}~HrMTEV687_(4@M-_~-Elw1B{&Q1xX(b@l>?MsnJN{(0V@KD zAYg%VW#u;wAaf=f%6d3~?%xdb`Xnhou^wptX>mS7xuw6F9LZL;N@+%jGVYrx>eWwm z{*Ra74_cyocKG5)PL8t=TAvcXMDR5{Lf#oKwP@i{ylz^93R^$&kqx*hoATOd4qKg7 z4@Rf+ayrfDMvt`gc|Y*Un&&x$nqX0h1@6tcw+DorN-pHh z)Pf$Q(rQ31YsEj0x1|1kC6tWQ45~~&I)YpA<%~C-LUJ6l6nxV`r}aTHekqQ3@Vpl zvGE2yFTXdTU$FVOnCEj0Fq#&PR@im7;#CKrtYSbO!1VVFai9mTUd3+K0g*dcRxaO# z5Hmnn<4c;bMGo!<28kbmc%59`EWOrT;zanuOtVgYUthk+Wl-?EmJRNOqaep4+513^9Ywg%#o#-4fM_ia%IyeZ#r1C3HXn{>bAkYTqD|8|6Bx^MO+kpjU zLO}S^9`<}&2)b2qIX+9K-@Qi$xT%xB19EbSjot^!$(@7IxYL(|(C%ne4ls_pFx>qb z7}gU-T17ph^-g9YU~J+q1WZq-!?5}~0kz+grIE4X_kw4qU=vFvP)~!;1HxyMMNCf{ zb8~#H%kx*|M$PKEa+t#Ig94-r(V0(TMUGL*TJJqiYHc(w41cAtXnuuJJyndq5{LCP z9+3`fLqx=oA9w7)KqJXvwX8Rm)ES%&e`vi~5ct9_-+IdW=ms;Gg`pH#+Xj{`+@A_B zd;9L1OfBf7sUn)JZaL<8`}$rSk)u|@->=d-EObMM(bD=C`VaT#K+!DL9t@?q?54jt z!{-yE992uW#-KVbze%>r>u5omo1)3^5|BO@Z<+U2M;j5LD`4kYgGFPFuU+*1TkI43?)C6IK*SZ!%>>fHjy{I zY)a3JvbTWndyw<|edDDH zx|Q9$*&)OGd_-Ti{b@2W9+~6Xlw@<@YSLRxZn@3=Q&jiLms-`(SkJYyi-C(3LbA-A zV^~$1!BGUw93U zWgu4rkTINU3;#sA6iBT6akn_OMZ+nMy+1WV)E|RWVLMaBJ8=gThYY)NQ@lddObncF z$47JJxj^W0x>C>;|GXI&&x$~F?=d-xMn1;2=}Ov-*yiW$p9R=rSL=Afqd#8(@bmCA zZrS7Yz|xAn?N1$JAB$#@j6JB>p76(TLnG5#sSntfIspx4DT~QSg)AyRKiC^0DH+u8 zl<70)?PQb3$$X@>kUy$=eKsRfNK_LcPvG8zLz{;oY@*Ek*KkOk3$Djta(Ig$KJ8>d z@VEYLGhAD8?j0-bxlWo8b$g*(Go-nPdtpL=>2x$-EW~`4jhsGIy20*P$@FsI5n|Xe z4MZLM=I2LF%2yWF8R@IqOpnh)$`2B}gtia7QvR|zv>aHGc zvG4EbvhVJw6O)Hye&`tAWA~@r*|__DxGf(duZ4wa&VGZ@6*wqt9D>sFb+SAmF{KwwVzE&C*} ztvLe(k&l0kLIR8#kP1?fnhDbAW6wdq`r${cOnSzgJ*VF`*AHiW6>$DxY!*01QQ~8u zzB^o&Om|r=ty`=T;uUH4t@s2a!R4RQ3^MF;6plxvvGoEhMzSSUllg_NKuET2^IV-n z-AX0*8>&wHO*m79V&E}AQ|2G4d%m!De{)cGR#}=s%>Nq{PJBVaI&-g-3HRA&>#2%2 zAT*#uz4h+rp}U!4YcTNWh^gQOI?W1blK4Cy5% zB-rXrcCP`%@s+bQLCstmXNC|nzwLa~G@G#UtLccz#MSLlGu=klT+}BMkV1xcMm07B zzx8w*ULb<#xY-Q9bWw3pidV0g%?{&QHiZx42n@FE+MeovvwbB+t<4wcH>~+Q@LB8c ziSp>o)VDQvd0U|cm?UGvD1v9vZm)zh-UO!^r##q4j`zsA;r^ zqvAUwTnoKAV3drtQ(va;s7TUat;Tt3q#^oH!kwHf`(pakD96ILB=^C3hCW|~h$T9> zKpXME-U<*|ZYKaLUoub^XZk25lTmsfN~XJ~pK+Sw_5VzxTLbS?Z8vvD65gYTo3y5M z2Urj?pIWT#4GO@hDZx#AHe&mTMSME>m~R^%lnlQthtRO3Mxm`ZlJii_7T^`SHQJwB zjEhAOgJ{B~WJhr+>!sCnhocBEeMp!X>3tkZRG4!?tpwMw> zsUE7N`AYmW;(|KST0_FIZXm|0VUvM=Vz!2?WJ?MePs--*y0y~s%#&=tsNaKPzWDfi zli3Y><6Y~65B1>vLgV>IZ?RT=)3px;OA6JoMvs6^RFaP_Mo{~U;$thWcSaK^oH5U% z^u955-Z)PbwQ_olX`YO=RgPCgGhjb;-JiA-RV4l#DaWukm$j!E090w|j*-8A_-{c7^MBGH!O0(iKqnVf zf!EHL%&N!z4iZ#gHfG+v-7A3`Nco6$%N`#IT8N(*yYH`Yon9_GJyuLo5RLK>20@pR zAZ>9vf_eYu1$qNp1(xdRsY#%-6;4+vN~ZVmGZsg6b*H5)y&I^>P*hY`P20}ogG1|G zO0}k$^IJ8uuPhF8qW#u}2!YWORYxP?RJP9Xj>i?@(Z6vkw}t6X$%4&Yu=`~oQwSNi z1Z$a|Fa#_qN;*DU>=Y7eC|HYb`}1N8_a!k7i%qyK89YngQ=i&O+rx+tV%skFE+*tM zmO@wR3{%U*hA}r-lB$iO@rnGp<1tM` zDDLq$+8BmVmAL$|w^qo5XS3ySz;7jKZSE7vd&FDGo!}7XJ}Z6>U`(`Z{P~_ub!A{S z_4!0(%P@gcgn&j6A*VdicX`;Z zW>;nB`IwWYE|EARC(Y8wzAcxe@qCu$r)l5LC4&m7KOAf;a$ZJIJtpB_^$An09Wn01b|w%Rr=fUi^4jVWRjZI}{&GLe5gsYA zmTtOj?Yz9Z;H!U&bn>%!yDam#DJnRnl+srl$0>n|VPZ|J9tAHw!c7GR4{)bdZ zNZ@1_ph_zrAvXe)8zPQMkJ*5W%cu&|%8CdD!qxKOQqSa8`KH>n3akA1UzVZ?!sYH0 zTjJTpk{`v{c{QGwj(nEIYgERLN=r*}RI%t!KAHvlKmn(O1L|hSGTG{BIFnIR8UbCC z_GV?g#e?uFlCq~4v*l368&TRPwMndcZR4ZF%~2JH$4ZGM+Y$${4>1ZyZ$Dqi)!BbK z+F?oD6Wi=PVEKlC&PQsgWQWg-1@#!Z85bg@@I2K%3Ul>XOzjA$?~1fx7W{wN8v+! zNnOftd51|kkNSqEm+MRSGStErViDZ4dKMGM+TyUKx`D2gUr+S5I5m5bb48` zBkp?}e3s#Ok1e=yuHl~qLRo8!*36!@whWG*Az$MJWxH5WN;{^M*szbXp;QayJ7(Ett*6Rd;R8}@FI{z}`Y~C~F zg9#o{F&5R+xI;^F&Jza93R`n}#E&QrVKa6ZZ0m!9)T!}lKaM}_ooHv(f@W`X0OfJH zRw&&FPK(3aO`iX()Hu#r|dL*JfH%V#bd3# z|Jh$cVWiPH8wA$q`@tvhRsvAwap`4@^{X2fsN7dIYgtgll?MkGRfo^x`Z}iG28i8J z2;feKP`+46o+taH0Gbkw1fS%%pUM7^M~)*ds$=ceCUPk80-^2X9{(vo#{Qvy!~LcG z%g((a;=uM*{S0atpu$-e?etPwVfBXjnTzI-lTT~xffWz$N$0uSjfum3n=n{2sKvT10lG=+zKYzB( zgc0NL+(cQ1IRc7_C@@(k7j%N!<51uKch|j-iXq)j*=Y^J#F|_X`1H#-q)|6zJ{|n)`R1fa56=U@h-@q4cvo4Gqy^XEe zg;!=~M*tbg209<@0T5J5LdyGk*@#}hUqpOvMbEDmOFPG(8l(kKvnP`NnEF)&V_wsQ8cj7Z`)mu>T;Vpo2REo=Y$S z<_}_X1R7Mpg^?CoEmfL@8Rn-+HZBLEJA%UVCAJ)+8uF5>A9zHi@^rC&fdA+8Jbf)( zVNmaADtssm^wQ&{`b0pV$i)zTo~~>XVg`F?UT{a05u@;Rz{a<~s;Yt#qJViUNGC#AKGg|K3MZ zz##s6;QxK>PeD~(bocl2@q%YA^fC1;3%}a1s8s7z$o|@BxET*Y>^4*eGwBsbi31)- z2Z&IP1lDwB}`%IqFlleGGX`Cys)R5qe18 z{0EZepAH}aSwHx5d%8o*d+?hCTtnlXh=*`WrVM~m+rK`~bp(;Omc&(kche=aD@NM= z0rFrES~ldV(fvp0{TB}^fp8n+U;Of2z0Jh4Id+~3+;?l#GO7jq!g3`)yvs)% zu@Wd0UOry~EHk3$`z8{X_7Rp}P=Wo*1R%6OX=2kjY%~nsiI60ZoGJYD;4@|;NV}u92bxHLenuvp-)@%%3Z!mq*Zjy^ zRwmt_9{Zx?1Sp?u5YE6aLXa4Huvq%Ag_WhAWyRPU(%z%-wue<2KSY!PWvin?0|Yy~ zD>`~Q{7kq>J3HD}MItcN*qD+}_5XeK;L;_ko-LDzuglSU^Apu?dQv|nO%p(lKWW9< zjVb&?{)4TBi7ARod=U+Y%KH!&YQ}G;^mtF~9MRh@#8oID%9tiG|1&MfY_8Uhe+w}c z83dQ73P~uJ#{j^*i@}Bx^sIx{5FGJ=H84bKd>mnj+<$*0CrIVd5Ui!Dlamv%pkwyd zuWyu}LBT&iC#eCfL{Jig-wnN)osivNdsmUiDl-K%ySlj1-JA$7wT-BStmQu@T0f>n z0Tr4^FYM?Gz89cIQ$zW?Mmx!~*ZQM=`5^)E&~=5@XiK!K-Wt2?-)t5+yB9XFA-VFg zt3XDlCziBTe+>83U;OPA67?Df59}9Zgxe30KCR#Cb(RD-ge*(zYg!yD0Mx81$9w&*JU_UReLm$a zFYZ_hK1pG*4YnlBk`&@H4r{E$2uU2s+(?tiI7?>M>bo1kNIm&HOp>WAj1~oRfEvo_ zvQIJ`3dpl=H2dT z{cctNNS(vd_!jT5%i&eDka-4}RDGBE+mrH?e)Zuu&=Nn87KYHq6+oHSCq`OOQyT(8 zMwruuP^!>|_Q-NuWO}yOmrVE^maS$F1|l6ezySJp?H5^$n}&K zD$j%P=S5F~_wY#VT9pZYe5$2D-+*Ebw?7@O#Nj?b-2c?y?w=jLWa6A?{|54S`3o4NG{gSM7CAwI8z9mnK{c6|4Z1% zkwIXx90>{tx&vDmW)W5Ot?7_hrNb`W&7Yn=U>64}V>HR{X-TF2Q%KoAihve(GSn!j zj*w)Bk6{s&<-66ayeR6*J-z5Etm(;VlFp+H<`&vgh`;-alwTW!Ebm)vCTKfe7&iY! z_WpUc-i#1V4R7M%fe`T}vSfKibE+#>!W(k5yFt1rSbesou5~g`4E+^%o~t4r{Gkki ztIJ$I%|*vE0&%}m#>#Gs@cC-=%VYHAVh$W3H>{Bw;YSYjBxNrj81l}h&Wryvy>W(c z-oJttOE4nw(7IZxhO0(sAWOpD+PON2S*Rzom#k?NKj_Nma;{IpfH(Fyta^wRPEyhY z%xqg;f1}a-M5a@ceExv#ALxnarR7l6ZWrm$*BFV}p+7&5R)Qudr~8sFB`~zyucu0E zvj5tHGILlb|Dp8>J$hhJ@e7NNr4yV-Vs~iuHgWHNt-{@3@X>{!7p5_k3(M4sl zq)yeEx@9pdJ%f+RZoj|(`rhCtF|>XHArif$a0Q^{1#^(|2Oo3eGiN1rqeW%m&Qw*V z+2^mbkweRkB}{!vQqN9A?$`T&#>D#r9*hclO3c?s?&;qwAzZhLXBS332djrYGbet$8 zx}?y+hCXsgv;Td(81Tu7 zIGQ=n6q|=oVILz7qp~Fv1W?mw=PgURKc9FE4uPykj*G0&Rxc4x&g$=7M1RY$&Ypsl zf52$QR_is|-eNNuID2FOb`Q1fl!qO$ozJ5^0SP));uYz9Z#HZ8OZYcsv#OsKoD*RL zWH1DrzGF#77@Ly0t7|#@B<$}_XZ{2~`N4?3Mj0V6-2cXfx^d&r0ve&fly(_QPhM?u zp}e>Ys2`BD^=0H4ND|?JJ3bD2acgqGo^7VH?21~Q!7f!w!nBXl_V=T1|^jzj| z0%XcEL|~X3L%z~#n3C6Kyl2y31~Asp8di_B|6QgyhZL!~$18#!H@Nr%6}U}rthWaN z9j0|Z2sHSX=Cbkr^f4SLju7LZ`xffLr{Lj8h4?$a4hF&3;{Vc%a54UPwP6i#O&ci7 zWIkZOLuKkn`lFs7+m`U2J022)HQJnU(s|9hd}@`u8~3LHYo z!upQ~kc9)WM;$xw(ktHG*prKS4q&a88P&noywQ}6%*TA*q&v#f))ogpEOx^Re5S!b zbNmQU&%p#Zlzz8PU_u66%|1{=&v640R@Izph9GRq%)Y(%$sM5ozHxo@xCQ-H7@(Hs z0xY_0EJfx(X_pJM`In%!=W^ZE1-1DA7N=UK2nm#)E{!oQ2H0dYI54N}Fdj=BM5`-0 zXQD}?|3XpU?Hef4DUT`0^ki0Plk>DQXAGbdpSx`e4YRrvBbgGRhUhX1XcT503XIN(}s%1Hkq+Q3_isARI&#g=w{HFdc+d=?He%$_}CPgJ5K zxxprHJ5DY>8X6z2^xW&>u~`RW8NT#I?`F0wQ6_)SyFUDIC$F)6M!vdYH=m@#Y38VZYMG^D# zbUd6{_*3d9Bccy!ADD=tV2Ei}M!JFJ4=wR66= zNAl%iV~OFW6Huu?m~}*;l=?B?H!Ewk>#g)x}N=Jn|mM3A}{-Xg|-~Y##J7 zAS6b3q+cg}tK>j!Jti-)g>I5?2h-?^B)2x9_&>KSzyN9rEvxuN{m(Z8)aff++bz8x z*3jn8LeFe+Jl3wi8dNqJE;YM}Yv0wt{vI&du3IX=_|9*q$)S_>cdvkx<$GOtmX^JR z&=~@(*FWF*Kv4ux)<*-+z$zGIbo{bwrYBB(;rlyj&^PVoi}_refY5!Ql>eeKKi%Tv z)z}O|l3Z~>zFC^2*>MToyQ-oKY+?!pe%$e z=4O-o^{)cSfFYnINw;iHfD&X-&mD-nk_7z`&l#Gtc&wKXKIv9givUlf-zTpor@Ptx zus1WYsPud`fq4Z+5|=5h8ZwyT_QlZTi+|fhM@y(CibR}(?VoP~qZ)C(*avW$wVZohW!@ zti0T2r!_hCB&Y_aG)z{T#sjFkgF$4>m&ta{7vF$eWo{mc90QBTLC8yKe?VOmBcR54 zLvbKWoC>O4 c2`+#!jk{F-1{46YMj@|J4H0O(Xw;zZ^y5&^E&f>R+K`QvFG%;N` zTKH1H!Mv}>`|i`;;kVrGm^rV6xoVdOcK!>`cP`?oOhyCM%%wM6usWqrjr57`dgUFp z%HMZjQ8e%Wak~8bMnGr)X7GQ3CF=eNKpB%`eQ8-&khZeOx+o7*PSEZxNWTR?nN*Cf!jr9e9AH$>75z zE?b%*#lv*-w-1qG#RLiVR3Ul!-^&7A5GP*XqWHprZb8uVlqpS`t&sx>L7d=B9id9i ze>nNJSpx`=4+VaH(4CHNM~UQY-^geG@bCx(QnpUXvHe@h9n!&7&biG@@;bZ_@7J!x zkXEx>{7yF&h=dpdaGtf#k?d6txf&JOjKN-T`wcEzv*t?!4L^FhJk`}p9HWE;p-vkY zBTlz2e^~fk?hBrdaj^6)T;6eJV7rix*i4qZ2h)S{8EiMPLqNRVH2n&>N&Ym{;)+2o zIFd3&g=@0?br+2k1WqE0x1NqII|0}0H*p!!Cm`l;h9Rdw9;um!I26LkkMU0yKyS&)i<+L+C8HH!W@gs0 zi@e=I4{p7G)JDoDtDElX_-TQ#jL?d%!Ewc4q9Euv6oeTxtrWfd_b?2Mg5Q>7$)6;p5gW79@phusW4?j+j? zP?BIHf?dK{K7s1%_he!j7xle`SafQIHy-ns7-E(f1|4D-Kw6lkpSO$f(AV00Dz)ujZ`VrNC7?A$XDKo39E@^&A^RqrbquScs-u|o5 zE#I;eWNFt@>g$gSwS^fDsE0f%KLTlZnn;>MjXX988Y4|}?3<3{(&IAw_l*cnNu3jZ z@;LO1Ud)@he6Pq5sBF745NVzAVmWhFGISIlFcbO`#WEAJ(ON1um!cwi{)P_+^>DE7 z@`p6;Z9eFb%wBT5&lsn6)e16D$wqj2rvnio^L$y4D`Aja>~25lNr?v>7Tx|@&Hq5) z5}=~=-vp<+2J$nvgN4{4EoRHvCn-6}PdT+T9&29oii^a-!$!d~+ z?}Q4jw^NDw4~2ckr@(H}_S{1^?MPjTU8~Qm_)mBaqz<}Cn&z8>Bc+8=g5wxyUpEv- zOx|3Q$2m$01i#y}CnNGT5vNV%kWEM>qMbR+(*zqb5+#JZ~ngfiAJN`#9IrJmk|BepWY${nZw`oiSRSSn=RP(MI@UX`51@zm>oRdg+{FXC(Eu&X(;qv3?%;jB?LyCLpqQ58H{dp ztEfb&xgXI86lZwC&6$x@-ibNc?Wrd|oEUEv+TJW%9V*o1*KQ|u$l__>&(D@VZL?j- z_h!*eHTfelO#n(FFFEQFq-iF6_b2@5aI|!q2aCp^)4zowJ1(Pzq3Bv>*%;1S^;W>( zuyyX_)sy@FgSg9k43l+HiS85sSDgVtPLL(ClhwCHC%ggSpme-=J_>qx6hk&tOk`cFg9trb|y$|&dUxqe;0C`h zU%A#@X*LB4rRu4BwL`oCn^0`j`kirU=W;O=fhp4TN%%L~G^^FIxK5|U2GLO!T3;)$ zhx3a-PZAj7&*SKMc*vz$s41quu<8Pcm>HJzQZ&Fn%1~nlWXzj2UQkNt;W4KT`()y= zZ17(+7&_e~8sxJkeB_sZagnL;{TwO#u7MvR8DPIP2}Msy~L-OIeD zPkXnzhSm2DtS@WF)`L&(Q3xzWc)q*Zgl$Iuo_orD({BDQDn~i>HJGZ=hukJ6cF1#! z-LBlrLx)qQ`?@B6C6AExTB!Xd`odugP|SIf6zE+i$gHBgOg5bO1kC~ft)=$f!jfq@ zW9IbBM9u$%G38QDTPa<`H-4^2)VRhBtEGS%L!*85B=x8&0myN$47M zEB!94pJ)Jjjk`1V6D`gyR5ts-r;QD6j zhv+#Zb7mmXc)R74ytZ>S_p`iTluu zcWAcR^b=1TVz!RZyyGDILR1eKNND`)IU2(~(=cqBSN%&SpVfh;1bFF}CAbHtXbLum zvtj|x0Ruoy)jOkA>O1HlMdWf|Vc)A;26CDA!NI|ruxo&!>j*?dhq9C2Z%zPK!1<~J zW!|wJv+%a*Q^r)eex1EG#_PqVhB{h3``o&-hONv<;FIMZmO(rZq8tJFQ%X~rphsWx zd$^&3a%7^d0uGB&&KrGSSWK&R8^`u#&wS6_%YrU?fg3*Q38T% z^7RSB-Ip4!OcH2v1a42bKc0QW0}oShpH!^Gc+&fD;-@&H7f}-Rxctx~k9PSD+}>+gkj0td?4CkRyq@xC`Cwz$mrrhw>Et+~8Ph2MPD-`!5 z5*b4$+|LeV5!)Yr;<~OoGVp~CvHxT7g|Gm(uY2d`XmD{f3d@b1;%nQLc$31pcKwz! zw9B{ZJ-uWeGex=~V3#`%fsi~7;9VpUc1x#Uv5epn;!`Bl`P$&!@Y>foAzXzlV4@fX zFM&DZeS4jQtaFf@f~ANp^kq=M?HL5Bg+)p$)hf`-nE_p#RV#6z(66LL2e*YxXOMKQ27eWgsmx5~o>5 zxSleTFWM%`X0V>51Y!~s$|D)n2>%0$wy9Pz&ktmlR&EEb;8#(mmD`K3ori&4ZMQEV z=&c7#US()~xNih=c*%}ISKSBvbze-|L*vEfu|$q&bml=2xW%yOMH0AjbW^Yk0xmd( zfJJ-yJcH3(WN%C?6Gzf)^JA8A8;1|D#)EMFE9X1F8Se@nnM1swv?_2-Y(6yFXnhcL z`m9-Qw6N)yOVJkI4AMW3fq+y`Iin8#u3bEBxp@TORx{vwSP4zM!7NuK&({bbe0h9D zO#JTjXRQ$kqOusu5URz-*ICv8El{Y{0nZ{yB7;f-5VHI~Yd0&JHy>M0mTJKF*YR7+&Iy6UuTCpjb-7TyefPVX_o zmk?-o<)+obrb|J ztZ7;ACiS>2`tTbs{q}06(9Z5UK6@hKy$oaXctV~-4IN#b2mIQB$jFLBOyHw*DGU4h z5^)%@27d}n;V`yO#&jpk&Hzg2VvF^jc-H{~`5bAFaMwe3i4X88zz|b~23uz6d$r+u z1YAeg+A~go3U+#$^x1Tvc~uOMQSY=z5sa9Uw;jNP=Ql<&B4Q#V2Zn5)bISHPkcDmm z#O~)~AZ?EhWY5(M!b<5fqjYM<=pACUr~1|xK*M&myhZLW*PPoiuO7_UFHq2^1_w=P zRJK`2TCp1#=(YBHkRKnkms^~$(aFZ_K6%yj)Vz}Gvx0L=lKCMSf_l+xDbDzZObrBl zSXl{Nb?BFDR+_`TlsGQ>E3FsM|E4xVP`6=S_>A-T6u(>10y$vN@;UoOTR;8p)0PBL z&!nEsNAj06adwg+_|BSZLhZ!R%Y@Y~fjN z@PMFZm$B*sJ^uwM_vCr+lfm<^0DsYL7zNs~-*z}^jizRDNAMn3h-C8aNy{oL?gzkT zf}o+()d(^~$_=--=D{IVatvoN44lh(01npHbLpW8LoqEqd@AXL6aCo~!xty5{tn>$ znqd4qopdNh<dtZG&>y@=jI z9mdDE4BlMTgWLogutm9n+0Ss=?lVlusy)uB%T z6GYk$_2rF@h>Kei7vQ#2lfz1J;S|BXks+d`Ym(b7zPRH&j8l&RdEC+=xx(WWO#iAt zS(hd*D0x0pp)mvx7aQJuKHI-3OwFs=gm3_iNwLth%E<&O4$P7V3Xd$6Hii}4WoMQ8 z5Z5+EAu)sIY_^;zaz4A+@lE6vLFn??#V0lLH8i%>yIlem1?|Iu2`RpB|HKi40kWF8 zE6PDKGNn5tG@VulQReuBGenLx%5a)#_8PD-8(=%LoO;yoqUq`dWDpnaGr)ry+rQ3n zdLQXpgIqrPVU(3pb+ji06alY?Up?2a-9suFOwQtd*1^PgX)f+w*R%LZMp?Hg8u@Lq zO5=F=jGf|D^o-XkqGnFKpyf;Hzg~Xm8c@WA>u3!-9QvxyQd^N4mBVvHGs(q%p-f)! zLIbRbfKrr0-+Faq{o&AqN*HNf#1fInbQDq9K*uINnQ^7u`XjZ}d;W=HsvgCdXFCmr zzS1+(g)YnQL<>=v;36bg5p+4_0}jF~oWwZk2Uc|?k6NZ+MtPCF&}bp;2iD01Y}(WF zbWdj@lkG_3GP8||(tUS3byO6p4rBUOQn%Ed2{)?o?JwH(N@`T$oD%l$+q4aL=W}bn z7+!>W!Q(QcBgzuDxcfDoP3lWwVWs9}sB4V}aW>JG%cs29(D!JCH=e2cY<}oso?kaQ{P|p-;<3xt5CIVyGi#Dr6L`61 zmRcX`0U<C+K0PKYdu{=)sPj72bw}9K-rei_AaT7IDsX4Gyd4G3ryj# za&g%Ob(VJTvXn#!iKNFIHLG4Mr6_9_JwA4O>P`3yG+8wOiQC;{G`*V_-MCMH_|*4) z$w`t#Npo{HE4X`nkl`QFCHh#8CR6d3$SNz{p@XUwQnCOzrWDxV zinYr?0AAq2n#NppZP}lN)k}AHElh<@I3XG<$YfLh#OlfZLe((x@5i+#etHahv){ts zNa)sBrxn&`k4b?Um#lbqIl1AZ(QZFMLCeY=Y-|$HMpl;|rx84jq91qE_SEj$)_S(0 z7E}Dk`X@x#i<}I7YDayCg>0WD9`)DR^NvPjk{hQ6Gt&NuLAwQ6I36n`T*W3=Fa~cM z)-!cEKiJPm%gL9Zm?Oa%3E{+iV%634a0Cs)*k@VqDl^vL`}-S*LW725{?5Z8$d1_d z$2!i*i(9uYGRttTwbEq~*e1KH3u|FpnniuFc-P$MnYm0iswQIqXQq#*J3*6}`Mt*h zrI6$pu9SMij=Pp8!<8?RlmF;fyRgxDp_)AbV|(yQKcAEeqA1HDT&EY6-3sre;yQ&= ztwyvz?{lJSKehk9BkjZ-Kr`3j^9 zo;Sd=$2I9J5eWV0_*x#vMkc(Q*n+`g1ziAKCclVEudZZnB%>M zr6W6rahYs8zR{zG*fPI`d=9vW+5;x%BlY-?HL~|yp;8=C$!G=SbV5X@sc7^>!!Toc z%OsA8NwWWaN(C{v$GG+p&rM*z!2Ov5-}3{1oRa zr)yM?8qb&cxD)!VTbI1sf$jI+O5Ne_@c+I9=tIXsvE}!0KlUWA#$;KC>Hp>TJ?X)B zhkr`(IGCAN@sZD-{(l>%B0@j{nt&E<5Zj9CGr12K=$=vt^ob}+GH}+98d>ZjVNj_b zF4wTVU_)Sg#dC2RSPj~GUR(6ddYp|hy>Sx}S{rOOPwTlNiqtG+I9|Yi;J6gr^uBGx*+U$Il*VGdR5k zvy|2^mLkQuno_aPS3ts&eyE66M*t`zs>pp#N6$%a&*vOA zK=@9~%Y!ttn0TmAcjv6iQ?i-2Z|4(7TYa?){l&Lx`UBaM%yXgCfoj?;UseKDQSGWI zIPGW9+aAzHy?m#}q3q*8fgu%#4KyT(2ft_!3Z#643js@~2qYn;M6i^_LCJQ@YI$;}U}!AZi?R$= zqE~?gU;I6-Wnkshx@G(;up91%6aVZ>EBNt=o^Rle(!N?lms)tJ1oy+wUg-dD$Mtx5 zx!+~#TN*UtZ9QP%$s3Cj09C*Iz+kk9j7@19Es4fQT$+cH#DGv${kZPH&tmN-$NO&! zO5nNWNr~eH+Dyhie%gojc(&2?&l&ait093(9S6qw6=|0;g9$2$AmPvmOYG9*2cI`k zB|<}{`oLp!V7p!d!ix`FW}y|+OF*7HV;V+*;C2SAt9_7wt_VwX(er6D=iSQgKEUCv zs>;^72ZHnu*&@wC+J#29R3Ocrc>^$wjNR|Q%eaB4w$QM#_7q5HJ-O9=fn+1FZNncP zEBxDLBnWo|=b&Ymh))qemOt|cdEnq(RYZqVMfx@0Pz@b5q^&JYh?c+?haHFB zR7KRqNe?NrP0Cw*IDK6;1ue-wJ$+YIh5vBW!}v|7@|Ellr4;@c@q72HUj z=DDs>MW`5*4o&~*&a|-m5@;5*&vyX_$zw^ShoT_4X4Z3n5lgBGOz~;^lB1HobI#le z#Mm4LVzcQkM?*$OOXU08z*)(2IQ$6qyENvRx#Vx!vJl}TSI^EKglGGvcc&{`ZDlaV zV9;;e!PX0+H0pk>s|0M+d$XnN;jAWMQpFz||EXF}>w!Dw|Nq{Wydyzn-q7kmfOOd< zizd?5*0X>(LnV{(K6vcMelc&y%XpT*vnf&?V1n#>@E3gTRzQU5Q0| zZogj2m*GRU5|Kt5peC-_k36@av_x{J>I=YhG8zCCZu;@y!+l2kAh`wA5F_cY-=Ata z>hTYtD{@D@PsFCpL&8U*oV0wCo+yD+pJNKuS{7WzK%7F>+~gK%2T09cZtkf(D4LndKw}`^CdL9dTf6HB30EDDsll4!A#BzmXV#8_ zFEY$_Ep82`f}oc}ikq98;s9u!X0EZL7ef9FupzU$i4lQd2&)GbG;@5iGD`-AmX1dF zf>sEqmH!~r_9}K}5(FlEb;A!nx;%`gIiZ~H5rmZdFfhVQk5fE%h~RWoWad@y&=bx7 z*>sPxB$2!pg{-ECI4O0+bGaNA%Yx9bwvu5Ikza}sEC5-Gegdl#Uh&V%#Jgs7_m79k> zb#j#!gX^-J2M^CbdN)e&1DPr z7dd-0rcG>9SR!wc&vTh2!I?23st^#4Sf6mfgGYmJ8lL=T>a$Ch@@Ycj-2IJ*`uQ6} z)i_0|Oibuf5U}%{%dQsJM%&kqsM5rLP~bx7OclluSg}Ecnw%&5o0JF~GnGilZT8uh z9!I)$;bVT2JWPs`NT7o+mE6R}05G4Lw+Y1WQ9Dn>W7&e&e*@smO;+OYp%MaPJoN`P z1RuT|2q#%5p1Q8x--LfSF(#cIzHoVWW6rI4Rn*RL&>8W=j@^@|F`6xv6vCpy8lXg@ zfjLN*DYcQRQkCPxylxWKjGg@#r5V}uhJq_#@BX5dtfHdpWJNl(fBYo><>b z%bwiSRg_nE%VEP=`bi14M`EJEK8uQahG4h2l{sHgKzPhUYz-PkQa*}u9{WIhKYK&e zH9@ICm`70`1c}4=`{oMo8x-fuSnhu0 z0NM~9Rdro|Os;BO>Vd#;?njZ;-eW=1DC}&zdsxZ4ec&0fFd>(9a_$2eTT*&oe`;Y` z7crnw8i?ViZ#eta;RKLTjygZXId5`ds*5_UH#YAMeO{Y-9V$z5ZuKe7JQymcm;*=% zd)3#-g2^-vZXyNvb+Wb z1zsak#{h4c@rny3T?^A}|Mt0Cvg;|YG`i64(^E2kHPR;pVY-bwPnN!UXq~T@t6GV( z?pTFr)+@jVILHo!Te+5_&AYX#lmtJ6vD-Q6!Er*lh8p?{pL^sP-^8gSJ?Xe6Sb4Y%1ohBXC?D(ys*ENWG+Ev@M zq%^z5g|2IYZVAypAnS57@zF>us-?ip2vZabE$fZwrXFwc|E*Ohn*kT8_3E796vhK4 z%8~jZG-NL+XN-ltRcCN}p`{Hjby978&$Hq&UASfeEM8NVKI*W=dd&w!+EVe5^82cU zA<=LuUroGPupucJHY#9^a%n_K;@yc+l@7+?&}}5MqZv#$@R% zJjdJ1SQWqjS?JEuYF*bEDY-r%yILbp!#T(<=$zKtZv_?REmNjou5D0b(V6OMqac0; zvkd|&OVR`Afi=>ez@CbN1RlReij-GprFm7Gd$57)5wIKJ!04MqQZxpfBvER95d9c? z0~+kOCg$F@PQpYBgR+u$EQ!2$?8-&ahPkeBO4^8eid7T4MRNN`umvfkQ(PWR7ZM@mz znRP7U*<|j}x9@|sr_i1XaQ{=&gGC6j!WW-eWL$B!CS;1oR8S_MHfpW4Kk_2=ZSpp` zQZ}lrkK}o9!tIi=+yn;cX!Qw{#iTsYeHYs}x6mJE;+iW8y!?y!_0|QU#&!*jB>jrF z(*ezV(pEPY?^B4&^DuQa=7#QYAh*3&l~fo*S&`Jk4@`sN0$DFSH$Ia$=wI@1{ac`3 zd%TW3FiXe$iD4WYC!aB<`dZ0Pt!hKW>ZohACy4bFgGNND|JebU)0^t5<9vs`$P?fr zH0+}oxn#^1yy9TmQj9D#x<3I!btn9;+v~TNG*q88q|3^RW!^InKiEjPT&yu8%&=`l zaiu8i;bhwVT^OrCd0bxv34KDtpA3+@bdvYqcOadru~qlt?m(uIFKi!6RqtPn@HwGB zQpbj=){H5n1S%{wW?lx@HuVl=^-rq;CcqtI$8X2}5m`xuBjD4*pTgM=M ze@tRkKfj}<2or?ZyFrDeBK~TQL|iXZo&YYTm_B>P*b>&rYGpYY%L~{V*kSU50_9mFP9k=@t#<1JxCFspd+pFC# zqt6l~xXdHPl*hxkamY2&70IZ&INuS`@yGSWHEIWAQ7{8%CIR2eQ5$n#koP=?OT4L> z7<(O*YC^8rd3cI zyQcx`cGuT-q~XLjV6b^%TGA!LG9vFKpM7nKD3ajOA#Q1L5nKW!TJL9RS8{=S&d zZy!isI15%ldQ-akiLRgQlds!*oiR35KDGI?;Wo033x-A%17-mM`aqpMAu@uEeB~jX zYMng+1rywAAJ{*eq%D%y8&@ulwd!AQ7$J379qV5+C6T2u+u4wOPJwQl5iGex?70UIT<9>{QYE=a7j}r_#Su>-P}L z+EBWLHD2;hgzZIvmTW z%~j0r_RtWs40mwJXN`AVus9xQv}-!$4F60msrfT4J+2m7@QCsKCeK{8Jv(yekk{Zc z$yR;-y4_=(t)sq<&i)~73cA87K!*dz9($N88`-StFU7%Q>&U_f{U{=Tmbx`tGn*#6 z#5K@=5e-(_e5@I90Pok7LOiw=+zq%sI*3rn4iGABx$|$ZgN3A$_qFpMts%Xj z6v6n9UhWH%FRKpFxb5DaK=H`%hDqZ#dSVCVBf0TxsMdl8-s=_%>e5d&6nz1+AvzLFeRXWaH6E9gpB$^3DfU;vCW|S6tWgLe}QR`#jBR3SX zrU-W}{P>+xcSNg6pzq6XOre*HVcy*HTk@s%E|HkLs5ySfq)gRyv1ZEu~7Kj*Okb6l_mc;?eIK;ESI4Ryx33k*nTauwC zU43u|>yV#LFw=Sm-hEzmDXk;c!;7zH$N#?1)?1I0nK%B`zq-j6s5Gsp7yD?Y{jAyP z*~Ue)*gw6OA*UVo&%I_%tpga?{V;?xhh?I-oJ*Ded;KKvePdS8;{RCZ5>?XI3FTH? zh^>W=dH-*e4$F;OO=`~HET+NFB^R|>-@R5h!@1)r@4eQ~7tavalvaLLy-*JA3nn(l z0>*Z_>2n2xcb>+)9s{qCd= zyaBkH9@PbC_A_&Bs@yrfG*YJ}o)@L1gWU-P72>H6&|A5Sg*LzXWZ-exv;-a|ql{Cu z&n?y+POhjmt(w`%oHP^eCtT_<^AoC}t>qD*vB6@;x~YRD9p?S*(T5%0GFF_q4Es-B(X{L{H}F52zX zmvNp&)NT21FMqx~ksU0$o)kas?kD2vbl#I@!|}~YQqQZaG|V8wQBl#$x)e0PrU*+{ zVe^aY^aZ1xRMG)bpX`;Bpi~itXh?wu`(R&oq8_aX=QZ zu#+ARg?;%Ylp6O!uWsFGyeh51dDimP&zV^!;TK0S;8RzB7sn(1I3HGso<$cI*;7Zj zopVnxMk+S=5;*i<7YDk{$DiFYHM|Iz9p-twQ1H`@+?GQZ-!bGS%wEu3;F!NyjZHz1 znw)+FmVe#n>pXO3+Vn%kj-Tr52QY}!hK7=JFn(HEho3)}TYQg$zDaeLK8o6RTeou> z;^1}UTFSS`C3&jlobmv@I6(N{b+TV|4p;YBl6LbwZf%LlXIkZSQi9ny)TmP`-S`R` zrT+f#Q((3b=B?YJ`fbo3tUSps(}4L>QhdRHPWlGtdl~oFZ{3dL{s5QFP6NQw)nCp z6*A4HqR))>bE1q!c)07cZ}{q8Rn(oDv-cCK!8{N_`2jiJJ7;VmHG=S9$Ba%;sFHp* zU0mP#sxf?eV)qFpowE;sXYQfR2Y}+;>T8xvZh9PfQj}AlW`j zy^T7o>0#tBtsXsxAx8efz+X&Wfh%2nG(BlaZi#7{BO&NDJDT8QlH^GRbPyUxI5fY= zTkVT+;lG7C$UTmo)W3ZDrbOrZP9Nv{jqIg^D3Y}XG&v4AD;*bf2A2)o^YOd2Jw&*h zAG8~YL;U*F^7CD;&#d^rpR8v&w!8UVw4V_FimQB^a41Cfk}vHd$G5}yS`M*G^)2{{ zR^dr&g?1H8=~gs8PT}smnL)1KGa$ZcUS+*5SYqoGnE8c}BP|&t$;khqBmaG)rO5Ta zX+`nJ^Csdy|6{aNARPTqnVrxhzW$zesv}(?CO-Bsw6C)KVmkGCnMLMl4c}vzU3)6C z=8RpuO?-ip{(U8F3Vigo=Ge}B+J3AF1Jj=V386Iqql*&GgwO{EwoCmXNn}UNS1x(YuITvY z-fWWa8-#0mD@R0euL%QLrfluf+s7^&fK|Mek|z&1;}qatmM( zb4m>aw=3io0H;;-Bv&!-drKlQ^P^Z5qVH*nv1x$Jr@iSjvAIkzaU5XuZLoXs^-X2f zi_Rq~ZeFQT!HgRnHl(h3MZfPOEUcLHd%DQ}-%(p;n|P zgyrU&c8kX4*bI^Q$G_{@E*pE&f7kh9|2Vm|;b62-ra4qc-22Z{;QmZu`49+z zU{iOy@#R6UsBR2%oV)_@JUd1}oruB@t)7~@Y`ewQu|JdOhhwZ`9z1CMXXw2#z* z|BDw?Wn|KOh}q!DZG;3Pyde5@sA{t!wd^GIr7I0!&@vLIUVR=(<`+V(duMq5sXfE> zKjY>H*HSgK?AmO4B=5IF?lwGzEpyiqE*|N_GL&L;L$rRMP^Skc-!|XEEQW^s%VqH( zM^A69)=iGq6VZddMhFm;-r%md(DZzOZL0o}5GjsklgZus@R!I?P7oGD;5F>9VM<9y z>o=e2tX8tix9P>aczF-&c zm$CVOCY1tbuclL%gI{pshdg(;-1imPI$nI!K18r&k<5A!{2zAno2!E+XXOqaO4XGa zyOgZE>(09j;F|fe5+x?}7ic8k0h{A75Zq}L(F(g9AWo7CRpkNQvNTECII8-SG&_Xn z5e+r|xlSU8!#Vnh$7m+(x-)PX%br||7j7T08hK>!!-YpkRM*e&$3vGOwUQPR9!H8E zsPhoAm~&^YH2nlbqSl!2UQR<@`FPReyG${sZmyc;KDKi@_D}K8@3)G6(k)wkQ)+*6 zmxJBBJ_4F$spBVh-3RPC;f7pHw4@8Et5oG?m%%Xi? zxe&l~E(ZPUYf@_X_nfD{GCELaN3E0=EyDf#%(LH6B$bZY&`KJCvg2g&^w z7<{}H{S$($AO8FpN=!9|@~v_{^!laKb{Gh_F>{yC;B7XkwUh?Y_ku;_TspO^agw)S z^)?%)t;j7WEgGC`1V2Hrus!zxX_E;e@U=it!sf^F`L6^YF1-RCXsqYI zhDklIoU(HaDv+cSmJ0t0B%EuPiBA{md(MFW5I;K>6k(;R5X=3rx&mavp4ay)P*1D$ ztnn2mz}s2~iPget&&wBaxH1g%^paZwj)AS&f}kY_l&{ql(Jkm-xQ0^W5exi=bCtj! za%BDl>Do^7?~2y(xt-IHiMjfNPMz<(C4v#MDSyutMEFg?c?y_GuZ+6ZwUu?*v#9=tf z#Px{2+j6RByqmp|AfLeDzfZGy2F^q-@T4a3kxvv|-)W4fbT|RB=bq>Sn&Y-_0J5dP zC6ord55FC28$0!DxhMNDQ3!iC3x*VyJd3?_qNs?Dl8<8aTTpJ4W$_z7k4*WOTEF5_ zcxuhzQ^j8;L@i$5*{-6ft(?BK2v1=c2y_Ws5u&mmzxol25)nXl0)4dUz5@fwB>C_u zmITt@=U{1Z>cr|A__wPHhT$vTz=lkkw+jfBY_OY8OgSWi4uLOBeLrxFA)`1OPi5mS z<~Ak->-u%{_=IBh^k!rzu;VLsq z5-@Q=x#M?CslOspczq;L%C~F^L9u6-)sqqv$rAvEY!$uzS+!U_fjcSWSruqJC zZ+39FiDKl=5J!q8(MyczrQavNx30zW3j_n*V$OXvv|es*DsU%eO;njbl5v&+yw+#3 zGSJ84b9@QtRVWn{t^f!zS2!zIhR#>_mR0P4;Y|bU^o`-=E;X*~mhZw)>m_3qAcxKZ z-`CQw>00ilz&c0>D6*Nw75H!;lJ9r98pzN3x8H417kS@_eFfCayQ)<94M4|=Y(hc} z)J2@R2KvDTXb&@ckoZcLg{$&(vtrjZqq#40;4yRX;yb}-3OXd+;kdHo1K?UXwYKY) z@eg2BGlLT3V?abdf7E_z6nx9Xm6&7YUn!F2fWF)JsOn$sbys8_?&Ed40+MwbOCHKx z>-x>vds#3hCuz_lQf^E%eC#;u7Wk2^=ei9mrp`*()_OM%bHfEK6OBU93F0*~MEf;( z=)E*6A&?eJd4XhJUUcV(Q1gaCxpURQfNq+!0()D}9EopO085lth z19V#yHNKhMtooF@KhI-qqB&C)q}fEjyB@cfp-LQ;J<-W~?2^3;_7qch)g{|*g{q>9 zJLW4QDdIfzymg>6%>n%p1F#5XwAlx(p(H6NCxHfyjJ5@m)H4szWEQ*Tc)T$f&XC~x z2xLgK0`!IGi-rm*WJ!f#Syx zY#%aV&~LDD_gKE}#xm_tUP9B)Bp?Z7H7-ppJ&;%T3$4PI1LUdcluBAHXx#q9p@g=P zHJF;;jV(oS!@@mUM;y%qb0WXmzn6eSi(0nb9Dm!Bhsh1S1BYD7WRo0$meX|O75FRO z1tDG!NkJF&u83u+LSh~nYY`_z=jH@`B%rvsR!TKh7Jnabrc}dFWNN&lP$IJEL#_XK zb*t}qd;a*4=TcUsRr|9NW%inZhw1az0zj!0zrpBTwc#zJEKbKhoOHeGm`SPbyjrfS z`*yHX&;oy6*Za?G>eKN|+K&JHX+lGRvmvzX!#%YbM6yEsJ#F4`N50jMe)A&BrOFX| z)jd;`By9ay@av}OmzKXxp7R?Raz&$MU=2|2BKi($zNmF_Vw+tUL(&d)kIupN^$FT!F*xr6P)#D;N5<4m!*x`q1&n)S4`8la7g8K&0Hd ztB;XigbNDQN^?Xf$P1MR%QO|Agv*+6aUTx;{BU3%V<$wJmJ!S+qJfvMT{JaNymOwt z8z-qMM0FXgRU@_O$hIEy$yV6u=$bVCA9vECha#T#;jw)R|5V@(BI8j8rX>p{ZkZxH zthqBvP`8J;{dSl+z6eY`kXP?kL=4jcu&=y$4U+dmf$h#QnvxyQ08k@YhpX^M6 z3oj_-w4zi($9(rCDqjma397MpLu$Ly16Cc3_AfKuA#n~+{p}}$@y65TO;wP0!syzG zF$X&y`iUa3ILF77{0@Khy(+44>*&-o!{{Vf|I~iFx5V7 zc`%#fI6S}~L!|<7%NJ$}POx7z6k!OsveUEhTbGb=__q2SDR*5zxT*nX{nQEi0S1Ad zNJH3*Ey6@gHF!B3_K6X`64q3`Gtky2bKmF-`~GkMx)XwUfQZzHIbl*9QBw-F1kQTm zbsHjBKdmEFiij9pq!lQ3qk>tL73&^6w3g)lzbt?Qsv{}iF(n-H6g>O!k{gmpFEve3 zdlX6hH;{O?Sc61`b6rFp~(niJ({ z*YO=lw1f~Qsmnbmg_y%^!gR+YX?@TLrjJ^VVqNOX`gE)F`=6V^Xk?I)mJH^bGvPh= zIMMt2oDJlVd%wbji^*A#yz^;~j-TORR}cl|^&2Wb&jJ*6ar{anYmwUC?G+V2t)!@f zA3i#w9P#PAaM8hKCjKd(h$wBKzzPf1VS6to;U=1?rGTVeMZY~dl57Wvk}^Qhif*m{ zi4gP=uS57cg8E6(u&uYODg>kIgMoBy>i*x8X@{~WGrU~wI`>8J&YyqeBH$|0e&hk= zG*>`X?`jNfet5)AfHp)x^_$kthWD954b*0)q}oZOOlp5}C9*$%bnpTs0FI zed+l+p8B;bDzhY0!G`-0Jz$N*!5qmU-y`YW zS*^>x+ix3gf#*M)9i|qrxbzp2>!+CnF=FJr|LQmCw8G5mH9k>=IoK8wWbBCuSa;9T zqT7&!3!q2+Jh_NVj1WMx`4}3GamCO|q(V-WiHi}xid_ti#KEa&J0^)Hi8Me)ptmMB z%{MrcJEKPpJQ|fHmm-?)rkA2yt%QL-Rj3*+9)xOhwI@wNW5Ft5&mqa5L4l@R@`^Z* zq{#4f*Qe$pV<{{HyPmtt# zC6sBrq1;7e`le-lT|C9?!4iW~uYC@QxsGhDx&`PVCL|42?%~uT_-|{3bm}+I<2Ray z=?onOD-Nc)BG(9+Jb=3nT{mD|!3%AZn93b`w#uta!v%ZQQYS*{tt#tqXkV&O^{;FJ zXPP>m;PQ}+m9p!Jd*dnVwks{IbaVwzc_I68mcSii5m2QJCgi|8XVimQKSk({<-pd-A{Xa zf)e9lbILJLZ(e{UWF-|S#dabtaz$yDS?JfILRq6v9;maf*b61H9|}dKK4Q3IuzN=; zTsqTjhZVts3}cHSn~SY2Bngrp#Up^GdN2^D$erhIj-N%r25iCwHio)G%IZn%F1{u3 z2&S$AX`Us$o)3uQIG;I4TD?4SFKqS_BjC;FNm)8KZclMd@@-|m&}mtBnlIVneKrf- z8azPBT&;ds?mfHR_W;nKU&X0PiRItAcMRZe9Lh$L7;09j6xX;Tj!ORd>HS%;&*78P zDmDXgQpK)cg`10K>356gR{Wo?q2FvAV)7-Kha_w}IwejvX8Jj{|QgR)nX=$Pjy3}~2J8a|Fqom7Iw#~&gZw0hH&-)Yh`WNL| zl1I?GNX|K=2ZPczc>b4bcpHhAl!U}|G`e-*(%@rqN0?P8w+&vG7-~}hP`zOBxb&aMY;vk1O-n6HvW^J+aOKIo%^6#i?Jfyk{Ed^q?YcwP z8p0wpnbVi`leJicQIFqwnI+2xnbed>FH5PERRv0yWT~2FWToO8&Os_X?#BHv!B4^7lTFpg-JZ|h=C4L} zC$OxrC;Ub2@;rk)+~l+e`r~L4@@5-+V^Ne?Ks362{a19+C_}S>;k{b zooseDXmNL9&X2xY8{rD|K6&pE6XlXUNKWpvM%U~%Kg9)Q5U~zzb5(g5$_m%Z73uD` zR=*;j3Z4mqtV>N)&MI|BabsVkc09R71+CBW2t>=;!B4ZTwmW%k+ zL|r~9Yt{p_SRXL_o9)z4m; zRPCl^Ilyf840Pw{J(4>C8^oeBT^nBcp6G^8=Wz8yUDrdnmlylKp)CZSk zN_{MXVqu`H=ibO^3xDgy`Zo0iBdY|dc-&vTCQexj)nuU!wGE*SUHj*quD|AvJkm(- zyHr$ENrf_N|FbG5$mB$QXh`r(-pOE~jm!%#2AcI5I`XtHM05{rBOhm$GL<$#6QuDIIZ=+jZHc?^=uY=e%xqF|2`7-``OgzRq+T70YX(t!r>Vlq^ z(v&8Zj6Jv6i&>0u3dAhjvg*lU{)DuJywG{mZb$g1S#rc?_zCPxHBN*8e%X7SteY=y z0fGbF9&RvVXkV0i#C)FT8q%*iCYHMj7lc_Y-(VF&H?@!de&Tw0qlB~Pk{R<8f4ILu z>ck;R^L<+1CH z@3NQ5k7IvUZ^(x1Q7y{pButu_rL}oEr?X}`UNM?&#a8`jRc9Y z2YpZ(_xZYKQ))!rE}0_g8(dziS>b=wuqn2PU}*JbsezPSzNFZcqu5Qswl>V`IV^dP z3|pt?#gBfGDrIw_>Dx#>5u>65Z&T)_7j<-ISqV+`G!(NS{#V; z3@|5X&~7g!-YUQ&`9b8$y#fX9=;!MfW8A1n6B+#_)Q4(Z$zkHma?I9`rbcN9&y4AN z`STQU8{4Cz1)5$xqPLh|n=%>+jYV1pof(v=U&#gpD$>Y!b;B@h!lUZszZ9*0!Gp7-AYl zh8n*d3&eSmTS?(fYxYIy?jk7>2V?co5f6(!cM&nLD979Y$#Yx7py6QcPy))w#``NqJK3v%0*eBY5pVLN0 za$>r-wrpeL+Tcoj1ZBc%Ebf@QGHrblnZ9${0`K`Mn9*A{i-DdpsyA280IUR#&!AwtPPtwk>O|?JK&x%U;86P42RL1SU0PSHx zn}(%;3-XMd&FAFm>}JmzmrQQ2mmh5;2y^8hx-!r7#hqYVv9i0jPei`|L}v4+sEwJN zg6WInaLz$uddv_F8S(V1YB5^MivOiUKh!_3;imY$l&z9BL?k&%urs}~}iern|n2R*Q zGnR+%yoWLS-2zCi$tEkltl$ZDO5}<^s2E5uo6*Epm{VVayqi49l)QMxo&|9tN_txM z$&DZQm7cMNW<41#l5@M!w>3|XOn|R~#B)u4`7ByBmPL}T=lOBzIZqT5=>y*1!y}r$ zlR>bz$#AlW7Ln4B2~UB+wYVD=8`IFiKN|DA{~(ez1kJJ8)$B~tV#~+3r;q&eE~jH= z;LFX+=OsU?O^lTn&XoWS(-relL^g1UuzV1Y$jvYYbV5YE6_|NnH zbhrm_m_%Fp{j@fAyhiVl`8albvdPviA zzO)4Aq`|_~46M%zhpeuRP$xRW==_l5`ko15ePdnPfNLX3iYM! z=1TsDBJ(PT_C1O<;NI5q{xely_4B{hgtJs=t#zHN$WX*mdx}JYTI0s|FaESxy>wUW zDJERR->@xwB4(#Vhd^#>=N!jVxgSuJy9d(5YU_wz2};~G@T9-4p6cg5tTfu*T8WQzWddMocuorO{5)<*0MvL`<1W)`PH%lTS`iL zK}K@+F7dKpg2?aOM-R{tj|z?wBs|oyYpVtRC)EbJ-?3C(coB4#Mfms(K+Rgf!Mk^5{^jK$Q^2kbB@@TY9}rCcysA$$2zavvt!I_WMANI& z-ZTI$(G+@L0#EiXkKPk4bs2yIomx!+$3QtrSN(QL1X2C|93YcB8eFDFI#gO3d_j-# znB6L4|Jr-WSj|`FEF#{6B?)Bg)SOem*E4LpM#P!^fyaEEXYZl_GZDxJgh=??8d6#t zl53KWXkOzx=ZW@nb_#u|vU=TZf(7lTx4RH;(@bF}P6PQ2R_ZCiQz$LL&0R0O$5y&o z_?b!GsX5Jt>I(9D?{g20ZiF}^4mmuZGT6Ni%KGWHAKGnXQ#@j{pPa86Y|dgnO_WtY zU7VGqQj?~el%H(^eY4;k8+tOr^5d!VeI~{1;qcMMj}n0;H&od7sM+XE7!L?w4j@N= z)}u(RN_unE>(vQuQ}of}5LWA6It9@>I+#CH*dHC_!|w0>xNIwgIWSN^G6X;fc6tf- zhQXn10dw0dS}`fmikK05Y%jGMNnW+pk40^1oka##rFG8}JEUCWsHXV!m zG5D^21LqIm{%>vrg5jT$hskW~Kn0Oumn_u}RUYNnA)o)DclWIYlKe5N7{BddOeu` z^`g+3Gik?gxfwt&t$&*(Z2LjJ%lfQmjp{xYzVOi)peXs5gCta{ThDFhHEOIhW?!4w zcz>xM8k}piP|(hzxs%vTh$TWP74w>T)E@uET%$@l|W;LTkw$NC()x@xG63Nn?;&2c#H; z@7YH$8d}lf*S!nZK${MW`MMb11t2#5d#?x!6OBHbg$$jXf5U?J^z(g#W?&vwQlzAF z_+`4INEue2^q_7;Bay~NsViU&v`14PJJr69Aw%j5Amjik?qitZlJndr@447lK!j9j z;Mn{JF;jEB0?D4={k^OO6OPffGr;Ok^64IqSdVLBJyt;a7dS&|UuP8-y3O|g?Nr9{ zKK|YiKttSlBM&P!Tq8@7qJ0XQem{N8mGW|tEChh>(yjVek(Yjce#ao?8h?;JYfWY4 zo^HQ0Ry*pCtKk;RdWC7}Ui{`_VkN}-SK@3x%f<`mxrWliB|VzE4&56$w>bh<&&wAf zpR=|nL(=)97j4Pio1Mm=zM5K?jm+Wmu&H|17%%YrZ$6iEZ6uuw%s#2PHk)0OEL6Zvcdg&Y%YDX!IspB7w*_f{sD zMhW00`d#a|eKc}U({KJXOJeiHCuEYHq00f#heam!_dEbBsqbhG2VR0knnY9C)f4cQ zRgustRUSf8Hwv!HfdSJS^UdS3Dg`v@U^NyrOwfPfq%8p&Wy7A;C2-BTBeH{Ti;mv@ zzW43{KiEs7n~wY35^+|^I3$Q&G6q%T=I`*RNTKgYjJ7~yi->1-NL@BbW(1@yVj`k< z4f^(;ffbj)*(?Dzw7IXb$$-+sivYMu^VXR*DM}R+Vi#FK(y&z&K$kFk;7XJP>ftT$ z7m>+vzEY+$m8v1k;c)Y>vdea1wL;11 zS7EBbIXIVOgd7{2zYFvf>k1(bl|8)Df{+jffTb&62kbTG=!z%++Wia4d@_0~A|yD4 zwLc6-^9%v6Gl8t@P6NRYdgu6kq7dOf4SLxacY%BJC?Q5{8QtbFD1LFb-8^_Te-dn% zoErAoVhcndfmRLq_4^*0&b=FrE_N*!L>i>|f!x(bhAHVjnT}|seQ|1(6*Aug6u9^# z-YAsp_J4`Uh8t%ftL|lH8KH24A2kQ0iho{H+0!@!!?Fs~q z%KzLy_!&%mV%5o;V+D&?@SFDjwKxD$N9vn&fVQRg{zt0lrH4vq+Tl)xq*1?1~xVM(Yw3LqxH`>s*-ja zT##8bDbkcVpbWqp3I~(Gyhz=BR!5?GD*g58ww>%I)vWX(Zl{vyDoDWE4a^xE`k@l7 zEI$w8IEFm}Oe_@M-cTlKSU7*9uIG%bV=k>)kesS{A*?TVmYKzw_vyq_%yERlg$6PT z$e5&8uH+ol>$u?y(P)qpzkbzX)Q@E3+}50Gh6afrnFFEPTu)vW*{?DG7w?ZN@;WglKzOV170 z#h;)2BAC>jTlePK&pp)m=!pqig?EtZFd6b$Kpf8Nz2wD7<~Ma69y|sIFTNI*f*`b3 z(f#W-mme4xTxA53%YUm6CXq!ywDjzw^f3&RrzN&wYUvM%qGmZ@NFZHs;c5{uvIOsc z6X9Q#)rfE=TiL`#(si49?fLP)-VT%A-{dx1^-kB@6}F|DbT)T>m$!W@Pg!IIFLsj( zP<@%gz@=v>=wjJ^)ORPl#`W7sHH(VSRSluk+1N)?(X}c5qzO-M3mlIGZDAR+YQ&!# zMTh<|n}|duO%KteI|4%GfM%f^&+zJpL$m1 zbLng(z-DtXGE-|4k>vn=>Sz_`x_Wz^ms0k->ow7O95_%$ld1fO^j|fNu&m#gj-f2a z?U98(u+GV!Y$wY=ia0(*FUFvIi?=?GuW<4TvKgEI!YeUw=rVN?n~1?F$wM+lAb~;S zn#Uvcxq0n{u~=)axcTY8N6dA)<78F~87c9qpt*M*_N0Hvq$#WH48;;ZQgfA)iQ~}{ zm$MTXK&~iNQG**pT`bW7JDF5KK=7UrKhZVOm*|+Af=dItv1W@ROh;K!PE2&$owp`l z5tlu*QIU_wWY6EPP%fZ}l1wrtG+fc--q+{d`UT?SP4agRl#)?Ll0>rz_|;80r2W1( zTTL+vAk!p2138V4O*|~~1A*!Hdl%*NQ<<#-uPSfIGyolSlpiShfoy>$BqBfdaHQT~ z2;JpBUXT(eB-EZ@OguX1B=OU3ApJ9taZMR9Y+@zs!j)>f^d)5kj&ezCmA2Zcb;e$+ zbpja@`;>jbxFQ0F!1PRO;_>{mO z5P$So?l8sel3#zB4%m6Xq_HBoMIJKA2e$lU?wVX1;;(7I<2Z%~f;Q+2-!evr97%*zl<>Z)RLOgDSe*p$7PP`ZPb>tuZY0Oy@dmXEsrfJ=!7% zv?7uHwL8^u3Nc5gdz%tP$4A+Ny*~Up`E19PxWq5zrD{3_@)M~C`rBptWH!2Bd9f-h zit%tG5X>e5i7E4U924DROJHHH2p3US60-9w>;SnDD^Fg~WUb_dt)Z8*%aR`cr(T4t z4Qu5WEiNt_j6|uSlHnbH<@9Oo#I6#8%Z`5U(VG*r;*Xlib}*LOO9C7G9xFC0O$P}f zS|2_hkvz#A_ejrF5qSG_h18YMS!HBANC=`0Caj;3P2Jx8TfQ@o$YWKBo)OQ*_d7A2 z$>^vNNkrN|t!BMg^qF@fHK&m`?7GA?XS|}Wp|bbZpmAn%RDt`lDA>+K2GXW5Y|o9m z!AMsMb+Le}2XS|x zrKMeA)C1%;XoWvnXW%OC<2meH!Ac491sdDmBZ>71xRQ9hF8{qW`aSpZIFT%B5LSjq zc&V6&CPn-f$O~udUN-Y&cZJFqI&gR|Y%m`1AbDCYf9^{i>cbY{2K^C_mKb&5w8RuY zJh*to1Wp_d6V#fJk@CE{26ZCuGHD?tE4FB0SgXSSo1k-TDn9MOlYhR$cE=Qw?z&X* z@_X3EUTcp1a$t_VoOM-t$a4;JYQogr5gb%MzAjOl^qK9{H2R=YS51|U-ly$(t*-%& zp$zqSLj(jyhhH74w-f2^g#%j^G!w1012=aAWo4*HrDJM@+WJE(VN2gqommYg+d3vB z3kIx+R1X`5e{o)R70*Q-LA+s7#4_1>4j?BKWF;P)5y!s;Z8U-50CV;hyt-{sbva{PkYdRYA?fgn>;^yJfI( z+}4Yfwn_h+o9YJyQ`u)vzhI#Qn+y)sflx6<^##uZJQbegx~hoFJ%^(NO&<57#6eyW z%XuYy{$yfV=31>c8x%MUG8X~9f^$^DxFX#aDa(PG)I5|1fdRNtkQ-^`x-}3kGlJ!| z_->q@Djo(k5p_O~7Tb`GhbiM(pT({X7T=7oDw=B~;$UIhDOQf4+d(_4i`*x1&~h{) z5DE^urKNa@r+!4_tM6_ir{5uQnbbZH`q{7bKbg8|H7XkQ8b$SMSEq)g3|*AqyT}|U z+F9DmOXJAiNyb5*Q{UKHLpOrQl=r9xc9U`guJPZD4L%RrWzDezE<|IzF33>k-6_a!d|h(m^$mQDAzXH z(+(ls2n>xfG=fNXBZ%VAUDBa|ba$6DC>AB%(hZW*-O}B4p7DI&`OaT^Z}-0T1@q4P z#J$$~EspQkHG^~>xv#{HmRaRHBbdpA(9xU{wbId z&GBYUGq|Jfty^+|vB+*#i|)<-Recjuf|8<3GM#~uVKfOGx}($tPmo@eh0uKHzUy?% zI*|I)=8rw$-Cesp6X44!%%C>DtkQxagvsRC(#6IYs@57C%SEXkvmi}OWhXiDMTyJ< zw?kIAU79cpLWaZTGOA`Sx=c_z$ zzuEpYy`$L3RIfhcuo59}c-MpM*B`uubp#P)($(i)l_uT3+&?FbxRIth=ndKXU}W0L zb;(8@%8VvA!i?4N>EmJ6A|$ zjHlwA3Xbaci2nE2yVW}52h->}VnIf$oZBK_X=sMJce`iY_IbvEP#owmO-^wb9js38 zWC|BTWb5V70JkjKRmL5`=uD*d9rCq6k9cP!EojlQ#Mvd8rr~t))A5m}!1sp#i@R-( z0H93}XfZe0BqU13%wb^Ga?>s;iIGemlD%_~S9Y)mp4)UNl?>X_QRraA1o=Fq{Yf;7 z4LOZXO>23x>Lh(4Q(yrWJ?XX{+&5=?AmR7gN=~E(E79I#{Z^Cc64Zl0Co~yex;uZ? z{yJjx3hdWG^r(_n&4CCabmaxlrkv4oaR8Mf>IF48(-hH;hpdFXSLflDFyn4D3gf{- zf1tpo_g7Nb#xmX7Fqnjy$cnk1^$#Y=5>Q>esv<+GF7216{J@PE{e-Ou-=UT_gOI=I zqY@AAFsb64`#yhk|B3K!@ec}LhgKOa(xs?FQe&ll>0{WaiKD~%3XNp=M>uN7#t~S)YV52 z`w>fNT*xY)>5D{sa-wCwt#5EAZaV%rk;4_?!I8utgc zl3OMRho0*{;75{Xt&cMJuC$8|{*tVy zC#~`wQXD60LxFgl$gPa-ZR{OyiP(5ahXP33+qQV(q~fUBLkcz=6vw@scV%acb0K13 zkqa}V=g}QV%r*1v;l((U0ZG~9EXi@K*jQ43WH5xd>U?y}tHT|S zng2L+c(xJm-*p{La=x!#=-6;K(A6rbGsu9exXr~{+DY(!?pD*SuiaxnNA6q+*KbE| zF9pk_Yt_Rjrv~yF{aA)lAS%!XlZ|q)awkSv*Zuf9)AjGtA@Kx{BmLt2Lauhh#_|rm zZ8(A5)@d~h);xs>-ae&(!Jcbxy;=5){56SI>ZLD!k&8?XWPioda%r;ghfS+ndjp48 ze_9HExHMaEw~$`xGstpy5t+%AX|$RWap>Q15L8msU4lP9=#6Dft51ZwgK%^k$%*eTTlmm43r^u%5Q!VCh%Zj+gKfmXflA zykxW_t2VsZa*}tTbs5t1I{#Hz@B93#$T9m@!8|)C;|Z&YJ*N2@1;IcP>J+AgeZT1O z7^r_CcwgF7Oq22mWv%fB*5qt|7iqaeZidm0qNl!ZHNIdiV^^@jMGpJLg;e!*(vYeXsHdG1Ql5 zB~Fg!acny;`W!J}in#NQ2oWpN8`j9Ez3K9JG}u`1ZRSKgXLi}(h+^uOttXC3D&ra) zYCrs>@km|KcD}J}HI;E`&Aw67bh`TG-i}7zEatcF&c==2_3)+f8TGV3qsS1Dd)lKu zW#ez?LjIk%_Cr|#BP)5)j9g=tfNo!lUeO;eC4G4aZhs_dPVM=hClCzlr;zWG3{Pv` z7W|JygWy(Lg(J~Xy$2~nzBSb;MPlS{{ZJd}uSD*A%rDZde(~{m!J>Vt$thiSife7{ z?6L~=swMbuj>Rssr&wmYs=TR-ndXv-Q9QCl#LEt9rU*Nk67dqqGuoHZqrMzxbQxf= zQ%-a!3VpnJbWTM^qiFB`Jh_PPx*(FmNtfM{4i7~<82!q}sqD-0`}?>jGjI69?`{r+ zRZIVqcV)im$}%5)KjCg+n!jN;jglT5xkRG*O*J#msk2@(n)lx~)g5e2>PP3|g8!b% zKaQFoF`f7%X($)RrsRBwugj9cr5YL!Xizni{n}&#tB8|BRa&mII+;m|v}k|Kaic>_ z!8TEt^ln?A8+WWg@i(PVYRA|vW4+`h3ikJ&MTr@C6qNd|ln1?Md?s~srcZ5PyM#Yd z(D*CECLsqL>$W!&tOWOQP2f{&yt?>s*V+a<4Q4!w+^p**wV$h9q;4W#1#Ag z0!O>$axzeI5ktEF5S#!N$PlbP(fZ@T60Q@{_meu;kcD zLH)@19wFNz5FU_+QOE)=bvbw9;}zj`;En81aj# zR||^q9`(}8ca04^AMdr*qvIqH9V0ErycA{B?gH}5MX&IYOV+di=Nl^k+(EDc(#F;Q zhe3@LhXMj94P|}a!zRWc{rYi6{wN6mMM*l_o$2dg$<{SIgvwTaOi@Tf=nFGWh!AZp zqp_x9j=1^^X5irkl8i1isB836r-+x{UI6XV1 zt_}0Yaji15$T6T(kF=B|4wS6)=D%iURC=5XHNz0(%PDE!PUZ7FzfMvR_Hciwed4k) zd8B35zys%-tF)5(0sf9>uD(;~?q!qMyRwH&Oe~W*S~U-K(NSuPhOHUYj={ zrX2?w37(D=y}^jcfGrOIfp+abqM_{i?{t=)*j)@9awsj(DF_7ncD4|aT4BJejFSNT zec7`)R|5(`C)3r8J;2z7=Foj3lu+vXA1%z6eo>-1$lxF&yjCf`72MQ1wBzh8B=e;| zgJkC$M66gSE?iS~kR`aew2OxA{@mxD1;lnX_(=`}FTp2oi_!z&o6`n~(2Qpustd*-wZajCu@C>0sEWP$g*ztr+`Fbj zom6R}ScenDz)DD$=XvU~ks&u_;_ZyrRoPl%;g5XEy7xR***N<@)xa*aR`+I(9`O8O z^|fXZNcO^jCJw_i^J_A_1UA^=jBCAS3a9x=yKWz?F?AE?IMAVii(6H(NPN8$U0W(RNUcTFljrR!tqnTD0gJM2740VG= zd{cE(lRzHgcLeTY)LCi|*$crd`vnVGGU`=!ceOY;Q7Weh;=9n{8%qJK0?4B&(dnXXy ziU6|cOU6Xv1ZG0A4_2V_k?#Rv3Rn~S0ruwUh)aZnK-mY6F zuSIkNNH+O*keui1|N2lBZG8-2r6o6F$NjwH3_rr_h>}o5XunmGFJc7l zU_>oy3oZCYcLhMs{qEFuMGT0Qrkty#}XwfyM)~j-J6$F7*7-X`lX!pvg+JM$!TZ0k(YvnY{g1&#% z7HC_ow~TR&;e_Y9&QYa47fT^!qpCtvJoP(efI6)Gw3W<=G|Tfy!H*O2h_%gYDeKb- z%1=NXm)%lM=U2({*b@Y^QG5ED!`XutDbxvX;^#N_&VyYswD!LQK<6oqdlr3E(jugD>>i4U&eDt=#+(7s5pS$t!cQpcqSG3Sd!ovRtF>zUVGtD^^Z@0mtq6_VeTQ3}_1 zRqZ9DR}V)X;#p09Z?rkjzFA0*Uww1Ufri4a6LBd4P-z-*wdFFMP4qk;5&`C@lYNBo z_tU1$^#?CjOK5;v`zjQ-LQ#Q1t=c>CWqp=`QuTJxxiXh)az_4#?`@N*zB8bUgtTUhGxfgCMBMe;jm4kjj~CL#ASf z_7gA++Kl(FoMxc4QP#8Ars^B-u=WC_*B+e{U*c)ud&bJO(BiZN3Zj(ZH!uJfLu-xO zIyuoZ`gA9;Uq=qHy10$PsKR(|Zq9>hPy9*~e#cqGJ&vpXOyJpK0;u?=;ME#83954A zcW2g+IBYN}UT{fzK z9W&2x#cmupW<>R3&gLD@uY)x1Pxn<%ux&;SO*bu~!?m_;*j*t`Xj$T)Rn{I&!9(@-DeX2ppE# z9)1h*y7C|kZwC0pPJn*V2P|V|WVc$~o#M-2J+H<>-M=+PZuGgo za{{zltuKDpv@$)5=k%TAra-z%j$w#?*2+{FI6F;+`>KnyTX^~7p zOt5hzpri|0?LR>q@awE*vKv9vbO1aZr4%%r^%r>3F!VJAo+t+L!{}Prf`@=~Tz$mx z>;q;1!z$+0S3FY!9BH@tCF~%KaJtTz_%#p>O$I=62_Pt&lPG-4e4hpsfynD4P$n_; zVP3T18g_)}L~^=ZOVKeyKjmJR7~i;~+ZOz3)S65939|!oo_J7ck>MDeiRZAOZhQ)e z(vyS%KiTjVSbUazUYK#NTm3;L_3i* zA9;R4h>1{S0NIe4G@_E~;JYESD6HEey*(`qpGZ!?T{$l+`85~?ePU=9>Z`xL-^qM1 z`+JPGtLbrIya=x(@nh+>CW$nx$Ge|w1DR!*D8E;b?dmB?$q0R+mifULXeuqAz<0Od zl{K&{e1181IeRVa>e=*j;#bqc?d`zLO{d6)XCp%b*&3E^9=XZ|jL^r1q;YUx>~4~= z>8?s5jqgdxR_~``D~$mBX3CWvI`r(E;}5WHg73DWk%G<>(-zgbwPjUed%t>ksVlOr zGP4A)v1jsZoCPWoNq^AHzSEEt^p2QfsM`W$1GhmL$J8av6eAoPR>u&F#~Pvw5^84x z#gue{_@w6q-H~j?HIj&wc>T~IH!(f@k0>~8T~a9aiB8|)v%;fFCSSM%gh@rj&0$3X zk7sTXEKo=?pwY;z&+)3)xSkWPuZE|IRqU0~^>QV@ptwwdj-HhvV`!1_)}M9JyJMP! zo;hPE2Ui|1l(Y#~=c35~cIT{u56f;rlSFMrcS4t;MvK5Nbenrx`k<|`M7pJyH03*N z=d(1bI$;ccN&vrcrX;nl56pp10xFwf_(LKCtE44A_K| z_Ebv4=c01eM&yNe8SGRZ3(@%HIg9|PNAuzg+{swomkEm$=4}Y#_VY#iG+|y>ktJ2) z8T_;9lI9M2P{=1t&BjWBFl zRZ5wu+zd%S?W5IPxHvwFu#m8nu#&Kbu+C;;t-(M_Sj>y$wmNF46sNF~`M}Men)S=4 zd`L~Z7*)4Kt)*(zDkRrTL=6X?(gTEAKiIc@Nn%>_X<6w(z`&chvUY`lBg;l zyjQ$I&soJxrmR@RkZNe^BByTs%H8|!SUSymn!X@6wBKI3Z!Y~eP>Ipw;JYWsrGqHK z+&m7CgVx2aVGS#GSP=mZDQ}i8i7rLFu3W4Me65*v1&vadyJ(g23gxXKHD><(&Fp03 z+%+~%j8b_=r6XTO;LjG#-<|v&<^lXUHK))gEO(7U$-V5P+>3ad0W)lJAEonGfW@w2 zuc$X2?fE#qJt3$4`vmbbi$D&* z3k4kN^T?&W@~7?_cD=teH1xdd`lOt*4F_W>U`IqROl~d?-y=W5wu&~|ZRSSNBFdpq zLOzlqn!R1j4}XSdPbr{TidN*Q%THwO$9~A@ASP2E3*W_gl6_(RXOPrLJ$s>D44bf< zYB^Y9lF{ksQZgpnLo$~P!|5NSg~Xw!5|Oy;GKYp(hv1Ft{sfBWFdfA+l(=9}U8uus zfJAbh2DG4L-O{Ge-FzT}Vl*6mo2Z)%!mX=D*S@nT+CD7>pwj{iaAwi4z98LKYOL=a}pfX=v17|`*b<18yF4?QP^-lM2 zm&bkS$77zHdl^~Y0&!IKS?gB;7^ivLxq8ZK4Phha{kcvn4_zsIDh_pYN-n})Y=-M3 zr|iG^>G*r72rRRvTqwc?!sWvCsgj1${squz*}U?pcNys4#UB2MEFdI7ydE_FzWhX5 z(gPLqV+9(93b9tZ;bSzja=Do=B2n`|p#Q9zHXXU>gD%=q-CxjCQd)``s)4TrUu5}y zy4VuHPC&>mvKI!Wf}r90@c8uCD?-tH~z~42*4n5LS7%aSS!PGm&kPr?p3EXJo z3E`*Pqw#fXSq^;0@+4fmj$pE%E2L#0XH>5|8DVOp@N1raYW+o`&A<6|DP;vzrLSUzF?Bmj7bK zWhN}pL0c+s0{Yv3{Yvpvz{w64` zzaHH>rhfm!AImpS;~H;2gbWPJwJ((I`#@Z-caO$KK4~%hfJ;=yNG)zg)5jV?ZqBYT zQmY-p`1tnZ9_6-pba{n7Z1-OA=@Qz@d^Kx_7UgQ3vIX z^E?mNx{MBY3+Cf_$5Ywqx^v5kf%>5N?YI$Hp}l$gJJKi@Zz2OHy-uD*6dte`FA%qZgn~4})$n3ggEdNm zIVsSvLsiGp2eu^kvH1Y>r*IOBXRf3kiKTb*CU@WPk@D|(KA*QpELtOl>bkx9t2Tjn z8(2{UgOLLKq%lYY$GvvJr=yAHR^`??!UWoHc|<)XdjA&KC5O;tFcNi&5$ESOgz2gO z^rm)SUHaDVdru}+2m`IInMR&i`>cH`Fr2)`(s}6Q`REIkhik{K>;0*0Z5))g{cS!K z$ZY`gV}!Aseyt#RediJAb+0jwM#jP5)BB03&vf*wKaxjoWF6wVNa2SxxW4>_El+l= z*L>I_LUOR=-qS+bM1`SDBwE(du|4QPT7{?Q{gS1hQ@>SErv_fjo1PVv8bSz3H4K#Q zXtnQvwyLUACK_%Df0mbe`u=0qN5WD_WIQ$Nhh}TNN#pEsKCqN?y_?9b*Cw1p>nON2 z*@_#+zuLa@_25k7Hy-RylUOU&k4E9GN_|?vG$z704RcZq??)0U2@C1>W&Jz+IphXl zcAUVVgv?(=qdQVEYJ^rW%y;@~ebsDC6_`IHava0id3D)FUl+3({6la4g4CS1L5K=H zu5@T86)prfvgqFEJDPmg0JpKPkyNfB8O~>r0)jt3oaHtzns9kc&d6Sl)#QJ;+a%ll z=5?(n>Hc;+pCWAt42sUizl;S*{jAuq9?PfumhnNC)piJ%DAIv2#AN2_Td4KKJ?xl_ zJ*o5IR{j@Re76tE1guPGCH=nr-{H9#HYO;uNBzOrU6kpEszbF06=8Q@XQk1z_xm&p zs~q|0(aTl7&bm#jXE?Pj#2o%1OJsg2$U*v&Uc$sNre&F9x(~u3!Tcf%M#Q3uldnzV zePM*?Xv_ekOTP^wk&jzPghd9U8)PV!3M*~d7U*RBH4lRNn}bxq;Of8Sa>#o&Z8A}ThW72($(&XsdZk3nW0cxpq+aBjZ_9Uh^u|s_O+9BU zWBa@lxa`$Pm`r@L)VVtUWTIAl!)7tie0vFwkA;F}OOP ze|cfU20fE6w;QAi#>Q45&5?>X*l9>0tmP*G_!UDy*Pk26@a8c#N zN>qyF+8K9;SN+0Gc;9J^YK_YhOBKB+u5{gSe2oQI5I+7G!PsVXL6rx$2Nh#gQ`-ET z#|8a5CDI2Iy7lIj2aH#U)-XkKzkL-LjlcE8cTGWQ^8?s5?F3`A;NkgP>BuB$`>^F} z%Gcj2RL^Ntta86{&Bl0wFX77{FPXMkGE1q7WypyL@<{#_ByxAsfhL|rVA$-W$p#ap z%GqYuS|ZP3A7C~-W3igDpD50}{pES-gcuI~a)ksd;lE*7WpH3?$2?tJp*d%c>y@fc zYOaIveI`-zpwoXv?6*wso9x0uB;cFV`m^yWpE?_um9PDoFF=SY&gJk2Jx9EquQ>up z{!-5Oe%ej%Vaf-SN@T$ZUqVVj535QOaF9`Zg!T_3cCj2$q&aW(Ez|s#7!gHK4ZJ?x zW~mW}gQg+JeAdL2FgL!G2SJ1$Vvt^b^L}zcfM!HY8u&lXfck0+mQm0XJ;E1)8GI_j|K;f74G2o(Dg;9+iJM(Tiqv;^*%oSZKr;xX$LrcEM ze0Jv5eD9+IHT~YS53dkO(O(c79<;UnCi}!}@5|TY#n%c?Y_wD<{}dVVVPgCH9yeSK z!iq5OGv`S=-+Tz+FEZ^cC#22lGmGHDrSY$c&w z38u|+b!ll}hOB~sbO{xMQEcCieIry(O&itsqc9xW2F1HkJ@$CNY8$;H(x1* z&F6fiL0{?#a_D>OgEFX$6-h)RDzD#^j-V)1Yfjn7J$lliy z2Pt~3nl)71X}N!|loJ7RruxS6>oHNpeqM?%t^?L0(CIZ&@P<=o9Ju36SvnANjz_@I z8r0j>PQLq`xeOGcmubJ)ovE%)n@70vU3RCR67%hbv1G*t;6Crxeqp%84Tyhq4j+Hc z$t_vlOQ7H<7J#7~GfneA(AH`f@J+C%I)ZUci02zZcrg$Hoc^fG^`PPjLoKr0zbdEk zDS)mN284Aj^RFv_EfQi{ev-3Z0EAO^Zhvz+%3!qDc<}=BZc?Im7bf+-_%2x{v1FEE z>bNMierJr#2TxMNkfy)QDoM01tK8q+2B>~nBR54uZk{N*9&Ycs7{j<%4Rn)#{TaUF zf)+e;P9}cq{HLeYs%UFw(v7m|rb+AoXu7?= zY;U?n?2BL8uOr&~9NgW4=R!>Gcz5^fPrlTeIjWMaeKJ7q)Qqere~{^OcL^+#Lx#eu zeL(iHF1($u(xeP{fS25cE7UK>UcXzF?y^0uqUK;UJ6`WmT12 zODDBM9&#_c8n47r>W)YiX_XlIRjxyx>?0gGrWmJO>wz!{^`OHvLP}D_TSa6PRu4nH zfk)^CQ6uDHUvj8Ap0sw*KhdO1t8K?`{`6aGJPH2ADnIm{|5NL6`LX=h8fQDXke&yv zG}}(&9Lr%I1omX3>gEmate?w33D6RUV-Kcv0v*n=K+>?6-s^!OFf3Raco z2e&o!asUo(MIf3M$)K3eaX3sTz0rS+P!clLxCDzei_+djIRL<4P{w?`%!+$yigMl&> zh6K~8QjQirk~dnO%TplINxs$wt!PX>dyUt2KofJGU82{SeJ4R!X)`sF9K!VA*>_WB z6j<+p-we3t1}mUh|KE8|#>c_9HzH`PML zh|Na+k>A0_(Cu0EPRPA#!`JxV!@0J8Nf;dEWvN5*>s}khB%#-rpDznS)C_0GKzT2b zft7AtPIqDlIU4O^O=jzFqHwmxMUHUCM}Zh7WQh_PRBXNP!&xIESW&IiJ1 z6-_}WoFOUi=AI|$}ggc?K6Mvg}KrvEftK-P{K>GvQ99Fe?b9g|H zbL`jG!1{rWUfZzfk6rklZd&WYgLw}l;aDE!hh&KuDQak6%kn$7W=>Kfhd~EW7Y(}h zhFP#aHhiZ0XQ1mNWZp$%dDWwgR?=w5h3$y!w^;-@ zChZ^em@w+51atYucs97?**ma3f-NRUmU~Z3{lslO)nezKln^Dh z9rj0v{@KgAcwuj_YtNiN%QK{J^+CE<7j_284tIL3a%S7INmG>vmAMp%r$FK5Ug3z? zG$|oDr6EG!K>CX_Sfme^b|o|d-}4wbuGR27+k3FpI)>bugH`>aMm$v3a?yY+dl>E`Zew^zkVW%G z6CTEO`1P0a#{<-;WD_?V99`Z=PTh3l?5Gj=@Q6ji0>(K~K^qq;T9jDw=w`zRE-t*X z>Cm6;vkD!O-x!!?8=IoWG^*9MdP9@+2N6O48%WJY-5mv?4~XI1){{?Os} z5$IQ0QOw3Wt-v7Z=9k25a5PtShzY;JqtqqZh>HDA)kp(>#+wGJ0&ZMJ`Da*6XgZxq zkZ@|&3a(B{aN3NrIs-u{oo#G6%fOT_xPuT;sONHfSk(t(iyWvk&?lyN{JYa~-fwY( zYB%IPfleD%#@bpX6C$Nxcd3hKBCI}Dw0&PLC=l%MhkFAzwqOuhJ%QiB^-UMeG3eT0 z7FQyX-+H`%fT|SB@U9~x8vWgRVGWBcdBCU57#T4vCj>|vz&G^m@nSI2T_|sKl^sj!V(~PYg>yM+Di-*$779CHub1mGlSF&RWh? zDGiLhWOsql2kSYks7J*?89+W<6dNWbKIq6u_3W?g0oLKJM^q`^HI|4bFx#1)z4;Z^ z1Gl`QPcFj2KxC4ZnFf1td`h-|}+9om=yez-qvU$sdM+fqiu+`DK)~ zeP;&KX9p)S<6`5<>wLz44W=MbdDbSK(qdW@6zCiyp4m9WjKK$Ye?|xYN`#SUXWUkr zb5`ox9GHe&64B+i7RrhH0P;{*L*mn-_x6_*{iB4b7S=-<@*%ET<5J|ZPV^O6BHRb0 zBw{I*lc79`{h^*#v&$?qmb805_rgtWF_#Gc8a5 z@oI6)IAuvO{17z{xwHnKY#WyPf!v-B9wQ2bM4500Qkt4TN;=Ep+qn=&zl*4Jp~BlzViVl30tR`* z!utmgDNQ$G8w@^(N96kp(?_=B4km{@{2R^?Bh1~gjuO4Ko_6=da#Lv zEOA3S?Yp?av;1^!92dZYBK3t^&WwQ8(Rm$sV`cTa+fkYD@HhQf~wREqI3&qs0BrUqg~uB;)}wN9E>l%L>Ty6hD^`&{)OWVq*#}; zI3ygH=h3R<<#M&Z{+m-wMIxbcV7$JeNPaknJhhvp>b7JC66tM9W)lfZi`7_>0vyvcYOjJ$-@`gi46bzh4 zR$`JgSL=MD~3=>mPk-H3Z4x zF$>QrX$1k(&y6`EN+R&=GBHISK1|9QJN6)(D*t1YL}$H zjNQD(A6}qp&2S7zK}!|~Bt=(|cU_NVrqSb+e#O?p%}Wjj8z_`;4O(K#GSozm;f3;l zIomPEZA;PckZ~W(a+cTZ8H@6CHLfM=*erZbLX~K*&vxh7_vn7RUW$)|PufskilznH z72b{R^cv$TXDEi+E?Yt?LN0%%qe?mDRD`C#-Kd4$JR0}8IWJ{if*I9BMVliN@PAoz2P8&qep3^BGe6IsA{PJdVG?b0`0^=MNQh3?CUR@__rV5R$Q^ z80br#^4R~y9@pATacq#ZBMzks!_j>R-Tc$Pl8VFf^m~HIp8M&!;I~iUfX)=+>w&8m zb!YFhAa>u=m93EQH7bhV%FML}u2TUOyEKi~f>lDNT=QXVL=` zGSVeA`a`|HHAh3lr|brT^VRkS*N#?0q^IbvX|QH4Tx_SxR(~>V2e@g?@uM~@(kKnS zZ|WS5D0F_Eyc3U7V6lviXh|WRJhY^7@Yq*pb=5CSLIy+IpZHo^QXn z+r1#0m-lgFEnv2zTf0Hf=9vbrvW{V`LPn~ag@gxOzE3HfMb?Y^Z|Q4mSLW8xCgkAA z2Y1qHiBhvDLe54TgkkQSX;-R$1DyLJNidUdW9#}W33Qdwf1x`1L>@GnsqPix?X<8O zzz&4`I6@L(DT}SE#Z9g)tS4aay2X9 z&r3-L9N29tA>y8d8L1r#)~!`alvp&&?n9%YcBKA^6K9``6DnK0yPeW;hX9q32LVzQ zmP2_EF*zDO?4FR|5F+M19GjtW6Y;HVXR8}_vjZdiKGW%k^tXI)2XP0kl9NF>Yg+A4 z_y=g|v^b@w!iweuBkq^;_QVrN_hx~z#aT@%Xy=^G8$)roI zZfx;f`Zz4Sy3_J&GK%|0z}%;XMkr-C6+XrJHgJ)FHTh z7pdEV)<_|w$5I=VkdYR4LzAb2^v|&uI2$iMW!TLAl^751VscumVJAgx=z^wt(S4O` zXq}~Gl7I;u@cWV>CCu&^*E%a4U(@lS;qir}U@003 z@=Nprauz!;x)H%pjKH(!k#uKXO9G0KcHR%eBnlHPpE=Xwqjr$(ac@w@txNzj+`EUt zW{_fh6HK#DNQ>CEpY6dp|2aPE+w}Bk?TY^#iO7*FBxVg6UJ8&;1`&~Ru%pJwZw%>% zN|PpX8i+0nzNu<5i(SFsxpS<3LFGRH9-nQ*AJI2<^fL~UOmPiq;DMQ<`7`T0!WP?$ z-qc(X`zsQA=h5tA@vPz2WXE|H^V&7C>A9I{Fq;60PBrTW>1O<6=e?BUwIYFq7&dD+ za4re*%_yD2Sm>gZ?}Ikp`I3gr1JQ~6j&7BcvOMOsw26t#k}meO zZCg0)8&&uRe70_P^yb$zr5sS%Prr3{$exYg@1^}(8XIX zuhH~3)UI{;6ET$|L+@B9?xz#XdY1S5mnHS4tR^zePNz>LA4S=(v&U9{ z^RXl$sdJf9sylzyJuZ{}J2x^}112Ru`?u`0~;D!aeypS-pIA zLQEC5l;PpDwY}1sZB_CM6`}N});4^>9`atJ-iYfo7*3-g3t+&)>PGY)C_2}awT1h4 z*esJii@@Gu?!@=Y{t1lV+oHX-7EZgyrSrA;%=4(@m+}+EdPRMvu)kV4EUFmDD<+tG z(GH-(Lty$Gp0cC`mA{O+pUSz{M)4yuXzB2XTpNG(9Y@#JXiXyWfw3c6SoS_BIw+4g zZKslG4t&qh?(T~QO@08!zi!>f+pBHHv?g62tLbf}CgR8?C{(7eY+=4Q-v)=*>cDX4 zdK`6$NL9vZLEUHBK%q!>X#PcfDC@wo8)%J0fUG5RT{@j>!$>#~%Z+9bGD*LVdHI*< z7D^LJ)F8h2A+BqZ4oM@Vw)ac%BqLStBH%Jp#~-&o`7PtwEcbp7LrjgCQG{b~j#i#N z&U$K*#b^6!LNrF1+>K){q<7YNtBRWnKh_t$iK1NjV796#FW^wmfD(n{p9??>(*EIR zhQ|OzaH*Xk>ir4_&q3kRxQcPp-LJJaJW*~5{)M=zUG4Ob+Sh$VMA-=sWr-ZyLyt|b zPViR!uCq%VdFp7UJ8tgO<~>}4bzN`G#%V8d{K#}tG<58=^asT36Ay#t6wkJg#N3xI zC;D4koG4u9Ai&X@i9_uaME6pg(rH=zvw~0$A%)!8^FKAwmQMs0Tw?eZh9gZPXTBT- zhkiV4>Ec-|OjwJFN;ho{41sZ%&FtCQrk8d;;>w9R^!wFnsVK_u#=tpjQ_em0WEI<7 z!X}PR@rryxr7|jcBjIQ_em`=iUw@tYE^2t(YrbNQXIuM;@UDof81q_hN|LBobTZku zM!FYbB4gt%#xoa(cxgG`saI=OJkktv_WJ_xgpESu21cf1C1XU7DzyH5r}Mf#RegJt ztmPV0B}>q4kEUpRlZ+L}(!{4yV!rJt&*C_4`-62NYWoI&LE}@i2hs%w>oCH{`DbY* zG!13^sOZ2VI1v~0j%bOWPJ( z@^LN+yZld?ZIS`P61$%p<%=^-mXvExgvKveUuOH4Go? zD=v^QdF<8U>E3%Q$8Mb7uPPGlk`*831PdV$^DIcJgnmi*D;9ED@SsFfCA5Y|oJHdo zot}sGL%k}TOG8=Y!w&dgn!_?43#1+rm@!C9nxdqt4?VvfFhl#Z_KL$mZ#NyB{{n$T zDPaf`8+!dW4P84Idm=Rn(13ow>^q?IlkXP;lvLe4b9?J>>T6 zpG)Fjc&q*syXW5Zq3E)(}^|JA9OmWqxyz~VfmVjEM|847;=FHV%h zh<~tRkvH=EGvhSPkLbJGYwThl2R1}z<5>SE>0_(E&#d%6Y<`IxipP8UBuEI2rAse9 z9C0G%1RkK({mO|fHWc?Dq=4uCDMg@O-IDcAV0e%XzoI-`oDzRAwbiE1PVY1(3&X8a z9&A3{glMz=jSL(UMYqEjf}9P1Uwt-zSMb>Vn%X4%1S>`3P~J-_(bEVffcwnBK=5Xv zhT?G5*~(#m$+Yz%B+{(2*eNfJ0!7_(}A|EYaG1ZpW7mq2ga2p6BHYxZaJmOQKt6C05y zI&DCe6#|&{P^3ZJLHt3&!Nhn9OqB0Eh)CcDezVV20y9Pja3yeXQr+*uMfe0tIXwZi zF0NA3ZRzq&)c%2(>3HGb+D=PKT7}qk&!@eK0u7}qZcV(Ozj*W-4QjQ$FW27G9tPLj zD93!K_^kkAN#?wtudAhJZ>(d}EHq?Sf=H{$r=^uB{7^kkaTt%*1d zRT1WwPLECIr{wGPT&twp(Kih20(@wlmGM9U)VIRzd=SW*d|xlwI$F146Z1Sj-SJzc zHV2L9IL&)UsT)2!gChP%gmu6w{V#%vUyr-?X}RttkbC#Pyx&D}4f$b7!6=CJB6U5O zpTrBB6E&zbbv*xr7J7hH|=Al+crOzV@c3E2G8Q(dp`Qj2g<4HCDj>8&CzswRsZUH zjoDuj(jT!Pn)X{;j`1q1P3&fsY@wnf%YB4^KUyx&@MqkXxkP)%wV`t5(s|U&(w9Na ztKQQGXh2#_p^iynzs`=%5VK?WMOD9f!Tw0(wg_TMi^9b1&BpO$^U?j6t6P51gK0HP zf2R8$1cms&#z$m2B0T*9y?`L2Fyin#TWq0*O88}#+cM^qQ3!QBN7vv5BC7<{IS!B% zJjF}ty#vKrYoI?P3HU)p*-ruXRH3-(?t>LL>uL9%W1s#cal~dY>c5>THIbtbb}Nah zT~3Mnd3`8qvY7C2zuNOxblg=D$2WSq&s~UfI0PkvjFVLKN3u&&`qulm?g-7}2$Hvb zo3lo$tJERkc#!?}7j3wYm^h9!{bNs0w%7UO+?hTqt>?GU>R7^93h&yb%>Zv9o&e|A z-Z-$9-|p4#`-hiwRdH$3i>4BA=Vl=;Mj2Aj;d9jnB;s$nVo*0Xua3t84X}IHy57@m zasj)H(k+ipu~$2{0EXKQsz5WV(C>`}N@4`P`Zuo2{&4c^KSaC7Ol;R=nd<{Di{_M@|{fl zG_aq8WCC7PE=MDnUGf0#oz;9p?FmRQ5dO)y0kwc@AgoprahWOPid)g#e6sm zSW|WW`n%Eno*~mANS*Zp;F!^j!w@JVQc3{*iqm!56F)gezr9ESJ_B_`w96f&M}mT; zajBnogV&Gt3CBwx8lM|SoqHhGs|f!vun}rTsaDF6LbSUfqi(^0X4`N!ZCwT$$fj(o z=S+cvtJF%@!=dREz~cJA6x}r4wyJbvmTXmYvQVcQTxPb{aYL%WFH>xoD@lO}p8m;y z-Iw*b7`VUnn+t$9Q|9Uv*g&SMOtgT;?lCD`k?HLTQgY*!|A(!&jEX91!-k0g2SHjo zhHj7+K{^EyLFtl4ItA$l>F$(L5NQUGlx{&lLTONGkgjhZp6B`2`>yr=ayit@Is5E= z-`9Nw&uNp*8aqsb^A)73y4Xf*2kENQO`RWsj-lIslPD&Z!}NG-M*8Xtk<}r`=P&f& zm)-lRz@2S>KLB+5SkWpie6!qg2y_Cgd8CuBZgPe5#JD!s3#c_B4lw(L_f6oR{z@X- z^jdWNq%-!|b1LFHHzNH<7Xf4j-X=;FEBIwM{W%M@)`?gat@9@OK$^EHES_**rP|F( zTn<4C*cRDjcYq&W#|XU@P~s&1eY9KSDi+r5`HWkb`z$Wce-W%%3dtnw3goVG*o$)Q zZ-iL35z&igK*M*e3#@iJI;1baD4rW~*M%q=&S0SWV-uZVAF!eo!BE~nvzZC^PUID^ zILnf>uN)2Z27=yl!9FltV!1~lP#L)e%`^>MA(bMAv?&ORs|MnG-8ahGo|SvPlP|UI zv(t(>Msx>?N3H=1SZ(A6(ptCHDqbI?0H#>|Fi$mD1*;?l3ZQFJ{s>OeEb#l#I;5$; zoy)W#8bO+jHYGz1l(`B;Sbou`!V4F@&TZU)d?2y#^=+fcc%^^iX*V7N0gmP|h_|BO zXF({citWqhf5Ml9EEbO%aVLcL7>0qfC_5DCc_m3a#>^>)2>H$5h1rV;QXCDlfrPXf zNbRp&I?`>Whrm5HF57)p>C#3nJC4H`yNb_diqmtes&^IWorx<=u|EK%fQHx4puHtO z;_8n^ZN6wyJy(O`PLJAUncU!ohb@Usn%7$D^>tcU(Ki*@JK*H||DWvtKId`Y;2;)z zqW-h-e#J}q2EqH$K|{t)?4*JN8Ue8>`S7kt6c{YB#JkdCdFC}x&3&2S7yns(ztJ*3 z;C=@%N#yvXTbBUR(Sj>bDK2AV5h(w_l*9om8Z2)~D8pBRv8NkBakCUGUy5+p00OA` z6jaXFxKt0-z;0z$ccQZ=Ke&L!IPg^Xry1#WRpCRx6I{)58F4DvCP`QXJ2wd@sfS-p z=}p_s)w2|CgV&=(YLw1F^)f=!ST zrE-a4a@YQCNp}O}vUP1$`u>vsW0F>*25)}eFU|6Qd1rOup76Z!C(=r@d|a5VJ)osr z%mA9!_HLr;qy7Y8{TgtZO%jqBH-YvkB>eLoTJNz0tT$*Z9=r?`MivH`Xb4RQ|>DEl$7@KE{>{mB8P>_l=ha{`&#^!&%$0A0T53J!0CPU9L3v^6t0k< z&zyEBt+{xB#eN*cM*bF;x^4Ve31A3YIst^EOZu4c5^D}d4k4-m$8(r74r&fpPL+O~ zzNSS0XZt;(D&qQi=fTocYz~xiet7#+l@O4@t2Y>gijFBeoZ6>U&g@7xS^Si;5e&Jx z(gWlIo>>{k`>$c}aycf4nD+pwtvMJOM!YcpM3L|ZPm>02Uy9&Ao_BdhX%qN^7r8*6 zCv*k5MVi*%!4AhFV%APoO~NQv^$2w&4N)$GoCj@WC7Rv0UpvtW!3!~~l)|4&?>Pr+ zQ+LsJu%ly>%^OMml5y{1s9l1PFJ4jj`cwQ9*b%<-{YrZ)%X zH0xg%w`NoOOhvy-KF3!WAKIlQg5ibeDTK&S`cq+9d7$Ezu`ks3k(z@7*D>*EY_U+z zaoCph1f`4@Ej|L8ppYu2EbcxqNJISq19s*6A};Gc7U#Y@K~$Z8VN}lHTOj^91x{x# zFD5;EUk;&i#ElA#5}3ySd(kOc^GVMH}}65+sfJzZR_bNN6kN6}bWW zd-#9F4w&TA{K7~;@H#vLd++vt@w-Y-bC0kwGAZ-`j2rJbU=*O;R&s63L3{qmhZC+E zb|zfhx1*zUpBpEb*PzFULkjaMz=_^%D1-l5hR{cwLk*fWB;Fd*`KM5PwUX#rk+r5v zV=rB{v#yKCbA5pps2wIjYgjl5-X8PUh#5(XIa?8;SnILxk6G9|r0!3a?{U?o1e+f( zT7;PCADXta2fhdUmg9xirZoutJnP+D?AbTFUtMFaIu{%O*39ge?r9CgFw&+wvg;)g z#?Nwr5xo@IX>S@f@E@hsj2M+LocAHwt$N~5)y{-?=UNr)%6XvR>_^NYv5~lq7kPA@ z6=I|4m4%@o=GH8*IQkaNz9tanfHf4~*-t&&UWXFon6EO@Tm1!VwAW$YFn+X~9_tCw zA2Lm9P{F70k39T({cOQ=Bb(2BAW3a2S@EoT+k<{oTd8%S`d+V!-Dh1Zv%ksT+@ap_ z34!tAvbTP5?II3WRO`59@};zYhj@@R<}gm&&n}LatZFoC>f%Rgxe#ao^{#*D7w>%K z`k7)TzYX&P8SXBhA_;1ZK~VORn&ezBt>66XdnF%>pp=I!kL3s~(q3so>_DUx6yfkXawjfw~3#3CuGKUo!jTktUONqOp?8em80J1n5_*(NLrF` zQ9?U;w;YPyT}<*b`hUsdWvh5-aX40|Nl6c8R%yQ8WwhEeRNsbwywi37LBU|%JVK+M ztxq7YY3)0END-m0pdVSuik=_f=M)~~w9=zPN(aaxi6GYjOSAj~;k6eK-L=YTX|dd7 z;&UP4jXO=7<<9LOA%Oiv!Yb;0WeVXSie4*tY>)?-s5a#P?VACN(xtHv6 zYP`{wg3@rgD?K!}Q~Wejx9wk65RNqnIcL%aUfoRpde2^3swg@daz5GP^81}1HoN}^ zc<#U641+)VTl&7d?Ty#s0?*$j(7%QFCQUv3>JYorD{tHrMJ{A0sWu?GgOt&Zx_VUe^ZCN=J{K#?fkmoA6;S!T719DSwn)BOYrG_MDK zJ?C!uM?uu*so;6jLcT73IRSh3OW`e*A^tO<#q>*{tjI)2R9rnL(VUU@)u|c5PivcX zzVub~_O8yRr1qL$ni@a-;-ZjooNZ-7S(^khG<0cvR;?QkK1QzUt;qkqzF-*`cSuzR za!3`NP=+Cw*P&9&tvQ}bLOcj3{~Ip=e7mecOcjkn4A!aKNsME9bkdtJ5_7?=5{ z&(ra)I+_^9Ddfff8C|4Z!Al1}Y_)~XqJwjv6$9p|BdPYFKJ7JM1P&=P@oABYCeWtX zVxEpNWf~l=VWDxV=h0Cp3EBQY&;rxXMQajS?Rm)`H=y@GpXh$1c=aT&TUrUCDgt(n zF6{5*2WxEC4o3~Wx5vrr*Q4;fO>FLLP+-648iu3V&`BVKDrX-aeb*SK-&Rp(zU_Hj z9oP0K>CqxF(fax1Wojnhe60l@F(M!>V-0G(fmzS5rUC$irs@;qbM4QMg;ERIrco>S z9u5nqn6(R%m|>z?How`m=ThE_SEU4gBI=ot#d<1Gr$8Wt-6zHmH&CwlM&&`C9n|zn zmdB0g#70pFKKYGa*u@YlMjt17`9^~@aPR{{IN|i=XRXgUbu?92M;z}7{43k}BAy`~ zqYj6jy<@;8<*Y)D3d7mv;Tn>@`P49P+1G+5+(5)E#wfalYym+gBID&crj1wi7j`dq z$>~i1Bx&AdzLDNOP%=>VSvA#xggag^)I05=Q+1a6`Mz_yNP}}XJAcXB>2;m9>yoik z_iQ?jUp^ZQL~4x<|F%5C(`j2wl!yp_xvm%-|FZklROC8e7W8HRTy$N83;Eej>c&^l zO4~^M+AQWu(C?al<&j&^(D}*9M($`LKp$rhR_4XSd=tZE_o_gpd8`%NAu(6;)b6T}*9OXHrfpTzPgr*Kx; zGmqhi3`tVf0xxpk1my9DT=(EqTCH_rmV+zS5~LNEDpY_4L`k>np?8Qj<^6&Wm)Dw|M!|<+E7pO%hi4_vrFu@#osKu+)oRB7eA*OUwxH_#zu{s zJ5I+Z<3)qpDTmrJ`aZ)<1anhB2hSOR76A&3&Cv3T@zd*FV=i{x`Z*o;7av&_D5_1@ zs*?|z)5}5zRf~Ul?f8CAAj$PPFRFb#*(pYQXj}ccHG>BbvrD`WhZ|DPtB`v{V}L_-PgBkd9CbsHUQHOY);fL^$Sh*Y;mhkx5srM47JvN%uuE# zwtEKnTy&~p0_NA8^cs&|{m`{V0NEdq>>_y4I~hR4TmXbD@3Y0!a3Q}4(Qn;_w8AJV z0`U<XyzH-5I~o z>&emf!=Faqsx*G3Qfb_QCXv2li2wxiIFgv}d;K^HjzCy2->h17*SW$yo1&+B*-LNr zaI}s7>cW5({oKfG=SFm!w}yr@`_Y@)ijtm~#_k-yt#6iFr`1wHZ^w_uxI^6gpkMrZm-MC0GzqMRr@`FQX}2?@?kBVqeJ$Zu(`=wx zdC-2$GH3mg6qWK2==%c7ZqO}@8dOaXKNEx=XICl z{R4^^US7I3<>tb>=&APJ`%LLr>Np~(q1=-zcRZ?xW51qBl~#@M0_q{b!ub z-H-E#TzL^>gL9xLD=ZLgUofM8R~dL_$w>SxC|8cXV4dyW0O)Qas9+x@33-R{a=pW= zy&2*kt;U*_VQ9^eB1XEDR^!Ii#%iyd1ceFEAkhk*&7?-vjqy&Q ztyITvgvE7a{06aVsdKwzra8^;%k@5-{K~W*rL|r*Q$Ze5h};DFPR%Y?Ddgg=Mb>FF zyQ6yUAI4&g!ne;4BGJks^S*p~^Q&X1U!0#rO+t4wlKPD;w?=f`9bVCo`18tV z)lgmgOB|BRcP0 znF(7g-hzCRdaAlIqHpeiA^+}NXX*dE0H6dm0F4Z77JPyXqtzu*qTefXH+D7s>Ifmm zZi>TR(xtH)YEMi^p3r>Kh(-}-^)QjY9k9q@1A7=K_qc&+JQc!pN z_l&*_ayRz@_}|Xdz19{ugz=PeKTa`%%TkPq5?>zTn^<{Bzg`rjQZ7KeLPeMOh3sr^ zp{U@xl*`N$^L;F}q5Sv9K{AG}SyP{udbrJF=pe>|TT$Jssz`?16p{8BK$Lm4ukZoNNQ?-Q)W}wNLtUgV1;(W^Bn6BOmS9XIB%P-Y zOpe^JPQht?Q_>!Niu$CEAB1Jb<&284 zP7~x!Dp(PXcOlb2S=zRiUQ#kkM)^5&aUqVDOIHA!hy5Sr^vf`E>Gw(#p*@|eJ7fDV zc8rtTMVA54UcV1o^==TkrokG+V6CsOP|mKFwsI9VD6)`{;SzO}zCnoBm%Wl5A_l5f z=DjnUkR}wmA@6q5aVW!F!t4_gO!*?lc-Nc0#AeR}A5nMiW#0*lSw)>|(eM+xoeMDM zBUNu-{wdc?u-vit>li~i?G5p+(1<7>*`~<3yeDwSjL&7NeI|-%i-?wuq7N1!qP@P8 zMg)|XhJ{XT+frB1$|O_(vlGHFeGUkVRFOkDRA=r~nFZ07LS>Di$G-q~O}n(e?)7AK z@yUoe9>rd`L)~-MptAs4)zlo9k;g9q*1F}?K(d!v8*4j`4I0Vj=ayq#8zwK%lv6I8UW9Vqr>>6rRQax*`Zw@4v?gs*+|W;dKvIYU z4+RlN-wDxHy9FnK64Ta@)8c0*{f6qJKa88~V4`F=-sWuGLv{4f#oK(yD!}Dg3nRxY38_n_Ym5>p(jDM{|TMgtJ&~s4Qr2p{W6OqFxI_90lXX9Uy zf$tOsE^V^EyY^bB`!$Ci#8x$RqOJI&pG2qmC=gXr&6ua6bysHJF!8zD8%SCi+wq=m zO$-&BUP#mC@D&b`Tjn6@USC-)ao#e@&fa>d=fV<$;BJf6{U$KdWp?I8)Y zOZI7;D*jg5QwqLLVp6hO_iIBa(b;5ki2ZsWhYEKiS_wNKSZ~1Z#c%(D9w8GvMmov zSbbjY1$v7wz;I- znJSqguM}iEyj@6n)7{E}ma5ut7J-6pTcUFxJ76ya&xnX!j3*-vaPjNIs-{Z8B+4T} zx{cqw>Z>)|bQ#giW6Au`O}yI!xS9&pyI01FQ?K!fe%>DEuKM`!MjKq1&Rbi|bZO|u z&*aq*R@ESaPL0nA+jfU)C(W@1?G=Gx-!?Al1P=&7y;$T7Dbjs)!e!UnAn>K1HaO{kVQ$#3}xW&V4RZ-i5r3I57Ak?Lp2J zm@K{HG;aE0OaA`g`E%Gn%Fj;DtXwWimHF^@$?U{Uspm(#cAxmk9L|H~xmE@u{Ff+I z)Er5|8h$KGa6}!B?AmNRi89Z2BW7K2H0_`ly=eF)l<`!NyYXMv*mc8s|wMukqc413~kDjc`u*eYJi|}_iA0%M#g^L_3-`_8iv*jRXEJ| zZ5BPnPR)-EjeZyti_yoRKpDj1-3Sr(U1EKD-mtC&e^16;EI8J$`$$oXp%GDx$Y4Nb z_%#9+prhLm6=@{aFH~)gJ(`EV^Ma%`DLnAWfF`U?-v*H6xO$IrMR@n$82VkgUC>CM zMKnqy*T|0er$dq!e|CF)(MNke>F*&Rbb^OAKIp{5BJ{WY@^@0b@?25vL>Q!D3<3DKfA0*$0 zE8sAu3+}9>{{^q1bWp@FP$gUK$}scd3|;lVmr1%HCe?t^o2v(tBM+bMj0Zbd1L4+x z@Y;R`|H#Kc#kaF2n!ogZmt^?Tbm_wC-vJh3t>;%ke}hLo*70!cYptMbJp)6($RcvATH!lfO5Tib&C~GwEob!eu_&Z6HMDXGXT(ey8?@D#H?5vARZ7@&=b$v1m zbmhruP&+@OeqFn9BQQlQxSAIf#1+SoOyoMx!XG%2`kmKOum`P`hZQMBljm`VEx^kJ zjum`@SCOafZ3OHl=uY~(dVc_qBpKQ7^>g-mU4g@&dlwW$@c2;1QBArgH%ea73J-Qg zt+oW}Nf2LFx&!YDp7J83^5~Z?tgdjE)R1JDENmwq!QXmw?KP*6DiqJvb$8pna~?C* zuS!ur^_N}x27V^gIZx?HmwUz!mfQ-;X~8_R5KoUw2b9y*_^A3%MPB(UEcPHe9;5n- z>imlqn2|*f4#?EA?cpTVFjjH&v6B!$A-8&Uck`J5FOPd5-g>;eDJH+xkn>Ao)G!nx z$sOzxuJxD;y#_>(Jn6L|-QTe^>6*c_)T0TR)_u+gN`Yet59`8P4#PslUxBqe8 z{yWQ-v|xB(X7y_DXx!eeE<>=;ylj2E16WpD9CH}A@5cKgZ8EBj089ec79ML72H z5LbkV)D}D(wVb7LOd1srL)1j(AvU)@3yUcny-8~sn3TZpJPTbb+v&I*kZT+;AG4x#yF~e_!OJ=rijxa zO<1ld%_xc7gZ01KLwQks`cI(QfG*f;(nYzascfIn?^CSh{jRvh1DG zFxUaOk+n#mRNoRQ4|V(&kjDjK7@2sR4Bn&i&AFu#vz~fZSUw^W2Oo>{rTuLLL4m9G z7mj>L!EfR7{I8vo8~Uk(($Oi`q}}%e;s9GYMEaSuxhX+BTi{EI@jPFHm7G$;k$Ccd z_Zn2A2Az(AyV32#oN{ITZS^^%9!L`6fYytBgrJ=wM07n%bh|c@={~4IMBY_*dO(%z zlnGi9lRk^-)zbV}!PuQwOT-8I;`LD{&%3^^@j0E+4+mADbhHo`!nljWr-S5rtIY=s z%?ATdnE*lR0y46W;sxSWv}u z!$R-?*w2ny1gjf7wpRNnQ z@J4$}RD)W8a)3JB*H!=3IlpQj?r&qFPgjHVZHtJI5rgX!?*d|l(D6OSki>esIY@8I z4(62kOce0AXdCv1_61bqi#z z<)0}C$g$3%y!w_ebB7#cXSxM!vNN<|+O;#e#<7INP>bpogtV@lr~e>L3ew}&|114n z_HSm{S33jB{-D$lD8sSotTPggSIMyqRJrWw4_*aw>Qr}`Pkf@smqr-u-T}TwCT=44 znzy+tPymCJ>zb-HrAyr^l=y5o69er)2OdDXE%k!{oeq4dCh0DLsct59o zAr2$Pn@q@7yVRu5$S;7l47M!$;N+f`N#{WmC}gK5UwB+n1zNk%(9up&v>HDS&_bPm zCP%cfJWv#1wU5zI-)bz_afhzH2byefJw3&9>QTjo?9(yg=g z0UOwICT#AvKBHI_Vhu(5Mu-Plz|f_CTbHoEH^hT!CnJ~;xU%KJ3#q}#XdJgn6U?@7 z(X9LnWuPUciD_u1`T!njMzdKmx(-0`0(YpoYNVV!X2dr(9$iIuajA`X(5=eU*by9v zD(a@DbqZsMVD24Cs5l8q(L-rj&U<)@m;VcI#!I67dS7ee)cksl^Vqj(Crp+i<=IMu ztmC&@;NC#My5k*iBJ-#xV88WE79sY-t`!S`U(Q6`$LGjl)-}Qd&2gtl|LR&O#k`4uU!Ufj_=6Gm(`r44E6aUySOiE! zxc{m@wSYZ;g?o$-A_g-M7zfbaq^D`E7!lozcE#yZet(El_=hw@^NB@L&;7ovM~x-q ztGyJzEmb2mrU^Hc8x6B{KXANI1}zo%aculjt&(=W=!X>C#quNqq7}^R2d7-zC<5{H zar7`}r`9~pMz$uAW!80|Hm~Q-g4cff&nmHI00@({5$Y$%gz*Uus(>H`IQWAimv)_3 zdZO7T4SA|!U@yApg^GKuc1q3VQ6%5q3dP1Bf`ijm1 z*{hN+AO8Rrg9)Es*nln%ygxK>zR9A<<3UCcJM{h+XyUgT7Cks4Ar)W4+lrRknO-~6 zfOkJ=k4?hVeMiUI(Z}tw_)Q~N4G&?iLmP3ohK;OfXAzR=ms|D6y5J4}w~71TKLPVn zl0UcCM(V!j+;VDfOAqKolZ6z#eLsGa{|B$See?9NF%@~|nWHBp2Do+~!8av6K;w=H zozUlA`djzt_8Yq=gz6{LhA|M|QPo3K5w}F z>1!lXhA)m%_!xQza=qRR#OGKG|M#<0qk?C=dWqed5_$lb;I^~1N+~=>+`yGD5#*RX z3AuYe7^@g6AQ?kZWEa8oj2rMvQ<;=g6oG+Uq3uj{#QlWj@2YyET;ENgxjV1^N)ZgG01UNLH2{ipVt-h6x)*42&WyxSaw zp+s2>?VA9j5`vB2I3(5=+jQJsW$@R{Fe%o+;3XMo9NE+3$-Fak3E*@n1bzHnHC_DO zHAT1268K4S_^K{dv_4tepBoDcG0NF@#Pr7DkrE^1>^C@+Y}M&Hnr|2FHv?bS@vu2*(Q=L0<<(8k&3?R8J zrNBqJ4=)Xo*VJdWD+D)!m=w6Q+SUvUb#uudLeDY^nwXxL>3>KFAgBYVx?48%_mh1i z6C9)}Vix}S@t=9ole$QoEe;%l5Q@rR`H!ZT&uL__)->>riqx;?Ti#y^UZckUJWi5O z72!{oBV`+GnHE5J5Lo^D&-39K;;(D9I=MDA!j5(pi9)g3HFR5bd}nB{>E_qeE!es9 z1l#iy#4X?>K;6(1CoLCncDvM^y~#}XSXj5oM39FQX{e($cDRJxhfi&ONNCFtOl|wz z_V{JDwaEV-Bgq#qpc`cud#et9k23};<}L+_m{vvcF~bCq=sc+tdR2V%eC~E;A}K@% z2!;AW4=|bB7c>%)Ky2Q7BFV_(#G<|F|K#G}W7Vrvrd=6F;_&8A{`o8F{!8)$X!Sl_lm`Ee2>Y z`;3zUo>3@f)Klc~W`Ht#0OrTA9K#0tY>4Z5OmzX+m~O^R+IK!kWZz|5qiHl6$oi=@ z5ZxxCsj>WM7p|-DqbFGj@^gi5$0{S($5;R3Yx%!R_rGndl|D|{Nnp7pRCOX-8#P{F z6!1Ik&K7R2StPFhX&6^=?o_L(Y{k&}UGgTa1M9S8Fb%!`t+*ry!~qQeq5$EOGSPRE zVsOV@Sq37+FKE)fn)7}OJnq4Y{C!cWhHu$&*dq(`uN6n1#CIuCot6~SX%5$Qn9so2X2A}hPY|b$N$X@VRiJp2AK!V4zD&Gb1&?zNzxOZC03)GWMOCs3(8Y!KT)^z0 zcgQFUw%~mj{eahP{IgM8RNJV&9=~max;Y|aW-Y3UhH%+ z$QfW@%8ATR&A`4C2Vyt+Ko!Y83dU{muiyUr{i>HyPW93&4>&Bj&o=;9;~6JnCm}l3D+5jm{sCheJ%#t1JKvNr?@xtj_F$(ujv-D9%Nb&7-*iQo7R1OmN|5tOYNT8 zX5Xw(IIc+#yf)TGs1cdzjZ%CGcrZIYq!rEoHf|RP8C%6_(A* z&Na^2H_u?0FO&d3ZZSH@v2Y7mghY^N75wQ1So$|o*9alTjsb0N|A#+(FShMQUL2uM zc$Mgoe^P?2ZH*U2UX6uv_r@54ycJAEdD0lmUV{{F226Tc|H2r07&Wp705_@;A>TmDbD9~c?OqOXi zm;urc6&*yJhYTmHkEZm(dcuHNaKjM`+^q=<*9scl7;+CUP8DnE7*as)T=hOw$ z9$84mVf~{rKZ;huP(F%`4%~es*^}4YZAP)8B%?y{Ctr<3>)8>4>{615!wflZ^j?t44I?}5*qAC$RN~&-sdL7IRp;u9||7m^}ogL!iwG1f#C*|5|;xqS>a?PbI5f|G=$mfgC{I18A$3PMOc>dDqS3C*rXoXo>YNTEs z9(evqE}!F32q;2)VtnzVzmqlF3l^Dn(sqz#4K#9@qLu}7db_rcG-m`}yHF{u$};?& zqQN5IZAs;6++Cj!qxEG4q|(|H8jxsn|NH{zR_>DEgAG0mQg19bFnmYU08(G%T{ECd zaYy^@>k=j86g}7AF#7Clt7a6&kr0F@t_{g{Y=*x&%v)sp9M5zLf;y>aC`)AGe1e&J zT0np2%b}_lT&z8%n-CT~3M2*XDPge=;;IEpi{q;+Qy4IwolXOBlM+yc5C_c5w>O%z zIZlkTTZ(#t0rij8ZRARfGcZQ#^G8K5e6#zj+G04vpuO*)op5swhXQKoggs7s`m5lxU zI?7LEM$+-^p9_(_B7w>vmTUww0)`=;qqFxJs8uFw>HUzEX|a(Q^_;Du>A-!-EvVf@ zJOvL>!9PE~@6f0cxJNoHnz1i^d*5`(YZ}k3p($+|N98Kxb>k(HhF} zqSSa34NqbDM<`L6-#4%Z!)8kX5UAg!m9PulbfVup3C+3M9U_hc7*iDu`|5_X- zJNKHyH1Ov0JXpZTIVP`QfwoiSI>#ykRQkX4fA(DXI%2=sU&eQ6+)vpUw{FBxWW{|K z@!g~CNjTb^j-Abt zon0bL8oF58m7nXUZJ1}ed2#)0sh=nDN^y-uD}YJ8ap{QVT-0EVc^3C6q)|-|lWh8h zr)%F)r->as)VxtA2dWOW0eGdqFT|zPM0VSd3^*wSU$Bw%16HSOv;RneP=?z6-PxCS zs9oc-&1C$)fV*|jO==X($%7DJrIc#Eiydq8xn+tF)9RWijW813h?R)r9fb0f!QaG0 z;8c*}m$UEPcmw6y9LAZ)026Hr3~6FQFGmBXCc(ik*Q}If0~Ce2a+EUbiQ<{oe7gPu zw8Ch;FJaz@H0JHzSy%(k$fJ;h&I?p=*!pVFAdjz1uq2-`Tj#O_V?WsbmEGt3%Bd9x z@7RQy#y+0PQJyAsbMf&OSxeH8?vSrq?+C2QP+*hR&XHZ1$+g&N1inS|4nonrXuhTgUMmXFf3t@9q0M7q0yj4 z4X@nm8`i!Am#+4N4hA!Q72e>uBz^^0U|anD)%YpxQYoFj;-GAN5BtfB>63Cgvr z3c`8*f+5{V0(sKzp3`ic@rJ%5Z@#qtLgl#e^0UQGD>R`($o^IO3Ily;T}N#(>lr4M z=W6`7cglkzxp7YS{UeEX?n!7ji_%yRx;b~kh}D@XaJlVT9VV|NTNhAg{lEzy9*nYc zO0`eP_LQ5d4g59fpFeze?&aR}#^uZItE_7LlC_pH;eJ(r{Cuh z_ro2yN9&ws&GgYzVBB!5T6oRD+yvQaja(FpJxdcvWd!+fl@ya?8SX_4ZVwpzf?PIJEv^bGGqrdXCbz z!d$M|KZEN;v;=3D8f>DAFU}^A@6Vex?QUv+zCrDH8Bhclc>5p9(ib^NQ>)eOqUji@ zkEMs_S&s&zr()VV35HyJ0AjYuo~S%d6U{{hPmrfb5E)7=71UPs?V7={wx$U_PzhXv zXHRh;b|*!^cTYg+#bl~Ww64X<7L+wE@WboT<7VDe`BfEetbQ}^GAhSO@0=pWW4(Qyu=TZd=Z zcFh@oLpgc6(i_`Qr55-%+O-^_+DC)4>&!7)gUKq6&`gX1Ro>v6qvA`wTB_T!6rA3& zh`G9S@~|%c`u_Sdh1@`Rg6(EleS-&F^5JQhPWiavW+Hj~xD0^z_*pqLGZfcK4o+#m za`QhQ!NWh>(VH=8XH&PBJo&(BICu*)0?6+T!Hn;;j_s&I;#eDsu@R5$dhvv+IB7fCP;xRypsUVtMBZBZqjrPbQIL+yp zHr2>L@^!g3jpFN4IeU41HoIWZO9BWnq}`&&pjU!?lHM)~ypqIMoR~u${Mi)x9tgu&nXFUepD&8-1rT$C%LTYJ3@(WM@@pHeiRqP=K>vDqJuC=9B zrQoQ&Dm1_IBwOD$PQL{m%dSW}0jf)9WmDS7G>_aW9hY^b33<2e$7sd#96K^|KU-Gn zrLF?T(k_=X11|z-?!3W$QqT)F9_UfRLBl7XT1%o68mA`9s=|QYf8x+-bahtI>soD; z!a))`5{jv!Z%Mk(Pr6t{MmzYJI0^$=>7eZGh*^ zAGLU4LxK-_MnaDTl&;UF-L_X#i)XYYALIUEEV6c6Z=3!d=GDM-;j3)6MRia3aS6Dt z9dO2jsG^H2l6mRJx>PWMP~lx4sN0MA9L&XAy_NVphqoI>8~L@!Ne@gn2o)?GTOXRq(ob`!>jlPyo$#|p`*Bj> z`Zjt0(6VdsMh@J1U zi#%JYTwohQ3pO)i*VEOqOQ&u@kO}8H+9GX+scj{>=iS0kyKoDs{J*ejN!a(FFE9;> z?GnJ60XVOAO%-e3MU8-9Rw63a8myc=$`Vzl?sanXw;whu%Ky+DNq2gHMsIzwpM4GPE~_V` zgD}U6UgOZRq|RR&`v^SS_W%O`s7R&Sbyg2>HEw}>XdO_YQ4r75{Wpx`c5U?Ch0U+l zw>Z5o_%A*RN`t|hN%t6uFsL{HUJw5Wz}Uj#kFc~J^l(78ix*O}@}^1yog2UtqfvIS zfkMjxeIKK44YWt65?+?5mM`nB_Cw(8Z12Xo=n?EBn5@QkOy2Z*h5!8dlZ(VeOs}hb zM_x@b4l`apj&QA|jqK*|=hvn!kt~;;map0AUY1km-aA>dXLB1PKfP`=k&-^oM=!lj z6`4I;gwzwn(o>8w%oXYa@)l3SMqZ3B<-Aa}KIWR(SAA_%Jn9S5wMpT8+>g@;5#x+| z@2PBi(s|6ih;UG4+VT5vQSV_^+(^_6+I_eOy}(Kf`{$=wW_h0a482?vdf_4L`fAsY zR6p}m#|(pUu>5hF4la1TG!K5caC4Ol+5NAN=C(b-n{nL*k!4>1y`W4Cd*Sy&TN}iX zT$+oRX6jGW&O50T7(D){lzseXLIE_h@4ve>B{I%WtZ{v6HRu0T>j%!PKN%Dk-rox3 zszEM?DBn{eA0Xg#24q?!qIA9)v_NLS7w;UYPCNO?G;_ov`}qeW*ya~HW4Kwg|Mdcu zkh98^Csz>oKN*^Nw13a2)gy=EJ`NtmqZ=I7O=fMMP=7i#W4NS+7+`y2Oue?TSHgcF z{hqN@hX(pQu5kRJc-kJ&fR>COweYz=%-qirvdf@y9g{-CQYVB2_w!zL*XNs}gE=-8 zjq;Gl;SBhAV+`I~4JJ~J1EAVto59?KT9(|y(<$=xw}@PYfmsXiX29Qt-cxA%UpmPl z+SObkJGm*G&bH%bg^w*K$A9m?ZUmP;uan-`Og@Bqwl79UZBJ2qs~G&y3k#UVP#PWj z9b8v#e_$uzFk|&Tnu-dHxo*b|;NZ!+psoEKhDXcIrB|QyRA@%l_xuk_iS_3n_h`hl z_33QGwH?x`!g&r4M)>xN73sCY=2K_BHu7dgk(QbPWj5rrEQ);cgD~GZuu& zEXfbn&448CU_^AcZ?!)WpvBx3M$j<9u70LxEe(u+p#(y4bLS@;y!$Xt=6V)1VQFOgpI3X_0@7)5QY=r zERWKl@l`&`iPlZ(<;c_Ozqe0IIH<_3ZGYB9UTSPS`rtgwq`nr)VK#hEC`m%p;eO_4 zHJS&bmY+%vucWOT>L&2}6kwfZp*{@wkJ4mw95slt}U%$x@D zlPstOw4ca%x(>qW;gpBAhAuu(qR8p~${OHP#DkH2F+3V#R@o*Rf=-7_vfw_P`NtpRePK>$ysxP@$6j z=q1Q;k{@r{i$w}a<|3Saf z+Fkdi?dmuYQeG2~0K1nIC_jRLMSD)N2V_H_$J=$$2cuE~LizwYgP+b4G3Fmm4rF`YfFV5cH~5-x zPzpmD)fs&pJwFFLt)m1K5K{;2Z!{nkL1PJk+0JJ06CZM3lVBT4681Re!}Z}qT2fIK z3z%8NU7ygg@UIN>4TZ*PSCtxYV6+=7$&YD&q0= zu|MSL>=IGf&0)40YQagUDHtg^FqRogjj;md!g(N}W3xAqCC@v75V;+Aen4G^4Bq2a z_`Pv9)cB-co;isdHph63{XlFk*e2S-@C49286aGT(E}v)92EA=k?z}e6>oE667>Dn@n!v!7`&=VkoC91EHU%&{pS0eCpC+F)6c2jq z8dl9+I)wBFdCScZDU~}FfPQ0mJV;>2S(FPo(2$Hlr&g`TqT#9E>MDR}t<7V6sBvzj5Vk z(NeH6ahhW!BqQg&~!1hNBy2b?<|uK7up25AC1V(;@m8@lPzK65kjHlc==M9#3~f6j=O zqQBfRCW*-jFmuSaz~85dou>zviFe=VUjcWE`r zK7VT5`rDj4HD?JzE?wowAD8%qYmBqSI#Hnm(^VJ&!;${|`9)9Ik&L?MK1{DOl(ifn zv%aMH%4Ia>$FN7|DE15&PD2jrLEV8Ml&Oh>Ek=`5{d&AdKnB1(+}(We+!As&HG`Cj zpb~u*M|1~Rw45f?T7_OK>gs8x8=y>qMu>e()Fx%9@W%iF*wrTcC$lvAazF~A4<$Jv z8Z*2#VPI%ELB!rJvT|TZ|Lf9hYM7LwZp(kME0Z#kEYG__Z4v4RUe?+C48ue9pJk2_ z8E%(5{rtMsUhW`8ac`Myuvoc>GwV_|xFgxCyl@E0Vyv~k*Sb=eY#O1qsF~uQJP4t<@PjQ$`!Q?QK(-%a zABo`%5h&p}C6yXeiM`F~ew^AsKee(5Aw*9y3jMA$6U*Dc?vlXip?77Wn{o8h|EEvP zZWt+-%d3$Jc^IoXGp3hIR3FP4+#~-fQ<=%>xKz2hU2K9*k=)%8VsjptikF z<<3Kk!peZnwdK>K+G|58%^4gi`|(w$jr;^oZnNg=Ho*M_^3!gKRCiZ7PRlS10VI*! zp{Kn{ueAgsij3(CEV3;;I4f6Ub909XM?pCt{47Ce$6N&#UKB&i?jB3Z5Yrv-hNVI{ z{=Wr0+>Hw#l??LA>|ek4{A;gHqv>z*Mz92$P|yyYp+4hw5T?wgbkfXL5;TeFA38hg zAt$aX0#tQ!DmE#vqX|=0r2dP2nQa^eolgs$4;^wH6zn1;3b}M>M9c|qJHAr8ST7{* z$&<>=T6YKjjg;U&3caI{?0%gl+Kfg>)U0{AZ7x2K!ziP6x~{m>5KwzB z1izc>vc1ZcGXKP`E16Fmo5Jf}LCn};c=@SP+f~Q5BI!CET%?7UXu8~7V>a%NzW;HW zik!afG~OkQQAjbULu4851-p;wH5_n@C!39Bna^X5j%zLA;g-&%h(fR6oJ|xt zl_LKd=J>-Ufolfbdbq!J=<WBiJuQV4t#F3v1G%)F@BJ^FREZF;0@t6TRtytQKTLl%z%D@)aGfjQr;Qh`2}P1zNtbfB5n08sT$er%N=mNGf53j=kH(WD^XM%z^IczvsJazUBIB1h z2pA$Gg~%b^mQE7ZNF|R_uRMxid{NEw9%8(;8k$%h>d&ndSL8vX?oTsRRjUUbovUt( zvRo+AF`qjr-mJ%x9i4BG(5>qg>>TH5{`haU4i^z--f(;nn$PW2=3KaC6{RtI_q&lw zDHgeRY`q|J2ag0AcgcM({hiduGn$De$?JRjMU4~pve)+$OXJc?*3VTH9=*@0pYFa7 zdZZ0gAoVBJXUyg#OFnfbNIrcJi9;iL>%xkg#bp<-n*5#}_Cggk*!!KE^KsYnXrASt zAM?FXEdBSBrzU|z*KTe)etu}+X_X7?<%9|fCTG20X_`^4HvatBxZF0V7ymD4h&mVJ z=sQC$7V$}aR~EM|1O%ws&E=VzI`yVZPv(mu9@BE1k#gJV#tYfOL?AzD2O9AI5RcCC zW;IcATmHd#1XXIZiPBGxSDk2%HmQ$|qdp8GOi8D2*Dpgp_@E@EJ4Krmm_eOu+2+l%ZsFjDHGZcPSf-TS`!?!W(P z2Oh3>v}+7MbJdXT$7hNOX)*2lziA-`eZtQYsQpKjgRhA*hR@$$5gZWrzCvGl_T6KQE?vgfd`abZYigbbv>GQvPj$0Omh$pSv0LT3*e)34D71UaMIP3s^cTpr`1-2-dvu0*R_>7KA0d(VPXGJ^SZYPOpD9=J{V` z=^pN`(j=k?n>#8L3V-aT7D0@94I@?>ZEtzqWrp7#3Bc6pQ7HQhtpL7hb?fa+JMhRA z^w|OO-Bh46VxNJO7mf7sRDQGwSWkcN<{RNzeZN&+wEQc9T7_O_ZU}Gpdt*l5!B7bs z7r#%Fii5||TqWJi{wS8fi`it7qYCo0(GybebMn$u|L%N3+giJ)9%H9fiaBH4e~za1 zIam@IyOjfMgL_efKZ)zQK_r#o840s|@^y^wh)2yq@%8$u2@ea@$)oWB{GbV-k&>W@ zQyAY)S+h>rpA&&%{Al5Afo?+>|@B9Bj8cN^YEP z^w|uj6?eZ;B^41Y^2C75xbr^baeYP@6@i5I-nb^qSe?0Ib^!^qpiIpTzXKvWw3vKzcA)DvQBEl_*9YG+U%p7>*Gk% zHl)R@H2zipd+a@jacsTqoE(&k^?x4B&AHI3Tkx^lj*LJq8?sO7Rh7VEmq`*~EzaKgR$k?p83e{Rjn^QV{SU&?Z-Dc`zyilb!!Xmd zr4KoZ%VvfZz+CDK6Ls`c<0fV^3Fokw>}tqev_xj9`sA@oa0A%sA|Rd-^#Ldw!EMOj zqXk7EBBKJ9Shs~+hLz9Ly_+az@GsEqcVVCqrXPf~i zO79U$>I>N-9)*~OviHBsM-FI1&{opRLo2zU8cnXk)d<$*GAicMBW5w99}6KbejL6- zU=x2ff{r{ua-{0EuBPX7yHt2`GjZH@j{^YJ|652X$Q3%Jjo3w&CvEz0`rn2?@nz7g zPLr!>#O~8Cp#Ow;MC&vBUlzcVqg`aU{Zz?-{3*aVlyOmY2gw$I{Q;LZmiP!rO(b7z=9x8SCcW+I0#o73o+vg+{ z9=N4alEw){$!-eJoIB;7zWAM#%J5AjW4|6M5&qczZxnW0#DpX0JI2r2hP6V|ur5-X z)1+e!lf;uV>YR~2fRNSy;$^||t_SGhr0x7RpS0g+u!05m0~zHk1b_+)#3jW+a(IbJA*jpJ zyn`#XGD*K;sP_GnURD~P=%?!kg&sU_DoILA%H--V0USWn$(?N^y~57)!O9DY@wMO7 zLG%fxsFDih{Jf^Cq}~i5?OhOG>GrZOrd7WW$P-@!I8FhF1lB@t12DSS$#5^Xf_{qW zm@632ZGXCxANJ~S?Ylw_-_4xbqEn9F%|({?5#+QM_vPk=z0LyKUcK7F^?UmWfw5*q z+v;x5OvIF_geO7B zGj+xq~2Z^rILG@N^wDMo4;?%^V5#L6=419Oma#S^4rI!H8A3Lsln4`7u>v(+u zJV|G@kuPxtaAUq0?)v3_K;frB{}_%#C@&X2l}!QgES+}+1f?WS-;{YkGDs7p(6H0@-ey)9SCvrcQAx60&cEafG8^I zv2MOMNHPOS#w^d?XBOHB|GFCvmDl{40O#IRzsd}s_)Q=%@SHWY#;2rjIkgRREs}PSXnCkqpBg zrmKa8S38Jxol^IlGCQ$yO;TYjPMTNsWwVfuxj`W5VIl@+ce10Hd*)Ui4$GFM&9z@I zfGf>06$u1uX8oiLScC$cMuUoDaO9MqrPO;Q5a@I@?Vy`4A{{$d0pQd?!|@AG{6pjJ z^ct|G{q#wvar6NaJt!}_6Y5?bk*IcWA5Pedb6Uc{!=M2s49~s4{drnzKlQzKe0i=|tMK+K2&>h9I0Y@~ z`e}A(m46ed4W2|kL}u^8UQui!dB&|=Y*xR;iG?h`*=YK`UvjM5+B;be-z*$x=CH0% zmf$*4s-?7tVv36W2Fu40&8U9-zZ7yHcg`6SiqZ$Jt;G#NN*VaLG0*yo#hrDdDCX=e z3XV1VuxBV;MMQOOge(?iIUpyy$BCncl629V9!i(>1Ywy{Fuowes6E4iM6s?gRllqP z+EHCF&W@XNL`e`vT4dyN&R6Pir*SK&BrKv@FpK(bBvQ%H#5#kQ%y*q@krSl_po1c7 z#woTf+YZ)&zh~XvZUMMrm90Tvdy#Ji%e)P}MVCAm2W2Rrs5bjym9}m>vMcU=mh<4t z#&EEcB{DQUuT2Y-WiqghAkje1`dkVd$W&bHbnWFEK)@{jN2Hj^1R5#680kLQxa+4- zMzGYyLNPFwi%zYR8)-aHAj3$^LR@;3+>0EqKWw)0c&kpivCn{k;%1my&|@R_`)|K< zcT?H$Ym=}qM-HZX1Ls|cf7ZLlEhW{osbM@W3f%Jv&4xpgTJ3ciY9d-PC|b;)_>VZOki`+ANFcPDzeJuHsC9+%YCjknZXzZGY}64g zD958qk5Nj$63o}y*e>T)P{DD-V3O1cR{Tc^5bVkEJ&x-@YDbq?N6J!5cCJLVDIZGqL0A742Q;7 zaW?NV!RNw6y;rK3O9wQgfureyIv{D91pB9yOi-wV?_DRR@98J3_wHbfk zA43gj99Orxex^{x(;&wE7-&LCQcD?^^N_pA z=yiq=CeFwPzSA6PwKU${u=5VUWi)GPrW>>80$XOXlj1i7QLgRrHibT+i4yd+5B-5I z0g+qZ76>lOF7Wp7r<^i>dw#kUf*0?PPUO`0{q)Fl6!@F%yX0Znm1b1XVp$WlFf{Si zEyTO>>KH~&EYV;;zB+91+Y#{2@sUq06o6eh1*V!>z%WipgRuO0Mf=^>gTNwL&&MD| z&2I>)#P5S*W&lhSN@3~~BN2kC?TwAYMDi^0h*q9gms>>j7jt^9NE)s|1XLdfc24H*2g&)l1$M)$Qkcjrn~3 z+;YPP=<jJ{@PvU^KJ;e;o#5gDRok%J;cPzb7}y%&Sy71? z?5}O9iqNEDxLDKEKo9M$;!7>pM};TU^@hral`q=Wc5Gq<+q6LsA)6KT$0W?hqk89evteqhXJOP9m& z&q$jrA~o7dMqIvX5FFkxgKUur-^rmXL6n7V!qyA*l)}VeGO-usV70PN0ffvv>tCg8RYj@akIMz2U?z zEX#ND=y3iEF0AZar;BzC%OaLo({U({vJS$jaK zjS|oTM4pMrt!iC=`|xb#-A!PsZ}N5KRr+b2@C7G6r($ABLCIzrS~H`qU`(HXXkm%5 z!)Tn3YL0I+Bork!f&e`W6>8X1Ij^Rnx0~2^NkgQ0cZ{6N5Tn7B>EhdLAiu8aj?^W0 z`X=^P;BZhAS2}qkTXUoO_4eHoz2YqGhXyQKhYRezBwe@k0Ng zLA&=CRTvyYM8j_Dv8XEqjmbX$m}~LDNTyP) zmZZylWq_9}?^b_0w~9}C58d0@w(bkt>39WaBk? z3f08^)!YG*_&*{jsBh3#JS_eGTbkYLHI)5hjBp~NPj7;Lkx^;p09bdY)c(;N;Ap$yRffD^4f>hzvmFK$EQB(K4tR1SLuKHMQYiMWPWQ>}T6|U_R&19R)!(U(&1jQ7$jj1RlyZ}MTLOnjJTfZEBU|YTG_mJYG?WaB&I8lIh?G&?)r}eYEDqH6 zA>x873>d_0y?ud`9RD4v+ETNumTid~N0gW#>v7|q(w6Xu)Dcb-MjJdfo8tS+L;6dMWU4y(wPD?lf$hK8}Y5w64l#mwNrsBKkm+wWE>n(QH zl)2ms*5=NwO6RiP!^yh;swoTJIwdKRNW4K zY>%&eLX5J5>ywIc`$)X`$J|CU=$#vle_n~22o-JiRqz8~$JclK1=@0PS-q;};%kd5{C&ArFH_wsxtnE2`R#^YGP4@c9* zEV0wJAr-*f04=3Q-k3%?=0~_fh{sS)EP6e#MI6J=(K$K(V;A2y&E-O!p~9kLvyP|$ z4i5g9g{uu`1-+(BLu)FtSK+Aezhn+RK@<7oa+35IEWmO0aipcfX4iA8tGjNfcZ-DXCu{ z@j79dv+Gng`Gx(p7Gi&_U05PGyp-Uu;6fg9yWly_iZ{feM{lY{0tO4s2>++m;hpSk z-76cT&i494W~$c1hDBPa=*^JTWgK zPUsUyh4UZ6h8r@Lphj^^lZ06Fc=Q9pVFV9)`B~BES}Oj6FUm;W@KpSZx>6Uh^%>Hd zDmcf&$dNGR(v9*wTS5KJU*f8gE~Yl@ZQfmp)qP@C%1MEXpP4d3jscvP12QY_Q)!Hq za?D{J*UPbH?>f^6r#WS&!=gblg$iUp zq{hq#hQoyUYH2VNYU>Xv2_=7aF&mveT%mh|#;`2AHtz5c2epkb-)w=K-l*qGKlT0{ zH)MG~E@6B8>Eqr>)p?86F=9Q$YpzeAxp7R@dfZVLP0p|U1vxzasH#)RY%Kc?h-`aH z35`p0&9b3Y6uNZWi1I4|QExPEUn-B zoY6Qo4R%r2H{_ik_s1}XH}rC?+)Mh#aPFU(SLtoW6Yh;&*}c!ikMsR`iTR|Z)C3vN zhurin&4cX;~=Fv+C-1x*u|Sh~#5TM8hEX!}DhWa^dK< z^g@9S+O`58z__QN>-)v@8^&O0gCcf3w@lA*5A7>7>A3_^K6Dk&2;bo{qo>CE!>+jcF(@-Ajb$06&7Y@`O8@A zn8cYwTFz6BiJ|c2&6OH9AvBvr;2Qqw3C*bRQ+o^Mn&;(w${##>Bs(QRl*j!_;@)AN zUM-8WvuJijbhvOKhM*Vi$LHjpi@x2TMQp#$_T;a%E6JDW|3S_&O_d77Z4({Y18h>Ut>=K)NtInl`B-l73eQkksdjK&nI=dlP|q7)#IZO{6Y!QZ6g&Z1c*ZR_O0pWx&(TI{bO9k>(#}w2HF%*Z*wRDJic7?|yH=gXnJf{8NNLE1g_j&BjK9O5CcEiL8qM21D1V)+61-G!rEj7S94G56Yw>;$kX# z%qjt={P0BOkm7X4g~en^6^FZFFD3Q)Z?hq!TK!G+dOoQyKAD0TdtxhjEnSiP3Z2BC zx6a114DZTBPVX8*X09jxTea#~0cEGU&|bOe;EP2UUrGQT`$h*f^nf+{ANT&>FL}v0 zj2{Y&TaO!)f#$S6*~_Gl6iA2iuq!#tCcYeR$r;*4{XJdR2M&H(zzBLoJO_FCi7>&p z$nPv7(7L(|8v(-!Mh5`i;-CUmrE*zvKu+7WqyX^(n1fv zmFvni0V{twLUUB@J-7iWY`z{IvwBHX)#`FY4>C=B6(Xu}xdkWKzq!0PDPb!3C25Ik znQzHt^i-)BDlAB2u-@+dDra;V=n~C%w3xudE;B(cb5)zhA_woercZjuopS-H!h67| z9fi@+&S$Do-_|@mKl*|Q&Jj;PNKvK%0Zjy+jZ_X&NfjT5oJ=>B5ckl;MGPkh7h+ND z01GV`h$Y`GN>Vs)t<~NKPRmr#F9<2bc$)iGmd&P^#U+m^cx`Y4y!JGl0}o~)GlZyI zfFLVbkHO6W-~Z$JtcoHc!X+UnciFC$g=9%K+&YfO%lj#++BeG7pR4)@pNi z*!c0l4NK=UN-$*Z@9ky9`axpJI&^;M%2LnygIqRD?0Slb30_ zpVH=^klr*|B%_C!kU5GtSLlClthZZQQA;it-r=$v+E)KtzvG9YkJB*i{*?6AwCRLD zm28x4Q0AW9_iY-C>=XEp?5hky!b+Vglp5(L{S1NeeZv@&y%Bi3Ks@bu5)SA*9Ls=! zEik0`Fn~753>5?7cLx42h>T^8ge4>vl%w5%G~{3p<8t-N9hhhv#tC&?Z=p$Hzk3VI zk9Gbv**yk7uWena+WI%x17Z2zxIfy#7C5lquVg!d_Qa|aAYvEbq2|(^z7GiKW!u1|5)X*C$xAnwo*xup zva&G;hZ?CgSgB3<#^;)Y4V0w<70EzYVSEDV8ctUhV%b?!Sc2DGDQF!4$Zxf&pOUPK z0rAUCXExVon>~C8QN;HCHo^tVV?Ge2co4kb>agkkcz?qNY;oo?H9*bZ;ugqz3I6UI z8p&wv3U7A}`@Z3O3Su7nydT{I0;pnZ@Fu_?$AWb_2}%@m4v{80?$1BK=+nB5ytv)t z^AxTW@vdA9(PO7Z(_TdQQr%#O@f6k5C5gZ^ReCIU9=p1 zkm-JO+n;s5{U?aOcUK)M6z&R}ioi9L(h*4SM_{ZRx6FWEH|Q4KAPia=ps2EXVx2Ne zC`aq5?T;M-__h!kLhR~*+F0Nt;GaJHM6Z8|gh7(brS}nzW7__Q(h_HMZTIsn6gEgs zHIV3vp-RBB>E-@zrc#>1_Wvaum7{q2-b;EN57UK$E8w!w=To)CS9DU=exFZXcX%G} z7L_(HmiZ}0suQ{&HVR4kGY4Wrg*|AxT4Urg`O4d}5*r@@qQ*lVK%-vH^tt1v#QWiL z;UnlwGd#Hfr%kJrjjwTrFa(Wji;9}^eNT^>K-L2p6+BNT(83n^KHQF*&Vn46J>*cJ zXKjx8%uFG=4a^^DP3yitZMBVe1G2tWSIfkGr~r#-zSs-M5O+Wphl5i&jMW7E;OUAU z&W|8ZOZF@lP^np!4XhV-+qrB>x1efsf~PMHklwJMm|w#NVidTd+wRo?adZbj*1QY2 zQRfT&ia0QysY=0#7hl%oQ^1&d6$C2~#{tS<8DORlzEOPcTScE-&}-yH*dJ(ZpEvQB z=x7!mOF#*<7H%X4J^(U_5s2VBvS_Bb2AOE9Rt17=0tJL$rlGMwyJ9$ZaGlr&NnxFs z$pAxGz@cM={82Cn8PlY3Ih@s^!<52jVR1MMRgMC2DGVkjp*x1d8GOFYd5>WD` zRysFVrN@7;+Iq!wl5hXHFEFVuV;CG5hxOxSZxvc&>;Ko~gGzb?p`1D~pBe~EM)cO) zs(-m%->3Q8IG_iv1?UD8jAJA+5_x8ewS9c`ly5WzN@lcGF*>VDs7;U92~Q9{BaM^^ z_Rm!wN6pNeR7^esHY7E#rU?k}EmNh(1{lW#>PfeI7jQ>a@vwzPzdax7;gcC*&G~1f z+j{=sYx&?;?HDDwWT)$7DtW6I|3Go~u8*;Ps9H{xeBYdn-6;%Tih zNt>v9(ZiQTZKTi47v6(d0fk6Ac-S~j60}y7UfJ{ssidLE; zMD^#R-HX4alo6tk>+f*(++hBeH}FSzTmw`B*n2WIJM2>O3l#WHvHT}IX>q)>KuB;h z2#0T!t|2^{M+?9C90frn=-pf3LP&Q$UY>hBacyZChk9Wa{(KM>nGT{-V{b6t1Pm;# zCLqV@Yn|hs;I_p2kIaV^^H>ba=U+5CCl%j`1cWs^q_G5)_)r3@J}oQC2{QtB!S2f) z+$W7s9>uMFZIQ0V@^CByz#R6l!XJ!?1ozbugjlt57^bQG<&35h$AZi6(+<4hagv2t zfEbn!&0wrq4fCCp8pPZ?wX)n|BO0XWfX~n*5qFUa3b@D$3-o`Rob^G=c>H(4RwhS^ zZkl{|v2sa@4u?r-&}KDmKmq$*2QlU&zhQ7_TxbrAO)r-6?_)H(xA;Z{kxX9MAW3@x zdB^#SHp-mvwS`7L>pogkrWht~?{3(ruvnbLp_X~hE`t-AbolcXyn~)+X&O*Y|4IMf z{^C5qHm9=e0_k%92SST@<+%u*EN1dC`r*(%Br--!8;R>g-)i|6Fo%YcQQSI#>u__e z>yjx^la~Kip{Rpub$w`6nRnBgXSvQw2S{|Xn;PuFlmiuZhg@W;DmbFg@w}J&sN?zs zq6E2cDAhFuYJtK(p!ORe6Dw|^Ei%1ZO6PaWI%jQIa;PDbpf7OeFtebgrm)xCwbi z8>&q-RqIX9SrC5_q*s_65cL`X_xaSiBF8xnozRNJCS%sp1#UW0?Pgpp<^|i~Ky5J1)|(icj+?#L zqBL2qvl_z!Np;wG^!VlYohI>~x*}l|JwL^RK4jmb~ZV z^pDE#YipJSL(T{cAAyPemECjFu&W-3)5XWH>3_9$YV_7^yy;0*-jD zxtFSj%ubNI0Zmqncdp!|ylpbAx5y2`ZipP6o*&yWX(SHgzYRguHyHRdlxWi^gao5O zKKZ%zA0!A||MDG%lQMH%IehQSMwfU6;%AW6XBf$;MhjJIE!}ka?Wsn{@Douq3^ z^42_$D%JMVwar_MRo`2wMHizu84{=!Odm!1%GHiDrgU`PepIf}JLe}<`ucYj75EQ< z&=d}4zD0bcdz1-=G?Tbzq=Ud1sZB?dX<{)`7Z8JGOh5>+82kEJw|xQ!&TwZi!Ok!= zifQiI-x(9nFJE1A^w$7>oiM$;EhzHU!F=NbpDpVkDc6k_WQ*-NH5H!BFi~kk*|Mi{ zW+XNVBwCAu2J$$oCJucs<1%1ry=8?;9sEbaxuSYQy7cB{l~B^r+!R9ew4-Vuvw|0E z64!rF5ThSLfi$MJskqcloV=;+bK1}r*%9*vm3TxbgJ=ePkJ&%V`i8UbIet>)y|av;A*IA6B7R+y}?3od={m! zClHUMNx+WPBbzF`Bh6C@0|8|@M`x-}iYK67dm{gW&e@Bk;`d?rh8<`MI}6P()tP#C zgu7o5$&Y^RPUE7HIabg)h(uV>b&5ISP9?5@f5~1UhK+BId=mSCtsz}2ffTv^|6bn;+5vkmQLE3qkgr$@DWQS0!$*gQ`LIn82l_s%=`Ja(QC*z zUa9vGn?Gn|gz|VDxz1zMLvFeLX#UkjGlW5HB2ftj8JbK6uLG@%oD-AZDp)HwEERAN zID~|}VbVwD7}|Q*2DH*YCr)$g82cN9dCK^%i)Ot4jtu?vcHwwVz|dLcyd34L%@0E= zdc?ZIV|z&JB4>@ZS~f5-u2gQUibbMP@p)kdz)d(5mpWgK1IuN&`f3Cu_}or@bERH- zOe>G{RQ07L$3KM0(B%Giw?e(#wR0GcA{2Uez7M z>Az6#w<#&C=($HSMcZq&X_+w-Dv{=~y*gQK@ND|}1oHX$-k9xu8I?)*6Ll`z4b}h5 zWc&+ZBp&T;n-b8=VeWrXA>fgA-ODiNi!x7EgvR}(&xxaN$nwl6ABY&I>VA%v}^AJfG9#u+itAJ?Gms_*w` zQ93HROFb=B4Nbuj0h6F79tpb&+JLcQy06s!{@46{q9S@Plm zjU*b*5b%#8%5DYj+U)M&&^1K-ehFlU>^ARzaq6Da>e@E*cLlRiJIg1DYBI4R!43-3pX8EOU%_7g0o369yPhX={?Ks5+w_t=&mfT*PS!}+qV3=Kz_i!I zt0h9^!-JaOjVA3B(VlriY;tYRXr zrvtnTjb$==svb)p0m_iFD3A2-J7PBPgvf2X&uL^S3#++Ax(5cL>!FrCoV5G{udY;e)*h+WB8?g`8e!jCVfs=2Eozd`T@Y;;axCO<}WTa@c6@4#DnI&71qX#8 zU*Y7R+J9iR!L|}D4PXGE5l!84GDv^7Q}jeu<)in1@ovJOH?`ND-$LjxJ5Vq zA2rw?-@%@GyV)FD`Ks9*WhROIUiXyZ&S9a3sI4D6IHU9U|D8nW0c11=ge1H#W+P#m!X8O~L`KrC^q_LTpU_uh zAU`)0op@_74$84Ik(}rMa*)IJqfbUW@ebDK)B($=noY7MM#X9A55rOq$|UY9Qi)$o z1C?7^qO7zoKTp39Puw%c#Vnt%Jy#&B+N{yapm~Jg9rsT+dO^AA!1Jk^QlRL!qt2tOGa;zJl~@L(4Z+u-LbPm+%+Z*vN2#YWZ{sZ$D9 zyKQA=;1j|LW^_NrP2V$@{o8E)xZ7X8dwTRaNvP>mTT@ue8mti5fml`EuWP5D5{Pmg`YQjdD&f#@IKovDz&__t;SQK6xg$2ZVFY}G!vp~UjV!LDkY^R7 z&!92Tj><4rOH3|l*TkKL#KG=lQT|qohsxW|N|?(~Y5b*bpYT}P+8_?ARb^*8!dDyd zif~`^Y1Q+SNILJ&qoRb=aYqKdNs?+U&{%Tq$qCJ;bO>!3qxkG?BO9Db2q#QItw*J? zrm)%B(<^G7p-J6;#j@DC?aN*J5?}nZO{PQ>T&xDyHP$HoPG)J+<}|hb+kC*i9S*J` z9z2ZmwEwew{^#++ZjjhGuty_fg7h#G1JJTxlQdU7xUQDP6E%N@)#u952=rPTzvy_>9m!Z+zDg+jY-;^*2$81GpR#+-XP0y z2Vg+Sz$GV2`1aFpqfnTjiS-nuXEXNNYmyhI${oqtND~Y7k{en3Ei#m|3UG_dX_ype+n4IhTd!9nnTi>B;GMtNmOSw)574f-52M z)ZFXOH|7tw#mXruurvYiu;R@D>ycm%Z!`vuQt)_`u#v4faeq63$UlXd1WkO1C$M6C zQl+)LtC

wAr%6#`^)=6|k?Z#!;#jiIZs8YMvr!JlSYgbhocG347H{#31S0OO=#$ zXZSHlexx5GGMui`@O~#Z5MvVO9GC)*nBDG8yNx#4y=Qq2mRxiWphJ>cEqP6u}8(!;)#d&$`cntI2#Pk%MT_ zT*q9-U|fwHzwoAau637`aV(f+q$igM82Z zZjPCQTiC{#CMxxwK8A$$bGr5Vl7(VZbCu=AWjGfFt*9>LL9Gq9aW#MnTPXKHP>rMb zi>OhMp8C;s*^&WZV~W}R+Yt<3(xJkpG@CG-+@t4Msx_wTH%P%$QV1*#8+jXL%>h!E z^*?Q{viqBIt=BYP5Do%sqwDDlQrX9_TIj#Hlb%P|J`It|Q>e`BLFy3`%CedLHhATU zEk{W4I|hD%F00=5wTl(f0SV%Py7zTccYARs^M}n|UWyg{n_QG`dVY$ggHS?;vy6ue z0k^l=UwTp>|9KsX-N~s_cU}9FBjBY|%WB5ta;OPD9P*ndOxYEtP8$!{7MVA()E=ki zz#X4Ro@?YGv-)*Y-#9GGebp-u1v+mv=D%{jI!eCoV1_jXh`+<-#r~8OMG8Q|(W;7g zqE1#(lhBB=Q4=_HmT>1jrSRgLafmGb9oJN+tePDj?&=S49WFBMtY2pcTWSA#M|wVGbsZHIT-t$N!pL){yqwm;s;7;=2W;vyu}qln>Eo z*(BbMz0fflNx*D-@Nk=D->U~Er1%=+fsbujM>x4G3I){+$AC7==&1wAeg(#)b8&Kq z-XLv!2KiVg#zuJNbew+xBcPD7Lo-ABbbW?qUEx$kT zr0MZ_m;U(M@w{q?SMNtL&f2BH-ftyh8~GF4dOXFM5!0w=bxE zp10a;2H)BclHiwc+W8WdH4k2y$F`D&PI#I zb?llY0C@N|K-0h*Mkw%t)wK)$N$1lM;JlK)bOd7WVG#SiF@A}4v0+ah=@*7czH)yJ z$irm6ha-K=I*`iUT+<95g6UGN>B*~LWe|=+rrej{RtrHiA{s%b{r?Ti>9?4v!J}ok z@j-KIzG5c7EfW50i>m;(^~{f|>8EwOrfG!BioGO#%c8 z@Hf3P%J=CfHix!Ma)@n5A#z0-6RKET>t?1yuqZCtWCp ztZ1&9Z(uI3TJRaR+}$FLg5T>{E>z{f@_=3+^ZAS`JCL!PMISL7sEV;<$My*w8+FI* zA}^=;64x`>dHaF9G0XLic39DrLS2%#15e@Wp*@s&c8IiASs)FXC32Ze5i+STC8-`{n4_>8!dv2IN+-2=tV{3#3!UE) zA;x1*%3_7x%@~jLJdhVt9e-4(`ky~~mldr0F4oz=mk5x4`BEJJ-a~8R^L(2gGE}1-kg_E^=ECR)%p?i6^DQ;v{sJQD zKVjTm>|~c{n!KMN@`W)3lagcWi}E;7bZqZUf}n9tE1DGo>^}_A?~P$S1+7@_|6`T9 zVYACDs1AEMSfmh+C5>cK7{iqa>tn|uiGKTXh7e+&t|$l^3i`B3)sT9Lj{-rQ|JY^H z4fQGQ#SP5GW{t5|9&a*AL;MC}ki4s!MacGd15~Mrseth-) ze&2WRA9pZ@9uytt?7g40*P3gtIr$^(w#m42zLAXt4zwCBM?RHqi(-et4ZX)AvX3Ve`EVkM*ClCw<=&S&}%e`338aNXKS z3az1DubsAkKwIXN<4vG?8ikOtdcnMvr~w_<}IbXajPEZj=R z%Vj3b)*%_^`(7H|C~1_!%n=qz`KLP3k`|Lg5-w4|Q}kPVK`(L8U`c0KTEQr#^+TWZ zKA3b5-@k`XKfU)g3v0XyZECg&MxCV|Qezc<4|}@+T~J^5RdB|%jW(3LTxIk8dl5)V$f>g_p!OZ5A_L^H|^Y<+tIuMwORn{1ZJsJh;HN3PA_4u7>cP0D?K zPm#N-rgh58k?gtLD>Rn^SJJ7cxEOHVvILVB(p+I;d)9FlOJ0e`Woz2SkG(ta_;0fz zl6YA$$3lqEISOLIbVryCI;RnGh!#Z)Aw;lXL6ZnNlC~17NH|_O7*TeMC&k5#4eCL< z5axdCXqvu6o?V}Cd-b8B9FN%7Bo{}!k)vnlx6>~ZnK^Vr{vHyi5U~U#48w53xf!f+ z3I?f*Rb!_%-wyW)52@6U;>QPHs78dxof9cCyG=wgyU`-}6sR{9M%XY>u-{8L$56Qm z_ZF53qKDTodWo)|fj%dzMY_L}9Nd|X@>u>0=go8j%hObsF&+Ewfn2wU%d`z#;!=6` zKJ=YD{p=xtW$=Tx&Z#wt$k@~%$em&TzAV|6sfJz|kF>E2j2>q%-{#(D!(l$tdT~!$ zd<|#)=oG7|XbN65ntjV3CM>;-Po{9#Z}3~N$P@Y#nJ;z!jA^z$18$P>>PiCAG4xdB z74w(kc-wi~ZNFZQLc>eyT854Cd$8-pm=wD{3I^s?o z@Wp~Wd5eweTc&*q*!&;;AIbF?rkqYRa;Wafjr7u^Uoz)+kWZOezAqP(ti0`nW;iG} zWva1N%ZrF@ds4)e;<3R@B1OOz=p%uaZk-+3F2f zW@vmf`_>&DmP-GQsa-v%2{VQxinPmZE>ce%ZC;Y$qggb=ms|P|X_=JgF<+nDpvNQB zh8XKVWNdk4@b1KhyWFs6P^D>+roV?dR>~B{IbH`QB4=}WUQiL5((tO3HE$6RC(71w z(9^wqOvc7D!S?KJOC!d3W)v>^K7OpagS#}R^N-BBs$stgRXcHJv`x6ay|H66njmow zD!TTfe}&9j3estdlunFzEci3drp)Ki@L}@w;nwuH4sV;V=~bu^c2CYVFgIG5fiD~p z>VI7{9k_@V+kxL&O2In*Xw(EUlSc}RWjN#O$A+7x-@{n77hNIk)PC&3eofL<(3Kyj zZ!fqj-|AxIz6+Z40zs7D$pa#|-?q)uoIKjRb%w;;#_KJ9M^8V@DI!@@mM_-1yFY*~ z=GTuf9^?o6$`P8KKO;%Pi(E4tDk?Y=P>_(skmRJqH9Y@~6>`vUS5}vf2$Uu`po^KM z34nIBJh8&UGd7l??`nI2LRTglJ?N`TU*hwv4id7ga;z>)<#@ka5=@!bnVN`8|Ga%q z&a=0afBMA?JudYUD2Ko(AY9xpKdjWM>v0z_aatsHIQw84jh2;qJ35#q{Jp+Y=}yLo748rk>SfdHE3^* zj<|Z*juar7DIHk>BRghN)dv}g(^mC(Lq>wH(E~q|8_DO8QQOUyoOf9buQQv4w%Q1k zovkJjIkvMeI}75SRIZEyQy=2n*6fGcU~SRfMzu4&k{9V7-JGii%gm^NwmQ1?t2kedz1RKKF2I=4ax(oV|XkEM#v+Moz5*bGm7O~D9o=a@K79E z>KrfSjEGQlf+{qu2*|uhNZ9nFM~}h3-|QAe#`F%(5i^z4q_Wt zH9Ik9YueY?nc;;9?Y0p(wc*J;JVvsNH&%O_a|er@Sl;#`v%VdLz_1$(ctjMl0N>Pd${gcFU=2 zi!(q!ezJKT2>2FffNwE>O71SVaxA z*bZiR^a`mh7;}v93+Ji|?f3VXx`%;i*iKp~1renK7j)aaI0DO22zIuY~zXovmw@ThbV_J%mdvcUzb~FzvWabQu&f?={ zJPLx6$$Lmyw;eyPv2xUEM5nw%Q$zXSun@Mwyo@@Au8QA5HH`k`4W!Uif_+ujZcJUm zt&cOtif%9FXiD6HiG_jcRwlQ29PV628Ls=Cz*nJMHb-d#Y54~GC)3}T{fd+y3~LNEVLp>S zsD>KrCSR`(4L&CY2~re!n@RHrs$#J?roQSdF5XmMv)!*bqTvFvw}_i8lW4#2QJ3Ez zd}+EwMWR*}Y7~gefxbl!NRy8u&jR`G#N&d5j|cCpXv)-b%or__;R8o+X7{&cD)V`H zzrAv_T~jYVQr*|{{Vy&=GGQc7g(`L=H=|(GrY!+cKIdj=cC<>J0FT+K`r$Al_68#^ zt30i#pdLNG@kT*?!Ygj`!kl}jgKHX!aw+NJA!v+AE^>Bs?9p3~VwpLlv!@OJ0;_4Z zxN8$7ik!ZAcb6Cy;}$YI+O-6|4j#S_u1a>XBuVCoxoL|QvWkU>BUBB#1FCPQBDYIFoTEMtN59 zE>+nIYp&h4smW{gpF#`bbW}*B$s6s#m)EL_tgAdg=1xSyh!m#r1FF+m|Fevc`_2X-?2J$To$a{3PLXj^DpF)4B@hWtnd zV=H~CaPr&ZsC5J?Zm=Z&7&`A;Hj(Dh_YKH+rr<^)4@SKZiB%7eM;`F0CZi>x-3|0< zvpQ*yfVnU7kYqXp`Gqmy>({X?_&l!1`X&tkHQ=%_;+Pa4>lLJJz{E#QCc&KdGzt_QShs0b|^WS zp<%dA=7&35&y8gtUYqStFRm8mtpfDIu_@6pc`@kw^H{ryiAXx>?oYlz@b<+fQjwW+ ziQ0EN-PyCHFQ)KkUMG|MB`3~vb02Hw)C6s31cT(HTP$44t1tfCSJ@%WN!i*}akjsY z{=!|{v9941PVvZ*iV+^i&N{Bl;qwO!tpna%fzK=(cXwe;-E~Avp@W^naHd>VZjh^7 znML>4eh6))v1eQJoT+5`a9I&Em2HN$K2qIxpWBOSoNt(Zl$xWnuO!tp5>L zX2rb8B~Z+QitsKR)UaQED>BJNM1MX_VJMP68jyQyOv;8IIir8@*vMzLt8%v5f~E(4 zXG1EJxR%s#3tU34-VW>KqEVz>I9l|FCL{iO-^Aa+dl_rt|P0o9UqwAHtN!z(oXv#|>*XXqNvircWdZ~5*^6~+xmRlO6iaygJ3OZ+l;diPq`w;uf+9P?eyxvc2&)uEpzY1{rop| zLP0O7`K#(?*w(#nxn-i&l{f6Mqv zx^}C^IhE;sx=->JG%xdewZOOq8`Dc2TD!yF3-Mu z-vy?W#T2d+#_x0(6_f5kkRQa8->d(wz#=)ahIbbby^b9G+ZmNpifXk5f;$^3-mJ2S zO<@0xQZ#6w;xh3opZ>kEKvEt=|7(GW>b+K^;;Lv4rLT66U)FIp8kPe*rDY&}76s(#@9||uxUlgYK^?!}E9roq(8KJB_xtq@u2`i# z$a>;Iu{-c7d6t>XAGAviw_t;i934p2DgafNqrX}V2sf-W-@svunYi;ulkOW(97{1+ zeUfknKqlu)6wgulM}U{S@|x}~lW{0(WxBx4cwlBY)}2h6vSQ7~T?G7>{^?eO4HUz` zTbrsbv<2`d2Y)9JK+^HKJk$oX=eND7qJ{uYxgnnE3$V1fkFHbSvXdc^Eda^b_X9!x zUvfYZ%&heDQ8YH+M6>WSsvR&q(F4fzG`0?z<&iy%%iw&yhh%UdqT^n1^_Xse)^#-f zlbOaXz}a?NKNtXMG$Z)WKUP_fcK4NcDJOvvuTuvZ42q5Q?aMX+F)VjLk%qmO0k&+J z=MKIA%TuegP#kA00d2%5plEoW1^SXDc6L)$3pFuyTn7T3&X+f9fu7%JeM11}1~5nL zo*e}?=Ah>y;#l713UC*~Bcls2s?fFA9&Qe%T8LAmF%keK7E%ET^8=EJy0=*+b-I1g zow8=5-Z6NlEXfN%C@XoogEqi4^dVyHyJ1JjN!pl`MU>Ark(C$Szai0hU&tg8ib zQlB2cL8=D}y7N5(f;$fikP$L2l)i!{j(hq(UVyz1Sq1NFtW69dkl7X&s z(Dv*C0L+gfu}Sn04H^a%8G2#a+zj#)eP4hn0!LBNafT9FB*>IO>EjGw>y%&vU&Ih7 zn6_F7>;WF_w>PBRGXkg>T1>bU;M$3~ar*X3Gu z;Nut^0sLA(f!1g3{=Rw_#iAOrjn#Myr1oS2jJ|~zmNTXI0%0-nyqoFR{NG=icAFeS*m?cEWPuks4t;mHZbO8w z9?5G`y0?v3?+siNp-h}32TclJo1X=mISSI0s_3_} z6D>v_hTZ-7Zz#Nj!ivj@h3!f7`w55pBW+`5@NB}XZh@(8cD}(6CN1G*mYz`82!$}> zA#rpapTyE_V?0Er3=V`U>%D$#RbKz5;s zv(x$J@P=J$FC}&Z=&%|<(=@s3ohC2moBg+SD{oT?O|UZ!iDmf~Q09j3m1$AxzNsRQ z5PAi>uXX>xzX5Q{4MJeVoUu3KE%Rv+5rxb=ry8u-TaBO@HDkruAk{+cB{Sl~^CROd zd%abrY9IKtwa9x8mPn%Ax-&0J_xXn~uxLZTgL|rN{u4m~6EdMP4L0Bvybq%Ml+J90 zTwljIu*&$sH~7eTjiznVNYWrE!ef||QQ{j)t3S3o-!SAk%wL->^n=5;Zgb|AMBq6K zoEx-qhpc{W3JaTvx4vMLVe$p-mskKP>()iawFBt6P>%8)ID1()rlAvG)U56e3JfR{+rT^}KRW9IZU=NnpZLBE@3c;x3` z!0QtoZyDS6WMwYhjf;kdAvErw8JDe7X#rG_gwN24yR`J|R$epqC)#k`hIfkH6=#9` zI)$9}Wzqf12A^(p>={FjxS;Ppv2c?X~*Iw9Q2C(MEgG3KI*(eie#7 zMzyh!MUTtlx@g|+H7I97rmiMLHZ`$UVKD9XZ1K`$e_dq@(Rcr%hK{Jc5}6OFT+Ko1 z5qHNQXsA_v8tiJ#x&S0ZH}cJdK|0ViPtki_!3n}w?MfJHiG!}Tr6H~DRAp4?h{=mk z{HwAu@$)y49Q`1Cuy{pf)tt6hz$Nu=qoBPw1={?GcZ7AC&9}U%acd&EED@kmC~f^LM^g8&Y2L!ypokqmJXswax3GXFU)UH4HZdS!HO zz}^B?Bcbfikx!_7&Q&odLBI$?>OtM^sAg#G5K2Bo-{<3Nc64dG8Fah4ruhs7zZD0+ zFULCvQi-W^CnCG9!h3?m*OeHVmL@*QA}{ZtNbZon{9E;)PhvB0yas_+k0zXIbFI`P{YZ3(+46RmlRYU^!O9llTe2yj)p}AR|5I5^el&CpMYsN=kb0h<@F5x&eb5FEPia9idwn>6UqZ+b2jJx zZ;fQEB&hgU#$SGhe*unOc;5+%IT#f~dbW6^u`SDwm)zR0VVcen$$Z zR*3_R*$#QOpT9L41yC#{Pm^45G1<6I%EXq>5=?e>_;5-@d=JimE5oL)8Xa_GDM}+>reLJ>KH}AR-sQLeQ#^ayK(hz1 ztU)(J+kYa9&ZgzC`ikiO3nP6G<96gkKUPNBoWhcZ0(eZBNrx`V`=@X#PMN@Em2_&= z02>DSjF;P~AtREyMu!rj4Q{`-i|AB}#Qj6v?vXDTq)~sFkMDh6dCfNRVyg3zCkRLn z)fCzJx%!^3|GRa-!9d4SW9(h64@5!LMIDT8WuvHcZ^EFY-2$efz;dnVvH!qUz;7r=KCk}m&Oa& z09bZMs=Q?9iGkRjPiHuezvv72oPyiv-BRSNQtvjAFDArq#5~3 zGatWx(frO!=-F*N#$J90Qq+1}`nnJkj$Hc;m8p5bX>8XnoHLlQ*!(`23w~6q49_%U zb-c{g@51AoNi1E+(SVs#vRO>7LH1ZYz^&(yH2f_jNv=aW$Y0hf@sJG{FNy${K8lM8 z%lt0fV15ImB8-a)n;oU`V#Tbl< zhPhM_{_`yv6MxuAP;xyCaR%2PR1ks)sv?J{5>YEd?TuhZ5Tdc&!&Mm9X^&a@QJ2Jm25F}C@Ib+k4Q-)L&9!W$Oz zCb4PAGX>$$4ygVv-ZvR?eHG;=o5sPz9BP&R<1C5M3u@m`BBLa}ivOq^aqo~-A1&Nm z-%Z?t#*(_?wbt(R&(zHq|2MiX(D-p-I^KT$9J$`V9qBj$r|ez7zQn2olO5-KI?9re zvf!XzF&gr+hcsG2KSrLZT0-6Cw`{aIQV0g&du@=l3g5)Hft-gN!rt1dg4&@hb4?+d z?BeAv+rfv-yLX}#dT8wQuW6YbNP}S9b$Ps8?leXYikKGYhr`cauFN-VlR=~4Hi?1%%iJK+iGJ`7LAPopzv7QrYiF$82mS((nn806|D_L*RG(x6HS=R zfz9tL%4Bn^=eKBjCGuOv{EMpdT(tRisNx4OEPOUD_(ge_87(&RSBdDFA=fkX1%L7! z2jHC8?_Dg&^9^413sh7bq*}O9L^2ePuRd9n7hb*AL6x`*0#Xny*2_bJUK8Th0GQp& zp~rgV-8AtS-YOZ)`GzHXgVbk`YPG=JxB0HeH(MpB3Q4L?d@gonp5X;*V&hXinZ0np z*@Ebhe=6f;7)md#m$Asg+}Ai9@+)xvC|=gsc>K_?RWlU`%PzRq8<_DEAROTN5%5FZ zM*H)R*XB2mRWn}WckfTPdkr@+;E->B!L^YLa!HZd=}cMS zR(I~Ntlb{Q+vF~|(>Jtp3Vuo^_NvqRCoEanVirG%gd=C7wgEoA7m2#<-g!W>nh5%# z5v3w!Rfvy`pMz**ilpC;Dy*u8mi12}oOgd5E~6!*9g^*lyil}fyJyr?Z>qePX4WoI(I4hw@3 zv9fr;%iRV_+>R=beU3O%OdgSUPY#q!l=q<3M*ekvDi+&;7TF6f^cFkQfSiPc^GnH-ei+K`e=jntQ7zQ`c?{scz5bjT*KoK zv+oA1}7tnI6 z={Y7(82cJ-FS0B6m%LtBoy=_T&8@F zGdF=d=rk(6u)ExJW990djW<*_QM2a@zi+1Mim$n}W?^nlulmlS(NBf$?d@3`+@jaj z+7Fl|Sg&kQ8pf76$x$>Pim@rTkAfrtQgrwEh09Y+_Rm+}57nqC?sgORKLc)h+gA1S z4@noy)lMlk^97r}r1&8sY>UE>i%B+fCPn-1{K}bJrSwrWZ$f<3C?fZz41p`#o!JFN zW~cUVUZbkjcXYgEt=;}O{RtEKBc0&&rRd69>CNl2rsAF3V4cKhQehXrlA!l&$l=qJ zZJjxWeRf`Y^U;QB!LM^e>PYvOsi&93#0%We2N$D7k0q9%Wu^n)MA`T|WH%&Db;=jK zi6H77tXN>|G=brBsTz})+Aiz?X}QZDjj-CeZqcC_i`_A-WJbMyjU(3YBLRZ9ce}Hl zUPYQ$n41dx-7iCA;pH^nL*CKj%hJ-$kC%_3T@*_jgsnLh3`Wwd$o&5P|Ld1DDs3{|R8C3goTY6Q zV2w7&Zd|=R%7gB4(GR;B;>6i_eh0KLMcvnz4{-)368wBcdS!aj=@ zX=3o7cUDc(rT=WB`aYHb*qanAg%1yh;>tj%BxSd%+3T~UeG!$2W`R)DZG7GsRcbcN)4 z_o2gxsDKm47q=%(o9xn6F)fR69OsrZ_|=A2k8t zl$sunPw+@8VFjE`A;9zMhcf`&&v7L_6y@I3^Eh=m1Nm7%8Rr;W?&G-x+T0Zo+cCt0 z=Nh20E}LM5UL1kdn*w}Fb5L8>KGG7uU!1J4XO1|{Gq40MqM!dWT3M|d2H6*$@NbRr75dGGEgeEZ6!p-eNC6f*j%wo z2(Q_Ts3eC8)B{8LZ3bojZ}(6)V#I2`P%6*gyX6-_|IPjo{SP=Tc4mv8snArj z>!Vp9j8;nFSQdGEkW0DC$k*$S z05ClQ-8>yIhoFD_;XduDF1VwU7pZ>cK_ zn*$E*7n{UFcgVS9Q%Qcm>N~JP`N?vUtB`0iAhiF6H6VaZ9@DwXa;Wt)AUftM_Am(p zD9jZco=-@fKoVygg&nhk2`O`oiA+>Vd8F`-h;pq7_VqUzCHaD`=fhdw!Gpy>XsseEj*Mtaa(eEpK zPRp|66`jw7fvPnLx@r`&*He1IZJ$dC@0DNeVRRv2`E3&R{lOP4Q`GFfKQS4r9ybg; zm-)>=B6$Y8SB~)`A<@Zr)747K*)XSqf+PVOD~#uCj9Ex*U&ies=ERMU405 z*`eM#vw(1WwvR7RUbgb^Q0CVrn@~~yydL^smeXopp7(q_#!An&Jm?cC0=r~SU7$MU z7k;xF&U|XllfzJMVdT7ICEnxlnDB-n#K`Lg9Y$K{UWVee3v=3>IK;;|XU|D%2aI%+ z0%wt}s*#!JjKT)g3I&AP?-kEJXE|~s?B1sQ)NTMlcjgzzrG;AiIRLgZk#SN(e6aE> z^qPVWc$xxkwm1Wbs6mS~gvulD96NsuX3d;Fr;4ey#-FV_TOjSnGJi;}#(sn6X;1*M zu5z*K*BeHVExRiyaHaHcQET((tf1ZpR*vNdtZcyP{e;}EnP1}HMubo?p9U%uU^)?X z|Ml_HrgO+RWL$nJE%0_&{RHs9oO!MsXJ|eoQb->bAR-Ux+yhqxw0?yimXi@+nOFO3 zFTbz*>kR+tk)LlGW96RExE7NRtMlf{+#D_#dEvWa^UPXvRng;1AG1% zhd?_JG&r7+7Q{IhX(4=)%llbpZ?-+!2J;qsg?nLt){z-=P=ZdHlUN4Hf05|5)ZJ|i z_SvozXQ)oGPrDe=D^KAZsn|^++*jUQXOLAcHh=~kgVipL3djQ?>(*J@^R3Td7IZtm z9_SK{Pyvt9-$oRQxk+C)*B^XlsdEI+#x!88Z8mTZsyliJs(4ef3InFlsC^0V%ghE= z75UXr?&4e_m&~GMIpLPPWNQ~}F^Rvo{DzvC!6TRLnMs$XEIpm$(OiM5`0)|!qrRSo8w*ZLO$J-1FD7?W)2 zZUSMHD#?>?45>|2{i@*x^unl_V~k{TlI(DViF6b7XmNav_6XASH+#LFb!5uw=E1;6 zm}{@}obo8jqjkIUJ~6>QA{Hh5KPE*7&_eX@bM|8U7DDIY=OYUAqV{1vm7Y)R)N#m} ztqciwcH|5^+Mit2wFz$Tjv4<=&=G86SP@APa1@Y6zQ%0xXjR~b*3U2Gxk%}Ja1-sY z2c~RZdyujRibBgR`dM6SPwNsY1?$;#auHFatoU(7GsS#9p{^WrEwitO57r@oT3CGa#%}tk8XD~(3a8uIc^673<`av^L{J58VaJ~w; zpS0Y<#w@lM80BhSO%JnoG0&EFjUY)c=R;Ua7`)x!PwUtpOu)5PO&_bl^gFRn)bJ_J zy{UzujER@yvr08jGYV>WI*hc&lB_G52m3X5b;LhzA~DOoj!a~TQM#?dyXI;ab0pd1 z_DRFVD%)OtG>Jbk%Ujz955|korAy4R*N!`wcB>xMdSUQ|p)y5x^VJQR^0Aog28Yqp z%q8|7FfVj(YFLI{03n>vz&s*aU2^m%_)|Xp^J!^ZkWt9;lgrUi&(|cx_VxM);mTq4WONe_4 z5|kOwYr~yy53WT{+JI#5BxC=9bNU(5SYHhamkgzg#xQ@Ok6z>5zX5#`ckl-7+2_n% z(pW;c|^BAv?3n(kk6}i?|NfI99T*Psen0 zAuo79f-uNsX&9B)VizP-QF*VvktA1)ADi1iH2O=XICob7;+yl|pLDXShq79il`L;9 zRzsK2><6QSJcG~0VBCCDmUP-*2$Y3}%!`qpu$)tu3Qdgzo2YLQLFveymp8yF^i_SLPss`7mEI1nuPQ`v7x|Cj`RiS`u#kSjy3G#3%d5F8jGI0R|Ln7P^l>7y@$bCc3FWS?P^m@rSo~S)ds#UB7KMHzWdZ0axTdb8wQGN{A8n>`9y~s!LG&$`ES+taAhdRLOt>iqzoBHa{uH<8Z zJJE%EObp5YF416LSxT7tTu-uXMW|;0dt>1h8uvrtIL7uWpCX~y)SS)73d-y27{Z6O z5ZYabB;nxMP(F+_>v5*kH&5g>q;4VtIk1$3BhPlNKh{*-yW|`3UQ|$iu+gE!KeO>j zcY3g|IOXNYNbp8&(%*N0P8jXyqkcLkn;)y!^4fuuuI>xf6*?MQz`n);_4AamC%Hl` z7n&{9w=7QFr^byqB}b}@r|bt-OYH!H;q$ae`!ZO=!4k;rmGtDpd+sIWfHumw*>Ehg z`pk3NxndixP0PfW@k#I39(D+p;32L8V!m`=^9ovXOUC$4 zftc|{64@0p@<$~z4Q}?oSX7<2PcVrL^YMMyeT0v2LAYnSjeL2fg860R^OgDev7Q0e zMs1vrQ>lsa_tZOXg!J9$aOc5I`Z-5F^=u*lS{3c&i9|8J2y}A$Fg2P!4uwMQqxGzq88`_Tt4c2Za<(Dl*dYKy!JobO8yNxmbb#_EUG*R&fl>;| z--HNm9;XRSJ<}zq7Trv^{F5vBXTgBQoQcoQYL<3^vAD~Aeo0pBLBT+x!em!v4j}n! zxQGd4%X`DdRR8@3#GYb7zHv36{WS%s0Cy;x z=m=n6KwIo5sH6*x#yDSl-;1b-rl+9+<3^+JSC?QQ-4aX(ls~HZ^s^$I zoL^~w`Cac(m2KnYuK7A5KL(m_tZ6|bp8#9@94HZ11E@v0a0)PuW_Dxk8uuU11`7j- z0zz;;d8-M438$@^PvA^7c%9(<*$pmMklgipM3eu|aeRxaVf?*Fhg%zOHb;?Muf{gf z09aQYK`NJFJHUIkyI=I$YaB>sxnnXxMwBHL`b0yR9C;?936ON4ClCz+!G>rQwBuK6 za^<(cJoTLBgiQ(xE2I*rcL+|A6VchmS$H0no$dUS3gA6zR$DwS-My z8~U8KSG^4&W$6J&V{`1IuxCw=w)johLk$Lj4tdB$P%)9Ky)SwcNZxLvJS1RLs)EvR zjxQ%Bt6ARBO~W7$vxSCxpevZ;VY+)K#fvx0g9J)~_7F@_Z9h8&X`w#a$jej+mj7md zf9NWY4V<%Z;e$cR{vV(aErTX@SDL1*3+uIEHqJiug3mQvVc{G=NrPm5sGf-&#>e$Q zE0h)eZw?^F>%S4{ppo2u^Zz^~zaN)9Z87E1Ld~F*3GG^SkG-b`D)|-q{z!@=Bl`tj zO!=^I#lOK+ z%?1@Fr1WTu$8-6ejK29r;?F_Ai5a?0mm>IUfF4;%)t3O_ zseVukSZ_?pZDS&xWR+AC>JN&>8KweSpTqc+R1YhC<>#cMXWtv$`}I53JC%@GF4e<{ zC}Y5geS^2Gvl6pkx-&qXk{e^4Q<&h(H&pHK{=3p0ApV-YiGK5+VD$UdhFUXDN^$qK z(HRiEGsym&)1H*e5j1AtW}r=4Y>p@}26LXn)0S#0jVw)Z8nkouFP@Qzfu=&ZH#oVj zV0unZT3Wi=FG!cf63H%AL!A0&zx*s2vz6bD1LxOL0v}BhEi%;E+By|6>0$ZyUs zs=%Em^Qn6S5FdMkKAQQFA>`G)VMU^5HqPfg^ ze%S~wE#Mr#Ln4y@Bun<*qAu*jYQ53==S>%NA}T5USrK!Vpa}D&KolQxAJ6Nv@um-{ zsoQ}#S1%MxdAA~|m$Gua@IR_Mvuegr{V5f@8j(yb&A!J{{H+}Se6S5MG3D99I6t&r ztxZ1&yA@t94Lgw9M+nCRppbF5;vhjhW9{i1FqX$}Db|080&K)SMb{-SnZCM21q%g+ z`t;>O(3%IB)-A@eXF^I}L2S$e@1keUS74Ui2&$azD!wt;dttj=5-O4Ph_3?y1=l43 z%QAaANmB#Mi`y_|Limv1;zCwAXLnD}>01&0V7~p*9vuCwM@p~g%um7dx0K@%X-!WNj16ZN$P5(p%ca_*NlAzEqB1; zdHXkSoy4O_dq=75B%Q_tK7)rNBuT&>ADHntcr8lJJB=wDN^1~LYY?L;}^ zMS(51YgI;lHy&GqfaGSXWQqHH>pfbp2mtpLTCgLC9Ki|d_*o%8t%Y@CE1bG*dX?N~ z)W>QD2WEiy9N%&9aPc)=hJjwtd*y@4UwSVSw`V_1*EuffH%lyOS|Yi@Vqyc>8F(D( z8WPn`LFZJ4Bnxo&ch!ovvMWRv4y)$?n=+I93EhzUQV)T?VW*^cvWe2gXSk!YV>9i` zk1s?Vz!xg4!c!zxLpUh<vu=ueeD;C#TT7N!npnvQ*cy``nyG>4#!(zQ7$X9zfdZ}}Z~;uxge zt@GOXJQl$_Upc0}8DP}jXsE1wW~1U1(f7`N_zW~+hMHoWM#0&h36nZ4SdpnP;A1x2 z%4FH&PUOjHFEjqC&w58EpS%p-$xx?=J;pI$))aa~(8&#!q&9axHb9rm zR(MXmp=pjmIpt@+WP~vO#ca;yVd}`F+tux2m9NMl^N0VhmQmVMj4b@YF8Vk2La_m* z9;LphJWlTSCWukKVPttq>Z^;<)fZP1LOFz(2s)~7ZNAV|EqT-2-ohHfn=}A9qT#;c zk-P)AZl=JMYy<9q?(dJeV!4~CK2W%Iw}6oJ-GoSA0Hcht2FrEYjY#iuVK1C@x5M9D%In|`!<>mT7SB=w_)g(2wIWkf4==UckcQEx|~8r{IkyLNBo4Q zSm4R50e>*qhw&w{-14I`@FaX^2L=>4#G?9Ie}c6>f{ef$LjmI61!-b`f<{~$xS#b` zf!t;^b`B6l>(qQex27JF9_gRM(QO)*kW)f`ZqxP}kMHHqye`alW3N%csaEIK??~(4 zNMLj;qVdr^Li9h8_dn~_iUMfmwSU{0{F?xdt^~yR$(z((MxYya>%RTpvch#e`+W(| z{ZWhsJLm$b0a7(OfoMXENd@#he+mb3fRR-Xnf}kl`U?_3nND$46$1Pt8|`4wmz)Hd z#xu~FbTeQS1~gGuKz)+#IR*{oNf0<}0oT47!)1q4^py+JZ> z?+Nu&utCIZ=mHb`R=^9OJf2-|3gBNRKvKkE9}zbOO>87Tbj+B3zNHQFsM#-h6271> zI#y$+SK9>gUk{!pspmPM&t>hO#!&$}7RPHfi(5;tFbMP~1zk4Gph^nrQq+KMuO2ut zvlc$5Dkc^?Y~Ps!lMFdtB3CD-s|DWgYKMVV%`?Ex zZ7K#^KUCyWBojeqZc|hq3^gcSfx1Y8yI>9%7;>p*&Fm+lKizB`Yy|bkr=?X3jmzA4 zss+3J=^`MAFcw#yb9oKAj{saaXRYSkT`e3))&BFu$v*Xy!HBLkk^Ao_&|e6D5RM6& znQ5#;fGlkdsZQtL)su@GEV!fHEV&MjWlf~WS(m2S9FTv`nGcC(Ne%%rdL0ug@1Fqw z20tth%M_NT0ce0nM?3%`>#gLww`7&$YNVHhZ9!cS&9pgCU9?DBdqKU`NyFnphnZr2M^A`0zGZ;|qaV1Dm;hAeGbNw`+TV(jIzItL{WPd4O*ZfgK*8k-b#P46 z?W{h8LY!ApmNF+$#OvL|57oIvIq<&^|naRVM% zC#Kt-E&b#^>S4;uIF;ub+eI{ng<)!Qz$z~q!R&wgR^eQHvg)B>ksb3IB=lb=jlb0t zKovsa^g1p!whn0kY87I~o?f_1{}U$smtk5L6;FKJ@jKbc~(8Q<60#t1WN#(7=MZ>t>v)rsON8#eRTxIHG9aBzA za(PU2CRr|rd9h=GlT=-dBEVxWS=5c&1>2d&D&vrt^c~#s-#MbvR|K8ci~vC3!p@3N zlQfmi%P=UGMeNN(?%nJU5h#*z2jJ7klS8!k={G36ET1~wtJu7Qn<})i4uF9zJ0LZb zUHDo%oKW)&h}KS5zj|F8NmitX91B9~sso^UCtl^7haO2IORY3t!m3u`FdGe>_OKJYhrhJ!oEg!;$7 zAp1XmzMSvZfVMGHUkRQ+%+j*9KFrqy+y1`t{102KI zLS1gA)A^8JQ*t8*Pp2^%tG@#l$o-$-$#YY_Iu~l~89mTJMo)z>^zrs+-N$*02F1!% zTY!;~tZT*gv~hyHfS9ZxOZ&aTM#?(Sb#fVr1okZB9+E_>n0J|I(smi5bB-T=5p%t@qS65?)Gs{(rRqg8P#B-A+TUvF;Zx6>3p} z-W;b&@;&aY5F8EWQc$l=H36oJWsOf$CV|>Zg6G$x65yJr3U#H=abkKX{TDVn099Ak zYKQKhHT#P=xzm(Esz<+DWO@wVTH{X&D2|o)7+G?3?Jf0hR-5g7)Bdtq*??KV49=8i z$)O(#7MZ9dI}?w+WPzLk$0?7^7@PKPsaPvi#CBw>G)onW2C(BJ4C1>>%f%5>%WIjD zeAUi_Us&P$05f|S30$JPuK>M%3Qz#I`0fxHT ziMu`q|2BHukvj%uzlQ;k_um^FdbOt@7RYAv6RjFWB^(92&I>#*Q=lG?)d)v*q0Sy7 zEwCIPIl#oUEDF+yMtiL_FCJ)INYn-f8l$>5hRk`XUhBH$4I7-Nd8<2!FRA2>O1>|M zCjbk_E(TOOY#>=LEDPJ3NsE$j2AA+`F~5!!6*qb7#Ef0xSg*p{)lV_dZfC$`cNk{9 z5svRl(Rj3N2sC#(pKN@WclFVM1tBCd0cT`aOU|RAKLXg62x!+^4LVkTh;rnM3D6)* z(D&znJwwow{VghoNkBdFh0^AK9soqSUjz0q!8O@42|1$kGYX1K8m;gfhPDxz>0+-s zEL4ITIIsdh0+J2Me`Vwi5EPOF1+1|VI- zLG`^d1Rz4$73B+;_PBMz8DE#n>|F&yh12P|E3M+5!g;4ejvJ@{v~g65Q0O(4(b?{g zeKc(XbUTZG3W_QdzlNmu{j>NR&$XFP3XacmlQ};tC1@CXpC2d`sI084Z0F}?5nIrr z;>v=l4jbE_bxxb#>py!gY8ZH|S$w}93BK(6?5kMMS@aK}%81f3XUF0PstBg0IVcO& z<%FIAJ5kNC!Z?|tG#SOjg6TX+8AP)NjM5rqJ2G5d>?ZvaBDAEXgUG*%vkUY1xfL+adTX4HVlt2#e&n7Se`#u$rTb%N&zdAhV#5VMuOf!K8-*m6TZN=VpN#8 zgI!+(CEoqdJ91)7LoR&QoAB-vb_M;v$&r5*6bRcpHk;tzPEk}KwaCN z`IZ{n$qFwPVsmkt`>+$w@Ug(%+$6g>ik1J06-+hQg1&@{)7&J}c zwQT10JzOiREd29XRQyor-^rS}BgZG-rSlUD{@aYYwGv-%3&cNnNB?O^#k}zN$eG`I zcC?;|c-+&|Q~YdTIbM-Pk{DFN)+qVzMxNM(Eq-_7)U+bC7_mw&|i>-eErxuoAL3{}+ICTTA zqczZe&L^cf@&PDle)Re0K%U)bjV_SXode*<;&)Hj$~-}o7i9?R)a-(5auJBjVjzaI z!F}v5oC8&viU5W817~*5O#;S>uz9gbT=D{=)VD=I4O&A*(g5MLwPXBy{tm=&?qF|P za7Ur(H&YZjSOO{p&wK`pQMhDC;#2zrZe|!f%NgzqVMuxV34D1Of2C}Q~wGq^QjObQA0RJ=x zfUU58DT`9Du=h4n>|`L*Yr>eI;1nn@?ju&4UL6423JHF!qcaj+L?{wOfEf~YccnH+ z+98%7bJA291Ym1c?~eUCw^?e52?!)AKtCnr-A>1w3^G7R0VOvNDC^gQhl{C)pN@B1 zpSK3`lLcJ4G-lMj&YJ#c)Am=Xjk{;+HcH~}IU363w`qv}h5>ZLW-&{>d_lp5^gBxE8Ljes=Kx>77;O5>=2O8)CQW(3Skp`b^z7cs&uGmM0{>Cl6`)6D zgTXsp0;xBHSxW9)pISoN!h3he239e%d_>=B7y@m%JJ=r?_saXn_#UHSe<*dTfj^4z z*hwFW*ZZZ+WO8n0gt=C&+0!;|l6Qpv>k9jULc z&jAvbM;=Oj1n12!L7L!D$F;Dqw1b&vlcVSu_$B)l(-_{j2twtI3Oj}Be@ygmjm=yq zqy_ODd-f0qa3qs=w%w<)1_vKqnW~n?w`UmG66Wp5)q~VdhZ_^4&0uqF{Tg0O^z#0{ znjeAr0o=yPg$IH-#(=%p7)}#Z8ZdGZT}Us!CvUko~6W=s{}1apg*` zdv`(l8$n2ZkBxYc+4!#mxWhAILSnp+@IRzKCB_f7=yb<&0IWtJv76t&eJYAo|imH^E$H0fE1S*vAUh9j{;T%`OS5>*(>#-Xhl zkx!p3mW3Ha5ZxM;LkooRrKQa@XEk*OA!1KByh>d%Z=kG;ErWpT! zc0FyFNN0{k41RH$R?pJqsL`Q18Y0yD7E~5!oMrj!Mdcuc3qMWeFFQ?^M_^ixeR}=e zoUFqc-099n1!UhY%DTTzPssM>xcKz~i}us231N6ykP~v`o{8-M_Y-hw#I08*<36e{ zRwUJMeeqI1H&TV12z;q>jTk<)^3Qd6-|HY{F?@fKU5sW^%&Z@SbvF8?MQ4hoUVBYf z=F{A=tD;R$QRe|(#?X@D$hiU$he_QR_F;n7YGzEmxn{R-hr&tu{oLux@B6pA{^q0! zU3`x;bC@D{5xCtYd6uKE78m^Kk|8C-8CfHatD5>{p9ChFjLqrV(|c2@6+SkLM{7!p zFIi&QDT%9D^ZIgbRPF65nPKR_9LRpDYf(;#sX`m!#g&$^wi^}3iQlr9KkdsUz))VK z1~zL#>KB!`bT0d<>lwW$S70KsKA6gFy&Z>wT)k%A z+2tX+Z;tB6Tai&5Y$5yHhvZi!B9kgtrBjHv#<%$@+n&CINhFrE8t!Iuv&Zz zD23PK+!#(U5X5Y_ECSw@__TaJ~ zB!rTaNj;=3Qj!CV)03T_zBvuHT?X!?E-mbQzxS0ZJJ_mHQhCJBEmQU(95ys^P`J%IV zwH@IIk?#Fh)>+;Bri8?;+tBoX4a|>@l1SYDRO-~@aaXOgL14KQp?&Z^M5p#TlS7HL zP{8+}se}+WMCX>EC<$;xfD3y6J&$6!{vZ2MrCYQ@<}I%(W-6Eq&u{BlOL}~HVHSVl zqLwX^(u7UfI2=G0S)B0m?f4OBipyD%h+p+$^?R*P> z4%Q_Tg14IybidofPxmz?L`hBt@BT2;zmFmx!F>Fmnf~aw?=xbo@D(Vn9Nt`=`enOI zc7_&McX(jTPmlQ82NHH0%PT=-$eatT53%~F{u*tdL)}iRgNZrY%1n$ERKY-MT4G`Ws1%RA^ z38%0XK6+Z_?6=%zCmLBpMQdrD1lJy}v%Rj%207X)(zuSGw@*kr=^@y0xeMSCUIUU#dxN)L!(S zeSWlQfN+ydPB0~mA5o59WuYcLdp+J97dcoZk!9L>6UIM}!cx?L&P%gLG}n&_YX)e*mEIHAMYH;{*KKuFw41 zpRC}TD$8^RDw;drIHNIdlLFK(Bqi{`49KS#I#E68uwcu$RfGUsDF+w6;w<4J4fzsL zhxRl6mpS^bu z5GI`j4~hBn#}v1Ve_aPVBz<-0;$zdv+su?23<5Z~1!smIzUG-k`5@&;`5i-N^g^AP zCReYkTqKN;Zweht@*g0-_FM;ZHYhRJ$stTJLLZ%hY4DEM5KNaj^OE#7l#Jz|x&$@_ zDF-53L56ZOygMcZvPJ8$kn!mYR82yNM85DbhwE==3@j`}nvOVR#`YcF)C^3V5Km#f zh=ZX@s&$-J7hJltC_NOR%JDLL;Gs520c)P=v>KR%j~inyHJ^%f$gz0BH~Ya0HN`SC z%yPf}%Hk<~S$~huo<8Ex5dBR)`E$SR-NK(roc9?X+{Y#1dACZLiwkO+o!is1+AZ$9 zb2a;!Q2!BbqniLqkprI)$WkgeNKE$Pw2I)EQq_H`9J@}zOm8MF+tHe$R@C(FrWO8< zcRx zxxoa+j(j}@__p+BvJu1gQ0+bCmxSLk<&t}1h)irLh$AyqWO1=(T{dDoz!RPC7k=o= zav}#Ba*g<*^pAf1{HcvbrTWgr%;)ib2tmJv_Fi%*NM@mlhlivV9TqE^#k&N=AAMc5 z3kZ$qeKYp!R;9xMKqvLoO;Qv2^S8)49{OM){*1~_dUu&XL_`?Za6;OrRK<+($E^MQ z2^kk_q9S>wazx-_zp_6-*MJGK&=V{y_A>1jm=A<&pB-u$I`?bO9juG-$vsN7_k59^ zxR_4(BHwG*a^OvR_P<3>yb(EDsOZA<&4DNpVvY`0+h5$~9^Yrgl_>(MEpK>f`2RV3 z%15G$q-3o?Mxo~OBT*nAP*sr7`b{I^nlX!kT`c>n$2(d&G(;qa_utE{l4{`ldMJY? zTojG7V*EBRUz(g8yN52j^Ww(mUikda!Gq=pU7GcOZP2e5fnyD@aUH+$T;;d!`uWd; z;DLuahQz*%?9c!6{aIsaz;??opQ9!B-KPDTCZX9x9;_w`970upjZMLkiRB{xi#o!8 zeWvYM|A#1asCD$epZLXLN3*7awujSv0~E6L-aP%sCW0A{1|jG*?XcC~jPmC{S?+-4 z&t9hS{?A{24;sJpG>ah^>goFY-#8`TBWFUdj1fZmrp#{Om41r?e*F1@3s|_JNY{@f z^KZrnyn8%g*3=ynWc+@z_V0H2I)X8L!By+__b0K0^MfXfq?$?o*KhtDF8uQH_8@{g zeW%0f-)OWS?P>!H(qrWD*)Q?LcN6*X-e4q}fbSg(iKY6l-=M)rv_;+$`w#oAiveaA zuUdHWKYnvf8;k_nJ}2;(@%gzmU<*V8n#^x0^bPX+r(T){@%aCC;|btBZ#N>r}$-`CsPs%^B1w_(ohiQ*fDALFPGF zaoC6-#~hmdkag>Nakb1}t^!sEf|vhShdE88_b`}~zU6e!F5HqChzj-lnGzY$Z_a!m;$C(YD++pl1zOzx4uAF33c&E8IfP%hfX}kd% zs;Z`TQz=#;ADsH>gA>pkkXA-DJ@Ju2?0@(7xAJ{My8(DZ0M*;pW#FJR3z%r2)@lcB zNT#)#iYYj(;8_4wzVv~_X$}CPak)vpMxY@q{2P_4y>EvzQ`N!`ZO6-3f(vJPdYi9Xh{5C%(;9 zuqY7Iwd@&U;6EMLuUMVZx>jUpEuJ7Hahgf6iy$`iuQC1nU9tqJS##pt-f3j;2=wmV z|FtU!zyW}}L_BGOmi2gvKJ<|hwaob~O8?0WBz(!w8CPDO>{oK-gk4IT%Xk77rqmiy zkR%fz2I@{If|8K=S|HOOn+Z;m0Q$AK5tOa!jnn1WZHr7lPXu1)n%WRR)xHFxL}w@3 zf{Nw=&=huLI#f6FC=D-Quenb>4{6z#9tQ_ua_WagEQRK{eam zmMivxfK!M8jb7xW=-+O z3}ubO0l+pPiOv(qAR|Kw;_2WV(9RL~IO8@k`9y}ueg;OxTH-GOep z5;Y&FdD@3|oW2R5(hc2S?N>jC1->IR<^bS4oumd(8U4_V>j^mkjysPB!dIqXUsebh zV?98;WY}mmz!{v&mrfz{GiNYO372{S+yS;;V5w7U3*NiE(sl%m0j%c};(f74 z6d5x0+dB|X^eoJESwmf0f#?>Y3L62Q!V{iip4SGT)boDF$ZxW%TmbrI>UomB6iFTS zl8qztm|d^g5BaG{26LLOpcYOOkI8d8L0T)-a8rPEeMt~avgsxumz8phPk|%sKY0Ka zC?WaEz|T`H;xyo3+AFno+~xUXVBMy^6_-{ptz^a8qY3gX;$(PFlq zy8)1bvsogjZV}2icmh3to|3i-cV}Tkgo@kh44qLVI3W1Rb9^_ma8?kg>iV&QUCTz2 zkDkVc03df<0uof#K#$)>4P@W8b1ZWSO1@hkjRg+`wqbTwd4&&_qCM2wa0G3~IUpyz z24Lf5JejobRV;xLSsZfR>vE_Ig0Cn<8IT1QZIukoIRK1bL^M?RFO6^qn1uQY5q+s- zBZ_hW=iqr}M^M1Jz&QY*{7)};%m6WqLu){~`UB7}vtXd8dIZfU5Q7QQ_AbZjHTv}l zL|~i6PZ~lguU~oY87HvUviJHQk39%Izob$8y5KJljs@{-N4X9IIF;uZDhtE`*VA8e z^iI5;Oq&QC7KnCJd#MV@4-cIL$IE=%j?&9B2pIT3$Yyu7VO) z$8YFiz%+E~)YLAWBNX#Uk78!HXOL^`@hO19e8c44k)_QtI^x~UIVey$1ZBWu3>_K; ziviy!x)v~cr=kV`Pr({YUdMkTjRDuq_qa>&cO&`9a3t&~z>?Dk5J2X)YQtc+S_s#5 zy^wnnKj$c?8GWw7p;(X**1NJ1GJ5l3T9l3n4R1IivWH~R#x8{T1c2M2Jw?;<)m`*J za4ii5P+Qiw(nv=XTP`w^>kwr731}T`f#i5F9f|5I$=-*8yw}$hf6Ewu-5K(d00z!& z0Ay9pKBuR>ddIDW;H~7dy?FcQ_}N|b0z)D}YQLB`pZHMLST`CrTN2Er(pGHUTeAEl z;rW&7(KM6bFqek&On+L&eLZMZ@G&R4sMd-jtVHO@N~cnSty+?=Sdj9!^&{(LB~Evz zz9pPUtq^DD6aPYW2TO;EaX+giBaNFOZP zr<(|i_EX}SH}bbU!vE-0*v*Jmy;693+o{YuYGitqI=19f%8$`reth5$=Vjkshn>&Y zX|4y?XdT&ZMrK z+(S=5G7$nbG61O8tHzHJ_-OI*>}?d2b*@kr2sh8=D(%ey#u>o<#l+C`Yu8oW{v3u~ z(;osHusk4}-3u^@1ia9({g&)LtuN$gg!h!xyubD`E^Fv|K)WbTsq^!D_2L9w2hT+VW;<(sA%T5T)D%=h-Nw2`^1x21GJ13?W`asR0$kxW0 z9f?|f3~yw?YDEmf0j`5A4LPB~_a&>B97eFyPedlmWVFGr0Fc_0AjnYxh(@1**Z{pa z2lg`xmg|7TPEdeRuzlsyKP3*m%f^VZ51?;}q)uk7OMEx!^ zE;xKkeyh^W$~2FQ1Mi*@psirno%lXwUi4Zu4k+262s*Of=m`+^2AFczKH*fMqL4my zi1EVBd%Z>gU~8#^UNoES)0Vy;K`aBLPwX7o2<7XywwIezliEE~<=erR7xY@6uqT*{mo$i!^+7gjr{Em0iQc>sF*gq-L>50wcpTKil=e=>LJJ^ z=u6^M3XP>KBKr zfXJ&w7Z5KEv61zqWU|FqYSeeFo255pEjH1Q2+|Mi`x~EXzoFQI9Z3UkBuK7 zkO~sC+z9nTi-6kyYaq{K1qeTh)BBeUSj8%Gj+_bzUvbo{EcP*PO$i{V;1Du1vROy= zz?j&BV2TvuE(8%wf-+D|Pe5dP+N}yu&ZS59PQq=r1jmTElF_jFch?Ocp?L}uRs`;V zx@CP%y(nn(T;sUr4&2-l$TT7vIsg*hxdNmVO1fOfak1AXJ(Qf-3S*oLZa+X7K)ju6 zIc-xc2#4JOpv{+MqL<=9e%nY1D_fc95HJ##8Q*;wOFmc9xx$pPkHu|e>IQ-R3ep|| zhoCZ(9C;$%J087&J*ng!{T(6-fFon``Hwubk^$C0g^LIXTg4>t&fZ0r- zZqh_jPuG($H+zGARg`sa7jAX-j^0@IJH*@3q%BOrB@FSS{)R zn?oDICb;YqP*8qpAdOsV7YYNa0g*N&f`(V3j`5p)u*^o75$bt?QuC}@hYTFtHCMI) z@ZTneP(4gb_cq&M3AXk+gpD20MBo{8Y4T!plh@o_0~|%t=i3*S59-Z> z=Lu`R)z;8>W~F)TdB(1D_ig%FKnBcWWMyJgiuUDf)68d5gkM(EHQffV?+?hb`58S< zb9c_%2+p@jt`oc|5cSDZ9gcabgmQ;R`Aj;>EytiF1bQwACoG^AY`5Z!5u zE|93AI=BHCX}+tPaBTG6APb)CsTJ=TDRuERG z0@Vy8${zxC^~?YRzHfe7BY_%MNybKMCG+USTvz*ga8m<8w`KIWk=7O>igyP$1Galx zMjTvR!A9jyjH;KMd%djHa0aJ84PVt8erIOlvD3ZsRoh49U8i zkd;v+{K4d(^@QVGNmAn!%?fKDpqCz2#tDkIdSz@ep6n8t%%#Ae_=G6*8&+eP} z##R{fc4hGG+)?;~j^{qhvYoAJ+jh)ZQRZ#P%(Oecv9mOHAq4Qfw>tZwe8E zhqn>ic-v$^SwXC9H7^7k-F`l}SDrJ@OLo@QO{tc@By`bs*3@f@^FhksO?jMA%YtOg z+pV28dr@IWNG;BAp^T@~6|t;}Rk~<_-`$=Rw*e_69QRyvg4FE;HrlSUmozp5y&Gnn zlhDfjU@5Y06vR^6LWb9`r9iJAn|AB4Q#*KVvb;ELkVABRTjbEJ`;D}#pEGrYn6B+e zrjg`RC!rjWgkIH^h_nXMAZ~!vX`Zc((e=slx{b??Q=TUt8UaG9t+rg2 zXGddMgblgo<1F&9inX4G^y>*T==q??N4c+B)=)W|QFKFD?{tOV!W~;gdyqDHtGw|= zN$48}XvZwr!azS0g=SQPW@LO%ludnPGmuT1Atr+_?q@^f`h;^{2b}twK#SO3*!%Uo zl7^Xe16xHj=6G?go4kr|s*a&Uwder+VjI;2fejlLSDzCkQUNc}xk9TsvbB7DT&)@z z=D#K#xv%on$yUf&QSqCRSGLBQsT;M&E0GjT;t9FvBV)7;J!LPJ;Ir2wHR{#nwz5W3 z$;}hoA0kOLuTFVr-Npmz^E@#XUvOvaHEa@b5Ih`P`Wz}Rx0PM*LlD9TOsRQ4GV+0G zvt1z9Z0`zX?1?fq8X%~~mxaM9viT$%;O~397LVI;Y2qijcz_SSqphW+Jr1awvkG+A z;U6{{jCV#VQ)!D-<=4<;li4OdxgK(kl_c0;n38!c=i5k)tQ8PIlsR0P4D`dmKWc|J zu~|bwA^{>v;1QG)hZZV`%h5-GZlL@Xe18`zOWUv=U5Vm_>x4xfj^urO zOR1Kn)!88hk8HHPr!yHpcn9CuA_BjavvAs5=?reZ#kpcfXx9BI=Cos|IM`Ir6OkAi zlbcW5mPYKXP1!~gvkbogn(ad{PjfN+go<>Qa{kW5nOnJo!~``63iFwT(jA@vb)`xF zL0RAj76e&I<=${~l}Hs7Vb=o5CGVq&)a9qn?)|-6LeOYplaTO{&L=ItG)6>yE?B!q$ahMu<@1Doz~Ba5ess7rp`*-GF$8V)w~dZh4{R) z2Y?6wR!m@qp7wdQOEIf1ia&Kl{{vb}@^2eW}a>b>W9@vC6`pXpT;C1ie>^aeF80Bb&CBGkJ-` zT1#{fU)xXBTx*y%(|%2Zl5bIek1o}g&Q2to?R}ZsAR9Rbx9Xr$DtH&ItlXb^RZTM# zr%E-tKCArc*jW26%9+i?UK2J@yW444;p|k9w^!+Yb)M{+lEjt3DYB{LkdS}gEY`hZ zAI-cKPo(;g{d8veqhy~c7T#oLKmY!3S^&Jk?3eDHzro0I@a#tgzmwdAtDEG5Gx+KMZIBtmb-PDvt5t|r|b8kJM#vG>$Oog952s`royreqnZ`f+@ljC`E(K z?V_7&6-!$6PS#<)HcY3}JY?&f0OU{lcH5ogkfP91h&oKEO_COOxouTu@(Jd^%vJ!k ziTMVQ4so1(9fk1T_4eGcFrIQyoqTAt3WlewcfkIwSV`Q}AY4=`>nq`dRyh)a0iSde1hPgs-kx z<_PpC&SFP!exn}NG(CLUQwG{E>ZNfcX{nTF!}YroN*jr}JzPLMN@kBQhHOf<(#C08 zS)6iJ<5S8|mN~v|QQXFRId1Spg#eG+Ke9BQ;Z@?kQ_-F3<+zhH_>&8^Y8m`Ze94SX zZkF=OyPKB_-_ImgQtpzq6;E^Uv5IYG$7N%-$9eaiOXN7uXLA3UFetrnyY11nr0u+} zw78bx7tCsII3=d5*;bZ3SA|Kp8y_n+=>Kjc!)H1Txq0O zRTy8$#9S||w->{+3wJoxC*bGxezvC7gw;C9Yccy6Tau&0n~V~N*Oy$mxSvd4UUj|g zuG!L@kr^Gfub-t&2-+Lzk=L;A6ds3nPtxAdysgm;ZTav|kbp<0 z#d&(OovEV-skin-p7SVl6XkJ)%%p3jKULn`wl56}wVG|zoXATsjy6ig9`bGs+A3ds zowrzexT!t5df~LvaxLOM-Y#jnP^~>!4oBGvS({8zVPb8Nmd*4z?VvKJjd6h_?;{qmON~z*6G9TB zchJ+5)ZP;t_>pG8#?e5$YlhtoVq;TLKCdRH6P9I9 zmrAwn+4{^fynsR7Q;AH+qv!Z9F+=B;uVVhONPtA5!%CXnF=1A@c39p{1J$6d%43yI zw|njkkLpeOPqacFa21N5t->}=Ef9BJR3rD+LLYj*I}(;_Fd2X1h-w~|jY)jRpm`EK z6g~P4-7gP7K&r9K3V>O`lFYjfuS857UGX;-w_GJbC;GHKq89Vaa0JId8ND)2r}?%s zUio@o6%o}HcUl>{anYh5cO#Vz{dAkkZ}Boi7mdDKteVo9=`5c9i{(JNHl?t1eMdGj zDU)-)=K%hqdbbAu6(zx^u^zg3>XVW0cBk6&A}}|O8AaRd$&?AP*`Agw02L?N@z2`S z@5;x-qp0NJCvw>)pn zZY%LO8q2r2(j3^>T;`ZJlZ4E+Z{{pZDGSY}$J%`4A@|E{9c#|Ldsn44A&lCaAK9^d zYTo5x6x)2odl%2oA$@HHb)@5+f;7iwkw^{tnGGO!6nW0ij6DM z%hZ_l-ceijtx@IWsiXS`T~->ZBY`HCKWYIly2|jfA2?;Q+|x-nF!(g1OZEtbEh+5E z-M00|Q%VNn{n1>}6-e)$^t?5@jfukM?C{aoyiaC3nW3c`sAW}D{Ju45S8tmAs3u+%()bIG52AmgbwV99fFN8d%=aV+<` znB8b-9cXhOevBL;D;~dkTDb~NI+SE|fHxPimsX-hXnJ1;@o_ie^G&+5DoW&-1J=VZ#HEEBQaj*;Uaqx>dN-@lCD$fL0;SM1e0DcUbn1~*ivoS5_-F=D7HG3^DN#yhVG7DS#we$l>P@h?qxk>j$049o{t6uY+-y=iE1_gkf_tW?#48d!jmhm zB_HFe%Z`QG-CnDG5oTJ$cw;k3a*!9RA){@CAj4n-Cv%piiqaJ3*aa-EU&8kdx;e1nH z#8FtP$v0jhC|3LvtI?t)f`6r!5if7a$DyxQR?q40m6J({zLC2+v+sGLmDWDEWu<&? z>dNCGi}RQSw}qFr7)t|zERkeTAq?Ap4WJlJ|s0*zjcjGpZ=p0FR*8>$P0rS@)7731Bugq7 z5o=FI)r!syyQXgGFW;ZUDjh&kA)AI)GwfxPu2MBK8$oOsN5dZ7JyDr63^R{kLu@-9 zpFOJ8+g2G9oQdkAd(i5#t5MO6#o)s7BBLz_&Be2scJGo$pPV;n@emzcxn(Gf*3e>p zwSQ8(E-&$>M!4z~k_2V6#f#YhB%>Sibuie1m$^lAmL-U!4b_Y0ECV69Glj-7a}Kjd zTSBQ>`}0)FHsm)G>1`&??N&4K4oepqwmO`;Y$%NH$S*3E6oxo%a}Gt?1|`yW35J*_@aT0e

iM9uix_FAZZ$Mup}J`Qiha4@5USYIjX-zB&gsRh=0@ zxU{xXdr*=Cy)2JCyvp>LqdJne=>itgf_^jJndi;?vLF3U@1rvbg5T}^ZBcK>s z`JO{Og@Y$B@!q-GtYVLmuO376Q{=D;34yq#=lB=P7d#TBv8m&u9|aA!43i%1@EgoF zt|VN;!b?U^qURUaEX0PNPV2Oyaptc5kUE|4aTASrMLSZ7gEX%8wI%<~%KG)uBRLm%sDWyGUDuO?z&}MC5AioNZ(FG$*tef3%5L^!UqAJ?Fkp zhZ+?YVCwS1>vVhSohYG|d8n`_Hy>RXkL12BN^hFRNQs_C#7XJB^vayQ*iMzA7ExZ* zboEi0!R)!Q%N=VLaePdLvFRAKdV1&bv7Q4;*v12$66*VantkdmDI&d9)hApVlgrIc z*Qay{u(8j2izw$J-hu=gJ=cum{Iz$IoeFREFq@?!1X69ES% zdu(E+4AzpgA-h(Sfk{ZLVy%ITo1;!|Cgx@?6MDijft>xe0;luyB;lBkbS#~r6JVG8 zGI>D~`0VcDE2Em}Q~5`Py^;9iMndgg4UPyE32otay1I9;`LV5-gmp%3pS!JT39Ycx-lDlQ(P&0^-A^*e{+=%I4EYZ^;J|BxKAJEl}`<} zCA0f%)0r=NC0Ysh78&#@6)Fnzc_y}Mk4IXI#R?-mTFdz=npgbKwo|`RY_K#i=pKnL z#4o-tJ|Pa7Venj-Y?u^8dTgFsn0$xA2pZ@2X5R+uJNDD_@lmzD9SX34usa-WTiMQ+ zKvB?Uhe!`wd72U%q@sIWbozxyVWVhL`Ccd{P+P6e^{Df?w@MVbyfzoh2S+9vgp`!C zI5VHKWsK*t>8(l?TizItTM}V&RIFz5wrl5$JdJf~DE{!2W5J_AyDt9_x3NJ?un3z1 zw?grfxp#EL%i>Gw0|D{bdJy#!-AiG5^ve*e)16B)OCsWYOki4u^zM|5ZwtU~BG_t1 zBkB{Z;|jQ=t&=u3`91e;ShQlVh6PR)46m2g(?m$=H|2zvqY_KX$@`Hv zktYL?r>)fMDi82Z296>x*wxDP6&biraymddi-!*$9VPZRl)M$1bRg!+vySVBEXoAs({oCoGMWlx#K+#E;fP^ni;i_O&pcY@LWz!bA$3bBC~tbNxnT`U>;#y-XSe-4JVj zAM=}f)0h0=H+axdN&r^ze}3)6Ur-+e1lai(;KcX-)9CmAK@vAP5BMdN_fNV0iaq(| zuCKxYq2s8U5#OI%{3WmX`XEd4g`+`ZzcMs`y{jakyWYCy`tJC*xA+6JWv`@MP*idEx6wb+UR)@dfH3(gGl+xF<0UhoNJ~+B=Owl&^QLE+syy)M*;obE#TdVfGI z|H&kOSdQ=WBkXW$s?PU!UxzGL%ALQJxqfi-bhsQ#=KfBGcyCc^7zAh;{rOUPdiIOS z&msIB@1JJJs7{MoF%#eBNnTc&Ol?y5Dl+gdGgf&=OuA2?gQx`eJ~=l;9&+wOvFhoA z9UU#3nadG>V|-~8NW3Nrg{LXxEc^I#b>Fj=G||{U<YZ_WJ!93a3p?a8H)e$dlSO%=|Wzo z|BY+}-d)0#oKis|bs~*EX9vSvG_&4irr9FUnCki$V#Jp@FQLO?o^58XzPx~Z84LBk zQCy`6)j_rO@N5CCC!ss96Ue`CD z>wFPWs0JsQ*>;=ggf~`{diQ}+`Pf#M%j8@a!+vvfgPEyE-S=~M%Q|_DOsOb%&NrM+ zR`qUB>Uue@fg0cJD?*Tt~DD&kDWN?{5^=UDdbMTYoI8ztkCxSa$HX&FbkvOmM@b`T;Qs9<^>D zlqIE-m0mgE=G@=!w%+ryBZu3DQK2dRKQM(y&-mW3u&<96%&)AbwpopJ!FM8R#u-%c z^$J?_B%E)uhUs@}XT04JtVy0{#Y9W3vKXT+E#)6I+)2iDvh4QSHeH0`l`tsWuCqUw zOJ)xTjG{5Cj(6f;c05L{Z@;r#sH}oVW6a)Z8x{4qFXY+nx`hcQ?NSx)>_img8kN4W zq#w{$J8>@LuU_(%3I(M#RjAY{bI1&{KLh=wzGTbTQ&QpPxzXj~fpimyetzHC1XoM+ zyDY?=9yi5iuPyy{ZzIz#>>Vvb>rSr0JotK-&H}^%VfJoUr}<=HSwKWXB5FXyT^H_Z zAo@f~J30;S?{dB1tXLCuiG`s4qxJSxJrvGxcE4qUqsMeZl!u4|hWTMwV-&fi`ZG(- zr_a!jaG9|9P336=OM2dd^aan}8Jn#NSb~eaTRAD}=HWT(e~LSmJDfQ7L|B?HrWzpV zIt}(`NNH!JjU9ewrdS~sMfP4D_PSVFEk(UyZ$ovdR^F$A*`O-&m z8Wkh3*|>^e)+F7L*qbxBsanLRnWWwchsq-)G>5g8->Y|dzHQZ?pu&C1ESq-Armz%a zhcSCvogc)M?2%}%rjI2>aa-=XKTkV*ojf+2ocNr#oaU9yxZOFfu9pq-^D^FZZ`}Kz zj!f(Nz`=a*jGaVE;|OsSglLB*{7#aZ(Mm)eO!aV80?yRgtw33-*BwdDQ0g0*Q9 z_lpq|j{^;gJ^7uB*KCc3-p|Z$`*-f$iQ~caaAQ}h7W5iZgZn-DYmZOf7v3I*U_-Da z-aE3hqC2(*jkJv{?Tv#Q)Y$i0oxRz9Lv6$gkoAvIxBWeb3>f43&{aqTw*NSZH( z_TQEz;E*81RZE%W#Iy;r$8R&ePExbZ6v?#18jjXjU!FA(R<3+a$=!jObDHPf9C6@z z6w7u3mG}=eOiI$8f7W`U?}hE|jIcr86&j7%TYcE({7UK_&4VG&Y~yyPFKcf}3Ni{AZ9-q>>^759 z^TgZYuMD05mFNhU!@}D88m0PCX%p8}3a<*vYaqt}nr*pb_nD=jd1;UIdYom# zSR9U6TTn7>+wuh77P?VAjd3T(?b@EpjUm1uNX=VddeF4nrEwS4#b`?%`!3do zY9`Z=gKX%G=%qfBQNltQY5O5P$-u&ze)p|lZ`#lU1+goL5R(#7GJ}g{3x*>(5z3H> zuEFhomTNR5cZ7D9(A*7>T};>FtT9^pr9JDj{OYH>e>ME2BS)irYJA#0qLKs+*RzRI z-IT>VUu|6-Sn=3kQGj61k&!u~84guAQ0MI=N68KzxU^SzEXk=|jViROtieW$siR3{ z&cBH_{s;-1Sy>N-;WES;wLVyp)(tnQwoHd#$~0_os5^u_stNToslo2N!(}L<)@(g% z1=F6ATQa?{^k>++3X5V8zae1<^-I2Iv+YcxWW8$pvI2%*%`jj|U3?G&>oVNy7NP9)pxF2s$kI_^#Kgl|#c|CT z%Z0KA!N|@XW_sOTXK#V?v&j<=j-w%~$7b;@i9C5J1+wb3^v!)Yorg|#VHyRp_0|)! zE1tg!vJB0X%ic+Ly5>Bj?cSU)U7d=Y-v1xbYIxwQaA6TTZb|op<>us;mjw1DSOTr* zC|3WW8k{@x!=|Y9t;FOB>GozGJ0fD{l9o4O`s!S?@x>3Yfhmi8|)jv94W) z))qeAVXp%3`7mR&Wo9*Lg!d*Vd2#l-BT*ong;#JtU3A11+3||9=3_0Gd$@&6*C{Ur zrDrA;?hA|pp{p7P$;j$qEjO-&k1J_Abw*5y2#}?iPPw1z#lZ(Wdr75e^D@1*xTCd~ zG2UWgWyA-_ZB|GcrLuNI(fZ!DtHMT;QL5Ze=97;EDr;T|J(5M zWy;eMRBj|wQ<2O%`5_v)K()^EY@p*fiCS^T&I9z$?IT9NM=U?0vN{Em5C1Uqzox>c z+l?Y7?$74PNK;x~cpB01=zeT8ykh;ptjJKZqik2jm3F02@f}|Z2}fqi$`x_rq#u}?RXv!le-i%1|fOrss%dmyQoCoHMZ!C8k!K7<`!4YE$pojHc$qo1M1joUc&9(gz&ITBlvN*p$1~ zvd7ct4MGnqU8{WdywCwQbV3gQr1%5MbIYB_B=y~lK!xR8EO$Z*RI2zujbP~CB1qu* z4*1@%wTJVV6Th2bw8C4jmx#|!3kvhdUMW=4LIhTJm+8$(QYbYlZ+4W`8X*@bNNL>G zVrFHsMb474Yy6Mut~?&f_3bm6LP#iEQYRH934=&RPDBRT*GvgxtXZ=RhMLYPk?dqE z#Mt*Oj2tT2vS%#$A^Xz7*ur~1b(T&)gw~;N&A0xsO0YZZLlj#eYjWTWlxNcBObMKKeWGe_E?Y`pL>PW+F6rbMCzkvOZgFMcWUg56`tY;;b?1ol%-*oaw)YnrbFF+>i4g zQt9HAz@?lh$Up9?mo0x>ek=7|4r9zOYl*zw8}Elr3bctHH);}O)b#IRX&ZYTX-ng_#*H>MgEq>TCVXdR)r&BaasQ36?|Wdgj=44R`Gc zs3g*^!keyG03PGRotp2>io-DETTPrk?_@NjKX620N@sb}pz9bXw&Zsf9-% ze5g6d72-#byqOH+4q!G=S#`y&6^PJ3uezl5NTEPy>oacZ;+Hhl-k*!%e}|X<{Dcl; z3^YKRhk5F{(ryzgCL51U)^Go;rpLFRmmG?38jI>fR z(<%4HGKZ=vGO-hE1@42AQ&Xpp%SHgIP+M4Mr=@tYGoN2ZOJEJwl4xQ2uqj6FG}(yz zpX^b8r<20&A@e)5##1NJ{RU@>grS5BmFLsI!i6+lLbb!+*68gNcb^C8M8VgUm!hd# zJ|x-?B+_UP?HDpULG(^RUiZXyco>NWBcmX?>vfQ8Ybva+(P zqvI9=fsnlMiY|lL%f|bD5macOjk6PrE(A&)sV`ofq}v4=KJ$xgx{b`upW78QNv3+> zur@XYKoMfSWVWcdxNEetzu%5m(l%Em^hDW`%|K8PODxbp@LOG~Z{dx9d_|ydZBccn z3HY7!j=cY4E5r4*wP{EQT(rcW2dIoa-OGkq1HIkn(d7e&fEb3P^CvZ_>k7e8BWXkU z={=3hpM!n-EE}VS>Y+0xNG61~y@YHpD@*Ayw*wRwZlH^;!WEJz`ZOlD)S;bKUhYW} z^BJH$79jQFsnY`?0Xz1k?)OH83#ydnu^yo+(J!$jn$B5(j51p=}Be zFuAW2dGmy zEy_6{@s%m?j`}(m{Q|q(-8z5S&e}RpH$x8+7&PJMVl)v1uf7-Pip*RGgd(op%lG^l z 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 D4v(6! literal 0 HcmV?d00001 From 035f1b98ddfec01af4a990b85b4753561e3afb95 Mon Sep 17 00:00:00 2001 From: Grant Doyle Date: Thu, 11 Jun 2026 12:57:50 -0500 Subject: [PATCH 8/8] chore(release): 1.5.0 Co-authored-by: Isaac --- .release-please-manifest.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 50f0c45..dd8fde7 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.4.4" + ".": "1.5.0" } diff --git a/package-lock.json b/package-lock.json index 46a835a..d134f34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mason", - "version": "1.4.4", + "version": "1.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mason", - "version": "1.4.4", + "version": "1.5.0", "hasInstallScript": true, "dependencies": { "electron-window-state": "5.0.3", diff --git a/package.json b/package.json index f2ceff0..be239d5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mason", - "version": "1.4.4", + "version": "1.5.0", "description": "Desktop chat app for Databricks AI Gateway with MCP tool calling", "author": "Databricks", "main": "build/ts/main.js",