From 9c129ef233730c5a80e53610f799b37ba37c7f0a Mon Sep 17 00:00:00 2001 From: amszuidas <254873068+amszuidas@users.noreply.github.com> Date: Mon, 19 Oct 2026 14:14:11 +0800 Subject: [PATCH] Add MoMo Collab (momo-collab) MiniApp MoMo is a project steward: it files stray thoughts, screenshots, and links as records under ideas, writes a task once an idea's goal, deliverable, and done criteria are clear, runs the task in its own Agent session with a full handoff, checks the outputs against each done criterion in a separate session, and hands the result to the user for review. A board shows every task as To do, In progress, In review, or Done. The package declares agentAccess (session.run), agentConcurrency 8, surface.window, a managed MCP endpoint (momo), and a Skill that lets an ordinary conversation hand its conclusions to MoMo. Approvals are only sent after the user clicks them. The README documents the files it writes, including the Agent-readable uploads/outputs folder it locates from context.dataDir, which relies on the current Host data layout. --- README.md | 1 + README.zh-CN.md | 1 + .../momo-collab/.minimax-plugin/plugin.json | 22 + plugins/amszuidas/momo-collab/LICENSE | 21 + plugins/amszuidas/momo-collab/README.md | 70 + plugins/amszuidas/momo-collab/README.zh-CN.md | 70 + plugins/amszuidas/momo-collab/docs/board.png | Bin 0 -> 93712 bytes .../amszuidas/momo-collab/docs/preview.png | Bin 0 -> 200065 bytes plugins/amszuidas/momo-collab/icon.png | Bin 0 -> 29244 bytes .../momo-collab/miniapp/client/app.css | 319 +++++ .../momo-collab/miniapp/client/app.js | 1242 +++++++++++++++++ .../momo-collab/miniapp/client/client-api.ts | 387 +++++ .../momo-collab/miniapp/client/index.html | 67 + .../momo-collab/miniapp/client/momo.svg | 22 + .../momo-collab/miniapp/miniapp.json | 35 + .../momo-collab/miniapp/node/engine.mjs | 1017 ++++++++++++++ .../momo-collab/miniapp/node/mcp.mjs | 512 +++++++ .../momo-collab/miniapp/node/miniapp-api.ts | 397 ++++++ .../momo-collab/miniapp/node/prompts.mjs | 198 +++ .../momo-collab/miniapp/node/server.mjs | 473 +++++++ .../momo-collab/miniapp/node/store.mjs | 671 +++++++++ plugins/amszuidas/momo-collab/package.json | 10 + .../amszuidas/momo-collab/servers.mcp.json | 12 + .../momo-collab/skills/momo-collab/SKILL.md | 23 + 24 files changed, 5570 insertions(+) create mode 100644 plugins/amszuidas/momo-collab/.minimax-plugin/plugin.json create mode 100644 plugins/amszuidas/momo-collab/LICENSE create mode 100644 plugins/amszuidas/momo-collab/README.md create mode 100644 plugins/amszuidas/momo-collab/README.zh-CN.md create mode 100644 plugins/amszuidas/momo-collab/docs/board.png create mode 100644 plugins/amszuidas/momo-collab/docs/preview.png create mode 100644 plugins/amszuidas/momo-collab/icon.png create mode 100644 plugins/amszuidas/momo-collab/miniapp/client/app.css create mode 100644 plugins/amszuidas/momo-collab/miniapp/client/app.js create mode 100644 plugins/amszuidas/momo-collab/miniapp/client/client-api.ts create mode 100644 plugins/amszuidas/momo-collab/miniapp/client/index.html create mode 100644 plugins/amszuidas/momo-collab/miniapp/client/momo.svg create mode 100644 plugins/amszuidas/momo-collab/miniapp/miniapp.json create mode 100644 plugins/amszuidas/momo-collab/miniapp/node/engine.mjs create mode 100644 plugins/amszuidas/momo-collab/miniapp/node/mcp.mjs create mode 100644 plugins/amszuidas/momo-collab/miniapp/node/miniapp-api.ts create mode 100644 plugins/amszuidas/momo-collab/miniapp/node/prompts.mjs create mode 100644 plugins/amszuidas/momo-collab/miniapp/node/server.mjs create mode 100644 plugins/amszuidas/momo-collab/miniapp/node/store.mjs create mode 100644 plugins/amszuidas/momo-collab/package.json create mode 100644 plugins/amszuidas/momo-collab/servers.mcp.json create mode 100644 plugins/amszuidas/momo-collab/skills/momo-collab/SKILL.md diff --git a/README.md b/README.md index 3270a592..b0d0a77a 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ The MiniMax Code Agent communicates through the host MCP client. Business reques | [CadQuery Viewer](plugins/hanzijie/cq-viewer/) | View STL / STEP models from your project (STEP keeps assembly part names, colours, and real CAD edges), watch models pushed from CadQuery scripts, and mark positions on the model to send to the conversation in one click; MCP tools let the AI add model folders and open models, and a CadQuery parametric-design skill is bundled | [HanZijie](https://github.com/HanZijie) · [flowful-ai](https://github.com/flowful-ai) | | [MiniMax Bot](plugins/minimax-ai/minimax-bot/) | **Official plugin** that turns MiniMax Code into an always-on team of Bots: a Chief Bot builds the team, group chats hand work between members with `@` mentions, and every Bot has its own memory, scheduled tasks, and usage stats. Requires MiniMax Code 3.1.2 or later | [MiniMax-AI](https://github.com/MiniMax-AI) | | [心屿 / Xinyu Island](plugins/yujiachen-y/xinyu-daily/) | A cozy daily-mood island that plays like an indie game: an inspiration card shaped by your answers, mood stickers, a joy jar, a decision slot machine, a Book of Answers, and 20 hidden secrets guarded by a grumpy crab. Optionally, Xiaoyu writes you a daily letter and chats with you using your model. Requires MiniMax Code 3.1.2 or later | [yujiachen-y](https://github.com/yujiachen-y) | +| [MoMo Collab](plugins/amszuidas/momo-collab/) | A project steward that turns stray thoughts, screenshots, and links into ideas, writes clear ideas up as tasks, runs each task in its own Agent session with a full handoff, checks the result against your done criteria, and tracks everything on a board. Requires MiniMax Code 3.1.2 or later | [amszuidas](https://github.com/amszuidas) |
Preview: Token Usage Board diff --git a/README.zh-CN.md b/README.zh-CN.md index 1897c890..bbb99f5c 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -72,6 +72,7 @@ MiniMax Code Agent 通过宿主 MCP 客户端与 MiniApp 协作。业务请求 | [CadQuery Viewer](plugins/hanzijie/cq-viewer/README.zh-CN.md) | 本地查看项目里的 STL / STEP(保留装配零件名称、颜色和真实边线),实时显示 CadQuery 脚本推送的模型,在模型上标注位置后一键发给 AI;AI 可通过 MCP 工具添加模型文件夹、打开模型,并内置 CadQuery 参数化建模 skill | [HanZijie](https://github.com/HanZijie) · [flowful-ai](https://github.com/flowful-ai) | | [MiniMax Bot](plugins/minimax-ai/minimax-bot/) | 【官方插件】把 MiniMax Code 变成常驻的 Bot 团队:总管 Bot 帮你组建团队,群聊里用 `@` 在成员之间交接工作,每个 Bot 有自己的记忆、定时任务和用量统计;需要 MiniMax Code 3.1.2 或更高版本(说明文档为英文) | [MiniMax-AI](https://github.com/MiniMax-AI) | | [心屿](plugins/yujiachen-y/xinyu-daily/README.zh-CN.md) | 像独立小游戏的每日心情小岛:由你的回答决定的今日灵感卡、心情贴纸、小确幸罐子、纠结老虎机、答案之书,还有坏脾气螃蟹藏起来的 20 个秘密;可选开启小屿陪伴,借用你的模型每天写信、陪你聊天。需要 MiniMax Code 3.1.2 或更高版本 | [yujiachen-y](https://github.com/yujiachen-y) | +| [协作 · MoMo](plugins/amszuidas/momo-collab/README.zh-CN.md) | 项目管家 MoMo:把随手的想法、截图和链接攒成想法,写清后变成任务,派 Agent 会话带着完整背景去执行,按验收标准核对结果后交你审核,进展在看板上一眼可见。需要 MiniMax Code 3.1.2 或更高版本 | [amszuidas](https://github.com/amszuidas) |
预览:Token 用量看板 diff --git a/plugins/amszuidas/momo-collab/.minimax-plugin/plugin.json b/plugins/amszuidas/momo-collab/.minimax-plugin/plugin.json new file mode 100644 index 00000000..fbe713fa --- /dev/null +++ b/plugins/amszuidas/momo-collab/.minimax-plugin/plugin.json @@ -0,0 +1,22 @@ +{ + "schemaVersion": 1, + "name": "momo-collab", + "displayName": "协作 · MoMo", + "version": "0.2.0", + "description": "项目管家 MoMo:把随手冒出的想法攒起来、理清楚,写成任务后替你安排会话去执行,交接背景、跟进进度、收回结果;任务进展在看板上一眼可见。", + "author": "amszuidas", + "icon": "icon.png", + "category": "Productivity", + "exampleQueries": [ + "打开协作,把我最近在想的一件事交给 MoMo", + "MoMo,现在进展到哪了?哪些事需要我处理?", + "把这个会话里聊出的结论交给 MoMo,归到对应的想法" + ], + "apps": [], + "mcpServers": [ + "servers.mcp.json" + ], + "skills": [ + "skills/momo-collab/SKILL.md" + ] +} diff --git a/plugins/amszuidas/momo-collab/LICENSE b/plugins/amszuidas/momo-collab/LICENSE new file mode 100644 index 00000000..f50e3745 --- /dev/null +++ b/plugins/amszuidas/momo-collab/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 amszuidas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/amszuidas/momo-collab/README.md b/plugins/amszuidas/momo-collab/README.md new file mode 100644 index 00000000..d221a986 --- /dev/null +++ b/plugins/amszuidas/momo-collab/README.md @@ -0,0 +1,70 @@ +# MoMo Collab + +English | [简体中文](README.zh-CN.md) + +MoMo is a project steward for MiniMax Code. Drop a half-formed thought, a screenshot, or a link on MoMo and it files the note under the right idea. MoMo helps you think the idea through and, once it is clear, writes it up as a task. After you agree, it runs the task in an Agent session. It hands over the background, follows progress, checks the result against your done criteria, and brings the conclusion back to the idea. A board shows every task as To do, In progress, In review, or Done. + +Author: [amszuidas](https://github.com/amszuidas) · Version: `0.2.0` + +![MoMo Collab with synthetic data: a chat with MoMo, a running task card, and the "needs you" rail](docs/preview.png) + +*The preview uses synthetic data. The app interface is currently in Chinese.* + +## Install and open + +Copy this entire directory, including the hidden `.minimax-plugin` directory, to `/plugins/momo-collab/`. By default `` is `~/.minimax`. Restart MiniMax Code and confirm the plugin is recognized and enabled. + +Open it from a **project conversation** (one with a project folder): type `@协作 · MoMo` in the input box and send, or open it from the Mini App entry in the conversation's side panel. The page can be popped out into its own window. Tasks run in the project of the conversation the page is open in, unless MoMo sets another folder on the task. + +## What it does + +- **One conversation with MoMo.** Send a sentence, a screenshot, or a link. MoMo files it as a record under an existing idea, opens a new idea, or keeps it unattributed, and shows a receipt under its reply. Say "不对,记到 X" to move a record. +- **Ideas.** Each idea tracks three things: what to do, what to produce, and how to tell it is done. Each is marked as confirmed or inferred. An idea also keeps its records, decisions, tasks, and outputs. MoMo can question you one item at a time to sharpen an idea. +- **Tasks.** When all three are clear, MoMo writes a task card. Press **开始** (Start) to run it, or **先不做** (Later) to park it. Starting creates a new Agent session with the full handoff: goal, deliverable, done criteria, confirmed facts, and records. Task status follows that session; the board has no drag-and-drop and no manual status buttons. +- **Live progress.** While a task runs, its card shows elapsed time, the current tool, and the step count. Click the card to open the task session in the chat side panel and watch it or take over. +- **Blocked tasks.** Cards show why a task is blocked (needs approval, needs information, run failed, or project unavailable) and offer the one next step: approve or deny, answer, retry, or set a project folder. +- **Review.** When a session submits, MoMo checks the outputs against each done criterion in a separate session, then hands the task to you. Approve it, or send change requests back to the same session. +- **Hand a chat to MoMo.** In any ordinary conversation, say "把这个会话交给 MoMo". The bundled Skill files that conversation's conclusions as records under the matching idea (MoMo Collab must be running). +- **Coming back.** When you return after a while, MoMo lists what needs you. The **试用指标** (Trial metrics) page summarizes local usage. + +![The board with synthetic data](docs/board.png) + +## Tested environment + +- MiniMax Code 3.1.3 (internal preview build) on macOS 26.6. The package declares `agentAccess`, `agentConcurrency`, and `surface.window`, so MiniMax Code 3.1.2 or later is required. +- Windows has not been tested. +- The task lifecycle (create, start, host stop, continue, approval, submit, verification, review, done) was also exercised end to end against the real Node entry and MCP endpoint with a mock Agent, and the page was checked in a browser at 1180 px and 420 px widths. + +## Data & access + +- **Agent turns.** The package declares `agentAccess: ["session.run"]` with `agentConcurrency: 8`. It runs three kinds of Agent turns, each with the Agent's full capabilities (files, shell, Skills, MCP): + - **MoMo**: one long-lived session that files records, maintains ideas, and writes tasks through this plugin's `momo` MCP tools. + - **Task sessions**: one per task. The page creates each one with `agent.create()`, so it uses that conversation's agent, model, and project folder. The first message hands over the task and asks the Agent to report progress and submit outputs through the `momo` tools. + - **Verification**: a separate session per check that reads the submitted outputs and records a pass or fail for each done criterion. +- **Approvals.** Every `permission.requested` is shown on the task card or in the chat. `allowOnce` is sent only after you click it; nothing is approved automatically. A failed task run is retried once automatically, in the same session, with a message asking the Agent to pick up where it left off. That happens when the turn ends with `status: "error"`, or when the call is rejected as retryable before any tool started. `AGENT_OUTCOME_UNKNOWN` is never retried. +- **Files written by the plugin:** + - `context.dataDir/state.json`: ideas, records, tasks, and chat, written atomically through a temporary file and rename. + - `context.dataDir/telemetry.jsonl`: local usage events for the trial metrics page. Nothing is uploaded. + - An Agent-readable folder next to the Host's per-agent data: `/agents/mavis/miniapps/momo-collab/`. Its `uploads/` subfolder holds images you attach. Its `outputs//` subfolder holds outputs of tasks that have no project folder. The plugin locates it from `context.dataDir` by going up four levels. This relies on the current, unspecified layout of the Host data directory; when that folder does not exist, the plugin uses `context.dataDir` instead. It is used because the primary agent can read it without an approval prompt, so a task can read your screenshots. +- **Files read outside `dataDir`.** The page previews a task output (at most 5 MiB) only if the task session reported that path. The verifier reads text outputs the same way. Anything inside the MiniMax Code data directory is refused, except the outputs folder above. A project folder must be an existing absolute directory and may not be inside the MiniMax Code data directory. +- **Files written by the Agent.** Task sessions work in the project folder, and they may create or change files there as the task requires. That is the point of a task, and it is subject to your approvals. +- **Processes.** None spawned by the plugin. +- **Network.** The Node service makes no outbound requests, and the page loads no remote resources. +- **Browser storage.** `sessionStorage` keeps a random page ID so a reloaded page keeps receiving its own dispatch requests. +- **MCP.** `servers.mcp.json` declares the `momo` MCP server, served by this Mini App at `/mcp/momo`. Its tools read and change ideas, records, and tasks; they cannot read or write files. + +## Known limitations + +- Task sessions are created through the Mini App Agent API. They are hidden from the conversation list, and you open them from the task card. +- When a task has no project folder set, it runs in the project of the conversation the page is open in. Opened outside a project conversation, it falls back to the plugin's own Agent folder. +- There is no sidebar entry, global shortcut, or system notification; open the Mini App from a conversation. +- Restarting MiniMax Code interrupts running tasks. They show **执行失败** (run failed); click **继续执行** (Continue) to resume in the same session. +- The `momo` MCP tools are available only while the Mini App is running. + +## Source + +The page is in `miniapp/client/` and the Node service in `miniapp/node/`: `server.mjs` handles HTTP, SSE, and uploads; `engine.mjs` handles dispatch, verification, and Agent events; `store.mjs` handles state; `mcp.mjs` serves the MCP endpoint; and `prompts.mjs` holds the role, handoff, and verification prompts. No build step and no dependencies. + +## License + +[MIT](LICENSE). diff --git a/plugins/amszuidas/momo-collab/README.zh-CN.md b/plugins/amszuidas/momo-collab/README.zh-CN.md new file mode 100644 index 00000000..9dc95c7c --- /dev/null +++ b/plugins/amszuidas/momo-collab/README.zh-CN.md @@ -0,0 +1,70 @@ +# 协作 · MoMo + +[English](README.md) | 简体中文 + +MoMo 是 MiniMax Code 里的项目管家。随手冒出的想法、一张截图、一个链接,丢给 MoMo 就行:它把内容记成一条记录,归到对应的想法里;陪你把想法聊清楚,写成任务;你同意后安排 Agent 会话去执行,交接背景、跟进进度、按你定的验收标准核对结果,再把结论收回想法。看板上一眼可见每个任务处在待办、进行中、待审核还是已完成。 + +作者:[amszuidas](https://github.com/amszuidas) · 版本:`0.2.0` + +![协作 · MoMo(合成数据):与 MoMo 的对话、执行中的任务卡片和「等你处理」侧栏](docs/preview.png) + +*预览图使用合成数据。* + +## 安装与打开 + +把整个目录(包括隐藏的 `.minimax-plugin` 目录)复制到 `/plugins/momo-collab/`,`` 默认是 `~/.minimax`。重启 MiniMax Code,确认插件已识别并启用。 + +在**项目会话**(有项目目录的会话)里打开:在输入框输入 `@协作 · MoMo` 发送,或从会话侧边面板的 Mini App 入口打开。页面可以弹出为独立窗口。任务默认在打开页面的那个会话所在项目里执行,MoMo 也可以给任务另设项目目录。 + +## 能做什么 + +- **和 MoMo 的一段对话**:一句话、截图或链接,MoMo 记成记录,归到已有想法、新建想法或先记为未归属,并在回复下挂回执;说「不对,记到 X」可以改记。 +- **想法**:每个想法跟踪三项——做什么、产出什么、怎么算做完,并标出「已确认」还是「推断」;同时保留记录、已确定的事、任务和产出。想深挖时,MoMo 会一问一答帮你把想法问清楚。 +- **任务**:三项写清后,MoMo 写出任务卡片。点「开始」执行,或点「先不做」先放着。开始后新开一个 Agent 会话,并带上完整交接:目标、产出、验收标准、已确认的事实和记录。任务状态跟着会话走,看板不能拖拽,也没有手动改状态的按钮。 +- **实时进度**:执行中的卡片显示已用时间、当前工具和步数;点卡片在聊天侧边面板打开任务会话,可以看过程或直接接手。 +- **受阻**:卡片说明受阻原因(待批准、待补充信息、执行失败、项目不可用),并给出唯一的下一步:批准或拒绝、回答、重试、设置项目目录。 +- **审核**:执行会话提交后,MoMo 在独立会话里按每条验收标准核对产出,再交给你审核;可以直接通过,也可以提修改意见,回到原会话继续改。 +- **把会话交给 MoMo**:在任意普通会话里说「把这个会话交给 MoMo」,内置 Skill 会把这个会话的结论记到对应想法下(需要协作 Mini App 正在运行)。 +- **回来时**:离开一段时间再回来,MoMo 会列出需要你处理的事;「试用指标」页汇总本地使用数据。 + +![看板(合成数据)](docs/board.png) + +## 测试环境 + +- macOS 26.6 上的 MiniMax Code 3.1.3(内部预览版)。本插件声明了 `agentAccess`、`agentConcurrency` 和 `surface.window`,需要 MiniMax Code 3.1.2 或更高版本。 +- 未在 Windows 上测试。 +- 另外用真实的 Node 入口、MCP 端点和模拟 Agent 端到端跑过任务全流程(创建、开始、宿主停止、继续、批准、提交、核对、审核、完成),并在浏览器中检查了 1180 px 和 420 px 两种宽度下的页面。 + +## 数据与权限 + +- **Agent 轮次**:声明 `agentAccess: ["session.run"]`,`agentConcurrency: 8`。会运行三类 Agent 轮次,每类都具备 Agent 的完整能力(文件、shell、Skill、MCP): + - **MoMo**:一个长期会话,通过本插件的 `momo` MCP 工具记录、维护想法和写任务。 + - **任务会话**:每个任务一个。由页面调用 `agent.create()` 创建,因此使用该会话的 agent、模型和项目目录。首条消息交接任务,并要求 Agent 通过 `momo` 工具汇报进度、提交产出。 + - **核对**:每次核对一个独立会话,读取提交的产出,按每条验收标准记录满足或不满足。 +- **批准**:每个 `permission.requested` 都显示在任务卡片或对话里,只有你点击后才发送 `allowOnce`,从不自动批准。任务执行失败会在同一会话里自动重试一次,提示 Agent 从中断处继续;触发条件是轮次以 `status: "error"` 结束,或者调用在任何工具开始前就以可重试错误被拒绝。`AGENT_OUTCOME_UNKNOWN` 从不重试。 +- **插件写入的文件**: + - `context.dataDir/state.json`:想法、记录、任务和对话,通过临时文件加重命名原子写入。 + - `context.dataDir/telemetry.jsonl`:「试用指标」页用的本地使用事件,不上传。 + - 与 Host 各 agent 数据并列的一个 Agent 可读目录:`/agents/mavis/miniapps/momo-collab/`。其中 `uploads/` 存放你附加的图片,`outputs//` 存放不绑定项目的任务产出。插件从 `context.dataDir` 向上四级定位这个目录,依赖的是 Host 数据目录当前未公开约定的布局;该目录不存在时改用 `context.dataDir`。用它是因为主 agent 读取这里无需批准,任务才能直接读到你的截图。 +- **读取 `dataDir` 以外的文件**:页面只预览任务会话上报过路径的产出(最大 5 MiB),核对会话同样只读这些文本产出。MiniMax Code 数据目录内的文件一律拒绝,上面的产出目录除外。项目目录必须是已存在的绝对路径,且不能在 MiniMax Code 数据目录内。 +- **Agent 写入的文件**:任务会话在项目目录里工作,可能按任务需要创建或修改其中的文件;这正是任务的目的,并受你的批准约束。 +- **进程**:插件本身不启动任何进程。 +- **网络**:Node 服务不发起任何外部请求,页面不加载远程资源。 +- **浏览器存储**:`sessionStorage` 保存一个随机页面 ID,让刷新后的页面继续收到属于自己的开工请求。 +- **MCP**:`servers.mcp.json` 声明 `momo` MCP 服务,由本 Mini App 在 `/mcp/momo` 提供;工具只读写想法、记录和任务,不读写文件。 + +## 已知限制 + +- 任务会话通过 Mini App Agent API 创建,不出现在会话列表里,需要从任务卡片打开。 +- 任务没有设置项目目录时,在打开页面的那个会话所在项目里执行;不在项目会话里打开时,回退到插件自己的 Agent 目录。 +- 没有侧栏入口、全局快捷键和系统通知,需要从会话里打开 Mini App。 +- 重启 MiniMax Code 会中断执行中的任务,任务显示「执行失败」,点「继续执行」在原会话续跑。 +- `momo` MCP 工具只在 Mini App 运行时可用。 + +## 源码 + +页面在 `miniapp/client/`,Node 服务在 `miniapp/node/`:`server.mjs` 负责 HTTP、SSE 和上传,`engine.mjs` 负责开工调度、核对和 Agent 事件,`store.mjs` 管理状态,`mcp.mjs` 提供 MCP 端点,`prompts.mjs` 放角色、交接和核对的提示词。无需构建,没有依赖。 + +## 许可证 + +[MIT](LICENSE)。 diff --git a/plugins/amszuidas/momo-collab/docs/board.png b/plugins/amszuidas/momo-collab/docs/board.png new file mode 100644 index 0000000000000000000000000000000000000000..3fcfa923a47c63832b14200e766e9e6b935de727 GIT binary patch literal 93712 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

4@nHUKE_i|lQ* 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 literal 0 HcmV?d00001 diff --git a/plugins/amszuidas/momo-collab/docs/preview.png b/plugins/amszuidas/momo-collab/docs/preview.png new file mode 100644 index 0000000000000000000000000000000000000000..39bbfb06e71732ceba4c8e84bcde0151a496b05a GIT binary patch literal 200065 zcmd43^+VNJ7dI;1ozmSUBB6A5mvnb4p@fps9a7>Eln}fD zyGD3TMP5cfAa5%lBZ#!OJL>z6u8OiUL;6@?)?6SFX)+V7jJ|Zr3sMC%dHEtvWb9}L z+M*Mtwv)L)Pef6Q#g0dZ$ep+Izjr4dKUN7SpznM0<;%A(U%qIpz2U@(NttkVE6#N$N z(>e7LX~YO6gHHFtF$i2H%Q9Hi$Ypf7_Y7~`xN+d<=2p>bLsUwTHn{TR$B&g2)J{g_ zw^9Dt|1Oh-gtLKFqtp1(r&7Jwp7T>mvgR^OG*ThrDYT}IF1}mSwZ5BE8N8-V-phSn z7IEafWk%J{7)psttGc?o3kwVL^YhKk%)H|COerYEj*fVBz5@ zh^l4s)EF@}hPB$ie@cYUEh;L?!h&jzFQnElUAFYQ>m~NzGN_I?WT^4mn)d#=rh&@D z#H7yl@qPA9`=ITaM#kbp&SI^C#DapLgP2U*w+NfwSYZqvi`LW8{5xo8TD8XT8rIg< ziiJeS*is5*7*yP@Qx(5|{~i#g$z)ik(AWH=(yH>`Lu6^qkQkpIZ@1iEpXh!W&px!+?J`OqjK_pm^(-NOTfnkvTUrAfw zIcr(T$nqN3NJl-vr!^@ztmJP$UD|#<-`CIIaWOG6;(offc*k$+@#I9(Vr+Euk1zG? zRCyU(24RMj0#?_$EFWF^dU@@6c3e)uUOSBBV5Pd$l)XcO6}~Z|@iYOK`7102sqs4X zUA2$N=B)p#!Q=+D8P|uw2ZQWtS$w>P6;BHbnUTAr@XB>xs%}nI&Ig}-gUT{{^=EhdcYa@<$5HSn%3-B)!CE`iU-x53(2P!WWB%EUDl>7b^qz+L zzuTztsI{V+8-!RIZ<^5}sba>CoKV zEaW*ylQ0N7UGA84=X-nb`RuoExkAo9%Y6w7apa5|`ue0x-`{833Q&gpT4%3xB~Z_7 zr|zvXZ^0(#;TU-Iq(Q1##`%6Ud0oQ$VKqYM{9J!rBhP4dBxFgx!j^&U@^mgXGjlW@ z?lOkINAW=AY^qV;2vN(#F!75+Hb&sja?L`ecs`!kH{9$zyd6z+J#5}_a%$>3As45L z2~-B@h1Y&VI@%2cNBGNdz88y!~}~GM~j~URKZ|;W(Nn zU^An{&dx6L+trHk;~}ctKr3mfGnv8-9&^{}W{uf5j`e6Cs&qSEP1MD3n4A6A==cjr z$&JYsBILP47N^)#x|{@W-=WH*jWmdf6X9lh&oqj5_nUg=1zn%pbzDAtPKH`xk6B6@ z8eBEsr5>yx9^Im7ZHhM(8(a}zUwd{0o_OrczWH2jNl!?~;Liz1#eY7ySZbigx?2>( zu#CyQyr3azap6YyO7>D`H!8{2^pi)baj(-uXr8G3+iX`}rlsDq>eNM(H{nM$p_8W+ ze3K*CLMfh4>aWz>E8{KPn?Tqmb= zdOnrt$X$Fz!7&_m5~?zNjc?7*&o6_if*zGYjT`<*y~pD6(a^2j_}~trPA#;*BjG?$ z?UaEi1r6HH)te+Vl&BI6jM3%2=QD{>h-`-A^8F2Hi!Mei_)82GYAI{{B zaKZ=@cDARe~Csb5Hix96IM zZXcG_{Xyyv&)?x*gr=)#b(d_BzSNAsu+CmcEEiYXh+J$p!$5w+C_5?l;;cXcD?!m# zzrtsu_6vJ>;YXbJKP)PYZwMW*t3Q2=67u`(yC89b-^JnHp7NSZ$e&C9(a?;kyERsw zQFzpcH}I&0e$2H6E(NX{o*jPFqL&J}*zc$BMuG{WR`BlQM-tg0)7KuiT&GC+3!v%I zhzE?x^p1}cN%32>8kcAzx*~D1_$?*%Ua5bncYKnqalufinDFpm1uOOJ^z@zIq9d%U zJ;OdN-aYb|?_D(xX%o+n)$%{m{?^n<9~e+ZNG@>Dh=_<#wk`K3eJ(RlQ;V7y&K0R9 zH6HuI(GZD45fv4+@$qjvP`OtW|1MUjUt`uo=sK^VoKyUg6(L(8((#a+0VxqhbOKI4 zC$Bt5Y-gDZPq_eUKUIybMbdXmH~c5(TlF8twmd(+(tu4LYY#sBG2Z0=et6K?L1bj);C>Nt{TH`J6ND5)l%T_Vj!)jNU>cnNC`?j}r__1v@ePLI zb0&N3>>IXy$J<|%XvAJvA`a~J-riq_!Qo7wGI`T+RLFfszrL0{j#XTI_RE*+S;=EE zUJ0y{JMPA9IuEjuT>+r@c%$!q zFYpehp%(GL&Hui!F;k>M!h1bdt;1+_D8u^Fi`%=Ut+lnvqK#{rUNR_Jw<}Io4fa;1 zwC%qFl7#|_?B(*8&A=U1tY_*PiE@gwNle5g2;Qh-3ba+|dx{cP(D$^`ZSI)9M2DmyR|+|JkhWSW%*&$b!m%x-#Rl#)@QF{#{Va0_p+OfNuvvE`L91(AP33Zo{C9)BQx+I}JS| z9<%R0WN;h3{mpGZl*P}_`gz!PAeqtRbgI%6&f^($o9%OAs^7QT?T6C$kB*K`PAp%0 z&{oit`2$#sn4#Z>H4zR3aK@Xm_#{g2`xdjt2vyAV^bfjA1-ta8M_I#}_OD)V^mErN zC3@|o>XzmOh%KaA=QnxIC+@+pm%7-Kd+_qvvu6O#r&8C88{aT8Ljw7bzd>?ybD%P;kM#!`arQn&=qs8~@H_hEw`S7dH^* z41$St5-ucP)D)5%J^&cJW!s1A>FHULpjWKPh#uDMuvQ4LR?ZEN_O^bA+kE>aESy~c z^aCOyqT`PZk6;9v1oT7`@f%om4okmqrVkGbi&tC@L&I6pFe}!iRenvwHPEU}mSh&G zSCYldeDu7Gg3rvDrKd80d|v2}iQC> z`R~>h0e?wj*Yf!I3XktPQ{d0F5fj17oYWd$5zjfm+ENG}T3Xuw)=EeX){4^AjGrrx z>BW1)LYrg!;G7$`->MHz)W&j{ytt1lOdU_I;Jv?BHuv27zP$Xnn^+zCFRA-}k2aR* zY<0RKiHXa-YUm%xK%4hldw+ePRQ$m~p7;aegBP-q*k01yh84hB(@7_tXSiJVg z*Tx_idTcjQq;fUK8k|yET3X`Ar%nS^^vex3Gi6!HXauo|a=HVm9A08Z3QF9neP#gvN0p){uOU`F@+KEB` zAE|WCOsAA}B3pD|opKS#A;XB9q`bab#1VNwF=$a1q<;Uf^3Dm5Vf_orJTpQ42ld&E zQ(I)jE0jb#R?#uuHGL)u@iJK9E5sk9lI}3R8z1+cs<@j#Epj#2&CSiJg`AS1yG2u@qR_4Eo})a~sr5*C_ne0?LZqGwzjS@LT@Q&{*Qb5H{h- z6uY-39{j7pZZI{8PNGgLlT|Gf8m0?ya+}|>@b%cK7Wrq$y*^QQ&&j3Wd%Yv^bb*LQ*FjSv5W4Zbqm4-<6m%y!Nwo~F z$OAEWq5ArJFlHOm`;+KD?{d%@>g#_x`}ti*;xMP4xXkLEy#0v$QzZcAJ+6fey7?eNK)i484qF zvZ_of$e(uTowWS&WE4P^vpOE!b; z^z3B*(JLQr{=!6ifyJ}$>B#+5a&}(+IC3|9{O9rU6grM&M@TSqJ;MB7Ri=uC zI{(3o%A&{bd<3al&2dHKDsRMgx(LXNHA^d9AL+7dpfZ%qx4f6x!uw4ULMTvA29T^l7wP)h{kC96mh5Yt~wV7#y|`FigwVGVhYST4F?Dd-ue9aG=uOL8BU$7C3(f_9npo>GmK=g+#CS~PnhO{f;Uo1dSz6MXRSq3kaq=Lx&<7YYRQ zlJdcP=5Oy4i-OWXCiJgE=PI)|EBzq+n6(GpHN>j1A0`)n zV;Qs``IlLz#sadi7mBX%=zIN>n%9tZf{8B5R7<=m;7;70aj%r_{{0)7!#1~%SdRTl zbi0NbJBqFUpxjfAFG(LU=14uat@=uLz+e%Gs=U{E#TZO7#+CD9m^E&nI)n zkbrY!0FAf~#C*TO`D2-3Wf>lMxpD0hv{xW=)HF0awlp*}Fn#PBO^8aR@Z-A?a!=gW zVn?6eD4a5lXmtYyb^h7%5v=CU`EEzGP9b{86^*9*4pT|XXC4R=9*wBi*}*EN<3KV3 zy4nmICd2-Lx4-}HRAug(&B5Bp*BWcNbG!4CT>>nuaY@;!)6>&|8%f0CL>3knKGHNr zlYq8iK+nAPv`isY?Y6R_;L@Orpu*V4-Uhi;^O>M>9`d7f< zWNC#l@8YUFIgiVotG1LPMW!3XCgWN-<27rpnVSM4TMU3*R|DHu-D!WRw^7p7v;Ays zvHJ<45?Ukce+bx6*Jr{$ZG6FlG&?VWZd6r@7^af^i`G-3 z33@mZUz4PM8&??D0@7TXT4ra74AFQSkt|XF@Mr@~gle~Fy&8AK0&DUKIy(AH@wh)5 z@t;wl>hDn0u#0Jnpx!OTGSpw;xQVf6(Wf`4(nsC#Ixb6-;?{pQD&x0*$?zNL9bSP6 z1)}H7N8Zp6*m!uoC~&%n@S1 zNyI@T|99FHIp~H=3Z&_X=E@g|fjH4p$jBq?vBw~pQ;Dus+$>#T(46zE@^i+sw_Gqt z4vvl@jfl(CQYEy3r|BYO(b70I6F|e$k>b8-vb&GXQ)!6~oS_e}T*vv&ora$v7GfK% zuB|d$dWZruhx56@dx^OKz27YPN-{RdV-_JI4NN~ z-WC^_>+yjnJDtWE96HE8dM;G6g6DQfNJw=*4!33+ail=t(kWfR9D{L!ODAruX7zD$ zXL7HP+Oxsq+l*ugpvRc|>^;nIH0+Jd5s5({6xye;X)N@5IRn8NR-zA1KUF^-YSHMe z)r$)6@45NnaGeeJ!yj#SjM-PJ8C-)Hhx)rm_(( z7wG)8iFb?4HwJR`)gdFCOZjle*fBp z%vHx|rp8Qba;UNbcttAXxO3*99u3G=| z0@-0YdoQVOitgT3SEerxm31V`Y>ET4f6>hI^bGhNFp=>U~nLUKqW=EK6oguHG21qr*4B}GQ%mdhtm z@1-8#Bn>*YN%T_fA(s!^9v&TD1~U}l(@S0~bcVM+__;K&I-I4|$y{`E>xqkMYip~v zJUAwiNHVOQZS^W+#ai^Bq*Qj$#g@o~qZcYAO%XrOQ%a=48ey~`t0@2+`|h%CKB5Kd zt}V&>!h&{_+g*LJPuM?rfuaMfgmyOf@uMD_v^oeJ_;ljY%yB*5{`J4bX9Zkr1kbHG zEssOK)LUX5A-DMNRt5KH$d5cJn6UABsqK4ky}xl-S-c2v+r);r5bF>|&cttnYqy#} z>TH$vV*2Z2u^gowRNlg5tT$*D>$%QTj_k`ygXe9?Jwr@E@>y#5xxA@`Dg|QwV{sOz z9=f~!xdw1M@77_=4T(r?(^eF8+{dxR`-_{KaSlJBBR5hCMJ?iJ0u}%;GcYg!<9%RC z(4>nH52SSWIq}~yp?P_EW%{Lcp7W9#Qiw~lxA&c=$u*?5LGXf#r@eiDAdUS4U=Nt( zF>OMe$4g29!OBiKk7D$bsg93HPVU2@TOuRgJ={)(@%cq*smX79#vtmYQ~li>?UKs_ zFpT(8!&`C%U80TObk=wT72)DqbEPI*$`{s*M6Y|^-lbqk_1a|Lay0}T z;`Yl%+MJ9-djYiDG4vc6ol4_cCOBB&g8Txqe^ocYHla}HBp%-HPo(u3ux2VQHz=Qa zdc)Q#%L}!9^5Nb>C)h*hXJ^h>&eo9QM~8cC1l`x@d8nRv*Xc~kgb>o%q9w&ZY_N&f}UjMWj)gLV?5Cgv0| ze8n@f>JhraBsO~=`fpW9?Ai#D4yI3_VE#ed84QW_z&cDbD|;VRQzO8;1)|mq1)O@Z z(FQUe<1rwA1-gk}CqAG;AKVW`!EhMP+y;0D_FNduh&)5t zH@~MaD{V|w0_471IF8v^v|bE&d11loay^!m6IlU`_XXbLhx^P$CdqQML-G!UYL`8I zeKY!qh9~3Ch03_8CN;it?bshsjfyD!3mN`k@mrJ0hI{#*7VBUnWQ;o{Bl#~&1=r2zxUz$Gch?z#$-Pt!JzntgWgP{+M@#k_#SC{~@D;p>1nyT0nlj{?!Z-Jd_H;qyCue0)j&ompT>iS$wy zFXUr{-DjNkVVFHEcv&2B`D-v6MGd8DsQp|W{&8|D5w@RzLq8r=X;f_ij^*NL$k{5z z4@R&nx8S{XgTZrUi!tedO5F^y=0F_|1>f|0nVs$J1d>I;;NW0;+^#qGzBf4wbet0D zvU|A?u&WC4@bK_r^Vm^m7k^VeVfnP0_HT7452YTXP5~#?%D+7c>O7#G=C^N|(5){b z`b%|R4z&fIj6Re23Z?=DP217wzU}$WP1w)%$?`0=niYmlkUzoMq{J?L@N*5;NIDE$ zx!b-QTj1&dU5wERU)|b*;k$ab-T@Rc-1b2SSLj8CRc6^h#o<77X}N!U+Xy3pGbG|u zOsAFC{rf$a!M_emEvQ~$B`=ngaJZvMr&`F6trE)qyQ&Xv6-k5|$SKg1kdV|njlY1V z2lC`tZ|{{}28RU%;atfPK+=`SsSAI#0IQs>Byvhh%H6vzpR4dh>_{G5oO<)R3Fp3c z|LV85Ad?48eFOZ(aJ&7JlPAZi5>da7HbKV|jq9Cw>+6~#0782NMfduaNN4mS4~WPS zJpi?E%#2&h2imTdxdvsRH8+=9kbi3mTfOm}1!MPs@cCMiXOS)0zfz{G1y+<$o1uxc z^t)76HRy@@2-!%O07MAW;r6{XbxWiMWK+}?tRw;7O9%aGpM7$iRpoXN?u`OXJ@Cb9=~;*I-^Q?#49XJ zE|s5`2NU|s30H9V28N7Epu)fBzB;vmw`RaqwgTIZTs`xjY=JU2rIu>K{iMR!zl`p{ z1@Vr_8AV^egj%W-yf?eB+=1w|z4~4q_I6pB)~GrE!Unn3(A=xw?9UN=i}%qQ6dZfe&bQfQWA2zI_#A=)oTi z{ky#IBf7W#)7-Cqmx0{>ZQopGfG0~9idN`#Y2@MxBa|Ib1Gq?;pm-rvdL=-vNQGU6 z7L-lEx2(8Yh1Q@wJfwDB>1+HJrM1AbpEq1(D*h>w2*OlhX=W~A;mnO#LfPNkdvuwW zjG|@#{2DUZt0!l{v#K?=v9S@=PlmNYIlYZkZ4WNFUoR84?J&H*8EKM=bIaXpQPm zjwT+QPS^N^TmRG~A}&i3UXV(PS6zMx)I{O90-kc>7CL@E>~gA0Xj^Pd%s(&4jUJAK z&#ll$JlEn6{USckd>FmEt}&{#K281A;V*6Hui(=w|B5V}!h%4OZiONDHW~bf&z`U7 z{3Jg?_vw6^ZS|6_W`07?)hqD-v-7%~G-Aq_jLyhG^T||m(?H+ehFrdtTmxbW(^8Y; z67nSU;sKA}0T#ZpqKQ|5w1Rr`sXtFL_=+Aa+1s4@Mp<8GIx2UB;P}BiP1RhaxiWHe znH}oC*wkJvuQL)RPuk!iJ)dd-z#AAVAaS&S1}1pLx*&Zs2-;$27@EC$C^Fhm26ypx zozJSOf(|ss4rukD)!NKQ;L{Ni-FWo-0)7+(I`g>M24@odE5nv|ttW;AMAFZanSACC z=UN{eE4hNi1UOV)mr{6JpkjvOSxU2cEUErn89(gtH<^F7`WABNl_XI_yEo_O<3s$1 z2w1qEuP+_}ft1(6;|6_54*7X`Jywc|rwsum8kA;p#-+xp?x;f#0${2nHSC zh=9OAAR27|E)`-)zcjjLRabk0Dgm;ut&NRIlUwOLnyG|@#Be%iJwOFi5PF}_V}FOj ztB`7+RR{?>-+sMJX1rPfKm|S4zr+c&FD-&|~(wBv; zt-ZnXK5Tp3s;a4aiuZaBoX3O6;WaH2Wibp9$%klH9Wq%Tqs5X)Ryjvbhg1}{n#DqF z%m4s=iGC^Z;TQ&e(6vYR7v6;eOCY!|h-$i+>eeuR1wRTT@m`Y=5_$n+01Y18N-04> zL9qPm)BB@VA)p((}!cgyLUtg?!;nyGzn~~!dJ(vO$^#3b++N}^c@6IWIRIRS# zMsXRGU4Z9vvOE8|AO5h!Ia&Xxq!~pC&B}mBo#Di#D4Q8WLE;dwvLI-0|%)T z|LR{VaU8Kszwbb0($muNcC$lr!e?cGy&&j?`$+c9V%!Sz7V^O&CvJEza1h+<@G1nqD9Q(kG&OZ~+#C_nft%Zr#~K0sAtili!qlw)KHt?jgGhX!C9^zbDI$0Jdi21@A8aP?BO|l5N#o zXrF&`ub+*0XZdu0c`33J$Nj@IiKUF6mcf_T?xD)v2cAXpC(IEwTMU7c6u)%|7V;~| zXWhQtccjS$j|zMFgn&K-e7Of4t$!GcKpW+fx>9S&T0rOk3%n00iS`aU!OQ&5kVS;D z1;H0&KT`niuV4E^%EYP}x>VHTJOx)Uq?nZwNjF|R0br?SC_$jMkfkhNS&2Eqg&x4zst-0-ExL_fgc>jItbZSeUq$Z%t%;}c4XiZ70cUxxAT?Gqt2 zD30lNW&KqC^JAPDR0WVB5WYp*zCfcvA|L3X0!Zo9VneYN&HUWA0qn%fHv_I+VDpVN zE2N!Pcl-vOQ%&$BY+vTq&1`jzT;T)}q%t9V(FX^1;+Ut=Z}X!wd*z5cud;B&LDDd6BdDkrz|x}WEsftH2`Zkd!`mE z22l;sK#|}8ynjviR6Z>XZTlR4!`~L)kxuc)_55%{1f~Mj&1=N zr|ID&I4-guiQG>i@+B%JhF}a`6Pn2sWHpJo;?ZrgR|zm$yi_fzOCdP30vfVp1_Igz zkA6P@ZZqDH`SI1YAX@@vbo;d;WSL{2V?spUib6hiX@YR_h3nA@85;ujZQLm?dW?Pn zQS3eWx!d|v6G8>p6nuFD7o3Br9NOGH(nvLy=a_px}y>9B(6R zp!~4OBH0z#q)mT8(=Mr+dH+#4zEGxvPBO^&4*td&3~Yu0VygX5tq<<;;`X9!3xd_Q zzXgYyMC*D`o>?hx_Cn~<-d=Lkub0TX*WP3XP^FVum`%h;+u04JtCV1Kg4QXBM5lF* zLE%@8LjO#8v4 zDz(UQqYZ=egid@O+BOUC|8S*yxF3Z{ykPwCPv|-fWh;bvlsfa!SCZ{gmcSE5=7WBM>F)NmNu#eYJ2tcF_trGge94ue zXo5Q3gq0tuKO}1RGK_q{Y%Kk%_YKBT}-f_(+H(sd20?A-vA7cap(cD2;_fQ21iE7u{ zetC9y*cD`AQ3oJ>R@V?O8c=KUO1{>V2#*AunoSFBGnbJRy(h{D&?=%Z7X25Q^wi%3 zXHnFq@*@+Uu$Uyp4rP-`Yf!tsia6c{155SsS}ib1oPeJfzc#BOm)9gY_gmGQrj=ZK zdtdV%AK`4YJZq;GrerJAO72Ge2g#2?A3=87iwYeGAL&ubL#tCP@xzsZ`BF&>2YK*7 z2O9^+_1oK9AzH17-M?CZjZ%qv9%(ARXBWliAAfiQbe8#88Vrt6d(M?iO|_SHcax}5 zC(Dx}UUaDugjEA35=8%i+`d7$VZ_YDxtEJ<;s+tM>0l5lO)jX&5L~ujZgR`iIS^&H z$xA0+lFK>p7nj;bmaDxlw(P-USI69XcI%DAZ7fEXO6h;_=EzO!t_V1CSdF%_^78VK zFCwB7c6;iO0(zeazibca;Kk4pCr39yiE zP=>Jikn5w1#=+aa}rBaV0H{{YC2Ec}qkn;R6|DYfJXJ!(HBe9QEW z&&#PdWVi6Bf}wZUFmK=eM^<{q&FC{aOVS zV>0AWO?$_00ZB$@lbM!=#;bTQS|t>%Alm;k$eZ4JO&;HJ?)ZC%5fl1%zOv*BK|n}} z)`w8?wR*N-K=P`xmcCoFLhJF>O!~BIF0acm$#s3ovo=O3!0vv|?a^fREc+8Ay(85Z z5A!tYpS3wPn${7v;K0E5{~sy*`FtdaoF5q?aZ2?Xi52jgAHKEt+aouaLTDUw&;Zrcfrs9qQj5*2(J-BXVd z{yb7EPjtLgk3Cwz-S|ac+h+D0(m#-K0X_F;?6F};fwY5xcBR(Os z@rLl?UdfmO0xlMP{yQGhP(AHVN9(2w#0_-gQHLidN_raHebii- zW*s548j@!14O|?15LuXS@vnX!JNFo{BO0FXy1(7CaSwTxQ5*yI^J-vTm#U^)JQm4|aP0tj z!Vw}tzby0z{=n4{s4hT2uFYj2qJ9x+vc9pRZ@4EZy8PC6(~WrH@h^O{0Y?z$53#PW zh_VH#4pfCVBYp5;N-(}RhP5ff@SbsJ@U&a+4R=-I2a24Ch~ArF=aPWX#XG>y$+{5` zLG%U4h~sU{4O4*6h$juo;-5;W$1rAW(MTfFo9~!(+%#LuzUipFsFUxdFgDBaHX`cA z(&!LY>Y;v>+4U}EmT<6xDYGM%yJ^!2C`RK2nBe)}wefAI$lv@5r89mFz@(7#VhB&t*7}%wFZSn$^M7hMPG~ zB1ErV0e$weV@g;q2|K}clbjIc^Wij(Kr8~PN!Wy8up-$(9aR`eZ#&QWydpUY20?hB+>H0ioxE}ljh zpmZGHHKNk;h#3`Y;P#*k%eB04{`ksCXJG%9Y(5Tp0`_*H_E#~dhRxjDDJkA+lC+XR zlnIY`b#o|$W*V(y9@ezbXdRw?FBmpVMis=|cFc9oy=6rrBy+6(d>KPei%c;_v$sv-Z3+n_AM@DgCo3K^r37dG_r&{jEIeKe{}QT(M^ z=Fyk-&JYK4>Vy7sizqZwt`l=%!fQ}8r@DF<(xt27@`IdO>EfDjErW&>vx@3Ax^-{| zJC+L-AK^pN5i3!>W~tnmN$+C-%iVXu_olR$1cka1UyYpCBqH{SoAl@lLw>X7u{F|? zE9tluQy=-+9^z5u7DkpuXPkAtSk9USz37sE|-*p{@|u6Nu7V z9z6SssT+8R0dZP;2G=X03is(_DK!-nt%A6+31b#Czn+~d<&5?p31LFmNqFEGor(-J zA=#U9A)ib<4qEoXx)3^rTwxTYkay#okg=zb@+=D3pf8d^A$AW$2n#N@+AY(#8@bC~ zpQhoUY>+<8mppX(Xj{0-xlP%MV)DtUYSZy$ZD)wbf?b$lRbinm*eDH8uezYcwHK&$ zD4#{`1_*`jHiqyC;A^Wn;i{Q7fUDc^b#YYkeCwvrPr@X%@Kd-sLhq(YN=P^cj2Z+d zh!!yea{+tiNqo1`mrcChu%E)Ph0soFShUfjPmYiKK7f04#dzJNoY)#r>{B4s`?;9{ zY!x{hq0b;(Ni@7~obzff0k6nuS;U&qOYbTZvx0<<*Nzn#Q^7iNi^<0#oS7KFCB1XR9kRHE!KZS z=Sz-=XCkVPFiqfdb1#n59>{pAght-x(Qk{K2(OW2?Vn_%z6y?Uu77_q@s9QR(&7s* z)b%$D9E>tv=02_NAc1LOeLg)eS%Hyu^}$057mvoW^hzk3B2! zW0NdK#FHnRwhjA+17h)5H1yAy#i;Nv3rWeB_G6WCogeA#l^l?v9w;pjS(Z zoEbwAnT0FI%bu1~6|0h?oBQkINtTMW%QyP9&SgfNNn1_#5>s~{r3)aR<{&`oJa*N7 z9_7mK)%qPVtaH!pZ4UBQ995M_2H`BN{zCL(@!s=JyT$#6U7g6!wz9*i(YDq@sB1x^Qv_SL@7pohG&Z4-ngfxHHU*scY1zq1XKu|niv|@ zjWLfl^_9Ow8^-V$G9u->DX6y!3+`SQOTTx>pav?=+3{CTcFjDFz@7&8 zug6)IAt|q0;5HmbkTN-E4Fe9iZWt1b#P%}{&LH0$*yyM>hYJV_P~}LfwTegHu1@A5 ziN4nS3G8bKBpC5WU7L9~W@^IyTBlIanzAsgI>kVK&EPtV*pt;dKZ5urFAy0s2A9FjTZuG3KZORzRUK~2`eKs9VV zlOK^m>wY;|X-eTE?-wR_8SyO2J3Jv0O|i+l;}9*f7`NqllPgN*z89G1Ls$!|LB^tP zk=SI1B-?7v(YCTpk{^kaOKG;h#u0O2v3~qeT42tKNXI&=@5cG4#)_aN@rfSny_4oU zExnx8txzHMrbYC&6~z_@Fkp#)888VobzF5nBM*a~ovTJk_iR-8^Sg0=2QW4l*xaVf zK1!R?uA$+n{df$Mnu1FolQ-xoX-O!=N!D2-wb>hFf70>F2|YwXM&3$7*JGfKB@Y*V z1UFnDG-m+`Em5}fwz2fcJXK{YkXW(BLWiX@lST3_M?~f$T;-POvv@E}W#punew@f2 zHwaYGYS?Rh$7*Z|mD~q)X}n_c#5B}DSNwq>c8^2R1c31rJY!1UHSjl_#4KCjWNF7scL{ai60%qp;SL2C@c{wRHP^?2PabxgPoWwvt-}AP63lE^ zMeul{p2%3%d_o6DF8j-SYI{8qp}Sb2tV(^1Y63|dlu9TC$^@^w7`1BJfjr@* z0%@F@_1OqNNjK*BuW(g0@bE{d-Ib<>jr#Jin?S0=N z1woe0F+_N97=hFU;SO0WIr{bLM&RDxIv&9|xeWL@uztJ}6ht`XQ_+Ab$OmFRKa%^B zl$ouzHlc;9Pc+RO*>n-T7$)2zXa<+(bTh)Rf#r{cy{ZX&yij5`%gz~VzNiuY=I4UA zR0X(yQKa`Tx*TJW53Gj7Mlu=!op^EzD1l-Ddkp%+aA}m?5fMct?Rqzs0F$Btm%r@Y zw}{9{m|vFA{<8QAfs!ou9}a!F`Bw`_DiE+nUK)S)Su>)L@c_Vj^0zuGp)I5QCp4k& zCiA&_cKUb5UJN#KR{kTbDCVPMkSya?5KmG1Y)r{0~@)tKQRt8CO zku-iqwP~MI{GfRH*?$=fI~k%chmy%Hg}tuKH7k_w0S>v|m=-Z@aueVY#jo2}zZ7ND z%oFvTYi;BjAa=XD%4O&HfB*a@-siiQGV3Kp1>tq|3%66m_^B)4^`ujA5H-x?y8N@ zls1Z=Pdwhw6L!xr`|%Flj9>kwz5?Oxg3RJ`=F3h#3{}GSSB1&%D$@!NEuLt6K_#oPNRcx3RX|Q;~7bB8Vp>T`reXWAX~k zqY&Ts_CdnRF`0a)5mBz-&ad+VHRwhelY9^eh3I%xEG_mU1yb~ys?{+%30Fae^XSTw zZCP3^^J)u;R(b|6om4D5{fO_8-mltJx6^rC^2G-Cz9WOt8B?S?BW(26pIno(gOC>r zgYJKY@n8ESpcFhUr39OiT4?*W~kB9aek%?PGNc zjK&=0sFQf|oAyJbX3Q?Pb{G0qFtIh$Am z$*WN#losaH;Q`$Xrg~iy*2W?jQeKfYA!DYHSc2?%?bJIU$-9@AmoH^gAIffahXsi7 z-%7_*Jph-a|8fRY6hEDP&Fyq`5O>ffqis6dH0xqQ_Lmgi#0gvDHDqaAq36CuxVb)K z^T=l<->i#AY@xLzVy}#Y>5aoYj4ZWu4g}}Tr#aB}I>V_pv=dGdkpI<)Y>@{Mu1++qrvftksNPt6cQ~*~16wzLs905aF(0atL_dl%PaKIr zpIl?h+T>ET>c9FN3h<&iQ&V#He&T0zBR@`b(b_sqx~?Zf5ZC$>V;RF;cZA|}i6cM-p)5{TQ+5`J(zk;O^VZ0FmznahpdYN^_Cr& zVqTpOsWErMW%*2-ct~0D(k~hA;OUF&o9j0%^~McX`R$3;eSL2B@mefK~s7vhEUE^Hq`klAudjwUG+68Ihjx__PP!2k&K~3kF+hKY2b+s zWCFCE2bzR2Ns%{!C5BRTC(wxp2)w6w<{?h%Q^6fX9zh$CPIHG7L!NmJT=SU{3y%6L zGEBjNfxfii<(0k%yus}ix*Vh0>noQXN+O~N?SF&e-WYm&6|v{CUK8#(cfPfpEOese zV%4nNkb%~!!A3$=<;7Q5uh@FUkKDK5#s^e(7TmAYzLtDzf3yRi^BPKIrj(s3;2nI> zsPDNy)&(0;n@4oi$TFCWj4T7v!)_1%T%N2NIyyS`6ectJuYoMrcmmV{kug_kDw0lZ zvYhPXGf{hm2$Uq}y?C)a@OqFuUgquJ_8uE3xb4eDst8vUy%acvrb~7^{#X>S?H|aW zb!$tbK39JpD+xisbh{RRyPtq&C552F}pYCb@xzET_D<0hVPLEc4nyBY&UG~^<{%vTnh zn2gLHRRn(y!zd!~67c1KMoD0Ev1rAX2^vn63C7)5+i_i?;<8_s?n8#M$2RUB>x(z; zZzX0P7xy_>esX+zOPqElSU$f~FTCY(EQ{kpShM^ZL@1twXK@)oht_L$`K0LXA{=9O zpT67BkIGdfi=3izMf6xf((JL zRQ3PW0AK1ygCV*G-_>U7K)AvZmdQ99(O!rhk99#BBI}9Z+}zlRwFA3h+#yv~t`=Y{ z=$1-)dalwNGKD(g%s2*1-wtPQjz1s7{%m^_DN`EdPiPNwP34OBI^VJz&qvO(f(F6T^ zpNLpS>iiBLw#NP7CijDj#8^oOF#CnS%hrk_d&(S&`vyrJ~)G(vn_C6z4{^pSLh*EvpQT#Ip(%pyJVm>4jazvS&dWt0f z4{h%q&-K5D50`ARvqH!YA&D|VG9r7AkgQaw&>(y7nUI-CN=6ZtnNfl zRI2&(g!;g3UZff=M}1Ov;fHgbcusdQ6l=P<%J1x)^1?pg^7`4TW17j=HhwC)@OSjn z20uv%JYvlrtb5on`TF|Ki_4d~?&wui_8e^lDlqryrP`Ze@kcIaxh`G0;jl+rL3#ML zEoBhdF23t*PM0uYhjKP-uU|2DijA5y?&J-f4gOdI^AaoY-m0QB2f|`XZR_vLDKL(t zbbCy3qD0hjJ$wuA^byLwSBi4({gl+RP-ddVf!WD}wV3o;h#uwYTk^iRD6vg{zK?0a zn7o|3KFMbHV^j`>vlIjFwC0RdA)j0!kG7&O4n1h9Tl9K@`#_WpdZlu_#2)Pqf$6tE znjx7i$)!-z{?60LjZ&%9V1RI#V%8qPu&ATfuly&5=Tnm>8Qd2#nOnKBE^ZIYnLzx-#zKVPf@>=aPx#?nj|7Q25) z@QBs|lV2xtvp=cBA<(<%s-Lu{$Oz^7uO{cQ>#)j$r{pTHn-}OKhZ>KdU3G?tfvNl(Z9N+#0=8^ zu-+mD+~cC7nZR~@#rd}ov18i3@=lyMqT?ML#sBOBO8ztRi~GLakDx;_s6~>S^9kTz z;`H+LjEsw$lJ~_w-+f3obg6f*;AlM4f>snQ<41O6vq?b*Sq>W+P8nxuzfUqw60V*v z`HfiqLk43J7QTrdkH~q72Xs1^XSxdqFS#)KeDC~}c(b}wI2R1kg;y1db%I+AKlw(- z)YDonWG>J>xjzJpaL=E`V}bCu`4DJZCY| z-R{q7Lipu$6xp(k@abB^EhYTID>YW-Hi`J38@p)#fZUgwx2n^~owOeIkz5E*v$AQ3 z7VpsN+jTkC68`Jle-D7$_T?7*c|%-fA5Ku@Uj9pIEZxy?M}l5bDYtQa$2xu;{?tU- z*zqfUd?y2NPy_}hY$~tg?`GO2^xxk?{0Z0ExXMCTvMw)B5f7V(V(EOjqQAg(ZaU(W z62GI!G&Ry5KYw?#b6JJR_`u6GcZOys20GE<^)J8e-u*rM#79*}Gi?{$Sl7wLjo88H z%*eQlU!r*>_l}j5ETwr$$Eb)*L&f3d?Qd6XXc&$DvGQMu+eD^=HykA!Gs@0*oUwHC z=PJ*$BPzlv9g_k!#|(s&hRU-3=S_z%v5k>MLe24z7^&KvSJ8J*Cc78o+x~Crda@N| zB$LBE{=Hl3oJ|iov(1Z*iG}|6VGQLE`g+XEUcS_C;1W;2a(FW^PvFk}5d1702Q|ZE zc2Bi&uHps4S}v)-4kkOIcAWA7=gw90hBj~o7cf_wvufOaa^vdxmTx45t(7OW>D#l; zYR|`Li8rCgPaFNC@S>3N-qLAD&BH<=c5_*QO8?9u?{b4qvyy-y$S&_`54h~o()5i4 zUoS7ObLaZoW6_iYVW8snL9YX9@4cus_$0x1qso8PC#92xl@&yg3NVN;4majGg)n*r z{!!peJB_1@n;WS!ezFQJ!JKtVVV4S60Zph#9}Me9J2HB}FNgVRe0@T}k5ct?XrU_m5Jacg{@7`u%wu=H9k-D(-nWf9A%O zvX~I|bBz!0Wr{{v2me+zv(??LxWlaSZ-6EaSA*T5!-s(fzE%zhj#mB%-rVQj!U*M$ z=ro|06ooHW_hTrh2=k74$7Bq^#R;%~VG8MJsGTs6zCgq*=12N zibi$#{;7JPzTio6fj7ez>9;N^5(gI{75^2Jl1FcgkLKL^umb6EKW3KETXpCHdQc&| z*K~Q}lSL{zW^_H9m!~}rW|vvLYbB-GJ6|pHqhMQ-*~t--up4ZD`r6N`svpl34}YfG z^_to2(WhJKN?$+KHU4>ERdz!nx&O*Q)`mv@qX>|Dmy_H&edVpU-#wSN-*Vk{$F%XUlH%RZ0L2Q=aT~my zIl{usyvMaCk6$CYJ>I00!Q7XA6VeI_{y1$-} z7uSACFM`FKY41K-1icRWI#^)ll_)AH+1iX>RDBLu&sno%x!qps;P&pT(i0HhK0-1_ zmM$7t`1z9{fa0aq1lWO|(Ur&-qUW6V6q*No7XVi#@37hlw_0=3R zhX*2H2cT(fd)3@Ps3q#A;KZRc$|^r5jz7gTK{}tx2hZ%{ux6ENRA{rB*w|cqYh(5H z_7A_?dF!u+_VW6CFc-=-O@FlX=ds(2t)Rf+YRO-J9^RU_n)&P=zMvewZ7A(oQ`gtk z=62K6@2ok|?C-cp6%B_@4c=*^-Riww6_cXvx4$iS-|W>fOD?ltcix!2%q}k2+qS3q zZwe%0JD)0`<0VfzJFbHV`kYXf@Y>XGy=V%uS$)w252Ok0s%sVk0g#Bv2P)l$R zIMEyENuf~OGDntD1BDg@WYM)ZK25CbAQTzU5X#I0shT4*!=-S@fI*rU%sO0ex4xt^ z9Rf!<^nL({v&%{NF))D6bzj@-aQ8do??ZLqdrspAp_d2imnJ(So!y8jnU0@c0pg4- z-cW&qhnktkx9-ntw5t)bXZPyACR{ic;z+J(s$VBhNhz`YO6tp&p0O*dSM4a7Hj<+4 z?EUSY27)Kg#qH7^T(FIeVhhR9r}BI?)8Qn?2OfK7i@&{atTe07o+0YQftFofNB*Ud z23CvmK``YJl&9+{wNJwv3d}_iGr?e0dhgHYD0%F0HZ1P+4u*z@V^SsvuhwM&KSHRzX2w z9qlB8HkICB{448vABh(wZKFONbY}9!aKs_0@bK;?Cp>5Y9EI3i{H8gtu=Iy?jr>8z#*31mBE1f#& zDW#}?-buLY5?kECf-*2LkYR55_PVukf-uq(*a8iPr8pRCSMVeo+`uT#25T0B z2DS541ACgSJ0%S(JQTbq8D6%bW%pbKOm!CgNyvSoQti zUePGHVB(c*=gF=wLi^8D)he(%+nIQV&hT|L@6$`V$6sh1d_nzw(glh_zc+siJEEH3 zP&oIQ!6^kMTq(GU<;rh`?7OD6h#zK&!tZ7spXnpki{(jG*oN9}%zw9m>#EE(rnZ6zm(+>R;<#yOZ0{0Ha)H;iLU$9{GN= zLu_XBJEn0McFPRc4S6WSj$u6bmpAnr3s@=cKBS5YKX8JDBi-xcXvzr zp-;jIHPQM$mJZk6?{+^Y6{&XpPt}s{!I9{oRqHH|<%{fmHeBQJtFnhf+oS6pyvAJX z>5OO2h^AF?dOlaaboMVBNUfYA-5~4#uosXq#0tUQ>#PGzBi=3$WbE4K8~{9-}>azgo(J?K#=2$m*wP zPw(2)C2IwvRkUS>*~&cw37Ok*w{{_pf$R>UX~7)fEs&qCk89`=Pre<{lemAZP0`D6 zfTI(HFn#h`I0^z_`kgR=IS)cd>$CXd43RzYlzx7GEaDbY@3Rk}X87`|?ZbyFAPNw1 z_o>!nN_n=C9nsB)?en=1CW{j0;alZ$*m?D4ZomS3m)}3CH{ec>RlLJ*%wtjt7XUKW z3tP1>`#(BEZ(Uw7{bquNso!XfY@YV*vwbgLJ4`4=xt8%&`E7yFx1PpPm^+b*67(96-G51jySq%_b-d zK{&Hr>E>p#!CJ55tA4F`_#)+vjg9EFt;AVNMD+FbEkVj=8?5J= zpW8{An~*?HE6Q(Z>NJVrmO%#&J~5)joA<}T1izgK5CAhD$4LlI?rpkym6xfUnqACk z@8yTPPJdC=EUfKqJ11!%`eprP{i;XctCJ7Tf2$YD^8FopExcfwp;_C7<&V}>gxDWi zKJksAKdX0hk3QXc#O9*J3)QV6Cl?b~j>lU+cyIhz>`Z9&@ajQJ&o>VrMrk)3{Af)5 zFGkuKL-w8pQ#3@kd6LEskc1-OKgwh6MpK*YH(;kWOlPL$!}}2IaE#Jl9xWYQB;d&Dhsu;XN;dI4RDj@;Ysoe8*~V#m^B zE}hXjbK*=AtBsjVeJdHAlF?|n(DnE#laGIL+;=^wFG*e65j6FY_u*`n*>}d1S=Shp z7Y)AL>s!&ZEzGF$OL~5j<$qZ^o|Kdn{PH}zM;DP()N&4tc*yM(zsVGlu45!PX{0)e zMh2JXHZA2;0I|bT;0=#9~ zj@@Gz`G*B`_g{bB+BeN*;(udFq z!ip=QXFuF0nB3wB#Sg;pi_^`aR50$u_@)783*%r=;UquVqqgYB$oQUigA_sWk2zwa z8Cg+LK`=pm#>Bn*z-aQFeVu>fQ0B8YJ%-K1jN3H!L4rjCK(}ecreG#cDf>XyOr&Pe zuD(J#G1FVv4nh$Zmr?Q7$jX(;6n%p*Z+OG6#{d5P8&Dse&aTXBv*2Zd3g(otOHEcr zUclUb^BEWE2SK7K!~4Vr#b(KaW6p@NpZv|KI54n9IEQf~N#}`-SCn{k{?&{EQQ@q0 z-NW~OJeK)#E<3_*pf&ZG?c&IZx%#2~b@zLnw4~nNCI8=b(~K^0-GRA87TPkH3Q(PA z^38IK^{B8amolr=)IWDl*5+`-@i#^aVCu6>Z`vn+8^^85MO8r6elMCBWKS7ARIu<= zA>Son7mI=xQJk`dW*ey)?C8)M2<=nloF-!(&kxPJ6U{0?Elv8v6as|FY~rH8-?i1Y zMewyeq4pp|yYpI`tX=kWnn2aJKD)s=Sese+Cn7SEGXJP~0$20hDZ*k}-$<1=|6Vkr z-oQn;+BE!zV}!_ARm$IN`zRQ>)@i@|xYR1a!hV{SjYK2!`TdKfmXCjhF)Ny=WdX2w z7Uz}H@KeOW!~Aouo%xdiIiF$2_1{HHWeZP3E7#rsg4#qw{MraMC5P^_iWqEq59bP7 zHSSZNW@bPiu}mrs%us+82N5SxEC3bl%q*XDTE@&h!#|HOngP&^YRT=A1d<#|3(C=j zCCw~!!FU=Bfjx|!gtQxd?6F4|Yhq$z26N^xck5;1jew}JJ8jnxs^+O$#SWo0*u50U z`Cfun5Taw6t&phD>g&IR|AgydZF(*)U5VYH!IV zy!7LKu#*(<7HwGON%3{9Mn=AFWs>bW)iKTKyG*q=?#7<&i|Qn{x{l8Ng>N|j#J_GE zGcB+GE3m;0?3f`*RYiw5@vdk{cHRiUfCR0Bp_-tK4AVke0AowPuD!iIp3iREhb)YY zW22+rq0oinb_Wc!v!IgR)FNZ6apV~6WhGF}3cb;mKyRU}6$5%~`aGf>3gc~^II3KU zfe6gshHJ|7a!Zi~0bqYeFVVrS^B6eHO4JRXl8)~f1Uufdz_>W%tu)FIg`{+)TB9Pu z1sJMEx|4&jhdRX^f2PX0fM=De0e$dyWRZAwX-5oBMI@bgk`@*=UmgK$kDA9(mJ_=T z?tJiq9W3-wawPJX;3oo4)HMEf>LsutxZ%i=APX@9Pjif;G|&MZ1>0? z0g)8J>#0hm4i?=Hj~;MMXu(ZRkr` zwrvwM`C^Ql1VYcw+1c3<6d0)HF{IEc`Jsdsf6IG{ajJP#S*gLh#l>k4YajhA$$@$m zU2Dv{DNRaIE`#f`sJGC%#qCQ0o-gnB-5%i8%s|;sU?-RZiRNFJf&**FE{JU;C57kT z29=}N3UBflsl6B5nYHg5kPEufR4s~!v0`%4Fwr_Z3L9%ltLl) zZzh^QMMG?V*h#-+^7zt|Qwj~QtLARS~JJrE!V?2|n;3R%2{8P30ZjuOw|9W`hVi)mq#qyVx zQ6_A|YKLKdBdQldNxg=(O4j;sSVI19gmYl5c({PQEYiru=K%q?lAMsDOyN8I*oTc` zY%w=}yzsw8H1WyWHx6!BP@rm7_=hs$A8P%7UO~eMM4grZz5n|i{(rGR5y%J(3?u@W{F_-c;D5jtFijtfVTpEv{6ACT3ps%z3J~y)6AFeDK6P?Z z@Cq2VZ!c>@K?efbhqrIvzIkIAa}L&!OG^XFFlK~n`$N!HC|jJs0-u0n?G;@z4CS%z zgFjIA+K0OHGwC;P-pt5gdw$f@(-T9(6R`fV#u$E);}QVIrVt91-;>P0y^xSV4y^24 z13U?v%AHJYA1gX3jJ`m)1t*OP@g1FlX~^)?#>yC_!yW*XW(^1TB*bboDLaa+LHb&Z z$|uA4_8YPtAi7f^cn;SkF2MG3Vdsn25Vz&OCriEA=4e^%+KcTx4pQv6vIbWpZ5sUU zDRQ4qRE_`;|Cb>3w<=lU-@ktarF_)QpqQi;$Wu6eeHkAwIMoHs8Lo46Lgj}ZAbt_K z^5U^ZkmD=%1Ag6wa3~5F?hhl6E@4H+hJksHw3Yr1)MzkEKNJqj9?$*q;P@lCZ`iqG1oO$v~r zGRQJFOIgai`tU(TR@P+oHJr!T35N_M2<9X_$df&JK?Z4qsBdOFKXkwde9L&wQWZ;9^nRhq5fM(c4T9-A&N4xP|WEs;*F|7gxD; zx&b#J{R!|Ni@s47yOoNIEn9|t$W->8L#)61{{_jmX?_+$h=pJNr3x~ znmhWK0&rlT&RUM*DoWz9`v!C!jf8z%(_+qj0O2NBgEPrRCZb8_nDP4cYw$&$krGHa z?YX((ZPA`+U`1X6F8l-^{1Q4ZIMTxOvBFP2l{d6#_unZQaXz z#Fy2ip*9^kSPY*X3JMBMP0iOLUv3I`N=NqYH>DbID7SlLRbp%z2K?97E;%}dk71&iUf`6Ct#cbAcS~2pb}Ak4z@Njp`@lGJo>~;7~JV^ zqxSER>cAzZ%@eq{TPFbN1;|N@hH1Ig5-xG`*L|jDw=H@h8AO24{0Hc$X$5yUxO%D^ z*#kVFx>gDo^=rEG^Yck=&z*k&#tPkkmRYwCbubQkUV)RZm?!=QXn78fcO(dQc!~N^&7?Mfh@)I7g-)Gk1;K76Nqb3|!Ca_rq z)o3#Tx!2Y<3SvW-S;@p^zPizqxS!z(*Mj!@ezl2W5?qXUgn}H{44B}5@OL%7NQK{C z%HCvnWuN-_R7{4}no#pyfsNftXC2cR>QE5V-p9P&mG4uvu(%j%q9%mGl>WqhBIdol z??&}BVMjtPgD!ZLAuYFHONZlac$(pYNHqcqVSx;q+SjjNwaDLwl@Uv)BpD>>gTMqm znkhr5TUMwVJc>&)LYTpGiaij#W`E2=ilq&`lL~}Vp@y?kj=(UYeN7Kx0vq-(_bv?2 zqpAf^BvgGKrXk;XWiCTM3bAK+SXh(f`L82y+-u^u@1t8L6{1-opRX`skkz6JGKHrb zu+THFEBFAco_Vyy>X>IDyJhKP))|8ANwCnF&AW3B4844L7m$6=KHLOV$E&-wI5I5^ zS-2YTh1IyFT&tXOXX(g)+_;gUn=NnX{PcS0cnW=G+#cZiFeAQ)`iWs0IA^z9n&lKO z2ZO~gkxwTh*3@38tE*#;5*~X1OC*g3=0O1U zxj&vjp$q6^0hiABK26QH0eprN``0g^!ENhoY#N)Ao6pM~eV2FzNR*9e^a-HQa&Ek7 zOjcVM8&?RkX*jdMZL>3JXG8$A%YK9-+E!d~8G9X7xbgwH~<{D)Q+ZDH$6W;GN z?60Lq9M@^*QSXHCv&_%_FBI4vh&@~=7iBtb;rY3S^&b`>&Kh|O@WBr>L#=T=+c*$mxH7x_e%V%)3#3GOC(4;5|LqRo%6a&VM(&r062 zDz9)Oy5Y6RY+bOJ!wows?B#NLc}b@_^uTJFx72B>`?1^EKR%BHj0p@H%+5s z0yf;V99&y}iJ~h1n*>c4?nlD`39DRqV}ZNKqNjrns73wJ{L}3{`j~qX?w0}Kghj}+ z(sliEu)OT|DL8wnbNpGyt&iKBve(J{q|sOfQ7U(CiES1r#mLx7S1uz z9d?Ju6!BC#j(mc0MQV4aHla+yHLT4?lv5XL`nEVT^m1s(+>qt9NL{KOYGBO6smXpI zyq`>QaM&%tJWcxjTBDkPlQClKWRINuZE(SW4a2D8BMN1N5R?PLvAPIFxTjIA$Yd$2 zT|Cxi+q8B_@a}|{DP`jX`}p=Rj=KY8?c=dzERt40w8C=@v3vxCW%8|L$#5n>HOkAA zbWJLFsP7vS7gy{~^?RME9X!;BW`baHw2hciofi3ov`zTm>uGI~DB%_s7oUYJZ%Fw) zlr1n?6}boJi6$x-Pi!{N>8WkQb_*vz@YUqGPoKa`#JFzjLM3VXn_D9^rY|V2mN$Q% znjSq7dnrcj)Y*W;hYs1dqYefNwjUV;eRt^N-s0>JD5&-9(Im)Jpg6`PNZVIP^)z2^ zK|ujLcuEq6LYTL5$Q-&ek|A$kivx(1Ky2Fa`aR5`W^>oM8>msy2SFHdPE!vb4S*9^ zKX6mjK+biY;K)c;QNn0-)7hJIks&Ahs-ImyIozScLhN)y77klA0$7V%tIEqSTi3amW@Tk@1yM?feB*=-j$`7pyT#&W8Y6V~vi+Rqea?PAC%2z7tg~eK zVP&J8D1(J_8v{+m3Dtw4hb#*n9Z!Dp?wD}UVGIk~H}uTz;>17|=$mU_`->y0H3O(p zCc`F9Ze8Q-35XgFITi<9-ay)?2}}=h+MngB{DGI)-IK5wEK9(iCoAuZGB+i@577q9 z%+PQ=DUV?hMFvX8C}bi@*Sqc5TCs}n*KD~Xf)^M`wrqiCse7M-%84eWxSufwYh+Qkhn&Ep>t_-xW=Ieg_@Ps}{@Mh#ix{3oE z?*qG<9GICbq%Hz;OF62exbxQm3Y{{%QY$*(b8{0DDLr+GJSX@+-E7~X1reCHmse8O zb!GqP;^^C(3`|@NK+rvSdjF+m545OAnE5}PXgyN(3dy6X$LTf0JzJN$d`_cSnH+8gn(^ zEhj0|$bL{6&Ay97=k@@@NkhkN>VC0% zV(yCI_=cP9uH4*e^yH>*fgemM(GG2SLYu5hFBoLoN)vLIC4Kj?_}xCB@3pBrjqc)H zd(n7;*no)sfMwd%(~luj2*BH~hg;5^{ryf0UB>sp4U|`tW>7$F{}iV8OAuE={5RvV zgi+hO`kNBMzqvRO&iH#wX2aM5-e83%vDAs}B}qW0DLlwg?+<54pv3Ka`R=N6<1u=4 z6;FR^SkZ12bKu%NPEwz1+gYlolF9H(=oAW-UvIS%e>GNNy7fHt{&-Z+mAVL*n`n!N zv-AA&zT3QxG+C>&1d)aGdxqexM_S~+_S}JVRc*bG`NJ-sFRwI?u3p?&yCCnFN&ca9 ze5Msus{5s}ptd9GC0DqFU0k zP{-Wk@poN*F*^Chqhp%ihC6g~bBBGPa7;C2h{By+=^=&|EFrxN!td@4bUMjebJHnH zJ<0hX9Wp_))O-u%ZG6{!MU0C#)opwqQ09+&Wrq+%f!x?-?pn)Mx^$nmFtz77>n$2Ab7XMeA!4KKbEp=? zT12y;Q%QRasL#i)pxNIWMEKtb9G+N12UlrnJEalWm9&wh1qMdc_M(I!)6mh+ukRJ= zb>ZJV>0hywcVAsb=u4XsRYl1P)dA)L1SA+>#Tb;`=x(9>Mn!+ae+@A|Ki5hBeEy&h zN8?b)ud{OiHT~$(MQpkvJz-GtzQSByhR5#r9G6dfh!EKza{s*N01PST8|Gf$+j297 zgyvYank~>`fOmB7VG64F!s_Vv{lo~RnmhmMjZgc%#t#AeU{axp4>{v|Dt+QVS-=bp z8)1@i(a6GHH6h#IQJS4Qo10eo&+x8Du`;HizAksuyxTnjvKFMnVdP7>kul8iLM?qF zEnkH1nVyyBusi6WATJLGU-GY|=!(EGxQkW@?c=Fejo<9cXbVqWSp005MT+YF&;hL6 zdHFz$>QvaV&qH^2EWSZC(nbU3TQ6w!#3Yl0KWun+3kovqbU*|+(_0YMZ!NcIq*}EM zW=pz%9xPPF_MQLsUJ0lzVfdz4d;b|x_{VdV`lsfEz=8356|74uH6;4!c6wja)<&mb zU{>Joa|Mea$Es z;@y)v2>tz74eh5)D-)rtiae3fQr2ZoyJboV;dzGjy!IKH>Y#xH`*@R71a9Pz$+EhD#q!TpruSz08Eo8IkwUI0a*QkVd-lu&3BlYOIWmx@itX$2!HeT=VK?6b zx@q9n_%F&Xu$~0XG0*hnK!uR15K~*1p56SXNyMu;7{l{rDAlv8naIzMfRJwyE+2Oy z?uX@ItwKjy?eBM6i;o1Qi#-;VD!tD2`RB;Nsl2q3Z-uE`_NP#p-bZ2v)LRWNfg(}^ z9#2-Z#wfB@0G*9RC38I%=vSx$uX6!sj>c4>Gwos%&^lyy0TqyO>dxh`TfU_0~Zzq zxI(kzr3C~gfxK2IAhxEEe?n|S&L6mA?u|_f=)+g(@*LWMRnVMw8^fG$R1ynd%K%Uz>S%OE9;yRlP-hE? z$&;s2m7&=!9ByYb$6{+Gi{=*>`;*@xop&0@i)h=3QwwzVtw(SHJAwm8sYz+FflCF% zxw!@d`hW!{{DVC;zSmW>tdyFTo9*IpnX}x4;NkkJh>E1d&yN&~W{!VL^5Rn5Yt=hv zh$()gtScI%!xCv(uze;xk0-TuQN5r%M%z0GzuC;9RmIKJv)?wW_yyhM%O7Jref@oNllENPwjsV}PO>(X zQW?)ajyheu?hyJeT;S#Zcmegk5#vb?vd2zrVcNfiPAzv9b-FeW^;W;Dh1mRd2DqA) z=GE39UAK{1X;O-7g`zTE2Ld}9d+qy_*BASMXDefKj*!qtBE~Ao_lONX^w#Rp!Y+6I z{ub~gFbnnlv*9o6#dj#re3G{YC=I#-ZfUi-ZDi|3b=LuD0VLePGl|xT68dFgl)yH9 zt>(L?OgE66>a+QKRV(9YvZe4wxV+X;1x4EZ2A|E6V%Ho6p&8PeWB zM@_OuiIDOhRxZ_#up`s+dw>%xbndbV6Uz|5g{>Sxg-wxqIr~lH9YbCanpQ+zJ`iGgjs}sBp3~p8?|miwkB04mITsV5=?GFcjxsGg91p; zE@4vg$}&#|&J)%pN}~j(w;G%k#!0iM2GbTleTjb2-#0j0Ob@;QvD0Sy8KC_Fqb3!1 zXNv1I(zX1Sl;6WEj?2fS?nNc2=rvBtQ$_^!goJ{__jtDu48zh*zuthSgk^-2TrzYI zwhai0i|d{aI~kt1?N<3=vc@IwgVhvOFS)hQ^Tp?%yKyhs$((WeBIEb_eo}KUV@QWN zU#fB6j9m#FqPwm$Nx^UQb`chtSpg ze1Kbnih9!_?c^M8HFb4>D~LWXQY+0jsi{V@S3g8akRY^A$Ed`ZyYoWd)S9M&7M{l) z>}<1nLYuC0$0?%N4`Ar>4oMa@e$vi0kMR#NI?*0hX!-NB7QbO~!w zJ5Q|3@j3=y)It1%u?63`;WT(DP~LqnhK$Xuz4>e?_0ImWY^zE1Dqd_1gz)UNv?Rkg zRWr$yM-_jNTw0cai)=(r(;MbWa~L@Zcyv$8J{-IPTpQUJLq% zle8lBm7_s|Y|r}cDhmax`yKA_)c65T(GmlHBI9?ql{b~l{3j@uQVkiW8?+cK z5C{fYNm-^nlmWRg-MXi?(=uH3$~IE)6Nt%9{!vYdeP4vVhPIse(Z+Vn)i_Yy!{OMQ zWX*|VGp9(U&%w27J>KJS)w)b*JDOK3e3o zy9m;feGr49C{U~MhDd*qMm%);UiYFM)GqYVcaXSb^guwLXH=d6o?hd=UUf%!5`J8B zp$}ztdaxBS-3k?|2;J}g9Jy?BM;MN9VJJxLnEhW5eNpT3lE+naM96h_z7GE>W|N%Q z)&W)fZDx$ObT43JFuCX=hXKr%f%on0?E$uGacEHplhZHhqaTrE$#R3O!(TB+1$)b1 z*-)I!6W^-HMk!o3L_?wy!zGlwX<-pg3v9bL@#LZu(aV;f2V0@crcS2IU`2=h6>e6i ztmkDB*%5KeKG{)eLm{cyO8N|?+7l}W!XoI@xK`wIsqKU=o+#}t*4zF@b2lSz-anaN zLqJhUBlUy*?0UVF&N~GASStXZ0j}^vd%2>u8b;cgt@#4o!bT7hmQt7DZ zx{lSBSi45HImVsy+PCYe#mSMU5o2aa1$mtrwNJE2e^FN3qWF#)l*!ic<}#y(&pTt? z`^@agOQ5|~UfEjB7PKb~s9XnLJYmSQpP z{b17v?Ms451r$`XWP`Z6xNDU46cnUaOpa)*edDr_3YR-;|0RLg=e+tWR5qHYWZ$Vu zPlGCFz06JlQx-8=u@IZ9M&omHD0+GolZ`g%!ZoaGbVRUh0gimn|OL)a256 z)I#cjC&O_6QOsg42Hnt$AT|62RVW-WLug4Lp(eA#lDK>2sI-tiZjQ}qwbtL8W#W0gOUk`ZM0Q*jrz zQkFa%)*LZgOQnw|XKJ*|AN~MVF?uY&%Fu_DA^y_iR2VC>pMDT9{WDyQ>cl4RF6WAJ zgZ9jEj$ZQgX7RVWLAjH)&i@*%9Bu8OZEmdChwKo@V8F=AI6f*s%l}HO@?+GDtLMbR z@5IqY-S^5tK^r`oGM~gu`i1p_uCkwvEZh@np6VF*DB3!&N38kyw)q9bg{>aJ0e7Za zFDxzVlss}K_V*&^5!2K}S66m4pPtA=7$XAOc=dAq_DvKeiJDg+@4(~%%cnPK=P%KF zpF4M`&uW#DB53gOIU}r528rVLVxPW4p^)y?3PjiJ&&VnAggsjCj{{mM?{@^)wOtRg_Z{GuK*7e2kbGeMDPR4Y`;G64@@E?)?;+G~4$i;y2r^^~$2A5Q0JsJedO>~zQDM{mx4Do=c3QEl0(7k)y@cJaA$ zjl~wd7v4<1zwTNb>7u-iZbVQfcQH>jgz((}g*w`trgBlY>NQmT+~hhwkAfnD7A(Np zP$uL(=*_6+2EaK6jR)XY1wua2%|@#|Re?FS%TPE2*s!b}K1+ZD_!2N&p-$qvc_w1h zMYO#Rq_9nl^I(&(0(#hh@>hB!u{abPj<;qNF3|#HoyDhPZJ&q=(V4&>M&y$dwYcoY ziG(V)BK(<9(oxX1Aqw+uFzM4Kh0cU+dBrjSlCQ_Byyu|63Afa8V=4xx@|yeG%(osX zIA4-vT#jMN@P^IGg;o@ux8k}lL#WS6f7c7>At`nVN0;?{i;r!q4;MOJ7Qo=x&}hN; zcDTWIMYrqmQ8cZDd+?;0LjKhv&}nNEN@x*fLab?NXtv9H<_%tu(1yoJ_w&!9FW?Jf z(4KfjM9ty}C>7?Qkw{4y`1n{?I#KB`(a|Y1Neqr;4^H5ZKN`iw^-Z#$VeYA_)6A*D zt$v@wX*q(M?@3S8s$$9y8~-ZSkcIw)E>aNjm_)G;<$4n?gRru-;$PGR(3gafk^NMc z>&3NJ&QpwsLz8qw%_CIZ~W zCQv6(u@N8uRlQ<6cA2k_=yRkF0BcXC;E?mMAo$$r5@p;)C~;6L0MnT%%T}gsqq(qI zIw)Aj+am`u2Wo1z$Xo*j1Mg)TN*tJhA`D)adn+ zhpEkNZDe~He76?pYEX25Y218nPFI-ve(_E8IynO z%dZIul9_d9(={3PCKTr7v$&*SY&JquyX-r*8iE#|!YjB@Sl@O|c%18>XIy@}lE>uA^QC z7r~u}cI1H7v)6_x+?cK%I6uvzNS8fyJ`MfQm`@|k!HkCS-`@;LP_kqkp%9{y97{ou z4#ig3et&t|oW~()1{qiuAINr)L(K14b9EJZAf&L(bK#ZFH zplqj9ztMDFsLFVo-P?Z;3)I!xCvv2aBV^~H_-Ko{^P8E2}k86Nf$n-VcYCDUR+6% zz<>MXY0(QKsqAG3CtIXWR;|4-CpqqL_eGfHk4x;ge($%Vt5%G!a5}&DqCBVjFmrJz21`;t`@BR8|j*Pm?(5RIx$$3aq0+c0LS=H{N_VJPA{@K zc%a%bbT!#YuF~7nwz*d>{isgKOdnzRnXERW5lpT?o5vyo(vAq&%DPks5`lt(07zN$ zle(2Po+>B}BGzhRNEX?wx#*%E5Q{{%Dja%0=|7~i$$1%Voaezflh9%5@1WJ9 zOJ;G;*io05Ye3QUCY$X6L3^oP9p=2EU5Qx`dNhGO(qCyv)Ac5i?&tGh)X5IhA$M{C35wNz!Mg&USoqkS*>JS6DKU zjoK9Cjb)j@#9hmcW#9GM3_Ub?y`?v0GFFY#^)@6z;zB}TGwerd*SnnE4|zut$D2Vt zI;le#MBuLr>x8r06OU~z9QmqcFZoOuho^_z2~%U#2(PMSje~qt`#9sAWQ&&iB%-c- z;v5cz43kV&(G;{`~MuHcXk?G&KrlM?p zL+_?bITF^&JX)tEkTU;JVUk0Qd4T+Vl}V`)s*2#FVRuva{6w;`Rxx1HPY;OR_qhVL zd}J%~^96mxrnq^>jV*rYT5a_9hcj#3Ne^+#O+hRedU9+ z@9wXYPE7jzX&UCoRYX319$RGkM5xG)$iw5TlPCZalhR-I)0RZ zZWf81tlw`8RRzqr{XbHG5*UMYe0&O`Iy!B!ONE-H0(B$@9{V?Vj;ZmV?Eshibz55- zHnV_HDf;U)&zyYL1#=^Tdxs(YXVpIzQBc#mC%IXvVzE}?X^mye@okji<`iRJ1b8&9 z_$qkI8?v_^oi=&Kc+V^6exgwW4+u8;i5WmSj(_*n$8j}2bPZFy0x$vQee{x%^QSV? z({y84L=5aflLik9?Mv_@oyV-LwmoJge^m|IO5kviSBiJOkx1!8M&G$}=al$^UoUo} za}=$#>POuNCCYWDO5n7kH0B&wSIp&u66b2KE4WW%z=ew%t+4(H%Ub26$sXsl?YKA0 zcum%gKf>4kE|PxB6~SsNmP%r;A1o3D-puDRB=4^~_a!--#Df0zIlSrS=H|)7N(h>@ zgqL)qj&0t3Z*0y!c9qR-<5S^-GHLnK>1P>P3jN$#XuG2e4X?gd2r1&yzSI6wSH4sr znV0cF+Ka|ZVx5BAe@8#BNI{R+jM;UAbPz-tOk?=T1_4F^n#<%TDZ_vp_0CXxW4tOK z#+SA`O3RzE3rJhT@V40uQQf(@`lyxVpWu*e zyz&TwbjNSW@BS6tq@o!1Hj0Azwg?)$z~U+CXtgIrHA=tE5_76xI2>)tK-NF~sECf5 zdOx5h5{wW9U|fL4`YqIT$HdUo@2gw`SRt0!on%d$eVaMu3!Fu@cT;?8{D3WspasVM|nXx z`8@M1fP{^W4JiYJDvfJ6nd2^CBjTZZrlTb4Ol1;ao&K4jvc8z2cgK@7vI5t^{I-Q* zD@7$SZCFk75kOQ}`0)b-u|IST`<-?ZKpN7Zz@oT{=6FUo!5Pztx<{FD<&%M&H4DYw zSyvY6Ke}|1Hukl*n{QF>l=!b|L?A9@^5MakU9btk&2wfh%sGjZyP}7U&Phk^wZ}X@ z-j5LuVM}w+-kvwU593X2*`AodA;MQ!op0YkbN*2Nx+*QXItmT@dM3AawMT;0du3!6 zKyJp9{=m3>dp!ycltW|l`rh*2_Xq~OX*d)_c4^Ahq``J$&f~)XhtqTyV1JAOSp71 zxgPC8naZ|}kum6a*WuDxd7)3e`j@eQ9bV*w?@Wfcr4d34r{B|gxyDrrdhg^H>zPbX zJnRY@y=6GMoa9b;Z(vEwPg!TP6J}$&#f>Fb9@;zXdL_^PKXlU3@%V9J-K(1n2T;xA z<>j3agTjHyeW(bh1rym4JYXbm%L;o{Ah*~ko?k}z`eJpUz*%44h}?%>6NeFrrZ#&q zSn%a{6%#H9mX$?Nya7lIDp&!%raf*uMHmYg*BY!}#$56Gqhe!6DjY&`b9d01t7RUV zF?^wM9wxm`ZWv9e<=h$&;)u;MsF!al1=Hf#lP|=gh=9ei8QwDlZbM1wH^?g5zx`<3 zE-R4ip;2;6B`8%lLS5>R^hC3M-B@5EOYh_rJF)sF-c)1HdSPj0?dS&;hloFV>i^z6 z?dKv?&3+dza?*x_9;|i*N>78gR*0sMoHGYus!NP20a7zqKyeiMGFT(|74;>5FovnP zcSx23FrWl`kd{;SGtMl7seF7$h&qDpq~joZ!WAqm)9CU7&;YxCYvL*8 z1NVWhckgVkWaE3b0J`N9hU}#Si=CjYX?t?|NH`=xJ(VYptT%a00;h*mkt7o(i!zrP zXm=AiVlfQKT~stQd$PLyMBTm>r0T3K{7=x@P)tfrPVnZbOTlf#gA2BOc;QF!ebX0W zg3|1?-gkCYk1x*4b$awX+E z8*LTR(B69|MKoko+9{%~Z8bDV(<&+|N%DKXopbK*@2~qj?sKHi=ly!WuIqZPD>uRD z4W@Do?>2$%78?L|z)K)@9FAdY5F7Z!uM4;EhZV4fgoKRe^WJ=ZL&*x4FN@B)(f_QG z^@Yl_%9D8LT3Ll4#=6{$qmyvn5iAoJ_y?z+^299u4$(!^=dMD?ivqaxf(Jgbwsy!- zV2jyRchbdW4j7BeL-k=oV+TN_dk?ewc>txv2Fq;1!jDyF!e!QXp>6d>3W#?VHnjEx z94PT7WMr^kpii68oFk{Hfy^CqT6Z2iJFE-LLDdolH#epEr${_Tnfd>JV;pf+?xt_b zBESQE5G{6uJ}UHL$QBH6#_+tpeft(c%48>&bV!Qu@<9Ca2f)VBbP-lgPO2Rl9gNLz z6l9-(oM&=o=0t&3GSmPd9*fX)EIzw7fK3#2IXAB@KE^aFMGO;JyB@)AfbT7d&Y}a| z#i>)LG>kf)8RIfbx5zUz!oMMCP+$kB%lO(K;nyc$P(;#M=M5#LX52SXQBjQwn{DLW zATMV>vi|?Yffc5qI|sbju(4J&;H2SKjKFC?O*)s^Iqrj)yMPCcGJTxo7Up)jkcNf^ zEpV;5fH_gm9{Mgjp@@_za3#q!zSxr58J33E3=43Gy+1JXjE_JVN^_L3Wb-;c@AdWZHdJ6k64mS6-L~b*MV%!MwUgV>FfluLTmOdz(EAPG zbf6m>=HhxnyFoAY6_?=a2-(Rv)Y?SZeZYurede2PAeV^8SJ@?*vj{IEXU6lwl~tm` z(*0+_|j+GRS!vOBlJ@aa!}kCcngl_AEpzo z8!y45zJ#_7w4JJpKbmOZDH)HHd53@Sv{;sT;zozV>!D%*OiRGJ%j{~Uh3aQtmyTX} z4&gx{Y5?FDETS43vy2YBI0iK>LH)u7#~Ce^TqHX}Ku~|YSob02GE7SP!1|*(GG7oL zczNoR=dSGZA8eI~4m`uBQ#$?`6>0Ia{<|wjUP%`QVs|)Es`uhSb!YC)4+t~!EL(e5 z!(}-)yyBPa6_t<>v7;N=R9tKn2OLR|`GG4I6CGxRIs82#&0O^{`v zqNj2(nvC$=d8XjDSd~kbtT>cD{W;R1t1Fz|L+4EwfZwOtVsNhspP%W%D)2w#k_HZb zT@R>p-0FoEHiB~w3%Iaw>yx1un>L@h%Dnr=!l_E}oG0`eH++6aUJxzoIkWExbJOV7 ziL<}OwK|H(BuH`xjnJf>_5b!y$4Xb4*hqrFKb*|R*kyblO+!6i75!I?Se3mt4qfLO zmnsJ*XLPC5LU&_JOXx~WLBM#~O^d}#v>Wao=g%K?*h2PqQc6(_y61xtn6(szfQ&}tuW9g!x33!nyL)=a(6jI*6;&j3zyt9nBC=nVx4yP%g+s~s9N)r}jA59%mvxQaHUUs08vD3?EO;r2JP z$E^(P-|0Mc-I v0Kw89sil1;obC44Inf240`a8HSZ1839;sJd#P3vy5#af9X)NT zb821>ro9e_9-yM6RBn9z`VwNNC~IqLrJed6psWDGP-2+BWhr&SN>nCrV?8M;sj*Rc z{Tf1v3Ba!=X&i|measGh7q5t({0cD~XfHx;n9}J@DVFOcoU)H^q60Xvo>VH#mnn=U4sMVDFUc-3AUqNpMkZosp2e+Iwc$#ue@*dV2b-)lk7Ib`!KGk-JA~ zJ@WtWOgPQ#XEw1Z@A|pPIc*@Ruf?84JFSh;)lf=~qs6u@H*zp#Y_Qp~)6)bFzw0P% zn7NQe$eVtqCvy>z#e^Xhza@I>sw1?I5mH_a?Qv@7@mtz(gOuX39)nE}8Z)UTjBdb> zZ{5N>SqG~^W#xA?4Fm?)*3#Co$$l6H2X$YBc2}Vl1r-z0Rs{S!#)H@&LWXo^V)Zr& zwl@Qx&EYZ=>en*|COZG>7R|wNP*dX+m^Wz4vH$3w-)lXndv;xJL6c~FUanK^EW(T4F>B|6lHa1~OTh-F|nUtefp_;gqE zM9R%1z~pMGsy8W_IPkJVpjQW&6zG-SE+K=PjVOR&2P6&dJid>$N{aZa@tLM>BLt6( zt>+}@o$)$YX&14E_@=ehjF{u!&n8e3)X=iHztX!}PEKw-@ed}p*bgLDOHXQ!73P(X zPfxcte4+Ar>y3}^9p@==oMl1E-aY6(vmu+DSQ}4&a6n9mthIWv=is}^SvMWe!^JRX zG#((5RWOyKS;94Fjs;KX85b1Jp^TVSKwJjG3a-l{;}iIGFunoY4gm9|vny50+BY9P zm`XLV7_$Asai)8X%uem1JULg;;<}w!cDOrW zn(hPdg?pj z^2ZPty)Y>YLszd6U=RmS53W26Q@;oEH5oXRStM-dB*^@JgEc@Vb?^dGLm1~c_|TOo z!td@N^-=P2Sn)b~uH<4of7RdSN*+#bA=nojMlH&}$m0Pj%$Q(8ot ztSUkCwORRlRwVohL%%#Faag{FM_ye+;}GNpl9EZ^Z#{hYaBX!};A{XZQMsxaO>Fjd z#WS`!NRg|z-xW>$d}qAWR*Y{@q$g&xcDER9)841qO&KItb)t9no+RJwT?E+pNFD#~ z$0=Qo5S*Y0UdeO9$n0I@>*Lepqr%YSaJ0URK>G~m1obKI_TvFjK7wqh>4eSJG|u z7wr6Q)NeTV3~#JT`a(v(Ltxw2HXxB$iGv)}KsZRD^(8YM7z_+cYS#2=;OQ_(F^H*U zGRFV{}r ztxw|R@{XI+&aMC9v0{yA!?Z{y?GzG}Ff*w#w6qvmj=Fj*e?9@<-|Dt7Vhal-9a>kB zhvdygnFWxFkNZ9@R7GGX%G$0qV!uaxyuSo2z0~xkX~}J*_@qy*n;(v)H+z109oo7e z#%SVQ*CI&RpAMXXkILN!wFqrKmnV7f0Olvu(rxe5CuI2o(wi$(^aL*`*B!J)tLY)X zN*TEf|KsG3$aM#qY!4wy#jxBAq#_a7vY6~U{E7D&JNkGxxf1KTE-$i{HilHHaYLdf zW!p+QP4P;5l*~VZ!_`D^V>Rd;V(i?B+9$@^wRP;p=;(G?zA+le-XkqbP~Aa(SJo87>EhWN{_hbTh?1^#4b^1ofv`QBOw%yd zaMC2F`fRS{_1^P)TL`Yhw9Q-vn9vq9U)Zdb>hf$L02J$z4ApU&k`D)QwVqqTGwmXi z;}J{weLCy-(%vHh+e0hTSEw5&b4_>{x3ZfMg{|(0A32b+*l#TVloIhE3B~18%g3m| zv+RJh30&0F+n96tn1zq}ohH>L?JXiOlgq0~f@)e1KkR2R(KadO>nT^{Z4%sfdc*t0 zLelNvNcx=~&&J1Y>RyQ}e_y=8p!T-@hTNmbU3Lq<@Ye6#vE$45_@)+;MAGYIq!;Ig z_Bw{R1j)q2#N0zG#6nfYZqbS1Y0>$JcE*brFHpu=#LbR3(2$v;*yEjuTtK~9G}rbL zhTKk}ShqfO+y$jGD87}cbGvsGjSqw)w;IvY*x}xoZZ%(3ok9ZLixjYZ+)6;$=qAFq zsvPIVOx5b(cr)HWeMEEU5KGoZKAV@9mrQo=YI?D(u6tmVuxW*WjN|W`6w&}catexB zJD%6f?=)_3PIC^w&=0v8|EB-B)cC{%6E}APSC{AET{d6;J(x7`Y5y9~AI{;z2p%%; z{H1}N0x0q7RBFOgZc4GLOxQhP$gla3=^W0O65%Z#K`r|E!pUIcKJvuk-SwB%dih4i zSxPeV!i9}Iysh2GK3xA$m~Wn@QQj)3H|Zd0jP&M^%En(zg`^E<(0U;#Jq7K!ci-Qe z*Bl*kXmH3p5+g$9iUs=Lsl7OX=x(z~^o55HA0}3W+_9>}^>Pbutmjts=NLsbq#D_^ zK|N2_IRHFGKZ%M<*muKD+oLcxyfB0yQ2oC7gJ;j5pF;&ldEbFlySbuU!QY_&j-s7< z-3ie_^@g@`!ripjQN%vP%1_1_z-#b(bp&Tr9F=UmTTSM9jlb-Z1KP$fKQ`D)_)S!m z#hgO7qP2?=oA7OGI8SXs=%~;HX4D*o@7+CnV{KOArw2w`-$R;z8#$wWP=7GrWxl*S zrlDo(ZP@(0&<>yfumFc#N3v)J??Xic+nAa8@Xlww&ZEe9P;_TT^lP{&f6t^>)c!#y z1D3NlNCf#yo;Fg(8k&&ir;F{0Fd8dlz8ZhT%ug->txY$t-PqU|4A;S_5B-1D^dFE> zCY{hV17Zm8HBI&Qej>mFE+7E71REJ1K5)m-4i_a3?G-C_qz+f2Ypjr94)B{;B{FIb zvP_~G^u;&n{rcrmVaV{Jj$(nM8a@|t^D?xwfBt@sQ5aoMsMJe_7#2jT{Jo2LR7igV z-CP~IcS6*479NJ1L)vLV<)8`W6n;EbYFhb$F5{1Kd<};G-uh?NYg(0>Hfa9oQBK zyL}ifB{LUS8uGMNxlmH`yAW?60u+&g&nw?v=evAwg}jFQvY*J= z&sx_I3w4?94f-1U6^&-AIt&J{b`5E4ymV>0@uPEC7BP$O`r5P253Nf1;e-&e=10G9 zhaaZ6e@I?Ko$|<|dNvZ#(9m29<91Kewp6W|xCX(7$O*kW)i-Z1dW7wN>eQ3^axtx; zPEU{GQoH@K=4iQHPa0d*F6` zb)I9I$RR;(zae#+dioSjEn3r3*KpP~AV%8%Aus&#ZHqAzVagIRS^fqS^|207*o)iL|C z2U-~*qkR ze4d;_?MVFLXS!ZuUS{0{QJnn#l0&QJvhvRKlaBzA{o&d|O~<%i9#a;ONx6+>^}Lxm zv=c`!PR;v>pMyTH1?_oNc*=MvYB*oNM9K2LGt$n3J+EK0w_8;?BtLFM(kiU&wJS{G zJZU}XiX&a?FDvW{8E|3^OS;hO%v-i3m#HozYnP@G*1FAt zOV^Z(PVaE?%g#(4zwmcL28QI+ERC|;7lg|U7o;FD3JOT+(-=;|D1x|O%7KXWoqiuS zb8EZ|ny7mut;)>9$g?!2;yZuJmq|liI8R6@M4O%Imhep;>V{XzyU>{W%|DK0W~Tg9 z_vm-S-1x&WK}`suI)5l~Jm~rQ?Afy~U+Wdft+=<)a|!JXt9UJtDF&hOOHytMn#H<& zP??^D|9j7#J*%_%A^Hb!LV$6^D9vLGj_+U9kcrjf-9G&gHK;z&+5ULp-Ng zn7BiMjf+dnHt^>^^Lyvc%Vu6YWn=IaU3LP!z+=g?+`V`vR(2Y+`^R^WCqgZc%*tfu zlaNy@mpdLMVzR>5Bn^&^h<-)0Zm_0YcQ_&a0vskL_kc)JU!1;hnKq-TtOftlb^zp} z!L|_VS1ZQ&Wmp0Y(i;6L3^c#eQ&Kiw`U+i*HQSAK%{Y!lf_kzm3y2b_`;4_@Y37bM zj{_5B&8FO{YP9d}QR}O;&BG4I(>@+BeV~>my(@WtJnx&l+N?>l>#RQ{{XD$FJNaJ$F{|yy6_~8oZo>^$W@?HYEB| z+3G{p&R4LHNZhvAGmfIn|LoN$j-I!;fvJPT^>y&QsdLD?^KxY=5~``CO+ zk(dNtG-~KbKHqJ-*-FG|+fLr{z~qk>Vg+UQhoII5W73t_hZOQwg&OalfZ{t}kv*yC&ZUJ`YZb@Kaq|*sY$zcg1s^^18E?aC zKt0yLT^q;d5@KQAHF7BKce~ba0arJVu}>~(*yV5sdajNG&M)kRj61BsxQ45z_I)~0 zWm;eq=Xco5CK8W!PD`GoTSemSHplC9Kl?bZ(>0F{j8BNgZ3)dzb>A9M|H-J$A@2E% zIO}3_$HX67ixVmu^&5l|?OknacUa3NB0nIkUKLb>3?w7i#Uu$3(xZHFdtAM?K0wr01S_`)gVe8Y`1!X}t1go^A1>`Sey)ED*!XL@r@4V|Q z_+T`&zN-s1ckZAACe*Yt)O<%|ILkla07gee1ucAAS&8T*;uZ%dD=-&oh2dguXM2tm z{Eqw8fGRBuxI9Armd^XBXio`L+}fWN$9KJ`X?OhI{A}L%ts7KUm$FDw>XpwN&S3jn zTc*=-z|&=q6A@Z+Zl35NApJdFY{FD}eu&XZFmb2So)osC0kb;>xqym95Bw{e&!u;l z#zPn;jj%`M!nhND!5Mue{u5|2G1Qmgr&jXrrYziU5x6D~X3O+NyQk4JMySJWJ~4s=#Lg+}SRlw>fCXQ<@)~X2^wdy_bkkTXF2Cn6H3tl!~5 zJg}lI!)h$uH@E{)I#)=o@`dXZK8N`(8mhl(K*;Hn-{y?`JczEg<`4-INII`bG>Wq`l6L?^s)(tyR{@P5 z7H(&@9Z-#;6TTM~_sbKS5b8f>CqIVOd-0G?v9|a19s8>fOFBu{A2)Zb9*prw(jA=P{mlm?%O!$$1q?8866uSCia?%?O=7q~KfHsJ@6 z)`@qOxY;P^xx6vO6UzQW&~>P8U=q_-L4K-bIr zNJYS~I{@^ISe_#11_X~Q&RdW2h--lmwhZk$NGt`CI?DL%ArmWWxGcl8C4~g_KmAcB zB^6aN360r@k!t6?5y9e~US1LHgpeHd6uUM61#Mm?rKt?~?Hd>v$Z-|lD-@AYS30po zA~&+ilxr_=c~4)wRqkq;4rtPm4r=>`YlJ4F1WyO7MbsZ478CVIPjTBv`!G)kQ_tn8 zcv;C)AUB8h`tgQ4z~RzlTRz^)w&4cc7$Z@C#D!Lo?1dYF8+8Vw3}_NAf-MGN$?i1q z75RK@PJxyloMoW%CyzL~rWwrXXUmjuS%uQa)?0QwJc$!E_dAAoK-1}=?3H+b0s+xg z9J)!0Z#ewN1K8HzK@S6*70Cm4%^&O$qYY#42Yj)$iYWtq@+kq@(g*)k2BLw&?PDfh+v#Op`ks<>m5 zAmXlhC($Cf&?o`0znjf6)JfmLMdGfoGK%7u8H-d_#x6f%bt&vzY-#79kVzIAmW+iP~X!>cCNenCMWvXUSbr z!I6pdPwB0biE;%;uiOXCZsscHhx!)V0K=xkVDT_C>Wi{`e6}Fl&0f#B+hB}dxFG3K z;mO%Qgp9mX7_pH6O+@bD(ENs{@mA{EyFVKgz$mOOju9Sv$3Eqeyiiay>!}r=ilpe>@pSYA|goJ zxm{G7r6_(5OvfscwDi41!{P;g!{Z1R@8tTEY!cT$8tX=P7dT=AOVw$qUAcF@_}M`<=BzliwX9ZtPO~J z7VQi63|xnDh6GQB`dIJ-RF9RVC6>>O+9QUl)cdim*>vioQ7xV6r}EJi)cg*aFm(XxVhP^Z?d&VyR{FKutwO@W_~L7oKr? zoWWKy#+12exksnu>NsBQE?{q95h~;3=l|!A6lk6|@n%DI&k>P0Q|io}mh5=imK6Bs z7_#J7WEpL?0R0Oe6GzvQ>XpreY25A#e0>5P4NfTdt~eqnE6x+ zq=N-1xC;LH6Rh6U3i9&h5MB#>l9b>6-I(^QC>E6-S>ceHk+JXMXI774Hd!6o4ml@7 z!{29BN-rmNFxiv}b=5wT+IWA#k#=oj1*!|a1Re6C*szp5Frgfr8h$Ft-*V_(WS-c=~Rfl+t zWOh%eRm;1cm083N5N){S{^tkWA|H#q)v4#eUq)5k8$wjTj-tbg$-QUxLBS*-y!U;n zz$@>F8zWKQQp^_*^y6cFyC$}S~mU`xs+U%FUn0MrYbI}&6@rY!U zV0A1FOO4%oR0U5nLdQpaGTGC~T5ys^miX$HOvKnON8?*5T^VK0xqUeQWoM_bVb(_Z z7B=2#R@;T8y{sktF@uI7JB3vk(#v>?1dSGI)bUfPQd5P^Jwi1PtNB_jGFxByKzuo( zTRw|GM*LY@fb`}yF_9N0z1+vuBf;;>Fi!UPSW68LNVCb@lXFTJfzjc7F{bwME^2OyKH+e=H}5JvyLZa>!71@jI%FsI4|NLTRH zC3bUU%^v59gov}!ii4Z`3EJTgXWGm~)&z&9LB-59L?m6Icw-Q^LT!9hDb_sC>xUdh z?2X)fCPv1jAgfu8^bcFq1WXxX|F-Y-W+tSw;X!^_L9>a=xHCm?h8Rd8&i4lvjlpgW zje_JMmm$e^g{zV_s#wi|=~Lq##is={e%NtO{plyTQuOfc9xbQTo|B9(R++S@PrrO&Ki6IW^>S3ud7T?elO-Xq1o$VC?fHSgydMm~O z@(BDs5?RBTKjE&M@nrE&CIq7WP`0}MK_02pHkcamUPIK}nI_zaE~k32xr}vv1SJ|B zE%IWcK1<0@Wqb4X^_-KI9tB%am38X)UP1euncjHE@efvqaX~!Ap3cV-CPa@!% z?U(=LU~a>A2N=!R0%=%91ac_yVzRgV!aGWQU8U62#RB9`h3IxWX%kUc{{ zFBm=4@4>~`fJP4u906tkAR0mbgo7~5V84>k+09_QUpi{f8bK@(ntx~2a1aj}ZBW%P zS2N*wpnL-bLGp3?j-&}BBolFdn9};tYtO9K`| z-HjwGG=P(-@ZWX>UyfM$?}Unl3eP$9+YP^=1fsBscFj0N(>~7@;pWM^^44R9;x(H>Ge;vF_K}8bX&%&9nE~iEI-g zcclF8iD!rd#+y5S0gYHcfD{Z@{H87>{3QkfMdlB_p1%}3Yug~n$48PK84(d9u-SAH zpFC4J%#iP~$5ce|w@ubr3+$(l*R^(ci^As1mkaUqS@b-xRgU3jQP6E8BaNaJKE5w1 zO`U!n*@5<24l3AfEg#?5@z8S{lX6a4!ZLvT)kh$AFvU<-Z=AIF?7*lxN$K{B-q%&a zs6Y!Z@?sF9llZByuv;Og**^Du02UPy^c9JJ+*{EwVm503hJe5+iPnq_VY}zhzAjx- z8veS9sHqX=xu*9=gSO-aJM`ILO**dIld4YaCcl4}1|C>DcbiwVRJh?fGtG9+?zMyM zwm5JlkZyRc;Z>S{kxQz*O^9{qf-Xsm;3IZy)uq~){4N*6ml?E9Y<##W1IDZrXHlbO z1M;Zz?(Pd^HZ7K|w)F|c_(6?9o3;2HOUuXt_tHfUKwbsHxg4a?iaGBmdwFVdk*Ed? zX7SvI%mkDe1Jh)xhdV{qHX)=c{u(h35i0b*QHD7FEowGvkfx0`1UA$-CB$6YK(N+L zni=Vo9oa01!N!#b`3RfSK9hBEwBRmJ!s>k|n#r#QQ}IT(omX&^C?GXFucrXYur_Gyp*d0@$ThGXSxUgU28L>Vj5Yh>uvx}6W6zuf* zy21bC{901oHM5Ra%Ki8yeY$Jw;>woUTM4_`w{*9+pTz`&Fd|Ec62QQH9xM@c?z_4E z0yB&FkivX@{yZtoyN4hfYH`nk@Mvbgc`fMq9HWWJ31qrqPHzmy4g*#{=a+fp#qrog z*XuD+Z{2EePi4^#L=vO@g2NE2F|QQpesM^``$M_*I027YmBVVMizNq1RU)iGB;um= zboYXs3@f-?5ls;AZ z5d9Kx2q5nu?Y4cAzx35Y{e(GrWOxXt_Gzr|RDo&{kb5$AC1n>R9exkkE2;;V23q#h zm49%D06UEyYWie73XR${q>$!Or&NTEk>RZ%QLrT;WZ%F_#J;u~p*da-z8VZl4*bJv z@pMo-)mS9xCULswj#fGHpX3dPR3N%Sh%aauA0Rd?geJa9HRt8X9i$>#4pu7nj8;4U z`1)v`ke+y^`?po$dOJQ^28-8f(UAwZ^bta4&Zx@FN-UUrx(;im={ zy>m0wtsmPHSd1aTMJg%PuT8<^M{hm;t+2`X4lYHY0MAl)GhCppNfAs1IidA(Q)C5} zcz-Oxq=`$9)QzT6Mj`kQYGWfNf}5=Pf<^krj22D@0ONtYI?aZVub^K?3;_{r;Rb*} z163-iX#Dk)wa>(EIeA8tg&i+AC8b7E@LA)r;WpqRjADu5W{!lFr-GE-M5cDPH>b|K zy7Ls=WGi$snnm;c3V9Xhn5beS`z|9m9Eh&<-B-8uOP3Y)1Uvk~h>n*%++iMt4boKK z7V@9rOYBa&XSRe++>7buE&DS>#umrW^Ae@7T0OKP`6O8ul_#|q)k)L)JNBKSHQE5L zT%jf;us_96;WrKfp$5rtg6n#P;$9(Ne+vo;&Nf$U;{=XxqrT;7 zme6yj_jC3->8#(Q&0n%5%OsNXyMh_sPHQD9Jj$kQZKRplO=rw{cmzvoUVX zYD`JVgGQIn3n??3{2*?VxFe*+*UCwK7muIORcce(RgzVv`TwxWUDCJOOTXRkltq7R2|f*`f5Vz(4`g#GMX&w z9zNtFhsBSsi2Ta#$)Gvs!v`wlDl9b`1ZwPluB}a)@n6r~R%zcEa-NAkxrBUqXj+zH zIFa#Nh}?$%{$s(_4B+ufSf4Y=f4<}57N^h&j3`=y8eK`MFU_Sn@K&#jP5a)yeViFn zQ-q;-;)o%!&;3sFZL|pWdxZ3Nnw`60yuL@s=H+!>xu4U7m%wt@`^eY%e?Cmx_87g$ z%n#!|#+~e4U>VHHHRIrFP!Od6WbOOXQqOe-S(k(~mTj27GnPZAi|6upuze!>KIhlZ z5Yp6pACX8zbk&YQtS6$-RL{Ky6O3vJRW&#t7jl38?M*5N2^R&hepQaBTp$jOLCQ`% z^g~|&Oak|xuf02h#)z6B1))B<#?-TDRe=6ZyoWr|6F6zU!4u|t6v6vKHjTh5XIxI% zXx>@bGT~lfa1`Ah?)IP!Wi(*ew#lPyfzDL5gj7&pTP>k*cR%lUgv1x`|EQKrZ%cEQ zEOozi<0cbH4neWVfQN870z=Z^3Q~k>Wn01?eya8dFe}Rme-SE&jN=vb5?nNcw3TJv zQ9*OrPxn@I2M((YoL4a$zEcE(czhl@0hbWRKCpTQ@eqiHzfhm>!dM~H_q6uBg94@8 z@03KvwT?onT;qXQAVWUkkZ?YRw+LNwQ?K!0F%-;FZVilUuOg<#9-&GB0thILmAg2&4q zHYs>K{Hgbtq{#Y!=KPw*KFW4%v?YI{R?~UFb6N}UIo5Bbf(20hBG7>y`Pt;)eF(~T8M&70v{L%-e#+ufS%gSJN_~FGO7unrvfN6}B z*jN=uy6NA8_u_THkBjIy7_iP^tas-`PgT<4{Bo#RMqGfs@|HnJlm9OL{d;Sp@t4@_ z;R}D`_F(cVRTgC*+C1$BHZ~n>Rs?()+DOdPi)w758F>Gd9F zav-Qa%j<%cpaqRZFOK&oQHjL(nML(tX{#->F%4xIo*921;CN)BIYmH3SZn3b@5UA`66bmb8DQ;!?m zBA#Ynk8PcuyLp{4MXGGXW`)=4Or<`{MTnd8d$mt{!NTtvJ(wX` zv&loC=HVtr6VIT7=gytW^$NegbLmtz5n;T8Qq23nFH@ni57fmA3m*$_uVn<$uSILh zA1j*QrEoODNxj)=_qXvTQXwsIDN2ds1H=|yua7TOB+BMgo)XO=`N#?kc2UBbB2#ghzevO#Ws!gV}~@S&?n<% zWpz?|B(q#XbYfNuFo4a0*xAF(aEF3pD`hdHnQp@s|hxV{| z8W;=%{+v6bF@?DXjDrz(bYOiP*Vj{?piBI{e{D)oainu}mb!{p!uZK$pX}E6A+5hqEh}G5EWF>S+*2&lvHPiY;4Nax#pw-fsPT3-PV3V4JID(mm~;a@@`1 zyNw2KYJR=}@xvt_Ytc91r}8z~&9)wTB4$?g5mVjTH~iuocO_g$81`;yIoeQ}Z5$Nl`slAa;#SrLS9vAt%7nH)@oKf7S z$a`XK>g^)Ca=#duFA zd=;vP+`-gW3_o9^8G&_@hAA5tu>Bw$MijIhJUQ*C;Rs87QD&p&KU1wv@s-%%GF!Qm zi0xl{@*VNE6-cskH5O6IzHZSFaOzIX;29qfr>?%^d|?6{a=sKK&OmNzmr?3LS?mlk z|08;zSlV{;etGi8{JmxI07LO@2M&9>iGBefDIk08^0$JLw}K-!b+-ePYr-%n5!>S& zY?o-5uH*=DKg%oDCs7g$gt5)@%>$I%F8Ve>c+yk6)ZTUVjE06ifNwlQ$dF_k6so) zD*BQe@w(Q$#CFEmY9TeOND@1E@bYv=vgZj*^|^Q4bz#Ja?r-e$_4amn95jGA4o*&F zF=Vjz8I-j~B_V&Gfq=VnOvDHGWfLH{fDcJzxcPRq zi0T)vlRG!HF_ds-8O+N5{PpX$ZuSZ^EbjFmaE0XdGhO;L6@(%SW{OJh9(-;xcF)^7 zFoaEHgTqK5dL%;hODJI!H%%&G|3*{_y3~A=0h}>PDgQrW5TI^J*bgzqwc^A?eKb7{ zFtnnDil!v5gcJhy5|bN2Lb)o1I!N33DB@0t)Xo|f8H;-hLaO+VA@2f8EV3hFo)AA=vyzuda^KP32$)rFDbB!QvtkR#d03#+W>Z<(jIoC1pe=EQjtJe;PX~ zEPVlo>~!etP|~u_6eVBJN+{-x*qK+Bf^$@V-wSj1SQ(|-Gj&y?ca8j#d4$TCJD|YD z!nH+g=Fmz)O2Jc)BJ^&AL)(M)O||pM61}9uXIX!zz@_x@?8LpLdm4$m9Jr1ZXfe(0 zgRs@;2>pfA4J}aGG=6H-_wn&*8>)84aBg1Z;DN>nhV_FF8cBJvrjA@lhU>u7O$!bc zyS>xiVCp0iUOuY+@?>YryL?({FqaO$d*8E<2pGaziR3qRId~J!pvEw!0T{dJ2%ZEea++#~Krs7g(okNeNWg(b;_WbZCc^%qro!#p#8N=?blI&SFAkBYld zayNGl_3y1sZ68cVoM64LnBkx^#iFYfAk!mHYuQS-R`^Fu2EI)aPK;7wF`**Mxr5(9 z$WYpKzlqD%w572(h$X&>sAnm&nv4it&YmB$_EJMHkFeVH9@YQ;Ew^k5=n?tGap zFG+Wk2j4o!5cT%+bu;+H7@3Dzuf8ZZ(n<4$HK=ye$y>57JFnIr9~T5p7ry+=)Mjrb z!Sx87$JIK1V|m5#SO{4L?@K>#{}W;1;Tjqmc=%L#Y9a-Q3{dlGy<<0d6o60Ih@elL zGuB>{mA;0Uvi793-wsnKN#JX$as)O0DS4mIK-_^f^$8Ry2~6K}1@9}$6Wpu7PrCr`L-s!`J{zvW_bo_&SeCM z+-EMDV$NoZl>zuetY{V9Q1iEAMc^LX{NriEkK}m;nv`kl>gt+oJ}H!IEAsf3ot#?( zPts#Z;zo5tebwkFe!Q@xrbr?-t53aMiX;&YKL%w>@*O$uCp$EDwC~t*Tj25uG8_$# z7itR|XIM)TS579pbU;cgSe<&q=8!P?=IK;T;=HWL-F@IJxXSd`b^pzb&oy~@CktzG z702!>n!L1}3->&M0i&$~R++j2?MB*f}JEZ|b|Q{8$V zD(nHV{)@f-!;{$eunB&=8x_Y=Dq(nL<_II#wWjW4a)S0rH`s%3`25Sb|D9)(le_YR zUXNNj*5{r7UseTvh>xSXhVbUZ@RXik4`Uc&nEekM8uC&7@4rY=M{6P>;z)B57ZIJs z^QoId#%2sVj&UolU0P}7oEh9PVSScieTuhvSavu~t z84g;rAY2iO-V4gv%PT8;PTWfaqB-?e5j&cJXA=_VB;FPv*I*HXV<)Gk?6%IKrnB#{ zXjBIc4xs9u)kQQTkfZV|V|)U{dll2PidKk0&K8VzDKU%vEM5jgA@6cwvVs1%S|K?KAbO$ijiUy zw{M7Ep40fg`serA-BtlkVn#1293L%wlg<#^#bsSSK9~2aMVtA5dvou`!U%Qw&+i{H zG>8F`!F5LHR@r$F_#-KXX@yC^4Im0X`UU8aMp7VGA*v<5wB$;(XS2}vEN64XKLyIN z0pbD_Rrwb$w0Pk0K-dcuPUI452H2OkW9ba@Hz1s25TEoR(7_u6P4$UCzux@;=~E~a zgT{B*C1SEQ1$Q5n>LgA735QHAmb{+#H+E-ld%c&XXSb?az9wmv=I%i8A{mZY_K3&6 z&)oR==}h-2FnnB|Yxgr#gjqRg;!VJK9W@Yn}z559v`@4{mJA z04i3uqYYg06ND)b(jIgi^tpAeqcW`&)Ns6IO%SixsQk?A6K0w-DsH@nd>kR>|Yi^ruFhx@{dJ}db z1LZySSgewvT=+4(11%_zmB`8aV*P81fZ;?8^TKLm#Kee2qLe|0oatDJ{2I`8fYS?f zx1U;|p`#;Y_JeGqE5%LEP*Pxui1L3}SNADjoZkQeGg_Q;GCVw+_Fn0I$LKT1W~dQ% z%EH3^4cw)Tb=JnC>$uNh9)4P<;I@?=PO?{j9NVj@L!+(eG4_XPL}+L4cF7K3iRqAA9!+MU)kHd5nC^9YZ!5GGN-(U0@<3=gV- z!M2*#9BP&33(e%w1=uVRx&tY@0Fuzm^Z;LrZ*0z4yo5KUV%I6CVNh}r>Us@tfwS%o zs%U;~Z}s9?i)-XkWlciYt}j0Ce|Yz<+r{s5;SuXTIs+nRHS^&bub|ZbwIi$3$0Sft zuJxdQ&iAu#s|rK*T32VaZ@7tNzV0swwy++q5j9iYf2V|euYI|zq4VPl%kMssYSLas zfEWk+<1g=bbk!X#9}!*VqWiy%$kP3#R)6whSWI5X0$TbCG0n^f#QS2W9`=upDo9F} zfv<&_8kRsyns1 zvBvG~2qp%UWVg{8-3UyZw~45%WZi?gc3WsUBr({4zp!|`?bP@Z79@Xdx{ocdLM4o=L> zkl*Gfoqm2J5ZEdN#{GFe;9Itic4?w;PauL*(@Sz_T$sq%>@8e2Tj$5HOC)IVMc@h_ zEDf;=bqj4wsmvTSw%K;Y5eBYI7#5*H+%L_wC*FVmeJG{)>E*&|)C;*q2a~L^>PQX@2hU#V;fK9fY}vt)95v&5=-1qfrbd zN?LeJ;uen#u)JuOVupg>)L|mURS|Li#$LNE76Z`LbFwVWOF99+JrJEGpQz-OF!MMH`9Fjw%fesr4m-oyT2>l-mo3=;w?O3b>^Ps@ZCU( zUQ4QKwM%Mi7x>&v7lfyG-g-!~!>hsAw^#`Y{8H<1(oB-8 z@M2aU0u5<%0=yhaZ&46W^KKACs#pUO91x{OMSuSnSi zxp+xPQ4qsB^%}8Cmr=#SREl|=5qLUUY`U*_aNVcmBo2_bdAap?er23gk>|jEp>A(hbaqsH*qw*x;+d%NB7vG%8rH z!owABd0LY|(xo!%GF8Zpk;SPxZBv+|=uhnzUJdp4TGA`a&8eMa<>Y%QeiTrNwOWjb za_&?ojiA}4HA|vHN=11a3zHA&z;3>ZgQn{G>?d|)d<=yhRV&-e({l~y2N{~jm}nqE zS9fZ}sg}3L>5}#%FB4yX4WVV*cjY(Mq+pK#^d!Pib&`?Uic98RWwFJ%x0i-|e`b~Y z3f^|xL8r(m?kt;q`&qf=Lq2mF>X~X-ZpIYUjfr2K}UgcER<39It#xed+Ut!QEsn=dIl#m*_l_KO0RIw%5y5Io0&E z&W7GSk;)-!m~(Kyx6j7eWWQ}{^j6>J`{JvMVogS*X6di8{61!A-ImDDz#4nvc+a;v zOCK5K)o_tz&(^ht+MOZ?uW6W_Ws=x)i0#quv^^TB4-*@=>uc0hHJkQ&e;{q^wZ3KZ zOdy{0iQ>c~U3ku?=P=(aUZ>D^%T;U!jGP}BH^Iq&n}6LQ|*9>E->{3oVPF_8$2M@IM4uWaQnG>-bq@%pi-}I zx}pi$<5Rooi995g4kaEM$H(#G&NUAnJkS!zd31K#rhaWIw5E6aPhf6D2;JLQk(N501~{wH~*yZe8F$fsP2h!!i^n@a@1b`$OIK1*#il$?*Eq zFE;&3AFFosxBJvYGaALj4>#41U-J#!HmgtXeQa@H?Gx}mK2iF?Kx5- z1}5`rr*wBMsAueFpFw_t{63`W}-G=<9+J|5q291Odn!+FR;V*R(SMuQC+ z>hJ^V`Kf)CQc?v22u#MN!gJ@phP9H`|C)HbC*%FaLN6?jE_1k5(^}!&Q10pTLI1$j z+s|0_^kSS&mT2m6bU(Jack-A3<;>#N?k+k#f2Dn^XQJ2E%xAZ^e3%yGN8MBanQs zMuB>xZC6>3@ypBc)&43iS8mbeCyE3!)zsA-v#~4B)IR)yBt80eik5ZB!}~4``CNWN z3BN=ShnfuZn+Q5Dd0cvP!uXV*VKBME%|9-u8FD_ZMIKVz+A3TUOhttO{UnXz;h76b%AxT=%%f_>G@v+?{kL3x4Xy$gl zAcg`4Iqn?Pv_22}J~ zCLC&TgWd#p$6z7CI}_}%L%b#oRj|mj6OLNu0Qz`9m+6?*qBUZ$2BJ_2c!xrr_wFND z0bc`33I7v~BMkdr9XnD9SFXiS{xE(FF(e!1r361<@Br*QN3v;W505$F6$jQztP&+E z{NjX72fs7>+V&)2(B zXKD0=${nlpk{&4yc8vBJ-=!knp2;UOohMH@voQ&tn)72Gj`N2XRr)?m@us<=`*&g^ z`hL*?l8wqO(&CP;eUa42-~2jNrYp-R#6ujS>fBKjwh{fq?`P_F8>XgAO=uVPDyAm7 zsLz88{@Udl_N<*~4Rfq}uKH~F-p#Ixw_RBv8Wvql5J4`52w0qFRGj=0Y%5Cg$EB)l zKndx6ec&m)KMmuCoki@r52KGnx=^FDwwx2hlp=p|1d#P%p-dCR7vXV z>K(Qu+xoye4mW!mtjHM*sK6AB>Yg5jXTF0lmMoC4BIxV!UGk2Ighz;)2XB^C<`n4Is9IJlc-U@%r@B%(+@@t#l$5^_}Kv;sCJ?Euo8Lq&z zX>=r(`uLfsaSDCqgS&>blR^j1FN4Y0Ba(!hY0X1748c&dE|6g6`O(b+3qfq{-tr<@ z`KJpuVJ%@bICxeMnAik3lvMZd>PANk0(q$gfl4Br1< zLLQBX+WG@Eg@f(*rf}>)>1`qWxjw~X*{f=JZ{fru+vohMvOw^@>_;V)aj%u&ckir+sWx}XG&Rq{}u%@qb zQNcN-!nOMt=hp`V0hwYLkpgC-_LUGjdlB@6Tv6H#A$H(J(cRgLo6#SfhPAM$PEc}u zJd4$b9jufQ#>0qj#&#|JD%I>K@^AwSA?+X0Kf!3c5dX_^TW8f%+uYn(jg7UIsE!To zh2-h*sadXxF_Ss2s_Xusyiz!IE}TX&!tnVsO+40(#Gq-e$f~CT~a9wk%c{f#eY;J4r6QF{AtcC?wB

G6$D=k6{#j!G@V~2TYvd=cx7>y;yQVM)=L?Z<5ju|ih-}zIm5~W ze-+)0{y2ZBO_j5Ivu!(C9TY)Q8%O6ZMCzCKp)(05!nrFtlQt zJhdJ*UTwg6cK}j>d@Ii}Q$mE3=g#eJxKrHC)^4Fc^Rh;C?f2eAl?e8*D3PGyH~}p0 zjc3CcdvihDBc>;yZdVLYdLL&MXB*QUDf7Y2W#XkWjKV{)5s8W!Cvb6_rAxw}`2)gl zBQ0(1GbgqkUy>P0S3W^+V##X_DxN@`M>IYo^cOjKK|oHfQPkI2oRxA^9Y0eI498K9 zuoWx=s=5@jH#`gmUD$mmC~irh*ScXiCp`!tGb>*?qtaUG&=};^TI22-zRtM&d5iZW zD-!OdsUKBB(#Lkqdu1g);8=&YTxwi32>BiJuD@gT8&8uGI=<9l>22X#@%P|P#lOWQUOt#(R*N?s%sSOr8yGtni3V=yQC_U$&`29nv&`<;;+`l zGv+_nRzC%TeCNkxN;$;a!4W1uOV>tku?eQ0v1w}MGhs+~`=F(@Og>a6LHVbDCO<)I zjGezPU}+9yui<#Uz?q>L_E5I#-mebmx)qjVN1iFSv4;E}2-}gNwzk@RG_b(mElmCW( zdG!0;QgV0_Z(Ir^iA+ctjCh|rgFQ}Kx34)|yJr-LD7WZw+&P=kHqWUM6E$%Xvc!5h zZnCeSrl!&tBGN;0<()azTvN}jlj&#E+Hb;zo4pn{Br4nbrUw2td1ajSv{jopUkmz> zel7pblkI+&yXRj8rK>BSTzotIIU}Zu<@_y&-I^lYpru#bTum{io$W=8c})Iw2j6ff zPrJb}zR!iFf`W|M-xH5zNKWRN=&kk16e!B77~bNk2eS$998r&--gti~P?viYkbf}o zVHnAX7+TYBcgY)>{Ql%HD4vy%ScHG|#hLj`*@BdNmf>JBeo1M;1DA)tWZt@u(XP8@ zwKF2VI8kg&%000CX1N~TLi!D>Y=%xpG;duj<*M6RgBF&1a$gXz3V1};1?6zr6v9$U z^Qzx90#p1EnUFQ~-Ri}gf}%xQOPwi`*og3`rTIgyPfdd?ub)3Wp#|%V-JqC1G9glf3#^MAdh@woGL)Tw8meYg)Nh_= zbcVyWy_18-$R+r5fV-qRZHC392EyiqEi2jb@;kENnedz}#%#n%i``5M!78D?9~}FK z+~jgd>c<@LLQ?srw-oGrp$#4wc?ExT&RBb@YyeKTI-Z$Qzl6_9O9f)+-X4^!dNUk z)eb9D_FUC z*4D=QOb+Sf6cXw>x@U0DQzWO&k1Yc#PGP~pos5pw<4mt`1M9})yr!EPvk-1oHmE|4adDQZ?a@T}79wqD&3S60&@*QX@g zs3^7G``nF1kVudBu2~C3eX>4m7rSe?^!#^coQJ8l73*+U2PxzE)Ox~il>D^d%z>qm z+RVET2U}^Rq~{MxffYaiz2-)>%{QL<9dlpwan?+Yuqj#+w(R9T*SUAAis#+&E5Ccb z=g%JWT_YuP8_hj=tbNDVpYC?R-QB{k;Qj_Yq zD&4FC)|KO1`uxN=(@g~7=6xnXk?X%Rks48E)dL;9qJ!LAT_i=}u%XHN(pe3dyl2tO za*xCWai`&iw=44$P%K7Oh8MlVO^ow5r0Sw21HjnP-CvI<)C;eq13b*8fZ$2_*5Z8v zP7M0!x+`#850Ux0&=~FFMb}WM%-trn{!mQPyiHhd{)lCk`{&tL$b?K%1XW>0M_*we zpf@3@tOW#jucS>7;KKL@$H{N`FoMsAdGUtFOK15zUr8GCx`kO;7xkDmC2H=t+#I;o zi;es$%~|s~xe6opF8F1)Fn5j57ap*errsHmB^n?0K{S3TV5KQ<_yk&yUcoTVV8L|z z9&p137xH;r)+&?>IH{KA<{0sTv$zVXfM~Gh8HE3VmgF(j1YHFqHkdxhSv1mm7N?G1 z)6l}MoRvyriL^`4oniS0ippcLi;*6em)P_#WYmVLLJ>HOZrs0Uoz5ZOgkTkz=j&s0ZF=`6Y}6YZ;SusvZ6+}_1Ac3yo}&P~x;*{E zXW1vnI*yGvizv*xo#N=;sCByD=8;W25a-Mr?9^Ob`1)Z#rtUA8HuWe2>t8K^n3MxB zP%~hFX7313p|c%|K7eonl2Tr$P>eY&hoV!Ycm~D zE|0{9q&@M6T;YC|&($L(4r3L`m3E(EP1}5qSF7F51$(e-Xpo`$7M%TBz(0}6b9h}F zPlj&lNR7)*_ct679=$b-eR>hT_@cCljZcoK5Qr3VzktuEzfo<;0iS*}GG3o#)v&}`Eac@1%R#aMahU-Nbw?R`ck)+0-?6B31ra7M`y^8Gp zTD%9}NST03B={QP+5OpGtKV#{^v(MXjP?Ap=>y@ES{{{0f32S#feQ0N51RXVAV;NE%hx<11{rhE@70_xIj zx7D78&*6kE#}B?Ie_n-YNoyZ)L@Xn}3qiyEJz`uNPo{Nsnp0qbM0+a*P1xIq*i}8a z)#7Ft(i};gPJWxl_W?rY0}%h-u(Oi^=`ZF5#gALoQbiX4=PvQq(;1Go))LHOKENg~ zD|mn<^VV2!y`LZA0ocSXm%#r(L`2kIyVDh49$gug+*ii(!wq%|mt-fY!ABPxQO{;U zqM_07`Rk@NCanxoAjG`8#aS&-yv*=8tH07EtftQKXXI%i4K*Y^KW5vbm26iGl3$^mNQnE+y z19b7nh(^Gm>QXCZMy?iF2UcR2(;KGKPsUA2zlxwv#HUQfX6@Q%j27~@qfXM2!kyz* zw_{$IQc{`gqnBWbq#@QB(LBqxFYdinB)D!zQpB1>OU0pcPu2>yqOPm7Jfz^B{X%yB zj!OEu#&W-cg*2VX$*;uB-!<0RQY4?fg$ow-q;jsZ$^H_knKmOXzjE6U|6r;O|J1LI z_Q#Kk7m3An)ky&ejxPHjVn1ykiEp=msLE=%CqIz0ptX5#`Y&m!_xb}R<+DH@vszMF zhnk`{&1)~jpHGJ){r7F}TAO+PmTffV+b-?$v=&z89M&32r#UpFvcU;E-PJIa!F50Q ziZx_;MyAiDo*~*vq%|Lu!Q~2)mB+fLCY=YdFQ@zElH25)%cy(?9a3&}gajBs;MJUk z{ceZ>jk%`~F@VK2#}4?cpV6D}1()rMOcDLSUk{`)(w7j1e&5!iM8$xTN%vccDpAOn zt9Yl4#Jqub3&_gxQb=G~jk9Qp2EPQRNhd>~z|9kC#-UnC#_U;=s}~iwS8zD z^k)q%^vb!!XF*97^~N=!wZA80u?@N0<>Oil#%pn6eygwyXwE0kyj_{f4jXXRRkc1* zdOAWfVg=o`$MkcTbr8Aa%NcqUX^odDEt2aD?V4{($5h|R^RWG`4&JmDR1Nk64mliZ zcOiPYlAY~krr<*2kefsK4pI0MJX}xhG`~G9rm!_8-@M0N6HK)rn30D|_ffZe$iJjC zVfJO7S*Yb>f1SCzHzJ#l%UVnF>Nw2J?7suYTZ`D$?{gTDy-*d;)1p)mH|1C500AR(-jC2bw+mqB}0w_(rbO`mb zjK91@0B$ZN%41kZ@jYMJsEmz_ZP-ZF6hya#pp~boKhI_E{kUx^okE&*+4)T#6tCUL zN-rru*&%S^tS859_>xrchUgl>H3#3O=$HLGv%-UssW&4hq+x90qDup!shg!1oB&zj0HkucOgakh@FdAhiyHC zAOme{1LrX1JM3Xh-dgLZ8!4v|52O5|{`9R!H~xh~wng9uHP6jnZb&Bhb;TL9x=!-? z7M!KCAuL=7MGCe=N&do{g;6(cnLr_GqQdZNeDdJk1R@*?s-F-Z%TtUL{;UC? zh^%O`r~mp4NxAkeKk?@uhYx#NDFJ`S_0U%kHOvOZuVD@7<-1UJU8TE5#=zX-hZ{rlq{% zW3klyj%MtkCa&0fr^qpi-(hC!m!DL4So#I)xeP|vzMk#hv^F=17pQ9>Cd1R^US z^d{YQ2NSGJq-(WN+uK&H9;SSWuoCQLnSDf)bpuR(52*MDNlR0E!2minjg+q^_Mf<^!Fvtl&+lP4ha+b%Z(s6J zHf_=N$bgQq=T>9o5C}t(;fVN70(hHZN6#I=%x#XFm3??wjXomb{j!BEj zjd?osfXq2&l#^wDHFlMXlW}#{Bj+EVagc>2DPKg42H)~B5^2EaD~VUfN)uygNfHox zFBtcSR%Q@xPDK-6juJvL85K_C=vtuaC7QvJUHvK zels&p$%*EVdd*PdjfY*5=UcwN@j-2qmN-_C-za9jzztLzv%d(n&_HP#TnHGKw!D5} z-P7HTmt*vVPfAa<+Ng$UA7^}c))^Vb6ZICIi9+CbWOd7DrF??L4w(96=csnT#&z8I z9lqUqG~V;g8?cSEL2MP;fa5q<*gTFpSGb=GLV$~KNrQ+x5CeB(f5Mq#=1MmO&_Y#T zrVM~dO*6ewVzbGvUWx2!+}uYa;A+Z$H_AwSID}P%VoXa|h+a;d*5rquU`GSDLql6I z-|VJ)GRkcl5bIK%F~p?VbdV{bQEWh51(vJl{l9P%9hexER`FHVL-M2Mea1EUPStEG zpF-Xr-JT&rYfvxfLD0byT&)2e7;Q@c7)vZq$7FBy=)#D34-X?H$;R`8v|kKIvGCLX zxTUxveKJ<`R?6#hCFvFZk5w|z8pB|2+DTO5@vxI?$8o;6fP!rn4GfXE9?uE;N*n=I z0c8s^7yidn=~$0I-HIe3+=zbU&eE{_-AqlOLrUb+B&;-}Ly-8qnK#%!rt|s=;Va;j zI4R3SeonK8!AO^tEbw*rl$eE*gSP`i&8pPe&BxLdOS81M{lHomJ3qSvH;qHum3{Ws z(Y3}p3PS(dUw^bXB_!^G*+A3ba|lohBNZ$R(`n98@K)# zN+mNUt?<6w8wJiu5$n(ROaCc9<`~1eVjTTZG2mB=*~89ZWqxw+)r&KyQ>}ofaRxfj zqzVLFl^fkV0f6%rUbZDpHs*Q(tP(EiZ!}+vMKxX(iNCEc&0m>Azx#-4lx&OI@tz6S zsWuQ^!)RR+(4~n~)UIGd<*WX59VKF1N|bZ)q_wFA^5Wuo2< zcAiVok6IarAaA$=gL>4xp>=9s6bwr{o=+zN)iWeO5w)nAGY3k;-` z&79R@5{Sl_ob}>Ud2lG5_{y!eJA0f@n|mt_bKvfWejCCQp-7|6)p-V**?!N(nQoqv z1;J;WHNQcD;kd`3t)LSx5w!i^9J20a{#F>g#5fBRu{)60#tq+-#Cf>J0y}mcS>#^V zFRV;QLuX+yy{MPg^LtAm=xVYBtKG%5Aq9)E&4YqMldN*hKaUSmS+1B8ziVk{D8s*Y zTG_rFuO(Jcq?>o~T5I9pjAPxRSiH>^IV+eojk;AVW9LB^zw&BJMC+~FClNg^l|VFK zl&>FN^lU@M3_0ZTM~Z@$Ww*%>CLz@5X^eK&cE_)x$-#phULbN2laMqsZvPxo5~EeF zfw5L<;#F4{y9;|oJsqxbRxn(E1U^2y!>10uN^>K>$l<62rP8s;(|r)d@RWVPl00eh zD8jdwOfmz4O2l(SGz%J59*V5{QcGaqy8w>MlcT6=~kuUK94B}{I=Z9)YawQjQRwO zy&rHEvm0jcqMf?@)%K?xnt4*5NlWJtjm{WN;N24#q!uBvBfd^l=NwKmh~U`<^J+|y zN|ukFFg6%`q5EjO7>(TeF)Zc76-HSflfdei+e+~s%HBVN=gOIyf!`m>zz0ctSeSEp zgZKKCk7NTk;48bax8tsyhw^f|g*kbC10zS#cA^BMhzYJ9ov$(tfFy)fD5#pQCV1$*wKt0P19_f&vLwZvkO)?G+& zuo{IN0(rgt&mFg%7Bgl`g^302kmfgTtx4oW%y>DMgE|uE@$)v|m`SQpVymUMNZk4z z0PZrtu&W1pqm%LuUbFQjdL_zyyc0&=^)@~xd<8vlA>z|4dxYP~5sLqPrfvyRapk>H zD8&qtr=LMG;CffGzFYPXyY|&6%0e%IFJXB4E*}>c*Ko#>o|8J!rY!Lkw&60nk>+nW zvImIa={fzdewJD zbA^fQ_yn+c*Zim*uK0y$D3J$Q>Cs;1QceEDX=8P+Y&nX9j3EIzb~6Rt|P{MJu6D zjI3Jy(&-;JOFom?+TNihW0Nm{tp174itV7!P>E0rH^{9+Pn&<8Bjyw*F6$Y?Y&e!J zm}?}dYqwN&-$U_FFAtX&vq7i?Dieju6cPmPRX4=y{G|K8l@bb_80Pi*b>Uk`u0`UI zqtYeNaejEX(Lj-^D#1NoaoU26(7JDWu2At|M%D$LIUjGLJC;5>Pu~g5xHiz;cerB3 zOU)=lq%Z)<XibwC^;C&HM`Kw z;OQSRaOwV&E6e=Pe;y=8d&{qra+%mXUDW>RR3(a(ZM6<|mC>#)UX57;ebzpe)8)t;UU z81TVu^^JWNR78mC@UIL90@Sh-jLKkX{h4dO3=u`O4^cN+85sCh#vndLzoGawcjD}J zOy0YeSQHHXhk>l$+gzVJBrgq5} z8&$G~!2F{56MQUJWn>I1^^_KVL$sP+$OINV3O3$HwD4Fm5@)0wJxANQ)7zE)hagD6 zkroj)-xGe(wiK4^3c3|D3x-n;;g5fTeTyf}^7R!`>f;WWt1Xgq-0h8U`QP)%C=km4tN zG|iAz;9)3#2fx<|G;@_@B#KU|!cYmI2?UweOw4|+oORlFpu+a9FN`(;%xJN7UF}akU996)BB&`6S4@}qE?kxf4CoH zqK2Tnn20)N0&QW1X>MZxzMz&5C8dClY7bx4M7ugJ(95StvK#<%;UkgO7WMGc34O^| zL4sTmrLE1eI-yQ2qE(cz?0aw}B#;d69^ECwf#Fu1U}^gG4yO z$@TPaqnHb1kkrEJWzg)FjB7i|!x9egM@(^j3I08TxM(9YE2=N3C@3(#8GuC)J^WUt zVV`TpfmycMC={|{XaY|xGQ;uQKtOC;MndAYfWQ_J8S(fAxY+qz!9q)k#CCW8Scy5} z3oRM5E3LtQyqF_nXG?`@-YBybPl_P0ly<;>4LL~D9ymi6=Cdjr^tf0dsuc0c4W`hQ z3rK;2dVw^AK8x?`>)cj6=(ZbJS)BQOmpR*loUA!~{dXUzz@x005-bZDHh9}M&p|!} zQ(h;3h?d6fCzmH-$}r?rD&EkET3NU^1i)|smlA}u4?7&%Qinw7F0sKuSf|6+54(yW zoQodF-w%<(Q^CnK*L`PA$|38F$Z2vPnp!;Q3Fw7nRYs*GvT8$ z?Ws0|UeZ3}(nLHy`t0@Tx;`oaz!$G>el^1*_21Wq0VQ8l=~@c1xd5oP46s!JoEE@I z1Q+%I`))*>LiH6Eo(cdc2BL4mc>zTz>f*L4$x?84UzhwuhEE!4zcB#7P7)#>X=#(npQ{e$P10E0&3@}zn!PXvU&WN1v64z)6%kvB zYl>Eef}=t`K}K|IGQ5eDJ!6O~IM2u=s3qXMjZS|9nOtn&GVkwK0BS4~SqRZrHrc*+ z6wFtjvk&x!^d&0=_vLaBZ^$l0X-P4q9u+DR!e)*^{)(+O?$5rG78gzO*e5?B{b)N5 z8q_Zzy5l6vbR!BYKPGii2pcsf3zEJ{1a4YOTiX>m9&rXLqWE4M141jb!M?k@TQ8^C z*f;Wj`TvT(%^+Z)KWhSlNv7$Nt)!?14@9}>GpGy>J^%X+bidBP-#l6+keRUtZ$(OH z?Dh9u18~Mb44K)!9>N^MM*M`1D6sqXnT*YIE7y$Sx*EU{50SA^;CArw-v2pIJJoY> z9tJbSq{L1fxq$WKpjWB9-huuz5PPS>N?N6kamL^0bOy#hUAoukjru1h?e{i~JlkPe7)mWi zT7Xr{ql!nO-_JC?5gD0naJ@3e6Ko1W6W9$5_D!-Q?D)t#$cF%?ZiVvS5BtmWw#L%3 zT;e$c6YO_Z#^J_<$!G&-L%Tm@mE(?|1O7o(73Tv^Eq-D;M8M!kQJNNqd+$p4&!9Vf6lp+7mz!9^Sb=B;Tr6c_ib~1h0~TFz~Ofp>UvulINp9 z0EEnxoYdDc?n=Lbx8`a}dLf__$*oNx#oq3bz`N#fbJVke7Kyj|Y>A_vozf>w^C2ii zV3(7OABTxGU1evp*AlA(SDsjA=_|gPUG#syNkK|;vJw(B++z$wk!?gc<7gG#vY5eP zU-{*&+}1!NGvrSE?ZPD7=Rp#_nIILS#?V(eu$1y^C@VltBMAtB{nH` z3WMo9_0Vo2DTsrKe}FxOGvp5vC`?BXs%E1jagIsYSS93$MM|hfco-)Qwsv5K^MdCy)RsAa;rR_x&=z&sB;mljuwa0Wq<8hu_mMzN-lOjW`n}!(LV$@Vuknz-=Eg6*ZL# z(O?Qce?VpINP@?>kA~n&qe_E&l{8OMy<${;~eOUOc-ek9z-( zTi_QG(Z;&4D>;OFw&Q?VCCXbZOnth44B5qFav+xV#cVlm_$W9_Th#U*CFa`oN9>{U z;Kjy>q1?;v{*a?5E3d-ELM(Lc=xoIzJsDn-ymyS@eY`F(m(Ng}D;30EEfRM3NPF&q z9-(XO5kS~5jt!qQa8GvY1?#11*5-csAT^Zkhci9Fp!)DXas)-Dfs!kN2bcRNye?n@ zn|${75#IPRHLs3p$1C`f^L398-^zslsK~pKi-U)!JPpBONANCBs`HgYxCqnH5}|fD zogyxUlM||qB#Z(EBl)71zgaVk2LO-%p5HSE-1lX4?;sLhmVD$BeXCp1;I(7j#yGA9 zB7pKwYee6E6t>+a{B>nM{SIgl1bl9Q;k}564X@-5U@Xr(Q%v=tmhPLyOTnga zPFMiGnL~}>Yys#Fy-GIk5-#Cj4yHEK^6j|4x}|h=3$SZuT1F935|OWzv5;hk@e#O3 z;7nUyvptjAU^gA-hS& zLE;Xh#lLX0_mFB|VU zfXn9eO>c{JJBE(SYx%X)843`5O_TG1a9BX?Y}kYJ4xcR8O+;%7KQFE6_pSkNhuKhN z45neylWdQuAc#N1JOP-@dG&m^&`&B?2I5y5=dI;OMgLByrKx41a>ZT&VQntOx~lFRg(FLV5~Lq6k309L0F#th@Hov?$BA{XASdhG{0{V8XzIlT5QB` zz^1Dy$X}mM>liCpLL|yX$;I-P$)B8+@h{>zjeV`o93PV7YW9Cm>7)p!C_dNuW`4P} zY6l>T(a}^iAf9ailR8fKJyW4A6RyrITg@$he0crf`n>=FM30ri3M`+4VKxr%NH&9I z1?p~UOd6D1uiRCH?_fMBGv01P_{BNgaXGyB#zyE&ti^EDg76JJ0HaUx zq;_TDh)O$<<4sugnXR;=rutg-2pdeX+neB0KIrcPi5^_KEo~->(-v!PULTpk@Zd7< zFE3lR%3rg(JSU{)8Fe+08@zQaT5^>jRC9x`WC7l;_EWAIBo=UahRQKrbF8y+L)^?# z?J6PrTbId-908sIew;{rCI2X-YFGq@_VKtRl!TyXk(8{F4j`I7pVWO}#gaBCY$u85Zqh(T5ddNw=dcL|i3Afkx=&9} zZCY%+GYZpf;amH_^|6rND`4)Z#`y9ooj#GhRLNQ({#K{;%3x835IkMc^VD2q|7Wo7 zFoerOnXu9vtrMDm4>xng4W{+*tjGFxU@n-LAm%})85-+#iNa!Ky!X*Lltb5FnfpTS zFo)Llb-$;4OB;_z0?iK^_i(A?XXiqPKYX%4%i(O%KM&vkPMH#Cfy>QXRj>vXWOz7Z zf}M0xty4j4xh!0Lm)yQ!_xX?6NGUS)zxdCy)<^rXqRFW0MrC%n1DBJ10n z7p{n$#rA+PU+M;XijVr};!YDlalK8788YBuhr--eE;7o@9(!$E! zAVdzR>RKTIHgY3j&LOqdeZgZNCn;)NJpG*O|y8U}J`fz-F89(@; zr4cV3g~=q&j*xZeLG~K~O*%fpQCVGG-GnAMUPkM#$dl&6E; zAlKmpBHRKn*e&|R?1!OKV4?fZ{*bP~}A;nt!r#Z~NG>4wE)HaBc@pg0*%G_Fbk)}rwq?<=1KY!1AO=2Zy{WinC_M#%ZIr}8VPM8AT0;HLuv31unMV^SxYp1+ z+-*lmGX`Ti$eQ{5{amB{bP&aev{9rQY*n?MSOPg^qh(kfv^3jUp2#6b^L+i^9*SNuQ+;ZIwUeq&FduW%Wb4WPo`>fT zhwd%?7+>(J8^mCr!GpDB;}F3RITe!;lddeE@#_65jxDpJt~m_J{oK70`+QVvam1`Y zLC&RU7suVXWBw{ga^*K5p;s(G0>w+^(-25^>Cu?I`CY+9PbOKAES!kmV`*`!h@Nd$ z+r2hH4!bkyXf>~2Y`NS=UC->Wmgq4Zp4Fui^Nv?g(L+- z$^S=S!VGM1d5`=aH=cWH`>z%NJNdG%tANYPy3sPMgwC9TlWr8Sub~s{gso9F%8%)k z2fMcfWWoWnv&^>*N)8R~N4QkTgMK1C2?9+$&QL(U*(J%_YZ7by;2)~48PC7+3$n`` zXby9k@C@G8Ll-hK%Bg#gwWzG6OvJ2IlMVD8Z~XMcm!a zeiGsSp`6e^mc$=eNubS!?GDjP_4#}U@9#?SC5!qtab`sfK-977+<}G~@Z-)7zPhLRF`S8NsOe=Je#NXxobX+ra zRrL-=GgCijp<|oTDDzG}CMIS(GYOkKBFq!k#Idj-R@{Mr^tnJpLLKo6_zIQ)MFjOsX5qR_CQ*K2DPLqVj-u>}&wPRpQOXlV z2ev9clmq?!#kb7>_(x5`$|Wr=-RgGBNAZYxM9Wb5;Fj5vI{Hn~tSyOjP?Of9vCm*G zk=p;EM=rhq=h439#E3csP2cJ~3AjKF6ph0!G!33WglBh1GYPKE#L{7&40*R0>S{ z{<)(`S8f2KhvOGTE*25GmFkN^BvU}udvXIp?j+fW>5lwiAPxF-PHsE3i`0AU%p6~m zc3)GU+h#Dv|7xsI=`zmaa6;n#a_d?Dr`1l5f5cuZB8aP|2V^*4C8y62%lMVDb3IMB zi5%jI`RC+?7CZ4+V2cx0)lmJ}Tpu+zEC)Rv(&$w zZ)U(VYF#ozx&x??bZ>D$pr#ETT zC}D%^O{OaPx3+XQxK2er0e106R$xjd((fORirVXNFih&{3W zReE$8;6GP>WdtlK5vA33XK$Q^&&M6a*LEms_87qwJI4LwS92b%SjTkuORCP@+x*|` zX9ZPRMWf@J8aHzGbd<2qZ9lza9&cZr?qR-0%*MbWR~Va-`hPZ%cJWx7GBJ;buAG$N zPOtNhv$dqM5GyQ6eF+hmUkjExb95a@7)Y}I-ow(HPg43`D_N?uu4ZgO`+t5i_!-mT zeH}z*C93nO>?6x#f=17UNDV zyi?+ogze5Rc)Vq!t57}n@2{F*576lTks(rBH&NrMw?tJ)c>z{S;fcAUU-Mh87t~>U zu8yz8+@-!Kd$8J4-oKg;dvo;9!ZlZ1nLrc&-UTWM_pYi$W>e&S4VzB)8DSZ!PH5Y^ zsIXiH&xMQAtazQR)2r|&j3?Xt-ZgFLPEPp$Yl`=}wdB2VN*>BY{>&aMUs)RzXXjXJ ziMdi2a7SKGE-~crb|<}nP?EII#hizkvwpwqSDnW5?(*u+UGEuVeWQ#xSHK9vKWVh; z--g8UyFvoerqF=qCIow4d~fqz`!QPFi`FlJPSsJ!vWxA`KOU{O)d%9}#E7f!6E*&) z6%l@%G2*5<1S2EA0d-O-D$BOf!oq?Ck1RolAkY&4I{2%)d|oB~VN@HVVDA^p{#KJWyNN7wl@6$56 zKk(ui;YXdHME_ME#&8)5ZoAiU8CB8pW5fe8d5z7-8LhX%c|{EyBb7*6-s&X2P5Vcd zOOM{FbxA9*8iGa3FKEXc|DR8x4r4Awr#z@xq(L>4f`I|@nD1dJ*Y!*ViR^$IX}Xxb zI(_aSA05@6fHY(n;iB$A2ckFBf^mb&&*XWk^YmeoF^Ve!4v+S{!6Y8B&kj|`OhOGG z%iJEn(~~}H+GvzV#_JeAy-%C!$2k8LU6OmVxRu4?)ch=q>yRn{l`?+W3Qn3%AjX zDw{Vz;kSZB4Jhwd7ni|}1rCSLzygMm){Pk0tu4@T&qaTD3*-ncEOYD38Ort(5a;QD zeQJ)?6!P^o<4uG@7-Hf;;avc(4i;a%Rl$isjD>(o84L)Jq zbCk-rHxGta7+ecLD6wIR15Y0vUr!MsIbhrPRk{MKnGmFWA4u6OUn)04Oy=Wh<4PhZzcgVVwjP7byRhZ@}vD_+L^m zLGsXw%}+47IS8|sj8|Wr{R#)K0nA%!cB{+F`etgw^`=M?HBWHSF89vF#K+^~ z;k5!n2;2n9HB~L3uEE;E9aAp{D%9Tq-lqtDw-e8iROT7@ctLe-i*b-Z!Eyv(SZr>(q0!Z(|6lp#v-xSdSChkY{Ot3j)l0RjcyW#vA1)e@LiN)}1p5f}OvkiC@YsD&Gq5o^If?e(gU$%cGN~#F8NS&x=&EKcH`Wp|G%!CB=rI>^AftmOV!}b0z*|E z%xPq6)T!DV=t62C4V8#HqcyJny;FY_sTIEFSTCt{=iX%NAPJ@*$M~NpRA^AnPMlvuYMELmVRqFrQr|OUAt}c?s zjnB-8J!@w6q*{P;v!>5k#G!hC?F!y+i1`Lep^|M)WsTGBKZ^+x)^ngN4tKWs5wTYc z{+oEkKX#B~EA_K;!bm>KSf__RFGdSm;zaS78@zInDX%#;w&yyJB_1u=e0{X>uh#6J%0-Oi z{Un*V(-U4O=A)S+8{wy`6rC=A0JNPqrW)9E1FqAzz|&fyT~uis4^IvR0khuZC+I}=d$9xMR0gRrNShYvmf<_NO+82N~Gar?S;w3?$`nEzp$IG1(fMzz#K zVjE+d9?7NEcV|9ORqII_y@m6vZbY^3$TjBI|6u`Rnw0fkVi|PQ)Jx0DQN4E%y#{NT ztb7nup_C8euzSUvmaa}c@$d*tBR6UmmWe3D`hQVq{4$M1`X=nYgyk1^Iytht+aX9!*GoG0Vy2dMD|gk?beAxJ73b89`HV z3NZ{J$XAQl@F5msBf%!=gn7nV@Z9M2aP>KDhrZHn#=7`2@jW!kvE8@CBFiyW-2jV8 z#|jx+faGZBW8>nqqTASI_3uTBHS?7jBSBhY!xUsDb8#l=m)}7aCVh-YHCUh|gD+do z66X%%E<;!{&uF{taCDt+mXaTduC<}<-D8insW!|E_MCNWo65!liRhG@lLKswRD1%u z+gB{!T^dTIe zp5T;se+jF?NwLqjA-C$et{{%OL3Ypz9)&;bFJqkz$aZb&T87s=4xyZE*X49XMOz-?{=2sSn zoRhGv0K!0Y@#l_*jlua4#`Ir^^@MMVQR6@&vFZhswi>%90m7>rH&H-|wlTLv zTv$^F)P9qgc|GaNGW67>H5ugrzV;f#Q1P_U^v73GwNO!0M_z{GOTA@*VbNVmKs_Ho z2C{!UR2NA2IEFzVmU2XCZ+`|GlOmmHD3Tsh991=4F7$p<*KXgw9VlBC2g;TARmzb? zu@Hz#Xv6YIR%wI}To(x0zH@Swq|m;khjLHMhx=Jq_Hn1}Zi;YFjAdE&U>Q^cT!AC{ zcjbCVI5Kx$(L&@Z3)jGUTyMq$vnsPl>Qb3>%~OzBQ<3v6zVmnrqdc4 z!cg+@rD9zGWie!C;Xh;3UoBF@s^>vZSz*_4u_Jb^m(ovWraV+RHz$a)IBiSCW_q;) z%$jM!B68|qT5G6x-y>F0TqS?o2Q39S7;A+4nm6LPbgt|>=>wRqrY@PRhx!Gr@JpT_ z&!CJ>(dL6dXLy$5?QUl#2f!iA`MG(RSqS6 zk^@2_5ENOnXbc25z6#G4Sd3ozn!st^UMOH*v=mpw!mOGetdxd(44j4!IItXt)@?D@R6OCylBl_#LstpZubp_rQy`xeM%Vbc(27x zb?E1B31Q{roSB~P$Yd&27@Sl0riO&(6C=+vG!ugF?sv$-Qt?W zXK1XWQv|e!#Z_YV6tZF!O{=3!nFTlgR$H@=bcch*(U#(=biABn&8hp_2noD0D0g6Y zsr>@dsNRT~crUEQ_mXrAZr>K3wf|?c_f-wUd;GKcOojXkY;0&X_9!c3K^Pv^CR0;W zO$)0G8FtzYOKtoLmZge`HUL|_{qF5sSkv?G+}Z7RGGQ(?6>r=i*yWfk_^Q*VPq$SO zUl5JttyHnSqOG>onbd`M#romtrl}v<6lHP24gE0jI}Di2-idI9jtTw!?Zm%=o{Y+r z|MUM+%sVllfV534WJlbLPmkFL*2 zSD`m53FJ;$tb=GDlIoL?{Gwggpfs2++bu#oTs(=HeEdhz_x2v4zgA!4R-9rO#awxr z6?Qp<+Ra@i=ck@GowFBWVq>W_^%G+MkBF~<3EOrpc#ZAFsEMY9z+oC?2Zs+x0YenWA%z7W zx4OSGU9=B%(9aZzy@*9U2v}34XrNJkqxcqf-ds!UtqqiQR-T0bl63bDbCxMMyd&^p zOHfpl41O}=fT8#1G73gz*l!?Qu=Q-Y9nQ=cox(z=$M??=XEcK?WaG+k+egJJiH(p@ zNbkz*Mwx??Lqgm04T=$p&U5q6bCIi_Ro7J$u)#o ze|VoI5CQBvVtMh(U7ld85$+Kz;mWnTysV5hLHN%|8gV{(-&?y>un=@9?!Q-|=^c?n zO-X58?i};LchGU z1#BHhF|hZIfSlYC=F?+lA~(N9d)>Q?PPYg$zkNwfjv2d-(Hk#dt~mp#0-F2WdyXq9 zBxt2B#f86N!X^0c(D5lYzG^h+&Q|5hr=};%CL;i$RXDqDd=2~4atiWk$5NC_y#0#d zc)#ml*b-Po(_kttEqx#IEJ_Q<%Mc+O(^OC8=Hz@pTj;vK1uN7wKCjFT?LyAV_+?DS z3?d90P}1MtM6&~{{ywz*py6YNB*n%p79i(=hPcBcgLu=lJ5;QBa&b1w4i8IIaU0VW zM`S1~D`U6xGLF_P8hzkaxg{%HZtW>fsCe}0wr#sgm~X~FXC1yADaQ=lbSvRu`S+?X z@o*}7fBy32H_BjGv^+sG&0vApiuqgjp$b728j|SfXq-k1^5dA%8t{ynk4))I6HLq( z!CI2u05XN0!i0>9vb3>LJ+Up)W<3|mb;=uPp9xc_Hyglk7PNx+kk9; zM0&6o#v&A?q@y&n**wzkPxJo`^n3!>qWoLYhnW^Oen4HMDTcb8vI{361P=r^DfiT- zmKZ@?_Qzp=_PNk?aw0O_$BZ#h$FtB!)o_J{{Xy}34mvjAI7^rtmUV7L-#ulVdk%x@ zlys+HFr7E}JhE{Zau2jh^*?6BB{}_j2!)53dC{h?TS8W$dmN2lV~m}=4hejq$7p|5 zcKZ zw*xRLh*q=9ro949Czf+`4uL$ z7(l;Uf3BUgE>Hyp7^zpcTP2$#kje!c^agFs`06|Bx{v?6(oHmpc-z;J0O9gA0kb~H ztX3K*XapCYKp@f)(ys_tyx!VYir?N{cDs+<8;UGvGUGo9*#ZdHtoWsT`ziPhGF zG)4&WJ2>};_>Z1>{&XZX`!R+oZ7+xD$LcvfjpL|O;a*)XZ}Osd#thAmk+J;o zw@MGV36RP^9|r82^V~dPs^*hRNIIG%n0Pt}9zzXZ9-}^a!188FObixGNp{-|_2i!m zLhzXCqjWGXV>?JsDd8izV?UD3tFNRERR2*ix3)YZwIgFaz4F<#_(b%u=m2zlkcwA< znXki3wdUt$%c3Xk13zzyXo2I@II3=pBF^B0ON&LumH)7S>cbCxMufhN&dw^slN%_j z(+~8X1mK6O4|~M2Pe8t~7mVGZyISL0SsVZm?V?ttNGG!jDCUq?$tWlQ_;B44{&fc@ z1d-a7*_y!<19E5T9s)KZRtBHjli*)b`vZ~|Y^EbMNS+3K-$|YsonmgBz+_z^o2!Z; zTXD0S@s=Z&EnQvvjg^X9Yb5KV(L0fmk&R^QEkaQ49B?rIY9^g-|uhUgVEfAYyaaW1NRf&0(q`M4e^gU;A4)8|b zxyUuW3dMHcj4PLKyLgb9NuYK1wV>pk*p#{J%$Fld70{a`T2UAWU}i&32su#7Og%AA zo9l*2B$V~^jp}&6^VoxxUitE***RD2QF?k5o0;i>1w(-Nln@KYxY70U2NJT9g=d_WM(q%V_lsHbhY)s1=E z`ai#aYR(UGANN9~Vsbd+9ZoBTLm&oX@;Rc(C1qciu~UyeFG2He+H2x|V4fGWs;IrA zhTT~GMd8gt(KC6Q_Ib63+{@1&c$J(=E0$4%xfDzVF08752)*%7GE7@yhf&V&$F;S% zYmd+`_&XuH|3TJO_xLW+V733skGSIZ5aMkf6%sxMJBT57Ks;06{&jMS4TDI zR8Y2ns{Nr_CkiTn0>mH=zDRjWGZ|Yp&r6GS?|g?EKq=4{OmLh8%2Ik46iL#^I1SYf zMO0#`m4PH<6I221+5dJ022B}8g^=&P1_}an*x1+@V1Zt76Bg)HB@B4pQ1Z)%U&6(= z1QP^bne;=~+FWk46=Lp^Pb}PS5v!k%7pu#gaj~R39A;yaF9*wn!gj8 zEd3VX`1&-WdZt+rTA>JcZID9c@$#@JzXpO#(M{io%Y0hoDw0)0#I9xBTnE!hZ}0{4 zA)&7^-}w%bk8G{L#!*pXvnI|&?>~JbLs~_-wM3czdN5rHNyyaKuWC5J?WL&n-$Z9A zcv+%+%yYxG;&Pk;LglvWJIDu=ZT=9oqC{NB&A?2>I{b=-j518B++QKijP2aJFIi7V z#}2ZspIfP^XWl=leb9m+y*4vIQ!$q)T^}zQ4F0AafGBG7#YewT>+yRO+tofeaET== z@xYlDL+sKL*ER9=Vh6cm%`o>#r{y26D_dxKtGU>|9jI{Y7Bk~|xj=6z zO?~QcHUEDbUa@>oM*OR$WsXhkSGByqfB$~|HBaWfDJniuV%+rAZO?p20?QS$occo34swfM2Go$&cLxDYXp&<$X>d{eWU{%{dL|k%2>B^d zMTh^_tSt?vy^K9NKk^Wy%LYIsV33H6XOXrbT88H3{uVU1 znD*c1EJew8pBt-zx$LiwF#nq?$xi-U^MQc&*s)K6KDS6!<-cCfeh{!tI7fv)4=MN~ zT1oy}qo8CeFO6EBMz&x})cK2-Bqr_;t)xb6zToo8G6ql{_yV-kP6>0=sZc_gD!rQ%)UOklAH=Suil?aSdb?n#!j!ITWs>uI6 zG0vmyDeqEbty-`7;7sew360MMILULIyu$SV{HEZeoetM7BOnW0V?O7WS**@FmerWb z|3Bd7{%-P|x~Fel0gmRjz*Jz8>iDbJQ#Q$DCFi66*M$bn%U*VOZ%;|4qXG@U!Q>ZE z`~8*Vp^m5HWdA3lB{g!jwP}vB*-Cs|)*8vUoS~2A_p15q*cM6t52PU>-+l15;k=}! zIQ9Nlc4R=_f^LnT)LhYv;SGD6MR#o(Yp`-f68^!-u(i7rM?zIq6`W-o&Cy3?eZfII zLT?jZ`$<~bB5xWT&b`O~!n?(x-~{8L#o*=vtvZ?iJz}ea{L~V;q^ec+$}UgN8WwTx zmy-WJiN)5M_MBJZUUzT3M`hv2AvYyECHZHZ^}hW7J(KKKR_mU4+UoZWr7Dk$N#9u! zd$7&$_XQcSAHWxgIwll#hCWa{N>3=Y=%!SJYq|Avx5|8e;xeT1(uyI|30*S4JK2^S_&BJ?&ST<#k8Myf5>L$QSu{5r zZHWr?pD2gLiNbHOQHw1sDl&2*5Ux>lJQaV>(WGE%8kr>^S^LsOx%sK7HY-o!_r8j# z>MhJGmzn+Yg*bo2gYw3bt7~`ppO5nhO-{1FkImyR_KN3HQ>Pul>STIPt$aOBG|w%{Mpmhq z^iYK;q*)7`qnO#Mb-C-}<&`fl-XD)mI^Nu3Q*$k! zn@#(2rKz7H2XCbjxs&-Hj%XS7vbXO3{(HZ_T$1SrMMtkp_-hJKxct;mx%zJ_abAO; zMk3K-&+o>$neFeUEy!QHpJ07=|1#g>A;%0Ikyj7IICV__P+K?_!{U^MvH8?WJy{3? zm7ts)aT5>*!oM1XN)ac9_`0c?4DYu>khg89mm{x8v%80_SzpH{T}1FPYu*s&yUWVI zMVE4hD{J$`B!w?64)=XFrK!AmK>x%SVAQ58mJLL@Orr{vvXx-FB)X`}`4944d|8{# z`t~nLk9S#n`mdbMsv|SR{$@*&bCvALhQ|QqI8mwa7ljSAsy{& zS0y)o5{H*3=IUF;r=dRuwq^mJ}@eLhrdx#hEX zWs}Q=L2LeI&zfz{-BWEJ$^RbtzyA`rj-PswKVSKAWL9Lpe?o%MAEib|`78IWEo9ku z{cw4}p7oqfhgzcKRk3lO<>swC=~7l5!lS8e-jAlHxdn~fs&|#fIc*#!XySjKfMzxx zf%w}}Z4FaV%fyb+9XrNWC(D~0MJzXxX@~rSfY$ZXcTb|j#*)*szkZzl1#o8D z4kcf8fp|b$K!DI`cb^+TX{6JOM)f)B+JviMsl6cgU)_&prUE)KffGj*37>XUy5OhB zd5!e-+tJv>yqnoJo(oV*uh74YubDt;j3;_APW!K36yN!?{&P-ia>l#&5UmR|GU$v= zcf4-Kzn=Ct(7KN>%h_I=?9ZI(>1U}STuOB)>VOlGrp>#98Wmd?O_ z^DHnN?!Bjq%x-m}<_}(*3j*x{Fc2kU8+#zA8cZ(7Ph@)y+yNsk{&Nc8+!1t`kWU$2 zk(dtLiXp)bLT&&g2mTf9f+hmMotv}K6`#m#WJ{dF_|XqRAcSjcYlG4eMjr3$z5@>W zg+DQK74B68+p8Z3|3QvQqB~A@=(@J+_L0lhjpKCeGP!2A3}FJzD9!x2ftVH$H6z`D zg{!u}Xuqwj96|R3XBwxg-WM)BmjM!ysoHpge+ctGSD-Wa$cw;%Lg~G?`8^98hQ5|1yRF#^OT}_RC)*~4tSZ`g^*a0h_v{G zgh;kmY_Tyh_|0uYLwiux%33y~>%J;v=yIS0L%$wg7{fZ>fd0DrgN%T{B*2w^>4*Xk zP%80T0Cck4u}!VS(9&zGpO6m$T8U{(T*Q<+41`QFFMBNEsR0OkyU!OI4@}0gPn=`e zEkI*_0}?Xm3sYD!7|M5rta*ZzDjQR9-a zt?hB`WXZE!CGnShvES|Hyx6cMz=e(+wT7(NCy*nG1z^i_CHOoojbk|AwgTm?$r z|AIn_xj{a4sgW_G<&=~Aib%Av&{5x;tEa-pW0@YwG`o=Lh{fz;=ZQTmk-#Tn=qD>O zq?)`{^X}$6%dhgfQO=SdeOCN;1@B2r+h-tFh7(u?;N`@L6Y<$-?CAERJ;j@K;aMUU zZZ`gytL*tZT8$@nmlG=rz*{X?HK=IC(yq^b{Yn@k{p5aHC;&6&t#9P@?*ZG^2vt7~ z@`SPr05-`bK?};~uQ5wmeE{m!o7^P+1S_(j4Csv6^K$v;#D%R;!nS?Dofs`mWDzAH z+i9+`MR|*tWuXy=h|1sDmQ_=bvG)U=2nFgv?H)4LpN)X8lT957wD`qm9Bs{QyOnr@ z_}5Sk0@&&q7^p^^0YlMRU3BR7h2*&2KJX|&6ZwKx570kL;;4j@1aq>4V~VXc*o3tzlznKgdPgjMzGJNKGHU4+d;x5AcfUmL`<>#4t#t5>-zvj?svY!f9z7xM-wF?!n@bh~|HnN3Xym?duAGBJHB_l15-_4mVKo-=``?7kBQ*^zFaMKg|pnx89Eo z6y~6`cRnGw4&b&}3}dBq9YRa4xRGkFtKGlHoa@)e6QM>_e-=+Fd+PH`KzW={!Sh!*wsCo8i88r6j?Q4y%B)Pxaic4ytM-$ZAax{#G**W{iF8Alzbt>G>@+54iJeEI zuOS8ZY2NAQ(h2!Gh1Cq+akG4H=Z6n!5)!n15C-^b7Tx{6G6GdcNB^dpR$OPynj2ta zg$#{X;r+bWlz4E4&qlY)Dto_?fu20NiL~_)NC~Iv+~>TR02^0HU_gaYvAKxl%a7>k zS^CMPiBK$V*Zv=uE1_Wd-HdUR@O_kB^Vf{{aHu4{ps)b;qfmoCX9ae=3%( zX_`Fb{yyOMl)ixm3WJ@$_*d?Jw6I zBh*uBK7Fa*t$b?ey`5B~;N;nJzix$8R@TpbQQ5W3BWbPnqSE_p>@vo|h1MmG=y5nv z>#6#Uw<5MYyCvlJVjxjUkG!=wyo4#CYgboN*XDN~LHCTsiqznL7TYC!8^n#W&#?TX z6NGom>0+D8Lisbed97!W%A_eJD5b6M;IVpHDLc1PX7MYWQ~9sTT{^zZNp_|3tanst znfoQ*S9@lTN)GhJzgfA>SeW!8c*s~>Mfc5gYvxx)k%;E-hmmRFciOY|?5d)#JLuvf zU=*!r3o#r5`_9bw8JO@5+W_f|xRM+nud2v?35@w$a32WkGCO4)hM(j9pxFBAB#w&o zOv$f_aY11JWyT;3Th6&*)>P=a^U({x^c=bOg<5klPz!m%8P3;tSbssxdH~M1QNNmA zc=f@?#TU34r0KJ@p|!?5_49ciHl z_Snw!;HYZI5E93bq~W#(ESEW=?9mFr$S#CjR63ZGxK4K}o`z!H^`n-W3B%v^L3=Zg z>3XbHNg%X03kF9puoG&7(F?W@kFPA6ojrSY(ML$_DHHvSv*{Ke#p6t9Ja;#Gu~^F- z!aiWxp0L6*gA^hW85;$sII^;ZW7{%PKFzP}FelZ@p3SCDBFG@>+odhe#m>1pQ@W7jczFY3#H91#mOmd)tAR>_P zoD&i#5@c!j(dxXnm?cG3T6!S$=Of6Jps7}1{B(p~D1JnDp8rmyc3#r^8mM?GZBRb7 zv9`kSWdF7!XFjXE-DBd0J0b$>2=9VnLz=GU$fm%<7r4~_mUse1ce7xGg6i93URYd# zuM9k_IgF=5lP3xoSG(OvXdG@LaiK(XgQ?7dosZ>Z@pMCNiQ|tXLl46^>=M!Dy;r$^ zrcO=yiHz8MRCuMm%7ug*^cQ7soe4comsEcC{`<6grhI%VRGVo|ZGzCQG>z;ZsUVjt z0*@y{qi>aY0NO7PL?#?w^;>jFtm&7oJ1Dk9WGG>@9!?uprPdLTbCH!>7&?l^$}|Jp zt|$8knrj$?bkGtnTkvo5vOarTv!*fZno*%f6!|U%l$|*Z16UTh!Lk2@&dE z=u#||o2Hpw7O*^E^lCJy4_tHxSMMhZ_VdKxJ-XM7v5cQIe%E1R?qJeIuJ6kz;x*65 zNOR|#+*8=NZlW!)FTAP*g8yuj;joUuAf&N%U(>J5p?qU+538ntTRVDX`a>Am(#r*A z62do$Xeh*_V&tv}zDV=Dzll`*ID6>1e-WxyvifLc7_SwiuTA%TLSjeUW9i&|7wTEC zul2VUr_LXg9@@TRpeV%>u?iuj+@!uovO#U&>5-!Pfx)uQ&Z9%WG|6&iJ!z%0dV8Ulh%wxYib1l9y zdA1${l9l&ja9mpGh6)dGb+tSEjhMv@=04+xeDPr`mcAlfXclK#?t1(9G>t_zgEbVO zaG+=rK8|kQ<)mc)-Vy6%Vh_Mq^-Ar^B9@|v!9fk7&7r43;8jCgY^lr!42SVPK=A|o zQI%4cx|SbPQYfctU3)QgXZd2NRGHkK+0`6-YkR z9h;g*$zsJg>#B$nyQYe_cah;IpqBJ?2tK%4ou_52(j|WmW5fft?8oxuacJt1;T)Q-(vglrKLoOOM z5s`MItn=%S83>bLF^b!!$#oY>wnPa2o=@_KLUqy-P)sDJdMGiA)Z_K9MS4Xy)cBg% zxpj5A9Y=R^aX8vgM3>f?kw?77$)xfan}kDRPEL-=)9O|>RI_(y_DileHs{%#QyY_N zjtL(zV-*gkTwYpgwxajW{fD4PXd%($SmknXSRZc|X0Eqeq??yWue9)2!XO2X9wB?aVRG5&ZF}!vAD^_r$_lFr#M^4Xs>z?+cgC^kNvp%21ZE z1A`%Uhu-meP7-K4J~jsbrgPt!B3D267hf9E50l;!uq9-6d^`=tgNVrSf9mIXEge-| z#Q820wSA$}eV211Anhg*vY!4VYI+7ieZsyBq8%yuyTviRWt1<-6pYt}7|v6y%>d>( z(9aLrG&Z^utylyJnM==sY8rctdU#7`YH9ou?b&j#)W$nRsW3i19!gVp<}kEtVD5Xa z?b7Q!FsF+O%&&xSYIh6o?Il0ETZ{beGlxk7jL#0J_@1me*q_H|0o%>wt|xvIx_281 zUxxZDbaq026OWmyuX;s2gTtY9!&4_uUUG2YVA;NXyUFWBs%xltdr^$HTFLIajoG`n zAERh158KT{B_t#+ZMwO5Fgu@(gMR<7^PEEvlY@xYlih5Qw_e4Ddo})1fm*`4IP#0M zi=Of8PkqzdPf!0&j&Qy5Niy=x*7qE~7M8Y}$i!d4*DD1sI3g*ToMyiNB&{w@jHO*J zUhF&lV47Okxjl?bOpy&0ng&d8(8h3MrTgqOi*~cRaou_YWO#4H>kiD%&EcF_AzS*? z$$S?6z6%Y?9t56_4dj?^CPE5%l(Ai|Zer}CuMTu+Th0nqGrou&kY&3>{vz9Ex0wM{ zBIAcik6}^C*%~9%>}^j3p6QyW+EG%_vggig7Lna!{>1$3_+W(jy+_EEsLR@7u)^c6 zQS<}IfXf4nvETeIMlJmzqW^76gTj#BJ9<8;~!ebTI1)^suABWLF?Q z$a?VwCQ6grRdBTNc13!h;Tx5_u-X^{-LLV+L%z#bP(3s5$9bNEFe+NC`S=DRa(mJ? zXyYe(vi3wZ$d$2dnBp(MXgF^{p77?+?3J>c%k4ON#idM+K0sWZ<6F~KOpAQv1(%{V zv9Vw&d&gAEQtQ0mtwDJF?4^U4ZUUZFT$o7~yv?vnDeFD@Uj9tWwXvG$1F#b=M;@>K z{up-NFG(xmJgypjeTXuxir~R0`O*;eCrVgzoB+o>J@#N&lZWSo=Z@zwMQJ&2QN#?@ zpH<#>54Id4zikcnz@EmX($wdBXKGU=!A1($W14%@p2&tUhzIdKHy>l<=Dz#XS)zp3 zo)H?+LoNFaq(q9;$|Wrv;n4gLA{)#s0T#RaK#DL^B}^;(UJ62^*G9V04@_35&Cr%i zL5&3u=45})l8+CSHhb1#G?BT_#jNfnRq`dj2U1Eb3#yyuI|QF?np;y}zMO+Hg{XL6 z+_0@9Hpgsy`13n%MlNd&jo(l^Rc&--+`FhlTHKs3I>dRF$s7npD3f8?8XA}h+Vvkl zn$s}|Z8p-|&D8(;7d(6bFT}ax#(lv5P{Wp&xk>ng%D$e_f6mm;Mu;7mQOY&h(9zH^ z|2fwk4i(5h&h1PTYt$9rw(O%vOeL(xMe?9pq;$;-@(C>;tLEOKU5Xbq~EP# zBB`J1Ut5KC8;I^NkJ_cnAk@UM?qKkDx`?T!CuiP@7b@BelT%tpxa%d#=Z^@au=uJ5 z+GzS%U$}Z(=Q>OlgCSwGeqbu+#Vn9+pNu0$OGhV(lL2+$W3UO6Mn&>%C$$95qk+QcRUkw} z80lVEUgiSz;C)E}eKw^;<>B{GU}-smN;u&rju>T{T3VQUoJ)t+WJU2q++&I`yK;uYSIBnqUGkWr6L`LJc2|L0ro-s(98-MxoA zC)|;e4$ng$RF}4xt{tjnhc&#g@l(g|3;OkPUy49l%I{NuZ)SAeW|VSg5GSBTA!Z)X zo&-PHCniQ6c6zTJLb?ni2gi99m|Ad+9U5!ER8LhD6F5JEegCIskU`b_7CVV?Ab}qH zLtuuXPh0vi$@KUtfcoWPWf)a{j7bejiLkJ+Jr#XiLV&qE9Rm>=EdP`4M&EPj>&suU`3@c|Ld0)6-LEB+N00DgS$$)!;0bbROBhW3?57)_S0Cz~`py zrjM#2sMyKsM&2(NNLx(Pcnh6$-N@&E+1|fjxr6;bbqSF^(@OvM35ciI!TJBE{}`e% z&MPJ+ws-HaiyU#01z2;DQSlEq_rD*mgO*z2Bnb)0#;;#dQBic{QABlR#t{$@fN>+j zf*tk1KLwXS5L{{^q4&o~4csE2x`PkKCeaK6KhVF!r@fgO>CoTLZ*gJ4_sp4X|GsD+ zk+STQ*wdq?rp8`^YP-)Cc?!yFi@%dBsPI3}&)-0`fT;K{_x4_l#kw5w-A@n^niG*c zo_agmZ-_ykwzf_pU;jOJ3`=V;OYsTO3DLpxv?hs*xjF&2k-7Q#A^JpyG)-ACOvqh$ zfeEquTsMqwzn(?1U$Cn4G^=f-rlnnh1Z!a-_peY)BK5}XSeZ;yAV%=gb#STcSm#&^ zxwmicZ@}Me&-{uyaHH8JkDNt_hIyPdb(ri5W;*9C&k6oTfcLvNO4P}UB4LP<1&XKV z8VNBcv1U82SmF}9`TF^#GLMaoLEaR|GC@UqNzk6`e*FO}YJLoje@g!=h$yEM3t4#D z8gd8^v4UdiR#A~i`qf_1mk>0zXHp1>WB3$0tJI!JtKRd<7Bs|J4o&?#3-?A_vAOy0 zpIahtyP1)}&T_5j9**JNEy*#rLHh;@(u-Trlww+n_@pVM;bJ`XyQ%m(wzH&|7->2g z%(oDSc0TfPG>$YT1IHL8?Jx9;mrc$lmyh58r;ze{^+2Q{u-~P94OMm77%`T(+oal2 zp3`AzzZ^=9ix)1i8Rk$DKVKm2r8}b&Ln=ArQKpVC>j32&zAtHxg^W2%Z@+Z-wBUz~ zz>vLr_u>^BxM9VqO0MJmH^lc(6ZW-6GhO6SR$0kxz`sf@acD`}7q8gh9H~y*?_>I= zUm51^HZgd;CE?fM3`u=qswB8r8FAtuL{~bN#(j89`W0;&n=j4^(3F~UZvmP?X`p1P zyC`F{u2(1%;;L(HG-pa$u@i1T+$6{C0{a zm$)~lj(bG&MyH(FV#DUV4Ec}2$97%ozaobAS^iFujpjq@r6t;oOp@6;_%=Zlj(>lX zXl}*kt@Af^Zsj%_@?(yMs4r^#3IbI#SSD96AOUudW5*7uFl^Ej2vx1wMf6SgN9({J z8W|-8`b5XX;CdRAt0czv+0TbLKHjmA6GRbu;A(c;cf)#SiF-euy?CeIx`T3!D|8c)EECvy`~9X-p|w+JxpG-3X4V5oTjA zX>Z?VF;KRj;F3|mVcdUwZmzU&_SD|WRJ!{P4ZnedKC2!^&9s$E@wvnzK=_=qYeSfi zfyG`_4$83%3}_SeiWm==PVmqelD@#POV^E_nwnF_F+5-qnPCH}bN^t>4`W|BC}dM; zn-?E&3<@@O^^z0?aUSIB5 zQMnbUYuYu@R&aVLs35a|-QHmL@1$Z6d%NN!OG_`A^VRSFl$+L`HTtHh{muDLhoxMB z$d$40*AJ>G@?8wjq7N=#iS4l8R=c17I0=7DNmStKML;bUV1PB%YVY2?yKh%3B#$j382OjSK|pE2^k)i70xf^egaij`CN!-R zYlhsUj|PetRiVTx7A23;sBJ@gfX3$Fuk%?ZnwT;y=PSdI&;ka_oV$Ud#LY7)qGbJv zl3hC#s!U<@v;M@KBf`V`sv{fu9<36xj4{FH@ag)#DM#cK>2=$nDs=#Jl^qleIc$``*w{ zBySI96IFb#=;Pt^iAR)vpG=H)V)vQfJMSF+*jFuDlqj1Zy_KVOTi^5_J4LVSZ~jzC zKDeNg)O|!E>bsQPR+-c9zSrrB%O4I6Xh*M?=hspEAR%T-URCC4)j?~GZWx0`&KoxE0zPpVCTM8==s{QJh zD2LyZn<&tyuaM{qY2qI(EwIKk5v;^gQg_iO_Fq4Jifce+t3ln`jl?DA(q0CmBz-|j zIqxU1UXjs5>WY+1n~gq%GH9eRV#`zyo)>;`*t>QMIc+09Wa@-1y-yc!DPau2zYh+! zMbxE`cMw;n0FX7f1TJ~^@N_830bFfoR!SkR(he2&KB#%kwh?{-D9>Pd3^xe1q59_o z1FRwI7$&tZ> zfr55)TW?sUNC|ZIKb#DEJR|+&_?;_uao%ALvKG$^y(~u6By&F6DR|g6(U;tO`19NC zGbz^7t{cP4ZNsrVVaJOv{Ja+MewW==hBHwI3J!A2e(d-0-XVTRD0`PKN6h2+yDAA^u7~o2g^oodOF*yqFBgKkNXj^g@O>Qv$9$M&GI>y^&XMzx{;8z z?&j-HE5^)@-2J%k z$LF0&LekRGB`3%7U;&m5OhW-}pt7dN=jgkXZERSD!WgXo;ul8GfMT4%K%!5n)P9-W za&rFQE`e)5d`C$`VG9{T&ByDjxhpVX%1P^EIO+KVp|t0QGQ}hEl4_u_2-+EUj{)6; zR~6iA*16$T7t&shZRB#xYC;za&ie28EGXRJ$-52`*10}djg zc~%30a|5ep&mzX78mjYi9_JGFJ!SD^Z|?^UhrXo!g=BR0MY*SpdxSqaD7>SQWi@rN z6gnnbRw~77cJoYddG?C&<2|dt`?{qC4zGw7e>E3ST)dexef82)h3;&M@|mi4Mcnob z$7dV297;F!uJ!WF(%ZdKv>2Q_{g%1>Ys9o}xQC1DSzTd9SB49I6rD+9>vmW`r|0he z8Dr^-dlF`jU5oUz>et$NOFxx=U6IXjfo?>cup`^&XZme!a#FxdwP;uky#Mi-Xz7^d zW;`4dk#@}T>fghmBW5>-GlVzY5>B@v7IX%m8A;4h3F;jgsfBXJawqTp=9ZSbFW!|O zjVmaaK)WkEE(B2&+PC4)&vCUeUEE0jjCDLVC7qJYGBV{CMP4$7&)j!rK5)o01T0yq z8+KLQ$|LNG=%J4w6EcWZPaEP}P88E}Dbik)F>?Pl2|FsYkYdR)?Qv8C$>^-0A;T2l zDRf>?%_Ueh0XZb3C@Mo+x%b{Y0Mf7Gl{-ug-akG~%!MIa+_|BQNuRJ7 z$1DTiV=_PXSN{AR&$#!FmeZ}Ne$JM;;GWA&Z0ms!ojx{jGg*-AJW9bsT5*DxCBx|A z@B1f){d&xWDqjrP8!2s@ZGX%*pwPuNa?E#pPhZHM({v|ic^k^w<2(HnD-sU&p{`eM zM<a(U&clR#+d84IQc6ABZB&viQ23o6!J$OUfI_$C^i4l#65Cv z^f^iMq7)+yDwZ=J!5-~{v7APGEi5Ma!wqchwwK0Y-nYNCiVF;MunE~wAH);iPp_D_WQvGWTj@%{*Hj}|kVgUA4x z|0mZc3}WdLC6tc!Wp@E3jNVX1*A7vE@XaUInlQ-N@*5>|29<Ya9-JeQL>XPTSYK zw1v}ez%&^V#Q2kQ|;+Oj8y(iU0+~BVUrSZnKHk1;WL?AN;P1A!ZQ_jUZ+C zHJ&Ifv5XVLd3r(Qs*T+U%EHmhRZH?HfEdC)oc+d~5Q2(3nq`-*c4p*BccC)EvJb4y z(Szt(B2R2hmn}a5z88a3U)fo#&%5CsT8=Qa^s8U-I%Kk(yKhvW=vqW02G)P0q3>pE z2$;XFcz92?!^_ceON1F9D4`u{PA~ga&VPy>Ti3Hs{AdjR?s)w;w+f$`5Fq%}LwqE}c=T=K0y@;x6}|O+XLKpW<4h zH{wmdb0}0FQHsgo^7_aAkB>sWNvW_1@jBmhFS31jkMkXCB9D^qCY}Y8!$nuyDvDCB zXcGS|%|epb&-xI~KClXWHT z(Yvia))1wANG$~FGybeD-xOZRl32kHo|D8@hB#_{32fWa7b|+9CrhWZBb~Y{T{Ox+ zD>$iWJ?_0(m!XNiUnwao z+s;pJf6WkG6q@p#?>y{9?iNlch-Mx+0E-B&6+7LS`?q)x5m97YtRa=cXVx9MDatd) zG5W1-M{u}-$3{2*0J=DlDt2bm>228ka@)JvlSAqkn1Z4&?pPib`l|Wu(UZ`MHtkxO zdS;PCW3Jrzh0r8_m4V=NekbeK-++6$?? zX6Lb>)S#ZTmm1769`kqxb8?IbniQ(hhP)yXmlt}FIDS0mQzhGxUGHgGmbu->g;73w4wSuca8I>sg9Qb->a#?hb`H+r!^5+#$byR|u zuLzY7=1F0LK^}zcRs|n?XdUdZ6UL%lkz6@gxXBa~>kK~*4vXK+B@c#O&w5>)kp}`*F?m z-B>CSNs=0H96x4C8;*vA>Q;f9Otrlle=n?d4C@ zltt8pE#`YlUQ`&Ex2QZ`efWE4 z*|=tA!NHC{b(iYb`nx`w1544m$S&?O;8TBe#$LiQr7fl)8=9E3nRRek=uDK6$3o*Z z-A-fS-uWWcf!HQpixNe0w)(Y2J356*42A-+3oHQ}f()rE;^bq>gSh1onqUPV9-jBu zVo;AwvqQR&RhgW`4DujyaW)bg+Dj49cImLt&j3PLm~J9JSM>X0?UkBZ16rx#({D7X zq7vW5@2*nu>|RVxmi~l|j&KS!qci7!ZG#dAfS-clSt}N)CCZXb(Dz!qalSEy(h8;om50T zS5_k$UEYKE@%_QyMyQoM6~ti1(*v?7FR(v$e~PyC%dW1jzCPx}iNm>+ZbMtmMRfMC zBu>g75q`mS=lj&bw_6a2k0JphqF>CB=4j)2$h>wEGxAH`5c?FMM)Cju$olSZuKPCZ zlD)T#itL#bA(dGXvPVW%!zyG&*<^=AND<1)EM$~D(?Yh)$|_3I(tCdI`+1)CJ&yM{ zo$rPDej@w%~$I=o46pe23DRnZB37=Otr=8 zKt|{g52y8Z->nGecOB2Ro;;&zyS{c{aLxrN}i$eB6F#=qb{x}ePm8ooLN=H zM_*y|(tNJ#(*Y6Klk{>}IGr|2eXVJFeBLtPccT2Sq5Wk`U-QJy4shuFy)vTh(`0S+ z?Rs3JSLfjV?3AY|*DkylG<>ieF{iU6b1RN z-_E-~H{_0L;a<17^5w(JJJ{M#$1N5fd}Lo0Eb zo{XL>G>I&lK}4sM{5*&aP}>;Khb=a$^8peSR<9P_E? zu-jGt6V)s#kb1lQ_0&azp%dIM;lNG!lNGB6b@bsw#&a;-xtUL;A{iVL6LaMiuhsje zU*w;+3p{mr=3`WK1=%njTzkmt8{t$UP{g#7H3-r|U_WoR#}*dp^D+x5a<@$^C-c5_ z3E!PGkMg0iJ~CqNJ291gJyqO#+4n`d0>9BObS}~*1NRC$Wb`Ad!&q;;)DtbkA_k2$ z?=-nZQ{6i_Vx8l@?xobrM{#;!B@&>c@*4%}zsD^&(`R4*h*(H{w?DvK_t|{>ylz6x z(#7ssi8c5Ad4-w$-tF=J_qQ=3H{pc!KT~!%dCxi*q%<{tT=|UV6*%D~{r- zj#c!>sz94m{U7w74Gq3aYY+Ngch@l#UoKwqY>u08#?5vBWMq+bfICCQk3P@L@c(Mf z_NYdFS$_HY%E|_~DS(FU*)PD#_*|8 zP*BG7POR(!7ObJNt(ip339W8n<4DDs7`>miku^=-`aJo-(%@zlbJDMqpJx!o3*1&E zk%MapEB|$6c|`V#%XKcoIbfoE#pZvN5YOns1i)ZyaF0duJd3JjU73mwTgPy!) zTn*MDTui2P{vxmfMtgm(kX9JJ=g3IXu2T16F-kNu)<|BWZ}wbL^t(^ zysGQY(eL_v7URFQ99UnhmoNSR{8Kvc&2jUW-{1123Gr7yL*_H(N%{B$j6)YbprvV% znDifzAZKr7XWq+~ab9IOYl34}zMdqA3DBWEvgCg1rC)8O%2y)U{3Mq906p2(?e}19 z=z?(9blQ;bNGT>%v%F zcYdt=M{}}~d#s#Hb~wSZuu=8#D&aTwRnQ^GK8eY{cZ4%r1ZLn#;lHCAjO#`;^>X&e z?xJV$FKMs-^8I`2k8ox~+WX`Oqn^k!B%BfH5m2{j63mtt3cU}O>ng;Ej3FWtFbNu1 z4?#*GTo9J0qQc%(Z!^`R+jEB7=&wcGn%zw_NzND1^h^#CF!C~E`o43zUZ<*>)uNcg z_soKeBG7toKvwPgbi!j%5z}-pzE;Uzsj%5ha41wJnSv;`?(|mWG$6h1TdzA?jLrej zl-9wgN0F5_%VKT-_@DDUPpR^_o6_fa0s%soRbOhuzKzDKWMsS9Dc&>B{iKgOC|w+Fyp_-7mG?pfaK%p+ zGK$B1L&tVq4a*GMXcXSB&Op7R%oN*fcykX)FU#u^iNN&q^!bb7PZ~S+No)_J5gUm+ znqzLR#vRB$L4Cr}|8I7f!Lxv?--NXg3x`3PnLU49OXTng?>M6;m%rX0(beHTtzk0J zLVnnHWfHetd&p1&TAL|V-mAE{EBdI6GI>;9iiwzeENw+YgF^a9*@re6$n6}EzBi%ZxqUlZBoK8l%ybBsV8W$TO zIsK{m$Dor0Yq|p!Q^sLfDCQiEdz9F($EBgr<3ZXEm26>~2n|9s z6>-c@2LL~cFn4K@B??IJhg`Oc=X3UzJr`hOnk{(OCLqKdnvWvOmcrbdx0j;G6l4uw2*t++R60eiPnyFX&hKt(5V{R5m~p38Lpqyzl9PkywGe)hY> z?mJJzM8nr~n=`XZVJLkQoe};gfX)q^;;>>B<3~$k=ik~+6N15WFWX7A6Xfnh=sk6O z9QvuC^G;AHwJ-Z|q7hU%3A<~uH^(GB^Y3-UV#sZ>)Ng^sY% z-3{dW%NX$%cu=<0FbetKu|U(D(wcMi#OkDR?QvQ2+4cdeB-pCvX>ORC2^ z50d|*%}nCu`Bn}W2(k4eYSFIIH`@=(6{g;R_ijSJHvGP^_mg^%tzZ210jMe6YgqPW zP!)rncq-1`nO$RX?gva8QQd2>um)ipShm7M`HM57;!6*Unce}}I+Gd>j*IHMPWoLQ zOT@_MxKDYsi`N|PC1*vHjP_c^JT}pMr?lL%n~}k zVJ!#4Vr-k753U`D%WmO-ZCI}QzZ?qnEx$tls^(~f@m0y1#EO!wi9hP2qG)id;*eS| z=ZXi5hdp`y%q*|G=yn#_9q|N+smdvHQm6G%Y>2=wr!;-^UEn`Pn^KtELaPXIr(G@T zeSFr@qJA#`ADC7uMY%s-Q(7*S>%iTSg zA>?exzz9MJay21bbon097t#hWwRrq3x<$d{TYRciav?XyAR>iQXpebCtB1SmUFPht z)(YAqyOR&^#?TX<1|bKB9I;)yb+}*(WKk?UDI4ICP5thX^PrULU^CwQKR~vi^4B|9 zqr6?kM&))?%?~V^vIl^g*gJ+>v%%s;-fXFUee<<<$)ncZ-d>N~9tcn&Erc$`qE>e_ zZiG^k>0fGFRQmwm`Vla5L^%eWJBL1~at58uXquR?KDg-x*u`c48cO`A^;f(clkSgR z$0X6_s$pj>tbHp1zZ)*ywv1iBh-FEna3ptr0v0N1k9V+iNBV+D#ChNXkEb?Z{Ic1i>jh+x`9 z2R*@3FP!#<(LMDjQYpmG*jDmYy6P31?KKmqR5!~2zuw7Eg5rEq=wWj^Inv9PHFb5! zMwC`k;yETfQwq14ZmKZ`qlV+=OK%oA5shy=1oVR%2%+*`=k0?0^lEx`79JvE1HY7+ z;UB;la|LFyGGxx7ZmoIcez_vr zsKPLp+FSF&8j2ehE;ll*d&zrt3mB{XSNp1|p}ZAI4vh{n#t%;!7g>QLhodgpriIuh z?Z3;rts!;h>^sC)^W;fXYCCd{?FeIXw_Pc&t}c`JYz|&m?DVyCcyZfYfYxm1?P@H0 z((_uLDYFq=mGQU8D>5mubd*iv@K6;JYE3Qp=&8s(V}f9f7Ma%r1K%Lt3R}latA|B2 z5yJ5&Dn8!iY6@p(3hv=f{+^HeWoPC0%kn%;2^$l}jtE@96sJ{ak^CSI+J4{%3yXZ- z!ew#2tS#P+z?~i_S~sO0^trIkF7R1Qv9C@>z}L*o3>>PT7Y|!c*6VM}iiyfblp4oI%ZsMmrAGe_mY>!n_5P4x5rhRaYF5%@>cVtMkXscO_>#KIP5wg_rl>%Y{= zr?DMH`2=Aa-uWPa;;35zoHMN;?Ae@;)kVgD33%KjmGmETEPg$Zn+BpxB0jpJXcWK-8@Tk`wsu_0~rXj zno%{sd`ZN%K&$F6m@W$buX9hvfjus-G5g`m!<+fU^o?~jUJDSUGGsvb$5XJg7*Pi^ zIGdL97Z#(qIKj~#nK-`g%JTw$IoxZ(_LT52a;^xY-$T3deP{)=OLnMB*?R zt{$wqz*Uwwb_!D6SL^xL7T~p48t`L|15OC|wb~z#65&VgGZB&8F5@n@t^V^gK&{&; zQZ^w25EpBek3|cvQj*M`8Ob_~2JrF1BzrwJHkp$*3CriXHQ;f$kZxkBbzW#bm$#}! zq>~nPA*?a|@YPyNH>h#3EP948G%y+L?Ci)P7`gBrHmeCN05Rm8U2bAU0BYDn?7BhE zbdUS6u(+6kh8yU;c_z&;#T@+f|MP5O(qWrFkV#Fzg5dJ{AkAjDJunT*2f(+ z!(4-8`g;TBm|Ols>LoaqZF)9>Tsg0i9{3hb4|cNfCV=Q1%jTrx&phU+jZkB(R=(p~ zK|^~HK}BK@fJamU6T;_cdQa53g%Kba@P+-2+k~?Z-ai3fW~pR=6+?!C#K1hfZXRpy zzsEq>!nawb5A#EKDnr2-0EMml@+_=E&J>jZe)sqHAO7kN#>a?~UwBp)FXf&dS)0Gy zvQD!fKZ*o4TjO|Xh}bW}WSD6}$5oa*?ItX2xZ^srN*wt2aQ{C>M*>o4x_dDd<1h}x zy+R}d+&&%f_rR#H$e4h+EnhY;W)>!`ykco?Cfk< z6d@u(Q~id=F~=|%5bE@vScuDD4?Flm5%VT=#Ne;30Ho|@y(KF;!&^qpD#FW5NRHhn z<+-83O=z0=k5M z>t`jL06*6TCflQT1yK2IF-vQ%~1u@f?0N=kW9c5rFVPM8=o zYKiaLEfN|a6>r5uD2+^;`WR(COfM&-NTem1kxoLqxnLITIrcLqJ|12cZmR4`h|a^ppg5jZ z1WujLarTJKLEuq!b?7aQC@EQPwwmC(+BVDzWHim|ST>~nEACEiKHLQ91IG~QeFpyg zPmyh5Cd`wvq@($NOBEvySX^D*FMMz`)7x2CAT&W4+5UMq+6j=X{IXTU32S^c(olY8 zO=xQ|d;i7a2=Xf1MQJ;frC9N=I0|?9)FMzwi1LUL2x_9B8zG00f>4y_E2e)>9pswX zHCP{mDe)kljnYw27_1AtMY!tFgk~VA4u?!9KJI)d(%HZ=r8!5{d_t#x-cvq-K$3-Z zma7TZzJKpdJ8q!2+vxT~=Z2puub}W4OfyB%35~3AA!?sb=(-UfQ%QQOj7OYpY?qZNK1~Yo;199XK1%|-~bBmv^Z)=^x#fzU& z2SJ&q?4Z2-c|(+bV*X%d zm^+!w2ww%QNE41@!|bM~OL#PTP*#f?Qkf~KJD~)?8>elviKl{MgA7Z91lDl+@ArI# z8$%_8gxV!SYUtd0CTiT(4zu6bvKc=4q+l~td&2*xC5P9zO0q@F)v{3zhu4xD%7F`mXbH>x1-1C+Xewb#cy*4j~oXRaE; z&!#91Pvjst9NdZZc$4ZmSN5KQB@FH|A?YKB1y{Qxt6xj;g&kkH8gueYrJY-S7G+R# zrHNZTebt2QRi9SR`+vT^`IUF>{`a@r?_S|KXKu5iGSjPE{`5(}51qb(j`w6G={sxL ze-vL4rqH1JZXVG%;XOaiFt}QA`?AX^{~phE&85~GlzZhc(OK268mP6ZDK z%+OgoZ4PBs&raWZk-!(aut%?3uO(L?1$QJ&=q05FcB{CP9~AA8xwyUmQ_gHmi1nLx z??7~cIPCat6Z7+E$y*32AJ{Fnix90gKa>CQSqkBLki$gPxQSF0oTd~xpA&fiUeCXC z0uV2N#*m;?{1(}As)Wb%3Lz|QkGt`AeRa7eH~zm^!0HMT2W>4<6c7~??W!Sac(T0t9F%#e^Fd)o(3x_-sQSx;dFXUGw~fWHjL--9Dbz8|8vR(nd!ZTQ@c~Vy4xs z^TD&jQpr(p+b8X%q~*~rF?nU4Qvgj^=6KK@T-%}d%Sh0|ZOYAXc;|%|VlKfq)sx<5 zCsnaRbJz0hvKe3hkx6$qR93XBr_PGQkOK^h)hpHYJ^DL>eYWge+c=jcgcK=@3_Ka-8(coI+G|cJ&D=r(bZgOcZ_w0&bl_eSszxezjJzz5u4=L ze_DG@Dwn7Br`&oypV}``T;}qh`gC53%hLMXwxX{mb|8Djmz# z#AXL(2i&XCHo=e0e&!u@zGyVr1W0aTNsAz zw@hQKjq3d@f$Me(T8h{gN6;dD1kbf|1%7YYll&ZE@GyRKMwB^j=WkznXBHh_#Q75jz<|Cg|o?jq+czj?i%q@O zPXJxgmQ;HO^>)^L8mg_V{0hQ2p(OC%GRRezm>(jy)P-dlCjOH9mS_$Lg6SfI$pmPl zxG8$fG23oJV0%ue40C#|T^n_>EeQJ&7M_?%0YWb=(zT7_%lJV)5;)Q)u zF8SZ}7M$L_eBgiN^lkCK9||3Z$~h{!^7jvjl@DbN4h2o!T-%j;JnZ-v8WwY_)<=qa z#`M_Fi4UjG4NN_Y`k-z4M@KhWFVrW)Yk2>+h9R?r;*2Zioj)9pZV9myL1nkR zg#PT&5x!Wxh-1?LmGo9N;RdKTvx%$Pp5#(ukbdA0c)n50CVNcwBE;IdiKE{blj--8as`71HhQ`{V44|ktJ zY6wOLm|V0(Om5+eaNxd&i79gMIn)_*O-N2&1t>=_g|$-~Nb;&~{He`tM0sgVuK!P&|AbE*lr}>u)+IGaw{vdnE?Z1+gwJ7PKH&MPj`w0pg0-@a`Z)1@9s zMmvx}C2@P}YH8nP&8%zKpko^Lj@&uyLPxzV5J56;;V(HX_u-p9tcY&sp>v;cX~_k1 z!1SOG_I)1rFpaxO-3oGpb>u=Kc@D$U! zSb&wi6ON_zS`|e^F3kH%}~*RWzmPJ&(6eh#~lOq|Cy{CkuD9~AALDvpuySt zM0mG!cfV4p*Xx8nkMmURD;m-O(W#+*xe%z29wXD+>V4LE&TvhLHG zecwsp*lVL71LeOT&e$?!L89_jD`a@UIz1u~ojOl05ktUp(N5>uqOpxQB0+;(qr?BmiaP_5LbY zPXYmh@QTqY6P9T^zkhl=o(T2LEx%qNJdu;c7g!r3yR;#rVuCw-SX!wHPCt9bH<>fpJ}*qw*C21%|dAsdI2``85S?!7j>L`>9AmgwGM8YU@< z^|u~PKpvC>zRmY_G&R{`)EL^ITolPYR+8{=;bquZ0OL8>`o6y3m@^g7sBf|SHrkNX zIC^x8k5GyIQ8EP@F~-3nfI7l#WnO7g7hT30aO*|bwx)HkKlEPFipaGN{-$Lqk8+b# zI5~QQ@e93M_scQrar)@Tr+zRqYIK~B-Vq|*%=^Yn{P>)EOQ51}{?0n>BS!rkFSBT_ zUoMv{Rd!!WEHGrs+^DXeTx{GkmfWWC+q3!6m;0i1MZQN$O9IWsUw%?Kfa>d_N5yxzGlk(bt(@jLH@oSy6_$Jg{DDOz&Y0q*599t0&ye?C8cG&5 zgr@Zd=F?mEVF;@EiJX?>ZLya)b1`Xj$Me%QwY358WAxzi16OS`#KsSNM)z6_8tjH@?DH(jN%m^ zgxJ-marI<(m3iGh)G5)EaPkx{k-Iu@*NmF_=z35~ctlb2)GabC?!#B)XPGYWjh(7- zBA}*z?%7iqxgNjiBR5+I#<8$NFj?qqjUr-d8mU_S+=52j4v5KnK`{v;dR9yfjR}cu z^p2_qNgiErX!`H2P6Wu`sQL5!!r3h!*an5%Bo^rHFP%Ca@s;6bR3BMaxTCR67q>qm zYJkl|d&5d8t!aT}Mk(~vrqd(&G9om)KhAt3*JRg>>jLBVO!h6d~3)-xKb{(mI^a-D+v#M1VP))ghgiPu1j^^cscqk zH8O;BZRH?4u&*j|+kWRnQhzTnM)o}WEkj5mJ!`i1(_dvCt==W|Nbkb+u;N|SX)ekX zj)H&brr(A9WT3Fr1b#+K4%#mC(}6%pJW*6uV~_3-#QDo^+a-FxJ@ zL@JQMK1MU&i>LlL3B#5lJjcrArAmxC%axcj?_*wSv%E}`8<*4I*0^q0D@W$_?b5K< zsAJg6kat~obsG%|dJC?os(&+%`1!>1!`;mX&e?RjhvfcT{QLDngnswmXNoSKzXz9j z0v!qy+Pb4n#`ZrRJv+Ac^l6&L%MQOMo;qJXI#d?0|4?6z{mF2EJFrmV&0n8JzooB{ zGkCE)oAm95_Vz_~D^@Dou)Ij4|CRn!>f)j_)+EtyYPjr2X@|nXS{p~61^&fqB|Bel z9RZqDp5)Jw17JH+Ji|?zVq}l23>rmYiC9INr|l{~-b+t{Qg*2e@)_8WTtbrDN5b6(b4XQ4L%3anWIeVT4jY%VMHG&w!Kr%E zvuVVvgy)UA34dD~5?mh7^r)q`wzf7#G{z$^&)JIhzHu-S-RJ7ezroM5BR4nAyLBM3 zywSF_tOze)Qh7;kW71W&^5U_Rlm)mUJINqV`+&jif%{T|kcWfjWtnneBEr(hhh@hO z0-*)kTR0kyde)L>dT?w?LZx-mro0%br|4U<9LrwH-;k%?AhZvT+s>Mk9bkyx%gd|0 z>_j0Z);QDHp)xo5yiu%>-%<4;BK3UHI!AFWf@V&mO2nv*sE=0Pjg7qS>?(IJkCXps zY@F#`ongX}B0e1VF-zffz1(;#8c9eXqh|xQqx+c=;ijSq;K(Q^?i2@z6 z8wv%Axq)}|?18ssKW*~fX}1Gxp*DWhQl-yzTj;jEe8&BTpFF%f#Ftmxp``E+}WZHfke%KJ_M@?BpJYFn`xN zx=J18+JXrBX3F#y0~-}pd;*%%-_{Y5;#@wi|y_0j;!6`&;{Lo9N_t_FQ<4Pi8{QM0K4JSRmrh>OrIskhn1 zJcO*=Yi!@CV8O1upgMP8&1O%;$|gG}vq7kl$MEcXaT{CZ7BiySz>8_CQu^{yGsegY z8i94KKXNT+n!5)|5^ILT-pm+mwszqyykTPF$Uy279v9!x6>TJ0<~GwzTFJqOw}5$1 z{ujbbAFQ6_lR6AmHp%nrF+zv$sa&uck@q`p+Ig=iLBplt*Y2~%ZjXkpba>ux7#Dpu zwCN+|T~4z2+@3{=b5g`%UuOmCWi8Qtt=n{VokWo=S04yY%O`bNMgooa*|nuaBQ7M!re& zlr`o4nbLpxY}5lbb9WHjaJi61y~0R5ys8tgKpB%wZg*`cq#C)(>A#~Y8eHydyM77s zF-YOe?h`f>XA6qP(ec)2M8v1bxpG-Xf$t&i(yLc)r@_;P$`22?v2y!kl}{L{kx;mG zEk!Sc5oyY1nc-@O-6k6$P9z~Sgxk2QuMkx+HBT>8x{u2fApJT7#|c8k#LkZ`??yo; z*`ck86x<{Q!FLN8ReqTElHUjkDdWneir*H{ZSV@46OCwTS#FbiBfDS%G%Wg(d3oX# z-E@h^=I_gtGPAgNq;7M_U8BnOtjPeI1ap$x&AH2s!O%34AE99t*>XK1rv*Ck!PD5> z#F<(Z*a+5q?|=1qC7bc`mp8{jyn1jbAEU#J1AN1{2CK2&q#C1^at2;0(dRlAAkTiq z8S})6Zp*aDx-%#*1nn}ME2W}dGk4F8FR$Od+jZCVbij{1Z=+zs(2helpl50~{y2?td}2nB~!PCC5VdCC2 zx=4xB;(%2@W$9trB0^@ymqz3fq*1&t!luOOWH}h+5^uLuvwgm=zj>_&don7cW^ta| zr11q!iZ|d~-aQ4US z7HT*k;mRj)WhlOOywh+0L@RYoafA0aN2_7;sG#{Apr5iE*s_Y5mGRCpNolE&vIePpqC9`FjAGv+y3Y83^#w->U3JOr@H(>UG(CYq%%KjS^~W9&>ctVhHOZtx z3!AOCPFRx5{j<58CRwMxc__tx8crtBLU%J_NAB-UlTiDSAGVkANOd((o2Sq^3zLoW$hJQvTZ|$L;;C>*NP!z92ah?El)*fnBei7ylmQ6GdonPqXJ%_a7tv>Ycr%Btz{glCH z#GHQLy9<1P!}Lx7Xr)8t8RV;Lc$_XCWZU4)S9ItBi4aqe>&0n&GiE0(d(&OU0^Gd8 zHuwP!Ghyhy#I{ax7^djiPQ21Z$4}p%U|=+s7e4DvE9DEoz9N&Wco(MMWKsE1oS^x) z4>NwIeVCgQT$X-IRW8W?ROOZp*V*NVfpiPjUrbIMR!(i!8;SQm%XaJ3d6#*We+<_4 zPu?K&^4pM#{ch3Ae1&a_M%1xfA5>dL1RGGsw8nc!@7_Xv3i3~X%_}XfsT6VkpEDNT zzxiW$`m}dJwwmpqGOayY5&oYtxJnPO9~Un14%q2Pd9OR5kgw)?=28k}%vxJOPGzRq zvTNa7w#Aspiz+b{{pPDFd}Y;EwW&L$`MY^bHZs0E=o_{kXuWj5X!HIq@iub5W1DZu zJX^%(X`aNSQshju`2&X6joswGcCtLC`fOh@ZRMD;kD7Uvaq;hK0em1F3 z=kqI;^J{kOa2~9%VlaZX#Bypu4d?vWbaW%S($`ofW%nzA7=i#3{0Yt$!kTXHF*r;7 zUA=<C0)cJecg5-Ley z-P^YkEEYjnh2P0KCE4%CZNW{D1j;w0L z5AKwDLa05rZ^Z<2-0A3uxKV&m6Jh06r((kKdVgt&6`xh;m?A~PE$d^a#XADKcX(Y| zSg(Hj;gWx1{$Lc-&2J0GL(B@Vuv_`YZa3EW9&p&=4EwQzuj|5ItQyBA$KOoWQGVC* z-J{jp{`tIE*6hMuf1Qx-&JmJy@;o0_zlUMirSXeq!2x?!b|LrC+&pMsXZMsX*mySDB za^#j^a;ITUq|8%h0aN^Gl7D!k-xMk~J*v{VP0G<$*!}1^Z2@qMOFg%1Di*vtG_Dbq zBO^9Wuq%J9pAQ$OvOMV}_?6T>W5>9bNn7+qbrNxsMx?%0SW<&=+{gj#$qm zGdSy8qR@nK=chX0x&oU?=lFL`H@v`z87i?Va3_ui}-)IO8@^q%wcTTG%_;s^nBFi zz)ZY=V#*Kt6y)XINUi_*2}!isjWWW#yp70Wk(VE`k*37o7Px7PjMn$>Q*dv>3WhKg zJ(IOQlFvIK=}GO|O#J_HJ(~tzY3Wykg9osy1>b>;npK%v2r6vGBqvJ!##Ug{mX?Rt><>crG|M^rPzHhP&bW_I)(3zR2i$8!r^6uqJhW0`*CJ2i}RRdv1Ht9sr z+HlKYS#S>O893!(-7#SWQk=obS;kNj(i9j(=ssYmdX7mldt=w?IRT$#x-jKz~>L#)tphfIoumAjUnhsGg{@dW;pfL06U&sy;0 z_{>w#XRWC%Wa!`^(6Y&_YQR`vWkW_z{`1F=%f7yZ2_q$6O6QAjzNXW0qVVS0u^T3a z10&*WBK1C{pJ$1s%~(zLCM^{eL|eJqAihN}p8f-lqyb_TeKK z(G5ur0>o#qi=vGUKOnTNmPK`fQ}1I)BXhydA#r&LBu}i;yF=(mDbfG5^Dg+9U6?4 z!6i=HO7?lGg@Q!5vR-2=9D{p$DXpWkhzH!9EOi%-jepVh)`AC;Y5B&BxAux>l#&$MfJa>ZK z30N(tSOWrhoM}Wor_`?L?6hoEDJK1HohbaDKDTA5CqY} z%x1Ew%E}V*XSK1EI@!8E>x8@WA3tQ@RP;D6uO^q*Tk6T7#lKBIFInOqpF?{}pxsM~hGvtF&bWjw0SX#1#R|UkmOzTS)yP4q^3IHtxo%70C7457; z)mM`~+cm_zQGR&y>IY#5a!1eCQfzsgu6t z!n-fHMZ1+xjz1DLlcb(qxeDS}4LjyFb0qM*$bFt5d~)YfQ2H*CMKp>gJ+T1+0Wjbs zOiHX{R@^}};qaEla?H(jL~{DM{mVJYYnj0oE^muDXSry1&{I^(f8@I?K3N_S7**pB z+Rlp8zGo%|rHy{y+hYW=e_P3L18W3cJbT%*?iTq?!U8>N|%NwI{Dk`w|9Wsg2F^g!TukRnkQcX6AeM zuZ4&6Y22YWc(4w`m)$g2k61}0|H`qG-VS#4Dm*aN(#*$6O$HvE+%?QgCVUyQmAr!d zex?I8%JQU_*UKoqw;Vpf_(iUf?sV?)L_A#lXZmzMzH7l;-kiXh&t9kqO?f!&1k*(cRD;P(OvXr+TXZeWBu!J{bNze!N zWILzVA?$35^>fJIGV<_T0Xhj&6}VEf@;+uteykR1%O!MtnTkkrtPxP4>02>mJn6*o zOnvL$ymjv838!C-Pikws10?kvQOj1bNWO3zB!+06og1LU0W~Xr|M_(e#Us`f9BzW0 ztj8Qd4u^|Kg8g6i5_Auf2jy~(e+010H-6y$W-|1--{EP?<%x!0P#q2xp@6A{;hbo3 zZ=)H>2j48Eos@w43_=r^?PQ6}kqK`G9S^VK>0 zpB^`gL2d2enNoqoY#~_JD(qY+BV})uUfc0yi>FxW>7%ot#AogXN?S$iW(^M=s;n?Db1WnB7kv8ay`b{`TF+TKd7%V9Sp zXx62qq%ezs4^@$~J1o7HGJX-{M%-kfpINl7tEzNbOyjX(KiPQbGG-|dB%U=tFJ=43 z3{zmIoj-lMZE5WHJv2A;?-uocy&W)df3o9ei2K{0>8f`Yc={-haU?R@-E$}tGYl$y z{UOAv;OOqATdIHJ1{sDgKfNe!s2*8%iBfNp6LA_masr@Rufs9B z;Ah}GBrTR*;k?~Uyd@C$h^FDY$0_G<#Qz=zP4gSlMtBIKP) zG|!I!T+QIFC*>@)g)i@=W+CZk)?LD#gi-XNs|MSk3fxQg3JHNFEIU%AyK)|45zt<6 z1Ejfo%1h--c}YDtMQ#Z0aSuS+fFudMNL4k#!_r1Sv%69|j2=4mIK7aV0tJvkju6yb z*`b|oI!D>@DR*4Bg??o+KFR%0IhBZ#OF4E=;1+}WZOG9?kEB-Qwk4tp`>#v}yIent zGxm3l*9;k%*XL2|l-rsg(6q^093Er7`NK;s_}h9D-Rj5J{PcIQIO@7Fl)voW(I8_v z7W|~JQO0-I0l&L$u_tUe>GWwf&zasYxEfco_t&PJ25DMv^>>$oo}aswHD}kP*5k;B z*a~c~c6SzvvzhwXOky8Ca>BHpMfEr}Nrh0! zg!Qd(<`J+MZ32tW2H6Jx3JPG}a|~ZC48$G%84tb6*XO?#52mF}9iZ0(+&>vr9q_t{ zXshaiQDR8$ncDx*f@2=0O#JbbwXIH*!Z%Z^O;r{4;dUQKD(&XT3kY2X!4Y8Klp)<+ zifK8)d_lr28{~!N5S2fW^W=e@GE!qzrlF^SGt{y=z2%Iu&xu^7%IfOWht-e9HJ~cV zaeX5gtj`=pA@&dlj!4ACAd1!yN-GK`Ay(OlKbCOvm7;U8$6!A2)@^7Ri74xMobsd} zB`rBBg}excbg9}OA8Lj0zzZIyey$;600nOxG9%XGo~{~nJFh?#cEGtZeLwVhp*InV zNOEz9%5)1Bg>16<9~1WMt0yP=acXv1H5(h=rwaXd^Z%-NG0Jp|O8vyq@*V98yp86v z`G`i3`0gUZ1iA|I9ly;&YHPp*n-&zf#?dxb&HXN(@k@6kC3|V)#~nAqGefU><*`mB zA3awQS(+WFA0};i#DUt+#k9;rNxUsobff&M&&xW#R-W{W)Yt!%N&mEb_2lgSiGzQY zGBQICA9Ht(;nq&%Q#^M7qhf#H);WKrt2V<{mS3YqQe5JfW9iF=P*W0#kv}&-oj0%mber`8Y^cYA#sQD&= zARg!+^Z^fO~s%S1JQrB`O_H^Rvv$Sn&h`|Vhb`C zAu;Qz6v0%&Ej{&8A#P`o7-I?%WfU%;Cgo?S%$xj%g8m3vtdN9i((V^%<9xr&M)6#o#)IGZ{tYr@*B^?GX{T8vY=n~` zU{^u~%m_I*oO(JS>Oi!w!b)<7;5nK%q!n20V42{IG|6^uJ6wGgG>V1thkwknROY$) zJQHKKRS#ELHOsVLQ{=i7P}KDyNt)~4VB8yye4fWk8AEp>gbX(r%5!cl%FMa% ze5BjEtKsXOmNYuutM93-Tso+K74C3x9dw8uKG?&`$+cSdCQM9`BwciRuzlf*RlB9` z-RGBA`xIQ)-miM!k@Q^Gz5g+C2t<*lkN&+My`Gcmu(RFox&7|)B-i3-G%Xv^y&I9= zelbcNO7_kjpek1MGd=r)-$l#)+((_KdZ5Hy=53Pe5^HNuu#Ds*ro-1IpS8?1zd2c; z{B!6QI{cBEvoOlodU_rLy13PduhmvG@kx;bU$5s$_?u?4 zUqRPimI8(`W|YH)&yk(Wr*?+vOFKb)`FV1u;Q1jDv|`l4@!ZUPPD)SJ&jf74p(9bs z0Kl~;IUxWS?P*-~5aA?lpuvkt-SV;*doulwPux-2kiq#LoW6PqTN;#+=`%zzL1`)A zT$%WZ0d6l7E9)cVJk5eB(nCnuoIcZC28u8a5AvdERixM~f&NXCHN{Mg0POKx(*!yW6-F1fwsD4g#137(p! zpZRlwK8_;&cm2KZFHF9+^H?+9Rd4z)7I0p^nrvMwmG9izNaDI|?Lx&YhdWDye{I*h zj`H#Ij&-pNCCUK``~RNm&AOyz|Fr1jg^mR|g@>)rJvKcmUmVJqPdhm~c5>#C>25jo z1`KBh1DNq?m@2Fc>RZ|y=vRiLu4^J)Z~0be6q!ah`o#9U5?*(gYi|uyI9n@hO_`P1 zzF-PPZ~t3{KUw8S+rR?@Z@Y@@5`migDLb~qYQ;F1>`|plMw6*7cvbowPeDY5cEtt7 zy!X^yrZ42L$b(vraJoJiiKW(j@tb^d&}nz$C6;(g-5I{fYF%hq4!8_uv6$9Zyy@f$#fR&G`t8QJP)6 zgxr{-qv>O{=kcO%O`JaHJ-fU5KJnJo%1poBHy?ggJ4flBtM_g!Q4dj6Z~gRb^Vw9c z_RCLg)Wwb0YS(tIO4vl+Yup`{E4LDNZA5R_ds8Rm^JR0#pZ|xf?~cd1@82%jglvV( zvNIy1WQQb_8M2iqGE!9b$OuUaSCpA`$}E%_QYzU>NExXRBFb}ouj{^k_wRY0`;Y5& zCvl$N^YeL+<9!^*Ted-YkopX=v|QroQeD!e#cQ%f2E{3fqOITbc+M@AzfuidhZ|M= zY*5_dQPou^&?ZyJ3K8p9)?l`F1;z}rIhdd8&LYk#>}K5;nc)t46F1ia{r0_6Y3H6K zDxD)l{=`1#t-;}%yjbGam#NU$O6QJK-QC`_LdbV9Jv_KR*NnRD(`TdApx^!9ct2lR z>NS7JOD{#p!C$)xY#zNFAs19mel?F)LJok$rr?}7rh)|+L>|4pP{%ol{TK2)e#}?B zH0H!wTku>6>(J&GuvQw}js_77M>na#+lj?9fP|%mitIx3?!Mu35O55?b^qp*O-42= zXHbY;YGcjgJ?6U{ukp63)Bj8M>uE>dn+}OSkMxzQcC0no+AlR8<$tP0Mstzf`$~+> zw9d1&TV*!KHj$e#3?EL7rBubFpnz#`W$L9$(L|7Yz>~NTAQif;Tf>V3R_3yFIWY}n*ALgR=5neNE7ZKV7k6^zh=TWt ze7+s`Uz#N>i$Tq6&Jtn#SV8XHyLX@jWP2pgTkcA}5+A~pG%nMc*6ja1aN@29z3V=m z^*2qP&ZS+#^-J#6N3~zh6ue44aW^kr%3C$kMYCtaVTU(cKC8-mf4kXTTXt@*zkaB6 zL;LqDPq(=FX&VpPKQYM1zZf35c+RZUYTp0zY&b8x`?t|u8S}tHTpRYx8tp34_ch!K zFN9T}_w&oes;6v;0W$Gzndf!l^XT4t<6tAxd^GKc%cRm7jENC({=g|{@t1`pb@=8F zo452rKMIsXW3Yj?gTWb&R?zBrz|EZdv}q^WKER(NwHMBD{b4ekz<`ku4I;s*{$4!R zF3MR3WKf+={YHne3&?hmjbM-n?!>#9F2}oY<{cl~HXJj7%eIDPY-P-Y?fDjE4gecq zF6N@Es|%`eA_M;?Q0^jELmSSYKc5s=H+~O%s{v&*sWW8}n8;%#WA>>Z7+tGV=3srG zHIQ9c!9GM8C;vd%a1_Ye2$tjB=2ukoM@u`fRdJ6!%j^$;cb=+{pqFLd`8GMZdk?!c zr{VSqp1n6r?;l4PxcItL#N_};4X z;M#>73|)_7H)y);AJ;Uq^4QbT^-PK_38bBg*qDf>XJOEnV9YQ)S!3m{@1M}Ifu!!& z#`|`BSZIgcHVuZ`decAa}T{gg@yu6jssQhX?Uh4Xrbp1xg~Skz-)Lgc#3;cOw#r=rsYpR^kr*d z;%2F1d(jE0CkX%pz+dOC^i?olyK^lm0>r=GT-nFGgFZ%?<1?|8Mlp=Qd!hOqXvXbn z@>bts?`l^%p~wd$DqY4g!mkpe?AEhRsMTbtUpwa4bcIaxF9?xV1SEzyn*% zi4wCN0<>%NS)adrDa_42e9KUUI*L1$Wzw=GE>>jZF%6R;pCv_Q{U+Bf^d~ zVJG>WgHLa6ewsNM!N79~&R$u2xskVdeb}V9n6Z&s!Kt0eIu{ula%5i&*>%l1!ZKRq zLloayiKPdF$CqhBR~%5Hsp4KP?hUh9+eW72z|YJ`>(4K9Wq-<=!pOQ`zgzk2F%AjK`wl<%h8`#QMdU zPt((n@DZZX=xC&U`4DI#h@lp=gcWAv0UlI^Z}IR|CsDsM82)-ftoc?_jO>Dst>U zICfKuxj{dLzL85Ub2eSOYm zuhURuqG4c%u)|M693uJ_OVL1_^%-KcQ>djD7sc) zr{r|30I{OBw)XpAITgE(OHd2wn?v~0r=xXdW7!y@(#eF;vERCzltSL5?NL4Z<;xn1 z47dWE_!D*yKy>Qf)6C>?JuMNY{0-{*_LjyK(e06#JwI2eEMhj zU5DqD!ucWfwdWhEBN^7d(+6UIcBM6ujaQrNgkHSHKjeAIrllvoY2-~oKWfs`${ZK+ zR5v*5j%szyI4@p&(=p_~asEYLG+MZ*3NFFP%V%kqA}6j{$u)U?$^Oyc>-TBo`pb<` zxAYU{W`=}^S|BMrGLl%A*uh&vHkFty|Ce*z8D`Pr`Ai0FfDPuVt^*sbl#q_M-OyHr?*B?WNZK=%>SFoVgmzqZ{^(v9wwQ}@+dwK0@W*>jSp9#S*mOId1F z`Fz0-Gi`or3!t5t*+dXlOuvIRZ|Qjipa|OfT9|?t^#R?+Z?ZzBE;>?2iy`en- zivDVq^0!lvCt&rXYp5xUHMzza;pN5kQJc$1MNY=x`F{1jIfL+A3uKF1^OeySr`~722Kf4 zs~6Twz{28UAa<|Jov9H}Jm+%$<%bU&Xow#k6pS4M{Q;=0VGNBCuCip4 zA94D^srh2mV5IWu!fwM`pI3jh@dlXW-(d>~_sH>@WD3I&nn88X{OYx%Uc^vBudBW9 zps$g0JteX`gZ^@lR( zbVx(UPcB7IJ2H;OdQMhWr;DQ`e@u)HVzPrpM_xl%Njs?C@g(fLQ4?r@qXJLFd@(66 zrPQntol9i;-oE1pMb~NH)3QSQRc7qwgpQ^uWJy-dnW)*DsOV7fhYd8ewY9x`DS4Wi zmLhm+>Qtakw>_~l^b<;WlDVGL_i~E9!ZsWAy1hg8Ya>F~;f>#dN<%sR9$_1C^0juO z-(UVQ2QbCQditbyB=6UcoE0bzUd-c8xpC&+EU^>S6^NGc;paL86U{yGWvT6F(Idgy zFYsvrV>^WZO2dRctooE7#>3oHt{DLe$EM^uVZF-CaUp~5?i{{Mdwlwkj?TTHmWaE= zZ)s>&f^RM3%q^~cDK^w6FLN{{cR@qFbm=l`*psHeO>v)9iKQ=`=12P>d7*W-J_wAt zgZG#}3F!5%P~9xWnwAIRYuOd=Two{He&&<&Vq6+AJai0inhJVZF})mUD+)RhfDhpVIp|>SMrfEP^csY zdT$!$Y4&O)9pdfL_}8TH;srJPj*bq|UAuHrGBV4;m_KFAzo+4!@P))klGRsux9!*R zTmQ1(7E0_(?W8$j(Hp1pQH5po7Rp?ud~K||D$RbC$@M=1=?g3>6b5Q`#uy&ApV7|S z6d-KN_}{I3^n)0IBT0Z|F**r!05yz=>-5qu{97|%bhh=Es>~HJ#|Fr+0RI%gE^g!a>FDFx zd3fTMdP;0|>{icv`_F=Q;$ZSh)yrL5i8!?W7aWO>Fl9 z_xng_#e-vc49jy^7zC~>H4V))vJ|U9S63IXv=%LY{}mivWGRMf=jKeX(6&U2Xq)H0z_XB3%B4gJr5g@|Myjhm=&c)MpEsispKju^UjGKNxMki8wwOqz;daj5x_2xUz0RsUE2vQvdwMZbou5j4QGC z;sQi7*^`~OzN7f~^XE_M70g6{pdQUo7Vh`Pr*h%Kg`&1)UP>w|&GQ6MSMvK++P>0W zOL#?sq;?Lu3eVt$i)pDXPb6R7*mrokTVNE6*zuo}yr68)(WK=$-7O3S>-{(WAOgX2 zwf}+X;pLt0NcM!+EFS;0(-v7^v^AK@pBv#8Me7i_FrkO(EgUYh0{;Abx^cVM8&EP@ zy0eft=Vq1JJweTF3X}pO3RqoPKfO{<=*LK?;_EN7g{xbXKeq^sR!tz#vSs*-|W&xoFxg`i+|N9~mh`{Xo+WwId-d)s& zGuX}Q2Kzyy?aHfbYt2#x2Nvq;>z!+N`-`vK2TCJ<_8n&z^*6m<0J8PVJ;lD6-F+PP%0ovKu}cv?tnxMie#J~$9WQo1gaf;ag1!_ z>eyWD1@`XUi#3TrL_2T+KIs$MT2z#{k92A7BXwNL^x|IU&l4=&|2+9p7MP69;ZO$n z`(wA)N(L1pZ@bnYi!^v+5FaFp5~gZY-|_6>TOGhh$}D?eRxCzEzIRLK1DW?w^i znPj(P%89;TDp|+W4wD7Y3Rw5F0iwPN8VLRX^P?B;eS|>a;0BD~xsPLdH*xfoBDW3L zlRP{;y;3(w{z#JlIRO)uum;i^^?7!E=T$rv49BlxOZ1yJZ@|C^2?h#C9OI#T`Xcqz zxH1EvLKJ=x#nHf>21Y{Gk@I#s^I3(G3`bW!ro9WhcOh^sFKpR(^ym>5A9&({y>^{D zOLNi_q>GhW_d!_?0rK>W3>cdK`w}i5nCj^|E1H4BoSK>%_9YcIY`EM0moCAUo1Fb! zLa&#>FeP%M-7;IHQzk;{K9CZCU&rUswgbU{huiG|8w0<&e0cThgk_otKF( zy##o+h;u~vOCS&WnfuSPnD9t=Kw$=BRt?s_1B-iz*v`6rf9rWrEQCTp3YYQkiC>#0 zP)R3&Oi|a3(%cwIMhuWHX7~w1e#dnLhVsWvp!Z;%VJ4C~ zQ3^de?+hLWr$7Q|{+qzJ-&{8VvOj3~GuY%?OMC=TWV37|*TE}qsWrhPI8O|SfjD3a z9dfVfm4Nm&f{M(7XN>d=D^d@D)N^uRfy!QWrnu2T9&Cz02e6`Z5#(=_LG-J=^5=#s zrqP?Free5dTS^f4p3~v(J~II+9M+h1XrJ+fPgJ@I<1Ey#pQkT~n>>**u3Bhhe# zwMUq*q~Vbd0djIdQk;K#+p z=lv5L6in2}r(xNRMtuPDp_akJ3QbM?AdXcUWoZyDFL?6~TOpJf{X*z0p3pvI^Rl8K zPG|P^iS{}Z8lt!()eA@<7GuV~IkfVPXIQ=iXez1f2B*#mt6F z!L?+z+Nd-96Fq$hQ|baH6&00z`>KIPdB0Ex{H`8B4(hEXii*tih0iZ{aB&rM$#0Lt za3sPvX=?#eEv(e}k}YOeVonFHccz0Z56T;^LUToZ{bfYjMATv`2@bd!4dc>l`28TL zXW*P80+uNlz&&aK0K6W%1zN&LjxS&lAleYGu7=RE)RXPHhyUHb|0xDie(H;pZ>D_^ zgU%O8p&?c0q>q5qZxWRfjzNp21m53DtobBmdu-F>zgPeT1=f;mphgH8XtF+TezN`& z+q9?c${OhAgDb~Bw>+4zREY?QRmxeSZlhUGJ2YUM8X~KCECx zC{#4uHU786CnOzd3ai_yc3gMxL!kOtOEXDH= z6J@H%fwgZRo}SD%;x|B2S;IMY5#N~3yKA(WFG&l$y7|BRpz(irl8B~BA;DHeG6Ktx z_u!RS_zf=yqGo_>3Xp>1)raZ6V0 zv8=A8g$3vfj4>er#l=4005T^owXe4~Y+=6u#RFq5x&>S|Nt?&VU0}^hy)`E7i+QM&gW?#Tn*#dwTkZ3(iqeP}qn6H(y>) zOFNMi`J?vIl9=_)yu7?S#2$B$BbrRu0B*EWi<1;3%M23;wZ5Ae`(eK}Pa&x;&a}o(H>A9L7BZY4A zBGT9%TLyGdy7&BHHHYr}eFWeJCQi<&FJEFVrQxX&5+jl3_`juMy-fG`l3l2oj`H&I ze*5+fsf5Uz)o72sabmwuPEt9VBAZ+iCnCj-3fk0moL}4>L{Zc*#FDFj`i%Ay;bh_| z$S8=-u`}23V~m5Fn~;Y;z=cnKMdqIp;T#2R(Lj}Xg*lNBggq_%mEil@jAapD|EUw& zPiXBi*#{0SNd9<1|M|X$-Umi*&ZN%c9}$84qDgH7QOW+_%Bz3#yksKsyqcOClIm9g zIZ*5!UmlH%{=e#g6cA^gwEvy_-34~okJUp5M*Q%LJF?w^*|@nSh(9K6GWyi~bJstA zaRNp*Y;0`K(wv;N8Td{;KL2y$U;q7Q9|vN3^r6u+`lUrY)QL)FHkWJhOftqzOoCy0 zbr*g7nnu(IRHnIY!ZJd2nHS=stMed$`{yc7yi9^xq+?Ox8WUcF2}|yr-L`EK29)!D zjSN~JpHd(5&-Ce^p6rM4{4triICa_A>kMjM$@XJZG;la7cYC+fL{z z@hP&B{k`;N;X!n~G|(-TdI}a@|I7}~``#}_{&~ZCH2;V8e3nqz!95?{dG%^&fC6D=Ez}bkc z64|PAW#f~R#MmrRcdpobv|+OWJRA=RF$aoauqp+jIr;*$Ev$d6URHtJ3y}8q*I{H- z(1lU{>J0?qoTPww|iGvUD)ZPK{1xa_v5;62P zoYZ@BUip zP@N&{d|r5tk%mS_<+fSyx0hg4rAII|j$vGC`KNXD5u})e)yK4B73=H^P&D0r!&l7r z!FxRFV0uH1#KOUi^>UaNe-%Ix$98RfroZ$9TAb?#8uii5ccaKGcSc!>#5DnW7ztT>t~j?+ci=#cdEzF_?Mz|Y zSl)v1oF524@Cdei?6X00EA$9z2krL_A|l?ZUNk0rT6y5%TcThS@b=*at4z^k}6@voKY;cZv>yvyziTYyn3nle#=FUY{Pc`S`o@u>=M8$cEM3;<+E;f|`OH zThF`8YbZW$4@<2t*YYPxt^J?}vJPU=Cx%E0Fz-jFanmPw$nJhwVSd)eshWx+kerAv zb?Q!8J3bk)ewtgkY)YX_TNH)?RLb7d-EhGVz!jM@61j2zl*gdL1a!D=oldN4#6&9N z3uc{uU#&za>yFr1J9Ki+_&_9J8oabPeJr04?OJ%q!0P8QW;<@jP?-eXq}CPy{K(Rz zkqatTC1eO1#$phqlvtMUT10go7PVt`zjbVN^}cuN58cjI0l^?F<3`*SO+@x9#c9qh zUA8rA3C8+jU|G~s9lgD5&-87akc$?qs9U+m#_trf2vC}5<9E6Uwp|4Q0>UNHT}b^H zHuCNH05r+`Sm7Cik zVIxPh9SF(^ljkePcryUomgGM{e|L3#rA&7dKrsat3qUok9um8Q{Mftr&q^=t=G6nP z&>Rc~k`LN*X_BIo$CVWo-|dfI8rY59rA+5pu$u>1`h1;~G9Wx8p41dY9e*a?rsG#` zyP^k-x@80=Zm0J-x1TnzFzdPYo#)Qh#tMg9KEDheImVYtYm*Q6Z&=?Ierj1>lu9=| znrD!h_3X>5lT5!tOyiE8rCZ;9^|O?tsU6LYMpmwS-hL?k7D|E`9RnTjw}+Lg5*hH~ zk9=+StWy#J0c++9dvhD~MWAydgn#K2d-d^L61ri|lTU5{f1Dh^6Zrg|`CwewqYa34 z^Sm+O9{Brj<&@qe>K{DSr8s)XT!U}zoi%tC zTQ=s;Z_TIb88el<-Pb=D@Tb+VTqh+#>ipTL1zNR=&lKMXE6iL5>I3$5ML5&((Ja0O z7d-O_Sd}{rZSW~qN{0H}(IE%u$DzL_n676{C(pBsZoBU9g0F!bEG)!^1Zarddb6F| z_64%pX8_XfKFiV1%2Fd-A#P4Az8Yh%6vZy87Sm>8@puW|Gr++D)v^TuG-G%7B@Y6A zNgN*=o0pCI?0GilLo$#XN>@tWzK8u;RRN-gtsq|I9=#i{+00Y8yc6b)=~zg5wh35G znq+($ciuO6mWS+vUR7pqz5yP>8m25`Z;nq#xDWkCGjjW#fS4}0T9QXc(eZe_H8cyl zmCPHLFkyI{GtkIdZfVuScPVag{@z$I2A3b}fB1zz7&8CLB=aEUZGxooWW_7zt)W`mL3xw{eLPc*S?xm3vX~@jBW?RV!M! z_b>0OMvjq;hUCv|OCrav19f0;fQzXPOnmz$b<9Ygkdy-I$9tjT+Wax|<@N3r^gmu1 zc9lKa%!WW_imMu$#4SCXgjP}VCvd5bW1iWFJ|KV>!vyTRc`cwZzlWy6+zxm!6Z?Rx zL7iT(FA9Bxk_LKENuG1ZMa3 zr$8iG^fimIy=%y#8L%`Kx`_>cP_Jn02mYA#jv4f&n-`IIqK0*B$Vb}Tg^`U@ScnP7 z5R0BL(#O!FqF~ry2n58z)Bd>3GClkQK0Ma5Rtaz3SR*Ibh6D_x87Eo`Kb5 zQEGcYMEjja&F;$bC(wxaZohS+-fe}W0`5IElCG|Kttf!K6&jhE$CK{e0!b$RCK?{=YfUP_Zvf= zlNDW>3T-Y=>UK08{vcEJ^U{mkTLmQ4@=m)uFDkpxZY2qm=-)Iku^g=+Mwc{%&|mlb z7DgvrR`eW-H*Wb@tXmyn68X{jOjhmdBn1(b!1JjSHE#ca8U~fd3wDTxZ(*bZKAF~d z6#buSzvE*>yS%$tIG)0aia{JsFIHcdz>16r3$xRB z76&Tf4Yw(Lz!ga2M0D}vk1edW>uz9cS*m=WO}CG?Zj$3A{Pj&e0pfCHN$!$eLl&=} zUcKsW%#$9nHj%cfW z&r41nsU2T(DAzfpb?%p`<&N8$G` z7QmE5kA+ZbTrA6*j!xg2KHOf~OaA7`<$%FEdLl{M%RO6|bXMrZjd6&%_;q*O=Mlcj z`rcmCDeOD)^=?lgl~kV2Wks>qY8r`V3mDbx!#YOcfV%}A9wfOr(TDxLfCZghm1!1E zt-U=x_~f$6*#h7&0a4ix)bRCJ3bx3#`e)95~TdOKjb5a*!r_1+HM%4sS#T+J!_ROztc@(a;BMCLO8jq;5PqOr_=Ndhk zoFMkTvGnV4kz~3DUoS^b6S05KUwfG1SD3UYC88{)Pu4^YoJHMNd7XDw&aVJm zE>f5U6Nh<*a9b9qj01UB>>cd!Sx0GDaF7s`trm;QePlOH%d`AycUV`p6_+=H6g0)t z*ES_h@ifZRm6mZY_ldKR*d*HLPPI?PIcjT>F26rI-(R9tRDq%ORJTjFb9?ag)Mam< z+Y6~7e=(-nnP)znAEAKQXJ*L9@br?>N3QN3r@hkGRQQE*Gy_n}cd)ba&PVA4vr_yJ zic~@tXF_et)w1<(QD?<8!e#_F4Fdxb^ce(X<816mRPxS!s)4i=&b$cd`631jOb29S zWJpRHZ|p6&-vS-g;No>?dJ)^wia0rU0S+2Wv=3aM+I0r^7X+V)qe>IBVdl^{WyTTn zIcy?cNv`ROZq4Gg+eea1*t1elV!15Ztj8&Bw@LOLPADczR_0{ygXn8GaPZ906mi}K z1CDpb6;b6uRnVd4v!IDH|Fc{<)U3gL`_NMaron374+aiAe?oi zyBaU1qg}KKT1Ga2`D$3|{!qB~N-RI6FT9&ymFzO#b6qt5;3r@6#Z!-kMAA?HsATp4KuxcLDB# zb-rKNt3V>hxsqNX2#W!iF5Qv8O!m+?t@qo#6tpB(!}pmcxtpj1;3laiZ(JpREI2v& zz|K!4lnvh>=izT!f5Y>9YuFPt=WT3mCS(;~ewg<-UoL6-T3BIzOD_F={wrIHTfKD$ zYv=#4HGdqgB2S6miULn`$8pBph1)8yP|YG_;XGEbzBy%JB`85#bc;;Y0oQ!y6+p{Z zsq_1l3GQoXXec}^>Ag4aplGFTdxPO3vRpMNgusxy!!NJTw*mG@&UhcjtZKbH8rqk5 zzYdc^`e$R96qj)aGAZjlpk1LWLU6mQ+FEP`Syt95vO#aj;3EM<8997!*`b-7oV_BM z)nb5Zy%@Vx@9eB1TPyg+_JaD)xW#eTU^%|GrolOs_00O#yE%m`pN@UmBp#U(vc35! zrg;e{HZ{L?ROu3XIQ9aK4*Q*WyRz})f&Kf@sKdhiE@4h1o5W!dXY}iu)qAE1K8c&L zT~8q4OH~~>VC|E?Q>jI$M82yRTfsbKom)py1w2OD@;x~J6CH}tfr{f+o~CX)>j5I8 zvm~q?Yd(O(k-K~`Id0T5cHv&(?J=3&v#I`j+nr z4blxBd3~hp;$+^kWs9>~wFxcZ#Qv>^vLYnRr1rr0U%^-8&s0?*5W&^KzR~>T<(#h? z)atqM$N9b=&@iC{<$d#;8?pR4oLig5R`2t1(v`aaIL17GITi=PNPYPrmi(r;D3* zgcrB_DFaVVg#p{6*(Jh`=IYg}Sz&i*>F91qI@sA~leJ^Wr@Kkqtf=7w=r{UQyQ@*! zH4!1U*0nM%t!nIUtOfby`?(1EDyXSQ{$@X(8cbA?gJZ@tbrX0377qC#q0C)Q(3g!@ z>|!Qo-Db^djXbOT58>w)g3=6pAMotsJ_0e2scF6KYRqZJ=_i)oB4RIGYArWLdF6Jq5? z+VKyJM%H|a>hhtMb%7d5dzRz(2$Ev2*cu;>P-MKa?aA{JC(X5Aza_kN48NZ8g_XCf z*15Z^m*xBP)7HPoO%Mb_8e~tb=;QBc3>`B>!;wxM)m^Jk3{QP`ygEG{Ii!Wc?F4HU?PT^s0&pnpsaAu`a z)&cz-KR-W_N$-5hC)UWD%QZ~rj}3$~Km3idZ$Pg+nC=5R3O$%JeMGf2XD+h2ov(lH zXpZsy0czQYA17hkGw}+DXUEF?YqyjV^4(Ofwb0#z; zcQn#wsVUQUMeGEyzW5s91_So+G4l|2>65r-@#0Yd-9ewiZkT9h4^oaZj~w|;g_(U zlTgo92aBSMOEsz!Cm3Q1%gV zhmVB+P})b@0ESZJ?gKdWbnTnU*Xj_Ef)TeXw{SH1@f^5R0hny4O52M+EvX|up)?Fl zvDt&;G-k%M5?N9Eqei%u9H_oFnTC3Yy-GUhOfHqBCG+;0P?l~9rgqdhFMCkPy%=9S z5i)epW3PC63C{JH1DM0UU^F4=H^!fCz71<-P6lk=bs1k0RMBu#IX|Qpf1J9Msy0%b zciXnj!Qqa#HY5}(tip{121;!I}Y+{S}n=?O7wNc1dQTD*ZduL5l1~7O@aEh6Y4L`JxqVJ0^wE802XSiAp z(H24DiwbPOg4VO&x&mJuqp|Bg>Ysv|t~2 zi^yaECwdfCLUsnr2GLp6c=BISI5)CRY=$-*LLe(TqM`g{EHm>lDN~^g_5xt$!n2#@ zxHhKvqY{8UUO(OVagTM8{o5m);pcbpP@d`QT<6_-#taTg>0A?$KV-ioa&Rbe*D(9< z(`6E2F8BJCwCQ+^9sRB?-J{!{j)&ux;fxea06 z7ufbOzPtBHqWldTjb5%|ZM5f?H@|(p4Sp4`iccRoq7`nLR@^@+{{8jJ!4!qh?aa*u zl_?jGj0FpvrRS*PI?JtKJK|_wSs%rbkZX3|OoNE$faF#d8s3EdOEwU2Mthf1dYZL^~$iA$8YNStELxVrMnF<#bK%eq#P0 zk75;~So_`;yWL)vSskA3(K8pKe>WEN@wOvsSN!Dn$(y-EVw)}wV^-tor??Qm9E;?< z^=g)=^Zq|*zPWFmN3)MUt7VMXfCm6r=kazE9^k>k7|b&j5J~A_at7_ZP-}6>mN#O> zsOSq_E$*?Et{f6d`*S+chT7{)86Ce~)|B--gE#g9yPnTjN3wrVs@sSo{c7r)ZoPVt zdQsa=`DT9J9*b7lb1&WqKc-jODF6KUvTxU`1Iel(Ai_s4`hMB_Rv9gDjssXK*C42wzh_? zxwl(6#jGksgvfSD(>bq!;fbnlPstIr0}V$qH!(JcFKyhdFAustKGi&Dog5}aejx-p zC-z7Uy5#Y2hj0OkA(J|b!>41|PeBJr_t)rlA#yUk1SZC^+V3wJKKKRP3KI#NQ$2Xa z{@l4fmr1{$8?)5=8MwJvH!V`d(yVWbRl~>u4RGXT!o?UL0?r`0FU8g1-p4Sqn~_9E zDOa~;!WfjMmSKV<=kD?IiYe1INhpLP#zN$8oU~8GfRRnqM2pRjv>!dDx_a2ljugqX zs?O!VXouvlKFG}Ykw+F5?{1h+aVGRB%lxiqkE5h7yDiCdT%=I40W_4J0qQV*JG1NN zdwCdov>))@UDk#0Satd>Wu7A@duYEK%m?fTqz2!sF4v)k#W=F*10GcKqWYp;jEFR_ z^VyyPmXw>8{K?m>ER?E<>dxb4PL1AO)dRBDBEO#$${m?JReBbk9L8h?e!_DnbI*(v z@?Bsgqtj<~qo>mlBHWZ96?k~#%AkC$(XZvEK<*}FLZbz*5;%wdg#80|saeOzO}*vO z2DM06NQ}J#TDn%+w^VO5exUmK$>NCu%jfw^-p;l~hVCaVkMH}|^8TLv#?|VC;!N$T z5CPSOi)Fw6CiCgeY)O<4ksWN+pV{1)`%c_{(|eVxH2T3eH#wCK+d(sx8`lnE^cL~W zr9H5P^Zg9Cte1>x2L%lU&AqreXh9_+ZEoy6_PS&lSM#+Al0_I-BSS-IW-_|SUm%er zR<78!V7f@rDhFonlw{?NOe{K4YgD_~ejO9wM*RvCQFXfmkjn^izXn^bOdDoZh;iCl zTB*;eXHECF9f;j_EI`;{;p99OS(#zR6dzMS4v)|qYD;H=v?_fJq#;DESg#2cZl1s+ zxY=3MpH5wWZTmW={i3_>f%10YcAiRGw)4FDr|_2pRN2XJAUOfHNue42lAw_o;M7yf ztb3W$mg$|JLwwy6?2iN`IehOK%ODSDdn+ZQ z*&hJp!X{%ADS<|Tp`j=X##y#5!HIL+Dtw(s-A!3(R;8t@dj?%)$e%>@+UGDAz5U|8 zdP0yOA?W$42{y3g3~kRHsP8X5DfsTN)=5r(%dM6Qr(W|R_qQbHQ)`mWxqHxjIHkmTVKHF z{4O}Y*~iNT!V)U3Kg#LiujSusRL2nOVcqmIH0Magc^FQeSVi*%cuJf2V=&Y1`|E_fLg?!=nQsGuV)hM3A$YSQBw2|U_+EaMIpvEWQH^gQZ)3h&WsDr0@E3&x^*9afG~7H^Vz@bF zopd260bJ+uxCbMFiH7;y(ftB%VN-~D~H7}#u%~gR+wm2Jf=Mq?4ab(Ld`94MHJ)3V^8m3Mwh}P@0P5b zhe};Xnek~UfDpn|7Y*$BE&xP;rfZSUt2{o+q%O1_frqg!!(3q_+kK>o+>?!K2e1Cw zkIOj29oiVwO~^|IrJ4Gc&^@0ko)5LTe5+JUl%(6#AejvYKBz~!`(Fon|To)o8b`&P#b6(Hi~x>tb62 zlU3ouWO07e1xZkSLl&cBV?m5`I!hts=j$s!v$*9OW#LzBrCx6HdG`Yug>cY8WwQ$Z zg9&a<8ET3A5?rJ-UcN3t!+8vNv<*h5G6QyjMgDXntxL*Y@}r#LEhC8si47rL4QCJE z8iH5w{Y56%hu2wY>i8Ef0$;{(i7?zdT`e@EH1|}84CsTU#SR88UUm)ggThW4m`j{4 z9np7}r1J8Rk*v^ZPVwP)ruIRH@)3FOd&oGU4@{;PE@2=a{=^)CMSF^ z{vbWX-PDTAm-da4_2!#&f^m;?4(uAQ`<6GKGm|o0aT&-upFR7Gq1n(S7OyRvgBKO0 z$jiPLwKsBe?JjuLrGB&$$ypU+<$RqLZD*Cq6g7SVA2@R+%=hb%FTC8qYdhZ}BxsjG zVjP<9s-ww_7V^Uqt{%xJ+6pi`?W#qa zwx=PGd*{vF@t&vQ{yklT*Aa|HHOu9TrQB|y=rLlhqfAVS=9X3B6 zFIA>brls9~ja!J-N3y%~ZuGU8H+*Xo?Mq4MOB==shwpDmX=yv2+XH5DzH2EmCvq)U z^WHW+ci03i>GZ0P9SJV!zS2TLHOb)2_Q?a^HP;bOg{W*3z@a=XB!t=DRuaB_8Pz_uG@Xdd+!h`Cq8NBE~Vt%bmePP}dO^{PyjP>KoYgI-?Tte{~88)zO z&`ug8*>8DBW?z-==jF8u$oiAHbm)xU-fJ5f>FF4?XTP2H1L;1BbzqatG~xPV{MCp`P<(NB9su?@lS89#|M{O@PUl=K1&VTM{H z8#GKAhN;q7z=zHc5+>>*JjV-si(p_D@e;TUeT>>|8f{a%yIwvr%ied5u#x@Jr{`?9 zC+%g_2&{R63k>ViEC#zqrRG+lZ3MVx_ObzJPo!l!ZUf`GU6e^@n%Z)-z8F-;L?} z7Yl&BacLFn4ab)IQ`@5%1^FqO!<&QKoAtcBy*rbH>5dmI#$x=|*f&;QA-~2qinkA% zs+5RfyNV)%)01D4JwnTt!)a{XpAe#ZuVemmJRtDKiAKwW5W)DJWR0g=JK_X4KTjHZ zdg7fL$Mdr*(8?KI`oze7k871NIA(lvaLnO2Za%VuQtQgu^+}mEZuLSve(%z5I4|&2 z2xP-r?r#QfpcX+9$>JgVgVBuJB(X1z>42nH6BD0`m-&gq#kTd^WB62A z^?y@@Vdv$O)QAyOKwYkPFPlLv1U$TzCs7}(JTN8L6#W|E8ucgiri^kJQpTDR4igOd zk(K;Rl_XNa`*WJQja-d^r)zh1!>8AGmU2HC19^!(s$yW?MggfIF~p(-Oc(gGuy!mH3{Eb^R;blzRR@p9uD=s|z{SRRI6po_=RuPx+XCCFk4zf0JaG&~EBy zO!ovw&SB zLc1y=-a0C7&@ymM-^}oIDPOXi#GF3rT(bk)$=P2-%{~gmea9_Kek()vJM1YKKaD4g z$jRuuj|YR7cu7u9PEkU%b5zz&1Xzc_)414Jbd932mtBN7l7VVkz$CO;eLg;`=Cgk6g<`DsqVQGD58%E%Ujp1PnLb-vmbt3u(~p z+mp39ySiS0%v-U5lL9pSjcAtLc9dRsJZEK_B6l^fUY;KhH@d?@oAZoFAI{&nHgC)H z6WcILm>hb3?6rMD7E{5)Gm?6QHEGP~fb;KN+$!7x@fzZ!SRETyef5y5EykJ#sF;{n zBYu$iQqV=1Y`)-UHujJ#J+mkT^gQ42=CI!_T{Sf|5L0QlkazE>WG+UBWXz(GE~~(A zAmy;Awem>*bhp-T+8mC9n$`sgUmrft3Jq<>MaQW+_t+hU7w`>wt~h4luc@aaPoatt z$J+&etR(;%h$m{c_n7iEb(1)uU{ufQSGayvyymZQ<|2l`9XkoV>y8LVJ3+g7{uqYr%TJ)=^Cz|)< zkGSH3A#Xh6=3;?dO$FE@iSpr3ft!F|J0rrFm=6A4ykSJfwBB0bK0!F9X*_N%zt5$( zjgxlv!K|bTiYo|j715RZ>~OFuT16|R^9S=Ood*qIrLuQh1JhI~waF8k-o!iFtK$u~ zlMZmqi-JH^K8#(86C4j2*7&|$ZDbuiu0cbSBWs1k-6PLu z7))alzg3G~JGKD_UOofT9@W6lUm|>^#NmTd_EKN?EpAN1p2H(`&EXC%`=}iBBc>C|L zp8K~C9H+haRvJQE0}W{pQM9LaqNSn|Qc8PAA(i%^og#(yMx~*Wc2tr=Ng2P#+vmPN z_woIG|NmZx!*yLp*LaWT>wKQ)W1agMmD4f!Wpi|P`_xKzD!mUi%vl{^h-PXt$-yJ5 zMa)7qzn_>?D|o$W2V*=n$;8mq!QM<|Te_RB8o1hz-xi~Mo@0|1YS`?R-}vgKkks+m z$E8w31CZT2=_hk(IZ0#oCEM&(@-QuU`yqzL68se`8J6i_S;}S?@nf(fG#Fd^jR6 z?{bGr#+jx{?FhM);va!w8gdqSnkUk64r73T4TCuD8_X6?VTYe@G1$pTnyD zZ9;mUY*R}wbAyKO(bZD52Pe-QT-|$;p*zo#cYuO}#^{OYyP@``Bj=Qbgeqp}B}CB) zwyabC*d59GYtWuoDmdmyz+-(8R&{6UdDpNL907+{R2XDelWQ%f{UT2!l>CnQA!9zi zF!qo%n%qj2Nce*h#dwR?p{P>7;)swf#)zm-HGY}icfW4Mr%d_t4|;zN&(^l+FrhDQ ztJaM&WA3Dtr@fePL)w)&p5($YOG?wo0Ie7HG3B-mx1Z!U{QdLpoY<*_ASad;6W}9+ z`5T8skZP0k*j~j~-CTx~0pDov4`r9djui}*S-)#*YxD7rWK|r0=$qs)3kbUEl`1w3 zhrKDSfAmNu=gPBt(047tZX)n$kRm@wKyiLX{J@7Pe&k+$@Mrll{!}J}PBF8mZ8F+j z`7R7Woka5F1O7enKjjoc_Y+KPf}OVs$8K8c9bnwIh(bb@qhG)~DS_xH_) z0RmRvD4|!~lN=uSCifM(c-IV6p&|jDkO8WER!7m317jW(m#5kWe@0$$a?{W_i0SJd zx^%@at5;C6@3XrwEj2v|fR?ZkSwzM#mVUryr6cJIq0}o9uJe3y&f_C=nLoWa6zspPv*Ycoe^krrp`v?8rcr2xKBs#`=-BrVaLV1&xVr@ z(;}1xkui%fIImh#x@P`Q?v5axq9H+-)tm3yBODs!Kd5n?y;n&BZKEO3x&aY0t#emz z++4~7;DOK03aUN86036Wvs8%>H6gwQ^LSK)n#%+1zB#NIy(gs6rftOI7Ng`oC;)@+ z)&~5ndMvDu<3Bw`Fx00sk~9_C@j{jqqED)z9%s4JW@wLVSpC}idJ0dlJvj``f&?iTUlmc^+Yggh5vDkR`#!v{{@%|Gaqp3=9Ysm9OORk_V4c|G`_c92P`mj!3V#a0SL^x%X?r zFT2)df<&P;+;Qi`dK(2HdEMZ#*OQh|1DQcIuXFf3`}N*s-Ok#uulGK3aEbl^D-2T9 z$M%lD^LlsP=aw=@rVzzlfI2@^$whg<&*vhLSbYlhtA zj%xx>(Fb7elK;i%5RC%yKp5S^;?j}=f~kbv7&g@J@lmBXGIG^;$1skFa`W7;j?3vr zkMH;j1a!!7)7JFjYHNJ0y3jp$K#Z;@q};i-XuRD4kyq~Cj7Mex-!V?*z-N)M)-qe)KP-yGjv( zaB17Nf=aIfAa7q*R^peh09^$zb*9iRQlmn-B2!Mq|7Z;@Uk+;tuV_GVpRPirx+gaK z#hscZdqZ1D&o6R3<8XWEU`z1SuN+CfQ-d6A9rb{M@&<<`G?ZE^DU$`{n={x*qTofX za4$F{GohD}K~H^mQjV~_aL>#+Czsfk(_3ICw8c9-^cP1ea~I923Tq-wih@Sbrm-$`yK0XCPXgTLeU#Q8Wj3=pVv~YtDQ4+lXF_$VQt(d~h zVLV_DoY|o8B#%5HaBid(9-uyGkT}TqMD7O;d8a`j+0&ikQ^#lC@`~#Bq4sy?NNlt_ zyQ5c4M_NGN+(#-3`p+>{S?^hcy;2v{{SIEwl&T`?<_)2d-H*LF;sXD?u>6#oFFe|e z_TJY7Awg$!qr<>s|H`T!9*#rS3<>272Prz&@}i$q$X}S_IP7ljZe@XuWVoV3rG0pa zsGh}yO>r+UF%)4TN z2ti?duT z^gBSREL51O92BoYsV#mryPXq}fIJ`6N5su5roi)u1}_!zO|~ju+cFmVNS%6vk9sG? zOpuVGSmXe?FKI4$l)Pg*4QKToz$`*qJ49Pe3Hh>Ffp);n=dx}wZR-C5h*54CMYCNV zNc|x1u{uW2y}!mFEoC_BVix&R$oRs(97%t3r%ecN7>)^O5NrYNtb+7LLZxC!%A2ez zX4A?1!O8kw1?GK4l&_(strn+Bnl{(}Jz~|gu;UD~+Jeks{#g18%RM8m$FeJswFHF~ zrPMx^R8=CEIukE7z0Yr~Ua!gg**?a1vGKml=YpI)<90FsI3xeM_nC-u zZYb5j+Podx=Xb-0drH6NPbc~KeiRAm#!}7aX)|mwY2*hp@2J7kHfM78U$c|wPt?>8 zj`0|0tsqHYl|aIn>N?nbg3CtnfxuyzrHhUjFy9DFS8`YF;J?P5FaQdo?S>odIjaG5 zKZ4CFBIl;FSKmB=u*qEBiM*NonRPoGzg~rNYuxG)3MwaNHNkd6FDr9~8DS;8G6uR< zIbMV7%c94HRLRH5H+Ck)r(ay1QqA_E&nftOdZz(RGwrC;DNR4?^0s3eO*B}7raL)5 z+ojxoUN?_YdhcVp2F5Ucl>vYOBB~4|!GtA_B*z&P?rIfuwShT__eu) z0|Z=b4Ok3zAtRk5=6V@eUy6$ijeD*)Usl}=U+>vD#*omsU%&!go0A|>6lK~i<)FaM zdz-SY2(MC`$O$dFC@s|4&CGO&i1qk*AaJy@i$yu(3iF3qRp%QdIEgW>u4`37Hl zMdXa2LqD`^5=%z4cVOY8DXQ9n*??Aoc>~KQ5uu5yf*6u~t~TO^nI{`7`yG}PCFZdp zmc8muKE}HT7gu}U)}8HS7w!o&OyV$eOMY1qxE8AD($#;WI+sS&C|hi&*8>Sj*1T3Y=JrBEgcVjvB4N)+p(joPDAvc%WO z)B^xxa;OYk-6c`t@3^OUXV1j)suGo~^|QxVq_xlH(UfyI>FoJh|r*nc9|z;f52u2PSb-GvT#zy;IR=e&_xvAs-HicUF4x}88bE33fC0c(=j?a zI!!90M*M8)wzV8I$&?cs8%fWgmEs_$*cbW$^Jhxsj@@HHe}7;4-1&Z_CC{fI`0OGs zbnND;1TE+9fm7mr?tq3+m1Z{E17nSxNU5r}>6Wc1jh*=+G)eq4<89C zzps6LfdoCN#}`64L+gVlgUgfl+*dyr9ud(Z@0zfMybK{S*IT;&Qcf%<&tSlxO9!<( z`>bX0+_u~uJ|T7fsQ%F|)tMPGtwm_M47Y!&9o!FsPhf{Q_0i}zqCa>m#W%P~cljB< zQ$FgqdtT4CHd2T&^5jO|^yuoS8g+!VImcAOlo7DNoYE1>ZMyu@j6KOK z#SX>H#iTcP1!46;$@ybKuXe1Nl5Cw`Kf^S-mZyLI-OS%T-p8_D4zgXgH9pvR1zG_GT9F#6#oi~>h-y);r}D){1!pP2=ATXr6K z#@CJboaLqe1a&an)L+ed{o09ACqspvUqb5~uT|#C7wYAqBj@Fd)Phqa(IFe-0fri= z=W#SZb;wUL6gI=yIM}dH#6{OWW9nI+fAkHQh??U$hZG;4ekxtUMq&!0_O!J%K6S)s zE%x>GL3&W*dkyrbeG;gEK14*Z;XUaOgb$J@k>Nat>13izz_tKM8V<)(z?&c!8OJh3) z-#6ZM^+oi|5r+Vh*c@T5H%1qodwwd3eyqJN@mimUT7R|cF0-N{0KXvJYO(Z{;J0*V zapI->hAIeSC<4D9KCG!UG|9LA_~fYk%gJm&|dI9!LeU(HEb+jT+HSY2JVvqp4N3`Y0%2 zc}n6Ux^}`ocU-kZT-5P2(Nx=%I#0Z3wLCq);Oc}EN-O?yaE+?v2X4T z!g%(dFd@8Oo%%ecnD4u%g+eJg!t{welHpwTIPnBCFQP!OMfXU1(>?-{&^o5&S78kp zQTN<){!ZZ$A#+d?+$F`uqUl_DL-k+e#I=xx#Ldl(ac8ae7PNqNy-;`ab`X+Jo`S)z z2<%jtv*M1Ug2$-RAXmW+pSB@Iyc)JL7|2oa^M?k|2wq`PqbebxF8M(b@CphV`#vw* zO9)~_A1QEB9$(K*IYJ14FcpE7!9Q+kS4lY?L9(*I?UV4IuMXoi?=I*6zB!z2W19a` zID~gQ^#6z&Hh$G1S|KKuv;CKfit1lF=;d8sNy9 zkN+fMeF;mbqs&H^edo>{LR8Q(#l~w8gA<;puS%Omq=YFn4T}dT_}6xp2Y5{){OYFZ z>bGz8*ub_Wg``q|b>4Xiv9YZJ0>O}f2>v;NteGt&=56k>3W5Azj!paA?59tnOz};2 z1gOTQT=w1<5HR5!LZH;Z_o@4JG%JRC*>7RV{lKh6^9aG?M} zC5QPbq^5IMx&)wU+7zGu2x}A_AxSFHyz%*)@p0}SdiXmRQjJAm-!NIG|Ds5#z>5bQ z({?)$8f(NSK(mz?6Vn6CKlX(XK0+nFukmiEr>X)Sa=zD(Ml{Nz)Iu4=mc z-Y%wd)J`XW^Xai8n27iTJ3_Pq35qR5v0)S!QMNQU2U{!M{+SRU2;GrX8S^OV8vjfl zh6e!`K~E*m(WjvSp*@M4vB$LRB6brGpYJ@p52rwI=wCo?1eUpQw3c(XP7#z?^mEM>_APu$)iHJ^5DpN8 zcUcC&dqPsIL{-A3wiF>lWl4c+Zixe;`d;>zudi9S73j*Cs{amc*zpe3fcq+ zQbG&1J6luYo`h6IJYv%hL0PstTjkUhk^`(z6c&go=+w{2MmRWHJ~Qz;UDeJRO;tUJ zL+<5MVU09}F#YIJR4%C`qiNtD`j#-AOMqK&?k=zP)h3X0MOp6=?9+-)WbFs4)MjML zo^Pc5RJ_~fYVaG;($KVCyo}t!e_5uwjn+(3#4C4ElHQP1aNvpg{_Weh^>u&-ns^ex z4hkn<*$b&eq6;!u${wc?f}0@~fr3?>^teUXh$-DC{xl85PU8N20&=?VXhD~8(#}M8 zOxBp2Lrj{qe}$F98q^M3<{3dBJ`FSkK<~uOiEAX@;NtYw(Ap6BqhfwcGpr?|#S$MY zIB7hkF|2CGrr#mxtI-Pty0-NkP#?K6JREyo0RV{uGa+NhM36rU8h)B>)nX$Z_}M3b zsuNDKI9!@`j7T`CH&Gk&;yM5hz8ld906S}g^)!l#&wxy7ssGrQ517Za!H!(3NT#95zWY$F8_rk{Gi zR!KFIh`c5Kg(A8rdNYQ9k*gXpC0E1le5-uUv>#0mW9yQssc9nhh340<&qEEINR7%v z*r3M*yx&Ui7exCSo4_q^l*>bDfMzjnY?N~i_RH=lFg3d>uYtr0HSD1@Gag{GqUZ;K zcm-T6zCL7Jn`cZSqF{Lh4Z|DY@ts69SJ*qt_-4rCkD2~Wde7R_-BnjzA+Ap;O-skS ztGN}e33NH)XPeuwCGI1%cKRF@2DR)(pP%x1$L~H0g4;}28PV#YTn~^R2vde+dE`&RvIG1({Ane~_WmJG!X$K~pm!i&K*G+EI^1WTV-r=)aP*5g$p=v#a+y+ZQBcu-W$ceLeg3sjHgR`gWARM7-qN zQ`T^rzTw)9YRAxGJ&fx|J4MQi5HG=>jQbYA7!F*KnnGfi*R&c%0p?h) z^Q2z}<4lqE>gmq-NtFoaW0dyrMj;BHz972olrH4s@u?2dE!qnZR2hYzuz);)g6u4Y z0{n08a5cYrrT4h^4UuiuM&=m}G_ocwRrW1p?gp=N09Zo9qNNv=E4j4#j)g;lot=Gs z&x*8G+@>*jnjlOaO4zyp@>TG5a?jaw<=6-OQwq*B=~ESQ+`RD=h95A6sQNtk{8gv5 z++8pP)Tqa3mFAGH+^o<$p&i}_!oJJ>LIqLn`GCSBDajz8<##>oR!j=&<4)0zRDCwL`F(3 z@gKXXv$ILwNdu3Vw@E0Megmc_oQwM9XQz3R#Sb1NgriX`GRAdOS_`p`axx!|)6`{d z98(cpo_M~iUulCj%3dprX_eI>a7fRNE)P2_m0h=G2uli#m8(1pFrRf%S&XC;gwQR2 zW*3|eysx0?WA^gvOho{heSAsu)$I)riY(mao%vpA*YgP4M?VwqxBAS-c_eBcLPV|O zj>jMmJdsYNnZVv}oT)PQ%_Q053MfowR#xxOfe3KEWRK-gJc)8U{TgcQ!i^c0-l`MZ}pK4{Ws0-DCBUzysI9@=?9-Q!gBB7(Ow#TL@>4| zP`Q+^+K|g?DE3#qGsZ`}Pf~IfF6W46SQBZ5yTak>CKf|)ior#+br>!dNg56u7OtTvdQw-2XLBrG*{XV?E9`Y%)eHX~Ovg!y2 zEI5qTehm#>m(&-+<90MSwOP)|1gf|6xG3j(UX3<%89c3hGqXL zqLk5pYvwmMRXzg6?e^hN{)qG;Dc!Uf$kM1Abl@|0IS68F#Sm$A*BPOrO|~|4Nu5>}|d)ak{p`vY6CTuhc7) z=em1{xBj)nD-ZBQT^~^!B>o)2&e0i%u7aRFMiUV+fEXl@L1d>>N)ibsr|i32+12XU z$Szsx8Kx_T%ZHoj)B9UFKgEI*3BjDptlbGB6^WUv*E+iZ9<>DREpmT$V(@;y7X>&x zQxv5eYvR0=nBxkv&S7aMa$g=^yqYp%_GR^_i4( z0^;)WF5|apt`}QNqM?&sR?<=hg!h3#-J|%)$m+(17DGQOG6i3@&n?02_V8a@kPpJJ z<&mreUju@Cn2*zs<6Z|y`uMxX*S&RvRF$+uqVq%>wtj&jQ@jE@BC?`cJxsWA_k~)b zqfUrx?zHQz%=9`n!ihn7gnN_JyaMIB=kUz}xQv^8lIi_blDd!e2i(R4yIq(WC|&wA zmLP^c=tKy}MMuxngv#KzGA>*Qp=F83B>S$WrY3~!R=s@)@1Saa239ct+_U`xad;Y! zu~}dl#}0FT)srW^>GI#esp2*i`}PgOBq5Q+`^U&8;hj@Jm@~xC7=Zh$YYGf_zghBW z*`EMK8&$&A%|QeLK*FLb@A1GTHIo0GN#}B#b#`*8We_)9M0rB+L_gAv@p)njk>t_e z!Eg`*21;zq*+(>Txd>t5TV`KVU!mML#lz5o8N%WkY zo61I1fqnHLUR@3EJ9r@Q3kDFo0Z7H!x+O1n217fv=%YaKLfEK)j~5Y#d+SFJ6Hy|p zh&Y2kx%&u%)Kbw3LKvPO)5|-p{&{jZrTDI>9cT4;d`R2H+uN^;R2?40tn<*U zoGzHOfh_X~(glR_pke5y^7+X!c+WJ^TZjgVS4e)X8_n$>fZ*BP92ut{6)(H zdsZ<|nqi3TsMROQ<9x~9HhdK%{D1!&wf)s#c4;YE{dhNJ{p#wUNr;$Os#9 zm|w?r_-D0+ekmPT(HzvPud!f#QRo82GJhJMY1Yt;`B@B6IK}yap+`6{Phz;PIaJ|v zQSGzbqsNaQ6Ta*OzXgsGC-it29AYXxqQk;8c}TMlmP1TYxlFb6e#av~tW{Xp-P&K8 z!ibJ{=5L0%>ZG72_5IM@Sx8jJTb@r&#kR)r5hFELaZKh2MS-Y45l6c)p=bWE zu)y)I>7jMFiz=O?S(xD&%BvC`vaKhZ3%Xobg#=HWIKgPNJK9|31;i?!(BZCqKY|Z* z3WpuWwG!h8Z_pf#KEHbD9M7EBZ83{MiD^ecr89f7$vLcKPTAj#lS>ds-Z-}X!Dp`T z-Vajn9v*0fsb?F@x=lO-blL-qK=ee6-x}1o1pDttH+-!^@^aDJKfa{-4aeK|$xa41 z9=r$?qSC~s9^}sjLm}T6sMgo5w_xJ)wm~N27^2@)JO5_?pU%7SwEK8%|EpJojV53^ z#K7tr^+D9MjE;L*vO~W|mQ9}~kla?M)aiMZexbxwU(|}_$Ucd5ew*VA zwKAbAtE7pfNNR@d0Q&$xN2Dv|Jss&(cx43yWwHBct;371OoFkl&H@rz9^s?8Nyhz zjkAic1p|*vi#6e0lt)#JAm>O>40Tise(UUrVllmU{yR#5T@0)X zB_8GEx8`SNLX5(b<%%%p67=5Ra6B;->2Qe7S>gWdNfEt-wgbx%L{Ypj^!AAygLN1n zJlAJ>(2notW(U=yyB@~Fa{z6L+T}*NAD`ZO!p*#C|4>giu8Qw}u>d2={x+c=Weqho zwbgbNODDp;^z71VYeI*`GaW3BW1^oD%VKpXiDD+F*vUr$Ej0IE%_)}GnacEfM!4nO z+&+QuR#W3#fBg-C_8N<>Ucp%|IQxK$&3HQSiYlzl$w)?AMMZpnODf@RNjUXszYtko zN4|9XN^pf|rREn{ajx$xGR%)wAP=Xw%5Dm+W zxHFe8S?DFAeS4a(3z~_M(UM#xI&l{Qs1XuVq&LGLYx&rnvv*c6JK(U0q7V~4HY!@$ zl%?a=BvEIKUTxlqL%9)N)zdc{VW1O#oJ3pMGGaBr9P#t(oyc1;(zeh1y?+@(-8r?m zTHIft-HmqG<@W(WmJ2VdKZ_@O!~5xDe-o|7>_T-Rhw{PGit9gIp@c1|OHF8nqFW)j# zwnHNawE)ZDyF=Rbpgg{w#&iy5a&mI4XfPEdxzXeWIFJCXz8Qah^)2SSBOlR;q9R6% zKQAoYZyIwHGiQuR(HD_AeEooIam}jJL0jcnYg0)i%B> zlz7_qDc%@Lleag>nKZaIdGkX-@l9Lb1J1k8GsK*Kwx+Rbg|q5i^p&R*d5fpb?}$G4 zFU@;4(RNy5zSLY$+s%9Nsr}zQEm4IIPNYdgT7$12wEUVZ;|OfnYQLddZtv{IZ#n<= zC^hSq8ypwg`ySO+Jc>Ped%|!;bPr>BgBwLGLldKbpdpDimzqH90K@c${o}+mqaGSv zx^-dezN79F+}8C+w~mjtjw?8SP>}i&ZC_tsdE}Fg?%U$flY+tEOt2d4=wr`@%^B)k z#^g1|7`Vxc_RoWw!M+J8i5EeC1F`yrbI}V+YO>qJyF-C}_!{Dk8e?k$ zSB1aDG&LUp3<85FRCM-rL4W_?syF+{i=D}I#{$oE!#P$`Qc`^}BqRhGJ+X0?R#q|Q zSP;Yf%NVl|up|>O;CD;#xraqWzz@cl)`PeMG@{GP%Y)ys%mRPe9N^(A!tD#?CPByP z0tw3ol~rFl#c16@}c6yLkfC@*REvtEtKC|>67g(+3OBXV0 zkEOn<%X@OTV>tJc%J%08?KO{=9J28Il$n;^>(_m5Lm!-Cc?Soa|%05q+^V^SjuW0 zD{2_1%d|b)x^+7b?I${NLaNgB;evoT(0;Tkbaa6OvLoo@0p9HwmC{L1PaoU}N7J{y zv9U5#%%03V-Uo$Wb+NRpY=pD4Do%?uB2V4Q|*}i?zk?buSmgg^(c+DV($BazAEQO5}aBleTRAquMWQpsIRxLNwZgD zeH7fW{_r_b$9*o>kAv2M_N(;lCpYRMf*m62v(=jxURMaLhd(u=x%OvL-0X+?7W1jU zrShZ)oFyZ9=>Ph9Gz=Yfv(;zk;IHHHe{|y8QtG_Py;e?>TItq_07+nFkmkb$&(PHk z$&+2k+;2V|S{|elfy6tp8l@N~Q=?CmW1~C&QR{JPHIbCl%zGpD&_so9NbA-z9JTj;XLr_R!-DbsgLvoH zr`ish{jTP6E^nqG=iSBEFx5k+D4o6*zY_BM*(u||?Y~8P1S#T}M89xMl~#n`I+gpQ z^rVFK@A10TqJ=+ihmvmm={kt3&40$>Jd?;=f z&E5U$lFtiX(GK?t0RaK9Xc^ClM5U#xu5E8zPyoJ$v7exJ7*9M*dzv+wnR@_{f(r;l zIjiQlzViM^{2_HS!bt2vb;A3Zuv0zK3U+uYxDiG5$|y;;$jC= zo)^t8@5%dU^0bW?JJ`AK41f5O|8wa`|IyZRPv^(8+4iMr_IgG#kN!sIHl2BC`6a$? zXh`u!%CtdZcBG@v(UN>w+9SMLmwNISlXT`1R{9nvdo4UFB6588IntjwBQf_n_ULxz z&yPxnMAPOcTiJA$8h_J5HW&z>U%&32sBgISK`U|Ob7C8Pe>$+*YXGd&Ts{@PrlF@t zY+;4L@y89aaW@k;QpY<3V_<+f^0R9rWWDb3p-DZU=m3%AD) zioIa`4cVc6W*DCF*L(W(r9SO4ke*4NSWor;sZiJP<4vl@#r+wcceDMqtlyVz6*iGI zNE%$ep=6}sUH!hH^c|Jxn-=Q_yHk2hbsstp&nEt|R9@6GV!A&uWp zF7(n)F3?TAt^IxW{8O*G}d`MCX)|l)NtiGMpFsP>SHGn-%AL& zhj$JT0`uCAszsd6$j|T4pJKKvR^L{&_P{z3Jix0)Xwtm={BXI~E*gSPfe@u+F+l!g zP9E%FO9%5f=(2H!f#r%IoC#nW|3kDLW)*g-sZ;oirIXF~n$GI5hf`+98VfaGujLkk zod0o?|Jw_&%3vCm<|%%ieB8n}X4e8=@bS_pE=$u(twz5r-o8A(=Zd>nYjnGF+~ZGY z-kQ~1SUP@&$>PmopkjH%)g=Ql%TrE&gSIQmG<^?UjQrF=lPkK=yxvc{qf&QPamLH) zQ3gG2qP}R_-PEQ0@kQT+8-HfA?OyGxU-*9SavE=A!SAyVtjcK^uT)M?1UXyY)BG|~ zpRE#1ggyzzmTLeDF3xxFJxdTN!}X1&*^+u+K7FF@$9~lvHBR4C5QsR;dHFJ7ar_|x z0S8>xh=YdoS__ zFb&>Yx$j~koq}+@smKbopNv<|15a?RIS>%0*F#|Q55Vm^R_$_kqUsEFfGK|Ve+^Z? zdN8XDkl^T`8J-ogqRh#qyS~arnM)7f(oM)d54T8k-mHCU_WkLbPwi%moF$>3WGHga zbs6`m`W#>rFUV?1U(WrcaOm}}-`-oAEk~8r&6CTz=zjOS4gZ+kf9R29kf~k<_hm`3 z`!^E zYvJADwf1!S$(RmTffue^LZ;&O6QT+;+!b$u51Qof4Sv1_Ee2s5lc)!{o$a4LAH1)0 zJ8~M^n+UWr&?>+gG?k=BlYuVd&hlG)qbW3$0ixn7c4oMm(VeRHSv1Tz`4%Lld`ELL z(FY2u2LLt3!b8owfXlrA(*(E#Y6+v)1i|I<2pnk+V_&YuvPhhkL*kWj9Hpk+JY*H^ z`A++7|MSobG6Rp5@}pHXUQLhvxQZWk1SLU8M%Yk6XUGz5;B)DJv4B$*QU9@*6GEQ} zU~*{U4=RUK8U@H)>AEksh?TUgn-6r@X=X(@e+H;@n<-53)Kbq z`~O(~dX`|naYp^{t|XR|mv;p!7EVqoka>QU)7MziG$bg|pf-VCA?NcLo%zBw^+rsErwk+J@DT_WdP~O3s8bULqe{; zp{jzGVH`+0xY4nr!U&Z|2_Zb}&#(9ffaLfNDt^uk=O1Wi;w%l3yw4Q8L}70dSjSWQ zC5{ID83+6Rh7iKIc^S)!SVNX^0<>z1SBFrE&s_Sd6&ew-M;(?t@qhQ%u?q|=EC(^f zJ-ZKu;|%&dM=x!Vd*4c`8N+5>;|d^@u%H}cxiDFoKQb@1WEii#Eixcx*WzTEy(E%h z9KzHV_mbnX=v8hdd!j9bs?1~Su$>qt_;nVZXlSxNb4F5;qqbn*fRBFXeb_uby|5h@6CCF##xHpkxMOg01#U(P(sHM8= z0dJTgCAISIXP-ekeFd&2N!~NVzBk$a4_~2|)q6~fD=#twzINf02M5Q`K916cDTom5 zZu?%8R9RX1SKHCu$|q_Gk(RUZRz^D)QA(q>MR#8sw5cOxK466 z4;c7etf-XV>;=5f7&Tog=x})b_Zb-V)~+utEs5A>H9x~3_5m?0*o7-~-(Zbnadvh9 z`M+|9_h-tl-HoP=O5T~!fyvgNx8w!c@Yk(%HxDcW^FBs%xlm~!1Uk{#%3+Am=reH@ zYJ_}wu}w!Vl$_m2z%F4u?M(JhHpyqb@0ny+zZuW>q^tVl3j{P@P08P z9geeFJ%!&kQs8m@o*U$d`F;;^U&YV_a6G4e80Bw;ngKA)rXCz^cv1x-|BMP|I&Bn)9hC2p;u$9gS1Dp zCx7>OtyWHFzEcqWws8A;#Yy55+U2ufsnw%m>lj&m=!ruo_|9KC^mjcvyJzR`Z~94Z zZJtcq_;i?*ru*cI{t-$@+h!8`HUZQ zUG>A7j8$XrA1l^B3n<1q`}?I(O%Og0Dm1o^FEEhc8Wy(@>Xq{E4c{fR`}it*c@)mX z^zw3SeTn|q4}1f=KmCN4G;XEOI}tLSXBy46x|s^TRNFGp2E$=Go53o`v)h61z1{Hm z`}fdi4B(+yf6lfM1T_W*%?=ro-BCEm$c{&kee7#+M=8M;Zx$}O&r5+~X!RjeLYBYG z!)p^Qx9$SgyU#y#+uMIi;~vc8(>h-vgZBUNfUe(>%EqDRZ7N!;#vbD&=_$^uI0*iF zyK)eK$77mEStMfO{;SlH-Y7c-Jt}zpg4ewj>cUPF^=1{AyIj z?U}o#TfDDb=J3SWL*ca@3FOODQ&XU=XS3N0XYnYae6b=lm{_Wyge59-2@s_Sbd#qM zkBoD*qeHUrvBf^O%JQ1j#foctrK4qdUU{UHc-%8%>B=fj9iVx0R`uwkt*5!nE!@9N zwKqd48Y&jNY4)t=6wRikR7+&J8L4bU>|Od0J#6JDvea&Tke-6w%lv@NF1z}#@3i-; zNI%t0eOX8B@$#am#|H@?>8op&wd5Q&+EkHzWWl)gh1w9;1bYkr98jke>Uu01D1!0Z(jcX>Fp`H_B$uD z<3s#+$+ON8Bq3}1Ii?QHT3DZ&YlH=Y^>xR9XwR5W6E+7Z?VR$p0HFau%;UQjMfuZe z4|u9kHs|7+$YQdXe&xi_wn_BPYq?iu62t37}^b|QCUT{%V&M1u^oaz zO=)*J%W9diP|XMXa)xYGVkkDchdo4~G$#c9A7g<=wBy78&|35?67wXc7Z^D?e`3+_ z+TSvd)o8O9F-IXL;bx;O>K(DfotGi&Y4a?p=E)P~z+ZAX#G}+6+*7|-aNY22CQkcj z$6V?Hk~yqY%&cY@5*bN>&=f+acxh|6YzK3)Ts{UA!ItFVpn{F#H$VL?tZ^{4Zu@J- zJWRDmjxW#P=I^t#OXO}BlR8!=f6|%u|D`&!DeIkI;#n|#^SEm`-OHO2SCZ8ksZ$4{ zZ1yq@eD8g!VdE#UaOtu%g?zkUwpZEy&WLc-NURP4<=fv4pQ2 zm6kQ02liA66E>VeXk#Z({nGKsR+zuItbu%_1A)jnzjA8NiO*B;`-aY zc@vHDY|2?ou=Lz=s|)>m7H@fo*N(ynOYl=~Gp6H^+&*{U=@iyUm`LQGY+~8|0d-0mK?ih4c%n zim^SKGw&v-!^g$Lgdo!oD&m@&nq8_9PLEX2?E%D1Flg+>Nhw1gQsBvZ=B+Vd&tmK; zV0!$Ikir8W30!Uj*_?m*WxNr1&_ONqlmiGvBzyr7LX}EYN5Vo*fVEh|F5^dh+zp%; zI<`>L7A^v_*Z{F&9VR*m0idgaitDE)lgf1koa^OY=!&QEoURp()cs*N?nO-;meKLa z>TV67bb%j8rYUj`8~$3hl`JC4Xx^8r4BW2u&j+MVv->*|H81buR5+eHf*BMqqCY!hNg^6kIrKWP4Dj{kf zMY3gubFa$Et5g~7SH0+a(a*A99)T}pj@8e6cNHU{mo@2f>fMcFa>RRv*Q%O@`f9cy>$|YBinK? z87z22dGIKt2tnY5u(_2GhYY=@bo-E+qG~sZsLHEGL;Em<0|u?lXkt|qaG}BENc#Ln zDjFJ%NPnc?Kr8ff2&ZZAPhnQXpiK@Yck-+AU%q5MyRplmek8r7<`7)~ZY}Hqk|)#H zy)lPPif{JaE-1%U3jGiC`vO%`0s=E&!z&ENZr`X%okk-*{}8U}==a#C%N=WvvqntnrabISB~~pV|!@!m12KLsC;r zL@2eTtmZphviv8;G6`&8ObqqDz+Y?Hz8}z%^Ht34P;yOE{d_Q;GelhcvG5T5f*%^lC$Ac%HOTf>G3>YRjMFf4d)#{qVQKS1m~dA=&CMj5eS zN!-5+y;GYikJ3SAR7NV@82l^DSWy@2xn@YzjO_(mxP%sX)<&)7TLo0vd9U;*8;UT5{POSr)I%xxepd4ZB;@L(isiY z1p!kv@eNH(CTt_$`0LxC4pF8QM=ls`XqNGL8L<52#>=daxiNZ?I_<=MkEh*7u z-s65FBWXa=Xl+sSHyT@g8Oy&W=&xY1A*4Jp!@hM7A&NAX7`37PBi=)1B6tfE!zENk z=z{Q|L4jGGwlDa#}0-X&FXUt3FP6VNx-Tg(42)IdL3lW)0bw;|_67O}vV z)eE>l?atO+`m+1S7G?zqqvY)xcYJ<{T6PCndJfb?)KuKxYE)Eq_!R)LM!U)4N!)x6Iu3l#_chFlfBi2OfN2>E^%!Dr`o)(N)tu>J8x1){ zdQaC>%SeiYhLEx|FtS&SVogxlp(F3P+F`lD{KzN1{>f>rR&u*X+)DjsM4O z#Di#JRpSjX9p@h4`+#Q!Uk|0GB9ENc4hfy`vrM60s33kmJ~VW6e{g$b=QDJ+JrBfY zOtjK>Uw2%YBZ$MNA33QjR=5s{JSA3hx%gIRXQjY!?%}2zlLULlpouDtD$@gAH({0C zoF0tikw<0kjiPC`Co<%E75s|}ksf}pQQYQ9q_g4I07s*UI!Z^9Its*QbX2j5u(s^( zMO>IT@n$9FjJ5<*BHC8T8~Dbs`|RTw*4KUwg6?zmbcJJQ8BifU=9^DWT~Ryc-gv*| zNCS*Wrnh}AUZkd?!uI#LO185hOoa?}{*Mbz{)SOx%Hj9QrwW2VKv0X}()9ZmadB~2 zqTvtaHl>B5@&1KUK9cL(`rGcug(Aao*mHP(p2XczZ*FCUJ>{l6+o!0-Xjo#m*3iAz zjw9{PQZNyhF}|2mAfKy;D5xQ&0IB~2YOkolozmGrC8Qn~42JNrU7nwtW3eKlXo3bv z{dpMvh=`bF>A21GvALyX4~}5L?l^{tqBA)y%T఍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

Gp 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 literal 0 HcmV?d00001 diff --git a/plugins/amszuidas/momo-collab/miniapp/client/app.css b/plugins/amszuidas/momo-collab/miniapp/client/app.css new file mode 100644 index 00000000..e0568021 --- /dev/null +++ b/plugins/amszuidas/momo-collab/miniapp/client/app.css @@ -0,0 +1,319 @@ +:root, +[data-theme='light'] { + --mcode-bg: #ffffff; + --mcode-bg-secondary: #f5f5f5; + --mcode-surface: #ffffff; + --mcode-surface-muted: #fafafa; + --mcode-text: #171717; + --mcode-text-muted: #666666; + --mcode-text-subtle: #adadad; + --mcode-border: #0a0a0a14; + --mcode-border-strong: #0a0a0a33; + --mcode-accent: #0094fc; + --mcode-success: #04b54b; + --mcode-warning: #f56811; + --mcode-danger: #f73646; + --mcode-primary-action-bg: #171717; + --mcode-primary-action-text: #ffffff; + --shadow-pop: 0 0 20px rgb(10 10 10 / 8%); +} + +[data-theme='dark'] { + --mcode-bg: #171717; + --mcode-bg-secondary: #1c1c1c; + --mcode-surface: #262626; + --mcode-surface-muted: #2e2e2e; + --mcode-text: #ededed; + --mcode-text-muted: #949494; + --mcode-text-subtle: #666666; + --mcode-border: #ffffff14; + --mcode-border-strong: #ffffff33; + --mcode-accent: #3aa6ff; + --mcode-success: #22c55e; + --mcode-warning: #f08a3c; + --mcode-danger: #ef4458; + --mcode-primary-action-bg: #ffffff; + --mcode-primary-action-text: #171717; + --shadow-pop: 0 0 24px rgb(0 0 0 / 40%); +} + +* { box-sizing: border-box; } +html, body { height: 100%; margin: 0; } +body { + display: flex; + flex-direction: column; + overflow: hidden; + background: var(--mcode-bg); + color: var(--mcode-text); + font: 14px/1.55 ui-sans-serif, -apple-system, system-ui, 'Segoe UI', Helvetica, Arial, sans-serif; + -webkit-font-smoothing: antialiased; +} +h1, h2, h3, h4, p { margin: 0; } +h2 { font-size: 15px; line-height: 22px; font-weight: 600; } +h3 { font-size: 13px; line-height: 18px; font-weight: 500; } +button, input, select, textarea { font: inherit; color: inherit; } +code, .mono { font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; } +.subtle { color: var(--mcode-text-muted); font-size: 12px; line-height: 16px; } +.faint { color: var(--mcode-text-subtle); } +.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; } +[hidden] { display: none !important; } + +/* ------------------------------------------------------------------ controls */ +button { cursor: pointer; border-radius: 8px; border: 1px solid transparent; height: 32px; padding: 0 12px; background: none; transition: background-color 150ms ease-out, border-color 150ms ease-out, opacity 150ms ease-out; white-space: nowrap; } +button:disabled { opacity: 0.45; cursor: default; } +:focus-visible { outline: 2px solid var(--mcode-accent); outline-offset: 1px; } +.primary { background: var(--mcode-primary-action-bg); color: var(--mcode-primary-action-text); font-weight: 500; } +.primary:hover:not(:disabled) { opacity: 0.86; } +.secondary { background: var(--mcode-surface); border-color: var(--mcode-border-strong); } +.secondary:hover:not(:disabled), .ghost:hover:not(:disabled), .icon-button:hover { background: var(--mcode-bg-secondary); } +.danger-text { color: var(--mcode-danger); } +.link { color: var(--mcode-accent); padding: 0 2px; height: auto; border: 0; } +.link:hover { text-decoration: underline; } +.small { height: 28px; padding: 0 10px; font-size: 13px; } +.xsmall { height: 24px; padding: 0 8px; font-size: 12px; border-radius: 6px; } +.icon-button { width: 30px; height: 30px; padding: 0; display: inline-flex; align-items: center; justify-content: center; border-radius: 8px; cursor: pointer; color: var(--mcode-text-muted); flex-shrink: 0; } +.icon-button svg { width: 16px; height: 16px; } +.row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } +.row.end { justify-content: flex-end; } +.row.tight { gap: 4px; } +.spread { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-width: 0; } +.grow { flex: 1; min-width: 0; } +.ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + +.chip { display: inline-flex; align-items: center; gap: 4px; height: 20px; padding: 0 7px; border-radius: 999px; font-size: 12px; line-height: 20px; background: var(--mcode-bg-secondary); color: var(--mcode-text-muted); white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis; } +.chip svg { width: 12px; height: 12px; flex-shrink: 0; } +.chip.warn { color: var(--mcode-warning); background: color-mix(in srgb, var(--mcode-warning) 12%, transparent); } +.chip.danger { color: var(--mcode-danger); background: color-mix(in srgb, var(--mcode-danger) 12%, transparent); } +.chip.ok { color: var(--mcode-success); background: color-mix(in srgb, var(--mcode-success) 12%, transparent); } +.chip.accent { color: var(--mcode-accent); background: color-mix(in srgb, var(--mcode-accent) 12%, transparent); } +.chip.dashed { background: none; border: 1px dashed var(--mcode-text-subtle); line-height: 18px; } +.count { min-width: 18px; height: 18px; padding: 0 5px; border-radius: 9px; font-size: 11px; line-height: 18px; text-align: center; background: var(--mcode-danger); color: #fff; font-weight: 600; } +.count.muted { background: var(--mcode-bg-secondary); color: var(--mcode-text-muted); } + +/* status dots: never color alone — always paired with text */ +.dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; background: var(--mcode-text-subtle); } +.dot.todo { background: transparent; border: 1.5px solid var(--mcode-text-subtle); } +.dot.in_progress { background: var(--mcode-accent); } +.dot.pending_review { background: var(--mcode-warning); } +.dot.done { background: var(--mcode-success); } + +/* live indicator (Multica-style: renders only while a run is active) */ +.live { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--mcode-accent); min-width: 0; max-width: 100%; } +.live .label { flex-shrink: 0; white-space: nowrap; } +.live .pulse { width: 6px; height: 6px; border-radius: 50%; background: currentColor; animation: pulse 1.4s ease-in-out infinite; flex-shrink: 0; } +.live.queued { color: var(--mcode-text-muted); } +.live.queued .pulse { animation: none; opacity: 0.5; } +.live .what { flex: 1 1 auto; min-width: 0; color: var(--mcode-text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +[data-live] { min-width: 0; max-width: 100%; overflow: hidden; } +@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } } + +/* ------------------------------------------------------------------ top bar */ +.topbar { display: flex; align-items: center; gap: 12px; padding: 8px 12px; border-bottom: 1px solid var(--mcode-border); flex-shrink: 0; min-height: 48px; } +.brand { display: flex; align-items: center; gap: 8px; height: auto; padding: 2px 6px 2px 2px; border-radius: 10px; text-align: left; } +.brand:hover { background: var(--mcode-bg-secondary); } +.brand-text { display: flex; flex-direction: column; line-height: 1.2; } +.brand-text strong { font-size: 14px; font-weight: 600; } +.tabs { display: flex; gap: 2px; padding: 2px; background: var(--mcode-bg-secondary); border-radius: 8px; margin-left: auto; } +.tabs button { height: 28px; padding: 0 10px; color: var(--mcode-text-muted); display: inline-flex; align-items: center; gap: 6px; border-radius: 6px; } +.tabs button[aria-selected='true'] { background: var(--mcode-surface); color: var(--mcode-text); box-shadow: 0 0 0 1px var(--mcode-border); font-weight: 500; } +.topbar-actions { display: flex; align-items: center; gap: 4px; } + +#banner { margin: 8px 12px 0; padding: 8px 12px; border-radius: 8px; background: color-mix(in srgb, var(--mcode-warning) 12%, transparent); font-size: 13px; } + +/* ------------------------------------------------------------------ layout */ +.layout { flex: 1; min-height: 0; display: flex; } +.pane { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; } +/* narrow (side panel): one pane at a time, chosen by the tab bar */ +.layout[data-view='chat'] .pane-side { display: none; } +.layout:not([data-view='chat']) .pane-chat { display: none; } +.pane-side { overflow: auto; } + +@media (min-width: 900px) { + .tabs { margin-left: 24px; } + .layout[data-view='chat'] .pane-side, .layout[data-view='inbox'] .pane-side { display: flex; } + .layout[data-view='chat'] .pane-chat, .layout[data-view='inbox'] .pane-chat { display: flex; } + .layout[data-view='chat'] .pane-side, .layout[data-view='inbox'] .pane-side { flex: 0 0 clamp(320px, 34vw, 420px); border-left: 1px solid var(--mcode-border); background: var(--mcode-bg-secondary); } + .layout[data-view='board'] .pane-chat, .layout[data-view='ideas'] .pane-chat { display: none; } + .tabs [data-view='inbox'] { display: none; } +} + +/* ------------------------------------------------------------------ chat */ +.chat-log { flex: 1; min-height: 0; overflow: auto; padding: 16px; display: flex; flex-direction: column; gap: 14px; } +.msg { display: flex; gap: 10px; max-width: 720px; width: 100%; } +.msg.user { align-self: flex-end; justify-content: flex-end; } +.msg .body { display: flex; flex-direction: column; gap: 6px; min-width: 0; flex: 1; } +.msg.user .body { align-items: flex-end; flex: 0 1 auto; } +.bubble { padding: 8px 12px; border-radius: 14px; word-break: break-word; } +.msg.user .bubble { background: var(--mcode-bg-secondary); white-space: pre-wrap; border-bottom-right-radius: 4px; } +.msg.from-momo .bubble { padding: 2px 0; } +.msg.error .bubble { color: var(--mcode-danger); } +.meta { font-size: 11px; color: var(--mcode-text-subtle); } +.thumb { max-width: 180px; max-height: 140px; border-radius: 10px; border: 1px solid var(--mcode-border); display: block; } +.md p + p, .md p + ul, .md ul + p, .md p + ol, .md ol + p { margin-top: 6px; } +.md ul, .md ol { margin: 4px 0; padding-left: 20px; } +.md code { background: var(--mcode-bg-secondary); padding: 1px 4px; border-radius: 4px; } +.md strong { font-weight: 600; } +.typing { display: inline-flex; gap: 3px; padding: 8px 0; } +.typing span { width: 5px; height: 5px; border-radius: 50%; background: var(--mcode-text-subtle); animation: pulse 1.2s ease-in-out infinite; } +.typing span:nth-child(2) { animation-delay: 0.2s; } +.typing span:nth-child(3) { animation-delay: 0.4s; } +.tool-line { font-size: 12px; color: var(--mcode-text-subtle); display: flex; gap: 6px; flex-wrap: wrap; } +.notice { align-self: center; max-width: 640px; width: 100%; display: flex; flex-direction: column; gap: 6px; } +.notice-text { font-size: 12px; color: var(--mcode-text-muted); text-align: center; } +.notice > .row { justify-content: center; } +.notice > .inline-card { align-self: center; width: 100%; } +.day-sep { align-self: center; font-size: 11px; color: var(--mcode-text-subtle); } + +.receipt { display: inline-flex; align-items: center; gap: 6px; padding: 3px 8px 3px 4px; border-radius: 8px; border: 1px solid var(--mcode-border); background: var(--mcode-surface); font-size: 12px; color: var(--mcode-text-muted); cursor: pointer; height: auto; max-width: 100%; text-align: left; } +.receipt:hover { border-color: var(--mcode-border-strong); } +.receipt strong { color: var(--mcode-text); font-weight: 500; } +.receipt .ellipsis { max-width: 360px; } + +/* ------------------------------------------------------------------ cards */ +.card { border: 1px solid var(--mcode-border); border-radius: 12px; background: var(--mcode-surface); } +.inline-card { padding: 10px 12px; display: flex; flex-direction: column; gap: 8px; max-width: 520px; min-width: 0; } +.inline-card.attention { border-color: color-mix(in srgb, var(--mcode-warning) 50%, var(--mcode-border)); } +.inline-card .title-line { display: flex; align-items: center; gap: 8px; min-width: 0; } +.inline-card .title-line strong { font-weight: 500; min-width: 0; } +.kv { display: grid; grid-template-columns: auto 1fr; gap: 2px 10px; font-size: 12px; } +.kv dt { color: var(--mcode-text-muted); } +.kv dd { margin: 0; color: var(--mcode-text); word-break: break-word; } +.pre { margin: 0; max-height: 140px; overflow: auto; font: 12px/1.5 'JetBrains Mono', ui-monospace, Menlo, monospace; background: var(--mcode-bg-secondary); padding: 8px; border-radius: 8px; white-space: pre-wrap; word-break: break-all; } + +.options { display: flex; flex-direction: column; gap: 6px; } +.option-chip { display: flex; align-items: flex-start; gap: 8px; height: auto; padding: 8px 10px; border-radius: 10px; border: 1px solid var(--mcode-border-strong); background: var(--mcode-surface); text-align: left; white-space: normal; width: 100%; } +.option-chip:hover { border-color: var(--mcode-text-muted); } +.option-chip[aria-pressed='true'] { border-color: var(--mcode-accent); background: color-mix(in srgb, var(--mcode-accent) 8%, var(--mcode-surface)); } +.option-chip .label { font-weight: 500; } +.option-chip .desc { display: block; font-size: 12px; color: var(--mcode-text-muted); } +.option-chip .rec { margin-left: auto; } +.other-input { width: 100%; padding: 7px 10px; border-radius: 8px; border: 1px solid var(--mcode-border-strong); background: var(--mcode-surface); } + +/* task row/card used in rail, board, inbox */ +.task { padding: 10px 12px; display: flex; flex-direction: column; gap: 6px; cursor: pointer; text-align: left; width: 100%; height: auto; white-space: normal; position: relative; } +.task:hover { border-color: var(--mcode-border-strong); } +.task.attention { border-color: color-mix(in srgb, var(--mcode-warning) 50%, var(--mcode-border)); } +.task-title { font-weight: 500; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; line-height: 20px; } +.task-meta { display: flex; align-items: center; gap: 6px; min-width: 0; font-size: 12px; color: var(--mcode-text-muted); } +.task-meta > * { min-width: 0; } +.task-meta > .faint { flex-shrink: 0; white-space: nowrap; } +.task-actions { display: flex; gap: 6px; flex-wrap: wrap; } +.reason { font-size: 12px; color: var(--mcode-text-muted); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } + +/* side rail */ +.rail { display: flex; flex-direction: column; gap: 18px; padding: 14px; } +.section { display: flex; flex-direction: column; gap: 8px; } +.section-head { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 500; color: var(--mcode-text-muted); } +.section-head .count { margin-left: 2px; } +.section-head .grow + * { margin-left: auto; } +.empty { font-size: 12px; color: var(--mcode-text-subtle); padding: 6px 0; } +.empty-block { padding: 24px 12px; text-align: center; display: flex; flex-direction: column; align-items: center; gap: 8px; color: var(--mcode-text-muted); font-size: 13px; } + +/* board */ +.board-wrap { display: flex; flex-direction: column; min-height: 0; flex: 1; } +.board-toolbar { padding: 10px 16px; display: flex; gap: 8px; align-items: center; border-bottom: 1px solid var(--mcode-border); flex-wrap: wrap; } +.board-toolbar select { height: 28px; border-radius: 8px; border: 1px solid var(--mcode-border-strong); background: var(--mcode-surface); padding: 0 8px; font-size: 13px; } +.board { flex: 1; min-height: 0; overflow: auto; padding: 12px 16px 16px; display: flex; flex-direction: column; gap: 18px; } +@media (min-width: 900px) { + .board { display: grid; grid-template-columns: repeat(4, minmax(220px, 1fr)); gap: 12px; align-items: start; } + .column-body { background: var(--mcode-bg-secondary); border-radius: 12px; padding: 8px; min-height: 120px; } +} +.column { display: flex; flex-direction: column; gap: 8px; min-width: 0; } +.column-head { display: flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 500; padding: 0 2px; } +.column-body { display: flex; flex-direction: column; gap: 8px; } + +/* ideas */ +.idea { padding: 10px 12px; display: flex; flex-direction: column; gap: 6px; cursor: pointer; width: 100%; height: auto; text-align: left; white-space: normal; } +.idea:hover { border-color: var(--mcode-border-strong); } +.idea.loose { border-style: dashed; } +.gates { display: flex; gap: 4px; align-items: center; } +.gates span { width: 18px; height: 4px; border-radius: 2px; background: var(--mcode-border-strong); } +.gates span.on { background: var(--mcode-accent); } + +/* ------------------------------------------------------------------ composer */ +.composer { padding: 8px 12px 12px; display: flex; flex-direction: column; gap: 6px; flex-shrink: 0; } +.composer-box { border: 1px solid var(--mcode-border-strong); border-radius: 14px; background: var(--mcode-surface); padding: 8px 8px 6px 12px; display: flex; flex-direction: column; gap: 4px; transition: border-color 150ms ease-out; } +.composer-box:focus-within { border-color: var(--mcode-text-muted); } +.composer textarea { border: 0; outline: 0; resize: none; background: none; min-height: 22px; max-height: 160px; padding: 2px 0; } +.composer textarea::placeholder { color: var(--mcode-text-subtle); } +.composer-row { display: flex; align-items: center; gap: 6px; } +.hint { font-size: 11px; color: var(--mcode-text-subtle); margin-right: auto; } +.composer-context { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--mcode-text-muted); padding: 0 4px; } +.composer-context .icon-button { width: 20px; height: 20px; } +.composer-context .icon-button svg { width: 12px; height: 12px; } +.attachments { display: flex; gap: 8px; flex-wrap: wrap; } +.attachments:empty { display: none; } +.attachment { position: relative; } +.attachment img { width: 52px; height: 52px; object-fit: cover; border-radius: 8px; border: 1px solid var(--mcode-border); display: block; } +.attachment button { position: absolute; top: -6px; right: -6px; width: 18px; height: 18px; padding: 0; border-radius: 50%; background: var(--mcode-primary-action-bg); color: var(--mcode-primary-action-text); font-size: 11px; line-height: 18px; } +.suggestions { display: flex; flex-wrap: wrap; gap: 6px; } +.suggestions button { border: 1px solid var(--mcode-border); background: var(--mcode-surface); height: 28px; font-size: 12px; border-radius: 999px; color: var(--mcode-text-muted); } +.suggestions button:hover { color: var(--mcode-text); border-color: var(--mcode-border-strong); } + +.hero { margin: auto; display: flex; flex-direction: column; align-items: center; gap: 10px; text-align: center; padding: 24px 16px; max-width: 380px; } +.hero .momo { width: 72px; height: 72px; } +.hero p { color: var(--mcode-text-muted); font-size: 13px; } + +/* ------------------------------------------------------------------ drawer */ +.drawer { position: fixed; inset: 0; z-index: 20; display: flex; justify-content: flex-end; } +.drawer-scrim { position: absolute; inset: 0; background: rgb(0 0 0 / 18%); } +.drawer-panel { position: relative; width: min(520px, 100%); height: 100%; background: var(--mcode-bg); border-left: 1px solid var(--mcode-border); box-shadow: var(--shadow-pop); display: flex; flex-direction: column; animation: slide-in 200ms ease-out; } +@keyframes slide-in { from { transform: translateX(24px); opacity: 0; } to { transform: none; opacity: 1; } } +.drawer-head { display: flex; align-items: center; gap: 8px; padding: 10px 12px; border-bottom: 1px solid var(--mcode-border); } +.drawer-head h2 { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.drawer-body { flex: 1; overflow: auto; padding: 14px 16px 24px; display: flex; flex-direction: column; gap: 18px; } +.status-line { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } +.gate-row { padding: 8px 10px; border-radius: 10px; border: 1px solid var(--mcode-border); display: flex; flex-direction: column; gap: 2px; } +.gate-row.missing { border-style: dashed; color: var(--mcode-text-subtle); } +.gate-row .label { font-size: 12px; color: var(--mcode-text-muted); display: flex; justify-content: space-between; align-items: center; gap: 8px; } +.check { display: flex; gap: 8px; align-items: flex-start; font-size: 13px; } +.check .mark { width: 16px; height: 16px; border-radius: 50%; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; font-size: 11px; color: #fff; margin-top: 2px; } +.check .mark.ok { background: var(--mcode-success); } +.check .mark.no { background: var(--mcode-danger); } +.check .evidence { font-size: 12px; color: var(--mcode-text-muted); } +.output { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border: 1px solid var(--mcode-border); border-radius: 8px; font-size: 13px; } +.output .name { font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace; font-size: 12px; } +.timeline { display: flex; flex-direction: column; gap: 0; padding-left: 4px; } +.timeline li { list-style: none; display: grid; grid-template-columns: 12px 1fr auto; gap: 8px; font-size: 12px; padding: 3px 0; color: var(--mcode-text-muted); } +.timeline li .dot { margin-top: 5px; } +.timeline { margin: 0; padding: 0; } +.record { font-size: 13px; padding: 8px 10px; border-radius: 10px; border: 1px solid var(--mcode-border); display: flex; flex-direction: column; gap: 4px; } +.record.highlight { border-color: var(--mcode-accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--mcode-accent) 18%, transparent); } +.record .text { white-space: pre-wrap; word-break: break-word; } +details > summary { cursor: pointer; font-size: 12px; color: var(--mcode-text-muted); list-style: none; } +details > summary::-webkit-details-marker { display: none; } +details > summary::before { content: '▸ '; } +details[open] > summary::before { content: '▾ '; } + +/* ------------------------------------------------------------------ dialogs */ +dialog { border: 1px solid var(--mcode-border); border-radius: 12px; background: var(--mcode-surface); color: var(--mcode-text); padding: 18px; width: min(520px, 92vw); max-height: 86vh; box-shadow: var(--shadow-pop); } +dialog::backdrop { background: rgb(0 0 0 / 28%); } +dialog form { display: flex; flex-direction: column; gap: 12px; } +dialog textarea, dialog input[type='text'] { width: 100%; padding: 8px 10px; border-radius: 8px; border: 1px solid var(--mcode-border-strong); background: var(--mcode-surface); } +dialog label.option { display: flex; gap: 8px; align-items: center; padding: 4px 0; } +.preview { max-height: 60vh; overflow: auto; white-space: pre-wrap; font: 12px/1.5 'JetBrains Mono', ui-monospace, Menlo, monospace; background: var(--mcode-bg-secondary); padding: 12px; border-radius: 8px; } +.preview-img { max-width: 100%; border-radius: 8px; } +.color-row { display: flex; gap: 8px; } +.swatch { width: 34px; height: 34px; border-radius: 50%; border: 2px solid transparent; padding: 0; } +.swatch[aria-pressed='true'] { border-color: var(--mcode-text); } + +.toast { position: fixed; bottom: 92px; left: 50%; transform: translateX(-50%); background: var(--mcode-primary-action-bg); color: var(--mcode-primary-action-text); padding: 8px 14px; border-radius: 8px; font-size: 13px; z-index: 30; max-width: 86vw; box-shadow: var(--shadow-pop); } + +/* ------------------------------------------------------------------ MoMo */ +.momo { display: inline-block; width: 24px; height: 24px; background: var(--momo-image, url('/collab/momo.svg')) center / contain no-repeat; flex-shrink: 0; border-radius: 50%; filter: var(--momo-filter, none); } +.momo[data-size='16'] { width: 16px; height: 16px; } +.momo[data-size='20'] { width: 20px; height: 20px; } +.momo[data-size='28'] { width: 28px; height: 28px; } +.momo[data-state='thinking'] { animation: momo-bob 1.4s ease-in-out infinite; } +.momo[data-state='noted'] { animation: momo-pop 600ms ease-out 1; } +@keyframes momo-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-3px); } } +@keyframes momo-pop { 0% { transform: scale(1); } 40% { transform: scale(1.18); } 100% { transform: scale(1); } } + +@media (max-width: 520px) { + .brand-text .subtle { display: none; } + .tabs button { padding: 0 8px; } + .chat-log { padding: 12px; } +} +@media (prefers-reduced-motion: reduce) { + * { animation: none !important; transition: none !important; } +} diff --git a/plugins/amszuidas/momo-collab/miniapp/client/app.js b/plugins/amszuidas/momo-collab/miniapp/client/app.js new file mode 100644 index 00000000..91734b04 --- /dev/null +++ b/plugins/amszuidas/momo-collab/miniapp/client/app.js @@ -0,0 +1,1242 @@ +// @ts-check +/** @typedef {import('./client-api.js').McodeClientAPI} McodeClientAPI */ +/** @typedef {import('./client-api.js').MiniAppAgentQuestionnaire} Questionnaire */ +/** @typedef {import('./client-api.js').MiniAppAgentQuestionAnswer} QuestionAnswer */ + +const STATUS = /** @type {const} */ ({ todo: '待办', in_progress: '进行中', pending_review: '待审核', done: '已完成' }); +const BLOCKED = /** @type {Record} */ ({ need_approval: '待批准', need_info: '待补充信息', run_failed: '执行失败', project_unavailable: '项目不可用' }); +const GATES = /** @type {const} */ ([['objective', '做什么'], ['deliverable', '产出什么'], ['done_criteria', '怎么算做完']]); +const COLORS = /** @type {Record} */ ({ sky: 'none', mint: 'hue-rotate(-50deg)', peach: 'hue-rotate(150deg) saturate(1.3)', lilac: 'hue-rotate(60deg)', lemon: 'hue-rotate(-150deg) saturate(1.4)' }); +const FIRST_PROMPTS = ['把最近在想的一件事告诉我', '贴一张截图试试', '你能帮我做什么?']; +const LATER_PROMPTS = ['现在进展到哪了?', '哪些事需要我处理?', '哪个想法快能开始了?']; +const TOOL_LABELS = /** @type {Record} */ ({ + add_record: '记录', move_record: '改记', update_idea: '整理想法', create_task: '写任务', update_task: '改任务', start_task: '派任务', + get_overview: '查看进展', get_idea: '查看想法', approve_task: '确认完成', revise_task: '提交修改意见', stop_task: '停止任务', + revise_item: '修正', confirm_record: '确认', delete_record: '删除记录', delete_idea: '删除想法', task_report: '汇报进度', + submit_task_result: '提交结果', record_verification: '核对', momo_verify: 'MoMo 核对', + read: '读文件', write: '写文件', edit: '改文件', bash: '执行命令', grep: '搜索', glob: '查找文件', ls: '列目录', + web_search: '搜索网页', web_fetch: '读网页', task: '委派子任务', ask_user: '提问', todo_write: '整理待办', +}); + +const host = /** @type {McodeClientAPI | undefined} */ (window.mcode); +const agentApi = host?.miniapp?.agent; +const windowApi = host?.miniapp?.window; +const clientId = sessionStorage.getItem('momo.client') ?? crypto.randomUUID(); +sessionStorage.setItem('momo.client', clientId); + +/** @type {any} */ +let state = null; +const ui = { + view: /** @type {'chat' | 'inbox' | 'board' | 'ideas'} */ ('chat'), + focusIdeaId: /** @type {string | null} */ (null), + drawer: /** @type {null | { kind: 'task' | 'idea', id: string, highlight?: string }} */ (null), + boardFilter: '', + attachments: /** @type {{ id: string, url: string }[]} */ ([]), + streams: /** @type {Map} */ (new Map()), + report: /** @type {any} */ (null), + windowMode: 'inline', + claimed: /** @type {Set} */ (new Set()), + answers: /** @type {Map>>} */ (new Map()), +}; + +const $ = (/** @type {string} */ id) => /** @type {HTMLElement} */ (document.getElementById(id)); +const esc = (/** @type {unknown} */ value) => + String(value ?? '').replace(/[&<>"']/g, (char) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[char] ?? char); +const base = (/** @type {string} */ file) => String(file ?? '').split(/[\\/]/).filter(Boolean).pop() ?? file; +const shortPath = (/** @type {string} */ dir) => { + const parts = String(dir ?? '').split(/[\\/]/).filter(Boolean); + return parts.length > 2 ? `…/${parts.slice(-2).join('/')}` : dir; +}; +function ago(/** @type {number} */ at) { + const s = Math.max(0, Math.round((Date.now() - at) / 1000)); + if (s < 60) return '刚刚'; + if (s < 3600) return `${Math.floor(s / 60)} 分钟前`; + if (s < 86400) return `${Math.floor(s / 3600)} 小时前`; + return `${Math.floor(s / 86400)} 天前`; +} +function duration(/** @type {number} */ ms) { + if (ms < 60000) return `${Math.max(1, Math.round(ms / 1000))} 秒`; + const m = Math.round(ms / 60000); + return m < 60 ? `${m} 分钟` : `${Math.floor(m / 60)} 小时 ${m % 60} 分`; +} +function clock(/** @type {number} */ at) { + const date = new Date(at); + return new Date().toDateString() === date.toDateString() + ? date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }) + : date.toLocaleString('zh-CN', { month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit' }); +} +const toolName = (/** @type {string} */ name) => { + const short = String(name ?? '').replace(/^mcp__.+?__/, ''); + return TOOL_LABELS[short] ?? short; +}; + +/** Minimal safe Markdown: paragraphs, **bold**, `code`, bullet/number lists, links as text. @param {string} text */ +function md(text) { + const blocks = esc(text).split(/\n{2,}/); + return blocks + .map((block) => { + const lines = block.split('\n'); + const inline = (/** @type {string} */ line) => + line.replace(/`([^`]+)`/g, '$1').replace(/\*\*([^*]+)\*\*/g, '$1'); + if (lines.every((line) => /^\s*[-*•]\s+/.test(line))) return `
    ${lines.map((line) => `
  • ${inline(line.replace(/^\s*[-*•]\s+/, ''))}
  • `).join('')}
`; + if (lines.every((line) => /^\s*\d+[.、)]\s*/.test(line))) return `
    ${lines.map((line) => `
  1. ${inline(line.replace(/^\s*\d+[.、)]\s*/, ''))}
  2. `).join('')}
`; + return `

${lines.map(inline).join('
')}

`; + }) + .join(''); +} + +// --------------------------------------------------------------------- theme & avatar + +const media = window.matchMedia('(prefers-color-scheme: dark)'); +const applyTheme = () => document.documentElement.setAttribute('data-theme', media.matches ? 'dark' : 'light'); +media.addEventListener('change', applyTheme); +applyTheme(); + +function applyAvatar() { + const settings = state?.settings ?? {}; + const root = document.documentElement.style; + if (settings.avatarUploadId) { + root.setProperty('--momo-image', `url('/api/uploads/${encodeURIComponent(settings.avatarUploadId)}')`); + root.setProperty('--momo-filter', 'none'); + } else { + root.removeProperty('--momo-image'); + root.setProperty('--momo-filter', COLORS[settings.momoColor] ?? 'none'); + } +} + +// --------------------------------------------------------------------- network + +/** @param {string} path @param {Record} [body] */ +async function post(path, body = {}) { + const response = await fetch(path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ ...body, clientId }) }); + const data = await response.json().catch(() => ({})); + if (!response.ok) throw Object.assign(new Error(data.message ?? '请求失败'), { code: data.error }); + return data; +} +const taskPost = (/** @type {string} */ id, /** @type {string} */ action, body = {}) => post(`/api/tasks/${encodeURIComponent(id)}/${action}`, body); + +/** @param {string} event @param {Record} [fields] */ +function track(event, fields = {}) { + void post('/api/track', { event, fields }).catch(() => {}); +} + +let toastTimer = 0; +/** @param {string} text */ +function toast(text) { + const node = $('toast'); + node.textContent = text; + node.hidden = false; + clearTimeout(toastTimer); + toastTimer = window.setTimeout(() => (node.hidden = true), 3000); +} +/** @param {unknown} error */ +const showError = (error) => toast(error instanceof Error ? error.message : String(error)); + +function connect() { + const events = new EventSource(`/api/events?client=${encodeURIComponent(clientId)}`); + events.onmessage = (message) => { + const event = JSON.parse(message.data); + if (event.type === 'state') { + const first = !state; + state = event.state; + if (first) onFirstState(); + render(); + } else if (event.type === 'activity') { + if (!state) return; + state.activity = event.activity; + renderLive(); + } else if (event.type === 'chat.stream') { + ui.streams.set(event.messageId, event.text); + const node = document.querySelector(`[data-stream="${CSS.escape(event.messageId)}"]`); + if (node) { + node.innerHTML = md(event.text); + scrollChat(false); + } + } else if (event.type === 'momo.pending') { + if (!state) return; + state.momo = event.pending; + render(); + } else if (event.type === 'dispatch.request') { + if (event.clientId === clientId) void dispatch(event.taskId); + } + }; + events.onerror = () => { + $('banner').hidden = false; + $('banner').textContent = '和 MoMo 的连接断开了,正在重连…(MiniMax Code 重启后请点 Mini App 工具栏的刷新)'; + }; + events.onopen = () => { + if (state?.agentAvailable !== false) $('banner').hidden = true; + }; +} + +/** Creates the task session through the Host bridge and hands it to the Node service. @param {string} taskId */ +async function dispatch(taskId) { + if (ui.claimed.has(taskId)) return; + ui.claimed.add(taskId); + try { + if (!agentApi?.create) throw new Error('host_unsupported'); + const { sessionId } = await agentApi.create(); + await taskPost(taskId, 'claim', { sessionId }); + } catch (error) { + await taskPost(taskId, 'dispatch-failed', { code: error instanceof Error ? error.message : 'unknown' }).catch(() => {}); + } finally { + ui.claimed.delete(taskId); + } +} + +function onFirstState() { + track('collab_view', { view: ui.view, entry: windowApi ? 'miniapp' : 'browser' }); + if (state.hasChangesSinceSeen && state.briefing.needs_you.length) { + ui.report = state.briefing; + track('collab_report_view', countBy(state.briefing.needs_you)); + } + void post('/api/seen').catch(() => {}); + if (!state.agentAvailable) { + $('banner').hidden = false; + $('banner').textContent = '当前 MiniMax Code 没有向 Mini App 开放 Agent,MoMo 暂时无法对话和派任务。'; + } +} + +/** @param {any[]} items */ +function countBy(items) { + return { + pending_review_count: items.filter((item) => item.status === 'pending_review').length, + blocked_count: items.filter((item) => item.blocked).length, + todo_count: items.filter((item) => item.status === 'todo').length, + }; +} + +// --------------------------------------------------------------------- derived data + +const ideaById = (/** @type {string} */ id) => state.ideas.find((/** @type {any} */ idea) => idea.id === id); +const taskById = (/** @type {string} */ id) => state.tasks.find((/** @type {any} */ task) => task.id === id); +const tasksOf = (/** @type {string} */ ideaId) => state.tasks.filter((/** @type {any} */ task) => task.ideaId === ideaId); +const recordsOf = (/** @type {string | null} */ ideaId) => state.records.filter((/** @type {any} */ record) => record.ideaId === ideaId); + +/** Tasks where the user is the bottleneck, ordered by urgency (Claude Projects "Waiting on you"). */ +function needsYou() { + const order = (/** @type {any} */ task) => + task.blocked?.reason === 'need_approval' ? 0 + : task.blocked?.reason === 'need_info' ? 1 + : task.status === 'pending_review' ? 2 + : task.blocked ? 3 + : 4; + return state.tasks + .filter((/** @type {any} */ task) => task.status === 'pending_review' || task.blocked || (task.status === 'todo' && task.consent !== 'later')) + .sort((/** @type {any} */ a, /** @type {any} */ b) => order(a) - order(b) || b.updatedAt - a.updatedAt); +} + +/** Running = in progress, not blocked, or dispatch in flight. */ +const isRunning = (/** @type {any} */ task) => (task.status === 'in_progress' && !task.blocked) || Boolean(task.dispatch); + +// --------------------------------------------------------------------- task presentation + +/** One-line live status, Multica-style: renders only while a run is active. @param {any} task */ +function liveHtml(task) { + const live = state.activity?.[task.id]; + if (task.dispatch) return '正在开工'; + if (!live || task.status !== 'in_progress' || task.blocked) return ''; + if (live.queued) return `排队中 · 第 ${live.queued} 位`; + const doing = live.tool ? toolName(live.tool) : task.progress || '思考中'; + const detail = `${doing}${live.steps ? ` · ${live.steps} 步` : ''}`; + return `执行中 · ${duration(Date.now() - (task.runStartedAt ?? live.startedAt))}· ${esc(detail)}`; +} + +/** The single next step for the task, by state (Multica "what to do" per reason). @param {any} task @param {{ compact?: boolean }} [opts] */ +function primaryActions(task, opts = {}) { + const id = esc(task.id); + const b = (/** @type {string} */ action, /** @type {string} */ label, kind = 'primary') => ``; + const reason = task.blocked?.reason; + if (reason === 'need_approval') return b('open-task', '去批准'); + if (reason === 'need_info') return b('open-task', '去回答'); + if (reason === 'run_failed') return task.sessionId ? b('continue-task', '继续执行') + b('open-session', '看会话', 'secondary') : b('start-task', '重新开始'); + if (reason === 'project_unavailable') return task.sessionId && task.status !== 'todo' ? b('continue-task', '重试') : b('set-project', '设置项目目录') + (task.status === 'todo' ? b('start-task', '重试开始', 'secondary') : ''); + if (task.status === 'pending_review') return b('approve-task', '可以') + b('revise-task', '提修改意见', 'secondary'); + if (task.status === 'todo' && !task.dispatch) { + if (task.interrupted) return b('continue-task', '继续执行') + (opts.compact ? '' : b('stop-forget', '不做了', 'secondary')); + return b('start-task', '开始') + (opts.compact || task.consent === 'later' ? '' : b('defer-task', '先不做', 'secondary')); + } + return ''; +} + +/** Explains the state in plain words; empty when the card's live line already says it. @param {any} task */ +function reasonLine(task) { + if (task.blocked) return task.blocked.detail ?? ''; + if (task.status === 'todo' && task.interrupted) return task.interrupted.by === 'user' ? '你停了这次执行,会话还在,可以接着做' : '执行在会话里被停止了,会话还在,可以接着做'; + if (task.status === 'todo' && task.consent === 'later') return '先放着,想做时点开始'; + if (task.status === 'pending_review') return task.verification?.notes ?? `${task.outputs.length} 个产出等你看`; + if (task.status === 'done') return `${clock(task.finishedAt ?? task.updatedAt)} 完成${task.outputs.length ? ` · ${task.outputs.length} 个产出` : ''}`; + return ''; +} + +/** @param {any} task */ +function statusChip(task) { + if (task.blocked) { + const danger = task.blocked.reason === 'run_failed' || task.blocked.reason === 'project_unavailable'; + return `${esc(BLOCKED[task.blocked.reason])}`; + } + if (task.status === 'pending_review') return '待审核'; + if (task.status === 'done') return '已完成'; + if (task.status === 'todo' && task.interrupted) return '已暂停'; + return ''; +} + +/** @param {any} task */ +function projectChip(task) { + const dir = task.projectDir ?? null; + return dir + ? `${esc(base(dir))}` + : ''; +} + +/** Compact card (Multica board-card): 2-line title, chips only when informative. @param {any} task @param {{ showStatus?: boolean, showIdea?: boolean }} [opts] */ +function taskCard(task, opts = {}) { + const attention = task.blocked || task.status === 'pending_review'; + const idea = ideaById(task.ideaId); + const live = liveHtml(task); + const reason = live ? '' : reasonLine(task); + const actions = primaryActions(task, { compact: true }); + return `
+
${esc(task.title)}${opts.showStatus === false ? '' : statusChip(task)}
+ ${live ? `
${live}
` : ``} + ${reason ? `
${esc(reason)}
` : ''} +
${projectChip(task)}${opts.showIdea === false || !idea ? '' : `${esc(idea.title)}`}${ago(task.updatedAt)}
+ ${actions ? `
${actions}
` : ''} +
`; +} + +/** Live task card inside the chat (Claude Projects thread card / Devin session status). @param {any} task */ +function chatTaskCard(task) { + const live = liveHtml(task); + const reason = live ? '' : reasonLine(task); + const attention = task.blocked || task.status === 'pending_review' || (task.status === 'todo' && task.consent === 'pending'); + const actions = primaryActions(task); + const consent = task.status === 'todo' && !task.sessionId && !task.blocked && task.consent !== 'given'; + return `
+
${esc(task.title)}${statusChip(task) || `${STATUS[/** @type {keyof typeof STATUS} */ (task.status)]}`}
+ ${consent ? `
做什么
${esc(task.objective)}
产出
${esc(task.deliverable)}
做完
${esc(task.doneCriteria)}
在哪做
${task.projectDir ? `${esc(shortPath(task.projectDir))}` : '不绑定项目,产出放到协作的产出目录'}
` : ''} + ${live ? `
${live}
` : ``} + ${reason ? `
${esc(reason)}
` : ''} + ${task.blocked?.reason === 'need_approval' && task.approvals[0] ? approvalCard(task.approvals[0], task.id) : ''} + ${task.blocked?.reason === 'need_info' && task.question ? questionCard(task.question, task.id) : ''} +
${task.blocked?.reason === 'need_approval' || task.blocked?.reason === 'need_info' ? '' : actions}
+
`; +} + +/** @param {any} approval @param {string} owner 'momo' or taskId */ +function approvalCard(approval, owner) { + return `
+
需要你批准:${esc(toolName(approval.toolName))}待批准
+ ${approval.reason ? `
${esc(approval.reason)}
` : ''} + ${approval.toolInput ? `
${esc(approval.toolInput)}
` : ''} +
+ + +
+
`; +} + +/** + * Inline questionnaire with option chips (Qoder: choices + recommended + write your own). + * Single-select single-step questions answer on click; others collect then submit. + * @param {any} pending @param {string} owner 'momo' or taskId + */ +function questionCard(pending, owner) { + const request = pending.request; + const picked = ui.answers.get(pending.requestId) ?? new Map(); + const quick = request.steps.length === 1 && request.steps[0].selectionMode === 'single'; + const steps = request.steps + .map((/** @type {any} */ step) => { + const selected = picked.get(step.id) ?? new Set(); + return `
+
${md(step.header && step.header !== step.question ? `**${step.header}** ${step.question}` : step.question)}
+ ${step.description ? `
${esc(step.description)}
` : ''} +
${step.options + .map((/** @type {any} */ option, /** @type {number} */ index) => { + const recommended = index === 0 && /推荐|recommend/i.test(`${option.label}${option.description ?? ''}`); + return ``; + }) + .join('')}
+ +
`; + }) + .join(''); + return `
+ ${request.title ? `${esc(request.title)}` : ''} + ${steps} +
${quick ? '' : ``}
+
`; +} + +// --------------------------------------------------------------------- render + +const NAV = /** @type {const} */ ([ + ['chat', '对话'], + ['inbox', '待处理'], + ['board', '看板'], + ['ideas', '想法'], +]); + +function render() { + if (!state) return; + applyAvatar(); + const attention = needsYou().filter((/** @type {any} */ task) => task.status !== 'todo' || task.blocked).length; + const todo = needsYou().length - attention; + document.title = attention ? `(${attention}) 协作 · MoMo` : '协作 · MoMo'; + $('nav').innerHTML = NAV.map(([view, label]) => { + const badge = view === 'inbox' ? (attention ? `${attention}` : todo ? `${todo}` : '') : view === 'board' && attention ? `${attention}` : ''; + return ``; + }).join(''); + $('popout').hidden = !windowApi || ui.windowMode === 'window'; + $('main').dataset.view = ui.view; + if (ui.focusIdeaId && !ideaById(ui.focusIdeaId)) ui.focusIdeaId = null; + renderChat(); + renderSide(); + renderComposer(); + renderDrawer(); +} + +/** Updates only the live lines (activity frames arrive often). */ +function renderLive() { + document.querySelectorAll('[data-live]').forEach((node) => { + const task = taskById(/** @type {HTMLElement} */ (node).dataset.live ?? ''); + if (!task) return; + const html = liveHtml(task); + node.innerHTML = html; + /** @type {HTMLElement} */ (node).hidden = !html; + }); +} + +function renderComposer() { + const busy = state.momo?.busy; + $('stop').hidden = !busy; + /** @type {HTMLButtonElement} */ ($('send')).disabled = !state.agentAvailable; + const focus = ui.focusIdeaId ? ideaById(ui.focusIdeaId) : null; + const context = $('composer-context'); + context.hidden = !focus; + context.innerHTML = focus + ? `正在聊:${esc(focus.title)}` + : ''; + $('attachments').innerHTML = ui.attachments + .map((item) => `待发送图片`) + .join(''); +} + +let lastChatSignature = ''; +function renderChat() { + const log = $('chat-log'); + const messages = state.chat.filter((/** @type {any} */ message) => message.status !== 'merged'); + const pending = state.momo ?? {}; + const referenced = new Set(messages.flatMap((/** @type {any} */ m) => m.receipts.filter((/** @type {any} */ r) => r.taskId).map((/** @type {any} */ r) => r.taskId))); + for (const entry of ui.report?.needs_you ?? []) referenced.add(entry.task_id); + const taskSig = state.tasks.filter((/** @type {any} */ t) => referenced.has(t.id)).map((/** @type {any} */ t) => [t.id, t.status, t.blocked?.reason, t.consent, t.updatedAt, t.dispatch ? 1 : 0, t.approvals.length, t.question?.requestId]); + const signature = JSON.stringify([messages.map((/** @type {any} */ m) => [m.id, m.status, m.text.length, m.receipts.length, m.tools.length]), pending.question?.requestId, pending.approvals?.map((/** @type {any} */ a) => a.requestId), ui.report ? 1 : 0, state.ideas.length, taskSig, [...ui.answers.keys()]]); + if (signature === lastChatSignature) return; + lastChatSignature = signature; + const nearBottom = log.scrollHeight - log.scrollTop - log.clientHeight < 80; + const active = document.activeElement; + const typing = active instanceof HTMLInputElement && active.dataset.other ? { step: active.dataset.other, request: active.dataset.request, value: active.value } : null; + if (!messages.length && !state.ideas.length) { + log.innerHTML = `

我是 MoMo,你的项目管家

+

随手丢给我一句话、一张截图或一个链接。我帮你攒着、理清楚,写成任务后替你安排会话去做,做完先核对再交给你。

+
${FIRST_PROMPTS.map((text) => ``).join('')}
`; + return; + } + // A task card is shown once, at its latest mention; earlier mentions collapse to a link. + const lastMention = new Map(); + messages.forEach((/** @type {any} */ m, /** @type {number} */ index) => m.receipts.forEach((/** @type {any} */ r) => r.taskId && r.kind === 'task_card' && lastMention.set(r.taskId, index))); + const parts = []; + let lastDay = ''; + messages.forEach((/** @type {any} */ message, /** @type {number} */ index) => { + const day = new Date(message.createdAt).toDateString(); + if (day !== lastDay && lastDay) parts.push(`
${new Date(message.createdAt).toLocaleDateString('zh-CN', { month: 'long', day: 'numeric', weekday: 'short' })}
`); + lastDay = day; + parts.push(renderMessage(message, index, lastMention)); + }); + if (pending.question) parts.push(`
${questionCard(pending.question, 'momo')}
`); + for (const approval of pending.approvals ?? []) parts.push(`
${approvalCard(approval, 'momo')}
`); + if (ui.report) parts.push(renderReport(ui.report)); + if (!pending.busy && !pending.question) parts.push(`
${LATER_PROMPTS.map((text) => ``).join('')}
`); + log.innerHTML = parts.join(''); + if (typing) { + const input = /** @type {HTMLInputElement | null} */ (log.querySelector(`input[data-other="${CSS.escape(typing.step ?? '')}"][data-request="${CSS.escape(typing.request ?? '')}"]`)); + if (input) { + input.value = typing.value; + input.focus(); + } + } + scrollChat(nearBottom); +} + +/** @param {boolean} force */ +function scrollChat(force) { + const log = $('chat-log'); + if (force || log.scrollHeight - log.scrollTop - log.clientHeight < 120) log.scrollTop = log.scrollHeight; +} + +/** @param {any} message @param {number} index @param {Map} lastMention */ +function renderMessage(message, index, lastMention) { + // Chips (receipts, task links) flow inline; full task cards stack below them. + /** @type {string[]} */ + const chips = []; + /** @type {string[]} */ + const cards = []; + for (const receipt of message.receipts) { + if (receipt.kind === 'task_card' || receipt.kind === 'task') { + const task = taskById(receipt.taskId); + if (!task) continue; + if (lastMention.get(task.id) === index || (receipt.kind === 'task' && !lastMention.has(task.id))) cards.push(chatTaskCard(task)); + else chips.push(``); + } else { + chips.push(renderReceipt(receipt)); + } + } + const attachments = `${chips.length ? `
${chips.join('')}
` : ''}${cards.join('')}`; + if (message.role === 'system') { + return `
${message.text ? `
${esc(message.text)}
` : ''}${attachments}
`; + } + const images = message.images.map((/** @type {any} */ image) => `附图`).join(''); + if (message.role === 'user') { + const focus = message.focusIdeaId ? ideaById(message.focusIdeaId) : null; + return `
${message.text ? `
${esc(message.text)}
` : ''}${images} + ${focus ? `关于「${esc(focus.title)}」 · ` : ''}${message.kind === 'quick_capture' ? '随手记 · ' : ''}${clock(message.createdAt)}
`; + } + const live = message.status === 'thinking' || message.status === 'streaming'; + const text = live ? ui.streams.get(message.id) ?? '' : message.text; + const running = message.tools.filter((/** @type {any} */ tool) => tool.ok === null); + const toolLine = live && running.length ? `
${running.map((/** @type {any} */ tool) => `正在${esc(toolName(tool.name))}…`).join('')}
` : ''; + const failed = message.tools.filter((/** @type {any} */ tool) => tool.ok === false); + return `
+ +
+ ${text ? `
${md(text)}
` : live ? `
` : ''} + ${toolLine} + ${attachments} + ${failed.length ? `
${failed.map((/** @type {any} */ tool) => `${esc(toolName(tool.name))}失败`).join('')}
` : ''} + ${clock(message.createdAt)} +
+
`; +} + +/** @param {any} receipt */ +function renderReceipt(receipt) { + const attrs = `data-action="open-receipt" data-idea="${esc(receipt.ideaId ?? '')}" data-record="${esc(receipt.recordId ?? '')}"`; + const icon = ''; + switch (receipt.kind) { + case 'new_idea': + return ``; + case 'record': + return ``; + case 'unattributed': + return ``; + case 'moved': + return ``; + case 'gates': + return ``; + case 'task_done': + return ``; + default: + return ''; + } +} + +/** Away briefing: the list was captured on arrival, but each row reflects the task as it is now. @param {any} report */ +function renderReport(report) { + const entries = report.needs_you + .map((/** @type {any} */ entry) => ({ entry, task: taskById(entry.task_id) })) + .filter(({ task }) => task && (task.status === 'pending_review' || task.blocked || (task.status === 'todo' && task.consent !== 'later'))); + const verb = (/** @type {any} */ task) => (task.status === 'pending_review' ? '等你审核' : task.blocked ? BLOCKED[task.blocked.reason] ?? '受阻' : task.interrupted ? '已暂停' : '等你说开始'); + return `
+ ${entries.length ? `你不在的时候,有 ${entries.length} 件事等你:` : '你不在时等你的事都处理完了'} +
${entries.slice(0, 6).map(({ entry, task }) => ``).join('')}
+ ${report.running.length ? `
还在跑:${report.running.map((/** @type {any} */ entry) => `「${esc(entry.title)}」`).join('、')}
` : ''} + ${report.almost_ready_idea ? `
「」只差${esc(report.almost_ready_idea.missing.join('、'))}
` : ''} +
+
`; +} + +function renderSide() { + const pane = $('pane-side'); + const view = ui.view === 'chat' ? 'rail' : ui.view; + if (view === 'board') pane.innerHTML = renderBoard(); + else if (view === 'ideas') pane.innerHTML = renderIdeas(); + else pane.innerHTML = renderRail(); + const select = /** @type {HTMLSelectElement | null} */ (pane.querySelector('#board-filter')); + if (select) select.value = ui.boardFilter; +} + +/** Rail / inbox: 等你处理 → 进行中 → recent. */ +function renderRail() { + const waiting = needsYou(); + const urgent = waiting.filter((/** @type {any} */ task) => task.status !== 'todo' || task.blocked); + const todo = waiting.filter((/** @type {any} */ task) => task.status === 'todo' && !task.blocked); + const running = state.tasks.filter(isRunning); + const recent = state.tasks.filter((/** @type {any} */ task) => task.status === 'done').slice(0, 3); + const later = state.tasks.filter((/** @type {any} */ task) => task.status === 'todo' && task.consent === 'later' && !task.blocked); + const section = (/** @type {string} */ title, /** @type {any[]} */ items, /** @type {string} */ empty, badge = false) => `
${title}${items.length ? `${items.length}` : ''}
+ ${items.length ? items.map((task) => taskCard(task)).join('') : empty ? `
${empty}
` : ''}
`; + if (!state.tasks.length) { + return `
还没有任务想法的三项门槛写清后,MoMo 会写成任务,进度会出现在这里。
${renderIdeaShortlist()}
`; + } + return `
+ ${section('等你处理', urgent, '没有要你处理的事', true)} + ${running.length ? section('进行中', running, '') : ''} + ${todo.length ? section('待开始', todo, '') : ''} + ${later.length ? section('先放着', later, '') : ''} + ${recent.length ? section('最近完成', recent, '') : ''} + ${renderIdeaShortlist()} +
`; +} + +function renderIdeaShortlist() { + const ideas = state.ideas.filter((/** @type {any} */ idea) => !tasksOf(idea.id).some((/** @type {any} */ task) => task.status !== 'done')).slice(0, 4); + if (!ideas.length) return ''; + return `
想法
${ideas.map(ideaCard).join('')}
`; +} + +/** @param {any} idea */ +function ideaCard(idea) { + const tasks = tasksOf(idea.id); + return ``; +} + +function renderBoard() { + const tasks = state.tasks.filter((/** @type {any} */ task) => !ui.boardFilter || task.ideaId === ui.boardFilter); + const urgency = (/** @type {any} */ task) => (task.blocked ? 0 : 1); + const columns = ['todo', 'in_progress', 'pending_review', 'done'] + .map((status) => { + const items = tasks.filter((/** @type {any} */ task) => task.status === status).sort((/** @type {any} */ a, /** @type {any} */ b) => urgency(a) - urgency(b) || b.updatedAt - a.updatedAt); + return `
${STATUS[/** @type {keyof typeof STATUS} */ (status)]}${items.length}
+
${items.length ? items.map((task) => taskCard(task, { showStatus: Boolean(task.blocked) || (status === 'todo' && task.interrupted) })).join('') : '
暂无
'}
`; + }) + .join(''); + return `
状态跟着会话走,看板不能拖拽
${columns}
`; +} + +function renderIdeas() { + const loose = recordsOf(null); + return `
+ ${loose.length ? `
未归属${loose.length}
${loose + .map((/** @type {any} */ record) => `
${esc(record.text || '(图片)')}
`) + .join('')}
` : ''} +
想法${state.ideas.length}
+ ${state.ideas.length ? state.ideas.map(ideaCard).join('') : '
还没有想法,在对话里跟 MoMo 说一句就能开始
'}
+
`; +} + +// --------------------------------------------------------------------- drawer (task / idea detail) + +function renderDrawer() { + const drawer = $('drawer'); + if (!ui.drawer) { + drawer.hidden = true; + return; + } + const entity = ui.drawer.kind === 'task' ? taskById(ui.drawer.id) : ideaById(ui.drawer.id); + if (!entity) { + ui.drawer = null; + drawer.hidden = true; + return; + } + drawer.hidden = false; + $('drawer-title').textContent = entity.title; + const scroll = $('drawer-body').scrollTop; + $('drawer-body').innerHTML = ui.drawer.kind === 'task' ? taskDetail(entity) : ideaDetail(entity); + $('drawer-body').scrollTop = scroll; + if (ui.drawer.highlight) { + const node = $('drawer-body').querySelector(`[data-record-id="${CSS.escape(ui.drawer.highlight)}"]`); + node?.closest('details')?.setAttribute('open', ''); + node?.scrollIntoView({ block: 'center' }); + ui.drawer.highlight = undefined; + } +} + +/** @param {any} task */ +function taskDetail(task) { + const idea = ideaById(task.ideaId); + const live = liveHtml(task); + const reason = reasonLine(task); + const actions = primaryActions(task); + const checks = task.verification?.checks?.length ? task.verification.checks : task.criteria ?? []; + const history = [...task.history].reverse().slice(0, 12); + const editableProject = task.status === 'todo' && !task.sessionId; + return ` +
${STATUS[/** @type {keyof typeof STATUS} */ (task.status)]}${statusChip(task)}${ago(task.updatedAt)}更新
+ ${live ? `
${live}
` : ``} + ${reason ? `
${esc(reason)}
` : ''} + ${task.blocked?.reason === 'need_approval' ? task.approvals.map((/** @type {any} */ a) => approvalCard(a, task.id)).join('') : ''} + ${task.blocked?.reason === 'need_info' && task.question ? questionCard(task.question, task.id) : ''} + ${actions || task.sessionId ? `
${actions}${task.sessionId ? '' : ''}${task.status === 'in_progress' ? '' : ''}
` : ''} + + ${task.status === 'pending_review' || task.status === 'done' ? `

结果

+ ${task.summary ? `
${md(task.summary)}
` : '
没有提交结论
'} + ${checks.length ? `
${checks.map((/** @type {any} */ c) => `
${c.met ? '✓' : '!'}${esc(c.criterion)}${c.evidence ? `
${esc(c.evidence)}
` : ''}
`).join('')}
` : ''} + ${task.verification ? `
MoMo 核对${task.verification.verdict === 'pass' ? '通过' : '有疑问'}${task.verification.notes ? `:${esc(task.verification.notes)}` : ''}
` : ''} +
` : ''} + + ${task.outputs.length ? `

产出 · ${task.outputs.length}

${task.outputs.map((/** @type {any} */ o, /** @type {number} */ i) => `
${esc(base(o.file))}${o.description ? `
${esc(o.description)}
` : ''}
${o.reviewed ? '已审核' : ''}
`).join('')} + ${task.fileCheck?.missing?.length ? `
找不到:${esc(task.fileCheck.missing.map(base).join('、'))}
` : ''}
` : ''} + +

完成标准

+
做什么${esc(task.objective)}
+
产出什么${esc(task.deliverable)}
+
怎么算做完${esc(task.doneCriteria)}
+ ${task.notes ? `
${esc(task.notes)}
` : ''} +
+ +

在哪做

+
${task.projectDir ? esc(task.projectDir) : task.workspace ? esc(task.workspace) : '不绑定项目,产出放到协作的产出目录'}${editableProject ? `` : ''}
+
+ + ${idea ? `

来源想法

` : ''} + +

经过

    ${history.map((/** @type {any} */ h) => `
  • ${esc(h.note || STATUS[/** @type {keyof typeof STATUS} */ (h.to)])}${h.from ? ` → ${STATUS[/** @type {keyof typeof STATUS} */ (h.to)]}` : ''}${clock(h.at)}
  • `).join('')}
`; +} + +/** @param {any} idea */ +function ideaDetail(idea) { + const tasks = tasksOf(idea.id); + const records = recordsOf(idea.id); + const confirmed = idea.facts.filter((/** @type {any} */ f) => f.certainty === 'confirmed'); + const inferred = idea.facts.filter((/** @type {any} */ f) => f.certainty !== 'confirmed'); + const gate = (/** @type {[string, string]} */ [key, label]) => { + const value = idea.gates[key]; + if (!value) return `
${label}还没写清
`; + return `
${label}${value.certainty === 'confirmed' ? '' : `推断`}${esc(value.text)}
`; + }; + const fact = (/** @type {any} */ item) => `
${item.certainty === 'confirmed' ? '' : `推断`}${esc(item.text)}
`; + return ` +
${[0, 1, 2].map((i) => ``).join('')}门槛 ${idea.progress.filled}/3${idea.progress.missing.length ? ` · 还差${esc(idea.progress.missing.join('、'))}` : ''} +
+

下一个任务的三项门槛

${GATES.map(gate).join('')}
+ ${confirmed.length ? `

已确定

${confirmed.map(fact).join('')}
` : ''} + ${inferred.length ? `

待确认

${inferred.map(fact).join('')}
` : ''} +

任务 · ${tasks.length}

${tasks.length ? tasks.map((task) => taskCard(task, { showIdea: false })).join('') : '
还没有任务
'}
+
记录 · ${records.length}${records.map(renderRecord).join('')}
`; +} + +/** @param {any} record */ +function renderRecord(record) { + const images = record.images.map((/** @type {any} */ image) => `记录附图`).join(''); + const source = /** @type {Record} */ ({ chat: '对话', quick_capture: '随手记', session_handoff: '会话交接', session_return: '任务回传', review: '修改意见' })[record.source] ?? record.source; + return `
${esc(record.text)}${images} + ${clock(record.createdAt)} · ${esc(source)}${record.movedFrom ? ` · 从「${esc(record.movedFrom.title)}」移来` : ''} + ${record.attribution === 'inferred' ? `归属推断` : ''}
`; +} + +// --------------------------------------------------------------------- modals + +const modal = /** @type {HTMLDialogElement} */ ($('modal')); +/** @param {string} html @param {(form: HTMLFormElement) => Promise | void} [onSubmit] */ +function openModal(html, onSubmit) { + modal.innerHTML = `
${html}
`; + const form = /** @type {HTMLFormElement} */ (modal.querySelector('form')); + form.addEventListener('submit', async (event) => { + const submitter = /** @type {HTMLButtonElement | null} */ (/** @type {SubmitEvent} */ (event).submitter); + if (!submitter || submitter.value === 'cancel' || !onSubmit) return; + event.preventDefault(); + submitter.disabled = true; + try { + await onSubmit(form); + modal.close(); + } catch (error) { + showError(error); + } finally { + submitter.disabled = false; + } + }); + modal.showModal(); +} + +/** @param {string} title @param {string} text @param {string} confirmLabel */ +function confirmDialog(title, text, confirmLabel) { + return new Promise((done) => { + openModal(`

${esc(title)}

${esc(text)}

`, () => done(true)); + modal.addEventListener('close', () => done(false), { once: true }); + }); +} + +/** @param {string} recordId */ +function moveRecordDialog(recordId) { + const record = state.records.find((/** @type {any} */ item) => item.id === recordId); + const options = state.ideas.filter((/** @type {any} */ idea) => idea.id !== record?.ideaId).map((/** @type {any} */ idea) => ``).join(''); + openModal( + `

记到哪个想法?

${esc(record?.text?.slice(0, 120) ?? '')}

${options || '没有其他想法'}
+ +
`, + async (form) => { + const data = new FormData(form); + const newIdeaTitle = String(data.get('newIdeaTitle') ?? '').trim(); + const ideaId = String(data.get('idea') ?? ''); + if (!newIdeaTitle && !ideaId) throw new Error('选一个想法或写新想法的名字'); + const result = await post(`/api/records/${encodeURIComponent(recordId)}/move`, newIdeaTitle ? { newIdeaTitle } : { ideaId }); + ui.drawer = { kind: 'idea', id: result.idea.id, highlight: recordId }; + toast(`已改记到「${result.idea.title}」`); + }, + ); +} + +/** @param {any} task */ +function projectDialog(task) { + const known = [...new Set(state.tasks.map((/** @type {any} */ t) => t.projectDir).filter(Boolean))]; + openModal( + `

「${esc(task.title)}」在哪做?

填项目文件夹的绝对路径(支持 ~/),任务会在这个目录里新开会话执行。留空表示不绑定项目。

+ ${known.length ? `
${known.map((dir) => ``).join('')}
` : ''} + +
`, + async (form) => { + await taskPost(task.id, 'project', { projectDir: String(new FormData(form).get('projectDir') ?? '') }); + toast('项目目录已更新'); + }, + ); + modal.querySelectorAll('[data-fill]').forEach((button) => + button.addEventListener('click', () => { + /** @type {HTMLInputElement} */ (modal.querySelector('input[name="projectDir"]')).value = /** @type {HTMLElement} */ (button).dataset.fill ?? ''; + }), + ); +} + +/** @param {any} task */ +function reviseDialog(task) { + openModal( + `

对「${esc(task.title)}」提修改意见

意见会回到原来的会话继续改,不用重新交代背景。

+
${['把没满足的完成标准补上', '结论再精简一些', '补充来源和证据'].map((text) => ``).join('')}
+ +
`, + async (form) => { + await taskPost(task.id, 'revise', { feedback: String(new FormData(form).get('feedback') ?? '') }); + toast(`「${task.title}」回到进行中`); + }, + ); + modal.querySelectorAll('[data-fill]').forEach((button) => + button.addEventListener('click', () => { + const area = /** @type {HTMLTextAreaElement} */ (modal.querySelector('textarea')); + area.value = area.value ? `${area.value}\n${/** @type {HTMLElement} */ (button).dataset.fill}` : /** @type {HTMLElement} */ (button).dataset.fill ?? ''; + area.focus(); + }), + ); +} + +/** @param {string} taskId @param {number} index */ +async function previewOutput(taskId, index) { + const task = taskById(taskId); + const output = task?.outputs[index]; + if (!output) return; + const response = await fetch(`/api/tasks/${encodeURIComponent(taskId)}/outputs/${index}`); + if (!response.ok) { + const data = await response.json().catch(() => ({})); + throw new Error(data.message ?? '无法预览'); + } + const type = response.headers.get('content-type') ?? ''; + let body; + if (type.startsWith('image/')) body = `${esc(base(output.file))}`; + else if (type.startsWith('text/')) { + const text = await response.text(); + body = /\.(md|markdown)$/i.test(output.file) ? `
${md(text)}
` : `
${esc(text)}
`; + } else body = `

这种文件不能在这里预览,请在文件夹里打开:

${esc(output.file)}`; + openModal(`

${esc(base(output.file))}

${esc(output.file)}

${body}
`); +} + +function avatarDialog() { + const current = state.settings.momoColor; + openModal( + `

MoMo 的形象

${Object.keys(COLORS).map((color) => ``).join('')}
+ +
`, + ); +} + +// --------------------------------------------------------------------- actions + +/** @param {any} task */ +async function openSession(task) { + if (!task.sessionId) return; + track('collab_board_card_click', { task_id: task.id, status_type: task.status, action: 'session' }); + if (!agentApi?.open) { + toast('这个页面不在 MiniMax Code 会话里打开,没法跳到会话'); + return; + } + try { + await agentApi.open(task.sessionId); + } catch (error) { + const code = error instanceof Error ? error.message : ''; + toast(code === 'session_not_found' ? '这个会话已经不存在了' : code === 'not_permitted' ? '请在 MiniMax Code 的会话侧栏里打开协作,才能跳到任务会话' : '打不开会话,稍后再试'); + } +} + +/** @param {string} owner @param {string} requestId @param {QuestionAnswer[]} answers */ +async function submitAnswers(owner, requestId, answers) { + ui.answers.delete(requestId); + if (owner === 'momo') await post('/api/momo/answer', { requestId, answers }); + else await taskPost(owner, 'answer', { requestId, answers }); +} + +/** @param {string} owner */ +function pendingFor(owner) { + return owner === 'momo' ? state.momo?.question : taskById(owner)?.question; +} + +/** @param {string} owner @param {string} requestId @param {{ stepId?: string, otherText?: string }} [extra] */ +function collectAnswers(owner, requestId, extra = {}) { + const pending = pendingFor(owner); + if (!pending || pending.requestId !== requestId) return null; + const picked = ui.answers.get(requestId) ?? new Map(); + const log = $('drawer').hidden ? $('chat-log') : $('drawer-body'); + return pending.request.steps.map((/** @type {any} */ step) => { + const ids = [...(picked.get(step.id) ?? [])]; + const input = /** @type {HTMLInputElement | null} */ (document.querySelector(`input[data-other="${CSS.escape(step.id)}"][data-request="${CSS.escape(requestId)}"]`) ?? log.querySelector(`input[data-other="${CSS.escape(step.id)}"]`)); + const otherText = extra.stepId === step.id ? extra.otherText ?? '' : input?.value.trim() ?? ''; + const selectedOther = Boolean(otherText); + return { + stepId: step.id, + selectedOptionIds: ids, + selectedOther, + ...(selectedOther ? { otherText } : {}), + ...(!ids.length && !selectedOther ? { skipped: true } : {}), + }; + }); +} + +/** @param {File} file */ +async function upload(file) { + const response = await fetch('/api/uploads', { method: 'POST', headers: { 'content-type': file.type }, body: file }); + const data = await response.json().catch(() => ({})); + if (!response.ok) throw new Error(data.message ?? '图片上传失败'); + return data.upload; +} + +/** @param {FileList | File[]} files */ +async function addFiles(files) { + for (const file of [...files].slice(0, 9 - ui.attachments.length)) { + if (!file.type.startsWith('image/')) continue; + try { + const item = await upload(file); + ui.attachments.push({ id: item.id, url: `/api/uploads/${encodeURIComponent(item.id)}` }); + } catch (error) { + showError(error); + } + } + renderComposer(); +} + +/** @param {string} text */ +async function send(text) { + const trimmed = text.trim(); + if (!trimmed && !ui.attachments.length) return; + const attachments = ui.attachments.map((item) => item.id); + ui.attachments = []; + ui.report = null; + try { + await post('/api/chat', { text: trimmed, attachments, focusIdeaId: ui.focusIdeaId, source: ui.windowMode === 'window' ? 'quick_capture' : 'chat' }); + } catch (error) { + showError(error); + } + scrollChat(true); +} + +/** @param {'chat' | 'inbox' | 'board' | 'ideas'} view */ +function go(view) { + ui.view = view; + track('collab_view', { view, entry: 'switch' }); + render(); +} + +const input = /** @type {HTMLTextAreaElement} */ ($('input')); +const autosize = () => { + input.style.height = 'auto'; + input.style.height = `${Math.min(input.scrollHeight, 160)}px`; +}; +input.addEventListener('input', autosize); +input.addEventListener('keydown', (event) => { + if (event.key === 'Enter' && !event.shiftKey && !event.isComposing) { + event.preventDefault(); + $('composer').requestSubmit(); + } +}); +input.addEventListener('paste', (event) => { + const files = [...(event.clipboardData?.files ?? [])].filter((file) => file.type.startsWith('image/')); + if (files.length) { + event.preventDefault(); + void addFiles(files); + } +}); +$('composer').addEventListener('submit', (event) => { + event.preventDefault(); + const text = input.value; + input.value = ''; + autosize(); + void send(text); +}); +$('file-input').addEventListener('change', (event) => { + const target = /** @type {HTMLInputElement} */ (event.target); + if (target.files) void addFiles(target.files); + target.value = ''; +}); +document.addEventListener('dragover', (event) => event.preventDefault()); +document.addEventListener('drop', (event) => { + event.preventDefault(); + if (event.dataTransfer?.files.length) void addFiles(event.dataTransfer.files); +}); +$('stop').addEventListener('click', () => void post('/api/chat/stop').catch(showError)); +$('avatar-button').addEventListener('click', avatarDialog); +$('popout').addEventListener('click', () => { + // Stays synchronous inside the click so the Host sees the user gesture. + windowApi?.update({ mode: 'window', width: 1080, height: 720 }).catch(showError); +}); + +document.addEventListener('keydown', (event) => { + const target = /** @type {HTMLElement} */ (event.target); + if (event.key === 'Escape' && ui.drawer && !modal.open) { + ui.drawer = null; + renderDrawer(); + return; + } + if ((event.key === 'Enter' || event.key === ' ') && target.matches('.task[role="button"]')) { + event.preventDefault(); + target.click(); + return; + } + if (event.key === 'Enter' && target instanceof HTMLInputElement && target.dataset.other && target.value.trim()) { + event.preventDefault(); + const owner = target.dataset.owner ?? ''; + const requestId = target.dataset.request ?? ''; + const answers = collectAnswers(owner, requestId, { stepId: target.dataset.other, otherText: target.value.trim() }); + if (answers) void submitAnswers(owner, requestId, answers).catch(showError); + } +}); + +document.addEventListener('change', async (event) => { + const target = /** @type {HTMLInputElement | HTMLSelectElement} */ (event.target); + if (target.id === 'board-filter') { + ui.boardFilter = target.value; + renderSide(); + return; + } + if (target instanceof HTMLInputElement && target.dataset.action === 'avatar-file' && target.files?.[0]) { + try { + const item = await upload(target.files[0]); + await post('/api/settings', { avatarUploadId: item.id }); + modal.close(); + } catch (error) { + showError(error); + } + } +}); + +document.addEventListener('click', async (event) => { + const target = /** @type {HTMLElement | null} */ (/** @type {HTMLElement} */ (event.target).closest('[data-action], [data-view]')); + if (!target) return; + if (target.matches('[role="tab"][data-view]')) { + go(/** @type {any} */ (target.dataset.view)); + return; + } + const action = target.dataset.action; + const task = target.dataset.task ? taskById(target.dataset.task) : undefined; + if (action !== 'open-task') event.stopPropagation(); + try { + switch (action) { + case 'go': + go(/** @type {any} */ (target.dataset.view)); + break; + case 'suggest': + if (target.dataset.text === '贴一张截图试试') /** @type {HTMLInputElement} */ ($('file-input')).click(); + else { + // Fill, don't send (Multica/MiniMax Bot quick prompts): the user can edit first. + input.value = target.dataset.text ?? ''; + autosize(); + input.focus(); + } + break; + case 'remove-attachment': + ui.attachments = ui.attachments.filter((item) => item.id !== target.dataset.id); + renderComposer(); + break; + case 'close-drawer': + ui.drawer = null; + renderDrawer(); + break; + case 'open-task': + if (task) { + ui.drawer = { kind: 'task', id: task.id }; + track('collab_board_card_click', { task_id: task.id, status_type: task.status }); + renderDrawer(); + } + break; + case 'open-idea': + ui.drawer = { kind: 'idea', id: target.dataset.idea ?? '' }; + renderDrawer(); + break; + case 'open-receipt': + if (target.dataset.idea) ui.drawer = { kind: 'idea', id: target.dataset.idea, highlight: target.dataset.record || undefined }; + else go('ideas'); + renderDrawer(); + break; + case 'focus-idea': + ui.focusIdeaId = target.dataset.idea ?? null; + ui.drawer = null; + ui.view = 'chat'; + render(); + input.focus(); + break; + case 'unfocus': + ui.focusIdeaId = null; + renderComposer(); + break; + case 'dismiss-report': + ui.report = null; + renderChat(); + break; + case 'report-task': + if (task) { + track('collab_report_click', { task_id: task.id, ...countBy(state.briefing.needs_you) }); + ui.drawer = { kind: 'task', id: task.id }; + renderDrawer(); + } + break; + case 'open-session': + if (task) await openSession(task); + break; + case 'start-task': + if (task) { + await taskPost(task.id, 'start'); + await post('/api/track', { event: 'collab_board_card_click', fields: { task_id: task.id, status_type: task.status, action: 'start' } }).catch(() => {}); + } + break; + case 'defer-task': + if (task) await taskPost(task.id, 'defer'); + break; + case 'continue-task': + if (task) await taskPost(task.id, 'continue'); + break; + case 'stop-task': + if (task && (await confirmDialog(`停止「${task.title}」?`, '会话会保留,之后可以点「继续执行」接着做。', '停止'))) await taskPost(task.id, 'stop'); + break; + case 'stop-forget': + if (task) await taskPost(task.id, 'defer'); + break; + case 'set-project': + if (task) projectDialog(task); + break; + case 'approve-task': + if (task) { + await taskPost(task.id, 'approve'); + toast(`「${task.title}」已完成`); + } + break; + case 'revise-task': + if (task) reviseDialog(task); + break; + case 'preview-output': + await previewOutput(target.dataset.task ?? '', Number(target.dataset.index)); + break; + case 'approval': { + const owner = target.dataset.owner ?? ''; + const body = { requestId: target.dataset.request, decision: target.dataset.decision }; + target.closest('.inline-card')?.querySelectorAll('button').forEach((b) => ((/** @type {HTMLButtonElement} */ (b)).disabled = true)); + if (owner === 'momo') await post('/api/momo/approval', body); + else await taskPost(owner, 'approval', body); + break; + } + case 'pick-option': { + const owner = target.dataset.owner ?? ''; + const requestId = target.dataset.request ?? ''; + const stepId = target.dataset.step ?? ''; + const optionId = target.dataset.option ?? ''; + const pending = pendingFor(owner); + const step = pending?.request.steps.find((/** @type {any} */ s) => s.id === stepId); + if (!step) break; + const picked = ui.answers.get(requestId) ?? new Map(); + const set = new Set(step.selectionMode === 'multiple' ? picked.get(stepId) ?? [] : []); + if (set.has(optionId)) set.delete(optionId); + else set.add(optionId); + picked.set(stepId, set); + ui.answers.set(requestId, picked); + if (target.dataset.quick === 'true') { + const answers = collectAnswers(owner, requestId); + if (answers) await submitAnswers(owner, requestId, answers); + } else { + renderChat(); + renderDrawer(); + } + break; + } + case 'submit-answer': + case 'skip-answer': { + const owner = target.dataset.owner ?? ''; + const requestId = target.dataset.request ?? ''; + const pending = pendingFor(owner); + if (!pending) break; + const answers = + action === 'skip-answer' + ? pending.request.steps.map((/** @type {any} */ s) => ({ stepId: s.id, selectedOptionIds: [], selectedOther: false, skipped: true })) + : collectAnswers(owner, requestId); + if (answers) await submitAnswers(owner, requestId, answers); + break; + } + case 'confirm-item': + await post(`/api/ideas/${encodeURIComponent(target.dataset.idea ?? '')}/confirm-item`, { kind: target.dataset.kind, itemId: target.dataset.item }); + break; + case 'confirm-record': + await post(`/api/records/${encodeURIComponent(target.dataset.record ?? '')}/confirm`); + break; + case 'move-record': + moveRecordDialog(target.dataset.record ?? ''); + break; + case 'delete-record': + if (await confirmDialog('删除这条记录?', '原话会从想法里移除,无法恢复。', '删除')) await post(`/api/records/${encodeURIComponent(target.dataset.record ?? '')}/delete`); + break; + case 'delete-idea': { + const idea = ideaById(target.dataset.idea ?? ''); + if (idea && (await confirmDialog(`删除想法「${idea.title}」?`, `它的记录会变成未归属,不会丢${tasksOf(idea.id).length ? ';已有的任务会保留' : ''}。`, '删除'))) { + await post(`/api/ideas/${encodeURIComponent(idea.id)}/delete`); + ui.drawer = null; + render(); + } + break; + } + case 'rename-idea': { + const idea = ideaById(target.dataset.idea ?? ''); + if (!idea) break; + openModal( + `

改名

`, + async (form) => { + await post(`/api/ideas/${encodeURIComponent(idea.id)}/rename`, { title: String(new FormData(form).get('title') ?? '') }); + }, + ); + break; + } + case 'pick-color': + await post('/api/settings', { momoColor: target.dataset.color, avatarUploadId: null }); + modal.close(); + break; + default: + break; + } + } catch (error) { + showError(error); + } +}); + +if (windowApi) { + windowApi.getState().then((value) => { + ui.windowMode = value.mode; + render(); + }).catch(() => {}); + windowApi.onDidChangeState((value) => { + ui.windowMode = value.mode; + render(); + }); +} + +// Elapsed time on live lines and "x 分钟前" labels refresh without a server round trip. +setInterval(() => { + if (!state) return; + renderLive(); +}, 15_000); + +connect(); diff --git a/plugins/amszuidas/momo-collab/miniapp/client/client-api.ts b/plugins/amszuidas/momo-collab/miniapp/client/client-api.ts new file mode 100644 index 00000000..2ce7ab69 --- /dev/null +++ b/plugins/amszuidas/momo-collab/miniapp/client/client-api.ts @@ -0,0 +1,387 @@ +/** + * Copyable Mini App Client declarations for TypeScript and JavaScript/JSDoc authoring. + * No Host implementation or runtime import: Electron preload provides window.mcode. + * Keep the .ts filename because Electron dependency packaging filters out .d.ts assets. + * Node start(context) uses miniapp-api.ts instead. See docs/client.md for usage and errors. + */ +export interface MiniAppMessageAPI { + /** + * Ask the user to append plain text to this Mini App's conversation input. + * Accepts at most 100,000 UTF-16 code units and preserves spaces, tabs, and newlines. + * An empty string still requires confirmation and leaves the draft unchanged. + * Resolves after the confirmed append; rejects on cancellation or failure. + * Does not send a message or insert, edit, or delete sent chat history. + */ + append(text: string): Promise; +} + +export type MiniAppAgentQuestionMode = 'questionnaire' | 'feature-enable' | 'plan'; + +export interface MiniAppAgentQuestionOption { + readonly id: string; + readonly label: string; + readonly description?: string; +} + +export interface MiniAppAgentQuestionStep { + readonly id: string; + readonly header?: string; + readonly question: string; + readonly description?: string; + readonly selectionMode: 'single' | 'multiple'; + readonly options: readonly MiniAppAgentQuestionOption[]; + /** Free-text "other" is always accepted; see `MiniAppAgentQuestionAnswer.otherText`. */ + readonly allowOther: true; +} + +/** Same questionnaire the Host chat renders; render every step and answer each one. */ +export interface MiniAppAgentQuestionnaire { + readonly schemaVersion: 2; + readonly id: string; + readonly title?: string; + readonly steps: readonly MiniAppAgentQuestionStep[]; + readonly mode?: MiniAppAgentQuestionMode; + /** Plan review Markdown and other mode-specific data. */ + readonly modePayload?: { readonly [key: string]: unknown }; + /** Unix ms; the request is withdrawn after this. */ + readonly expiresAt?: number; +} + +export interface MiniAppAgentQuestionAnswer { + readonly stepId: string; + readonly selectedOptionIds: readonly string[]; + readonly selectedOther: boolean; + readonly otherText?: string; + readonly skipped?: boolean; +} + +/** A model as the Host's composer selects it; take the values from `models()`. */ +export interface MiniAppAgentModelSelection { + readonly providerId: string; + readonly modelId: string; + /** `''` means thinking is off; it is preserved, distinct from an absent `variant`. */ + readonly variant?: string; + readonly thinking?: { readonly effort: string }; +} + +/** One model this user can use. */ +export interface MiniAppAgentModelOption { + readonly providerId: string; + readonly modelId: string; + readonly displayName: string; + readonly variants?: readonly string[]; + readonly thinking?: { readonly efforts: readonly string[]; readonly default?: string }; +} + +export interface MiniAppAgentModels { + readonly models: readonly MiniAppAgentModelOption[]; + /** What a session without a host chat model starts with. */ + readonly default: MiniAppAgentModelSelection; + /** The host chat's current model (the default when the chat has none); only when opened from a chat. */ + readonly host?: MiniAppAgentModelSelection; +} + +export interface MiniAppAgentAgentOption { + /** Canonical name; what `agent` takes. */ + readonly name: string; + readonly displayName: string; + /** The default agent. */ + readonly primary: boolean; +} + +export interface MiniAppAgentAgents { + readonly agents: readonly MiniAppAgentAgentOption[]; +} + +/** Final outcome of one Agent turn. */ +export interface MiniAppAgentRunResult { + /** Pass back in `run({ sessionId })` to continue the same conversation. */ + readonly sessionId: string; + /** Absent when no turn ran: an `answer` accepted without starting a continuation. */ + readonly turnId?: string; + /** The model the turn was dispatched with; absent when no turn ran. */ + readonly model?: MiniAppAgentModelSelection; + /** `aborted` only when the Host confirmed the abort; an unconfirmed cancel rejects with `result_unknown`. */ + readonly status: 'finished' | 'error' | 'aborted'; + /** Final assistant reply; absent when the turn produced none or did not finish. */ + readonly text?: string; + readonly error?: { readonly code: string; readonly message: string }; + /** Set when the turn ended waiting on a questionnaire; render `request` and continue with `agent.answer`. */ + readonly pendingQuestion?: { + readonly requestId: string; + readonly mode: MiniAppAgentQuestionMode; + readonly request: MiniAppAgentQuestionnaire; + }; +} + +/** + * Progress of one run. `turnId` matches the result's, except that a `permission.requested` raised + * by a delegated child session carries that child's own `sessionId` and `turnId`; `reply` with + * those, and do not filter events by the root turn. Ignore unknown `type` values: new event + * types may be added without notice. + * - `run.queued`: all run slots are taken and the call waits at 1-based `position`; sent once, + * before the turn starts, so it has no `turnId` and names a session only when continuing one. + * - `turn.started`: the Agent began the turn; `model` is the model it was dispatched with. + * - `text.delta`: a piece of the reply as it streams. + * - `message.committed`: one complete assistant message (a turn can have several). + * - `tool.started` / `tool.finished`: a tool call by name; `ok` is false when it failed. + * - `permission.requested`: a tool call waits for approval; decide it with `agent.reply`. + * - `question.requested`: the turn paused on a questionnaire; continue with `agent.answer`. + * - `turn.finished`: always last; same `status`, `text`, and `error` as the run result. + * Every event except `run.queued` names its `sessionId`, so `reply` works before the `run` that + * created the session has returned. `answer` does not: call it after the paused `run` resolves, + * or it rejects as busy. + */ +export type MiniAppAgentRunEvent = + | { readonly type: 'run.queued'; readonly sessionId?: string; readonly position: number } + | { + readonly type: 'turn.started'; + readonly sessionId: string; + readonly turnId: string; + readonly model: MiniAppAgentModelSelection; + } + | { + readonly type: 'text.delta'; + readonly sessionId: string; + readonly turnId: string; + readonly text: string; + } + | { + readonly type: 'message.committed'; + readonly sessionId: string; + readonly turnId: string; + readonly text: string; + } + | { + readonly type: 'tool.started'; + readonly sessionId: string; + readonly turnId: string; + readonly name: string; + } + | { + readonly type: 'tool.finished'; + readonly sessionId: string; + readonly turnId: string; + readonly name: string; + readonly ok: boolean; + } + | { + readonly type: 'permission.requested'; + readonly sessionId: string; + /** Absent when the runtime did not attribute the ask to a turn. */ + readonly turnId?: string; + readonly requestId: string; + readonly toolName: string; + readonly toolInput?: string; + readonly reason: string; + } + | { + readonly type: 'question.requested'; + readonly sessionId: string; + readonly turnId?: string; + readonly requestId: string; + readonly mode: MiniAppAgentQuestionMode; + readonly request: MiniAppAgentQuestionnaire; + } + | { + readonly type: 'turn.finished'; + readonly sessionId: string; + readonly turnId: string; + readonly status: 'finished' | 'error' | 'aborted'; + readonly text?: string; + readonly error?: { readonly code: string; readonly message: string }; + }; + +export interface MiniAppAgentRunInput { + /** Plain text, at most 100,000 UTF-16 code units. */ + readonly text: string; + /** Continue a session this Mini App created earlier; omit to start a new one. */ + readonly sessionId?: string; + /** + * Switch the session to this model before the turn (validated like the composer validates a new + * session's model: it must be one `models()` lists; unusable is `invalid_request`, missing BYOK + * credentials end the turn with `status: 'error'`); the session keeps it. Omit to use the session's current model. + */ + readonly model?: MiniAppAgentModelSelection; + /** + * Only when starting a session (no `sessionId`): an agent `name` from `agents()`. A session + * never changes its agent, so passing it with `sessionId` is `invalid_request`. + */ + readonly agent?: string; + /** Your id for `cancel(requestId)`; 1-128 characters, unique among this page's active runs. */ + readonly requestId?: string; +} + +export interface MiniAppAgentReplyInput { + readonly sessionId: string; + /** `requestId` of the `permission.requested` event. */ + readonly requestId: string; + /** No `allowAlways`: a Mini App never writes the user's global rules. */ + readonly decision: 'allowOnce' | 'deny'; +} + +export interface MiniAppAgentAnswerInput { + readonly sessionId: string; + /** `requestId` of the `question.requested` event or of `result.pendingQuestion`. */ + readonly questionId: string; + /** One answer per questionnaire step. */ + readonly answers: readonly MiniAppAgentQuestionAnswer[]; + /** Same as `MiniAppAgentRunInput.model`, applied before the continuation turn. */ + readonly model?: MiniAppAgentModelSelection; + /** Your id for `cancel(requestId)`, as in `run`. */ + readonly requestId?: string; +} + +export interface MiniAppAgentRunOptions { + readonly onEvent?: (event: MiniAppAgentRunEvent) => void; +} + +/** + * Absent on older Hosts. Calls reject with `not_permitted` unless the manifest declares + * `agentAccess: ["session.run"]`. A new session uses the Agent of the chat this page is open in; + * a page without a host chat uses the primary Agent unless `run({ agent })` / `create({ agent })` + * names one from `agents()`. Continuing a session keeps its Agent. + * The Agent works in the host chat's project directory when it has one, otherwise in this Mini + * App's own Agent directory; a continued session keeps its directory. A new session takes the + * model and thinking level of the host chat (the runtime default when the chat has none) and keeps + * them; `run` / `answer` with `model` switch the session's model first. + * It runs one turn with that Agent's full capabilities (files, shell, Skills, MCP including this + * Mini App's own), and resolves when it ends. + * + * Interaction is yours to render: handle `permission.requested` with `reply` and + * `question.requested` with `answer`. A permission request waits only while this run passed + * `onEvent` or the session is open in a chat side panel through `open`; otherwise it is denied + * and the model is told why. A run with `onEvent` that does not handle the request waits until + * `cancel` or the page closes; there is no timeout. Up to `agentConcurrency` runs (default 4) per + * Mini App run at once, one per session; further `run` / `answer` calls wait in order (up to four + * times that many, see `run.queued`), and `cancel` on a waiting call rejects it with `cancelled`. + * `busy` means the session already has a run in progress or the wait queue is full. + * Failures reject with an `Error` whose `message` is `invalid_request`, `not_permitted`, `busy`, + * `session_not_found`, `runtime_unavailable`, `cancelled`, `result_unknown`, or + * `request_not_found`. `runtime_unavailable` means nothing was started; `result_unknown` means + * the turn may have run (or ended without the Host knowing whether a question is pending), so + * never repeat it automatically. + */ +export interface MiniAppAgentAPI { + run( + input: MiniAppAgentRunInput, + options?: MiniAppAgentRunOptions, + ): Promise; + /** Decide a pending permission request; `request_not_found` when it is unknown or settled. */ + reply(input: MiniAppAgentReplyInput): Promise; + /** + * Answer a questionnaire; the continuation turn is observed like `run`. Rejects with + * `result_unknown` when the answer was recorded but the continuation turn was not observed. + */ + answer( + input: MiniAppAgentAnswerInput, + options?: MiniAppAgentRunOptions, + ): Promise; + /** Ask the Host to stop an active run or answer; unknown or finished ids are ignored. */ + cancel(requestId: string): void; + /** + * Show one of this Mini App's sessions as a tab in the side panel of the chat the + * Mini App is open in, where the user can read the conversation and answer + * permission and question cards themselves. Permission requests wait for the tab only + * while it stays open; once the user closes it, handle `permission.requested` yourself + * or call `open` again. Any session this Mini App owns can be opened, whichever chat + * created it; `session_not_found` otherwise. + */ + open(sessionId: string): Promise; + /** + * Returns the model catalog, runtime `default` and optional chat `host`. Pass `default` / `host` + * directly as `model` to `run` / `answer`. For a catalog entry, extract `providerId` / `modelId`, + * add a selected `variant` if needed and preserve `variant: ''`. If thinking is supported, + * select an effort from `thinking.efforts` and pass `thinking: { effort }`. + */ + models(): Promise; + /** The agents the user has configured; `agent` of `create` / a first `run` must be one of them. */ + agents(): Promise; + /** + * Create a session bound to this chat (agent, project directory, model) without running a + * turn, and return its `sessionId`; continue it with `run({ sessionId })` here or hand it to + * Node code. `agent` picks the session's agent from `agents()`; a session never changes it. + * Takes no run slot. If the Host's 60 s deadline passes it rejects with `runtime_unavailable` + * although the session may already exist. + */ + create(input?: { readonly agent?: string }): Promise<{ readonly sessionId: string }>; +} + +export interface MiniAppClientAPI { + readonly message: MiniAppMessageAPI; + readonly agent?: MiniAppAgentAPI; + /** + * Proposed: shape may still change. Feature-detect with + * `window.mcode?.miniapp?.window` before use. + */ + readonly window?: MiniAppWindowHandle; +} + +/** Live state of the popped-out window: current position, size, and flags — not the manifest declaration. */ +export interface MiniAppWindowOptions { + readonly width: number; + readonly height: number; + readonly minWidth: number; + readonly minHeight: number; + readonly x: number; + readonly y: number; + readonly visible: boolean; + readonly resizable: boolean; + readonly frame: boolean; + readonly transparent: boolean; + readonly alwaysOnTop: boolean; + readonly clickThrough: boolean; + readonly skipTaskbar: boolean; +} + +export type MiniAppWindowState = + | { readonly mode: 'inline' } + | { readonly mode: 'window'; readonly window: MiniAppWindowOptions }; + +/** Every field is optional; omitted fields are left unchanged. */ +export type MiniAppWindowPatch = Partial<{ + mode: 'inline' | 'window'; + width: number; + height: number; + x: number; + y: number; + visible: boolean; + resizable: boolean; + alwaysOnTop: boolean; + clickThrough: boolean; + skipTaskbar: boolean; +}>; + +export interface MiniAppWindowDisposable { + dispose(): void; +} + +/** + * Page-side handle for this Mini App's own window (proposed). Separate from + * `message`; the Host injects both under the same `mcode.miniapp` namespace. + */ +export interface MiniAppWindowHandle { + /** Opaque and stable across pop-out and return; never a Session or launch identifier. */ + readonly id: string; + getState(): Promise; + /** + * `update({ mode: ... })` or `focus()` require the call to originate from a + * user gesture (`navigator.userActivation.isActive`); otherwise they reject + * with `not_permitted` before any Host round trip. + */ + update(patch: MiniAppWindowPatch): Promise; + focus(): Promise; + /** Returns a disposable; call `dispose()` to stop receiving updates. */ + onDidChangeState(listener: (state: MiniAppWindowState) => void): MiniAppWindowDisposable; +} + +export interface McodeClientAPI { + readonly miniapp: MiniAppClientAPI; +} + +declare global { + interface Window { + /** Absent outside a supported managed Mini App main page. Check before use. */ + readonly mcode?: McodeClientAPI; + } +} diff --git a/plugins/amszuidas/momo-collab/miniapp/client/index.html b/plugins/amszuidas/momo-collab/miniapp/client/index.html new file mode 100644 index 00000000..627c5992 --- /dev/null +++ b/plugins/amszuidas/momo-collab/miniapp/client/index.html @@ -0,0 +1,67 @@ + + + + + + 协作 · MoMo + + + +
+ + +
+ +
+
+ + + +
+
+
+
+ +
+
+ +
+ + Enter 发送 · Shift+Enter 换行 + + +
+
+
+
+ +
+
+ + + + + + + + diff --git a/plugins/amszuidas/momo-collab/miniapp/client/momo.svg b/plugins/amszuidas/momo-collab/miniapp/client/momo.svg new file mode 100644 index 00000000..f0188621 --- /dev/null +++ b/plugins/amszuidas/momo-collab/miniapp/client/momo.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/amszuidas/momo-collab/miniapp/miniapp.json b/plugins/amszuidas/momo-collab/miniapp/miniapp.json new file mode 100644 index 00000000..f72c5eae --- /dev/null +++ b/plugins/amszuidas/momo-collab/miniapp/miniapp.json @@ -0,0 +1,35 @@ +{ + "schemaVersion": 1, + "artifacts": { + "client": [ + "./miniapp/client" + ], + "node": [ + "./miniapp/node" + ] + }, + "runtime": { + "kind": "process", + "entry": "./miniapp/node/server.mjs", + "lifecycle": "on-demand" + }, + "surface": { + "path": "/collab", + "window": { + "width": 1080, + "height": 720, + "minWidth": 720, + "minHeight": 480 + } + }, + "mcpEndpoints": [ + { + "server": "momo", + "path": "/mcp/momo" + } + ], + "agentAccess": [ + "session.run" + ], + "agentConcurrency": 8 +} diff --git a/plugins/amszuidas/momo-collab/miniapp/node/engine.mjs b/plugins/amszuidas/momo-collab/miniapp/node/engine.mjs new file mode 100644 index 00000000..11ff84ba --- /dev/null +++ b/plugins/amszuidas/momo-collab/miniapp/node/engine.mjs @@ -0,0 +1,1017 @@ +// @ts-check +// Runs MoMo conversation turns and task sessions through the Host Agent API. + +import { mkdir, readFile, stat } from 'node:fs/promises'; +import { isAbsolute, join, resolve } from 'node:path'; + +import { + MOMO_ROLE, + ROLE_VERSION, + autoFixMessage, + resumeMessage, + revisionMessage, + stateDigest, + taskHandoff, + verificationPrompt, +} from './prompts.mjs'; +import { DomainError } from './store.mjs'; + +/** @typedef {import('./miniapp-api.js').MiniAppAgentClient} MiniAppAgentClient */ +/** @typedef {import('./miniapp-api.js').MiniAppAgentRunEvent} MiniAppAgentRunEvent */ +/** @typedef {import('./miniapp-api.js').MiniAppAgentRunResult} MiniAppAgentRunResult */ +/** @typedef {import('./miniapp-api.js').MiniAppAgentQuestionAnswer} MiniAppAgentQuestionAnswer */ + +const MAX_OUTPUT_PREVIEW = 5 * 1024 * 1024; +const TEXT_OUTPUT = /\.(md|markdown|txt|csv|json|ya?ml|html?|js|mjs|ts|tsx|py|go|rs|java|sql|sh|css)$/i; + +/** @param {unknown} error */ +export function errorCode(error) { + return error && typeof error === 'object' && 'code' in error ? String(/** @type {any} */ (error).code) : ''; +} + +/** @param {unknown} error */ +function errorText(error) { + return error && typeof error === 'object' && 'message' in error ? String(/** @type {any} */ (error).message) : String(error); +} + +const clip = (/** @type {string} */ text, max = 300) => (text.length > max ? `${text.slice(0, max)}…` : text); + +export class Engine { + /** + * @param {{ + * store: import('./store.mjs').Store, + * agent: MiniAppAgentClient | undefined, + * logger: import('./miniapp-api.js').MiniAppLogger, + * broadcast: (event: Record) => void, + * protectedRoots: string[], + * outputsRoot: string, + * pickPage: (preferred: string | null) => string | null, + * }} options + */ + constructor(options) { + this.store = options.store; + this.agent = options.agent; + this.logger = options.logger; + this.broadcast = options.broadcast; + this.protectedRoots = options.protectedRoots; + /** Agent-readable root for outputs of tasks without a project folder. */ + this.outputsRoot = options.outputsRoot; + /** Returns a connected page client id, preferring `preferred` when it is still connected. */ + this.pickPage = options.pickPage; + /** + * Live run activity per task (not persisted; a run never survives a restart): + * taskId → { steps, tool, toolAt, startedAt, queued }. + * @type {Map} + */ + this.activity = new Map(); + this.disposed = false; + /** In-flight task runs: taskId → AbortController. */ + this.taskRuns = new Map(); + /** Verifier runs in flight: taskId → AbortController. */ + this.verifications = new Map(); + /** Queued MoMo inputs, drained one turn at a time. @type {{ messageId: string, text: string, images: any[], focusIdeaId: string | null, clientId: string | null }[]} */ + this.momoQueue = []; + /** @type {AbortController | null} */ + this.momoRun = null; + /** MoMo pending questionnaire and approvals (in memory; a restart cancels the run anyway). */ + this.momoPending = { question: /** @type {any} */ (null), approvals: /** @type {any[]} */ ([]) }; + /** The page client that sent the latest MoMo message; dispatch requests go to it first. */ + this.lastClientId = /** @type {string | null} */ (null); + /** Dispatch requests waiting for a page to create the session: taskId → timer. */ + this.dispatchTimers = new Map(); + this.flushTimers = new Map(); + } + + get available() { + return Boolean(this.agent); + } + + requireAgent() { + if (!this.agent) { + throw new DomainError('agent_unavailable', '这个 MiniMax Code 版本没有向 Mini App 开放 Agent,无法运行 MoMo'); + } + return this.agent; + } + + /** Called once after the store loads: runs that died with the previous process need the user. */ + async recover() { + for (const task of Object.values(this.store.state.tasks)) { + if (task.status !== 'in_progress') continue; + if (task.blocked?.reason === 'need_info' && task.question) continue; + if (task.blocked?.reason === 'run_failed' || task.blocked?.reason === 'project_unavailable') continue; + await this.store.updateTask(task.id, { + blocked: { reason: 'run_failed', detail: '执行被 MiniMax Code 重启中断了,点「继续执行」接着做' }, + patch: { approvals: [], progress: '执行被中断' }, + }); + } + for (const task of Object.values(this.store.state.tasks)) { + if (task.dispatch) await this.store.updateTask(task.id, { patch: { dispatch: null } }); + } + for (const message of this.store.state.chat) { + if (message.role === 'momo' && (message.status === 'thinking' || message.status === 'streaming')) { + await this.store.updateChat(message.id, (item) => { + item.status = 'error'; + item.text = item.text || 'MoMo 这条回复被 MiniMax Code 重启打断了,请再说一次。'; + }); + } + } + } + + dispose() { + this.disposed = true; + this.momoRun?.abort(); + for (const controller of this.taskRuns.values()) controller.abort(); + for (const controller of this.verifications.values()) controller.abort(); + for (const timer of this.dispatchTimers.values()) clearTimeout(timer); + for (const timer of this.flushTimers.values()) clearTimeout(timer?.handle ?? timer); + } + + /** Coalesces streaming updates into at most ~8 broadcasts per second per key. @param {string} key @param {() => void} send */ + throttle(key, send) { + if (this.flushTimers.has(key)) return; + this.flushTimers.set( + key, + setTimeout(() => { + this.flushTimers.delete(key); + if (!this.disposed) send(); + }, 120), + ); + } + + /** Live activity snapshot sent with every state frame. */ + activityView() { + return Object.fromEntries(this.activity); + } + + /** + * Updates live run activity in memory and pushes it to pages: the first change goes out at + * once, later ones at most every 500 ms (state.json is not touched per tool event). + * @param {string} taskId + * @param {Partial<{ steps: number, tool: string | null, toolAt: number, startedAt: number, queued: number | null }>} patch + */ + touchActivity(taskId, patch) { + const current = this.activity.get(taskId) ?? { steps: 0, tool: null, toolAt: 0, startedAt: Date.now(), queued: null }; + this.activity.set(taskId, { ...current, ...patch }); + const key = `activity:${taskId}`; + const timer = this.flushTimers.get(key); + if (timer) { + timer.dirty = true; + return; + } + this.broadcast({ type: 'activity', activity: this.activityView() }); + const entry = { dirty: false, handle: /** @type {any} */ (null) }; + entry.handle = setTimeout(() => { + this.flushTimers.delete(key); + if (entry.dirty && !this.disposed) this.broadcast({ type: 'activity', activity: this.activityView() }); + }, 500); + this.flushTimers.set(key, entry); + } + + /** @param {string} taskId */ + clearActivity(taskId) { + const key = `activity:${taskId}`; + const timer = this.flushTimers.get(key); + if (timer) { + clearTimeout(timer.handle); + this.flushTimers.delete(key); + } + if (!this.activity.delete(taskId)) return; + this.broadcast({ type: 'activity', activity: this.activityView() }); + } + + /** Where a task without a project folder writes its outputs (agent-readable, previewable). @param {string} taskId */ + outputDirFor(taskId) { + return join(this.outputsRoot, taskId); + } + + // ======================================================================= MoMo conversation + + /** + * @param {{ text: string, images: { id: string, name: string, agentPath?: string }[], focusIdeaId: string | null, clientId: string | null, source: 'chat' | 'quick_capture' }} input + */ + async sendToMomo(input) { + this.requireAgent(); + const text = input.text.trim(); + if (!text && !input.images.length) throw new DomainError('invalid', '消息不能为空'); + if (input.focusIdeaId) this.store.idea(input.focusIdeaId); + const userMessage = await this.store.addChat({ + role: 'user', + text, + images: input.images, + focusIdeaId: input.focusIdeaId, + kind: input.source, + }); + const momoMessage = await this.store.addChat({ role: 'momo', text: '', status: 'thinking', focusIdeaId: input.focusIdeaId }); + if (input.clientId) this.lastClientId = input.clientId; + this.momoQueue.push({ messageId: momoMessage.id, text, images: input.images, focusIdeaId: input.focusIdeaId, clientId: input.clientId }); + if (input.source === 'quick_capture') { + await this.store.emit('collab_quick_capture', { + idea_id: input.focusIdeaId ?? '', + is_unattributed: !input.focusIdeaId, + has_tag: /#\S/.test(text), + has_image: input.images.length > 0, + char_count: text.length, + }); + } + void this.drainMomo(); + return { userMessage, momoMessage }; + } + + async drainMomo() { + if (this.momoRun || this.disposed) return; + const batch = this.momoQueue.splice(0, this.momoQueue.length); + if (!batch.length) return; + // Several messages typed while MoMo was busy become one turn; the last placeholder answers them. + const answerId = batch[batch.length - 1].messageId; + for (const item of batch.slice(0, -1)) { + await this.store.updateChat(item.messageId, (message) => { + message.status = 'merged'; + message.text = ''; + }); + } + const focusIdeaId = batch[batch.length - 1].focusIdeaId; + const lines = batch.map((item) => { + const images = item.images.map((image) => ` - 图片「${image.name}」(附件 id: ${image.id}):${image.agentPath}`).join('\n'); + return `${item.text || '(只发了图片)'}${images ? `\n[附件,可用 read 工具查看]\n${images}` : ''}`; + }); + await this.runMomoTurn(answerId, focusIdeaId, { kind: 'run', lines }); + } + + /** + * @param {string} answerId + * @param {string | null} focusIdeaId + * @param {{ kind: 'run', lines: string[] } | { kind: 'answer', requestId: string, answers: readonly MiniAppAgentQuestionAnswer[] }} input + */ + async runMomoTurn(answerId, focusIdeaId, input) { + const agent = this.requireAgent(); + const controller = new AbortController(); + this.momoRun = controller; + const session = this.store.state.momoSession; + let streamed = ''; + const flush = () => + this.broadcast({ type: 'chat.stream', messageId: answerId, text: streamed }); + /** @param {MiniAppAgentRunEvent} event */ + const onEvent = (event) => { + switch (event.type) { + case 'turn.started': + if (session.sessionId !== event.sessionId) void this.store.updateMomoSession({ sessionId: event.sessionId, role: ROLE_VERSION }); + void this.store.updateChat(answerId, (message) => { + message.status = 'streaming'; + }); + break; + case 'text.delta': + streamed += event.text; + this.throttle(`chat:${answerId}`, flush); + break; + case 'message.committed': + // A turn may commit several assistant messages; keep them as one MoMo reply. + streamed = streamed && !event.text.startsWith(streamed) && !streamed.endsWith(event.text) ? `${streamed}\n\n${event.text}` : event.text; + this.throttle(`chat:${answerId}`, flush); + break; + case 'tool.started': + void this.store.updateChat(answerId, (message) => { + message.tools.push({ name: event.name.replace(/^mcp__[^_]+(?:_[^_]+)*?__/, ''), ok: null }); + }); + break; + case 'tool.finished': + void this.store.updateChat(answerId, (message) => { + const short = event.name.replace(/^mcp__[^_]+(?:_[^_]+)*?__/, ''); + const tool = [...message.tools].reverse().find((item) => item.name === short && item.ok === null); + if (tool) tool.ok = event.ok; + }); + break; + case 'permission.requested': + this.momoPending.approvals.push({ + requestId: event.requestId, + sessionId: event.sessionId, + toolName: event.toolName, + toolInput: clip(event.toolInput ?? '', 1500), + reason: event.reason, + messageId: answerId, + }); + this.broadcastMomoPending(); + break; + default: + break; + } + }; + try { + let result; + if (input.kind === 'answer') { + result = await agent.answer( + { sessionId: /** @type {string} */ (session.sessionId), requestId: input.requestId, answers: input.answers }, + { signal: controller.signal, onEvent }, + ); + } else { + const fresh = !session.sessionId || session.role !== ROLE_VERSION; + const text = [ + fresh ? MOMO_ROLE : '', + stateDigest(this.store, focusIdeaId), + `[本轮消息头] reply_to: ${answerId}${focusIdeaId ? `;用户正在聊想法 ${focusIdeaId},默认针对这个想法` : ''}`, + '[用户消息]', + ...input.lines, + ] + .filter(Boolean) + .join('\n\n'); + try { + result = await agent.run({ text, ...(fresh ? {} : { sessionId: session.sessionId }) }, { signal: controller.signal, onEvent }); + } catch (error) { + // A deleted or foreign MoMo session: start a new one with the full role. + if (errorCode(error) !== 'SESSION_NOT_FOUND') throw error; + await this.store.updateMomoSession({ sessionId: null }); + const retryText = [MOMO_ROLE, stateDigest(this.store, focusIdeaId), `[本轮消息头] reply_to: ${answerId}`, '[用户消息]', ...input.lines].join('\n\n'); + result = await agent.run({ text: retryText }, { signal: controller.signal, onEvent }); + } + } + await this.store.updateMomoSession({ sessionId: result.sessionId, role: ROLE_VERSION, runs: (session.runs ?? 0) + 1 }); + this.momoPending.question = result.pendingQuestion ? { ...result.pendingQuestion, messageId: answerId } : null; + const finalText = result.text?.trim() || streamed.trim(); + await this.store.updateChat(answerId, (message) => { + message.status = result.status === 'finished' ? 'done' : result.status === 'aborted' ? 'aborted' : 'error'; + message.text = + result.status === 'error' + ? `${finalText ? `${finalText}\n\n` : ''}MoMo 这一轮出错了:${result.error?.message ?? '未知错误'}` + : finalText || (result.pendingQuestion ? '我有个问题想先确认一下:' : result.status === 'aborted' ? '已停止。' : '好的。'); + }); + } catch (error) { + const code = errorCode(error); + this.logger.warn('momo.turn_failed', { code }); + await this.store.updateChat(answerId, (message) => { + message.status = 'error'; + message.text = + code === 'AGENT_OUTCOME_UNKNOWN' + ? 'MoMo 这一轮的结果不确定,可能已经做了一部分。请看看看板和想法再决定要不要重说。' + : code === 'REQUEST_CANCELLED' + ? '已停止。' + : `MoMo 暂时没法回复(${code || errorText(error)})。稍后再试。`; + }); + } finally { + this.momoRun = null; + this.momoPending.approvals = []; + this.broadcastMomoPending(); + void this.drainMomo(); + } + } + + stopMomo() { + this.momoRun?.abort(); + } + + broadcastMomoPending() { + this.broadcast({ type: 'momo.pending', pending: this.momoPendingView() }); + } + + momoPendingView() { + return { + busy: Boolean(this.momoRun), + question: this.momoPending.question, + approvals: this.momoPending.approvals, + }; + } + + /** @param {string} requestId @param {readonly MiniAppAgentQuestionAnswer[]} answers */ + async answerMomoQuestion(requestId, answers) { + const pending = this.momoPending.question; + if (!pending || pending.requestId !== requestId) throw new DomainError('not_found', '这个问题已经结束了'); + if (this.momoRun) throw new DomainError('busy', 'MoMo 正在回复,请稍等'); + this.momoPending.question = null; + const steps = /** @type {any} */ (pending).request?.steps ?? []; + const picked = answers + .map((answer) => { + const step = steps.find((/** @type {any} */ item) => item.id === answer.stepId) ?? steps[0]; + const labels = (answer.selectedOptionIds ?? []).map((id) => step?.options?.find((/** @type {any} */ option) => option.id === id)?.label ?? id); + if (answer.otherText) labels.push(answer.otherText); + return labels.join('、'); + }) + .filter(Boolean); + await this.store.addChat({ role: 'user', text: picked.length ? picked.join('\n') : '(跳过)' }); + const reply = await this.store.addChat({ role: 'momo', text: '', status: 'thinking' }); + void this.runMomoTurn(reply.id, null, { kind: 'answer', requestId, answers }).catch(() => {}); + return reply; + } + + /** @param {string} requestId @param {'allowOnce' | 'deny'} decision */ + async replyMomoApproval(requestId, decision) { + const approval = this.momoPending.approvals.find((item) => item.requestId === requestId); + if (!approval) throw new DomainError('not_found', '这个请求已经结束了'); + try { + await this.requireAgent().reply({ sessionId: approval.sessionId, requestId, decision }); + } catch (error) { + if (errorCode(error) !== 'REQUEST_NOT_FOUND') throw error; + } finally { + this.momoPending.approvals = this.momoPending.approvals.filter((item) => item.requestId !== requestId); + this.broadcastMomoPending(); + } + } + + /** + * Attaches a receipt to the MoMo reply named by `reply_to`, or posts a standalone notice when the + * tool was called from somewhere else (an ordinary chat handing a session over). + * @param {string | undefined} replyTo + * @param {Record} receipt + */ + async receipt(replyTo, receipt) { + const target = replyTo ? this.store.state.chat.find((message) => message.id === replyTo && message.role === 'momo') : undefined; + if (target) { + await this.store.updateChat(target.id, (message) => message.receipts.push(receipt)); + return; + } + await this.store.addChat({ role: 'system', kind: 'notice', text: '', receipts: [receipt] }); + } + + /** A short system line in the chat; with `taskId` the page renders the live task card under it. @param {string} text @param {{ taskId?: string }} [link] */ + notice(text, link = {}) { + return this.store.addChat({ role: 'system', kind: 'notice', text, receipts: link.taskId ? [{ kind: 'task_card', taskId: link.taskId }] : [] }); + } + + // ======================================================================= task dispatch + + /** + * Asks a page to create the task session (`agent.create()` binds the host chat's agent and + * model). The session works in `task.projectDir` when set, otherwise in the task's own output + * folder. Without a connected page the task waits; Node never silently starts it. + * @param {string} taskId + * @param {string | null} clientId the page that asked, when the request came from a page + */ + async requestDispatch(taskId, clientId) { + this.requireAgent(); + const task = this.store.task(taskId); + if (task.status === 'done') throw new DomainError('invalid', '任务已经完成了'); + if (this.taskRuns.has(taskId)) throw new DomainError('busy', '任务正在执行'); + if (task.projectDir) { + const problem = await this.checkProjectDir(task.projectDir); + if (problem) { + await this.store.updateTask(taskId, { blocked: { reason: 'project_unavailable', detail: problem } }); + throw new DomainError('project_unavailable', problem); + } + } + // Agreeing to start confirms what MoMo inferred for this idea. + await this.store.confirmInferredGates(task.ideaId); + await this.store.updateTask(taskId, { patch: { consent: 'given', interrupted: null } }); + if (task.sessionId) { + await this.continueTask(taskId); + return { mode: 'reuse' }; + } + const target = this.pickPage(clientId ?? this.lastClientId); + if (!target) { + await this.store.updateTask(taskId, { + blocked: { reason: 'project_unavailable', detail: '协作页面没有打开,打开后点「开始」' }, + }); + throw new DomainError('no_page', '需要先打开协作页面'); + } + await this.store.updateTask(taskId, { blocked: null, patch: { dispatch: { requestedAt: Date.now(), clientId: target } } }); + this.broadcast({ type: 'dispatch.request', taskId, clientId: target }); + clearTimeout(this.dispatchTimers.get(taskId)); + this.dispatchTimers.set( + taskId, + setTimeout(() => { + this.dispatchTimers.delete(taskId); + void this.dispatchTimedOut(taskId); + }, 20_000), + ); + return { mode: 'new' }; + } + + /** @param {string} taskId */ + async dispatchTimedOut(taskId) { + const task = this.store.state.tasks[taskId]; + if (!task?.dispatch || task.sessionId) return; + await this.store.updateTask(taskId, { + patch: { dispatch: null }, + blocked: { reason: 'project_unavailable', detail: '协作页面没有响应开工请求,刷新页面后再点「开始」' }, + }); + } + + /** + * A page created the session (`agent.create()` in its host chat). First claim wins. + * @param {string} taskId @param {string} sessionId + */ + async claimDispatch(taskId, sessionId) { + const task = this.store.task(taskId); + if (task.sessionId) return { claimed: false, sessionId: task.sessionId }; + if (task.status === 'done') throw new DomainError('invalid', '任务已经完成了'); + clearTimeout(this.dispatchTimers.get(taskId)); + this.dispatchTimers.delete(taskId); + await this.store.updateTask(taskId, { + status: 'in_progress', + blocked: null, + note: '已新开会话执行', + patch: { sessionId, dispatch: null, startedAt: task.startedAt ?? Date.now(), progress: '正在开工' }, + }); + const updated = this.store.task(taskId); + await this.store.emit('task_session_dispatch', { + idea_id: updated.ideaId, + task_id: taskId, + task_session_id: sessionId, + project_name: projectName(updated.projectDir), + mode: 'new', + handoff_item_count: this.handoffItemCount(updated), + }); + const outputDir = this.outputDirFor(taskId); + if (!updated.projectDir) await mkdir(outputDir, { recursive: true }); + void this.runTask(taskId, { kind: 'run', text: taskHandoff(this.store, updated, { outputDir }) }); + return { claimed: true, sessionId }; + } + + /** "先不做": keeps the task in 待办 without nagging. @param {string} taskId */ + async deferTask(taskId) { + const task = this.store.task(taskId); + if (task.status !== 'todo') throw new DomainError('invalid', '只有待办任务可以先不做'); + await this.store.updateTask(taskId, { blocked: null, patch: { consent: 'later' } }); + await this.store.emit('collab_confirm_ask', { action_type: 'dispatch', result: 'decline' }); + } + + /** Returns a user-facing problem, or '' when the folder can be used. @param {string} dir */ + async checkProjectDir(dir) { + if (!isAbsolute(dir)) return `项目目录不是绝对路径:${dir}`; + if (this.isProtected(resolve(dir))) return '不能把 MiniMax Code 的数据目录当作项目'; + const info = await stat(dir).catch(() => undefined); + if (!info?.isDirectory()) return `项目目录不存在:${dir}`; + return ''; + } + + /** @param {any} task */ + handoffItemCount(task) { + const idea = this.store.state.ideas[task.ideaId]; + const facts = (idea?.facts ?? []).filter((/** @type {any} */ fact) => fact.certainty === 'confirmed').length; + const records = idea ? this.store.recordsOf(idea.id).filter((record) => record.certainty === 'confirmed').length : 0; + return 3 + facts + Math.min(records, 20); + } + + /** "继续执行" after a failure or interruption: resumes in the same session. @param {string} taskId */ + async continueTask(taskId) { + const task = this.store.task(taskId); + if (!task.sessionId) throw new DomainError('invalid', '任务还没有会话'); + if (this.taskRuns.has(taskId)) throw new DomainError('busy', '任务正在执行'); + await this.store.updateTask(taskId, { status: 'in_progress', blocked: null, note: '继续执行', patch: { retryUsed: false, interrupted: null, consent: 'given', progress: '继续执行' } }); + await this.store.emit('task_session_dispatch', { + idea_id: task.ideaId, + task_id: taskId, + task_session_id: task.sessionId, + project_name: projectName(task.projectDir ?? task.workspace), + mode: 'reuse', + handoff_item_count: 0, + }); + void this.runTask(taskId, { kind: 'run', text: resumeMessage(task) }); + } + + /** @param {string} taskId @param {string} feedback */ + async reviseTask(taskId, feedback) { + const task = this.store.task(taskId); + if (task.status !== 'pending_review' && task.status !== 'done') throw new DomainError('invalid', '只有待审核的任务可以提修改意见'); + if (!task.sessionId) throw new DomainError('invalid', '任务没有会话'); + const text = feedback.trim(); + if (!text) throw new DomainError('invalid', '修改意见不能为空'); + const round = task.reviewRound + 1; + await this.store.addRecord({ text, target: task.ideaId, source: 'review', certainty: 'confirmed', attribution: 'confirmed', originTitle: `任务「${task.title}」的修改意见` }); + await this.store.updateTask(taskId, { + status: 'in_progress', + blocked: null, + note: `修改意见(第 ${round} 轮)`, + patch: { reviewRound: round, verification: null, autoFixUsed: false, retryUsed: false, progress: '按修改意见继续改' }, + }); + await this.store.emit('task_session_dispatch', { + idea_id: task.ideaId, + task_id: taskId, + task_session_id: task.sessionId, + project_name: projectName(task.projectDir ?? task.workspace), + mode: 'reuse', + handoff_item_count: 0, + }); + void this.runTask(taskId, { kind: 'run', text: revisionMessage(task, text, round) }); + } + + /** @param {string} taskId */ + async approveTask(taskId) { + const task = this.store.task(taskId); + if (task.status !== 'pending_review') throw new DomainError('invalid', '只有待审核的任务可以确认完成'); + const outputs = task.outputs.map((/** @type {any} */ output) => output.file); + await this.store.addRecord({ + text: `任务「${task.title}」已完成。${task.summary ? `结论:${task.summary}` : ''}${outputs.length ? `\n产出:${outputs.join('、')}` : ''}`, + target: task.ideaId, + source: 'session_return', + certainty: 'confirmed', + attribution: 'confirmed', + originTitle: `任务「${task.title}」`, + originSessionId: task.sessionId ?? undefined, + userActivity: true, + }); + await this.store.updateTask(taskId, { + status: 'done', + blocked: null, + note: '用户确认完成', + patch: { outputs: task.outputs.map((/** @type {any} */ output) => ({ ...output, reviewed: true })) }, + }); + } + + /** Stops a running task; it returns to todo and keeps its session for later. @param {string} taskId */ + async stopTask(taskId) { + const task = this.store.task(taskId); + if (task.status === 'done') throw new DomainError('invalid', '任务已经完成了'); + const controller = this.taskRuns.get(taskId); + await this.store.updateTask(taskId, { patch: { stopRequested: true } }); + controller?.abort(); + this.verifications.get(taskId)?.abort(); + clearTimeout(this.dispatchTimers.get(taskId)); + this.dispatchTimers.delete(taskId); + if (!controller) { + await this.store.updateTask(taskId, { + status: 'todo', + blocked: null, + note: '用户先不做了', + patch: { stopRequested: false, dispatch: null, approvals: [], question: null, progress: '', interrupted: task.sessionId ? { at: Date.now(), by: 'user' } : null }, + }); + } + } + + /** + * One run of a task session, followed to its end: question → need_info, failure → one retry, + * finish → MoMo verification → pending_review. + * @param {string} taskId + * @param {{ kind: 'run', text: string } | { kind: 'answer', requestId: string, answers: readonly MiniAppAgentQuestionAnswer[] }} input + */ + async runTask(taskId, input) { + const agent = this.requireAgent(); + if (this.taskRuns.has(taskId)) throw new DomainError('busy', '任务正在执行'); + const controller = new AbortController(); + this.taskRuns.set(taskId, controller); + const runStartedAt = Date.now(); + let toolStarted = false; + let steps = this.activity.get(taskId)?.steps ?? 0; + this.touchActivity(taskId, { startedAt: runStartedAt, queued: null, tool: null, toolAt: 0, steps }); + /** @param {MiniAppAgentRunEvent} event */ + const onEvent = (event) => { + switch (event.type) { + case 'run.queued': + this.touchActivity(taskId, { queued: event.position }); + break; + case 'turn.started': + this.touchActivity(taskId, { queued: null }); + break; + case 'tool.started': + toolStarted = true; + steps += 1; + this.touchActivity(taskId, { tool: event.name, toolAt: Date.now(), steps }); + this.clearSettledApprovals(taskId, event.sessionId); + break; + case 'tool.finished': + this.touchActivity(taskId, { tool: null }); + break; + case 'text.delta': + case 'message.committed': + this.clearSettledApprovals(taskId, event.sessionId); + break; + case 'permission.requested': + void this.addTaskApproval(taskId, event); + break; + default: + break; + } + }; + let result; + try { + const task = this.store.task(taskId); + result = + input.kind === 'answer' + ? await agent.answer({ sessionId: task.sessionId, requestId: input.requestId, answers: input.answers }, { signal: controller.signal, onEvent }) + : await agent.run({ text: input.text, sessionId: task.sessionId }, { signal: controller.signal, onEvent }); + } catch (error) { + this.taskRuns.delete(taskId); + await this.taskRunRejected(taskId, error, toolStarted); + this.clearActivityUnlessRerun(taskId); + return; + } + this.taskRuns.delete(taskId); + await this.taskRunSettled(taskId, result, runStartedAt); + this.clearActivityUnlessRerun(taskId); + } + + /** Live activity ends with the run unless a follow-up run (retry, nudge, auto-fix) took over. @param {string} taskId */ + clearActivityUnlessRerun(taskId) { + if (!this.taskRuns.has(taskId) && !this.verifications.has(taskId)) this.clearActivity(taskId); + } + + /** + * A run that ended by abort: back to 待办 with the session kept, so 「继续执行」 resumes it. + * `by` tells the user whether they stopped it here or in the MiniMax Code session itself. + * @param {string} taskId @param {'user' | 'session'} by + */ + async markInterrupted(taskId, by) { + const task = this.store.state.tasks[taskId]; + if (!task) return; + await this.store.updateTask(taskId, { + status: 'todo', + blocked: null, + note: by === 'user' ? '你先不做了' : '会话里停止了执行', + patch: { + stopRequested: false, + approvals: [], + question: null, + progress: '', + interrupted: task.sessionId ? { at: Date.now(), by } : null, + }, + }); + } + + /** @param {string} taskId @param {Extract} event */ + async addTaskApproval(taskId, event) { + const task = this.store.state.tasks[taskId]; + if (!task) return; + const approval = { + requestId: event.requestId, + sessionId: event.sessionId, + toolName: event.toolName, + toolInput: clip(event.toolInput ?? '', 1500), + reason: event.reason, + at: Date.now(), + }; + await this.store.updateTask(taskId, { + blocked: { reason: 'need_approval', detail: `等你批准:${event.toolName}` }, + patch: { approvals: [...task.approvals.filter((/** @type {any} */ item) => item.requestId !== event.requestId), approval] }, + }); + await this.store.emit('permission_request_view', { collab_task_id: taskId, tool_name: event.toolName }); + } + + /** + * Progress in a session after an approval means the user answered it elsewhere (side panel). + * @param {string} taskId @param {string} sessionId + */ + clearSettledApprovals(taskId, sessionId) { + const task = this.store.state.tasks[taskId]; + if (!task?.approvals.length) return; + const left = task.approvals.filter((/** @type {any} */ item) => item.sessionId !== sessionId); + if (left.length === task.approvals.length) return; + void this.store.updateTask(taskId, { + patch: { approvals: left }, + blocked: left.length ? task.blocked : null, + }); + } + + /** @param {string} taskId @param {string} requestId @param {'allowOnce' | 'deny'} decision */ + async replyTaskApproval(taskId, requestId, decision) { + const task = this.store.task(taskId); + const approval = task.approvals.find((/** @type {any} */ item) => item.requestId === requestId); + if (!approval) throw new DomainError('not_found', '这个请求已经结束了'); + try { + await this.requireAgent().reply({ sessionId: approval.sessionId, requestId, decision }); + } catch (error) { + if (errorCode(error) !== 'REQUEST_NOT_FOUND') throw error; + } + const left = this.store.task(taskId).approvals.filter((/** @type {any} */ item) => item.requestId !== requestId); + await this.store.updateTask(taskId, { + patch: { approvals: left }, + blocked: left.length ? this.store.task(taskId).blocked : null, + }); + await this.store.emit('permission_request_click', { collab_task_id: taskId, decision }); + if (decision === 'deny') await this.notice(`已拒绝「${task.title}」的请求,任务会换个做法`, { taskId }); + } + + /** @param {string} taskId @param {string} requestId @param {readonly MiniAppAgentQuestionAnswer[]} answers */ + async answerTaskQuestion(taskId, requestId, answers) { + const task = this.store.task(taskId); + if (!task.question || task.question.requestId !== requestId) throw new DomainError('not_found', '这个问题已经结束了'); + if (this.taskRuns.has(taskId)) throw new DomainError('busy', '任务正在执行'); + await this.store.updateTask(taskId, { blocked: null, patch: { question: null, progress: '已补充信息,继续执行' } }); + void this.runTask(taskId, { kind: 'answer', requestId, answers }); + } + + /** @param {string} taskId @param {unknown} error @param {boolean} toolStarted */ + async taskRunRejected(taskId, error, toolStarted) { + const code = errorCode(error); + const task = this.store.state.tasks[taskId]; + if (!task) return; + this.logger.warn('momo.task_run_rejected', { code }); + if (task.stopRequested || code === 'REQUEST_CANCELLED') { + await this.markInterrupted(taskId, task.stopRequested ? 'user' : 'session'); + return; + } + if (code === 'SESSION_NOT_FOUND') { + await this.store.updateTask(taskId, { + blocked: { reason: 'project_unavailable', detail: '任务会话已不存在。点「重新开工」会新开一个会话' }, + patch: { sessionId: null, approvals: [], progress: '' }, + }); + return; + } + const retryable = /** @type {any} */ (error)?.retryable === true && code !== 'AGENT_OUTCOME_UNKNOWN'; + if (retryable && !task.retryUsed && !toolStarted) { + await this.store.updateTask(taskId, { patch: { retryUsed: true, progress: '执行没起来,MoMo 正在重试' } }); + await new Promise((done) => setTimeout(done, 3000)); + if (!this.disposed) void this.runTask(taskId, { kind: 'run', text: resumeMessage(task) }); + return; + } + await this.store.updateTask(taskId, { + blocked: { + reason: 'run_failed', + detail: + code === 'AGENT_OUTCOME_UNKNOWN' + ? '执行结果不确定,可能已做了一部分。去会话里看看,再决定「继续执行」' + : `执行失败(${code || errorText(error)})`, + }, + patch: { approvals: [], progress: '' }, + }); + } + + /** @param {string} taskId @param {MiniAppAgentRunResult} result @param {number} runStartedAt */ + async taskRunSettled(taskId, result, runStartedAt) { + const task = this.store.state.tasks[taskId]; + if (!task) return; + if (task.sessionId !== result.sessionId) await this.store.updateTask(taskId, { patch: { sessionId: result.sessionId } }); + if (result.status === 'aborted') { + await this.markInterrupted(taskId, task.stopRequested ? 'user' : 'session'); + return; + } + if (result.pendingQuestion) { + await this.store.updateTask(taskId, { + blocked: { reason: 'need_info', detail: result.pendingQuestion.request.steps[0]?.question ?? '需要你补充信息' }, + patch: { question: result.pendingQuestion, approvals: [], progress: '等你补充信息' }, + }); + return; + } + if (result.status === 'error') { + if (!task.retryUsed) { + await this.store.updateTask(taskId, { patch: { retryUsed: true, approvals: [], progress: `出错了(${result.error?.message ?? '未知'}),MoMo 正在重试` } }); + void this.runTask(taskId, { kind: 'run', text: resumeMessage(task) }); + return; + } + await this.store.updateTask(taskId, { + blocked: { reason: 'run_failed', detail: `执行失败:${clip(result.error?.message ?? '未知错误', 200)}` }, + patch: { approvals: [], progress: '' }, + }); + await this.notice(`「${task.title}」重试后仍然失败`, { taskId }); + return; + } + const submitted = (task.submittedAt ?? 0) >= runStartedAt; + if (!submitted && !task.nudged) { + // The session finished without calling submit_task_result: ask once in the same session. + await this.store.updateTask(taskId, { patch: { nudged: true, approvals: [], progress: '提醒执行会话提交结果' } }); + void this.runTask(taskId, { + kind: 'run', + text: `请调用 momo 的 submit_task_result(task_id: ${taskId})提交这次的结论和全部产出文件;如果任务还没做完,先做完再提交。`, + }); + return; + } + if (!submitted) { + await this.store.updateTask(taskId, { patch: { summary: result.text?.trim() ?? '', criteria: [] } }); + } + await this.store.updateTask(taskId, { patch: { nudged: false, approvals: [], retryUsed: false } }); + await this.verifyTask(taskId); + } + + /** + * Called by `submit_task_result`. When no Node run follows the session (the user sent a message + * in the session directly), verification starts here instead of after a run. + * @param {string} taskId + * @param {{ summary: string, outputs: { file: string, description: string }[], criteria: { criterion: string, met: boolean, evidence: string }[] }} submission + */ + async submitResult(taskId, submission) { + const task = this.store.task(taskId); + if (task.status === 'done') throw new DomainError('invalid', '任务已经完成了;需要再改请先在协作里提修改意见'); + await this.store.updateTask(taskId, { + patch: { + summary: submission.summary, + outputs: submission.outputs.map((output) => ({ ...output, reviewed: false })), + criteria: submission.criteria, + submittedAt: Date.now(), + progress: '已提交结果,等待 MoMo 核对', + }, + }); + if (!this.taskRuns.has(taskId) && !this.verifications.has(taskId)) { + if (task.status !== 'in_progress') await this.store.updateTask(taskId, { status: 'in_progress', blocked: null, note: '会话里直接提交了结果' }); + void this.verifyTask(taskId); + } + } + + /** @param {string} taskId */ + async verifyTask(taskId) { + const agent = this.requireAgent(); + const task = this.store.task(taskId); + const fileCheck = await this.checkOutputs(task); + await this.store.updateTask(taskId, { patch: { progress: 'MoMo 正在核对', verification: null, fileCheck } }); + this.touchActivity(taskId, { tool: 'momo_verify', toolAt: Date.now() }); + const controller = new AbortController(); + this.verifications.set(taskId, controller); + let verdict; + try { + const result = await agent.run( + { text: verificationPrompt(this.store.task(taskId), fileCheck) }, + { + signal: controller.signal, + // The verifier may only read; any approval request is declined so it never acts for the user. + onEvent: (event) => { + if (event.type === 'permission.requested') { + void agent.reply({ sessionId: event.sessionId, requestId: event.requestId, decision: 'deny' }).catch(() => {}); + } + }, + }, + ); + verdict = this.store.task(taskId).verification; + if (!verdict) { + verdict = { + verdict: fileCheck.missing.length ? 'fail' : 'pass', + notes: result.status === 'finished' ? clip(result.text?.trim() || 'MoMo 核对完成。', 400) : 'MoMo 核对没有完成,请你直接审核产出。', + fixRequest: fileCheck.missing.length ? `这些产出文件找不到:${fileCheck.missing.join('、')}` : '', + at: Date.now(), + fallback: true, + }; + } + } catch (error) { + verdict = { verdict: 'pass', notes: `MoMo 核对没有完成(${errorCode(error) || errorText(error)}),请你直接审核产出。`, fixRequest: '', at: Date.now(), fallback: true }; + } finally { + this.verifications.delete(taskId); + this.clearActivityUnlessRerun(taskId); + } + const current = this.store.state.tasks[taskId]; + if (!current || current.stopRequested) { + if (current) await this.markInterrupted(taskId, 'user'); + return; + } + if (verdict.verdict === 'fail' && !current.autoFixUsed && verdict.fixRequest) { + await this.store.updateTask(taskId, { patch: { verification: verdict, autoFixUsed: true, progress: 'MoMo 核对发现问题,让执行会话补做' } }); + void this.runTask(taskId, { kind: 'run', text: autoFixMessage(current, verdict.fixRequest) }); + return; + } + await this.store.updateTask(taskId, { + status: 'pending_review', + blocked: null, + note: verdict.verdict === 'pass' ? 'MoMo 核对通过' : 'MoMo 核对有疑问,交给你判断', + patch: { verification: verdict, autoFixUsed: false, progress: '' }, + }); + await this.notice(`「${current.title}」可以审核了${verdict.verdict === 'pass' ? '' : '(MoMo 核对有疑问)'}`, { taskId }); + } + + /** + * @param {string} taskId + * @param {{ verdict: 'pass' | 'fail', notes: string, fixRequest: string, checks: { criterion: string, met: boolean, evidence: string }[] }} input + */ + async recordVerification(taskId, input) { + if (!this.verifications.has(taskId)) throw new DomainError('invalid', '这个任务现在没有在核对'); + await this.store.updateTask(taskId, { patch: { verification: { ...input, at: Date.now() } } }); + } + + /** + * Checks each reported output exists and inlines a bounded text preview. The verifier session + * runs outside the project folder, so file reads there would need approvals it cannot get. + * @param {any} task + */ + async checkOutputs(task) { + const present = []; + const missing = []; + let budget = 24_000; + for (const output of task.outputs ?? []) { + const file = this.resolveOutput(task, output.file); + const info = file ? await stat(file).catch(() => undefined) : undefined; + if (!info || !(info.isFile() || info.isDirectory())) { + missing.push(output.file); + continue; + } + let preview = ''; + if (info.isFile() && budget > 0 && TEXT_OUTPUT.test(output.file) && !this.isProtected(/** @type {string} */ (file))) { + const text = await readFile(/** @type {string} */ (file), 'utf8').catch(() => ''); + preview = text.slice(0, Math.min(8_000, budget)); + budget -= preview.length; + } + present.push({ file: output.file, size: info.size, ...(preview ? { preview, truncated: preview.length < info.size } : {}) }); + } + return { present, missing }; + } + + /** + * MiniMax Code's private data is never previewed or used as a project, except this Mini App's + * own agent-readable outputs folder. + * @param {string} file + */ + isProtected(file) { + const within = (/** @type {string} */ root) => file === root || file.startsWith(`${root}/`) || file.startsWith(`${root}\\`); + if (within(this.outputsRoot)) return false; + return this.protectedRoots.some(within); + } + + /** @param {any} task @param {string} file */ + resolveOutput(task, file) { + if (typeof file !== 'string' || !file) return undefined; + if (isAbsolute(file)) return resolve(file); + const base = task.projectDir ?? task.workspace ?? this.outputDirFor(task.id); + return resolve(base, file); + } + + /** A regular output file outside MiniMax Code's private data, small enough to preview. @param {string} taskId @param {number} index */ + async outputFile(taskId, index) { + const task = this.store.task(taskId); + const output = task.outputs[index]; + if (!output) throw new DomainError('not_found', '产出不存在'); + const file = this.resolveOutput(task, output.file); + if (!file) throw new DomainError('not_found', '产出路径无法解析'); + if (this.isProtected(file)) { + throw new DomainError('forbidden', '这个位置的文件不能在协作里预览'); + } + const info = await stat(file).catch(() => undefined); + if (!info?.isFile()) throw new DomainError('not_found', '产出文件已不存在'); + if (info.size > MAX_OUTPUT_PREVIEW) throw new DomainError('too_large', '文件太大,无法在协作里预览,请在项目文件夹里打开'); + return { file, size: info.size }; + } +} + +/** @param {string | null | undefined} workspace */ +export function projectName(workspace) { + return workspace ? workspace.split(/[\\/]/).filter(Boolean).pop() ?? '' : ''; +} diff --git a/plugins/amszuidas/momo-collab/miniapp/node/mcp.mjs b/plugins/amszuidas/momo-collab/miniapp/node/mcp.mjs new file mode 100644 index 00000000..2f6e9f7e --- /dev/null +++ b/plugins/amszuidas/momo-collab/miniapp/node/mcp.mjs @@ -0,0 +1,512 @@ +// @ts-check +// Streamable HTTP MCP server "momo": the only way the Agent reads or writes collaboration data. + +import { homedir } from 'node:os'; +import { join, resolve } from 'node:path'; + +import { DomainError, GATE_KEYS, GATE_LABELS, newId } from './store.mjs'; +import { ideaDetail } from './prompts.mjs'; + +const PROTOCOL_VERSIONS = ['2025-06-18', '2025-03-26', '2024-11-05']; +const replyTo = { type: 'string', description: '本轮消息头里的 reply_to,原样传入;不在 MoMo 对话里时省略' }; +const ideaRef = { type: 'string', description: '想法 id(idea_xxx)或完整想法名' }; +const taskId = { type: 'string', pattern: '^task_[a-f0-9]{10}$', description: '任务 id(task_xxx)' }; + +/** @param {Record} properties @param {string[]} [required] */ +const schema = (properties, required = []) => ({ type: 'object', properties, required, additionalProperties: false }); + +const TOOLS = [ + { + name: 'get_overview', + description: '读取协作全貌:全部想法(门槛进度)、未归属记录、需要用户处理的任务、进行中的任务。回答「进展到哪了」「哪些事要我处理」时先调用。', + inputSchema: schema({}), + }, + { + name: 'get_idea', + description: '读取一个想法的详情:三项门槛、已确定/待确认的内容、任务、产出和记录。', + inputSchema: schema({ idea: ideaRef }, ['idea']), + }, + { + name: 'add_record', + description: + '把用户丢进来的一条内容(原话、截图、链接、结论)记成一条记录。三选一:idea 记到已有想法;new_idea_title 新建想法并记进去;unattributed: true 记为未归属。提问、指令、纠正不要调用。', + inputSchema: schema( + { + text: { type: 'string', maxLength: 20000, description: '用户原话,原样保留' }, + idea: ideaRef, + new_idea_title: { type: 'string', maxLength: 80, description: '新想法的名字,4~16 个字' }, + unattributed: { type: 'boolean', description: '判断不了归属时为 true' }, + attachments: { type: 'array', maxItems: 9, items: { type: 'string' }, description: '本条消息附件 id' }, + source: { type: 'string', enum: ['chat', 'quick_capture', 'session_handoff'], description: '默认 chat;用户把普通会话交给 MoMo 时用 session_handoff' }, + origin_title: { type: 'string', maxLength: 120, description: '出处说明,如来源会话标题' }, + reply_to: replyTo, + }, + ['text'], + ), + }, + { + name: 'move_record', + description: '用户说「不对,记到 XX」时把记录改记到另一个想法(或新建想法)。改记后归属变为已确认。', + inputSchema: schema( + { record_id: { type: 'string' }, idea: ideaRef, new_idea_title: { type: 'string', maxLength: 80 }, reply_to: replyTo }, + ['record_id'], + ), + }, + { + name: 'confirm_record', + description: '用户明确说这条记录和它的归属是对的时调用,去掉「推断」标签。', + inputSchema: schema({ record_id: { type: 'string' }, reply_to: replyTo }, ['record_id']), + }, + { + name: 'update_idea', + description: + '更新想法:改名、写三项门槛(objective 做什么 / deliverable 产出什么 / done_criteria 怎么算做完)、追加已确定或待确认的内容。MoMo 推断的写入默认为「推断」;用户明确认可时 confirmed: true。已确认的门槛只有用户明确推翻时才传 user_override: true。', + inputSchema: schema( + { + idea: ideaRef, + title: { type: 'string', maxLength: 80 }, + objective: { type: 'string', maxLength: 2000 }, + deliverable: { type: 'string', maxLength: 2000 }, + done_criteria: { type: 'string', maxLength: 2000 }, + facts: { type: 'array', maxItems: 10, items: { type: 'string', maxLength: 2000 }, description: '已确定或待确认的要点' }, + confirmed: { type: 'boolean', description: '用户明确认可这些内容' }, + user_override: { type: 'boolean', description: '用户明确推翻了已确认的内容' }, + reply_to: replyTo, + }, + ['idea'], + ), + }, + { + name: 'revise_item', + description: '确认、修正或删除想法里的一项门槛或要点。item_kind=gate 时 item_id 为 objective/deliverable/done_criteria;item_kind=fact 时为 fact_xxx。已确认内容只有用户明确推翻时才能改(user_override: true)。', + inputSchema: schema( + { + idea: ideaRef, + item_kind: { type: 'string', enum: ['gate', 'fact'] }, + item_id: { type: 'string' }, + action: { type: 'string', enum: ['confirm', 'correct', 'remove'] }, + text: { type: 'string', maxLength: 2000, description: 'correct 时的新内容' }, + user_override: { type: 'boolean' }, + reply_to: replyTo, + }, + ['idea', 'item_kind', 'item_id', 'action'], + ), + }, + { + name: 'delete_record', + description: '删除一条记录。必须先问用户,用户同意后才传 confirmed_by_user: true。', + inputSchema: schema({ record_id: { type: 'string' }, confirmed_by_user: { type: 'boolean' } }, ['record_id', 'confirmed_by_user']), + }, + { + name: 'delete_idea', + description: '删除一个想法;它的记录变为未归属,已产出的任务保留。必须先问用户,用户同意后才传 confirmed_by_user: true。', + inputSchema: schema({ idea: ideaRef, confirmed_by_user: { type: 'boolean' }, reply_to: replyTo }, ['idea', 'confirmed_by_user']), + }, + { + name: 'create_task', + description: + '三项门槛都写清后,把想法写成一个待办任务(门槛自动带入)。界面会在你的回复下挂带「开始 / 先不做」按钮的任务卡,不要再追问要不要开始。涉及代码仓库、本地文件或某个项目时必须填 project_dir。', + inputSchema: schema( + { + idea: ideaRef, + title: { type: 'string', maxLength: 80, description: '任务名,也是执行会话的名字,10~24 字' }, + project_dir: { type: 'string', maxLength: 4096, description: '任务要在其中工作的项目目录绝对路径,例如 /Users/me/projects/app;纯调研、写作类任务可以省略' }, + notes: { type: 'string', maxLength: 4000 }, + reply_to: replyTo, + }, + ['idea', 'title'], + ), + }, + { + name: 'update_task', + description: '修改还没开始的待办任务:改任务名、补项目目录或补充说明。', + inputSchema: schema( + { task_id: taskId, title: { type: 'string', maxLength: 80 }, project_dir: { type: 'string', maxLength: 4096 }, notes: { type: 'string', maxLength: 4000 } }, + ['task_id'], + ), + }, + { + name: 'start_task', + description: '用户在对话里明确同意开始时调用:新任务新开会话执行(工作目录为任务的 project_dir);已有会话的任务回到原会话继续。', + inputSchema: schema({ task_id: taskId, user_agreed: { type: 'boolean', description: '用户已明确同意开始' }, reply_to: replyTo }, ['task_id', 'user_agreed']), + }, + { + name: 'approve_task', + description: '用户审核待审核任务后说「可以」时调用:任务变为已完成,结论和产出收回想法。', + inputSchema: schema({ task_id: taskId, reply_to: replyTo }, ['task_id']), + }, + { + name: 'revise_task', + description: '用户对待审核(或已完成)的任务提修改意见时调用:意见记入想法,任务回到原会话继续改,状态回到进行中。', + inputSchema: schema({ task_id: taskId, feedback: { type: 'string', maxLength: 8000, description: '用户的修改意见原话' }, reply_to: replyTo }, ['task_id', 'feedback']), + }, + { + name: 'stop_task', + description: '用户说进行中的任务先不做了,且同意停掉会话时调用;任务回到待办,会话保留。', + inputSchema: schema({ task_id: taskId, user_agreed: { type: 'boolean' } }, ['task_id', 'user_agreed']), + }, + { + name: 'task_report', + description: '【执行会话专用】任务执行会话开工时和关键进展时调用:workspace 填当前工作目录绝对路径,progress 一句话进展。', + inputSchema: schema({ task_id: taskId, workspace: { type: 'string', maxLength: 4096 }, progress: { type: 'string', maxLength: 200 } }, ['task_id']), + }, + { + name: 'submit_task_result', + description: '【执行会话专用】任务完成后提交结论和全部产出文件,交给 MoMo 核对。', + inputSchema: schema( + { + task_id: taskId, + summary: { type: 'string', maxLength: 8000, description: '结论' }, + outputs: { + type: 'array', + maxItems: 50, + items: schema({ file: { type: 'string', maxLength: 4096, description: '产出文件绝对路径' }, description: { type: 'string', maxLength: 300 } }, ['file']), + }, + criteria: { + type: 'array', + maxItems: 20, + description: '逐条对照「怎么算做完」', + items: schema({ criterion: { type: 'string', maxLength: 300 }, met: { type: 'boolean' }, evidence: { type: 'string', maxLength: 600 } }, ['criterion', 'met']), + }, + }, + ['task_id', 'summary', 'outputs'], + ), + }, + { + name: 'record_verification', + description: '【MoMo 核对会话专用】提交对任务结果的核对结论。', + inputSchema: schema( + { + task_id: taskId, + verdict: { type: 'string', enum: ['pass', 'fail'] }, + notes: { type: 'string', maxLength: 1000, description: '写给用户看的 1~2 句核对结论' }, + checks: { + type: 'array', + maxItems: 20, + description: '逐条核对「怎么算做完」里的每个要求', + items: schema({ criterion: { type: 'string', maxLength: 300 }, met: { type: 'boolean' }, evidence: { type: 'string', maxLength: 600 } }, ['criterion', 'met']), + }, + fix_request: { type: 'string', maxLength: 2000, description: 'fail 时需要补做的内容' }, + }, + ['task_id', 'verdict', 'notes', 'checks'], + ), + }, +]; +const TOOL_NAMES = new Set(TOOLS.map((tool) => tool.name)); + +/** @param {unknown} value */ +const str = (value) => (typeof value === 'string' ? value.trim() : ''); + +/** + * @param {{ store: import('./store.mjs').Store, engine: import('./engine.mjs').Engine, pageCount: () => number, resolveAttachments: (ids: string[]) => Promise }} deps + */ +export function createMcpHandler({ store, engine, pageCount, resolveAttachments }) { + /** @param {string} ref */ + const idea = (ref) => { + const found = store.findIdea(ref); + if (!found) throw new DomainError('not_found', `找不到想法「${ref}」,先用 get_overview 看看现有想法`); + return found; + }; + /** @param {any} item */ + const ideaView = (item) => ({ idea_id: item.id, title: item.title, progress: store.gateProgress(item) }); + /** + * Normalizes a model-supplied project folder: `~` expansion, absolute, existing, not MiniMax + * Code's own data. Empty means "no project". + * @param {unknown} value + */ + const projectArg = async (value) => { + const raw = str(value); + if (!raw) return null; + const dir = raw === '~' || raw.startsWith('~/') ? join(homedir(), raw.slice(1)) : raw; + const problem = await engine.checkProjectDir(resolve(dir)); + if (problem) throw new DomainError('invalid', `${problem}。请向用户确认正确的项目路径`); + return resolve(dir); + }; + + /** @param {string} name @param {Record} args */ + async function callTool(name, args) { + switch (name) { + case 'get_overview': { + const ideas = store.liveIdeas().sort((a, b) => b.updatedAt - a.updatedAt).map((item) => ({ + ...ideaView(item), + tasks: store.tasksOf(item.id).map((task) => ({ task_id: task.id, title: task.title, status: task.status, blocked: task.blocked?.reason ?? null })), + record_count: store.recordsOf(item.id).length, + })); + const unattributed = Object.values(store.state.records).filter((record) => !record.ideaId).map((record) => ({ record_id: record.id, text: record.text.slice(0, 300) })); + return { ideas, unattributed_records: unattributed, ...store.briefing(), open_pages: pageCount() }; + } + case 'get_idea': { + const item = idea(args.idea); + const tasks = store.tasksOf(item.id).map((task) => ({ + task_id: task.id, + title: task.title, + status: task.status, + blocked: task.blocked, + summary: task.summary, + outputs: task.outputs, + verification: task.verification, + review_round: task.reviewRound, + })); + return { detail: ideaDetail(store, item.id), tasks, records: store.recordsOf(item.id).map((record) => ({ record_id: record.id, text: record.text, certainty: record.certainty, attribution: record.attribution, source: record.source })) }; + } + case 'add_record': { + const choices = [Boolean(str(args.idea)), Boolean(str(args.new_idea_title)), args.unattributed === true].filter(Boolean).length; + if (choices !== 1) throw new DomainError('invalid', 'idea、new_idea_title、unattributed 三选一'); + const images = await resolveAttachments(Array.isArray(args.attachments) ? args.attachments.map(String) : []); + const target = args.unattributed === true ? null : str(args.new_idea_title) ? { newIdeaTitle: str(args.new_idea_title) } : idea(args.idea).id; + const { record, idea: owner, createdIdea } = await store.addRecord({ + text: String(args.text ?? ''), + target, + images, + source: str(args.source) || 'chat', + certainty: 'confirmed', + attribution: 'inferred', + originTitle: str(args.origin_title) || undefined, + }); + await engine.receipt(str(args.reply_to) || undefined, { + kind: createdIdea ? 'new_idea' : owner ? 'record' : 'unattributed', + recordId: record.id, + ideaId: owner?.id ?? null, + ideaTitle: owner?.title ?? null, + excerpt: record.text.slice(0, 60), + }); + return { record_id: record.id, idea: owner ? ideaView(owner) : null, created_idea: createdIdea }; + } + case 'move_record': { + if (Boolean(str(args.idea)) === Boolean(str(args.new_idea_title))) throw new DomainError('invalid', 'idea 和 new_idea_title 二选一'); + const target = str(args.new_idea_title) ? { newIdeaTitle: str(args.new_idea_title) } : idea(args.idea).id; + const { record, idea: to, from } = await store.moveRecord(str(args.record_id), target); + await engine.receipt(str(args.reply_to) || undefined, { + kind: 'moved', + recordId: record.id, + ideaId: to.id, + ideaTitle: to.title, + fromTitle: from?.title ?? '未归属', + excerpt: record.text.slice(0, 60), + }); + return { record_id: record.id, idea: ideaView(to), moved_from: from?.title ?? '未归属' }; + } + case 'confirm_record': { + const record = await store.confirmRecord(str(args.record_id)); + return { record_id: record.id, attribution: record.attribution }; + } + case 'update_idea': { + let item = idea(args.idea); + if (str(args.title)) item = await store.renameIdea(item.id, str(args.title)); + const gates = Object.fromEntries(GATE_KEYS.filter((key) => str(args[key])).map((key) => [key, str(args[key])])); + let gateResult; + if (Object.keys(gates).length) { + gateResult = await store.updateGates(item.id, gates, { confirmed: args.confirmed === true, userOverride: args.user_override === true }); + } + const facts = Array.isArray(args.facts) ? args.facts.map(str).filter(Boolean) : []; + for (const fact of facts) await store.addFact(item.id, fact, args.confirmed === true); + const progress = store.gateProgress(store.idea(item.id)); + if (gateResult?.changed.length) { + await engine.receipt(str(args.reply_to) || undefined, { kind: 'gates', ideaId: item.id, ideaTitle: item.title, changed: gateResult.changed, filled: progress.filled }); + } + return { + idea: ideaView(store.idea(item.id)), + gates_changed: gateResult?.changed ?? [], + gates_refused_confirmed: gateResult?.refused ?? [], + ready_for_task: progress.filled === GATE_KEYS.length, + ...(gateResult?.refused.length ? { note: `这些门槛用户已确认,没有修改:${gateResult.refused.join('、')}。只有用户明确推翻时才传 user_override: true` } : {}), + }; + } + case 'revise_item': { + const item = idea(args.idea); + const updated = await store.reviseItem( + item.id, + { kind: args.item_kind, id: str(args.item_id), text: str(args.text) }, + args.action, + { userOverride: args.user_override === true }, + ); + return { idea: ideaView(updated) }; + } + case 'delete_record': { + if (args.confirmed_by_user !== true) throw new DomainError('confirm_required', '删除前先问用户,用户同意后再调用'); + const record = await store.deleteRecord(str(args.record_id)); + return { deleted: record.id }; + } + case 'delete_idea': { + if (args.confirmed_by_user !== true) throw new DomainError('confirm_required', '删除前先问用户,用户同意后再调用'); + const { idea: removed, keptTasks } = await store.deleteIdea(idea(args.idea).id, 'momo'); + await store.emit('collab_confirm_ask', { action_type: 'delete', result: 'agree' }); + return { + deleted: removed.title, + kept_tasks: keptTasks.map((task) => ({ task_id: task.id, title: task.title, status: task.status })), + note: keptTasks.length ? '已产出的任务保留了,问用户要不要一起处理' : undefined, + }; + } + case 'create_task': { + const owner = idea(args.idea); + const projectDir = await projectArg(args.project_dir); + const task = await store.createTask({ ideaId: owner.id, title: str(args.title), notes: str(args.notes), projectDir }); + await engine.receipt(str(args.reply_to) || undefined, { kind: 'task_card', taskId: task.id }); + return { + task_id: task.id, + title: task.title, + status: task.status, + project_dir: task.projectDir, + next: '任务卡已挂在你的回复下,用户点「开始」或在对话里说开始时再 start_task;不要追问要不要开始', + }; + } + case 'update_task': { + const task = store.task(str(args.task_id)); + if (task.status !== 'todo' || task.sessionId) throw new DomainError('invalid', '只能修改还没开始的待办任务'); + const projectDir = args.project_dir === undefined ? undefined : await projectArg(args.project_dir); + await store.updateTask(task.id, { + blocked: projectDir !== undefined && task.blocked?.reason === 'project_unavailable' ? null : undefined, + patch: { + ...(str(args.title) ? { title: str(args.title).slice(0, 80) } : {}), + ...(projectDir !== undefined ? { projectDir } : {}), + ...(str(args.notes) ? { notes: str(args.notes).slice(0, 4000) } : {}), + }, + }); + const updated = store.task(task.id); + return { task_id: updated.id, title: updated.title, project_dir: updated.projectDir }; + } + case 'start_task': { + if (args.user_agreed !== true) { + await store.emit('collab_confirm_ask', { action_type: 'dispatch', result: 'no_reply' }); + throw new DomainError('confirm_required', '派任务前先问用户,用户同意后再调用'); + } + await store.emit('collab_confirm_ask', { action_type: 'dispatch', result: 'agree' }); + const result = await engine.requestDispatch(str(args.task_id), null); + const task = store.task(str(args.task_id)); + await engine.receipt(str(args.reply_to) || undefined, { kind: 'task_card', taskId: task.id }); + return { task_id: task.id, mode: result.mode, project_dir: task.projectDir, note: '进度会实时显示在任务卡上,不必在回复里复述' }; + } + case 'approve_task': { + await engine.approveTask(str(args.task_id)); + const task = store.task(str(args.task_id)); + await engine.receipt(str(args.reply_to) || undefined, { kind: 'task_done', taskId: task.id, ideaId: task.ideaId, taskTitle: task.title }); + return { task_id: task.id, status: task.status }; + } + case 'revise_task': { + await engine.reviseTask(str(args.task_id), String(args.feedback ?? '')); + const task = store.task(str(args.task_id)); + return { task_id: task.id, status: task.status, review_round: task.reviewRound, note: '修改意见已收进想法,任务回到原会话继续改' }; + } + case 'stop_task': { + if (args.user_agreed !== true) throw new DomainError('confirm_required', '停掉会话前先问用户'); + await engine.stopTask(str(args.task_id)); + return { task_id: str(args.task_id), note: '已停止,任务回到待办,会话保留' }; + } + case 'task_report': { + const task = store.task(str(args.task_id)); + const workspace = str(args.workspace); + await store.updateTask(task.id, { + patch: { + ...(workspace ? { workspace } : {}), + ...(str(args.progress) ? { progress: str(args.progress).slice(0, 200) } : {}), + }, + }); + return { ok: true }; + } + case 'submit_task_result': { + const outputs = (Array.isArray(args.outputs) ? args.outputs : []) + .map((output) => ({ file: str(output?.file), description: str(output?.description) })) + .filter((output) => output.file); + const criteria = (Array.isArray(args.criteria) ? args.criteria : []) + .map((item) => ({ criterion: str(item?.criterion), met: item?.met === true, evidence: str(item?.evidence) })) + .filter((item) => item.criterion); + await engine.submitResult(str(args.task_id), { summary: str(args.summary), outputs, criteria }); + return { ok: true, note: 'MoMo 会核对后交给用户审核。请用一两句话结束本轮回复。' }; + } + case 'record_verification': { + const checks = (Array.isArray(args.checks) ? args.checks : []) + .map((item) => ({ criterion: str(item?.criterion), met: item?.met === true, evidence: str(item?.evidence) })) + .filter((item) => item.criterion); + await engine.recordVerification(str(args.task_id), { + verdict: args.verdict === 'fail' ? 'fail' : 'pass', + notes: str(args.notes), + fixRequest: str(args.fix_request), + checks, + }); + return { ok: true }; + } + default: + throw new DomainError('invalid', `未知工具 ${name}`); + } + } + + const sessions = new Set(); + + /** @param {any} message */ + async function handleRpc(message) { + if (!message || typeof message !== 'object' || message.jsonrpc !== '2.0') { + return { jsonrpc: '2.0', id: null, error: { code: -32600, message: 'Invalid Request' } }; + } + const { id, method, params } = message; + const isNotification = id === undefined || id === null; + switch (method) { + case 'initialize': { + const requested = params?.protocolVersion; + return { + jsonrpc: '2.0', + id, + result: { + protocolVersion: PROTOCOL_VERSIONS.includes(requested) ? requested : PROTOCOL_VERSIONS[0], + capabilities: { tools: { listChanged: false } }, + serverInfo: { name: 'momo', version: '0.2.0' }, + instructions: `MoMo 协作工具:想法、记录、三项门槛(${GATE_KEYS.map((key) => GATE_LABELS[key]).join(' / ')})、任务。普通会话里用户说「把这个会话交给 MoMo」时,把会话结论用 add_record(source: session_handoff)记进对应想法。`, + }, + }; + } + case 'ping': + return isNotification ? undefined : { jsonrpc: '2.0', id, result: {} }; + case 'tools/list': + return { jsonrpc: '2.0', id, result: { tools: TOOLS } }; + case 'tools/call': { + const name = params?.name; + if (!TOOL_NAMES.has(name)) return { jsonrpc: '2.0', id, error: { code: -32602, message: `Unknown tool: ${name}` } }; + try { + const value = await callTool(name, params?.arguments ?? {}); + return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: JSON.stringify(value, null, 1) }] } }; + } catch (error) { + const text = error instanceof DomainError ? `${error.code}: ${error.message}` : '内部错误'; + return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text }], isError: true } }; + } + } + default: + if (isNotification) return undefined; + return { jsonrpc: '2.0', id, error: { code: -32601, message: `Method not found: ${method}` } }; + } + } + + /** + * @param {import('node:http').IncomingMessage} request + * @param {import('node:http').ServerResponse} response + * @param {unknown} body + */ + return async function handleMcp(request, response, body) { + if (request.method === 'DELETE') { + sessions.delete(request.headers['mcp-session-id']); + response.writeHead(200).end(); + return; + } + if (request.method !== 'POST') { + response.writeHead(405, { allow: 'POST, DELETE' }).end(); + return; + } + let session = request.headers['mcp-session-id']; + if (session && !sessions.has(session)) { + response.writeHead(404).end(); + return; + } + const batch = Array.isArray(body); + const messages = batch ? body : [body]; + if (!batch && /** @type {any} */ (body)?.method === 'initialize') { + session = newId('mcp'); + sessions.add(session); + response.setHeader('mcp-session-id', session); + } + const replies = (await Promise.all(messages.map(handleRpc))).filter((reply) => reply !== undefined); + if (!replies.length) { + response.writeHead(202).end(); + return; + } + response.writeHead(200, { 'content-type': 'application/json; charset=utf-8' }); + response.end(JSON.stringify(batch ? replies : replies[0])); + }; +} diff --git a/plugins/amszuidas/momo-collab/miniapp/node/miniapp-api.ts b/plugins/amszuidas/momo-collab/miniapp/node/miniapp-api.ts new file mode 100644 index 00000000..f5d8b7a4 --- /dev/null +++ b/plugins/amszuidas/momo-collab/miniapp/node/miniapp-api.ts @@ -0,0 +1,397 @@ +/** + * Agent-facing Mini App runtime authoring declarations. + * + * Copy this file into a generated plugin for type checking. It contains no Host implementation; + * the Host injects runtime values through start(context). + * Keep the .ts filename: Electron packaging excludes .d.ts files from dependency assets. + */ +export type JsonPrimitive = null | boolean | number | string; +export type JsonValue = JsonPrimitive | JsonObject | readonly JsonValue[]; +export type JsonObject = { readonly [key: string]: JsonValue }; + +declare const HOST_CONNECTOR_TOOL_REF: unique symbol; +export type HostConnectorToolRef = string & { + readonly [HOST_CONNECTOR_TOOL_REF]: 'HostConnectorToolRef'; +}; + +export interface HostConnectorTool { + readonly toolRef: HostConnectorToolRef; + readonly provider: string; + readonly name: string; + readonly description?: string; + readonly inputSchema: JsonValue; + readonly outputSchema?: JsonValue; +} + +export interface HostConnectorListResult { + readonly tools: readonly HostConnectorTool[]; + readonly partial: boolean; +} + +export interface HostConnectorCallOptions { + readonly signal?: AbortSignal; +} + +export interface HostConnectorCallResult { + readonly invocationId: string; + /** + * Raw provider result; it is not normalized by the Host and may be an object, array, or primitive. + * A single text-block array is one provider shape, not a global Host transport contract. + * Decode only a probe-observed envelope; preserve every other value, including direct strings. + */ + readonly value: JsonValue; +} + +export interface HostConnectorClient { + /** Candidate-safe inventory only; available before and after activation. */ + list(options?: HostConnectorCallOptions): Promise; + /** Activation-only business dispatch; call from request handling, never start(context). */ + call( + toolRef: HostConnectorToolRef, + arguments_: JsonObject, + options?: HostConnectorCallOptions, + ): Promise; +} + +export type HostConnectorErrorDisposition = + | 'not_dispatched' + | 'provider_reported' + | 'unknown_after_dispatch'; + +export type HostConnectorErrorCode = + | 'TOOL_REF_STALE' + | 'SERVICE_RESTARTED' + | 'REQUEST_CANCELLED' + | 'CONNECTOR_TIMEOUT' + | 'INVALID_ARGUMENTS' + | 'CONNECTOR_PROVIDER_ERROR' + | 'CONNECTOR_UNAVAILABLE' + | 'CONNECTOR_OUTCOME_UNKNOWN'; + +export interface HostConnectorError extends Error { + readonly code: HostConnectorErrorCode; + readonly disposition: HostConnectorErrorDisposition; + readonly retryable: boolean; + readonly invocationId?: string; + readonly diagnostic?: { + readonly issues: readonly { + readonly path: string; + readonly constraint: string; + readonly limit?: number; + }[]; + }; +} + +export interface MiniAppLogger { + debug(message: string, fields?: JsonObject): void; + info(message: string, fields?: JsonObject): void; + warn(message: string, fields?: JsonObject): void; + error(message: string, fields?: JsonObject): void; +} + +export type MiniAppAgentRunStatus = 'finished' | 'error' | 'aborted'; + +export type MiniAppAgentQuestionMode = 'questionnaire' | 'feature-enable' | 'plan'; + +export interface MiniAppAgentQuestionOption { + readonly id: string; + readonly label: string; + readonly description?: string; +} + +export interface MiniAppAgentQuestionStep { + readonly id: string; + readonly header?: string; + readonly question: string; + readonly description?: string; + readonly selectionMode: 'single' | 'multiple'; + readonly options: readonly MiniAppAgentQuestionOption[]; + /** Free-text "other" is always accepted; see `MiniAppAgentQuestionAnswer.otherText`. */ + readonly allowOther: true; +} + +/** Same questionnaire the Host chat renders; render every step and answer each one. */ +export interface MiniAppAgentQuestionnaire { + readonly schemaVersion: 2; + readonly id: string; + readonly title?: string; + readonly steps: readonly MiniAppAgentQuestionStep[]; + readonly mode?: MiniAppAgentQuestionMode; + /** Plan review Markdown and other mode-specific data. */ + readonly modePayload?: JsonObject; + /** Unix ms; the request is withdrawn after this. */ + readonly expiresAt?: number; +} + +export interface MiniAppAgentQuestionAnswer { + readonly stepId: string; + readonly selectedOptionIds: readonly string[]; + readonly selectedOther: boolean; + readonly otherText?: string; + readonly skipped?: boolean; +} + +/** A model as the Host's composer selects it; take the values from `models()`. */ +export interface MiniAppAgentModelSelection { + readonly providerId: string; + readonly modelId: string; + /** `''` means thinking is off; it is preserved, distinct from an absent `variant`. */ + readonly variant?: string; + readonly thinking?: { readonly effort: string }; +} + +/** One model this user can use. */ +export interface MiniAppAgentModelOption { + readonly providerId: string; + readonly modelId: string; + readonly displayName: string; + readonly variants?: readonly string[]; + readonly thinking?: { readonly efforts: readonly string[]; readonly default?: string }; +} + +export interface MiniAppAgentModels { + readonly models: readonly MiniAppAgentModelOption[]; + /** What a Node-created session starts with. */ + readonly default: MiniAppAgentModelSelection; +} + +export interface MiniAppAgentAgentOption { + /** Canonical name; what `agent` takes. */ + readonly name: string; + readonly displayName: string; + /** The default agent. */ + readonly primary: boolean; +} + +export interface MiniAppAgentAgents { + readonly agents: readonly MiniAppAgentAgentOption[]; +} + +export interface MiniAppAgentRunResult { + /** Pass back in `run({ sessionId })` to continue the same conversation. */ + readonly sessionId: string; + /** Absent when no turn ran: an `answer` accepted without starting a continuation. */ + readonly turnId?: string; + /** The model the turn was dispatched with; absent when no turn ran. */ + readonly model?: MiniAppAgentModelSelection; + /** `aborted` only when the Host confirmed the abort; an unconfirmed one rejects with `AGENT_OUTCOME_UNKNOWN`. */ + readonly status: MiniAppAgentRunStatus; + /** Final assistant reply; absent when the turn produced none or did not finish. */ + readonly text?: string; + readonly error?: { readonly code: string; readonly message: string }; + /** Set when the turn ended waiting on a questionnaire; render `request` and continue with `agent.answer`. */ + readonly pendingQuestion?: { + readonly requestId: string; + readonly mode: MiniAppAgentQuestionMode; + readonly request: MiniAppAgentQuestionnaire; + }; +} + +/** + * Progress of one run. `turnId` matches the result's, except that a `permission.requested` raised + * by a delegated child session carries that child's own `sessionId` and `turnId`; `reply` with + * those, and do not filter events by the root turn. Ignore unknown `type` values: new event + * types may be added without notice. + * - `run.queued`: all run slots are taken and the call waits at 1-based `position`; sent once, + * before the turn starts, so it has no `turnId` and names a session only when continuing one. + * - `turn.started`: the Agent began the turn; `model` is the model it was dispatched with. + * - `text.delta`: a piece of the reply as it streams. + * - `message.committed`: one complete assistant message (a turn can have several). + * - `tool.started` / `tool.finished`: a tool call by name; `ok` is false when it failed. + * - `turn.finished`: always last; same `status`, `text`, and `error` as the run result. + */ +/** + * Every event except `run.queued` names its session, so `reply` can be called from `onEvent` even + * before the `run` that created the session has returned. `answer` cannot: the paused `run` still holds the + * session's single-run slot while `question.requested` is delivered, so call `answer` only after + * that `run` resolves (an earlier call rejects with `BUSY`). Ignore event types you do not know. + */ +export type MiniAppAgentRunEvent = + | { readonly type: 'run.queued'; readonly sessionId?: string; readonly position: number } + | { + readonly type: 'turn.started'; + readonly sessionId: string; + readonly turnId: string; + readonly model: MiniAppAgentModelSelection; + } + | { + readonly type: 'text.delta'; + readonly sessionId: string; + readonly turnId: string; + readonly text: string; + } + | { + readonly type: 'message.committed'; + readonly sessionId: string; + readonly turnId: string; + readonly text: string; + } + | { + readonly type: 'tool.started'; + readonly sessionId: string; + readonly turnId: string; + readonly name: string; + } + | { + readonly type: 'tool.finished'; + readonly sessionId: string; + readonly turnId: string; + readonly name: string; + readonly ok: boolean; + } + /** A tool call is waiting for approval; decide it with `agent.reply`. */ + | { + readonly type: 'permission.requested'; + readonly sessionId: string; + /** Absent when the runtime did not attribute the ask to a turn. */ + readonly turnId?: string; + readonly requestId: string; + readonly toolName: string; + readonly toolInput?: string; + readonly reason: string; + } + /** The turn paused on a questionnaire; continue with `agent.answer`. */ + | { + readonly type: 'question.requested'; + readonly sessionId: string; + readonly turnId?: string; + readonly requestId: string; + readonly mode: MiniAppAgentQuestionMode; + readonly request: MiniAppAgentQuestionnaire; + } + | { + readonly type: 'turn.finished'; + readonly sessionId: string; + readonly turnId: string; + readonly status: MiniAppAgentRunStatus; + readonly text?: string; + readonly error?: { readonly code: string; readonly message: string }; + }; + +export interface MiniAppAgentRunOptions { + readonly signal?: AbortSignal; + readonly onEvent?: (event: MiniAppAgentRunEvent) => void; +} + +export type MiniAppAgentErrorCode = + | 'NOT_PERMITTED' + | 'BUSY' + | 'SESSION_NOT_FOUND' + | 'INVALID_ARGUMENTS' + | 'AGENT_UNAVAILABLE' + | 'REQUEST_CANCELLED' + | 'AGENT_OUTCOME_UNKNOWN' + | 'REQUEST_NOT_FOUND'; + +export interface MiniAppAgentError extends Error { + readonly code: MiniAppAgentErrorCode; + /** + * True when nothing was started and the same call may be retried later. + * `AGENT_OUTCOME_UNKNOWN` is never retryable: the turn may have run. + */ + readonly retryable: boolean; + /** Set on every `BUSY`: the session already has a run in flight, or the wait queue is full. */ + readonly reason?: 'session_busy' | 'queue_full'; +} + +/** + * Present only when the manifest declares `agentAccess: ["session.run"]`. + * A new Node session uses the primary Agent unless `agent` names one from `agents()`, and works in + * this Mini App's own Agent directory and on the runtime default model; + * continuing a session keeps its Agent, directory and model (unless `model` switches it), including + * one a page created using its host chat's Agent, project directory and model; a session never + * changes its Agent. Runs one turn with that Agent's full capabilities + * (files, shell, Skills, MCP including this Mini App's own), and resolves when the turn ends. + * + * Interaction is yours to render: pass `onEvent` and handle `permission.requested` with + * `reply` and `question.requested` with `answer`. A permission request waits only while + * someone can answer it: this run passed `onEvent`, or a page of this Mini App currently has + * the session open in a chat side panel via `open`. Otherwise it is denied (the model is told + * why), and a questionnaire ends the turn with `result.pendingQuestion`. A run waiting on a + * permission is released by `cancel`/abort, process exit or republish. Up to `agentConcurrency` + * runs (default 4) per Mini App run at once; further `run` / `answer` calls wait in order (up to + * four times that many, see `run.queued`), and aborting a waiting call rejects it with + * `REQUEST_CANCELLED`. + */ +export interface MiniAppAgentClient { + run( + input: { + readonly text: string; + readonly sessionId?: string; + /** + * Switch the session to this model before the turn (validated like the composer validates a + * new session's model: it must be one `models()` lists; unusable is `INVALID_ARGUMENTS`, + * missing BYOK credentials end the turn with `status: 'error'`); the session keeps it. Omit to use the session's current model. + */ + readonly model?: MiniAppAgentModelSelection; + /** + * Only when starting a session (no `sessionId`): an agent `name` from `agents()`. Passing it + * with `sessionId` is `INVALID_ARGUMENTS`. + */ + readonly agent?: string; + }, + options?: MiniAppAgentRunOptions, + ): Promise; + /** + * Decide a pending permission request. Never writes the user's global rules. Node replies + * have 16 separate pending slots; `run` / `answer` / `models` / `agents` / `create` and Connector + * requests share another 16. Filling that ordinary budget still permits replies. A full reply + * budget rejects with retryable `AGENT_UNAVAILABLE`. + */ + reply(input: { + readonly sessionId: string; + readonly requestId: string; + readonly decision: 'allowOnce' | 'deny'; + }): Promise; + /** + * Answer a questionnaire; the continuation turn is observed like `run`. Rejects with + * `AGENT_OUTCOME_UNKNOWN` when the answer was recorded but the continuation turn was not observed. + */ + answer( + input: { + readonly sessionId: string; + readonly requestId: string; + readonly answers: readonly MiniAppAgentQuestionAnswer[]; + /** Same as `run`'s `model`, applied before the continuation turn. */ + readonly model?: MiniAppAgentModelSelection; + }, + options?: MiniAppAgentRunOptions, + ): Promise; + /** + * Returns the model catalog and runtime `default`. Pass `default` directly as `model` to + * `run` / `answer`. For a catalog entry, extract `providerId` / `modelId`, add a selected `variant` + * if needed and preserve `variant: ''`. If thinking is supported, select an effort from + * `thinking.efforts` and pass `thinking: { effort }`. + */ + models(): Promise; + /** The agents the user has configured; `agent` of `create` / a first `run` must be one of them. */ + agents(): Promise; + /** + * Create a session (agent, this Mini App's Agent directory, the runtime default model) without + * running a turn, and return its `sessionId`; continue it with `run({ sessionId })`. A page's + * `create` binds the session to its chat instead, and its `sessionId` can be continued here. + * Takes no Host run slot but uses the ordinary Node request budget. `agent` picks the session's + * agent from `agents()`; a session never changes it. + */ + create(input?: { readonly agent?: string }): Promise<{ readonly sessionId: string }>; +} + +export interface MiniAppLifecycle { + dispose(): void | Promise; +} + +export interface MiniAppContext { + readonly pluginId: string; + readonly pluginRoot: string; + readonly dataDir: string; + readonly listen: Readonly<{ readonly host: '127.0.0.1'; readonly port: number }>; + readonly signal: AbortSignal; + readonly logger: MiniAppLogger; + readonly hostConnector?: HostConnectorClient; + readonly agent?: MiniAppAgentClient; +} + +export interface MiniAppModule { + /** Resolve only after the listener accepts connections and every route is installed. */ + start(context: MiniAppContext): Promise; +} diff --git a/plugins/amszuidas/momo-collab/miniapp/node/prompts.mjs b/plugins/amszuidas/momo-collab/miniapp/node/prompts.mjs new file mode 100644 index 00000000..1e2e137d --- /dev/null +++ b/plugins/amszuidas/momo-collab/miniapp/node/prompts.mjs @@ -0,0 +1,198 @@ +// @ts-check +// Model-facing text. Tool names are the MCP tool names of the `momo` server. + +import { GATE_KEYS, GATE_LABELS } from './store.mjs'; + +const clip = (/** @type {string} */ text, max = 400) => (text.length > max ? `${text.slice(0, max)}…` : text); + +export const ROLE_VERSION = 'momo-role-v2'; + +/** Standing role for the global MoMo session; sent on its first turn and whenever it changes. */ +export const MOMO_ROLE = `你是 MoMo,MiniMax Code「协作」里全局唯一的项目管家(一只淡蓝色的小水母)。你帮用户把随手冒出的想法攒起来、理清楚,写成任务后安排会话去执行,并跟进、核对、收回结果。 + +协作数据只存在 momo 工具(MCP server「momo」)背后;不要自己建文件记录,也不要自己动手执行任务。 + +## 每条用户消息先分类 +1. 内容类(想法、截图、链接、一句观察、结论、待办)→ add_record 记录原话(text 原样保留;截图把附件 id 放进 attachments)。 +2. 提问、指令、纠正(「进展到哪了」「开始吧」「不对,记到 XX」)→ 不记录,直接处理。 + +## 归类 +- 属于已有想法 → 记到该想法;新的一件事 → new_idea_title(4~16 字);判断不了 → unattributed: true,之后再问。 +- 消息里的 #标签 视为用户指定的想法名。用户说「不对,记到 XX」→ move_record。 + +## 三项门槛:做什么 / 产出什么 / 怎么算做完 +- 能推断的直接用 update_idea 写入(默认「推断」,界面会标出来,用户可以一键确认),不要为了确认推断而追问。 +- 还没写清的门槛,用 ask_user 一次问一项:给 2~4 个具体选项,把最合适的放第一个并在描述里写「推荐」,用户也可以自己写。不要在正文里用一大段文字追问。 +- 想法可以一直停在积累阶段,不要催。用户想深挖时,像 grill-me 一样一问一答。 +- 已确认的内容你不能自己改;只有用户明确推翻时才传 user_override: true。 + +## 任务 +- 三项写清后 create_task。任务涉及代码仓库、本地文件或某个项目时,在 project_dir 填该项目的绝对路径(用户提到的路径,或从上下文能确定的路径;不确定就用 ask_user 问,选项里给出你能确定的候选路径)。纯调研、写作类任务可以不填,产出会放到协作自己的产出目录。 +- 写好任务后不要追问「要不要开始」:界面会在你的回复下自动出现带「开始 / 先不做」按钮的任务卡。只有用户在对话里明确说开始,才调用 start_task(user_agreed: true)。 +- 一件事太大时拆成几个任务,分别 create_task。 +- 待审核:用户说「可以」→ approve_task;提修改意见 → revise_task(回到原会话继续改)。 +- 进行中的任务卡在界面上实时显示进度、待批准和待补充信息,用户直接在卡片上处理;你不能替用户批准。 +- 用户说先不做了 → 问是否停掉,同意后 stop_task。 + +## 回复风格 +- 一到三句话,中文,口语,直接说结果。不复述记录内容,不罗列统计,不解释界面怎么用、项目怎么选、审批怎么点这些机制,用户问了再说。 +- 回执、任务卡、进度都会自动挂在你的回复下,不要在正文里重复。 +- 删除想法或记录前必须先问,用户同意后才传 confirmed_by_user: true。 + +调用 add_record / move_record / update_idea / create_task 等工具时,务必把本轮消息头里的 reply_to 原样传入,界面靠它把回执和任务卡挂到你的回复下。`; + +/** + * A bounded digest of the current state, prepended to every MoMo turn. + * @param {import('./store.mjs').Store} store + * @param {string | null} focusIdeaId + */ +export function stateDigest(store, focusIdeaId) { + const ideas = store.liveIdeas().sort((a, b) => b.updatedAt - a.updatedAt).slice(0, 40); + const lines = ideas.map((idea) => { + const progress = store.gateProgress(idea); + const tasks = store.tasksOf(idea.id).map((task) => `${task.title}[${task.status}${task.blocked ? `/${task.blocked.reason}` : ''}]`); + return `- ${idea.id}「${idea.title}」门槛 ${progress.filled}/3${progress.missing.length ? `,差:${progress.missing.join('、')}` : ''}${tasks.length ? `;任务:${tasks.join(',')}` : ''}`; + }); + const unattributed = Object.values(store.state.records).filter((record) => !record.ideaId).slice(-10); + const parts = [`[协作现状] 想法 ${store.liveIdeas().length} 个`, ...lines]; + if (unattributed.length) { + parts.push('[未归属记录]', ...unattributed.map((record) => `- ${record.id}:${clip(record.text, 120)}`)); + } + const briefing = store.briefing(); + if (briefing.needs_you.length) { + parts.push('[需要用户处理]', ...briefing.needs_you.map((item) => `- ${item.task_id}「${item.title}」${item.status}${item.blocked ? `(${item.blocked})` : ''}`)); + } + const projects = [...new Set(Object.values(store.state.tasks).map((task) => task.projectDir).filter(Boolean))].slice(-8); + if (projects.length) parts.push('[用过的项目目录]', ...projects.map((dir) => `- ${dir}`)); + if (focusIdeaId) { + try { + parts.push(ideaDetail(store, focusIdeaId)); + } catch { + /* the focused idea was deleted meanwhile */ + } + } + return parts.join('\n'); +} + +/** @param {import('./store.mjs').Store} store @param {string} ideaId */ +export function ideaDetail(store, ideaId) { + const idea = store.idea(ideaId); + const gate = (/** @type {typeof GATE_KEYS[number]} */ key) => { + const value = idea.gates[key]; + return `${GATE_LABELS[key]}:${value ? `${value.text}(${value.certainty === 'confirmed' ? '已确认' : '推断'})` : '未写清'}`; + }; + const records = store.recordsOf(idea.id).slice(-15); + return [ + `[当前聚焦想法] ${idea.id}「${idea.title}」`, + ...GATE_KEYS.map(gate), + ...(idea.facts.length ? ['已确定/待确认:', ...idea.facts.map((/** @type {any} */ fact) => `- ${fact.id} ${fact.text}(${fact.certainty === 'confirmed' ? '已确认' : '推断'})`)] : []), + ...(records.length ? ['最近记录:', ...records.map((record) => `- ${record.id} ${clip(record.text, 160)}`)] : []), + ].join('\n'); +} + +/** + * The handoff written into a task session's first message. + * @param {import('./store.mjs').Store} store + * @param {any} task + * @param {{ outputDir: string }} where + */ +export function taskHandoff(store, task, where) { + const idea = store.state.ideas[task.ideaId]; + const confirmedFacts = (idea?.facts ?? []).filter((/** @type {any} */ fact) => fact.certainty === 'confirmed'); + const records = idea ? store.recordsOf(idea.id).filter((record) => record.certainty === 'confirmed').slice(-20) : []; + const priorOutputs = idea + ? store.tasksOf(idea.id).filter((other) => other.id !== task.id && other.status === 'done').flatMap((other) => other.outputs.map((/** @type {any} */ output) => `- ${output.file}(来自任务「${other.title}」)`)) + : []; + const attachments = records.flatMap((record) => record.images.map((/** @type {any} */ image) => image.agentPath).filter(Boolean)); + return [ + `你接到一个由项目管家 MoMo 派发的任务(task_id: ${task.id})。`, + '', + `# 任务:${task.title}`, + `来源想法:${idea?.title ?? '(已删除)'}`, + '', + '## 三项门槛(完成标准)', + `- 做什么:${task.objective}`, + `- 产出什么:${task.deliverable}`, + `- 怎么算做完:${task.doneCriteria}`, + ...(task.notes ? ['', '## 补充说明', task.notes] : []), + ...(confirmedFacts.length ? ['', '## 已确定的内容', ...confirmedFacts.map((/** @type {any} */ fact) => `- ${fact.text}`)] : []), + ...(records.length ? ['', '## 相关记录(用户原话)', ...records.map((record) => `- ${clip(record.text, 600)}`)] : []), + ...(attachments.length ? ['', '## 相关截图(用 read 工具查看)', ...attachments.map((file) => `- ${file}`)] : []), + ...(priorOutputs.length ? ['', '## 之前任务的产出', ...priorOutputs] : []), + '', + '## 工作位置', + task.projectDir + ? `- 项目目录:${task.projectDir}。所有读写、命令都在这个目录里进行(先 cd 进去),产出文件也写在这里。` + : `- 这个任务不绑定项目。产出文件写到:${where.outputDir}(目录已建好)。`, + '', + '## 工作约定', + `1. 开始时先调用 momo 的 task_report(task_id: ${task.id}):workspace 填你实际工作的目录绝对路径,progress 用一句话写计划。`, + '2. 不要使用后台子任务(不要 run_in_background),需要委派时用前台方式等待结果。', + '3. 每完成一个关键步骤,调用 task_report 更新 progress(一句话,例如「已读完 3 个模块,开始整理清单」)。', + '4. 缺少关键信息时用 ask_user 提问(给出选项),不要猜。', + `5. 全部完成后调用 momo 的 submit_task_result(task_id: ${task.id}):summary 写结论(3~6 句),outputs 列出每个产出文件的绝对路径和一句说明,criteria 逐条对照「怎么算做完」给出 met(true/false)和证据。然后用一两句话结束回复。`, + ].join('\n'); +} + +/** @param {any} task @param {string} feedback @param {number} round */ +export function revisionMessage(task, feedback, round) { + return [ + `用户审核了任务「${task.title}」(task_id: ${task.id},第 ${round} 轮修改),修改意见如下:`, + '', + feedback, + '', + `请按意见修改。完成后重新调用 momo 的 submit_task_result(task_id: ${task.id})提交完整的最新结果(outputs 要列全,包括没改的文件)。`, + ].join('\n'); +} + +/** @param {any} task */ +export function resumeMessage(task) { + return `继续完成任务「${task.title}」(task_id: ${task.id})。之前的执行被中断了,请先检查已完成的部分,再接着做完。完成后调用 momo 的 submit_task_result 提交结果。`; +} + +/** + * Sent to a fresh verifier session after the task run settles. + * @param {any} task + * @param {{ missing: string[], present: { file: string, size: number }[] }} fileCheck + */ +export function verificationPrompt(task, fileCheck) { + return [ + `你是项目管家 MoMo,现在核对任务「${task.title}」(task_id: ${task.id})的执行结果,判断能否交给用户审核。`, + '', + '## 完成标准', + `- 做什么:${task.objective}`, + `- 产出什么:${task.deliverable}`, + `- 怎么算做完:${task.doneCriteria}`, + '', + '## 执行会话的汇报', + task.summary || '(没有提交汇报)', + '', + '## 执行会话的逐条自检', + ...(task.criteria?.length ? task.criteria.map((/** @type {any} */ item) => `- [${item.met ? '满足' : '未满足'}] ${item.criterion}:${item.evidence || '(无证据)'}`) : ['(没有自检)']), + '', + '## 产出文件', + ...(fileCheck.present.length + ? fileCheck.present.flatMap((item) => [ + `- ${item.file}(${item.size} 字节)`, + ...(item.preview ? ['```', item.preview, item.truncated ? '…(后略)' : '', '```'].filter((line) => line !== '') : []), + ]) + : ['(没有提交产出文件)']), + ...(fileCheck.missing.length ? ['', '## 汇报了但找不到的文件', ...fileCheck.missing.map((file) => `- ${file}`)] : []), + '', + '## 要求', + '- 只根据上面的汇报和文件内容判断,不要调用除 record_verification 之外的任何工具。', + `- 核对完调用 momo 的 record_verification(task_id: ${task.id}):verdict 为 pass(满足完成标准,可以交给用户审核)或 fail(明显没做完或产出缺失);checks 逐条列出「怎么算做完」里的每个要求,给出 met 和一句证据;notes 用 1~2 句话写给用户看的结论;fail 时 fix_request 写清需要执行会话补做什么。`, + '- 只调用一次 record_verification,然后结束。', + ].join('\n'); +} + +/** @param {any} task @param {string} fixRequest */ +export function autoFixMessage(task, fixRequest) { + return [ + `MoMo 核对任务「${task.title}」(task_id: ${task.id})时发现问题,请补做:`, + '', + fixRequest, + '', + `补做完成后重新调用 momo 的 submit_task_result 提交完整的最新结果。`, + ].join('\n'); +} diff --git a/plugins/amszuidas/momo-collab/miniapp/node/server.mjs b/plugins/amszuidas/momo-collab/miniapp/node/server.mjs new file mode 100644 index 00000000..39f5e6a5 --- /dev/null +++ b/plugins/amszuidas/momo-collab/miniapp/node/server.mjs @@ -0,0 +1,473 @@ +// @ts-check +// MoMo collaboration Mini App: Node entry. Serves the page, its JSON/SSE API, uploads, and the +// managed MCP endpoint. All mutations go through Store → Engine. + +import { createHash, randomBytes } from 'node:crypto'; +import { createReadStream, existsSync } from 'node:fs'; +import { mkdir, readFile, rename, stat, writeFile } from 'node:fs/promises'; +import { createServer } from 'node:http'; +import { homedir } from 'node:os'; +import { extname, join, resolve } from 'node:path'; + +import { Engine, errorCode } from './engine.mjs'; +import { createMcpHandler } from './mcp.mjs'; +import { DomainError, Store } from './store.mjs'; + +/** @typedef {import('./miniapp-api.js').MiniAppContext} MiniAppContext */ +/** @typedef {import('./miniapp-api.js').MiniAppLifecycle} MiniAppLifecycle */ + +const MAX_BODY = 1024 * 1024; +const MAX_UPLOAD = 20 * 1024 * 1024; +const UPLOAD_ID = /^[a-f0-9]{12}\.(png|jpe?g|gif|webp)$/; +const MEDIA_TYPES = /** @type {Record} */ ({ + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.gif': 'image/gif', + '.webp': 'image/webp', + '.svg': 'image/svg+xml', + '.pdf': 'application/pdf', + '.md': 'text/plain; charset=utf-8', + '.txt': 'text/plain; charset=utf-8', + '.json': 'text/plain; charset=utf-8', + '.csv': 'text/plain; charset=utf-8', + '.html': 'text/plain; charset=utf-8', + '.js': 'text/plain; charset=utf-8', + '.ts': 'text/plain; charset=utf-8', + '.py': 'text/plain; charset=utf-8', + '.yaml': 'text/plain; charset=utf-8', + '.yml': 'text/plain; charset=utf-8', +}); +const STATIC_FILES = /** @type {Record} */ ({ + '/collab': ['index.html', 'text/html; charset=utf-8'], + '/collab/app.js': ['app.js', 'text/javascript; charset=utf-8'], + '/collab/app.css': ['app.css', 'text/css; charset=utf-8'], + '/collab/momo.svg': ['momo.svg', 'image/svg+xml'], +}); + +/** + * `context.dataDir` is `/v2/plugin-data/liveboards/`. The primary agent may + * read its own `agents/mavis/miniapps/` directory without approval, so uploaded images + * live there; otherwise every screenshot read would need an approval the user cannot pre-grant. + * @param {MiniAppContext} context + */ +function agentReadableDir(context) { + const root = resolve(context.dataDir, '..', '..', '..', '..'); + if (existsSync(join(root, 'agents'))) return join(root, 'agents', 'mavis', 'miniapps', context.pluginId); + return context.dataDir; +} + +/** + * @param {MiniAppContext} context + * @returns {Promise} + */ +export async function start(context) { + const clientDir = join(context.pluginRoot, 'miniapp', 'client'); + const agentDir = agentReadableDir(context); + const uploadsDir = join(agentDir, 'uploads'); + const mcodeRoot = resolve(context.dataDir, '..', '..', '..', '..'); + const store = new Store(context.dataDir, context.logger); + await store.load(); + + /** Connected pages: clientId → SSE response. @type {Map} */ + const clients = new Map(); + /** @param {Record} event */ + const broadcast = (event) => { + const frame = `data: ${JSON.stringify(event)}\n\n`; + for (const client of clients.values()) client.write(frame); + }; + let snapshotQueued = false; + store.subscribe(() => { + if (snapshotQueued) return; + snapshotQueued = true; + queueMicrotask(() => { + snapshotQueued = false; + broadcast({ type: 'state', state: viewState() }); + }); + }); + + const engine = new Engine({ + store, + agent: context.agent, + logger: context.logger, + broadcast, + // MiniMax Code's own data (credentials, sessions) is never served as a task output. + protectedRoots: [mcodeRoot, context.dataDir], + outputsRoot: join(agentDir, 'outputs'), + pickPage: (preferred) => (preferred && clients.has(preferred) ? preferred : ([...clients.keys()].at(-1) ?? null)), + }); + + const viewState = () => ({ + ...store.snapshot(), + agentAvailable: engine.available, + momo: engine.momoPendingView(), + activity: engine.activityView(), + pages: clients.size, + }); + await engine.recover(); + + /** @param {string[]} ids */ + const resolveAttachments = async (ids) => { + const out = []; + for (const id of ids.slice(0, 9)) { + if (!UPLOAD_ID.test(id)) continue; + const info = await stat(join(uploadsDir, id)).catch(() => undefined); + if (info?.isFile()) out.push({ id, name: id, agentPath: join(uploadsDir, id) }); + } + return out; + }; + + const handleMcp = createMcpHandler({ store, engine, pageCount: () => clients.size, resolveAttachments }); + + // ------------------------------------------------------------------ helpers + + /** @param {import('node:http').ServerResponse} response @param {number} status @param {unknown} body */ + const json = (response, status, body) => { + response.writeHead(status, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' }); + response.end(JSON.stringify(body)); + }; + + /** @param {import('node:http').IncomingMessage} request */ + const readBody = (request) => + new Promise((done, fail) => { + let size = 0; + /** @type {Buffer[]} */ + const chunks = []; + request.on('data', (chunk) => { + size += chunk.length; + if (size > MAX_BODY) { + fail(new DomainError('too_large', '请求太大')); + request.destroy(); + return; + } + chunks.push(chunk); + }); + request.on('end', () => { + if (!chunks.length) return done({}); + try { + done(JSON.parse(Buffer.concat(chunks).toString('utf8'))); + } catch { + fail(new DomainError('invalid', '请求不是合法 JSON')); + } + }); + request.on('error', fail); + }); + + /** @param {import('node:http').IncomingMessage} request @param {import('node:http').ServerResponse} response */ + const receiveUpload = async (request, response) => { + const type = String(request.headers['content-type'] ?? '').split(';')[0].trim(); + const ext = { 'image/png': 'png', 'image/jpeg': 'jpg', 'image/gif': 'gif', 'image/webp': 'webp' }[type]; + if (!ext) { + request.resume(); + return json(response, 415, { error: 'unsupported', message: '只支持 PNG、JPEG、GIF、WebP 图片' }); + } + /** @type {Buffer[]} */ + const chunks = []; + let size = 0; + const ok = await new Promise((done) => { + request.on('data', (chunk) => { + size += chunk.length; + if (size > MAX_UPLOAD) { + request.destroy(); + done(false); + return; + } + chunks.push(chunk); + }); + request.on('end', () => done(true)); + request.on('error', () => done(false)); + }); + if (!ok || !size) return json(response, ok ? 400 : 413, { error: 'invalid', message: ok ? '图片是空的' : '图片超过 20MB' }); + const data = Buffer.concat(chunks); + const id = `${createHash('sha256').update(data).digest('hex').slice(0, 12)}.${ext}`; + await mkdir(uploadsDir, { recursive: true }); + const temp = join(uploadsDir, `.part-${randomBytes(4).toString('hex')}`); + await writeFile(temp, data); + await rename(temp, join(uploadsDir, id)); + return json(response, 200, { upload: { id, name: id, size } }); + }; + + /** @param {import('node:http').ServerResponse} response @param {string} file @param {number} size */ + const sendFile = (response, file, size) => { + const type = MEDIA_TYPES[extname(file).toLowerCase()]; + response.writeHead(200, { + 'content-type': type ?? 'application/octet-stream', + 'content-length': size, + 'x-content-type-options': 'nosniff', + 'content-security-policy': "default-src 'none'; img-src 'self' data:; style-src 'unsafe-inline'; sandbox", + 'cache-control': 'no-store', + ...(type ? {} : { 'content-disposition': 'attachment' }), + }); + createReadStream(file).on('error', () => response.destroy()).pipe(response); + }; + + // ------------------------------------------------------------------ routes + + /** @param {import('node:http').IncomingMessage} request @param {import('node:http').ServerResponse} response */ + const route = async (request, response) => { + const url = new URL(request.url ?? '/', 'http://miniapp.local'); + const path = url.pathname; + const method = request.method ?? 'GET'; + + if (path === '/mcp/momo') return handleMcp(request, response, method === 'POST' ? await readBody(request) : undefined); + + const asset = method === 'GET' ? STATIC_FILES[path] : undefined; + if (asset) { + const content = await readFile(join(clientDir, asset[0])); + response.writeHead(200, { 'content-type': asset[1], 'cache-control': 'no-store' }); + response.end(content); + return; + } + + if (method === 'GET' && path === '/api/events') { + const clientId = String(url.searchParams.get('client') ?? '').slice(0, 64) || randomBytes(6).toString('hex'); + response.writeHead(200, { 'content-type': 'text/event-stream; charset=utf-8', 'cache-control': 'no-store', 'x-accel-buffering': 'no' }); + clients.get(clientId)?.end(); + clients.set(clientId, response); + response.write(`data: ${JSON.stringify({ type: 'state', state: viewState() })}\n\n`); + const ping = setInterval(() => response.write(': ping\n\n'), 15_000); + request.on('close', () => { + clearInterval(ping); + if (clients.get(clientId) === response) clients.delete(clientId); + }); + return; + } + + if (method === 'GET' && path === '/api/state') return json(response, 200, viewState()); + + if (method === 'POST' && path === '/api/uploads') return receiveUpload(request, response); + const upload = method === 'GET' ? /^\/api\/uploads\/([^/]+)$/.exec(path) : null; + if (upload) { + const id = decodeURIComponent(upload[1]); + if (!UPLOAD_ID.test(id)) return json(response, 404, { error: 'not_found', message: '图片不存在' }); + const info = await stat(join(uploadsDir, id)).catch(() => undefined); + if (!info?.isFile()) return json(response, 404, { error: 'not_found', message: '图片不存在' }); + return sendFile(response, join(uploadsDir, id), info.size); + } + + const output = method === 'GET' ? /^\/api\/tasks\/([^/]+)\/outputs\/(\d+)$/.exec(path) : null; + if (output) { + const { file, size } = await engine.outputFile(decodeURIComponent(output[1]), Number(output[2])); + return sendFile(response, file, size); + } + + if (method === 'GET' && path === '/api/metrics') return json(response, 200, await metrics(store)); + + if (method !== 'POST') return json(response, 404, { error: 'not_found', message: '没有这个接口' }); + const body = /** @type {Record} */ (await readBody(request)); + const clientId = typeof body.clientId === 'string' ? body.clientId.slice(0, 64) : null; + + switch (path) { + case '/api/chat': { + const images = await resolveAttachments(Array.isArray(body.attachments) ? body.attachments.map(String) : []); + const result = await engine.sendToMomo({ + text: String(body.text ?? ''), + images, + focusIdeaId: typeof body.focusIdeaId === 'string' && body.focusIdeaId ? body.focusIdeaId : null, + clientId, + source: body.source === 'quick_capture' ? 'quick_capture' : 'chat', + }); + return json(response, 200, result); + } + case '/api/chat/stop': + engine.stopMomo(); + return json(response, 200, { ok: true }); + case '/api/momo/answer': + return json(response, 200, { message: await engine.answerMomoQuestion(String(body.requestId), body.answers ?? []) }); + case '/api/momo/approval': + await engine.replyMomoApproval(String(body.requestId), body.decision === 'allowOnce' ? 'allowOnce' : 'deny'); + return json(response, 200, { ok: true }); + case '/api/seen': + await store.updateSettings({ lastSeenAt: Date.now() }); + return json(response, 200, { ok: true }); + case '/api/settings': { + const patch = {}; + if (['sky', 'mint', 'peach', 'lilac', 'lemon'].includes(body.momoColor)) Object.assign(patch, { momoColor: body.momoColor }); + if (body.avatarUploadId === null || (typeof body.avatarUploadId === 'string' && UPLOAD_ID.test(body.avatarUploadId))) { + Object.assign(patch, { avatarUploadId: body.avatarUploadId }); + } + await store.updateSettings(patch); + return json(response, 200, { ok: true }); + } + case '/api/track': { + const allowed = new Set(['collab_view', 'collab_report_view', 'collab_report_click', 'collab_board_card_click']); + if (!allowed.has(body.event)) throw new DomainError('invalid', '未知埋点'); + const fields = Object.fromEntries( + Object.entries(body.fields ?? {}) + .filter(([, value]) => ['string', 'number', 'boolean'].includes(typeof value)) + .slice(0, 12) + .map(([key, value]) => [key.slice(0, 40), typeof value === 'string' ? value.slice(0, 120) : value]), + ); + await store.emit(body.event, fields); + return json(response, 200, { ok: true }); + } + default: + break; + } + + const recordRoute = /^\/api\/records\/([^/]+)\/(move|confirm|delete)$/.exec(path); + if (recordRoute) { + const id = decodeURIComponent(recordRoute[1]); + if (recordRoute[2] === 'move') { + const target = typeof body.newIdeaTitle === 'string' && body.newIdeaTitle.trim() ? { newIdeaTitle: body.newIdeaTitle } : String(body.ideaId ?? ''); + return json(response, 200, await store.moveRecord(id, target)); + } + if (recordRoute[2] === 'confirm') return json(response, 200, await store.confirmRecord(id)); + return json(response, 200, await store.deleteRecord(id)); + } + + const ideaRoute = /^\/api\/ideas\/([^/]+)\/(delete|rename|confirm-item)$/.exec(path); + if (ideaRoute) { + const id = decodeURIComponent(ideaRoute[1]); + if (ideaRoute[2] === 'delete') { + const result = await store.deleteIdea(id, 'user'); + return json(response, 200, { keptTasks: result.keptTasks.length }); + } + if (ideaRoute[2] === 'rename') return json(response, 200, await store.renameIdea(id, String(body.title ?? ''))); + return json(response, 200, await store.reviseItem(id, { kind: body.kind === 'fact' ? 'fact' : 'gate', id: String(body.itemId ?? '') }, 'confirm')); + } + + const taskRoute = /^\/api\/tasks\/([^/]+)\/([a-z-]+)$/.exec(path); + if (taskRoute) { + const id = decodeURIComponent(taskRoute[1]); + switch (taskRoute[2]) { + case 'start': + return json(response, 200, await engine.requestDispatch(id, clientId)); + case 'claim': + return json(response, 200, await engine.claimDispatch(id, String(body.sessionId ?? ''))); + case 'dispatch-failed': { + const task = store.task(id); + if (task.dispatch && !task.sessionId) { + await store.updateTask(id, { + patch: { dispatch: null }, + blocked: { reason: 'project_unavailable', detail: `没法开工(${String(body.code ?? 'unknown').slice(0, 40)}),刷新协作页面后再试` }, + }); + } + return json(response, 200, { ok: true }); + } + case 'continue': + await engine.continueTask(id); + return json(response, 200, { ok: true }); + case 'redispatch': + await store.updateTask(id, { blocked: null, patch: { sessionId: null } }); + return json(response, 200, await engine.requestDispatch(id, clientId)); + case 'defer': + await engine.deferTask(id); + return json(response, 200, { ok: true }); + case 'project': { + const task = store.task(id); + if (task.sessionId) throw new DomainError('invalid', '任务已经开工,不能换项目'); + const raw = typeof body.projectDir === 'string' ? body.projectDir.trim() : ''; + const dir = raw ? resolve(raw.startsWith('~/') ? join(homedir(), raw.slice(2)) : raw) : null; + if (dir) { + const problem = await engine.checkProjectDir(dir); + if (problem) throw new DomainError('invalid', problem); + } + await store.updateTask(id, { + patch: { projectDir: dir }, + blocked: task.blocked?.reason === 'project_unavailable' ? null : undefined, + }); + return json(response, 200, { ok: true, projectDir: dir }); + } + case 'approve': + await engine.approveTask(id); + return json(response, 200, { ok: true }); + case 'revise': + await engine.reviseTask(id, String(body.feedback ?? '')); + return json(response, 200, { ok: true }); + case 'stop': + await engine.stopTask(id); + return json(response, 200, { ok: true }); + case 'approval': + await engine.replyTaskApproval(id, String(body.requestId), body.decision === 'allowOnce' ? 'allowOnce' : 'deny'); + return json(response, 200, { ok: true }); + case 'answer': + await engine.answerTaskQuestion(id, String(body.requestId), body.answers ?? []); + return json(response, 200, { ok: true }); + default: + break; + } + } + return json(response, 404, { error: 'not_found', message: '没有这个接口' }); + }; + + const server = createServer((request, response) => { + route(request, response).catch((error) => { + const known = error instanceof DomainError; + const agentCode = errorCode(error); + const status = known + ? { not_found: 404, forbidden: 403, busy: 409, too_large: 413 }[/** @type {string} */ (error.code)] ?? 400 + : agentCode === 'BUSY' + ? 409 + : 500; + if (!known) context.logger.error('momo.request_failed', { path: (request.url ?? '').split('?')[0], code: agentCode }); + if (response.headersSent) { + response.end(); + return; + } + json(response, status, { + error: known ? error.code : agentCode || 'internal', + message: known ? error.message : agentCode === 'BUSY' ? '正忙,稍后再试' : '内部错误', + }); + }); + }); + server.requestTimeout = 0; + server.headersTimeout = 60_000; + + await new Promise((done, fail) => { + server.once('error', fail); + server.listen(context.listen.port, context.listen.host, () => { + server.off('error', fail); + done(undefined); + }); + }); + context.logger.info('momo.listening', { agent: Boolean(context.agent) }); + + let disposed = false; + const dispose = async () => { + if (disposed) return; + disposed = true; + context.signal.removeEventListener('abort', onAbort); + engine.dispose(); + for (const client of clients.values()) client.end(); + clients.clear(); + await store.chain; + const closed = new Promise((done) => server.close(() => done(undefined))); + server.closeAllConnections?.(); + await closed; + }; + const onAbort = () => void dispose(); + context.signal.addEventListener('abort', onAbort, { once: true }); + if (context.signal.aborted) await dispose(); + return { dispose }; +} + +/** Trial metrics computed from local telemetry. @param {Store} store */ +async function metrics(store) { + const events = await store.readTelemetry(); + const ideas = Object.values(store.state.ideas); + const tasks = Object.values(store.state.tasks); + const day = 24 * 60 * 60 * 1000; + const activity = store.state.activity; + const eligible = ideas.filter((idea) => Date.now() - idea.createdAt >= day); + const returned = eligible.filter((idea) => activity.some((at) => at >= idea.createdAt + day && at <= idea.createdAt + 7 * day)); + const withTask = ideas.filter((idea) => tasks.some((task) => task.ideaId === idea.id)); + const dispatched = tasks.filter((task) => task.startedAt); + const done = dispatched.filter((task) => task.status === 'done'); + const firstPass = done.filter((task) => task.reviewRound === 0); + const revise = events.filter((event) => event.event === 'idea_fact_revise'); + const inferredOf = (/** @type {string} */ type) => events.filter((event) => event.event === 'idea_fact_infer' && event.fact_type === type).length; + const corrections = (/** @type {string} */ type) => + revise.filter((event) => event.fact_type === type && (event.revise_type === 'correct' || event.revise_type === 'overturn')).length; + const pct = (/** @type {number} */ a, /** @type {number} */ b) => (b ? Math.round((a / b) * 1000) / 10 : null); + const correction = (/** @type {string} */ type) => ({ value: pct(corrections(type), inferredOf(type)), numerator: corrections(type), denominator: inferredOf(type) }); + return { + seven_day_return: { value: pct(returned.length, eligible.length), numerator: returned.length, denominator: eligible.length }, + idea_to_task: { value: pct(withTask.length, ideas.length), numerator: withTask.length, denominator: ideas.length }, + task_pass_rate: { value: pct(done.length, dispatched.length), numerator: done.length, denominator: dispatched.length }, + first_pass_rate: { value: pct(firstPass.length, done.length), numerator: firstPass.length, denominator: done.length }, + attribution_correction_rate: correction('attribution'), + content_correction_rate: { value: pct(corrections('gate') + corrections('record'), inferredOf('gate') + inferredOf('record')), numerator: corrections('gate') + corrections('record'), denominator: inferredOf('gate') + inferredOf('record') }, + event_count: events.length, + }; +} diff --git a/plugins/amszuidas/momo-collab/miniapp/node/store.mjs b/plugins/amszuidas/momo-collab/miniapp/node/store.mjs new file mode 100644 index 00000000..fa07bbad --- /dev/null +++ b/plugins/amszuidas/momo-collab/miniapp/node/store.mjs @@ -0,0 +1,671 @@ +// @ts-check +// Single owner of MoMo collaboration state. Every mutation goes through `commit`, which applies the +// change in memory, persists atomically, records telemetry, and only then notifies listeners. + +import { randomBytes } from 'node:crypto'; +import { appendFile, mkdir, readFile, rename, writeFile } from 'node:fs/promises'; +import { join } from 'node:path'; + +export const TASK_STATUSES = /** @type {const} */ (['todo', 'in_progress', 'pending_review', 'done']); +export const BLOCKED_REASONS = /** @type {const} */ (['need_approval', 'need_info', 'run_failed', 'project_unavailable']); +export const GATE_KEYS = /** @type {const} */ (['objective', 'deliverable', 'done_criteria']); +export const GATE_LABELS = { objective: '做什么', deliverable: '产出什么', done_criteria: '怎么算做完' }; +export const RECORD_SOURCES = /** @type {const} */ (['chat', 'quick_capture', 'session_handoff', 'session_return', 'review']); + +const TEXT_MAX = 20_000; +const TITLE_MAX = 80; + +export class DomainError extends Error { + /** @param {string} code @param {string} message */ + constructor(code, message) { + super(message); + this.code = code; + } +} + +/** @param {string} prefix */ +export function newId(prefix) { + return `${prefix}_${randomBytes(5).toString('hex')}`; +} + +/** @param {unknown} value @param {string} label @param {number} [max] */ +export function cleanText(value, label, max = TEXT_MAX) { + const text = typeof value === 'string' ? value.trim() : ''; + if (!text) throw new DomainError('invalid', `${label}不能为空`); + if (text.length > max) throw new DomainError('invalid', `${label}超过 ${max} 字`); + return text; +} + +/** @param {unknown} value */ +function optionalText(value, max = TEXT_MAX) { + if (value === undefined || value === null) return undefined; + const text = String(value).trim(); + return text ? text.slice(0, max) : undefined; +} + +function emptyState() { + return { + version: 1, + ideas: /** @type {Record} */ ({}), + records: /** @type {Record} */ ({}), + tasks: /** @type {Record} */ ({}), + chat: /** @type {any[]} */ ([]), + activity: /** @type {number[]} */ ([]), + settings: { momoColor: 'sky', avatarUploadId: null, lastSeenAt: 0 }, + momoSession: { sessionId: null, runs: 0 }, + }; +} + +export class Store { + /** @param {string} dataDir @param {{ info: Function, warn: Function, error: Function }} logger */ + constructor(dataDir, logger) { + this.dir = dataDir; + this.file = join(dataDir, 'state.json'); + this.telemetryFile = join(dataDir, 'telemetry.jsonl'); + this.logger = logger; + this.state = emptyState(); + /** @type {Promise} */ + this.chain = Promise.resolve(); + /** @type {Set<(state: any) => void>} */ + this.listeners = new Set(); + } + + async load() { + await mkdir(this.dir, { recursive: true }); + try { + const raw = JSON.parse(await readFile(this.file, 'utf8')); + this.state = { ...emptyState(), ...raw, settings: { ...emptyState().settings, ...raw.settings } }; + } catch (error) { + if (/** @type {any} */ (error)?.code !== 'ENOENT') { + // Keep the unreadable file for inspection instead of overwriting the user's data. + const aside = `${this.file}.corrupt-${Date.now()}`; + await rename(this.file, aside).catch(() => {}); + this.logger.error('momo.state_unreadable', { aside }); + } + } + } + + /** @param {(state: any) => void} listener */ + subscribe(listener) { + this.listeners.add(listener); + return () => this.listeners.delete(listener); + } + + /** + * Serializes one mutation: apply, persist, telemetry, then notify. A failed write rolls the + * in-memory state back so memory never runs ahead of disk. + * @template T + * @param {(state: ReturnType, emit: (event: string, fields: Record) => void) => T} mutate + * @returns {Promise} + */ + commit(mutate) { + const run = async () => { + const before = JSON.stringify(this.state); + /** @type {{ event: string, fields: Record }[]} */ + const events = []; + let result; + try { + result = mutate(this.state, (event, fields) => events.push({ event, fields })); + const temp = `${this.file}.${process.pid}.tmp`; + await writeFile(temp, JSON.stringify(this.state)); + await rename(temp, this.file); + } catch (error) { + this.state = JSON.parse(before); + throw error; + } + if (events.length) await this.track(events); + for (const listener of this.listeners) { + try { + listener(this.state); + } catch { + /* a listener failure never undoes a committed change */ + } + } + return result; + }; + const next = this.chain.then(run, run); + this.chain = next.catch(() => {}); + return next; + } + + /** @param {{ event: string, fields: Record }[]} events */ + async track(events) { + const lines = events.map(({ event, fields }) => JSON.stringify({ event, ts: Date.now(), ...fields })).join('\n'); + await appendFile(this.telemetryFile, `${lines}\n`).catch((error) => + this.logger.warn('momo.telemetry_write_failed', { code: String(error?.code ?? '') }), + ); + } + + async readTelemetry() { + try { + return (await readFile(this.telemetryFile, 'utf8')) + .split('\n') + .filter(Boolean) + .map((line) => { + try { + return JSON.parse(line); + } catch { + return undefined; + } + }) + .filter(Boolean); + } catch { + return []; + } + } + + // ---------------------------------------------------------------- reads + + /** @param {string} id */ + idea(id) { + const idea = this.state.ideas[id]; + if (!idea || idea.deleted) throw new DomainError('not_found', `想法 ${id} 不存在`); + return idea; + } + + /** @param {string} id */ + task(id) { + const task = this.state.tasks[id]; + if (!task) throw new DomainError('not_found', `任务 ${id} 不存在`); + return task; + } + + /** @param {string} id */ + record(id) { + const record = this.state.records[id]; + if (!record) throw new DomainError('not_found', `记录 ${id} 不存在`); + return record; + } + + liveIdeas() { + return Object.values(this.state.ideas).filter((idea) => !idea.deleted); + } + + /** Resolves an idea by id or exact (case-insensitive) title. @param {string} ref */ + findIdea(ref) { + const key = String(ref ?? '').trim(); + if (!key) return undefined; + if (this.state.ideas[key] && !this.state.ideas[key].deleted) return this.state.ideas[key]; + const lower = key.toLowerCase(); + return this.liveIdeas().find((idea) => idea.title.toLowerCase() === lower); + } + + /** @param {any} idea */ + gateProgress(idea) { + const missing = GATE_KEYS.filter((key) => !idea.gates[key]?.text); + return { filled: GATE_KEYS.length - missing.length, missing: missing.map((key) => GATE_LABELS[key]) }; + } + + /** @param {string} ideaId */ + recordsOf(ideaId) { + return Object.values(this.state.records) + .filter((record) => record.ideaId === ideaId) + .sort((a, b) => a.createdAt - b.createdAt); + } + + /** @param {string} ideaId */ + tasksOf(ideaId) { + return Object.values(this.state.tasks) + .filter((task) => task.ideaId === ideaId) + .sort((a, b) => a.createdAt - b.createdAt); + } + + // ---------------------------------------------------------------- ideas & records + + /** @param {any} state @param {string} title @param {'user'|'momo'} actor @param {Function} emit */ + static createIdeaIn(state, title, actor, emit) { + const now = Date.now(); + const idea = { + id: newId('idea'), + title: cleanText(title, '想法名', TITLE_MAX), + createdAt: now, + updatedAt: now, + gates: { objective: null, deliverable: null, done_criteria: null }, + facts: [], + deleted: false, + }; + state.ideas[idea.id] = idea; + emit('idea_change', { idea_id: idea.id, action: 'create', actor }); + return idea; + } + + /** + * Adds a record. `target` is an idea id, `{ newIdeaTitle }`, or null for unattributed. + * @param {{ text: string, target: string | { newIdeaTitle: string } | null, source: string, + * certainty?: 'inferred' | 'confirmed', attribution?: 'inferred' | 'confirmed', originTitle?: string, + * originSessionId?: string, images?: { id: string, name: string }[], userActivity?: boolean }} input + */ + addRecord(input) { + if (!RECORD_SOURCES.includes(/** @type {any} */ (input.source))) throw new DomainError('invalid', '记录来源无效'); + const text = input.text ? cleanText(input.text, '记录内容') : ''; + const images = (input.images ?? []).slice(0, 9); + if (!text && !images.length) throw new DomainError('invalid', '记录内容不能为空'); + return this.commit((state, emit) => { + let ideaId = null; + let createdIdea = false; + if (input.target && typeof input.target === 'object') { + const existing = this.findIdea(input.target.newIdeaTitle); + if (existing) ideaId = existing.id; + else { + ideaId = Store.createIdeaIn(state, input.target.newIdeaTitle, 'momo', emit).id; + createdIdea = true; + } + } else if (typeof input.target === 'string') { + ideaId = this.idea(input.target).id; + } + const now = Date.now(); + const record = { + id: newId('rec'), + ideaId, + text, + images, + source: input.source, + certainty: input.certainty ?? 'confirmed', + attribution: ideaId ? (input.attribution ?? 'inferred') : 'inferred', + originTitle: optionalText(input.originTitle, 120), + originSessionId: optionalText(input.originSessionId, 128), + movedFrom: null, + createdAt: now, + }; + state.records[record.id] = record; + if (ideaId) state.ideas[ideaId].updatedAt = now; + if (input.userActivity !== false) state.activity.push(now); + emit('idea_record_add', { + idea_id: ideaId ?? '', + record_id: record.id, + source: input.source, + material_types: [text ? 'text' : '', images.length ? 'image' : ''].filter(Boolean), + text_length: text.length, + image_count: images.length, + attribution_type: !ideaId ? 'unattributed' : createdIdea ? 'new' : 'supplement', + certainty: record.certainty, + }); + if (ideaId && record.attribution === 'inferred') emit('idea_fact_infer', { idea_id: ideaId, fact_type: 'attribution' }); + return { record, idea: ideaId ? state.ideas[ideaId] : null, createdIdea }; + }); + } + + /** @param {string} recordId @param {string | { newIdeaTitle: string }} target */ + moveRecord(recordId, target) { + return this.commit((state, emit) => { + const record = this.record(recordId); + const from = record.ideaId ? state.ideas[record.ideaId] : null; + let to; + let createdIdea = false; + if (typeof target === 'object') { + to = this.findIdea(target.newIdeaTitle); + if (!to) { + to = Store.createIdeaIn(state, target.newIdeaTitle, 'user', emit); + createdIdea = true; + } + } else to = this.idea(target); + record.movedFrom = from ? { ideaId: from.id, title: from.title } : { ideaId: null, title: '未归属' }; + record.ideaId = to.id; + record.attribution = 'confirmed'; + to.updatedAt = Date.now(); + state.activity.push(Date.now()); + emit('idea_fact_revise', { idea_id: to.id, fact_type: 'attribution', revise_type: from ? 'correct' : 'confirm' }); + return { record, idea: to, from, createdIdea }; + }); + } + + /** @param {string} recordId */ + confirmRecord(recordId) { + return this.commit((state, emit) => { + const record = this.record(recordId); + if (record.ideaId) record.attribution = 'confirmed'; + record.certainty = 'confirmed'; + emit('idea_fact_revise', { idea_id: record.ideaId ?? '', fact_type: 'record', revise_type: 'confirm' }); + return record; + }); + } + + /** @param {string} recordId */ + deleteRecord(recordId) { + return this.commit((state) => { + const record = this.record(recordId); + delete state.records[recordId]; + return record; + }); + } + + /** @param {string} ideaId @param {'user'|'momo'} actor */ + deleteIdea(ideaId, actor) { + return this.commit((state, emit) => { + const idea = this.idea(ideaId); + idea.deleted = true; + idea.updatedAt = Date.now(); + // Records keep their text; they fall back to unattributed so nothing the user said is lost. + for (const record of Object.values(state.records)) { + if (record.ideaId === ideaId) { + record.movedFrom = { ideaId, title: idea.title }; + record.ideaId = null; + record.attribution = 'inferred'; + } + } + emit('idea_change', { idea_id: ideaId, action: 'delete', actor }); + const keptTasks = Object.values(state.tasks).filter((task) => task.ideaId === ideaId); + return { idea, keptTasks }; + }); + } + + /** @param {string} ideaId @param {string} title */ + renameIdea(ideaId, title) { + return this.commit(() => { + const idea = this.idea(ideaId); + idea.title = cleanText(title, '想法名', TITLE_MAX); + idea.updatedAt = Date.now(); + return idea; + }); + } + + /** + * Updates gates. Confirmed gates are only changed when `userOverride` is set (the user explicitly + * overturned them); MoMo's own inferences never overwrite what the user confirmed. + * @param {string} ideaId + * @param {Partial>} values + * @param {{ confirmed?: boolean, userOverride?: boolean }} options + */ + updateGates(ideaId, values, options = {}) { + return this.commit((state, emit) => { + const idea = this.idea(ideaId); + const refused = []; + const changed = []; + for (const key of GATE_KEYS) { + const raw = values[key]; + if (raw === undefined || raw === null) continue; + const text = cleanText(raw, GATE_LABELS[key], 2000); + const current = idea.gates[key]; + if (current?.certainty === 'confirmed' && !options.userOverride && current.text !== text) { + refused.push(GATE_LABELS[key]); + continue; + } + const certainty = options.confirmed || options.userOverride ? 'confirmed' : 'inferred'; + if (current && current.text !== text) { + emit('idea_fact_revise', { + idea_id: idea.id, + fact_type: 'gate', + revise_type: current.certainty === 'confirmed' ? 'overturn' : 'correct', + }); + } else if (current && certainty === 'confirmed' && current.certainty !== 'confirmed') { + emit('idea_fact_revise', { idea_id: idea.id, fact_type: 'gate', revise_type: 'confirm' }); + } + idea.gates[key] = { text, certainty, updatedAt: Date.now() }; + if (certainty === 'inferred') emit('idea_fact_infer', { idea_id: idea.id, fact_type: 'gate' }); + changed.push(GATE_LABELS[key]); + } + idea.updatedAt = Date.now(); + return { idea, changed, refused, progress: this.gateProgress(idea) }; + }); + } + + /** Agreeing to start a task confirms the gates MoMo inferred for its idea. @param {string} ideaId */ + confirmInferredGates(ideaId) { + return this.commit((state, emit) => { + const idea = state.ideas[ideaId]; + if (!idea || idea.deleted) return; + for (const key of GATE_KEYS) { + const gate = idea.gates[key]; + if (gate && gate.certainty !== 'confirmed') { + gate.certainty = 'confirmed'; + emit('idea_fact_revise', { idea_id: idea.id, fact_type: 'gate', revise_type: 'confirm' }); + } + } + }); + } + + /** @param {string} ideaId @param {string} text @param {boolean} confirmed */ + addFact(ideaId, text, confirmed) { + return this.commit((_state, emit) => { + const idea = this.idea(ideaId); + const fact = { id: newId('fact'), text: cleanText(text, '内容', 2000), certainty: confirmed ? 'confirmed' : 'inferred', createdAt: Date.now() }; + idea.facts.push(fact); + if (!confirmed) emit('idea_fact_infer', { idea_id: idea.id, fact_type: 'record' }); + idea.updatedAt = Date.now(); + return { idea, fact }; + }); + } + + /** + * @param {string} ideaId + * @param {{ kind: 'gate' | 'fact', id: string, text?: string }} item + * @param {'confirm' | 'correct' | 'remove'} action + * @param {{ userOverride?: boolean }} options + */ + reviseItem(ideaId, item, action, options = {}) { + return this.commit((state, emit) => { + const idea = this.idea(ideaId); + /** @type {any} */ + let target; + if (item.kind === 'gate') { + if (!GATE_KEYS.includes(/** @type {any} */ (item.id))) throw new DomainError('invalid', '门槛名称无效'); + target = idea.gates[item.id]; + if (!target && action !== 'correct') throw new DomainError('not_found', '该门槛还没有内容'); + } else { + target = idea.facts.find((/** @type {any} */ fact) => fact.id === item.id); + if (!target) throw new DomainError('not_found', '内容不存在'); + } + const wasConfirmed = target?.certainty === 'confirmed'; + if (wasConfirmed && action !== 'confirm' && !options.userOverride) { + throw new DomainError('forbidden', '这条内容用户已确认,只有用户明确推翻时才能修改(传 user_override: true)'); + } + if (action === 'confirm') { + target.certainty = 'confirmed'; + } else if (action === 'correct') { + const text = cleanText(item.text, '修正内容', 2000); + if (item.kind === 'gate') idea.gates[item.id] = { text, certainty: options.userOverride ? 'confirmed' : 'inferred', updatedAt: Date.now() }; + else { + target.text = text; + target.certainty = options.userOverride ? 'confirmed' : 'inferred'; + } + } else if (item.kind === 'gate') idea.gates[item.id] = null; + else idea.facts = idea.facts.filter((/** @type {any} */ fact) => fact.id !== item.id); + idea.updatedAt = Date.now(); + emit('idea_fact_revise', { + idea_id: idea.id, + fact_type: item.kind === 'gate' ? 'gate' : 'record', + revise_type: action === 'confirm' ? 'confirm' : wasConfirmed ? 'overturn' : 'correct', + }); + return idea; + }); + } + + // ---------------------------------------------------------------- tasks + + /** @param {{ ideaId: string, title: string, notes?: string, projectDir?: string | null }} input */ + createTask(input) { + return this.commit(() => { + const idea = this.idea(input.ideaId); + const progress = this.gateProgress(idea); + if (progress.filled < GATE_KEYS.length) { + throw new DomainError('gates_incomplete', `还差:${progress.missing.join('、')}。三项门槛写清后才能写成任务`); + } + const now = Date.now(); + const task = { + id: newId('task'), + ideaId: idea.id, + title: cleanText(input.title, '任务名', TITLE_MAX), + objective: idea.gates.objective.text, + deliverable: idea.gates.deliverable.text, + doneCriteria: idea.gates.done_criteria.text, + notes: optionalText(input.notes, 4000) ?? '', + status: 'todo', + blocked: null, + sessionId: null, + projectDir: input.projectDir ?? null, + workspace: null, + consent: 'pending', + interrupted: null, + reviewRound: 0, + outputs: [], + summary: '', + verification: null, + approvals: [], + question: null, + progress: '', + createdAt: now, + updatedAt: now, + startedAt: null, + runStartedAt: null, + finishedAt: null, + history: [{ at: now, to: 'todo', note: '任务已写好' }], + }; + this.state.tasks[task.id] = task; + idea.updatedAt = now; + return task; + }); + } + + /** + * The only status writer. Emits `task_status_result` for every status or blocked-reason change. + * @param {string} taskId + * @param {{ status?: typeof TASK_STATUSES[number], blocked?: null | { reason: typeof BLOCKED_REASONS[number], detail: string }, note?: string, patch?: Record }} change + */ + updateTask(taskId, change) { + return this.commit((state, emit) => { + const task = this.task(taskId); + const fromStatus = task.status; + const fromBlocked = task.blocked?.reason ?? ''; + if (change.patch) Object.assign(task, change.patch); + const now = Date.now(); + if (change.status && change.status !== task.status) { + task.status = change.status; + if (change.status === 'in_progress') task.runStartedAt = now; + if (change.status === 'done') task.finishedAt = now; + task.history.push({ at: now, from: fromStatus, to: change.status, note: change.note ?? '' }); + if (task.history.length > 100) task.history.splice(0, task.history.length - 100); + } + if (change.blocked !== undefined) { + task.blocked = change.blocked ? { ...change.blocked, since: now } : null; + } + task.updatedAt = now; + const toBlocked = task.blocked?.reason ?? ''; + if (fromStatus !== task.status || fromBlocked !== toBlocked) { + emit('task_status_result', { + idea_id: task.ideaId, + task_id: task.id, + task_session_id: task.sessionId ?? '', + project_name: task.workspace ? task.workspace.split(/[\\/]/).filter(Boolean).pop() ?? '' : '', + from_status: fromStatus, + status_type: task.status, + blocked_reason: toBlocked, + review_round: task.reviewRound, + duration_ms: task.startedAt ? now - task.startedAt : 0, + }); + } + return task; + }); + } + + /** @param {string} event @param {Record} fields */ + emit(event, fields) { + return this.commit((_state, emit) => emit(event, fields)); + } + + noteUserActivity() { + return this.commit((state) => { + state.activity.push(Date.now()); + if (state.activity.length > 5000) state.activity.splice(0, state.activity.length - 5000); + }); + } + + // ---------------------------------------------------------------- MoMo conversation log + + /** + * One message in the global MoMo conversation. `parts` for MoMo turns hold text and receipts. + * @param {{ role: 'user' | 'momo' | 'system', text: string, images?: { id: string, name: string }[], focusIdeaId?: string | null, kind?: string, status?: string, receipts?: any[], tools?: any[] }} input + */ + addChat(input) { + return this.commit((state) => { + const message = { + id: newId('msg'), + role: input.role, + text: input.text, + images: input.images ?? [], + focusIdeaId: input.focusIdeaId ?? null, + kind: input.kind ?? 'message', + status: input.status ?? 'done', + receipts: input.receipts ?? [], + tools: input.tools ?? [], + createdAt: Date.now(), + }; + state.chat.push(message); + if (state.chat.length > 500) state.chat.splice(0, state.chat.length - 500); + if (input.role === 'user') state.activity.push(Date.now()); + return message; + }); + } + + /** @param {string} messageId @param {(message: any) => void} update */ + updateChat(messageId, update) { + return this.commit((state) => { + const message = state.chat.find((item) => item.id === messageId); + if (message) update(message); + return message; + }); + } + + /** @param {Record} patch */ + updateSettings(patch) { + return this.commit((state) => Object.assign(state.settings, patch)); + } + + /** @param {Record} patch */ + updateMomoSession(patch) { + return this.commit((state) => Object.assign(state.momoSession, patch)); + } + + // ---------------------------------------------------------------- projections + + /** What MoMo and the page show as "needs you": review, blocked, todo — in that order. */ + briefing() { + const tasks = Object.values(this.state.tasks); + const ideaTitle = (/** @type {string} */ id) => this.state.ideas[id]?.title ?? '已删除的想法'; + const view = (/** @type {any} */ task) => ({ + task_id: task.id, + title: task.title, + idea: ideaTitle(task.ideaId), + status: task.status, + blocked: task.blocked?.reason ?? null, + blocked_detail: task.blocked?.detail ?? null, + }); + const needsYou = [ + ...tasks.filter((task) => task.status === 'pending_review'), + ...tasks.filter((task) => task.blocked && task.status !== 'pending_review'), + ...tasks.filter((task) => task.status === 'todo' && !task.blocked && task.consent !== 'later'), + ].map(view); + const running = tasks.filter((task) => task.status === 'in_progress' && !task.blocked).map(view); + const almost = this.liveIdeas() + .map((idea) => ({ idea, progress: this.gateProgress(idea) })) + .filter(({ idea, progress }) => progress.filled === 2 && !this.tasksOf(idea.id).some((task) => task.status !== 'done')) + .sort((a, b) => b.idea.updatedAt - a.idea.updatedAt)[0]; + const unattributed = Object.values(this.state.records).filter((record) => !record.ideaId).length; + return { + needs_you: needsYou, + running, + almost_ready_idea: almost ? { idea_id: almost.idea.id, title: almost.idea.title, missing: almost.progress.missing } : null, + unattributed_records: unattributed, + }; + } + + /** Changes since the user last looked: drives the "while you were away" report. */ + changesSince(/** @type {number} */ since) { + if (!since) return false; + return Object.values(this.state.tasks).some((task) => task.updatedAt > since && task.history.some((entry) => entry.at > since)); + } + + snapshot() { + const ideas = this.liveIdeas() + .map((idea) => ({ ...idea, progress: this.gateProgress(idea) })) + .sort((a, b) => b.updatedAt - a.updatedAt); + return { + ideas, + records: Object.values(this.state.records).sort((a, b) => a.createdAt - b.createdAt), + tasks: Object.values(this.state.tasks).sort((a, b) => b.updatedAt - a.updatedAt), + chat: this.state.chat.slice(-200), + settings: this.state.settings, + briefing: this.briefing(), + hasChangesSinceSeen: this.changesSince(this.state.settings.lastSeenAt), + }; + } +} diff --git a/plugins/amszuidas/momo-collab/package.json b/plugins/amszuidas/momo-collab/package.json new file mode 100644 index 00000000..e11fd85e --- /dev/null +++ b/plugins/amszuidas/momo-collab/package.json @@ -0,0 +1,10 @@ +{ + "name": "momo-collab", + "version": "0.2.0", + "private": true, + "type": "module", + "mcode": { + "schemaVersion": 2, + "miniApp": "./miniapp/miniapp.json" + } +} diff --git a/plugins/amszuidas/momo-collab/servers.mcp.json b/plugins/amszuidas/momo-collab/servers.mcp.json new file mode 100644 index 00000000..4429c46e --- /dev/null +++ b/plugins/amszuidas/momo-collab/servers.mcp.json @@ -0,0 +1,12 @@ +{ + "schemaVersion": 1, + "mcpServers": { + "momo": { + "type": "streamable-http", + "url": "http://127.0.0.1:9/mcp/momo", + "headers": {}, + "description": "MoMo 协作工具:想法、记录、三项门槛、任务看板。普通会话里也能把结论交给 MoMo。", + "timeout": 60000 + } + } +} diff --git a/plugins/amszuidas/momo-collab/skills/momo-collab/SKILL.md b/plugins/amszuidas/momo-collab/skills/momo-collab/SKILL.md new file mode 100644 index 00000000..3bdd7f8a --- /dev/null +++ b/plugins/amszuidas/momo-collab/skills/momo-collab/SKILL.md @@ -0,0 +1,23 @@ +--- +name: momo-collab +description: 用户在普通会话里说「把这个会话交给 MoMo」「记到 MoMo / 协作里」「这个想法记一下」,或问协作里的想法、任务进展时使用。通过 momo MCP 工具把会话结论整理成记录归到对应想法,或查询协作看板。 +--- + +# 把会话交给 MoMo + +MoMo 是 MiniMax Code「协作」里的项目管家,数据都在 `momo` MCP 工具背后(协作 Mini App 运行时可用)。 + +## 用户把当前会话交给 MoMo +1. 调用 `get_overview` 看现有想法。 +2. 把本会话里**已经得出的结论**整理成 1~5 条要点(不要编造,没有结论就如实说)。每条用 `add_record` 记录: + - `source: "session_handoff"`,`origin_title` 写本会话的主题。 + - 能对上已有想法就传 `idea`,是新的一件事就传 `new_idea_title`,判断不了就 `unattributed: true`。 + - 不传 `reply_to`。 +3. 告诉用户记到了哪些想法,并提示可以打开「协作 · MoMo」继续推进。 + +## 用户问协作进展 +调用 `get_overview`,按「需要你处理 → 进行中 → 快写清楚的想法」简短回答,不罗列统计数字。 + +## 不要做 +- 不要在普通会话里 `start_task` 派任务;请用户到协作页面里说「开始」,任务会在那个会话所在的项目里执行。 +- `momo` 工具不可用时,说明需要先打开「协作 · MoMo」Mini App(在输入框 @ 它,或从 Mini App 入口打开)。