From 5cb832697b87cb0c3d3cdf2ce8c6015de9ab57d8 Mon Sep 17 00:00:00 2001 From: wangfenghe Date: Fri, 20 Mar 2026 11:03:07 +0800 Subject: [PATCH 01/21] feat(back): align db schema to linkwork_* and litellm gateway --- .../com/linkwork/mapper/GitLabAuthMapper.java | 18 + .../com/linkwork/model/entity/Approval.java | 78 + .../model/entity/BuildRecordEntity.java | 94 ++ .../com/linkwork/model/entity/CronJob.java | 41 + .../com/linkwork/model/entity/CronJobRun.java | 27 + .../linkwork/model/entity/FileNodeEntity.java | 42 + .../model/entity/GitLabAuthEntity.java | 46 + .../model/entity/McpServerEntity.java | 99 ++ .../model/entity/McpUsageDailyEntity.java | 33 + .../model/entity/McpUserConfigEntity.java | 35 + .../com/linkwork/model/entity/RobotFile.java | 52 + .../com/linkwork/model/entity/RoleEntity.java | 84 + .../linkwork/model/entity/SecurityPolicy.java | 46 + .../linkwork/model/entity/SkillEntity.java | 64 + .../java/com/linkwork/model/entity/Task.java | 96 ++ .../model/entity/TaskGitAuthEntity.java | 39 + .../model/entity/UserFavoriteRoleEntity.java | 25 + .../linkwork/model/entity/UserSoulEntity.java | 44 + .../linkwork/service/McpUsageAggregator.java | 115 ++ .../service/ModelRegistryService.java | 83 + .../service/TaskOutputEstimatorAgent.java | 797 ++++++++++ .../linkwork/service/WeeklyReportService.java | 1370 +++++++++++++++++ back/src/main/resources/application.yml | 352 +++++ .../V20260302_2__skill_visibility.sql | 41 + .../db/migration/V20260309__mcp_gateway.sql | 32 + ...311__fix_robot_mcp_usage_daily_columns.sql | 4 + back/src/main/resources/scripts/config.json | 30 + 27 files changed, 3787 insertions(+) create mode 100644 back/src/main/java/com/linkwork/mapper/GitLabAuthMapper.java create mode 100644 back/src/main/java/com/linkwork/model/entity/Approval.java create mode 100644 back/src/main/java/com/linkwork/model/entity/BuildRecordEntity.java create mode 100644 back/src/main/java/com/linkwork/model/entity/CronJob.java create mode 100644 back/src/main/java/com/linkwork/model/entity/CronJobRun.java create mode 100644 back/src/main/java/com/linkwork/model/entity/FileNodeEntity.java create mode 100644 back/src/main/java/com/linkwork/model/entity/GitLabAuthEntity.java create mode 100644 back/src/main/java/com/linkwork/model/entity/McpServerEntity.java create mode 100644 back/src/main/java/com/linkwork/model/entity/McpUsageDailyEntity.java create mode 100644 back/src/main/java/com/linkwork/model/entity/McpUserConfigEntity.java create mode 100644 back/src/main/java/com/linkwork/model/entity/RobotFile.java create mode 100644 back/src/main/java/com/linkwork/model/entity/RoleEntity.java create mode 100644 back/src/main/java/com/linkwork/model/entity/SecurityPolicy.java create mode 100644 back/src/main/java/com/linkwork/model/entity/SkillEntity.java create mode 100644 back/src/main/java/com/linkwork/model/entity/Task.java create mode 100644 back/src/main/java/com/linkwork/model/entity/TaskGitAuthEntity.java create mode 100644 back/src/main/java/com/linkwork/model/entity/UserFavoriteRoleEntity.java create mode 100644 back/src/main/java/com/linkwork/model/entity/UserSoulEntity.java create mode 100644 back/src/main/java/com/linkwork/service/McpUsageAggregator.java create mode 100644 back/src/main/java/com/linkwork/service/ModelRegistryService.java create mode 100644 back/src/main/java/com/linkwork/service/TaskOutputEstimatorAgent.java create mode 100644 back/src/main/java/com/linkwork/service/WeeklyReportService.java create mode 100644 back/src/main/resources/application.yml create mode 100644 back/src/main/resources/db/migration/V20260302_2__skill_visibility.sql create mode 100644 back/src/main/resources/db/migration/V20260309__mcp_gateway.sql create mode 100644 back/src/main/resources/db/migration/V20260311__fix_robot_mcp_usage_daily_columns.sql create mode 100644 back/src/main/resources/scripts/config.json diff --git a/back/src/main/java/com/linkwork/mapper/GitLabAuthMapper.java b/back/src/main/java/com/linkwork/mapper/GitLabAuthMapper.java new file mode 100644 index 0000000..566e9e9 --- /dev/null +++ b/back/src/main/java/com/linkwork/mapper/GitLabAuthMapper.java @@ -0,0 +1,18 @@ +package com.linkwork.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.linkwork.model.entity.GitLabAuthEntity; +import org.apache.ibatis.annotations.*; + +@Mapper +public interface GitLabAuthMapper extends BaseMapper { + + @Select("SELECT * FROM linkwork_user_auth_gitlab WHERE user_id = #{userId} AND gitlab_id = #{gitlabId} AND scope = #{scope} LIMIT 1") + GitLabAuthEntity selectIncludingDeleted(@Param("userId") String userId, @Param("gitlabId") Long gitlabId, @Param("scope") String scope); + + @Update("UPDATE linkwork_user_auth_gitlab SET user_id=#{userId}, gitlab_id=#{gitlabId}, username=#{username}, " + + "name=#{name}, avatar_url=#{avatarUrl}, access_token=#{accessToken}, refresh_token=#{refreshToken}, " + + "token_alias=#{tokenAlias}, expires_at=#{expiresAt}, scope=#{scope}, " + + "created_at=#{createdAt}, updated_at=#{updatedAt}, is_deleted=#{isDeleted} WHERE id=#{id}") + int updateIncludingDeleted(GitLabAuthEntity entity); +} diff --git a/back/src/main/java/com/linkwork/model/entity/Approval.java b/back/src/main/java/com/linkwork/model/entity/Approval.java new file mode 100644 index 0000000..3e104c8 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/entity/Approval.java @@ -0,0 +1,78 @@ +package com.linkwork.model.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 审批实体 + * 对应数据库表: linkwork_approval + */ +@Data +@TableName("linkwork_approval") +public class Approval { + + @TableId(type = IdType.AUTO) + private Long id; + + /** 审批编号 */ + private String approvalNo; + + /** 关联任务编号 */ + private String taskNo; + + /** momo-worker 审批请求 ID(用于 Redis List 回写响应) */ + private String requestId; + + /** 任务标题快照 */ + private String taskTitle; + + /** 待审批操作内容 */ + private String action; + + /** 风险描述 */ + private String description; + + /** 风险等级: low/medium/high/critical */ + private String riskLevel; + + /** 状态: pending/approved/rejected/expired */ + private String status; + + /** 决策: approved/rejected */ + private String decision; + + /** 审批意见 */ + private String comment; + + /** 审批操作人ID */ + private String operatorId; + + /** 审批操作人名称 */ + private String operatorName; + + /** 审批操作来源IP */ + private String operatorIp; + + /** 过期时间 */ + private LocalDateTime expiredAt; + + /** 决策时间 */ + private LocalDateTime decidedAt; + + /** 创建人ID */ + private String creatorId; + + /** 创建人名称 */ + private String creatorName; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createdAt; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updatedAt; + + @TableLogic + private Integer isDeleted; +} diff --git a/back/src/main/java/com/linkwork/model/entity/BuildRecordEntity.java b/back/src/main/java/com/linkwork/model/entity/BuildRecordEntity.java new file mode 100644 index 0000000..1ef4687 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/entity/BuildRecordEntity.java @@ -0,0 +1,94 @@ +package com.linkwork.model.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Map; + +/** + * 构建记录实体 + * 记录每次镜像构建的完整信息 + */ +@Data +@TableName(value = "linkwork_build_record", autoResultMap = true) +public class BuildRecordEntity { + + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 构建唯一编号,格式: build-{timestamp}-{random} + * 由前端生成并传入,用于关联 Redis Stream 事件 + */ + private String buildNo; + + /** + * 关联的岗位 ID + */ + private Long roleId; + + /** + * 岗位名称快照(构建时的岗位名称) + */ + private String roleName; + + /** + * 构建状态: PENDING, BUILDING, SUCCESS, FAILED, CANCELLED + */ + private String status; + + /** + * 构建产物镜像标签 + */ + private String imageTag; + + /** + * 构建耗时(毫秒) + */ + private Long durationMs; + + /** + * 失败原因 + */ + private String errorMessage; + + /** + * 构建日志文件 URL (OSS) + */ + private String logUrl; + + /** + * 构建时的配置快照(JSON 格式) + * 包含: baseImage, envVars, mcpModules, skills, knowledgeBases 等 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private Map configSnapshot; + + /** + * 创建者 ID + */ + private String creatorId; + + /** + * 创建者名称 + */ + private String creatorName; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createdAt; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updatedAt; + + @TableLogic + private Boolean isDeleted; + + // ========== 状态常量 ========== + public static final String STATUS_PENDING = "PENDING"; + public static final String STATUS_BUILDING = "BUILDING"; + public static final String STATUS_SUCCESS = "SUCCESS"; + public static final String STATUS_FAILED = "FAILED"; + public static final String STATUS_CANCELLED = "CANCELLED"; +} diff --git a/back/src/main/java/com/linkwork/model/entity/CronJob.java b/back/src/main/java/com/linkwork/model/entity/CronJob.java new file mode 100644 index 0000000..2302bc2 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/entity/CronJob.java @@ -0,0 +1,41 @@ +package com.linkwork.model.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@TableName("linkwork_cron_job") +public class CronJob { + @TableId(type = IdType.AUTO) + private Long id; + private String jobName; + private String creatorId; + private String creatorName; + private Long roleId; + private String roleName; + private String modelId; + private String fileIdsJson; + private String scheduleType; + private String cronExpr; + private Long intervalMs; + private LocalDateTime runAt; + private String timezone; + private String taskContent; + private Integer enabled; + private Integer deleteAfterRun; + private Integer maxRetry; + private Integer consecutiveFailures; + private LocalDateTime nextFireTime; + private String notifyMode; + private String notifyTarget; + private Integer totalRuns; + private LocalDateTime lastRunTime; + private String lastRunStatus; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + private Integer isDeleted; +} diff --git a/back/src/main/java/com/linkwork/model/entity/CronJobRun.java b/back/src/main/java/com/linkwork/model/entity/CronJobRun.java new file mode 100644 index 0000000..e5150f1 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/entity/CronJobRun.java @@ -0,0 +1,27 @@ +package com.linkwork.model.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@TableName("linkwork_cron_job_run") +public class CronJobRun { + @TableId(type = IdType.AUTO) + private Long id; + private Long cronJobId; + private String taskNo; + private String creatorId; + private Long roleId; + private String status; + private String triggerType; + private LocalDateTime plannedFireTime; + private LocalDateTime startedAt; + private LocalDateTime finishedAt; + private Long durationMs; + private String errorMessage; + private LocalDateTime createdAt; +} diff --git a/back/src/main/java/com/linkwork/model/entity/FileNodeEntity.java b/back/src/main/java/com/linkwork/model/entity/FileNodeEntity.java new file mode 100644 index 0000000..936c5e6 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/entity/FileNodeEntity.java @@ -0,0 +1,42 @@ +package com.linkwork.model.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@TableName("linkwork_file_node") +public class FileNodeEntity { + + @TableId(type = IdType.AUTO) + private Long id; + + private String nodeId; + + private String parentId; + + private String entryType; + + private String name; + + private String spaceType; + + private String workstationId; + + private String userId; + + private String fileId; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createdAt; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime updatedAt; + + private LocalDateTime deletedAt; +} diff --git a/back/src/main/java/com/linkwork/model/entity/GitLabAuthEntity.java b/back/src/main/java/com/linkwork/model/entity/GitLabAuthEntity.java new file mode 100644 index 0000000..be34cf1 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/entity/GitLabAuthEntity.java @@ -0,0 +1,46 @@ +package com.linkwork.model.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@TableName("linkwork_user_auth_gitlab") +public class GitLabAuthEntity { + + @TableId(type = IdType.ASSIGN_ID) + @JsonSerialize(using = ToStringSerializer.class) + private Long id; + + private String userId; + + private Long gitlabId; + + private String username; + + private String name; + + private String avatarUrl; + + private String accessToken; + + private String refreshToken; + + private String tokenAlias; + + private LocalDateTime expiresAt; + + private String scope; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createdAt; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updatedAt; + + @TableLogic + private Boolean isDeleted; +} diff --git a/back/src/main/java/com/linkwork/model/entity/McpServerEntity.java b/back/src/main/java/com/linkwork/model/entity/McpServerEntity.java new file mode 100644 index 0000000..db4902b --- /dev/null +++ b/back/src/main/java/com/linkwork/model/entity/McpServerEntity.java @@ -0,0 +1,99 @@ +package com.linkwork.model.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +/** + * MCP 服务实体类 + */ +@Data +@TableName(value = "linkwork_mcp_server", autoResultMap = true) +public class McpServerEntity { + + @TableId(type = IdType.AUTO) + private Long id; + + /** MCP编号,格式: MCP-{timestamp} */ + private String mcpNo; + + /** 服务名称 */ + private String name; + + /** 服务端点地址 */ + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String endpoint; + + /** 服务描述 */ + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String description; + + /** 可见性: public/private */ + private String visibility; + + /** 状态: online/offline/degraded/unknown */ + private String status; + + /** MCP类型: http/sse */ + private String type; + + /** 服务URL (http/sse) */ + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String url; + + /** HTTP请求头 JSON */ + @TableField(typeHandler = JacksonTypeHandler.class, updateStrategy = FieldStrategy.ALWAYS) + private Map headers; + + /** 网段标记: internal(服务器内网), office(办公网), external(外部互联网) */ + private String networkZone; + + /** 健康检查URL */ + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String healthCheckUrl; + + /** 最近探活延迟(ms) */ + private Integer healthLatencyMs; + + /** 最近探活消息 */ + private String healthMessage; + + /** 连续失败次数 */ + private Integer consecutiveFailures; + + /** 版本号 */ + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String version; + + /** 标签 */ + @TableField(typeHandler = JacksonTypeHandler.class, updateStrategy = FieldStrategy.ALWAYS) + private List tags; + + /** 最后健康检查时间 */ + private LocalDateTime lastHealthAt; + + /** 服务配置 JSON */ + @TableField(typeHandler = JacksonTypeHandler.class, updateStrategy = FieldStrategy.ALWAYS) + private Map configJson; + + private String creatorId; + + private String creatorName; + + private String updaterId; + + private String updaterName; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createdAt; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updatedAt; + + @TableLogic + private Boolean isDeleted; +} diff --git a/back/src/main/java/com/linkwork/model/entity/McpUsageDailyEntity.java b/back/src/main/java/com/linkwork/model/entity/McpUsageDailyEntity.java new file mode 100644 index 0000000..f38e4f0 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/entity/McpUsageDailyEntity.java @@ -0,0 +1,33 @@ +package com.linkwork.model.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Data +@TableName("linkwork_mcp_usage_daily") +public class McpUsageDailyEntity { + + @TableId(type = IdType.AUTO) + private Long id; + + private LocalDate date; + + private String userId; + + private String mcpName; + + private Integer callCount; + + private Long reqBytes; + + private Long respBytes; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createdAt; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updatedAt; +} diff --git a/back/src/main/java/com/linkwork/model/entity/McpUserConfigEntity.java b/back/src/main/java/com/linkwork/model/entity/McpUserConfigEntity.java new file mode 100644 index 0000000..f8cced9 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/entity/McpUserConfigEntity.java @@ -0,0 +1,35 @@ +package com.linkwork.model.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Map; + +@Data +@TableName(value = "linkwork_mcp_user_config", autoResultMap = true) +public class McpUserConfigEntity { + + @TableId(type = IdType.AUTO) + private Long id; + + private String userId; + + private Long mcpServerId; + + @TableField(typeHandler = JacksonTypeHandler.class) + private Map headers; + + @TableField(typeHandler = JacksonTypeHandler.class) + private Map urlParams; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createdAt; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updatedAt; + + @TableLogic + private Boolean isDeleted; +} diff --git a/back/src/main/java/com/linkwork/model/entity/RobotFile.java b/back/src/main/java/com/linkwork/model/entity/RobotFile.java new file mode 100644 index 0000000..b098181 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/entity/RobotFile.java @@ -0,0 +1,52 @@ +package com.linkwork.model.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@TableName("linkwork_file") +public class RobotFile { + + @TableId(type = IdType.AUTO) + private Long id; + + private String fileId; + + private String fileName; + + private Long fileSize; + + private String fileType; + + private String contentType; + + private String spaceType; + + private String workstationId; + + private String userId; + + private String ossPath; + + private String parsedOssPath; + + private String parseStatus; + + private String memoryIndexStatus; + + private String fileHash; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createdAt; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime updatedAt; + + private LocalDateTime deletedAt; +} diff --git a/back/src/main/java/com/linkwork/model/entity/RoleEntity.java b/back/src/main/java/com/linkwork/model/entity/RoleEntity.java new file mode 100644 index 0000000..42e06ea --- /dev/null +++ b/back/src/main/java/com/linkwork/model/entity/RoleEntity.java @@ -0,0 +1,84 @@ +package com.linkwork.model.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +@Data +@TableName(value = "linkwork_workstation", autoResultMap = true) +public class RoleEntity { + + @TableId(type = IdType.AUTO) + private Long id; + + private String roleNo; + + private String name; + + private String description; + + private String category; // devops, security, developer, research + + private String icon; + + private String image; + + private String prompt; + + private String status; // active, maintenance, disabled + + @TableField(typeHandler = JacksonTypeHandler.class) + private RoleConfig configJson; + + private Boolean isPublic; + + private Integer maxEmployees; + + private String creatorId; + + private String creatorName; + + private String updaterId; + + private String updaterName; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createdAt; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updatedAt; + + @TableLogic + private Boolean isDeleted; + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class RoleConfig { + private List mcp; // List of IDs + private List skills; // List of IDs/Names + private List knowledge; // List of IDs/Names + private String deployMode; // K8S / COMPOSE + private String runtimeMode; // SIDECAR / ALONE + private String runnerImage; // 仅 SIDECAR 生效 + private Boolean memoryEnabled; // 岗位级记忆开关 + private List gitRepos; + private List env; + + @Data + public static class GitRepo { + private String url; + private String branch; + } + + @Data + public static class EnvVar { + private String key; + private String value; + } + } +} diff --git a/back/src/main/java/com/linkwork/model/entity/SecurityPolicy.java b/back/src/main/java/com/linkwork/model/entity/SecurityPolicy.java new file mode 100644 index 0000000..868b1fb --- /dev/null +++ b/back/src/main/java/com/linkwork/model/entity/SecurityPolicy.java @@ -0,0 +1,46 @@ +package com.linkwork.model.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 安全策略实体 + * 对应数据库表: linkwork_security_policy + */ +@Data +@TableName("linkwork_security_policy") +public class SecurityPolicy { + + @TableId(type = IdType.AUTO) + private Long id; + + /** 策略名称 */ + private String name; + + /** 策略描述 */ + private String description; + + /** 类型: system/custom */ + private String type; + + /** 是否启用 */ + private Boolean enabled; + + /** 规则列表 JSON */ + private String rulesJson; + + private String creatorId; + + private String creatorName; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createdAt; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updatedAt; + + @TableLogic + private Integer isDeleted; +} diff --git a/back/src/main/java/com/linkwork/model/entity/SkillEntity.java b/back/src/main/java/com/linkwork/model/entity/SkillEntity.java new file mode 100644 index 0000000..0a4a264 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/entity/SkillEntity.java @@ -0,0 +1,64 @@ +package com.linkwork.model.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 技能实体类 + */ +@Data +@TableName(value = "linkwork_skill", autoResultMap = true) +public class SkillEntity { + + @TableId(type = IdType.AUTO) + private Long id; + + /** 技能编号,格式: SKL-{timestamp} */ + private String skillNo; + + /** 技能标识名(唯一) */ + private String name; + + /** 显示名称 */ + private String displayName; + + /** 技能描述 */ + private String description; + + /** 技能实现代码 */ + private String implementation; + + /** 状态: draft/ready/disabled */ + private String status; + + /** 是否公开 */ + private Boolean isPublic; + + /** Git 分支名称 */ + private String branchName; + + /** 最新 commit SHA */ + private String latestCommit; + + /** 最后同步时间 */ + private LocalDateTime lastSyncedAt; + + private String creatorId; + + private String creatorName; + + private String updaterId; + + private String updaterName; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createdAt; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updatedAt; + + @TableLogic + private Boolean isDeleted; +} diff --git a/back/src/main/java/com/linkwork/model/entity/Task.java b/back/src/main/java/com/linkwork/model/entity/Task.java new file mode 100644 index 0000000..dea4e62 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/entity/Task.java @@ -0,0 +1,96 @@ +package com.linkwork.model.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.linkwork.model.enums.TaskStatus; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 任务实体 + * 对应数据库表: linkwork_task + */ +@Data +@TableName("linkwork_task") +public class Task { + + /** + * 主键ID(自增) + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 任务编号,格式: MSN-{yyyyMMddHHmmssSSS} + * 对外展示使用此字段 + */ + private String taskNo; + + /** + * 关联岗位ID + */ + private Long roleId; + + /** + * 岗位名称快照 + */ + private String roleName; + + private String prompt; + + private TaskStatus status; + + private String image; + + private String selectedModel; + + private Long assemblyId; + + private String configJson; + + /** + * 任务来源:MANUAL / CRON + */ + private String source; + + /** + * 定时任务来源时关联 linkwork_cron_job.id + */ + private Long cronJobId; + + private String creatorId; + + private String creatorName; + + private String creatorIp; + + private String updaterId; + + private String updaterName; + + private Integer tokensUsed; + + private Integer inputTokens; + + private Integer outputTokens; + + private Integer requestCount; + + private Long tokenLimit; + + private BigDecimal usagePercent; + + private Long durationMs; + + private String reportJson; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createdAt; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updatedAt; + + @TableLogic + private Integer isDeleted; +} diff --git a/back/src/main/java/com/linkwork/model/entity/TaskGitAuthEntity.java b/back/src/main/java/com/linkwork/model/entity/TaskGitAuthEntity.java new file mode 100644 index 0000000..12d3501 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/entity/TaskGitAuthEntity.java @@ -0,0 +1,39 @@ +package com.linkwork.model.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 任务与 Git 认证映射实体 + */ +@Data +@TableName("linkwork_task_git_auth") +public class TaskGitAuthEntity { + + @TableId(value = "task_id", type = IdType.INPUT) + private String taskId; + + private String userId; + + private String provider; + + private Long gitlabAuthId; + + private LocalDateTime expiresAt; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createdAt; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updatedAt; + + @TableLogic + private Boolean isDeleted; +} diff --git a/back/src/main/java/com/linkwork/model/entity/UserFavoriteRoleEntity.java b/back/src/main/java/com/linkwork/model/entity/UserFavoriteRoleEntity.java new file mode 100644 index 0000000..6e9cf09 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/entity/UserFavoriteRoleEntity.java @@ -0,0 +1,25 @@ +package com.linkwork.model.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@TableName("linkwork_user_favorite_workstation") +public class UserFavoriteRoleEntity { + + @TableId(type = IdType.AUTO) + private Long id; + + private String userId; + + private Long roleId; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createdAt; +} diff --git a/back/src/main/java/com/linkwork/model/entity/UserSoulEntity.java b/back/src/main/java/com/linkwork/model/entity/UserSoulEntity.java new file mode 100644 index 0000000..47a76d7 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/entity/UserSoulEntity.java @@ -0,0 +1,44 @@ +package com.linkwork.model.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@TableName("linkwork_user_soul") +public class UserSoulEntity { + + @TableId(type = IdType.AUTO) + private Long id; + + private String userId; + + private String content; + + private String presetId; + + private Long version; + + private String creatorId; + + private String creatorName; + + private String updaterId; + + private String updaterName; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createdAt; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updatedAt; + + @TableLogic + private Integer isDeleted; +} diff --git a/back/src/main/java/com/linkwork/service/McpUsageAggregator.java b/back/src/main/java/com/linkwork/service/McpUsageAggregator.java new file mode 100644 index 0000000..60775a8 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/McpUsageAggregator.java @@ -0,0 +1,115 @@ +package com.linkwork.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.linkwork.mapper.McpUsageDailyMapper; +import com.linkwork.model.entity.McpUsageDailyEntity; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Map; + +/** + * 每小时将 Redis 中的 MCP 使用量数据聚合到 linkwork_mcp_usage_daily 表。 + *

+ * Redis key 格式: + * - mcp:usage:user:{YYYYMMDD} field = "{userId}:{mcpName}" value = count + * - mcp:usage:bytes:{YYYYMMDD} field = "{taskId}:{mcpName}:req" value = bytes + * (bytes 按 user 维度聚合困难, 这里仅聚合 user 维度的 count) + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class McpUsageAggregator { + + private final StringRedisTemplate redisTemplate; + private final McpUsageDailyMapper usageDailyMapper; + + private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyyMMdd"); + + @Scheduled(cron = "0 5 * * * *") + public void aggregateCurrentDay() { + String dateStr = LocalDate.now().format(DATE_FMT); + aggregateDate(dateStr); + } + + @Scheduled(cron = "0 10 0 * * *") + public void aggregateYesterday() { + String dateStr = LocalDate.now().minusDays(1).format(DATE_FMT); + aggregateDate(dateStr); + } + + void aggregateDate(String dateStr) { + String userKey = "mcp:usage:user:" + dateStr; + Map entries; + + try { + entries = redisTemplate.opsForHash().entries(userKey); + } catch (Exception e) { + log.warn("Failed to read Redis usage data for {}: {}", dateStr, e.getMessage()); + return; + } + + if (entries == null || entries.isEmpty()) { + return; + } + + LocalDate date = LocalDate.parse(dateStr, DATE_FMT); + int upserted = 0; + + for (Map.Entry entry : entries.entrySet()) { + String field = entry.getKey().toString(); + String value = entry.getValue().toString(); + + String[] parts = field.split(":", 2); + if (parts.length < 2) continue; + String userId = parts[0]; + String mcpName = parts[1]; + if (!StringUtils.hasText(userId) || !StringUtils.hasText(mcpName)) continue; + + int count; + try { + count = Integer.parseInt(value); + } catch (NumberFormatException e) { + continue; + } + + try { + upsertUsageDaily(date, userId, mcpName, count); + upserted++; + } catch (Exception e) { + log.warn("Failed to upsert usage for date={}, user={}, mcp={}: {}", + dateStr, userId, mcpName, e.getMessage()); + } + } + + log.info("MCP usage aggregation completed: date={}, records={}", dateStr, upserted); + } + + private void upsertUsageDaily(LocalDate date, String userId, String mcpName, int callCount) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(McpUsageDailyEntity::getDate, date) + .eq(McpUsageDailyEntity::getUserId, userId) + .eq(McpUsageDailyEntity::getMcpName, mcpName); + + McpUsageDailyEntity existing = usageDailyMapper.selectOne(wrapper); + if (existing != null) { + existing.setCallCount(callCount); + usageDailyMapper.updateById(existing); + } else { + McpUsageDailyEntity entity = new McpUsageDailyEntity(); + entity.setDate(date); + entity.setUserId(userId); + entity.setMcpName(mcpName); + entity.setCallCount(callCount); + entity.setReqBytes(0L); + entity.setRespBytes(0L); + usageDailyMapper.insert(entity); + } + } +} diff --git a/back/src/main/java/com/linkwork/service/ModelRegistryService.java b/back/src/main/java/com/linkwork/service/ModelRegistryService.java new file mode 100644 index 0000000..8d5a74a --- /dev/null +++ b/back/src/main/java/com/linkwork/service/ModelRegistryService.java @@ -0,0 +1,83 @@ +package com.linkwork.service; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestTemplate; + +import java.time.Duration; +import java.util.Map; + +/** + * 模型注册表读取服务(后端代理模型网关,避免前端跨域直连) + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class ModelRegistryService { + + @Value("${robot.model-registry.gateway-url:http://172.18.228.32:4000/v1/models}") + private String gatewayUrl; + + @Value("${robot.model-registry.timeout-ms:5000}") + private long timeoutMs; + + @Value("${robot.model-registry.auth-token:}") + private String authToken; + + @Value("${robot.model-registry.x-litellm-api-key:}") + private String xLitellmApiKey; + + private final RestTemplateBuilder restTemplateBuilder; + private final ObjectMapper objectMapper; + + public Map fetchModels() { + RestTemplate restTemplate = restTemplateBuilder + .setConnectTimeout(Duration.ofMillis(timeoutMs)) + .setReadTimeout(Duration.ofMillis(timeoutMs)) + .build(); + + try { + HttpHeaders headers = new HttpHeaders(); + String resolvedAuthToken = StringUtils.hasText(authToken) ? authToken.trim() : ""; + if (StringUtils.hasText(resolvedAuthToken)) { + headers.setBearerAuth(resolvedAuthToken); + } + String resolvedXKey = StringUtils.hasText(xLitellmApiKey) ? xLitellmApiKey.trim() : resolvedAuthToken; + if (StringUtils.hasText(resolvedXKey)) { + headers.set("x-litellm-api-key", resolvedXKey); + } + HttpEntity requestEntity = new HttpEntity<>(headers); + + ResponseEntity response = restTemplate.exchange( + gatewayUrl, + HttpMethod.GET, + requestEntity, + String.class + ); + if (!response.getStatusCode().is2xxSuccessful()) { + throw new IllegalStateException("模型网关返回非 2xx: " + response.getStatusCode().value()); + } + + String body = response.getBody(); + if (!StringUtils.hasText(body)) { + throw new IllegalStateException("模型网关返回空响应"); + } + + return objectMapper.readValue(body, new TypeReference>() { + }); + } catch (Exception e) { + log.error("读取模型列表失败: gatewayUrl={}", gatewayUrl, e); + throw new IllegalStateException("模型列表加载失败: " + e.getMessage(), e); + } + } +} diff --git a/back/src/main/java/com/linkwork/service/TaskOutputEstimatorAgent.java b/back/src/main/java/com/linkwork/service/TaskOutputEstimatorAgent.java new file mode 100644 index 0000000..fcf3e8b --- /dev/null +++ b/back/src/main/java/com/linkwork/service/TaskOutputEstimatorAgent.java @@ -0,0 +1,797 @@ +package com.linkwork.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.linkwork.model.dto.TaskCreateRequest; +import com.linkwork.model.entity.RoleEntity; +import com.linkwork.model.enums.TaskOutputType; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 任务产出预估 Agent + * + * 在任务执行前基于用户诉求、岗位 system prompt 与任务属性进行预估: + * 1) 优先调用 LLM Gateway 输出结构化结果; + * 2) LLM 不可用时,降级到规则引擎,避免阻断任务创建。 + */ +@Slf4j +@Service +public class TaskOutputEstimatorAgent { + + private static final String DIALOG_CONCLUSION_CODE = TaskOutputType.DIALOG_CONCLUSION.getCode(); + private static final String GIT_BRANCH_CODE = TaskOutputType.GIT_BRANCH.getCode(); + private static final String DONE_TOKEN = "[DONE]"; + private static final int MAX_BRANCH_LENGTH = 64; + private static final int MAX_PROMPT_SNIPPET = 1200; + + private static final Set DIALOG_ONLY_KEYWORDS = Set.of( + "只要结论", "仅回复", "仅对话", "不用文件", "无须文件", "不要文件", "只给建议", + "only conclusion", "only answer", "dialog only", "no file output" + ); + + private static final Set API_RESULT_KEYWORDS = Set.of( + "api调用", "api call", "webhook", "callback", "回调", "curl", "http request", "openapi", "postman" + ); + + private static final Set GIT_KEYWORDS = Set.of( + "git", "branch", "commit", "push", "分支", "提交" + ); + + private static final Set PULL_REQUEST_KEYWORDS = Set.of( + "pull request", "merge request", "创建pr", "提pr", "提交pr", "pr链接", "mr链接" + ); + + private static final Set EXCEL_KEYWORDS = Set.of( + "excel", "xlsx", "xls", "sheet", "数据透视", "表格" + ); + + private static final Set CSV_KEYWORDS = Set.of( + "csv", "逗号分隔" + ); + + private static final Set WORD_KEYWORDS = Set.of( + "word", "doc", "docx", "技术文档", "文档" + ); + + private static final Set PPT_KEYWORDS = Set.of( + "ppt", "pptx", "slide", "slides", "演示文稿" + ); + + private static final Set PDF_KEYWORDS = Set.of( + "pdf" + ); + + private static final Set MARKDOWN_KEYWORDS = Set.of( + "markdown", "readme", "md文档", ".md" + ); + + private static final Set TXT_KEYWORDS = Set.of( + "txt", "纯文本", "text file", "日志文件" + ); + + private static final Set PYTHON_KEYWORDS = Set.of( + "python", "pandas", "jupyter", "notebook", ".py" + ); + + private static final Set JAVA_KEYWORDS = Set.of( + "java", "spring", "springboot", "maven", "gradle", "jvm" + ); + + private static final Set JAVASCRIPT_KEYWORDS = Set.of( + "javascript", "nodejs", "node.js", "frontend js", ".js" + ); + + private static final Set TYPESCRIPT_KEYWORDS = Set.of( + "typescript", "tsx", ".ts" + ); + + private static final Set SQL_KEYWORDS = Set.of( + "sql", "ddl", "dml", "select ", "insert ", "update ", "delete " + ); + + private static final Set SHELL_KEYWORDS = Set.of( + "shell", "bash", "zsh", "sh脚本", "命令脚本" + ); + + private static final Set CONFIG_KEYWORDS = Set.of( + "配置文件", "yaml", "yml", "toml", "ini", "properties", ".env" + ); + + private static final Set JSON_KEYWORDS = Set.of( + "json", "jsonl" + ); + + private static final Set PNG_KEYWORDS = Set.of( + "png", "image", "chart", "plot", "graph", "可视化", "截图", "图表" + ); + + private static final Set ARCHIVE_KEYWORDS = Set.of( + "zip", "tar", "tar.gz", "压缩包", "归档" + ); + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Value("${robot.output-estimator.llm.enabled:true}") + private boolean llmEnabled; + + @Value("${robot.output-estimator.llm.gateway-url:}") + private String gatewayUrl; + + @Value("${robot.output-estimator.llm.model:minimax-m2.1}") + private String llmModel; + + @Value("${robot.output-estimator.llm.max-tokens:256}") + private int llmMaxTokens; + + @Value("${robot.output-estimator.llm.stream:true}") + private boolean llmStream; + + @Value("${robot.output-estimator.llm.connect-timeout-ms:3000}") + private int connectTimeoutMs; + + @Value("${robot.output-estimator.llm.read-timeout-ms:12000}") + private int readTimeoutMs; + + @Value("${robot.output-estimator.llm.auth-token:}") + private String authToken; + + @Value("${robot.output-estimator.llm.x-litellm-api-key:}") + private String xLitellmApiKey; + + public record EstimateResult(List estimatedOutput, String branchName, String source) { + public EstimateResult { + estimatedOutput = estimatedOutput == null ? List.of() : List.copyOf(estimatedOutput); + source = StringUtils.hasText(source) ? source : "rule"; + } + } + + public List estimate(TaskCreateRequest request, RoleEntity role) { + return estimateWithBranch(null, request, role).estimatedOutput(); + } + + public EstimateResult estimateWithBranch(String taskNo, TaskCreateRequest request, RoleEntity role) { + String promptRaw = request != null ? request.getPrompt() : null; + String prompt = normalize(promptRaw); + String systemPrompt = normalize(role != null ? role.getPrompt() : null); + String mergedText = prompt + "\n" + systemPrompt; + + if (containsAny(mergedText, DIALOG_ONLY_KEYWORDS)) { + return new EstimateResult(List.of(DIALOG_CONCLUSION_CODE), null, "rule"); + } + + LinkedHashSet ruleEstimated = new LinkedHashSet<>(); + addByTextRules(mergedText, ruleEstimated); + addByTaskAttributes(request, ruleEstimated); + + if (ruleEstimated.isEmpty()) { + ruleEstimated.add(TaskOutputType.DIALOG_CONCLUSION); + } + + EstimateResult llmEstimate = estimateByLlm(taskNo, request, role, promptRaw); + if (llmEstimate != null && !llmEstimate.estimatedOutput().isEmpty()) { + LinkedHashSet mergedOutputs = new LinkedHashSet<>(llmEstimate.estimatedOutput()); + LinkedHashSet attributeOutputs = new LinkedHashSet<>(); + addByTaskAttributes(request, attributeOutputs); + for (TaskOutputType type : attributeOutputs) { + mergedOutputs.add(type.getCode()); + } + normalizeDialogOutput(mergedOutputs); + + String branchName = llmEstimate.branchName(); + if (mergedOutputs.contains(GIT_BRANCH_CODE) && !StringUtils.hasText(branchName)) { + branchName = buildFallbackBranchName(taskNo, promptRaw); + } + if (!mergedOutputs.contains(GIT_BRANCH_CODE)) { + branchName = null; + } + return new EstimateResult(new ArrayList<>(mergedOutputs), branchName, "llm"); + } + + LinkedHashSet fallbackOutputs = new LinkedHashSet<>(toCodes(ruleEstimated)); + normalizeDialogOutput(fallbackOutputs); + String fallbackBranchName = fallbackOutputs.contains(GIT_BRANCH_CODE) + ? buildFallbackBranchName(taskNo, promptRaw) + : null; + return new EstimateResult(new ArrayList<>(fallbackOutputs), fallbackBranchName, "rule"); + } + + private EstimateResult estimateByLlm(String taskNo, TaskCreateRequest request, RoleEntity role, String promptRaw) { + if (!llmEnabled || !StringUtils.hasText(gatewayUrl)) { + return null; + } + + String token = resolveGatewayToken(); + if (!StringUtils.hasText(token)) { + return null; + } + + try { + String systemPrompt = buildEstimatorSystemPrompt(); + String userPrompt = buildEstimatorUserPrompt(taskNo, request, role); + String completion = callLlmGateway(systemPrompt, userPrompt, token, resolveEstimatorModel(request)); + if (!StringUtils.hasText(completion)) { + return null; + } + return parseLlmEstimate(completion, taskNo, promptRaw); + } catch (Exception e) { + log.warn("LLM 产物预估失败,降级规则模式: taskNo={}, error={}", taskNo, e.getMessage()); + return null; + } + } + + private String callLlmGateway(String systemPrompt, String userPrompt, String token, String modelId) throws IOException, InterruptedException { + JsonNode payload = buildGatewayPayload(systemPrompt, userPrompt, modelId); + String requestBody = objectMapper.writeValueAsString(payload); + + HttpClient client = HttpClient.newBuilder() + .connectTimeout(Duration.ofMillis(Math.max(connectTimeoutMs, 1000))) + .build(); + + HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(URI.create(gatewayUrl)) + .timeout(Duration.ofMillis(Math.max(readTimeoutMs, 2000))) + .header("Authorization", "Bearer " + token) + .header("Content-Type", "application/json"); + + if (StringUtils.hasText(xLitellmApiKey)) { + requestBuilder.header("x-litellm-api-key", xLitellmApiKey.trim()); + } + + HttpRequest request = requestBuilder + .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8)) + .build(); + + if (llmStream) { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()); + if (response.statusCode() >= 400) { + String errorBody = readErrorBody(response.body()); + throw new IllegalStateException("LLM Gateway 请求失败: status=" + response.statusCode() + ", body=" + errorBody); + } + return readStreamCompletion(response.body()); + } + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); + if (response.statusCode() >= 400) { + throw new IllegalStateException("LLM Gateway 请求失败: status=" + response.statusCode() + ", body=" + response.body()); + } + + String text = extractTextFromGatewayPayload(response.body()); + return StringUtils.hasText(text) ? text : response.body(); + } + + private JsonNode buildGatewayPayload(String systemPrompt, String userPrompt, String modelId) { + var root = objectMapper.createObjectNode(); + root.put("model", modelId); + root.put("max_tokens", llmMaxTokens); + root.put("stream", llmStream); + + var messages = root.putArray("messages"); + var system = messages.addObject(); + system.put("role", "system"); + system.put("content", systemPrompt); + + var user = messages.addObject(); + user.put("role", "user"); + user.put("content", userPrompt); + return root; + } + + private String readStreamCompletion(InputStream bodyStream) throws IOException { + StringBuilder completion = new StringBuilder(); + StringBuilder raw = new StringBuilder(); + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(bodyStream, StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + raw.append(line).append('\n'); + String trimmed = line.trim(); + if (trimmed.isEmpty() || trimmed.startsWith(":")) { + continue; + } + if (trimmed.startsWith("data:")) { + trimmed = trimmed.substring(5).trim(); + } + if (!StringUtils.hasText(trimmed) || DONE_TOKEN.equals(trimmed)) { + if (DONE_TOKEN.equals(trimmed)) { + break; + } + continue; + } + + String delta = extractTextFromGatewayPayload(trimmed); + if (StringUtils.hasText(delta)) { + completion.append(delta); + } + } + } + + if (completion.length() > 0) { + return completion.toString(); + } + return raw.toString(); + } + + private String extractTextFromGatewayPayload(String payload) { + if (!StringUtils.hasText(payload)) { + return ""; + } + + String trimmed = payload.trim(); + if (!trimmed.startsWith("{")) { + return trimmed; + } + + try { + JsonNode root = objectMapper.readTree(trimmed); + StringBuilder text = new StringBuilder(); + + appendTextNode(root.path("output_text"), text); + appendTextNode(root.path("content"), text); + + JsonNode choices = root.path("choices"); + if (choices.isArray()) { + for (JsonNode choice : choices) { + appendTextNode(choice.path("delta").path("content"), text); + appendTextNode(choice.path("message").path("content"), text); + appendTextNode(choice.path("text"), text); + } + } + + JsonNode data = root.path("data"); + if (data.isTextual()) { + appendTextNode(data, text); + } else if (data.isObject()) { + appendTextNode(data.path("content"), text); + } + + if (text.length() > 0) { + return text.toString(); + } + if (root.isTextual()) { + return root.asText(); + } + return ""; + } catch (Exception ignore) { + return trimmed; + } + } + + private void appendTextNode(JsonNode node, StringBuilder builder) { + if (node == null || node.isMissingNode() || node.isNull()) { + return; + } + + if (node.isTextual()) { + builder.append(node.asText()); + return; + } + + if (node.isArray()) { + for (JsonNode child : node) { + appendTextNode(child, builder); + } + return; + } + + if (node.isObject()) { + appendTextNode(node.path("text"), builder); + appendTextNode(node.path("content"), builder); + appendTextNode(node.path("value"), builder); + } + } + + private EstimateResult parseLlmEstimate(String completion, String taskNo, String promptRaw) { + String jsonText = extractJsonObject(completion); + if (!StringUtils.hasText(jsonText)) { + log.warn("LLM 产物预估解析失败(未找到 JSON): taskNo={}, completion={}...", taskNo, truncate(completion, 240)); + return null; + } + + try { + JsonNode root = objectMapper.readTree(jsonText); + LinkedHashSet outputs = new LinkedHashSet<>(); + collectOutputCodes(root.path("estimatedOutput"), outputs); + collectOutputCodes(root.path("estimated_output"), outputs); + collectOutputCodes(root.path("outputs"), outputs); + + if (outputs.isEmpty()) { + addAllowedCode(root.path("outputType").asText(null), outputs); + addAllowedCode(root.path("output_type").asText(null), outputs); + } + + String branchName = firstNonBlank( + root.path("branchName").asText(null), + root.path("branch_name").asText(null), + root.path("estimatedBranchName").asText(null) + ); + branchName = sanitizeBranchName(branchName); + + if (StringUtils.hasText(branchName)) { + outputs.add(GIT_BRANCH_CODE); + } + + if (outputs.isEmpty()) { + return null; + } + normalizeDialogOutput(outputs); + + if (outputs.contains(GIT_BRANCH_CODE) && !StringUtils.hasText(branchName)) { + branchName = buildFallbackBranchName(taskNo, promptRaw); + } + if (!outputs.contains(GIT_BRANCH_CODE)) { + branchName = null; + } + + return new EstimateResult(new ArrayList<>(outputs), branchName, "llm"); + } catch (JsonProcessingException e) { + log.warn("LLM 产物预估解析失败(JSON 非法): taskNo={}, error={}", taskNo, e.getMessage()); + return null; + } + } + + private void collectOutputCodes(JsonNode node, LinkedHashSet outputs) { + if (node == null || node.isMissingNode() || node.isNull()) { + return; + } + + if (node.isArray()) { + for (JsonNode item : node) { + if (item.isTextual()) { + addAllowedCode(item.asText(), outputs); + continue; + } + addAllowedCode(item.path("code").asText(null), outputs); + addAllowedCode(item.path("type").asText(null), outputs); + } + return; + } + + if (node.isTextual()) { + String text = node.asText(); + for (String piece : text.split("[,,\\s]+")) { + addAllowedCode(piece, outputs); + } + return; + } + + if (node.isObject()) { + addAllowedCode(node.path("code").asText(null), outputs); + addAllowedCode(node.path("type").asText(null), outputs); + } + } + + private void addAllowedCode(String code, LinkedHashSet outputs) { + if (!StringUtils.hasText(code)) { + return; + } + TaskOutputType.fromCode(code.trim()) + .map(TaskOutputType::getCode) + .ifPresent(outputs::add); + } + + private void normalizeDialogOutput(LinkedHashSet outputs) { + if (outputs.size() > 1) { + outputs.remove(DIALOG_CONCLUSION_CODE); + } + if (outputs.isEmpty()) { + outputs.add(DIALOG_CONCLUSION_CODE); + } + } + + private String extractJsonObject(String text) { + if (!StringUtils.hasText(text)) { + return null; + } + + String trimmed = text.trim(); + if (trimmed.startsWith("```") && trimmed.endsWith("```")) { + int firstBrace = trimmed.indexOf('{'); + int lastBrace = trimmed.lastIndexOf('}'); + if (firstBrace >= 0 && lastBrace > firstBrace) { + return trimmed.substring(firstBrace, lastBrace + 1); + } + } + + if (trimmed.startsWith("{") && trimmed.endsWith("}")) { + return trimmed; + } + + int start = trimmed.indexOf('{'); + int end = trimmed.lastIndexOf('}'); + if (start >= 0 && end > start) { + return trimmed.substring(start, end + 1); + } + return null; + } + + private String buildEstimatorSystemPrompt() { + String allowedOutputs = Arrays.stream(TaskOutputType.values()) + .map(type -> "- " + type.getCode() + ": " + type.getDescription()) + .collect(Collectors.joining("\\n")); + + return """ + 你是任务产物预估助手,只负责判断任务执行后可能产出的结果类型。 + 必须遵守以下规则: + 1) 只返回 JSON,不要 markdown,不要解释文字。 + 2) JSON 字段固定为: estimatedOutput(array), branchName(string|null), reason(string)。 + 3) estimatedOutput 只能使用下面白名单枚举,允许多个并存。 + 4) 若只会给对话结论,则 estimatedOutput 仅保留 dialog_conclusion。 + 5) 只有当预计有代码提交时才返回 branchName;否则 branchName 必须为 null。 + 6) branchName 使用小写 kebab-case,建议格式 auto/{topic}-{taskShortId}。 + + 允许的 estimatedOutput: + """ + allowedOutputs; + } + + private String buildEstimatorUserPrompt(String taskNo, TaskCreateRequest request, RoleEntity role) throws JsonProcessingException { + var root = objectMapper.createObjectNode(); + root.put("taskNo", taskNo == null ? "" : taskNo); + root.put("userPrompt", truncate(request != null ? request.getPrompt() : null, MAX_PROMPT_SNIPPET)); + root.put("modelId", request != null ? request.getModelId() : ""); + + var fileIds = root.putArray("fileIds"); + if (request != null && request.getFileIds() != null) { + for (String fileId : request.getFileIds()) { + fileIds.add(fileId); + } + } + + var roleNode = root.putObject("role"); + roleNode.put("roleId", role != null && role.getId() != null ? role.getId() : 0L); + roleNode.put("roleName", role != null ? role.getName() : ""); + roleNode.put("rolePrompt", truncate(role != null ? role.getPrompt() : null, MAX_PROMPT_SNIPPET)); + + var gitRepos = roleNode.putArray("gitRepos"); + if (role != null && role.getConfigJson() != null && role.getConfigJson().getGitRepos() != null) { + for (RoleEntity.RoleConfig.GitRepo repo : role.getConfigJson().getGitRepos()) { + var repoNode = gitRepos.addObject(); + repoNode.put("url", repo.getUrl()); + repoNode.put("branch", repo.getBranch()); + } + } + + return "请基于以下任务要素做产物预估,按 system 要求返回 JSON:\n" + + objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(root); + } + + private String resolveEstimatorModel(TaskCreateRequest request) { + if (request != null && StringUtils.hasText(request.getModelId())) { + return request.getModelId().trim(); + } + return llmModel; + } + + private String resolveGatewayToken() { + if (StringUtils.hasText(authToken)) { + return authToken.trim(); + } + return null; + } + + private String readErrorBody(InputStream inputStream) { + if (inputStream == null) { + return ""; + } + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line); + } + return sb.toString(); + } catch (Exception e) { + return ""; + } + } + + private String buildFallbackBranchName(String taskNo, String prompt) { + if (!StringUtils.hasText(taskNo)) { + return null; + } + + String topic = slugify(prompt); + if (!StringUtils.hasText(topic)) { + topic = "task"; + } + + String taskShort = taskNo.replaceAll("[^a-zA-Z0-9]", "").toLowerCase(Locale.ROOT); + if (taskShort.length() > 8) { + taskShort = taskShort.substring(taskShort.length() - 8); + } + + return sanitizeBranchName("auto/" + topic + "-" + taskShort); + } + + private String sanitizeBranchName(String branchName) { + if (!StringUtils.hasText(branchName)) { + return null; + } + + String sanitized = branchName.trim().toLowerCase(Locale.ROOT) + .replace('\\', '/') + .replace(' ', '-'); + + sanitized = sanitized.replaceAll("[^a-z0-9/_\\-.]+", "-"); + sanitized = sanitized.replaceAll("/{2,}", "/"); + sanitized = sanitized.replaceAll("-{2,}", "-"); + sanitized = sanitized.replaceAll("^/+", ""); + sanitized = sanitized.replaceAll("/+$", ""); + + if (!StringUtils.hasText(sanitized)) { + return null; + } + + if (!sanitized.contains("/")) { + sanitized = "auto/" + sanitized; + } + + if (sanitized.length() > MAX_BRANCH_LENGTH) { + sanitized = sanitized.substring(0, MAX_BRANCH_LENGTH); + sanitized = sanitized.replaceAll("[-/]+$", ""); + } + + return StringUtils.hasText(sanitized) ? sanitized : null; + } + + private String slugify(String text) { + if (!StringUtils.hasText(text)) { + return ""; + } + + String slug = text.toLowerCase(Locale.ROOT) + .replaceAll("[^a-z0-9]+", "-") + .replaceAll("^-+", "") + .replaceAll("-+$", ""); + + if (slug.length() > 24) { + slug = slug.substring(0, 24).replaceAll("-+$", ""); + } + return slug; + } + + private String firstNonBlank(String... values) { + if (values == null) { + return null; + } + for (String value : values) { + if (StringUtils.hasText(value)) { + return value; + } + } + return null; + } + + private void addByTextRules(String text, LinkedHashSet estimated) { + if (containsAny(text, GIT_KEYWORDS)) { + estimated.add(TaskOutputType.GIT_BRANCH); + } + if (containsAny(text, PULL_REQUEST_KEYWORDS)) { + estimated.add(TaskOutputType.PULL_REQUEST); + estimated.add(TaskOutputType.GIT_BRANCH); + } + + if (containsAny(text, PYTHON_KEYWORDS)) { + estimated.add(TaskOutputType.PYTHON_FILE); + } + if (containsAny(text, JAVA_KEYWORDS)) { + estimated.add(TaskOutputType.JAVA_FILE); + } + if (containsAny(text, JAVASCRIPT_KEYWORDS)) { + estimated.add(TaskOutputType.JAVASCRIPT_FILE); + } + if (containsAny(text, TYPESCRIPT_KEYWORDS)) { + estimated.add(TaskOutputType.TYPESCRIPT_FILE); + } + if (containsAny(text, SQL_KEYWORDS)) { + estimated.add(TaskOutputType.SQL_FILE); + } + if (containsAny(text, SHELL_KEYWORDS)) { + estimated.add(TaskOutputType.SHELL_SCRIPT); + } + if (containsAny(text, CONFIG_KEYWORDS)) { + estimated.add(TaskOutputType.CONFIG_FILE); + } + + if (containsAny(text, MARKDOWN_KEYWORDS)) { + estimated.add(TaskOutputType.MARKDOWN); + } + if (containsAny(text, TXT_KEYWORDS)) { + estimated.add(TaskOutputType.TXT); + } + if (containsAny(text, WORD_KEYWORDS)) { + estimated.add(TaskOutputType.WORD); + } + if (containsAny(text, EXCEL_KEYWORDS)) { + estimated.add(TaskOutputType.EXCEL); + } + if (containsAny(text, CSV_KEYWORDS)) { + estimated.add(TaskOutputType.CSV); + } + if (containsAny(text, PPT_KEYWORDS)) { + estimated.add(TaskOutputType.PPT); + } + if (containsAny(text, PDF_KEYWORDS)) { + estimated.add(TaskOutputType.PDF); + } + + if (containsAny(text, JSON_KEYWORDS)) { + estimated.add(TaskOutputType.JSON); + } + if (containsAny(text, PNG_KEYWORDS)) { + estimated.add(TaskOutputType.PNG); + } + if (containsAny(text, ARCHIVE_KEYWORDS)) { + estimated.add(TaskOutputType.ARCHIVE); + } + + if (containsAny(text, API_RESULT_KEYWORDS)) { + estimated.add(TaskOutputType.API_CALL_RESULT); + } + } + + private void addByTaskAttributes(TaskCreateRequest request, LinkedHashSet estimated) { + if (request != null && request.getFileIds() != null) { + for (String fileId : request.getFileIds()) { + TaskOutputType.fromFileName(fileId).ifPresent(estimated::add); + } + } + } + + private List toCodes(LinkedHashSet estimated) { + List output = new ArrayList<>(estimated.size()); + for (TaskOutputType type : estimated) { + output.add(type.getCode()); + } + return output; + } + + private String normalize(String text) { + if (text == null) { + return ""; + } + return text.toLowerCase(Locale.ROOT); + } + + private boolean containsAny(String text, Set keywords) { + if (text.isBlank()) { + return false; + } + for (String keyword : keywords) { + if (text.contains(keyword)) { + return true; + } + } + return false; + } + + private String truncate(String text, int maxLength) { + if (!StringUtils.hasText(text)) { + return ""; + } + if (text.length() <= maxLength) { + return text; + } + return text.substring(0, maxLength); + } +} diff --git a/back/src/main/java/com/linkwork/service/WeeklyReportService.java b/back/src/main/java/com/linkwork/service/WeeklyReportService.java new file mode 100644 index 0000000..7af6472 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/WeeklyReportService.java @@ -0,0 +1,1370 @@ +package com.linkwork.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAdjusters; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * 读取 git 提交记录并生成周报。 + */ +@Slf4j +@Service +public class WeeklyReportService { + + private static final ZoneId ZONE_ID = ZoneId.systemDefault(); + private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + private static final DateTimeFormatter GIT_TIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + private static final String DONE_TOKEN = "[DONE]"; + + private static final List THEME_RULES = List.of( + new ThemeRule("任务执行与终止链路", Pattern.compile("task|任务|mission|terminate|终止|dispatch|websocket|stream|redis", Pattern.CASE_INSENSITIVE)), + new ThemeRule("审批与风控能力", Pattern.compile("approval|审批|risk|风控|\\bip\\b|\\bipv4\\b|\\bipv6\\b", Pattern.CASE_INSENSITIVE)), + new ThemeRule("GitLab 认证与权限", Pattern.compile("gitlab|oauth|auth|认证|权限", Pattern.CASE_INSENSITIVE)), + new ThemeRule("部署与环境治理", Pattern.compile("deploy|部署|docker|compose|prod|robot_env|environment", Pattern.CASE_INSENSITIVE)), + new ThemeRule("前端体验与可读性", Pattern.compile("front|ui|style|layout|sidebar|table|readability|看板|动画|交互", Pattern.CASE_INSENSITIVE)), + new ThemeRule("文档与规范", Pattern.compile("docs|文档|guideline|spec", Pattern.CASE_INSENSITIVE)) + ); + + private static final List KEYWORD_RULES = List.of( + new KeywordRule("任务终止", Pattern.compile("terminate|终止", Pattern.CASE_INSENSITIVE)), + new KeywordRule("审批风控", Pattern.compile("approval|审批|risk|风控", Pattern.CASE_INSENSITIVE)), + new KeywordRule("GitLab认证", Pattern.compile("gitlab|oauth|认证|auth", Pattern.CASE_INSENSITIVE)), + new KeywordRule("Redis队列", Pattern.compile("redis|queue|stream", Pattern.CASE_INSENSITIVE)), + new KeywordRule("部署发布", Pattern.compile("deploy|部署|docker|compose", Pattern.CASE_INSENSITIVE)), + new KeywordRule("Runtime模式", Pattern.compile("runtime|sidecar|alone", Pattern.CASE_INSENSITIVE)), + new KeywordRule("输出估算", Pattern.compile("estimate|estimation|taxonomy|报告", Pattern.CASE_INSENSITIVE)), + new KeywordRule("前端可读性", Pattern.compile("readability|table|layout|sidebar|ui|style", Pattern.CASE_INSENSITIVE)), + new KeywordRule("安全策略", Pattern.compile("security|安全|jwt|策略", Pattern.CASE_INSENSITIVE)) + ); + + private static final Map KEYWORD_PHRASE_MAP = Map.ofEntries( + Map.entry("审批风控", "审批流中心全栈联通"), + Map.entry("任务终止", "任务终止链路闭环(API-队列-流式回显)"), + Map.entry("GitLab认证", "GitLab 认证稳定性与生产部署能力提升"), + Map.entry("Redis队列", "任务队列与流式事件联调收敛"), + Map.entry("部署发布", "生产部署流程统一与环境治理升级"), + Map.entry("Runtime模式", "Sidecar/Alone 运行模式能力贯通"), + Map.entry("输出估算", "任务产出估算与报告自动化"), + Map.entry("前端可读性", "执行流与看板 UI 可读性专项优化"), + Map.entry("安全策略", "风险 IP 审计能力补齐") + ); + + private static final Map AUTHOR_KEYWORD_PHRASE_MAP = Map.ofEntries( + Map.entry("审批风控", "审批流与风险控制能力建设"), + Map.entry("任务终止", "任务终止与执行链路优化"), + Map.entry("GitLab认证", "GitLab 认证与权限治理"), + Map.entry("Redis队列", "Redis 队列与流式通道联调"), + Map.entry("部署发布", "部署发布流程优化"), + Map.entry("Runtime模式", "Runtime 模式能力建设"), + Map.entry("输出估算", "任务产出估算与报告能力"), + Map.entry("前端可读性", "前端交互与可读性优化"), + Map.entry("安全策略", "安全策略与认证治理") + ); + + private static final Map AUTHOR_ALIAS_MAP = Map.of(); + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Value("${robot.weekly-report.repo-path:}") + private String configuredRepoPath; + + @Value("${robot.weekly-report.llm.enabled:true}") + private boolean llmEnabled; + + @Value("${robot.weekly-report.llm.gateway-url:}") + private String llmGatewayUrl; + + @Value("${robot.weekly-report.llm.model:minimax-m2.1}") + private String llmModel; + + @Value("${robot.weekly-report.llm.max-tokens:900}") + private int llmMaxTokens; + + @Value("${robot.weekly-report.llm.stream:true}") + private boolean llmStream; + + @Value("${robot.weekly-report.llm.connect-timeout-ms:3000}") + private int llmConnectTimeoutMs; + + @Value("${robot.weekly-report.llm.read-timeout-ms:12000}") + private int llmReadTimeoutMs; + + @Value("${robot.weekly-report.llm.auth-token:}") + private String llmAuthToken; + + @Value("${robot.weekly-report.llm.x-litellm-api-key:}") + private String xLitellmApiKey; + + public String buildCurrentWeekMarkdown() { + DateWindow window = currentWeekWindow(); + WeeklyReportData data = collectWeeklyData(window); + + String fallbackMarkdown = renderRuleMarkdown(data); + String llmMarkdown = renderLlmMarkdown(data); + if (StringUtils.hasText(llmMarkdown)) { + return llmMarkdown; + } + return fallbackMarkdown; + } + + private WeeklyReportData collectWeeklyData(DateWindow window) { + List branches = listAllBranches(); + Map> authorBranches = collectAuthorBranches(branches, window); + List authorStats = buildAuthorStats(authorBranches); + List allCommits = listAllCommits(window); + int mergeCommitCount = countMergeCommits(window); + DiffStats diffStats = collectDiffStats(window); + List focusModules = summarizeFocusModules(window); + + Set allAuthors = allCommits.stream() + .map(CommitRecord::author) + .collect(Collectors.toCollection(LinkedHashSet::new)); + + List themes = summarizeThemes(allCommits); + List keywords = summarizeKeywords(allCommits); + List authorContributions = buildAuthorContributions(allCommits); + + return new WeeklyReportData( + window.startDate(), + window.endDate(), + allCommits.size(), + mergeCommitCount, + diffStats.addedLines(), + diffStats.deletedLines(), + allAuthors, + countActiveBranches(authorStats), + focusModules, + authorContributions, + themes, + keywords + ); + } + + private String renderRuleMarkdown(WeeklyReportData data) { + StringBuilder sb = new StringBuilder(); + sb.append(String.format("### 本周周报(%s ~ %s)\n\n", + data.startDate().format(DATE_FMT), + data.endDate().format(DATE_FMT))); + + sb.append("#### 本周提交作者与产出(按作者分类)\n\n"); + if (data.authorContributions().isEmpty()) { + sb.append("- 本周暂无提交记录\n\n"); + } else { + for (AuthorContribution author : data.authorContributions()) { + sb.append(String.format("**%s**(%d)\n", author.author(), author.commitCount())); + for (String output : author.outputs()) { + sb.append(String.format("- %s\n", output)); + } + sb.append("\n"); + } + } + + sb.append("#### 本周工作周报\n\n"); + List weeklyBullets = buildWeeklyBullets(data); + for (String bullet : weeklyBullets) { + sb.append("- ").append(bullet).append("\n"); + } + sb.append("\n"); + + sb.append("#### 本周产出关键词\n\n"); + List outputKeywords = buildOutputKeywords(data); + if (outputKeywords.isEmpty()) { + sb.append("- 本周暂无可沉淀的关键词产出\n"); + } else { + for (String keyword : outputKeywords) { + sb.append("- ").append(keyword).append("\n"); + } + } + + return sb.toString(); + } + + private List buildWeeklyBullets(WeeklyReportData data) { + List bullets = new ArrayList<>(); + bullets.add(buildSummaryBullet(data)); + + if (data.themeStats().isEmpty()) { + bullets.add("本周以零散修复为主,暂无可聚合主题。"); + return bullets; + } + + List prioritized = data.themeStats().stream() + .filter(theme -> !"文档与规范".equals(theme.name())) + .limit(5) + .toList(); + if (prioritized.isEmpty()) { + prioritized = data.themeStats().stream().limit(5).toList(); + } + + for (ThemeStat theme : prioritized) { + bullets.add(buildThemeBullet(theme)); + } + return bullets; + } + + private String buildSummaryBullet(WeeklyReportData data) { + String modulePhrase = formatFocusModules(data.focusModules()); + return String.format("本周共提交 %d 个非合并 commit(另有 %d 个 merge),累计变更约 +%d/-%d 行,重点集中在 %s。", + data.commitCount(), + data.mergeCommitCount(), + data.addedLines(), + data.deletedLines(), + modulePhrase); + } + + private String formatFocusModules(List modules) { + if (modules == null || modules.isEmpty()) { + return "多个核心模块"; + } + List names = modules.stream().map(ModuleCount::module).toList(); + if (names.size() == 1) { + return names.get(0) + " 模块"; + } + if (names.size() == 2) { + return String.join("、", names) + " 两块"; + } + return String.join("、", names) + " 三块"; + } + + private String buildThemeBullet(ThemeStat theme) { + String detail = themeDetailHint(theme.name()); + return switch (theme.name()) { + case "任务执行与终止链路" -> String.format("完成“任务与执行链路”核心能力:%s(相关提交 %d 条)。", detail, theme.count()); + case "审批与风控能力" -> String.format("完成“风险治理与审计”建设:%s(相关提交 %d 条)。", detail, theme.count()); + case "GitLab 认证与权限" -> String.format("完成“GitLab 认证与权限”增强:%s(相关提交 %d 条)。", detail, theme.count()); + case "部署与环境治理" -> String.format("完善“部署与环境治理”能力:%s(相关提交 %d 条)。", detail, theme.count()); + case "前端体验与可读性" -> String.format("持续优化“前端体验与可读性”:%s(相关提交 %d 条)。", detail, theme.count()); + case "文档与规范" -> String.format("同步“文档与规范”沉淀:%s(相关提交 %d 条)。", detail, theme.count()); + default -> String.format("推进“%s”:%s(相关提交 %d 条)。", theme.name(), detail, theme.count()); + }; + } + + private String themeDetailHint(String themeName) { + return switch (themeName) { + case "任务执行与终止链路" -> "覆盖任务终止 API、Redis 队列联动、WebSocket 流式状态同步与执行稳定性修复"; + case "审批与风控能力" -> "覆盖审批流联动、风险等级展示、风险 IP 记录与审计链路补齐"; + case "GitLab 认证与权限" -> "覆盖认证前置校验、读写权限区分、认证提示与失败重试优化"; + case "部署与环境治理" -> "覆盖 ROBOT_ENV 分层、部署脚本统一、镜像与配置路径标准化"; + case "前端体验与可读性" -> "覆盖任务流状态展示、表格可读性提升、侧边栏与详情交互优化"; + case "文档与规范" -> "覆盖 API 设计、任务进展追踪与实施规范同步"; + default -> "覆盖核心功能迭代与稳定性改进"; + }; + } + + private String normalizeCommitSubject(String subject) { + if (!StringUtils.hasText(subject)) { + return ""; + } + String cleaned = subject.trim(); + cleaned = cleaned.replaceFirst("^\\[[^]]+\\]\\s*", ""); + cleaned = cleaned.replaceFirst("^[A-Za-z]+\\([^)]*\\):\\s*", ""); + cleaned = cleaned.replaceFirst("^[A-Za-z]+:\\s*", ""); + cleaned = cleaned.replaceFirst("^(?i)(fix|feat|chore|docs|refactor|style|test|ci|build|perf|revert)\\s+", ""); + cleaned = cleaned.replaceFirst("^[,,;;。\\s]+", ""); + cleaned = cleaned.replaceFirst("[。;;\\s]+$", ""); + return truncate(cleaned, 42); + } + + + private String truncate(String value, int maxLen) { + if (!StringUtils.hasText(value)) { + return ""; + } + String trimmed = value.trim(); + return trimmed.length() > maxLen ? trimmed.substring(0, maxLen) + "..." : trimmed; + } + + private List buildOutputKeywords(WeeklyReportData data) { + List keywords = new ArrayList<>(); + for (KeywordCount keyword : data.keywordCounts()) { + if (keyword.count() < 2) { + continue; + } + String phrase = KEYWORD_PHRASE_MAP.get(keyword.keyword()); + if (StringUtils.hasText(phrase) && !keywords.contains(phrase)) { + keywords.add(phrase); + } + if (keywords.size() >= 5) { + return keywords; + } + } + + if (keywords.size() < 5) { + for (ThemeStat theme : data.themeStats()) { + String fallback = switch (theme.name()) { + case "任务执行与终止链路" -> "任务与终止链路稳定性提升"; + case "审批与风控能力" -> "审批风险治理能力持续增强"; + case "GitLab 认证与权限" -> "GitLab 认证与权限治理完善"; + case "部署与环境治理" -> "部署流程与环境配置标准化"; + case "前端体验与可读性" -> "前端交互与可读性专项优化"; + default -> null; + }; + if (StringUtils.hasText(fallback) && !keywords.contains(fallback)) { + keywords.add(fallback); + } + if (keywords.size() >= 5) { + break; + } + } + } + + return keywords; + } + + private String renderLlmMarkdown(WeeklyReportData data) { + if (data.commitCount() == 0) { + return null; + } + if (!llmEnabled || !StringUtils.hasText(llmGatewayUrl) || !StringUtils.hasText(llmAuthToken)) { + return null; + } + + try { + String systemPrompt = buildLlmSystemPrompt(); + String userPrompt = buildLlmUserPrompt(data); + String completion = callLlmGateway(systemPrompt, userPrompt); + String markdown = cleanupLlmMarkdown(completion); + if (!StringUtils.hasText(markdown)) { + return null; + } + if (!isValidWeeklyReportMarkdown(markdown, data)) { + log.warn("LLM 周报格式校验失败,降级规则输出"); + return null; + } + return markdown; + } catch (Exception e) { + log.warn("LLM 周报生成失败,降级规则输出: {}", e.getMessage()); + return null; + } + } + + private String buildLlmSystemPrompt() { + return """ + 你是研发团队周报助手。请根据输入 JSON 生成“信息密度高、可直接群发”的 Markdown。 + 输出要求: + 1) 仅输出 Markdown 正文,不要解释,不要代码块。 + 2) 必须包含且仅包含以下三级标题: + - #### 本周提交作者与产出(按作者分类) + - #### 本周工作周报 + - #### 本周产出关键词 + 3) 第二节“本周工作周报”必须使用 5~7 条列表,且第一条固定为统计总览句,句式参考: + 本周共提交 X 个非合并 commit(另有 Y 个 merge),累计变更约 +A/-D 行,重点集中在 M1、M2、M3 三块。 + 4) 第二节其余条目要按主题写“完成/推进/优化 + 具体事项”,不得只写“若干优化/持续迭代”等空话。 + 5) 第三节“本周产出关键词”输出 5 条左右短语,不带计数。 + 6) 第一节按作者分组,仅列举作者本周代表提交产出,不展示分支列表。 + 7) 第一节按提交量控制篇幅:commit>=100 输出 6~8 条,commit>=50 输出 4~6 条,其余输出 2~4 条;禁止流水账式逐条罗列。 + 8) 全文尽量使用中文表达,英文仅保留必要技术名词(如 GitLab、Redis、WebSocket),避免生硬中英混写。 + 9) 数据必须严格来自输入,不得编造作者、分支、提交量。 + """; + } + + private String buildLlmUserPrompt(WeeklyReportData data) throws Exception { + Map payload = new LinkedHashMap<>(); + payload.put("weekStart", data.startDate().format(DATE_FMT)); + payload.put("weekEnd", data.endDate().format(DATE_FMT)); + payload.put("commitCount", data.commitCount()); + payload.put("mergeCommitCount", data.mergeCommitCount()); + payload.put("addedLines", data.addedLines()); + payload.put("deletedLines", data.deletedLines()); + payload.put("authorCount", data.authors().size()); + payload.put("authors", data.authors()); + payload.put("activeBranchCount", data.activeBranchCount()); + + List> focusModules = new ArrayList<>(); + for (ModuleCount module : data.focusModules()) { + Map moduleObj = new LinkedHashMap<>(); + moduleObj.put("module", module.module()); + moduleObj.put("count", module.count()); + focusModules.add(moduleObj); + } + payload.put("focusModules", focusModules); + + List> authors = new ArrayList<>(); + for (AuthorContribution author : data.authorContributions()) { + Map authorObj = new LinkedHashMap<>(); + authorObj.put("author", author.author()); + authorObj.put("commitCount", author.commitCount()); + authorObj.put("minOutputCount", minOutputsForAuthor(author.commitCount())); + authorObj.put("maxOutputCount", maxOutputsForAuthor(author.commitCount())); + authorObj.put("outputs", author.outputs()); + authors.add(authorObj); + } + payload.put("authorContributions", authors); + + List> themes = new ArrayList<>(); + for (ThemeStat theme : data.themeStats()) { + Map themeObj = new LinkedHashMap<>(); + themeObj.put("theme", theme.name()); + themeObj.put("count", theme.count()); + themeObj.put("summaryHint", themeDetailHint(theme.name())); + themes.add(themeObj); + } + payload.put("weeklyThemes", themes); + + List> keywords = new ArrayList<>(); + for (KeywordCount keyword : data.keywordCounts()) { + Map keywordObj = new LinkedHashMap<>(); + keywordObj.put("keyword", keyword.keyword()); + keywordObj.put("count", keyword.count()); + keywordObj.put("phraseHint", KEYWORD_PHRASE_MAP.get(keyword.keyword())); + keywords.add(keywordObj); + } + payload.put("keywords", keywords); + payload.put("ruleWeeklyBullets", buildWeeklyBullets(data)); + payload.put("ruleKeywordBullets", buildOutputKeywords(data)); + + return "请严格按 system 要求输出周报 Markdown,输入数据如下:\n" + + objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(payload); + } + + private String callLlmGateway(String systemPrompt, String userPrompt) throws IOException, InterruptedException { + JsonNode requestBody = buildLlmRequestPayload(systemPrompt, userPrompt); + + HttpClient client = HttpClient.newBuilder() + .connectTimeout(java.time.Duration.ofMillis(Math.max(llmConnectTimeoutMs, 1000))) + .build(); + + HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(URI.create(llmGatewayUrl)) + .timeout(java.time.Duration.ofMillis(Math.max(llmReadTimeoutMs, 2000))) + .header("Authorization", "Bearer " + llmAuthToken.trim()) + .header("Content-Type", "application/json"); + + if (StringUtils.hasText(xLitellmApiKey)) { + requestBuilder.header("x-litellm-api-key", xLitellmApiKey.trim()); + } + + HttpRequest request = requestBuilder + .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(requestBody), StandardCharsets.UTF_8)) + .build(); + + if (llmStream) { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()); + if (response.statusCode() >= 400) { + String errorBody = readErrorBody(response.body()); + throw new IllegalStateException("LLM Gateway 请求失败: status=" + response.statusCode() + ", body=" + errorBody); + } + return readStreamCompletion(response.body()); + } + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); + if (response.statusCode() >= 400) { + throw new IllegalStateException("LLM Gateway 请求失败: status=" + response.statusCode() + ", body=" + response.body()); + } + + String text = extractTextFromGatewayPayload(response.body()); + return StringUtils.hasText(text) ? text : response.body(); + } + + private JsonNode buildLlmRequestPayload(String systemPrompt, String userPrompt) { + var root = objectMapper.createObjectNode(); + root.put("model", llmModel); + root.put("max_tokens", llmMaxTokens); + root.put("stream", llmStream); + + var messages = root.putArray("messages"); + var system = messages.addObject(); + system.put("role", "system"); + system.put("content", systemPrompt); + + var user = messages.addObject(); + user.put("role", "user"); + user.put("content", userPrompt); + + return root; + } + + private String readStreamCompletion(InputStream bodyStream) throws IOException { + StringBuilder completion = new StringBuilder(); + StringBuilder raw = new StringBuilder(); + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(bodyStream, StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + raw.append(line).append('\n'); + String trimmed = line.trim(); + if (trimmed.isEmpty() || trimmed.startsWith(":")) { + continue; + } + if (trimmed.startsWith("data:")) { + trimmed = trimmed.substring(5).trim(); + } + if (!StringUtils.hasText(trimmed) || DONE_TOKEN.equals(trimmed)) { + if (DONE_TOKEN.equals(trimmed)) { + break; + } + continue; + } + + String delta = extractTextFromGatewayPayload(trimmed); + if (StringUtils.hasText(delta)) { + completion.append(delta); + } + } + } + + if (completion.length() > 0) { + return completion.toString(); + } + return raw.toString(); + } + + private String extractTextFromGatewayPayload(String payload) { + if (!StringUtils.hasText(payload)) { + return ""; + } + + String trimmed = payload.trim(); + if (!trimmed.startsWith("{")) { + return trimmed; + } + + try { + JsonNode root = objectMapper.readTree(trimmed); + StringBuilder text = new StringBuilder(); + + appendTextNode(root.path("output_text"), text); + appendTextNode(root.path("content"), text); + + JsonNode choices = root.path("choices"); + if (choices.isArray()) { + for (JsonNode choice : choices) { + appendTextNode(choice.path("delta").path("content"), text); + appendTextNode(choice.path("message").path("content"), text); + appendTextNode(choice.path("text"), text); + } + } + + JsonNode data = root.path("data"); + if (data.isTextual()) { + appendTextNode(data, text); + } else if (data.isObject()) { + appendTextNode(data.path("content"), text); + } + + if (text.length() > 0) { + return text.toString(); + } + if (root.isTextual()) { + return root.asText(); + } + return ""; + } catch (Exception ignore) { + return trimmed; + } + } + + private void appendTextNode(JsonNode node, StringBuilder builder) { + if (node == null || node.isMissingNode() || node.isNull()) { + return; + } + + if (node.isTextual()) { + builder.append(node.asText()); + return; + } + + if (node.isArray()) { + for (JsonNode child : node) { + appendTextNode(child, builder); + } + return; + } + + if (node.isObject()) { + appendTextNode(node.path("text"), builder); + appendTextNode(node.path("content"), builder); + appendTextNode(node.path("value"), builder); + } + } + + private String cleanupLlmMarkdown(String completion) { + if (!StringUtils.hasText(completion)) { + return null; + } + String markdown = completion.trim(); + if (markdown.startsWith("```")) { + markdown = markdown.replaceFirst("^```[a-zA-Z]*\\s*", ""); + markdown = markdown.replaceFirst("\\s*```$", ""); + } + return markdown.trim(); + } + + private boolean isValidWeeklyReportMarkdown(String markdown, WeeklyReportData data) { + String lower = markdown.toLowerCase(Locale.ROOT); + boolean hasSections = lower.contains("本周提交作者与产出") + && lower.contains("本周工作周报") + && lower.contains("本周产出关键词"); + if (!hasSections) { + return false; + } + + boolean hasSummarySentence = markdown.contains("本周共提交") + && markdown.contains(String.valueOf(data.commitCount())) + && markdown.contains(String.valueOf(data.mergeCommitCount())); + int workBullets = countSectionBullets(markdown, "#### 本周工作周报", "#### 本周产出关键词"); + int keywordBullets = countSectionBullets(markdown, "#### 本周产出关键词", null); + return hasSummarySentence && workBullets >= 5 && keywordBullets >= 4; + } + + private int countSectionBullets(String markdown, String startTitle, String endTitle) { + int start = markdown.indexOf(startTitle); + if (start < 0) { + return 0; + } + int from = start + startTitle.length(); + int end = endTitle == null ? markdown.length() : markdown.indexOf(endTitle, from); + if (end < 0) { + end = markdown.length(); + } + + int count = 0; + for (String line : markdown.substring(from, end).split("\n")) { + if (line.trim().startsWith("- ")) { + count++; + } + } + return count; + } + + private String readErrorBody(InputStream inputStream) { + if (inputStream == null) { + return ""; + } + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line); + } + return sb.toString(); + } catch (Exception e) { + return ""; + } + } + + private DateWindow currentWeekWindow() { + LocalDate today = LocalDate.now(ZONE_ID); + LocalDate monday = today.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)); + LocalDate sunday = monday.plusDays(6); + LocalDateTime since = monday.atStartOfDay(); + LocalDateTime until = sunday.plusDays(1).atStartOfDay(); + return new DateWindow(monday, sunday, since, until); + } + + private List listAllBranches() { + List remoteBranches = runGit(List.of("git", "for-each-ref", "--format=%(refname:short)", "refs/remotes/origin")); + List normalizedRemote = remoteBranches.stream() + .map(String::trim) + .filter(s -> !s.isBlank()) + .filter(s -> !"origin/HEAD".equals(s)) + .sorted() + .toList(); + if (!normalizedRemote.isEmpty()) { + return normalizedRemote; + } + + List localBranches = runGit(List.of("git", "for-each-ref", "--format=%(refname:short)", "refs/heads")); + return localBranches.stream() + .map(String::trim) + .filter(s -> !s.isBlank()) + .sorted() + .toList(); + } + + private Map> collectAuthorBranches(List branches, DateWindow window) { + Map> result = new HashMap<>(); + String since = formatGitTime(window.sinceInclusive()); + String until = formatGitTime(window.untilExclusive()); + + for (String branch : branches) { + List lines = runGit(List.of( + "git", "log", branch, + "--first-parent", + "--since=" + since, + "--until=" + until, + "--no-merges", + "--pretty=format:%an" + )); + + if (lines.isEmpty()) { + continue; + } + + Map authorCount = lines.stream() + .map(String::trim) + .filter(s -> !s.isBlank()) + .collect(Collectors.groupingBy(s -> s, Collectors.counting())); + + String displayBranch = normalizeBranchName(branch); + authorCount.forEach((author, count) -> result + .computeIfAbsent(author, k -> new HashMap<>()) + .merge(displayBranch, count, Long::sum)); + } + + return result; + } + + private List buildAuthorStats(Map> authorBranches) { + return authorBranches.entrySet().stream() + .map(entry -> { + String author = entry.getKey(); + List branches = entry.getValue().entrySet().stream() + .map(e -> new BranchCount(e.getKey(), e.getValue())) + .sorted(Comparator.comparingLong(BranchCount::count).reversed() + .thenComparing(BranchCount::branch)) + .toList(); + long total = branches.stream().mapToLong(BranchCount::count).sum(); + return new AuthorBranchStat(author, total, branches); + }) + .sorted(Comparator.comparingLong(AuthorBranchStat::totalCommits).reversed() + .thenComparing(AuthorBranchStat::author)) + .toList(); + } + + private List buildAuthorContributions(List commits) { + Map commitCountByAuthor = new HashMap<>(); + Map> subjectsByAuthor = new HashMap<>(); + + for (CommitRecord commit : commits) { + String author = canonicalAuthorName(commit.author()); + commitCountByAuthor.merge(author, 1, Integer::sum); + subjectsByAuthor.computeIfAbsent(author, k -> new ArrayList<>()).add(commit.subject()); + } + + return commitCountByAuthor.entrySet().stream() + .map(entry -> { + String author = entry.getKey(); + List outputs = summarizeAuthorOutputs(subjectsByAuthor.getOrDefault(author, List.of()), entry.getValue()); + return new AuthorContribution(author, entry.getValue(), outputs); + }) + .sorted(Comparator.comparingInt(AuthorContribution::commitCount).reversed() + .thenComparing(AuthorContribution::author)) + .toList(); + } + + private List summarizeAuthorOutputs(List subjects, int commitCount) { + if (subjects == null || subjects.isEmpty()) { + return List.of("常规功能迭代与缺陷修复"); + } + + int maxOutputs = maxOutputsForAuthor(commitCount); + int minOutputs = minOutputsForAuthor(commitCount); + + Set outputSet = new LinkedHashSet<>(); + for (String subject : subjects) { + String output = toLocalizedAuthorOutput(subject); + if (!StringUtils.hasText(output)) { + continue; + } + outputSet.add(output); + if (outputSet.size() >= maxOutputs) { + break; + } + } + + List outputs = new ArrayList<>(outputSet); + + Map phraseCounts = new HashMap<>(); + for (KeywordRule rule : KEYWORD_RULES) { + long count = subjects.stream() + .filter(StringUtils::hasText) + .filter(subject -> rule.pattern().matcher(subject).find()) + .count(); + if (count <= 0) { + continue; + } + String phrase = AUTHOR_KEYWORD_PHRASE_MAP.getOrDefault(rule.name(), rule.name()); + phraseCounts.merge(phrase, count, Long::sum); + } + + List phraseOutputs = phraseCounts.entrySet().stream() + .sorted(Map.Entry.comparingByValue().reversed() + .thenComparing(Map.Entry::getKey)) + .map(Map.Entry::getKey) + .toList(); + + if (outputs.size() < minOutputs) { + for (String phrase : phraseOutputs) { + if (outputs.size() >= maxOutputs) { + break; + } + if (!outputs.contains(phrase)) { + outputs.add(phrase); + } + if (outputs.size() >= minOutputs) { + break; + } + } + } + + if (!outputs.isEmpty()) { + return outputs.stream().limit(maxOutputs).toList(); + } + + if (!phraseOutputs.isEmpty()) { + return phraseOutputs.stream().limit(maxOutputs).toList(); + } + + return List.of("常规功能迭代与缺陷修复"); + } + + private String toLocalizedAuthorOutput(String subject) { + String normalized = normalizeCommitSubject(subject); + if (!StringUtils.hasText(normalized)) { + return ""; + } + + if (containsChinese(normalized)) { + return truncate(normalized, 42); + } + + return translateEnglishSubject(subject, normalized); + } + + private String translateEnglishSubject(String rawSubject, String normalizedSubject) { + String lower = rawSubject == null ? "" : rawSubject.toLowerCase(Locale.ROOT); + + if (lower.contains("weekly-report") || lower.contains("weekly report")) { + if (lower.contains("output coverage") || lower.contains("heavy contributors")) { + return "周报作者代表产出覆盖范围提升"; + } + if (lower.contains("author contributions") || lower.contains("branch breakdown")) { + return "周报改为按作者归类展示代表产出"; + } + if (lower.contains("chinese") || lower.contains("localization") || lower.contains("alias")) { + return "周报输出中文化与作者别名归并优化"; + } + if (lower.contains("safe.directory")) { + return "周报仓库 safe.directory 兼容修复"; + } + if (lower.contains("runtime image") || lower.contains("install git")) { + return "周报运行镜像补充 Git 依赖"; + } + if (lower.contains("origin branches") || lower.contains("repo path")) { + return "周报按 origin 分支聚合作者提交"; + } + if (lower.contains("summary") || lower.contains("stats")) { + return "周报统计总览与重点模块信息增强"; + } + return "周报生成能力优化"; + } + + if (lower.contains("gitlab") && (lower.contains("auth") || lower.contains("oauth"))) { + return "GitLab 认证与权限能力优化"; + } + + if ((lower.contains("risk") && lower.contains("ip")) || lower.contains("approval ip")) { + return "风险 IP 审计链路补齐"; + } + + if (lower.contains("deploy") || lower.contains("docker") || lower.contains("compose") || lower.contains("robot_env")) { + return "部署流程与环境治理优化"; + } + + if (lower.contains("readability") || lower.contains("table") || lower.contains("sidebar") || lower.contains("layout")) { + return "前端可读性与交互体验优化"; + } + + if (lower.contains("terminate") || lower.contains("websocket") || lower.contains("redis") || lower.contains("task")) { + return "任务执行与终止链路优化"; + } + + if (lower.contains("approval")) { + return "审批流程能力优化"; + } + + String singleWord = normalizedSubject.toLowerCase(Locale.ROOT); + if (singleWord.matches("^(fix|test|wip|tmp|misc|draft|done)$")) { + return authorThemeLabel(rawSubject); + } + + return authorThemeLabel(rawSubject); + } + + private String authorThemeLabel(String subject) { + if (!StringUtils.hasText(subject)) { + return "通用能力迭代"; + } + + String normalizedSubject = subject.toLowerCase(Locale.ROOT); + if (normalizedSubject.contains("weekly-report") || normalizedSubject.contains("weekly report") || normalizedSubject.contains("周报")) { + return "周报能力优化"; + } + + for (ThemeRule rule : THEME_RULES) { + if (!rule.pattern().matcher(subject).find()) { + continue; + } + return switch (rule.name()) { + case "任务执行与终止链路" -> "任务执行与终止链路优化"; + case "审批与风控能力" -> "审批风控能力优化"; + case "GitLab 认证与权限" -> "GitLab 认证与权限优化"; + case "部署与环境治理" -> "部署与环境治理优化"; + case "前端体验与可读性" -> "前端体验与可读性优化"; + case "文档与规范" -> "文档与规范同步"; + default -> "通用能力迭代"; + }; + } + return "通用能力迭代"; + } + + private int maxOutputsForAuthor(int commitCount) { + if (commitCount >= 100) { + return 8; + } + if (commitCount >= 70) { + return 7; + } + if (commitCount >= 40) { + return 6; + } + if (commitCount >= 20) { + return 5; + } + return 4; + } + + private int minOutputsForAuthor(int commitCount) { + if (commitCount >= 100) { + return 6; + } + if (commitCount >= 70) { + return 5; + } + if (commitCount >= 40) { + return 4; + } + if (commitCount >= 20) { + return 3; + } + return 2; + } + + private String canonicalAuthorName(String rawAuthor) { + if (!StringUtils.hasText(rawAuthor)) { + return "未知作者"; + } + String author = rawAuthor.trim(); + String alias = AUTHOR_ALIAS_MAP.get(author); + if (StringUtils.hasText(alias)) { + return alias; + } + + String normalized = normalizeAuthorKey(author); + for (Map.Entry entry : AUTHOR_ALIAS_MAP.entrySet()) { + if (normalizeAuthorKey(entry.getKey()).equals(normalized)) { + return entry.getValue(); + } + } + return author; + } + + private String normalizeAuthorKey(String author) { + return author.toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9\u4e00-\u9fa5]", ""); + } + + private boolean containsChinese(String text) { + if (!StringUtils.hasText(text)) { + return false; + } + for (int i = 0; i < text.length(); i++) { + Character.UnicodeScript script = Character.UnicodeScript.of(text.charAt(i)); + if (script == Character.UnicodeScript.HAN) { + return true; + } + } + return false; + } + + private List listAllCommits(DateWindow window) { + String since = formatGitTime(window.sinceInclusive()); + String until = formatGitTime(window.untilExclusive()); + + List lines = runGit(List.of( + "git", "log", "--all", + "--since=" + since, + "--until=" + until, + "--no-merges", + "--pretty=format:%H%x09%an%x09%s" + )); + + List records = new ArrayList<>(); + for (String line : lines) { + String trimmed = line.trim(); + if (trimmed.isEmpty()) { + continue; + } + String[] parts = trimmed.split("\\t", 3); + if (parts.length < 3) { + continue; + } + records.add(new CommitRecord(parts[0], parts[1], parts[2])); + } + return records; + } + + private int countMergeCommits(DateWindow window) { + String since = formatGitTime(window.sinceInclusive()); + String until = formatGitTime(window.untilExclusive()); + + List lines = runGit(List.of( + "git", "log", "--all", + "--since=" + since, + "--until=" + until, + "--merges", + "--pretty=format:%H" + )); + int count = 0; + for (String line : lines) { + if (StringUtils.hasText(line)) { + count++; + } + } + return count; + } + + private DiffStats collectDiffStats(DateWindow window) { + String since = formatGitTime(window.sinceInclusive()); + String until = formatGitTime(window.untilExclusive()); + + List lines = runGit(List.of( + "git", "log", "--all", + "--since=" + since, + "--until=" + until, + "--no-merges", + "--pretty=tformat:", + "--numstat" + )); + + long added = 0; + long deleted = 0; + for (String line : lines) { + if (!StringUtils.hasText(line)) { + continue; + } + String[] parts = line.split("\t"); + if (parts.length < 2) { + continue; + } + if (!"-".equals(parts[0])) { + try { + added += Long.parseLong(parts[0]); + } catch (NumberFormatException ignore) { + // ignore malformed numstat + } + } + if (!"-".equals(parts[1])) { + try { + deleted += Long.parseLong(parts[1]); + } catch (NumberFormatException ignore) { + // ignore malformed numstat + } + } + } + + return new DiffStats(added, deleted); + } + + private List summarizeFocusModules(DateWindow window) { + String since = formatGitTime(window.sinceInclusive()); + String until = formatGitTime(window.untilExclusive()); + + List lines = runGit(List.of( + "git", "log", "--all", + "--since=" + since, + "--until=" + until, + "--no-merges", + "--name-only", + "--pretty=format:" + )); + + Map moduleCounts = new HashMap<>(); + for (String line : lines) { + String filePath = line == null ? "" : line.trim(); + if (filePath.isEmpty()) { + continue; + } + String module = toModuleName(filePath); + moduleCounts.merge(module, 1L, Long::sum); + } + + return moduleCounts.entrySet().stream() + .map(e -> new ModuleCount(e.getKey(), e.getValue())) + .sorted(Comparator.comparingLong(ModuleCount::count).reversed() + .thenComparing(ModuleCount::module)) + .limit(3) + .toList(); + } + + private String toModuleName(String filePath) { + String normalized = filePath.replace('\\', '/'); + int idx = normalized.indexOf('/'); + if (idx < 0) { + return normalized; + } + String top = normalized.substring(0, idx); + if ("robot-web-front".equals(top) || "robot-web-service".equals(top) || "docs".equals(top)) { + return top; + } + return top; + } + + private List summarizeThemes(List commits) { + Map statMap = new LinkedHashMap<>(); + for (ThemeRule rule : THEME_RULES) { + statMap.put(rule.name(), new ThemeStatBuilder(rule.name())); + } + ThemeStatBuilder other = new ThemeStatBuilder("综合修复与其他"); + + for (CommitRecord commit : commits) { + String subject = commit.subject(); + boolean matched = false; + for (ThemeRule rule : THEME_RULES) { + if (rule.pattern().matcher(subject).find()) { + statMap.get(rule.name()).addSample(subject); + matched = true; + break; + } + } + if (!matched) { + other.addSample(subject); + } + } + + List top = statMap.values().stream() + .filter(v -> v.count > 0) + .map(ThemeStatBuilder::build) + .sorted(Comparator.comparingLong(ThemeStat::count).reversed()) + .limit(5) + .collect(Collectors.toCollection(ArrayList::new)); + + ThemeStat otherStat = other.build(); + if (otherStat.count() > 0 && top.size() < 5) { + top.add(otherStat); + } + + return top; + } + + private List summarizeKeywords(List commits) { + List counts = new ArrayList<>(); + for (KeywordRule rule : KEYWORD_RULES) { + long count = commits.stream() + .map(CommitRecord::subject) + .filter(subject -> rule.pattern().matcher(subject).find()) + .count(); + if (count > 0) { + counts.add(new KeywordCount(rule.name(), count)); + } + } + + return counts.stream() + .sorted(Comparator.comparingLong(KeywordCount::count).reversed()) + .limit(8) + .toList(); + } + + private long countActiveBranches(List authorStats) { + Set branches = new LinkedHashSet<>(); + for (AuthorBranchStat author : authorStats) { + for (BranchCount branch : author.branches()) { + branches.add(branch.branch()); + } + } + return branches.size(); + } + + private String formatGitTime(LocalDateTime time) { + return time.format(GIT_TIME_FMT); + } + + private String normalizeBranchName(String raw) { + if (!StringUtils.hasText(raw)) { + return "-"; + } + String branch = raw.trim(); + if (branch.startsWith("origin/")) { + return branch.substring("origin/".length()); + } + return branch; + } + + private List runGit(List command) { + Path repoDir = resolveRepoPath(); + if (!Files.exists(repoDir.resolve(".git"))) { + throw new IllegalStateException("未找到 Git 仓库,请配置 robot.weekly-report.repo-path"); + } + + List actualCommand = withSafeDirectory(command, repoDir); + ProcessBuilder pb = new ProcessBuilder(actualCommand); + pb.directory(repoDir.toFile()); + + try { + Process process = pb.start(); + List lines; + try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) { + lines = reader.lines().toList(); + } + + String err; + try (BufferedReader errReader = new BufferedReader(new InputStreamReader(process.getErrorStream(), StandardCharsets.UTF_8))) { + err = errReader.lines().collect(Collectors.joining("\n")); + } + + int code = process.waitFor(); + if (code != 0) { + throw new IllegalStateException(String.format("git 命令执行失败: %s, err=%s", String.join(" ", actualCommand), err)); + } + return lines; + } catch (Exception e) { + throw new IllegalStateException("读取 git 记录失败", e); + } + } + + private List withSafeDirectory(List command, Path repoDir) { + if (command == null || command.isEmpty()) { + return command; + } + if (!"git".equals(command.get(0))) { + return command; + } + + List cmd = new ArrayList<>(); + cmd.add("git"); + cmd.add("-c"); + cmd.add("safe.directory=" + repoDir.toString()); + cmd.addAll(command.subList(1, command.size())); + return cmd; + } + + private Path resolveRepoPath() { + if (configuredRepoPath != null && !configuredRepoPath.isBlank()) { + Path configured = Paths.get(configuredRepoPath).toAbsolutePath().normalize(); + if (Files.exists(configured.resolve(".git"))) { + return configured; + } + log.warn("robot.weekly-report.repo-path 未找到 .git: {}", configured); + } + + Path current = Paths.get("").toAbsolutePath().normalize(); + Path cursor = current; + while (cursor != null) { + if (Files.exists(cursor.resolve(".git"))) { + return cursor; + } + cursor = cursor.getParent(); + } + return current; + } + + private record DateWindow(LocalDate startDate, + LocalDate endDate, + LocalDateTime sinceInclusive, + LocalDateTime untilExclusive) { + } + + private record CommitRecord(String hash, String author, String subject) { + } + + private record ThemeRule(String name, Pattern pattern) { + } + + private record KeywordRule(String name, Pattern pattern) { + } + + private record BranchCount(String branch, long count) { + } + + private record ModuleCount(String module, long count) { + } + + private record DiffStats(long addedLines, long deletedLines) { + } + + private record AuthorBranchStat(String author, long totalCommits, List branches) { + } + + private record AuthorContribution(String author, int commitCount, List outputs) { + } + + private record ThemeStat(String name, long count, List samples) { + } + + private record KeywordCount(String keyword, long count) { + } + + private record WeeklyReportData(LocalDate startDate, + LocalDate endDate, + int commitCount, + int mergeCommitCount, + long addedLines, + long deletedLines, + Set authors, + long activeBranchCount, + List focusModules, + List authorContributions, + List themeStats, + List keywordCounts) { + } + + private static class ThemeStatBuilder { + private final String name; + private long count; + private final List samples = new ArrayList<>(); + + private ThemeStatBuilder(String name) { + this.name = name; + } + + private void addSample(String sample) { + this.count++; + if (samples.size() < 2) { + samples.add(truncate(sample, 80)); + } + } + + private ThemeStat build() { + return new ThemeStat(name, count, samples); + } + + private static String truncate(String value, int maxLen) { + if (value == null) { + return "-"; + } + return value.length() > maxLen ? value.substring(0, maxLen) + "..." : value; + } + } +} diff --git a/back/src/main/resources/application.yml b/back/src/main/resources/application.yml new file mode 100644 index 0000000..9118ce9 --- /dev/null +++ b/back/src/main/resources/application.yml @@ -0,0 +1,352 @@ +server: + port: 8081 + +spring: + application: + name: robot-web-service + + # 文件上传配置 + servlet: + multipart: + max-file-size: 100MB + max-request-size: 100MB + + # MySQL 数据库配置 + datasource: + url: jdbc:mysql://host.docker.internal:3307/linkwork?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8 + username: robot + password: ${MYSQL_PASSWORD:} + driver-class-name: com.mysql.cj.jdbc.Driver + hikari: + minimum-idle: 5 + maximum-pool-size: 20 + idle-timeout: 30000 + connection-timeout: 30000 + + # Redis 配置(通过 Docker 网络直连 momo-redis 容器) + data: + redis: + host: momo-redis + port: 6379 + +# MyBatis Plus 配置 +mybatis-plus: + mapper-locations: classpath:/mapper/**/*.xml + configuration: + map-underscore-to-camel-case: true + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + global-config: + db-config: + id-type: assign_id + logic-delete-field: isDeleted + logic-delete-value: true + logic-not-delete-value: false + +logging: + level: + com.momo.robot: DEBUG + org.springframework.web: INFO + com.baomidou.mybatisplus: DEBUG + +# 认证配置 +robot: + litellm: + base-url: http://172.18.228.32:4000 + api-key: sk-48sbQpOd2cCeXW1q3H4TBQ + default-chat-model: openrouter/anthropic/claude-sonnet-4.5 + auth: + # 访问密码(BCrypt 哈希) + password: ${ROBOT_AUTH_PASSWORD:} + # JWT 密钥(至少 32 字符) + jwt-secret: ${ROBOT_AUTH_JWT_SECRET:} + # JWT 过期时间(毫秒),默认 24 小时 + jwt-expiration: 86400000 + # 任务调度配置(与 worker 联调对齐) + dispatch: + workstation-id: ${DISPATCH_WORKSTATION_ID:test-post-001} + log-stream-prefix: logs + approval-key-prefix: approval + cron: + enabled: ${CRON_ENABLED:true} + scan-interval-ms: ${CRON_SCAN_INTERVAL_MS:60000} + dispatch-lead-ms: ${CRON_DISPATCH_LEAD_MS:180000} + max-jobs-per-user: ${CRON_MAX_JOBS_PER_USER:50} + max-jobs-per-role: ${CRON_MAX_JOBS_PER_ROLE:100} + max-runs-per-job: ${CRON_MAX_RUNS_PER_JOB:100} + lock-key: ${CRON_LOCK_KEY:lock:cron:scanner} + lock-ttl-seconds: ${CRON_LOCK_TTL_SECONDS:55} + gitlab: + base-url: ${GITLAB_BASE_URL:https://git.wemomo.com} + auth-base-url: ${GITLAB_AUTH_BASE_URL:${robot.gitlab.base-url}} + client-id: ${GITLAB_CLIENT_ID:afc81060f89b3e664bbaa2772baf399f232ba4a207b0a4f995d5f82788acdc08} + client-secret: ${GITLAB_CLIENT_SECRET:} + redirect-uri: ${GITLAB_REDIRECT_URI:http://172.18.228.32:3003/auth/callback/gitlab} + zzd: + # zzd 服务调用 /api/v1/tasks/{taskId}/git-token 时使用的服务身份令牌 + api-server-token: ${ZZD_API_SERVER_TOKEN:} + output-estimator: + llm: + enabled: ${TASK_OUTPUT_ESTIMATOR_ENABLED:true} + gateway-url: ${TASK_OUTPUT_ESTIMATOR_GATEWAY_URL:${robot.litellm.base-url}/v1/chat/completions} + model: ${TASK_OUTPUT_ESTIMATOR_MODEL:${robot.litellm.default-chat-model}} + max-tokens: ${TASK_OUTPUT_ESTIMATOR_MAX_TOKENS:256} + stream: ${TASK_OUTPUT_ESTIMATOR_STREAM:true} + connect-timeout-ms: ${TASK_OUTPUT_ESTIMATOR_CONNECT_TIMEOUT_MS:3000} + read-timeout-ms: ${TASK_OUTPUT_ESTIMATOR_READ_TIMEOUT_MS:12000} + # 固定 LiteLLM Virtual Key(sk- 开头) + auth-token: ${TASK_OUTPUT_ESTIMATOR_AUTH_TOKEN:${robot.litellm.api-key}} + x-litellm-api-key: ${TASK_OUTPUT_ESTIMATOR_X_LITELLM_API_KEY:${robot.litellm.api-key}} + model-registry: + gateway-url: ${MODEL_REGISTRY_GATEWAY_URL:${robot.litellm.base-url}/v1/models} + timeout-ms: ${MODEL_REGISTRY_TIMEOUT_MS:5000} + auth-token: ${MODEL_REGISTRY_AUTH_TOKEN:${robot.litellm.api-key}} + x-litellm-api-key: ${MODEL_REGISTRY_X_LITELLM_API_KEY:${robot.litellm.api-key}} + billing: + gateway-url-template: ${BILLING_GATEWAY_URL_TEMPLATE:http://ai-agent-gateway.momo.com/v1/billing/tasks/{taskId}} + timeout-ms: ${BILLING_TIMEOUT_MS:3000} + sync-retries: ${BILLING_SYNC_RETRIES:3} + retry-interval-ms: ${BILLING_RETRY_INTERVAL_MS:200} + task-share: + secret: ${TASK_SHARE_SECRET:${robot.auth.jwt-secret}} + base-url: ${TASK_SHARE_BASE_URL:https://robot.wemomo.com} + default-expire-hours: ${TASK_SHARE_DEFAULT_EXPIRE_HOURS:24} + max-expire-hours: ${TASK_SHARE_MAX_EXPIRE_HOURS:168} + skills: + repo-url: ${SKILLS_REPO_URL:} + deploy-token: ${SKILLS_DEPLOY_TOKEN:} + project-id: ${SKILLS_PROJECT_ID:100001744} + weekly-report: + repo-path: ${WEEKLY_REPORT_REPO_PATH:} + llm: + enabled: ${WEEKLY_REPORT_LLM_ENABLED:true} + gateway-url: ${WEEKLY_REPORT_LLM_GATEWAY_URL:${robot.litellm.base-url}/v1/chat/completions} + model: ${WEEKLY_REPORT_LLM_MODEL:${robot.litellm.default-chat-model}} + max-tokens: ${WEEKLY_REPORT_LLM_MAX_TOKENS:900} + stream: ${WEEKLY_REPORT_LLM_STREAM:true} + connect-timeout-ms: ${WEEKLY_REPORT_LLM_CONNECT_TIMEOUT_MS:3000} + read-timeout-ms: ${WEEKLY_REPORT_LLM_READ_TIMEOUT_MS:12000} + auth-token: ${WEEKLY_REPORT_LLM_AUTH_TOKEN:${robot.litellm.api-key}} + x-litellm-api-key: ${WEEKLY_REPORT_LLM_X_LITELLM_API_KEY:${robot.litellm.api-key}} + +# 调度服务配置 +schedule: + cluster: + namespace: linkwork-dev + scheduler-name: volcano + kubeconfig-path: /root/.kube/config + + queue: + prefix: ai-worker + + images: + registry: docker.momo.com + # Agent 基础镜像(本地构建用,固定 digest 保证一致性) + agent: 10.30.107.146/robot/rockylinux9-agent@sha256:cb7593392021adc22da5e74721ed3d11414adffd722a51565a98a5442b3f2777 + # Runner 默认镜像(K8s 运行时使用内部仓库镜像) + runner: ${RUNNER_IMAGE:10.30.107.146/robot/rockylinux9-agent@sha256:cb7593392021adc22da5e74721ed3d11414adffd722a51565a98a5442b3f2777} + + # Agent 启动脚本配置(main.py 从链接下载,启动 agent+executor) + agent-bootstrap: + main-py-url: ${MAIN_PY_URL:} + + # 文件放置配置 + file-placement: + # token 文件(仅 executor 可访问) + token-path: /workspace/.credentials/token + token-dir-mode: "700" + token-file-mode: "600" + token-owner: executor:executor + # SSH 密钥(agent 和 executor 都可访问) + ssh-path: /workspace/.ssh + ssh-dir-mode: "755" + ssh-key-mode: "600" + ssh-pub-mode: "644" + + network: + api-base-url: http://api-gateway.ai-worker.svc:8080 + ws-base-url: ws://ws-gateway.ai-worker.svc:8080 + llm-gateway-url: http://llm-gateway.ai-worker.svc:8080 + redis-url: ${REDIS_URL:redis://10.136.55.3:6379} + + ssh: + port: 22 + key-type: ed25519 + + default-resources: + agent: + cpu-request: "1" + cpu-limit: "2" + memory-request: 2Gi + memory-limit: 4Gi + runner: + cpu-request: "1" + cpu-limit: "4" + memory-request: 2Gi + memory-limit: 8Gi + + # OSS 挂载配置(需配合节点级 ossfs 三路挂载) + # 节点级 ossfs 挂载: + # oss://robot-agent-files/system/ → {host-path}/system + # oss://robot-agent-files/user-files/ → {host-path}/user-files + # oss://robot-agent-files/workstation/ → {host-path}/workstation + oss-mount: + enabled: ${OSS_MOUNT_ENABLED:true} + host-path: /mnt/oss/robot-agent-files # 三个 ossfs 挂载点的公共父目录 + mount-path: /data/oss/robot # 容器内产出物挂载根 (system/{wsId}) + read-only: false # 产出物挂载是否只读 + user-files-mount-path: /mnt/user-files # 容器内个人空间挂载根 + workstation-mount-path: /mnt/workstation # 容器内岗位空间挂载根 + + pod-mode-rules: + default-mode: SIDECAR + overrides: + data_analysis: ALONE + ops: SIDECAR + development: SIDECAR + +# 镜像构建配置 +# 注意:仅构建 Agent 镜像,Runner 由运行时 agent 启动 +image-build: + # 是否启用镜像构建阶段(true: 构建新镜像, false: 直接使用基础镜像) + enabled: true + + # 是否推送镜像到仓库(true: 构建后推送, false: 只构建不推送) + push-enabled: true + + # 镜像拉取策略 + # - Always: 总是从仓库拉取 + # - IfNotPresent: 本地有则不拉取 + # - Never: 从不拉取,要求镜像必须已在节点上 + image-pull-policy: ${IMAGE_PULL_POLICY:IfNotPresent} + + # Docker 连接配置 + docker-host: ${DOCKER_HOST:unix:///var/run/docker.sock} + + # 默认 Agent 基础镜像(K8s 模式,固定 digest 保证一致性) + default-agent-base-image: ${DEFAULT_AGENT_BASE_IMAGE:10.30.107.146/robot/rockylinux9-agent@sha256:cb7593392021adc22da5e74721ed3d11414adffd722a51565a98a5442b3f2777} + + # Compose 模式基础镜像(用户本地构建使用) + compose-base-image: ${COMPOSE_BASE_IMAGE:docker.momo.com/osbase/rockylinux9:9.4} + + # 镜像仓库配置 + # K8s 模式下构建的镜像会推送到此仓库 + # 注意:直接配置,不依赖环境变量(避免环境变量为空时覆盖默认值) + registry: 10.30.107.146/robot + registry-username: ${IMAGE_REGISTRY_USERNAME:} + registry-password: ${IMAGE_REGISTRY_PASSWORD:} + + # K8s 拉取私有镜像的 Secret 名称 + image-pull-secret: ${IMAGE_PULL_SECRET:robot-registry-secret} + + # 构建脚本路径(用户补充内容) + build-script-path: ${BUILD_SCRIPT_PATH:/opt/scripts/build.sh} + + # 入口点脚本 + entrypoint-script: /entrypoint.sh + + # 构建超时时间(秒)- build.sh 含 dnf 安装,首次构建需要较长时间 + build-timeout: ${BUILD_TIMEOUT:900} + + # 构建上下文临时目录 + build-context-dir: ${BUILD_CONTEXT_DIR:/tmp/docker-build} + + # SDK 仓库配置(镜像构建阶段克隆,拷贝 momo-agent-sdk 和 docker/agent/zzd 到镜像中) + sdk-repo-url: ${SDK_REPO_URL:} + sdk-repo-branch: ${SDK_REPO_BRANCH:} + sdk-repo-username: ${SDK_REPO_USERNAME:} + sdk-repo-password: ${SDK_REPO_PASSWORD:} + sdk-source-path: ${SDK_SOURCE_PATH:/opt/momo-agent-build/sdk-source} + zzd-binaries-path: ${ZZD_BINARIES_PATH:/opt/momo-agent-build/zzd-binaries} + +# NFS 存储配置(替代原 OSS) +# 后续更换 NFS 服务器地址只需重新 mount + 修改 NFS_STORAGE_BASE_PATH 环境变量,无需改代码 +nfs: + storage: + base-path: ${NFS_STORAGE_BASE_PATH:/mnt/oss/robot-agent-files} + download-base-url: /api/v1/files + task-output-base-url: /api/v1/task-outputs + +# Memory 服务配置 +memory: + enabled: ${MEMORY_ENABLED:true} + milvus: + uri: ${MILVUS_URI:http://milvus:19530} + token: ${MILVUS_TOKEN:} + embedding: + model: ${MEMORY_EMBEDDING_MODEL:text-embedding-3-small} + dimension: ${MEMORY_EMBEDDING_DIMENSION:1536} + index: + max-chunk-size: 1500 + overlap-lines: 2 + queue-key: memory:index:jobs + oss-mount-path: ${MEMORY_OSS_MOUNT_PATH:/data/oss} + +# 构建队列配置 +build-queue: + # 是否启用队列功能(false 时直接异步执行,兼容旧行为) + enabled: true + # CPU 使用率阈值 (0.0-1.0),低于此值才允许新构建 + cpu-threshold: ${BUILD_QUEUE_CPU_THRESHOLD:0.7} + # 内存使用率阈值 (0.0-1.0),低于此值才允许新构建 + memory-threshold: ${BUILD_QUEUE_MEMORY_THRESHOLD:0.7} + # 硬性并发上限(即使资源充足也不超过此值) + max-concurrent: ${BUILD_QUEUE_MAX_CONCURRENT:2} + # 队列最大容量 + max-queue-size: ${BUILD_QUEUE_MAX_SIZE:50} + # 构建超时时间(秒) + task-timeout: ${BUILD_QUEUE_TASK_TIMEOUT:1200} + # 资源检查间隔(毫秒) + check-interval: ${BUILD_QUEUE_CHECK_INTERVAL:1000} + # 残留文件清理阈值(小时) + stale-context-hours: ${BUILD_QUEUE_STALE_CONTEXT_HOURS:1} + +# LinkWork Starter 配置(统一 linkwork.agent.* 前缀) +linkwork: + agent: + storage: + enabled: ${LINKWORK_AGENT_STORAGE_ENABLED:true} + provider: ${LINKWORK_AGENT_STORAGE_PROVIDER:nfs} + nfs: + base-path: ${LINKWORK_AGENT_STORAGE_NFS_BASE_PATH:${nfs.storage.base-path:/mnt/oss/robot-agent-files}} + mount-path: ${LINKWORK_AGENT_STORAGE_NFS_MOUNT_PATH:/workspace} + read-only: ${LINKWORK_AGENT_STORAGE_NFS_READ_ONLY:false} + uid: ${LINKWORK_AGENT_STORAGE_NFS_UID:1000} + gid: ${LINKWORK_AGENT_STORAGE_NFS_GID:1000} + skill: + enabled: ${LINKWORK_AGENT_SKILL_ENABLED:true} + provider: ${LINKWORK_AGENT_SKILL_PROVIDER:gitlab} + retry-times: ${LINKWORK_AGENT_SKILL_RETRY_TIMES:2} + retry-backoff-ms: ${LINKWORK_AGENT_SKILL_RETRY_BACKOFF_MS:200} + cache-ttl-ms: ${LINKWORK_AGENT_SKILL_CACHE_TTL_MS:10000} + gitlab: + url: ${LINKWORK_AGENT_SKILL_GITLAB_URL:${robot.gitlab.base-url:https://git.wemomo.com}} + token: ${LINKWORK_AGENT_SKILL_GITLAB_TOKEN:} + repo-url: ${LINKWORK_AGENT_SKILL_GITLAB_REPO_URL:${robot.skills.repo-url:}} + deploy-token: ${LINKWORK_AGENT_SKILL_GITLAB_DEPLOY_TOKEN:${robot.skills.deploy-token:}} + project-id: ${LINKWORK_AGENT_SKILL_GITLAB_PROJECT_ID:${robot.skills.project-id:}} + branch: ${LINKWORK_AGENT_SKILL_GITLAB_BRANCH:main} + root-path: ${LINKWORK_AGENT_SKILL_GITLAB_ROOT_PATH:skills} + mcp: + enabled: ${LINKWORK_AGENT_MCP_ENABLED:true} + mode: ${LINKWORK_AGENT_MCP_MODE:gateway} + gateway: + agent-base-url: ${LINKWORK_AGENT_MCP_GATEWAY_AGENT_BASE_URL:http://linkwork-mcp-gateway.linkwork-mcp-gateway.svc.cluster.local:9080} + proxy-base-url: ${LINKWORK_AGENT_MCP_GATEWAY_PROXY_BASE_URL:http://192.168.100.1:38090} + client: + connect-timeout-ms: ${LINKWORK_AGENT_MCP_CONNECT_TIMEOUT_MS:5000} + read-timeout-ms: ${LINKWORK_AGENT_MCP_READ_TIMEOUT_MS:35000} + security: + encryption-key: ${LINKWORK_AGENT_MCP_ENCRYPTION_KEY:${robot.mcp.encryption-key:}} + sandbox: + provider: ${LINKWORK_AGENT_SANDBOX_PROVIDER:${LINKWORK_SANDBOX_PROVIDER:k8s-volcano}} + k8s: + namespace: ${LINKWORK_AGENT_SANDBOX_K8S_NAMESPACE:${K8S_NAMESPACE:linkwork-dev}} + scheduler-name: ${LINKWORK_AGENT_SANDBOX_K8S_SCHEDULER_NAME:${schedule.cluster.scheduler-name:volcano}} + queue-name: ${LINKWORK_AGENT_SANDBOX_K8S_QUEUE_NAME:default} + kubeconfig-path: ${LINKWORK_AGENT_SANDBOX_K8S_KUBECONFIG_PATH:${KUBECONFIG_PATH:/root/.kube/config}} + default-agent-image: ${LINKWORK_AGENT_SANDBOX_K8S_DEFAULT_AGENT_IMAGE:${schedule.images.agent:}} + default-runner-image: ${LINKWORK_AGENT_SANDBOX_K8S_DEFAULT_RUNNER_IMAGE:${schedule.images.runner:}} + default-image-pull-policy: ${LINKWORK_AGENT_SANDBOX_K8S_IMAGE_PULL_POLICY:${image-build.image-pull-policy:IfNotPresent}} + registry: ${LINKWORK_AGENT_SANDBOX_K8S_REGISTRY:${image-build.registry:}} + registry-username: ${LINKWORK_AGENT_SANDBOX_K8S_REGISTRY_USERNAME:${image-build.registry-username:}} + registry-password: ${LINKWORK_AGENT_SANDBOX_K8S_REGISTRY_PASSWORD:${image-build.registry-password:}} diff --git a/back/src/main/resources/db/migration/V20260302_2__skill_visibility.sql b/back/src/main/resources/db/migration/V20260302_2__skill_visibility.sql new file mode 100644 index 0000000..4d6e20a --- /dev/null +++ b/back/src/main/resources/db/migration/V20260302_2__skill_visibility.sql @@ -0,0 +1,41 @@ +-- Skill 可见性控制:支持用户隔离 + 公开 Skill + +SET @column_exists = ( + SELECT COUNT(1) + FROM information_schema.columns + WHERE table_schema = DATABASE() + AND table_name = 'linkwork_skill' + AND column_name = 'is_public' +); + +SET @add_column_sql = IF( + @column_exists = 0, + 'ALTER TABLE linkwork_skill ADD COLUMN is_public TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''是否公开'' AFTER status', + 'SELECT 1' +); +PREPARE add_column_stmt FROM @add_column_sql; +EXECUTE add_column_stmt; +DEALLOCATE PREPARE add_column_stmt; + +-- 历史系统技能(无创建人)默认转公开,避免升级后不可见 +UPDATE linkwork_skill +SET is_public = 1 +WHERE (creator_id IS NULL OR creator_id = '') + AND is_deleted = 0; + +SET @index_exists = ( + SELECT COUNT(1) + FROM information_schema.statistics + WHERE table_schema = DATABASE() + AND table_name = 'linkwork_skill' + AND index_name = 'idx_skill_visibility' +); + +SET @add_index_sql = IF( + @index_exists = 0, + 'ALTER TABLE linkwork_skill ADD INDEX idx_skill_visibility (is_deleted, status, is_public, creator_id)', + 'SELECT 1' +); +PREPARE add_index_stmt FROM @add_index_sql; +EXECUTE add_index_stmt; +DEALLOCATE PREPARE add_index_stmt; diff --git a/back/src/main/resources/db/migration/V20260309__mcp_gateway.sql b/back/src/main/resources/db/migration/V20260309__mcp_gateway.sql new file mode 100644 index 0000000..37f30db --- /dev/null +++ b/back/src/main/resources/db/migration/V20260309__mcp_gateway.sql @@ -0,0 +1,32 @@ +-- MCP Gateway 迁移: 新增 network_zone 字段 + 用户凭证表 + 使用量日汇总表 + +-- 1. linkwork_mcp_server 表新增 network_zone 字段 +ALTER TABLE linkwork_mcp_server + ADD COLUMN network_zone VARCHAR(20) NOT NULL DEFAULT 'external' + COMMENT '网段标记: internal(服务器内网), office(办公网), external(外部互联网)' + AFTER health_check_url; + +-- 2. 用户个人 MCP 凭证表 +CREATE TABLE IF NOT EXISTS linkwork_mcp_user_config ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + user_id VARCHAR(64) NOT NULL COMMENT '用户 ID', + mcp_server_id BIGINT NOT NULL COMMENT 'MCP Server ID (FK linkwork_mcp_server.id)', + headers JSON COMMENT '用户个人 Headers (加密存储的 JSON)', + url_params JSON COMMENT '用户个人 URL 参数', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + is_deleted TINYINT(1) DEFAULT 0, + UNIQUE KEY uk_user_mcp (user_id, mcp_server_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户 MCP 个人凭证配置'; + +-- 3. MCP 使用量日汇总表 +CREATE TABLE IF NOT EXISTS linkwork_mcp_usage_daily ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + date DATE NOT NULL COMMENT '统计日期', + user_id VARCHAR(64) NOT NULL COMMENT '用户 ID', + mcp_name VARCHAR(128) NOT NULL COMMENT 'MCP Server 名称', + call_count INT NOT NULL DEFAULT 0 COMMENT '调用次数', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_date_user_mcp (date, user_id, mcp_name) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MCP 使用量日汇总'; diff --git a/back/src/main/resources/db/migration/V20260311__fix_robot_mcp_usage_daily_columns.sql b/back/src/main/resources/db/migration/V20260311__fix_robot_mcp_usage_daily_columns.sql new file mode 100644 index 0000000..81cb351 --- /dev/null +++ b/back/src/main/resources/db/migration/V20260311__fix_robot_mcp_usage_daily_columns.sql @@ -0,0 +1,4 @@ +-- 补齐 MCP 使用量日汇总表缺失列,兼容已存在的旧表结构 +ALTER TABLE linkwork_mcp_usage_daily + ADD COLUMN IF NOT EXISTS req_bytes BIGINT NOT NULL DEFAULT 0 COMMENT '请求字节数', + ADD COLUMN IF NOT EXISTS resp_bytes BIGINT NOT NULL DEFAULT 0 COMMENT '响应字节数'; diff --git a/back/src/main/resources/scripts/config.json b/back/src/main/resources/scripts/config.json new file mode 100644 index 0000000..edc0658 --- /dev/null +++ b/back/src/main/resources/scripts/config.json @@ -0,0 +1,30 @@ +{ + "claude_settings": { + "model": "openrouter/anthropic/claude-sonnet-4.5", + "language": "Chinese", + "env": { + "ANTHROPIC_BASE_URL": "http://172.18.228.32:4000", + "ANTHROPIC_AUTH_TOKEN": "sk-48sbQpOd2cCeXW1q3H4TBQ", + "ANTHROPIC_API_KEY": "sk-48sbQpOd2cCeXW1q3H4TBQ", + "LITELLM_BASE_URL": "http://172.18.228.32:4000", + "LITELLM_API_KEY": "sk-48sbQpOd2cCeXW1q3H4TBQ", + "API_TIMEOUT_MS": "600000", + "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1" + } + }, + "agent": { + "name": "test-employee", + "max_turns": 50, + "max_thinking_tokens": 5000, + "permission_mode": "bypassPermissions", + "allowed_tools": [], + "disallowed_tools": [], + "can_use_tools": [], + "zz_enabled": true + }, + "system_prompt": { + "use_preset": true, + "preset": "claude_code", + "append": "强制规则(不可违反):\n1) 开始前必须先读取 `AGENTS.md`;未读取=任务失败。\n2) 目录与产出规则只以 `AGENTS.md` 为准。\n3) 最终交付只能写入 `/workspace/workstation`;`/workspace/logs` 仅中间文件。\n4) 禁止只回复分析不落地文件;至少产出 1 个最终文件。\n5) 若任一规则无法满足,立即报错并停止,禁止伪完成。" + } +} From bf61e2fd6d0dae190b5c394506550d858c882c9b Mon Sep 17 00:00:00 2001 From: wangfenghe Date: Fri, 20 Mar 2026 11:17:22 +0800 Subject: [PATCH 02/21] chore(back): include starter dependencies in pom --- back/pom.xml | 235 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 back/pom.xml diff --git a/back/pom.xml b/back/pom.xml new file mode 100644 index 0000000..212a8b7 --- /dev/null +++ b/back/pom.xml @@ -0,0 +1,235 @@ + + + 4.0.0 + + com.momo + robot-web-service + 1.0.0-SNAPSHOT + jar + + Robot Web Service + AI 员工执行环境 - 后端 Web 服务 + + + org.springframework.boot + spring-boot-starter-parent + 3.2.5 + + + + + 21 + 0.1.0-SNAPSHOT + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-websocket + + + + + com.fasterxml.jackson.core + jackson-databind + + + + + org.projectlombok + lombok + 1.18.36 + true + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + com.mysql + mysql-connector-j + runtime + + + + + com.baomidou + mybatis-plus-spring-boot3-starter + 3.5.5 + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + io.jsonwebtoken + jjwt-api + 0.12.5 + + + io.jsonwebtoken + jjwt-impl + 0.12.5 + runtime + + + io.jsonwebtoken + jjwt-jackson + 0.12.5 + runtime + + + + + org.springframework.security + spring-security-crypto + + + + + io.fabric8 + kubernetes-client + 6.10.0 + + + + + io.linkwork + linkwork-k8s-starter + ${linkwork.server.version} + + + io.linkwork + linkwork-storage-starter + ${linkwork.server.version} + + + io.linkwork + linkwork-skill-starter + ${linkwork.server.version} + + + io.linkwork + linkwork-mcp-starter + ${linkwork.server.version} + + + + + com.github.docker-java + docker-java-core + 3.3.4 + + + + com.github.docker-java + docker-java-transport-zerodep + 3.3.4 + + + + + com.google.guava + guava + 32.1.3-jre + + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + + + + + io.milvus + milvus-sdk-java + 2.5.15 + + + + + org.apache.tika + tika-core + 3.2.3 + + + org.apache.tika + tika-parsers-standard-package + 3.2.3 + + + org.apache.commons + commons-compress + 1.27.1 + + + + commons-io + commons-io + 2.16.1 + + + org.apache.commons + commons-lang3 + 3.16.0 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + 21 + 21 + + + org.projectlombok + lombok + 1.18.36 + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + From c1a66c9de8a7fe66300ded5890934222eaa62445 Mon Sep 17 00:00:00 2001 From: wangfenghe Date: Fri, 20 Mar 2026 13:59:26 +0800 Subject: [PATCH 03/21] feat(back): add missing linkwork backend sources for dev_0.2 build --- .../com/linkwork/LinkWorkApplication.java | 18 + .../java/com/linkwork/common/ApiResponse.java | 50 + .../com/linkwork/common/ClientIpResolver.java | 55 + .../common/FileConflictException.java | 37 + .../common/ForbiddenOperationException.java | 11 + .../common/GlobalExceptionHandler.java | 100 + .../common/ResourceNotFoundException.java | 11 + .../linkwork/common/SnowflakeIdGenerator.java | 122 ++ .../java/com/linkwork/config/AsyncConfig.java | 36 + .../com/linkwork/config/BuildQueueConfig.java | 71 + .../java/com/linkwork/config/CronConfig.java | 19 + .../com/linkwork/config/DispatchConfig.java | 118 ++ .../java/com/linkwork/config/EnvConfig.java | 157 ++ .../com/linkwork/config/ImageBuildConfig.java | 127 ++ .../com/linkwork/config/KubernetesConfig.java | 47 + .../com/linkwork/config/MemoryConfig.java | 48 + .../linkwork/config/MyBatisPlusConfig.java | 47 + .../com/linkwork/config/NfsStorageConfig.java | 40 + .../com/linkwork/config/WebSocketConfig.java | 24 + .../com/linkwork/context/UserContext.java | 61 + .../java/com/linkwork/context/UserInfo.java | 36 + .../controller/ApprovalController.java | 116 ++ .../linkwork/controller/AuthController.java | 135 ++ .../controller/BuildLogController.java | 174 ++ .../controller/BuildQueueController.java | 106 + .../controller/BuildRecordController.java | 121 ++ .../controller/CronJobController.java | 106 + .../linkwork/controller/FileController.java | 191 ++ .../controller/GitLabAuthController.java | 56 + .../linkwork/controller/HealthController.java | 20 + .../controller/ImageBuildController.java | 52 + .../controller/K8sClusterController.java | 150 ++ .../controller/McpInternalController.java | 91 + .../controller/McpServerController.java | 204 ++ .../controller/McpUserConfigController.java | 70 + .../linkwork/controller/MemoryController.java | 83 + .../controller/ModelRegistryController.java | 23 + .../controller/PublicTaskController.java | 63 + .../controller/ReportExportController.java | 103 + .../linkwork/controller/RoleController.java | 424 ++++ .../controller/ScheduleController.java | 319 +++ .../controller/SecurityPolicyController.java | 88 + .../linkwork/controller/SkillController.java | 265 +++ .../controller/StorageController.java | 31 + .../linkwork/controller/TaskController.java | 218 ++ .../controller/TaskOutputController.java | 235 +++ .../controller/UserSoulController.java | 50 + .../controller/WorkspaceController.java | 24 + .../filter/InternalApiAuditFilter.java | 50 + .../linkwork/filter/InternalApiIpFilter.java | 106 + .../com/linkwork/filter/JwtAuthFilter.java | 169 ++ .../com/linkwork/mapper/ApprovalMapper.java | 12 + .../linkwork/mapper/BuildRecordMapper.java | 12 + .../com/linkwork/mapper/CronJobMapper.java | 9 + .../com/linkwork/mapper/CronJobRunMapper.java | 9 + .../com/linkwork/mapper/FileNodeMapper.java | 9 + .../com/linkwork/mapper/McpServerMapper.java | 12 + .../linkwork/mapper/McpUsageDailyMapper.java | 9 + .../linkwork/mapper/McpUserConfigMapper.java | 9 + .../com/linkwork/mapper/RobotFileMapper.java | 9 + .../java/com/linkwork/mapper/RoleMapper.java | 9 + .../linkwork/mapper/SecurityPolicyMapper.java | 12 + .../java/com/linkwork/mapper/SkillMapper.java | 12 + .../linkwork/mapper/TaskGitAuthMapper.java | 9 + .../java/com/linkwork/mapper/TaskMapper.java | 12 + .../mapper/UserFavoriteRoleMapper.java | 9 + .../com/linkwork/mapper/UserSoulMapper.java | 9 + .../java/com/linkwork/model/FileNode.java | 20 + .../linkwork/model/dto/BuildQueueStatus.java | 102 + .../com/linkwork/model/dto/BuildTask.java | 159 ++ .../linkwork/model/dto/ClusterEventDTO.java | 18 + .../linkwork/model/dto/ClusterNodeInfo.java | 25 + .../model/dto/ClusterOverviewDTO.java | 23 + .../linkwork/model/dto/ClusterPodInfo.java | 25 + .../model/dto/ContainerStatusInfo.java | 21 + .../model/dto/CreateFolderRequest.java | 11 + .../linkwork/model/dto/CredentialConfig.java | 17 + .../model/dto/CronJobCreateRequest.java | 36 + .../linkwork/model/dto/CronJobResponse.java | 42 + .../model/dto/CronJobRunResponse.java | 27 + .../model/dto/CronJobToggleRequest.java | 10 + .../model/dto/CronJobUpdateRequest.java | 36 + .../model/dto/CronSchedulePreviewRequest.java | 15 + .../model/dto/FileMentionResponse.java | 16 + .../linkwork/model/dto/FileNodeResponse.java | 23 + .../com/linkwork/model/dto/FileResponse.java | 19 + .../model/dto/FileSpaceSyncRequest.java | 10 + .../model/dto/FileSpaceSyncResponse.java | 15 + .../model/dto/FileTransferRequest.java | 29 + .../com/linkwork/model/dto/GeneratedSpec.java | 19 + .../linkwork/model/dto/ImageBuildResult.java | 70 + .../linkwork/model/dto/McpDiscoverResult.java | 56 + .../linkwork/model/dto/McpProbeResult.java | 32 + .../model/dto/MemoryIndexBatchRequest.java | 12 + .../linkwork/model/dto/MemoryIndexJob.java | 31 + .../model/dto/MemoryIngestRequest.java | 11 + .../model/dto/MemorySearchRequest.java | 11 + .../com/linkwork/model/dto/MergedConfig.java | 139 ++ .../model/dto/PodGroupStatusInfo.java | 19 + .../linkwork/model/dto/PodLogResponseDTO.java | 14 + .../com/linkwork/model/dto/PodStatusInfo.java | 24 + .../model/dto/ReportExportFieldOption.java | 34 + .../model/dto/ReportExportFieldResponse.java | 26 + .../model/dto/ReportExportRequest.java | 41 + .../linkwork/model/dto/ResourceConfig.java | 14 + .../com/linkwork/model/dto/ResourceSpec.java | 20 + .../linkwork/model/dto/ResourceStatus.java | 75 + .../linkwork/model/dto/ResourceUsageInfo.java | 26 + .../com/linkwork/model/dto/ScaleRequest.java | 27 + .../com/linkwork/model/dto/ScaleResult.java | 95 + .../model/dto/ScheduleApiResponse.java | 48 + .../com/linkwork/model/dto/ScheduleEvent.java | 44 + .../model/dto/ServiceBuildRequest.java | 109 + .../model/dto/ServiceBuildResult.java | 193 ++ .../model/dto/ServiceResumeResult.java | 79 + .../linkwork/model/dto/ServiceSnapshot.java | 57 + .../model/dto/ServiceStatusResponse.java | 20 + .../com/linkwork/model/dto/StopResult.java | 30 + .../model/dto/TaskCompleteRequest.java | 80 + .../linkwork/model/dto/TaskCreateRequest.java | 39 + .../model/dto/TaskDispatchMessage.java | 79 + .../model/dto/TaskGitTokenResponse.java | 25 + .../com/linkwork/model/dto/TaskResponse.java | 95 + .../model/dto/TaskShareCreateRequest.java | 15 + .../model/dto/TaskShareLinkResponse.java | 19 + .../linkwork/model/dto/UserSoulResponse.java | 17 + .../model/dto/UserSoulUpsertRequest.java | 17 + .../model/dto/event/BuildEventData.java | 76 + .../model/dto/event/ContainerEventData.java | 34 + .../model/dto/event/EnvSetupData.java | 34 + .../model/dto/event/ImageEventData.java | 34 + .../model/dto/event/InitCompleteData.java | 53 + .../model/dto/event/PodSchedulingData.java | 41 + .../model/dto/event/SessionEventData.java | 46 + .../linkwork/model/enums/ConflictPolicy.java | 20 + .../model/enums/ContainerEventType.java | 117 ++ .../com/linkwork/model/enums/DeployMode.java | 9 + .../com/linkwork/model/enums/PodMode.java | 9 + .../com/linkwork/model/enums/ResumeType.java | 16 + .../com/linkwork/model/enums/ServiceType.java | 9 + .../linkwork/model/enums/SnapshotStatus.java | 16 + .../linkwork/model/enums/TaskOutputType.java | 133 ++ .../com/linkwork/model/enums/TaskStatus.java | 28 + .../linkwork/service/AdminAccessService.java | 33 + .../service/ApprovalRequestConsumer.java | 269 +++ .../com/linkwork/service/ApprovalService.java | 295 +++ .../com/linkwork/service/AuthService.java | 168 ++ .../com/linkwork/service/BuildExecutor.java | 211 ++ .../com/linkwork/service/BuildLogBuffer.java | 180 ++ .../linkwork/service/BuildQueueService.java | 329 +++ .../linkwork/service/BuildRecordService.java | 218 ++ .../linkwork/service/ConfigMergeService.java | 184 ++ .../service/CronExpressionHelper.java | 168 ++ .../com/linkwork/service/CronJobExecutor.java | 120 ++ .../com/linkwork/service/CronJobService.java | 519 +++++ .../com/linkwork/service/CronScheduler.java | 56 + .../service/DistributedLockService.java | 246 +++ .../service/DockerComposeGenerator.java | 115 ++ .../com/linkwork/service/FileNodeService.java | 398 ++++ .../linkwork/service/FileParseConsumer.java | 130 ++ .../com/linkwork/service/FileService.java | 934 +++++++++ .../service/FileSpaceSyncService.java | 167 ++ .../linkwork/service/GitLabAuthService.java | 435 ++++ .../linkwork/service/ImageBuildService.java | 923 +++++++++ .../linkwork/service/K8sClusterService.java | 450 +++++ .../com/linkwork/service/K8sOrchestrator.java | 78 + .../linkwork/service/K8sOrchestratorImpl.java | 1606 +++++++++++++++ .../linkwork/service/McpCryptoService.java | 185 ++ .../linkwork/service/McpDiscoveryService.java | 201 ++ .../linkwork/service/McpHealthChecker.java | 242 +++ .../linkwork/service/McpServerService.java | 643 ++++++ .../service/McpUserConfigService.java | 79 + .../linkwork/service/NfsStorageService.java | 163 ++ .../service/PodGroupSpecGenerator.java | 91 + .../linkwork/service/PodSpecGenerator.java | 761 +++++++ .../com/linkwork/service/QueueSelector.java | 36 + .../linkwork/service/ReportExportService.java | 475 +++++ .../linkwork/service/RoleHotRankService.java | 83 + .../com/linkwork/service/RoleService.java | 491 +++++ .../linkwork/service/RuntimeModeService.java | 187 ++ .../service/ScheduleEventPublisher.java | 377 ++++ .../service/SecurityPolicyService.java | 186 ++ .../service/ServiceResumeService.java | 100 + .../linkwork/service/ServiceScaleService.java | 506 +++++ .../service/ServiceScheduleService.java | 953 +++++++++ .../service/ServiceSnapshotService.java | 364 ++++ .../linkwork/service/SkillGitLabService.java | 248 +++ .../com/linkwork/service/SkillService.java | 704 +++++++ .../linkwork/service/SnapshotSyncTask.java | 233 +++ .../service/SystemResourceMonitor.java | 142 ++ .../service/TaskBillingUsageService.java | 149 ++ .../service/TaskDispatchConsumer.java | 198 ++ .../service/TaskEventBroadcaster.java | 55 + .../service/TaskEventConsumerService.java | 291 +++ .../linkwork/service/TaskGitAuthService.java | 81 + .../linkwork/service/TaskGitTokenService.java | 106 + .../TaskOutputWorkspaceSyncService.java | 835 ++++++++ .../service/TaskPathlistSyncService.java | 263 +++ .../com/linkwork/service/TaskService.java | 1774 +++++++++++++++++ .../service/TaskShareLinkService.java | 166 ++ .../service/TaskStatusSyncService.java | 500 +++++ .../com/linkwork/service/UserSoulService.java | 173 ++ .../linkwork/service/WorkspaceService.java | 54 + .../service/ZzdServiceAuthService.java | 42 + .../service/memory/DocumentParserService.java | 65 + .../service/memory/EmbeddingService.java | 87 + .../service/memory/MemoryIndexConsumer.java | 95 + .../service/memory/MemoryService.java | 248 +++ .../service/memory/MilvusStoreService.java | 397 ++++ .../service/memory/TextChunkerService.java | 148 ++ .../websocket/TaskWebSocketHandler.java | 432 ++++ .../main/resources/scripts/00-platform.cedar | 104 + back/src/main/resources/scripts/build.sh | 1584 +++++++++++++++ .../main/resources/scripts/start-runner.sh | 241 +++ 214 files changed, 32146 insertions(+) create mode 100644 back/src/main/java/com/linkwork/LinkWorkApplication.java create mode 100644 back/src/main/java/com/linkwork/common/ApiResponse.java create mode 100644 back/src/main/java/com/linkwork/common/ClientIpResolver.java create mode 100644 back/src/main/java/com/linkwork/common/FileConflictException.java create mode 100644 back/src/main/java/com/linkwork/common/ForbiddenOperationException.java create mode 100644 back/src/main/java/com/linkwork/common/GlobalExceptionHandler.java create mode 100644 back/src/main/java/com/linkwork/common/ResourceNotFoundException.java create mode 100644 back/src/main/java/com/linkwork/common/SnowflakeIdGenerator.java create mode 100644 back/src/main/java/com/linkwork/config/AsyncConfig.java create mode 100644 back/src/main/java/com/linkwork/config/BuildQueueConfig.java create mode 100644 back/src/main/java/com/linkwork/config/CronConfig.java create mode 100644 back/src/main/java/com/linkwork/config/DispatchConfig.java create mode 100644 back/src/main/java/com/linkwork/config/EnvConfig.java create mode 100644 back/src/main/java/com/linkwork/config/ImageBuildConfig.java create mode 100644 back/src/main/java/com/linkwork/config/KubernetesConfig.java create mode 100644 back/src/main/java/com/linkwork/config/MemoryConfig.java create mode 100644 back/src/main/java/com/linkwork/config/MyBatisPlusConfig.java create mode 100644 back/src/main/java/com/linkwork/config/NfsStorageConfig.java create mode 100644 back/src/main/java/com/linkwork/config/WebSocketConfig.java create mode 100644 back/src/main/java/com/linkwork/context/UserContext.java create mode 100644 back/src/main/java/com/linkwork/context/UserInfo.java create mode 100644 back/src/main/java/com/linkwork/controller/ApprovalController.java create mode 100644 back/src/main/java/com/linkwork/controller/AuthController.java create mode 100644 back/src/main/java/com/linkwork/controller/BuildLogController.java create mode 100644 back/src/main/java/com/linkwork/controller/BuildQueueController.java create mode 100644 back/src/main/java/com/linkwork/controller/BuildRecordController.java create mode 100644 back/src/main/java/com/linkwork/controller/CronJobController.java create mode 100644 back/src/main/java/com/linkwork/controller/FileController.java create mode 100644 back/src/main/java/com/linkwork/controller/GitLabAuthController.java create mode 100644 back/src/main/java/com/linkwork/controller/HealthController.java create mode 100644 back/src/main/java/com/linkwork/controller/ImageBuildController.java create mode 100644 back/src/main/java/com/linkwork/controller/K8sClusterController.java create mode 100644 back/src/main/java/com/linkwork/controller/McpInternalController.java create mode 100644 back/src/main/java/com/linkwork/controller/McpServerController.java create mode 100644 back/src/main/java/com/linkwork/controller/McpUserConfigController.java create mode 100644 back/src/main/java/com/linkwork/controller/MemoryController.java create mode 100644 back/src/main/java/com/linkwork/controller/ModelRegistryController.java create mode 100644 back/src/main/java/com/linkwork/controller/PublicTaskController.java create mode 100644 back/src/main/java/com/linkwork/controller/ReportExportController.java create mode 100644 back/src/main/java/com/linkwork/controller/RoleController.java create mode 100644 back/src/main/java/com/linkwork/controller/ScheduleController.java create mode 100644 back/src/main/java/com/linkwork/controller/SecurityPolicyController.java create mode 100644 back/src/main/java/com/linkwork/controller/SkillController.java create mode 100644 back/src/main/java/com/linkwork/controller/StorageController.java create mode 100644 back/src/main/java/com/linkwork/controller/TaskController.java create mode 100644 back/src/main/java/com/linkwork/controller/TaskOutputController.java create mode 100644 back/src/main/java/com/linkwork/controller/UserSoulController.java create mode 100644 back/src/main/java/com/linkwork/controller/WorkspaceController.java create mode 100644 back/src/main/java/com/linkwork/filter/InternalApiAuditFilter.java create mode 100644 back/src/main/java/com/linkwork/filter/InternalApiIpFilter.java create mode 100644 back/src/main/java/com/linkwork/filter/JwtAuthFilter.java create mode 100644 back/src/main/java/com/linkwork/mapper/ApprovalMapper.java create mode 100644 back/src/main/java/com/linkwork/mapper/BuildRecordMapper.java create mode 100644 back/src/main/java/com/linkwork/mapper/CronJobMapper.java create mode 100644 back/src/main/java/com/linkwork/mapper/CronJobRunMapper.java create mode 100644 back/src/main/java/com/linkwork/mapper/FileNodeMapper.java create mode 100644 back/src/main/java/com/linkwork/mapper/McpServerMapper.java create mode 100644 back/src/main/java/com/linkwork/mapper/McpUsageDailyMapper.java create mode 100644 back/src/main/java/com/linkwork/mapper/McpUserConfigMapper.java create mode 100644 back/src/main/java/com/linkwork/mapper/RobotFileMapper.java create mode 100644 back/src/main/java/com/linkwork/mapper/RoleMapper.java create mode 100644 back/src/main/java/com/linkwork/mapper/SecurityPolicyMapper.java create mode 100644 back/src/main/java/com/linkwork/mapper/SkillMapper.java create mode 100644 back/src/main/java/com/linkwork/mapper/TaskGitAuthMapper.java create mode 100644 back/src/main/java/com/linkwork/mapper/TaskMapper.java create mode 100644 back/src/main/java/com/linkwork/mapper/UserFavoriteRoleMapper.java create mode 100644 back/src/main/java/com/linkwork/mapper/UserSoulMapper.java create mode 100644 back/src/main/java/com/linkwork/model/FileNode.java create mode 100644 back/src/main/java/com/linkwork/model/dto/BuildQueueStatus.java create mode 100644 back/src/main/java/com/linkwork/model/dto/BuildTask.java create mode 100644 back/src/main/java/com/linkwork/model/dto/ClusterEventDTO.java create mode 100644 back/src/main/java/com/linkwork/model/dto/ClusterNodeInfo.java create mode 100644 back/src/main/java/com/linkwork/model/dto/ClusterOverviewDTO.java create mode 100644 back/src/main/java/com/linkwork/model/dto/ClusterPodInfo.java create mode 100644 back/src/main/java/com/linkwork/model/dto/ContainerStatusInfo.java create mode 100644 back/src/main/java/com/linkwork/model/dto/CreateFolderRequest.java create mode 100644 back/src/main/java/com/linkwork/model/dto/CredentialConfig.java create mode 100644 back/src/main/java/com/linkwork/model/dto/CronJobCreateRequest.java create mode 100644 back/src/main/java/com/linkwork/model/dto/CronJobResponse.java create mode 100644 back/src/main/java/com/linkwork/model/dto/CronJobRunResponse.java create mode 100644 back/src/main/java/com/linkwork/model/dto/CronJobToggleRequest.java create mode 100644 back/src/main/java/com/linkwork/model/dto/CronJobUpdateRequest.java create mode 100644 back/src/main/java/com/linkwork/model/dto/CronSchedulePreviewRequest.java create mode 100644 back/src/main/java/com/linkwork/model/dto/FileMentionResponse.java create mode 100644 back/src/main/java/com/linkwork/model/dto/FileNodeResponse.java create mode 100644 back/src/main/java/com/linkwork/model/dto/FileResponse.java create mode 100644 back/src/main/java/com/linkwork/model/dto/FileSpaceSyncRequest.java create mode 100644 back/src/main/java/com/linkwork/model/dto/FileSpaceSyncResponse.java create mode 100644 back/src/main/java/com/linkwork/model/dto/FileTransferRequest.java create mode 100644 back/src/main/java/com/linkwork/model/dto/GeneratedSpec.java create mode 100644 back/src/main/java/com/linkwork/model/dto/ImageBuildResult.java create mode 100644 back/src/main/java/com/linkwork/model/dto/McpDiscoverResult.java create mode 100644 back/src/main/java/com/linkwork/model/dto/McpProbeResult.java create mode 100644 back/src/main/java/com/linkwork/model/dto/MemoryIndexBatchRequest.java create mode 100644 back/src/main/java/com/linkwork/model/dto/MemoryIndexJob.java create mode 100644 back/src/main/java/com/linkwork/model/dto/MemoryIngestRequest.java create mode 100644 back/src/main/java/com/linkwork/model/dto/MemorySearchRequest.java create mode 100644 back/src/main/java/com/linkwork/model/dto/MergedConfig.java create mode 100644 back/src/main/java/com/linkwork/model/dto/PodGroupStatusInfo.java create mode 100644 back/src/main/java/com/linkwork/model/dto/PodLogResponseDTO.java create mode 100644 back/src/main/java/com/linkwork/model/dto/PodStatusInfo.java create mode 100644 back/src/main/java/com/linkwork/model/dto/ReportExportFieldOption.java create mode 100644 back/src/main/java/com/linkwork/model/dto/ReportExportFieldResponse.java create mode 100644 back/src/main/java/com/linkwork/model/dto/ReportExportRequest.java create mode 100644 back/src/main/java/com/linkwork/model/dto/ResourceConfig.java create mode 100644 back/src/main/java/com/linkwork/model/dto/ResourceSpec.java create mode 100644 back/src/main/java/com/linkwork/model/dto/ResourceStatus.java create mode 100644 back/src/main/java/com/linkwork/model/dto/ResourceUsageInfo.java create mode 100644 back/src/main/java/com/linkwork/model/dto/ScaleRequest.java create mode 100644 back/src/main/java/com/linkwork/model/dto/ScaleResult.java create mode 100644 back/src/main/java/com/linkwork/model/dto/ScheduleApiResponse.java create mode 100644 back/src/main/java/com/linkwork/model/dto/ScheduleEvent.java create mode 100644 back/src/main/java/com/linkwork/model/dto/ServiceBuildRequest.java create mode 100644 back/src/main/java/com/linkwork/model/dto/ServiceBuildResult.java create mode 100644 back/src/main/java/com/linkwork/model/dto/ServiceResumeResult.java create mode 100644 back/src/main/java/com/linkwork/model/dto/ServiceSnapshot.java create mode 100644 back/src/main/java/com/linkwork/model/dto/ServiceStatusResponse.java create mode 100644 back/src/main/java/com/linkwork/model/dto/StopResult.java create mode 100644 back/src/main/java/com/linkwork/model/dto/TaskCompleteRequest.java create mode 100644 back/src/main/java/com/linkwork/model/dto/TaskCreateRequest.java create mode 100644 back/src/main/java/com/linkwork/model/dto/TaskDispatchMessage.java create mode 100644 back/src/main/java/com/linkwork/model/dto/TaskGitTokenResponse.java create mode 100644 back/src/main/java/com/linkwork/model/dto/TaskResponse.java create mode 100644 back/src/main/java/com/linkwork/model/dto/TaskShareCreateRequest.java create mode 100644 back/src/main/java/com/linkwork/model/dto/TaskShareLinkResponse.java create mode 100644 back/src/main/java/com/linkwork/model/dto/UserSoulResponse.java create mode 100644 back/src/main/java/com/linkwork/model/dto/UserSoulUpsertRequest.java create mode 100644 back/src/main/java/com/linkwork/model/dto/event/BuildEventData.java create mode 100644 back/src/main/java/com/linkwork/model/dto/event/ContainerEventData.java create mode 100644 back/src/main/java/com/linkwork/model/dto/event/EnvSetupData.java create mode 100644 back/src/main/java/com/linkwork/model/dto/event/ImageEventData.java create mode 100644 back/src/main/java/com/linkwork/model/dto/event/InitCompleteData.java create mode 100644 back/src/main/java/com/linkwork/model/dto/event/PodSchedulingData.java create mode 100644 back/src/main/java/com/linkwork/model/dto/event/SessionEventData.java create mode 100644 back/src/main/java/com/linkwork/model/enums/ConflictPolicy.java create mode 100644 back/src/main/java/com/linkwork/model/enums/ContainerEventType.java create mode 100644 back/src/main/java/com/linkwork/model/enums/DeployMode.java create mode 100644 back/src/main/java/com/linkwork/model/enums/PodMode.java create mode 100644 back/src/main/java/com/linkwork/model/enums/ResumeType.java create mode 100644 back/src/main/java/com/linkwork/model/enums/ServiceType.java create mode 100644 back/src/main/java/com/linkwork/model/enums/SnapshotStatus.java create mode 100644 back/src/main/java/com/linkwork/model/enums/TaskOutputType.java create mode 100644 back/src/main/java/com/linkwork/model/enums/TaskStatus.java create mode 100644 back/src/main/java/com/linkwork/service/AdminAccessService.java create mode 100644 back/src/main/java/com/linkwork/service/ApprovalRequestConsumer.java create mode 100644 back/src/main/java/com/linkwork/service/ApprovalService.java create mode 100644 back/src/main/java/com/linkwork/service/AuthService.java create mode 100644 back/src/main/java/com/linkwork/service/BuildExecutor.java create mode 100644 back/src/main/java/com/linkwork/service/BuildLogBuffer.java create mode 100644 back/src/main/java/com/linkwork/service/BuildQueueService.java create mode 100644 back/src/main/java/com/linkwork/service/BuildRecordService.java create mode 100644 back/src/main/java/com/linkwork/service/ConfigMergeService.java create mode 100644 back/src/main/java/com/linkwork/service/CronExpressionHelper.java create mode 100644 back/src/main/java/com/linkwork/service/CronJobExecutor.java create mode 100644 back/src/main/java/com/linkwork/service/CronJobService.java create mode 100644 back/src/main/java/com/linkwork/service/CronScheduler.java create mode 100644 back/src/main/java/com/linkwork/service/DistributedLockService.java create mode 100644 back/src/main/java/com/linkwork/service/DockerComposeGenerator.java create mode 100644 back/src/main/java/com/linkwork/service/FileNodeService.java create mode 100644 back/src/main/java/com/linkwork/service/FileParseConsumer.java create mode 100644 back/src/main/java/com/linkwork/service/FileService.java create mode 100644 back/src/main/java/com/linkwork/service/FileSpaceSyncService.java create mode 100644 back/src/main/java/com/linkwork/service/GitLabAuthService.java create mode 100644 back/src/main/java/com/linkwork/service/ImageBuildService.java create mode 100644 back/src/main/java/com/linkwork/service/K8sClusterService.java create mode 100644 back/src/main/java/com/linkwork/service/K8sOrchestrator.java create mode 100644 back/src/main/java/com/linkwork/service/K8sOrchestratorImpl.java create mode 100644 back/src/main/java/com/linkwork/service/McpCryptoService.java create mode 100644 back/src/main/java/com/linkwork/service/McpDiscoveryService.java create mode 100644 back/src/main/java/com/linkwork/service/McpHealthChecker.java create mode 100644 back/src/main/java/com/linkwork/service/McpServerService.java create mode 100644 back/src/main/java/com/linkwork/service/McpUserConfigService.java create mode 100644 back/src/main/java/com/linkwork/service/NfsStorageService.java create mode 100644 back/src/main/java/com/linkwork/service/PodGroupSpecGenerator.java create mode 100644 back/src/main/java/com/linkwork/service/PodSpecGenerator.java create mode 100644 back/src/main/java/com/linkwork/service/QueueSelector.java create mode 100644 back/src/main/java/com/linkwork/service/ReportExportService.java create mode 100644 back/src/main/java/com/linkwork/service/RoleHotRankService.java create mode 100644 back/src/main/java/com/linkwork/service/RoleService.java create mode 100644 back/src/main/java/com/linkwork/service/RuntimeModeService.java create mode 100644 back/src/main/java/com/linkwork/service/ScheduleEventPublisher.java create mode 100644 back/src/main/java/com/linkwork/service/SecurityPolicyService.java create mode 100644 back/src/main/java/com/linkwork/service/ServiceResumeService.java create mode 100644 back/src/main/java/com/linkwork/service/ServiceScaleService.java create mode 100644 back/src/main/java/com/linkwork/service/ServiceScheduleService.java create mode 100644 back/src/main/java/com/linkwork/service/ServiceSnapshotService.java create mode 100644 back/src/main/java/com/linkwork/service/SkillGitLabService.java create mode 100644 back/src/main/java/com/linkwork/service/SkillService.java create mode 100644 back/src/main/java/com/linkwork/service/SnapshotSyncTask.java create mode 100644 back/src/main/java/com/linkwork/service/SystemResourceMonitor.java create mode 100644 back/src/main/java/com/linkwork/service/TaskBillingUsageService.java create mode 100644 back/src/main/java/com/linkwork/service/TaskDispatchConsumer.java create mode 100644 back/src/main/java/com/linkwork/service/TaskEventBroadcaster.java create mode 100644 back/src/main/java/com/linkwork/service/TaskEventConsumerService.java create mode 100644 back/src/main/java/com/linkwork/service/TaskGitAuthService.java create mode 100644 back/src/main/java/com/linkwork/service/TaskGitTokenService.java create mode 100644 back/src/main/java/com/linkwork/service/TaskOutputWorkspaceSyncService.java create mode 100644 back/src/main/java/com/linkwork/service/TaskPathlistSyncService.java create mode 100644 back/src/main/java/com/linkwork/service/TaskService.java create mode 100644 back/src/main/java/com/linkwork/service/TaskShareLinkService.java create mode 100644 back/src/main/java/com/linkwork/service/TaskStatusSyncService.java create mode 100644 back/src/main/java/com/linkwork/service/UserSoulService.java create mode 100644 back/src/main/java/com/linkwork/service/WorkspaceService.java create mode 100644 back/src/main/java/com/linkwork/service/ZzdServiceAuthService.java create mode 100644 back/src/main/java/com/linkwork/service/memory/DocumentParserService.java create mode 100644 back/src/main/java/com/linkwork/service/memory/EmbeddingService.java create mode 100644 back/src/main/java/com/linkwork/service/memory/MemoryIndexConsumer.java create mode 100644 back/src/main/java/com/linkwork/service/memory/MemoryService.java create mode 100644 back/src/main/java/com/linkwork/service/memory/MilvusStoreService.java create mode 100644 back/src/main/java/com/linkwork/service/memory/TextChunkerService.java create mode 100644 back/src/main/java/com/linkwork/websocket/TaskWebSocketHandler.java create mode 100644 back/src/main/resources/scripts/00-platform.cedar create mode 100755 back/src/main/resources/scripts/build.sh create mode 100755 back/src/main/resources/scripts/start-runner.sh diff --git a/back/src/main/java/com/linkwork/LinkWorkApplication.java b/back/src/main/java/com/linkwork/LinkWorkApplication.java new file mode 100644 index 0000000..19526b2 --- /dev/null +++ b/back/src/main/java/com/linkwork/LinkWorkApplication.java @@ -0,0 +1,18 @@ +package com.linkwork; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; + +import java.util.TimeZone; + +@SpringBootApplication +@MapperScan("com.linkwork.mapper") +@EnableScheduling +public class LinkWorkApplication { + public static void main(String[] args) { + TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai")); + SpringApplication.run(LinkWorkApplication.class, args); + } +} diff --git a/back/src/main/java/com/linkwork/common/ApiResponse.java b/back/src/main/java/com/linkwork/common/ApiResponse.java new file mode 100644 index 0000000..3890e0b --- /dev/null +++ b/back/src/main/java/com/linkwork/common/ApiResponse.java @@ -0,0 +1,50 @@ +package com.linkwork.common; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.UUID; + +/** + * 统一 API 响应结构 + */ +@Data +public class ApiResponse { + + private Integer code; + private String msg; + private T data; + private String traceId; + + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'") + private LocalDateTime timestamp; + + private ApiResponse(Integer code, String msg, T data) { + this.code = code; + this.msg = msg; + this.data = data; + this.traceId = UUID.randomUUID().toString(); + this.timestamp = LocalDateTime.now(); + } + + public static ApiResponse success(T data) { + return new ApiResponse<>(0, "success", data); + } + + public static ApiResponse success() { + return new ApiResponse<>(0, "success", null); + } + + public static ApiResponse error(Integer code, String msg) { + return new ApiResponse<>(code, msg, null); + } + + public static ApiResponse error(String msg) { + return new ApiResponse<>(50000, msg, null); + } + + public static ApiResponse error(Integer code, String msg, T data) { + return new ApiResponse<>(code, msg, data); + } +} diff --git a/back/src/main/java/com/linkwork/common/ClientIpResolver.java b/back/src/main/java/com/linkwork/common/ClientIpResolver.java new file mode 100644 index 0000000..58774a1 --- /dev/null +++ b/back/src/main/java/com/linkwork/common/ClientIpResolver.java @@ -0,0 +1,55 @@ +package com.linkwork.common; + +import org.springframework.util.StringUtils; + +import jakarta.servlet.http.HttpServletRequest; + +/** + * Resolve client IP from proxy headers and servlet request. + */ +public final class ClientIpResolver { + + private static final String[] IP_HEADER_CANDIDATES = { + "X-Forwarded-For", + "X-Real-IP", + "Proxy-Client-IP", + "WL-Proxy-Client-IP", + "HTTP_CLIENT_IP", + "HTTP_X_FORWARDED_FOR" + }; + + private ClientIpResolver() { + } + + public static String resolve(HttpServletRequest request) { + if (request == null) { + return null; + } + + for (String header : IP_HEADER_CANDIDATES) { + String value = normalize(request.getHeader(header)); + if (StringUtils.hasText(value)) { + return value; + } + } + + return normalize(request.getRemoteAddr()); + } + + private static String normalize(String rawIp) { + if (!StringUtils.hasText(rawIp)) { + return null; + } + + String first = rawIp.split(",")[0].trim(); + if (!StringUtils.hasText(first) || "unknown".equalsIgnoreCase(first)) { + return null; + } + + if ("0:0:0:0:0:0:0:1".equals(first) || "::1".equals(first)) { + return "127.0.0.1"; + } + + return first.length() > 64 ? first.substring(0, 64) : first; + } +} diff --git a/back/src/main/java/com/linkwork/common/FileConflictException.java b/back/src/main/java/com/linkwork/common/FileConflictException.java new file mode 100644 index 0000000..d2de3e0 --- /dev/null +++ b/back/src/main/java/com/linkwork/common/FileConflictException.java @@ -0,0 +1,37 @@ +package com.linkwork.common; + +import lombok.Getter; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +@Getter +public class FileConflictException extends RuntimeException { + + private final String conflictType; + private final Map existingNode; + + public FileConflictException(String message, String fileId, String name, + String entryType, Long fileSize, LocalDateTime updatedAt) { + super(message); + this.conflictType = "NAME_EXISTS"; + this.existingNode = new HashMap<>(); + this.existingNode.put("fileId", fileId); + this.existingNode.put("name", name); + this.existingNode.put("entryType", entryType); + if (fileSize != null) { + this.existingNode.put("fileSize", fileSize); + } + if (updatedAt != null) { + this.existingNode.put("updatedAt", updatedAt.toString()); + } + } + + public Map toResponseData() { + Map data = new HashMap<>(); + data.put("conflictType", conflictType); + data.put("existingNode", existingNode); + return data; + } +} diff --git a/back/src/main/java/com/linkwork/common/ForbiddenOperationException.java b/back/src/main/java/com/linkwork/common/ForbiddenOperationException.java new file mode 100644 index 0000000..4e80dc0 --- /dev/null +++ b/back/src/main/java/com/linkwork/common/ForbiddenOperationException.java @@ -0,0 +1,11 @@ +package com.linkwork.common; + +/** + * 禁止访问异常 + */ +public class ForbiddenOperationException extends RuntimeException { + + public ForbiddenOperationException(String message) { + super(message); + } +} diff --git a/back/src/main/java/com/linkwork/common/GlobalExceptionHandler.java b/back/src/main/java/com/linkwork/common/GlobalExceptionHandler.java new file mode 100644 index 0000000..f93165c --- /dev/null +++ b/back/src/main/java/com/linkwork/common/GlobalExceptionHandler.java @@ -0,0 +1,100 @@ +package com.linkwork.common; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.FieldError; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.resource.NoResourceFoundException; + +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 全局异常处理器 + */ +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResponse handleValidationException(MethodArgumentNotValidException e) { + String errorMsg = e.getBindingResult().getFieldErrors().stream() + .map(FieldError::getDefaultMessage) + .collect(Collectors.joining(", ")); + log.warn("参数校验失败: {}", errorMsg); + return ApiResponse.error(40000, errorMsg); + } + + @ExceptionHandler(IllegalArgumentException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResponse handleIllegalArgumentException(IllegalArgumentException e) { + log.warn("参数错误: {}", e.getMessage()); + return ApiResponse.error(40000, e.getMessage()); + } + + @ExceptionHandler(MissingServletRequestParameterException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResponse handleMissingServletRequestParameterException(MissingServletRequestParameterException e) { + String errorMsg = "缺少必填参数: " + e.getParameterName(); + log.warn("参数缺失: {}", errorMsg); + return ApiResponse.error(40000, errorMsg); + } + + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResponse handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) { + String errorMsg = "参数类型错误: " + e.getName(); + log.warn("参数类型错误: {}", e.getMessage()); + return ApiResponse.error(40000, errorMsg); + } + + @ExceptionHandler(NoResourceFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public ApiResponse handleNoResourceFoundException(NoResourceFoundException e) { + log.warn("接口不存在: {}", e.getResourcePath()); + return ApiResponse.error(40400, "接口不存在: " + e.getResourcePath()); + } + + + @ExceptionHandler(ForbiddenOperationException.class) + @ResponseStatus(HttpStatus.FORBIDDEN) + public ApiResponse handleForbiddenOperationException(ForbiddenOperationException e) { + log.warn("禁止访问: {}", e.getMessage()); + return ApiResponse.error(40300, e.getMessage()); + } + + @ExceptionHandler(FileConflictException.class) + public ResponseEntity>> handleFileConflictException(FileConflictException e) { + log.info("文件同名冲突: {}", e.getMessage()); + ApiResponse> response = ApiResponse.error(40901, e.getMessage(), e.toResponseData()); + return ResponseEntity.status(HttpStatus.CONFLICT).body(response); + } + + @ExceptionHandler(ResourceNotFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public ApiResponse handleResourceNotFoundException(ResourceNotFoundException e) { + log.warn("资源不存在: {}", e.getMessage()); + return ApiResponse.error(40400, e.getMessage()); + } + + @ExceptionHandler(RuntimeException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ApiResponse handleRuntimeException(RuntimeException e) { + log.error("系统异常", e); + return ApiResponse.error(50000, "服务器内部错误"); + } + + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ApiResponse handleException(Exception e) { + log.error("未知异常", e); + return ApiResponse.error(50000, "服务器内部错误"); + } +} diff --git a/back/src/main/java/com/linkwork/common/ResourceNotFoundException.java b/back/src/main/java/com/linkwork/common/ResourceNotFoundException.java new file mode 100644 index 0000000..74fcd31 --- /dev/null +++ b/back/src/main/java/com/linkwork/common/ResourceNotFoundException.java @@ -0,0 +1,11 @@ +package com.linkwork.common; + +/** + * 资源不存在异常 + */ +public class ResourceNotFoundException extends RuntimeException { + + public ResourceNotFoundException(String message) { + super(message); + } +} diff --git a/back/src/main/java/com/linkwork/common/SnowflakeIdGenerator.java b/back/src/main/java/com/linkwork/common/SnowflakeIdGenerator.java new file mode 100644 index 0000000..1e376e2 --- /dev/null +++ b/back/src/main/java/com/linkwork/common/SnowflakeIdGenerator.java @@ -0,0 +1,122 @@ +package com.linkwork.common; + +import org.springframework.stereotype.Component; + +import java.net.NetworkInterface; +import java.util.Enumeration; + +/** + * 雪花算法 ID 生成器 + * + * 生成分布式全局唯一 64 位 ID: + * - 1 bit: 符号位(固定 0) + * - 41 bits: 时间戳(毫秒级,可用约 69 年) + * - 10 bits: 机器 ID(支持 1024 个节点) + * - 12 bits: 序列号(每毫秒最多 4096 个 ID) + */ +@Component +public class SnowflakeIdGenerator { + + // 起始时间戳 (2024-01-01 00:00:00 UTC) + private static final long EPOCH = 1704067200000L; + + // 机器 ID 位数 + private static final long WORKER_ID_BITS = 10L; + // 序列号位数 + private static final long SEQUENCE_BITS = 12L; + + // 最大机器 ID + private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS); + // 最大序列号 + private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS); + + // 时间戳左移位数 + private static final long TIMESTAMP_SHIFT = WORKER_ID_BITS + SEQUENCE_BITS; + // 机器 ID 左移位数 + private static final long WORKER_ID_SHIFT = SEQUENCE_BITS; + + private final long workerId; + private long sequence = 0L; + private long lastTimestamp = -1L; + + public SnowflakeIdGenerator() { + this.workerId = generateWorkerId(); + } + + /** + * 生成下一个分布式唯一 ID + */ + public synchronized long nextId() { + long timestamp = System.currentTimeMillis(); + + // 时钟回拨检测 + if (timestamp < lastTimestamp) { + throw new RuntimeException("时钟回拨,拒绝生成 ID: " + (lastTimestamp - timestamp) + " ms"); + } + + if (timestamp == lastTimestamp) { + // 同一毫秒内,序列号递增 + sequence = (sequence + 1) & MAX_SEQUENCE; + if (sequence == 0) { + // 序列号溢出,等待下一毫秒 + timestamp = waitNextMillis(lastTimestamp); + } + } else { + // 新的毫秒,序列号重置 + sequence = 0L; + } + + lastTimestamp = timestamp; + + return ((timestamp - EPOCH) << TIMESTAMP_SHIFT) + | (workerId << WORKER_ID_SHIFT) + | sequence; + } + + /** + * 生成字符串格式的 ID(用于 taskNo) + */ + public String nextIdStr() { + return String.valueOf(nextId()); + } + + /** + * 生成带前缀的任务编号 + */ + public String nextTaskNo() { + return "TSK-" + nextId(); + } + + /** + * 等待下一毫秒 + */ + private long waitNextMillis(long lastTimestamp) { + long timestamp = System.currentTimeMillis(); + while (timestamp <= lastTimestamp) { + timestamp = System.currentTimeMillis(); + } + return timestamp; + } + + /** + * 基于机器 MAC 地址生成 workerId + */ + private long generateWorkerId() { + try { + Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); + while (interfaces.hasMoreElements()) { + NetworkInterface network = interfaces.nextElement(); + byte[] mac = network.getHardwareAddress(); + if (mac != null && mac.length > 0) { + // 取 MAC 地址后两个字节计算 workerId + long id = ((0x000000FF & (long) mac[mac.length - 2]) + | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) & MAX_WORKER_ID; + return id; + } + } + } catch (Exception e) { + // 获取失败时使用随机值 + } + return (long) (Math.random() * MAX_WORKER_ID); + } +} diff --git a/back/src/main/java/com/linkwork/config/AsyncConfig.java b/back/src/main/java/com/linkwork/config/AsyncConfig.java new file mode 100644 index 0000000..0a94c5f --- /dev/null +++ b/back/src/main/java/com/linkwork/config/AsyncConfig.java @@ -0,0 +1,36 @@ +package com.linkwork.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; + +/** + * 异步任务配置 + */ +@Configuration +@EnableAsync +@EnableScheduling +@Slf4j +public class AsyncConfig implements AsyncConfigurer { + + @Override + public Executor getAsyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(5); + executor.setMaxPoolSize(10); + executor.setQueueCapacity(100); + executor.setThreadNamePrefix("async-build-"); + executor.setWaitForTasksToCompleteOnShutdown(true); + executor.setAwaitTerminationSeconds(60); + executor.initialize(); + + log.info("Async executor initialized: corePoolSize=5, maxPoolSize=10, queueCapacity=100"); + + return executor; + } +} diff --git a/back/src/main/java/com/linkwork/config/BuildQueueConfig.java b/back/src/main/java/com/linkwork/config/BuildQueueConfig.java new file mode 100644 index 0000000..e0308db --- /dev/null +++ b/back/src/main/java/com/linkwork/config/BuildQueueConfig.java @@ -0,0 +1,71 @@ +package com.linkwork.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * 构建队列配置 + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "build-queue") +public class BuildQueueConfig { + + /** + * CPU 使用率阈值 (0.0 ~ 1.0) + * 当系统 CPU 使用率低于此值时,才允许启动新的构建任务 + * 默认 0.7 (70%) + */ + private double cpuThreshold = 0.7; + + /** + * 内存使用率阈值 (0.0 ~ 1.0) + * 当系统内存使用率低于此值时,才允许启动新的构建任务 + * 默认 0.7 (70%) + */ + private double memoryThreshold = 0.7; + + /** + * 硬性并发上限 + * 即使系统资源充足,同时运行的构建任务也不会超过此值 + * 这是一个安全阀,防止资源监控失效时系统过载 + * 默认 3 + */ + private int maxConcurrent = 3; + + /** + * 队列最大容量 + * 超过此容量时,新任务将被拒绝 + * 默认 50 + */ + private int maxQueueSize = 50; + + /** + * 构建超时时间(秒) + * 单个构建任务的最大执行时间 + * 默认 600 秒 (10 分钟) + */ + private int taskTimeout = 600; + + /** + * 资源检查间隔(毫秒) + * 队列消费线程检查资源的间隔 + * 默认 1000 毫秒 (1 秒) + */ + private long checkInterval = 1000; + + /** + * 残留文件清理阈值(小时) + * 超过此时间的临时构建目录会被清理 + * 默认 1 小时 + */ + private int staleContextHours = 1; + + /** + * 是否启用队列功能 + * 设为 false 时,构建任务将直接异步执行(兼容旧行为) + * 默认 true + */ + private boolean enabled = true; +} diff --git a/back/src/main/java/com/linkwork/config/CronConfig.java b/back/src/main/java/com/linkwork/config/CronConfig.java new file mode 100644 index 0000000..dda43ef --- /dev/null +++ b/back/src/main/java/com/linkwork/config/CronConfig.java @@ -0,0 +1,19 @@ +package com.linkwork.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Data +@Configuration +@ConfigurationProperties(prefix = "robot.cron") +public class CronConfig { + private boolean enabled = true; + private long scanIntervalMs = 60_000; + private long dispatchLeadMs = 180_000; + private int maxJobsPerUser = 50; + private int maxJobsPerRole = 100; + private int maxRunsPerJob = 100; + private String lockKey = "lock:cron:scanner"; + private int lockTtlSeconds = 55; +} diff --git a/back/src/main/java/com/linkwork/config/DispatchConfig.java b/back/src/main/java/com/linkwork/config/DispatchConfig.java new file mode 100644 index 0000000..82f1e3f --- /dev/null +++ b/back/src/main/java/com/linkwork/config/DispatchConfig.java @@ -0,0 +1,118 @@ +package com.linkwork.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * 任务调度配置(与 momo-worker 对齐) + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "robot.dispatch") +public class DispatchConfig { + + /** 默认 workstationId(仅 roleId 缺失时兜底) */ + private String workstationId = "test-post-001"; + + /** 日志流 key 前缀 */ + private String logStreamPrefix = "logs"; + + /** 审批 key 前缀 */ + private String approvalKeyPrefix = "approval"; + + // ==================== 派生 Key 方法 ==================== + + /** 任务调度队列 key: workstation:{workstationId}:tasks */ + public String getTaskQueueKey(Long roleId) { + return "workstation:" + resolveWorkstationId(roleId) + ":tasks"; + } + + /** 日志流 key: logs:{workstationId}:{taskId} */ + public String getLogStreamKey(Long roleId, String taskId) { + return logStreamPrefix + ":" + resolveWorkstationId(roleId) + ":" + taskId; + } + + /** 审批请求队列 key: approval:{workstationId} */ + public String getApprovalRequestKey(Long roleId) { + return approvalKeyPrefix + ":" + resolveWorkstationId(roleId); + } + + /** 审批响应 key: approval:{workstationId}:response:{requestId} */ + public String getApprovalResponseKey(Long roleId, String requestId) { + return approvalKeyPrefix + ":" + resolveWorkstationId(roleId) + ":response:" + requestId; + } + + + /** 任务终止控制队列 key: workstation:{workstationId}:control */ + public String getTaskControlQueueKey(Long roleId) { + return "workstation:" + resolveWorkstationId(roleId) + ":control"; + } + + /** approval 请求队列匹配模式(含 response key,调用方需过滤) */ + public String getApprovalRequestKeyPattern() { + return approvalKeyPrefix + ":*"; + } + + /** + * roleId -> workstationId 解析规则: + * 1) roleId 存在时直接使用 roleId(真实岗位链路) + * 2) 否则回退默认配置 + */ + public String resolveWorkstationId(Long roleId) { + if (roleId != null && roleId > 0) { + return String.valueOf(roleId); + } + return workstationId; + } + + // ==================== 兼容旧调用(默认 workstation) ==================== + + /** + * @deprecated use {@link #getTaskQueueKey(Long)}. + */ + @Deprecated + public String getTaskQueueKey() { + return getTaskQueueKey(null); + } + + /** + * @deprecated use {@link #getLogStreamKey(Long, String)}. + */ + @Deprecated + public String getLogStreamKey(String taskId) { + return getLogStreamKey(null, taskId); + } + + /** + * @deprecated use {@link #getApprovalRequestKey(Long)}. + */ + @Deprecated + public String getApprovalRequestKey() { + return getApprovalRequestKey(null); + } + + /** + * @deprecated use {@link #getApprovalResponseKey(Long, String)}. + */ + @Deprecated + public String getApprovalResponseKey(String requestId) { + return getApprovalResponseKey(null, requestId); + } + + /** + * @deprecated use {@link #getTaskControlQueueKey(Long)}. + */ + @Deprecated + public String getTaskControlQueueKey() { + return getTaskControlQueueKey(null); + } + + /** + * @deprecated use {@link #getTaskControlQueueKey(Long)}. + */ + @Deprecated + public String getTaskTerminateQueueKey() { + return getTaskControlQueueKey(null); + } +} diff --git a/back/src/main/java/com/linkwork/config/EnvConfig.java b/back/src/main/java/com/linkwork/config/EnvConfig.java new file mode 100644 index 0000000..32937b4 --- /dev/null +++ b/back/src/main/java/com/linkwork/config/EnvConfig.java @@ -0,0 +1,157 @@ +package com.linkwork.config; + +import com.linkwork.model.dto.ResourceSpec; +import com.linkwork.model.enums.PodMode; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.HashMap; +import java.util.Map; + +/** + * 系统环境配置(从 YAML 加载) + * + * 设计说明: + * - 所有代码已打入镜像,不需要代码拉取配置 + * - 文件放置配置定义 token 和 ssh-key 的路径和权限 + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "schedule") +public class EnvConfig { + + // 集群配置 + private ClusterConfig cluster = new ClusterConfig(); + + // 镜像配置 + private ImagesConfig images = new ImagesConfig(); + + // Agent 启动脚本配置 + private AgentBootstrapConfig agentBootstrap = new AgentBootstrapConfig(); + + // 文件放置配置 + private FilePlacementConfig filePlacement = new FilePlacementConfig(); + + // 网络配置 + private NetworkConfig network = new NetworkConfig(); + + // SSH 配置 + private SshConfig ssh = new SshConfig(); + + // 默认资源配置 + private DefaultResourcesConfig defaultResources = new DefaultResourcesConfig(); + + // NFS 挂载配置(沿用 oss-mount YAML key 以兼容现有 K8s 配置) + private OssMountConfig ossMount = new OssMountConfig(); + + // 模式决策规则 + private PodModeRulesConfig podModeRules = new PodModeRulesConfig(); + + @Data + public static class ClusterConfig { + private String namespace = "ai-worker"; + private String schedulerName = "volcano"; + private String kubeconfigPath; // kubeconfig 文件路径 + } + + @Data + public static class ImagesConfig { + private String registry = ""; + private String agent = "ai-worker/agent-base:v1.0"; // Agent 基础镜像 + private String runner = "ai-worker/runner-base:v1.0"; // Runner 默认镜像(Sidecar 模式) + private Map defaultRunners = new HashMap<>(); // Runner 镜像(代码已内置) + } + + /** + * Agent 启动脚本配置 + * main.py 从链接下载,执行后启动 agent 和 executor 两个进程 + */ + @Data + public static class AgentBootstrapConfig { + private String mainPyUrl; // main.py 下载链接 + } + + /** + * 文件放置配置 + * - token: 仅 executor 用户可访问 + * - ssh-key: agent 和 executor 都可访问 + */ + @Data + public static class FilePlacementConfig { + // token 文件配置(仅 executor 可访问) + private String tokenPath = "/workspace/.credentials/token"; + private String tokenDirMode = "700"; + private String tokenFileMode = "600"; + private String tokenOwner = "executor:executor"; + + // SSH 密钥配置(agent 和 executor 都可访问) + private String sshPath = "/workspace/.ssh"; + private String sshDirMode = "755"; + private String sshKeyMode = "600"; // 私钥 + private String sshPubMode = "644"; // 公钥 + } + + @Data + public static class NetworkConfig { + private String apiBaseUrl; + private String wsBaseUrl; + private String llmGatewayUrl; + private String redisUrl; + } + + @Data + public static class SshConfig { + private Integer port = 2222; + private String keyType = "ed25519"; + } + + @Data + public static class DefaultResourcesConfig { + private ResourceSpec agent = ResourceSpec.builder() + .cpuRequest("1").cpuLimit("2") + .memoryRequest("2Gi").memoryLimit("4Gi") + .build(); + private ResourceSpec runner = ResourceSpec.builder() + .cpuRequest("1").cpuLimit("4") + .memoryRequest("2Gi").memoryLimit("8Gi") + .build(); + } + + /** + * OSS 挂载配置 + * 通过 hostPath 将宿主机上的 ossfs 挂载目录映射到容器内 + * + * 节点级挂载 (DaemonSet / ossfs): + * oss://robot-agent-files/system/ → hostPath/system + * oss://robot-agent-files/user-files/ → hostPath/user-files + * oss://robot-agent-files/workstation/ → hostPath/workstation + * + * 容器级挂载 (Pod hostPath → container): + * 1. oss-data: hostPath/system/{wsId} → mountPath (读写,产出物挂载根) + * 2. oss-user-files: hostPath/user-files → /mnt/user-files (读写,个人空间挂载根) + * 3. oss-workstation: hostPath/workstation/{wsId} → /mnt/workstation (读写,岗位空间挂载根) + */ + @Data + public static class OssMountConfig { + /** 是否启用 NFS 挂载 */ + private boolean enabled = false; + /** 宿主机 NFS 挂载根目录 */ + private String hostPath = "/mnt/oss/robot-agent-files"; + /** 容器内主挂载路径(产出物挂载根) */ + private String mountPath = "/data/oss/robot"; + /** 主挂载是否只读 */ + private boolean readOnly = false; + + /** user-files 容器内挂载路径(记忆-个人空间挂载根) */ + private String userFilesMountPath = "/mnt/user-files"; + /** workstation 容器内挂载路径(记忆-岗位空间挂载根) */ + private String workstationMountPath = "/mnt/workstation"; + } + + @Data + public static class PodModeRulesConfig { + private PodMode defaultMode = PodMode.SIDECAR; + private Map overrides = new HashMap<>(); + } +} diff --git a/back/src/main/java/com/linkwork/config/ImageBuildConfig.java b/back/src/main/java/com/linkwork/config/ImageBuildConfig.java new file mode 100644 index 0000000..c7acbc3 --- /dev/null +++ b/back/src/main/java/com/linkwork/config/ImageBuildConfig.java @@ -0,0 +1,127 @@ +package com.linkwork.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * 镜像构建配置 + * + * 设计说明: + * - 仅构建 Agent 镜像,Runner 由运行时 agent 启动 + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "image-build") +public class ImageBuildConfig { + + /** + * 是否启用镜像构建 + */ + private boolean enabled = false; + + /** + * 是否推送镜像到仓库(K8s 模式) + * 设置为 false 时只构建不推送,适用于测试环境 + */ + private boolean pushEnabled = false; + + /** + * 镜像拉取策略 + * - Always: 总是拉取(默认,需要镜像在仓库中) + * - IfNotPresent: 本地有则不拉取(适合本地构建 + 单节点/共享 Docker) + * - Never: 从不拉取(要求镜像必须已在节点上) + */ + private String imagePullPolicy = "IfNotPresent"; + + /** + * K8s 拉取私有镜像的 Secret 名称 + * 需要在 K8s 中预先创建,或由服务自动创建 + */ + private String imagePullSecret = "robot-registry-secret"; + + /** + * Docker 连接配置 + * 默认使用 unix socket: unix:///var/run/docker.sock + */ + private String dockerHost = "unix:///var/run/docker.sock"; + + /** + * 默认 Agent 基础镜像(K8s 模式构建使用内网 Harbor) + */ + private String defaultAgentBaseImage = "10.30.107.146/robot/rockylinux9-agent@sha256:b49d75f52f6b3c55bbf90427f0df0e97bc8e3f3e03727721cafc2c9d775b8975"; + + /** + * Compose 模式基础镜像(用户本地构建,需要可公开拉取的镜像) + */ + private String composeBaseImage = "rockylinux:9"; + + /** + * 镜像仓库地址 + * K8s 模式下构建的镜像会推送到此仓库 + */ + private String registry = ""; + + /** + * 镜像仓库用户名 + */ + private String registryUsername = ""; + + /** + * 镜像仓库密码 + */ + private String registryPassword = ""; + + /** + * 构建脚本路径 + * 此脚本会在 Dockerfile 中被 COPY 并执行 + */ + private String buildScriptPath = "/opt/scripts/build.sh"; + + /** + * 构建超时时间(秒) + */ + private int buildTimeout = 300; + + /** + * 入口点脚本名称 + */ + private String entrypointScript = "/entrypoint.sh"; + + /** + * 构建上下文临时目录 + */ + private String buildContextDir = "/tmp/docker-build"; + + /** + * SDK 仓库 HTTPS 地址(用于镜像构建阶段克隆) + */ + private String sdkRepoUrl = ""; + + /** + * SDK 仓库克隆分支(默认 test122) + */ + private String sdkRepoBranch = "test122"; + + /** + * SDK 仓库克隆用户名 + */ + private String sdkRepoUsername = ""; + + /** + * SDK 仓库克隆密码 + */ + private String sdkRepoPassword = ""; + + /** + * SDK 源码在镜像中的目标路径 + * 从 SDK 仓库的 momo-agent-sdk/ 目录拷贝 + */ + private String sdkSourcePath = "/opt/momo-agent-build/sdk-source"; + + /** + * zzd 二进制文件在镜像中的目标路径 + * 从 SDK 仓库的 docker/agent/zzd/ 目录拷贝 + */ + private String zzdBinariesPath = "/opt/momo-agent-build/zzd-binaries"; +} diff --git a/back/src/main/java/com/linkwork/config/KubernetesConfig.java b/back/src/main/java/com/linkwork/config/KubernetesConfig.java new file mode 100644 index 0000000..19b2b8e --- /dev/null +++ b/back/src/main/java/com/linkwork/config/KubernetesConfig.java @@ -0,0 +1,47 @@ +package com.linkwork.config; + +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClientBuilder; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Kubernetes 客户端配置 + */ +@Configuration +@Slf4j +public class KubernetesConfig { + + private final EnvConfig envConfig; + + public KubernetesConfig(EnvConfig envConfig) { + this.envConfig = envConfig; + } + + @Bean + public KubernetesClient kubernetesClient() { + String kubeconfigPath = envConfig.getCluster().getKubeconfigPath(); + + if (kubeconfigPath != null && !kubeconfigPath.isBlank()) { + log.info("Loading kubeconfig from: {}", kubeconfigPath); + try { + String kubeconfigContent = Files.readString(Path.of(kubeconfigPath)); + Config config = Config.fromKubeconfig(kubeconfigContent); + return new KubernetesClientBuilder().withConfig(config).build(); + } catch (IOException e) { + log.error("Failed to load kubeconfig from {}: {}", kubeconfigPath, e.getMessage()); + throw new RuntimeException("Failed to load kubeconfig", e); + } + } + + // 使用默认配置(从环境变量或默认路径) + log.info("Using default Kubernetes configuration"); + return new KubernetesClientBuilder().build(); + } +} diff --git a/back/src/main/java/com/linkwork/config/MemoryConfig.java b/back/src/main/java/com/linkwork/config/MemoryConfig.java new file mode 100644 index 0000000..72bf8c4 --- /dev/null +++ b/back/src/main/java/com/linkwork/config/MemoryConfig.java @@ -0,0 +1,48 @@ +package com.linkwork.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Data +@Configuration +@ConfigurationProperties(prefix = "memory") +public class MemoryConfig { + + private boolean enabled = true; + private Milvus milvus = new Milvus(); + private Embedding embedding = new Embedding(); + private Index index = new Index(); + private String ossMountPath = "/data/oss"; + + @Data + public static class Milvus { + private String uri = "http://milvus:19530"; + private String token = ""; + } + + @Data + public static class Embedding { + private String model = "text-embedding-3-small"; + private int dimension = 1536; + } + + @Data + public static class Index { + private int maxChunkSize = 1500; + private int overlapLines = 2; + private String queueKey = "memory:index:jobs"; + } + + public String collectionName(String workstationId, String userId) { + return "memory_" + sanitize(workstationId) + "_" + sanitize(userId); + } + + public String userCollectionName(String userId) { + return "memory_user_" + sanitize(userId); + } + + private static String sanitize(String s) { + return s.replaceAll("[^a-zA-Z0-9_]", "_"); + } +} diff --git a/back/src/main/java/com/linkwork/config/MyBatisPlusConfig.java b/back/src/main/java/com/linkwork/config/MyBatisPlusConfig.java new file mode 100644 index 0000000..1eeea89 --- /dev/null +++ b/back/src/main/java/com/linkwork/config/MyBatisPlusConfig.java @@ -0,0 +1,47 @@ +package com.linkwork.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.time.LocalDateTime; + +/** + * MyBatis Plus 配置 + */ +@Configuration +public class MyBatisPlusConfig { + + /** + * 分页插件 + */ + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + return interceptor; + } + + /** + * 自动填充处理器 + */ + @Bean + public MetaObjectHandler metaObjectHandler() { + return new MetaObjectHandler() { + @Override + public void insertFill(MetaObject metaObject) { + this.strictInsertFill(metaObject, "createdAt", LocalDateTime.class, LocalDateTime.now()); + this.strictInsertFill(metaObject, "updatedAt", LocalDateTime.class, LocalDateTime.now()); + } + + @Override + public void updateFill(MetaObject metaObject) { + this.strictUpdateFill(metaObject, "updatedAt", LocalDateTime.class, LocalDateTime.now()); + } + }; + } +} diff --git a/back/src/main/java/com/linkwork/config/NfsStorageConfig.java b/back/src/main/java/com/linkwork/config/NfsStorageConfig.java new file mode 100644 index 0000000..a8e88cc --- /dev/null +++ b/back/src/main/java/com/linkwork/config/NfsStorageConfig.java @@ -0,0 +1,40 @@ +package com.linkwork.config; + +import jakarta.annotation.PostConstruct; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.nio.file.Files; +import java.nio.file.Path; + +@Slf4j +@Data +@Configuration +@ConfigurationProperties(prefix = "nfs.storage") +public class NfsStorageConfig { + + /** NFS 本地挂载根路径,后续更换 NFS 服务器只需重新 mount + 改此值 */ + private String basePath = "/mnt/oss/robot-agent-files"; + + /** 后端文件下载 API 的 URL 前缀 */ + private String downloadBaseUrl = "/api/v1/files"; + + /** 任务产出物下载 API 的 URL 前缀 */ + private String taskOutputBaseUrl = "/api/v1/task-outputs"; + + @PostConstruct + public void validate() { + Path base = Path.of(basePath); + if (Files.isDirectory(base)) { + log.info("NFS storage configured: basePath={}", basePath); + } else { + log.warn("NFS storage basePath does not exist or is not a directory: {}", basePath); + } + } + + public Path resolve(String relativePath) { + return Path.of(basePath).resolve(relativePath); + } +} diff --git a/back/src/main/java/com/linkwork/config/WebSocketConfig.java b/back/src/main/java/com/linkwork/config/WebSocketConfig.java new file mode 100644 index 0000000..46d014d --- /dev/null +++ b/back/src/main/java/com/linkwork/config/WebSocketConfig.java @@ -0,0 +1,24 @@ +package com.linkwork.config; + +import com.linkwork.websocket.TaskWebSocketHandler; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; + +@Configuration +@EnableWebSocket +public class WebSocketConfig implements WebSocketConfigurer { + + private final TaskWebSocketHandler taskWebSocketHandler; + + public WebSocketConfig(TaskWebSocketHandler taskWebSocketHandler) { + this.taskWebSocketHandler = taskWebSocketHandler; + } + + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + registry.addHandler(taskWebSocketHandler, "/api/v1/ws") + .setAllowedOrigins("*"); + } +} diff --git a/back/src/main/java/com/linkwork/context/UserContext.java b/back/src/main/java/com/linkwork/context/UserContext.java new file mode 100644 index 0000000..9dad7bc --- /dev/null +++ b/back/src/main/java/com/linkwork/context/UserContext.java @@ -0,0 +1,61 @@ +package com.linkwork.context; + +/** + * 用户上下文(ThreadLocal) + *

+ * 替代现有的 X-User-Id Header 和 Mock 硬编码。 + * 由 JwtAuthFilter 在请求进入时设置,请求结束时清除。 + * 用户信息全部来自 JWT payload,不查数据库。 + */ +public final class UserContext { + + private static final ThreadLocal HOLDER = new ThreadLocal<>(); + + private UserContext() { + } + + /** + * 设置当前用户(由 Filter 调用) + */ + public static void set(UserInfo userInfo) { + HOLDER.set(userInfo); + } + + /** + * 获取当前用户(完整信息) + */ + public static UserInfo get() { + return HOLDER.get(); + } + + /** + * 获取当前用户 ID + */ + public static String getCurrentUserId() { + UserInfo info = HOLDER.get(); + return info != null ? info.getUserId() : null; + } + + /** + * 获取当前用户姓名 + */ + public static String getCurrentUserName() { + UserInfo info = HOLDER.get(); + return info != null ? info.getName() : null; + } + + /** + * 获取当前用户邮箱 + */ + public static String getCurrentEmail() { + UserInfo info = HOLDER.get(); + return info != null ? info.getEmail() : null; + } + + /** + * 清除当前用户(由 Filter 在 finally 中调用,防止内存泄漏) + */ + public static void clear() { + HOLDER.remove(); + } +} diff --git a/back/src/main/java/com/linkwork/context/UserInfo.java b/back/src/main/java/com/linkwork/context/UserInfo.java new file mode 100644 index 0000000..0efae09 --- /dev/null +++ b/back/src/main/java/com/linkwork/context/UserInfo.java @@ -0,0 +1,36 @@ +package com.linkwork.context; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 当前登录用户信息(从 JWT payload 解析,不查数据库) + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UserInfo { + + /** 唯一用户标识 */ + private String userId; + + /** 姓名 */ + private String name; + + /** 邮箱 */ + private String email; + + /** 工号 */ + private String workId; + + /** 头像 URL */ + private String avatarUrl; + + /** 权限列表 */ + private List permissions; +} diff --git a/back/src/main/java/com/linkwork/controller/ApprovalController.java b/back/src/main/java/com/linkwork/controller/ApprovalController.java new file mode 100644 index 0000000..df15528 --- /dev/null +++ b/back/src/main/java/com/linkwork/controller/ApprovalController.java @@ -0,0 +1,116 @@ +package com.linkwork.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.linkwork.common.ApiResponse; +import com.linkwork.common.ClientIpResolver; +import com.linkwork.context.UserContext; +import com.linkwork.model.entity.Approval; +import com.linkwork.service.ApprovalService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import org.springframework.util.StringUtils; + +import java.util.HashMap; +import jakarta.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.Map; + +/** + * 审批控制器 + */ +@Slf4j +@RestController +@RequestMapping("/api/v1/approvals") +@CrossOrigin(origins = "*") +@RequiredArgsConstructor +public class ApprovalController { + + private final ApprovalService approvalService; + + /** + * 获取审批列表 + * GET /api/v1/approvals + */ + @GetMapping + public ApiResponse> listApprovals( + @RequestParam(required = false) String status, + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "20") Integer pageSize) { + String userId = UserContext.getCurrentUserId(); + if (!StringUtils.hasText(userId)) { + throw new IllegalStateException("用户未登录或登录态失效"); + } + log.info("获取审批列表: status={}, page={}, pageSize={}, userId={}", status, page, pageSize, userId); + + Page approvalPage = approvalService.listApprovals(status, page, pageSize, userId); + List> items = approvalService.toResponseList(approvalPage.getRecords()); + + Map pagination = new HashMap<>(); + pagination.put("page", approvalPage.getCurrent()); + pagination.put("pageSize", approvalPage.getSize()); + pagination.put("total", approvalPage.getTotal()); + pagination.put("totalPages", approvalPage.getPages()); + + Map result = new HashMap<>(); + result.put("items", items); + result.put("pagination", pagination); + + return ApiResponse.success(result); + } + + /** + * 获取审批统计 + * GET /api/v1/approvals/stats + */ + @GetMapping("/stats") + public ApiResponse> getStats() { + String userId = UserContext.getCurrentUserId(); + if (!StringUtils.hasText(userId)) { + throw new IllegalStateException("用户未登录或登录态失效"); + } + log.info("获取审批统计: userId={}", userId); + return ApiResponse.success(approvalService.getStats(userId)); + } + + /** + * 提交审批决策 + * POST /api/v1/approvals/{approvalNo}/decision + */ + @PostMapping("/{approvalNo}/decision") + public ApiResponse> decide( + @PathVariable String approvalNo, + @RequestBody Map request, + HttpServletRequest servletRequest) { + String userId = UserContext.getCurrentUserId(); + String userName = UserContext.getCurrentUserName(); + String decision = request.get("decision"); + String comment = request.get("comment"); + String operatorIp = ClientIpResolver.resolve(servletRequest); + log.info("审批决策: approvalNo={}, decision={}, userId={}, operatorIp={}", approvalNo, decision, userId, operatorIp); + + Approval approval = approvalService.decide(approvalNo, decision, comment, userId, userName, operatorIp); + return ApiResponse.success(approvalService.toResponse(approval)); + } + + /** + * 创建审批请求(内部接口,由 Agent/Worker 调用) + * POST /api/v1/approvals/create + */ + @PostMapping("/create") + public ApiResponse> createApproval(@RequestBody Map request) { + log.info("创建审批请求: taskNo={}, action={}", request.get("taskNo"), request.get("action")); + + Approval approval = approvalService.createApproval( + request.get("taskNo"), + request.get("taskTitle"), + request.get("action"), + request.get("description"), + request.get("riskLevel"), + request.getOrDefault("creatorId", "agent"), + request.getOrDefault("creatorName", "AI Agent") + ); + + return ApiResponse.success(approvalService.toResponse(approval)); + } +} diff --git a/back/src/main/java/com/linkwork/controller/AuthController.java b/back/src/main/java/com/linkwork/controller/AuthController.java new file mode 100644 index 0000000..58aad70 --- /dev/null +++ b/back/src/main/java/com/linkwork/controller/AuthController.java @@ -0,0 +1,135 @@ +package com.linkwork.controller; + +import com.linkwork.common.ApiResponse; +import com.linkwork.context.UserContext; +import com.linkwork.context.UserInfo; +import com.linkwork.service.AuthService; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * 认证控制器 + * 处理登录验证和 Token 校验 + */ +@Slf4j +@RestController +@RequestMapping("/api/v1/auth") +@CrossOrigin(origins = "*") +@RequiredArgsConstructor +public class AuthController { + + private final AuthService authService; + public static final String COOKIE_NAME = "robot_token"; + + /** + * 登录验证 + * POST /api/v1/auth/login + */ + @PostMapping("/login") + public ApiResponse> login(@Valid @RequestBody LoginRequest request) { + log.info("登录请求"); + + // 验证密码 + if (!authService.validatePassword(request.getPassword())) { + log.warn("密码验证失败"); + return ApiResponse.error(40100, "密码错误"); + } + + // 生成 Token + String token = authService.generateToken("robot-user"); + + Map result = new HashMap<>(); + result.put("token", token); + result.put("expiresIn", 86400); // 24 小时(秒) + + log.info("登录成功"); + return ApiResponse.success(result); + } + + /** + * 验证 Token + * POST /api/v1/auth/verify + */ + @PostMapping("/verify") + public ApiResponse> verify(@RequestHeader(value = "Authorization", required = false) String authHeader) { + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + return ApiResponse.error(40101, "未提供有效的 Token"); + } + + String token = authHeader.substring(7); + + if (!authService.validateToken(token)) { + return ApiResponse.error(40101, "Token 无效或已过期"); + } + + Map result = new HashMap<>(); + result.put("valid", true); + result.put("subject", authService.getSubjectFromToken(token)); + + return ApiResponse.success(result); + } + + /** + * 生成密码哈希(工具端点,生产环境可禁用) + * GET /api/v1/auth/encode?password=xxx + */ + @GetMapping("/encode") + public ApiResponse> encodePassword(@RequestParam String password) { + String hash = authService.encodePassword(password); + Map result = new HashMap<>(); + result.put("password", password); + result.put("hash", hash); + return ApiResponse.success(result); + } + + /** + * 获取当前登录用户信息(从 JWT payload 解析)。 + */ + @GetMapping("/me") + public ApiResponse> me() { + UserInfo userInfo = UserContext.get(); + if (userInfo == null) { + return ApiResponse.error(40100, "未登录"); + } + + Map data = new HashMap<>(); + data.put("userId", userInfo.getUserId()); + data.put("name", userInfo.getName()); + data.put("email", userInfo.getEmail()); + data.put("workId", userInfo.getWorkId()); + data.put("avatarUrl", userInfo.getAvatarUrl()); + data.put("permissions", userInfo.getPermissions()); + return ApiResponse.success(data); + } + + /** + * 登出:清理 token cookie。 + */ + @PostMapping("/logout") + public ApiResponse> logout(HttpServletResponse response) { + Cookie cookie = new Cookie(COOKIE_NAME, ""); + cookie.setPath("/"); + cookie.setHttpOnly(true); + cookie.setMaxAge(0); + response.addCookie(cookie); + + Map data = new HashMap<>(); + data.put("logoutUrl", "/login"); + return ApiResponse.success(data); + } + + @Data + public static class LoginRequest { + @NotBlank(message = "密码不能为空") + private String password; + } +} diff --git a/back/src/main/java/com/linkwork/controller/BuildLogController.java b/back/src/main/java/com/linkwork/controller/BuildLogController.java new file mode 100644 index 0000000..80b6a0e --- /dev/null +++ b/back/src/main/java/com/linkwork/controller/BuildLogController.java @@ -0,0 +1,174 @@ +package com.linkwork.controller; + +import com.linkwork.service.BuildLogBuffer; +import com.linkwork.service.BuildRecordService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * 构建日志 SSE 控制器 + * 直接从 build.sh 执行输出推送日志,不依赖 Redis Stream + */ +@Slf4j +@RestController +@RequestMapping("/api/v1/build-logs") +@RequiredArgsConstructor +public class BuildLogController { + + private final BuildLogBuffer logBuffer; + private final BuildRecordService buildRecordService; + + /** + * 兼容旧查询入口:按 roleId 获取最近一次构建日志 + */ + @GetMapping + public Map getLogsByQuery( + @RequestParam(required = false) String buildId, + @RequestParam(required = false) Long roleId, + @RequestParam(defaultValue = "0") int afterIndex) { + + String targetBuildId = buildId; + if ((targetBuildId == null || targetBuildId.isBlank()) && roleId != null) { + var latest = buildRecordService.getLatestByRoleId(roleId); + if (latest == null || latest.getBuildNo() == null || latest.getBuildNo().isBlank()) { + return Map.of( + "buildId", "", + "logs", List.of(), + "totalCount", 0, + "completed", true, + "success", false + ); + } + targetBuildId = latest.getBuildNo(); + } + + if (targetBuildId == null || targetBuildId.isBlank()) { + throw new IllegalArgumentException("buildId 或 roleId 至少传一个"); + } + return getLogs(targetBuildId, afterIndex); + } + + /** + * SSE 端点:实时接收构建日志 + * + * @param buildId 构建 ID + * @return SSE 事件流 + */ + @GetMapping(value = "/{buildId}/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public SseEmitter streamLogs(@PathVariable String buildId) { + log.info("SSE connection opened for buildId: {}", buildId); + + // 10 分钟超时 + SseEmitter emitter = new SseEmitter(TimeUnit.MINUTES.toMillis(10)); + + // 先推送历史日志 + List history = logBuffer.getHistory(buildId); + for (BuildLogBuffer.LogEntry entry : history) { + try { + emitter.send(SseEmitter.event() + .name("log") + .data(Map.of( + "timestamp", entry.timestamp(), + "level", entry.level(), + "message", entry.message() + ))); + } catch (IOException e) { + log.debug("Failed to send history log: {}", e.getMessage()); + } + } + + // 如果构建已完成,发送完成事件并关闭 + if (logBuffer.isCompleted(buildId)) { + try { + Boolean success = logBuffer.getCompletionStatus(buildId); + emitter.send(SseEmitter.event().name("complete").data(Map.of( + "success", success != null ? success : false, + "message", success != null && success ? "构建成功" : "构建失败" + ))); + emitter.complete(); + } catch (IOException e) { + log.debug("Failed to send complete event: {}", e.getMessage()); + } + return emitter; + } + + // 订阅新日志 + java.util.function.Consumer subscriber = entry -> { + try { + emitter.send(SseEmitter.event() + .name("log") + .data(Map.of( + "timestamp", entry.timestamp(), + "level", entry.level(), + "message", entry.message() + ))); + + // 检查是否完成 + if (logBuffer.isCompleted(buildId)) { + Boolean success = logBuffer.getCompletionStatus(buildId); + emitter.send(SseEmitter.event().name("complete").data(Map.of( + "success", success != null ? success : false, + "message", success != null && success ? "构建成功" : "构建失败" + ))); + emitter.complete(); + } + } catch (IOException e) { + log.debug("Failed to send log via SSE: {}", e.getMessage()); + } + }; + + logBuffer.subscribe(buildId, subscriber); + + // 连接关闭时取消订阅 + emitter.onCompletion(() -> { + logBuffer.unsubscribe(buildId, subscriber); + log.debug("SSE connection completed for buildId: {}", buildId); + }); + + emitter.onTimeout(() -> { + logBuffer.unsubscribe(buildId, subscriber); + log.debug("SSE connection timeout for buildId: {}", buildId); + }); + + emitter.onError(e -> { + logBuffer.unsubscribe(buildId, subscriber); + log.debug("SSE connection error for buildId: {}", buildId); + }); + + return emitter; + } + + /** + * 轮询接口:获取历史日志(备用) + */ + @GetMapping("/{buildId}") + public Map getLogs( + @PathVariable String buildId, + @RequestParam(defaultValue = "0") int afterIndex) { + + List logs = logBuffer.getLogsAfter(buildId, afterIndex); + List allLogs = logBuffer.getHistory(buildId); + boolean completed = logBuffer.isCompleted(buildId); + Boolean success = logBuffer.getCompletionStatus(buildId); + + return Map.of( + "buildId", buildId, + "logs", logs.stream().map(e -> Map.of( + "timestamp", e.timestamp(), + "level", e.level(), + "message", e.message() + )).toList(), + "totalCount", allLogs.size(), + "completed", completed, + "success", success != null ? success : false + ); + } +} diff --git a/back/src/main/java/com/linkwork/controller/BuildQueueController.java b/back/src/main/java/com/linkwork/controller/BuildQueueController.java new file mode 100644 index 0000000..404f310 --- /dev/null +++ b/back/src/main/java/com/linkwork/controller/BuildQueueController.java @@ -0,0 +1,106 @@ +package com.linkwork.controller; + +import com.linkwork.common.ApiResponse; +import com.linkwork.model.dto.BuildQueueStatus; +import com.linkwork.service.BuildQueueService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +/** + * 构建队列控制器 + * + * 提供队列状态查询和任务管理 API + */ +@RestController +@RequestMapping("/api/v1/build-queue") +@Slf4j +public class BuildQueueController { + + private final BuildQueueService buildQueueService; + + public BuildQueueController(BuildQueueService buildQueueService) { + this.buildQueueService = buildQueueService; + } + + /** + * 获取队列状态 + * + * 返回:等待任务数、执行中任务数、系统资源状态等 + */ + @GetMapping("/status") + public ResponseEntity> getStatus() { + if (!buildQueueService.isEnabled()) { + return ResponseEntity.ok(ApiResponse.error("Build queue is disabled")); + } + + BuildQueueStatus status = buildQueueService.getStatus(); + return ResponseEntity.ok(ApiResponse.success(status)); + } + + /** + * 获取任务在队列中的位置 + * + * @param buildId 构建 ID + * @return 位置信息:正数表示排队位置,-1 表示正在执行,null 表示不存在 + */ + @GetMapping("/position/{buildId}") + public ResponseEntity> getPosition(@PathVariable String buildId) { + if (!buildQueueService.isEnabled()) { + return ResponseEntity.ok(ApiResponse.error("Build queue is disabled")); + } + + Integer position = buildQueueService.getPosition(buildId); + + if (position == null) { + return ResponseEntity.ok(ApiResponse.error("Task not found in queue")); + } + + String status; + String message; + if (position == -1) { + status = "RUNNING"; + message = "Task is currently executing"; + } else { + status = "WAITING"; + message = "Task is waiting at position " + position; + } + + PositionResponse response = new PositionResponse(buildId, position, status, message); + return ResponseEntity.ok(ApiResponse.success(response)); + } + + /** + * 取消排队中的任务 + * + * 注意:只能取消等待中的任务,正在执行的任务无法取消 + * + * @param buildId 构建 ID + */ + @DeleteMapping("/{buildId}") + public ResponseEntity> cancel(@PathVariable String buildId) { + if (!buildQueueService.isEnabled()) { + return ResponseEntity.ok(ApiResponse.error("Build queue is disabled")); + } + + log.info("收到取消任务请求: buildId={}", buildId); + + boolean cancelled = buildQueueService.cancel(buildId); + + if (cancelled) { + return ResponseEntity.ok(ApiResponse.success(null)); + } else { + return ResponseEntity.ok(ApiResponse.error("Cannot cancel task: either running or not found")); + } + } + + /** + * 位置响应 DTO + */ + public record PositionResponse( + String buildId, + int position, + String status, + String message + ) {} +} diff --git a/back/src/main/java/com/linkwork/controller/BuildRecordController.java b/back/src/main/java/com/linkwork/controller/BuildRecordController.java new file mode 100644 index 0000000..b75ecd7 --- /dev/null +++ b/back/src/main/java/com/linkwork/controller/BuildRecordController.java @@ -0,0 +1,121 @@ +package com.linkwork.controller; + +import com.linkwork.model.entity.BuildRecordEntity; +import com.linkwork.service.BuildRecordService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +/** + * 构建记录 Controller + * + * 提供构建历史查询接口 + */ +@Slf4j +@RestController +@RequestMapping("/api/v1/build-records") +@CrossOrigin(origins = "*") +@RequiredArgsConstructor +public class BuildRecordController { + + private final BuildRecordService buildRecordService; + + /** + * 获取构建记录列表 + * + * @param roleId 岗位 ID(可选,不传则查询所有) + * @param status 状态过滤(可选) + * @param page 页码 + * @param pageSize 每页数量 + */ + @GetMapping + public Map listBuildRecords( + @RequestParam(required = false) Long roleId, + @RequestParam(required = false) String status, + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "20") int pageSize) { + + Map response = new HashMap<>(); + response.put("code", 0); + response.put("msg", "success"); + response.put("timestamp", LocalDateTime.now()); + + Map data; + if (roleId != null) { + data = buildRecordService.listByRoleId(roleId, page, pageSize); + } else { + data = buildRecordService.listRecent(page, pageSize, status); + } + response.put("data", data); + + return response; + } + + /** + * 根据构建编号获取详情 + */ + @GetMapping("/{buildNo}") + public Map getBuildRecord(@PathVariable String buildNo) { + Map response = new HashMap<>(); + + BuildRecordEntity entity = buildRecordService.getByBuildNo(buildNo); + if (entity == null) { + response.put("code", 404); + response.put("msg", "Build record not found: " + buildNo); + response.put("timestamp", LocalDateTime.now()); + return response; + } + + response.put("code", 0); + response.put("msg", "success"); + response.put("timestamp", LocalDateTime.now()); + + Map data = new HashMap<>(); + data.put("id", entity.getId().toString()); + data.put("buildNo", entity.getBuildNo()); + data.put("roleId", entity.getRoleId() != null ? entity.getRoleId().toString() : null); + data.put("roleName", entity.getRoleName()); + data.put("status", entity.getStatus()); + data.put("imageTag", entity.getImageTag()); + data.put("durationMs", entity.getDurationMs()); + data.put("errorMessage", entity.getErrorMessage()); + data.put("configSnapshot", entity.getConfigSnapshot()); + data.put("creatorId", entity.getCreatorId()); + data.put("creatorName", entity.getCreatorName()); + data.put("createdAt", entity.getCreatedAt()); + data.put("updatedAt", entity.getUpdatedAt()); + + response.put("data", data); + return response; + } + + /** + * 获取岗位最新一次构建记录 + */ + @GetMapping("/role/{roleId}/latest") + public Map getLatestBuildRecord(@PathVariable Long roleId) { + Map response = new HashMap<>(); + + Map records = buildRecordService.listByRoleId(roleId, 1, 1); + @SuppressWarnings("unchecked") + java.util.List> items = (java.util.List>) records.get("items"); + + if (items == null || items.isEmpty()) { + response.put("code", 0); + response.put("msg", "success"); + response.put("data", null); + response.put("timestamp", LocalDateTime.now()); + return response; + } + + response.put("code", 0); + response.put("msg", "success"); + response.put("data", items.get(0)); + response.put("timestamp", LocalDateTime.now()); + return response; + } +} diff --git a/back/src/main/java/com/linkwork/controller/CronJobController.java b/back/src/main/java/com/linkwork/controller/CronJobController.java new file mode 100644 index 0000000..829a59f --- /dev/null +++ b/back/src/main/java/com/linkwork/controller/CronJobController.java @@ -0,0 +1,106 @@ +package com.linkwork.controller; + +import com.linkwork.common.ApiResponse; +import com.linkwork.context.UserContext; +import com.linkwork.model.dto.CronJobCreateRequest; +import com.linkwork.model.dto.CronJobResponse; +import com.linkwork.model.dto.CronJobRunResponse; +import com.linkwork.model.dto.CronJobToggleRequest; +import com.linkwork.model.dto.CronJobUpdateRequest; +import com.linkwork.model.dto.CronSchedulePreviewRequest; +import com.linkwork.model.entity.CronJob; +import com.linkwork.model.entity.CronJobRun; +import com.linkwork.service.CronJobExecutor; +import com.linkwork.service.CronJobService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +@RestController +@RequestMapping("/api/v1/cron-jobs") +@RequiredArgsConstructor +public class CronJobController { + + private final CronJobService cronJobService; + private final CronJobExecutor cronJobExecutor; + + @PostMapping + public ApiResponse create(@Valid @RequestBody CronJobCreateRequest request) { + String userId = UserContext.getCurrentUserId(); + String userName = UserContext.getCurrentUserName(); + return ApiResponse.success(cronJobService.create(request, userId, userName)); + } + + @PostMapping("/preview") + public ApiResponse> preview(@RequestBody CronSchedulePreviewRequest request) { + return ApiResponse.success(Map.of( + "nextFireTimes", + cronJobService.previewSchedule(request.getScheduleType(), request.getCronExpr(), request.getIntervalMs(), + request.getRunAt(), request.getTimezone(), request.getLimit()) + )); + } + + @GetMapping + public ApiResponse> listMine( + @RequestParam(value = "scope", defaultValue = "mine") String scope, + @RequestParam(value = "roleId", required = false) Long roleId, + @RequestParam(value = "enabled", required = false) Boolean enabled, + @RequestParam(value = "scheduleType", required = false) String scheduleType, + @RequestParam(value = "keyword", required = false) String keyword, + @RequestParam(value = "page", defaultValue = "1") Integer page, + @RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) { + if (StringUtils.hasText(scope) && !"mine".equalsIgnoreCase(scope.trim())) { + throw new IllegalArgumentException("MVP 仅支持 scope=mine"); + } + return ApiResponse.success(cronJobService.listMine( + UserContext.getCurrentUserId(), roleId, enabled, scheduleType, keyword, page, pageSize)); + } + + @GetMapping("/{id}") + public ApiResponse detail(@PathVariable Long id) { + return ApiResponse.success(cronJobService.getDetail(id, UserContext.getCurrentUserId())); + } + + @PutMapping("/{id}") + public ApiResponse update(@PathVariable Long id, + @Valid @RequestBody CronJobUpdateRequest request) { + return ApiResponse.success(cronJobService.update(id, request, UserContext.getCurrentUserId())); + } + + @PutMapping("/{id}/toggle") + public ApiResponse toggle(@PathVariable Long id, + @Valid @RequestBody CronJobToggleRequest request) { + return ApiResponse.success(cronJobService.toggle(id, request, UserContext.getCurrentUserId())); + } + + @PostMapping("/{id}/trigger") + public ApiResponse trigger(@PathVariable Long id) { + CronJob job = cronJobService.getOwnedJob(id, UserContext.getCurrentUserId()); + CronJobRun run = cronJobExecutor.dispatchManual(job); + return ApiResponse.success(cronJobService.toRunResponse(run)); + } + + @GetMapping("/{id}/runs") + public ApiResponse> listRuns(@PathVariable Long id, + @RequestParam(value = "page", defaultValue = "1") Integer page, + @RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) { + return ApiResponse.success(cronJobService.listRuns(id, UserContext.getCurrentUserId(), page, pageSize)); + } + + @DeleteMapping("/{id}") + public ApiResponse delete(@PathVariable Long id) { + cronJobService.delete(id, UserContext.getCurrentUserId()); + return ApiResponse.success(); + } +} diff --git a/back/src/main/java/com/linkwork/controller/FileController.java b/back/src/main/java/com/linkwork/controller/FileController.java new file mode 100644 index 0000000..68697d7 --- /dev/null +++ b/back/src/main/java/com/linkwork/controller/FileController.java @@ -0,0 +1,191 @@ +package com.linkwork.controller; + +import com.linkwork.common.ApiResponse; +import com.linkwork.context.UserContext; +import com.linkwork.model.dto.CreateFolderRequest; +import com.linkwork.model.dto.FileMentionResponse; +import com.linkwork.model.dto.FileNodeResponse; +import com.linkwork.model.dto.FileResponse; +import com.linkwork.model.dto.FileSpaceSyncRequest; +import com.linkwork.model.dto.FileSpaceSyncResponse; +import com.linkwork.model.dto.FileTransferRequest; +import com.linkwork.service.FileNodeService; +import com.linkwork.service.FileSpaceSyncService; +import com.linkwork.service.FileService; +import com.linkwork.service.NfsStorageService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.InputStreamResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; + +@Slf4j +@RestController +@RequestMapping("/api/v1/files") +@RequiredArgsConstructor +public class FileController { + + private final FileService fileService; + private final FileNodeService fileNodeService; + private final FileSpaceSyncService fileSpaceSyncService; + private final NfsStorageService nfsStorageService; + + @PostMapping("/upload") + public ApiResponse uploadFile( + @RequestParam("file") MultipartFile file, + @RequestParam("spaceType") String spaceType, + @RequestParam(value = "workstationId", required = false) String workstationId, + @RequestParam(value = "conflictPolicy", required = false) String conflictPolicy, + @RequestParam(value = "parentId", required = false) String parentId) { + String userId = UserContext.getCurrentUserId(); + return ApiResponse.success(fileService.uploadFile(file, spaceType, workstationId, userId, conflictPolicy, parentId)); + } + + @GetMapping("/tree") + public ApiResponse> listTree( + @RequestParam("spaceType") String spaceType, + @RequestParam(value = "workstationId", required = false) String workstationId, + @RequestParam(value = "parentId", required = false) String parentId) { + String userId = UserContext.getCurrentUserId(); + return ApiResponse.success(fileNodeService.listChildren(spaceType, workstationId, parentId, userId)); + } + + @PostMapping("/sync") + public ApiResponse syncSpace(@RequestBody FileSpaceSyncRequest request) { + String userId = UserContext.getCurrentUserId(); + return ApiResponse.success(fileSpaceSyncService.syncSpace(userId, request)); + } + + @PostMapping("/folders") + public ApiResponse createFolder(@RequestBody CreateFolderRequest request) { + String userId = UserContext.getCurrentUserId(); + return ApiResponse.success(fileNodeService.createFolder(request, userId)); + } + + @PutMapping("/nodes/{nodeId}/rename") + public ApiResponse renameNode(@PathVariable String nodeId, + @RequestBody Map body) { + String userId = UserContext.getCurrentUserId(); + fileNodeService.renameNode(nodeId, body.get("name"), userId); + return ApiResponse.success(); + } + + @DeleteMapping("/nodes/{nodeId}") + public ApiResponse deleteNode(@PathVariable String nodeId) { + String userId = UserContext.getCurrentUserId(); + fileNodeService.deleteNode(nodeId, userId); + return ApiResponse.success(); + } + + @GetMapping + public ApiResponse> listFiles( + @RequestParam("spaceType") String spaceType, + @RequestParam(value = "workstationId", required = false) String workstationId, + @RequestParam(value = "fileType", required = false) String fileType, + @RequestParam(value = "keyword", required = false) String keyword, + @RequestParam(value = "page", defaultValue = "1") Integer page, + @RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) { + return ApiResponse.success(fileService.listFiles(spaceType, workstationId, fileType, keyword, page, pageSize, + UserContext.getCurrentUserId())); + } + + @GetMapping("/{fileId}") + public ApiResponse getFileDetail(@PathVariable String fileId) { + return ApiResponse.success(fileService.getFileDetail(fileId, UserContext.getCurrentUserId())); + } + + @GetMapping("/{fileId}/download") + public ResponseEntity downloadFile( + @PathVariable String fileId, + @RequestParam(value = "inline", defaultValue = "false") boolean inline) throws IOException { + String userId = UserContext.getCurrentUserId(); + FileService.DownloadInfo info = fileService.getDownloadInfo(fileId, userId); + + Path filePath = nfsStorageService.getAbsolutePath(info.storagePath()); + if (!Files.exists(filePath)) { + throw new IllegalStateException("文件不存在于存储中: " + info.storagePath()); + } + + String encodedFileName = URLEncoder.encode(info.fileName(), StandardCharsets.UTF_8).replace("+", "%20"); + String contentType = resolveContentType(info.contentType(), info.fileName(), filePath); + String disposition = inline ? "inline" : "attachment"; + InputStream inputStream = Files.newInputStream(filePath); + + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, disposition + "; filename*=UTF-8''" + encodedFileName) + .contentType(MediaType.parseMediaType(contentType)) + .contentLength(Files.size(filePath)) + .body(new InputStreamResource(inputStream)); + } + + private String resolveContentType(String rawContentType, String fileName, Path filePath) { + if (rawContentType != null && !rawContentType.isBlank() + && !"application/octet-stream".equalsIgnoreCase(rawContentType)) { + return rawContentType; + } + try { + String detected = Files.probeContentType(filePath); + if (detected != null && !detected.isBlank()) { + return detected; + } + } catch (IOException ignore) { + // noop, fall through to extension mapping + } + String lowerName = fileName == null ? "" : fileName.toLowerCase(java.util.Locale.ROOT); + if (lowerName.endsWith(".pdf")) { + return MediaType.APPLICATION_PDF_VALUE; + } + if (lowerName.endsWith(".md") || lowerName.endsWith(".txt") || lowerName.endsWith(".log")) { + return MediaType.TEXT_PLAIN_VALUE; + } + if (lowerName.endsWith(".json")) { + return MediaType.APPLICATION_JSON_VALUE; + } + return MediaType.APPLICATION_OCTET_STREAM_VALUE; + } + + @DeleteMapping("/{fileId}") + public ApiResponse deleteFile(@PathVariable String fileId) { + fileService.deleteFile(fileId, UserContext.getCurrentUserId()); + return ApiResponse.success(); + } + + @PutMapping("/{fileId}") + public ApiResponse replaceFile( + @PathVariable String fileId, + @RequestParam("file") MultipartFile file) { + return ApiResponse.success(fileService.replaceFile(fileId, file, UserContext.getCurrentUserId())); + } + + @PostMapping("/{fileId}/copy") + public ApiResponse copyFile(@PathVariable String fileId, + @RequestBody FileTransferRequest request) { + return ApiResponse.success(fileService.copyFile(fileId, request, UserContext.getCurrentUserId())); + } + + @PostMapping("/{fileId}/move") + public ApiResponse moveFile(@PathVariable String fileId, + @RequestBody FileTransferRequest request) { + return ApiResponse.success(fileService.moveFile(fileId, request, UserContext.getCurrentUserId())); + } + + @GetMapping("/mention") + public ApiResponse> mentionFiles( + @RequestParam("workstationId") String workstationId, + @RequestParam(value = "keyword", required = false) String keyword) { + return ApiResponse.success(fileService.mentionFiles(workstationId, keyword, UserContext.getCurrentUserId())); + } +} diff --git a/back/src/main/java/com/linkwork/controller/GitLabAuthController.java b/back/src/main/java/com/linkwork/controller/GitLabAuthController.java new file mode 100644 index 0000000..66d205f --- /dev/null +++ b/back/src/main/java/com/linkwork/controller/GitLabAuthController.java @@ -0,0 +1,56 @@ +package com.linkwork.controller; + +import com.linkwork.common.ApiResponse; +import com.linkwork.context.UserContext; +import com.linkwork.model.entity.GitLabAuthEntity; +import com.linkwork.service.GitLabAuthService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/v1/auth/gitlab") +@RequiredArgsConstructor +public class GitLabAuthController { + + private final GitLabAuthService gitLabAuthService; + + @GetMapping("/url") + public ApiResponse> getAuthUrl( + @RequestParam(required = false) String redirectUri, + @RequestParam(defaultValue = "write") String scopeType) { + String url = gitLabAuthService.getAuthUrl(redirectUri, scopeType); + return ApiResponse.success(Map.of("url", url)); + } + + @PostMapping("/callback") + public ApiResponse callback(@RequestBody Map body) { + String userId = UserContext.getCurrentUserId(); + String code = body.get("code"); + String redirectUri = body.get("redirectUri"); + String scopeType = body.getOrDefault("scopeType", "write"); + gitLabAuthService.callback(userId, code, redirectUri, scopeType); + return ApiResponse.success(null); + } + + @GetMapping("/users") + public ApiResponse> listUsers() { + String userId = UserContext.getCurrentUserId(); + // We mask the tokens in the response + List list = gitLabAuthService.listUsers(userId); + list.forEach(item -> { + item.setAccessToken(null); + item.setRefreshToken(null); + }); + return ApiResponse.success(list); + } + + @DeleteMapping("/users/{id}") + public ApiResponse deleteUser(@PathVariable String id) { + String userId = UserContext.getCurrentUserId(); + gitLabAuthService.deleteUser(userId, id); + return ApiResponse.success(null); + } +} diff --git a/back/src/main/java/com/linkwork/controller/HealthController.java b/back/src/main/java/com/linkwork/controller/HealthController.java new file mode 100644 index 0000000..286dd95 --- /dev/null +++ b/back/src/main/java/com/linkwork/controller/HealthController.java @@ -0,0 +1,20 @@ +package com.linkwork.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +@RestController +public class HealthController { + + /** + * 健康检查接口 + * Docker 健康检查 + 外部监控使用 + * 同时映射 /health 和 /api/v1/health + */ + @GetMapping({"/health", "/api/v1/health"}) + public Map health() { + return Map.of("status", "UP"); + } +} diff --git a/back/src/main/java/com/linkwork/controller/ImageBuildController.java b/back/src/main/java/com/linkwork/controller/ImageBuildController.java new file mode 100644 index 0000000..69eafd8 --- /dev/null +++ b/back/src/main/java/com/linkwork/controller/ImageBuildController.java @@ -0,0 +1,52 @@ +package com.linkwork.controller; + +import com.linkwork.common.ApiResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.List; + +/** + * 镜像构建 Controller + * + * 提供构建相关的配置查询接口 + */ +@Slf4j +@RestController +@RequestMapping({"/api/v1/build", "/api/v1/image-build"}) +@CrossOrigin(origins = "*") +public class ImageBuildController { + + /** + * 获取可选的构建基础镜像列表 + * + * @return 基础镜像列表 + */ + @GetMapping("/base-images") + public ApiResponse> listBaseImages() { + List images = new ArrayList<>(); + images.add(new BaseImageInfo( + "10.30.107.146/robot/rockylinux9-agent@sha256:b49d75f52f6b3c55bbf90427f0df0e97bc8e3f3e03727721cafc2c9d775b8975", + "Rocky Linux 9 Agent", + "基于 Rocky Linux 9 的 Agent 基础镜像,固定 digest 保证构建环境一致性", + true + )); + return ApiResponse.success(images); + } + + /** + * 基础镜像信息 DTO + * + * @param id 镜像标识(唯一) + * @param name 镜像显示名称 + * @param description 镜像描述 + * @param isDefault 是否为默认选项 + */ + public record BaseImageInfo( + String id, + String name, + String description, + boolean isDefault + ) {} +} diff --git a/back/src/main/java/com/linkwork/controller/K8sClusterController.java b/back/src/main/java/com/linkwork/controller/K8sClusterController.java new file mode 100644 index 0000000..bca0b7a --- /dev/null +++ b/back/src/main/java/com/linkwork/controller/K8sClusterController.java @@ -0,0 +1,150 @@ +package com.linkwork.controller; + +import com.linkwork.common.ApiResponse; +import com.linkwork.context.UserContext; +import com.linkwork.context.UserInfo; +import com.linkwork.model.dto.*; +import com.linkwork.service.K8sClusterService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.*; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * K8s 集群监控 API + * 仅允许配置的 workId 用户访问 + */ +@Slf4j +@RestController +@RequestMapping("/api/v1/k8s-monitor") +@RequiredArgsConstructor +public class K8sClusterController { + + private final K8sClusterService k8sClusterService; + + @Value("${schedule.cluster.namespace:ai-worker}") + private String defaultNamespace; + + @Value("${robot.k8s-monitor.allowed-users:}") + private String allowedUsersConfig; + + private Set getAllowedUsers() { + Set set = new HashSet<>(); + for (String id : allowedUsersConfig.split(",")) { + String trimmed = id.trim(); + if (!trimmed.isEmpty()) { + set.add(trimmed); + } + } + return set; + } + + private boolean isUserAllowed(UserInfo user) { + if (user == null) return false; + Set allowed = getAllowedUsers(); + if (user.getWorkId() != null && allowed.contains(user.getWorkId().trim())) return true; + if (user.getUserId() != null && allowed.contains(user.getUserId().trim())) return true; + return false; + } + + private String resolveNamespace(String namespace) { + return (namespace == null || namespace.isBlank()) ? defaultNamespace : namespace; + } + + private void checkPermission() { + UserInfo user = UserContext.get(); + if (user == null) { + throw new SecurityException("未登录"); + } + if (!isUserAllowed(user)) { + log.warn("K8s Monitor 访问被拒绝: userId={}, workId={}, name={}", user.getUserId(), user.getWorkId(), user.getName()); + throw new SecurityException("无权访问 K8s 集群监控"); + } + } + + @GetMapping("/access-check") + public ApiResponse checkAccess() { + UserInfo user = UserContext.get(); + if (user == null) return ApiResponse.success(false); + log.debug("K8s Monitor access-check: userId={}, workId={}, name={}", user.getUserId(), user.getWorkId(), user.getName()); + return ApiResponse.success(isUserAllowed(user)); + } + + @GetMapping("/namespaces") + public ApiResponse> namespaces() { + checkPermission(); + return ApiResponse.success(k8sClusterService.listNamespaces()); + } + + @GetMapping("/overview") + public ApiResponse overview(@RequestParam(required = false) String namespace) { + checkPermission(); + return ApiResponse.success(k8sClusterService.getOverview(resolveNamespace(namespace))); + } + + @GetMapping("/nodes") + public ApiResponse> nodes() { + checkPermission(); + return ApiResponse.success(k8sClusterService.listNodes()); + } + + @GetMapping("/pods") + public ApiResponse> pods( + @RequestParam(required = false) String namespace, + @RequestParam(required = false) String status, + @RequestParam(required = false) String node, + @RequestParam(required = false) String podGroup) { + checkPermission(); + return ApiResponse.success(k8sClusterService.listPods(resolveNamespace(namespace), status, node, podGroup)); + } + + @GetMapping("/podgroups") + public ApiResponse> podGroups(@RequestParam(required = false) String namespace) { + checkPermission(); + return ApiResponse.success(k8sClusterService.listPodGroups(resolveNamespace(namespace))); + } + + @GetMapping("/pods/{podName}/logs") + public ApiResponse podLogs( + @PathVariable String podName, + @RequestParam(required = false) String namespace, + @RequestParam(required = false) String container, + @RequestParam(defaultValue = "200") int tailLines) { + checkPermission(); + return ApiResponse.success(k8sClusterService.getPodLogs(resolveNamespace(namespace), podName, container, tailLines)); + } + + @GetMapping("/events") + public ApiResponse> events( + @RequestParam(required = false) String namespace, + @RequestParam(defaultValue = "50") int limit) { + checkPermission(); + return ApiResponse.success(k8sClusterService.listEvents(resolveNamespace(namespace), limit)); + } + + @GetMapping("/pods/{podName}/events") + public ApiResponse> podEvents( + @PathVariable String podName, + @RequestParam(required = false) String namespace) { + checkPermission(); + return ApiResponse.success(k8sClusterService.listPodEvents(resolveNamespace(namespace), podName)); + } + + @DeleteMapping("/pods/{podName}") + public ApiResponse deletePod( + @PathVariable String podName, + @RequestParam(required = false) String namespace) { + checkPermission(); + k8sClusterService.deletePod(resolveNamespace(namespace), podName); + return ApiResponse.success("Pod " + podName + " deleted"); + } + + @ExceptionHandler(SecurityException.class) + public ApiResponse handleSecurity(SecurityException e) { + return ApiResponse.error(40300, e.getMessage()); + } +} diff --git a/back/src/main/java/com/linkwork/controller/McpInternalController.java b/back/src/main/java/com/linkwork/controller/McpInternalController.java new file mode 100644 index 0000000..6ae274b --- /dev/null +++ b/back/src/main/java/com/linkwork/controller/McpInternalController.java @@ -0,0 +1,91 @@ +package com.linkwork.controller; + +import com.linkwork.common.ApiResponse; +import com.linkwork.model.entity.McpServerEntity; +import com.linkwork.model.entity.McpUserConfigEntity; +import com.linkwork.model.entity.Task; +import com.linkwork.model.enums.TaskStatus; +import com.linkwork.service.McpServerService; +import com.linkwork.service.McpUserConfigService; +import com.linkwork.service.TaskService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.util.*; + +/** + * MCP Gateway 内部 API — 仅供 Gateway 调用,不经过用户认证 + *

+ * /api/internal/** 路径已在 JwtAuthFilter 中放行 + */ +@Slf4j +@RestController +@RequestMapping("/api/internal") +@RequiredArgsConstructor +public class McpInternalController { + + private final McpServerService mcpServerService; + private final McpUserConfigService mcpUserConfigService; + private final TaskService taskService; + + /** + * Gateway 定时拉取完整 MCP Server 注册表 + */ + @GetMapping("/mcp-servers/registry") + public ApiResponse> registry() { + List allServers = mcpServerService.listByTypes(List.of("http", "sse")); + List> servers = new ArrayList<>(); + + for (McpServerEntity s : allServers) { + Map entry = new LinkedHashMap<>(); + entry.put("name", s.getName()); + entry.put("type", s.getType()); + entry.put("networkZone", s.getNetworkZone() != null ? s.getNetworkZone() : "external"); + entry.put("url", s.getUrl()); + entry.put("headers", s.getHeaders()); + entry.put("healthCheckUrl", s.getHealthCheckUrl()); + entry.put("status", s.getStatus()); + servers.add(entry); + } + + Map result = new LinkedHashMap<>(); + result.put("servers", servers); + result.put("updatedAt", LocalDateTime.now().toString()); + return ApiResponse.success(result); + } + + /** + * Gateway 验证 taskId(即 taskNo)是否有效且未结束 + */ + @GetMapping("/tasks/{taskId}/validate") + public ApiResponse> validateTask(@PathVariable String taskId) { + Map data = new LinkedHashMap<>(); + data.put("taskId", taskId); + + try { + Task task = taskService.getTaskByNo(taskId); + boolean active = task.getStatus() == TaskStatus.PENDING + || task.getStatus() == TaskStatus.RUNNING + || task.getStatus() == TaskStatus.PENDING_AUTH; + data.put("valid", active); + data.put("userId", task.getCreatorId() != null ? task.getCreatorId() : ""); + } catch (IllegalArgumentException e) { + data.put("valid", false); + data.put("userId", ""); + } + return ApiResponse.success(data); + } + + /** + * Gateway 查询用户个人 MCP 凭证 + */ + @GetMapping("/mcp-user-configs") + public ApiResponse getUserConfig( + @RequestParam String mcpName, + @RequestParam String userId) { + McpUserConfigEntity config = mcpUserConfigService.getByUserAndMcpName(userId, mcpName); + return ApiResponse.success(config); + } +} diff --git a/back/src/main/java/com/linkwork/controller/McpServerController.java b/back/src/main/java/com/linkwork/controller/McpServerController.java new file mode 100644 index 0000000..4b88414 --- /dev/null +++ b/back/src/main/java/com/linkwork/controller/McpServerController.java @@ -0,0 +1,204 @@ +package com.linkwork.controller; + +import com.linkwork.common.ApiResponse; +import com.linkwork.context.UserContext; +import com.linkwork.model.dto.McpDiscoverResult; +import com.linkwork.model.dto.McpProbeResult; +import com.linkwork.model.entity.McpServerEntity; +import com.linkwork.model.entity.RoleEntity; +import com.linkwork.service.McpDiscoveryService; +import com.linkwork.service.McpHealthChecker; +import com.linkwork.service.McpServerService; +import com.linkwork.service.RoleService; +import com.linkwork.service.AdminAccessService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +/** + * MCP 服务控制器 + */ +@Slf4j +@RestController +@RequestMapping("/api/v1") +@CrossOrigin(origins = "*") +@RequiredArgsConstructor +public class McpServerController { + + private final McpServerService mcpServerService; + private final McpHealthChecker mcpHealthChecker; + private final McpDiscoveryService mcpDiscoveryService; + private final RoleService roleService; + private final AdminAccessService adminAccessService; + + /** + * 获取 MCP 服务列表 + */ + @GetMapping("/mcp-servers") + public ApiResponse> listMcpServers( + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "20") int pageSize, + @RequestParam(required = false) String status, + @RequestParam(required = false) String keyword) { + String userId = UserContext.getCurrentUserId(); + Map data = mcpServerService.listMcpServers(page, pageSize, status, keyword, userId); + return ApiResponse.success(data); + } + + /** + * 获取所有可用的 MCP 服务(用于下拉选择) + */ + @GetMapping("/mcp-servers/available") + public ApiResponse>> listAvailable() { + String userId = UserContext.getCurrentUserId(); + List> data = mcpServerService.listAllAvailable(userId); + return ApiResponse.success(data); + } + + /** + * 获取所有 MCP Server 的健康状态 + */ + @GetMapping("/mcp-servers/health") + public ApiResponse> getHealthStatus() { + String userId = UserContext.getCurrentUserId(); + Map data = mcpServerService.getHealthStatus(userId); + return ApiResponse.success(data); + } + + /** + * 获取单个 MCP 服务详情 + */ + @GetMapping("/mcp-servers/{id}") + public ApiResponse> getMcpServer(@PathVariable Long id) { + String userId = UserContext.getCurrentUserId(); + Map entity = mcpServerService.getMcpServerForRead(id, userId); + return ApiResponse.success(entity); + } + + /** + * 测试单个 MCP 服务的连通性 + */ + @PostMapping("/mcp-servers/{id}/test") + public ApiResponse testMcpServer(@PathVariable Long id) { + String userId = UserContext.getCurrentUserId(); + McpServerEntity entity = mcpServerService.getMcpServerForManage(id, userId); + McpProbeResult result = mcpHealthChecker.probeSingle(entity); + // 同时更新 DB 健康状态 + mcpServerService.updateHealth(entity.getId(), result.getStatus(), result.getLatencyMs(), result.getMessage(), + "online".equals(result.getStatus()) ? 0 + : (entity.getConsecutiveFailures() != null ? entity.getConsecutiveFailures() + 1 : 1)); + return ApiResponse.success(result); + } + + /** + * 发现 MCP Server 的工具列表 + *

+ * 对目标 MCP server 发送 JSON-RPC 请求(initialize → initialized → tools/list), + * 获取并返回工具清单。成功时同步更新 configJson。 + */ + @PostMapping("/mcp-servers/{id}/discover") + public ApiResponse discoverTools(@PathVariable Long id) { + String userId = UserContext.getCurrentUserId(); + McpServerEntity entity = mcpServerService.getMcpServerForManage(id, userId); + + McpDiscoverResult result = mcpDiscoveryService.discover(entity, userId); + + // 如果成功,把 tools 存入 config_json + if (result.isSuccess() && result.getTools() != null) { + Map configJson = entity.getConfigJson(); + if (configJson == null) { + configJson = new HashMap<>(); + } + configJson.put("tools", result.getTools()); + configJson.put("serverName", result.getServerName()); + configJson.put("serverVersion", result.getServerVersion()); + configJson.put("lastDiscoveredAt", LocalDateTime.now().toString()); + entity.setConfigJson(configJson); + mcpServerService.updateById(entity); + } + + return ApiResponse.success(result); + } + + /** + * 创建 MCP 服务(支持 type/url/headers/healthCheckUrl/version/tags) + */ + @PostMapping("/mcp-servers") + public ApiResponse> createMcpServer( + @RequestBody Map request) { + String userId = UserContext.getCurrentUserId(); + String userName = UserContext.getCurrentUserName(); + McpServerEntity entity = mcpServerService.createMcpServer(request, userId, userName); + return ApiResponse.success(Map.of("id", entity.getId(), "mcpNo", entity.getMcpNo())); + } + + /** + * 更新 MCP 服务(支持 type/url/headers/healthCheckUrl/version/tags) + */ + @PutMapping("/mcp-servers/{id}") + public ApiResponse> updateMcpServer( + @PathVariable Long id, + @RequestBody Map request) { + String userId = UserContext.getCurrentUserId(); + String userName = UserContext.getCurrentUserName(); + McpServerEntity entity = mcpServerService.updateMcpServer(id, request, userId, userName); + return ApiResponse.success(Map.of("id", entity.getId(), "mcpNo", entity.getMcpNo())); + } + + /** + * 删除 MCP 服务 + */ + @DeleteMapping("/mcp-servers/{id}") + public ApiResponse deleteMcpServer(@PathVariable Long id) { + String userId = UserContext.getCurrentUserId(); + mcpServerService.deleteMcpServer(id, userId); + return ApiResponse.success(null); + } + + /** + * 根据岗位生成 mcp.json 配置 + *

+ * 从岗位 configJson.mcp 中获取 MCP ID 列表,查询对应 MCP Server 配置, + * 生成 SDK 兼容的 mcp.json 格式。 + */ + @GetMapping("/roles/{roleId}/mcp-config") + public ApiResponse> getMcpConfigByRole(@PathVariable Long roleId) { + String userId = UserContext.getCurrentUserId(); + RoleEntity role = roleService.getRoleForWrite(roleId, userId); + + // 兼容数字 ID 和名称字符串两种格式 + List mcpIds = new ArrayList<>(); + if (role.getConfigJson() != null && role.getConfigJson().getMcp() != null) { + List mcpNames = new ArrayList<>(); + for (String ref : role.getConfigJson().getMcp()) { + if (ref == null || ref.isBlank()) continue; + try { + mcpIds.add(Long.parseLong(ref)); + } catch (NumberFormatException e) { + mcpNames.add(ref); + } + } + // 按名称查询 + if (!mcpNames.isEmpty()) { + com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper byNameQuery = + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .in(McpServerEntity::getName, mcpNames); + if (!adminAccessService.isAdmin(userId)) { + byNameQuery.and(w -> w.eq(McpServerEntity::getCreatorId, userId) + .or().eq(McpServerEntity::getVisibility, "public")); + } + List byNames = mcpServerService.list(byNameQuery); + for (McpServerEntity entity : byNames) { + mcpIds.add(entity.getId()); + } + } + } + + Map config = mcpServerService.generateMcpConfig(mcpIds); + return ApiResponse.success(config); + } +} diff --git a/back/src/main/java/com/linkwork/controller/McpUserConfigController.java b/back/src/main/java/com/linkwork/controller/McpUserConfigController.java new file mode 100644 index 0000000..dd9589b --- /dev/null +++ b/back/src/main/java/com/linkwork/controller/McpUserConfigController.java @@ -0,0 +1,70 @@ +package com.linkwork.controller; + +import com.linkwork.common.ApiResponse; +import com.linkwork.context.UserContext; +import com.linkwork.model.entity.McpUserConfigEntity; +import com.linkwork.service.McpUserConfigService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@RestController +@RequestMapping("/api/v1/mcp-user-configs") +@CrossOrigin(origins = "*") +@RequiredArgsConstructor +public class McpUserConfigController { + + private final McpUserConfigService mcpUserConfigService; + + @GetMapping + public ApiResponse>> list() { + String userId = UserContext.getCurrentUserId(); + List configs = mcpUserConfigService.listByUser(userId); + List> result = configs.stream() + .map(c -> { + Map m = new LinkedHashMap<>(); + m.put("id", c.getId()); + m.put("mcpServerId", c.getMcpServerId()); + m.put("hasHeaders", c.getHeaders() != null && !c.getHeaders().isEmpty()); + m.put("hasUrlParams", c.getUrlParams() != null && !c.getUrlParams().isEmpty()); + m.put("createdAt", c.getCreatedAt()); + m.put("updatedAt", c.getUpdatedAt()); + return m; + }) + .collect(Collectors.toList()); + return ApiResponse.success(result); + } + + @GetMapping("/{mcpServerId}/detail") + public ApiResponse> detail(@PathVariable Long mcpServerId) { + String userId = UserContext.getCurrentUserId(); + McpUserConfigEntity config = mcpUserConfigService.getByUserAndServer(userId, mcpServerId); + if (config == null) { + return ApiResponse.success(Map.of("headers", Map.of(), "urlParams", Map.of())); + } + Map result = new LinkedHashMap<>(); + result.put("headers", config.getHeaders() != null ? config.getHeaders() : Map.of()); + result.put("urlParams", config.getUrlParams() != null ? config.getUrlParams() : Map.of()); + return ApiResponse.success(result); + } + + @PutMapping("/{mcpServerId}") + public ApiResponse> saveOrUpdate( + @PathVariable Long mcpServerId, + @RequestBody Map request) { + String userId = UserContext.getCurrentUserId(); + McpUserConfigEntity entity = mcpUserConfigService.saveOrUpdate(userId, mcpServerId, request); + return ApiResponse.success(Map.of("id", entity.getId())); + } + + @DeleteMapping("/{mcpServerId}") + public ApiResponse delete(@PathVariable Long mcpServerId) { + String userId = UserContext.getCurrentUserId(); + mcpUserConfigService.deleteConfig(userId, mcpServerId); + return ApiResponse.success(null); + } +} diff --git a/back/src/main/java/com/linkwork/controller/MemoryController.java b/back/src/main/java/com/linkwork/controller/MemoryController.java new file mode 100644 index 0000000..2155dc2 --- /dev/null +++ b/back/src/main/java/com/linkwork/controller/MemoryController.java @@ -0,0 +1,83 @@ +package com.linkwork.controller; + +import com.linkwork.model.dto.MemoryIndexBatchRequest; +import com.linkwork.model.dto.MemoryIngestRequest; +import com.linkwork.model.dto.MemorySearchRequest; +import com.linkwork.service.memory.MemoryService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +@org.springframework.boot.autoconfigure.condition.ConditionalOnProperty(name = "memory.enabled", havingValue = "true", matchIfMissing = true) +@RestController +@RequestMapping("/api/v1/memory") +@RequiredArgsConstructor +public class MemoryController { + + private final MemoryService memoryService; + + @PostMapping("/search") + public ResponseEntity>> search( + @RequestParam String workstationId, + @RequestParam String userId, + @Valid @RequestBody MemorySearchRequest request) { + List> results = memoryService.search( + workstationId, userId, request.getQuery(), request.getTopK()); + return ResponseEntity.ok(results); + } + + @PostMapping("/ingest") + public ResponseEntity> ingest( + @RequestParam String workstationId, + @RequestParam String userId, + @Valid @RequestBody MemoryIngestRequest request) { + memoryService.ingest(workstationId, userId, request.getContent(), request.getSource()); + return ResponseEntity.ok(Map.of("status", "queued")); + } + + @PostMapping("/index/file") + public ResponseEntity> indexFile( + @RequestParam String workstationId, + @RequestParam String userId, + @RequestParam String filePath) { + memoryService.triggerIndexFile(workstationId, userId, filePath); + return ResponseEntity.ok(Map.of("status", "queued")); + } + + @PostMapping("/index/batch") + public ResponseEntity> indexBatch( + @RequestParam String workstationId, + @RequestParam String userId, + @Valid @RequestBody MemoryIndexBatchRequest request) { + int count = memoryService.triggerBatchIndex(workstationId, userId, request.getFilePaths()); + return ResponseEntity.ok(Map.of("status", "queued", "fileCount", count)); + } + + @GetMapping("/recent") + public ResponseEntity>> recent( + @RequestParam String workstationId, + @RequestParam String userId, + @RequestParam(defaultValue = "5") int limit) { + return ResponseEntity.ok(memoryService.recent(workstationId, userId, limit)); + } + + @GetMapping("/stats") + public ResponseEntity> stats( + @RequestParam String workstationId, + @RequestParam String userId) { + return ResponseEntity.ok(memoryService.stats(workstationId, userId)); + } + + @DeleteMapping("/source") + public ResponseEntity> deleteSource( + @RequestParam String workstationId, + @RequestParam String userId, + @RequestParam String source) { + memoryService.deleteSource(workstationId, userId, source); + return ResponseEntity.ok(Map.of("status", "deleted")); + } +} diff --git a/back/src/main/java/com/linkwork/controller/ModelRegistryController.java b/back/src/main/java/com/linkwork/controller/ModelRegistryController.java new file mode 100644 index 0000000..171e688 --- /dev/null +++ b/back/src/main/java/com/linkwork/controller/ModelRegistryController.java @@ -0,0 +1,23 @@ +package com.linkwork.controller; + +import com.linkwork.service.ModelRegistryService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +/** + * 模型列表接口(同源调用,后端代理模型网关) + */ +@RestController +@RequiredArgsConstructor +public class ModelRegistryController { + + private final ModelRegistryService modelRegistryService; + + @GetMapping("/api/v1/models") + public Map listModels() { + return modelRegistryService.fetchModels(); + } +} diff --git a/back/src/main/java/com/linkwork/controller/PublicTaskController.java b/back/src/main/java/com/linkwork/controller/PublicTaskController.java new file mode 100644 index 0000000..434da90 --- /dev/null +++ b/back/src/main/java/com/linkwork/controller/PublicTaskController.java @@ -0,0 +1,63 @@ +package com.linkwork.controller; + +import com.linkwork.common.ApiResponse; +import com.linkwork.model.dto.TaskResponse; +import com.linkwork.model.entity.Task; +import com.linkwork.service.TaskShareLinkService; +import com.linkwork.service.TaskService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +/** + * 公共任务查询接口(免鉴权最小返回) + */ +@Slf4j +@RestController +@RequestMapping("/api/v1/public/tasks") +@CrossOrigin(origins = "*") +@RequiredArgsConstructor +public class PublicTaskController { + + private final TaskService taskService; + private final TaskShareLinkService taskShareLinkService; + + /** + * 按任务 ID 查询任务使用模型 + * GET /api/v1/public/tasks/{taskNo}/model + */ + @GetMapping("/{taskNo}/model") + public ApiResponse> getTaskModel(@PathVariable String taskNo) { + log.info("公共接口查询任务模型: taskNo={}", taskNo); + + Task task = taskService.getTaskByNo(taskNo); + Map result = new HashMap<>(); + result.put("taskId", task.getTaskNo()); + result.put("modelId", task.getSelectedModel()); + result.put("userId", task.getCreatorId()); + return ApiResponse.success(result); + } + + /** + * 访客通过分享 token 查看任务详情 + * GET /api/v1/public/tasks/{taskNo}/share-detail?token=... + */ + @GetMapping("/{taskNo}/share-detail") + public ApiResponse getSharedTaskDetail( + @PathVariable String taskNo, + @RequestParam("token") String token) { + log.info("公共接口查询任务分享详情: taskNo={}", taskNo); + + taskShareLinkService.validateShareToken(taskNo, token); + Task task = taskService.getTaskByNo(taskNo); + return ApiResponse.success(taskService.toShareResponse(task)); + } +} diff --git a/back/src/main/java/com/linkwork/controller/ReportExportController.java b/back/src/main/java/com/linkwork/controller/ReportExportController.java new file mode 100644 index 0000000..a484590 --- /dev/null +++ b/back/src/main/java/com/linkwork/controller/ReportExportController.java @@ -0,0 +1,103 @@ +package com.linkwork.controller; + +import com.linkwork.common.ApiResponse; +import com.linkwork.common.ForbiddenOperationException; +import com.linkwork.context.UserContext; +import com.linkwork.context.UserInfo; +import com.linkwork.model.dto.ReportExportFieldResponse; +import com.linkwork.model.dto.ReportExportRequest; +import com.linkwork.service.ReportExportService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ContentDisposition; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; + +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashSet; +import java.util.Set; + +/** + * 运维报表导出控制器 + */ +@Slf4j +@RestController +@RequestMapping("/api/v1/reports") +@CrossOrigin(origins = "*") +@RequiredArgsConstructor +public class ReportExportController { + + private static final DateTimeFormatter FILE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"); + private final ReportExportService reportExportService; + + @Value("${robot.k8s-monitor.allowed-users:}") + private String allowedUsersConfig; + + @GetMapping("/export/fields") + public ApiResponse listExportFields(@RequestParam String type) { + checkPermission(); + return ApiResponse.success(reportExportService.listFields(type)); + } + + @PostMapping("/export") + public ResponseEntity export(@Valid @RequestBody ReportExportRequest request) { + checkPermission(); + + String normalizedType = "role".equalsIgnoreCase(request.getType()) ? "role" : "task"; + String fileName = normalizedType + "-report-" + LocalDateTime.now().format(FILE_TIME_FORMATTER) + ".csv"; + + StreamingResponseBody responseBody = outputStream -> reportExportService.exportCsv(request, outputStream); + + return ResponseEntity.ok() + .header(HttpHeaders.CACHE_CONTROL, "no-store, no-cache, must-revalidate, max-age=0") + .contentType(MediaType.parseMediaType("text/csv;charset=UTF-8")) + .header(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment() + .filename(fileName, StandardCharsets.UTF_8) + .build() + .toString()) + .body(responseBody); + } + + private void checkPermission() { + UserInfo user = UserContext.get(); + if (user == null) { + throw new ForbiddenOperationException("未登录"); + } + if (!isUserAllowed(user)) { + log.warn("报表导出访问被拒绝: userId={}, workId={}, name={}", user.getUserId(), user.getWorkId(), user.getName()); + throw new ForbiddenOperationException("无权访问报表导出功能"); + } + } + + private boolean isUserAllowed(UserInfo user) { + Set allowed = getAllowedUsers(); + if (user.getWorkId() != null && allowed.contains(user.getWorkId().trim())) { + return true; + } + return user.getUserId() != null && allowed.contains(user.getUserId().trim()); + } + + private Set getAllowedUsers() { + Set set = new HashSet<>(); + for (String id : allowedUsersConfig.split(",")) { + String trimmed = id.trim(); + if (!trimmed.isEmpty()) { + set.add(trimmed); + } + } + return set; + } +} diff --git a/back/src/main/java/com/linkwork/controller/RoleController.java b/back/src/main/java/com/linkwork/controller/RoleController.java new file mode 100644 index 0000000..3e68202 --- /dev/null +++ b/back/src/main/java/com/linkwork/controller/RoleController.java @@ -0,0 +1,424 @@ +package com.linkwork.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.linkwork.context.UserContext; +import com.linkwork.model.entity.McpServerEntity; +import com.linkwork.model.entity.RoleEntity; +import com.linkwork.model.entity.SkillEntity; +import com.linkwork.model.enums.DeployMode; +import com.linkwork.service.K8sOrchestrator; +import com.linkwork.service.McpServerService; +import com.linkwork.service.RoleService; +import com.linkwork.service.RuntimeModeService; +import com.linkwork.service.ServiceSnapshotService; +import com.linkwork.service.SkillService; +import lombok.RequiredArgsConstructor; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@RestController +@RequestMapping("/api/v1/roles") +@CrossOrigin(origins = "*") +@RequiredArgsConstructor +public class RoleController { + private static final java.util.Set SUPPORTED_ROLE_STATUSES = java.util.Set.of("active", "maintenance", "disabled"); + + private final RoleService roleService; + private final RuntimeModeService runtimeModeService; + private final McpServerService mcpServerService; + private final SkillService skillService; + private final K8sOrchestrator k8sOrchestrator; + private final ServiceSnapshotService snapshotService; + + private String getCurrentUserId() { + return UserContext.getCurrentUserId(); + } + + private String getCurrentUsername() { + return UserContext.getCurrentUserName(); + } + + @GetMapping + public Map listRoles( + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "20") int pageSize, + @RequestParam(required = false) String query, + @RequestParam(required = false) String category, + @RequestParam(required = false) String status, + @RequestParam(defaultValue = "all") String scope + ) { + String userId = getCurrentUserId(); + Page result = roleService.listRoles(page, pageSize, query, category, scope, status, userId); + Map favoriteCountMap = roleService.queryFavoriteCountMap( + result.getRecords().stream().map(RoleEntity::getId).collect(Collectors.toList()) + ); + + Map response = new HashMap<>(); + response.put("code", 0); + response.put("msg", "success"); + + Map data = new HashMap<>(); + List> items = result.getRecords().stream().map(role -> { + RuntimeModeService.RuntimeSnapshot runtimeSnapshot = runtimeModeService.resolveForRole(role); + + Map map = new HashMap<>(); + map.put("id", role.getId().toString()); + map.put("name", role.getName()); + map.put("description", role.getDescription()); + map.put("category", role.getCategory()); + map.put("icon", role.getIcon()); + map.put("image", role.getImage()); + map.put("status", coerceRoleStatus(role.getStatus())); + map.put("deployMode", resolveDeployMode(role)); + map.put("runtimeMode", runtimeSnapshot.getRuntimeMode()); + map.put("zzMode", runtimeSnapshot.getZzMode()); + map.put("runnerImage", runtimeSnapshot.getRunnerImage()); + map.put("memoryEnabled", resolveMemoryEnabled(role)); + map.put("isMine", userId.equals(role.getCreatorId())); + map.put("isFavorite", roleService.isFavorite(role.getId(), userId)); + map.put("favoriteCount", favoriteCountMap.getOrDefault(role.getId(), 0L)); + map.put("isPublic", Boolean.TRUE.equals(role.getIsPublic())); + map.put("maxEmployees", role.getMaxEmployees()); + + Map resourceCount = new HashMap<>(); + if (role.getConfigJson() != null) { + resourceCount.put("mcp", role.getConfigJson().getMcp() != null ? role.getConfigJson().getMcp().size() : 0); + resourceCount.put("skills", role.getConfigJson().getSkills() != null ? role.getConfigJson().getSkills().size() : 0); + } + map.put("resourceCount", resourceCount); + + return map; + }).collect(Collectors.toList()); + + data.put("items", items); + + Map pagination = new HashMap<>(); + pagination.put("page", result.getCurrent()); + pagination.put("pageSize", result.getSize()); + pagination.put("total", result.getTotal()); + pagination.put("totalPages", result.getPages()); + data.put("pagination", pagination); + + response.put("data", data); + response.put("timestamp", LocalDateTime.now()); + + return response; + } + + @GetMapping("/hot") + public Map listHotRoles(@RequestParam(defaultValue = "4") int limit) { + String userId = getCurrentUserId(); + List roles = roleService.listHotRoles(limit, userId); + Map favoriteCountMap = roleService.queryFavoriteCountMap( + roles.stream().map(RoleEntity::getId).collect(Collectors.toList()) + ); + + List> items = roles.stream().map(role -> { + Map map = new HashMap<>(); + map.put("id", role.getId().toString()); + map.put("name", role.getName()); + map.put("description", role.getDescription()); + map.put("category", role.getCategory()); + map.put("status", coerceRoleStatus(role.getStatus())); + map.put("favoriteCount", favoriteCountMap.getOrDefault(role.getId(), 0L)); + map.put("isFavorite", roleService.isFavorite(role.getId(), userId)); + return map; + }).collect(Collectors.toList()); + + Map response = new HashMap<>(); + response.put("code", 0); + response.put("msg", "success"); + response.put("data", Map.of("items", items, "limit", Math.max(1, limit))); + response.put("timestamp", LocalDateTime.now()); + return response; + } + + @GetMapping("/{id:\\d+}") + public Map getRole(@PathVariable Long id) { + String userId = getCurrentUserId(); + RoleEntity role = roleService.getRoleForRead(id, userId); + RuntimeModeService.RuntimeSnapshot runtimeSnapshot = runtimeModeService.resolveForRole(role); + + Map response = new HashMap<>(); + response.put("code", 0); + response.put("msg", "success"); + + Map data = new HashMap<>(); + data.put("id", role.getId().toString()); + data.put("name", role.getName()); + data.put("description", role.getDescription()); + data.put("prompt", role.getPrompt()); + data.put("category", role.getCategory()); + data.put("icon", role.getIcon()); + data.put("image", role.getImage()); + data.put("status", coerceRoleStatus(role.getStatus())); + data.put("deployMode", resolveDeployMode(role)); + data.put("runtimeMode", runtimeSnapshot.getRuntimeMode()); + data.put("zzMode", runtimeSnapshot.getZzMode()); + data.put("runnerImage", runtimeSnapshot.getRunnerImage()); + data.put("memoryEnabled", resolveMemoryEnabled(role)); + data.put("isMine", userId.equals(role.getCreatorId())); + data.put("isFavorite", roleService.isFavorite(id, userId)); + data.put("isPublic", Boolean.TRUE.equals(role.getIsPublic())); + data.put("maxEmployees", role.getMaxEmployees()); + data.put("createdAt", role.getCreatedAt()); + + if (role.getConfigJson() != null) { + RoleEntity.RoleConfig config = role.getConfigJson(); + List> mcpModules = resolveMcpModules(config.getMcp()); + data.put("mcpModules", mcpModules); + data.put("mcpServers", mcpModules); // 兼容历史前端字段 + data.put("skills", resolveSkillModules(config.getSkills())); + + data.put("gitRepos", config.getGitRepos()); + data.put("envVars", config.getEnv()); + } + + response.put("data", data); + return response; + } + + private String resolveDeployMode(RoleEntity role) { + if (role == null || role.getConfigJson() == null) { + return DeployMode.K8S.name(); + } + + String rawDeployMode = role.getConfigJson().getDeployMode(); + if (!StringUtils.hasText(rawDeployMode)) { + return DeployMode.K8S.name(); + } + + try { + return DeployMode.valueOf(rawDeployMode.trim().toUpperCase()).name(); + } catch (IllegalArgumentException ex) { + throw new IllegalArgumentException("非法部署模式: " + rawDeployMode + ", roleId=" + role.getId()); + } + } + + private String coerceRoleStatus(String rawStatus) { + if (!StringUtils.hasText(rawStatus)) { + return "active"; + } + String normalized = rawStatus.trim().toLowerCase(); + return SUPPORTED_ROLE_STATUSES.contains(normalized) ? normalized : "active"; + } + + private boolean resolveMemoryEnabled(RoleEntity role) { + if (role == null || role.getConfigJson() == null) { + return false; + } + return Boolean.TRUE.equals(role.getConfigJson().getMemoryEnabled()); + } + + private List> resolveMcpModules(List refs) { + if (refs == null || refs.isEmpty()) { + return List.of(); + } + + Map byRef = new HashMap<>(); + List idRefs = new ArrayList<>(); + List nameRefs = new ArrayList<>(); + + for (String ref : refs) { + if (!StringUtils.hasText(ref)) { + continue; + } + String trimmedRef = ref.trim(); + try { + idRefs.add(Long.parseLong(trimmedRef)); + } catch (NumberFormatException ex) { + nameRefs.add(trimmedRef); + } + } + + if (!idRefs.isEmpty()) { + List mcpByIds = mcpServerService.list( + new LambdaQueryWrapper().in(McpServerEntity::getId, idRefs) + ); + for (McpServerEntity entity : mcpByIds) { + byRef.put(String.valueOf(entity.getId()), entity); + } + } + + if (!nameRefs.isEmpty()) { + List mcpByNames = mcpServerService.list( + new LambdaQueryWrapper().in(McpServerEntity::getName, nameRefs) + ); + for (McpServerEntity entity : mcpByNames) { + byRef.put(entity.getName(), entity); + } + } + + List> modules = new ArrayList<>(); + for (String ref : refs) { + if (!StringUtils.hasText(ref)) { + continue; + } + String trimmedRef = ref.trim(); + McpServerEntity entity = byRef.get(trimmedRef); + if (entity != null) { + modules.add(buildModule(String.valueOf(entity.getId()), entity.getName(), entity.getDescription())); + } else { + modules.add(buildModule(trimmedRef, trimmedRef, "MCP 配置不存在")); + } + } + return modules; + } + + private List> resolveSkillModules(List refs) { + if (refs == null || refs.isEmpty()) { + return List.of(); + } + + Map byRef = new HashMap<>(); + List idRefs = new ArrayList<>(); + List nameRefs = new ArrayList<>(); + + for (String ref : refs) { + if (!StringUtils.hasText(ref)) { + continue; + } + String trimmedRef = ref.trim(); + try { + idRefs.add(Long.parseLong(trimmedRef)); + } catch (NumberFormatException ex) { + nameRefs.add(trimmedRef); + } + } + + if (!idRefs.isEmpty()) { + List skillsByIds = skillService.list( + new LambdaQueryWrapper().in(SkillEntity::getId, idRefs) + ); + for (SkillEntity entity : skillsByIds) { + byRef.put(String.valueOf(entity.getId()), entity); + } + } + + if (!nameRefs.isEmpty()) { + List skillsByNames = skillService.list( + new LambdaQueryWrapper().in(SkillEntity::getName, nameRefs) + ); + for (SkillEntity entity : skillsByNames) { + byRef.put(entity.getName(), entity); + } + } + + List> modules = new ArrayList<>(); + for (String ref : refs) { + if (!StringUtils.hasText(ref)) { + continue; + } + String trimmedRef = ref.trim(); + SkillEntity entity = byRef.get(trimmedRef); + if (entity != null) { + modules.add(buildModule(String.valueOf(entity.getId()), entity.getName(), entity.getDescription())); + } else { + modules.add(buildModule(trimmedRef, trimmedRef, "Skill 配置不存在")); + } + } + return modules; + } + + private Map buildModule(String id, String name, String desc) { + Map module = new LinkedHashMap<>(); + module.put("id", id); + module.put("name", StringUtils.hasText(name) ? name : id); + module.put("desc", StringUtils.hasText(desc) ? desc : ""); + return module; + } + + /** + * 查询岗位下存活的 Pod 状态 + * + * 返回 runningPods(名称列表)、podCount、maxPodCount、alive(是否有存活 Pod)。 + */ + @GetMapping("/{id:\\d+}/pods") + public Map getRolePods(@PathVariable Long id) { + String serviceId = String.valueOf(id); + + List runningPods; + try { + runningPods = k8sOrchestrator.getRunningPods(serviceId); + } catch (Exception e) { + runningPods = List.of(); + } + + var snapshot = snapshotService.getSnapshot(serviceId); + int maxPodCount = snapshot != null && snapshot.getMaxPodCount() != null + ? snapshot.getMaxPodCount() : 0; + + Map data = new LinkedHashMap<>(); + data.put("roleId", id.toString()); + data.put("serviceId", serviceId); + data.put("alive", !runningPods.isEmpty()); + data.put("podCount", runningPods.size()); + data.put("maxPodCount", maxPodCount); + data.put("runningPods", runningPods); + + Map response = new HashMap<>(); + response.put("code", 0); + response.put("msg", "success"); + response.put("data", data); + return response; + } + + @PostMapping + public Map createRole(@RequestBody RoleEntity role) { + RoleEntity created = roleService.createRole(role, getCurrentUserId(), getCurrentUsername()); + + Map response = new HashMap<>(); + response.put("code", 0); + response.put("msg", "success"); + response.put("data", Map.of("id", created.getId().toString())); + return response; + } + + @PutMapping("/{id}") + public Map updateRole(@PathVariable Long id, @RequestBody RoleEntity role) { + roleService.updateRole(id, role, getCurrentUserId()); + + Map response = new HashMap<>(); + response.put("code", 0); + response.put("msg", "success"); + return response; + } + + @DeleteMapping("/{id}") + public Map deleteRole(@PathVariable Long id) { + roleService.deleteRole(id, getCurrentUserId()); + Map response = new HashMap<>(); + response.put("code", 0); + response.put("msg", "success"); + return response; + } + + @PostMapping("/{id}/favorite") + public Map toggleFavorite(@PathVariable Long id, @RequestBody Map body) { + boolean favorite = body.getOrDefault("favorite", false); + roleService.toggleFavorite(id, getCurrentUserId(), favorite); + + Map response = new HashMap<>(); + response.put("code", 0); + response.put("msg", "success"); + return response; + } +} diff --git a/back/src/main/java/com/linkwork/controller/ScheduleController.java b/back/src/main/java/com/linkwork/controller/ScheduleController.java new file mode 100644 index 0000000..1170d96 --- /dev/null +++ b/back/src/main/java/com/linkwork/controller/ScheduleController.java @@ -0,0 +1,319 @@ +package com.linkwork.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; +import com.linkwork.common.ApiResponse; +import com.linkwork.model.dto.*; +import com.linkwork.model.enums.DeployMode; +import com.linkwork.model.enums.PodMode; +import com.linkwork.service.K8sOrchestrator; +import com.linkwork.service.ServiceResumeService; +import com.linkwork.service.ServiceScaleService; +import com.linkwork.service.ServiceScheduleService; +import com.linkwork.service.ServiceSnapshotService; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** + * 调度控制器 + */ +@RestController +@RequestMapping("/api/v1/schedule") +@Validated +@Slf4j +public class ScheduleController { + + private final ServiceScheduleService scheduleService; + private final K8sOrchestrator orchestrator; + private final ServiceSnapshotService snapshotService; + private final ServiceResumeService resumeService; + private final ServiceScaleService scaleService; + private final ObjectMapper yamlMapper; + + public ScheduleController(ServiceScheduleService scheduleService, + K8sOrchestrator orchestrator, + ServiceSnapshotService snapshotService, + ServiceResumeService resumeService, + ServiceScaleService scaleService) { + this.scheduleService = scheduleService; + this.orchestrator = orchestrator; + this.snapshotService = snapshotService; + this.resumeService = resumeService; + this.scaleService = scaleService; + this.yamlMapper = new ObjectMapper(new YAMLFactory() + .enable(YAMLGenerator.Feature.MINIMIZE_QUOTES) + .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER)); + } + + @PostMapping("/build") + public ResponseEntity> build( + @Valid @RequestBody ServiceBuildRequest request) { + + log.info("Received build request for service {}", request.getServiceId()); + + if (request.getDeployMode() == DeployMode.COMPOSE) { + return ResponseEntity.badRequest() + .body(ApiResponse.error("COMPOSE 模式请使用 /compose/generate 接口")); + } + + if (request.getPodMode() == PodMode.SIDECAR && request.getDeployMode() != DeployMode.K8S) { + return ResponseEntity.badRequest() + .body(ApiResponse.error("Sidecar 模式仅支持 K8s 部署")); + } + + ServiceBuildResult result = scheduleService.build(request); + + if (result.isSuccess()) { + // 判断状态:BUILDING 返回 202 Accepted,SUCCESS 返回 200 OK + if ("BUILDING".equals(result.getStatus())) { + log.info("Build task submitted for service {}, returning 202 Accepted", request.getServiceId()); + return ResponseEntity.status(HttpStatus.ACCEPTED) + .body(ApiResponse.success(result)); + } else { + snapshotService.saveSnapshot(request, result); + return ResponseEntity.ok(ApiResponse.success(result)); + } + } else { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.error(result.getErrorMessage())); + } + } + + @PostMapping("/preview") + public ResponseEntity> preview( + @Valid @RequestBody ServiceBuildRequest request) { + + log.info("Received preview request for service {}", request.getServiceId()); + + GeneratedSpec spec = scheduleService.preview(request); + return ResponseEntity.ok(ApiResponse.success(spec)); + } + + @PostMapping("/preview/yaml") + public ResponseEntity previewYaml( + @Valid @RequestBody ServiceBuildRequest request) { + + log.info("Received preview YAML request for service {}", request.getServiceId()); + + try { + GeneratedSpec spec = scheduleService.preview(request); + + StringBuilder yaml = new StringBuilder(); + yaml.append("# ========== PodGroup ==========\n"); + yaml.append(yamlMapper.writeValueAsString(spec.getPodGroupSpec())); + yaml.append("\n---\n"); + + for (int i = 0; i < spec.getPodSpecs().size(); i++) { + yaml.append("# ========== Pod ").append(i).append(" ==========\n"); + yaml.append(yamlMapper.writeValueAsString(spec.getPodSpecs().get(i))); + if (i < spec.getPodSpecs().size() - 1) { + yaml.append("\n---\n"); + } + } + + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType("text/yaml")) + .body(yaml.toString()); + } catch (Exception e) { + log.error("Failed to generate YAML: {}", e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body("Error: " + e.getMessage()); + } + } + + @PostMapping("/config") + public ResponseEntity> getMergedConfig( + @Valid @RequestBody ServiceBuildRequest request) { + + log.info("Received config request for service {}", request.getServiceId()); + + MergedConfig config = scheduleService.getMergedConfig(request); + return ResponseEntity.ok(ApiResponse.success(config)); + } + + @GetMapping("/status/{serviceId}") + public ResponseEntity> getStatus( + @PathVariable String serviceId) { + + ServiceStatusResponse status = orchestrator.getServiceStatus(serviceId); + + if (status == null || status.getPodGroupStatus() == null) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(ApiResponse.error("Service not found: " + serviceId)); + } + + return ResponseEntity.ok(ApiResponse.success(status)); + } + + @PostMapping("/resume/{serviceId}") + public ResponseEntity> resume( + @PathVariable String serviceId) { + + log.info("Received resume request for service {}", serviceId); + + ServiceResumeResult result = resumeService.resume(serviceId); + + if (result.isSuccess()) { + return ResponseEntity.ok(ApiResponse.success(result)); + } else if (result.isRequireFullRequest()) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(ApiResponse.error(result.getMessage())); + } else { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.error(result.getMessage())); + } + } + +// @PostMapping("/{serviceId}/stop") +// public ResponseEntity> stop( +// @PathVariable String serviceId, +// @RequestParam(defaultValue = "true") boolean graceful) { +// +// log.info("Received stop request for service {}, graceful={}", serviceId, graceful); +// +// StopResult result = orchestrator.stopService(serviceId, graceful); +// +// if (result.isSuccess()) { +// snapshotService.onServiceShutdown(serviceId); +// return ResponseEntity.ok(ApiResponse.success(result)); +// } else { +// return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) +// .body(ApiResponse.error(result.getErrorMessage())); +// } +// } + + @DeleteMapping("/{serviceId}") + public ResponseEntity delete(@PathVariable String serviceId) { + log.info("Received delete request for service {}", serviceId); + + orchestrator.cleanupService(serviceId); + + return ResponseEntity.noContent().build(); + } + + /** + * 生成 Compose 构建包(zip 下载) + * + * 返回 zip 包含 docker-compose.yaml、Dockerfile、build.sh 等构建所需文件。 + * 用户解压后执行 docker compose up --build -d 即可在本地构建镜像并启动服务。 + */ + @PostMapping("/compose/generate") + public ResponseEntity generateCompose( + @Valid @RequestBody ServiceBuildRequest request) { + + log.info("Received compose package request for service {}", request.getServiceId()); + + request.setPodMode(PodMode.ALONE); + request.setDeployMode(DeployMode.COMPOSE); + + ServiceBuildResult result = scheduleService.generateComposePackage(request); + + if (!result.isSuccess()) { + log.error("Compose package generation failed for service {}: {}", + request.getServiceId(), result.getErrorMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.error(result.getErrorMessage())); + } + + String filename = "ai-worker-" + request.getServiceId() + ".tar.gz"; + log.info("Compose package ready for service {}, filename: {}", request.getServiceId(), filename); + + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"") + .contentType(MediaType.parseMediaType("application/gzip")) + .body(new ByteArrayResource(result.getComposeTar())); + } + + @PostMapping("/{serviceId}/scale-down") + public ResponseEntity> scaleDown( + @PathVariable String serviceId, + @RequestBody ScaleRequest request) { + + String podName = request != null ? request.getPodName() : null; + String source = request != null ? request.getSource() : "api"; + + if (podName == null || podName.isEmpty()) { + return ResponseEntity.badRequest() + .body(ApiResponse.error("podName is required for scale-down")); + } + + log.info("Received scale-down request for service {}, podName={}, source={}", + serviceId, podName, source); + + ScaleResult result = scaleService.scaleDown(serviceId, podName, source); + + if (result.isSuccess()) { + return ResponseEntity.ok(ApiResponse.success(result)); + } else { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.error(result.getErrorMessage())); + } + } + + @PostMapping("/{serviceId}/scale-up") + public ResponseEntity> scaleUp( + @PathVariable String serviceId, + @RequestBody(required = false) ScaleRequest request) { + + Integer targetPodCount = request != null ? request.getTargetPodCount() : null; + String source = request != null ? request.getSource() : "api"; + + log.info("Received scale-up request for service {}, targetPodCount={}, source={}", + serviceId, targetPodCount, source); + + ScaleResult result = scaleService.scaleUp(serviceId, targetPodCount, source); + + if (result.isSuccess()) { + return ResponseEntity.ok(ApiResponse.success(result)); + } else { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.error(result.getErrorMessage())); + } + } + + @PostMapping("/{serviceId}/scale") + public ResponseEntity> scale( + @PathVariable String serviceId, + @RequestBody ScaleRequest request) { + + if (request.getTargetPodCount() == null || request.getTargetPodCount() < 0) { + return ResponseEntity.badRequest() + .body(ApiResponse.error("targetPodCount is required and must be >= 0")); + } + + String source = request.getSource() != null ? request.getSource() : "api"; + + log.info("Received scale request for service {}, targetPodCount={}, source={}", + serviceId, request.getTargetPodCount(), source); + + ScaleResult result = scaleService.scale(serviceId, request.getTargetPodCount(), source); + + if (result.isSuccess()) { + return ResponseEntity.ok(ApiResponse.success(result)); + } else { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.error(result.getErrorMessage())); + } + } + + @GetMapping("/{serviceId}/scale") + public ResponseEntity> getScaleStatus( + @PathVariable String serviceId) { + + ScaleResult result = scaleService.getScaleStatus(serviceId); + return ResponseEntity.ok(ApiResponse.success(result)); + } + + @GetMapping("/health") + public ResponseEntity> health() { + return ResponseEntity.ok(ApiResponse.success("OK")); + } +} diff --git a/back/src/main/java/com/linkwork/controller/SecurityPolicyController.java b/back/src/main/java/com/linkwork/controller/SecurityPolicyController.java new file mode 100644 index 0000000..139dd87 --- /dev/null +++ b/back/src/main/java/com/linkwork/controller/SecurityPolicyController.java @@ -0,0 +1,88 @@ +package com.linkwork.controller; + +import com.linkwork.common.ApiResponse; +import com.linkwork.context.UserContext; +import com.linkwork.service.SecurityPolicyService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +/** + * 安全策略控制器 + */ +@Slf4j +@RestController +@RequestMapping("/api/v1/security/policies") +@CrossOrigin(origins = "*") +@RequiredArgsConstructor +public class SecurityPolicyController { + + private final SecurityPolicyService policyService; + + /** + * 获取所有安全策略 + * GET /api/v1/security/policies + */ + @GetMapping + public ApiResponse>> listPolicies() { + log.info("获取安全策略列表"); + return ApiResponse.success(policyService.listPolicies()); + } + + /** + * 获取单个策略详情 + * GET /api/v1/security/policies/{id} + */ + @GetMapping("/{id}") + public ApiResponse> getPolicy(@PathVariable Long id) { + log.info("获取安全策略详情: id={}", id); + return ApiResponse.success(policyService.getPolicy(id)); + } + + /** + * 创建自定义策略 + * POST /api/v1/security/policies + */ + @PostMapping + public ApiResponse> createPolicy(@RequestBody Map request) { + String userId = UserContext.getCurrentUserId(); + String userName = UserContext.getCurrentUserName(); + log.info("创建安全策略: name={}, userId={}", request.get("name"), userId); + return ApiResponse.success(policyService.createPolicy(request, userId, userName)); + } + + /** + * 更新策略 + * PUT /api/v1/security/policies/{id} + */ + @PutMapping("/{id}") + public ApiResponse> updatePolicy( + @PathVariable Long id, @RequestBody Map request) { + log.info("更新安全策略: id={}", id); + return ApiResponse.success(policyService.updatePolicy(id, request)); + } + + /** + * 切换策略启用/禁用 + * POST /api/v1/security/policies/{id}/toggle + */ + @PostMapping("/{id}/toggle") + public ApiResponse> togglePolicy(@PathVariable Long id) { + log.info("切换安全策略: id={}", id); + return ApiResponse.success(policyService.togglePolicy(id)); + } + + /** + * 删除策略 + * DELETE /api/v1/security/policies/{id} + */ + @DeleteMapping("/{id}") + public ApiResponse deletePolicy(@PathVariable Long id) { + log.info("删除安全策略: id={}", id); + policyService.deletePolicy(id); + return ApiResponse.success(null); + } +} diff --git a/back/src/main/java/com/linkwork/controller/SkillController.java b/back/src/main/java/com/linkwork/controller/SkillController.java new file mode 100644 index 0000000..15c4d6a --- /dev/null +++ b/back/src/main/java/com/linkwork/controller/SkillController.java @@ -0,0 +1,265 @@ +package com.linkwork.controller; + +import com.linkwork.common.ApiResponse; +import com.linkwork.common.ForbiddenOperationException; +import com.linkwork.common.ResourceNotFoundException; +import com.linkwork.context.UserContext; +import com.linkwork.service.SkillService; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; + +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; + +/** + * 技能控制器 - Git-based skill management + */ +@Slf4j +@RestController +@RequestMapping("/api/v1/skills") +@CrossOrigin(origins = "*") +@RequiredArgsConstructor +public class SkillController { + + private final SkillService skillService; + + /** + * 获取技能列表(分页) + */ + @GetMapping + public ApiResponse> listSkills( + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "20") int pageSize, + @RequestParam(required = false) String status, + @RequestParam(required = false) String keyword) { + String userId = UserContext.getCurrentUserId(); + Map data = skillService.listSkills(page, pageSize, status, keyword, userId); + return ApiResponse.success(data); + } + + /** + * 获取所有可用的技能(用于角色配置下拉选择) + */ + @GetMapping("/available") + public ApiResponse>> listAvailable() { + String userId = UserContext.getCurrentUserId(); + List> data = skillService.listAllAvailable(userId); + return ApiResponse.success(data); + } + + /** + * 从Git仓库同步所有技能 + */ + @PostMapping("/sync") + public ApiResponse> syncFromGit() { + try { + int count = skillService.syncAllFromGit(); + return ApiResponse.success(Map.of("synced", count)); + } catch (Exception e) { + log.error("Failed to sync skills from git", e); + return ApiResponse.error(500, e.getMessage()); + } + } + + /** + * 获取技能详情(含文件树) + */ + @GetMapping("/{name}") + public ApiResponse> getSkillDetail(@PathVariable String name) { + try { + String userId = UserContext.getCurrentUserId(); + Map detail = skillService.getSkillDetail(name, userId); + return ApiResponse.success(detail); + } catch (ResourceNotFoundException e) { + return ApiResponse.error(404, e.getMessage()); + } catch (ForbiddenOperationException e) { + return ApiResponse.error(403, e.getMessage()); + } catch (Exception e) { + log.error("Failed to get skill detail: {}", name, e); + return ApiResponse.error(500, e.getMessage()); + } + } + + /** + * 读取技能文件内容 + */ + @GetMapping("/{name}/files/**") + public ApiResponse> getFile(@PathVariable String name, HttpServletRequest request) { + try { + String path = extractFilePath(request, name); + String userId = UserContext.getCurrentUserId(); + String content = skillService.getFile(name, path, userId); + // 获取分支最新 commit 用于前端乐观锁 + String commitId = skillService.getLatestCommitId(name, userId); + return ApiResponse.success(Map.of( + "content", content, + "path", path, + "commitId", commitId != null ? commitId : "" + )); + } catch (ResourceNotFoundException e) { + return ApiResponse.error(404, e.getMessage()); + } catch (ForbiddenOperationException e) { + return ApiResponse.error(403, e.getMessage()); + } catch (IllegalArgumentException e) { + return ApiResponse.error(400, e.getMessage()); + } catch (Exception e) { + log.error("Failed to get file: {}/{}", name, extractFilePath(request, name), e); + return ApiResponse.error(500, e.getMessage()); + } + } + + /** + * 编辑并提交技能文件 + */ + @PutMapping("/{name}/files/**") + public ApiResponse> commitFile(@PathVariable String name, + @RequestBody Map body, + HttpServletRequest request) { + try { + String path = extractFilePath(request, name); + String content = (String) body.get("content"); + String commitMessage = (String) body.get("commitMessage"); + String lastCommitId = (String) body.get("lastCommitId"); + if (!StringUtils.hasText(commitMessage)) { + throw new IllegalArgumentException("commitMessage 不能为空"); + } + String userId = UserContext.getCurrentUserId(); + Map result = skillService.commitFile( + name, path, content, commitMessage, lastCommitId, userId); + return ApiResponse.success(result); + } catch (ResourceNotFoundException e) { + return ApiResponse.error(404, e.getMessage()); + } catch (ForbiddenOperationException e) { + return ApiResponse.error(403, e.getMessage()); + } catch (IllegalArgumentException e) { + return ApiResponse.error(400, e.getMessage()); + } catch (Exception e) { + log.error("Failed to commit file: {}/{}", name, extractFilePath(request, name), e); + return ApiResponse.error(500, e.getMessage()); + } + } + + /** + * 创建新技能 + */ + @PostMapping + public ApiResponse> createSkill(@RequestBody Map request) { + try { + String name = (String) request.get("name"); + String description = (String) request.get("description"); + Boolean isPublic = request.get("isPublic") instanceof Boolean + ? (Boolean) request.get("isPublic") + : null; + String userId = UserContext.getCurrentUserId(); + String userName = UserContext.getCurrentUserName(); + skillService.createSkill(name, description, isPublic, userId, userName); + return ApiResponse.success(Map.of("name", name)); + } catch (ForbiddenOperationException e) { + return ApiResponse.error(403, e.getMessage()); + } catch (IllegalArgumentException e) { + return ApiResponse.error(400, e.getMessage()); + } catch (Exception e) { + log.error("Failed to create skill", e); + return ApiResponse.error(500, e.getMessage()); + } + } + + /** + * 更新技能元数据(description/isPublic) + */ + @PutMapping("/{name}") + public ApiResponse> updateSkillMeta(@PathVariable String name, + @RequestBody Map request) { + try { + String userId = UserContext.getCurrentUserId(); + String userName = UserContext.getCurrentUserName(); + skillService.updateSkillMeta(name, request, userId, userName); + return ApiResponse.success(Map.of("name", name)); + } catch (ResourceNotFoundException e) { + return ApiResponse.error(404, e.getMessage()); + } catch (ForbiddenOperationException e) { + return ApiResponse.error(403, e.getMessage()); + } catch (IllegalArgumentException e) { + return ApiResponse.error(400, e.getMessage()); + } catch (Exception e) { + log.error("Failed to update skill meta: {}", name, e); + return ApiResponse.error(500, e.getMessage()); + } + } + + /** + * 删除技能 + */ + @DeleteMapping("/{name}") + public ApiResponse deleteSkill(@PathVariable String name) { + try { + String userId = UserContext.getCurrentUserId(); + skillService.deleteSkill(name, userId); + return ApiResponse.success(null); + } catch (ResourceNotFoundException e) { + return ApiResponse.error(404, e.getMessage()); + } catch (ForbiddenOperationException e) { + return ApiResponse.error(403, e.getMessage()); + } catch (Exception e) { + log.error("Failed to delete skill: {}", name, e); + return ApiResponse.error(500, e.getMessage()); + } + } + + /** + * 获取技能提交历史 + */ + @GetMapping("/{name}/history") + public ApiResponse>> getHistory(@PathVariable String name) { + try { + String userId = UserContext.getCurrentUserId(); + List> history = skillService.getHistory(name, userId); + return ApiResponse.success(history); + } catch (ResourceNotFoundException e) { + return ApiResponse.error(404, e.getMessage()); + } catch (ForbiddenOperationException e) { + return ApiResponse.error(403, e.getMessage()); + } catch (Exception e) { + log.error("Failed to get history: {}", name, e); + return ApiResponse.error(500, e.getMessage()); + } + } + + /** + * 回退到指定提交 + */ + @PostMapping("/{name}/revert") + public ApiResponse revertToCommit(@PathVariable String name, + @RequestBody Map body) { + try { + String commitSha = (String) body.get("commitSha"); + String userId = UserContext.getCurrentUserId(); + skillService.revertToCommit(name, commitSha, userId); + return ApiResponse.success(null); + } catch (ResourceNotFoundException e) { + return ApiResponse.error(404, e.getMessage()); + } catch (ForbiddenOperationException e) { + return ApiResponse.error(403, e.getMessage()); + } catch (IllegalArgumentException e) { + return ApiResponse.error(400, e.getMessage()); + } catch (Exception e) { + log.error("Failed to revert skill {} to commit", name, e); + return ApiResponse.error(500, e.getMessage()); + } + } + + /** + * 从请求URI中提取文件路径 + */ + private String extractFilePath(HttpServletRequest request, String name) { + String uri = request.getRequestURI(); + String prefix = "/api/v1/skills/" + name + "/files/"; + String encodedPath = uri.substring(uri.indexOf(prefix) + prefix.length()); + return URLDecoder.decode(encodedPath, StandardCharsets.UTF_8); + } +} diff --git a/back/src/main/java/com/linkwork/controller/StorageController.java b/back/src/main/java/com/linkwork/controller/StorageController.java new file mode 100644 index 0000000..7fae21b --- /dev/null +++ b/back/src/main/java/com/linkwork/controller/StorageController.java @@ -0,0 +1,31 @@ +package com.linkwork.controller; + +import com.linkwork.common.ApiResponse; +import com.linkwork.service.NfsStorageService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +@Slf4j +@RestController +@RequestMapping("/api/v1/storage") +public class StorageController { + + private final NfsStorageService nfsStorageService; + + public StorageController(NfsStorageService nfsStorageService) { + this.nfsStorageService = nfsStorageService; + } + + @GetMapping("/status") + public ApiResponse> getStatus() { + Map status = new HashMap<>(); + status.put("configured", nfsStorageService.isConfigured()); + status.put("type", "NFS"); + return ApiResponse.success(status); + } +} diff --git a/back/src/main/java/com/linkwork/controller/TaskController.java b/back/src/main/java/com/linkwork/controller/TaskController.java new file mode 100644 index 0000000..7c6d73c --- /dev/null +++ b/back/src/main/java/com/linkwork/controller/TaskController.java @@ -0,0 +1,218 @@ +package com.linkwork.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.linkwork.common.ApiResponse; +import com.linkwork.common.ClientIpResolver; +import com.linkwork.context.UserContext; +import com.linkwork.model.dto.TaskCompleteRequest; +import com.linkwork.model.dto.TaskCreateRequest; +import com.linkwork.model.dto.TaskGitTokenResponse; +import com.linkwork.model.dto.TaskResponse; +import com.linkwork.model.dto.TaskShareCreateRequest; +import com.linkwork.model.dto.TaskShareLinkResponse; +import com.linkwork.model.entity.Task; +import com.linkwork.service.TaskGitTokenService; +import com.linkwork.service.TaskShareLinkService; +import com.linkwork.service.TaskService; +import jakarta.validation.Valid; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 任务控制器 + * 对应 docs/back/api-design.md 3.1 任务执行模块 + */ +@Slf4j +@RestController +@RequestMapping("/api/v1/tasks") +@CrossOrigin(origins = "*") +@RequiredArgsConstructor +public class TaskController { + + private final TaskService taskService; + private final TaskGitTokenService taskGitTokenService; + private final TaskShareLinkService taskShareLinkService; + + /** + * 启动 AI 任务 + * POST /api/v1/tasks + */ + @PostMapping + public ApiResponse> createTask( + @Valid @RequestBody TaskCreateRequest request, + HttpServletRequest servletRequest) { + + String userId = UserContext.getCurrentUserId(); + String userName = UserContext.getCurrentUserName(); + String creatorIp = ClientIpResolver.resolve(servletRequest); + log.info("创建任务请求: prompt={}, roleId={}, modelId={}, userId={}, creatorIp={}", + request.getPrompt(), request.getRoleId(), request.getModelId(), userId, creatorIp); + + Task task = taskService.createTask(request, userId, userName, creatorIp); + TaskResponse taskResponse = taskService.toResponse(task); + + Map result = new HashMap<>(); + result.put("taskId", task.getTaskNo()); + result.put("status", task.getStatus().getCode()); + result.put("estimatedOutput", taskResponse.getEstimatedOutput()); + result.put("deliveryMode", taskResponse.getDeliveryMode()); + result.put("branchName", taskResponse.getBranchName()); + + return ApiResponse.success(result); + } + + /** + * 获取任务详情 + * GET /api/v1/tasks/{taskNo} + */ + @GetMapping("/{taskNo}") + public ApiResponse getTask(@PathVariable String taskNo) { + String userId = UserContext.getCurrentUserId(); + if (!StringUtils.hasText(userId)) { + throw new IllegalStateException("用户未登录或登录态失效"); + } + log.info("获取任务详情: taskNo={}, userId={}", taskNo, userId); + + Task task = taskService.getTaskByNo(taskNo, userId); + TaskResponse response = taskService.toResponse(task); + + return ApiResponse.success(response); + } + + /** + * zzd 按任务 ID 获取 Git token(服务到服务) + * GET /api/v1/tasks/{taskNo}/git-token + */ + @GetMapping("/{taskNo}/git-token") + public ResponseEntity> getTaskGitToken( + @PathVariable String taskNo, + @RequestHeader(value = "Authorization", required = false) String authorization) { + + // NOTE(temporary): API 端联调阶段先跳过 zzd 服务鉴权,优先保障任务链路可用。 + // 后续 API 端启用服务鉴权后,恢复 bearer token 校验逻辑。 + TaskGitTokenResponse response = taskGitTokenService.getTaskGitToken(taskNo); + return ResponseEntity.ok(ApiResponse.success(response)); + } + + /** + * 获取任务列表 + * GET /api/v1/tasks + */ + @GetMapping + public ApiResponse> listTasks( + @RequestParam(required = false) Long roleId, + @RequestParam(required = false) String status, + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "20") Integer pageSize) { + String userId = UserContext.getCurrentUserId(); + if (!StringUtils.hasText(userId)) { + throw new IllegalStateException("用户未登录或登录态失效"); + } + + log.info("获取任务列表: roleId={}, status={}, page={}, pageSize={}, userId={}", roleId, status, page, pageSize, userId); + + Page taskPage = taskService.listTasks(roleId, status, page, pageSize, userId); + List items = taskService.toResponseList(taskPage.getRecords()); + + Map pagination = new HashMap<>(); + pagination.put("page", taskPage.getCurrent()); + pagination.put("pageSize", taskPage.getSize()); + pagination.put("total", taskPage.getTotal()); + pagination.put("totalPages", taskPage.getPages()); + + Map result = new HashMap<>(); + result.put("items", items); + result.put("pagination", pagination); + + return ApiResponse.success(result); + } + + /** + * 任务完成回写(Worker 回调) + * POST /api/v1/tasks/{taskNo}/complete + */ + @PostMapping("/{taskNo}/complete") + public ApiResponse> completeTask( + @PathVariable String taskNo, + @Valid @RequestBody TaskCompleteRequest request) { + log.info("任务完成回写: taskNo={}, status={}, tokensUsed={}, durationMs={}", + taskNo, request.getStatus(), request.getTokensUsed(), request.getDurationMs()); + + Task task = taskService.completeTask(taskNo, request); + + Map result = new HashMap<>(); + result.put("taskId", task.getTaskNo()); + result.put("status", task.getStatus().getCode()); + result.put("tokensUsed", task.getTokensUsed()); + result.put("durationMs", task.getDurationMs()); + + return ApiResponse.success(result); + } + + /** + * 终止任务(主接口) + * POST /api/v1/tasks/{taskNo}/terminate + */ + @PostMapping("/{taskNo}/terminate") + public ApiResponse> terminateTask( + @PathVariable String taskNo) { + String userId = UserContext.getCurrentUserId(); + String userName = UserContext.getCurrentUserName(); + if (!StringUtils.hasText(userId)) { + throw new IllegalStateException("用户未登录或登录态失效"); + } + log.info("终止任务: {}, userId={}", taskNo, userId); + + Task task = taskService.abortTask(taskNo, userId, userName); + + Map result = new HashMap<>(); + result.put("taskId", task.getTaskNo()); + result.put("status", "terminate_requested"); + + return ApiResponse.success(result); + } + + /** + * 终止任务(兼容接口) + * POST /api/v1/tasks/{taskNo}/abort + */ + @PostMapping("/{taskNo}/abort") + public ApiResponse> abortTask( + @PathVariable String taskNo) { + return terminateTask(taskNo); + } + + /** + * 创建任务临时分享链接 + * POST /api/v1/tasks/{taskNo}/share-link + */ + @PostMapping("/{taskNo}/share-link") + public ApiResponse createTaskShareLink( + @PathVariable String taskNo, + @RequestBody(required = false) TaskShareCreateRequest request) { + String userId = UserContext.getCurrentUserId(); + if (!StringUtils.hasText(userId)) { + throw new IllegalStateException("用户未登录或登录态失效"); + } + Integer expireHours = request == null ? null : request.getExpireHours(); + log.info("创建任务分享链接: taskNo={}, userId={}, expireHours={}", taskNo, userId, expireHours); + TaskShareLinkResponse response = taskShareLinkService.createShareLink(taskNo, userId, expireHours); + return ApiResponse.success(response); + } +} diff --git a/back/src/main/java/com/linkwork/controller/TaskOutputController.java b/back/src/main/java/com/linkwork/controller/TaskOutputController.java new file mode 100644 index 0000000..4949a67 --- /dev/null +++ b/back/src/main/java/com/linkwork/controller/TaskOutputController.java @@ -0,0 +1,235 @@ +package com.linkwork.controller; + +import com.linkwork.service.NfsStorageService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.InputStreamResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +/** + * 任务产出物文件下载 — 供前端 WebSocket 事件中的 download_url 使用 + */ +@Slf4j +@RestController +@RequestMapping("/api/v1/task-outputs") +@RequiredArgsConstructor +public class TaskOutputController { + + private final NfsStorageService nfsStorageService; + private static final int ZIP_BUFFER_SIZE = 8192; + + /** + * 任务产出下载(按 objectName 直取)。 + * + * 示例: + * GET /api/v1/task-outputs/file?object=system%2F35%2Flogs%2Fuser-id%2FTSK-xxx%2Freport.md + */ + @GetMapping("/file") + public ResponseEntity downloadTaskOutputFile( + @RequestParam("object") String objectName, + @RequestParam(value = "inline", defaultValue = "false") boolean inline) throws IOException { + + String normalizedObject = normalizeObjectName(objectName); + Path filePath = nfsStorageService.getAbsolutePath(normalizedObject).normalize(); + if (!Files.exists(filePath) || !Files.isRegularFile(filePath)) { + log.warn("任务产出物文件未找到: object={}", normalizedObject); + return ResponseEntity.notFound().build(); + } + + String fileName = filePath.getFileName().toString(); + String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replace("+", "%20"); + String contentType = resolveContentType(fileName, filePath); + String disposition = inline ? "inline" : "attachment"; + InputStream inputStream = Files.newInputStream(filePath); + + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, disposition + "; filename*=UTF-8''" + encodedFileName) + .contentType(MediaType.parseMediaType(contentType)) + .contentLength(Files.size(filePath)) + .body(new InputStreamResource(inputStream)); + } + + @PostMapping(value = "/archive", consumes = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity downloadTaskOutputArchive( + @RequestBody ArchiveRequest request) throws IOException { + + List sourceFiles = prepareArchiveSourceFiles(request); + if (sourceFiles.isEmpty()) { + throw new IllegalArgumentException("items 参数不能为空"); + } + + String archiveFileName = normalizeArchiveFileName(request.fileName()); + String encodedFileName = URLEncoder.encode(archiveFileName, StandardCharsets.UTF_8).replace("+", "%20"); + + StreamingResponseBody responseBody = outputStream -> { + try (ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) { + byte[] buffer = new byte[ZIP_BUFFER_SIZE]; + for (ArchiveSourceFile sourceFile : sourceFiles) { + zipOutputStream.putNextEntry(new ZipEntry(sourceFile.entryName())); + try (InputStream inputStream = Files.newInputStream(sourceFile.path())) { + int readSize; + while ((readSize = inputStream.read(buffer)) > 0) { + zipOutputStream.write(buffer, 0, readSize); + } + } + zipOutputStream.closeEntry(); + } + zipOutputStream.finish(); + } + }; + + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encodedFileName) + .contentType(MediaType.parseMediaType("application/zip")) + .body(responseBody); + } + + private List prepareArchiveSourceFiles(ArchiveRequest request) { + if (request == null || request.items() == null || request.items().isEmpty()) { + return List.of(); + } + + List sourceFiles = new ArrayList<>(); + Set usedEntryNames = new HashSet<>(); + + for (ArchiveItem item : request.items()) { + String normalizedObject = normalizeObjectName(item == null ? null : item.object()); + Path sourcePath = nfsStorageService.getAbsolutePath(normalizedObject).normalize(); + if (!Files.exists(sourcePath) || !Files.isRegularFile(sourcePath)) { + throw new IllegalArgumentException("产出物不存在或不可访问: " + normalizedObject); + } + + String fallbackName = sourcePath.getFileName().toString(); + String normalizedEntryName = normalizeZipEntryName(item == null ? null : item.name(), fallbackName); + String dedupedEntryName = ensureUniqueZipEntryName(normalizedEntryName, usedEntryNames); + sourceFiles.add(new ArchiveSourceFile(sourcePath, dedupedEntryName)); + } + + return sourceFiles; + } + + private String normalizeArchiveFileName(String fileName) { + String normalized = fileName == null ? "" : fileName.trim(); + if (!StringUtils.hasText(normalized)) { + normalized = "task-artifacts.zip"; + } + if (normalized.contains("..") || normalized.contains("/") || normalized.contains("\\")) { + throw new IllegalArgumentException("fileName 参数非法"); + } + if (!normalized.toLowerCase().endsWith(".zip")) { + normalized = normalized + ".zip"; + } + return normalized; + } + + private String normalizeZipEntryName(String entryName, String fallbackName) { + String normalized = entryName == null ? "" : entryName.trim(); + if (!StringUtils.hasText(normalized)) { + normalized = fallbackName; + } + normalized = normalized.replace("\\", "/"); + while (normalized.startsWith("/")) { + normalized = normalized.substring(1); + } + while (normalized.endsWith("/")) { + normalized = normalized.substring(0, normalized.length() - 1); + } + if (!StringUtils.hasText(normalized)) { + normalized = fallbackName; + } + + String[] rawSegments = normalized.split("/"); + List safeSegments = new ArrayList<>(); + for (String segment : rawSegments) { + if (!StringUtils.hasText(segment) || ".".equals(segment)) { + continue; + } + if ("..".equals(segment)) { + throw new IllegalArgumentException("name 参数非法"); + } + safeSegments.add(segment); + } + if (safeSegments.isEmpty()) { + return fallbackName; + } + return String.join("/", safeSegments); + } + + private String ensureUniqueZipEntryName(String entryName, Set usedEntryNames) { + if (!usedEntryNames.contains(entryName)) { + usedEntryNames.add(entryName); + return entryName; + } + + int dotIndex = entryName.lastIndexOf('.'); + String baseName = dotIndex > 0 ? entryName.substring(0, dotIndex) : entryName; + String extension = dotIndex > 0 ? entryName.substring(dotIndex) : ""; + int index = 1; + String candidate = baseName + "(" + index + ")" + extension; + while (usedEntryNames.contains(candidate)) { + index++; + candidate = baseName + "(" + index + ")" + extension; + } + usedEntryNames.add(candidate); + return candidate; + } + + private String normalizeObjectName(String objectName) { + String normalized = objectName == null ? "" : objectName.trim(); + while (normalized.startsWith("/")) { + normalized = normalized.substring(1); + } + if (!StringUtils.hasText(normalized)) { + throw new IllegalArgumentException("object 参数不能为空"); + } + if (normalized.contains("..") || normalized.contains("\\")) { + throw new IllegalArgumentException("object 参数非法"); + } + return normalized; + } + + private String resolveContentType(String fileName, Path filePath) { + try { + String detected = Files.probeContentType(filePath); + if (StringUtils.hasText(detected)) { + return detected; + } + } catch (IOException ignore) { + // noop, fall through to extension mapping + } + String lowerName = fileName == null ? "" : fileName.toLowerCase(java.util.Locale.ROOT); + if (lowerName.endsWith(".pdf")) { + return MediaType.APPLICATION_PDF_VALUE; + } + if (lowerName.endsWith(".md") || lowerName.endsWith(".txt") || lowerName.endsWith(".log")) { + return MediaType.TEXT_PLAIN_VALUE; + } + if (lowerName.endsWith(".json")) { + return MediaType.APPLICATION_JSON_VALUE; + } + return MediaType.APPLICATION_OCTET_STREAM_VALUE; + } + + private record ArchiveRequest(String fileName, List items) {} + private record ArchiveItem(String object, String name) {} + private record ArchiveSourceFile(Path path, String entryName) {} +} diff --git a/back/src/main/java/com/linkwork/controller/UserSoulController.java b/back/src/main/java/com/linkwork/controller/UserSoulController.java new file mode 100644 index 0000000..3d2725b --- /dev/null +++ b/back/src/main/java/com/linkwork/controller/UserSoulController.java @@ -0,0 +1,50 @@ +package com.linkwork.controller; + +import com.linkwork.common.ApiResponse; +import com.linkwork.context.UserContext; +import com.linkwork.model.dto.UserSoulResponse; +import com.linkwork.model.dto.UserSoulUpsertRequest; +import com.linkwork.service.UserSoulService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@RequestMapping({"/api/v1/users/me/soul", "/api/v1/user-soul"}) +@CrossOrigin(origins = "*") +@RequiredArgsConstructor +public class UserSoulController { + + private final UserSoulService userSoulService; + + @GetMapping + public ApiResponse getCurrentUserSoul() { + String userId = UserContext.getCurrentUserId(); + if (!StringUtils.hasText(userId)) { + throw new IllegalStateException("用户未登录或登录态失效"); + } + UserSoulResponse response = userSoulService.getCurrentUserSoul(userId); + return ApiResponse.success(response); + } + + @PutMapping + public ApiResponse upsertCurrentUserSoul( + @Valid @RequestBody UserSoulUpsertRequest request) { + String userId = UserContext.getCurrentUserId(); + String userName = UserContext.getCurrentUserName(); + if (!StringUtils.hasText(userId)) { + throw new IllegalStateException("用户未登录或登录态失效"); + } + log.info("更新用户 Soul: userId={}, version={}", userId, request.getVersion()); + UserSoulResponse response = userSoulService.upsertCurrentUserSoul(userId, userName, request); + return ApiResponse.success(response); + } +} diff --git a/back/src/main/java/com/linkwork/controller/WorkspaceController.java b/back/src/main/java/com/linkwork/controller/WorkspaceController.java new file mode 100644 index 0000000..f15fb3e --- /dev/null +++ b/back/src/main/java/com/linkwork/controller/WorkspaceController.java @@ -0,0 +1,24 @@ +package com.linkwork.controller; + +import com.linkwork.model.FileNode; +import com.linkwork.service.WorkspaceService; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/v1/workspace") +@CrossOrigin(origins = "*") // 允许前端跨域 +public class WorkspaceController { + + private final WorkspaceService workspaceService; + + public WorkspaceController(WorkspaceService workspaceService) { + this.workspaceService = workspaceService; + } + + @GetMapping("/files") + public List listFiles(@RequestParam String taskId) { + return workspaceService.listFiles(taskId); + } +} diff --git a/back/src/main/java/com/linkwork/filter/InternalApiAuditFilter.java b/back/src/main/java/com/linkwork/filter/InternalApiAuditFilter.java new file mode 100644 index 0000000..0c1e4bb --- /dev/null +++ b/back/src/main/java/com/linkwork/filter/InternalApiAuditFilter.java @@ -0,0 +1,50 @@ +package com.linkwork.filter; + +import com.linkwork.common.ClientIpResolver; +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +/** + * 内部 API 调用审计日志 + *

+ * 记录所有 /api/internal/** 请求的调用方 IP、Method、Path、响应码。 + */ +@Slf4j +@Component +@Order(2) +public class InternalApiAuditFilter implements Filter { + + private static final String INTERNAL_PREFIX = "/api/internal/"; + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + + HttpServletRequest httpRequest = (HttpServletRequest) request; + String path = httpRequest.getRequestURI(); + + if (!path.startsWith(INTERNAL_PREFIX)) { + chain.doFilter(request, response); + return; + } + + String clientIp = ClientIpResolver.resolve(httpRequest); + String method = httpRequest.getMethod(); + long start = System.currentTimeMillis(); + + try { + chain.doFilter(request, response); + } finally { + int status = ((HttpServletResponse) response).getStatus(); + long elapsed = System.currentTimeMillis() - start; + log.info("[AUDIT] internal-api ip={} method={} path={} status={} elapsed={}ms", + clientIp, method, path, status, elapsed); + } + } +} diff --git a/back/src/main/java/com/linkwork/filter/InternalApiIpFilter.java b/back/src/main/java/com/linkwork/filter/InternalApiIpFilter.java new file mode 100644 index 0000000..a30db9d --- /dev/null +++ b/back/src/main/java/com/linkwork/filter/InternalApiIpFilter.java @@ -0,0 +1,106 @@ +package com.linkwork.filter; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.linkwork.common.ClientIpResolver; +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.io.IOException; +import java.net.InetAddress; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 内部 API IP 白名单过滤器 + *

+ * /api/internal/** 路径仅允许集群内网 IP(10.x、172.16-31.x、192.168.x)和 localhost 访问。 + * 可通过 robot.internal-api.extra-ips 配置额外放行的 IP。 + */ +@Slf4j +@Component +@Order(0) +@RequiredArgsConstructor +public class InternalApiIpFilter implements Filter { + + private static final String INTERNAL_PREFIX = "/api/internal/"; + + private final ObjectMapper objectMapper; + + @Value("${robot.internal-api.extra-ips:}") + private String extraIps; + + private final Set extraIpSet = ConcurrentHashMap.newKeySet(); + private volatile boolean initialized = false; + + private void ensureInitialized() { + if (!initialized) { + synchronized (this) { + if (!initialized) { + if (StringUtils.hasText(extraIps)) { + for (String ip : extraIps.split(",")) { + String trimmed = ip.trim(); + if (!trimmed.isEmpty()) { + extraIpSet.add(trimmed); + } + } + } + initialized = true; + } + } + } + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + + HttpServletRequest httpRequest = (HttpServletRequest) request; + String path = httpRequest.getRequestURI(); + + if (!path.startsWith(INTERNAL_PREFIX)) { + chain.doFilter(request, response); + return; + } + + ensureInitialized(); + + String clientIp = ClientIpResolver.resolve(httpRequest); + if (isAllowed(clientIp)) { + chain.doFilter(request, response); + } else { + log.warn("Internal API access denied: ip={}, path={}", clientIp, path); + HttpServletResponse httpResponse = (HttpServletResponse) response; + httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN); + httpResponse.setContentType("application/json;charset=UTF-8"); + httpResponse.getWriter().write(objectMapper.writeValueAsString( + Map.of("code", 40300, "msg", "Forbidden: IP not in whitelist", "data", Map.of()) + )); + } + } + + private boolean isAllowed(String ip) { + if (!StringUtils.hasText(ip)) { + return false; + } + if ("127.0.0.1".equals(ip) || "0:0:0:0:0:0:0:1".equals(ip) || "::1".equals(ip)) { + return true; + } + if (extraIpSet.contains(ip)) { + return true; + } + try { + InetAddress addr = InetAddress.getByName(ip); + return addr.isLoopbackAddress() || addr.isSiteLocalAddress(); + } catch (Exception e) { + return false; + } + } +} diff --git a/back/src/main/java/com/linkwork/filter/JwtAuthFilter.java b/back/src/main/java/com/linkwork/filter/JwtAuthFilter.java new file mode 100644 index 0000000..cffd870 --- /dev/null +++ b/back/src/main/java/com/linkwork/filter/JwtAuthFilter.java @@ -0,0 +1,169 @@ +package com.linkwork.filter; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.linkwork.context.UserContext; +import com.linkwork.context.UserInfo; +import com.linkwork.service.AuthService; +import jakarta.servlet.*; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.Map; +import java.util.Set; + +/** + * 全局 JWT 认证过滤器 + *

+ * 从 Cookie 或 Authorization Header 中提取 JWT Token, + * 解析用户信息存入 UserContext(ThreadLocal)。 + * 未认证的请求返回 401。 + */ +@Slf4j +@Component +@Order(1) +@RequiredArgsConstructor +public class JwtAuthFilter implements Filter { + + private final AuthService authService; + private final ObjectMapper objectMapper; + + private static final String AUTH_COOKIE_NAME = "robot_token"; + + /** 不需要认证的路径 */ + private static final Set EXCLUDE_PATHS = Set.of( + "/api/v1/auth/login", + "/api/v1/auth/verify", + "/api/v1/auth/encode", + "/api/v1/models", + "/health", + "/api/v1/health" + ); + + /** 不需要认证的路径后缀(内部 Worker 回调) */ + private static final Set EXCLUDE_SUFFIXES = Set.of( + "/complete", + "/git-token" + ); + + /** 不需要认证的精确内部路径 */ + private static final Set INTERNAL_PATHS = Set.of( + "/api/v1/approvals/create" + ); + + /** 不需要认证的路径前缀 */ + private static final Set EXCLUDE_PREFIXES = Set.of( + "/ws/", + "/api/v1/ws", + "/api/internal/" + ); + + private static final String PUBLIC_TASK_MODEL_PREFIX = "/api/v1/public/tasks/"; + private static final String PUBLIC_TASK_MODEL_SUFFIX = "/model"; + private static final String PUBLIC_TASK_SHARE_DETAIL_SUFFIX = "/share-detail"; + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + + HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletResponse httpResponse = (HttpServletResponse) response; + + String path = httpRequest.getRequestURI(); + + // 跳过不需要认证的路径 + if (isExcluded(path)) { + chain.doFilter(request, response); + return; + } + + try { + // 提取 JWT Token + String token = extractToken(httpRequest); + + if (token != null && authService.validateToken(token)) { + // 解析用户信息并存入 ThreadLocal + UserInfo userInfo = authService.getUserInfoFromToken(token); + UserContext.set(userInfo); + chain.doFilter(request, response); + } else { + // 未认证 + sendUnauthorized(httpResponse, "未登录或 Token 已过期"); + } + } finally { + // 清除 ThreadLocal,防止内存泄漏 + UserContext.clear(); + } + } + + /** + * 从请求中提取 JWT Token + * 优先从 Cookie 提取,其次从 Authorization Header 提取 + */ + private String extractToken(HttpServletRequest request) { + // 1. 从 Cookie 提取 + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if (AUTH_COOKIE_NAME.equals(cookie.getName())) { + String value = cookie.getValue(); + if (value != null && !value.isBlank()) { + return value; + } + } + } + } + + // 2. 从 Authorization Header 提取(兼容旧方式) + String authHeader = request.getHeader("Authorization"); + if (authHeader != null && authHeader.startsWith("Bearer ")) { + return authHeader.substring(7); + } + + return null; + } + + /** + * 判断路径是否排除认证 + */ + private boolean isExcluded(String path) { + if (EXCLUDE_PATHS.contains(path) || INTERNAL_PATHS.contains(path)) { + return true; + } + for (String prefix : EXCLUDE_PREFIXES) { + if (path.startsWith(prefix)) { + return true; + } + } + if (path.startsWith(PUBLIC_TASK_MODEL_PREFIX) + && (path.endsWith(PUBLIC_TASK_MODEL_SUFFIX) || path.endsWith(PUBLIC_TASK_SHARE_DETAIL_SUFFIX))) { + return true; + } + // Worker 回调接口:/api/v1/tasks/{taskNo}/complete 等 + for (String suffix : EXCLUDE_SUFFIXES) { + if (path.endsWith(suffix) && (path.startsWith("/api/v1/tasks/") || path.startsWith("/api/v1/roles/"))) { + return true; + } + } + return false; + } + + /** + * 返回 401 响应 + */ + private void sendUnauthorized(HttpServletResponse response, String message) throws IOException { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json;charset=UTF-8"); + Map body = Map.of( + "code", 40100, + "msg", message, + "data", Map.of() + ); + response.getWriter().write(objectMapper.writeValueAsString(body)); + } +} diff --git a/back/src/main/java/com/linkwork/mapper/ApprovalMapper.java b/back/src/main/java/com/linkwork/mapper/ApprovalMapper.java new file mode 100644 index 0000000..58f2b56 --- /dev/null +++ b/back/src/main/java/com/linkwork/mapper/ApprovalMapper.java @@ -0,0 +1,12 @@ +package com.linkwork.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.linkwork.model.entity.Approval; +import org.apache.ibatis.annotations.Mapper; + +/** + * 审批 Mapper + */ +@Mapper +public interface ApprovalMapper extends BaseMapper { +} diff --git a/back/src/main/java/com/linkwork/mapper/BuildRecordMapper.java b/back/src/main/java/com/linkwork/mapper/BuildRecordMapper.java new file mode 100644 index 0000000..0acf197 --- /dev/null +++ b/back/src/main/java/com/linkwork/mapper/BuildRecordMapper.java @@ -0,0 +1,12 @@ +package com.linkwork.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.linkwork.model.entity.BuildRecordEntity; +import org.apache.ibatis.annotations.Mapper; + +/** + * 构建记录 Mapper + */ +@Mapper +public interface BuildRecordMapper extends BaseMapper { +} diff --git a/back/src/main/java/com/linkwork/mapper/CronJobMapper.java b/back/src/main/java/com/linkwork/mapper/CronJobMapper.java new file mode 100644 index 0000000..818de29 --- /dev/null +++ b/back/src/main/java/com/linkwork/mapper/CronJobMapper.java @@ -0,0 +1,9 @@ +package com.linkwork.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.linkwork.model.entity.CronJob; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface CronJobMapper extends BaseMapper { +} diff --git a/back/src/main/java/com/linkwork/mapper/CronJobRunMapper.java b/back/src/main/java/com/linkwork/mapper/CronJobRunMapper.java new file mode 100644 index 0000000..32312ee --- /dev/null +++ b/back/src/main/java/com/linkwork/mapper/CronJobRunMapper.java @@ -0,0 +1,9 @@ +package com.linkwork.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.linkwork.model.entity.CronJobRun; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface CronJobRunMapper extends BaseMapper { +} diff --git a/back/src/main/java/com/linkwork/mapper/FileNodeMapper.java b/back/src/main/java/com/linkwork/mapper/FileNodeMapper.java new file mode 100644 index 0000000..77ff261 --- /dev/null +++ b/back/src/main/java/com/linkwork/mapper/FileNodeMapper.java @@ -0,0 +1,9 @@ +package com.linkwork.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.linkwork.model.entity.FileNodeEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface FileNodeMapper extends BaseMapper { +} diff --git a/back/src/main/java/com/linkwork/mapper/McpServerMapper.java b/back/src/main/java/com/linkwork/mapper/McpServerMapper.java new file mode 100644 index 0000000..8e291ae --- /dev/null +++ b/back/src/main/java/com/linkwork/mapper/McpServerMapper.java @@ -0,0 +1,12 @@ +package com.linkwork.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.linkwork.model.entity.McpServerEntity; +import org.apache.ibatis.annotations.Mapper; + +/** + * MCP 服务 Mapper + */ +@Mapper +public interface McpServerMapper extends BaseMapper { +} diff --git a/back/src/main/java/com/linkwork/mapper/McpUsageDailyMapper.java b/back/src/main/java/com/linkwork/mapper/McpUsageDailyMapper.java new file mode 100644 index 0000000..f72ee6e --- /dev/null +++ b/back/src/main/java/com/linkwork/mapper/McpUsageDailyMapper.java @@ -0,0 +1,9 @@ +package com.linkwork.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.linkwork.model.entity.McpUsageDailyEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface McpUsageDailyMapper extends BaseMapper { +} diff --git a/back/src/main/java/com/linkwork/mapper/McpUserConfigMapper.java b/back/src/main/java/com/linkwork/mapper/McpUserConfigMapper.java new file mode 100644 index 0000000..7222f68 --- /dev/null +++ b/back/src/main/java/com/linkwork/mapper/McpUserConfigMapper.java @@ -0,0 +1,9 @@ +package com.linkwork.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.linkwork.model.entity.McpUserConfigEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface McpUserConfigMapper extends BaseMapper { +} diff --git a/back/src/main/java/com/linkwork/mapper/RobotFileMapper.java b/back/src/main/java/com/linkwork/mapper/RobotFileMapper.java new file mode 100644 index 0000000..25a33a3 --- /dev/null +++ b/back/src/main/java/com/linkwork/mapper/RobotFileMapper.java @@ -0,0 +1,9 @@ +package com.linkwork.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.linkwork.model.entity.RobotFile; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface RobotFileMapper extends BaseMapper { +} diff --git a/back/src/main/java/com/linkwork/mapper/RoleMapper.java b/back/src/main/java/com/linkwork/mapper/RoleMapper.java new file mode 100644 index 0000000..d96fcba --- /dev/null +++ b/back/src/main/java/com/linkwork/mapper/RoleMapper.java @@ -0,0 +1,9 @@ +package com.linkwork.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.linkwork.model.entity.RoleEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface RoleMapper extends BaseMapper { +} diff --git a/back/src/main/java/com/linkwork/mapper/SecurityPolicyMapper.java b/back/src/main/java/com/linkwork/mapper/SecurityPolicyMapper.java new file mode 100644 index 0000000..a349e06 --- /dev/null +++ b/back/src/main/java/com/linkwork/mapper/SecurityPolicyMapper.java @@ -0,0 +1,12 @@ +package com.linkwork.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.linkwork.model.entity.SecurityPolicy; +import org.apache.ibatis.annotations.Mapper; + +/** + * 安全策略 Mapper + */ +@Mapper +public interface SecurityPolicyMapper extends BaseMapper { +} diff --git a/back/src/main/java/com/linkwork/mapper/SkillMapper.java b/back/src/main/java/com/linkwork/mapper/SkillMapper.java new file mode 100644 index 0000000..ef507c1 --- /dev/null +++ b/back/src/main/java/com/linkwork/mapper/SkillMapper.java @@ -0,0 +1,12 @@ +package com.linkwork.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.linkwork.model.entity.SkillEntity; +import org.apache.ibatis.annotations.Mapper; + +/** + * 技能 Mapper + */ +@Mapper +public interface SkillMapper extends BaseMapper { +} diff --git a/back/src/main/java/com/linkwork/mapper/TaskGitAuthMapper.java b/back/src/main/java/com/linkwork/mapper/TaskGitAuthMapper.java new file mode 100644 index 0000000..f1abca5 --- /dev/null +++ b/back/src/main/java/com/linkwork/mapper/TaskGitAuthMapper.java @@ -0,0 +1,9 @@ +package com.linkwork.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.linkwork.model.entity.TaskGitAuthEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface TaskGitAuthMapper extends BaseMapper { +} diff --git a/back/src/main/java/com/linkwork/mapper/TaskMapper.java b/back/src/main/java/com/linkwork/mapper/TaskMapper.java new file mode 100644 index 0000000..ee4c423 --- /dev/null +++ b/back/src/main/java/com/linkwork/mapper/TaskMapper.java @@ -0,0 +1,12 @@ +package com.linkwork.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.linkwork.model.entity.Task; +import org.apache.ibatis.annotations.Mapper; + +/** + * 任务 Mapper + */ +@Mapper +public interface TaskMapper extends BaseMapper { +} diff --git a/back/src/main/java/com/linkwork/mapper/UserFavoriteRoleMapper.java b/back/src/main/java/com/linkwork/mapper/UserFavoriteRoleMapper.java new file mode 100644 index 0000000..8a11205 --- /dev/null +++ b/back/src/main/java/com/linkwork/mapper/UserFavoriteRoleMapper.java @@ -0,0 +1,9 @@ +package com.linkwork.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.linkwork.model.entity.UserFavoriteRoleEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface UserFavoriteRoleMapper extends BaseMapper { +} diff --git a/back/src/main/java/com/linkwork/mapper/UserSoulMapper.java b/back/src/main/java/com/linkwork/mapper/UserSoulMapper.java new file mode 100644 index 0000000..fb289d1 --- /dev/null +++ b/back/src/main/java/com/linkwork/mapper/UserSoulMapper.java @@ -0,0 +1,9 @@ +package com.linkwork.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.linkwork.model.entity.UserSoulEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface UserSoulMapper extends BaseMapper { +} diff --git a/back/src/main/java/com/linkwork/model/FileNode.java b/back/src/main/java/com/linkwork/model/FileNode.java new file mode 100644 index 0000000..c9ac2fa --- /dev/null +++ b/back/src/main/java/com/linkwork/model/FileNode.java @@ -0,0 +1,20 @@ +package com.linkwork.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class FileNode { + private String name; + private String type; // "file" or "directory" + private String content; + private String size; + private List children; +} diff --git a/back/src/main/java/com/linkwork/model/dto/BuildQueueStatus.java b/back/src/main/java/com/linkwork/model/dto/BuildQueueStatus.java new file mode 100644 index 0000000..ca196ac --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/BuildQueueStatus.java @@ -0,0 +1,102 @@ +package com.linkwork.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 构建队列状态 DTO + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BuildQueueStatus { + + /** + * 等待中的任务数量 + */ + private int waitingCount; + + /** + * 正在执行的任务数量 + */ + private int runningCount; + + /** + * 硬性并发上限 + */ + private int maxConcurrent; + + /** + * 队列最大容量 + */ + private int maxQueueSize; + + /** + * 系统资源状态 + */ + private ResourceStatus resourceStatus; + + /** + * CPU 阈值配置 + */ + private double cpuThreshold; + + /** + * 内存阈值配置 + */ + private double memoryThreshold; + + /** + * 是否可以接受新任务 + */ + private boolean canAcceptNewTask; + + /** + * 等待中的任务列表(简要信息) + */ + private List waitingTasks; + + /** + * 正在执行的任务列表(简要信息) + */ + private List runningTasks; + + /** + * 任务简要信息 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class TaskInfo { + /** + * 构建 ID + */ + private String buildId; + + /** + * 服务 ID + */ + private String serviceId; + + /** + * 等待时间(毫秒) + */ + private long waitTimeMs; + + /** + * 执行时间(毫秒),仅对正在执行的任务有效 + */ + private long executionTimeMs; + + /** + * 在队列中的位置(从 1 开始) + */ + private int position; + } +} diff --git a/back/src/main/java/com/linkwork/model/dto/BuildTask.java b/back/src/main/java/com/linkwork/model/dto/BuildTask.java new file mode 100644 index 0000000..8eb98c8 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/BuildTask.java @@ -0,0 +1,159 @@ +package com.linkwork.model.dto; + +import lombok.Data; + +import java.time.Instant; +import java.util.concurrent.CompletableFuture; + +/** + * 构建任务 DTO + * 用于队列管理 + */ +@Data +public class BuildTask { + + /** + * 构建唯一标识 + */ + private final String buildId; + + /** + * 服务 ID + */ + private final String serviceId; + + /** + * 原始构建请求 + */ + private final ServiceBuildRequest request; + + /** + * 融合后的配置 + */ + private final MergedConfig config; + + /** + * 任务创建时间(入队时间) + */ + private final Instant createdAt; + + /** + * 任务开始执行时间 + */ + private Instant startedAt; + + /** + * 任务完成时间 + */ + private Instant completedAt; + + /** + * 任务状态 + */ + private TaskState state; + + /** + * 任务执行结果 Future + */ + private CompletableFuture resultFuture; + + /** + * 错误信息(如果失败) + */ + private String errorMessage; + + public BuildTask(ServiceBuildRequest request, MergedConfig config) { + this.buildId = request.getBuildId(); + this.serviceId = request.getServiceId(); + this.request = request; + this.config = config; + this.createdAt = Instant.now(); + this.state = TaskState.WAITING; + } + + /** + * 标记任务开始执行 + */ + public void markStarted() { + this.startedAt = Instant.now(); + this.state = TaskState.RUNNING; + } + + /** + * 标记任务完成 + */ + public void markCompleted() { + this.completedAt = Instant.now(); + this.state = TaskState.COMPLETED; + } + + /** + * 标记任务失败 + */ + public void markFailed(String errorMessage) { + this.completedAt = Instant.now(); + this.state = TaskState.FAILED; + this.errorMessage = errorMessage; + } + + /** + * 标记任务取消 + */ + public void markCancelled() { + this.completedAt = Instant.now(); + this.state = TaskState.CANCELLED; + } + + /** + * 获取等待时间(毫秒) + */ + public long getWaitTimeMs() { + if (startedAt != null) { + return startedAt.toEpochMilli() - createdAt.toEpochMilli(); + } + return Instant.now().toEpochMilli() - createdAt.toEpochMilli(); + } + + /** + * 获取执行时间(毫秒) + */ + public long getExecutionTimeMs() { + if (startedAt == null) { + return 0; + } + if (completedAt != null) { + return completedAt.toEpochMilli() - startedAt.toEpochMilli(); + } + return Instant.now().toEpochMilli() - startedAt.toEpochMilli(); + } + + /** + * 任务状态枚举 + */ + public enum TaskState { + /** + * 等待执行 + */ + WAITING, + + /** + * 正在执行 + */ + RUNNING, + + /** + * 执行完成 + */ + COMPLETED, + + /** + * 执行失败 + */ + FAILED, + + /** + * 已取消 + */ + CANCELLED + } +} diff --git a/back/src/main/java/com/linkwork/model/dto/ClusterEventDTO.java b/back/src/main/java/com/linkwork/model/dto/ClusterEventDTO.java new file mode 100644 index 0000000..f460cf9 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/ClusterEventDTO.java @@ -0,0 +1,18 @@ +package com.linkwork.model.dto; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class ClusterEventDTO { + private String type; + private String reason; + private String message; + private String objectKind; + private String objectName; + private String namespace; + private String firstTimestamp; + private String lastTimestamp; + private Integer count; +} diff --git a/back/src/main/java/com/linkwork/model/dto/ClusterNodeInfo.java b/back/src/main/java/com/linkwork/model/dto/ClusterNodeInfo.java new file mode 100644 index 0000000..0067639 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/ClusterNodeInfo.java @@ -0,0 +1,25 @@ +package com.linkwork.model.dto; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class ClusterNodeInfo { + private String name; + private String status; + private List roles; + private String kubeletVersion; + private long cpuCapacity; + private long cpuAllocatable; + private long cpuUsage; + private Double cpuUsagePercent; + private long memCapacity; + private long memAllocatable; + private long memUsage; + private Double memUsagePercent; + private int podCount; + private int podCapacity; +} diff --git a/back/src/main/java/com/linkwork/model/dto/ClusterOverviewDTO.java b/back/src/main/java/com/linkwork/model/dto/ClusterOverviewDTO.java new file mode 100644 index 0000000..92cca79 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/ClusterOverviewDTO.java @@ -0,0 +1,23 @@ +package com.linkwork.model.dto; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class ClusterOverviewDTO { + private String namespace; + private int totalPods; + private int runningPods; + private int pendingPods; + private int failedPods; + private int succeededPods; + private long totalCpuMillicores; + private long usedCpuMillicores; + private Double cpuUsagePercent; + private long totalMemoryBytes; + private long usedMemoryBytes; + private Double memoryUsagePercent; + private int podGroupCount; + private int nodeCount; +} diff --git a/back/src/main/java/com/linkwork/model/dto/ClusterPodInfo.java b/back/src/main/java/com/linkwork/model/dto/ClusterPodInfo.java new file mode 100644 index 0000000..bf541d4 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/ClusterPodInfo.java @@ -0,0 +1,25 @@ +package com.linkwork.model.dto; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class ClusterPodInfo { + private String name; + private String namespace; + private String phase; + private String nodeName; + private String podGroupName; + private String podGroupPhase; + private String serviceId; + private String userId; + private List containers; + private int restartCount; + private String startTime; + private String age; + private List images; + private ResourceUsageInfo resourceUsage; +} diff --git a/back/src/main/java/com/linkwork/model/dto/ContainerStatusInfo.java b/back/src/main/java/com/linkwork/model/dto/ContainerStatusInfo.java new file mode 100644 index 0000000..72d6f26 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/ContainerStatusInfo.java @@ -0,0 +1,21 @@ +package com.linkwork.model.dto; + +import lombok.Builder; +import lombok.Data; + +/** + * 容器状态信息 + */ +@Data +@Builder +public class ContainerStatusInfo { + private String name; // 容器名称 (agent/runner) + private boolean ready; // 是否就绪 + private String state; // waiting/running/terminated + private String reason; // 状态原因 + private Integer exitCode; // 退出码(terminated 时) + private Integer restartCount; // 重启次数 + + // 资源使用(来自 metrics-server) + private ResourceUsageInfo resourceUsage; // 容器级别资源使用 +} diff --git a/back/src/main/java/com/linkwork/model/dto/CreateFolderRequest.java b/back/src/main/java/com/linkwork/model/dto/CreateFolderRequest.java new file mode 100644 index 0000000..4f6c173 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/CreateFolderRequest.java @@ -0,0 +1,11 @@ +package com.linkwork.model.dto; + +import lombok.Data; + +@Data +public class CreateFolderRequest { + private String name; + private String spaceType; + private String workstationId; + private String parentId; +} diff --git a/back/src/main/java/com/linkwork/model/dto/CredentialConfig.java b/back/src/main/java/com/linkwork/model/dto/CredentialConfig.java new file mode 100644 index 0000000..abb5f21 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/CredentialConfig.java @@ -0,0 +1,17 @@ +package com.linkwork.model.dto; + +import lombok.Builder; +import lombok.Data; + +/** + * 凭证配置 + */ +@Data +@Builder +public class CredentialConfig { + private String credentialId; + private String key; + private String type; + private String mountPath; + private String envName; +} diff --git a/back/src/main/java/com/linkwork/model/dto/CronJobCreateRequest.java b/back/src/main/java/com/linkwork/model/dto/CronJobCreateRequest.java new file mode 100644 index 0000000..1a4de7d --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/CronJobCreateRequest.java @@ -0,0 +1,36 @@ +package com.linkwork.model.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.time.LocalDateTime; +@Data +public class CronJobCreateRequest { + @NotBlank(message = "任务名称不能为空") + private String jobName; + + @NotNull(message = "岗位ID不能为空") + private Long roleId; + + @NotBlank(message = "模型ID不能为空") + private String modelId; + + @NotBlank(message = "调度类型不能为空") + private String scheduleType; + + private String cronExpr; + private Long intervalMs; + private LocalDateTime runAt; + private String timezone; + + @NotBlank(message = "任务指令不能为空") + @Size(max = 10000, message = "任务指令长度不能超过10000字符") + private String taskContent; + + private Boolean deleteAfterRun; + private Integer maxRetry; + private String notifyMode; + private String notifyTarget; +} diff --git a/back/src/main/java/com/linkwork/model/dto/CronJobResponse.java b/back/src/main/java/com/linkwork/model/dto/CronJobResponse.java new file mode 100644 index 0000000..2f8dbe2 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/CronJobResponse.java @@ -0,0 +1,42 @@ +package com.linkwork.model.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +public class CronJobResponse { + private Long id; + private String jobName; + private String creatorId; + private String creatorName; + private Long roleId; + private String roleName; + private String modelId; + private String scheduleType; + private String cronExpr; + private Long intervalMs; + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + private LocalDateTime runAt; + private String timezone; + private String taskContent; + private Boolean enabled; + private Boolean deleteAfterRun; + private Integer maxRetry; + private Integer consecutiveFailures; + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + private LocalDateTime nextFireTime; + private List nextFireTimes; + private String notifyMode; + private String notifyTarget; + private Integer totalRuns; + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + private LocalDateTime lastRunTime; + private String lastRunStatus; + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + private LocalDateTime createdAt; + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + private LocalDateTime updatedAt; +} diff --git a/back/src/main/java/com/linkwork/model/dto/CronJobRunResponse.java b/back/src/main/java/com/linkwork/model/dto/CronJobRunResponse.java new file mode 100644 index 0000000..536ff13 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/CronJobRunResponse.java @@ -0,0 +1,27 @@ +package com.linkwork.model.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class CronJobRunResponse { + private Long id; + private Long cronJobId; + private String taskNo; + private String creatorId; + private Long roleId; + private String status; + private String triggerType; + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + private LocalDateTime plannedFireTime; + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + private LocalDateTime startedAt; + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + private LocalDateTime finishedAt; + private Long durationMs; + private String errorMessage; + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + private LocalDateTime createdAt; +} diff --git a/back/src/main/java/com/linkwork/model/dto/CronJobToggleRequest.java b/back/src/main/java/com/linkwork/model/dto/CronJobToggleRequest.java new file mode 100644 index 0000000..97e5713 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/CronJobToggleRequest.java @@ -0,0 +1,10 @@ +package com.linkwork.model.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class CronJobToggleRequest { + @NotNull(message = "enabled 不能为空") + private Boolean enabled; +} diff --git a/back/src/main/java/com/linkwork/model/dto/CronJobUpdateRequest.java b/back/src/main/java/com/linkwork/model/dto/CronJobUpdateRequest.java new file mode 100644 index 0000000..0639f2c --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/CronJobUpdateRequest.java @@ -0,0 +1,36 @@ +package com.linkwork.model.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.time.LocalDateTime; +@Data +public class CronJobUpdateRequest { + @NotBlank(message = "任务名称不能为空") + private String jobName; + + @NotNull(message = "岗位ID不能为空") + private Long roleId; + + @NotBlank(message = "模型ID不能为空") + private String modelId; + + @NotBlank(message = "调度类型不能为空") + private String scheduleType; + + private String cronExpr; + private Long intervalMs; + private LocalDateTime runAt; + private String timezone; + + @NotBlank(message = "任务指令不能为空") + @Size(max = 10000, message = "任务指令长度不能超过10000字符") + private String taskContent; + + private Boolean deleteAfterRun; + private Integer maxRetry; + private String notifyMode; + private String notifyTarget; +} diff --git a/back/src/main/java/com/linkwork/model/dto/CronSchedulePreviewRequest.java b/back/src/main/java/com/linkwork/model/dto/CronSchedulePreviewRequest.java new file mode 100644 index 0000000..612f05b --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/CronSchedulePreviewRequest.java @@ -0,0 +1,15 @@ +package com.linkwork.model.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class CronSchedulePreviewRequest { + private String scheduleType; + private String cronExpr; + private Long intervalMs; + private LocalDateTime runAt; + private String timezone; + private Integer limit; +} diff --git a/back/src/main/java/com/linkwork/model/dto/FileMentionResponse.java b/back/src/main/java/com/linkwork/model/dto/FileMentionResponse.java new file mode 100644 index 0000000..0952ca6 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/FileMentionResponse.java @@ -0,0 +1,16 @@ +package com.linkwork.model.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class FileMentionResponse { + private String fileId; + private String fileName; + private String fileType; + private Long fileSize; + private String spaceType; + private String workstationId; + private LocalDateTime createdAt; +} diff --git a/back/src/main/java/com/linkwork/model/dto/FileNodeResponse.java b/back/src/main/java/com/linkwork/model/dto/FileNodeResponse.java new file mode 100644 index 0000000..bccff54 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/FileNodeResponse.java @@ -0,0 +1,23 @@ +package com.linkwork.model.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class FileNodeResponse { + private String nodeId; + private String parentId; + private String entryType; + private String name; + private String spaceType; + private String workstationId; + private String fileId; + private Long fileSize; + private String fileType; + private String parseStatus; + private String memoryIndexStatus; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + private boolean hasChildren; +} diff --git a/back/src/main/java/com/linkwork/model/dto/FileResponse.java b/back/src/main/java/com/linkwork/model/dto/FileResponse.java new file mode 100644 index 0000000..2e1a8af --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/FileResponse.java @@ -0,0 +1,19 @@ +package com.linkwork.model.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class FileResponse { + private String fileId; + private String fileName; + private Long fileSize; + private String fileType; + private String contentType; + private String spaceType; + private String workstationId; + private String parseStatus; + private String memoryIndexStatus; + private LocalDateTime createdAt; +} diff --git a/back/src/main/java/com/linkwork/model/dto/FileSpaceSyncRequest.java b/back/src/main/java/com/linkwork/model/dto/FileSpaceSyncRequest.java new file mode 100644 index 0000000..1d73a05 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/FileSpaceSyncRequest.java @@ -0,0 +1,10 @@ +package com.linkwork.model.dto; + +import lombok.Data; + +@Data +public class FileSpaceSyncRequest { + private String spaceType; + private String workstationId; +} + diff --git a/back/src/main/java/com/linkwork/model/dto/FileSpaceSyncResponse.java b/back/src/main/java/com/linkwork/model/dto/FileSpaceSyncResponse.java new file mode 100644 index 0000000..d95afcc --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/FileSpaceSyncResponse.java @@ -0,0 +1,15 @@ +package com.linkwork.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class FileSpaceSyncResponse { + private String spaceType; + private String workstationId; + private int scannedCount; + private int syncedCount; + private int skippedCount; +} + diff --git a/back/src/main/java/com/linkwork/model/dto/FileTransferRequest.java b/back/src/main/java/com/linkwork/model/dto/FileTransferRequest.java new file mode 100644 index 0000000..40ca2dd --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/FileTransferRequest.java @@ -0,0 +1,29 @@ +package com.linkwork.model.dto; + +import com.linkwork.model.enums.ConflictPolicy; +import lombok.Data; + +@Data +public class FileTransferRequest { + private String targetSpaceType; + private String targetWorkstationId; + private String targetParentId; + private String conflictPolicy; + private String newName; + + /** + * @deprecated Use {@link #conflictPolicy} = "OVERWRITE" instead. + */ + @Deprecated + private Boolean overwrite; + + public ConflictPolicy resolveConflictPolicy() { + if (conflictPolicy != null && !conflictPolicy.isBlank()) { + return ConflictPolicy.fromString(conflictPolicy); + } + if (Boolean.TRUE.equals(overwrite)) { + return ConflictPolicy.OVERWRITE; + } + return ConflictPolicy.REJECT; + } +} diff --git a/back/src/main/java/com/linkwork/model/dto/GeneratedSpec.java b/back/src/main/java/com/linkwork/model/dto/GeneratedSpec.java new file mode 100644 index 0000000..bf66555 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/GeneratedSpec.java @@ -0,0 +1,19 @@ +package com.linkwork.model.dto; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +/** + * 生成的 Spec 结果(用于预览) + */ +@Data +@Builder +public class GeneratedSpec { + private String serviceId; + private Map podGroupSpec; // PodGroup YAML + private List> podSpecs; // Pod YAML 列表 + private String composeYaml; // Docker Compose YAML (如果是 COMPOSE 模式) +} diff --git a/back/src/main/java/com/linkwork/model/dto/ImageBuildResult.java b/back/src/main/java/com/linkwork/model/dto/ImageBuildResult.java new file mode 100644 index 0000000..cfb20e6 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/ImageBuildResult.java @@ -0,0 +1,70 @@ +package com.linkwork.model.dto; + +import lombok.Builder; +import lombok.Data; + +/** + * 镜像构建结果 + */ +@Data +@Builder +public class ImageBuildResult { + + /** + * 构建是否成功 + */ + private boolean success; + + /** + * 构建的 Agent 镜像地址 + * 格式:{registry}/service-{serviceId}-agent:{timestamp} + */ + private String agentImageTag; + + /** + * 构建的 Runner 镜像地址(Sidecar 模式) + * 格式:{registry}/service-{serviceId}-runner:{timestamp} + * Alone 模式时为 null + */ + private String runnerImageTag; + + /** + * 镜像构建耗时(毫秒) + */ + private long buildDurationMs; + + /** + * 是否已推送到仓库 + * K8s 模式为 true,Compose 模式为 false + */ + private boolean pushed; + + /** + * 错误信息(失败时) + */ + private String errorMessage; + + /** + * 创建成功结果 + */ + public static ImageBuildResult success(String agentImageTag, String runnerImageTag, + long buildDurationMs, boolean pushed) { + return ImageBuildResult.builder() + .success(true) + .agentImageTag(agentImageTag) + .runnerImageTag(runnerImageTag) + .buildDurationMs(buildDurationMs) + .pushed(pushed) + .build(); + } + + /** + * 创建失败结果 + */ + public static ImageBuildResult failed(String errorMessage) { + return ImageBuildResult.builder() + .success(false) + .errorMessage(errorMessage) + .build(); + } +} diff --git a/back/src/main/java/com/linkwork/model/dto/McpDiscoverResult.java b/back/src/main/java/com/linkwork/model/dto/McpDiscoverResult.java new file mode 100644 index 0000000..15adfef --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/McpDiscoverResult.java @@ -0,0 +1,56 @@ +package com.linkwork.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Map; + +/** + * MCP Server 工具发现结果 DTO + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class McpDiscoverResult { + + /** 是否成功 */ + private boolean success; + + /** 失败原因 */ + private String error; + + /** MCP server 返回的名称 */ + private String serverName; + + /** MCP server 返回的版本 */ + private String serverVersion; + + /** MCP 协议版本 */ + private String protocolVersion; + + /** 工具列表 */ + private List tools; + + /** + * MCP 工具定义 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class McpTool { + + /** 工具名称 */ + private String name; + + /** 工具描述 */ + private String description; + + /** 输入参数的 JSON Schema */ + private Map inputSchema; + } +} diff --git a/back/src/main/java/com/linkwork/model/dto/McpProbeResult.java b/back/src/main/java/com/linkwork/model/dto/McpProbeResult.java new file mode 100644 index 0000000..656f791 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/McpProbeResult.java @@ -0,0 +1,32 @@ +package com.linkwork.model.dto; + +import lombok.Builder; +import lombok.Data; + +/** + * MCP Server 探活结果 DTO + */ +@Data +@Builder +public class McpProbeResult { + + /** + * 健康状态:online / degraded / offline + */ + private String status; + + /** + * 探活延迟(毫秒) + */ + private int latencyMs; + + /** + * 探活结果描述,例如 "HTTP 200 (123ms)" + */ + private String message; + + /** + * 实际探测的 URL + */ + private String probeUrl; +} diff --git a/back/src/main/java/com/linkwork/model/dto/MemoryIndexBatchRequest.java b/back/src/main/java/com/linkwork/model/dto/MemoryIndexBatchRequest.java new file mode 100644 index 0000000..44ebe52 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/MemoryIndexBatchRequest.java @@ -0,0 +1,12 @@ +package com.linkwork.model.dto; + +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.List; + +@Data +public class MemoryIndexBatchRequest { + @NotEmpty + private List filePaths; +} diff --git a/back/src/main/java/com/linkwork/model/dto/MemoryIndexJob.java b/back/src/main/java/com/linkwork/model/dto/MemoryIndexJob.java new file mode 100644 index 0000000..d6c3631 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/MemoryIndexJob.java @@ -0,0 +1,31 @@ +package com.linkwork.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MemoryIndexJob { + + public enum JobType { + FILE_UPLOAD, + MEMORY_WRITEBACK, + SESSION_SUMMARY + } + + private String jobId; + private String workstationId; + private String userId; + private JobType jobType; + private String filePath; + private String content; + private String source; + private String fileType; + private String storageType; + private String objectName; + private String collectionName; +} diff --git a/back/src/main/java/com/linkwork/model/dto/MemoryIngestRequest.java b/back/src/main/java/com/linkwork/model/dto/MemoryIngestRequest.java new file mode 100644 index 0000000..9350a4c --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/MemoryIngestRequest.java @@ -0,0 +1,11 @@ +package com.linkwork.model.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class MemoryIngestRequest { + @NotBlank + private String content; + private String source = ""; +} diff --git a/back/src/main/java/com/linkwork/model/dto/MemorySearchRequest.java b/back/src/main/java/com/linkwork/model/dto/MemorySearchRequest.java new file mode 100644 index 0000000..dffa262 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/MemorySearchRequest.java @@ -0,0 +1,11 @@ +package com.linkwork.model.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class MemorySearchRequest { + @NotBlank + private String query; + private int topK = 10; +} diff --git a/back/src/main/java/com/linkwork/model/dto/MergedConfig.java b/back/src/main/java/com/linkwork/model/dto/MergedConfig.java new file mode 100644 index 0000000..c0f4392 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/MergedConfig.java @@ -0,0 +1,139 @@ +package com.linkwork.model.dto; + +import com.linkwork.config.EnvConfig.FilePlacementConfig; +import com.linkwork.config.EnvConfig.OssMountConfig; +import com.linkwork.model.enums.DeployMode; +import com.linkwork.model.enums.PodMode; +import lombok.Builder; +import lombok.Data; + +import java.util.Map; + +/** + * 融合后的配置(内部使用) + * + * 设计说明: + * - 镜像在 Build 时根据环境变量动态构建 + * - token 写入容器后仅 executor 可访问 + */ +@Data +@Builder +public class MergedConfig { + // 服务标识 + private String serviceId; + private String userId; + + // 岗位信息(用于运行时 MCP 配置拉取) + private Long roleId; + + // 模式配置 + private DeployMode deployMode; + private PodMode podMode; + private Integer podCount; + + // K8s 调度配置 + private String namespace; + private String queueName; + private String priorityClassName; + + // ========== 镜像配置 ========== + /** + * Agent 镜像地址 + * 在镜像构建后设置为构建的镜像地址 + */ + private String agentImage; + + /** + * Runner 镜像地址(Sidecar 模式) + * 在镜像构建后设置为构建的镜像地址 + */ + private String runnerImage; + + /** + * 镜像构建耗时(毫秒) + */ + private Long imageBuildDurationMs; + + /** + * 镜像拉取策略(Always/IfNotPresent/Never) + */ + private String imagePullPolicy; + + /** + * K8s 拉取私有镜像的 Secret 名称 + */ + private String imagePullSecret; + + // ========== 构建参数(用于镜像构建)========== + /** + * 环境变量列表(用于镜像构建) + */ + private Map buildEnvVars; + + /** + * Agent 基础镜像 + */ + private String agentBaseImage; + + /** + * Runner 基础镜像(Sidecar 模式) + */ + private String runnerBaseImage; + + /** + * 镜像仓库地址 + */ + private String imageRegistry; + + // Agent 启动脚本配置 + private String mainPyUrl; // main.py 下载链接(启动 agent+executor) + + // 凭证配置 + private String token; // API 凭证(写入容器,仅 executor 可访问) + + // 文件放置配置 + private FilePlacementConfig filePlacement; + + // 资源配置 + private ResourceSpec agentResources; + private ResourceSpec runnerResources; + + // 网络配置 + private String apiBaseUrl; + private String wsBaseUrl; + private String llmGatewayUrl; + private String redisUrl; + + // SSH 配置(仅 Sidecar) + private Integer sshPort; + + // ========== Agent 启动配置(双容器模式)========== + /** + * 工位 ID (WORKSTATION_ID 环境变量,默认使用 serviceId) + */ + private String workstationId; + + /** + * Agent config.json 内容(用于创建 ConfigMap 挂载到 /opt/agent/config.json) + */ + private String configJson; + + // 执行限制 + private Integer maxSteps; + private Integer maxRuntimeSeconds; + private Integer workspaceSizeLimit; + + // 任务元信息 + private String model; + private String runnerType; + private String description; + + // 回调配置 + private String callbackUrl; + + // 快速恢复配置 + private String preferredNode; // 优先调度节点(Resume 时使用) + + // OSS 挂载配置 + private OssMountConfig ossMount; +} diff --git a/back/src/main/java/com/linkwork/model/dto/PodGroupStatusInfo.java b/back/src/main/java/com/linkwork/model/dto/PodGroupStatusInfo.java new file mode 100644 index 0000000..f544bc9 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/PodGroupStatusInfo.java @@ -0,0 +1,19 @@ +package com.linkwork.model.dto; + +import lombok.Builder; +import lombok.Data; + +/** + * PodGroup 状态信息 + */ +@Data +@Builder +public class PodGroupStatusInfo { + private String name; // PodGroup 名称 + private String phase; // Pending/Running/Succeeded/Failed/Unknown + private Integer minMember; // 最小成员数 + private Integer running; // 运行中 Pod 数 + private Integer succeeded; // 成功 Pod 数 + private Integer failed; // 失败 Pod 数 + private Integer pending; // 等待中 Pod 数 +} diff --git a/back/src/main/java/com/linkwork/model/dto/PodLogResponseDTO.java b/back/src/main/java/com/linkwork/model/dto/PodLogResponseDTO.java new file mode 100644 index 0000000..ab79a70 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/PodLogResponseDTO.java @@ -0,0 +1,14 @@ +package com.linkwork.model.dto; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class PodLogResponseDTO { + private String podName; + private String namespace; + private String containerName; + private String logs; + private int tailLines; +} diff --git a/back/src/main/java/com/linkwork/model/dto/PodStatusInfo.java b/back/src/main/java/com/linkwork/model/dto/PodStatusInfo.java new file mode 100644 index 0000000..b0669dd --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/PodStatusInfo.java @@ -0,0 +1,24 @@ +package com.linkwork.model.dto; + +import lombok.Builder; +import lombok.Data; + +import java.time.Instant; +import java.util.List; + +/** + * Pod 状态信息 + */ +@Data +@Builder +public class PodStatusInfo { + private String name; // Pod 名称 + private String phase; // Pending/Running/Succeeded/Failed + private String nodeName; // 所在节点 + private String nodeHostname; // 节点主机名 + private Instant startTime; // 启动时间 + private List containers; // 容器状态列表 + + // 资源使用(来自 metrics-server) + private ResourceUsageInfo resourceUsage; // Pod 级别资源使用汇总 +} diff --git a/back/src/main/java/com/linkwork/model/dto/ReportExportFieldOption.java b/back/src/main/java/com/linkwork/model/dto/ReportExportFieldOption.java new file mode 100644 index 0000000..1ad6b3c --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/ReportExportFieldOption.java @@ -0,0 +1,34 @@ +package com.linkwork.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 报表字段元数据 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ReportExportFieldOption { + + /** + * Java 字段名 + */ + private String field; + + /** + * 数据库列名 + */ + private String column; + + /** + * 前端展示名 + */ + private String label; + + /** + * Java 类型名 + */ + private String javaType; +} diff --git a/back/src/main/java/com/linkwork/model/dto/ReportExportFieldResponse.java b/back/src/main/java/com/linkwork/model/dto/ReportExportFieldResponse.java new file mode 100644 index 0000000..1fe5589 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/ReportExportFieldResponse.java @@ -0,0 +1,26 @@ +package com.linkwork.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 报表可导出字段响应 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ReportExportFieldResponse { + + /** + * 导出类型:task / role + */ + private String type; + + /** + * 字段定义列表 + */ + private List fields; +} diff --git a/back/src/main/java/com/linkwork/model/dto/ReportExportRequest.java b/back/src/main/java/com/linkwork/model/dto/ReportExportRequest.java new file mode 100644 index 0000000..41de971 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/ReportExportRequest.java @@ -0,0 +1,41 @@ +package com.linkwork.model.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +import java.util.List; + +/** + * 报表导出请求 + */ +@Data +public class ReportExportRequest { + + /** + * 导出类型:task / role + */ + @NotBlank(message = "导出类型不能为空") + private String type; + + /** + * 开始时间,支持 yyyy-MM-dd'T'HH:mm 或 yyyy-MM-dd'T'HH:mm:ss + */ + @NotBlank(message = "开始时间不能为空") + private String startTime; + + /** + * 结束时间,支持 yyyy-MM-dd'T'HH:mm 或 yyyy-MM-dd'T'HH:mm:ss + */ + @NotBlank(message = "结束时间不能为空") + private String endTime; + + /** + * 导出字段(为空时导出全部) + */ + private List fields; + + /** + * 是否附带消息流(仅 task 生效) + */ + private Boolean includeEventStream; +} diff --git a/back/src/main/java/com/linkwork/model/dto/ResourceConfig.java b/back/src/main/java/com/linkwork/model/dto/ResourceConfig.java new file mode 100644 index 0000000..27736f7 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/ResourceConfig.java @@ -0,0 +1,14 @@ +package com.linkwork.model.dto; + +import lombok.Data; + +/** + * 资源配置 + */ +@Data +public class ResourceConfig { + private String cpuRequest; // "1" / "500m" + private String cpuLimit; + private String memoryRequest; // "2Gi" / "512Mi" + private String memoryLimit; +} diff --git a/back/src/main/java/com/linkwork/model/dto/ResourceSpec.java b/back/src/main/java/com/linkwork/model/dto/ResourceSpec.java new file mode 100644 index 0000000..60b4e38 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/ResourceSpec.java @@ -0,0 +1,20 @@ +package com.linkwork.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 资源规格 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ResourceSpec { + private String cpuRequest; + private String cpuLimit; + private String memoryRequest; + private String memoryLimit; +} diff --git a/back/src/main/java/com/linkwork/model/dto/ResourceStatus.java b/back/src/main/java/com/linkwork/model/dto/ResourceStatus.java new file mode 100644 index 0000000..f53a9ea --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/ResourceStatus.java @@ -0,0 +1,75 @@ +package com.linkwork.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 系统资源状态 DTO + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ResourceStatus { + + /** + * CPU 使用率 (0.0 ~ 1.0) + * -1 表示无法获取 + */ + private double cpuUsage; + + /** + * 内存使用率 (0.0 ~ 1.0) + * -1 表示无法获取 + */ + private double memoryUsage; + + /** + * 总内存(字节) + */ + private long totalMemory; + + /** + * 可用内存(字节) + */ + private long freeMemory; + + /** + * 可用处理器数量 + */ + private int availableProcessors; + + /** + * 获取 CPU 使用率百分比字符串 + */ + public String getCpuUsagePercent() { + if (cpuUsage < 0) return "N/A"; + return String.format("%.1f%%", cpuUsage * 100); + } + + /** + * 获取内存使用率百分比字符串 + */ + public String getMemoryUsagePercent() { + if (memoryUsage < 0) return "N/A"; + return String.format("%.1f%%", memoryUsage * 100); + } + + /** + * 获取可读的内存信息 + */ + public String getMemoryInfo() { + return String.format("%s / %s", + formatBytes(totalMemory - freeMemory), + formatBytes(totalMemory)); + } + + private String formatBytes(long bytes) { + if (bytes < 1024) return bytes + " B"; + if (bytes < 1024 * 1024) return String.format("%.1f KB", bytes / 1024.0); + if (bytes < 1024 * 1024 * 1024) return String.format("%.1f MB", bytes / (1024.0 * 1024)); + return String.format("%.1f GB", bytes / (1024.0 * 1024 * 1024)); + } +} diff --git a/back/src/main/java/com/linkwork/model/dto/ResourceUsageInfo.java b/back/src/main/java/com/linkwork/model/dto/ResourceUsageInfo.java new file mode 100644 index 0000000..85a6ed4 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/ResourceUsageInfo.java @@ -0,0 +1,26 @@ +package com.linkwork.model.dto; + +import lombok.Builder; +import lombok.Data; + +/** + * 资源使用信息(来自 metrics-server) + */ +@Data +@Builder +public class ResourceUsageInfo { + private String cpuUsage; // CPU 使用量,如 "100m" (100 millicores) + private String memoryUsage; // 内存使用量,如 "256Mi" + private Long cpuMillicores; // CPU 使用量(毫核) + private Long memoryBytes; // 内存使用量(字节) + + // 资源限制 + private String cpuLimit; // CPU 限制 + private String memoryLimit; // 内存限制 + private String cpuRequest; // CPU 请求 + private String memoryRequest; // 内存请求 + + // 使用率(百分比) + private Double cpuUsagePercent; // CPU 使用率 (usage/limit) + private Double memoryUsagePercent; // 内存使用率 (usage/limit) +} diff --git a/back/src/main/java/com/linkwork/model/dto/ScaleRequest.java b/back/src/main/java/com/linkwork/model/dto/ScaleRequest.java new file mode 100644 index 0000000..007b2b7 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/ScaleRequest.java @@ -0,0 +1,27 @@ +package com.linkwork.model.dto; + +import lombok.Data; + +/** + * 伸缩请求 + */ +@Data +public class ScaleRequest { + /** + * 目标 Pod 数量(可选) + * scale-up 时不指定则扩容到 maxPodCount + * scale 时必填 + */ + private Integer targetPodCount; + + /** + * 要删除的 Pod 名称(scale-down 时必填) + * 不指定则返回错误,防止误删 + */ + private String podName; + + /** + * 调用来源(用于日志追踪) + */ + private String source; +} diff --git a/back/src/main/java/com/linkwork/model/dto/ScaleResult.java b/back/src/main/java/com/linkwork/model/dto/ScaleResult.java new file mode 100644 index 0000000..0e311d4 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/ScaleResult.java @@ -0,0 +1,95 @@ +package com.linkwork.model.dto; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +/** + * 伸缩结果 + */ +@Data +@Builder +public class ScaleResult { + private String serviceId; + private boolean success; + + /** + * 伸缩类型:SCALE_UP / SCALE_DOWN / NO_CHANGE + */ + private String scaleType; + + /** + * 伸缩前 Pod 数量 + */ + private int previousPodCount; + + /** + * 伸缩后 Pod 数量 + */ + private int currentPodCount; + + /** + * 最大 Pod 数量(初始配置) + */ + private int maxPodCount; + + /** + * 当前运行的 Pod 列表 + */ + private List runningPods; + + /** + * 本次新增的 Pod 列表(扩容时) + */ + private List addedPods; + + /** + * 本次删除的 Pod 列表(缩容时) + */ + private List removedPods; + + /** + * 错误信息 + */ + private String errorMessage; + + public static ScaleResult success(String serviceId, String scaleType, + int previousCount, int currentCount, int maxCount, + List runningPods, + List addedPods, + List removedPods) { + return ScaleResult.builder() + .serviceId(serviceId) + .success(true) + .scaleType(scaleType) + .previousPodCount(previousCount) + .currentPodCount(currentCount) + .maxPodCount(maxCount) + .runningPods(runningPods) + .addedPods(addedPods) + .removedPods(removedPods) + .build(); + } + + public static ScaleResult noChange(String serviceId, int currentCount, int maxCount, + List runningPods) { + return ScaleResult.builder() + .serviceId(serviceId) + .success(true) + .scaleType("NO_CHANGE") + .previousPodCount(currentCount) + .currentPodCount(currentCount) + .maxPodCount(maxCount) + .runningPods(runningPods) + .build(); + } + + public static ScaleResult failed(String serviceId, String errorMessage) { + return ScaleResult.builder() + .serviceId(serviceId) + .success(false) + .errorMessage(errorMessage) + .build(); + } +} diff --git a/back/src/main/java/com/linkwork/model/dto/ScheduleApiResponse.java b/back/src/main/java/com/linkwork/model/dto/ScheduleApiResponse.java new file mode 100644 index 0000000..da617bc --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/ScheduleApiResponse.java @@ -0,0 +1,48 @@ +package com.linkwork.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Instant; + +/** + * 调度模块通用 API 响应 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ScheduleApiResponse { + private boolean success; + private T data; + private String errorCode; + private String errorMessage; + private Instant timestamp; + + public static ScheduleApiResponse success(T data) { + return ScheduleApiResponse.builder() + .success(true) + .data(data) + .timestamp(Instant.now()) + .build(); + } + + public static ScheduleApiResponse error(String message) { + return ScheduleApiResponse.builder() + .success(false) + .errorMessage(message) + .timestamp(Instant.now()) + .build(); + } + + public static ScheduleApiResponse error(String code, String message) { + return ScheduleApiResponse.builder() + .success(false) + .errorCode(code) + .errorMessage(message) + .timestamp(Instant.now()) + .build(); + } +} diff --git a/back/src/main/java/com/linkwork/model/dto/ScheduleEvent.java b/back/src/main/java/com/linkwork/model/dto/ScheduleEvent.java new file mode 100644 index 0000000..64134e9 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/ScheduleEvent.java @@ -0,0 +1,44 @@ +package com.linkwork.model.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 统一信封格式 + * 遵循 data-format.md 规范定义 + * + * 所有写入 Redis Stream 的消息共享统一的外层结构 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ScheduleEvent { + + /** + * 事件类型枚举,大写下划线分隔 + * 例如:POD_SCHEDULING, POD_SCHEDULED, IMAGE_PULLING 等 + */ + @JsonProperty("event_type") + private String eventType; + + /** + * ISO 8601 带时区,精确到微秒 + * 格式:2026-01-29T10:00:00.000000+00:00 + */ + private String timestamp; + + /** + * 任务标识(对应 serviceId) + */ + @JsonProperty("task_id") + private String taskId; + + /** + * 业务负载,结构由 event_type 决定 + */ + private Object data; +} diff --git a/back/src/main/java/com/linkwork/model/dto/ServiceBuildRequest.java b/back/src/main/java/com/linkwork/model/dto/ServiceBuildRequest.java new file mode 100644 index 0000000..0ff238a --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/ServiceBuildRequest.java @@ -0,0 +1,109 @@ +package com.linkwork.model.dto; + +import com.linkwork.model.enums.DeployMode; +import com.linkwork.model.enums.PodMode; +import com.linkwork.model.enums.ServiceType; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +/** + * 服务构建请求(Build 服务输入) + * + * 设计说明: + * - Build 服务会根据传入的环境变量实时构建镜像 + * - 构建流程:拉取基础镜像 → 注入环境变量 → 执行 build.sh → 推送镜像仓库 + * - K8s 模式:构建后推送到镜像仓库,然后创建 PodGroup/Pod + * - Compose 模式:本地构建,返回 docker-compose.yaml + * - token 写入容器后仅 executor 用户可访问 + * - ssh-key 在容器启动时生成,agent 和 executor 都可访问 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ServiceBuildRequest { + + // ========== 服务信息(必填)========== + @NotBlank + private String serviceId; // 服务唯一标识 + + @NotBlank + private String userId; // 用户 ID + + // ========== 构建追踪(可选,由前端生成)========== + /** + * 构建唯一标识,由前端生成 (UUID 格式) + * 用于关联构建记录和 Redis Stream 事件 + */ + private String buildId; + + /** + * 关联的岗位 ID + */ + private Long roleId; + + /** + * 岗位名称(用于构建记录快照) + */ + private String roleName; + + // ========== 服务信息(可选)========== + private String description; // 服务描述(可选) + + private ServiceType serviceType; // CODE_WRITING / TASK_EXECUTION(可选) + + // ========== 部署配置(必填)========== + @NotNull + private DeployMode deployMode; // K8S / COMPOSE + + // ========== 镜像构建配置(必填)========== + /** + * 环境变量列表(必填)- 用于镜像构建 + * 这些环境变量会被写入 Dockerfile 的 ENV 指令,并在 build.sh 中使用 + * 注意:token 也应放入此 Map 中,在 build.sh 执行前会 export + */ + @NotEmpty + private Map buildEnvVars; + + // ========== 镜像配置(可选)========== + /** + * Runner 基础镜像地址(Sidecar 模式使用) + * K8s + Sidecar 模式下,Runner 容器使用此镜像 + * 为空时使用系统默认配置:schedule.images.runner + * 注意:非 K8s 模式或 Alone 模式不使用此参数 + */ + private String runnerBaseImage; + + /** + * 镜像仓库地址(可选) + * 为空时使用系统默认配置:image-build.registry + * K8s 模式下构建的镜像会推送到此仓库 + */ + private String imageRegistry; + + // ========== 可选配置 ========== + private PodMode podMode; // SIDECAR / ALONE,为空时使用默认值 + private Integer podCount; // Pod 数量,默认 1,最大 10(仅 K8s) + private Integer priority; // 0-100,默认 50 + private ResourceConfig resourceConfig; // 自定义资源配置 + + // ========== 工作目录配置 ========== + private Integer workspaceSizeLimit; // GB,默认 10 + + // ========== 回调配置(仅 K8s)========== + private String callbackUrl; // 状态回调 URL + + // ========== 快速恢复相关(Resume 时使用)========== + /** + * 优先调度节点(Resume 时设置) + * 设置后会在 Pod Spec 中添加节点亲和性,优先调度到原节点以命中镜像缓存 + */ + private String preferredNode; +} diff --git a/back/src/main/java/com/linkwork/model/dto/ServiceBuildResult.java b/back/src/main/java/com/linkwork/model/dto/ServiceBuildResult.java new file mode 100644 index 0000000..0429e82 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/ServiceBuildResult.java @@ -0,0 +1,193 @@ +package com.linkwork.model.dto; + +import lombok.Builder; +import lombok.Data; + +import java.time.Instant; +import java.util.List; + +/** + * 服务构建结果(Build 服务输出) + * + * 包含镜像构建结果和 K8s 资源创建结果 + */ +@Data +@Builder +public class ServiceBuildResult { + private String serviceId; + private boolean success; + private String status; // 状态:SUCCESS, FAILED, BUILDING + private String podGroupName; // 创建的 PodGroup 名称(仅 K8s 模式) + private List podNames; // 创建的 Pod 名称列表(仅 K8s 模式,BUILDING 时为空) + private String queueName; // 分配的队列名称(仅 K8s 模式) + private Instant createdAt; + + // ========== 构建追踪 ========== + /** + * 构建 ID(用于订阅日志和查询状态) + */ + private String buildId; + + /** + * 提示信息(BUILDING 状态时的说明) + */ + private String message; + + // ========== 镜像构建结果 ========== + /** + * 构建的 Agent 镜像地址 + * 格式:{registry}/service-{serviceId}-agent:{timestamp} + * BUILDING 状态时为 null + */ + private String builtAgentImage; + + /** + * 镜像构建耗时(毫秒) + * BUILDING 状态时为 null + */ + private Long buildDurationMs; + + // ========== Compose 模式返回 ========== + /** + * docker-compose.yaml 内容(仅 Compose 模式,已废弃) + * @deprecated 使用 composeArchive 代替 + */ + @Deprecated + private String composeYaml; + + /** + * Compose 构建包 tar.gz 字节(仅 Compose 模式) + * 包含 docker-compose.yaml、Dockerfile、build.sh、config.json、start.sh、README.md 等 + */ + private byte[] composeTar; + + // 调度信息(用于快速恢复) + private String scheduledNode; // 实际调度到的节点,用于保存到 Snapshot(BUILDING 时为 null) + + // 失败时 + private String errorCode; + private String errorMessage; + + /** + * K8s 模式构建成功 + */ + public static ServiceBuildResult successK8s(String serviceId, String podGroupName, + List podNames, String queueName, + String builtAgentImage, Long buildDurationMs) { + return ServiceBuildResult.builder() + .serviceId(serviceId) + .success(true) + .status("SUCCESS") + .podGroupName(podGroupName) + .podNames(podNames) + .queueName(queueName) + .builtAgentImage(builtAgentImage) + .buildDurationMs(buildDurationMs) + .createdAt(Instant.now()) + .build(); + } + + /** + * K8s 模式构建成功(带调度节点) + */ + public static ServiceBuildResult successK8s(String serviceId, String podGroupName, + List podNames, String queueName, + String scheduledNode, + String builtAgentImage, Long buildDurationMs) { + return ServiceBuildResult.builder() + .serviceId(serviceId) + .success(true) + .status("SUCCESS") + .podGroupName(podGroupName) + .podNames(podNames) + .queueName(queueName) + .scheduledNode(scheduledNode) + .builtAgentImage(builtAgentImage) + .buildDurationMs(buildDurationMs) + .createdAt(Instant.now()) + .build(); + } + + /** + * Compose 模式构建成功(返回 tar.gz 构建包) + */ + public static ServiceBuildResult successCompose(String serviceId, byte[] composeTar) { + return ServiceBuildResult.builder() + .serviceId(serviceId) + .success(true) + .status("SUCCESS") + .composeTar(composeTar) + .createdAt(Instant.now()) + .build(); + } + + /** + * 兼容旧版本:K8s 模式构建成功(不含镜像信息) + * @deprecated 使用 successK8s 方法 + */ + @Deprecated + public static ServiceBuildResult success(String serviceId, String podGroupName, + List podNames, String queueName) { + return ServiceBuildResult.builder() + .serviceId(serviceId) + .success(true) + .podGroupName(podGroupName) + .podNames(podNames) + .queueName(queueName) + .createdAt(Instant.now()) + .build(); + } + + /** + * 兼容旧版本:K8s 模式构建成功(带调度节点,不含镜像信息) + * @deprecated 使用 successK8s 方法 + */ + @Deprecated + public static ServiceBuildResult success(String serviceId, String podGroupName, + List podNames, String queueName, + String scheduledNode) { + return ServiceBuildResult.builder() + .serviceId(serviceId) + .success(true) + .podGroupName(podGroupName) + .podNames(podNames) + .queueName(queueName) + .scheduledNode(scheduledNode) + .createdAt(Instant.now()) + .build(); + } + + public static ServiceBuildResult failed(String serviceId, String errorCode, String errorMessage) { + return ServiceBuildResult.builder() + .serviceId(serviceId) + .success(false) + .status("FAILED") + .errorCode(errorCode) + .errorMessage(errorMessage) + .createdAt(Instant.now()) + .build(); + } + + /** + * 构建中(任务已提交,异步执行中) + * + * @param serviceId 服务 ID + * @param buildId 构建 ID(用于订阅日志) + * @param podGroupName PodGroup 名称 + * @param queueName 队列名称 + * @param message 提示信息 + */ + public static ServiceBuildResult building(String serviceId, String buildId, + String podGroupName, String queueName, String message) { + return ServiceBuildResult.builder() + .serviceId(serviceId) + .success(true) + .status("BUILDING") + .buildId(buildId) + .podGroupName(podGroupName) + .queueName(queueName) + .message(message) + .createdAt(Instant.now()) + .build(); + } +} diff --git a/back/src/main/java/com/linkwork/model/dto/ServiceResumeResult.java b/back/src/main/java/com/linkwork/model/dto/ServiceResumeResult.java new file mode 100644 index 0000000..1333c94 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/ServiceResumeResult.java @@ -0,0 +1,79 @@ +package com.linkwork.model.dto; + +import com.linkwork.model.enums.ResumeType; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +/** + * 服务恢复结果(Resume 服务输出) + */ +@Data +@Builder +public class ServiceResumeResult { + + private String serviceId; + private boolean success; + + // 恢复类型 + private ResumeType resumeType; // WARM / COLD + + // 如果快照不存在或已过期,需要完整请求重新构建 + private boolean requireFullRequest; + + // 成功时的信息 + private String podGroupName; + private List podNames; + private String scheduledNode; // 实际调度节点 + private boolean imageCached; // 是否命中镜像缓存 + private Integer estimatedReadySeconds; // 预计就绪时间(秒) + + // 失败时的信息 + private String errorCode; + private String message; + + /** + * 快照不存在或已过期 + */ + public static ServiceResumeResult snapshotNotFound(String serviceId) { + return ServiceResumeResult.builder() + .serviceId(serviceId) + .success(false) + .resumeType(ResumeType.COLD) + .requireFullRequest(true) + .errorCode("SNAPSHOT_NOT_FOUND") + .message("快照不存在或已过期,需要完整的 Build 请求") + .build(); + } + + /** + * 恢复成功 + */ + public static ServiceResumeResult success(String serviceId, String podGroupName, + List podNames, String scheduledNode, + boolean imageCached) { + return ServiceResumeResult.builder() + .serviceId(serviceId) + .success(true) + .resumeType(imageCached ? ResumeType.WARM : ResumeType.COLD) + .podGroupName(podGroupName) + .podNames(podNames) + .scheduledNode(scheduledNode) + .imageCached(imageCached) + .estimatedReadySeconds(imageCached ? 20 : 90) + .build(); + } + + /** + * 恢复失败 + */ + public static ServiceResumeResult failed(String serviceId, String errorCode, String message) { + return ServiceResumeResult.builder() + .serviceId(serviceId) + .success(false) + .errorCode(errorCode) + .message(message) + .build(); + } +} diff --git a/back/src/main/java/com/linkwork/model/dto/ServiceSnapshot.java b/back/src/main/java/com/linkwork/model/dto/ServiceSnapshot.java new file mode 100644 index 0000000..74d5d8d --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/ServiceSnapshot.java @@ -0,0 +1,57 @@ +package com.linkwork.model.dto; + +import com.linkwork.model.enums.SnapshotStatus; +import lombok.Builder; +import lombok.Data; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Set; + +/** + * 服务快照(用于快速重启) + * + * 设计说明: + * - Runner 由运行时 agent 启动,快照不保存 runner 相关信息 + */ +@Data +@Builder +public class ServiceSnapshot { + + // ========== 服务信息 ========== + private String serviceId; + private String userId; + + // ========== 原始请求(完整保存,用于恢复)========== + private String originalRequestJson; // ServiceBuildRequest JSON + + // ========== 镜像信息 ========== + private String agentImage; // Agent 镜像地址 + private String podMode; // SIDECAR / ALONE + + // ========== 调度信息(关键:用于节点亲和)========== + private String lastScheduledNode; // ★ 上次运行的节点 + + // ========== 伸缩信息 ========== + private Integer maxPodCount; // 最大 Pod 数量(初始配置) + private Integer currentPodCount; // 当前 Pod 数量 + private Set runningPodNames; // 当前运行中的 Pod 名称列表 + private Integer nextPodIndex; // 下一个 Pod 序号(用于生成唯一 Pod 名称) + + // ========== 时间信息 ========== + private Instant createdAt; // 首次创建时间 + private Instant lastActiveAt; // 最后活跃时间 + private Instant shutdownAt; // 关闭时间 + + // ========== 快照状态 ========== + private SnapshotStatus status; // ACTIVE / EXPIRED + private Integer resumeCount; // 恢复次数 + + /** + * 快照 24 小时后过期 + */ + public boolean isExpired() { + return shutdownAt != null && + shutdownAt.plus(24, ChronoUnit.HOURS).isBefore(Instant.now()); + } +} diff --git a/back/src/main/java/com/linkwork/model/dto/ServiceStatusResponse.java b/back/src/main/java/com/linkwork/model/dto/ServiceStatusResponse.java new file mode 100644 index 0000000..fef5744 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/ServiceStatusResponse.java @@ -0,0 +1,20 @@ +package com.linkwork.model.dto; + +import lombok.Builder; +import lombok.Data; + +import java.time.Instant; +import java.util.List; + +/** + * 服务状态响应(Status 服务输出) + */ +@Data +@Builder +public class ServiceStatusResponse { + private String serviceId; + private PodGroupStatusInfo podGroupStatus; // PodGroup 整体状态 + private List pods; // 各 Pod 详细状态 + private Instant createdAt; + private Instant updatedAt; +} diff --git a/back/src/main/java/com/linkwork/model/dto/StopResult.java b/back/src/main/java/com/linkwork/model/dto/StopResult.java new file mode 100644 index 0000000..ad26d7e --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/StopResult.java @@ -0,0 +1,30 @@ +package com.linkwork.model.dto; + +import lombok.Builder; +import lombok.Data; + +/** + * 停止结果 + */ +@Data +@Builder +public class StopResult { + private String serviceId; + private boolean success; + private String errorMessage; + + public static StopResult success(String serviceId) { + return StopResult.builder() + .serviceId(serviceId) + .success(true) + .build(); + } + + public static StopResult failed(String serviceId, String errorMessage) { + return StopResult.builder() + .serviceId(serviceId) + .success(false) + .errorMessage(errorMessage) + .build(); + } +} diff --git a/back/src/main/java/com/linkwork/model/dto/TaskCompleteRequest.java b/back/src/main/java/com/linkwork/model/dto/TaskCompleteRequest.java new file mode 100644 index 0000000..cd35fe9 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/TaskCompleteRequest.java @@ -0,0 +1,80 @@ +package com.linkwork.model.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * 任务完成回写请求 DTO + * + * Worker 在任务执行完毕后,通过此接口回写执行结果。 + * 调用方:momo-worker (Workstation 执行器) + */ +@Data +public class TaskCompleteRequest { + + /** + * 最终任务状态 + * 允许值:COMPLETED, FAILED + */ + @NotBlank(message = "任务状态不能为空") + private String status; + + /** + * 消耗的 Token 总数 + */ + @NotNull(message = "Token 消耗不能为空") + private Integer tokensUsed; + + /** + * 任务执行时长(毫秒) + */ + @NotNull(message = "执行时长不能为空") + private Long durationMs; + + /** + * 任务报告(可选) + */ + private Report report; + + @Data + public static class Report { + /** + * 执行摘要 + */ + private String summary; + + /** + * 完成度百分比 (0-100) + */ + private Integer completion; + + /** + * 审计评级 (A/B/C/D) + */ + private String audit; + + /** + * 产出物列表 + */ + private List artifacts; + + /** + * Git 分支名 + */ + private String branch; + + /** + * Git 提交哈希 + */ + private String commit; + } + + @Data + public static class Artifact { + private String name; + private String url; + } +} diff --git a/back/src/main/java/com/linkwork/model/dto/TaskCreateRequest.java b/back/src/main/java/com/linkwork/model/dto/TaskCreateRequest.java new file mode 100644 index 0000000..02af06f --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/TaskCreateRequest.java @@ -0,0 +1,39 @@ +package com.linkwork.model.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * 创建任务请求 DTO + * + * 简化版本:前端只需传递核心参数,其他配置由后端根据岗位自动填充 + */ +@Data +public class TaskCreateRequest { + + /** + * 用户输入的任务指令 + */ + @NotBlank(message = "任务指令不能为空") + private String prompt; + + /** + * 执行岗位 ID + */ + @NotNull(message = "岗位 ID 不能为空") + private Long roleId; + + /** + * 选择的模型 ID(如 claude-opus-4-5, deepseek-v3 等) + */ + @NotBlank(message = "模型 ID 不能为空") + private String modelId; + + /** + * 用户上传的文档文件 ID 列表(OSS 文件 key) + */ + private List fileIds; +} diff --git a/back/src/main/java/com/linkwork/model/dto/TaskDispatchMessage.java b/back/src/main/java/com/linkwork/model/dto/TaskDispatchMessage.java new file mode 100644 index 0000000..3a66d90 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/TaskDispatchMessage.java @@ -0,0 +1,79 @@ +package com.linkwork.model.dto; + +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +/** + * 任务调度消息 DTO + * 用于 Redis 队列传输 + */ +@Data +@Builder +public class TaskDispatchMessage { + + /** + * 任务编号 + */ + private String taskNo; + + /** + * 岗位 ID + */ + private Long roleId; + + /** + * 岗位名称 + */ + private String roleName; + + /** + * 任务指令 + */ + private String prompt; + + /** + * 任务配置 + */ + private TaskConfig config; + + /** + * 创建者 ID + */ + private String creatorId; + + /** + * 创建者名称 + */ + private String creatorName; + + /** + * 创建时间 + */ + private LocalDateTime createdAt; + + /** + * 任务配置 + */ + @Data + @Builder + public static class TaskConfig { + private String image; + private String selectedModel; + private List mcp; + private List skills; + private List knowledge; + private List gitRepos; + private Map env; + } + + @Data + @Builder + public static class GitRepo { + private String id; + private String branch; + } +} diff --git a/back/src/main/java/com/linkwork/model/dto/TaskGitTokenResponse.java b/back/src/main/java/com/linkwork/model/dto/TaskGitTokenResponse.java new file mode 100644 index 0000000..2cae61c --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/TaskGitTokenResponse.java @@ -0,0 +1,25 @@ +package com.linkwork.model.dto; + +import lombok.Data; + +/** + * zzd 获取任务 Git token 响应 + */ +@Data +public class TaskGitTokenResponse { + + private String provider; + + private String tokenType; + + private String token; + + private String expiresAt; + + /** + * git commit 需要的提交身份(由 token 对应 Git 用户解析) + */ + private String commitUserName; + + private String commitUserEmail; +} diff --git a/back/src/main/java/com/linkwork/model/dto/TaskResponse.java b/back/src/main/java/com/linkwork/model/dto/TaskResponse.java new file mode 100644 index 0000000..0dc31b1 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/TaskResponse.java @@ -0,0 +1,95 @@ +package com.linkwork.model.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.linkwork.model.enums.TaskStatus; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +/** + * 任务响应 DTO + */ +@Data +public class TaskResponse { + + /** + * 任务编号(对外展示) + */ + private String taskNo; + + /** + * 关联岗位 ID + */ + private Long roleId; + + /** + * 岗位名称 + */ + private String roleName; + + private String prompt; + + private TaskStatus status; + + private String source; + + private String image; + + private String selectedModel; + + private String creator; + + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'") + private LocalDateTime createdAt; + + private Usage usage; + + private List estimatedOutput; + + private Report report; + + // ---- 从 configJson 解析的字段 ---- + private String runtimeMode; + private String zzMode; + private String runnerImage; + private String repo; + private String branch; + private String branchName; + private String deliveryMode; + private List mcp; + private List skills; + private List knowledge; + private Map env; + + @Data + public static class Usage { + private Integer tokensUsed; + private Integer inputTokens; + private Integer outputTokens; + private Integer requestCount; + private Long tokenLimit; + private BigDecimal usagePercent; + private String duration; + } + + @Data + public static class Report { + private String summary; + private Integer tokens; + private String duration; + private Integer completion; + private String audit; + private List artifacts; + private String branch; + private String commit; + + @Data + public static class Artifact { + private String name; + private String url; + } + } +} diff --git a/back/src/main/java/com/linkwork/model/dto/TaskShareCreateRequest.java b/back/src/main/java/com/linkwork/model/dto/TaskShareCreateRequest.java new file mode 100644 index 0000000..c14cb7a --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/TaskShareCreateRequest.java @@ -0,0 +1,15 @@ +package com.linkwork.model.dto; + +import lombok.Data; + +/** + * 创建任务分享链接请求 + */ +@Data +public class TaskShareCreateRequest { + + /** + * 过期时长(小时) + */ + private Integer expireHours; +} diff --git a/back/src/main/java/com/linkwork/model/dto/TaskShareLinkResponse.java b/back/src/main/java/com/linkwork/model/dto/TaskShareLinkResponse.java new file mode 100644 index 0000000..cf6ad37 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/TaskShareLinkResponse.java @@ -0,0 +1,19 @@ +package com.linkwork.model.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 任务分享链接响应 + */ +@Data +public class TaskShareLinkResponse { + private String taskId; + private String token; + private String shareUrl; + + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'") + private LocalDateTime expiresAt; +} diff --git a/back/src/main/java/com/linkwork/model/dto/UserSoulResponse.java b/back/src/main/java/com/linkwork/model/dto/UserSoulResponse.java new file mode 100644 index 0000000..e0acbed --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/UserSoulResponse.java @@ -0,0 +1,17 @@ +package com.linkwork.model.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class UserSoulResponse { + + private String content; + + private String presetId; + + private Long version; + + private LocalDateTime updatedAt; +} diff --git a/back/src/main/java/com/linkwork/model/dto/UserSoulUpsertRequest.java b/back/src/main/java/com/linkwork/model/dto/UserSoulUpsertRequest.java new file mode 100644 index 0000000..e7509bd --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/UserSoulUpsertRequest.java @@ -0,0 +1,17 @@ +package com.linkwork.model.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class UserSoulUpsertRequest { + + @NotBlank(message = "Soul 内容不能为空") + private String content; + + private String presetId; + + @NotNull(message = "version 不能为空") + private Long version; +} diff --git a/back/src/main/java/com/linkwork/model/dto/event/BuildEventData.java b/back/src/main/java/com/linkwork/model/dto/event/BuildEventData.java new file mode 100644 index 0000000..d00ee4a --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/event/BuildEventData.java @@ -0,0 +1,76 @@ +package com.linkwork.model.dto.event; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 构建事件数据 + * 用于 BUILD_STARTED / BUILD_PROGRESS / BUILD_COMPLETED / BUILD_FAILED / BUILD_PUSHING / BUILD_PUSHED 事件 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BuildEventData { + + /** + * 构建编号 + */ + @JsonProperty("build_no") + private String buildNo; + + /** + * 岗位 ID + */ + @JsonProperty("role_id") + private Long roleId; + + /** + * 岗位名称 + */ + @JsonProperty("role_name") + private String roleName; + + /** + * 基础镜像 + */ + @JsonProperty("base_image") + private String baseImage; + + /** + * 构建产物镜像标签 + */ + @JsonProperty("image_tag") + private String imageTag; + + /** + * 进度消息(用于 BUILD_PROGRESS) + */ + private String message; + + /** + * 进度步骤(用于 BUILD_PROGRESS) + */ + private String step; + + /** + * 错误码(用于 BUILD_FAILED) + */ + @JsonProperty("error_code") + private String errorCode; + + /** + * 错误消息(用于 BUILD_FAILED) + */ + @JsonProperty("error_message") + private String errorMessage; + + /** + * 构建耗时(毫秒) + */ + @JsonProperty("duration_ms") + private Long durationMs; +} diff --git a/back/src/main/java/com/linkwork/model/dto/event/ContainerEventData.java b/back/src/main/java/com/linkwork/model/dto/event/ContainerEventData.java new file mode 100644 index 0000000..f3457f6 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/event/ContainerEventData.java @@ -0,0 +1,34 @@ +package com.linkwork.model.dto.event; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * CONTAINER_STARTING / CONTAINER_READY 事件数据 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ContainerEventData { + + /** + * Pod 名称 + */ + @JsonProperty("pod_name") + private String podName; + + /** + * 容器名称 + */ + @JsonProperty("container_name") + private String containerName; + + /** + * 是否就绪(CONTAINER_READY 时填充) + */ + private Boolean ready; +} diff --git a/back/src/main/java/com/linkwork/model/dto/event/EnvSetupData.java b/back/src/main/java/com/linkwork/model/dto/event/EnvSetupData.java new file mode 100644 index 0000000..1b2285a --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/event/EnvSetupData.java @@ -0,0 +1,34 @@ +package com.linkwork.model.dto.event; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * ENV_SETUP / WORKSPACE_INIT 事件数据 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class EnvSetupData { + + /** + * Pod 名称 + */ + @JsonProperty("pod_name") + private String podName; + + /** + * 步骤标识 + * 例如:code_clone, workspace_setup, ssh_config 等 + */ + private String step; + + /** + * 步骤描述消息 + */ + private String message; +} diff --git a/back/src/main/java/com/linkwork/model/dto/event/ImageEventData.java b/back/src/main/java/com/linkwork/model/dto/event/ImageEventData.java new file mode 100644 index 0000000..04adb68 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/event/ImageEventData.java @@ -0,0 +1,34 @@ +package com.linkwork.model.dto.event; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * IMAGE_PULLING / IMAGE_PULLED 事件数据 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ImageEventData { + + /** + * Pod 名称 + */ + @JsonProperty("pod_name") + private String podName; + + /** + * 容器名称 + */ + @JsonProperty("container_name") + private String containerName; + + /** + * 镜像地址 + */ + private String image; +} diff --git a/back/src/main/java/com/linkwork/model/dto/event/InitCompleteData.java b/back/src/main/java/com/linkwork/model/dto/event/InitCompleteData.java new file mode 100644 index 0000000..4c67fbe --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/event/InitCompleteData.java @@ -0,0 +1,53 @@ +package com.linkwork.model.dto.event; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * INIT_COMPLETE / INIT_FAILED 事件数据 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class InitCompleteData { + + /** + * Pod 名称 + */ + @JsonProperty("pod_name") + private String podName; + + /** + * PodGroup 名称 + */ + @JsonProperty("pod_group_name") + private String podGroupName; + + /** + * 就绪 Pod 数量 + */ + @JsonProperty("ready_pods") + private Integer readyPods; + + /** + * 总 Pod 数量 + */ + @JsonProperty("total_pods") + private Integer totalPods; + + /** + * 错误信息(INIT_FAILED 时填充) + */ + @JsonProperty("error_message") + private String errorMessage; + + /** + * 错误码(INIT_FAILED 时填充) + */ + @JsonProperty("error_code") + private String errorCode; +} diff --git a/back/src/main/java/com/linkwork/model/dto/event/PodSchedulingData.java b/back/src/main/java/com/linkwork/model/dto/event/PodSchedulingData.java new file mode 100644 index 0000000..6e58e21 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/event/PodSchedulingData.java @@ -0,0 +1,41 @@ +package com.linkwork.model.dto.event; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * POD_SCHEDULING / POD_SCHEDULED 事件数据 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PodSchedulingData { + + /** + * Pod 名称 + */ + @JsonProperty("pod_name") + private String podName; + + /** + * Pod 索引 + */ + @JsonProperty("pod_index") + private Integer podIndex; + + /** + * 节点名称(POD_SCHEDULED 时填充) + */ + @JsonProperty("node_name") + private String nodeName; + + /** + * 队列名称 + */ + @JsonProperty("queue_name") + private String queueName; +} diff --git a/back/src/main/java/com/linkwork/model/dto/event/SessionEventData.java b/back/src/main/java/com/linkwork/model/dto/event/SessionEventData.java new file mode 100644 index 0000000..5bd8069 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/dto/event/SessionEventData.java @@ -0,0 +1,46 @@ +package com.linkwork.model.dto.event; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * SESSION_START / SESSION_END 事件数据 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SessionEventData { + + /** + * PodGroup 名称 + */ + @JsonProperty("pod_group_name") + private String podGroupName; + + /** + * Pod 数量 + */ + @JsonProperty("pod_count") + private Integer podCount; + + /** + * 是否优雅停止(SESSION_END 时填充) + */ + private Boolean graceful; + + /** + * 队列名称 + */ + @JsonProperty("queue_name") + private String queueName; + + /** + * 节点名称 + */ + @JsonProperty("node_name") + private String nodeName; +} diff --git a/back/src/main/java/com/linkwork/model/enums/ConflictPolicy.java b/back/src/main/java/com/linkwork/model/enums/ConflictPolicy.java new file mode 100644 index 0000000..660c5db --- /dev/null +++ b/back/src/main/java/com/linkwork/model/enums/ConflictPolicy.java @@ -0,0 +1,20 @@ +package com.linkwork.model.enums; + +import java.util.Locale; + +public enum ConflictPolicy { + REJECT, + OVERWRITE, + RENAME; + + public static ConflictPolicy fromString(String value) { + if (value == null || value.isBlank()) { + return REJECT; + } + try { + return valueOf(value.toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException e) { + return REJECT; + } + } +} diff --git a/back/src/main/java/com/linkwork/model/enums/ContainerEventType.java b/back/src/main/java/com/linkwork/model/enums/ContainerEventType.java new file mode 100644 index 0000000..ae08b7e --- /dev/null +++ b/back/src/main/java/com/linkwork/model/enums/ContainerEventType.java @@ -0,0 +1,117 @@ +package com.linkwork.model.enums; + +/** + * 容器日志事件类型枚举 + * 遵循 data-format.md 规范定义 + */ +public enum ContainerEventType { + + // ==================== 调度阶段 ==================== + + /** + * Pod 正在等待调度 + */ + POD_SCHEDULING, + + /** + * Pod 已调度到节点 + */ + POD_SCHEDULED, + + // ==================== 镜像阶段 ==================== + + /** + * 正在拉取容器镜像 + */ + IMAGE_PULLING, + + /** + * 镜像拉取完成 + */ + IMAGE_PULLED, + + // ==================== 启动阶段 ==================== + + /** + * 容器正在启动 + */ + CONTAINER_STARTING, + + /** + * 容器就绪(健康检查通过) + */ + CONTAINER_READY, + + // ==================== 环境阶段 ==================== + + /** + * 环境初始化步骤 + */ + ENV_SETUP, + + /** + * 工作区初始化 + */ + WORKSPACE_INIT, + + // ==================== 完成阶段 ==================== + + /** + * 全部初始化完成 + */ + INIT_COMPLETE, + + /** + * 初始化失败 + */ + INIT_FAILED, + + // ==================== 生命周期事件 ==================== + + /** + * 会话开始(Agent 启动) + */ + SESSION_START, + + /** + * 会话结束(服务停止) + */ + SESSION_END, + + // ==================== 镜像构建阶段 ==================== + + /** + * 构建开始 + */ + BUILD_STARTED, + + /** + * 构建进度(阶段性状态) + */ + BUILD_PROGRESS, + + /** + * 构建日志输出(Docker 实时日志行) + */ + BUILD_LOG, + + /** + * 构建成功完成 + */ + BUILD_COMPLETED, + + /** + * 构建失败 + */ + BUILD_FAILED, + + /** + * 正在推送镜像到仓库 + */ + BUILD_PUSHING, + + /** + * 镜像推送完成 + */ + BUILD_PUSHED +} diff --git a/back/src/main/java/com/linkwork/model/enums/DeployMode.java b/back/src/main/java/com/linkwork/model/enums/DeployMode.java new file mode 100644 index 0000000..0cf1cd7 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/enums/DeployMode.java @@ -0,0 +1,9 @@ +package com.linkwork.model.enums; + +/** + * 部署模式 + */ +public enum DeployMode { + K8S, // Kubernetes 部署 + COMPOSE // Docker Compose 部署(仅支持 Alone 模式) +} diff --git a/back/src/main/java/com/linkwork/model/enums/PodMode.java b/back/src/main/java/com/linkwork/model/enums/PodMode.java new file mode 100644 index 0000000..c496f28 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/enums/PodMode.java @@ -0,0 +1,9 @@ +package com.linkwork.model.enums; + +/** + * Pod 模式 + */ +public enum PodMode { + SIDECAR, // 双容器:Agent + Runner + ALONE // 单容器:三合一 +} diff --git a/back/src/main/java/com/linkwork/model/enums/ResumeType.java b/back/src/main/java/com/linkwork/model/enums/ResumeType.java new file mode 100644 index 0000000..bc6345f --- /dev/null +++ b/back/src/main/java/com/linkwork/model/enums/ResumeType.java @@ -0,0 +1,16 @@ +package com.linkwork.model.enums; + +/** + * 恢复类型 + */ +public enum ResumeType { + /** + * 温启动:命中镜像缓存,~20s + */ + WARM, + + /** + * 冷启动:未命中,需重新拉取镜像,~90s + */ + COLD +} diff --git a/back/src/main/java/com/linkwork/model/enums/ServiceType.java b/back/src/main/java/com/linkwork/model/enums/ServiceType.java new file mode 100644 index 0000000..5bd3613 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/enums/ServiceType.java @@ -0,0 +1,9 @@ +package com.linkwork.model.enums; + +/** + * 服务类型 + */ +public enum ServiceType { + CODE_WRITING, // 代码编写 + TASK_EXECUTION // 任务执行 +} diff --git a/back/src/main/java/com/linkwork/model/enums/SnapshotStatus.java b/back/src/main/java/com/linkwork/model/enums/SnapshotStatus.java new file mode 100644 index 0000000..163d766 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/enums/SnapshotStatus.java @@ -0,0 +1,16 @@ +package com.linkwork.model.enums; + +/** + * 快照状态 + */ +public enum SnapshotStatus { + /** + * 可用于恢复 + */ + ACTIVE, + + /** + * 已过期 + */ + EXPIRED +} diff --git a/back/src/main/java/com/linkwork/model/enums/TaskOutputType.java b/back/src/main/java/com/linkwork/model/enums/TaskOutputType.java new file mode 100644 index 0000000..d3e35a3 --- /dev/null +++ b/back/src/main/java/com/linkwork/model/enums/TaskOutputType.java @@ -0,0 +1,133 @@ +package com.linkwork.model.enums; + +import lombok.Getter; + +import java.util.Locale; +import java.util.Optional; + +/** + * 任务产出预估类型枚举 + */ +@Getter +public enum TaskOutputType { + GIT_BRANCH("git_branch", "code_delivery", "代码改动预计会落到 Git 分支"), + PULL_REQUEST("pull_request", "code_delivery", "预计会产出 PR/MR 链接"), + PYTHON_FILE("python_file", "code_delivery", "预计产出 Python 源码文件"), + JAVA_FILE("java_file", "code_delivery", "预计产出 Java 源码文件"), + JAVASCRIPT_FILE("javascript_file", "code_delivery", "预计产出 JavaScript 源码文件"), + TYPESCRIPT_FILE("typescript_file", "code_delivery", "预计产出 TypeScript 源码文件"), + SQL_FILE("sql_file", "code_delivery", "预计产出 SQL 脚本"), + SHELL_SCRIPT("shell_script", "code_delivery", "预计产出 Shell 脚本"), + CONFIG_FILE("config_file", "code_delivery", "预计产出配置文件"), + + TXT("txt", "document_delivery", "预计产出纯文本文件"), + MARKDOWN("markdown", "document_delivery", "预计产出 Markdown 文档"), + WORD("word", "document_delivery", "预计产出 Word 文档"), + EXCEL("excel", "document_delivery", "预计产出 Excel 表格"), + PPT("ppt", "document_delivery", "预计产出 PPT 文档"), + PDF("pdf", "document_delivery", "预计产出 PDF 文档"), + + JSON("json", "data_media_delivery", "预计产出 JSON 数据文件"), + CSV("csv", "data_media_delivery", "预计产出 CSV 数据文件"), + PNG("png", "data_media_delivery", "预计产出图片文件"), + + ARCHIVE("archive", "package_delivery", "预计产出压缩包"), + API_CALL_RESULT("api_call_result", "integration_delivery", "仅通过 API 调用产生结果"), + DIALOG_CONCLUSION("dialog_conclusion", "dialog_delivery", "无文件产出,仅对话结论"); + + private final String code; + private final String domain; + private final String description; + + TaskOutputType(String code, String domain, String description) { + this.code = code; + this.domain = domain; + this.description = description; + } + + public static Optional fromCode(String code) { + if (code == null || code.isBlank()) { + return Optional.empty(); + } + for (TaskOutputType type : values()) { + if (type.code.equalsIgnoreCase(code)) { + return Optional.of(type); + } + } + return Optional.empty(); + } + + public static Optional fromFileName(String fileName) { + if (fileName == null || fileName.isBlank()) { + return Optional.empty(); + } + + String lower = fileName.toLowerCase(Locale.ROOT); + + if (hasAnySuffix(lower, ".zip", ".tar", ".tar.gz", ".tgz", ".7z", ".rar")) { + return Optional.of(ARCHIVE); + } + + if (hasAnySuffix(lower, ".xlsx", ".xls")) { + return Optional.of(EXCEL); + } + if (hasAnySuffix(lower, ".csv")) { + return Optional.of(CSV); + } + if (hasAnySuffix(lower, ".docx", ".doc")) { + return Optional.of(WORD); + } + if (hasAnySuffix(lower, ".ppt", ".pptx")) { + return Optional.of(PPT); + } + if (hasAnySuffix(lower, ".pdf")) { + return Optional.of(PDF); + } + if (hasAnySuffix(lower, ".md", ".markdown")) { + return Optional.of(MARKDOWN); + } + if (hasAnySuffix(lower, ".txt", ".log")) { + return Optional.of(TXT); + } + + if (hasAnySuffix(lower, ".py")) { + return Optional.of(PYTHON_FILE); + } + if (hasAnySuffix(lower, ".java")) { + return Optional.of(JAVA_FILE); + } + if (hasAnySuffix(lower, ".ts", ".tsx")) { + return Optional.of(TYPESCRIPT_FILE); + } + if (hasAnySuffix(lower, ".js", ".jsx", ".mjs", ".cjs")) { + return Optional.of(JAVASCRIPT_FILE); + } + if (hasAnySuffix(lower, ".sql")) { + return Optional.of(SQL_FILE); + } + if (hasAnySuffix(lower, ".sh", ".bash", ".zsh")) { + return Optional.of(SHELL_SCRIPT); + } + + if (hasAnySuffix(lower, ".yaml", ".yml", ".toml", ".ini", ".properties", ".env", ".conf", ".xml")) { + return Optional.of(CONFIG_FILE); + } + if (hasAnySuffix(lower, ".json")) { + return Optional.of(JSON); + } + if (hasAnySuffix(lower, ".png", ".jpg", ".jpeg", ".svg", ".webp")) { + return Optional.of(PNG); + } + + return Optional.empty(); + } + + private static boolean hasAnySuffix(String value, String... suffixes) { + for (String suffix : suffixes) { + if (value.endsWith(suffix)) { + return true; + } + } + return false; + } +} diff --git a/back/src/main/java/com/linkwork/model/enums/TaskStatus.java b/back/src/main/java/com/linkwork/model/enums/TaskStatus.java new file mode 100644 index 0000000..d2480fe --- /dev/null +++ b/back/src/main/java/com/linkwork/model/enums/TaskStatus.java @@ -0,0 +1,28 @@ +package com.linkwork.model.enums; + +import com.baomidou.mybatisplus.annotation.EnumValue; +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.Getter; + +/** + * 任务状态枚举 + */ +@Getter +public enum TaskStatus { + PENDING("pending", "等待执行"), + RUNNING("running", "执行中"), + COMPLETED("completed", "已完成"), + FAILED("failed", "执行失败"), + ABORTED("aborted", "已终止"), + PENDING_AUTH("pending_auth", "等待人工授权"); + + @EnumValue + @JsonValue + private final String code; + private final String description; + + TaskStatus(String code, String description) { + this.code = code; + this.description = description; + } +} diff --git a/back/src/main/java/com/linkwork/service/AdminAccessService.java b/back/src/main/java/com/linkwork/service/AdminAccessService.java new file mode 100644 index 0000000..b8af703 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/AdminAccessService.java @@ -0,0 +1,33 @@ +package com.linkwork.service; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 管理员访问能力判断(统一维护,避免各模块配置漂移) + */ +@Service +public class AdminAccessService { + + @Value("${robot.admin-user-ids:}") + private String adminUserIdsConfig; + + public boolean isAdmin(String userId) { + return StringUtils.hasText(userId) && getAdminUserIds().contains(userId.trim()); + } + + private Set getAdminUserIds() { + if (!StringUtils.hasText(adminUserIdsConfig)) { + return Set.of(); + } + return Arrays.stream(adminUserIdsConfig.split(",")) + .map(String::trim) + .filter(StringUtils::hasText) + .collect(Collectors.toSet()); + } +} diff --git a/back/src/main/java/com/linkwork/service/ApprovalRequestConsumer.java b/back/src/main/java/com/linkwork/service/ApprovalRequestConsumer.java new file mode 100644 index 0000000..3b7d351 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/ApprovalRequestConsumer.java @@ -0,0 +1,269 @@ +package com.linkwork.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.linkwork.config.DispatchConfig; +import com.linkwork.model.entity.Approval; +import jakarta.annotation.PreDestroy; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.data.redis.connection.stream.StreamRecords; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Component; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * 审批请求消费者 + *

+ * 监听 momo-worker 写入的审批请求队列 (Redis List), + * 自动创建审批记录到数据库。 + *

+ * 队列 Key: approval:{workstationId} + * 消息格式: + *

+ * {
+ *   "request_id": "req-uuid",
+ *   "task_id": "test-task-001",
+ *   "tool_name": "Bash",
+ *   "command": "sudo whoami",
+ *   "risk_level": "high",
+ *   "timestamp": "2026-02-10T10:00:00Z"
+ * }
+ * 
+ */ +@Slf4j +@Component +@RequiredArgsConstructor +public class ApprovalRequestConsumer { + + private final StringRedisTemplate redisTemplate; + private final DispatchConfig dispatchConfig; + private final ApprovalService approvalService; + private final ObjectMapper objectMapper; + private final TaskService taskService; + + private final AtomicBoolean running = new AtomicBoolean(false); + private final ExecutorService executor = Executors.newSingleThreadExecutor(r -> { + Thread t = new Thread(r, "approval-consumer"); + t.setDaemon(true); + return t; + }); + + @EventListener(ApplicationReadyEvent.class) + public void onApplicationReady() { + running.set(true); + executor.submit(this::consumeLoop); + log.info("审批请求消费者已启动,监听队列模式: {}", dispatchConfig.getApprovalRequestKeyPattern()); + } + + @PreDestroy + public void shutdown() { + running.set(false); + executor.shutdownNow(); + log.info("审批请求消费者已停止"); + } + + private void consumeLoop() { + while (running.get() && !Thread.currentThread().isInterrupted()) { + try { + boolean consumed = false; + for (String queueKey : resolveApprovalRequestQueues()) { + String message = redisTemplate.opsForList().rightPop(queueKey); + if (message == null) { + continue; + } + processApprovalRequest(message); + consumed = true; + break; + } + if (!consumed) { + Thread.sleep(500); + } + } catch (Exception e) { + if (e instanceof InterruptedException) { + Thread.currentThread().interrupt(); + break; + } + if (running.get()) { + log.error("消费审批请求异常,5秒后重试", e); + try { Thread.sleep(5000); } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + break; + } + } + } + } + } + + private void processApprovalRequest(String message) { + try { + JsonNode json = objectMapper.readTree(message); + + String requestId = json.has("request_id") ? json.get("request_id").asText() : null; + String taskId = json.has("task_id") ? json.get("task_id").asText() : null; + String command = json.has("command") ? json.get("command").asText() : ""; + String reason = json.has("reason") ? json.get("reason").asText() : ""; + + // tool_name 优先,否则从 command_type 推导 + String toolName = json.has("tool_name") ? json.get("tool_name").asText() : null; + if (toolName == null || toolName.isEmpty()) { + String commandType = json.has("command_type") ? json.get("command_type").asText() : ""; + toolName = mapCommandTypeToToolName(commandType); + } + + // risk_level: 兼容整数 (momo-worker) 和字符串两种格式 + String riskLevel = mapRiskLevel(json.get("risk_level")); + + if (requestId == null) { + log.warn("审批请求缺少 request_id,跳过: {}", message); + return; + } + + log.info("收到审批请求: requestId={}, taskId={}, toolName={}, command={}, riskLevel={}", + requestId, taskId, toolName, command, riskLevel); + + // 构建操作描述 + String action = String.format("%s: %s", toolName, command); + String description = (reason != null && !reason.isEmpty()) + ? reason + : String.format("momo-worker 请求审批: %s", action); + + // 创建审批记录(通过 ApprovalService) + Approval approval = approvalService.createApproval( + taskId, // taskNo + "任务 " + taskId, // taskTitle + action, // action + description, // description + riskLevel, // riskLevel + "momo-worker", // creatorId + "AI 执行器" // creatorName + ); + + // 保存 requestId 到审批记录(用于后续回写响应) + approval.setRequestId(requestId); + approvalService.updateRequestId(approval.getApprovalNo(), requestId); + + log.info("审批记录已创建: approvalNo={}, requestId={}", approval.getApprovalNo(), requestId); + + // 向任务日志 Stream 写入 USER_CONFIRM_REQUEST 事件,驱动 WebSocket 通知前端 + publishApprovalEvent(taskId, approval, requestId, command, riskLevel, reason); + + } catch (Exception e) { + log.error("处理审批请求失败: {}", message, e); + } + } + + /** + * risk_level 映射:兼容整数 (momo-worker) 和字符串格式 + */ + private String mapRiskLevel(JsonNode node) { + if (node == null || node.isNull()) return "medium"; + if (node.isTextual()) { + String text = node.asText().toLowerCase(); + if (text.matches("low|medium|high|critical")) return text; + } + if (node.isNumber()) { + return switch (node.asInt()) { + case 1 -> "low"; + case 2 -> "medium"; + case 3 -> "high"; + case 4 -> "critical"; + default -> "high"; // 0 或未知 → high(策略触发的审批默认高风险) + }; + } + return "medium"; + } + + /** + * command_type → tool_name 映射 + */ + private String mapCommandTypeToToolName(String commandType) { + if (commandType == null || commandType.isEmpty()) return "Shell"; + return switch (commandType.toLowerCase()) { + case "shell", "bash" -> "Bash"; + case "python" -> "Python"; + default -> commandType; + }; + } + + /** + * 向任务日志 Stream 写入 USER_CONFIRM_REQUEST 事件 + * 让 TaskWebSocketHandler 能推送到前端 + */ + private void publishApprovalEvent(String taskId, Approval approval, + String requestId, String command, + String riskLevel, String reason) { + try { + Long roleId = resolveRoleIdByTaskNo(taskId); + String streamKey = dispatchConfig.getLogStreamKey(roleId, taskId); + + Map eventData = new LinkedHashMap<>(); + eventData.put("approval_no", approval.getApprovalNo()); + eventData.put("request_id", requestId); + eventData.put("task_id", taskId); + eventData.put("command", command); + eventData.put("risk_level", riskLevel); + eventData.put("reason", reason); + eventData.put("expired_at", approval.getExpiredAt() != null + ? approval.getExpiredAt().toString() : ""); + + Map fields = new LinkedHashMap<>(); + fields.put("event_type", "USER_CONFIRM_REQUEST"); + fields.put("timestamp", Instant.now().toString()); + fields.put("session_id", "backend"); + fields.put("data", objectMapper.writeValueAsString(eventData)); + + redisTemplate.opsForStream().add( + StreamRecords.string(fields).withStreamKey(streamKey)); + + log.info("USER_CONFIRM_REQUEST 事件已写入 Stream: key={}, approvalNo={}", + streamKey, approval.getApprovalNo()); + + } catch (Exception e) { + log.error("写入 USER_CONFIRM_REQUEST 事件失败(不影响审批创建): {}", e.getMessage()); + } + } + + private List resolveApprovalRequestQueues() { + Set keySet = redisTemplate.keys(dispatchConfig.getApprovalRequestKeyPattern()); + if (keySet == null || keySet.isEmpty()) { + return List.of(dispatchConfig.getApprovalRequestKey()); + } + List queues = new ArrayList<>(); + for (String key : keySet) { + if (key == null || key.contains(":response:")) { + continue; + } + queues.add(key); + } + if (queues.isEmpty()) { + queues.add(dispatchConfig.getApprovalRequestKey()); + } + Collections.sort(queues); + return queues; + } + + private Long resolveRoleIdByTaskNo(String taskNo) { + if (taskNo == null || taskNo.isEmpty()) { + return null; + } + try { + return taskService.getTaskByNo(taskNo).getRoleId(); + } catch (Exception e) { + log.debug("审批请求链路解析任务 roleId 失败,回退默认 workstation: taskNo={}", taskNo); + return null; + } + } +} diff --git a/back/src/main/java/com/linkwork/service/ApprovalService.java b/back/src/main/java/com/linkwork/service/ApprovalService.java new file mode 100644 index 0000000..c072bd1 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/ApprovalService.java @@ -0,0 +1,295 @@ +package com.linkwork.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.linkwork.common.SnowflakeIdGenerator; +import com.linkwork.config.DispatchConfig; +import com.linkwork.mapper.ApprovalMapper; +import com.linkwork.model.entity.Approval; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.util.*; + +import org.springframework.data.redis.connection.stream.StreamRecords; + +/** + * 审批服务 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class ApprovalService { + + private final ApprovalMapper approvalMapper; + private final StringRedisTemplate redisTemplate; + private final SnowflakeIdGenerator idGenerator; + private final DispatchConfig dispatchConfig; + private final ObjectMapper objectMapper; + private final TaskService taskService; + + /** + * 获取审批列表(分页) + */ + public Page listApprovals(String status, Integer page, Integer pageSize) { + return listApprovals(status, page, pageSize, null); + } + + /** + * 获取审批列表(分页,按创建人隔离) + */ + public Page listApprovals(String status, Integer page, Integer pageSize, String creatorId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (StringUtils.hasText(creatorId)) { + wrapper.eq(Approval::getCreatorId, creatorId); + } + if (StringUtils.hasText(status) && !"all".equalsIgnoreCase(status)) { + wrapper.eq(Approval::getStatus, status); + } + wrapper.orderByDesc(Approval::getCreatedAt); + return approvalMapper.selectPage(new Page<>(page, pageSize), wrapper); + } + + /** + * 获取审批统计 + */ + public Map getStats() { + return getStats(null); + } + + /** + * 获取审批统计(按创建人隔离) + */ + public Map getStats(String creatorId) { + Map stats = new LinkedHashMap<>(); + + LambdaQueryWrapper pendingWrapper = new LambdaQueryWrapper<>(); + if (StringUtils.hasText(creatorId)) { + pendingWrapper.eq(Approval::getCreatorId, creatorId); + } + pendingWrapper.eq(Approval::getStatus, "pending"); + stats.put("pending", approvalMapper.selectCount(pendingWrapper)); + + LambdaQueryWrapper approvedWrapper = new LambdaQueryWrapper<>(); + if (StringUtils.hasText(creatorId)) { + approvedWrapper.eq(Approval::getCreatorId, creatorId); + } + approvedWrapper.eq(Approval::getStatus, "approved"); + stats.put("approved", approvalMapper.selectCount(approvedWrapper)); + + LambdaQueryWrapper rejectedWrapper = new LambdaQueryWrapper<>(); + if (StringUtils.hasText(creatorId)) { + rejectedWrapper.eq(Approval::getCreatorId, creatorId); + } + rejectedWrapper.eq(Approval::getStatus, "rejected"); + stats.put("rejected", approvalMapper.selectCount(rejectedWrapper)); + + LambdaQueryWrapper totalWrapper = new LambdaQueryWrapper<>(); + if (StringUtils.hasText(creatorId)) { + totalWrapper.eq(Approval::getCreatorId, creatorId); + } + stats.put("total", approvalMapper.selectCount(totalWrapper)); + return stats; + } + + /** + * 提交审批决策 + */ + @Transactional + public Approval decide(String approvalNo, String decision, String comment, + String operatorId, String operatorName, String operatorIp) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(Approval::getApprovalNo, approvalNo); + Approval approval = approvalMapper.selectOne(wrapper); + + if (approval == null) { + throw new IllegalArgumentException("审批记录不存在: " + approvalNo); + } + if (!"pending".equals(approval.getStatus())) { + throw new IllegalArgumentException("审批已处理,当前状态: " + approval.getStatus()); + } + if (!"approved".equals(decision) && !"rejected".equals(decision)) { + throw new IllegalArgumentException("无效的决策: " + decision + ",允许值: approved/rejected"); + } + + approval.setStatus(decision); + approval.setDecision(decision); + approval.setComment(comment); + approval.setOperatorId(operatorId); + approval.setOperatorName(operatorName); + approval.setOperatorIp(operatorIp); + approval.setDecidedAt(LocalDateTime.now()); + approval.setUpdatedAt(LocalDateTime.now()); + approvalMapper.updateById(approval); + + // 通知 momo-worker:通过 Redis String 回写审批结果 + // key: approval:{workstationId}:response:{requestId} (String, TTL 120s) + String requestId = approval.getRequestId(); + if (StringUtils.hasText(requestId)) { + try { + Long roleId = resolveRoleIdByTaskNo(approval.getTaskNo()); + String responseKey = dispatchConfig.getApprovalResponseKey(roleId, requestId); + Map response = new LinkedHashMap<>(); + response.put("request_id", requestId); + response.put("status", decision); + if ("approved".equals(decision)) { + response.put("approved_by", operatorName != null ? operatorName : "system"); + } else { + response.put("rejected_by", operatorName != null ? operatorName : "system"); + } + response.put("comment", comment != null ? comment : ""); + response.put("responded_at", Instant.now().toString()); + String responseJson = objectMapper.writeValueAsString(response); + redisTemplate.opsForValue().set(responseKey, responseJson, Duration.ofSeconds(120)); + log.info("审批结果已回写 Redis (String, TTL=120s): key={}, decision={}", responseKey, decision); + } catch (Exception e) { + log.error("审批结果回写 Redis 失败: requestId={}", requestId, e); + } + } + + // 向任务日志 Stream 写入 USER_CONFIRM_RESOLVED 事件,通知前端审批已处理 + publishResolvedEvent(approval); + + // 兼容旧模式:通过 Redis Pub/Sub 发送审批结果 + if ("approved".equals(decision) && StringUtils.hasText(approval.getTaskNo())) { + String channel = "approval:" + approval.getTaskNo(); + redisTemplate.convertAndSend(channel, "approved:" + approvalNo); + log.info("审批通过,已通知 Agent (Pub/Sub): taskNo={}, approvalNo={}", approval.getTaskNo(), approvalNo); + } + + log.info("审批决策完成: approvalNo={}, decision={}, operator={}, operatorIp={}", + approvalNo, decision, operatorName, operatorIp); + return approval; + } + + /** + * 创建审批请求(由 Agent/Worker 触发) + */ + @Transactional + public Approval createApproval(String taskNo, String taskTitle, String action, + String description, String riskLevel, + String creatorId, String creatorName) { + String approvalNo = "AUTH-" + idGenerator.nextId(); + + Approval approval = new Approval(); + approval.setApprovalNo(approvalNo); + approval.setTaskNo(taskNo); + approval.setTaskTitle(taskTitle); + approval.setAction(action); + approval.setDescription(description); + approval.setRiskLevel(riskLevel != null ? riskLevel : "medium"); + approval.setStatus("pending"); + approval.setCreatorId(creatorId); + approval.setCreatorName(creatorName); + // 默认 30 分钟过期 + approval.setExpiredAt(LocalDateTime.now().plusMinutes(30)); + approval.setCreatedAt(LocalDateTime.now()); + approval.setUpdatedAt(LocalDateTime.now()); + approval.setIsDeleted(0); + approvalMapper.insert(approval); + + log.info("审批请求创建: approvalNo={}, taskNo={}, action={}, riskLevel={}", + approvalNo, taskNo, action, riskLevel); + + return approval; + } + + /** + * 更新审批记录的 requestId(momo-worker 审批请求 ID) + */ + public void updateRequestId(String approvalNo, String requestId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(Approval::getApprovalNo, approvalNo); + Approval approval = approvalMapper.selectOne(wrapper); + if (approval != null) { + approval.setRequestId(requestId); + approvalMapper.updateById(approval); + } + } + + /** + * 转换为响应格式 + */ + public Map toResponse(Approval approval) { + Map map = new LinkedHashMap<>(); + map.put("id", approval.getApprovalNo()); + map.put("taskNo", approval.getTaskNo()); + map.put("taskTitle", approval.getTaskTitle()); + map.put("action", approval.getAction()); + map.put("description", approval.getDescription()); + map.put("riskLevel", approval.getRiskLevel()); + map.put("status", approval.getStatus()); + map.put("decision", approval.getDecision()); + map.put("comment", approval.getComment()); + map.put("operatorName", approval.getOperatorName()); + map.put("expiredAt", approval.getExpiredAt()); + map.put("decidedAt", approval.getDecidedAt()); + map.put("creatorName", approval.getCreatorName()); + map.put("createdAt", approval.getCreatedAt()); + return map; + } + + public List> toResponseList(List approvals) { + List> list = new ArrayList<>(); + for (Approval approval : approvals) { + list.add(toResponse(approval)); + } + return list; + } + + /** + * 向任务日志 Stream 写入 USER_CONFIRM_RESOLVED 事件 + * 让 WebSocket 能推送审批结果到前端任务执行面板 + */ + private void publishResolvedEvent(Approval approval) { + String taskNo = approval.getTaskNo(); + if (taskNo == null || taskNo.isEmpty()) return; + + try { + Long roleId = resolveRoleIdByTaskNo(taskNo); + String streamKey = dispatchConfig.getLogStreamKey(roleId, taskNo); + + Map eventData = new LinkedHashMap<>(); + eventData.put("approval_no", approval.getApprovalNo()); + eventData.put("request_id", approval.getRequestId()); + eventData.put("task_id", taskNo); + eventData.put("decision", approval.getDecision()); + eventData.put("operator", approval.getOperatorName()); + eventData.put("comment", approval.getComment()); + + Map fields = new LinkedHashMap<>(); + fields.put("event_type", "USER_CONFIRM_RESOLVED"); + fields.put("timestamp", Instant.now().toString()); + fields.put("session_id", "backend"); + fields.put("data", objectMapper.writeValueAsString(eventData)); + + redisTemplate.opsForStream().add( + StreamRecords.string(fields).withStreamKey(streamKey)); + + log.info("USER_CONFIRM_RESOLVED 事件已写入 Stream: key={}, decision={}", + streamKey, approval.getDecision()); + } catch (Exception e) { + log.error("写入 USER_CONFIRM_RESOLVED 事件失败: {}", e.getMessage()); + } + } + + private Long resolveRoleIdByTaskNo(String taskNo) { + if (!StringUtils.hasText(taskNo)) { + return null; + } + try { + return taskService.getTaskByNo(taskNo).getRoleId(); + } catch (Exception e) { + log.debug("审批链路解析任务 roleId 失败,回退默认 workstation: taskNo={}", taskNo); + return null; + } + } +} diff --git a/back/src/main/java/com/linkwork/service/AuthService.java b/back/src/main/java/com/linkwork/service/AuthService.java new file mode 100644 index 0000000..fe1622f --- /dev/null +++ b/back/src/main/java/com/linkwork/service/AuthService.java @@ -0,0 +1,168 @@ +package com.linkwork.service; + +import com.linkwork.context.UserInfo; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; + +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.util.*; + +/** + * 认证服务 + * 处理密码验证和 JWT Token 生成/验证 + */ +@Slf4j +@Service +public class AuthService { + + private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + + @Value("${robot.auth.password:}") + private String configuredPasswordHash; + + @Value("${robot.auth.jwt-secret:}") + private String jwtSecret; + + @Value("${robot.auth.jwt-expiration:86400000}") + private long jwtExpiration; // 默认 24 小时 + + private SecretKey secretKey; + + @PostConstruct + public void init() { + if (jwtSecret == null || jwtSecret.isBlank()) { + throw new IllegalStateException("robot.auth.jwt-secret 未配置"); + } + + if (configuredPasswordHash == null || configuredPasswordHash.isBlank()) { + throw new IllegalStateException("robot.auth.password 未配置(需提供 BCrypt 哈希)"); + } + + // 初始化 JWT 密钥 + secretKey = Keys.hmacShaKeyFor(jwtSecret.getBytes(StandardCharsets.UTF_8)); + } + + /** + * 验证密码 + */ + public boolean validatePassword(String rawPassword) { + return passwordEncoder.matches(rawPassword, configuredPasswordHash); + } + + /** + * 生成 JWT Token + */ + public String generateToken(String subject) { + Date now = new Date(); + Date expiry = new Date(now.getTime() + jwtExpiration); + + return Jwts.builder() + .subject(subject) + .issuedAt(now) + .expiration(expiry) + .signWith(secretKey) + .compact(); + } + + /** + * 验证 JWT Token + */ + public boolean validateToken(String token) { + try { + Jwts.parser() + .verifyWith(secretKey) + .build() + .parseSignedClaims(token); + return true; + } catch (Exception e) { + log.debug("Token 验证失败: {}", e.getMessage()); + return false; + } + } + + /** + * 从 Token 获取主题 + */ + public String getSubjectFromToken(String token) { + Claims claims = Jwts.parser() + .verifyWith(secretKey) + .build() + .parseSignedClaims(token) + .getPayload(); + return claims.getSubject(); + } + + /** + * 工具方法:生成密码的 BCrypt 哈希(用于配置新密码) + */ + public String encodePassword(String rawPassword) { + return passwordEncoder.encode(rawPassword); + } + + /** + * 基于用户信息生成 JWT Token + * JWT payload 承载完整用户信息,后续请求直接从 JWT 解析,不查库 + */ + @SuppressWarnings("unchecked") + public String generateTokenForUser(UserInfo userInfo) { + Date now = new Date(); + Date expiry = new Date(now.getTime() + jwtExpiration); + + Map claims = new HashMap<>(); + claims.put("name", userInfo.getName()); + if (userInfo.getEmail() != null) { + claims.put("email", userInfo.getEmail()); + } + if (userInfo.getWorkId() != null) { + claims.put("workId", userInfo.getWorkId()); + } + if (userInfo.getAvatarUrl() != null) { + claims.put("avatarUrl", userInfo.getAvatarUrl()); + } + if (userInfo.getPermissions() != null) { + claims.put("permissions", userInfo.getPermissions()); + } + + return Jwts.builder() + .subject(userInfo.getUserId()) + .claims(claims) + .issuedAt(now) + .expiration(expiry) + .signWith(secretKey) + .compact(); + } + + /** + * 从 JWT Token 解析完整用户信息 + */ + @SuppressWarnings("unchecked") + public UserInfo getUserInfoFromToken(String token) { + Claims claims = Jwts.parser() + .verifyWith(secretKey) + .build() + .parseSignedClaims(token) + .getPayload(); + + List permissions = null; + Object permObj = claims.get("permissions"); + if (permObj instanceof List) { + permissions = (List) permObj; + } + + return UserInfo.builder() + .userId(claims.getSubject()) + .name(claims.get("name", String.class)) + .email(claims.get("email", String.class)) + .workId(claims.get("workId", String.class)) + .avatarUrl(claims.get("avatarUrl", String.class)) + .permissions(permissions) + .build(); + } +} diff --git a/back/src/main/java/com/linkwork/service/BuildExecutor.java b/back/src/main/java/com/linkwork/service/BuildExecutor.java new file mode 100644 index 0000000..0ba2364 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/BuildExecutor.java @@ -0,0 +1,211 @@ +package com.linkwork.service; + +import com.linkwork.config.ImageBuildConfig; +import com.linkwork.model.dto.BuildTask; +import com.linkwork.model.dto.ImageBuildResult; +import com.linkwork.model.dto.MergedConfig; +import com.linkwork.model.dto.ServiceBuildRequest; +import com.linkwork.model.dto.ServiceBuildResult; +import com.linkwork.model.enums.DeployMode; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +/** + * 构建执行器 + * + * 封装实际的构建逻辑,由 BuildQueueService 调用 + */ +@Component +@Slf4j +public class BuildExecutor { + + private final ImageBuildService imageBuildService; + private final ImageBuildConfig imageBuildConfig; + private final K8sOrchestrator orchestrator; + private final BuildRecordService buildRecordService; + private final ScheduleEventPublisher eventPublisher; + private final BuildLogBuffer buildLogBuffer; + + public BuildExecutor(ImageBuildService imageBuildService, + ImageBuildConfig imageBuildConfig, + K8sOrchestrator orchestrator, + BuildRecordService buildRecordService, + ScheduleEventPublisher eventPublisher, + BuildLogBuffer buildLogBuffer) { + this.imageBuildService = imageBuildService; + this.imageBuildConfig = imageBuildConfig; + this.orchestrator = orchestrator; + this.buildRecordService = buildRecordService; + this.eventPublisher = eventPublisher; + this.buildLogBuffer = buildLogBuffer; + } + + /** + * 执行构建任务 + * + * @param task 构建任务 + * @return 构建结果 + */ + public ServiceBuildResult execute(BuildTask task) { + ServiceBuildRequest request = task.getRequest(); + MergedConfig config = task.getConfig(); + String serviceId = request.getServiceId(); + String buildId = request.getBuildId(); + Long roleId = request.getRoleId(); + String roleName = request.getRoleName(); + long startTime = System.currentTimeMillis(); + + try { + log.info("开始执行构建: serviceId={}, buildId={}", serviceId, buildId); + addBuildLog(buildId, "INFO", "========== 开始构建 =========="); + addBuildLog(buildId, "INFO", "服务ID: " + serviceId); + addBuildLog(buildId, "INFO", "构建ID: " + buildId); + + // 更新构建记录状态为 BUILDING + if (roleId != null && StringUtils.hasText(buildId)) { + buildRecordService.markBuilding(buildId); + } + + // 1. 镜像构建(如果启用,仅构建 Agent 镜像) + if (imageBuildConfig.isEnabled() && request.getDeployMode() == DeployMode.K8S) { + log.info("镜像构建已启用,开始构建: serviceId={}", serviceId); + addBuildLog(buildId, "INFO", "========== 镜像构建阶段 =========="); + + // 发布 BUILD_STARTED 事件 + if (StringUtils.hasText(buildId)) { + eventPublisher.publishBuildStarted(buildId, buildId, roleId, roleName, + config.getAgentImage()); + eventPublisher.publishBuildProgress(buildId, buildId, "dockerfile", + "Generating Dockerfile..."); + } + + ImageBuildResult buildResult = imageBuildService.buildImages(request); + + if (!buildResult.isSuccess()) { + log.error("镜像构建失败: serviceId={}, error={}", serviceId, buildResult.getErrorMessage()); + addBuildLog(buildId, "ERROR", "镜像构建失败: " + buildResult.getErrorMessage()); + + // 发布 BUILD_FAILED 事件并更新记录 + long durationMs = System.currentTimeMillis() - startTime; + finishBuild(buildId, false, durationMs); + if (StringUtils.hasText(buildId)) { + eventPublisher.publishBuildFailed(buildId, buildId, "BUILD_ERROR", + buildResult.getErrorMessage(), durationMs); + if (roleId != null) { + buildRecordService.markFailed(buildId, buildResult.getErrorMessage(), durationMs); + } + } + return ServiceBuildResult.failed(serviceId, "BUILD_ERROR", buildResult.getErrorMessage()); + } + + // 只更新 Agent 镜像地址 + config.setAgentImage(buildResult.getAgentImageTag()); + config.setImageBuildDurationMs(buildResult.getBuildDurationMs()); + + // 发布镜像推送事件(如果已推送) + if (buildResult.isPushed() && StringUtils.hasText(buildId)) { + eventPublisher.publishBuildPushed(buildId, buildId, buildResult.getAgentImageTag()); + } + + log.info("镜像构建完成: serviceId={}, agentImage={}, duration={}ms", + serviceId, buildResult.getAgentImageTag(), buildResult.getBuildDurationMs()); + addBuildLog(buildId, "INFO", "镜像构建完成: " + buildResult.getAgentImageTag()); + } else { + log.info("镜像构建已禁用或非 K8s 模式,使用基础镜像: serviceId={}", serviceId); + addBuildLog(buildId, "INFO", "使用基础镜像: " + config.getAgentImage()); + } + + // 2. 创建 K8s 资源 + addBuildLog(buildId, "INFO", "========== K8s 资源创建阶段 =========="); + addBuildLog(buildId, "INFO", "命名空间: " + config.getNamespace()); + addBuildLog(buildId, "INFO", "Pod 数量: " + config.getPodCount()); + addBuildLog(buildId, "INFO", "Pod 模式: " + config.getPodMode()); + + ServiceBuildResult result = orchestrator.buildService(config); + + long durationMs = System.currentTimeMillis() - startTime; + + if (result.isSuccess()) { + log.info("构建完成: serviceId={}, duration={}ms", serviceId, durationMs); + addBuildLog(buildId, "INFO", "========== 构建成功 =========="); + addBuildLog(buildId, "INFO", "PodGroup: " + result.getPodGroupName()); + addBuildLog(buildId, "INFO", "Pods: " + result.getPodNames()); + addBuildLog(buildId, "INFO", "调度节点: " + result.getScheduledNode()); + addBuildLog(buildId, "INFO", "总耗时: " + durationMs + "ms"); + finishBuild(buildId, true, durationMs); + + // 发布 BUILD_COMPLETED 事件并更新记录 + if (StringUtils.hasText(buildId)) { + eventPublisher.publishBuildCompleted(buildId, buildId, config.getAgentImage(), durationMs); + if (roleId != null) { + buildRecordService.markSuccess(buildId, config.getAgentImage(), durationMs); + } + } + } else { + log.error("K8s 资源创建失败: serviceId={}, error={}", serviceId, result.getErrorMessage()); + addBuildLog(buildId, "ERROR", "========== K8s 资源创建失败 =========="); + addBuildLog(buildId, "ERROR", "错误码: " + result.getErrorCode()); + addBuildLog(buildId, "ERROR", "错误信息: " + result.getErrorMessage()); + addBuildLog(buildId, "INFO", "总耗时: " + durationMs + "ms"); + finishBuild(buildId, false, durationMs); + + // 发布 BUILD_FAILED 事件并更新记录 + if (StringUtils.hasText(buildId)) { + eventPublisher.publishBuildFailed(buildId, buildId, "K8S_ERROR", + result.getErrorMessage(), durationMs); + if (roleId != null) { + buildRecordService.markFailed(buildId, result.getErrorMessage(), durationMs); + } + } + } + + return result; + + } catch (Throwable t) { + log.error("构建执行异常: serviceId={}, error={}", serviceId, t.getMessage(), t); + + long durationMs = System.currentTimeMillis() - startTime; + + addBuildLog(buildId, "ERROR", "========== 构建执行异常 =========="); + addBuildLog(buildId, "ERROR", "异常类型: " + t.getClass().getSimpleName()); + addBuildLog(buildId, "ERROR", "异常信息: " + t.getMessage()); + addBuildLog(buildId, "INFO", "总耗时: " + durationMs + "ms"); + finishBuild(buildId, false, durationMs); + + // 发布 BUILD_FAILED 事件并更新记录 + if (StringUtils.hasText(buildId)) { + eventPublisher.publishBuildFailed(buildId, buildId, "INTERNAL_ERROR", + t.getMessage(), durationMs); + if (roleId != null) { + buildRecordService.markFailed(buildId, t.getMessage(), durationMs); + } + } + + return ServiceBuildResult.failed(serviceId, "INTERNAL_ERROR", t.getMessage()); + } + } + + /** + * 添加构建日志 + */ + private void addBuildLog(String buildId, String level, String message) { + if (StringUtils.hasText(buildId)) { + buildLogBuffer.addLog(buildId, level, message); + } + } + + /** + * 完成构建(标记完成并调度清理) + */ + private void finishBuild(String buildId, boolean success, long durationMs) { + if (StringUtils.hasText(buildId)) { + // 先标记完成状态 + buildLogBuffer.markCompleted(buildId, success); + // 添加最终状态日志(触发订阅者发送 complete 事件) + addBuildLog(buildId, "SYSTEM", success ? "构建完成" : "构建失败"); + // 延迟 30 分钟清理日志缓冲区 + buildLogBuffer.scheduleCleanup(buildId, 30); + } + } +} diff --git a/back/src/main/java/com/linkwork/service/BuildLogBuffer.java b/back/src/main/java/com/linkwork/service/BuildLogBuffer.java new file mode 100644 index 0000000..36190ad --- /dev/null +++ b/back/src/main/java/com/linkwork/service/BuildLogBuffer.java @@ -0,0 +1,180 @@ +package com.linkwork.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * 构建日志缓冲区 + * 用于存储构建过程中的日志,支持 SSE 推送 + */ +@Slf4j +@Component +public class BuildLogBuffer { + + /** + * 日志条目 + */ + public record LogEntry(long timestamp, String level, String message) {} + + /** + * 每个 buildId 的日志缓冲区 + */ + private final Map> buffers = new ConcurrentHashMap<>(); + + /** + * 每个 buildId 的订阅者(SSE 推送用) + */ + private final Map>> subscribers = new ConcurrentHashMap<>(); + + /** + * 构建是否完成的标记 + */ + private final Map completed = new ConcurrentHashMap<>(); + + /** + * 构建完成状态(success = true, failure = false) + */ + private final Map completionStatus = new ConcurrentHashMap<>(); + + /** + * 添加日志条目 + */ + public void addLog(String buildId, String level, String message) { + if (buildId == null || message == null) return; + + LogEntry entry = new LogEntry(System.currentTimeMillis(), level, message); + + // 存入缓冲区 + buffers.computeIfAbsent(buildId, k -> new CopyOnWriteArrayList<>()).add(entry); + + // 推送给所有订阅者 + CopyOnWriteArrayList> subs = subscribers.get(buildId); + if (subs != null) { + for (Consumer sub : subs) { + try { + sub.accept(entry); + } catch (Exception e) { + log.debug("Failed to push log to subscriber: {}", e.getMessage()); + } + } + } + } + + /** + * 获取所有历史日志 + */ + public List getHistory(String buildId) { + CopyOnWriteArrayList buffer = buffers.get(buildId); + return buffer != null ? List.copyOf(buffer) : List.of(); + } + + /** + * 获取指定索引之后的日志 + */ + public List getLogsAfter(String buildId, int afterIndex) { + CopyOnWriteArrayList buffer = buffers.get(buildId); + if (buffer == null || afterIndex >= buffer.size()) { + return List.of(); + } + return List.copyOf(buffer.subList(afterIndex, buffer.size())); + } + + /** + * 订阅日志(SSE 用) + */ + public void subscribe(String buildId, Consumer subscriber) { + subscribers.computeIfAbsent(buildId, k -> new CopyOnWriteArrayList<>()).add(subscriber); + } + + /** + * 取消订阅 + */ + public void unsubscribe(String buildId, Consumer subscriber) { + CopyOnWriteArrayList> subs = subscribers.get(buildId); + if (subs != null) { + subs.remove(subscriber); + } + } + + /** + * 标记构建完成(带状态) + * @param buildId 构建 ID + * @param success 是否成功 + */ + public void markCompleted(String buildId, boolean success) { + completed.put(buildId, true); + completionStatus.put(buildId, success); + } + + /** + * 检查构建是否完成 + */ + public boolean isCompleted(String buildId) { + return Boolean.TRUE.equals(completed.get(buildId)); + } + + /** + * 获取构建完成状态 + * @return true=成功, false=失败, null=未完成 + */ + public Boolean getCompletionStatus(String buildId) { + if (!isCompleted(buildId)) { + return null; + } + return completionStatus.get(buildId); + } + + /** + * 导出日志为纯文本格式 + * @param buildId 构建 ID + * @return 日志文本内容 + */ + public String exportAsText(String buildId) { + List entries = getHistory(buildId); + if (entries.isEmpty()) { + return ""; + } + + StringBuilder sb = new StringBuilder(); + java.time.format.DateTimeFormatter formatter = + java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); + + for (LogEntry entry : entries) { + java.time.LocalDateTime time = java.time.Instant.ofEpochMilli(entry.timestamp()) + .atZone(java.time.ZoneId.systemDefault()) + .toLocalDateTime(); + sb.append(String.format("[%s] [%s] %s%n", + formatter.format(time), + entry.level().toUpperCase(), + entry.message())); + } + + return sb.toString(); + } + + /** + * 清理缓冲区(构建完成后延迟清理) + */ + public void scheduleCleanup(String buildId, long delayMinutes) { + // 简单实现:使用虚拟线程延迟清理 + Thread.startVirtualThread(() -> { + try { + TimeUnit.MINUTES.sleep(delayMinutes); + buffers.remove(buildId); + subscribers.remove(buildId); + completed.remove(buildId); + completionStatus.remove(buildId); + log.debug("Cleaned up build log buffer for: {}", buildId); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + } +} diff --git a/back/src/main/java/com/linkwork/service/BuildQueueService.java b/back/src/main/java/com/linkwork/service/BuildQueueService.java new file mode 100644 index 0000000..7f459af --- /dev/null +++ b/back/src/main/java/com/linkwork/service/BuildQueueService.java @@ -0,0 +1,329 @@ +package com.linkwork.service; + +import com.linkwork.config.BuildQueueConfig; +import com.linkwork.model.dto.BuildQueueStatus; +import com.linkwork.model.dto.BuildTask; +import com.linkwork.model.dto.MergedConfig; +import com.linkwork.model.dto.ServiceBuildRequest; +import com.linkwork.model.dto.ServiceBuildResult; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + +/** + * 构建队列服务 + * + * 基于系统资源(CPU/内存)动态控制并发构建数量 + */ +@Service +@Slf4j +public class BuildQueueService { + + private final SystemResourceMonitor resourceMonitor; + private final BuildQueueConfig config; + private final BuildExecutor buildExecutor; + + // 等待队列 + private final LinkedBlockingQueue waitingQueue; + + // 正在执行的任务 + private final ConcurrentHashMap runningTasks = new ConcurrentHashMap<>(); + + // 调度器 + private ScheduledExecutorService scheduler; + + // 任务执行线程池 + private ExecutorService taskExecutor; + + // 是否已停止 + private final AtomicBoolean stopped = new AtomicBoolean(false); + + public BuildQueueService(SystemResourceMonitor resourceMonitor, + BuildQueueConfig config, + BuildExecutor buildExecutor) { + this.resourceMonitor = resourceMonitor; + this.config = config; + this.buildExecutor = buildExecutor; + this.waitingQueue = new LinkedBlockingQueue<>(config.getMaxQueueSize()); + } + + @PostConstruct + public void start() { + if (!config.isEnabled()) { + log.info("BuildQueueService is disabled"); + return; + } + + // 初始化调度器 + scheduler = Executors.newSingleThreadScheduledExecutor(r -> { + Thread t = new Thread(r, "build-queue-scheduler"); + t.setDaemon(true); + return t; + }); + + // 初始化任务执行线程池 + taskExecutor = Executors.newFixedThreadPool(config.getMaxConcurrent(), r -> { + Thread t = new Thread(r, "build-task-executor"); + t.setDaemon(true); + return t; + }); + + // 启动队列消费线程 + scheduler.scheduleWithFixedDelay( + this::processQueue, + 0, + config.getCheckInterval(), + TimeUnit.MILLISECONDS + ); + + log.info("BuildQueueService started: maxConcurrent={}, cpuThreshold={}, memoryThreshold={}, checkInterval={}ms", + config.getMaxConcurrent(), config.getCpuThreshold(), config.getMemoryThreshold(), config.getCheckInterval()); + } + + @PreDestroy + public void stop() { + stopped.set(true); + + if (scheduler != null) { + scheduler.shutdown(); + try { + if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) { + scheduler.shutdownNow(); + } + } catch (InterruptedException e) { + scheduler.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + + if (taskExecutor != null) { + taskExecutor.shutdown(); + try { + if (!taskExecutor.awaitTermination(60, TimeUnit.SECONDS)) { + taskExecutor.shutdownNow(); + } + } catch (InterruptedException e) { + taskExecutor.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + + log.info("BuildQueueService stopped"); + } + + /** + * 提交构建任务 + * + * @param request 构建请求 + * @param mergedConfig 融合后的配置 + * @return 构建任务 + * @throws IllegalStateException 如果队列已满 + */ + public BuildTask submit(ServiceBuildRequest request, MergedConfig mergedConfig) { + if (!config.isEnabled()) { + throw new IllegalStateException("Build queue is disabled"); + } + + BuildTask task = new BuildTask(request, mergedConfig); + + boolean offered = waitingQueue.offer(task); + if (!offered) { + throw new IllegalStateException("Build queue is full (max: " + config.getMaxQueueSize() + ")"); + } + + log.info("任务入队: buildId={}, serviceId={}, 当前队列长度={}", + task.getBuildId(), task.getServiceId(), waitingQueue.size()); + + return task; + } + + /** + * 处理队列(定期执行) + */ + private void processQueue() { + if (stopped.get()) { + return; + } + + try { + // 检查是否达到硬性并发上限 + if (runningTasks.size() >= config.getMaxConcurrent()) { + log.debug("达到并发上限: {}/{}", runningTasks.size(), config.getMaxConcurrent()); + return; + } + + // 检查系统资源是否充足 + if (!resourceMonitor.hasAvailableResources( + config.getCpuThreshold(), + config.getMemoryThreshold())) { + log.debug("系统资源不足,等待中..."); + return; + } + + // 从队列取出任务执行 + BuildTask task = waitingQueue.poll(); + if (task != null) { + executeTask(task); + } + } catch (Exception e) { + log.error("处理队列时发生错误", e); + } + } + + /** + * 异步执行构建任务 + */ + private void executeTask(BuildTask task) { + runningTasks.put(task.getBuildId(), task); + task.markStarted(); + + long waitTime = task.getWaitTimeMs(); + log.info("开始执行任务: buildId={}, serviceId={}, 等待时间={}ms, 当前并发={}", + task.getBuildId(), task.getServiceId(), waitTime, runningTasks.size()); + + CompletableFuture future = CompletableFuture.supplyAsync(() -> { + try { + return buildExecutor.execute(task); + } catch (Throwable t) { + log.error("构建任务执行失败: buildId={}", task.getBuildId(), t); + String error = t.getMessage() != null ? t.getMessage() : t.getClass().getSimpleName(); + task.markFailed(error); + return ServiceBuildResult.failed(task.getServiceId(), "BUILD_ERROR", error); + } finally { + runningTasks.remove(task.getBuildId()); + if (task.getState() == BuildTask.TaskState.RUNNING) { + task.markCompleted(); + } + log.info("任务执行完成: buildId={}, serviceId={}, 执行时间={}ms", + task.getBuildId(), task.getServiceId(), task.getExecutionTimeMs()); + } + }, taskExecutor); + + // 设置超时 + future.orTimeout(config.getTaskTimeout(), TimeUnit.SECONDS) + .exceptionally(e -> { + if (e instanceof TimeoutException) { + log.error("构建任务超时: buildId={}, timeout={}s", task.getBuildId(), config.getTaskTimeout()); + task.markFailed("Build timeout after " + config.getTaskTimeout() + " seconds"); + } + return ServiceBuildResult.failed(task.getServiceId(), "TIMEOUT", e.getMessage()); + }); + + task.setResultFuture(future); + } + + /** + * 取消排队中的任务 + * + * @param buildId 构建 ID + * @return true 如果取消成功 + */ + public boolean cancel(String buildId) { + // 检查是否在运行中 + if (runningTasks.containsKey(buildId)) { + log.warn("无法取消正在执行的任务: buildId={}", buildId); + return false; + } + + // 从等待队列中移除 + boolean removed = waitingQueue.removeIf(task -> task.getBuildId().equals(buildId)); + if (removed) { + log.info("任务已取消: buildId={}", buildId); + } + return removed; + } + + /** + * 获取任务在队列中的位置 + * + * @param buildId 构建 ID + * @return 位置(从 1 开始),-1 表示正在执行,null 表示不存在 + */ + public Integer getPosition(String buildId) { + // 检查是否正在执行 + if (runningTasks.containsKey(buildId)) { + return -1; + } + + // 查找在等待队列中的位置 + int position = 1; + for (BuildTask task : waitingQueue) { + if (task.getBuildId().equals(buildId)) { + return position; + } + position++; + } + + return null; + } + + /** + * 获取队列状态 + */ + public BuildQueueStatus getStatus() { + List waitingTaskInfos = new ArrayList<>(); + int position = 1; + for (BuildTask task : waitingQueue) { + waitingTaskInfos.add(BuildQueueStatus.TaskInfo.builder() + .buildId(task.getBuildId()) + .serviceId(task.getServiceId()) + .waitTimeMs(task.getWaitTimeMs()) + .position(position++) + .build()); + } + + List runningTaskInfos = runningTasks.values().stream() + .map(task -> BuildQueueStatus.TaskInfo.builder() + .buildId(task.getBuildId()) + .serviceId(task.getServiceId()) + .waitTimeMs(task.getWaitTimeMs()) + .executionTimeMs(task.getExecutionTimeMs()) + .position(-1) + .build()) + .collect(Collectors.toList()); + + boolean canAccept = waitingQueue.remainingCapacity() > 0 + && resourceMonitor.hasAvailableResources(config.getCpuThreshold(), config.getMemoryThreshold()); + + return BuildQueueStatus.builder() + .waitingCount(waitingQueue.size()) + .runningCount(runningTasks.size()) + .maxConcurrent(config.getMaxConcurrent()) + .maxQueueSize(config.getMaxQueueSize()) + .resourceStatus(resourceMonitor.getStatus()) + .cpuThreshold(config.getCpuThreshold()) + .memoryThreshold(config.getMemoryThreshold()) + .canAcceptNewTask(canAccept) + .waitingTasks(waitingTaskInfos) + .runningTasks(runningTaskInfos) + .build(); + } + + /** + * 检查队列是否启用 + */ + public boolean isEnabled() { + return config.isEnabled(); + } + + /** + * 获取等待中的任务数 + */ + public int getWaitingCount() { + return waitingQueue.size(); + } + + /** + * 获取正在执行的任务数 + */ + public int getRunningCount() { + return runningTasks.size(); + } +} diff --git a/back/src/main/java/com/linkwork/service/BuildRecordService.java b/back/src/main/java/com/linkwork/service/BuildRecordService.java new file mode 100644 index 0000000..dead57b --- /dev/null +++ b/back/src/main/java/com/linkwork/service/BuildRecordService.java @@ -0,0 +1,218 @@ +package com.linkwork.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.linkwork.mapper.BuildRecordMapper; +import com.linkwork.model.entity.BuildRecordEntity; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 构建记录 Service + * + * 管理镜像构建记录的完整生命周期 + */ +@Slf4j +@Service +public class BuildRecordService extends ServiceImpl { + + /** + * 创建构建记录 + * + * @param buildNo 构建编号(由前端生成) + * @param roleId 岗位 ID + * @param roleName 岗位名称 + * @param configSnapshot 构建配置快照 + * @param creatorId 创建者 ID + * @param creatorName 创建者名称 + * @return 构建记录实体 + */ + public BuildRecordEntity createBuildRecord(String buildNo, Long roleId, String roleName, + Map configSnapshot, + String creatorId, String creatorName) { + BuildRecordEntity entity = new BuildRecordEntity(); + entity.setBuildNo(buildNo); + entity.setRoleId(roleId); + entity.setRoleName(roleName); + entity.setStatus(BuildRecordEntity.STATUS_PENDING); + entity.setConfigSnapshot(configSnapshot); + entity.setCreatorId(creatorId); + entity.setCreatorName(creatorName); + + this.save(entity); + log.info("Created build record: {} for role {} by user {}", buildNo, roleId, creatorId); + return entity; + } + + /** + * 更新构建状态为 BUILDING + */ + public void markBuilding(String buildNo) { + updateStatus(buildNo, BuildRecordEntity.STATUS_BUILDING, null, null, null); + } + + /** + * 更新构建状态为 SUCCESS + */ + public void markSuccess(String buildNo, String imageTag, Long durationMs) { + updateStatus(buildNo, BuildRecordEntity.STATUS_SUCCESS, imageTag, durationMs, null); + } + + /** + * 更新构建状态为 FAILED + */ + public void markFailed(String buildNo, String errorMessage, Long durationMs) { + updateStatus(buildNo, BuildRecordEntity.STATUS_FAILED, null, durationMs, errorMessage); + } + + /** + * 更新构建状态为 CANCELLED + */ + public void markCancelled(String buildNo) { + updateStatus(buildNo, BuildRecordEntity.STATUS_CANCELLED, null, null, "Build cancelled by user"); + } + + /** + * 更新构建日志 URL + */ + public void updateLogUrl(String buildNo, String logUrl) { + BuildRecordEntity entity = getByBuildNo(buildNo); + if (entity == null) { + log.warn("Build record not found for log URL update: {}", buildNo); + return; + } + entity.setLogUrl(logUrl); + this.updateById(entity); + log.info("Updated build record {} log URL", buildNo); + } + + /** + * 更新构建记录状态 + */ + public void updateStatus(String buildNo, String status, String imageTag, + Long durationMs, String errorMessage) { + BuildRecordEntity entity = getByBuildNo(buildNo); + if (entity == null) { + log.warn("Build record not found: {}", buildNo); + return; + } + + entity.setStatus(status); + if (imageTag != null) { + entity.setImageTag(imageTag); + } + if (durationMs != null) { + entity.setDurationMs(durationMs); + } + if (errorMessage != null) { + entity.setErrorMessage(errorMessage); + } + + this.updateById(entity); + log.info("Updated build record {} status to {}", buildNo, status); + } + + /** + * 获取岗位最新构建记录 + */ + public BuildRecordEntity getLatestByRoleId(Long roleId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(BuildRecordEntity::getRoleId, roleId); + wrapper.orderByDesc(BuildRecordEntity::getCreatedAt); + wrapper.last("LIMIT 1"); + return this.getOne(wrapper, false); + } + + /** + * 根据构建编号获取记录 + */ + public BuildRecordEntity getByBuildNo(String buildNo) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(BuildRecordEntity::getBuildNo, buildNo); + return this.getOne(wrapper); + } + + /** + * 获取岗位的构建历史 + */ + public Map listByRoleId(Long roleId, int page, int pageSize) { + Page pageObj = new Page<>(page, pageSize); + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(BuildRecordEntity::getRoleId, roleId); + wrapper.orderByDesc(BuildRecordEntity::getCreatedAt); + + Page result = this.page(pageObj, wrapper); + + List> items = result.getRecords().stream() + .map(this::toResponseMap) + .collect(Collectors.toList()); + + Map response = new HashMap<>(); + response.put("items", items); + response.put("pagination", Map.of( + "page", result.getCurrent(), + "pageSize", result.getSize(), + "total", result.getTotal(), + "totalPages", result.getPages() + )); + return response; + } + + /** + * 获取最近的构建记录(全局) + */ + public Map listRecent(int page, int pageSize, String status) { + Page pageObj = new Page<>(page, pageSize); + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (status != null && !status.isEmpty()) { + wrapper.eq(BuildRecordEntity::getStatus, status); + } + wrapper.orderByDesc(BuildRecordEntity::getCreatedAt); + + Page result = this.page(pageObj, wrapper); + + List> items = result.getRecords().stream() + .map(this::toResponseMap) + .collect(Collectors.toList()); + + Map response = new HashMap<>(); + response.put("items", items); + response.put("pagination", Map.of( + "page", result.getCurrent(), + "pageSize", result.getSize(), + "total", result.getTotal(), + "totalPages", result.getPages() + )); + return response; + } + + /** + * 转换为响应 Map + */ + private Map toResponseMap(BuildRecordEntity entity) { + Map map = new HashMap<>(); + map.put("id", entity.getId().toString()); + map.put("buildNo", entity.getBuildNo()); + map.put("roleId", entity.getRoleId() != null ? entity.getRoleId().toString() : null); + map.put("roleName", entity.getRoleName()); + map.put("status", entity.getStatus()); + map.put("imageTag", entity.getImageTag()); + map.put("durationMs", entity.getDurationMs()); + map.put("errorMessage", entity.getErrorMessage()); + map.put("configSnapshot", entity.getConfigSnapshot()); + map.put("creatorId", entity.getCreatorId()); + map.put("creatorName", entity.getCreatorName()); + map.put("logUrl", entity.getLogUrl()); + map.put("createdAt", entity.getCreatedAt()); + map.put("updatedAt", entity.getUpdatedAt()); + return map; + } +} diff --git a/back/src/main/java/com/linkwork/service/ConfigMergeService.java b/back/src/main/java/com/linkwork/service/ConfigMergeService.java new file mode 100644 index 0000000..a11d871 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/ConfigMergeService.java @@ -0,0 +1,184 @@ +package com.linkwork.service; + +import com.linkwork.config.EnvConfig; +import com.linkwork.config.ImageBuildConfig; +import com.linkwork.model.dto.MergedConfig; +import com.linkwork.model.dto.ResourceConfig; +import com.linkwork.model.dto.ResourceSpec; +import com.linkwork.model.dto.ServiceBuildRequest; +import com.linkwork.model.enums.DeployMode; +import com.linkwork.model.enums.PodMode; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +/** + * 配置融合服务 + * 将请求参数与环境配置融合,生成 MergedConfig + * + * 设计说明: + * - 仅构建 Agent 镜像,Runner 由运行时 agent 启动 + * - token 放入 buildEnvVars,在 build.sh 执行前 export + */ +@Service +@Slf4j +public class ConfigMergeService { + + private final EnvConfig envConfig; + private final QueueSelector queueSelector; + private final ImageBuildConfig imageBuildConfig; + + public ConfigMergeService(EnvConfig envConfig, QueueSelector queueSelector, + ImageBuildConfig imageBuildConfig) { + this.envConfig = envConfig; + this.queueSelector = queueSelector; + this.imageBuildConfig = imageBuildConfig; + } + + /** + * 融合请求配置与环境配置 + */ + public MergedConfig merge(ServiceBuildRequest request) { + log.info("Merging config for service {}", request.getServiceId()); + + // 1. 决策 PodMode + PodMode podMode = decidePodMode(request); + + // 2. 决策 Pod 数量 + int podCount = decidePodCount(request); + + // 3. 选择调度队列(仅 K8s 模式) + String queueName = request.getDeployMode() == DeployMode.K8S + ? queueSelector.selectQueue(request.getPriority(), false) + : null; + + // 4. 融合资源配置 + ResourceSpec agentResources = mergeResources( + request.getResourceConfig(), envConfig.getDefaultResources().getAgent()); + ResourceSpec runnerResources = mergeResources( + request.getResourceConfig(), envConfig.getDefaultResources().getRunner()); + + // 5. 决定 Agent 镜像地址(使用默认配置) + String agentBaseImage = envConfig.getImages().getAgent(); + + // 6. 决定 Runner 镜像地址 + // - K8s + Sidecar 模式:使用 runnerBaseImage 或默认值 + // - 非 K8s 或 Alone 模式:不需要 Runner 镜像 + String runnerImage; + if (request.getDeployMode() == DeployMode.K8S && podMode == PodMode.SIDECAR) { + runnerImage = Optional.ofNullable(request.getRunnerBaseImage()) + .orElse(envConfig.getImages().getRunner()); + } else { + runnerImage = null; // 非 Sidecar 模式不需要 Runner 镜像 + } + + // 7. 构建融合配置 + return MergedConfig.builder() + // 服务标识 + .serviceId(request.getServiceId()) + .userId(request.getUserId()) + .roleId(request.getRoleId()) + // 模式配置 + .deployMode(request.getDeployMode()) + .podMode(podMode) + .podCount(podCount) + // K8s 配置 + .namespace(envConfig.getCluster().getNamespace()) + .queueName(queueName) + .priorityClassName(getPriorityClass(request.getPriority())) + // 镜像配置 + .agentImage(agentBaseImage) + .runnerImage(runnerImage) + .buildEnvVars(request.getBuildEnvVars()) + .agentBaseImage(agentBaseImage) + .imageRegistry(request.getImageRegistry()) + .imagePullPolicy(imageBuildConfig.getImagePullPolicy()) + .imagePullSecret(imageBuildConfig.getImagePullSecret()) + // Agent 启动脚本配置 + .mainPyUrl(envConfig.getAgentBootstrap().getMainPyUrl()) + // 文件放置配置 + .filePlacement(envConfig.getFilePlacement()) + // 资源配置 + .agentResources(agentResources) + .runnerResources(runnerResources) + // 网络配置 + .apiBaseUrl(envConfig.getNetwork().getApiBaseUrl()) + .wsBaseUrl(envConfig.getNetwork().getWsBaseUrl()) + .llmGatewayUrl(envConfig.getNetwork().getLlmGatewayUrl()) + .redisUrl(envConfig.getNetwork().getRedisUrl()) + // SSH 配置 + .sshPort(podMode == PodMode.SIDECAR ? envConfig.getSsh().getPort() : null) + // Agent 启动配置(双容器模式) + .workstationId(request.getServiceId()) + // 工作目录配置 + .workspaceSizeLimit(Optional.ofNullable(request.getWorkspaceSizeLimit()).orElse(10)) + // 任务元信息(可选) + .description(request.getDescription()) + // 回调配置 + .callbackUrl(request.getCallbackUrl()) + // 快速恢复配置 + .preferredNode(request.getPreferredNode()) + // OSS 挂载配置 + .ossMount(envConfig.getOssMount()) + .build(); + } + + /** + * 决策 PodMode + */ + private PodMode decidePodMode(ServiceBuildRequest request) { + // 1. 用户显式指定 + if (request.getPodMode() != null) { + return request.getPodMode(); + } + + // 2. Compose 强制 Alone + if (request.getDeployMode() == DeployMode.COMPOSE) { + return PodMode.ALONE; + } + + // 3. 使用默认值 + return envConfig.getPodModeRules().getDefaultMode(); + } + + /** + * 决策 Pod 数量 + */ + private int decidePodCount(ServiceBuildRequest request) { + // Compose 模式不支持多 Pod + if (request.getDeployMode() == DeployMode.COMPOSE) { + return 1; + } + + // 默认 Pod 数量为 4 + int count = Optional.ofNullable(request.getPodCount()).orElse(4); + return Math.min(Math.max(count, 1), 10); // 限制 1-10 + } + + /** + * 融合资源配置 + */ + private ResourceSpec mergeResources(ResourceConfig requested, ResourceSpec defaultSpec) { + if (requested == null) { + return defaultSpec; + } + return ResourceSpec.builder() + .cpuRequest(Optional.ofNullable(requested.getCpuRequest()).orElse(defaultSpec.getCpuRequest())) + .cpuLimit(Optional.ofNullable(requested.getCpuLimit()).orElse(defaultSpec.getCpuLimit())) + .memoryRequest(Optional.ofNullable(requested.getMemoryRequest()).orElse(defaultSpec.getMemoryRequest())) + .memoryLimit(Optional.ofNullable(requested.getMemoryLimit()).orElse(defaultSpec.getMemoryLimit())) + .build(); + } + + /** + * 获取优先级类 + */ + private String getPriorityClass(Integer priority) { + int p = Optional.ofNullable(priority).orElse(50); + if (p >= 90) return "critical-priority"; + if (p >= 70) return "high-priority"; + if (p >= 30) return "normal-priority"; + return "low-priority"; + } +} diff --git a/back/src/main/java/com/linkwork/service/CronExpressionHelper.java b/back/src/main/java/com/linkwork/service/CronExpressionHelper.java new file mode 100644 index 0000000..56a958f --- /dev/null +++ b/back/src/main/java/com/linkwork/service/CronExpressionHelper.java @@ -0,0 +1,168 @@ +package com.linkwork.service; + +import com.linkwork.model.entity.CronJob; +import org.springframework.scheduling.support.CronExpression; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +@Component +public class CronExpressionHelper { + + public String normalizeScheduleType(String scheduleType) { + if (!StringUtils.hasText(scheduleType)) { + throw new IllegalArgumentException("scheduleType 不能为空"); + } + return scheduleType.trim().toLowerCase(Locale.ROOT); + } + + public String normalizeTimezone(String timezone) { + if (!StringUtils.hasText(timezone)) { + return "Asia/Shanghai"; + } + String tz = timezone.trim(); + try { + ZoneId.of(tz); + } catch (Exception e) { + throw new IllegalArgumentException("无效时区: " + timezone); + } + return tz; + } + + public void validateSchedule(String scheduleType, + String cronExpr, + Long intervalMs, + LocalDateTime runAt, + String timezone) { + String type = normalizeScheduleType(scheduleType); + String tz = normalizeTimezone(timezone); + LocalDateTime now = LocalDateTime.now(ZoneId.of(tz)); + + switch (type) { + case "cron" -> { + if (!StringUtils.hasText(cronExpr)) { + throw new IllegalArgumentException("scheduleType=cron 时 cronExpr 必填"); + } + try { + CronExpression expression = CronExpression.parse(cronExpr.trim()); + ZonedDateTime from = ZonedDateTime.of(now.withSecond(0).withNano(0), ZoneId.of(tz)); + ZonedDateTime next = expression.next(from); + if (next == null) { + throw new IllegalArgumentException("cronExpr 无法计算下一次触发时间"); + } + long delta = java.time.Duration.between(from, next).toMillis(); + if (delta < 60_000) { + throw new IllegalArgumentException("cron 表达式最小粒度为 1 分钟"); + } + } catch (IllegalArgumentException e) { + throw e; + } catch (Exception e) { + throw new IllegalArgumentException("cronExpr 不合法: " + e.getMessage()); + } + } + case "every" -> { + if (intervalMs == null) { + throw new IllegalArgumentException("scheduleType=every 时 intervalMs 必填"); + } + if (intervalMs < 60_000) { + throw new IllegalArgumentException("intervalMs 最小为 60000"); + } + } + case "at" -> { + if (runAt == null) { + throw new IllegalArgumentException("scheduleType=at 时 runAt 必填"); + } + if (!runAt.isAfter(now)) { + throw new IllegalArgumentException("runAt 必须是未来时间"); + } + } + default -> throw new IllegalArgumentException("不支持的 scheduleType: " + scheduleType); + } + } + + public LocalDateTime computeFirstFireTime(String scheduleType, + String cronExpr, + Long intervalMs, + LocalDateTime runAt, + String timezone) { + String type = normalizeScheduleType(scheduleType); + ZoneId zoneId = ZoneId.of(normalizeTimezone(timezone)); + LocalDateTime now = LocalDateTime.now(zoneId).withSecond(0).withNano(0); + return switch (type) { + case "cron" -> { + CronExpression expression = CronExpression.parse(cronExpr.trim()); + ZonedDateTime next = expression.next(ZonedDateTime.of(now, zoneId)); + yield next == null ? null : next.toLocalDateTime(); + } + case "every" -> now.plusNanos(intervalMs * 1_000_000); + case "at" -> runAt; + default -> throw new IllegalArgumentException("不支持的 scheduleType: " + scheduleType); + }; + } + + public LocalDateTime computeNextFireTime(CronJob job, LocalDateTime currentFireTime) { + String type = normalizeScheduleType(job.getScheduleType()); + ZoneId zoneId = ZoneId.of(normalizeTimezone(job.getTimezone())); + LocalDateTime base = currentFireTime == null ? LocalDateTime.now(zoneId).withSecond(0).withNano(0) : currentFireTime; + + return switch (type) { + case "cron" -> { + CronExpression expression = CronExpression.parse(job.getCronExpr().trim()); + ZonedDateTime next = expression.next(ZonedDateTime.of(base, zoneId)); + yield next == null ? null : next.toLocalDateTime(); + } + case "every" -> base.plusNanos(job.getIntervalMs() * 1_000_000); + case "at" -> null; + default -> null; + }; + } + + public List previewNextFireTimes(String scheduleType, + String cronExpr, + Long intervalMs, + LocalDateTime runAt, + String timezone, + int limit) { + String type = normalizeScheduleType(scheduleType); + ZoneId zoneId = ZoneId.of(normalizeTimezone(timezone)); + List times = new ArrayList<>(); + int size = Math.max(limit, 0); + + if (size == 0) { + return times; + } + + if ("at".equals(type)) { + if (runAt != null) { + times.add(runAt.toString()); + } + return times; + } + + LocalDateTime cursor = LocalDateTime.now(zoneId).withSecond(0).withNano(0); + if ("every".equals(type)) { + for (int i = 0; i < size; i++) { + cursor = cursor.plusNanos(intervalMs * 1_000_000); + times.add(cursor.toString()); + } + return times; + } + + CronExpression expression = CronExpression.parse(cronExpr.trim()); + ZonedDateTime zonedCursor = ZonedDateTime.of(cursor, zoneId); + for (int i = 0; i < size; i++) { + zonedCursor = expression.next(zonedCursor); + if (zonedCursor == null) { + break; + } + times.add(zonedCursor.toLocalDateTime().toString()); + } + return times; + } +} diff --git a/back/src/main/java/com/linkwork/service/CronJobExecutor.java b/back/src/main/java/com/linkwork/service/CronJobExecutor.java new file mode 100644 index 0000000..1b4aace --- /dev/null +++ b/back/src/main/java/com/linkwork/service/CronJobExecutor.java @@ -0,0 +1,120 @@ +package com.linkwork.service; + +import com.linkwork.mapper.CronJobRunMapper; +import com.linkwork.model.dto.TaskCreateRequest; +import com.linkwork.model.entity.CronJob; +import com.linkwork.model.entity.CronJobRun; +import com.linkwork.model.entity.Task; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +@Slf4j +@Component +@RequiredArgsConstructor +public class CronJobExecutor { + + private final CronJobRunMapper cronJobRunMapper; + private final TaskService taskService; + + @Transactional + public CronJobRun dispatchScheduled(CronJob cronJob, LocalDateTime plannedFireTime) { + CronJobRun run = initRun(cronJob, "SCHEDULED", plannedFireTime); + cronJobRunMapper.insert(run); + + try { + Task task = taskService.createTask( + buildTaskCreateRequest(cronJob), + cronJob.getCreatorId(), + cronJob.getCreatorName(), + "cron-scheduler", + false, + "CRON", + cronJob.getId()); + + CronJobRun update = new CronJobRun(); + update.setId(run.getId()); + update.setTaskNo(task.getTaskNo()); + update.setStatus("DISPATCHED"); + update.setStartedAt(LocalDateTime.now()); + cronJobRunMapper.updateById(update); + + run.setTaskNo(task.getTaskNo()); + run.setStatus("DISPATCHED"); + run.setStartedAt(update.getStartedAt()); + log.info("Cron 调度触发成功: cronJobId={}, runId={}, taskNo={}", cronJob.getId(), run.getId(), task.getTaskNo()); + return run; + } catch (Exception e) { + CronJobRun fail = new CronJobRun(); + fail.setId(run.getId()); + fail.setStatus("FAILED"); + fail.setFinishedAt(LocalDateTime.now()); + fail.setErrorMessage(e.getMessage()); + cronJobRunMapper.updateById(fail); + throw e; + } + } + + @Transactional + public CronJobRun dispatchManual(CronJob cronJob) { + LocalDateTime now = LocalDateTime.now(); + CronJobRun run = initRun(cronJob, "MANUAL", now); + cronJobRunMapper.insert(run); + + try { + Task task = taskService.createTask( + buildTaskCreateRequest(cronJob), + cronJob.getCreatorId(), + cronJob.getCreatorName(), + "cron-manual-trigger", + false, + "CRON", + cronJob.getId()); + + CronJobRun update = new CronJobRun(); + update.setId(run.getId()); + update.setTaskNo(task.getTaskNo()); + update.setStatus("DISPATCHED"); + update.setStartedAt(LocalDateTime.now()); + cronJobRunMapper.updateById(update); + + run.setTaskNo(task.getTaskNo()); + run.setStatus("DISPATCHED"); + run.setStartedAt(update.getStartedAt()); + log.info("Cron 手动触发成功: cronJobId={}, runId={}, taskNo={}", cronJob.getId(), run.getId(), task.getTaskNo()); + return run; + } catch (Exception e) { + CronJobRun fail = new CronJobRun(); + fail.setId(run.getId()); + fail.setStatus("FAILED"); + fail.setFinishedAt(LocalDateTime.now()); + fail.setErrorMessage(e.getMessage()); + cronJobRunMapper.updateById(fail); + throw e; + } + } + + private CronJobRun initRun(CronJob cronJob, String triggerType, LocalDateTime plannedFireTime) { + CronJobRun run = new CronJobRun(); + run.setCronJobId(cronJob.getId()); + run.setCreatorId(cronJob.getCreatorId()); + run.setRoleId(cronJob.getRoleId()); + run.setStatus("PENDING"); + run.setTriggerType(triggerType); + run.setPlannedFireTime(plannedFireTime); + run.setCreatedAt(LocalDateTime.now()); + return run; + } + + private TaskCreateRequest buildTaskCreateRequest(CronJob cronJob) { + TaskCreateRequest request = new TaskCreateRequest(); + request.setRoleId(cronJob.getRoleId()); + request.setModelId(cronJob.getModelId()); + request.setPrompt(cronJob.getTaskContent()); + request.setFileIds(null); + return request; + } +} diff --git a/back/src/main/java/com/linkwork/service/CronJobService.java b/back/src/main/java/com/linkwork/service/CronJobService.java new file mode 100644 index 0000000..64d6e0b --- /dev/null +++ b/back/src/main/java/com/linkwork/service/CronJobService.java @@ -0,0 +1,519 @@ +package com.linkwork.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.linkwork.common.ForbiddenOperationException; +import com.linkwork.common.ResourceNotFoundException; +import com.linkwork.config.CronConfig; +import com.linkwork.mapper.CronJobMapper; +import com.linkwork.mapper.CronJobRunMapper; +import com.linkwork.mapper.RoleMapper; +import com.linkwork.model.dto.CronJobCreateRequest; +import com.linkwork.model.dto.CronJobResponse; +import com.linkwork.model.dto.CronJobRunResponse; +import com.linkwork.model.dto.CronJobToggleRequest; +import com.linkwork.model.dto.CronJobUpdateRequest; +import com.linkwork.model.entity.CronJob; +import com.linkwork.model.entity.CronJobRun; +import com.linkwork.model.entity.RoleEntity; +import com.linkwork.model.entity.Task; +import com.linkwork.model.enums.TaskStatus; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +@Slf4j +@Service +@RequiredArgsConstructor +public class CronJobService { + + private static final String NOTIFY_NONE = "none"; + + private final CronJobMapper cronJobMapper; + private final CronJobRunMapper cronJobRunMapper; + private final RoleMapper roleMapper; + private final CronExpressionHelper cronExpressionHelper; + private final CronConfig cronConfig; + + @Transactional + public CronJobResponse create(CronJobCreateRequest request, String creatorId, String creatorName) { + validateRoleVisible(request.getRoleId(), creatorId); + validateSchedule(request.getScheduleType(), request.getCronExpr(), request.getIntervalMs(), + request.getRunAt(), request.getTimezone()); + enforceQuota(creatorId, request.getRoleId()); + + RoleEntity role = roleMapper.selectById(request.getRoleId()); + CronJob job = new CronJob(); + job.setJobName(request.getJobName().trim()); + job.setCreatorId(creatorId); + job.setCreatorName(StringUtils.hasText(creatorName) ? creatorName : creatorId); + job.setRoleId(request.getRoleId()); + job.setRoleName(role.getName()); + job.setModelId(request.getModelId().trim()); + job.setFileIdsJson(null); + job.setScheduleType(cronExpressionHelper.normalizeScheduleType(request.getScheduleType())); + job.setCronExpr(StringUtils.hasText(request.getCronExpr()) ? request.getCronExpr().trim() : null); + job.setIntervalMs(request.getIntervalMs()); + job.setRunAt(request.getRunAt()); + job.setTimezone(cronExpressionHelper.normalizeTimezone(request.getTimezone())); + job.setTaskContent(request.getTaskContent().trim()); + job.setEnabled(1); + job.setDeleteAfterRun(Boolean.TRUE.equals(request.getDeleteAfterRun()) ? 1 : 0); + if ("at".equals(job.getScheduleType()) && request.getDeleteAfterRun() == null) { + job.setDeleteAfterRun(1); + } + job.setMaxRetry(normalizeMaxRetry(request.getMaxRetry())); + job.setConsecutiveFailures(0); + // Cron 通知策略临时下线:固定兼容值,实际结果通知由任务终态链路统一处理。 + job.setNotifyMode(NOTIFY_NONE); + job.setNotifyTarget(null); + job.setNextFireTime(cronExpressionHelper.computeFirstFireTime( + job.getScheduleType(), job.getCronExpr(), job.getIntervalMs(), job.getRunAt(), job.getTimezone())); + job.setTotalRuns(0); + job.setIsDeleted(0); + job.setCreatedAt(LocalDateTime.now()); + job.setUpdatedAt(LocalDateTime.now()); + + cronJobMapper.insert(job); + return toResponse(job, true); + } + + public Map listMine(String creatorId, Long roleId, Boolean enabled, String scheduleType, + String keyword, Integer page, Integer pageSize) { + int pageNum = page == null || page <= 0 ? 1 : page; + int size = pageSize == null || pageSize <= 0 ? 20 : pageSize; + + Page pageReq = new Page<>(pageNum, size); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(CronJob::getIsDeleted, 0) + .eq(CronJob::getCreatorId, creatorId) + .orderByDesc(CronJob::getCreatedAt); + if (roleId != null) { + wrapper.eq(CronJob::getRoleId, roleId); + } + if (enabled != null) { + wrapper.eq(CronJob::getEnabled, enabled ? 1 : 0); + } + if (StringUtils.hasText(scheduleType)) { + wrapper.eq(CronJob::getScheduleType, cronExpressionHelper.normalizeScheduleType(scheduleType)); + } + if (StringUtils.hasText(keyword)) { + wrapper.like(CronJob::getJobName, keyword.trim()); + } + + Page result = cronJobMapper.selectPage(pageReq, wrapper); + List items = result.getRecords().stream().map(job -> toResponse(job, false)).toList(); + + Map pagination = new HashMap<>(); + pagination.put("page", result.getCurrent()); + pagination.put("pageSize", result.getSize()); + pagination.put("total", result.getTotal()); + pagination.put("totalPages", result.getPages()); + + Map data = new HashMap<>(); + data.put("items", items); + data.put("pagination", pagination); + return data; + } + + public CronJobResponse getDetail(Long id, String creatorId) { + CronJob job = getOwnedJob(id, creatorId); + return toResponse(job, true); + } + + @Transactional + public CronJobResponse update(Long id, CronJobUpdateRequest request, String creatorId) { + CronJob job = getOwnedJob(id, creatorId); + + validateRoleVisible(request.getRoleId(), creatorId); + validateSchedule(request.getScheduleType(), request.getCronExpr(), request.getIntervalMs(), + request.getRunAt(), request.getTimezone()); + + RoleEntity role = roleMapper.selectById(request.getRoleId()); + job.setJobName(request.getJobName().trim()); + job.setRoleId(request.getRoleId()); + job.setRoleName(role.getName()); + job.setModelId(request.getModelId().trim()); + job.setFileIdsJson(null); + job.setScheduleType(cronExpressionHelper.normalizeScheduleType(request.getScheduleType())); + job.setCronExpr(StringUtils.hasText(request.getCronExpr()) ? request.getCronExpr().trim() : null); + job.setIntervalMs(request.getIntervalMs()); + job.setRunAt(request.getRunAt()); + job.setTimezone(cronExpressionHelper.normalizeTimezone(request.getTimezone())); + job.setTaskContent(request.getTaskContent().trim()); + job.setDeleteAfterRun(Boolean.TRUE.equals(request.getDeleteAfterRun()) ? 1 : 0); + if ("at".equals(job.getScheduleType()) && request.getDeleteAfterRun() == null) { + job.setDeleteAfterRun(1); + } + job.setMaxRetry(normalizeMaxRetry(request.getMaxRetry())); + // Cron 通知策略临时下线:固定兼容值,实际结果通知由任务终态链路统一处理。 + job.setNotifyMode(NOTIFY_NONE); + job.setNotifyTarget(null); + job.setConsecutiveFailures(0); + if (job.getEnabled() != null && job.getEnabled() == 1) { + job.setNextFireTime(cronExpressionHelper.computeFirstFireTime( + job.getScheduleType(), job.getCronExpr(), job.getIntervalMs(), job.getRunAt(), job.getTimezone())); + } + job.setUpdatedAt(LocalDateTime.now()); + cronJobMapper.updateById(job); + return toResponse(job, true); + } + + @Transactional + public CronJobResponse toggle(Long id, CronJobToggleRequest request, String creatorId) { + CronJob job = getOwnedJob(id, creatorId); + boolean enabled = Boolean.TRUE.equals(request.getEnabled()); + job.setEnabled(enabled ? 1 : 0); + if (enabled) { + cronExpressionHelper.validateSchedule(job.getScheduleType(), job.getCronExpr(), job.getIntervalMs(), + job.getRunAt(), job.getTimezone()); + job.setConsecutiveFailures(0); + job.setNextFireTime(cronExpressionHelper.computeFirstFireTime( + job.getScheduleType(), job.getCronExpr(), job.getIntervalMs(), job.getRunAt(), job.getTimezone())); + } else { + job.setNextFireTime(null); + } + job.setUpdatedAt(LocalDateTime.now()); + cronJobMapper.updateById(job); + return toResponse(job, true); + } + + @Transactional + public void delete(Long id, String creatorId) { + CronJob job = getOwnedJob(id, creatorId); + cronJobMapper.deleteById(job.getId()); + } + + public Map listRuns(Long cronJobId, String creatorId, Integer page, Integer pageSize) { + getOwnedJob(cronJobId, creatorId); + int pageNum = page == null || page <= 0 ? 1 : page; + int size = pageSize == null || pageSize <= 0 ? 20 : pageSize; + Page req = new Page<>(pageNum, size); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(CronJobRun::getCronJobId, cronJobId) + .orderByDesc(CronJobRun::getCreatedAt); + Page result = cronJobRunMapper.selectPage(req, wrapper); + + List items = result.getRecords().stream().map(this::toRunResponse).toList(); + Map pagination = new HashMap<>(); + pagination.put("page", result.getCurrent()); + pagination.put("pageSize", result.getSize()); + pagination.put("total", result.getTotal()); + pagination.put("totalPages", result.getPages()); + + Map data = new HashMap<>(); + data.put("items", items); + data.put("pagination", pagination); + return data; + } + + public List findDueJobs(LocalDateTime threshold) { + return cronJobMapper.selectList(new LambdaQueryWrapper() + .eq(CronJob::getEnabled, 1) + .eq(CronJob::getIsDeleted, 0) + .isNotNull(CronJob::getNextFireTime) + .le(CronJob::getNextFireTime, threshold) + .orderByAsc(CronJob::getNextFireTime)); + } + + @Transactional + public void advanceAfterDispatch(CronJob job, LocalDateTime firedAt) { + CronJob update = new CronJob(); + update.setId(job.getId()); + update.setTotalRuns((job.getTotalRuns() == null ? 0 : job.getTotalRuns()) + 1); + update.setUpdatedAt(LocalDateTime.now()); + + LocalDateTime next = cronExpressionHelper.computeNextFireTime(job, firedAt); + if (next == null) { + update.setNextFireTime(null); + if (Objects.equals(job.getDeleteAfterRun(), 1) || "at".equals(job.getScheduleType())) { + update.setEnabled(0); + } + } else { + update.setNextFireTime(next); + } + + cronJobMapper.updateById(update); + trimRunHistory(job.getId()); + } + + @Transactional + public void recordDispatchFailure(CronJob job, String error) { + int failures = (job.getConsecutiveFailures() == null ? 0 : job.getConsecutiveFailures()) + 1; + CronJob update = new CronJob(); + update.setId(job.getId()); + update.setConsecutiveFailures(failures); + update.setLastRunTime(LocalDateTime.now()); + update.setLastRunStatus("FAILED"); + update.setUpdatedAt(LocalDateTime.now()); + if (failures >= normalizeMaxRetry(job.getMaxRetry())) { + update.setEnabled(0); + update.setNextFireTime(null); + } + cronJobMapper.updateById(update); + + if (failures >= normalizeMaxRetry(job.getMaxRetry())) { + log.info("Cron 连续失败已自动禁用: cronJobId={}, jobName={}, reason={}", job.getId(), job.getJobName(), truncate(error)); + } + } + + @Transactional + public void onTaskStatusChanged(Task task, TaskStatus status) { + if (task == null || !"CRON".equalsIgnoreCase(task.getSource()) || task.getCronJobId() == null) { + return; + } + + CronJobRun run = cronJobRunMapper.selectOne(new LambdaQueryWrapper() + .eq(CronJobRun::getTaskNo, task.getTaskNo()) + .orderByDesc(CronJobRun::getId) + .last("LIMIT 1")); + if (run == null) { + return; + } + + String mappedStatus = switch (status) { + case RUNNING -> "RUNNING"; + case COMPLETED -> "COMPLETED"; + case FAILED -> "FAILED"; + case ABORTED -> "ABORTED"; + default -> null; + }; + if (mappedStatus == null) { + return; + } + + if (isTerminal(run.getStatus())) { + return; + } + + CronJobRun update = new CronJobRun(); + update.setId(run.getId()); + update.setStatus(mappedStatus); + if ("RUNNING".equals(mappedStatus)) { + if (run.getStartedAt() == null) { + update.setStartedAt(LocalDateTime.now()); + } + cronJobRunMapper.updateById(update); + return; + } + + LocalDateTime now = LocalDateTime.now(); + update.setFinishedAt(now); + if (task.getDurationMs() != null && task.getDurationMs() > 0) { + update.setDurationMs(task.getDurationMs()); + } else if (run.getStartedAt() != null) { + update.setDurationMs(Duration.between(run.getStartedAt(), now).toMillis()); + } + if (status == TaskStatus.FAILED) { + update.setErrorMessage(extractTaskError(task)); + } + cronJobRunMapper.updateById(update); + + CronJob job = cronJobMapper.selectById(task.getCronJobId()); + if (job == null || job.getIsDeleted() != null && job.getIsDeleted() == 1) { + return; + } + + CronJob jobUpdate = new CronJob(); + jobUpdate.setId(job.getId()); + jobUpdate.setLastRunTime(now); + jobUpdate.setLastRunStatus(mappedStatus); + jobUpdate.setUpdatedAt(now); + + if (status == TaskStatus.COMPLETED) { + jobUpdate.setConsecutiveFailures(0); + } else if (status == TaskStatus.FAILED) { + int failures = (job.getConsecutiveFailures() == null ? 0 : job.getConsecutiveFailures()) + 1; + jobUpdate.setConsecutiveFailures(failures); + if (failures >= normalizeMaxRetry(job.getMaxRetry())) { + jobUpdate.setEnabled(0); + jobUpdate.setNextFireTime(null); + } + } + cronJobMapper.updateById(jobUpdate); + + log.debug("Cron 任务终态已同步: cronJobId={}, taskNo={}, status={}", job.getId(), task.getTaskNo(), mappedStatus); + } + + @Transactional + public void disableByRoleId(Long roleId, String reason) { + if (roleId == null) { + return; + } + List jobs = cronJobMapper.selectList(new LambdaQueryWrapper() + .eq(CronJob::getRoleId, roleId) + .eq(CronJob::getIsDeleted, 0) + .eq(CronJob::getEnabled, 1)); + + for (CronJob job : jobs) { + CronJob update = new CronJob(); + update.setId(job.getId()); + update.setEnabled(0); + update.setNextFireTime(null); + update.setUpdatedAt(LocalDateTime.now()); + cronJobMapper.updateById(update); + log.info("Cron 任务因岗位变更被禁用: cronJobId={}, jobName={}, reason={}", job.getId(), job.getJobName(), reason); + } + } + + public CronJob getOwnedJob(Long id, String creatorId) { + CronJob job = cronJobMapper.selectById(id); + if (job == null || (job.getIsDeleted() != null && job.getIsDeleted() == 1)) { + throw new ResourceNotFoundException("定时任务不存在: id=" + id); + } + if (!Objects.equals(job.getCreatorId(), creatorId)) { + throw new ForbiddenOperationException("无权限操作该定时任务"); + } + return job; + } + + public CronJobResponse toResponse(CronJob job, boolean includePreview) { + CronJobResponse response = new CronJobResponse(); + response.setId(job.getId()); + response.setJobName(job.getJobName()); + response.setCreatorId(job.getCreatorId()); + response.setCreatorName(job.getCreatorName()); + response.setRoleId(job.getRoleId()); + response.setRoleName(job.getRoleName()); + response.setModelId(job.getModelId()); + response.setScheduleType(job.getScheduleType()); + response.setCronExpr(job.getCronExpr()); + response.setIntervalMs(job.getIntervalMs()); + response.setRunAt(job.getRunAt()); + response.setTimezone(job.getTimezone()); + response.setTaskContent(job.getTaskContent()); + response.setEnabled(job.getEnabled() != null && job.getEnabled() == 1); + response.setDeleteAfterRun(job.getDeleteAfterRun() != null && job.getDeleteAfterRun() == 1); + response.setMaxRetry(job.getMaxRetry()); + response.setConsecutiveFailures(job.getConsecutiveFailures()); + response.setNextFireTime(job.getNextFireTime()); + response.setNotifyMode(job.getNotifyMode()); + response.setNotifyTarget(job.getNotifyTarget()); + response.setTotalRuns(job.getTotalRuns()); + response.setLastRunTime(job.getLastRunTime()); + response.setLastRunStatus(job.getLastRunStatus()); + response.setCreatedAt(job.getCreatedAt()); + response.setUpdatedAt(job.getUpdatedAt()); + + if (includePreview) { + response.setNextFireTimes(cronExpressionHelper.previewNextFireTimes( + job.getScheduleType(), job.getCronExpr(), job.getIntervalMs(), job.getRunAt(), job.getTimezone(), 5)); + } + return response; + } + + public CronJobRunResponse toRunResponse(CronJobRun run) { + CronJobRunResponse response = new CronJobRunResponse(); + response.setId(run.getId()); + response.setCronJobId(run.getCronJobId()); + response.setTaskNo(run.getTaskNo()); + response.setCreatorId(run.getCreatorId()); + response.setRoleId(run.getRoleId()); + response.setStatus(run.getStatus()); + response.setTriggerType(run.getTriggerType()); + response.setPlannedFireTime(run.getPlannedFireTime()); + response.setStartedAt(run.getStartedAt()); + response.setFinishedAt(run.getFinishedAt()); + response.setDurationMs(run.getDurationMs()); + response.setErrorMessage(run.getErrorMessage()); + response.setCreatedAt(run.getCreatedAt()); + return response; + } + + public List previewSchedule(String scheduleType, String cronExpr, Long intervalMs, + LocalDateTime runAt, String timezone, Integer limit) { + cronExpressionHelper.validateSchedule(scheduleType, cronExpr, intervalMs, runAt, timezone); + int size = limit == null || limit <= 0 ? 5 : Math.min(limit, 10); + return cronExpressionHelper.previewNextFireTimes( + scheduleType, cronExpr, intervalMs, runAt, timezone, size); + } + + private void enforceQuota(String creatorId, Long roleId) { + long userCount = cronJobMapper.selectCount(new LambdaQueryWrapper() + .eq(CronJob::getIsDeleted, 0) + .eq(CronJob::getCreatorId, creatorId)); + if (userCount >= cronConfig.getMaxJobsPerUser()) { + throw new IllegalArgumentException("已达到每用户定时任务上限: " + cronConfig.getMaxJobsPerUser()); + } + + long roleCount = cronJobMapper.selectCount(new LambdaQueryWrapper() + .eq(CronJob::getIsDeleted, 0) + .eq(CronJob::getRoleId, roleId)); + if (roleCount >= cronConfig.getMaxJobsPerRole()) { + throw new IllegalArgumentException("已达到每岗位定时任务上限: " + cronConfig.getMaxJobsPerRole()); + } + } + + private void validateRoleVisible(Long roleId, String creatorId) { + RoleEntity role = roleMapper.selectById(roleId); + if (role == null || Boolean.TRUE.equals(role.getIsDeleted())) { + throw new IllegalArgumentException("岗位不存在: roleId=" + roleId); + } + if (!"active".equalsIgnoreCase(role.getStatus())) { + throw new IllegalArgumentException("岗位不可用: status=" + role.getStatus()); + } + boolean visible = Boolean.TRUE.equals(role.getIsPublic()) || Objects.equals(role.getCreatorId(), creatorId); + if (!visible) { + throw new ForbiddenOperationException("当前用户无权访问该岗位"); + } + } + + private void validateSchedule(String scheduleType, String cronExpr, Long intervalMs, + LocalDateTime runAt, String timezone) { + cronExpressionHelper.validateSchedule(scheduleType, cronExpr, intervalMs, runAt, timezone); + } + + private int normalizeMaxRetry(Integer maxRetry) { + if (maxRetry == null) { + return 3; + } + if (maxRetry < 1 || maxRetry > 20) { + throw new IllegalArgumentException("maxRetry 取值范围为 1~20"); + } + return maxRetry; + } + + private boolean isTerminal(String status) { + return "COMPLETED".equals(status) || "FAILED".equals(status) || "ABORTED".equals(status) || "SKIPPED".equals(status); + } + + private void trimRunHistory(Long cronJobId) { + int maxRuns = cronConfig.getMaxRunsPerJob(); + if (maxRuns <= 0) { + return; + } + List runs = cronJobRunMapper.selectList(new LambdaQueryWrapper() + .eq(CronJobRun::getCronJobId, cronJobId) + .orderByDesc(CronJobRun::getCreatedAt)); + if (runs.size() <= maxRuns) { + return; + } + List removeIds = runs.stream().skip(maxRuns).map(CronJobRun::getId).toList(); + for (Long id : removeIds) { + cronJobRunMapper.deleteById(id); + } + } + + private String truncate(String text) { + if (!StringUtils.hasText(text)) { + return "unknown"; + } + return text.length() > 200 ? text.substring(0, 200) + "..." : text; + } + + private String extractTaskError(Task task) { + if (task == null || !StringUtils.hasText(task.getReportJson())) { + return "unknown"; + } + return truncate(task.getReportJson()); + } +} diff --git a/back/src/main/java/com/linkwork/service/CronScheduler.java b/back/src/main/java/com/linkwork/service/CronScheduler.java new file mode 100644 index 0000000..a03c472 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/CronScheduler.java @@ -0,0 +1,56 @@ +package com.linkwork.service; + +import com.linkwork.config.CronConfig; +import com.linkwork.model.entity.CronJob; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.List; + +@Slf4j +@Component +@RequiredArgsConstructor +public class CronScheduler { + + private final CronConfig cronConfig; + private final DistributedLockService distributedLockService; + private final CronJobService cronJobService; + private final CronJobExecutor cronJobExecutor; + + @Scheduled(fixedDelayString = "${robot.cron.scan-interval-ms:60000}") + public void scanAndDispatch() { + if (!cronConfig.isEnabled()) { + return; + } + + String lockKey = cronConfig.getLockKey(); + String lockValue = distributedLockService.tryAcquireLockByKey(lockKey, cronConfig.getLockTtlSeconds(), 1); + if (lockValue == null) { + return; + } + + try { + LocalDateTime threshold = LocalDateTime.now().plusNanos(cronConfig.getDispatchLeadMs() * 1_000_000); + List dueJobs = cronJobService.findDueJobs(threshold); + if (dueJobs.isEmpty()) { + return; + } + + for (CronJob job : dueJobs) { + LocalDateTime firedAt = job.getNextFireTime(); + try { + cronJobExecutor.dispatchScheduled(job, firedAt); + cronJobService.advanceAfterDispatch(job, firedAt); + } catch (Exception e) { + log.error("Cron 调度执行失败: cronJobId={}, error={}", job.getId(), e.getMessage(), e); + cronJobService.recordDispatchFailure(job, e.getMessage()); + } + } + } finally { + distributedLockService.releaseLockByKey(lockKey, lockValue); + } + } +} diff --git a/back/src/main/java/com/linkwork/service/DistributedLockService.java b/back/src/main/java/com/linkwork/service/DistributedLockService.java new file mode 100644 index 0000000..b5b534c --- /dev/null +++ b/back/src/main/java/com/linkwork/service/DistributedLockService.java @@ -0,0 +1,246 @@ +package com.linkwork.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.lang.Nullable; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Redis 分布式锁服务 + * + * 用于多实例部署时,保证操作原子性: + * - 扩缩容操作:锁键 scale:lock:{serviceId} + * - OSS 目录操作:锁键 oss:lock:{serviceId}:{userId} + * + * 支持降级:Redis 不可用时自动降级为本地锁(用于测试环境) + */ +@Component +@Slf4j +public class DistributedLockService { + + private final StringRedisTemplate redisTemplate; + + /** + * 锁 Key 前缀 + */ + private static final String LOCK_PREFIX = "scale:lock:"; + + /** + * 锁超时时间(秒)- 防止死锁 + */ + private static final int LOCK_TIMEOUT_SECONDS = 30; + + /** + * 获取锁等待时间(秒) + */ + private static final int LOCK_WAIT_SECONDS = 35; + + /** + * Lua 脚本:原子释放锁(只释放自己持有的锁) + */ + private static final String RELEASE_LOCK_SCRIPT = + "if redis.call('get', KEYS[1]) == ARGV[1] then " + + " return redis.call('del', KEYS[1]) " + + "else " + + " return 0 " + + "end"; + + /** + * 本地锁(用于降级,仅在 Redis 不可用时使用) + */ + private final ConcurrentHashMap localLocks = new ConcurrentHashMap<>(); + + /** + * 本地锁标识前缀 + */ + private static final String LOCAL_LOCK_PREFIX = "LOCAL:"; + + /** + * Redis 是否可用 + */ + private volatile boolean redisAvailable = true; + + public DistributedLockService(@Nullable StringRedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + if (redisTemplate == null) { + this.redisAvailable = false; + log.warn("Redis not configured, using local locks only (not suitable for multi-instance deployment)"); + } + } + + /** + * 尝试获取扩缩容分布式锁(按 serviceId) + * + * @param serviceId 服务 ID + * @return lockValue 如果获取成功返回锁标识(用于释放),失败返回 null + */ + public String tryAcquireLock(String serviceId) { + return tryAcquireLockByKey(LOCK_PREFIX + serviceId); + } + + /** + * 尝试获取分布式锁(通用方法,支持自定义锁键) + * + * @param fullKey 完整的锁键(如 "oss:lock:svc1:user1") + * @return lockValue 如果获取成功返回锁标识(用于释放),失败返回 null + */ + public String tryAcquireLockByKey(String fullKey) { + return tryAcquireLockByKey(fullKey, LOCK_TIMEOUT_SECONDS, LOCK_WAIT_SECONDS); + } + + /** + * 尝试获取分布式锁(支持自定义超时时间) + */ + public String tryAcquireLockByKey(String fullKey, int lockTimeoutSeconds, int lockWaitSeconds) { + // 如果 Redis 不可用,降级为本地锁 + if (!redisAvailable || redisTemplate == null) { + return tryAcquireLocalLock(fullKey, lockWaitSeconds); + } + + String lockValue = UUID.randomUUID().toString(); + + long startTime = System.currentTimeMillis(); + long waitMillis = lockWaitSeconds * 1000L; + + while (System.currentTimeMillis() - startTime < waitMillis) { + try { + // SET NX EX:不存在则设置,带过期时间 + Boolean success = redisTemplate.opsForValue() + .setIfAbsent(fullKey, lockValue, lockTimeoutSeconds, TimeUnit.SECONDS); + + if (Boolean.TRUE.equals(success)) { + log.debug("Acquired distributed lock key={}, lockValue={}", fullKey, lockValue); + return lockValue; + } + + // 等待 100ms 后重试 + Thread.sleep(100); + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.warn("Interrupted while waiting for lock key={}", fullKey); + return null; + } catch (Exception e) { + // Redis 连接异常,降级为本地锁 + log.warn("Redis error, falling back to local lock key={}: {}", fullKey, e.getMessage()); + redisAvailable = false; + return tryAcquireLocalLock(fullKey, lockWaitSeconds); + } + } + + log.warn("Failed to acquire lock key={} within {} seconds", fullKey, lockWaitSeconds); + return null; + } + + /** + * 获取本地锁(降级方案) + */ + private String tryAcquireLocalLock(String key, int lockWaitSeconds) { + ReentrantLock lock = localLocks.computeIfAbsent(key, k -> new ReentrantLock(true)); + try { + boolean acquired = lock.tryLock(lockWaitSeconds, TimeUnit.SECONDS); + if (acquired) { + String lockValue = LOCAL_LOCK_PREFIX + UUID.randomUUID().toString(); + log.debug("Acquired local lock key={}, lockValue={}", key, lockValue); + return lockValue; + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.warn("Interrupted while waiting for local lock key={}", key); + } + return null; + } + + /** + * 释放扩缩容分布式锁(按 serviceId) + * + * @param serviceId 服务 ID + * @param lockValue 锁标识(tryAcquireLock 返回的值) + */ + public void releaseLock(String serviceId, String lockValue) { + releaseLockByKey(LOCK_PREFIX + serviceId, lockValue); + } + + /** + * 释放分布式锁(通用方法,支持自定义锁键) + * + * 使用 Lua 脚本保证原子性:只有锁的持有者才能释放 + * + * @param fullKey 完整的锁键 + * @param lockValue 锁标识(tryAcquireLockByKey 返回的值) + */ + public void releaseLockByKey(String fullKey, String lockValue) { + if (lockValue == null) { + return; + } + + // 如果是本地锁,释放本地锁 + if (lockValue.startsWith(LOCAL_LOCK_PREFIX)) { + releaseLocalLock(fullKey); + return; + } + + // 释放 Redis 分布式锁 + if (redisTemplate == null) { + return; + } + + try { + Long result = redisTemplate.execute( + new DefaultRedisScript<>(RELEASE_LOCK_SCRIPT, Long.class), + Collections.singletonList(fullKey), + lockValue + ); + + if (result != null && result == 1) { + log.debug("Released distributed lock key={}", fullKey); + } else { + log.warn("Lock key={} was not held by this instance or already expired", fullKey); + } + } catch (Exception e) { + log.error("Error releasing lock key={}: {}", fullKey, e.getMessage()); + } + } + + /** + * 释放本地锁 + */ + private void releaseLocalLock(String key) { + ReentrantLock lock = localLocks.get(key); + if (lock != null && lock.isHeldByCurrentThread()) { + lock.unlock(); + log.debug("Released local lock key={}", key); + } + } + + /** + * 检查扩缩容锁是否存在(用于测试/诊断) + * + * @param serviceId 服务 ID + * @return true 如果锁存在 + */ + public boolean isLocked(String serviceId) { + return isLockedByKey(LOCK_PREFIX + serviceId); + } + + /** + * 检查指定锁键是否存在(用于测试/诊断) + * + * @param fullKey 完整的锁键 + * @return true 如果锁存在 + */ + public boolean isLockedByKey(String fullKey) { + if (redisTemplate == null) { + ReentrantLock lock = localLocks.get(fullKey); + return lock != null && lock.isLocked(); + } + return Boolean.TRUE.equals(redisTemplate.hasKey(fullKey)); + } +} diff --git a/back/src/main/java/com/linkwork/service/DockerComposeGenerator.java b/back/src/main/java/com/linkwork/service/DockerComposeGenerator.java new file mode 100644 index 0000000..3e65dcf --- /dev/null +++ b/back/src/main/java/com/linkwork/service/DockerComposeGenerator.java @@ -0,0 +1,115 @@ +package com.linkwork.service; + +import com.linkwork.model.dto.MergedConfig; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +/** + * Docker Compose 构建包生成器 + * + * 仅支持 Alone 模式,生成用户可本地构建和启动的 docker-compose.yaml。 + * 镜像由用户在本地通过 docker compose up --build 构建,无需服务端推送。 + */ +@Component +@Slf4j +public class DockerComposeGenerator { + + /** + * 生成 docker-compose.yaml 内容 + * + * @param config 融合后的配置 + * @return docker-compose.yaml 字符串 + */ + public String generateComposeYaml(MergedConfig config) { + log.info("Generating Docker Compose YAML for service {}", config.getServiceId()); + + StringBuilder yaml = new StringBuilder(); + yaml.append(generateHeader(config)); + yaml.append(generateServices(config)); + yaml.append(generateVolumes()); + return yaml.toString(); + } + + private String generateHeader(MergedConfig config) { + return String.format(""" +# docker-compose.yaml - AI Worker Service +# Service ID: %s +# User ID: %s +# +# 使用方式: +# 1. 首次启动(构建镜像 + 启动容器): +# docker compose up --build -d +# 2. 查看日志: +# docker compose logs -f agent +# 3. 停止服务: +# docker compose down +# 4. 后续启动(已构建过镜像,无需重新构建): +# docker compose up -d +# +# 注意: +# - 首次构建需要拉取基础镜像和安装依赖,耗时较长(约 5-10 分钟) +# - 需要能访问 docker.momo.com 拉取基础镜像 +# - 需要能访问 git.wemomo.com 克隆 SDK 仓库 + +""", config.getServiceId(), config.getUserId()); + } + + private String generateServices(MergedConfig config) { + String workstationId = config.getWorkstationId() != null + ? config.getWorkstationId() : config.getServiceId(); + + StringBuilder sb = new StringBuilder(); + sb.append("services:\n"); + sb.append(" agent:\n"); + sb.append(" build:\n"); + sb.append(" context: .\n"); + sb.append(" dockerfile: Dockerfile\n"); + sb.append(String.format(" image: ai-worker-%s:latest\n", config.getServiceId())); + sb.append(String.format(" container_name: ai-worker-%s\n", config.getServiceId())); + sb.append(" user: root\n"); + sb.append(" command: [\"/opt/agent/start-single.sh\"]\n"); + sb.append(" environment:\n"); + + appendEnv(sb, "WORKSTATION_ID", workstationId); + appendEnv(sb, "REDIS_URL", config.getRedisUrl()); + appendEnv(sb, "CONFIG_FILE", "/opt/agent/config.json"); + appendEnv(sb, "IDLE_TIMEOUT", "86400"); + appendEnv(sb, "SERVICE_ID", config.getServiceId()); + appendEnv(sb, "USER_ID", config.getUserId()); + appendEnv(sb, "API_BASE_URL", config.getApiBaseUrl()); + appendEnv(sb, "WS_BASE_URL", config.getWsBaseUrl()); + appendEnv(sb, "LLM_GATEWAY_URL", config.getLlmGatewayUrl()); + if (config.getRoleId() != null) { + appendEnv(sb, "ROLE_ID", String.valueOf(config.getRoleId())); + } + + sb.append(" volumes:\n"); + sb.append(" - workspace:/workspace\n"); + sb.append(" restart: \"no\"\n"); + sb.append(" deploy:\n"); + sb.append(" resources:\n"); + sb.append(" limits:\n"); + sb.append(String.format(" cpus: '%s'\n", config.getAgentResources().getCpuLimit())); + sb.append(String.format(" memory: %s\n", config.getAgentResources().getMemoryLimit())); + sb.append(" reservations:\n"); + sb.append(String.format(" cpus: '%s'\n", config.getAgentResources().getCpuRequest())); + sb.append(String.format(" memory: %s\n", config.getAgentResources().getMemoryRequest())); + + return sb.toString(); + } + + private void appendEnv(StringBuilder sb, String key, String value) { + if (StringUtils.hasText(value)) { + sb.append(String.format(" - %s=%s\n", key, value)); + } + } + + private String generateVolumes() { + return """ + +volumes: + workspace: +"""; + } +} diff --git a/back/src/main/java/com/linkwork/service/FileNodeService.java b/back/src/main/java/com/linkwork/service/FileNodeService.java new file mode 100644 index 0000000..dcdef96 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/FileNodeService.java @@ -0,0 +1,398 @@ +package com.linkwork.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.linkwork.common.FileConflictException; +import com.linkwork.common.ForbiddenOperationException; +import com.linkwork.common.ResourceNotFoundException; +import com.linkwork.mapper.FileNodeMapper; +import com.linkwork.mapper.RobotFileMapper; +import com.linkwork.model.dto.CreateFolderRequest; +import com.linkwork.model.dto.FileNodeResponse; +import com.linkwork.model.entity.FileNodeEntity; +import com.linkwork.model.entity.RobotFile; +import com.linkwork.service.memory.MemoryService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class FileNodeService { + + private static final int NODE_NAME_MAX_LENGTH = 512; + private static final String DELETED_NAME_MARKER = "__deleted__"; + + private final FileNodeMapper fileNodeMapper; + private final RobotFileMapper robotFileMapper; + private final NfsStorageService nfsStorageService; + + @Autowired(required = false) + private MemoryService memoryService; + + public FileNodeResponse createFolder(CreateFolderRequest request, String userId) { + validateSpaceType(request.getSpaceType(), request.getWorkstationId()); + String normalizedSpace = request.getSpaceType().toUpperCase(Locale.ROOT); + + if (!StringUtils.hasText(request.getName()) || request.getName().trim().isEmpty()) { + throw new IllegalArgumentException("目录名称不能为空"); + } + String folderName = request.getName().trim(); + + validateParentId(request.getParentId(), userId, normalizedSpace, request.getWorkstationId()); + + FileNodeEntity existing = findSameNameNode(userId, normalizedSpace, + request.getWorkstationId(), request.getParentId(), folderName); + if (existing != null) { + throw new FileConflictException("目标目录已存在同名节点", + existing.getFileId() != null ? existing.getFileId() : existing.getNodeId(), + existing.getName(), existing.getEntryType(), + null, existing.getUpdatedAt()); + } + + FileNodeEntity node = new FileNodeEntity(); + node.setNodeId(UUID.randomUUID().toString().replace("-", "")); + node.setParentId(request.getParentId()); + node.setEntryType("DIR"); + node.setName(folderName); + node.setSpaceType(normalizedSpace); + node.setWorkstationId("WORKSTATION".equals(normalizedSpace) ? request.getWorkstationId() : null); + node.setUserId(userId); + node.setCreatedAt(LocalDateTime.now()); + node.setUpdatedAt(LocalDateTime.now()); + + fileNodeMapper.insert(node); + return toResponse(node, false); + } + + public List listChildren(String spaceType, String workstationId, + String parentId, String userId) { + validateSpaceType(spaceType, workstationId); + String normalizedSpace = spaceType.toUpperCase(Locale.ROOT); + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(FileNodeEntity::getUserId, userId) + .eq(FileNodeEntity::getSpaceType, normalizedSpace) + .isNull(FileNodeEntity::getDeletedAt) + .orderByAsc(FileNodeEntity::getEntryType) + .orderByAsc(FileNodeEntity::getName); + + if ("WORKSTATION".equals(normalizedSpace)) { + wrapper.eq(FileNodeEntity::getWorkstationId, workstationId); + } else { + wrapper.isNull(FileNodeEntity::getWorkstationId); + } + + if (StringUtils.hasText(parentId)) { + wrapper.eq(FileNodeEntity::getParentId, parentId); + } else { + wrapper.isNull(FileNodeEntity::getParentId); + } + + List nodes = fileNodeMapper.selectList(wrapper); + + Set dirNodeIds = nodes.stream() + .filter(n -> "DIR".equals(n.getEntryType())) + .map(FileNodeEntity::getNodeId) + .collect(Collectors.toSet()); + + Set dirsWithChildren = new HashSet<>(); + if (!dirNodeIds.isEmpty()) { + LambdaQueryWrapper childCheck = new LambdaQueryWrapper() + .in(FileNodeEntity::getParentId, dirNodeIds) + .isNull(FileNodeEntity::getDeletedAt) + .select(FileNodeEntity::getParentId) + .groupBy(FileNodeEntity::getParentId); + List childResults = fileNodeMapper.selectList(childCheck); + childResults.forEach(c -> dirsWithChildren.add(c.getParentId())); + } + + Set fileIds = nodes.stream() + .filter(n -> "FILE".equals(n.getEntryType()) && StringUtils.hasText(n.getFileId())) + .map(FileNodeEntity::getFileId) + .collect(Collectors.toSet()); + + Map fileMap = new HashMap<>(); + if (!fileIds.isEmpty()) { + LambdaQueryWrapper fileWrapper = new LambdaQueryWrapper() + .in(RobotFile::getFileId, fileIds) + .isNull(RobotFile::getDeletedAt); + robotFileMapper.selectList(fileWrapper) + .forEach(f -> fileMap.put(f.getFileId(), f)); + } + + return nodes.stream().map(node -> { + boolean hasChildren = dirsWithChildren.contains(node.getNodeId()); + FileNodeResponse resp = toResponse(node, hasChildren); + if ("FILE".equals(node.getEntryType()) && node.getFileId() != null) { + RobotFile rf = fileMap.get(node.getFileId()); + if (rf != null) { + resp.setFileSize(rf.getFileSize()); + resp.setFileType(rf.getFileType()); + resp.setParseStatus(rf.getParseStatus()); + resp.setMemoryIndexStatus(rf.getMemoryIndexStatus()); + } + } + return resp; + }).toList(); + } + + public FileNodeEntity createFileNode(String fileName, String spaceType, String workstationId, + String userId, String fileId, String parentId) { + validateParentId(parentId, userId, spaceType, workstationId); + + FileNodeEntity node = new FileNodeEntity(); + node.setNodeId(UUID.randomUUID().toString().replace("-", "")); + node.setParentId(parentId); + node.setEntryType("FILE"); + node.setName(fileName); + node.setSpaceType(spaceType.toUpperCase(Locale.ROOT)); + node.setWorkstationId("WORKSTATION".equals(spaceType.toUpperCase(Locale.ROOT)) ? workstationId : null); + node.setUserId(userId); + node.setFileId(fileId); + node.setCreatedAt(LocalDateTime.now()); + node.setUpdatedAt(LocalDateTime.now()); + fileNodeMapper.insert(node); + return node; + } + + public void renameNode(String nodeId, String newName, String userId) { + FileNodeEntity node = findActiveNode(nodeId); + if (!Objects.equals(node.getUserId(), userId)) { + throw new ForbiddenOperationException("无权限操作该节点"); + } + if (!StringUtils.hasText(newName) || newName.trim().isEmpty()) { + throw new IllegalArgumentException("名称不能为空"); + } + String trimmedName = newName.trim(); + + FileNodeEntity conflict = findSameNameNode(userId, node.getSpaceType(), + node.getWorkstationId(), node.getParentId(), trimmedName); + if (conflict != null && !conflict.getNodeId().equals(nodeId)) { + throw new FileConflictException("目标目录已存在同名节点", + conflict.getFileId() != null ? conflict.getFileId() : conflict.getNodeId(), + conflict.getName(), conflict.getEntryType(), + null, conflict.getUpdatedAt()); + } + + node.setName(trimmedName); + node.setUpdatedAt(LocalDateTime.now()); + fileNodeMapper.updateById(node); + + if ("FILE".equals(node.getEntryType()) && StringUtils.hasText(node.getFileId())) { + RobotFile file = robotFileMapper.selectOne(new LambdaQueryWrapper() + .eq(RobotFile::getFileId, node.getFileId()) + .isNull(RobotFile::getDeletedAt) + .last("limit 1")); + if (file != null) { + file.setFileName(trimmedName); + file.setUpdatedAt(LocalDateTime.now()); + robotFileMapper.updateById(file); + } + } + } + + public void deleteNode(String nodeId, String userId) { + FileNodeEntity node = findActiveNode(nodeId); + if (!Objects.equals(node.getUserId(), userId)) { + throw new ForbiddenOperationException("无权限操作该节点"); + } + softDeleteRecursive(node); + } + + public void updateNode(FileNodeEntity node) { + fileNodeMapper.updateById(node); + } + + public FileNodeEntity findByFileId(String fileId) { + return findByFileId(fileId, null, null, null); + } + + public FileNodeEntity findByFileId(String fileId, String userId, String spaceType, String workstationId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(FileNodeEntity::getFileId, fileId) + .isNull(FileNodeEntity::getDeletedAt) + .orderByDesc(FileNodeEntity::getUpdatedAt) + .last("limit 1"); + + if (StringUtils.hasText(userId)) { + wrapper.eq(FileNodeEntity::getUserId, userId); + } + + if (StringUtils.hasText(spaceType)) { + String normalized = spaceType.toUpperCase(Locale.ROOT); + wrapper.eq(FileNodeEntity::getSpaceType, normalized); + if ("WORKSTATION".equals(normalized) && StringUtils.hasText(workstationId)) { + wrapper.eq(FileNodeEntity::getWorkstationId, workstationId); + } else if ("USER".equals(normalized)) { + wrapper.isNull(FileNodeEntity::getWorkstationId); + } + } + + return fileNodeMapper.selectOne(wrapper); + } + + private void softDeleteRecursive(FileNodeEntity node) { + LocalDateTime now = LocalDateTime.now(); + node.setDeletedAt(now); + node.setUpdatedAt(now); + node.setName(buildDeletedTombstoneName(node.getName(), node.getNodeId())); + fileNodeMapper.updateById(node); + + if ("FILE".equals(node.getEntryType()) && StringUtils.hasText(node.getFileId())) { + cleanupFileResources(node.getFileId()); + } + + if ("DIR".equals(node.getEntryType())) { + List children = fileNodeMapper.selectList( + new LambdaQueryWrapper() + .eq(FileNodeEntity::getParentId, node.getNodeId()) + .isNull(FileNodeEntity::getDeletedAt)); + for (FileNodeEntity child : children) { + softDeleteRecursive(child); + } + } + } + + private String buildDeletedTombstoneName(String originalName, String nodeId) { + String baseName = StringUtils.hasText(originalName) ? originalName : "node"; + String safeNodeId = StringUtils.hasText(nodeId) + ? nodeId + : UUID.randomUUID().toString().replace("-", ""); + String suffix = DELETED_NAME_MARKER + safeNodeId; + int allowedBaseLength = NODE_NAME_MAX_LENGTH - suffix.length(); + if (allowedBaseLength <= 0) { + return suffix.substring(Math.max(0, suffix.length() - NODE_NAME_MAX_LENGTH)); + } + if (baseName.length() > allowedBaseLength) { + baseName = baseName.substring(0, allowedBaseLength); + } + return baseName + suffix; + } + + private void cleanupFileResources(String fileId) { + RobotFile file = robotFileMapper.selectOne(new LambdaQueryWrapper() + .eq(RobotFile::getFileId, fileId) + .isNull(RobotFile::getDeletedAt) + .last("limit 1")); + if (file == null) return; + + file.setDeletedAt(LocalDateTime.now()); + file.setUpdatedAt(LocalDateTime.now()); + robotFileMapper.updateById(file); + + try { + nfsStorageService.deleteFile(file.getOssPath()); + } catch (Exception e) { + log.warn("目录级联删除原文件失败: fileId={}, path={}, err={}", fileId, file.getOssPath(), e.getMessage()); + } + if (StringUtils.hasText(file.getParsedOssPath())) { + try { + nfsStorageService.deleteFile(file.getParsedOssPath()); + } catch (Exception e) { + log.warn("目录级联删除解析文件失败: fileId={}, path={}, err={}", fileId, file.getParsedOssPath(), e.getMessage()); + } + } + if (memoryService != null) { + try { + String source = StringUtils.hasText(file.getParsedOssPath()) ? file.getParsedOssPath() : file.getOssPath(); + memoryService.deleteSource(file.getWorkstationId(), file.getUserId(), source); + } catch (Exception e) { + log.warn("目录级联删除 Memory 索引失败: fileId={}, err={}", fileId, e.getMessage()); + } + } + } + + /** + * 校验 parentId 合法性:存在、是目录、同用户、同空间。 + * parentId 为空表示根目录,直接放行。 + */ + public void validateParentId(String parentId, String userId, String spaceType, String workstationId) { + if (!StringUtils.hasText(parentId)) return; + + FileNodeEntity parent = findActiveNode(parentId); + if (!"DIR".equals(parent.getEntryType())) { + throw new IllegalArgumentException("父节点不是目录"); + } + if (!Objects.equals(parent.getUserId(), userId)) { + throw new ForbiddenOperationException("无权在该目录下操作"); + } + String normalizedSpace = spaceType.toUpperCase(Locale.ROOT); + if (!parent.getSpaceType().equals(normalizedSpace)) { + throw new IllegalArgumentException("父节点与目标空间类型不匹配"); + } + String expectedWs = "WORKSTATION".equals(normalizedSpace) ? workstationId : null; + if (!Objects.equals(parent.getWorkstationId(), expectedWs)) { + throw new IllegalArgumentException("父节点与目标工作站不匹配"); + } + } + + FileNodeEntity findSameNameNode(String userId, String spaceType, String workstationId, + String parentId, String name) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(FileNodeEntity::getUserId, userId) + .eq(FileNodeEntity::getSpaceType, spaceType) + .eq(FileNodeEntity::getName, name) + .isNull(FileNodeEntity::getDeletedAt) + .last("limit 1"); + + if ("WORKSTATION".equals(spaceType)) { + wrapper.eq(FileNodeEntity::getWorkstationId, workstationId); + } else { + wrapper.isNull(FileNodeEntity::getWorkstationId); + } + + if (StringUtils.hasText(parentId)) { + wrapper.eq(FileNodeEntity::getParentId, parentId); + } else { + wrapper.isNull(FileNodeEntity::getParentId); + } + + return fileNodeMapper.selectOne(wrapper); + } + + private FileNodeEntity findActiveNode(String nodeId) { + FileNodeEntity node = fileNodeMapper.selectOne(new LambdaQueryWrapper() + .eq(FileNodeEntity::getNodeId, nodeId) + .isNull(FileNodeEntity::getDeletedAt) + .last("limit 1")); + if (node == null) { + throw new ResourceNotFoundException("节点不存在: " + nodeId); + } + return node; + } + + private FileNodeResponse toResponse(FileNodeEntity node, boolean hasChildren) { + FileNodeResponse resp = new FileNodeResponse(); + resp.setNodeId(node.getNodeId()); + resp.setParentId(node.getParentId()); + resp.setEntryType(node.getEntryType()); + resp.setName(node.getName()); + resp.setSpaceType(node.getSpaceType()); + resp.setWorkstationId(node.getWorkstationId()); + resp.setFileId(node.getFileId()); + resp.setCreatedAt(node.getCreatedAt()); + resp.setUpdatedAt(node.getUpdatedAt()); + resp.setHasChildren(hasChildren); + return resp; + } + + private void validateSpaceType(String spaceType, String workstationId) { + if (!StringUtils.hasText(spaceType)) { + throw new IllegalArgumentException("spaceType 不能为空"); + } + String normalized = spaceType.toUpperCase(Locale.ROOT); + if (!"USER".equals(normalized) && !"WORKSTATION".equals(normalized)) { + throw new IllegalArgumentException("spaceType 仅支持 USER 或 WORKSTATION"); + } + if ("WORKSTATION".equals(normalized) && !StringUtils.hasText(workstationId)) { + throw new IllegalArgumentException("WORKSTATION 空间必须提供 workstationId"); + } + } +} diff --git a/back/src/main/java/com/linkwork/service/FileParseConsumer.java b/back/src/main/java/com/linkwork/service/FileParseConsumer.java new file mode 100644 index 0000000..28ca92c --- /dev/null +++ b/back/src/main/java/com/linkwork/service/FileParseConsumer.java @@ -0,0 +1,130 @@ +package com.linkwork.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.linkwork.mapper.RobotFileMapper; +import com.linkwork.model.entity.RobotFile; +import com.linkwork.service.memory.DocumentParserService; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; + +@Slf4j +@Service +@RequiredArgsConstructor +public class FileParseConsumer { + + private static final String FILE_PARSE_QUEUE_KEY = "file:parse:jobs"; + private static final int POLL_INTERVAL_MS = 1000; + + private final StringRedisTemplate redisTemplate; + private final RobotFileMapper robotFileMapper; + private final NfsStorageService nfsStorageService; + private final FileService fileService; + + @Autowired(required = false) + private DocumentParserService documentParserService; + + private final AtomicBoolean running = new AtomicBoolean(true); + private ExecutorService executorService; + + @PostConstruct + public void start() { + executorService = Executors.newSingleThreadExecutor(r -> { + Thread t = new Thread(r, "file-parse-consumer"); + t.setDaemon(true); + return t; + }); + executorService.submit(this::consumeLoop); + log.info("FileParseConsumer started, queue={}", FILE_PARSE_QUEUE_KEY); + } + + @PreDestroy + public void stop() { + running.set(false); + if (executorService != null) { + executorService.shutdownNow(); + } + log.info("FileParseConsumer stopped"); + } + + private void consumeLoop() { + while (running.get()) { + try { + String fileId = redisTemplate.opsForList().rightPop(FILE_PARSE_QUEUE_KEY); + if (fileId == null) { + Thread.sleep(POLL_INTERVAL_MS); + continue; + } + processFile(fileId); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } catch (Exception e) { + log.error("消费文件解析任务失败", e); + try { + Thread.sleep(POLL_INTERVAL_MS * 2L); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + break; + } + } + } + } + + private void processFile(String fileId) { + RobotFile file = robotFileMapper.selectOne(new LambdaQueryWrapper() + .eq(RobotFile::getFileId, fileId) + .isNull(RobotFile::getDeletedAt) + .last("limit 1")); + if (file == null) { + log.warn("文件解析任务找不到文件记录: fileId={}", fileId); + return; + } + if (documentParserService == null) { + log.warn("DocumentParserService 未启用,跳过解析: fileId={}", fileId); + file.setParseStatus("FAILED"); + file.setUpdatedAt(LocalDateTime.now()); + robotFileMapper.updateById(file); + return; + } + + Path tempPath = null; + try { + tempPath = nfsStorageService.downloadToTempFile(file.getOssPath()); + String text = documentParserService.parseFile(tempPath); + String parsedOssPath = fileService.buildParsedPath(file.getOssPath()); + nfsStorageService.uploadTextToPath(text, parsedOssPath); + + file.setParsedOssPath(parsedOssPath); + file.setParseStatus("PARSED"); + file.setUpdatedAt(LocalDateTime.now()); + robotFileMapper.updateById(file); + + fileService.triggerMemoryIndex(file); + } catch (Exception e) { + log.error("文件解析失败: fileId={}", fileId, e); + file.setParseStatus("FAILED"); + file.setUpdatedAt(LocalDateTime.now()); + robotFileMapper.updateById(file); + } finally { + if (tempPath != null) { + try { + Files.deleteIfExists(tempPath); + } catch (Exception e) { + log.warn("删除临时文件失败: {}", tempPath, e); + } + } + } + } +} diff --git a/back/src/main/java/com/linkwork/service/FileService.java b/back/src/main/java/com/linkwork/service/FileService.java new file mode 100644 index 0000000..1558c7a --- /dev/null +++ b/back/src/main/java/com/linkwork/service/FileService.java @@ -0,0 +1,934 @@ +package com.linkwork.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.linkwork.common.FileConflictException; +import com.linkwork.common.ForbiddenOperationException; +import com.linkwork.common.ResourceNotFoundException; +import com.linkwork.mapper.RobotFileMapper; +import com.linkwork.model.enums.ConflictPolicy; +import com.linkwork.model.dto.FileMentionResponse; +import com.linkwork.model.dto.FileResponse; +import com.linkwork.model.dto.FileTransferRequest; +import com.linkwork.model.dto.MemoryIndexJob; +import com.linkwork.model.entity.RobotFile; +import com.linkwork.model.entity.RoleEntity; +import com.linkwork.service.memory.MemoryService; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.security.MessageDigest; +import java.time.LocalDateTime; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; + +@Slf4j +@Service +@RequiredArgsConstructor +public class FileService { + + private static final Set ALLOWED_TYPES = Set.of( + "txt", "md", "csv", "doc", "docx", "pdf", "ppt", "pptx", "xlsx", "xls", + "jpg", "jpeg", "png", "gif" + ); + private static final long MAX_FILE_SIZE = 100L * 1024 * 1024; + private static final Set PARSE_REQUIRED_TYPES = Set.of("doc", "docx", "pdf", "ppt", "pptx"); + private static final Set MEMORY_DIRECT_TYPES = Set.of("txt", "md", "csv"); + private static final Set MEMORY_SKIP_TYPES = Set.of("xlsx", "xls", "jpg", "jpeg", "png", "gif"); + private static final String FILE_PARSE_QUEUE_KEY = "file:parse:jobs"; + private static final String FILE_TRANSFER_DEDUP_KEY_PREFIX = "file:transfer:dedup"; + private static final long FILE_TRANSFER_DEDUP_SECONDS = 5L; + + private final RobotFileMapper robotFileMapper; + private final NfsStorageService nfsStorageService; + private final StringRedisTemplate redisTemplate; + private final ObjectMapper objectMapper; + private final RoleService roleService; + private final com.linkwork.config.MemoryConfig memoryConfig; + private final FileNodeService fileNodeService; + + @Autowired(required = false) + private MemoryService memoryService; + + public FileResponse uploadFile(MultipartFile file, String spaceType, String workstationId, String userId) { + return uploadFile(file, spaceType, workstationId, userId, null, null); + } + + public FileResponse uploadFile(MultipartFile file, String spaceType, String workstationId, + String userId, String conflictPolicyStr) { + return uploadFile(file, spaceType, workstationId, userId, conflictPolicyStr, null); + } + + public FileResponse uploadFile(MultipartFile file, String spaceType, String workstationId, + String userId, String conflictPolicyStr, String parentId) { + validateUpload(file, spaceType, workstationId, userId); + String normalizedSpace = spaceType.toUpperCase(Locale.ROOT); + fileNodeService.validateParentId(parentId, userId, normalizedSpace, workstationId); + ConflictPolicy policy = ConflictPolicy.fromString(conflictPolicyStr); + String originalName = file.getOriginalFilename(); + + com.linkwork.model.entity.FileNodeEntity existingNode = fileNodeService.findSameNameNode( + userId, normalizedSpace, workstationId, parentId, originalName); + if (existingNode != null) { + switch (policy) { + case REJECT -> { + RobotFile existingFile = "FILE".equals(existingNode.getEntryType()) && existingNode.getFileId() != null + ? findActiveByFileId(existingNode.getFileId()) : null; + throw new FileConflictException( + "目标目录已存在同名" + ("DIR".equals(existingNode.getEntryType()) ? "目录" : "文件"), + existingNode.getFileId() != null ? existingNode.getFileId() : existingNode.getNodeId(), + existingNode.getName(), existingNode.getEntryType(), + existingFile != null ? existingFile.getFileSize() : null, + existingNode.getUpdatedAt()); + } + case OVERWRITE -> { + if ("DIR".equals(existingNode.getEntryType())) { + throw new IllegalArgumentException("无法用文件覆盖目录"); + } + RobotFile existingFile = findActiveByFileId(existingNode.getFileId()); + return overwriteUpload(existingFile, file); + } + case RENAME -> originalName = generateUniqueNodeName(userId, normalizedSpace, workstationId, parentId, originalName); + } + } + + String ext = getExtension(originalName); + String fileId = UUID.randomUUID().toString().replace("-", ""); + String ossPath = buildOssPath(normalizedSpace, workstationId, userId, fileId, ext); + + RobotFile robotFile = new RobotFile(); + robotFile.setFileId(fileId); + robotFile.setFileName(originalName); + robotFile.setFileSize(file.getSize()); + robotFile.setFileType(ext); + robotFile.setContentType(file.getContentType()); + robotFile.setSpaceType(normalizedSpace); + robotFile.setWorkstationId(workstationId); + robotFile.setUserId(userId); + robotFile.setOssPath(ossPath); + robotFile.setMemoryIndexStatus("NONE"); + robotFile.setParseStatus(PARSE_REQUIRED_TYPES.contains(ext) ? "NONE" : "SKIP"); + robotFile.setFileHash(computeSha256(file)); + robotFile.setCreatedAt(LocalDateTime.now()); + robotFile.setUpdatedAt(LocalDateTime.now()); + + try { + nfsStorageService.uploadFileToPath(file, ossPath); + } catch (IOException e) { + throw new IllegalStateException("上传文件到存储失败", e); + } + robotFileMapper.insert(robotFile); + + fileNodeService.createFileNode(originalName, normalizedSpace, workstationId, userId, fileId, parentId); + + if ("NONE".equals(robotFile.getParseStatus())) { + robotFile.setParseStatus("PARSING"); + robotFile.setUpdatedAt(LocalDateTime.now()); + robotFileMapper.updateById(robotFile); + redisTemplate.opsForList().leftPush(FILE_PARSE_QUEUE_KEY, fileId); + } else if (MEMORY_DIRECT_TYPES.contains(ext)) { + triggerMemoryIndex(robotFile); + } + + return toResponse(robotFile); + } + + private FileResponse overwriteUpload(RobotFile target, MultipartFile newFile) { + String ext = getExtension(newFile.getOriginalFilename()); + try { + nfsStorageService.uploadFileToPath(newFile, target.getOssPath()); + } catch (IOException e) { + throw new IllegalStateException("覆盖上传文件失败", e); + } + + if (StringUtils.hasText(target.getParsedOssPath())) { + try { + nfsStorageService.deleteFile(target.getParsedOssPath()); + } catch (Exception e) { + log.warn("删除旧解析文件失败: fileId={}, err={}", target.getFileId(), e.getMessage()); + } + } + + target.setFileSize(newFile.getSize()); + target.setFileType(ext); + target.setContentType(newFile.getContentType()); + target.setFileHash(computeSha256(newFile)); + target.setParseStatus(PARSE_REQUIRED_TYPES.contains(ext) ? "PARSING" : "SKIP"); + target.setMemoryIndexStatus("NONE"); + target.setParsedOssPath(null); + target.setUpdatedAt(LocalDateTime.now()); + robotFileMapper.updateById(target); + + if (PARSE_REQUIRED_TYPES.contains(ext)) { + redisTemplate.opsForList().leftPush(FILE_PARSE_QUEUE_KEY, target.getFileId()); + } else if (MEMORY_DIRECT_TYPES.contains(ext)) { + triggerMemoryIndex(target); + } + + return toResponse(target); + } + + public Map listFiles(String spaceType, String workstationId, String fileType, + String keyword, Integer page, Integer pageSize, String userId) { + validateSpaceType(spaceType, workstationId); + int currentPage = page == null || page < 1 ? 1 : page; + int size = pageSize == null || pageSize < 1 ? 20 : pageSize; + if (size > 100) { + size = 100; + } + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(RobotFile::getUserId, userId) + .eq(RobotFile::getSpaceType, spaceType.toUpperCase(Locale.ROOT)) + .isNull(RobotFile::getDeletedAt) + .orderByDesc(RobotFile::getCreatedAt); + + if ("WORKSTATION".equalsIgnoreCase(spaceType)) { + wrapper.eq(RobotFile::getWorkstationId, workstationId); + } + if (StringUtils.hasText(fileType)) { + wrapper.eq(RobotFile::getFileType, fileType.toLowerCase(Locale.ROOT)); + } + if (StringUtils.hasText(keyword)) { + wrapper.like(RobotFile::getFileName, keyword.trim()); + } + + Page result = robotFileMapper.selectPage(new Page<>(currentPage, size), wrapper); + List items = result.getRecords().stream().map(this::toResponse).toList(); + + Map pagination = new HashMap<>(); + pagination.put("page", result.getCurrent()); + pagination.put("pageSize", result.getSize()); + pagination.put("total", result.getTotal()); + pagination.put("totalPages", result.getPages()); + + Map payload = new HashMap<>(); + payload.put("items", items); + payload.put("pagination", pagination); + return payload; + } + + public FileResponse getFileDetail(String fileId, String userId) { + RobotFile file = findActiveByFileId(fileId); + checkPermission(file, userId); + return toResponse(file); + } + + public DownloadInfo getDownloadInfo(String fileId, String userId) { + RobotFile file = findActiveByFileId(fileId); + checkPermission(file, userId); + return new DownloadInfo(file.getOssPath(), file.getFileName(), file.getContentType()); + } + + public record DownloadInfo(String storagePath, String fileName, String contentType) {} + + public void deleteFile(String fileId, String userId) { + RobotFile file = findActiveByFileId(fileId); + checkPermission(file, userId); + + file.setDeletedAt(LocalDateTime.now()); + file.setUpdatedAt(LocalDateTime.now()); + robotFileMapper.updateById(file); + + com.linkwork.model.entity.FileNodeEntity node = fileNodeService.findByFileId( + fileId, userId, file.getSpaceType(), file.getWorkstationId()); + if (node != null) { + fileNodeService.deleteNode(node.getNodeId(), userId); + } + + try { + nfsStorageService.deleteFile(file.getOssPath()); + } catch (Exception e) { + log.warn("删除原文件失败: fileId={}, path={}, err={}", fileId, file.getOssPath(), e.getMessage()); + } + if (StringUtils.hasText(file.getParsedOssPath())) { + try { + nfsStorageService.deleteFile(file.getParsedOssPath()); + } catch (Exception e) { + log.warn("删除解析文件失败: fileId={}, path={}, err={}", fileId, file.getParsedOssPath(), e.getMessage()); + } + } + if (memoryService != null) { + try { + String source = StringUtils.hasText(file.getParsedOssPath()) ? file.getParsedOssPath() : file.getOssPath(); + memoryService.deleteSource(file.getWorkstationId(), file.getUserId(), source); + } catch (Exception e) { + log.warn("删除 Memory 索引失败: fileId={}, err={}", fileId, e.getMessage()); + } + } + } + + public FileResponse replaceFile(String fileId, MultipartFile newFile, String userId) { + RobotFile file = findActiveByFileId(fileId); + checkPermission(file, userId); + validateUpload(newFile, file.getSpaceType(), file.getWorkstationId(), userId); + + String ext = getExtension(newFile.getOriginalFilename()); + try { + nfsStorageService.uploadFileToPath(newFile, file.getOssPath()); + } catch (IOException e) { + throw new IllegalStateException("覆盖上传文件失败", e); + } + + if (StringUtils.hasText(file.getParsedOssPath())) { + try { + nfsStorageService.deleteFile(file.getParsedOssPath()); + } catch (Exception e) { + log.warn("删除旧解析文件失败: fileId={}, err={}", fileId, e.getMessage()); + } + } + + file.setFileName(newFile.getOriginalFilename()); + file.setFileSize(newFile.getSize()); + file.setFileType(ext); + file.setContentType(newFile.getContentType()); + file.setFileHash(computeSha256(newFile)); + file.setParseStatus(PARSE_REQUIRED_TYPES.contains(ext) ? "PARSING" : "SKIP"); + file.setMemoryIndexStatus("NONE"); + file.setParsedOssPath(null); + file.setUpdatedAt(LocalDateTime.now()); + robotFileMapper.updateById(file); + + if (PARSE_REQUIRED_TYPES.contains(ext)) { + redisTemplate.opsForList().leftPush(FILE_PARSE_QUEUE_KEY, file.getFileId()); + } else if (MEMORY_DIRECT_TYPES.contains(ext)) { + triggerMemoryIndex(file); + } + + return toResponse(file); + } + + public FileResponse copyFile(String fileId, FileTransferRequest request, String userId) { + RobotFile source = findActiveByFileId(fileId); + checkPermission(source, userId); + validateSpaceType(request.getTargetSpaceType(), request.getTargetWorkstationId()); + String targetSpaceType = request.getTargetSpaceType().toUpperCase(Locale.ROOT); + String targetParentId = request.getTargetParentId(); + fileNodeService.validateParentId(targetParentId, userId, targetSpaceType, request.getTargetWorkstationId()); + + ConflictPolicy policy = request.resolveConflictPolicy(); + com.linkwork.model.entity.FileNodeEntity conflictNode = fileNodeService.findSameNameNode( + userId, targetSpaceType, request.getTargetWorkstationId(), targetParentId, source.getFileName()); + String targetFileName = source.getFileName(); + + if (conflictNode != null) { + switch (policy) { + case REJECT -> { + RobotFile conflictFile = "FILE".equals(conflictNode.getEntryType()) && conflictNode.getFileId() != null + ? findActiveByFileId(conflictNode.getFileId()) : null; + throw new FileConflictException( + "目标目录已存在同名" + ("DIR".equals(conflictNode.getEntryType()) ? "目录" : "文件"), + conflictNode.getFileId() != null ? conflictNode.getFileId() : conflictNode.getNodeId(), + conflictNode.getName(), conflictNode.getEntryType(), + conflictFile != null ? conflictFile.getFileSize() : null, + conflictNode.getUpdatedAt()); + } + case OVERWRITE -> { + if ("DIR".equals(conflictNode.getEntryType())) { + throw new IllegalArgumentException("无法用文件覆盖目录"); + } + RobotFile conflictFile = findActiveByFileId(conflictNode.getFileId()); + acquireTransferDedup(source.getFileId(), userId, "copy", targetSpaceType, + request.getTargetWorkstationId(), policy, targetParentId); + return toResponse(overwriteTargetFile(source, conflictFile)); + } + case RENAME -> { + if (StringUtils.hasText(request.getNewName())) { + com.linkwork.model.entity.FileNodeEntity newNameConflict = fileNodeService.findSameNameNode( + userId, targetSpaceType, request.getTargetWorkstationId(), targetParentId, request.getNewName()); + if (newNameConflict != null) { + RobotFile conflictFile = "FILE".equals(newNameConflict.getEntryType()) && newNameConflict.getFileId() != null + ? findActiveByFileId(newNameConflict.getFileId()) : null; + throw new FileConflictException( + "目标目录已存在同名" + ("DIR".equals(newNameConflict.getEntryType()) ? "目录" : "文件"), + newNameConflict.getFileId() != null ? newNameConflict.getFileId() : newNameConflict.getNodeId(), + newNameConflict.getName(), newNameConflict.getEntryType(), + conflictFile != null ? conflictFile.getFileSize() : null, + newNameConflict.getUpdatedAt()); + } + targetFileName = request.getNewName(); + } else { + targetFileName = generateUniqueNodeName(userId, targetSpaceType, + request.getTargetWorkstationId(), targetParentId, source.getFileName()); + } + } + } + } + + acquireTransferDedup(source.getFileId(), userId, "copy", targetSpaceType, + request.getTargetWorkstationId(), policy, targetParentId); + + String newFileId = UUID.randomUUID().toString().replace("-", ""); + String ext = source.getFileType(); + String targetOssPath = buildOssPath(targetSpaceType, request.getTargetWorkstationId(), userId, newFileId, ext); + nfsStorageService.copyObject(source.getOssPath(), targetOssPath); + + String targetParsedPath = null; + if (StringUtils.hasText(source.getParsedOssPath())) { + targetParsedPath = buildParsedPath(targetOssPath); + nfsStorageService.copyObject(source.getParsedOssPath(), targetParsedPath); + } + + RobotFile copied = new RobotFile(); + copied.setFileId(newFileId); + copied.setFileName(targetFileName); + copied.setFileSize(source.getFileSize()); + copied.setFileType(source.getFileType()); + copied.setContentType(source.getContentType()); + copied.setSpaceType(targetSpaceType); + copied.setWorkstationId("WORKSTATION".equals(targetSpaceType) ? request.getTargetWorkstationId() : null); + copied.setUserId(userId); + copied.setOssPath(targetOssPath); + copied.setParsedOssPath(targetParsedPath); + copied.setParseStatus(source.getParseStatus()); + copied.setMemoryIndexStatus("NONE"); + copied.setFileHash(source.getFileHash()); + copied.setCreatedAt(LocalDateTime.now()); + copied.setUpdatedAt(LocalDateTime.now()); + + try { + robotFileMapper.insert(copied); + } catch (Exception e) { + try { nfsStorageService.deleteFile(targetOssPath); } catch (Exception ignored) { } + if (targetParsedPath != null) { + try { nfsStorageService.deleteFile(targetParsedPath); } catch (Exception ignored) { } + } + throw e; + } + + fileNodeService.createFileNode(targetFileName, targetSpaceType, request.getTargetWorkstationId(), + userId, newFileId, targetParentId); + + if ("PARSED".equals(copied.getParseStatus()) || MEMORY_DIRECT_TYPES.contains(copied.getFileType())) { + triggerMemoryIndex(copied); + } + + return toResponse(copied); + } + + public FileResponse moveFile(String fileId, FileTransferRequest request, String userId) { + RobotFile source = findActiveByFileId(fileId); + checkPermission(source, userId); + validateSpaceType(request.getTargetSpaceType(), request.getTargetWorkstationId()); + String targetSpaceType = request.getTargetSpaceType().toUpperCase(Locale.ROOT); + String targetParentId = request.getTargetParentId(); + fileNodeService.validateParentId(targetParentId, userId, targetSpaceType, request.getTargetWorkstationId()); + + ConflictPolicy policy = request.resolveConflictPolicy(); + + com.linkwork.model.entity.FileNodeEntity sourceNode = fileNodeService.findByFileId( + fileId, userId, source.getSpaceType(), source.getWorkstationId()); + com.linkwork.model.entity.FileNodeEntity conflictNode = fileNodeService.findSameNameNode( + userId, targetSpaceType, request.getTargetWorkstationId(), targetParentId, source.getFileName()); + // move 排除自身节点 + if (conflictNode != null && sourceNode != null && conflictNode.getNodeId().equals(sourceNode.getNodeId())) { + conflictNode = null; + } + + // ── 阶段 1:校验(可能抛 409,不占 dedup,不做写操作) ── + if (conflictNode != null) { + switch (policy) { + case REJECT -> { + RobotFile conflictFile = "FILE".equals(conflictNode.getEntryType()) && conflictNode.getFileId() != null + ? findActiveByFileId(conflictNode.getFileId()) : null; + throw new FileConflictException( + "目标目录已存在同名" + ("DIR".equals(conflictNode.getEntryType()) ? "目录" : "文件"), + conflictNode.getFileId() != null ? conflictNode.getFileId() : conflictNode.getNodeId(), + conflictNode.getName(), conflictNode.getEntryType(), + conflictFile != null ? conflictFile.getFileSize() : null, + conflictNode.getUpdatedAt()); + } + case RENAME -> { + if (StringUtils.hasText(request.getNewName())) { + com.linkwork.model.entity.FileNodeEntity newNameConflict = fileNodeService.findSameNameNode( + userId, targetSpaceType, request.getTargetWorkstationId(), targetParentId, request.getNewName()); + if (newNameConflict != null && (sourceNode == null || !newNameConflict.getNodeId().equals(sourceNode.getNodeId()))) { + RobotFile cf = "FILE".equals(newNameConflict.getEntryType()) && newNameConflict.getFileId() != null + ? findActiveByFileId(newNameConflict.getFileId()) : null; + throw new FileConflictException( + "目标目录已存在同名" + ("DIR".equals(newNameConflict.getEntryType()) ? "目录" : "文件"), + newNameConflict.getFileId() != null ? newNameConflict.getFileId() : newNameConflict.getNodeId(), + newNameConflict.getName(), newNameConflict.getEntryType(), + cf != null ? cf.getFileSize() : null, + newNameConflict.getUpdatedAt()); + } + } + } + default -> { } + } + } + + // ── 阶段 2:dedup → 执行写操作 ── + acquireTransferDedup(source.getFileId(), userId, "move", targetSpaceType, + request.getTargetWorkstationId(), policy, targetParentId); + + if (conflictNode != null) { + switch (policy) { + case OVERWRITE -> { + if ("DIR".equals(conflictNode.getEntryType())) { + throw new IllegalArgumentException("无法用文件覆盖目录"); + } + RobotFile conflictFile = findActiveByFileId(conflictNode.getFileId()); + conflictFile.setDeletedAt(LocalDateTime.now()); + conflictFile.setUpdatedAt(LocalDateTime.now()); + robotFileMapper.updateById(conflictFile); + fileNodeService.deleteNode(conflictNode.getNodeId(), userId); + try { + nfsStorageService.deleteFile(conflictFile.getOssPath()); + if (StringUtils.hasText(conflictFile.getParsedOssPath())) { + nfsStorageService.deleteFile(conflictFile.getParsedOssPath()); + } + } catch (Exception e) { + log.warn("清理被覆盖文件失败: fileId={}, err={}", conflictFile.getFileId(), e.getMessage()); + } + } + case RENAME -> { + if (StringUtils.hasText(request.getNewName())) { + source.setFileName(request.getNewName()); + } else { + String newName = generateUniqueNodeName(userId, targetSpaceType, + request.getTargetWorkstationId(), targetParentId, source.getFileName()); + source.setFileName(newName); + } + } + default -> { } + } + } + + String oldWorkstationId = source.getWorkstationId(); + String oldSpaceType = source.getSpaceType(); + String oldOssPath = source.getOssPath(); + String oldParsedPath = source.getParsedOssPath(); + + String targetOssPath = buildOssPath(targetSpaceType, request.getTargetWorkstationId(), userId, source.getFileId(), source.getFileType()); + boolean storagePathChanged = !Objects.equals(oldOssPath, targetOssPath); + String targetParsedPath = StringUtils.hasText(oldParsedPath) ? buildParsedPath(targetOssPath) : null; + + if (storagePathChanged) { + nfsStorageService.copyObject(oldOssPath, targetOssPath); + if (StringUtils.hasText(oldParsedPath)) { + nfsStorageService.copyObject(oldParsedPath, targetParsedPath); + } + } else { + // Same path (e.g. move within same space/directory tree), no copy/delete required. + targetParsedPath = oldParsedPath; + } + + source.setSpaceType(targetSpaceType); + source.setWorkstationId("WORKSTATION".equals(targetSpaceType) ? request.getTargetWorkstationId() : null); + source.setOssPath(targetOssPath); + source.setParsedOssPath(targetParsedPath); + source.setUpdatedAt(LocalDateTime.now()); + source.setMemoryIndexStatus("NONE"); + robotFileMapper.updateById(source); + + // 更新 file node 归属 + if (sourceNode != null) { + sourceNode.setParentId(targetParentId); + sourceNode.setSpaceType(targetSpaceType); + sourceNode.setWorkstationId("WORKSTATION".equals(targetSpaceType) ? request.getTargetWorkstationId() : null); + sourceNode.setName(source.getFileName()); + sourceNode.setUpdatedAt(LocalDateTime.now()); + fileNodeService.updateNode(sourceNode); + } + + if (storagePathChanged) { + try { + nfsStorageService.deleteFile(oldOssPath); + } catch (Exception e) { + log.warn("删除旧原文件失败: fileId={}, path={}, err={}", fileId, oldOssPath, e.getMessage()); + } + if (StringUtils.hasText(oldParsedPath) && !Objects.equals(oldParsedPath, targetParsedPath)) { + try { + nfsStorageService.deleteFile(oldParsedPath); + } catch (Exception e) { + log.warn("删除旧解析文件失败: fileId={}, path={}, err={}", fileId, oldParsedPath, e.getMessage()); + } + } + } + + if (memoryService != null) { + try { + if ("WORKSTATION".equals(oldSpaceType)) { + memoryService.deleteSource(oldWorkstationId, source.getUserId(), oldOssPath); + } else { + memoryService.deleteSource(null, source.getUserId(), oldOssPath); + } + } catch (Exception e) { + log.warn("清理旧Memory索引失败: {}", e.getMessage()); + } + } + + if ("PARSED".equals(source.getParseStatus()) || MEMORY_DIRECT_TYPES.contains(source.getFileType())) { + triggerMemoryIndex(source); + } + + return toResponse(source); + } + + public List mentionFiles(String workstationId, String keyword, String userId) { + List wsFiles = listBySpaceForMention(userId, "WORKSTATION", workstationId, keyword); + List userFiles = listBySpaceForMention(userId, "USER", null, keyword); + return Stream.concat(wsFiles.stream(), userFiles.stream()) + .limit(50) + .map(this::toMentionResponse) + .toList(); + } + + public void triggerMemoryIndex(RobotFile file) { + if (memoryService == null || file == null) { + return; + } + + String fileType = file.getFileType(); + String objectName = null; + if (MEMORY_DIRECT_TYPES.contains(fileType)) { + objectName = file.getOssPath(); + } else if (PARSE_REQUIRED_TYPES.contains(fileType) && "PARSED".equals(file.getParseStatus())) { + objectName = file.getParsedOssPath(); + } else if (MEMORY_SKIP_TYPES.contains(fileType)) { + file.setMemoryIndexStatus("SKIP"); + file.setUpdatedAt(LocalDateTime.now()); + robotFileMapper.updateById(file); + return; + } else { + return; + } + + if (!StringUtils.hasText(objectName)) { + return; + } + + if ("WORKSTATION".equals(file.getSpaceType())) { + RoleEntity role = null; + if (StringUtils.hasText(file.getWorkstationId())) { + role = roleService.getOne(new LambdaQueryWrapper() + .eq(RoleEntity::getRoleNo, file.getWorkstationId()) + .last("limit 1")); + if (role == null) { + try { + role = roleService.getById(Long.valueOf(file.getWorkstationId())); + } catch (Exception ignored) { + // ignore non-numeric workstation id + } + } + } + Boolean enabled = role != null && role.getConfigJson() != null ? role.getConfigJson().getMemoryEnabled() : null; + if (Boolean.FALSE.equals(enabled)) { + file.setMemoryIndexStatus("SKIP"); + file.setUpdatedAt(LocalDateTime.now()); + robotFileMapper.updateById(file); + return; + } + } + + String collectionName = "USER".equals(file.getSpaceType()) + ? memoryConfig.userCollectionName(file.getUserId()) + : memoryConfig.collectionName(file.getWorkstationId(), file.getUserId()); + + MemoryIndexJob job = MemoryIndexJob.builder() + .jobId(UUID.randomUUID().toString()) + .workstationId(file.getWorkstationId()) + .userId(file.getUserId()) + .jobType(MemoryIndexJob.JobType.FILE_UPLOAD) + .fileType(fileType) + .source(objectName) + .storageType("NFS") + .objectName(objectName) + .collectionName(collectionName) + .build(); + + try { + String payload = objectMapper.writeValueAsString(job); + redisTemplate.opsForList().leftPush(memoryConfig.getIndex().getQueueKey(), payload); + file.setMemoryIndexStatus("INDEXING"); + file.setUpdatedAt(LocalDateTime.now()); + robotFileMapper.updateById(file); + } catch (Exception e) { + throw new IllegalStateException("触发 Memory 索引失败", e); + } + } + + private List listBySpaceForMention(String userId, String spaceType, String workstationId, String keyword) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(RobotFile::getUserId, userId) + .eq(RobotFile::getSpaceType, spaceType) + .isNull(RobotFile::getDeletedAt) + .orderByDesc(RobotFile::getCreatedAt) + .last("limit 50"); + if (StringUtils.hasText(workstationId)) { + wrapper.eq(RobotFile::getWorkstationId, workstationId); + } + if (StringUtils.hasText(keyword)) { + wrapper.like(RobotFile::getFileName, keyword.trim()); + } + return robotFileMapper.selectList(wrapper); + } + + private FileMentionResponse toMentionResponse(RobotFile file) { + FileMentionResponse response = new FileMentionResponse(); + response.setFileId(file.getFileId()); + response.setFileName(file.getFileName()); + response.setFileType(file.getFileType()); + response.setFileSize(file.getFileSize()); + response.setSpaceType(file.getSpaceType()); + response.setWorkstationId(file.getWorkstationId()); + response.setCreatedAt(file.getCreatedAt()); + return response; + } + + private void acquireTransferDedup(String fileId, String userId, String operation, + String targetSpaceType, String targetWorkstationId, + ConflictPolicy policy, String targetParentId) { + String key = buildTransferDedupKey(fileId, userId, operation, targetSpaceType, targetWorkstationId, policy, targetParentId); + Boolean acquired = redisTemplate.opsForValue().setIfAbsent( + key, "1", FILE_TRANSFER_DEDUP_SECONDS, TimeUnit.SECONDS); + if (!Boolean.TRUE.equals(acquired)) { + throw new IllegalStateException("重复提交,请稍后重试"); + } + } + + private RobotFile findSameNameFile(String userId, String spaceType, String workstationId, + String fileName, String excludeFileId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(RobotFile::getUserId, userId) + .eq(RobotFile::getSpaceType, spaceType) + .eq(RobotFile::getFileName, fileName) + .isNull(RobotFile::getDeletedAt) + .last("limit 1"); + if ("WORKSTATION".equals(spaceType)) { + wrapper.eq(RobotFile::getWorkstationId, workstationId); + } else { + wrapper.isNull(RobotFile::getWorkstationId); + } + if (StringUtils.hasText(excludeFileId)) { + wrapper.ne(RobotFile::getFileId, excludeFileId); + } + return robotFileMapper.selectOne(wrapper); + } + + private String generateUniqueNodeName(String userId, String spaceType, String workstationId, + String parentId, String originalName) { + String baseName; + String extension; + int dotIdx = originalName.lastIndexOf('.'); + if (dotIdx > 0) { + baseName = originalName.substring(0, dotIdx); + extension = originalName.substring(dotIdx); + } else { + baseName = originalName; + extension = ""; + } + + for (int i = 1; i <= 100; i++) { + String candidate = baseName + " (" + i + ")" + extension; + com.linkwork.model.entity.FileNodeEntity existing = fileNodeService.findSameNameNode( + userId, spaceType, workstationId, parentId, candidate); + if (existing == null) { + return candidate; + } + } + return baseName + " (" + UUID.randomUUID().toString().substring(0, 8) + ")" + extension; + } + + private String generateUniqueName(String userId, String spaceType, String workstationId, String originalName) { + String baseName; + String extension; + int dotIdx = originalName.lastIndexOf('.'); + if (dotIdx > 0) { + baseName = originalName.substring(0, dotIdx); + extension = originalName.substring(dotIdx); + } else { + baseName = originalName; + extension = ""; + } + + for (int i = 1; i <= 100; i++) { + String candidate = baseName + " (" + i + ")" + extension; + RobotFile existing = findSameNameFile(userId, spaceType, workstationId, candidate, null); + if (existing == null) { + return candidate; + } + } + return baseName + " (" + UUID.randomUUID().toString().substring(0, 8) + ")" + extension; + } + + private RobotFile overwriteTargetFile(RobotFile source, RobotFile target) { + nfsStorageService.copyObject(source.getOssPath(), target.getOssPath()); + + String targetParsedPath = target.getParsedOssPath(); + if (StringUtils.hasText(source.getParsedOssPath())) { + if (!StringUtils.hasText(targetParsedPath)) { + targetParsedPath = buildParsedPath(target.getOssPath()); + } + nfsStorageService.copyObject(source.getParsedOssPath(), targetParsedPath); + } else if (StringUtils.hasText(targetParsedPath)) { + try { + nfsStorageService.deleteFile(targetParsedPath); + } catch (Exception e) { + log.warn("清理覆盖前解析文件失败: fileId={}, path={}, err={}", + target.getFileId(), targetParsedPath, e.getMessage()); + } + targetParsedPath = null; + } + + target.setFileSize(source.getFileSize()); + target.setFileType(source.getFileType()); + target.setContentType(source.getContentType()); + target.setFileHash(source.getFileHash()); + target.setParseStatus(source.getParseStatus()); + target.setParsedOssPath(targetParsedPath); + target.setMemoryIndexStatus("NONE"); + target.setUpdatedAt(LocalDateTime.now()); + robotFileMapper.updateById(target); + + if ("PARSED".equals(target.getParseStatus()) || MEMORY_DIRECT_TYPES.contains(target.getFileType())) { + triggerMemoryIndex(target); + } + + return target; + } + + private String buildTransferDedupKey(String fileId, String userId, String operation, + String targetSpaceType, String targetWorkstationId, + ConflictPolicy policy, String targetParentId) { + String safeTargetWorkstationId = StringUtils.hasText(targetWorkstationId) + ? sanitizePathSegment(targetWorkstationId) + : "-"; + String safeTargetParentId = StringUtils.hasText(targetParentId) + ? sanitizePathSegment(targetParentId) + : "root"; + return String.format("%s:%s:%s:%s:%s:%s:%s:%s", + FILE_TRANSFER_DEDUP_KEY_PREFIX, + sanitizePathSegment(userId), + sanitizePathSegment(fileId), + sanitizePathSegment(operation), + sanitizePathSegment(targetSpaceType), + safeTargetWorkstationId, + policy.name(), + safeTargetParentId); + } + + private void validateUpload(MultipartFile file, String spaceType, String workstationId, String userId) { + if (file == null || file.isEmpty()) { + throw new IllegalArgumentException("文件不能为空"); + } + if (file.getSize() > MAX_FILE_SIZE) { + throw new IllegalArgumentException("文件大小不能超过 100MB"); + } + validateSpaceType(spaceType, workstationId); + String ext = getExtension(file.getOriginalFilename()); + if (!ALLOWED_TYPES.contains(ext)) { + throw new IllegalArgumentException("不支持的文件类型: " + ext); + } + if (!StringUtils.hasText(userId)) { + throw new IllegalArgumentException("用户信息缺失"); + } + } + + private void validateSpaceType(String spaceType, String workstationId) { + if (!StringUtils.hasText(spaceType)) { + throw new IllegalArgumentException("spaceType 不能为空"); + } + String normalized = spaceType.toUpperCase(Locale.ROOT); + if (!"USER".equals(normalized) && !"WORKSTATION".equals(normalized)) { + throw new IllegalArgumentException("spaceType 仅支持 USER 或 WORKSTATION"); + } + if ("WORKSTATION".equals(normalized) && !StringUtils.hasText(workstationId)) { + throw new IllegalArgumentException("WORKSTATION 空间必须提供 workstationId"); + } + } + + private RobotFile findActiveByFileId(String fileId) { + RobotFile file = robotFileMapper.selectOne(new LambdaQueryWrapper() + .eq(RobotFile::getFileId, fileId) + .isNull(RobotFile::getDeletedAt) + .last("limit 1")); + if (file == null) { + throw new ResourceNotFoundException("文件不存在: " + fileId); + } + return file; + } + + private void checkPermission(RobotFile file, String userId) { + if (!Objects.equals(file.getUserId(), userId)) { + throw new ForbiddenOperationException("无权限访问该文件"); + } + } + + public String buildParsedPath(String ossPath) { + String parsed = ossPath.replace("/original/", "/parsed/"); + int dotIndex = parsed.lastIndexOf('.'); + if (dotIndex > 0) { + return parsed.substring(0, dotIndex) + ".md"; + } + return parsed + ".md"; + } + + private String getExtension(String filename) { + if (!StringUtils.hasText(filename) || !filename.contains(".")) { + throw new IllegalArgumentException("文件名缺少扩展名"); + } + return filename.substring(filename.lastIndexOf('.') + 1).toLowerCase(Locale.ROOT); + } + + private String buildOssPath(String spaceType, String workstationId, String userId, String fileId, String ext) { + String normalized = spaceType.toUpperCase(Locale.ROOT); + String safeUserId = sanitizePathSegment(userId); + if ("USER".equals(normalized)) { + return String.format("user-files/%s/original/%s.%s", safeUserId, fileId, ext); + } + String safeWorkstationId = sanitizePathSegment(workstationId); + return String.format("workstation/%s/%s/original/%s.%s", safeWorkstationId, safeUserId, fileId, ext); + } + + private String sanitizePathSegment(String segment) { + if (segment == null) { + return ""; + } + return segment.replaceAll("[^a-zA-Z0-9_.-]", "_"); + } + + private String computeSha256(MultipartFile file) { + try (InputStream inputStream = file.getInputStream()) { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] buffer = new byte[8192]; + int len; + while ((len = inputStream.read(buffer)) > 0) { + digest.update(buffer, 0, len); + } + byte[] hash = digest.digest(); + StringBuilder sb = new StringBuilder(hash.length * 2); + for (byte b : hash) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } catch (Exception e) { + throw new IllegalStateException("计算文件哈希失败", e); + } + } + + public FileResponse toResponse(RobotFile file) { + FileResponse response = new FileResponse(); + response.setFileId(file.getFileId()); + response.setFileName(file.getFileName()); + response.setFileSize(file.getFileSize()); + response.setFileType(file.getFileType()); + response.setContentType(file.getContentType()); + response.setSpaceType(file.getSpaceType()); + response.setWorkstationId(file.getWorkstationId()); + response.setParseStatus(file.getParseStatus()); + response.setMemoryIndexStatus(file.getMemoryIndexStatus()); + response.setCreatedAt(file.getCreatedAt()); + return response; + } +} diff --git a/back/src/main/java/com/linkwork/service/FileSpaceSyncService.java b/back/src/main/java/com/linkwork/service/FileSpaceSyncService.java new file mode 100644 index 0000000..8fcfa3e --- /dev/null +++ b/back/src/main/java/com/linkwork/service/FileSpaceSyncService.java @@ -0,0 +1,167 @@ +package com.linkwork.service; + +import com.linkwork.model.dto.FileSpaceSyncRequest; +import com.linkwork.model.dto.FileSpaceSyncResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +@Slf4j +@Service +@RequiredArgsConstructor +public class FileSpaceSyncService { + + private static final String SPACE_TYPE_USER = "USER"; + private static final String SPACE_TYPE_WORKSTATION = "WORKSTATION"; + + private final NfsStorageService nfsStorageService; + private final TaskOutputWorkspaceSyncService taskOutputWorkspaceSyncService; + + public FileSpaceSyncResponse syncSpace(String userId, FileSpaceSyncRequest request) { + if (!StringUtils.hasText(userId)) { + throw new IllegalArgumentException("userId is required"); + } + if (request == null) { + throw new IllegalArgumentException("request is required"); + } + if (!nfsStorageService.isConfigured()) { + throw new IllegalStateException("NFS storage is not configured"); + } + + String spaceType = normalizeSpaceType(request.getSpaceType()); + String workstationId = normalizeWorkstationId(spaceType, request.getWorkstationId()); + List scopes = resolveScopes(spaceType, workstationId, userId); + int scannedCount = 0; + int skippedCount = 0; + Map> artifactsByObjectName = new LinkedHashMap<>(); + + for (SyncScope scope : scopes) { + List objectNames = nfsStorageService.listObjects(scope.prefix()); + scannedCount += objectNames.size(); + for (String objectName : objectNames) { + String normalizedObjectName = normalizePath(objectName); + String relativePath = resolveRelativePath(scope, normalizedObjectName); + if (!StringUtils.hasText(relativePath) || shouldSkipRelativePath(relativePath)) { + skippedCount++; + continue; + } + if (artifactsByObjectName.containsKey(normalizedObjectName)) { + skippedCount++; + continue; + } + Map artifact = new LinkedHashMap<>(); + artifact.put("relative_path", relativePath); + artifact.put("object_name", normalizedObjectName); + artifact.put("action", "upsert"); + artifact.put("size", resolveFileSize(normalizedObjectName)); + artifactsByObjectName.put(normalizedObjectName, artifact); + } + } + + List> artifacts = new ArrayList<>(artifactsByObjectName.values()); + if (!artifacts.isEmpty()) { + taskOutputWorkspaceSyncService.syncTaskPathListArtifacts( + "MANUAL_SYNC", + userId, + workstationId, + artifacts + ); + } + + log.info("manual file space sync done: userId={}, spaceType={}, workstationId={}, scanned={}, synced={}, skipped={}", + userId, spaceType, workstationId, scannedCount, artifacts.size(), skippedCount); + return new FileSpaceSyncResponse( + spaceType, + workstationId, + scannedCount, + artifacts.size(), + skippedCount + ); + } + + private String normalizeSpaceType(String spaceType) { + if (!StringUtils.hasText(spaceType)) { + throw new IllegalArgumentException("spaceType is required"); + } + String normalized = spaceType.trim().toUpperCase(Locale.ROOT); + if (!SPACE_TYPE_USER.equals(normalized) && !SPACE_TYPE_WORKSTATION.equals(normalized)) { + throw new IllegalArgumentException("spaceType must be USER or WORKSTATION"); + } + return normalized; + } + + private String normalizeWorkstationId(String spaceType, String workstationId) { + if (!SPACE_TYPE_WORKSTATION.equals(spaceType)) { + return null; + } + if (!StringUtils.hasText(workstationId)) { + throw new IllegalArgumentException("workstationId is required when spaceType=WORKSTATION"); + } + return workstationId.trim(); + } + + private List resolveScopes(String spaceType, String workstationId, String userId) { + if (SPACE_TYPE_USER.equals(spaceType)) { + return List.of(new SyncScope("user-files/" + userId, "user/", true)); + } + // WORKSTATION 手动同步补扫任务日志(system/{ws}/logs/{userId}/**), + // 并以 workstation/logs/** 形态入库展示在记忆空间。 + return List.of( + new SyncScope("workstation/" + workstationId + "/" + userId, "workstation/", true), + new SyncScope("system/" + workstationId + "/logs/" + userId, "workstation/logs/", false) + ); + } + + private String resolveRelativePath(SyncScope scope, String objectName) { + String normalized = normalizePath(objectName); + String prefix = scope.prefix(); + String prefixWithSlash = prefix.endsWith("/") ? prefix : prefix + "/"; + if (!normalized.startsWith(prefixWithSlash)) { + return null; + } + String subPath = normalized.substring(prefixWithSlash.length()); + if (!StringUtils.hasText(subPath)) { + return null; + } + if (scope.skipDerivedArtifacts() && (subPath.startsWith("original/") || subPath.startsWith("parsed/"))) { + return null; + } + return scope.relativePrefix() + subPath; + } + + private boolean shouldSkipRelativePath(String relativePath) { + String normalized = normalizePath(relativePath); + return "user/MEMORY.md".equalsIgnoreCase(normalized) + || "workstation/MEMORY.md".equalsIgnoreCase(normalized); + } + + private long resolveFileSize(String objectName) { + try { + Path path = nfsStorageService.getAbsolutePath(objectName); + return Files.exists(path) ? Files.size(path) : 0L; + } catch (Exception e) { + log.debug("resolve nfs file size failed: objectName={}, err={}", objectName, e.getMessage()); + return 0L; + } + } + + private String normalizePath(String path) { + String normalized = path == null ? "" : path.trim().replace('\\', '/'); + while (normalized.startsWith("/")) { + normalized = normalized.substring(1); + } + return normalized; + } + + private record SyncScope(String prefix, String relativePrefix, boolean skipDerivedArtifacts) { + } +} diff --git a/back/src/main/java/com/linkwork/service/GitLabAuthService.java b/back/src/main/java/com/linkwork/service/GitLabAuthService.java new file mode 100644 index 0000000..b825b6b --- /dev/null +++ b/back/src/main/java/com/linkwork/service/GitLabAuthService.java @@ -0,0 +1,435 @@ +package com.linkwork.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.linkwork.mapper.GitLabAuthMapper; +import com.linkwork.model.entity.GitLabAuthEntity; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestTemplate; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +@Slf4j +@Service +@RequiredArgsConstructor +public class GitLabAuthService { + + private static final String DEFAULT_GITLAB_BASE_URL = "https://git.example.com"; + + private final GitLabAuthMapper gitLabAuthMapper; + private final RestTemplate restTemplate = new RestTemplate(); + + @Value("${robot.gitlab.base-url}") + private String gitlabBaseUrl; + + @Value("${robot.gitlab.auth-base-url:}") + private String gitlabAuthBaseUrl; + + @Value("${robot.gitlab.client-id}") + private String clientId; + + @Value("${robot.gitlab.client-secret}") + private String clientSecret; + + @Value("${robot.gitlab.redirect-uri}") + private String defaultRedirectUri; + + public String getAuthUrl(String redirectUri, String scopeType) { + String normalizedScopeType = normalizeScopeType(scopeType); + String uri = resolveRedirectUri(redirectUri); + String scope = resolveScope(normalizedScopeType); + return String.format("%s/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code&state=%s&scope=%s", + resolveOauthBaseUrl(), clientId, uri, normalizedScopeType, scope); + } + + public void callback(String userId, String code, String redirectUri, String scopeType) { + String normalizedScopeType = normalizeScopeType(scopeType); + String uri = resolveRedirectUri(redirectUri); + + // 1. Exchange Token + String tokenUrl = resolveOauthBaseUrl() + "/oauth/token"; + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("client_id", clientId); + params.add("client_secret", clientSecret); + params.add("code", code); + params.add("grant_type", "authorization_code"); + params.add("redirect_uri", uri); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + ResponseEntity response = restTemplate.postForEntity(tokenUrl, new HttpEntity<>(params, headers), Map.class); + Map body = response.getBody(); + + if (body == null || body.get("access_token") == null) { + throw new RuntimeException("Failed to get access token from GitLab"); + } + + String accessToken = (String) body.get("access_token"); + String refreshToken = (String) body.get("refresh_token"); + Integer expiresIn = (Integer) body.get("expires_in"); + if (expiresIn == null) { + expiresIn = 7200; + } + + // 2. Get User Info + String userUrl = resolveApiBaseUrl() + "/api/v4/user"; + HttpHeaders authHeaders = new HttpHeaders(); + authHeaders.setBearerAuth(accessToken); + ResponseEntity userResp = restTemplate.exchange(userUrl, HttpMethod.GET, new HttpEntity<>(authHeaders), Map.class); + Map userBody = userResp.getBody(); + + if (userBody == null) { + throw new RuntimeException("Failed to get user info from GitLab"); + } + + Long gitlabId = ((Number) userBody.get("id")).longValue(); + String username = (String) userBody.get("username"); + String name = (String) userBody.get("name"); + String avatarUrl = (String) userBody.get("avatar_url"); + + String scope = resolveScope(normalizedScopeType); + + // 3. Save to DB - use custom query that includes soft-deleted records + GitLabAuthEntity entity = gitLabAuthMapper.selectIncludingDeleted(userId, gitlabId, scope); + + if (entity == null) { + entity = new GitLabAuthEntity(); + entity.setUserId(userId); + entity.setGitlabId(gitlabId); + entity.setCreatedAt(LocalDateTime.now()); + } + + entity.setUsername(username); + entity.setName(name); + entity.setAvatarUrl(avatarUrl); + entity.setAccessToken(encrypt(accessToken)); + entity.setRefreshToken(encrypt(refreshToken)); + entity.setExpiresAt(LocalDateTime.now().plusSeconds(expiresIn)); + entity.setTokenAlias(maskToken(accessToken)); + entity.setScope(scope); + entity.setUpdatedAt(LocalDateTime.now()); + entity.setIsDeleted(false); + + if (entity.getId() == null) { + gitLabAuthMapper.insert(entity); + } else { + gitLabAuthMapper.updateIncludingDeleted(entity); + } + } + + public List listUsers(String userId) { + List authList = gitLabAuthMapper.selectList(new LambdaQueryWrapper() + .eq(GitLabAuthEntity::getUserId, userId) + .eq(GitLabAuthEntity::getIsDeleted, false) + .orderByDesc(GitLabAuthEntity::getCreatedAt)); + + // 自动刷新过期的 token,让前端看到最新的过期时间 + LocalDateTime now = LocalDateTime.now(); + for (GitLabAuthEntity auth : authList) { + if (auth.getExpiresAt() != null && auth.getExpiresAt().isBefore(now)) { + tryRefreshToken(auth); + } + } + + return authList; + } + + public void deleteUser(String userId, String authId) { + gitLabAuthMapper.delete(new LambdaQueryWrapper() + .eq(GitLabAuthEntity::getId, authId) + .eq(GitLabAuthEntity::getUserId, userId)); + } + + /** + * 获取用户最新的 GitLab 授权。 + */ + public GitLabAuthEntity getLatestAuth(String userId) { + if (!StringUtils.hasText(userId)) { + return null; + } + + List authList = gitLabAuthMapper.selectList( + new LambdaQueryWrapper() + .eq(GitLabAuthEntity::getUserId, userId) + .eq(GitLabAuthEntity::getIsDeleted, false) + .orderByDesc(GitLabAuthEntity::getUpdatedAt) + .last("LIMIT 1") + ); + + if (authList == null || authList.isEmpty()) { + return null; + } + return authList.get(0); + } + + /** + * 根据 authId 获取有效 token,若已过期或将过期则先刷新。 + */ + public ValidToken getValidTokenByAuthId(Long authId, Duration refreshAheadWindow) { + if (authId == null) { + return null; + } + + GitLabAuthEntity auth = gitLabAuthMapper.selectById(authId); + if (auth == null || Boolean.TRUE.equals(auth.getIsDeleted())) { + return null; + } + + LocalDateTime now = LocalDateTime.now(); + Duration window = refreshAheadWindow != null ? refreshAheadWindow : Duration.ofMinutes(5); + LocalDateTime refreshThreshold = now.plus(window); + + if (auth.getExpiresAt() == null || !auth.getExpiresAt().isAfter(refreshThreshold)) { + String refreshedToken = tryRefreshToken(auth); + if (StringUtils.hasText(refreshedToken)) { + auth = gitLabAuthMapper.selectById(authId); + if (auth == null || Boolean.TRUE.equals(auth.getIsDeleted())) { + return null; + } + } + } + + if (auth.getExpiresAt() == null || !auth.getExpiresAt().isAfter(now)) { + log.warn("GitLab token expired after refresh attempt: authId={}, tokenAlias={}", + authId, auth.getTokenAlias()); + throw new IllegalStateException("GitLab token expired and refresh failed: authId=" + authId); + } + + String token = decrypt(auth.getAccessToken()); + if (!StringUtils.hasText(token)) { + throw new IllegalStateException("GitLab token is blank: authId=" + authId); + } + + ValidToken validToken = new ValidToken(); + validToken.setToken(token); + validToken.setTokenAlias(auth.getTokenAlias()); + validToken.setExpiresAt(auth.getExpiresAt()); + validToken.setScope(auth.getScope()); + validToken.setTokenType(resolveTokenType(auth, token)); + return validToken; + } + + /** + * 获取用户有效的 GitLab access token + * 优先返回未过期的 token;如果过期则自动使用 refresh_token 续期 + */ + public String getAccessToken(String userId) { + GitLabAuthEntity latest = getLatestAuth(userId); + if (latest == null) { + log.debug("No GitLab auth found for userId: {}", userId); + return null; + } + + ValidToken validToken = getValidTokenByAuthId(latest.getId(), Duration.ofMinutes(5)); + if (validToken == null) { + log.warn("No valid GitLab token available for userId: {}", userId); + return null; + } + return validToken.getToken(); + } + + /** + * 通过当前有效 token 查询 GitLab 用户身份,用于 git commit 身份注入。 + */ + public CommitIdentity resolveCommitIdentity(String accessToken) { + if (!StringUtils.hasText(accessToken)) { + throw new IllegalArgumentException("accessToken is required"); + } + + String userUrl = resolveApiBaseUrl() + "/api/v4/user"; + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(accessToken); + + ResponseEntity response = restTemplate.exchange( + userUrl, + HttpMethod.GET, + new HttpEntity<>(headers), + Map.class + ); + Map body = response.getBody(); + if (body == null) { + throw new IllegalStateException("GitLab user info response is empty"); + } + + String username = body.get("username") == null ? "" : String.valueOf(body.get("username")).trim(); + String email = body.get("email") == null ? "" : String.valueOf(body.get("email")).trim(); + if (!StringUtils.hasText(email)) { + email = body.get("public_email") == null ? "" : String.valueOf(body.get("public_email")).trim(); + } + if (!StringUtils.hasText(username)) { + throw new IllegalStateException("GitLab user info missing username"); + } + if (!StringUtils.hasText(email)) { + throw new IllegalStateException("GitLab user info missing email/public_email"); + } + + CommitIdentity identity = new CommitIdentity(); + identity.setUsername(username); + identity.setEmail(email); + return identity; + } + + /** + * 根据 scopeType 解析实际的 GitLab scope 字符串 + */ + private String resolveScope(String scopeType) { + if ("read".equals(scopeType)) { + return "read_user read_repository"; + } + // Keep write flow compatible with existing GitLab OAuth app configuration. + return "read_user api"; + } + + /** + * 多因子判定 tokenType(oauth / pat): + * 1. token 以 glpat- 开头 → PAT(GitLab Personal Access Token 固定前缀) + * 2. scope 含 api / read_repository 等 OAuth 授权特征 → oauth + * 3. 存在 refresh_token → oauth + * 4. 兜底 → oauth(当前 OAuth 授权流是主要入口) + */ + private String resolveTokenType(GitLabAuthEntity auth, String decryptedToken) { + if (StringUtils.hasText(decryptedToken) && decryptedToken.startsWith("glpat-")) { + return "pat"; + } + String scope = auth.getScope(); + if (StringUtils.hasText(scope) && (scope.contains("api") || scope.contains("read_repository"))) { + return "oauth"; + } + if (StringUtils.hasText(decrypt(auth.getRefreshToken()))) { + return "oauth"; + } + return "oauth"; + } + + /** + * 使用 refresh_token 向 GitLab 换取新的 access_token + */ + protected String tryRefreshToken(GitLabAuthEntity auth) { + String refreshToken = decrypt(auth.getRefreshToken()); + if (!StringUtils.hasText(refreshToken)) { + log.warn("No refresh token available for GitLab auth id: {}", auth.getId()); + return null; + } + + try { + String tokenUrl = resolveOauthBaseUrl() + "/oauth/token"; + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("client_id", clientId); + params.add("client_secret", clientSecret); + params.add("refresh_token", refreshToken); + params.add("grant_type", "refresh_token"); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + ResponseEntity response = restTemplate.postForEntity(tokenUrl, new HttpEntity<>(params, headers), Map.class); + Map body = response.getBody(); + + if (body == null || body.get("access_token") == null) { + log.warn("GitLab refresh response missing access_token for auth id: {}", auth.getId()); + return null; + } + + String newAccessToken = (String) body.get("access_token"); + String newRefreshToken = (String) body.get("refresh_token"); + Integer expiresIn = (Integer) body.get("expires_in"); + if (expiresIn == null) { + expiresIn = 7200; + } + + auth.setAccessToken(encrypt(newAccessToken)); + if (newRefreshToken != null) { + auth.setRefreshToken(encrypt(newRefreshToken)); + } + auth.setExpiresAt(LocalDateTime.now().plusSeconds(expiresIn)); + auth.setTokenAlias(maskToken(newAccessToken)); + auth.setUpdatedAt(LocalDateTime.now()); + gitLabAuthMapper.updateById(auth); + + log.info("GitLab token refreshed: userId={}, username={}, tokenAlias={}, expiresAt={}", + auth.getUserId(), auth.getUsername(), auth.getTokenAlias(), auth.getExpiresAt()); + return newAccessToken; + } catch (Exception e) { + log.warn("Failed to refresh GitLab token for auth id {}: {}", auth.getId(), e.getMessage()); + return null; + } + } + + private String resolveOauthBaseUrl() { + if (StringUtils.hasText(gitlabAuthBaseUrl)) { + return gitlabAuthBaseUrl; + } + if (StringUtils.hasText(gitlabBaseUrl)) { + return gitlabBaseUrl; + } + return DEFAULT_GITLAB_BASE_URL; + } + + private String resolveApiBaseUrl() { + if (StringUtils.hasText(gitlabBaseUrl)) { + return gitlabBaseUrl; + } + return DEFAULT_GITLAB_BASE_URL; + } + + private String resolveRedirectUri(String redirectUri) { + if (StringUtils.hasText(redirectUri)) { + return redirectUri; + } + return defaultRedirectUri; + } + + private String normalizeScopeType(String scopeType) { + if ("read".equals(scopeType)) { + return "read"; + } + return "write"; + } + + private String encrypt(String token) { + // TODO: Implement reversible encryption + return token; + } + + private String decrypt(String encryptedToken) { + // TODO: Implement reversible encryption (matches encrypt) + return encryptedToken; + } + + private String maskToken(String token) { + if (token == null || token.length() < 10) { + return "******"; + } + return token.substring(0, 4) + "****" + token.substring(token.length() - 4); + } + + @Data + public static class ValidToken { + private String token; + private String tokenAlias; + private LocalDateTime expiresAt; + private String scope; + private String tokenType; + } + + @Data + public static class CommitIdentity { + private String username; + private String email; + } +} diff --git a/back/src/main/java/com/linkwork/service/ImageBuildService.java b/back/src/main/java/com/linkwork/service/ImageBuildService.java new file mode 100644 index 0000000..4721f62 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/ImageBuildService.java @@ -0,0 +1,923 @@ +package com.linkwork.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.async.ResultCallback; +import com.github.dockerjava.api.command.BuildImageResultCallback; +import com.github.dockerjava.api.model.AuthConfig; +import com.github.dockerjava.api.model.BuildResponseItem; +import com.github.dockerjava.api.model.PullResponseItem; +import com.github.dockerjava.api.model.PushResponseItem; +import com.github.dockerjava.api.exception.NotFoundException; +import com.github.dockerjava.core.DefaultDockerClientConfig; +import com.github.dockerjava.core.DockerClientConfig; +import com.github.dockerjava.core.DockerClientImpl; +import com.github.dockerjava.zerodep.ZerodepDockerHttpClient; +import com.github.dockerjava.transport.DockerHttpClient; +import com.linkwork.config.BuildQueueConfig; +import com.linkwork.config.BuildQueueConfig; +import com.linkwork.config.BuildQueueConfig; +import com.linkwork.config.ImageBuildConfig; +import com.linkwork.model.dto.ImageBuildResult; +import com.linkwork.model.dto.ServiceBuildRequest; +import com.linkwork.model.enums.DeployMode; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.io.File; +import java.nio.file.DirectoryStream; +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.Base64; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * 镜像构建服务 + * + * 职责: + * 1. 生成 Dockerfile(基础镜像 + ENV 声明 + 执行 build.sh) + * 2. 调用 Docker API 构建 Agent 镜像 + * 3. 推送镜像到仓库(K8s 模式) + * + * 设计说明: + * - 仅构建 Agent 镜像,Runner 由运行时 agent 启动 + * - token 放入 buildEnvVars,在 build.sh 执行前 export + */ +@Service +@Slf4j +public class ImageBuildService { + + private static final DateTimeFormatter TIMESTAMP_FORMATTER = + DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(ZoneOffset.UTC); + + private final ImageBuildConfig config; + private final BuildQueueConfig buildQueueConfig; + private final ObjectMapper objectMapper; + private final ScheduleEventPublisher eventPublisher; + private final BuildLogBuffer logBuffer; + private final NfsStorageService nfsStorageService; + private final BuildRecordService buildRecordService; + private DockerClient dockerClient; + + public ImageBuildService(ImageBuildConfig config, + BuildQueueConfig buildQueueConfig, + ObjectMapper objectMapper, + ScheduleEventPublisher eventPublisher, + BuildLogBuffer logBuffer, + NfsStorageService nfsStorageService, + BuildRecordService buildRecordService) { + this.config = config; + this.buildQueueConfig = buildQueueConfig; + this.objectMapper = objectMapper; + this.eventPublisher = eventPublisher; + this.logBuffer = logBuffer; + this.nfsStorageService = nfsStorageService; + this.buildRecordService = buildRecordService; + } + + @PostConstruct + public void init() { + log.info("Initializing Docker client with host: {}", config.getDockerHost()); + + DefaultDockerClientConfig.Builder configBuilder = DefaultDockerClientConfig.createDefaultConfigBuilder() + .withDockerHost(config.getDockerHost()); + + // 注册 Registry 凭证到 DockerClientConfig,确保 Docker Daemon 能正确完成 Harbor Token 鉴权 + String registry = config.getRegistry(); + String registryHost = ""; + if (StringUtils.hasText(registry) && StringUtils.hasText(config.getRegistryUsername())) { + registryHost = registry; + if (registryHost.contains("/")) { + registryHost = registryHost.substring(0, registryHost.indexOf("/")); + } + configBuilder + .withRegistryUrl("http://" + registryHost) + .withRegistryUsername(config.getRegistryUsername()) + .withRegistryPassword(config.getRegistryPassword()); + log.info("Registry credentials configured for: {}", registryHost); + } + + // ★ 写入 ~/.docker/config.json,确保 Docker Daemon 能从凭据文件读取认证信息 + if (StringUtils.hasText(registryHost) && StringUtils.hasText(config.getRegistryUsername())) { + writeDockerConfigJson(registryHost, config.getRegistryUsername(), config.getRegistryPassword()); + } + + DockerClientConfig clientConfig = configBuilder.build(); + + DockerHttpClient httpClient = new ZerodepDockerHttpClient.Builder() + .dockerHost(clientConfig.getDockerHost()) + .sslConfig(clientConfig.getSSLConfig()) + .maxConnections(100) + .connectionTimeout(Duration.ofSeconds(30)) + .responseTimeout(Duration.ofSeconds(config.getBuildTimeout())) + .build(); + + this.dockerClient = DockerClientImpl.getInstance(clientConfig, httpClient); + + // 确保构建上下文根目录存在 + try { + Path buildContextBase = Path.of(config.getBuildContextDir()); + if (!Files.exists(buildContextBase)) { + Files.createDirectories(buildContextBase); + log.info("Created build context directory: {}", buildContextBase); + } + } catch (IOException e) { + log.warn("Failed to create build context directory: {}", e.getMessage()); + } + + log.info("Docker client initialized successfully"); + } + + @PreDestroy + public void cleanup() { + if (dockerClient != null) { + try { + dockerClient.close(); + } catch (IOException e) { + log.warn("Failed to close Docker client: {}", e.getMessage()); + } + } + } + + /** + * 写入 Docker 配置文件 ~/.docker/config.json + * 确保 Docker daemon 能通过凭据文件完成 Harbor Token 鉴权 + */ + private void writeDockerConfigJson(String registryHost, String username, String password) { + try { + Path dockerConfigDir = Path.of(System.getProperty("user.home"), ".docker"); + Files.createDirectories(dockerConfigDir); + Path configFile = dockerConfigDir.resolve("config.json"); + + String auth = Base64.getEncoder().encodeToString( + (username + ":" + password).getBytes(java.nio.charset.StandardCharsets.UTF_8) + ); + + // 同时写入 带http://前缀 和 不带前缀 的两种格式,确保匹配 + String configJson = "{\n" + + " \"auths\": {\n" + + " \"" + registryHost + "\": {\n" + + " \"auth\": \"" + auth + "\"\n" + + " },\n" + + " \"http://" + registryHost + "\": {\n" + + " \"auth\": \"" + auth + "\"\n" + + " }\n" + + " }\n" + + "}"; + + Files.writeString(configFile, configJson); + log.info("Docker config.json written to: {}", configFile); + } catch (Exception e) { + log.warn("Failed to write Docker config.json: {}", e.getMessage()); + } + } + + /** + * 构建 Agent 镜像 + * + * @param request 服务构建请求 + * @return 构建结果 + */ + public ImageBuildResult buildImages(ServiceBuildRequest request) { + String serviceId = request.getServiceId(); + String buildId = request.getBuildId(); + long startTime = System.currentTimeMillis(); + + log.info("Starting image build for service: {}, buildId: {}", serviceId, buildId); + publishLog(buildId, "info", "=== 开始构建镜像 ==="); + publishLog(buildId, "info", "服务ID: " + serviceId); + + try { + // 生成时间戳 tag + String timestamp = TIMESTAMP_FORMATTER.format(Instant.now()); + + // 解析镜像仓库 + String registry = resolveRegistry(request); + publishLog(buildId, "info", "镜像仓库: " + (StringUtils.hasText(registry) ? registry : "本地")); + + // 构建 Agent 镜像 + String agentBaseImage = resolveAgentBaseImage(request); + publishLog(buildId, "info", "基础镜像: " + agentBaseImage); + publishLog(buildId, "info", ""); + publishLog(buildId, "info", "=== 开始 Docker 构建 ==="); + + String agentImageTag = buildAgentImage(serviceId, timestamp, registry, + agentBaseImage, request.getBuildEnvVars(), buildId); + + publishLog(buildId, "info", ""); + publishLog(buildId, "info", "镜像构建成功: " + agentImageTag); + + // K8s 模式推送镜像(如果启用) + boolean pushed = false; + if (config.isPushEnabled() && request.getDeployMode() == DeployMode.K8S && StringUtils.hasText(registry)) { + publishLog(buildId, "info", ""); + publishLog(buildId, "info", "=== 开始推送镜像 ==="); + pushImage(agentImageTag, buildId); + pushed = true; + publishLog(buildId, "info", "镜像推送成功"); + + // 推送成功后删除本地镜像(K8s 会从仓库拉取,不需要保留本地副本) + removeLocalImage(agentImageTag, buildId); + } else { + publishLog(buildId, "warn", "跳过镜像推送(未配置或非 K8S 模式)"); + log.info("Image push disabled or no registry configured, skipping push for: {}", agentImageTag); + } + + long duration = System.currentTimeMillis() - startTime; + publishLog(buildId, "info", ""); + publishLog(buildId, "info", "=== 构建完成 ==="); + publishLog(buildId, "info", String.format("总耗时: %.1f 秒", duration / 1000.0)); + markBuildCompleted(buildId, true); + log.info("Image build completed for service: {}, duration: {}ms", serviceId, duration); + + return ImageBuildResult.success(agentImageTag, null, duration, pushed); + + } catch (Exception e) { + long duration = System.currentTimeMillis() - startTime; + publishLog(buildId, "error", ""); + publishLog(buildId, "error", "=== 构建失败 ==="); + publishLog(buildId, "error", "错误: " + e.getMessage()); + markBuildCompleted(buildId, false); + log.error("Image build failed for service: {}, duration: {}ms, error: {}", + serviceId, duration, e.getMessage(), e); + return ImageBuildResult.failed(e.getMessage()); + } + } + + /** + * 发布构建日志 + * 优先使用 BuildLogBuffer(直接 SSE 推送),同时保留 Redis Stream 兼容 + */ + private void publishLog(String buildId, String level, String message) { + if (!StringUtils.hasText(buildId)) return; + + // 写入日志缓冲区(SSE 直接推送) + if (logBuffer != null) { + logBuffer.addLog(buildId, level, message); + } + } + + /** + * 标记构建完成并上传日志到 OSS + * @param buildId 构建 ID + * @param success 是否成功 + */ + private void markBuildCompleted(String buildId, boolean success) { + if (logBuffer != null && StringUtils.hasText(buildId)) { + logBuffer.markCompleted(buildId, success); + + // 上传日志到 OSS + uploadBuildLogToOss(buildId); + + // 10 分钟后清理日志缓冲区 + logBuffer.scheduleCleanup(buildId, 10); + } + } + + /** + * 上传构建日志到 NFS 存储 + */ + private void uploadBuildLogToOss(String buildId) { + try { + if (nfsStorageService == null || !nfsStorageService.isConfigured()) { + log.warn("NFS 存储未配置,跳过日志上传: {}", buildId); + return; + } + + String logContent = logBuffer.exportAsText(buildId); + if (logContent == null || logContent.isEmpty()) { + log.warn("构建日志为空,跳过上传: {}", buildId); + return; + } + + String filename = buildId + ".txt"; + + String logPath = nfsStorageService.uploadText(logContent, "build-logs", filename); + log.info("构建日志已上传到 NFS: {} -> {}", buildId, logPath); + + if (buildRecordService != null) { + buildRecordService.updateLogUrl(buildId, logPath); + } + + } catch (Exception e) { + log.error("上传构建日志失败: {}, error: {}", buildId, e.getMessage(), e); + } + } + + /** + * 解析镜像仓库地址 + */ + private String resolveRegistry(ServiceBuildRequest request) { + if (StringUtils.hasText(request.getImageRegistry())) { + return request.getImageRegistry(); + } + return config.getRegistry(); + } + + /** + * 解析 Agent 基础镜像(使用系统默认配置) + */ + private String resolveAgentBaseImage(ServiceBuildRequest request) { + // 直接使用系统默认配置,不再从请求中获取 + return config.getDefaultAgentBaseImage(); + } + + /** + * 构建 Agent 镜像 + */ + private String buildAgentImage(String serviceId, String timestamp, String registry, + String baseImage, Map envVars, + String buildId) throws Exception { + String imageName = generateImageName(registry, serviceId, "agent", timestamp); + + log.info("Building Agent image: {}, baseImage: {}", imageName, baseImage); + publishLog(buildId, "info", "目标镜像: " + imageName); + + // 确保基础镜像已缓存到本地(避免每次 docker build 都从 registry 拉取) + ensureBaseImageCached(baseImage, buildId); + + // 创建构建上下文目录 + publishLog(buildId, "debug", "创建构建上下文..."); + Path buildContextPath = createBuildContext(serviceId, baseImage, envVars); + publishLog(buildId, "debug", "构建上下文路径: " + buildContextPath); + + try { + // 执行构建 + buildImage(buildContextPath, imageName, buildId); + return imageName; + } finally { + // 清理构建上下文 + publishLog(buildId, "debug", "清理构建上下文..."); + cleanupBuildContext(buildContextPath); + // 清理残留的历史构建上下文 + cleanupStaleContexts(); + } + } + + /** + * 确保基础镜像已缓存到本地 + * + * 先通过 inspectImageCmd 检查本地是否已存在该镜像: + * - 已存在 → 直接使用,跳过网络拉取(毫秒级) + * - 不存在 → 执行 pull 并输出进度日志 + * + * 这样 docker build 的 FROM 指令不再需要联网验证/拉取,大幅加快构建速度。 + */ + private void ensureBaseImageCached(String baseImage, String buildId) { + try { + dockerClient.inspectImageCmd(baseImage).exec(); + publishLog(buildId, "info", "基础镜像已缓存在本地,跳过拉取"); + log.info("Base image already cached locally: {}", baseImage); + } catch (NotFoundException e) { + // 本地不存在,需要拉取 + publishLog(buildId, "info", "本地未找到基础镜像,开始拉取: " + baseImage); + log.info("Base image not found locally, pulling: {}", baseImage); + try { + final String finalBuildId = buildId; + dockerClient.pullImageCmd(baseImage) + .exec(new ResultCallback.Adapter() { + @Override + public void onNext(PullResponseItem item) { + if (item.getStatus() != null) { + String status = item.getStatus(); + // 只记录关键进度,避免日志刷屏 + if (status.contains("Pulling") || status.contains("Pull complete") || + status.contains("Downloaded") || status.contains("digest") || + status.contains("Status")) { + publishLog(finalBuildId, "info", "[PULL] " + status); + } + } + } + }) + .awaitCompletion(config.getBuildTimeout(), TimeUnit.SECONDS); + publishLog(buildId, "info", "基础镜像拉取完成"); + log.info("Base image pulled successfully: {}", baseImage); + } catch (Exception pullEx) { + publishLog(buildId, "warn", "基础镜像拉取失败(将由 docker build 重试): " + pullEx.getMessage()); + log.warn("Failed to pre-pull base image: {}, will retry during build", baseImage, pullEx); + } + } catch (Exception e) { + publishLog(buildId, "warn", "检查本地镜像异常: " + e.getMessage()); + log.warn("Failed to inspect local image: {}", baseImage, e); + } + } + + /** + * 生成镜像名称 + * 格式:{registry}/service-{serviceId}-agent:{serviceId}-{timestamp} + */ + private String generateImageName(String registry, String serviceId, String type, String timestamp) { + String tag = serviceId + "-" + timestamp; + String imageName = String.format("service-%s-%s:%s", serviceId, type, tag); + if (StringUtils.hasText(registry)) { + return registry + "/" + imageName; + } + return imageName; + } + + /** + * 创建构建上下文目录 + */ + private Path createBuildContext(String serviceId, String baseImage, + Map envVars) throws IOException { + // 创建临时目录 + Path contextDir = Files.createTempDirectory( + Path.of(config.getBuildContextDir()), + String.format("build-%s-", serviceId) + ); + + // 生成 Dockerfile + String dockerfile = generateDockerfile(baseImage, envVars); + Files.writeString(contextDir.resolve("Dockerfile"), dockerfile); + + // 复制 build.sh(优先从 classpath 读取,其次从文件系统) + boolean buildScriptCopied = false; + + // 1. 尝试从 classpath 读取(打包后的资源) + try { + ClassPathResource resource = new ClassPathResource("scripts/build.sh"); + if (resource.exists()) { + try (var inputStream = resource.getInputStream()) { + Files.copy(inputStream, contextDir.resolve("build.sh")); + buildScriptCopied = true; + log.debug("Build script loaded from classpath"); + } + } + } catch (Exception e) { + log.warn("Failed to load build script from classpath: {}", e.getMessage()); + } + + // 2. 如果 classpath 中没有,尝试从文件系统读取 + if (!buildScriptCopied) { + Path buildScript = Path.of(config.getBuildScriptPath()); + if (Files.exists(buildScript)) { + Files.copy(buildScript, contextDir.resolve("build.sh")); + buildScriptCopied = true; + log.debug("Build script loaded from file system: {}", buildScript); + } + } + + // 3. 如果都没有,创建空的 build.sh 占位 + if (!buildScriptCopied) { + Files.writeString(contextDir.resolve("build.sh"), "#!/bin/bash\n# Placeholder build script\necho 'Build script executed'\n"); + log.warn("Build script not found, using placeholder"); + } + + // 复制 Cedar 策略文件到构建上下文(zzd fail-closed 需要至少一个 .cedar 文件) + Path cedarDir = contextDir.resolve("cedar-policies"); + Files.createDirectories(cedarDir); + try { + ClassPathResource cedarResource = new ClassPathResource("scripts/00-platform.cedar"); + if (cedarResource.exists()) { + try (var inputStream = cedarResource.getInputStream()) { + Files.copy(inputStream, cedarDir.resolve("00-platform.cedar")); + log.debug("Cedar policy loaded from classpath"); + } + } else { + log.warn("00-platform.cedar not found in classpath"); + } + } catch (Exception e) { + log.warn("Failed to load cedar policy: {}", e.getMessage()); + } + + // 复制默认 config.json 到构建上下文(镜像内兜底,运行时由 ConfigMap 覆盖挂载) + try { + ClassPathResource configResource = new ClassPathResource("scripts/config.json"); + if (configResource.exists()) { + try (var inputStream = configResource.getInputStream()) { + Files.copy(inputStream, contextDir.resolve("config.json")); + log.debug("Default config.json loaded from classpath"); + } + } + } catch (Exception e) { + log.warn("Failed to load default config.json: {}", e.getMessage()); + } + + log.debug("Build context created at: {}", contextDir); + return contextDir; + } + + /** + * 生成 Dockerfile 内容 + * Package-private:供 Compose 模式复用 + */ + String generateDockerfile(String baseImage, Map envVars) { + StringBuilder sb = new StringBuilder(); + + // FROM 指令 + sb.append("# Auto-generated Dockerfile\n"); + sb.append("FROM ").append(baseImage).append("\n\n"); + + // ENV 指令(注入环境变量) + if (envVars != null && !envVars.isEmpty()) { + sb.append("# Environment variables from build request\n"); + for (Map.Entry entry : envVars.entrySet()) { + String key = entry.getKey(); + Object valueObj = entry.getValue(); + String valueStr; + + if (valueObj instanceof String) { + valueStr = (String) valueObj; + } else { + try { + // 对象/数组序列化为 JSON 字符串 + valueStr = objectMapper.writeValueAsString(valueObj); + } catch (JsonProcessingException e) { + log.warn("Failed to serialize env var {} to JSON", key, e); + valueStr = String.valueOf(valueObj); + } + } + + // 转义特殊字符 + String value = escapeEnvValue(valueStr); + sb.append("ENV ").append(key).append("=").append(value).append("\n"); + } + sb.append("\n"); + } + + // Clone SDK repo and prepare sdk-source + zzd-binaries (before build.sh) + String sdkRepoUrl = config.getSdkRepoUrl(); + if (StringUtils.hasText(sdkRepoUrl)) { + // 使用 GIT_TOKEN 环境变量认证(已在 ENV 指令中注入),不再用硬编码的 sdkRepoUsername/Password 合并分支暂时注释,这是init时拉取sdk 仓库的节点过程,该节点是不需要用户的业务级仓库token的 +// String cloneCmd = sdkRepoUrl.replace("https://", "https://oauth2:${GIT_TOKEN}@"); + + // Build authenticated clone URL: https://user:pass@host/path.git + String cloneUrl = sdkRepoUrl; + if (StringUtils.hasText(config.getSdkRepoUsername()) + && StringUtils.hasText(config.getSdkRepoPassword())) { + cloneUrl = sdkRepoUrl.replace("https://", + "https://" + config.getSdkRepoUsername() + ":" + config.getSdkRepoPassword() + "@"); + } + + sb.append("# Cache-bust: force re-clone on every build to pick up latest binaries/SDK\n"); + sb.append("ARG CACHEBUST=").append(System.currentTimeMillis()).append("\n"); + sb.append("# Clone repo and organize into BUILD_ASSETS_ROOT for build.sh (production v2)\n"); + String branch = StringUtils.hasText(config.getSdkRepoBranch()) ? config.getSdkRepoBranch() : "test122"; + sb.append("RUN set -e \\\n"); + sb.append(" && git clone --depth 1 --single-branch -b ").append(branch).append(" ").append(cloneUrl).append(" /tmp/_sdk_repo \\\n"); + // BUILD_ASSETS_ROOT directory structure (build.sh expects these sub-directories) + sb.append(" && mkdir -p /opt/momo-agent-build/zzd-binaries \\\n"); + sb.append(" && mkdir -p /opt/momo-agent-build/sdk-source \\\n"); + sb.append(" && mkdir -p /opt/momo-agent-build/start-scripts \\\n"); + // zzd binaries + sb.append(" && for bin in zzd zz gen-key encrypt-key; do \\\n"); + sb.append(" cp /tmp/_sdk_repo/docker/agent/zzd/$bin /opt/momo-agent-build/zzd-binaries/; \\\n"); + sb.append(" done \\\n"); + // SDK source + sb.append(" && cp -a /tmp/_sdk_repo/momo-agent-sdk/. /opt/momo-agent-build/sdk-source/ \\\n"); + // start scripts + sb.append(" && cp /tmp/_sdk_repo/docker/agent/start-single.sh /opt/momo-agent-build/start-scripts/ \\\n"); + sb.append(" && cp /tmp/_sdk_repo/docker/agent/start-dual.sh /opt/momo-agent-build/start-scripts/ \\\n"); + sb.append(" && cp /tmp/_sdk_repo/docker/agent/ai_employee.py /opt/momo-agent-build/start-scripts/ \\\n"); + // cleanup + sb.append(" && rm -rf /tmp/_sdk_repo\n\n"); + + // Cedar 策略文件 → /tmp/cedar-policies/ (build.sh download_cedar_policies 会从这里读取) + sb.append("# Cedar policy files for build.sh to deploy\n"); + sb.append("COPY cedar-policies/ /tmp/cedar-policies/\n\n"); + } + + // 默认 config.json → /opt/agent/config.json(build.sh finalize_permissions 会设置权限) + sb.append("# Default agent config (overridden at runtime by ConfigMap mount)\n"); + sb.append("RUN mkdir -p /opt/agent\n"); + sb.append("COPY config.json /opt/agent/config.json\n\n"); + + // 复制并执行 build.sh(生产版 v2 — 完整部署: 依赖检查、zzd、SDK、agent用户、权限) + sb.append("# Copy and execute build script (production v2)\n"); + sb.append("COPY build.sh /build.sh\n"); + sb.append("RUN chmod +x /build.sh && /build.sh\n\n"); + + // ENTRYPOINT + sb.append("# Set entrypoint\n"); + sb.append("ENTRYPOINT [\"").append(config.getEntrypointScript()).append("\"]\n"); + + return sb.toString(); + } + + /** + * 转义环境变量值中的特殊字符 + */ + String escapeEnvValue(String value) { + if (value == null) { + return "\"\""; + } + // 如果包含空格或特殊字符,使用双引号包裹 + if (value.contains(" ") || value.contains("\"") || value.contains("$") || + value.contains("\\") || value.contains("\n")) { + return "\"" + value.replace("\\", "\\\\") + .replace("\"", "\\\"") + .replace("$", "\\$") + .replace("\n", "\\n") + "\""; + } + return value; + } + + /** + * 执行 Docker build + */ + private void buildImage(Path contextPath, String imageTag, String buildId) throws Exception { + log.info("Executing docker build: tag={}, context={}", imageTag, contextPath); + publishLog(buildId, "info", ""); + publishLog(buildId, "info", "--- Docker Build 日志 ---"); + + File contextDir = contextPath.toFile(); + File dockerfile = contextPath.resolve("Dockerfile").toFile(); + + // 用于在回调中引用 buildId(必须是 effectively final) + final String finalBuildId = buildId; + + BuildImageResultCallback callback = new BuildImageResultCallback() { + @Override + public void onNext(BuildResponseItem item) { + // 发布 Docker 构建的实时日志 + if (item.getStream() != null) { + String logLine = item.getStream().trim(); + if (!logLine.isEmpty()) { + log.debug("Build output: {}", logLine); + // 将 Docker 日志发布到 Redis Stream + publishLog(finalBuildId, "info", logLine); + } + } + if (item.getErrorDetail() != null) { + String errorMsg = item.getErrorDetail().getMessage(); + log.error("Build error: {}", errorMsg); + publishLog(finalBuildId, "error", "[ERROR] " + errorMsg); + } + // 发布构建进度信息 + if (item.getStatus() != null) { + String status = item.getStatus(); + log.debug("Build status: {}", status); + publishLog(finalBuildId, "debug", "[STATUS] " + status); + } + super.onNext(item); + } + }; + + String imageId = dockerClient.buildImageCmd() + .withDockerfile(dockerfile) + .withBaseDirectory(contextDir) + .withTags(Collections.singleton(imageTag)) + .withNoCache(false) + .withPull(false) // 使用本地镜像,避免网络问题 + .exec(callback) + .awaitImageId(config.getBuildTimeout(), TimeUnit.SECONDS); + + publishLog(buildId, "info", ""); + publishLog(buildId, "info", "--- Docker Build 完成 ---"); + publishLog(buildId, "info", "镜像ID: " + imageId); + log.info("Image built successfully: tag={}, imageId={}", imageTag, imageId); + } + + /** + * 推送镜像到仓库(带重试机制) + */ + private void pushImage(String imageTag, String buildId) throws Exception { + log.info("Pushing image: {}", imageTag); + publishLog(buildId, "info", "正在推送镜像: " + imageTag); + + final int maxRetries = 3; + Exception lastException = null; + + for (int attempt = 1; attempt <= maxRetries; attempt++) { + try { + doPushImage(imageTag, buildId, attempt); + // 推送成功 + publishLog(buildId, "info", "镜像推送完成: " + imageTag); + log.info("Image pushed successfully: {}", imageTag); + return; + } catch (Exception e) { + lastException = e; + String errorMsg = e.getMessage() != null ? e.getMessage() : e.toString(); + boolean isAuthError = errorMsg.contains("401") || errorMsg.contains("Unauthorized") + || errorMsg.contains("authentication") || errorMsg.contains("unauthorized"); + + if (isAuthError && attempt < maxRetries) { + publishLog(buildId, "warn", String.format( + "推送失败 (第%d次, 认证错误), %d秒后重试...", attempt, attempt * 5)); + log.warn("Push attempt {} failed with auth error, retrying in {}s: {}", + attempt, attempt * 5, errorMsg); + + // 重新写入凭据文件并刷新认证 + String registryHost = config.getRegistry(); + if (registryHost.contains("/")) { + registryHost = registryHost.substring(0, registryHost.indexOf("/")); + } + writeDockerConfigJson(registryHost, config.getRegistryUsername(), config.getRegistryPassword()); + + Thread.sleep(attempt * 5000L); + } else if (!isAuthError && attempt < maxRetries) { + publishLog(buildId, "warn", String.format( + "推送失败 (第%d次), %d秒后重试...", attempt, attempt * 3)); + Thread.sleep(attempt * 3000L); + } else { + // 最后一次重试也失败了 + publishLog(buildId, "error", String.format( + "推送失败 (第%d次/%d次): %s", attempt, maxRetries, errorMsg)); + } + } + } + + throw new RuntimeException("镜像推送失败 (重试" + maxRetries + "次后): " + + (lastException != null ? lastException.getMessage() : "unknown error")); + } + + /** + * 执行单次镜像推送 + */ + private void doPushImage(String imageTag, String buildId, int attempt) throws Exception { + // 构建认证配置 + AuthConfig authConfig = null; + if (StringUtils.hasText(config.getRegistryUsername()) && + StringUtils.hasText(config.getRegistryPassword())) { + String registryAddress = config.getRegistry(); + if (StringUtils.hasText(registryAddress) && registryAddress.contains("/")) { + registryAddress = registryAddress.substring(0, registryAddress.indexOf("/")); + } + // 使用 http:// 前缀明确标识为 HTTP 仓库,避免 daemon 默认走 HTTPS token 交换 + authConfig = new AuthConfig() + .withRegistryAddress("http://" + registryAddress) + .withUsername(config.getRegistryUsername()) + .withPassword(config.getRegistryPassword()); + publishLog(buildId, "debug", String.format( + "使用认证推送 (registry: %s, attempt: %d)...", registryAddress, attempt)); + } + + final String finalBuildId = buildId; + // 用于捕获回调中的推送错误 + final StringBuilder pushError = new StringBuilder(); + + ResultCallback.Adapter callback = new ResultCallback.Adapter() { + @Override + public void onNext(PushResponseItem item) { + if (item.getStatus() != null) { + String status = item.getStatus(); + log.debug("Push status: {}", status); + // 过滤一些噪音日志,只发布关键状态 + if (status.contains("Pushing") || status.contains("Pushed") || + status.contains("Layer") || status.contains("digest")) { + publishLog(finalBuildId, "info", status); + } + } + if (item.getProgressDetail() != null && item.getProgressDetail().getCurrent() != null) { + // 推送进度,可选择性发布 + Long current = item.getProgressDetail().getCurrent(); + Long total = item.getProgressDetail().getTotal(); + if (total != null && total > 0) { + int percent = (int) (current * 100 / total); + if (percent % 20 == 0) { // 每 20% 发布一次 + publishLog(finalBuildId, "debug", String.format("推送进度: %d%%", percent)); + } + } + } + if (item.getErrorDetail() != null) { + String errorMsg = item.getErrorDetail().getMessage(); + log.error("Push error: {}", errorMsg); + publishLog(finalBuildId, "error", "[ERROR] " + errorMsg); + pushError.append(errorMsg); + } + } + }; + + // ★ push 前先 docker login(刷新 Docker Daemon 的 Bearer Token) + if (authConfig != null) { + try { + // 同时尝试带 http:// 和不带前缀两种格式 + dockerClient.authCmd().withAuthConfig(authConfig).exec(); + publishLog(buildId, "debug", "Registry 认证刷新成功 (http://)"); + log.info("Registry auth refreshed for push: {}", authConfig.getRegistryAddress()); + } catch (Exception e) { + log.warn("Auth with http:// prefix failed, trying without: {}", e.getMessage()); + try { + // 回退:不带 http:// 前缀 + String plainAddress = authConfig.getRegistryAddress().replace("http://", ""); + AuthConfig fallbackAuth = new AuthConfig() + .withRegistryAddress(plainAddress) + .withUsername(config.getRegistryUsername()) + .withPassword(config.getRegistryPassword()); + dockerClient.authCmd().withAuthConfig(fallbackAuth).exec(); + // 如果不带前缀的成功了,使用这个 authConfig 进行推送 + authConfig = fallbackAuth; + publishLog(buildId, "debug", "Registry 认证刷新成功 (plain)"); + log.info("Registry auth refreshed (plain) for push: {}", plainAddress); + } catch (Exception e2) { + publishLog(buildId, "warn", "Registry 认证刷新失败: " + e2.getMessage()); + log.warn("Failed to refresh registry auth: {}", e2.getMessage()); + } + } + } + + if (authConfig != null) { + dockerClient.pushImageCmd(imageTag) + .withAuthConfig(authConfig) + .exec(callback) + .awaitCompletion(config.getBuildTimeout(), TimeUnit.SECONDS); + } else { + dockerClient.pushImageCmd(imageTag) + .exec(callback) + .awaitCompletion(config.getBuildTimeout(), TimeUnit.SECONDS); + } + + // 检查回调中是否捕获到错误 + if (pushError.length() > 0) { + throw new RuntimeException("镜像推送失败: " + pushError); + } + + publishLog(buildId, "info", "镜像推送完成: " + imageTag); + log.info("Image pushed successfully: {}", imageTag); + } + + /** + * 删除本地镜像(推送成功后清理,避免磁盘堆积) + */ + private void removeLocalImage(String imageTag, String buildId) { + try { + dockerClient.removeImageCmd(imageTag).withForce(false).withNoPrune(true).exec(); + publishLog(buildId, "info", "本地镜像已清理: " + imageTag); + log.info("Local image removed: {}", imageTag); + } catch (Exception e) { + // 清理失败不影响构建结果,仅告警 + publishLog(buildId, "warn", "本地镜像清理失败(不影响部署): " + e.getMessage()); + log.warn("Failed to remove local image {}: {}", imageTag, e.getMessage()); + } + } + + /** + * 清理构建上下文目录 + */ + private void cleanupBuildContext(Path contextPath) { + deleteDirectory(contextPath); + } + + /** + * 清理残留的历史构建上下文 + * 扫描 BUILD_CONTEXT_DIR 下超过指定时间的目录 + */ + private void cleanupStaleContexts() { + Path baseDir = Path.of(config.getBuildContextDir()); + if (!Files.exists(baseDir)) { + return; + } + + int staleHours = buildQueueConfig.getStaleContextHours(); + long staleThreshold = System.currentTimeMillis() - TimeUnit.HOURS.toMillis(staleHours); + + try (DirectoryStream stream = Files.newDirectoryStream(baseDir, "build-*")) { + for (Path dir : stream) { + try { + if (!Files.isDirectory(dir)) { + continue; + } + long lastModified = Files.getLastModifiedTime(dir).toMillis(); + if (lastModified < staleThreshold) { + log.info("清理残留构建上下文: {} (超过 {} 小时)", dir, staleHours); + deleteDirectory(dir); + } + } catch (IOException e) { + log.warn("检查目录时间失败: {}", dir, e); + } + } + } catch (IOException e) { + log.warn("扫描构建目录失败: {}", baseDir, e); + } + } + + /** + * 删除目录及其内容 + */ + private void deleteDirectory(Path path) { + try { + Files.walk(path) + .sorted((a, b) -> b.compareTo(a)) // 逆序,先删除文件再删除目录 + .forEach(p -> { + try { + Files.deleteIfExists(p); + } catch (IOException e) { + log.warn("Failed to delete: {}", p); + } + }); + log.debug("Build context cleaned: {}", path); + } catch (IOException e) { + log.warn("Failed to cleanup build context: {}", path, e); + } + } + + /** + * 获取 Dockerfile 预览(用于调试) + */ + public String previewDockerfile(String baseImage, Map envVars) { + return generateDockerfile(baseImage, envVars); + } +} diff --git a/back/src/main/java/com/linkwork/service/K8sClusterService.java b/back/src/main/java/com/linkwork/service/K8sClusterService.java new file mode 100644 index 0000000..4d1310b --- /dev/null +++ b/back/src/main/java/com/linkwork/service/K8sClusterService.java @@ -0,0 +1,450 @@ +package com.linkwork.service; + +import com.linkwork.model.dto.*; +import io.fabric8.kubernetes.api.model.*; +import io.fabric8.kubernetes.api.model.metrics.v1beta1.ContainerMetrics; +import io.fabric8.kubernetes.api.model.metrics.v1beta1.NodeMetrics; +import io.fabric8.kubernetes.api.model.metrics.v1beta1.PodMetrics; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClientException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.Instant; +import java.util.*; +import java.util.stream.Collectors; + +/** + * K8s 集群监控 Service — 多命名空间、全量资源查看 + * 复用现有 KubernetesClient Bean + */ +@Service +@Slf4j +@RequiredArgsConstructor +public class K8sClusterService { + + private final KubernetesClient kubernetesClient; + + public List listNamespaces() { + return kubernetesClient.namespaces().list().getItems().stream() + .map(ns -> ns.getMetadata().getName()) + .sorted() + .collect(Collectors.toList()); + } + + public ClusterOverviewDTO getOverview(String namespace) { + var podList = kubernetesClient.pods().inNamespace(namespace).list().getItems(); + + int running = 0, pending = 0, failed = 0, succeeded = 0; + for (Pod pod : podList) { + String phase = pod.getStatus() != null ? pod.getStatus().getPhase() : "Unknown"; + switch (phase) { + case "Running" -> running++; + case "Pending" -> pending++; + case "Failed" -> failed++; + case "Succeeded" -> succeeded++; + } + } + + long usedCpu = 0, usedMem = 0; + try { + var metrics = kubernetesClient.top().pods().inNamespace(namespace).metrics().getItems(); + for (PodMetrics pm : metrics) { + for (ContainerMetrics cm : pm.getContainers()) { + Quantity cpu = cm.getUsage().get("cpu"); + Quantity mem = cm.getUsage().get("memory"); + if (cpu != null) usedCpu += parseMillicores(cpu); + if (mem != null) usedMem += parseBytes(mem); + } + } + } catch (Exception e) { + log.warn("Metrics not available for namespace {}: {}", namespace, e.getMessage()); + } + + long totalCpu = 0, totalMem = 0; + int nodeCount = 0; + try { + var nodes = kubernetesClient.nodes().list().getItems(); + nodeCount = nodes.size(); + for (Node node : nodes) { + Quantity cpuCap = node.getStatus().getAllocatable().get("cpu"); + Quantity memCap = node.getStatus().getAllocatable().get("memory"); + if (cpuCap != null) totalCpu += parseMillicores(cpuCap); + if (memCap != null) totalMem += parseBytes(memCap); + } + } catch (Exception e) { + log.warn("Failed to get node info: {}", e.getMessage()); + } + + int pgCount = 0; + try { + pgCount = kubernetesClient + .genericKubernetesResources("scheduling.volcano.sh/v1beta1", "PodGroup") + .inNamespace(namespace).list().getItems().size(); + } catch (Exception ignored) {} + + return ClusterOverviewDTO.builder() + .namespace(namespace) + .totalPods(podList.size()) + .runningPods(running) + .pendingPods(pending) + .failedPods(failed) + .succeededPods(succeeded) + .totalCpuMillicores(totalCpu) + .usedCpuMillicores(usedCpu) + .cpuUsagePercent(totalCpu > 0 ? Math.round(usedCpu * 10000.0 / totalCpu) / 100.0 : null) + .totalMemoryBytes(totalMem) + .usedMemoryBytes(usedMem) + .memoryUsagePercent(totalMem > 0 ? Math.round(usedMem * 10000.0 / totalMem) / 100.0 : null) + .podGroupCount(pgCount) + .nodeCount(nodeCount) + .build(); + } + + public List listNodes() { + var nodes = kubernetesClient.nodes().list().getItems(); + + Map metricsMap = new HashMap<>(); + try { + kubernetesClient.top().nodes().metrics().getItems() + .forEach(nm -> metricsMap.put(nm.getMetadata().getName(), nm)); + } catch (Exception e) { + log.warn("Node metrics not available: {}", e.getMessage()); + } + + Map podCounts = new HashMap<>(); + try { + kubernetesClient.pods().inAnyNamespace().list().getItems() + .forEach(p -> { + String node = p.getSpec().getNodeName(); + if (node != null) podCounts.merge(node, 1, Integer::sum); + }); + } catch (Exception e) { + log.warn("Failed to count pods per node: {}", e.getMessage()); + } + + return nodes.stream().map(node -> { + String name = node.getMetadata().getName(); + String status = node.getStatus().getConditions().stream() + .filter(c -> "Ready".equals(c.getType())) + .findFirst() + .map(c -> "True".equals(c.getStatus()) ? "Ready" : "NotReady") + .orElse("Unknown"); + + List roles = node.getMetadata().getLabels().entrySet().stream() + .filter(e -> e.getKey().startsWith("node-role.kubernetes.io/")) + .map(e -> e.getKey().substring("node-role.kubernetes.io/".length())) + .collect(Collectors.toList()); + + long cpuCap = parseMillicores(node.getStatus().getCapacity().get("cpu")); + long cpuAlloc = parseMillicores(node.getStatus().getAllocatable().get("cpu")); + long memCap = parseBytes(node.getStatus().getCapacity().get("memory")); + long memAlloc = parseBytes(node.getStatus().getAllocatable().get("memory")); + int podCap = parseIntQuantity(node.getStatus().getCapacity().get("pods")); + + long cpuUsage = 0, memUsage = 0; + NodeMetrics nm = metricsMap.get(name); + if (nm != null) { + Quantity cpu = nm.getUsage().get("cpu"); + Quantity mem = nm.getUsage().get("memory"); + if (cpu != null) cpuUsage = parseMillicores(cpu); + if (mem != null) memUsage = parseBytes(mem); + } + + return ClusterNodeInfo.builder() + .name(name) + .status(status) + .roles(roles) + .kubeletVersion(node.getStatus().getNodeInfo().getKubeletVersion()) + .cpuCapacity(cpuCap) + .cpuAllocatable(cpuAlloc) + .cpuUsage(cpuUsage) + .cpuUsagePercent(cpuAlloc > 0 ? Math.round(cpuUsage * 10000.0 / cpuAlloc) / 100.0 : null) + .memCapacity(memCap) + .memAllocatable(memAlloc) + .memUsage(memUsage) + .memUsagePercent(memAlloc > 0 ? Math.round(memUsage * 10000.0 / memAlloc) / 100.0 : null) + .podCount(podCounts.getOrDefault(name, 0)) + .podCapacity(podCap) + .build(); + }).collect(Collectors.toList()); + } + + public List listPods(String namespace, String statusFilter, String nodeFilter, String podGroupFilter) { + var podList = kubernetesClient.pods().inNamespace(namespace).list().getItems(); + + Map metricsMap = new HashMap<>(); + try { + kubernetesClient.top().pods().inNamespace(namespace).metrics().getItems() + .forEach(pm -> metricsMap.put(pm.getMetadata().getName(), pm)); + } catch (Exception e) { + log.warn("Pod metrics not available for {}: {}", namespace, e.getMessage()); + } + + Map podGroupPhases = new HashMap<>(); + try { + kubernetesClient.genericKubernetesResources("scheduling.volcano.sh/v1beta1", "PodGroup") + .inNamespace(namespace).list().getItems().forEach(pg -> { + @SuppressWarnings("unchecked") + Map st = (Map) pg.getAdditionalProperties().get("status"); + String phase = st != null ? String.valueOf(st.getOrDefault("phase", "Unknown")) : "Unknown"; + podGroupPhases.put(pg.getMetadata().getName(), phase); + }); + } catch (Exception ignored) {} + + return podList.stream() + .map(pod -> buildClusterPodInfo(pod, metricsMap, podGroupPhases)) + .filter(p -> statusFilter == null || statusFilter.isEmpty() || p.getPhase().equalsIgnoreCase(statusFilter)) + .filter(p -> nodeFilter == null || nodeFilter.isEmpty() || nodeFilter.equals(p.getNodeName())) + .filter(p -> podGroupFilter == null || podGroupFilter.isEmpty() || podGroupFilter.equals(p.getPodGroupName())) + .collect(Collectors.toList()); + } + + public List listPodGroups(String namespace) { + List result = new ArrayList<>(); + try { + var pgList = kubernetesClient.genericKubernetesResources("scheduling.volcano.sh/v1beta1", "PodGroup") + .inNamespace(namespace).list().getItems(); + for (var pg : pgList) { + @SuppressWarnings("unchecked") + Map status = (Map) pg.getAdditionalProperties().get("status"); + @SuppressWarnings("unchecked") + Map spec = (Map) pg.getAdditionalProperties().get("spec"); + + result.add(PodGroupStatusInfo.builder() + .name(pg.getMetadata().getName()) + .phase(status != null ? String.valueOf(status.getOrDefault("phase", "Unknown")) : "Unknown") + .minMember(spec != null && spec.get("minMember") != null ? ((Number) spec.get("minMember")).intValue() : null) + .running(status != null && status.get("running") != null ? ((Number) status.get("running")).intValue() : 0) + .succeeded(status != null && status.get("succeeded") != null ? ((Number) status.get("succeeded")).intValue() : 0) + .failed(status != null && status.get("failed") != null ? ((Number) status.get("failed")).intValue() : 0) + .pending(status != null && status.get("pending") != null ? ((Number) status.get("pending")).intValue() : 0) + .build()); + } + } catch (Exception e) { + log.warn("Failed to list PodGroups in {}: {}", namespace, e.getMessage()); + } + return result; + } + + public PodLogResponseDTO getPodLogs(String namespace, String podName, String container, int tailLines) { + try { + var pod = kubernetesClient.pods().inNamespace(namespace).withName(podName).get(); + if (pod == null) { + return PodLogResponseDTO.builder().podName(podName).namespace(namespace).logs("Pod not found").tailLines(tailLines).build(); + } + + String targetContainer = container; + if (targetContainer == null || targetContainer.isEmpty()) { + var cs = pod.getSpec().getContainers(); + if (!cs.isEmpty()) targetContainer = cs.get(0).getName(); + } + + String logContent; + try (InputStream is = kubernetesClient.pods().inNamespace(namespace).withName(podName) + .inContainer(targetContainer).tailingLines(tailLines).getLogInputStream()) { + logContent = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)) + .lines().collect(Collectors.joining("\n")); + } + + return PodLogResponseDTO.builder() + .podName(podName).namespace(namespace).containerName(targetContainer) + .logs(logContent).tailLines(tailLines).build(); + } catch (Exception e) { + log.error("Failed to get logs for {}/{}: {}", namespace, podName, e.getMessage()); + return PodLogResponseDTO.builder().podName(podName).namespace(namespace) + .logs("Error: " + e.getMessage()).tailLines(tailLines).build(); + } + } + + public List listEvents(String namespace, int limit) { + var events = kubernetesClient.v1().events().inNamespace(namespace).list().getItems(); + events.sort((a, b) -> { + String ta = a.getLastTimestamp() != null ? a.getLastTimestamp() : ""; + String tb = b.getLastTimestamp() != null ? b.getLastTimestamp() : ""; + return tb.compareTo(ta); + }); + + return events.stream().limit(limit).map(e -> ClusterEventDTO.builder() + .type(e.getType()) + .reason(e.getReason()) + .message(e.getMessage()) + .objectKind(e.getInvolvedObject() != null ? e.getInvolvedObject().getKind() : null) + .objectName(e.getInvolvedObject() != null ? e.getInvolvedObject().getName() : null) + .namespace(e.getMetadata().getNamespace()) + .firstTimestamp(e.getFirstTimestamp()) + .lastTimestamp(e.getLastTimestamp()) + .count(e.getCount()) + .build() + ).collect(Collectors.toList()); + } + + public List listPodEvents(String namespace, String podName) { + return kubernetesClient.v1().events().inNamespace(namespace).list().getItems().stream() + .filter(e -> e.getInvolvedObject() != null && podName.equals(e.getInvolvedObject().getName())) + .map(e -> ClusterEventDTO.builder() + .type(e.getType()) + .reason(e.getReason()) + .message(e.getMessage()) + .objectKind(e.getInvolvedObject().getKind()) + .objectName(e.getInvolvedObject().getName()) + .namespace(namespace) + .firstTimestamp(e.getFirstTimestamp()) + .lastTimestamp(e.getLastTimestamp()) + .count(e.getCount()) + .build() + ).collect(Collectors.toList()); + } + + public void deletePod(String namespace, String podName) { + kubernetesClient.pods().inNamespace(namespace).withName(podName).delete(); + log.info("Deleted pod {}/{}", namespace, podName); + } + + // ─── private helpers ───────────────────────────────────────────── + + private ClusterPodInfo buildClusterPodInfo(Pod pod, Map metricsMap, Map podGroupPhases) { + String name = pod.getMetadata().getName(); + Map annotations = pod.getMetadata().getAnnotations(); + Map labels = pod.getMetadata().getLabels(); + + String pgName = annotations != null ? annotations.get("scheduling.volcano.sh/group-name") : null; + String pgPhase = pgName != null ? podGroupPhases.getOrDefault(pgName, "Unknown") : null; + String serviceId = labels != null ? labels.get("service-id") : null; + String userId = labels != null ? labels.get("user-id") : null; + + List containers = new ArrayList<>(); + int totalRestarts = 0; + + if (pod.getStatus() != null && pod.getStatus().getContainerStatuses() != null) { + for (var cs : pod.getStatus().getContainerStatuses()) { + String state = "waiting"; + String reason = null; + Integer exitCode = null; + if (cs.getState().getRunning() != null) state = "running"; + else if (cs.getState().getTerminated() != null) { + state = "terminated"; + reason = cs.getState().getTerminated().getReason(); + exitCode = cs.getState().getTerminated().getExitCode(); + } else if (cs.getState().getWaiting() != null) { + reason = cs.getState().getWaiting().getReason(); + } + + totalRestarts += cs.getRestartCount(); + containers.add(ContainerStatusInfo.builder() + .name(cs.getName()).ready(cs.getReady()).state(state) + .reason(reason).exitCode(exitCode).restartCount(cs.getRestartCount()) + .build()); + } + } + + List images = pod.getSpec().getContainers().stream() + .map(Container::getImage).collect(Collectors.toList()); + + String startTime = null; + String age = ""; + if (pod.getStatus() != null && pod.getStatus().getStartTime() != null) { + startTime = pod.getStatus().getStartTime(); + try { + Duration d = Duration.between(Instant.parse(startTime), Instant.now()); + if (d.toDays() > 0) age = d.toDays() + "d"; + else if (d.toHours() > 0) age = d.toHours() + "h"; + else age = d.toMinutes() + "m"; + } catch (Exception ignored) { age = "N/A"; } + } + + ResourceUsageInfo resUsage = null; + PodMetrics pm = metricsMap.get(name); + if (pm != null) { + long cpuUsage = 0, memUsage = 0; + for (ContainerMetrics cm : pm.getContainers()) { + Quantity cpu = cm.getUsage().get("cpu"); + Quantity mem = cm.getUsage().get("memory"); + if (cpu != null) cpuUsage += parseMillicores(cpu); + if (mem != null) memUsage += parseBytes(mem); + } + resUsage = ResourceUsageInfo.builder() + .cpuMillicores(cpuUsage).cpuUsage(formatMillicores(cpuUsage)) + .memoryBytes(memUsage).memoryUsage(formatBytes(memUsage)) + .build(); + } + + return ClusterPodInfo.builder() + .name(name) + .namespace(pod.getMetadata().getNamespace()) + .phase(pod.getStatus() != null ? pod.getStatus().getPhase() : "Unknown") + .nodeName(pod.getSpec().getNodeName()) + .podGroupName(pgName) + .podGroupPhase(pgPhase) + .serviceId(serviceId) + .userId(userId) + .containers(containers) + .restartCount(totalRestarts) + .startTime(startTime) + .age(age) + .images(images) + .resourceUsage(resUsage) + .build(); + } + + private long parseMillicores(Quantity q) { + if (q == null) return 0; + try { + double value = Double.parseDouble(q.getAmount()); + String fmt = q.getFormat(); + if (fmt != null) { + return switch (fmt) { + case "n" -> (long) (value / 1_000_000); + case "u" -> (long) (value / 1_000); + case "m" -> (long) value; + default -> (long) (value * 1000); + }; + } + return (long) (value * 1000); + } catch (NumberFormatException e) { return 0; } + } + + private long parseBytes(Quantity q) { + if (q == null) return 0; + try { + double value = Double.parseDouble(q.getAmount()); + String fmt = q.getFormat(); + if (fmt != null) { + return switch (fmt) { + case "Ki" -> (long) (value * 1024); + case "Mi" -> (long) (value * 1024 * 1024); + case "Gi" -> (long) (value * 1024 * 1024 * 1024); + case "K" -> (long) (value * 1000); + case "M" -> (long) (value * 1000_000); + case "G" -> (long) (value * 1000_000_000); + default -> (long) value; + }; + } + return (long) value; + } catch (NumberFormatException e) { return 0; } + } + + private int parseIntQuantity(Quantity q) { + if (q == null) return 0; + try { return Integer.parseInt(q.getAmount()); } + catch (Exception e) { return 0; } + } + + private String formatMillicores(long millicores) { + if (millicores >= 1000) return String.format("%.2f", millicores / 1000.0); + return millicores + "m"; + } + + private String formatBytes(long bytes) { + if (bytes >= 1024L * 1024 * 1024) return String.format("%.2fGi", bytes / (1024.0 * 1024 * 1024)); + if (bytes >= 1024L * 1024) return String.format("%.2fMi", bytes / (1024.0 * 1024)); + if (bytes >= 1024) return String.format("%.2fKi", bytes / 1024.0); + return bytes + "B"; + } +} diff --git a/back/src/main/java/com/linkwork/service/K8sOrchestrator.java b/back/src/main/java/com/linkwork/service/K8sOrchestrator.java new file mode 100644 index 0000000..2001655 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/K8sOrchestrator.java @@ -0,0 +1,78 @@ +package com.linkwork.service; + +import com.linkwork.model.dto.*; + +import java.util.List; + +/** + * K8s 编排器接口 + */ +public interface K8sOrchestrator { + + /** + * 构建服务(核心方法) + * 执行顺序:Secret → PodGroup → Pod ×N + * @param config 融合后的配置 + * @return 构建结果 + */ + ServiceBuildResult buildService(MergedConfig config); + + /** + * 获取服务状态 + * @param serviceId 服务 ID + */ + ServiceStatusResponse getServiceStatus(String serviceId); + + /** + * 停止服务 + * @param serviceId 服务 ID + * @param graceful true=优雅停止 + */ + StopResult stopService(String serviceId, boolean graceful); + + /** + * 清理服务资源 + * @param serviceId 服务 ID + */ + void cleanupService(String serviceId); + + /** + * 预览生成的 Spec(不实际创建) + * @param config 融合后的配置 + * @return 生成的 Spec + */ + GeneratedSpec previewSpec(MergedConfig config); + + // ==================== 动态伸缩接口 ==================== + + /** + * 缩容:删除指定的 Pod + * @param serviceId 服务 ID + * @param podName 要删除的 Pod 名称(必填,不指定则返回错误) + * @return 伸缩结果 + */ + ScaleResult scaleDown(String serviceId, String podName); + + /** + * 扩容:创建新的 Pod 到指定数量 + * @param serviceId 服务 ID + * @param targetPodCount 目标 Pod 数量 + * @param config 融合后的配置(用于创建新 Pod) + * @return 伸缩结果 + */ + ScaleResult scaleUp(String serviceId, int targetPodCount, MergedConfig config); + + /** + * 获取当前运行的 Pod 列表 + * @param serviceId 服务 ID + * @return Pod 名称列表 + */ + List getRunningPods(String serviceId); + + /** + * 扫描 K8s namespace,获取所有带 service-id label 的运行中服务 ID 列表 + * 用于后端重启后反向发现 K8s 中仍在运行的服务 + * @return 去重后的 serviceId 列表 + */ + List listAllServiceIds(); +} diff --git a/back/src/main/java/com/linkwork/service/K8sOrchestratorImpl.java b/back/src/main/java/com/linkwork/service/K8sOrchestratorImpl.java new file mode 100644 index 0000000..783bb91 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/K8sOrchestratorImpl.java @@ -0,0 +1,1606 @@ +package com.linkwork.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.linkwork.config.EnvConfig; +import com.linkwork.config.ImageBuildConfig; +import com.linkwork.model.dto.*; +import io.fabric8.kubernetes.api.model.*; +import io.fabric8.kubernetes.api.model.GenericKubernetesResource; +import io.fabric8.kubernetes.api.model.metrics.v1beta1.ContainerMetrics; +import io.fabric8.kubernetes.api.model.metrics.v1beta1.PodMetrics; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClientException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * K8s 编排器实现 + */ +@Service +@Slf4j +public class K8sOrchestratorImpl implements K8sOrchestrator { + + private static final ResourceSpec FALLBACK_AGENT_RESOURCES = ResourceSpec.builder() + .cpuRequest("1") + .cpuLimit("2") + .memoryRequest("2Gi") + .memoryLimit("4Gi") + .build(); + + private static final ResourceSpec FALLBACK_RUNNER_RESOURCES = ResourceSpec.builder() + .cpuRequest("1") + .cpuLimit("4") + .memoryRequest("2Gi") + .memoryLimit("8Gi") + .build(); + + private static final ResourceSpec PERMISSION_INIT_RESOURCES = ResourceSpec.builder() + .cpuRequest("100m") + .cpuLimit("500m") + .memoryRequest("128Mi") + .memoryLimit("512Mi") + .build(); + + private final KubernetesClient kubernetesClient; + private final PodGroupSpecGenerator podGroupSpecGenerator; + private final PodSpecGenerator podSpecGenerator; + private final EnvConfig envConfig; + private final ObjectMapper objectMapper; + private final ScheduleEventPublisher eventPublisher; + private final ImageBuildConfig imageBuildConfig; + private final DistributedLockService lockService; + + /** 存储目录级分布式锁键前缀(保留 oss: 前缀以兼容已有锁) */ + private static final String OSS_LOCK_PREFIX = "oss:lock:"; + + public K8sOrchestratorImpl(KubernetesClient kubernetesClient, + PodGroupSpecGenerator podGroupSpecGenerator, + PodSpecGenerator podSpecGenerator, + EnvConfig envConfig, + ObjectMapper objectMapper, + ScheduleEventPublisher eventPublisher, + ImageBuildConfig imageBuildConfig, + DistributedLockService lockService) { + this.kubernetesClient = kubernetesClient; + this.podGroupSpecGenerator = podGroupSpecGenerator; + this.podSpecGenerator = podSpecGenerator; + this.envConfig = envConfig; + this.objectMapper = objectMapper; + this.eventPublisher = eventPublisher; + this.imageBuildConfig = imageBuildConfig; + this.lockService = lockService; + } + + @Override + public ServiceBuildResult buildService(MergedConfig config) { + String serviceId = config.getServiceId(); + String podGroupName = "svc-" + serviceId + "-pg"; + + // 如果启用了 OSS 挂载,获取 OSS 目录级分布式锁 + String ossLockKey = buildOssLockKey(config); + String ossLock = null; + if (ossLockKey != null) { + ossLock = lockService.tryAcquireLockByKey(ossLockKey); + if (ossLock == null) { + log.warn("Failed to acquire OSS lock for service {}, key={}", serviceId, ossLockKey); + return ServiceBuildResult.failed(serviceId, "OSS_LOCK_FAILED", + "Another operation is modifying OSS directory for this service"); + } + log.info("Acquired OSS lock for service {}, key={}", serviceId, ossLockKey); + } + + try { + return doBuildService(config); + } finally { + if (ossLockKey != null && ossLock != null) { + lockService.releaseLockByKey(ossLockKey, ossLock); + log.info("Released OSS lock for service {}, key={}", serviceId, ossLockKey); + } + } + } + + private ServiceBuildResult doBuildService(MergedConfig config) { + String serviceId = config.getServiceId(); + String podGroupName = "svc-" + serviceId + "-pg"; + String namespace = config.getNamespace(); + List podNames = new ArrayList<>(); + String scheduledNode = null; + + try { + // ── 重复构建保护:先清理同 serviceId 的旧资源,再重新创建 ── + List existingPods = kubernetesClient.pods() + .inNamespace(namespace) + .withLabel("service-id", serviceId) + .list() + .getItems(); + if (!existingPods.isEmpty()) { + List oldPodNames = existingPods.stream() + .map(p -> p.getMetadata().getName()) + .collect(Collectors.toList()); + log.info("Service {} already has {} pod(s): {}, cleaning up before rebuild", + serviceId, existingPods.size(), oldPodNames); + doCleanupService(serviceId, namespace); + // 等待旧 Pod 完全删除,防止后续创建时出现 409 冲突 + waitForPodsDeleted(namespace, serviceId, 60); + } + + log.info("Building service {}, podCount={}, podMode={}, namespace={}, preferredNode={}", + serviceId, config.getPodCount(), config.getPodMode(), namespace, config.getPreferredNode()); + + createImagePullSecret(config); + createTokenSecret(config); + createAgentConfigMap(config); + ensureRunnerScriptsConfigMap(config.getNamespace()); + createPodGroup(config); + waitForPodGroupReady(namespace, podGroupName, 30); + + for (int i = 0; i < config.getPodCount(); i++) { + Pod pod = ensurePodResources(podSpecGenerator.generate(config, i), config); + Pod createdPod = createPodWithRetry(namespace, pod, 3, 2000); + String podName = createdPod.getMetadata().getName(); + podNames.add(podName); + log.info("Created Pod: {}", podName); + + eventPublisher.publishPodScheduling(serviceId, podName, i, config.getQueueName()); + } + + log.info("Service {} created successfully, pods: {}", serviceId, podNames); + + scheduledNode = waitForScheduledNodeWithEvents(namespace, serviceId, podNames, 10); + log.info("Service {} scheduled to node: {}", serviceId, scheduledNode); + + eventPublisher.publishInitComplete(serviceId, podNames.get(0), podGroupName, + podNames.size(), config.getPodCount()); + + return ServiceBuildResult.success(serviceId, podGroupName, podNames, + config.getQueueName(), scheduledNode); + + } catch (KubernetesClientException e) { + log.error("Failed to build service {}: {}", serviceId, e.getMessage(), e); + eventPublisher.publishInitFailed(serviceId, null, podGroupName, "K8S_ERROR", e.getMessage()); + cleanupService(serviceId); + return ServiceBuildResult.failed(serviceId, "K8S_ERROR", e.getMessage()); + } catch (Exception e) { + log.error("Unexpected error building service {}: {}", serviceId, e.getMessage(), e); + eventPublisher.publishInitFailed(serviceId, null, podGroupName, "INTERNAL_ERROR", e.getMessage()); + cleanupService(serviceId); + return ServiceBuildResult.failed(serviceId, "INTERNAL_ERROR", e.getMessage()); + } + } + + private String waitForScheduledNodeWithEvents(String namespace, String serviceId, + List podNames, int timeoutSeconds) { + long startTime = System.currentTimeMillis(); + long timeoutMillis = timeoutSeconds * 1000L; + + java.util.Set scheduledPods = new java.util.HashSet<>(); + String firstNodeName = null; + + while (System.currentTimeMillis() - startTime < timeoutMillis) { + try { + for (int i = 0; i < podNames.size(); i++) { + String podName = podNames.get(i); + + if (scheduledPods.contains(podName)) { + continue; + } + + Pod pod = kubernetesClient.pods() + .inNamespace(namespace) + .withName(podName) + .get(); + + if (pod != null && pod.getSpec() != null && pod.getSpec().getNodeName() != null) { + String nodeName = pod.getSpec().getNodeName(); + + eventPublisher.publishPodScheduled(serviceId, podName, i, nodeName); + scheduledPods.add(podName); + + if (firstNodeName == null) { + firstNodeName = nodeName; + } + + log.info("Pod {} scheduled to node {}", podName, nodeName); + } + } + + if (scheduledPods.size() == podNames.size()) { + break; + } + + Thread.sleep(500); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } catch (Exception e) { + log.warn("Error waiting for pods to be scheduled: {}", e.getMessage()); + } + } + + if (scheduledPods.size() < podNames.size()) { + log.warn("Timeout waiting for pods to be scheduled, scheduled: {}/{}", + scheduledPods.size(), podNames.size()); + } + + return firstNodeName; + } + + /** + * 创建镜像拉取 Secret(如果配置了仓库凭证) + */ + private void createImagePullSecret(MergedConfig config) { + String secretName = config.getImagePullSecret(); + if (secretName == null || secretName.isEmpty()) { + return; + } + + String registry = imageBuildConfig.getRegistry(); + String username = imageBuildConfig.getRegistryUsername(); + String password = imageBuildConfig.getRegistryPassword(); + + if (registry == null || registry.isEmpty() || + username == null || username.isEmpty() || + password == null || password.isEmpty()) { + log.info("Registry credentials not configured, skipping imagePullSecret creation"); + return; + } + + // 提取 host 部分作为 registry address(如 10.30.107.146/robot → 10.30.107.146:80) + // 容器运行时(containerd/Docker)按主机名匹配 auths,不含路径 + String registryHost = registry; + if (registryHost.contains("/")) { + registryHost = registryHost.substring(0, registryHost.indexOf("/")); + } + + String namespace = config.getNamespace(); + + // 检查 Secret 是否已存在 + Secret existing = kubernetesClient.secrets() + .inNamespace(namespace) + .withName(secretName) + .get(); + + if (existing != null) { + log.info("ImagePullSecret {} already exists in namespace {}", secretName, namespace); + return; + } + + // 构建 dockerconfigjson 格式的凭证 + // 格式: {"auths":{"registry-host":{"username":"user","password":"pass","auth":"base64(user:pass)"}}} + String auth = java.util.Base64.getEncoder().encodeToString((username + ":" + password).getBytes()); + String dockerConfigJson = String.format( + "{\"auths\":{\"%s\":{\"username\":\"%s\",\"password\":\"%s\",\"auth\":\"%s\"}}}", + registryHost, username, password, auth + ); + + Secret secret = new SecretBuilder() + .withNewMetadata() + .withName(secretName) + .withNamespace(namespace) + .endMetadata() + .withType("kubernetes.io/dockerconfigjson") + .addToData(".dockerconfigjson", + java.util.Base64.getEncoder().encodeToString(dockerConfigJson.getBytes())) + .build(); + + kubernetesClient.secrets() + .inNamespace(namespace) + .resource(secret) + .create(); + + log.info("Created ImagePullSecret {} in namespace {}", secretName, namespace); + } + + private void createTokenSecret(MergedConfig config) { + String secretName = "svc-" + config.getServiceId() + "-token"; + String namespace = config.getNamespace(); + + Secret existing = kubernetesClient.secrets() + .inNamespace(namespace) + .withName(secretName) + .get(); + + if (existing != null) { + log.info("Token Secret {} already exists, updating...", secretName); + kubernetesClient.secrets() + .inNamespace(namespace) + .withName(secretName) + .delete(); + } + + Secret secret = new SecretBuilder() + .withNewMetadata() + .withName(secretName) + .withNamespace(namespace) + .addToLabels("app", "ai-worker-service") + .addToLabels("service-id", config.getServiceId()) + .endMetadata() + .withType("Opaque") + .addToStringData("token", config.getToken()) + .build(); + + kubernetesClient.secrets() + .inNamespace(namespace) + .resource(secret) + .create(); + + log.info("Created Token Secret {} in namespace {}", secretName, namespace); + } + + /** + * 创建 Agent 配置文件 ConfigMap (per-service) + * + * ConfigMap 名称: svc-{serviceId}-agent-config + * 数据: config.json → 挂载到 /opt/agent/config.json + */ + private void createAgentConfigMap(MergedConfig config) { + String configMapName = PodSpecGenerator.agentConfigMapName(config.getServiceId()); + String namespace = config.getNamespace(); + + // 删除旧的(如果存在) + ConfigMap existing = kubernetesClient.configMaps() + .inNamespace(namespace) + .withName(configMapName) + .get(); + if (existing != null) { + kubernetesClient.configMaps() + .inNamespace(namespace) + .withName(configMapName) + .delete(); + log.info("Deleted existing Agent ConfigMap: {}", configMapName); + } + + // config.json 内容:优先使用 MergedConfig.configJson,否则从 classpath 加载默认配置 + String configJsonContent = config.getConfigJson(); + if (configJsonContent == null || configJsonContent.isBlank()) { + configJsonContent = loadDefaultAgentConfig(); + log.info("Agent configJson is empty, loaded default from classpath for service {}", config.getServiceId()); + } + + ConfigMap configMap = new ConfigMapBuilder() + .withNewMetadata() + .withName(configMapName) + .withNamespace(namespace) + .addToLabels("app", "ai-worker-service") + .addToLabels("service-id", config.getServiceId()) + .endMetadata() + .addToData("config.json", configJsonContent) + .build(); + + kubernetesClient.configMaps() + .inNamespace(namespace) + .resource(configMap) + .create(); + + log.info("Created Agent ConfigMap {} in namespace {}", configMapName, namespace); + } + + /** + * 从 classpath 加载默认 Agent config.json + * 与镜像构建时 COPY 进 /opt/agent/config.json 的内容一致 + */ + private String loadDefaultAgentConfig() { + try (InputStream is = getClass().getClassLoader().getResourceAsStream("scripts/config.json")) { + if (is != null) { + return new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)) + .lines().collect(Collectors.joining("\n")); + } + } catch (Exception e) { + log.warn("Failed to load default agent config from classpath: {}", e.getMessage()); + } + log.warn("Default agent config not found in classpath, falling back to '{}'"); + return "{}"; + } + + /** + * 确保 Runner 启动脚本 ConfigMap 最新 (集群级共享,所有 Pod 复用) + * + * ConfigMap 名称: runner-start-script + * 数据: start-runner.sh → 挂载到 /opt/runner/start-runner.sh + * + * 每次调用都 createOrReplace,避免旧脚本长期滞留导致权限模型不一致。 + */ + private void ensureRunnerScriptsConfigMap(String namespace) { + String configMapName = PodSpecGenerator.RUNNER_SCRIPT_CONFIGMAP; + + // 从 classpath 加载 start-runner.sh 内容 + String scriptContent = loadRunnerStartScript(); + + ConfigMap configMap = new ConfigMapBuilder() + .withNewMetadata() + .withName(configMapName) + .withNamespace(namespace) + .addToLabels("app", "ai-worker-service") + .addToLabels("component", "runner") + .endMetadata() + .addToData(PodSpecGenerator.RUNNER_SCRIPT_KEY, scriptContent) + .build(); + + kubernetesClient.configMaps() + .inNamespace(namespace) + .resource(configMap) + .createOrReplace(); + + log.info("Created/Updated Runner scripts ConfigMap {} in namespace {}", configMapName, namespace); + } + + /** + * 从 classpath 加载 start-runner.sh 脚本内容 + * 路径: classpath:scripts/start-runner.sh + */ + private String loadRunnerStartScript() { + try (InputStream is = getClass().getResourceAsStream("/scripts/start-runner.sh")) { + if (is != null) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { + return reader.lines().collect(Collectors.joining("\n")); + } + } + } catch (Exception e) { + log.warn("Failed to load start-runner.sh from classpath: {}", e.getMessage()); + } + + // 兜底:内联最小化的 start-runner.sh + log.warn("Using fallback inline start-runner.sh"); + return """ + #!/bin/bash + set -e + SHARED_KEY_DIR="/shared-keys" + PUBKEY_FILE="${SHARED_KEY_DIR}/zzd_pubkey.pub" + PUBKEY_TIMEOUT="${PUBKEY_TIMEOUT:-120}" + + echo "[Runner] Starting..." + + # 1. sshd 环境 + if [ ! -x /usr/sbin/sshd ]; then + dnf install -y openssh-server openssh-clients sudo && dnf clean all + fi + [ ! -f /etc/ssh/ssh_host_rsa_key ] && ssh-keygen -A + sed -i 's/^#*PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config + sed -i 's/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config + sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config + grep -q '^AuthorizedKeysFile' /etc/ssh/sshd_config || echo 'AuthorizedKeysFile .ssh/authorized_keys' >> /etc/ssh/sshd_config + + # 2. momobot 用户 + id momobot &>/dev/null || { groupadd -g 1000 momobot; useradd -u 1000 -g momobot -m -s /bin/bash momobot; echo 'momobot ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/momobot; chmod 0440 /etc/sudoers.d/momobot; } + + # 3. 等待公钥 + WAIT=0 + while [ ! -f "$PUBKEY_FILE" ] && [ $WAIT -lt $PUBKEY_TIMEOUT ]; do sleep 1; WAIT=$((WAIT+1)); done + [ ! -f "$PUBKEY_FILE" ] && { echo "[Runner] ERROR: pubkey timeout"; exit 1; } + + # 4. authorized_keys + mkdir -p /home/momobot/.ssh /root/.ssh + cp "$PUBKEY_FILE" /home/momobot/.ssh/authorized_keys + chown -R momobot:momobot /home/momobot/.ssh; chmod 700 /home/momobot/.ssh; chmod 600 /home/momobot/.ssh/authorized_keys + cp "$PUBKEY_FILE" /root/.ssh/authorized_keys; chmod 700 /root/.ssh; chmod 600 /root/.ssh/authorized_keys + + # 5. workspace 权限(共享组模型) + WORKSPACE_GROUP="${WORKSPACE_GROUP:-workspace}" + WORKSPACE_GID="${WORKSPACE_GID:-2000}" + if getent group "${WORKSPACE_GID}" >/dev/null 2>&1; then + resolved_group=$(getent group "${WORKSPACE_GID}" | cut -d: -f1) + elif getent group "${WORKSPACE_GROUP}" >/dev/null 2>&1; then + resolved_group="${WORKSPACE_GROUP}" + else + groupadd -g "${WORKSPACE_GID}" "${WORKSPACE_GROUP}" + resolved_group="${WORKSPACE_GROUP}" + fi + usermod -aG "${resolved_group}" momobot + for dir in /workspace /workspace/logs /workspace/user /workspace/workstation /workspace/task-logs /workspace/worker-logs; do + mkdir -p "$dir"; chgrp -R "${resolved_group}" "$dir"; chmod -R g+rwX "$dir"; find "$dir" -type d -exec chmod g+s {} +; chmod 2770 "$dir" + done + + # 6. sshd + exec /usr/sbin/sshd -D -e + """; + } + + private void createPodGroup(MergedConfig config) { + Map podGroupSpec = podGroupSpecGenerator.generate(config); + String namespace = config.getNamespace(); + + GenericKubernetesResource podGroup = new GenericKubernetesResource(); + podGroup.setApiVersion("scheduling.volcano.sh/v1beta1"); + podGroup.setKind("PodGroup"); + podGroup.setMetadata(new ObjectMetaBuilder() + .withName("svc-" + config.getServiceId() + "-pg") + .withNamespace(namespace) + .addToLabels("app", "ai-worker-service") + .addToLabels("service-id", config.getServiceId()) + .build()); + podGroup.setAdditionalProperties(Map.of( + "spec", podGroupSpec.get("spec") + )); + + kubernetesClient.genericKubernetesResources("scheduling.volcano.sh/v1beta1", "PodGroup") + .inNamespace(namespace) + .resource(podGroup) + .createOrReplace(); + + log.info("Created/Updated PodGroup svc-{}-pg in namespace {}", config.getServiceId(), namespace); + } + + /** + * 等待 PodGroup phase 离开 Pending 状态。 + * Volcano scheduler 处理 PodGroup 需要时间,如果在 phase=Pending 时就创建 Pod, + * admission webhook (validatepod.volcano.sh) 会拒绝请求。 + * + * @param namespace 命名空间 + * @param podGroupName PodGroup 名称 + * @param timeoutSeconds 最大等待秒数 + */ + private void waitForPodGroupReady(String namespace, String podGroupName, int timeoutSeconds) { + long deadline = System.currentTimeMillis() + timeoutSeconds * 1000L; + long pollIntervalMs = 500; + String phase = null; + + log.info("Waiting for PodGroup {} phase to leave Pending (timeout={}s)...", podGroupName, timeoutSeconds); + + while (System.currentTimeMillis() < deadline) { + try { + GenericKubernetesResource pg = kubernetesClient + .genericKubernetesResources("scheduling.volcano.sh/v1beta1", "PodGroup") + .inNamespace(namespace) + .withName(podGroupName) + .get(); + + if (pg != null) { + @SuppressWarnings("unchecked") + Map status = (Map) pg.getAdditionalProperties().get("status"); + phase = (status != null) ? (String) status.get("phase") : null; + + if (phase != null && !"Pending".equals(phase)) { + log.info("PodGroup {} phase is now '{}', proceeding to create Pods", podGroupName, phase); + return; + } + } + } catch (Exception e) { + log.warn("Error checking PodGroup {} status: {}", podGroupName, e.getMessage()); + } + + try { + Thread.sleep(pollIntervalMs); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.warn("Interrupted while waiting for PodGroup {}", podGroupName); + break; + } + } + + log.warn("PodGroup {} still in phase '{}' after {}s timeout, will attempt Pod creation anyway", + podGroupName, phase, timeoutSeconds); + } + + /** + * 等待指定 serviceId 的所有 Pod 完全删除。 + * 防止旧 Pod 处于 Terminating 状态时创建同名新 Pod 导致 409 冲突。 + * + * @param namespace 命名空间 + * @param serviceId 服务 ID + * @param timeoutSeconds 最大等待秒数 + */ + private void waitForPodsDeleted(String namespace, String serviceId, int timeoutSeconds) { + long deadline = System.currentTimeMillis() + timeoutSeconds * 1000L; + long pollIntervalMs = 1000; + + log.info("Waiting for all pods of service {} to be fully deleted (timeout={}s)...", serviceId, timeoutSeconds); + + while (System.currentTimeMillis() < deadline) { + try { + List remaining = kubernetesClient.pods() + .inNamespace(namespace) + .withLabel("service-id", serviceId) + .list() + .getItems(); + + if (remaining.isEmpty()) { + log.info("All pods of service {} have been fully deleted", serviceId); + return; + } + + List remainingNames = remaining.stream() + .map(p -> p.getMetadata().getName()) + .collect(Collectors.toList()); + log.debug("Still waiting for {} pod(s) to be deleted: {}", remaining.size(), remainingNames); + + } catch (Exception e) { + log.warn("Error checking pod deletion status for service {}: {}", serviceId, e.getMessage()); + } + + try { + Thread.sleep(pollIntervalMs); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.warn("Interrupted while waiting for pods of service {} to be deleted", serviceId); + break; + } + } + + log.warn("Timeout ({}s) waiting for pods of service {} to be fully deleted, proceeding anyway", + timeoutSeconds, serviceId); + } + + /** + * 创建 Pod,带重试机制。 + * 防止 PodGroup 短暂竞态导致 admission webhook 拒绝。 + * + * @param namespace 命名空间 + * @param pod Pod 对象 + * @param maxRetries 最大重试次数 + * @param retryDelayMs 重试间隔(毫秒) + * @return 创建成功的 Pod + */ + private Pod createPodWithRetry(String namespace, Pod pod, int maxRetries, long retryDelayMs) { + String podName = pod.getMetadata().getName(); + Exception lastException = null; + String podDebugJson = toDebugJson(pod); + + for (int attempt = 1; attempt <= maxRetries; attempt++) { + try { + log.info("Creating Pod {} with spec: {}", podName, podDebugJson); + return kubernetesClient.pods() + .inNamespace(namespace) + .resource(pod) + .create(); + } catch (Exception e) { + lastException = e; + String msg = e.getMessage(); + log.error("Create Pod {} failed on attempt {}/{} with spec: {}", podName, attempt, maxRetries, podDebugJson, e); + boolean isAdmissionReject = msg != null && msg.contains("podgroup phase is Pending"); + boolean isDeleteConflict = msg != null && msg.contains("object is being deleted"); + + if ((isAdmissionReject || isDeleteConflict) && attempt < maxRetries) { + log.warn("Pod {} creation failed (attempt {}/{}): {}, retrying in {}ms...", + podName, attempt, maxRetries, + isDeleteConflict ? "object is being deleted (409 Conflict)" : "admission webhook rejected", + retryDelayMs); + try { + Thread.sleep(retryDelayMs); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted while retrying Pod creation", ie); + } + } else { + // 非 admission 竞态错误 或 已达最大重试次数,直接抛出 + throw new RuntimeException("Failed to create Pod " + podName + " after " + attempt + " attempt(s)", e); + } + } + } + + // 不应该到这里,但以防万一 + throw new RuntimeException("Failed to create Pod " + podName + " after " + maxRetries + " retries", lastException); + } + + private Pod ensurePodResources(Pod pod, MergedConfig config) { + if (pod == null || pod.getSpec() == null) { + return pod; + } + + List normalizedContainers = new ArrayList<>(); + for (Container container : pod.getSpec().getContainers()) { + ResourceSpec defaults = "runner".equals(container.getName()) + ? normalizeDefaults(config.getRunnerResources(), FALLBACK_RUNNER_RESOURCES) + : normalizeDefaults(config.getAgentResources(), FALLBACK_AGENT_RESOURCES); + normalizedContainers.add(rebuildContainerResources(container, defaults)); + } + + List initContainers = pod.getSpec().getInitContainers(); + List normalizedInitContainers = new ArrayList<>(); + if (initContainers != null) { + for (Container initContainer : initContainers) { + ResourceSpec defaults = "permission-init".equals(initContainer.getName()) + ? PERMISSION_INIT_RESOURCES + : normalizeDefaults(config.getAgentResources(), FALLBACK_AGENT_RESOURCES); + normalizedInitContainers.add(rebuildContainerResources(initContainer, defaults)); + } + } + + Pod normalizedPod = new PodBuilder(pod) + .editSpec() + .withContainers(normalizedContainers) + .withInitContainers(normalizedInitContainers) + .endSpec() + .build(); + log.info("Pod {} resource summary before create: {}", pod.getMetadata().getName(), summarizePodResources(normalizedPod)); + return normalizedPod; + } + + private Container rebuildContainerResources(Container container, ResourceSpec defaults) { + if (container == null || defaults == null) { + return container; + } + + ResourceRequirements current = container.getResources(); + Map requests = current != null && current.getRequests() != null + ? new HashMap<>(current.getRequests()) + : new HashMap<>(); + Map limits = current != null && current.getLimits() != null + ? new HashMap<>(current.getLimits()) + : new HashMap<>(); + + putIfMissing(requests, "cpu", defaults.getCpuRequest()); + putIfMissing(limits, "cpu", defaults.getCpuLimit()); + putIfMissing(requests, "memory", defaults.getMemoryRequest()); + putIfMissing(limits, "memory", defaults.getMemoryLimit()); + + ResourceRequirements normalized = new ResourceRequirementsBuilder() + .withRequests(requests) + .withLimits(limits) + .build(); + return new ContainerBuilder(container) + .withResources(normalized) + .build(); + } + + private boolean putIfMissing(Map values, String key, String value) { + if (value == null || value.isBlank()) { + return false; + } + Quantity existing = values.get(key); + if (existing != null && existing.getAmount() != null && !existing.getAmount().isBlank()) { + return false; + } + values.put(key, new Quantity(value)); + return true; + } + + private ResourceSpec normalizeDefaults(ResourceSpec actual, ResourceSpec fallback) { + if (fallback == null) { + return actual; + } + if (actual == null) { + return fallback; + } + return ResourceSpec.builder() + .cpuRequest(isBlank(actual.getCpuRequest()) ? fallback.getCpuRequest() : actual.getCpuRequest()) + .cpuLimit(isBlank(actual.getCpuLimit()) ? fallback.getCpuLimit() : actual.getCpuLimit()) + .memoryRequest(isBlank(actual.getMemoryRequest()) ? fallback.getMemoryRequest() : actual.getMemoryRequest()) + .memoryLimit(isBlank(actual.getMemoryLimit()) ? fallback.getMemoryLimit() : actual.getMemoryLimit()) + .build(); + } + + private boolean isBlank(String value) { + return value == null || value.isBlank(); + } + + private String summarizePodResources(Pod pod) { + List parts = new ArrayList<>(); + if (pod.getSpec().getInitContainers() != null) { + for (Container container : pod.getSpec().getInitContainers()) { + parts.add("init:" + summarizeContainerResources(container)); + } + } + if (pod.getSpec().getContainers() != null) { + for (Container container : pod.getSpec().getContainers()) { + parts.add("main:" + summarizeContainerResources(container)); + } + } + return String.join("; ", parts); + } + + private String summarizeContainerResources(Container container) { + ResourceRequirements resources = container.getResources(); + Map requests = resources != null ? resources.getRequests() : null; + Map limits = resources != null ? resources.getLimits() : null; + return container.getName() + + "[req.cpu=" + quantityToString(requests, "cpu") + + ",req.mem=" + quantityToString(requests, "memory") + + ",lim.cpu=" + quantityToString(limits, "cpu") + + ",lim.mem=" + quantityToString(limits, "memory") + "]"; + } + + private String quantityToString(Map values, String key) { + if (values == null) { + return "null"; + } + Quantity quantity = values.get(key); + return quantity == null ? "null" : quantity.toString(); + } + + private String toDebugJson(Pod pod) { + try { + return objectMapper.writeValueAsString(pod); + } catch (Exception e) { + return "{\"error\":\"failed to serialize pod\",\"message\":\"" + e.getMessage() + "\"}"; + } + } + + @Override + public ServiceStatusResponse getServiceStatus(String serviceId) { + String namespace = envConfig.getCluster().getNamespace(); + + PodGroupStatusInfo podGroupStatus = getPodGroupStatus(serviceId, namespace); + List podStatuses = getPodStatuses(serviceId, namespace); + enrichWithMetrics(podStatuses, serviceId, namespace); + + return ServiceStatusResponse.builder() + .serviceId(serviceId) + .podGroupStatus(podGroupStatus) + .pods(podStatuses) + .updatedAt(Instant.now()) + .build(); + } + + private void enrichWithMetrics(List podStatuses, String serviceId, String namespace) { + try { + Map metricsMap = getPodMetrics(serviceId, namespace); + log.info("Found {} metrics for service {}", metricsMap.size(), serviceId); + + for (PodStatusInfo podStatus : podStatuses) { + PodMetrics metrics = metricsMap.get(podStatus.getName()); + if (metrics != null) { + log.info("Enriching metrics for pod {}", podStatus.getName()); + podStatus.setResourceUsage(aggregatePodMetrics(metrics, podStatus)); + enrichContainerMetrics(podStatus, metrics); + } + } + } catch (Exception e) { + log.warn("Failed to get metrics for service {}: {}", serviceId, e.getMessage(), e); + } + } + + private Map getPodMetrics(String serviceId, String namespace) { + Map metricsMap = new HashMap<>(); + + try { + List allMetrics = kubernetesClient.top() + .pods() + .inNamespace(namespace) + .metrics() + .getItems(); + + for (PodMetrics pm : allMetrics) { + Map labels = pm.getMetadata().getLabels(); + if (labels != null && serviceId.equals(labels.get("service-id"))) { + metricsMap.put(pm.getMetadata().getName(), pm); + } + } + } catch (Exception e) { + log.warn("Metrics not available: {}", e.getMessage()); + } + + return metricsMap; + } + + private ResourceUsageInfo aggregatePodMetrics(PodMetrics metrics, PodStatusInfo podStatus) { + long totalCpuMillicores = 0; + long totalMemoryBytes = 0; + + for (ContainerMetrics cm : metrics.getContainers()) { + Quantity cpuQuantity = cm.getUsage().get("cpu"); + Quantity memQuantity = cm.getUsage().get("memory"); + + if (cpuQuantity != null) { + totalCpuMillicores += parseMillicores(cpuQuantity.getAmount(), cpuQuantity.getFormat()); + } + if (memQuantity != null) { + totalMemoryBytes += parseBytes(memQuantity.getAmount(), memQuantity.getFormat()); + } + } + + String cpuLimit = null; + String memoryLimit = null; + String cpuRequest = null; + String memoryRequest = null; + + if (podStatus.getContainers() != null && !podStatus.getContainers().isEmpty()) { + ContainerStatusInfo firstContainer = podStatus.getContainers().get(0); + if (firstContainer.getResourceUsage() != null) { + cpuLimit = firstContainer.getResourceUsage().getCpuLimit(); + memoryLimit = firstContainer.getResourceUsage().getMemoryLimit(); + cpuRequest = firstContainer.getResourceUsage().getCpuRequest(); + memoryRequest = firstContainer.getResourceUsage().getMemoryRequest(); + } + } + + return ResourceUsageInfo.builder() + .cpuUsage(formatMillicores(totalCpuMillicores)) + .memoryUsage(formatBytes(totalMemoryBytes)) + .cpuMillicores(totalCpuMillicores) + .memoryBytes(totalMemoryBytes) + .cpuLimit(cpuLimit) + .memoryLimit(memoryLimit) + .cpuRequest(cpuRequest) + .memoryRequest(memoryRequest) + .cpuUsagePercent(calculateUsagePercent(totalCpuMillicores, cpuLimit)) + .memoryUsagePercent(calculateUsagePercent(totalMemoryBytes, memoryLimit)) + .build(); + } + + private void enrichContainerMetrics(PodStatusInfo podStatus, PodMetrics metrics) { + if (podStatus.getContainers() == null) return; + + Map containerMetricsMap = new HashMap<>(); + for (ContainerMetrics cm : metrics.getContainers()) { + containerMetricsMap.put(cm.getName(), cm); + } + + for (ContainerStatusInfo containerStatus : podStatus.getContainers()) { + ContainerMetrics cm = containerMetricsMap.get(containerStatus.getName()); + if (cm != null) { + Quantity cpuQuantity = cm.getUsage().get("cpu"); + Quantity memQuantity = cm.getUsage().get("memory"); + + long cpuMillicores = cpuQuantity != null + ? parseMillicores(cpuQuantity.getAmount(), cpuQuantity.getFormat()) : 0; + long memoryBytes = memQuantity != null + ? parseBytes(memQuantity.getAmount(), memQuantity.getFormat()) : 0; + + ResourceUsageInfo existingUsage = containerStatus.getResourceUsage(); + String cpuLimit = existingUsage != null ? existingUsage.getCpuLimit() : null; + String memoryLimit = existingUsage != null ? existingUsage.getMemoryLimit() : null; + + containerStatus.setResourceUsage(ResourceUsageInfo.builder() + .cpuUsage(formatMillicores(cpuMillicores)) + .memoryUsage(formatBytes(memoryBytes)) + .cpuMillicores(cpuMillicores) + .memoryBytes(memoryBytes) + .cpuLimit(cpuLimit) + .memoryLimit(memoryLimit) + .cpuRequest(existingUsage != null ? existingUsage.getCpuRequest() : null) + .memoryRequest(existingUsage != null ? existingUsage.getMemoryRequest() : null) + .cpuUsagePercent(calculateUsagePercent(cpuMillicores, cpuLimit)) + .memoryUsagePercent(calculateUsagePercent(memoryBytes, memoryLimit)) + .build()); + } + } + } + + private long parseMillicores(String amount, String format) { + try { + if (amount == null) return 0; + double value = Double.parseDouble(amount); + + if (format != null) { + if (format.equals("n")) { + return (long) (value / 1_000_000); + } else if (format.equals("u")) { + return (long) (value / 1_000); + } else if (format.equals("m")) { + return (long) value; + } + } + + return (long) (value * 1000); + } catch (NumberFormatException e) { + return 0; + } + } + + private long parseBytes(String amount, String format) { + try { + if (amount == null) return 0; + double value = Double.parseDouble(amount); + + if (format != null) { + switch (format) { + case "Ki": return (long) (value * 1024); + case "Mi": return (long) (value * 1024 * 1024); + case "Gi": return (long) (value * 1024 * 1024 * 1024); + case "K": return (long) (value * 1000); + case "M": return (long) (value * 1000 * 1000); + case "G": return (long) (value * 1000 * 1000 * 1000); + } + } + + return (long) value; + } catch (NumberFormatException e) { + return 0; + } + } + + private String formatMillicores(long millicores) { + if (millicores >= 1000) { + return String.format("%.2f", millicores / 1000.0); + } + return millicores + "m"; + } + + private String formatBytes(long bytes) { + if (bytes >= 1024L * 1024 * 1024) { + return String.format("%.2fGi", bytes / (1024.0 * 1024 * 1024)); + } else if (bytes >= 1024L * 1024) { + return String.format("%.2fMi", bytes / (1024.0 * 1024)); + } else if (bytes >= 1024) { + return String.format("%.2fKi", bytes / 1024.0); + } + return bytes + "B"; + } + + private Double calculateUsagePercent(long usage, String limit) { + if (limit == null || limit.isEmpty()) return null; + + try { + long limitValue; + if (limit.endsWith("m")) { + limitValue = Long.parseLong(limit.substring(0, limit.length() - 1)); + } else if (limit.endsWith("Gi")) { + limitValue = (long) (Double.parseDouble(limit.substring(0, limit.length() - 2)) * 1024 * 1024 * 1024); + } else if (limit.endsWith("Mi")) { + limitValue = (long) (Double.parseDouble(limit.substring(0, limit.length() - 2)) * 1024 * 1024); + } else { + limitValue = Long.parseLong(limit) * 1000; + } + + if (limitValue == 0) return null; + return Math.round(usage * 10000.0 / limitValue) / 100.0; + } catch (Exception e) { + return null; + } + } + + private PodGroupStatusInfo getPodGroupStatus(String serviceId, String namespace) { + String podGroupName = "svc-" + serviceId + "-pg"; + + try { + GenericKubernetesResource podGroup = kubernetesClient + .genericKubernetesResources("scheduling.volcano.sh/v1beta1", "PodGroup") + .inNamespace(namespace) + .withName(podGroupName) + .get(); + + if (podGroup == null) { + return null; + } + + @SuppressWarnings("unchecked") + Map status = (Map) podGroup.getAdditionalProperties().get("status"); + @SuppressWarnings("unchecked") + Map spec = (Map) podGroup.getAdditionalProperties().get("spec"); + + return PodGroupStatusInfo.builder() + .name(podGroupName) + .phase(status != null ? (String) status.get("phase") : "Unknown") + .minMember(spec != null ? (Integer) spec.get("minMember") : 1) + .running(status != null ? toInt(status.get("running")) : 0) + .succeeded(status != null ? toInt(status.get("succeeded")) : 0) + .failed(status != null ? toInt(status.get("failed")) : 0) + .pending(status != null ? toInt(status.get("pending")) : 0) + .build(); + } catch (Exception e) { + log.warn("Failed to get PodGroup status: {}", e.getMessage()); + return null; + } + } + + private List getPodStatuses(String serviceId, String namespace) { + List pods = kubernetesClient.pods() + .inNamespace(namespace) + .withLabel("service-id", serviceId) + .list() + .getItems(); + + return pods.stream() + .map(this::toPodStatusInfo) + .collect(Collectors.toList()); + } + + private PodStatusInfo toPodStatusInfo(Pod pod) { + PodStatus status = pod.getStatus(); + PodSpec spec = pod.getSpec(); + + Map containerSpecMap = new HashMap<>(); + if (spec != null && spec.getContainers() != null) { + for (Container c : spec.getContainers()) { + containerSpecMap.put(c.getName(), c); + } + } + + List containerStatuses = new ArrayList<>(); + if (status != null && status.getContainerStatuses() != null) { + for (ContainerStatus cs : status.getContainerStatuses()) { + Container containerSpec = containerSpecMap.get(cs.getName()); + ResourceUsageInfo resourceUsage = getContainerResourceLimits(containerSpec); + + containerStatuses.add(ContainerStatusInfo.builder() + .name(cs.getName()) + .ready(cs.getReady() != null && cs.getReady()) + .state(getContainerState(cs)) + .reason(getContainerReason(cs)) + .exitCode(getContainerExitCode(cs)) + .restartCount(cs.getRestartCount()) + .resourceUsage(resourceUsage) + .build()); + } + } + + String nodeHostname = spec != null ? spec.getNodeName() : null; + + return PodStatusInfo.builder() + .name(pod.getMetadata().getName()) + .phase(status != null ? status.getPhase() : "Unknown") + .nodeName(status != null ? status.getHostIP() : null) + .nodeHostname(nodeHostname) + .startTime(status != null && status.getStartTime() != null + ? Instant.parse(status.getStartTime()) : null) + .containers(containerStatuses) + .build(); + } + + private ResourceUsageInfo getContainerResourceLimits(Container container) { + if (container == null || container.getResources() == null) { + return null; + } + + ResourceRequirements resources = container.getResources(); + Map limits = resources.getLimits(); + Map requests = resources.getRequests(); + + return ResourceUsageInfo.builder() + .cpuLimit(limits != null && limits.get("cpu") != null + ? limits.get("cpu").getAmount() + (limits.get("cpu").getFormat() != null ? limits.get("cpu").getFormat() : "") : null) + .memoryLimit(limits != null && limits.get("memory") != null + ? limits.get("memory").getAmount() + (limits.get("memory").getFormat() != null ? limits.get("memory").getFormat() : "") : null) + .cpuRequest(requests != null && requests.get("cpu") != null + ? requests.get("cpu").getAmount() + (requests.get("cpu").getFormat() != null ? requests.get("cpu").getFormat() : "") : null) + .memoryRequest(requests != null && requests.get("memory") != null + ? requests.get("memory").getAmount() + (requests.get("memory").getFormat() != null ? requests.get("memory").getFormat() : "") : null) + .build(); + } + + private String getContainerState(ContainerStatus cs) { + if (cs.getState() == null) return "unknown"; + if (cs.getState().getRunning() != null) return "running"; + if (cs.getState().getWaiting() != null) return "waiting"; + if (cs.getState().getTerminated() != null) return "terminated"; + return "unknown"; + } + + private String getContainerReason(ContainerStatus cs) { + if (cs.getState() == null) return null; + if (cs.getState().getWaiting() != null) return cs.getState().getWaiting().getReason(); + if (cs.getState().getTerminated() != null) return cs.getState().getTerminated().getReason(); + return null; + } + + private Integer getContainerExitCode(ContainerStatus cs) { + if (cs.getState() == null) return null; + if (cs.getState().getTerminated() != null) return cs.getState().getTerminated().getExitCode(); + return null; + } + + @Override + public StopResult stopService(String serviceId, boolean graceful) { + String namespace = envConfig.getCluster().getNamespace(); + + // 如果启用了 OSS 挂载,通过 Pod label 获取 userId 并加锁 + String ossLockKey = buildOssLockKeyFromPods(serviceId, namespace); + String ossLock = null; + if (ossLockKey != null) { + ossLock = lockService.tryAcquireLockByKey(ossLockKey); + if (ossLock == null) { + log.warn("Failed to acquire OSS lock for stop service {}, key={}", serviceId, ossLockKey); + // 停止操作不应因锁失败而完全阻塞,记录警告后继续 + log.warn("Proceeding with stop without OSS lock for service {}", serviceId); + } else { + log.info("Acquired OSS lock for stop service {}, key={}", serviceId, ossLockKey); + } + } + + try { + return doStopService(serviceId, graceful, namespace); + } finally { + if (ossLockKey != null && ossLock != null) { + lockService.releaseLockByKey(ossLockKey, ossLock); + log.info("Released OSS lock for stop service {}, key={}", serviceId, ossLockKey); + } + } + } + + private StopResult doStopService(String serviceId, boolean graceful, String namespace) { + String podGroupName = "svc-" + serviceId + "-pg"; + + try { + log.info("Stopping service {}, graceful={}", serviceId, graceful); + + List pods = kubernetesClient.pods() + .inNamespace(namespace) + .withLabel("service-id", serviceId) + .list() + .getItems(); + int podCount = pods.size(); + + long gracePeriod = graceful ? 30L : 0L; + + kubernetesClient.pods() + .inNamespace(namespace) + .withLabel("service-id", serviceId) + .withGracePeriod(gracePeriod) + .delete(); + + try { + kubernetesClient.genericKubernetesResources("scheduling.volcano.sh/v1beta1", "PodGroup") + .inNamespace(namespace) + .withName(podGroupName) + .delete(); + } catch (Exception e) { + log.warn("Failed to delete PodGroup: {}", e.getMessage()); + } + + try { + String secretName = "svc-" + serviceId + "-token"; + kubernetesClient.secrets() + .inNamespace(namespace) + .withName(secretName) + .delete(); + log.info("Deleted Token Secret: {}", secretName); + } catch (Exception e) { + log.warn("Failed to delete Token Secret: {}", e.getMessage()); + } + + eventPublisher.publishSessionEnd(serviceId, podGroupName, podCount, graceful); + + return StopResult.success(serviceId); + + } catch (Exception e) { + log.error("Failed to stop service {}: {}", serviceId, e.getMessage()); + return StopResult.failed(serviceId, e.getMessage()); + } + } + + @Override + public void cleanupService(String serviceId) { + String namespace = envConfig.getCluster().getNamespace(); + + // 如果启用了 OSS 挂载,尝试加锁(cleanup 不阻塞) + String ossLockKey = buildOssLockKeyFromPods(serviceId, namespace); + String ossLock = null; + if (ossLockKey != null) { + ossLock = lockService.tryAcquireLockByKey(ossLockKey); + if (ossLock != null) { + log.info("Acquired OSS lock for cleanup service {}, key={}", serviceId, ossLockKey); + } + } + + try { + doCleanupService(serviceId, namespace); + } finally { + if (ossLockKey != null && ossLock != null) { + lockService.releaseLockByKey(ossLockKey, ossLock); + log.info("Released OSS lock for cleanup service {}, key={}", serviceId, ossLockKey); + } + } + } + + private void doCleanupService(String serviceId, String namespace) { + log.info("Cleaning up resources for service {} in namespace {}", serviceId, namespace); + + try { + kubernetesClient.genericKubernetesResources("scheduling.volcano.sh/v1beta1", "PodGroup") + .inNamespace(namespace) + .withName("svc-" + serviceId + "-pg") + .delete(); + } catch (Exception e) { + log.warn("Failed to delete PodGroup: {}", e.getMessage()); + } + + kubernetesClient.pods() + .inNamespace(namespace) + .withLabel("service-id", serviceId) + .delete(); + + kubernetesClient.secrets() + .inNamespace(namespace) + .withLabel("service-id", serviceId) + .delete(); + + kubernetesClient.configMaps() + .inNamespace(namespace) + .withLabel("service-id", serviceId) + .delete(); + + log.info("Cleaned up all resources for service {}", serviceId); + } + + // ==================== OSS 锁辅助方法 ==================== + + /** + * 从 MergedConfig 构建 OSS 锁键 + * 格式: oss:lock:{serviceId} (精确到 workstationId 级别) + * + * @return 锁键,如果 OSS 未启用返回 null + */ + private String buildOssLockKey(MergedConfig config) { + if (config.getOssMount() == null || !config.getOssMount().isEnabled()) { + return null; + } + return OSS_LOCK_PREFIX + config.getServiceId(); + } + + /** + * 从 serviceId 构建 OSS 锁键 + * 用于 stopService/cleanupService 等不传 MergedConfig 的场景 + * + * @return 锁键,如果 OSS 未启用返回 null + */ + private String buildOssLockKeyFromPods(String serviceId, String namespace) { + if (!envConfig.getOssMount().isEnabled()) { + return null; + } + return OSS_LOCK_PREFIX + serviceId; + } + + @Override + public GeneratedSpec previewSpec(MergedConfig config) { + Map podGroupSpec = podGroupSpecGenerator.generate(config); + + List> podSpecs = new ArrayList<>(); + for (int i = 0; i < config.getPodCount(); i++) { + Pod pod = podSpecGenerator.generate(config, i); + Map podMap = convertPodToMap(pod); + podSpecs.add(podMap); + } + + return GeneratedSpec.builder() + .serviceId(config.getServiceId()) + .podGroupSpec(podGroupSpec) + .podSpecs(podSpecs) + .build(); + } + + @SuppressWarnings("unchecked") + private Map convertPodToMap(Pod pod) { + try { + String json = objectMapper.writeValueAsString(pod); + return objectMapper.readValue(json, LinkedHashMap.class); + } catch (Exception e) { + log.error("Failed to convert Pod to Map: {}", e.getMessage()); + return Map.of("error", e.getMessage()); + } + } + + private int toInt(Object value) { + if (value == null) return 0; + if (value instanceof Integer) return (Integer) value; + if (value instanceof Number) return ((Number) value).intValue(); + return 0; + } + + @Override + public List getRunningPods(String serviceId) { + String namespace = envConfig.getCluster().getNamespace(); + + List pods = kubernetesClient.pods() + .inNamespace(namespace) + .withLabel("service-id", serviceId) + .list() + .getItems(); + + return pods.stream() + .filter(this::isReadyPod) + .map(pod -> pod.getMetadata().getName()) + .sorted() + .collect(Collectors.toList()); + } + + @Override + public List listAllServiceIds() { + String namespace = envConfig.getCluster().getNamespace(); + + try { + List pods = kubernetesClient.pods() + .inNamespace(namespace) + .withLabel("app", "ai-worker-service") + .list() + .getItems(); + + return pods.stream() + .filter(this::isReadyPod) + .map(pod -> pod.getMetadata().getLabels().get("service-id")) + .filter(id -> id != null && !id.isEmpty()) + .distinct() + .sorted() + .collect(Collectors.toList()); + + } catch (Exception e) { + log.error("Failed to list all service IDs from K8s: {}", e.getMessage(), e); + return List.of(); + } + } + + private boolean isReadyPod(Pod pod) { + if (pod == null || pod.getMetadata() == null || pod.getMetadata().getDeletionTimestamp() != null) { + return false; + } + if (pod.getStatus() == null || pod.getStatus().getPhase() == null) { + return false; + } + + String phase = pod.getStatus().getPhase(); + if (!"Running".equals(phase)) { + return false; + } + + List conditions = pod.getStatus().getConditions(); + if (conditions == null) { + return false; + } + + return conditions.stream() + .anyMatch(condition -> "Ready".equals(condition.getType()) && "True".equals(condition.getStatus())); + } + + @Override + public ScaleResult scaleDown(String serviceId, String podName) { + String namespace = envConfig.getCluster().getNamespace(); + + try { + List runningPods = getRunningPods(serviceId); + int previousCount = runningPods.size(); + + if (previousCount == 0) { + log.warn("No running pods found for service {}", serviceId); + return ScaleResult.failed(serviceId, "No running pods to scale down"); + } + + if (podName == null || podName.isEmpty()) { + return ScaleResult.failed(serviceId, "podName is required for scale-down"); + } + + String targetPod = podName; + if (!runningPods.contains(targetPod)) { + return ScaleResult.failed(serviceId, "Pod not found: " + targetPod); + } + + log.info("Scaling down service {}: deleting pod {}", serviceId, targetPod); + + kubernetesClient.pods() + .inNamespace(namespace) + .withName(targetPod) + .withGracePeriod(0L) + .delete(); + + runningPods.remove(targetPod); + + log.info("Service {} scaled down: {} -> {} pods", + serviceId, previousCount, runningPods.size()); + + return ScaleResult.success( + serviceId, + "SCALE_DOWN", + previousCount, + runningPods.size(), + previousCount, + runningPods, + null, + List.of(targetPod) + ); + + } catch (Exception e) { + log.error("Failed to scale down service {}: {}", serviceId, e.getMessage(), e); + return ScaleResult.failed(serviceId, e.getMessage()); + } + } + + @Override + public ScaleResult scaleUp(String serviceId, int targetPodCount, MergedConfig config) { + String namespace = envConfig.getCluster().getNamespace(); + + try { + cleanupTerminatedPods(serviceId, namespace); + + List runningPods = getRunningPods(serviceId); + int previousCount = runningPods.size(); + + if (targetPodCount <= previousCount) { + log.info("Service {} already has {} pods, target is {}, no scaling needed", + serviceId, previousCount, targetPodCount); + return ScaleResult.noChange(serviceId, previousCount, targetPodCount, runningPods); + } + + int podsToCreate = targetPodCount - previousCount; + + log.info("Scaling up service {}: {} -> {} pods (creating {} new pods)", + serviceId, previousCount, targetPodCount, podsToCreate); + + List existingIndices = runningPods.stream() + .map(name -> { + String[] parts = name.split("-"); + try { + return Integer.parseInt(parts[parts.length - 1]); + } catch (NumberFormatException e) { + return -1; + } + }) + .filter(i -> i >= 0) + .collect(Collectors.toList()); + + List addedPods = new ArrayList<>(); + int nextIndex = existingIndices.isEmpty() ? 0 : existingIndices.stream().max(Integer::compare).orElse(0) + 1; + + for (int i = 0; i < podsToCreate; i++) { + while (existingIndices.contains(nextIndex)) { + nextIndex++; + } + + Pod pod = podSpecGenerator.generate(config, nextIndex); + Pod createdPod = kubernetesClient.pods() + .inNamespace(namespace) + .resource(pod) + .create(); + + String podName = createdPod.getMetadata().getName(); + addedPods.add(podName); + runningPods.add(podName); + existingIndices.add(nextIndex); + + eventPublisher.publishPodScheduling(serviceId, podName, nextIndex, config.getQueueName()); + + log.info("Created Pod: {} (index={})", podName, nextIndex); + nextIndex++; + } + + waitForScheduledNodeWithEvents(namespace, serviceId, addedPods, 10); + + log.info("Service {} scaled up: {} -> {} pods, added: {}", + serviceId, previousCount, runningPods.size(), addedPods); + + return ScaleResult.success( + serviceId, + "SCALE_UP", + previousCount, + runningPods.size(), + targetPodCount, + runningPods, + addedPods, + null + ); + + } catch (Exception e) { + log.error("Failed to scale up service {}: {}", serviceId, e.getMessage(), e); + return ScaleResult.failed(serviceId, e.getMessage()); + } + } + + /** + * 清理已终止的 Pod(Succeeded/Failed),防止 scaleUp 创建同名 Pod 时 409 冲突。 + */ + private void cleanupTerminatedPods(String serviceId, String namespace) { + try { + List allPods = kubernetesClient.pods() + .inNamespace(namespace) + .withLabel("service-id", serviceId) + .list() + .getItems(); + + List terminatedPodNames = allPods.stream() + .filter(pod -> { + String phase = pod.getStatus() != null ? pod.getStatus().getPhase() : null; + return "Succeeded".equals(phase) || "Failed".equals(phase); + }) + .map(pod -> pod.getMetadata().getName()) + .collect(Collectors.toList()); + + if (terminatedPodNames.isEmpty()) { + return; + } + + log.info("Cleaning up {} terminated pod(s) for service {}: {}", + terminatedPodNames.size(), serviceId, terminatedPodNames); + + for (String podName : terminatedPodNames) { + kubernetesClient.pods() + .inNamespace(namespace) + .withName(podName) + .withGracePeriod(0L) + .delete(); + } + + waitForPodsDeleted(namespace, serviceId, 30); + } catch (Exception e) { + log.warn("Failed to cleanup terminated pods for service {}: {}", serviceId, e.getMessage()); + } + } +} diff --git a/back/src/main/java/com/linkwork/service/McpCryptoService.java b/back/src/main/java/com/linkwork/service/McpCryptoService.java new file mode 100644 index 0000000..a9ab20e --- /dev/null +++ b/back/src/main/java/com/linkwork/service/McpCryptoService.java @@ -0,0 +1,185 @@ +package com.linkwork.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.util.Base64; +import java.util.Map; + +/** + * AES-256-GCM 加解密服务。 + *

+ * 存储格式: base64(nonce[12] + ciphertext + tag[16]) + *

+ * 密钥来源优先级: + * 1. 环境变量 MCP_ENCRYPTION_KEY + * 2. 配置 robot.mcp.encryption-key + *

+ * 密钥必须为 32 字节(hex 64 字符 或 base64 44 字符)。 + * 当密钥未配置时,加解密方法退化为明文透传(兼容开发环境)。 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class McpCryptoService { + + private static final String ALGORITHM = "AES/GCM/NoPadding"; + private static final int GCM_NONCE_LENGTH = 12; + private static final int GCM_TAG_LENGTH = 128; + + private final ObjectMapper objectMapper; + + @Value("${robot.mcp.encryption-key:}") + private String configKey; + + private SecretKey secretKey; + private final SecureRandom secureRandom = new SecureRandom(); + + @PostConstruct + void init() { + String envKey = System.getenv("MCP_ENCRYPTION_KEY"); + String rawKey = StringUtils.hasText(envKey) ? envKey : configKey; + + if (!StringUtils.hasText(rawKey)) { + log.warn("MCP_ENCRYPTION_KEY not configured — encryption is DISABLED, data stored as plaintext"); + return; + } + + byte[] keyBytes = decodeKey(rawKey.trim()); + if (keyBytes.length != 32) { + throw new IllegalArgumentException( + "MCP_ENCRYPTION_KEY must be 32 bytes (got " + keyBytes.length + "). " + + "Use 64 hex chars or 44 base64 chars."); + } + secretKey = new SecretKeySpec(keyBytes, "AES"); + log.info("MCP encryption initialized (AES-256-GCM)"); + } + + public boolean isEnabled() { + return secretKey != null; + } + + /** + * 加密字符串,返回 base64 编码的 nonce+ciphertext+tag + */ + public String encrypt(String plaintext) { + if (!isEnabled() || !StringUtils.hasText(plaintext)) { + return plaintext; + } + try { + byte[] nonce = new byte[GCM_NONCE_LENGTH]; + secureRandom.nextBytes(nonce); + + Cipher cipher = Cipher.getInstance(ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(GCM_TAG_LENGTH, nonce)); + + byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)); + + ByteBuffer buffer = ByteBuffer.allocate(nonce.length + ciphertext.length); + buffer.put(nonce); + buffer.put(ciphertext); + + return Base64.getEncoder().encodeToString(buffer.array()); + } catch (Exception e) { + throw new RuntimeException("AES-GCM encryption failed", e); + } + } + + /** + * 解密 base64 编码的密文 + */ + public String decrypt(String cipherBase64) { + if (!isEnabled() || !StringUtils.hasText(cipherBase64)) { + return cipherBase64; + } + try { + byte[] decoded = Base64.getDecoder().decode(cipherBase64); + if (decoded.length < GCM_NONCE_LENGTH) { + log.warn("Ciphertext too short, returning as-is (possibly plaintext)"); + return cipherBase64; + } + + ByteBuffer buffer = ByteBuffer.wrap(decoded); + byte[] nonce = new byte[GCM_NONCE_LENGTH]; + buffer.get(nonce); + byte[] ciphertext = new byte[buffer.remaining()]; + buffer.get(ciphertext); + + Cipher cipher = Cipher.getInstance(ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(GCM_TAG_LENGTH, nonce)); + + byte[] plaintext = cipher.doFinal(ciphertext); + return new String(plaintext, StandardCharsets.UTF_8); + } catch (IllegalArgumentException e) { + log.debug("Not base64, treating as plaintext: {}", e.getMessage()); + return cipherBase64; + } catch (Exception e) { + log.warn("AES-GCM decryption failed, returning as-is (possibly plaintext data): {}", e.getMessage()); + return cipherBase64; + } + } + + /** + * 加密 Map 为加密后的 JSON 字符串 + */ + public String encryptMap(Map map) { + if (map == null || map.isEmpty()) { + return null; + } + try { + String json = objectMapper.writeValueAsString(map); + return encrypt(json); + } catch (JsonProcessingException e) { + throw new RuntimeException("Failed to serialize map for encryption", e); + } + } + + /** + * 解密 JSON 字符串为 Map + */ + public Map decryptMap(String encrypted) { + if (!StringUtils.hasText(encrypted)) { + return null; + } + String json = decrypt(encrypted); + try { + return objectMapper.readValue(json, new TypeReference<>() {}); + } catch (JsonProcessingException e) { + log.warn("Failed to parse decrypted map, returning null: {}", e.getMessage()); + return null; + } + } + + private byte[] decodeKey(String raw) { + if (raw.matches("[0-9a-fA-F]+") && raw.length() == 64) { + return hexToBytes(raw); + } + try { + return Base64.getDecoder().decode(raw); + } catch (IllegalArgumentException e) { + return raw.getBytes(StandardCharsets.UTF_8); + } + } + + private byte[] hexToBytes(String hex) { + byte[] bytes = new byte[hex.length() / 2]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16); + } + return bytes; + } +} diff --git a/back/src/main/java/com/linkwork/service/McpDiscoveryService.java b/back/src/main/java/com/linkwork/service/McpDiscoveryService.java new file mode 100644 index 0000000..30ccc19 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/McpDiscoveryService.java @@ -0,0 +1,201 @@ +package com.linkwork.service; + +import com.linkwork.agent.mcp.core.McpClient; +import com.linkwork.agent.mcp.core.model.McpDiscoverResponse; +import com.linkwork.agent.mcp.core.model.McpEndpoint; +import com.linkwork.model.dto.McpDiscoverResult; +import com.linkwork.model.entity.McpServerEntity; +import com.linkwork.model.entity.McpUserConfigEntity; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * MCP 工具发现服务(基于 linkwork-mcp-starter)。 + */ +@Service +@Slf4j +public class McpDiscoveryService { + + private final McpClient mcpClient; + private final McpUserConfigService mcpUserConfigService; + + public McpDiscoveryService(McpClient mcpClient, McpUserConfigService mcpUserConfigService) { + this.mcpClient = mcpClient; + this.mcpUserConfigService = mcpUserConfigService; + } + + public McpDiscoverResult discover(McpServerEntity server) { + return discover(server, null); + } + + public McpDiscoverResult discover(McpServerEntity server, String userId) { + DiscoveryTarget target = resolveDiscoveryTarget(server, userId); + if (!StringUtils.hasText(target.getUrl())) { + return McpDiscoverResult.builder() + .success(false) + .error("No URL configured for MCP server") + .build(); + } + + try { + McpEndpoint endpoint = toEndpoint(server, target); + McpDiscoverResponse response = mcpClient.discover(endpoint); + if (response == null || !response.isSuccess()) { + return McpDiscoverResult.builder() + .success(false) + .error(response == null ? "discover response is null" : response.getMessage()) + .build(); + } + + List tools = new ArrayList<>(); + if (response.getTools() != null) { + response.getTools().forEach(tool -> tools.add(McpDiscoverResult.McpTool.builder() + .name(tool.getName()) + .description(tool.getDescription()) + .inputSchema(tool.getInputSchema()) + .build())); + } + + return McpDiscoverResult.builder() + .success(true) + .error(null) + .serverName(response.getServerName()) + .serverVersion(response.getServerVersion()) + .protocolVersion(response.getProtocolVersion()) + .tools(tools) + .build(); + } catch (Exception e) { + String error = e.getClass().getSimpleName() + ": " + e.getMessage(); + if (error.length() > 500) { + error = error.substring(0, 500); + } + log.error("MCP discover failed for {}: {}", server.getName(), error); + return McpDiscoverResult.builder() + .success(false) + .error(error) + .build(); + } + } + + DiscoveryTarget resolveDiscoveryTarget(McpServerEntity server, String userId) { + String serverUrl = resolveUrl(server); + Map mergedHeaders = new LinkedHashMap<>(); + if (server.getHeaders() != null) { + mergedHeaders.putAll(server.getHeaders()); + } + + if (StringUtils.hasText(userId) && server.getId() != null) { + McpUserConfigEntity userConfig = mcpUserConfigService.getByUserAndServer(userId, server.getId()); + if (userConfig != null) { + mergePreferredValues(mergedHeaders, userConfig.getHeaders()); + serverUrl = applyUrlParams(serverUrl, userConfig.getUrlParams()); + } + } + + return new DiscoveryTarget(serverUrl, mergedHeaders); + } + + private McpEndpoint toEndpoint(McpServerEntity server, DiscoveryTarget target) { + McpEndpoint endpoint = new McpEndpoint(); + endpoint.setType(StringUtils.hasText(server.getType()) ? server.getType() : "http"); + endpoint.setUrl(target.getUrl()); + endpoint.setHeaders(target.getHeaders()); + + Map configJson = server.getConfigJson(); + if (configJson != null) { + endpoint.setCommand(parseCommand(configJson.get("command"))); + endpoint.setEnv(parseEnv(configJson.get("env"))); + } + return endpoint; + } + + private List parseCommand(Object raw) { + if (raw instanceof List list) { + List cmd = new ArrayList<>(); + for (Object item : list) { + if (item != null && StringUtils.hasText(String.valueOf(item))) { + cmd.add(String.valueOf(item)); + } + } + return cmd; + } + if (raw instanceof String text && StringUtils.hasText(text)) { + return List.of(text); + } + return null; + } + + @SuppressWarnings("unchecked") + private Map parseEnv(Object raw) { + if (!(raw instanceof Map map)) { + return Collections.emptyMap(); + } + Map env = new LinkedHashMap<>(); + ((Map) map).forEach((k, v) -> { + if (k != null && v != null && StringUtils.hasText(String.valueOf(k))) { + env.put(String.valueOf(k), String.valueOf(v)); + } + }); + return env; + } + + private String resolveUrl(McpServerEntity server) { + if (StringUtils.hasText(server.getUrl())) { + return server.getUrl(); + } + return server.getEndpoint(); + } + + private void mergePreferredValues(Map base, Map preferred) { + if (preferred == null || preferred.isEmpty()) { + return; + } + preferred.forEach((key, value) -> { + if (StringUtils.hasText(key) && value != null) { + base.put(key, value); + } + }); + } + + private String applyUrlParams(String baseUrl, Map urlParams) { + if (!StringUtils.hasText(baseUrl) || urlParams == null || urlParams.isEmpty()) { + return baseUrl; + } + + UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(baseUrl); + urlParams.forEach((key, value) -> { + if (StringUtils.hasText(key) && value != null) { + builder.replaceQueryParam(key, value); + } + }); + return builder.build().toUriString(); + } + + static final class DiscoveryTarget { + private final String url; + private final Map headers; + + DiscoveryTarget(String url, Map headers) { + this.url = url; + this.headers = headers != null + ? Collections.unmodifiableMap(new LinkedHashMap<>(headers)) + : Collections.emptyMap(); + } + + String getUrl() { + return url; + } + + Map getHeaders() { + return headers; + } + } +} diff --git a/back/src/main/java/com/linkwork/service/McpHealthChecker.java b/back/src/main/java/com/linkwork/service/McpHealthChecker.java new file mode 100644 index 0000000..f4ca0e8 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/McpHealthChecker.java @@ -0,0 +1,242 @@ +package com.linkwork.service; + +import com.linkwork.agent.mcp.core.McpClient; +import com.linkwork.agent.mcp.core.model.McpEndpoint; +import com.linkwork.agent.mcp.core.model.McpProbeResponse; +import com.linkwork.model.dto.McpProbeResult; +import com.linkwork.model.entity.McpServerEntity; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestTemplate; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * MCP Server 健康检查组件(基于 linkwork-mcp-starter)。 + */ +@Component +@Slf4j +public class McpHealthChecker { + + private final McpServerService mcpServerService; + private final McpClient mcpClient; + private final RestTemplate healthRestTemplate; + + public McpHealthChecker(McpServerService mcpServerService, McpClient mcpClient) { + this.mcpServerService = mcpServerService; + this.mcpClient = mcpClient; + + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setConnectTimeout(5000); + factory.setReadTimeout(5000); + this.healthRestTemplate = new RestTemplate(factory); + } + + @Scheduled(fixedRate = 30_000) + public void healthCheckAll() { + try { + List servers = mcpServerService.listByTypes(List.of("http", "sse")); + if (servers.isEmpty()) { + return; + } + for (McpServerEntity server : servers) { + checkSingle(server); + } + } catch (Exception e) { + log.error("MCP health check cycle failed unexpectedly", e); + } + } + + public McpProbeResult probeSingle(McpServerEntity server) { + String probeUrl = resolveProbeUrl(server); + if (!StringUtils.hasText(probeUrl)) { + return McpProbeResult.builder() + .status("offline") + .latencyMs(0) + .message("No probe URL configured") + .probeUrl(null) + .build(); + } + + if (hasDedicatedHealthCheckUrl(server)) { + return probeByHttp(server, probeUrl); + } + return probeByMcpClient(server, probeUrl); + } + + private void checkSingle(McpServerEntity server) { + McpProbeResult result = probeSingle(server); + + if ("online".equals(result.getStatus()) || "degraded".equals(result.getStatus())) { + int consecutiveFailures = "online".equals(result.getStatus()) + ? 0 + : (server.getConsecutiveFailures() != null ? server.getConsecutiveFailures() + 1 : 1); + mcpServerService.updateHealth(server.getId(), result.getStatus(), result.getLatencyMs(), + result.getMessage(), consecutiveFailures); + return; + } + + handleFailure(server, result.getMessage(), result.getLatencyMs()); + } + + private McpProbeResult probeByMcpClient(McpServerEntity server, String probeUrl) { + long start = System.currentTimeMillis(); + try { + McpEndpoint endpoint = toEndpoint(server, probeUrl); + McpProbeResponse response = mcpClient.probe(endpoint); + int latencyMs = response != null && response.getLatencyMs() > 0 + ? response.getLatencyMs() + : (int) Math.max(1, System.currentTimeMillis() - start); + + if (response != null && response.isSuccess()) { + String status = latencyMs < 2000 ? "online" : "degraded"; + return McpProbeResult.builder() + .status(status) + .latencyMs(latencyMs) + .message("MCP OK (" + latencyMs + "ms)") + .probeUrl(probeUrl) + .build(); + } + + return McpProbeResult.builder() + .status("offline") + .latencyMs(latencyMs) + .message(response == null ? "mcp probe failed" : response.getMessage()) + .probeUrl(probeUrl) + .build(); + } catch (Exception e) { + int latencyMs = (int) Math.max(1, System.currentTimeMillis() - start); + String error = e.getClass().getSimpleName() + ": " + e.getMessage(); + if (error.length() > 250) { + error = error.substring(0, 250); + } + return McpProbeResult.builder() + .status("offline") + .latencyMs(latencyMs) + .message(error) + .probeUrl(probeUrl) + .build(); + } + } + + /** + * 兼容 dedicated healthCheckUrl(普通 HTTP 健康检查)。 + */ + private McpProbeResult probeByHttp(McpServerEntity server, String probeUrl) { + long start = System.currentTimeMillis(); + try { + HttpMethod method = "sse".equalsIgnoreCase(server.getType()) ? HttpMethod.HEAD : HttpMethod.GET; + ResponseEntity resp = healthRestTemplate.exchange(probeUrl, method, HttpEntity.EMPTY, String.class); + int latency = (int) Math.max(1, System.currentTimeMillis() - start); + int statusCode = resp.getStatusCode().value(); + if (statusCode >= 200 && statusCode < 400) { + return McpProbeResult.builder() + .status(latency < 2000 ? "online" : "degraded") + .latencyMs(latency) + .message("HTTP " + statusCode + " (" + latency + "ms)") + .probeUrl(probeUrl) + .build(); + } + return McpProbeResult.builder() + .status("offline") + .latencyMs(latency) + .message("HTTP " + statusCode) + .probeUrl(probeUrl) + .build(); + } catch (Exception e) { + int latency = (int) Math.max(1, System.currentTimeMillis() - start); + String error = e.getClass().getSimpleName() + ": " + e.getMessage(); + if (error.length() > 250) { + error = error.substring(0, 250); + } + return McpProbeResult.builder() + .status("offline") + .latencyMs(latency) + .message(error) + .probeUrl(probeUrl) + .build(); + } + } + + private McpEndpoint toEndpoint(McpServerEntity server, String probeUrl) { + McpEndpoint endpoint = new McpEndpoint(); + endpoint.setType(StringUtils.hasText(server.getType()) ? server.getType() : "http"); + endpoint.setUrl(probeUrl); + endpoint.setHeaders(server.getHeaders()); + + Map configJson = server.getConfigJson(); + if (configJson != null) { + endpoint.setCommand(parseCommand(configJson.get("command"))); + endpoint.setEnv(parseEnv(configJson.get("env"))); + } + return endpoint; + } + + private List parseCommand(Object raw) { + if (raw instanceof List list) { + List cmd = new ArrayList<>(); + for (Object item : list) { + if (item != null && StringUtils.hasText(String.valueOf(item))) { + cmd.add(String.valueOf(item)); + } + } + return cmd; + } + if (raw instanceof String text && StringUtils.hasText(text)) { + return List.of(text); + } + return null; + } + + @SuppressWarnings("unchecked") + private Map parseEnv(Object raw) { + if (!(raw instanceof Map map)) { + return Collections.emptyMap(); + } + Map env = new LinkedHashMap<>(); + ((Map) map).forEach((k, v) -> { + if (k != null && v != null && StringUtils.hasText(String.valueOf(k))) { + env.put(String.valueOf(k), String.valueOf(v)); + } + }); + return env; + } + + private void handleFailure(McpServerEntity server, String errorMessage, int latencyMs) { + int currentFailures = server.getConsecutiveFailures() != null ? server.getConsecutiveFailures() : 0; + int newFailures = currentFailures + 1; + String status = newFailures >= 3 ? "offline" : "degraded"; + + if (errorMessage != null && errorMessage.length() > 250) { + errorMessage = errorMessage.substring(0, 250); + } + + mcpServerService.updateHealth(server.getId(), status, latencyMs, errorMessage, newFailures); + log.warn("MCP server {} health check failed (attempt {}): {} -> {}", + server.getName(), newFailures, errorMessage, status); + } + + private String resolveProbeUrl(McpServerEntity server) { + if (StringUtils.hasText(server.getHealthCheckUrl())) { + return server.getHealthCheckUrl(); + } + if (StringUtils.hasText(server.getUrl())) { + return server.getUrl(); + } + return server.getEndpoint(); + } + + private boolean hasDedicatedHealthCheckUrl(McpServerEntity server) { + return StringUtils.hasText(server.getHealthCheckUrl()); + } +} diff --git a/back/src/main/java/com/linkwork/service/McpServerService.java b/back/src/main/java/com/linkwork/service/McpServerService.java new file mode 100644 index 0000000..f070fa6 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/McpServerService.java @@ -0,0 +1,643 @@ +package com.linkwork.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.linkwork.common.ForbiddenOperationException; +import com.linkwork.common.ResourceNotFoundException; +import com.linkwork.mapper.McpServerMapper; +import com.linkwork.model.entity.McpServerEntity; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.net.URI; +import java.net.URISyntaxException; +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +/** + * MCP 服务 Service + */ +@Slf4j +@Service +public class McpServerService extends ServiceImpl { + + @Value("${robot.mcp-gateway.agent-base-url:}") + private String mcpGatewayAgentBaseUrl; + + @Autowired + private McpCryptoService cryptoService; + + private static final Set SUPPORTED_TYPES = Set.of("http", "sse"); + private static final Set SUPPORTED_VISIBILITIES = Set.of("public", "private"); + private static final Set SUPPORTED_STATUSES = Set.of("online", "degraded", "offline", "unknown"); + private static final Set SENSITIVE_HEADER_KEYS = Set.of( + "authorization", "proxy-authorization", + "cookie", "set-cookie", + "x-api-key", "apikey", "api-key", + "token", "access-token", "refresh-token", + "secret", "client-secret", "app-secret", + "password", "passwd" + ); + private static final Set SUPPORTED_NETWORK_ZONES = Set.of("internal", "office", "external"); + @Autowired + private AdminAccessService adminAccessService; + + /** + * 创建 MCP 服务 + */ + @SuppressWarnings("unchecked") + public McpServerEntity createMcpServer(Map request, String userId, String userName) { + McpServerEntity entity = new McpServerEntity(); + entity.setMcpNo("MCP-" + System.currentTimeMillis()); + entity.setName(normalizeRequiredText(request.get("name"), "MCP 名称不能为空")); + entity.setEndpoint(normalizeOptionalText(request.get("endpoint"))); + entity.setDescription(normalizeOptionalText(request.get("description"))); + entity.setVisibility(normalizeVisibility(request.getOrDefault("visibility", "private"))); + entity.setStatus("unknown"); + entity.setCreatorId(userId); + entity.setCreatorName(userName); + + entity.setType(normalizeType(request.getOrDefault("type", "http"))); + entity.setUrl(normalizeOptionalText(request.get("url"))); + entity.setHealthCheckUrl(normalizeOptionalText(request.get("healthCheckUrl"))); + entity.setVersion(normalizeOptionalText(request.get("version"))); + entity.setNetworkZone(normalizeNetworkZone(request.getOrDefault("networkZone", "external"))); + entity.setConsecutiveFailures(0); + + if (request.containsKey("headers")) { + entity.setHeaders((Map) request.get("headers")); + } + if (request.containsKey("tags")) { + entity.setTags((List) request.get("tags")); + } + if (request.containsKey("configJson")) { + entity.setConfigJson((Map) request.get("configJson")); + } + validateConnectivityFields(entity); + encryptSensitiveFields(entity); + + this.save(entity); + log.info("Created MCP server: {} (type={}) by user {}", entity.getMcpNo(), entity.getType(), userId); + decryptSensitiveFields(entity); + return entity; + } + + /** + * 更新 MCP 服务 + */ + @SuppressWarnings("unchecked") + public McpServerEntity updateMcpServer(Long id, Map request, String userId, String userName) { + McpServerEntity entity = requireOwnedMcpServer(id, userId); + + if (request.containsKey("name")) { + entity.setName(normalizeRequiredText(request.get("name"), "MCP 名称不能为空")); + } + if (request.containsKey("endpoint")) { + entity.setEndpoint(normalizeOptionalText(request.get("endpoint"))); + } + if (request.containsKey("description")) { + entity.setDescription(normalizeOptionalText(request.get("description"))); + } + if (request.containsKey("visibility")) { + entity.setVisibility(normalizeVisibility(request.get("visibility"))); + } + if (request.containsKey("status")) { + entity.setStatus(normalizeStatus(request.get("status"))); + } + if (request.containsKey("configJson")) { + entity.setConfigJson((Map) request.get("configJson")); + } + + if (request.containsKey("type")) { + entity.setType(normalizeType(request.get("type"))); + } + if (request.containsKey("url")) { + entity.setUrl(normalizeOptionalText(request.get("url"))); + } + if (request.containsKey("headers")) { + entity.setHeaders((Map) request.get("headers")); + } + if (request.containsKey("healthCheckUrl")) { + entity.setHealthCheckUrl(normalizeOptionalText(request.get("healthCheckUrl"))); + } + if (request.containsKey("version")) { + entity.setVersion(normalizeOptionalText(request.get("version"))); + } + if (request.containsKey("tags")) { + entity.setTags((List) request.get("tags")); + } + if (request.containsKey("networkZone")) { + entity.setNetworkZone(normalizeNetworkZone(request.get("networkZone"))); + } + validateConnectivityFields(entity); + encryptSensitiveFields(entity); + + entity.setUpdaterId(userId); + entity.setUpdaterName(userName); + + this.updateById(entity); + log.info("Updated MCP server: {} by user {}", entity.getMcpNo(), userId); + decryptSensitiveFields(entity); + return entity; + } + + /** + * 按类型查询 MCP Server 列表(内部 API / Gateway 使用,需解密) + */ + public List listByTypes(List types) { + if (types == null || types.isEmpty()) { + return Collections.emptyList(); + } + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.in(McpServerEntity::getType, types); + List result = this.list(wrapper); + result.forEach(this::decryptSensitiveFields); + return result; + } + + /** + * 更新 MCP Server 健康状态 + */ + public void updateHealth(Long id, String status, Integer latencyMs, String message, int consecutiveFailures) { + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); + wrapper.eq(McpServerEntity::getId, id) + .set(McpServerEntity::getStatus, status) + .set(McpServerEntity::getHealthLatencyMs, latencyMs) + .set(McpServerEntity::getHealthMessage, message) + .set(McpServerEntity::getConsecutiveFailures, consecutiveFailures) + .set(McpServerEntity::getLastHealthAt, LocalDateTime.now()); + this.update(wrapper); + } + + /** + * 根据 MCP ID 列表生成 SDK 兼容的 mcp.json 格式 + *

+ * 当 mcpGatewayAgentBaseUrl 配置非空时,URL 指向 Gateway 代理地址,不暴露原始 URL 和 Headers; + * 未配置时回退为直连模式(向下兼容)。 + * + * @return { "mcpServers": { "name": { "type": "http", "url": "...", "headers": {...} } }, "globalHeaders": {...} } + */ + public Map generateMcpConfig(List mcpIds) { + if (mcpIds == null || mcpIds.isEmpty()) { + return Map.of("mcpServers", Collections.emptyMap()); + } + + List servers = this.listByIds(mcpIds); + servers.forEach(this::decryptSensitiveFields); + Map mcpServers = new LinkedHashMap<>(); + + boolean useGateway = StringUtils.hasText(mcpGatewayAgentBaseUrl); + + for (McpServerEntity server : servers) { + Map serverConfig = new LinkedHashMap<>(); + serverConfig.put("type", server.getType() != null ? server.getType() : "http"); + + if (useGateway) { + String gatewayUrl = mcpGatewayAgentBaseUrl.replaceAll("/+$", "") + + "/proxy/" + server.getName() + "/mcp"; + serverConfig.put("url", gatewayUrl); + } else { + String serverUrl = server.getUrl(); + if (!StringUtils.hasText(serverUrl)) { + serverUrl = server.getEndpoint(); + } + if (StringUtils.hasText(serverUrl)) { + serverConfig.put("url", serverUrl); + } + if (server.getHeaders() != null && !server.getHeaders().isEmpty()) { + serverConfig.put("headers", server.getHeaders()); + } + } + + mcpServers.put(server.getName(), serverConfig); + } + + Map result = new LinkedHashMap<>(); + result.put("mcpServers", mcpServers); + + if (useGateway) { + Map globalHeaders = new LinkedHashMap<>(); + globalHeaders.put("X-Task-Id", "{taskid}"); + globalHeaders.put("X-User-Id", "{userid}"); + result.put("globalHeaders", globalHeaders); + } + + return result; + } + + /** + * 返回所有 MCP Server 的健康状态列表 + */ + public Map getHealthStatus(String userId) { + if (!StringUtils.hasText(userId)) { + return Map.of("items", List.of(), "checkedAt", LocalDateTime.now()); + } + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (!adminAccessService.isAdmin(userId)) { + wrapper.eq(McpServerEntity::getCreatorId, userId); + } + List allServers = this.list(wrapper); + List> items = allServers.stream() + .map(this::toHealthMap) + .collect(Collectors.toList()); + + Map result = new LinkedHashMap<>(); + result.put("items", items); + result.put("checkedAt", LocalDateTime.now()); + return result; + } + + /** + * 获取 MCP 服务列表(分页) + */ + public Map listMcpServers(int page, int pageSize, String status, String keyword, String userId) { + Page pageObj = new Page<>(page, pageSize); + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + applyVisibilityScope(wrapper, userId); + String normalizedStatus = normalizeStatusForQuery(status); + if (StringUtils.hasText(normalizedStatus)) { + wrapper.eq(McpServerEntity::getStatus, normalizedStatus); + } + if (StringUtils.hasText(keyword)) { + wrapper.and(w -> w.like(McpServerEntity::getName, keyword) + .or().like(McpServerEntity::getDescription, keyword)); + } + wrapper.orderByDesc(McpServerEntity::getCreatedAt); + + Page result = this.page(pageObj, wrapper); + result.getRecords().forEach(this::decryptSensitiveFields); + + List> items = result.getRecords().stream() + .map(entity -> toResponseMap(entity, userId)) + .collect(Collectors.toList()); + + Map response = new HashMap<>(); + response.put("items", items); + response.put("pagination", Map.of( + "page", result.getCurrent(), + "pageSize", result.getSize(), + "total", result.getTotal(), + "totalPages", result.getPages() + )); + return response; + } + + /** + * 获取所有可用的 MCP 服务(用于下拉选择) + */ + public List> listAllAvailable(String userId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + applyVisibilityScope(wrapper, userId); + wrapper.orderByDesc(McpServerEntity::getCreatedAt); + + return this.list(wrapper).stream() + .map(entity -> toSimpleMap(entity, userId)) + .collect(Collectors.toList()); + } + + /** + * 获取当前用户可访问的 MCP 服务详情 + */ + public Map getMcpServerForRead(Long id, String userId) { + McpServerEntity entity = requireReadableMcpServer(id, userId); + return toResponseMap(entity, userId); + } + + /** + * 获取当前用户可操作(写/管理)的 MCP 服务详情 + */ + public McpServerEntity getMcpServerForManage(Long id, String userId) { + McpServerEntity entity = requireOwnedMcpServer(id, userId); + decryptSensitiveFields(entity); + return entity; + } + + /** + * 删除 MCP 服务(仅创建者) + */ + public void deleteMcpServer(Long id, String userId) { + McpServerEntity entity = requireOwnedMcpServer(id, userId); + this.removeById(entity.getId()); + } + + private McpServerEntity requireOwnedMcpServer(Long id, String userId) { + McpServerEntity entity = this.getById(id); + if (entity == null) { + throw new ResourceNotFoundException("MCP server not found: " + id); + } + if (!canManage(entity, userId)) { + throw new ForbiddenOperationException("仅 MCP 创建者或管理员可访问或修改"); + } + return entity; + } + + private McpServerEntity requireReadableMcpServer(Long id, String userId) { + McpServerEntity entity = this.getById(id); + if (entity == null) { + throw new ResourceNotFoundException("MCP server not found: " + id); + } + if (adminAccessService.isAdmin(userId)) { + return entity; + } + boolean isOwner = StringUtils.hasText(userId) && userId.equals(entity.getCreatorId()); + boolean isPublic = "public".equals(coerceVisibility(entity.getVisibility())); + if (!isOwner && !isPublic) { + throw new ForbiddenOperationException("无权限访问该 MCP 服务"); + } + return entity; + } + + private String normalizeOptionalText(Object raw) { + if (raw == null) { + return null; + } + String value = String.valueOf(raw).trim(); + return value.isEmpty() ? null : value; + } + + private String normalizeRequiredText(Object raw, String message) { + String value = normalizeOptionalText(raw); + if (!StringUtils.hasText(value)) { + throw new IllegalArgumentException(message); + } + return value; + } + + private String normalizeType(Object rawType) { + String value = normalizeOptionalText(rawType); + if (!StringUtils.hasText(value)) { + return "http"; + } + String normalized = value.toLowerCase(Locale.ROOT); + if (!SUPPORTED_TYPES.contains(normalized)) { + throw new IllegalArgumentException("非法 MCP 类型: " + value + ",仅支持 http/sse"); + } + return normalized; + } + + private String normalizeVisibility(Object rawVisibility) { + String value = normalizeOptionalText(rawVisibility); + if (!StringUtils.hasText(value)) { + return "private"; + } + String normalized = value.toLowerCase(Locale.ROOT); + if (!SUPPORTED_VISIBILITIES.contains(normalized)) { + throw new IllegalArgumentException("非法 MCP 可见性: " + value + ",仅支持 public/private"); + } + return normalized; + } + + private String normalizeStatus(Object rawStatus) { + String value = normalizeOptionalText(rawStatus); + if (!StringUtils.hasText(value)) { + return "unknown"; + } + String normalized = value.toLowerCase(Locale.ROOT); + if (!SUPPORTED_STATUSES.contains(normalized)) { + throw new IllegalArgumentException("非法 MCP 状态: " + value + ",仅支持 online/degraded/offline/unknown"); + } + return normalized; + } + + private String normalizeStatusForQuery(String rawStatus) { + String value = normalizeOptionalText(rawStatus); + if (!StringUtils.hasText(value)) { + return null; + } + return normalizeStatus(value); + } + + private String coerceVisibility(String rawVisibility) { + if (!StringUtils.hasText(rawVisibility)) { + return "private"; + } + String normalized = rawVisibility.trim().toLowerCase(Locale.ROOT); + return SUPPORTED_VISIBILITIES.contains(normalized) ? normalized : "private"; + } + + private String coerceStatus(String rawStatus) { + if (!StringUtils.hasText(rawStatus)) { + return "unknown"; + } + String normalized = rawStatus.trim().toLowerCase(Locale.ROOT); + return SUPPORTED_STATUSES.contains(normalized) ? normalized : "unknown"; + } + + private String normalizeNetworkZone(Object rawZone) { + String value = normalizeOptionalText(rawZone); + if (!StringUtils.hasText(value)) { + return "external"; + } + String normalized = value.toLowerCase(Locale.ROOT); + if (!SUPPORTED_NETWORK_ZONES.contains(normalized)) { + throw new IllegalArgumentException("非法网段标记: " + value + ",仅支持 internal/office/external"); + } + return normalized; + } + + private void validateConnectivityFields(McpServerEntity entity) { + if (!StringUtils.hasText(entity.getUrl()) && !StringUtils.hasText(entity.getEndpoint())) { + throw new IllegalArgumentException("MCP url/endpoint 不能为空"); + } + } + + /** + * 保存前加密 URL 和 Headers(仅当加密服务启用时) + */ + private void encryptSensitiveFields(McpServerEntity entity) { + if (!cryptoService.isEnabled()) return; + if (StringUtils.hasText(entity.getUrl())) { + entity.setUrl(cryptoService.encrypt(entity.getUrl())); + } + } + + /** + * 读取后解密 URL 和 Headers(兼容明文和密文混存) + */ + private void decryptSensitiveFields(McpServerEntity entity) { + if (!cryptoService.isEnabled() || entity == null) return; + if (StringUtils.hasText(entity.getUrl())) { + entity.setUrl(cryptoService.decrypt(entity.getUrl())); + } + } + + private void applyVisibilityScope(LambdaQueryWrapper wrapper, String userId) { + if (StringUtils.hasText(userId)) { + if (adminAccessService.isAdmin(userId)) { + return; + } + wrapper.and(w -> w.apply("creator_id = {0} OR visibility = 'public'", userId)); + return; + } + wrapper.apply("visibility = 'public'"); + } + + private Map toResponseMap(McpServerEntity entity, String userId) { + boolean canManage = canManage(entity, userId); + boolean masked = shouldMaskSensitiveFields(entity, userId); + String urlForDisplay = firstNonBlank(entity.getUrl(), entity.getEndpoint()); + String healthUrlForDisplay = firstNonBlank(entity.getHealthCheckUrl(), entity.getUrl(), entity.getEndpoint()); + + Map map = new HashMap<>(); + map.put("id", entity.getId().toString()); + map.put("mcpNo", entity.getMcpNo()); + map.put("name", entity.getName()); + map.put("endpoint", masked ? null : entity.getEndpoint()); + map.put("description", entity.getDescription()); + map.put("visibility", coerceVisibility(entity.getVisibility())); + map.put("status", coerceStatus(entity.getStatus())); + map.put("type", entity.getType()); + map.put("url", masked ? null : entity.getUrl()); + map.put("headers", masked ? null : entity.getHeaders()); + map.put("networkZone", entity.getNetworkZone() != null ? entity.getNetworkZone() : "external"); + map.put("healthCheckUrl", masked ? null : entity.getHealthCheckUrl()); + map.put("displayUrl", maskUrlForDisplay(urlForDisplay)); + map.put("displayHeaders", maskHeadersForDisplay(entity.getHeaders())); + map.put("displayHealthCheckUrl", maskUrlForDisplay(healthUrlForDisplay)); + map.put("canManage", canManage); + map.put("masked", masked); + map.put("healthLatencyMs", entity.getHealthLatencyMs()); + map.put("healthMessage", entity.getHealthMessage()); + map.put("consecutiveFailures", entity.getConsecutiveFailures()); + map.put("version", entity.getVersion()); + map.put("tags", entity.getTags()); + map.put("lastHealthAt", entity.getLastHealthAt()); + map.put("configJson", entity.getConfigJson()); + map.put("creatorId", entity.getCreatorId()); + map.put("creatorName", entity.getCreatorName()); + map.put("createdAt", entity.getCreatedAt()); + map.put("updatedAt", entity.getUpdatedAt()); + + return map; + } + + private boolean isOwner(McpServerEntity entity, String userId) { + return StringUtils.hasText(userId) && userId.equals(entity.getCreatorId()); + } + + private boolean canManage(McpServerEntity entity, String userId) { + return adminAccessService.isAdmin(userId) || isOwner(entity, userId); + } + + private boolean shouldMaskSensitiveFields(McpServerEntity entity, String userId) { + return !canManage(entity, userId) && "public".equals(coerceVisibility(entity.getVisibility())); + } + + private String firstNonBlank(String... values) { + if (values == null || values.length == 0) { + return null; + } + for (String value : values) { + if (StringUtils.hasText(value)) { + return value; + } + } + return null; + } + + private String maskUrlForDisplay(String rawUrl) { + if (!StringUtils.hasText(rawUrl)) { + return null; + } + String value = rawUrl.trim(); + try { + URI uri = new URI(value); + if (StringUtils.hasText(uri.getScheme()) && StringUtils.hasText(uri.getHost())) { + StringBuilder builder = new StringBuilder(); + builder.append(uri.getScheme()).append("://").append(uri.getHost()); + if (uri.getPort() > 0) { + builder.append(":").append(uri.getPort()); + } + builder.append("/***"); + return builder.toString(); + } + } catch (URISyntaxException ignored) { + // ignore and fallback to generic mask + } + return maskGenericValue(value); + } + + private Map maskHeadersForDisplay(Map headers) { + if (headers == null || headers.isEmpty()) { + return Collections.emptyMap(); + } + Map masked = new LinkedHashMap<>(); + for (Map.Entry entry : headers.entrySet()) { + String headerName = entry.getKey() == null ? "" : entry.getKey().trim(); + String lowerKey = headerName.toLowerCase(Locale.ROOT); + String headerValue = entry.getValue(); + if (isSensitiveHeaderKey(lowerKey)) { + masked.put(headerName, "***"); + } else { + masked.put(headerName, maskGenericValue(headerValue)); + } + } + return masked; + } + + private boolean isSensitiveHeaderKey(String lowerKey) { + if (!StringUtils.hasText(lowerKey)) { + return false; + } + if (SENSITIVE_HEADER_KEYS.contains(lowerKey)) { + return true; + } + return lowerKey.contains("token") + || lowerKey.contains("secret") + || lowerKey.contains("password") + || lowerKey.contains("cookie") + || lowerKey.contains("auth"); + } + + private String maskGenericValue(String raw) { + if (!StringUtils.hasText(raw)) { + return "***"; + } + String value = raw.trim(); + if (value.length() <= 8) { + return "***"; + } + return value.substring(0, 3) + "***" + value.substring(value.length() - 2); + } + + private Map toSimpleMap(McpServerEntity entity, String userId) { + boolean canManage = canManage(entity, userId); + boolean masked = shouldMaskSensitiveFields(entity, userId); + String urlForDisplay = firstNonBlank(entity.getUrl(), entity.getEndpoint()); + + Map map = new HashMap<>(); + map.put("id", entity.getId().toString()); + map.put("name", entity.getName()); + map.put("description", entity.getDescription()); + map.put("endpoint", masked ? null : entity.getEndpoint()); + map.put("url", masked ? null : entity.getUrl()); + map.put("displayUrl", maskUrlForDisplay(urlForDisplay)); + map.put("visibility", coerceVisibility(entity.getVisibility())); + map.put("status", coerceStatus(entity.getStatus())); + map.put("type", entity.getType()); + map.put("networkZone", entity.getNetworkZone() != null ? entity.getNetworkZone() : "external"); + map.put("canManage", canManage); + map.put("masked", masked); + return map; + } + + private Map toHealthMap(McpServerEntity entity) { + Map map = new LinkedHashMap<>(); + map.put("id", entity.getId().toString()); + map.put("name", entity.getName()); + map.put("type", entity.getType()); + map.put("status", coerceStatus(entity.getStatus())); + map.put("latencyMs", entity.getHealthLatencyMs()); + map.put("lastHealthAt", entity.getLastHealthAt()); + map.put("consecutiveFailures", entity.getConsecutiveFailures()); + map.put("healthMessage", entity.getHealthMessage()); + return map; + } +} diff --git a/back/src/main/java/com/linkwork/service/McpUserConfigService.java b/back/src/main/java/com/linkwork/service/McpUserConfigService.java new file mode 100644 index 0000000..3cf72a3 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/McpUserConfigService.java @@ -0,0 +1,79 @@ +package com.linkwork.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.linkwork.mapper.McpUserConfigMapper; +import com.linkwork.model.entity.McpServerEntity; +import com.linkwork.model.entity.McpUserConfigEntity; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.List; +import java.util.Map; + +@Slf4j +@Service +@RequiredArgsConstructor +public class McpUserConfigService extends ServiceImpl { + + private final McpServerService mcpServerService; + + public McpUserConfigEntity getByUserAndMcpName(String userId, String mcpName) { + if (!StringUtils.hasText(userId) || !StringUtils.hasText(mcpName)) { + return null; + } + + LambdaQueryWrapper serverWrapper = new LambdaQueryWrapper<>(); + serverWrapper.eq(McpServerEntity::getName, mcpName); + McpServerEntity server = mcpServerService.getOne(serverWrapper, false); + if (server == null) { + return null; + } + + return getByUserAndServer(userId, server.getId()); + } + + public McpUserConfigEntity getByUserAndServer(String userId, Long mcpServerId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(McpUserConfigEntity::getUserId, userId) + .eq(McpUserConfigEntity::getMcpServerId, mcpServerId); + return this.getOne(wrapper, false); + } + + public List listByUser(String userId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(McpUserConfigEntity::getUserId, userId); + return this.list(wrapper); + } + + @SuppressWarnings("unchecked") + public McpUserConfigEntity saveOrUpdate(String userId, Long mcpServerId, Map request) { + McpUserConfigEntity entity = getByUserAndServer(userId, mcpServerId); + if (entity == null) { + entity = new McpUserConfigEntity(); + entity.setUserId(userId); + entity.setMcpServerId(mcpServerId); + } + + if (request.containsKey("headers")) { + entity.setHeaders((Map) request.get("headers")); + } + if (request.containsKey("urlParams")) { + entity.setUrlParams((Map) request.get("urlParams")); + } + + this.saveOrUpdate(entity); + log.info("Saved MCP user config for userId={}, mcpServerId={}", userId, mcpServerId); + return entity; + } + + public void deleteConfig(String userId, Long mcpServerId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(McpUserConfigEntity::getUserId, userId) + .eq(McpUserConfigEntity::getMcpServerId, mcpServerId); + this.remove(wrapper); + log.info("Deleted MCP user config for userId={}, mcpServerId={}", userId, mcpServerId); + } +} diff --git a/back/src/main/java/com/linkwork/service/NfsStorageService.java b/back/src/main/java/com/linkwork/service/NfsStorageService.java new file mode 100644 index 0000000..110e483 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/NfsStorageService.java @@ -0,0 +1,163 @@ +package com.linkwork.service; + +import com.linkwork.agent.storage.core.StorageClient; +import com.linkwork.config.NfsStorageConfig; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.UUID; + +/** + * NFS 文件存储服务 + * + * 通过 linkwork-storage-starter 的 StorageClient 覆盖替换原有文件系统实现, + * 对上层保持原方法签名不变。 + */ +@Slf4j +@Service +public class NfsStorageService { + + private final NfsStorageConfig config; + private final StorageClient storageClient; + + public NfsStorageService(NfsStorageConfig config, StorageClient storageClient) { + this.config = config; + this.storageClient = storageClient; + } + + public boolean isConfigured() { + return storageClient.supportsFileStorageOps() && storageClient.isConfigured(); + } + + /** + * 获取 objectName 对应的绝对路径 + */ + public Path getAbsolutePath(String objectName) { + if (storageClient.supportsFileStorageOps()) { + return storageClient.resolvePath(objectName); + } + return config.resolve(objectName); + } + + /** + * 上传 MultipartFile 到指定相对路径 + */ + public String uploadFileToPath(MultipartFile file, String objectName) throws IOException { + ensureConfigured(); + try (InputStream in = file.getInputStream()) { + String stored = storageClient.uploadToPath(in, objectName, file.getSize()); + log.info("文件上传成功: objectName={}, filename={}, size={}", stored, file.getOriginalFilename(), file.getSize()); + return stored; + } + } + + /** + * 上传文本内容到指定相对路径 + */ + public String uploadTextToPath(String content, String objectName) { + ensureConfigured(); + String stored = storageClient.uploadText(content, objectName); + log.info("文本上传成功: objectName={}, size={}", stored, content != null ? content.length() : 0); + return stored; + } + + /** + * 下载文件到临时文件 + */ + public Path downloadToTempFile(String objectName) throws IOException { + ensureConfigured(); + Path tmp = storageClient.downloadToTempFile(objectName); + log.info("下载 NFS 文件到临时文件: objectName={}", objectName); + return tmp; + } + + /** + * 复制文件 + */ + public void copyObject(String sourceObjectName, String destObjectName) { + ensureConfigured(); + storageClient.copyObject(sourceObjectName, destObjectName); + log.info("复制 NFS 文件: source={}, dest={}", sourceObjectName, destObjectName); + } + + /** + * 检查文件是否存在 + */ + public boolean doesObjectExist(String objectName) { + ensureConfigured(); + return storageClient.objectExists(objectName); + } + + /** + * 列出指定前缀(目录)下的所有文件 + */ + public List listObjects(String prefix) { + if (!isConfigured()) { + return List.of(); + } + return storageClient.listObjects(prefix); + } + + /** + * 删除文件 + */ + public void deleteFile(String objectName) { + if (!isConfigured()) { + return; + } + storageClient.deleteObject(objectName); + } + + /** + * 上传文本内容到按日期分目录的路径 + */ + public String uploadText(String content, String directory, String filename) { + String datePath = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")); + String objectName = directory + "/" + datePath + "/" + filename; + return uploadTextToPath(content, objectName); + } + + /** + * 上传 MultipartFile 到按日期分目录的路径 + */ + public String uploadFile(MultipartFile file, String directory) throws IOException { + String originalFilename = file.getOriginalFilename(); + String extension = ""; + if (originalFilename != null && originalFilename.contains(".")) { + extension = originalFilename.substring(originalFilename.lastIndexOf(".")); + } + String datePath = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")); + String objectName = directory + "/" + datePath + "/" + UUID.randomUUID() + extension; + return uploadFileToPath(file, objectName); + } + + /** + * 生成后端代理下载 URL + */ + public String buildDownloadUrl(String fileId) { + return config.getDownloadBaseUrl() + "/" + fileId + "/download"; + } + + /** + * 生成任务产出物下载 URL(按 objectName 直连后端代理)。 + */ + public String buildTaskOutputDownloadUrl(String objectName) { + String encodedObject = URLEncoder.encode(objectName, StandardCharsets.UTF_8).replace("+", "%20"); + return config.getTaskOutputBaseUrl() + "/file?object=" + encodedObject; + } + + private void ensureConfigured() { + if (!isConfigured()) { + throw new IllegalStateException("NFS 存储未配置"); + } + } +} diff --git a/back/src/main/java/com/linkwork/service/PodGroupSpecGenerator.java b/back/src/main/java/com/linkwork/service/PodGroupSpecGenerator.java new file mode 100644 index 0000000..a7b124e --- /dev/null +++ b/back/src/main/java/com/linkwork/service/PodGroupSpecGenerator.java @@ -0,0 +1,91 @@ +package com.linkwork.service; + +import com.linkwork.model.dto.MergedConfig; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * PodGroup Spec 生成器 + */ +@Component +@Slf4j +public class PodGroupSpecGenerator { + + /** + * 生成 PodGroup Spec(显式创建) + */ + public Map generate(MergedConfig config) { + String podGroupName = "svc-" + config.getServiceId() + "-pg"; + + log.info("Generating PodGroup spec: {}, minMember: {}", + podGroupName, config.getPodCount()); + + Map result = new LinkedHashMap<>(); + result.put("apiVersion", "scheduling.volcano.sh/v1beta1"); + result.put("kind", "PodGroup"); + + // metadata + Map metadata = new LinkedHashMap<>(); + metadata.put("name", podGroupName); + metadata.put("namespace", config.getNamespace()); + metadata.put("labels", Map.of( + "app", "ai-worker-service", + "service-id", config.getServiceId() + )); + result.put("metadata", metadata); + + // spec + Map spec = new LinkedHashMap<>(); + spec.put("minMember", config.getPodCount()); + spec.put("queue", config.getQueueName()); + spec.put("priorityClassName", config.getPriorityClassName()); + spec.put("minResources", buildMinResources(config)); + result.put("spec", spec); + + return result; + } + + /** + * 计算总资源需求 + */ + private Map buildMinResources(MergedConfig config) { + Map resources = new HashMap<>(); + + // 计算总 CPU(Pod 数量 × 单 Pod CPU) + double totalCpu = parseCpu(config.getAgentResources().getCpuRequest()) * config.getPodCount(); + resources.put("cpu", String.valueOf((int) Math.ceil(totalCpu))); + + // 计算总内存 + long totalMemory = parseMemory(config.getAgentResources().getMemoryRequest()) * config.getPodCount(); + resources.put("memory", formatMemory(totalMemory)); + + return resources; + } + + private double parseCpu(String cpu) { + if (cpu == null) return 1.0; + if (cpu.endsWith("m")) { + return Double.parseDouble(cpu.replace("m", "")) / 1000; + } + return Double.parseDouble(cpu); + } + + private long parseMemory(String memory) { + if (memory == null) return 2L * 1024 * 1024 * 1024; // 默认 2Gi + if (memory.endsWith("Gi")) { + return Long.parseLong(memory.replace("Gi", "")) * 1024 * 1024 * 1024; + } + if (memory.endsWith("Mi")) { + return Long.parseLong(memory.replace("Mi", "")) * 1024 * 1024; + } + return Long.parseLong(memory); + } + + private String formatMemory(long bytes) { + return (bytes / (1024 * 1024 * 1024)) + "Gi"; + } +} diff --git a/back/src/main/java/com/linkwork/service/PodSpecGenerator.java b/back/src/main/java/com/linkwork/service/PodSpecGenerator.java new file mode 100644 index 0000000..b4b9c89 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/PodSpecGenerator.java @@ -0,0 +1,761 @@ +package com.linkwork.service; + +import com.linkwork.config.EnvConfig.OssMountConfig; +import com.linkwork.model.dto.MergedConfig; +import com.linkwork.model.enums.PodMode; +import io.fabric8.kubernetes.api.model.*; +import io.fabric8.kubernetes.api.model.EnvVarBuilder; +import io.fabric8.kubernetes.api.model.VolumeMountBuilder; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Pod Spec 生成器 + * + * 双容器模式 (Sidecar): + * Agent 容器: /opt/agent/start-dual.sh → 密钥管理 → 等 Runner SSH → 启动 zzd + worker + * Runner 容器: /opt/runner/start-runner.sh → 等公钥 → 配置 authorized_keys → sshd -D + * + * 单容器模式 (Alone): + * Agent 容器: /opt/agent/start-single.sh → 本地模式启动 + * + * 共享卷: + * - shared-keys (emptyDir/Memory): Agent 写公钥 → Runner 读取 + * - workspace (emptyDir/PVC): 工作目录 + * + * ConfigMap: + * - svc-{serviceId}-agent-config: config.json → /opt/agent/config.json + * - runner-start-script: start-runner.sh → /opt/runner/start-runner.sh + */ +@Component +@Slf4j +public class PodSpecGenerator { + + private static final String PERMISSION_INIT_CPU_REQUEST = "100m"; + private static final String PERMISSION_INIT_CPU_LIMIT = "500m"; + private static final String PERMISSION_INIT_MEMORY_REQUEST = "128Mi"; + private static final String PERMISSION_INIT_MEMORY_LIMIT = "512Mi"; + + /** Runner 启动脚本 ConfigMap 名称(集群级共享) */ + public static final String RUNNER_SCRIPT_CONFIGMAP = "runner-start-script"; + + /** Runner 启动脚本在 ConfigMap 中的 key */ + public static final String RUNNER_SCRIPT_KEY = "start-runner.sh"; + + /** zzd 调用 /api/v1/tasks/{taskId}/git-token 的服务身份 token(可选) */ + @Value("${robot.zzd.api-server-token:}") + private String zzdApiServerToken; + + /** + * 生成 Pod Spec(根据模式选择) + */ + public Pod generate(MergedConfig config, int podIndex) { + return config.getPodMode() == PodMode.SIDECAR + ? generateSidecarPod(config, podIndex) + : generateAlonePod(config, podIndex); + } + + /** + * 获取 Agent ConfigMap 名称 + */ + public static String agentConfigMapName(String serviceId) { + return "svc-" + serviceId + "-agent-config"; + } + + /** + * 生成 Sidecar 模式 Pod + * - Agent 容器:root 启动, /opt/agent/start-dual.sh + * - Runner 容器:root 启动, /opt/runner/start-runner.sh (via ConfigMap) + * - 共享卷: /shared-keys (emptyDir/Memory), /workspace + */ + private Pod generateSidecarPod(MergedConfig config, int podIndex) { + String podName = "svc-" + config.getServiceId() + "-" + podIndex; + String podGroupName = "svc-" + config.getServiceId() + "-pg"; + + log.info("Generating Sidecar Pod: {}, preferredNode: {}", podName, config.getPreferredNode()); + + PodBuilder builder = new PodBuilder() + .withNewMetadata() + .withName(podName) + .withNamespace(config.getNamespace()) + .addToLabels("app", "ai-worker-service") + .addToLabels("service-id", config.getServiceId()) + .addToLabels("user-id", config.getUserId()) + .addToLabels("pod-index", String.valueOf(podIndex)) + .addToLabels("pod-mode", "sidecar") + // Volcano 注解(关联 PodGroup) + .addToAnnotations("scheduling.k8s.io/group-name", podGroupName) + .addToAnnotations("scheduling.volcano.sh/group-name", podGroupName) + .addToAnnotations("volcano.sh/queue-name", config.getQueueName()) + .endMetadata() + .withNewSpec() + .withSchedulerName("volcano") + .withPriorityClassName(config.getPriorityClassName()) + .withRestartPolicy("Never") + .withTerminationGracePeriodSeconds(30L) + // 私有镜像拉取凭证 + .withImagePullSecrets(buildImagePullSecrets(config)) + // 主容器 + .addToContainers(buildSidecarAgentContainer(config)) + .addToContainers(buildSidecarRunnerContainer(config)) + // Volumes + .addAllToVolumes(buildSidecarVolumes(config)) + .endSpec(); + + // 权限初始化 InitContainer:user-files / workstation 挂载根设为仅 x + Container permInit = buildPermissionInitContainer(config); + if (permInit != null) { + builder.editSpec() + .addToInitContainers(permInit) + .endSpec(); + } + + // 添加节点亲和配置(用于快速重启) + if (StringUtils.hasText(config.getPreferredNode())) { + builder.editSpec() + .withAffinity(buildPreferredNodeAffinity(config.getPreferredNode())) + .endSpec(); + } + + return builder.build(); + } + + /** + * 生成 Alone 模式 Pod + * - 单容器:/opt/agent/start-single.sh (ZZD_MODE=local) + */ + private Pod generateAlonePod(MergedConfig config, int podIndex) { + String podName = "svc-" + config.getServiceId() + "-" + podIndex; + String podGroupName = "svc-" + config.getServiceId() + "-pg"; + + log.info("Generating Alone Pod: {}, preferredNode: {}", podName, config.getPreferredNode()); + + PodBuilder builder = new PodBuilder() + .withNewMetadata() + .withName(podName) + .withNamespace(config.getNamespace()) + .addToLabels("app", "ai-worker-service") + .addToLabels("service-id", config.getServiceId()) + .addToLabels("user-id", config.getUserId()) + .addToLabels("pod-index", String.valueOf(podIndex)) + .addToLabels("pod-mode", "alone") + // Volcano 注解 + .addToAnnotations("scheduling.k8s.io/group-name", podGroupName) + .addToAnnotations("scheduling.volcano.sh/group-name", podGroupName) + .addToAnnotations("volcano.sh/queue-name", config.getQueueName()) + .endMetadata() + .withNewSpec() + .withSchedulerName("volcano") + .withPriorityClassName(config.getPriorityClassName()) + .withRestartPolicy("Never") + .withTerminationGracePeriodSeconds(30L) + // 私有镜像拉取凭证 + .withImagePullSecrets(buildImagePullSecrets(config)) + // 单容器 + .addToContainers(buildAloneContainer(config)) + // Volumes + .addAllToVolumes(buildAloneVolumes(config)) + .endSpec(); + + // 权限初始化 InitContainer:user-files / workstation 挂载根设为仅 x + Container permInit = buildPermissionInitContainer(config); + if (permInit != null) { + builder.editSpec() + .addToInitContainers(permInit) + .endSpec(); + } + + // 添加节点亲和配置(用于快速重启) + if (StringUtils.hasText(config.getPreferredNode())) { + builder.editSpec() + .withAffinity(buildPreferredNodeAffinity(config.getPreferredNode())) + .endSpec(); + } + + return builder.build(); + } + + // ==================== Sidecar 模式容器构建 ==================== + + /** + * Sidecar 模式 Agent 容器 + * + * 启动命令: /opt/agent/start-dual.sh + * 流程: SSH 密钥管理 → 写公钥到 /shared-keys → 等 Runner SSH → 启动 zzd + worker + * + * 必须以 root 启动 (写 /etc/zzd、起 zzd、再 sudo -u agent 拉 worker) + */ + private Container buildSidecarAgentContainer(MergedConfig config) { + List envVars = new ArrayList<>(); + + // ---- 必填环境变量 ---- + envVars.add(new EnvVar("WORKSTATION_ID", + config.getWorkstationId() != null ? config.getWorkstationId() : config.getServiceId(), null)); + if (config.getRedisUrl() != null) { + envVars.add(new EnvVar("REDIS_URL", config.getRedisUrl(), null)); + } + envVars.add(new EnvVar("CONFIG_FILE", "/opt/agent/config.json", null)); + envVars.add(new EnvVar("IDLE_TIMEOUT", "86400", null)); + + // ---- 双容器模式配置 ---- + // 同 Pod 内 Runner,通过 localhost 连接 + envVars.add(new EnvVar("ZZD_RUNNER_HOST", "localhost", null)); + appendRuntimeGitTokenEnv(envVars, config, true); + + // ---- 服务标识 ---- + envVars.add(new EnvVar("SERVICE_ID", config.getServiceId(), null)); + envVars.add(new EnvVar("USER_ID", config.getUserId(), null)); + // 注意: 不注入 ZZ_MODE / ZZD_MODE / SSH_PORT + // - ZZD_MODE 由 start-dual.sh 内部设置为 sandbox(见 zzd.md §运行模式) + // - SSH 端口由 zzd 配置模型内部管理(runner_port),非 Pod env var + + // ---- Downward API: Pod 名称 ---- + envVars.add(new EnvVarBuilder() + .withName("POD_NAME") + .withNewValueFrom() + .withNewFieldRef() + .withFieldPath("metadata.name") + .endFieldRef() + .endValueFrom() + .build()); + + // ---- 可选环境变量 ---- + if (config.getMainPyUrl() != null) { + envVars.add(new EnvVar("MAIN_PY_URL", config.getMainPyUrl(), null)); + } + if (config.getApiBaseUrl() != null) { + envVars.add(new EnvVar("API_BASE_URL", config.getApiBaseUrl(), null)); + envVars.add(new EnvVar("RUNTIME_GIT_API_FALLBACK_URL", config.getApiBaseUrl(), null)); + } + if (config.getWsBaseUrl() != null) { + envVars.add(new EnvVar("WS_BASE_URL", config.getWsBaseUrl(), null)); + } + if (config.getLlmGatewayUrl() != null) { + envVars.add(new EnvVar("LLM_GATEWAY_URL", config.getLlmGatewayUrl(), null)); + } + if (config.getRoleId() != null) { + envVars.add(new EnvVar("ROLE_ID", String.valueOf(config.getRoleId()), null)); + } + + String agentConfigMap = agentConfigMapName(config.getServiceId()); + + ContainerBuilder agentBuilder = new ContainerBuilder() + .withName("agent") + .withImage(config.getAgentImage()) + .withImagePullPolicy(config.getImagePullPolicy() != null ? config.getImagePullPolicy() : "IfNotPresent") + // ★ 启动命令: start-dual.sh(不要跑 build.sh) + .withCommand("/opt/agent/start-dual.sh") + .withEnv(envVars) + // ★ 以 root 启动(脚本里要写 /etc/zzd、起 zzd、再 sudo -u agent 拉 worker) + // NET_ADMIN: ENABLE_NETWORK_FIREWALL=true 时 start.sh 需要 iptables 配置 agent 用户网络白名单 + .withNewSecurityContext() + .withRunAsUser(0L) + .withNewCapabilities() + .addToAdd("NET_ADMIN") + .endCapabilities() + .endSecurityContext() + // ---- Volume Mounts ---- + .addToVolumeMounts(new VolumeMountBuilder() + .withName("workspace") + .withMountPath("/workspace") + .withReadOnly(false) + .build()) + .addToVolumeMounts(new VolumeMountBuilder() + .withName("shared-keys") + .withMountPath("/shared-keys") + .withReadOnly(false) + .build()) + // ★ 挂载配置文件: ConfigMap subPath → /opt/agent/config.json(只读) + .addToVolumeMounts(new VolumeMountBuilder() + .withName("agent-config") + .withMountPath("/opt/agent/config.json") + .withSubPath("config.json") + .withReadOnly(true) + .build()) + // ---- Resources ---- + .withNewResources() + .addToRequests("cpu", new Quantity(config.getAgentResources().getCpuRequest())) + .addToRequests("memory", new Quantity(config.getAgentResources().getMemoryRequest())) + .addToLimits("cpu", new Quantity(config.getAgentResources().getCpuLimit())) + .addToLimits("memory", new Quantity(config.getAgentResources().getMemoryLimit())) + .endResources(); + + // OSS 挂载 + addOssVolumeMount(agentBuilder, config); + + return agentBuilder.build(); + } + + /** + * Sidecar 模式 Runner 容器 + * + * 使用 Rocky 基础镜像(非独立 Runner 镜像) + * 启动脚本通过 ConfigMap 挂载: runner-start-script → /opt/runner/start-runner.sh + * + * 流程: 校验 sshd 环境 → 创建 momobot → 等公钥 → 写 authorized_keys → sshd -D -e + */ + private Container buildSidecarRunnerContainer(MergedConfig config) { + List envVars = new ArrayList<>(); + envVars.add(new EnvVar("SERVICE_ID", config.getServiceId(), null)); + envVars.add(new EnvVar("USER_ID", config.getUserId(), null)); + // 公钥等待超时(秒) + envVars.add(new EnvVar("PUBKEY_TIMEOUT", "120", null)); + // Downward API + envVars.add(new EnvVarBuilder() + .withName("POD_NAME") + .withNewValueFrom() + .withNewFieldRef() + .withFieldPath("metadata.name") + .endFieldRef() + .endValueFrom() + .build()); + if (config.getApiBaseUrl() != null) { + envVars.add(new EnvVar("API_BASE_URL", config.getApiBaseUrl(), null)); + } + + ContainerBuilder runnerBuilder = new ContainerBuilder() + .withName("runner") + .withImage(config.getRunnerImage()) // Rocky 基础镜像 + .withImagePullPolicy(config.getImagePullPolicy() != null ? config.getImagePullPolicy() : "IfNotPresent") + // ★ 启动命令: start-runner.sh(通过 ConfigMap 挂载) + .withCommand("/opt/runner/start-runner.sh") + .withEnv(envVars) + // Runner 也以 root 启动(需要 ssh-keygen -A、创建用户、启动 sshd) + .withNewSecurityContext() + .withRunAsUser(0L) + .endSecurityContext() + // ★ SSH 端口 22(sshd 默认端口,Agent 通过 localhost:22 连接) + .addToPorts(new ContainerPort(22, null, null, "ssh", "TCP")) + // ---- Volume Mounts ---- + .addToVolumeMounts(new VolumeMountBuilder() + .withName("workspace") + .withMountPath("/workspace") + .withReadOnly(false) + .build()) + .addToVolumeMounts(new VolumeMountBuilder() + .withName("shared-keys") + .withMountPath("/shared-keys") + .withReadOnly(false) + .build()) + // ★ 挂载启动脚本: ConfigMap → /opt/runner/start-runner.sh + .addToVolumeMounts(new VolumeMountBuilder() + .withName("runner-scripts") + .withMountPath("/opt/runner/start-runner.sh") + .withSubPath(RUNNER_SCRIPT_KEY) + .withReadOnly(true) + .build()) + // ---- Resources ---- + .withNewResources() + .addToRequests("cpu", new Quantity(config.getRunnerResources().getCpuRequest())) + .addToRequests("memory", new Quantity(config.getRunnerResources().getMemoryRequest())) + .addToLimits("cpu", new Quantity(config.getRunnerResources().getCpuLimit())) + .addToLimits("memory", new Quantity(config.getRunnerResources().getMemoryLimit())) + .endResources() + // ★ 就绪探针:SSH 22 端口 TCP 检查 + // Runner 需要: dnf install sshd + 等待 Agent 公钥 + 启动 sshd,首次可能需要较长时间 + .withNewReadinessProbe() + .withNewTcpSocket() + .withNewPort(22) + .endTcpSocket() + .withInitialDelaySeconds(30) + .withPeriodSeconds(10) + .withFailureThreshold(30) + .endReadinessProbe(); + + // OSS 挂载 + addOssVolumeMount(runnerBuilder, config); + + return runnerBuilder.build(); + } + + // ==================== Alone 模式容器构建 ==================== + + /** + * Alone 模式容器 + * 启动命令: /opt/agent/start-single.sh (ZZD_MODE=local,不需要 Runner) + */ + private Container buildAloneContainer(MergedConfig config) { + List envVars = new ArrayList<>(); + + // 必填环境变量 + envVars.add(new EnvVar("WORKSTATION_ID", + config.getWorkstationId() != null ? config.getWorkstationId() : config.getServiceId(), null)); + if (config.getRedisUrl() != null) { + envVars.add(new EnvVar("REDIS_URL", config.getRedisUrl(), null)); + } + envVars.add(new EnvVar("CONFIG_FILE", "/opt/agent/config.json", null)); + envVars.add(new EnvVar("IDLE_TIMEOUT", "86400", null)); + + // 服务标识 + envVars.add(new EnvVar("SERVICE_ID", config.getServiceId(), null)); + envVars.add(new EnvVar("USER_ID", config.getUserId(), null)); + appendRuntimeGitTokenEnv(envVars, config, false); + // 注意: 不注入 ZZD_MODE,start-single.sh 内部设置为 local + // Downward API + envVars.add(new EnvVarBuilder() + .withName("POD_NAME") + .withNewValueFrom() + .withNewFieldRef() + .withFieldPath("metadata.name") + .endFieldRef() + .endValueFrom() + .build()); + // 可选环境变量 + if (config.getMainPyUrl() != null) { + envVars.add(new EnvVar("MAIN_PY_URL", config.getMainPyUrl(), null)); + } + if (config.getApiBaseUrl() != null) { + envVars.add(new EnvVar("API_BASE_URL", config.getApiBaseUrl(), null)); + envVars.add(new EnvVar("RUNTIME_GIT_API_FALLBACK_URL", config.getApiBaseUrl(), null)); + } + if (config.getWsBaseUrl() != null) { + envVars.add(new EnvVar("WS_BASE_URL", config.getWsBaseUrl(), null)); + } + if (config.getLlmGatewayUrl() != null) { + envVars.add(new EnvVar("LLM_GATEWAY_URL", config.getLlmGatewayUrl(), null)); + } + if (config.getRoleId() != null) { + envVars.add(new EnvVar("ROLE_ID", String.valueOf(config.getRoleId()), null)); + } + + String agentConfigMap = agentConfigMapName(config.getServiceId()); + + + ContainerBuilder aloneBuilder = new ContainerBuilder() + .withName("agent") + .withImage(config.getAgentImage()) + .withImagePullPolicy(config.getImagePullPolicy() != null ? config.getImagePullPolicy() : "IfNotPresent") + // ★ 启动命令: start-single.sh + .withCommand("/opt/agent/start-single.sh") + .withEnv(envVars) + // root 启动; NET_ADMIN: ENABLE_NETWORK_FIREWALL=true 时 start.sh 需要 iptables 配置 agent 用户网络白名单 + .withNewSecurityContext() + .withRunAsUser(0L) + .withNewCapabilities() + .addToAdd("NET_ADMIN") + .endCapabilities() + .endSecurityContext() + .addToVolumeMounts(new VolumeMountBuilder() + .withName("workspace") + .withMountPath("/workspace") + .withReadOnly(false) + .build()) + // 挂载配置文件 + .addToVolumeMounts(new VolumeMountBuilder() + .withName("agent-config") + .withMountPath("/opt/agent/config.json") + .withSubPath("config.json") + .withReadOnly(true) + .build()) + .withNewResources() + .addToRequests("cpu", new Quantity(config.getAgentResources().getCpuRequest())) + .addToRequests("memory", new Quantity(config.getAgentResources().getMemoryRequest())) + .addToLimits("cpu", new Quantity(config.getAgentResources().getCpuLimit())) + .addToLimits("memory", new Quantity(config.getAgentResources().getMemoryLimit())) + .endResources(); + + // OSS 挂载 + addOssVolumeMount(aloneBuilder, config); + + return aloneBuilder.build(); + } + + /** + * 注入运行时 Git Token 相关环境变量。 + * - sidecar 模式默认强制 git 路由到 local,避免命中 runner 丢失 token 注入。 + * - API_BASE_URL 与 ZZD_API_SERVER_URL 同时下发:脚本可兜底回退。 + */ + private void appendRuntimeGitTokenEnv(List envVars, MergedConfig config, boolean sidecarMode) { + envVars.add(new EnvVar("ZZD_ENABLE_GIT_TOKEN", "true", null)); + if (sidecarMode) { + envVars.add(new EnvVar("ZZD_FORCE_GIT_LOCAL_ROUTE", "true", null)); + } + if (StringUtils.hasText(config.getApiBaseUrl())) { + envVars.add(new EnvVar("ZZD_API_SERVER_URL", config.getApiBaseUrl(), null)); + } + if (StringUtils.hasText(zzdApiServerToken)) { + envVars.add(new EnvVar("ZZD_API_SERVER_TOKEN", zzdApiServerToken, null)); + } + } + + // ==================== Volume 构建 ==================== + + /** + * Sidecar 模式 Volumes + * - workspace: 共享工作目录 (emptyDir) + * - shared-keys: Agent/Runner 传递公钥 (emptyDir/Memory, 每次 Pod 重建清空) + * - agent-config: ConfigMap, 挂载 config.json 到 /opt/agent/config.json + * - runner-scripts: ConfigMap, 挂载 start-runner.sh 到 /opt/runner/start-runner.sh + */ + private List buildSidecarVolumes(MergedConfig config) { + List volumes = new ArrayList<>(); + + // 共享工作目录 + volumes.add(new VolumeBuilder() + .withName("workspace") + .withNewEmptyDir() + .withSizeLimit(new Quantity(config.getWorkspaceSizeLimit() + "Gi")) + .endEmptyDir() + .build()); + + // ★ 共享密钥卷(emptyDir/Memory,Agent 写公钥 → Runner 读取) + volumes.add(new VolumeBuilder() + .withName("shared-keys") + .withNewEmptyDir() + .withMedium("Memory") + .endEmptyDir() + .build()); + + // ★ Agent 配置文件(ConfigMap → /opt/agent/config.json) + String agentConfigMap = agentConfigMapName(config.getServiceId()); + volumes.add(new VolumeBuilder() + .withName("agent-config") + .withNewConfigMap() + .withName(agentConfigMap) + .addNewItem() + .withKey("config.json") + .withPath("config.json") + .endItem() + .endConfigMap() + .build()); + + // ★ Runner 启动脚本(ConfigMap → /opt/runner/start-runner.sh, 0755 可执行) + volumes.add(new VolumeBuilder() + .withName("runner-scripts") + .withNewConfigMap() + .withName(RUNNER_SCRIPT_CONFIGMAP) + .withDefaultMode(0755) + .addNewItem() + .withKey(RUNNER_SCRIPT_KEY) + .withPath(RUNNER_SCRIPT_KEY) + .endItem() + .endConfigMap() + .build()); + + // OSS 挂载 + addOssVolume(volumes, config); + + return volumes; + } + + /** + * Alone 模式 Volumes + */ + private List buildAloneVolumes(MergedConfig config) { + List volumes = new ArrayList<>(); + + // 工作目录 + volumes.add(new VolumeBuilder() + .withName("workspace") + .withNewEmptyDir() + .withSizeLimit(new Quantity(config.getWorkspaceSizeLimit() + "Gi")) + .endEmptyDir() + .build()); + + // Agent 配置文件(ConfigMap) + String agentConfigMap = agentConfigMapName(config.getServiceId()); + volumes.add(new VolumeBuilder() + .withName("agent-config") + .withNewConfigMap() + .withName(agentConfigMap) + .addNewItem() + .withKey("config.json") + .withPath("config.json") + .endItem() + .endConfigMap() + .build()); + + // OSS 挂载 + addOssVolume(volumes, config); + + return volumes; + } + + // ==================== 权限初始化 ==================== + + /** + * 构建权限初始化 InitContainer + * + * 容器启动前设置 NFS 挂载目录权限: + * - user-files、workstation → 0711(owner rwx / others x-only), + * 非 root 用户无法 ls 挂载根,只能通过 zzd 创建的 symlink 访问被授权的子目录。 + * - oss-data(产出物 /data/oss/robot)→ 0777,agent worker 启动时需要写入探测, + * NFS 上新建目录默认 755,非 root 用户无法写入,需要显式放开。 + */ + private Container buildPermissionInitContainer(MergedConfig config) { + OssMountConfig ossMount = config.getOssMount(); + if (ossMount == null || !ossMount.isEnabled()) { + return null; + } + + String cmd = String.format("chmod 0777 %s && chmod 0711 %s %s", + ossMount.getMountPath(), + ossMount.getUserFilesMountPath(), + ossMount.getWorkstationMountPath()); + + return new ContainerBuilder() + .withName("permission-init") + .withImage(config.getAgentImage()) + .withCommand("sh", "-c", cmd) + .withNewSecurityContext() + .withRunAsUser(0L) + .endSecurityContext() + .withNewResources() + .addToRequests("cpu", new Quantity(PERMISSION_INIT_CPU_REQUEST)) + .addToRequests("memory", new Quantity(PERMISSION_INIT_MEMORY_REQUEST)) + .addToLimits("cpu", new Quantity(PERMISSION_INIT_CPU_LIMIT)) + .addToLimits("memory", new Quantity(PERMISSION_INIT_MEMORY_LIMIT)) + .endResources() + .addToVolumeMounts(new VolumeMountBuilder() + .withName("oss-data") + .withMountPath(ossMount.getMountPath()) + .withMountPropagation("HostToContainer") + .build()) + .addToVolumeMounts(new VolumeMountBuilder() + .withName("oss-user-files") + .withMountPath(ossMount.getUserFilesMountPath()) + .withMountPropagation("HostToContainer") + .build()) + .addToVolumeMounts(new VolumeMountBuilder() + .withName("oss-workstation") + .withMountPath(ossMount.getWorkstationMountPath()) + .withMountPropagation("HostToContainer") + .build()) + .build(); + } + + // ==================== OSS 挂载 ==================== + + /** + * 添加 OSS hostPath Volumes (3 个挂载点) + * + * 节点上 ossfs 分三路挂载: + * oss://robot-agent-files/system/ → {hostPath}/system + * oss://robot-agent-files/user-files/ → {hostPath}/user-files + * oss://robot-agent-files/workstation/ → {hostPath}/workstation + * + * 容器级: + * 1. oss-data: {hostPath}/system/{wsId} → /data/oss/robot (产出物,读写) + * 2. oss-user-files: {hostPath}/user-files → /mnt/user-files (个人空间,读写) + * 3. oss-workstation: {hostPath}/workstation/{wsId} → /mnt/workstation (岗位空间,读写) + */ + private void addOssVolume(List volumes, MergedConfig config) { + OssMountConfig ossMount = config.getOssMount(); + if (ossMount == null || !ossMount.isEnabled()) { + return; + } + + String workstationId = config.getWorkstationId() != null + ? config.getWorkstationId() : config.getServiceId(); + String hostBase = ossMount.getHostPath(); + + // 1. oss-data: {hostPath}/system/{workstationId} → /data/oss/robot + String systemHostPath = hostBase + "/system/" + workstationId; + volumes.add(new VolumeBuilder() + .withName("oss-data") + .withNewHostPath() + .withPath(systemHostPath) + .withType("DirectoryOrCreate") + .endHostPath() + .build()); + log.info("Added OSS volume [oss-data]: hostPath={}, mountPath={}", systemHostPath, ossMount.getMountPath()); + + // 2. oss-user-files: {hostPath}/user-files → /mnt/user-files + String userFilesHostPath = hostBase + "/user-files"; + volumes.add(new VolumeBuilder() + .withName("oss-user-files") + .withNewHostPath() + .withPath(userFilesHostPath) + .withType("DirectoryOrCreate") + .endHostPath() + .build()); + log.info("Added OSS volume [oss-user-files]: hostPath={}, mountPath={}", + userFilesHostPath, ossMount.getUserFilesMountPath()); + + // 3. oss-workstation: {hostPath}/workstation/{workstationId} → /mnt/workstation + String workstationHostPath = hostBase + "/workstation/" + workstationId; + volumes.add(new VolumeBuilder() + .withName("oss-workstation") + .withNewHostPath() + .withPath(workstationHostPath) + .withType("DirectoryOrCreate") + .endHostPath() + .build()); + log.info("Added OSS volume [oss-workstation]: hostPath={}, mountPath={}", + workstationHostPath, ossMount.getWorkstationMountPath()); + } + + /** + * 为容器添加 OSS VolumeMounts (全部读写,权限由 zzd 在任务生命周期内通过 symlink 控制) + * + * oss-data: /data/oss/robot → 产出物挂载根 (读写) + * oss-user-files: /mnt/user-files → 个人空间 (读写) + * oss-workstation: /mnt/workstation → 岗位空间 (读写) + */ + private void addOssVolumeMount(ContainerBuilder builder, MergedConfig config) { + OssMountConfig ossMount = config.getOssMount(); + if (ossMount == null || !ossMount.isEnabled()) { + return; + } + + // 1. oss-data (产出物,读写) + builder.addToVolumeMounts(new VolumeMountBuilder() + .withName("oss-data") + .withMountPath(ossMount.getMountPath()) + .withReadOnly(ossMount.isReadOnly()) + .withMountPropagation("HostToContainer") + .build()); + + // 2. oss-user-files (个人空间,读写) + builder.addToVolumeMounts(new VolumeMountBuilder() + .withName("oss-user-files") + .withMountPath(ossMount.getUserFilesMountPath()) + .withReadOnly(false) + .withMountPropagation("HostToContainer") + .build()); + + // 3. oss-workstation (岗位空间,读写) + builder.addToVolumeMounts(new VolumeMountBuilder() + .withName("oss-workstation") + .withMountPath(ossMount.getWorkstationMountPath()) + .withReadOnly(false) + .withMountPropagation("HostToContainer") + .build()); + } + + // ==================== 镜像拉取凭证 ==================== + + private List buildImagePullSecrets(MergedConfig config) { + if (StringUtils.hasText(config.getImagePullSecret())) { + return Collections.singletonList( + new LocalObjectReference(config.getImagePullSecret()) + ); + } + return Collections.emptyList(); + } + + // ==================== 节点亲和配置 ==================== + + private Affinity buildPreferredNodeAffinity(String preferredNode) { + return new AffinityBuilder() + .withNewNodeAffinity() + .addNewPreferredDuringSchedulingIgnoredDuringExecution() + .withWeight(100) + .withNewPreference() + .addNewMatchExpression() + .withKey("kubernetes.io/hostname") + .withOperator("In") + .withValues(preferredNode) + .endMatchExpression() + .endPreference() + .endPreferredDuringSchedulingIgnoredDuringExecution() + .endNodeAffinity() + .build(); + } +} diff --git a/back/src/main/java/com/linkwork/service/QueueSelector.java b/back/src/main/java/com/linkwork/service/QueueSelector.java new file mode 100644 index 0000000..a8e8d28 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/QueueSelector.java @@ -0,0 +1,36 @@ +package com.linkwork.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +/** + * 队列选择器 + */ +@Service +@Slf4j +public class QueueSelector { + + @Value("${schedule.queue.prefix:ai-worker}") + private String queuePrefix; + + /** + * 根据优先级选择调度队列 + * @param priority 优先级 0-100 + * @param gpuRequired 是否需要 GPU(K8s 不支持 GPU) + */ + public String selectQueue(Integer priority, Boolean gpuRequired) { + int p = Optional.ofNullable(priority).orElse(50); + + if (Boolean.TRUE.equals(gpuRequired)) { + log.warn("GPU tasks should use Compose mode, K8s does not support GPU"); + } + + if (p >= 90) return queuePrefix + "-critical"; + if (p >= 70) return queuePrefix + "-high"; + if (p >= 30) return queuePrefix + "-normal"; + return queuePrefix + "-low"; + } +} diff --git a/back/src/main/java/com/linkwork/service/ReportExportService.java b/back/src/main/java/com/linkwork/service/ReportExportService.java new file mode 100644 index 0000000..d6e1cf6 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/ReportExportService.java @@ -0,0 +1,475 @@ +package com.linkwork.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.linkwork.config.DispatchConfig; +import com.linkwork.mapper.RoleMapper; +import com.linkwork.mapper.TaskMapper; +import com.linkwork.model.dto.ReportExportFieldOption; +import com.linkwork.model.dto.ReportExportFieldResponse; +import com.linkwork.model.dto.ReportExportRequest; +import com.linkwork.model.entity.RoleEntity; +import com.linkwork.model.entity.Task; +import com.linkwork.model.enums.TaskStatus; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.connection.stream.MapRecord; +import org.springframework.data.redis.connection.stream.StreamOffset; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 运维报表导出服务 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class ReportExportService { + + private static final int PAGE_SIZE = 500; + private static final String TYPE_TASK = "task"; + private static final String TYPE_ROLE = "role"; + private static final String FIELD_EVENT_STREAM = "eventStream"; + private static final DateTimeFormatter DATE_TIME_OUT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + private static final List LOCAL_DATE_TIME_FORMATTERS = List.of( + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"), + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm"), + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"), + DateTimeFormatter.ISO_LOCAL_DATE_TIME + ); + + private static final Map TASK_FIELD_LABELS = Map.ofEntries( + Map.entry("id", "主键ID"), + Map.entry("taskNo", "任务编号"), + Map.entry("roleId", "岗位ID"), + Map.entry("roleName", "岗位名称"), + Map.entry("prompt", "任务目标"), + Map.entry("status", "任务状态"), + Map.entry("image", "镜像"), + Map.entry("selectedModel", "模型"), + Map.entry("assemblyId", "装配ID"), + Map.entry("configJson", "任务配置"), + Map.entry("source", "任务来源"), + Map.entry("cronJobId", "定时任务ID"), + Map.entry("creatorId", "创建人ID"), + Map.entry("creatorName", "创建人"), + Map.entry("creatorIp", "创建IP"), + Map.entry("updaterId", "更新人ID"), + Map.entry("updaterName", "更新人"), + Map.entry("tokensUsed", "总Tokens"), + Map.entry("inputTokens", "输入Tokens"), + Map.entry("outputTokens", "输出Tokens"), + Map.entry("requestCount", "请求次数"), + Map.entry("tokenLimit", "Token上限"), + Map.entry("usagePercent", "使用率"), + Map.entry("durationMs", "执行耗时(ms)"), + Map.entry("reportJson", "任务报告"), + Map.entry("createdAt", "创建时间"), + Map.entry("updatedAt", "更新时间"), + Map.entry("isDeleted", "逻辑删除标记") + ); + + private static final Map ROLE_FIELD_LABELS = Map.ofEntries( + Map.entry("id", "主键ID"), + Map.entry("roleNo", "岗位编号"), + Map.entry("name", "岗位名称"), + Map.entry("description", "岗位描述"), + Map.entry("category", "岗位分类"), + Map.entry("icon", "图标"), + Map.entry("image", "镜像"), + Map.entry("prompt", "岗位Prompt"), + Map.entry("status", "岗位状态"), + Map.entry("configJson", "岗位配置"), + Map.entry("isPublic", "是否公开"), + Map.entry("maxEmployees", "最大员工数"), + Map.entry("creatorId", "创建人ID"), + Map.entry("creatorName", "创建人"), + Map.entry("updaterId", "更新人ID"), + Map.entry("updaterName", "更新人"), + Map.entry("createdAt", "创建时间"), + Map.entry("updatedAt", "更新时间"), + Map.entry("isDeleted", "逻辑删除标记") + ); + + private final TaskMapper taskMapper; + private final RoleMapper roleMapper; + private final StringRedisTemplate redisTemplate; + private final DispatchConfig dispatchConfig; + private final ObjectMapper objectMapper; + private final Map, Map> fieldCache = new ConcurrentHashMap<>(); + + public ReportExportFieldResponse listFields(String type) { + String normalizedType = normalizeType(type); + List fields = TYPE_TASK.equals(normalizedType) + ? buildFieldOptions(Task.class, TASK_FIELD_LABELS) + : buildFieldOptions(RoleEntity.class, ROLE_FIELD_LABELS); + return new ReportExportFieldResponse(normalizedType, fields); + } + + public void exportCsv(ReportExportRequest request, OutputStream outputStream) throws IOException { + String normalizedType = normalizeType(request.getType()); + LocalDateTime startTime = parseDateTime(request.getStartTime(), "开始时间"); + LocalDateTime endTime = parseDateTime(request.getEndTime(), "结束时间"); + if (endTime.isBefore(startTime)) { + throw new IllegalArgumentException("结束时间必须大于或等于开始时间"); + } + + try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8))) { + // Excel 友好:UTF-8 BOM + writer.write('\uFEFF'); + + if (TYPE_TASK.equals(normalizedType)) { + exportTaskCsv(request, startTime, endTime, writer); + } else { + exportRoleCsv(request, startTime, endTime, writer); + } + writer.flush(); + } + } + + private void exportTaskCsv(ReportExportRequest request, + LocalDateTime startTime, + LocalDateTime endTime, + BufferedWriter writer) throws IOException { + List taskFields = buildFieldOptions(Task.class, TASK_FIELD_LABELS); + List selectedFields = resolveSelectedFields(request.getFields(), taskFields); + boolean includeEventStream = Boolean.TRUE.equals(request.getIncludeEventStream()); + if (includeEventStream) { + selectedFields = new ArrayList<>(selectedFields); + selectedFields.add(new ReportExportFieldOption(FIELD_EVENT_STREAM, "event_stream", "消息流", "String")); + } + + writeCsvRow(writer, selectedFields.stream().map(ReportExportFieldOption::getLabel).toList()); + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .ge(Task::getCreatedAt, startTime) + .le(Task::getCreatedAt, endTime) + .orderByAsc(Task::getCreatedAt) + .orderByAsc(Task::getId); + + long pageNo = 1L; + while (true) { + Page page = taskMapper.selectPage(new Page<>(pageNo, PAGE_SIZE), wrapper); + for (Task task : page.getRecords()) { + List row = new ArrayList<>(selectedFields.size()); + for (ReportExportFieldOption option : selectedFields) { + if (FIELD_EVENT_STREAM.equals(option.getField())) { + row.add(loadTaskEventStream(task)); + } else { + row.add(toCellValue(readField(task, option.getField()))); + } + } + writeCsvRow(writer, row); + } + if (pageNo >= page.getPages()) { + break; + } + pageNo++; + } + } + + private void exportRoleCsv(ReportExportRequest request, + LocalDateTime startTime, + LocalDateTime endTime, + BufferedWriter writer) throws IOException { + List roleFields = buildFieldOptions(RoleEntity.class, ROLE_FIELD_LABELS); + List selectedFields = resolveSelectedFields(request.getFields(), roleFields); + + writeCsvRow(writer, selectedFields.stream().map(ReportExportFieldOption::getLabel).toList()); + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .ge(RoleEntity::getCreatedAt, startTime) + .le(RoleEntity::getCreatedAt, endTime) + .orderByAsc(RoleEntity::getCreatedAt) + .orderByAsc(RoleEntity::getId); + + long pageNo = 1L; + while (true) { + Page page = roleMapper.selectPage(new Page<>(pageNo, PAGE_SIZE), wrapper); + for (RoleEntity role : page.getRecords()) { + List row = new ArrayList<>(selectedFields.size()); + for (ReportExportFieldOption option : selectedFields) { + row.add(toCellValue(readField(role, option.getField()))); + } + writeCsvRow(writer, row); + } + if (pageNo >= page.getPages()) { + break; + } + pageNo++; + } + } + + private List buildFieldOptions(Class entityClass, Map labelMap) { + List fields = new ArrayList<>(); + for (Field field : entityClass.getDeclaredFields()) { + if (Modifier.isStatic(field.getModifiers())) { + continue; + } + String fieldName = field.getName(); + fields.add(new ReportExportFieldOption( + fieldName, + toSnakeCase(fieldName), + labelMap.getOrDefault(fieldName, fieldName), + field.getType().getSimpleName() + )); + } + return fields; + } + + private List resolveSelectedFields(List fields, List allowedFields) { + if (fields == null || fields.isEmpty()) { + return new ArrayList<>(allowedFields); + } + + Map allowed = new LinkedHashMap<>(); + for (ReportExportFieldOption option : allowedFields) { + allowed.put(option.getField(), option); + } + + LinkedHashSet deduplicated = new LinkedHashSet<>(); + for (String field : fields) { + if (!StringUtils.hasText(field)) { + continue; + } + String normalizedField = field.trim(); + if (!allowed.containsKey(normalizedField)) { + throw new IllegalArgumentException("存在不支持的导出字段: " + normalizedField); + } + deduplicated.add(normalizedField); + } + + if (deduplicated.isEmpty()) { + throw new IllegalArgumentException("导出字段不能为空"); + } + + List selected = new ArrayList<>(deduplicated.size()); + for (String field : deduplicated) { + selected.add(allowed.get(field)); + } + return selected; + } + + private String loadTaskEventStream(Task task) { + if (!StringUtils.hasText(task.getTaskNo())) { + return ""; + } + + List streamKeys = Arrays.asList( + dispatchConfig.getLogStreamKey(task.getRoleId(), task.getTaskNo()), + "stream:task:" + task.getTaskNo() + ":events", + "stream:task:" + task.getTaskNo() + ); + + for (String streamKey : streamKeys) { + List> records; + try { + records = redisTemplate.opsForStream().read(StreamOffset.fromStart(streamKey)); + } catch (Exception e) { + log.warn("读取消息流失败: taskNo={}, key={}, error={}", task.getTaskNo(), streamKey, e.getMessage()); + continue; + } + if (records == null || records.isEmpty()) { + continue; + } + + List lines = new ArrayList<>(records.size()); + for (MapRecord record : records) { + lines.add(serializeStreamRecord(streamKey, record)); + } + return String.join("\n", lines); + } + + return ""; + } + + private String serializeStreamRecord(String streamKey, MapRecord record) { + Map valueMap = new LinkedHashMap<>(); + valueMap.put("streamKey", streamKey); + valueMap.put("recordId", record.getId().getValue()); + + record.getValue().forEach((k, v) -> valueMap.put(String.valueOf(k), normalizeStreamValue(v))); + + try { + return objectMapper.writeValueAsString(valueMap); + } catch (JsonProcessingException e) { + throw new IllegalStateException("序列化消息流记录失败: streamKey=" + streamKey, e); + } + } + + private Object normalizeStreamValue(Object value) { + if (!(value instanceof String text)) { + return value; + } + String trimmed = text.trim(); + if (!StringUtils.hasText(trimmed)) { + return text; + } + if ((trimmed.startsWith("{") && trimmed.endsWith("}")) || (trimmed.startsWith("[") && trimmed.endsWith("]"))) { + try { + return objectMapper.readValue(trimmed, Object.class); + } catch (Exception e) { + log.debug("消息流 JSON 解析失败,保留原文: {}", e.getMessage()); + } + } + return text; + } + + private Object readField(Object entity, String fieldName) { + Map classFields = fieldCache.computeIfAbsent(entity.getClass(), this::buildFieldMap); + Field field = classFields.get(fieldName); + if (field == null) { + throw new IllegalArgumentException("未知字段: " + fieldName); + } + try { + return field.get(entity); + } catch (IllegalAccessException e) { + throw new IllegalStateException("读取字段失败: " + fieldName, e); + } + } + + private Map buildFieldMap(Class clazz) { + Map map = new LinkedHashMap<>(); + for (Field field : clazz.getDeclaredFields()) { + if (Modifier.isStatic(field.getModifiers())) { + continue; + } + field.setAccessible(true); + map.put(field.getName(), field); + } + return map; + } + + private String toCellValue(Object value) { + if (value == null) { + return ""; + } + if (value instanceof TaskStatus taskStatus) { + return taskStatus.getCode(); + } + if (value instanceof LocalDateTime localDateTime) { + return localDateTime.format(DATE_TIME_OUT); + } + if (value instanceof Instant instant) { + return DATE_TIME_OUT.format(instant.atZone(ZoneId.systemDefault()).toLocalDateTime()); + } + if (value instanceof OffsetDateTime offsetDateTime) { + return DATE_TIME_OUT.format(offsetDateTime.atZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime()); + } + if (value instanceof CharSequence || value instanceof Number || value instanceof Boolean || value instanceof BigDecimal) { + return String.valueOf(value); + } + if (value.getClass().isEnum()) { + return ((Enum) value).name(); + } + try { + return objectMapper.writeValueAsString(value); + } catch (JsonProcessingException e) { + throw new IllegalStateException("序列化字段值失败: " + value.getClass().getSimpleName(), e); + } + } + + private void writeCsvRow(BufferedWriter writer, List columns) throws IOException { + for (int i = 0; i < columns.size(); i++) { + writer.write(escapeCsv(columns.get(i))); + if (i < columns.size() - 1) { + writer.write(','); + } + } + writer.write('\n'); + } + + private String escapeCsv(String value) { + if (value == null) { + return ""; + } + boolean needQuotes = value.contains(",") || value.contains("\"") || value.contains("\n") || value.contains("\r"); + if (!needQuotes) { + return value; + } + return "\"" + value.replace("\"", "\"\"") + "\""; + } + + private LocalDateTime parseDateTime(String value, String fieldName) { + String text = value == null ? "" : value.trim(); + if (!StringUtils.hasText(text)) { + throw new IllegalArgumentException(fieldName + "不能为空"); + } + for (DateTimeFormatter formatter : LOCAL_DATE_TIME_FORMATTERS) { + try { + return LocalDateTime.parse(text, formatter); + } catch (DateTimeParseException ignored) { + // 尝试下一个格式 + } + } + + try { + return OffsetDateTime.parse(text).atZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime(); + } catch (DateTimeParseException ignored) { + // 尝试下一个格式 + } + + try { + return Instant.parse(text).atZone(ZoneId.systemDefault()).toLocalDateTime(); + } catch (DateTimeParseException ignored) { + throw new IllegalArgumentException(fieldName + "格式不正确: " + text); + } + } + + private String normalizeType(String type) { + if (!StringUtils.hasText(type)) { + throw new IllegalArgumentException("导出类型不能为空"); + } + String normalized = type.trim().toLowerCase(Locale.ROOT); + if (!TYPE_TASK.equals(normalized) && !TYPE_ROLE.equals(normalized)) { + throw new IllegalArgumentException("不支持的导出类型: " + type); + } + return normalized; + } + + private String toSnakeCase(String camelCase) { + if (!StringUtils.hasText(camelCase)) { + return camelCase; + } + StringBuilder builder = new StringBuilder(camelCase.length() + 8); + for (int i = 0; i < camelCase.length(); i++) { + char c = camelCase.charAt(i); + if (Character.isUpperCase(c)) { + if (i > 0) { + builder.append('_'); + } + builder.append(Character.toLowerCase(c)); + } else { + builder.append(c); + } + } + return builder.toString(); + } +} diff --git a/back/src/main/java/com/linkwork/service/RoleHotRankService.java b/back/src/main/java/com/linkwork/service/RoleHotRankService.java new file mode 100644 index 0000000..efa3438 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/RoleHotRankService.java @@ -0,0 +1,83 @@ +package com.linkwork.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.lang.Nullable; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 热门岗位排行榜(Redis Sorted Set) + */ +@Slf4j +@Service +public class RoleHotRankService { + + public static final String HOT_ROLE_RANK_KEY = "rank:roles:favorite"; + + private final StringRedisTemplate redisTemplate; + + public RoleHotRankService(@Nullable StringRedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + public void incrementFavoriteScore(Long roleId, double delta) { + if (roleId == null || delta == 0D || redisTemplate == null) { + return; + } + String member = String.valueOf(roleId); + try { + Double score = redisTemplate.opsForZSet().incrementScore(HOT_ROLE_RANK_KEY, member, delta); + if (score != null && score <= 0D) { + redisTemplate.opsForZSet().remove(HOT_ROLE_RANK_KEY, member); + } + } catch (Exception e) { + log.warn("update role hot rank failed, roleId={}, delta={}, reason={}", roleId, delta, e.getMessage()); + } + } + + public List listTopRoleIds(int limit) { + if (limit <= 0 || redisTemplate == null) { + return List.of(); + } + try { + var members = redisTemplate.opsForZSet().reverseRange(HOT_ROLE_RANK_KEY, 0, limit - 1L); + if (members == null || members.isEmpty()) { + return List.of(); + } + List result = new ArrayList<>(members.size()); + for (String member : members) { + try { + result.add(Long.parseLong(member)); + } catch (NumberFormatException ignored) { + // 非法 member 直接跳过,避免影响榜单读取 + } + } + return result; + } catch (Exception e) { + log.warn("query role hot rank failed, reason={}", e.getMessage()); + return List.of(); + } + } + + public void rebuildRank(Map favoriteCountMap) { + if (redisTemplate == null || favoriteCountMap == null) { + return; + } + try { + redisTemplate.delete(HOT_ROLE_RANK_KEY); + favoriteCountMap.forEach((roleId, favoriteCount) -> { + if (roleId == null || favoriteCount == null || favoriteCount <= 0L) { + return; + } + redisTemplate.opsForZSet().add(HOT_ROLE_RANK_KEY, String.valueOf(roleId), favoriteCount.doubleValue()); + }); + } catch (Exception e) { + log.warn("rebuild role hot rank failed, reason={}", e.getMessage()); + } + } +} + diff --git a/back/src/main/java/com/linkwork/service/RoleService.java b/back/src/main/java/com/linkwork/service/RoleService.java new file mode 100644 index 0000000..3192e2b --- /dev/null +++ b/back/src/main/java/com/linkwork/service/RoleService.java @@ -0,0 +1,491 @@ +package com.linkwork.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.linkwork.common.ForbiddenOperationException; +import com.linkwork.common.ResourceNotFoundException; +import com.linkwork.mapper.RoleMapper; +import com.linkwork.mapper.UserFavoriteRoleMapper; +import com.linkwork.model.entity.RoleEntity; +import com.linkwork.model.entity.UserFavoriteRoleEntity; +import com.linkwork.model.enums.DeployMode; +import com.linkwork.model.enums.PodMode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.util.Comparator; +import java.util.LinkedHashSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +public class RoleService extends ServiceImpl { + + @Autowired + private UserFavoriteRoleMapper userFavoriteRoleMapper; + @Autowired + private CronJobService cronJobService; + @Autowired + private RoleHotRankService roleHotRankService; + @Autowired + private AdminAccessService adminAccessService; + + private static final Set SUPPORTED_RUNTIME_MODES = Set.of(PodMode.SIDECAR.name(), PodMode.ALONE.name()); + private static final Set SUPPORTED_DEPLOY_MODES = Set.of(DeployMode.K8S.name(), DeployMode.COMPOSE.name()); + private static final Set SUPPORTED_ROLE_STATUSES = Set.of("active", "maintenance", "disabled"); + + /** + * 分页查询岗位 + * @param page 页码 + * @param pageSize 每页条数 + * @param query 搜索关键词 + * @param category 分类 + * @param scope 范围: all, mine, favorite + * @param status 状态筛选: active, disabled, maintenance + * @param currentUserId 当前用户ID + * @return 分页结果 + */ + public Page listRoles(int page, int pageSize, String query, String category, String scope, String status, String currentUserId) { + Page pageParam = new Page<>(page, pageSize); + LambdaQueryWrapper wrapper = buildRoleQueryWrapper(query, category, scope, status, currentUserId); + + // 3. 排序 (默认按时间倒序) + wrapper.orderByDesc(RoleEntity::getCreatedAt); + + return this.page(pageParam, wrapper); + } + + /** + * 查询热门岗位(按收藏数倒序,最多 limit 条) + */ + public List listHotRoles(int limit, String currentUserId) { + int safeLimit = Math.max(1, limit); + LambdaQueryWrapper wrapper = buildRoleQueryWrapper(null, null, "all", null, currentUserId); + List roles = this.list(wrapper).stream() + .filter(Objects::nonNull) + .filter(role -> role.getId() != null) + .collect(Collectors.toList()); + if (roles.isEmpty()) { + return List.of(); + } + + Map roleMap = roles.stream() + .collect(Collectors.toMap(RoleEntity::getId, role -> role)); + + int rankFetchSize = Math.max(20, safeLimit * 5); + List rankedRoleIds = roleHotRankService.listTopRoleIds(rankFetchSize); + if (rankedRoleIds.isEmpty()) { + roleHotRankService.rebuildRank(queryAllFavoriteCountMap()); + rankedRoleIds = roleHotRankService.listTopRoleIds(rankFetchSize); + } + + LinkedHashSet orderedRoleIds = new LinkedHashSet<>(); + for (Long roleId : rankedRoleIds) { + if (roleMap.containsKey(roleId)) { + orderedRoleIds.add(roleId); + } + if (orderedRoleIds.size() >= safeLimit) { + break; + } + } + + if (orderedRoleIds.size() < safeLimit) { + Map favoriteCountMap = queryFavoriteCountMap( + roles.stream().map(RoleEntity::getId).collect(Collectors.toList()) + ); + List fallbackSorted = roles.stream() + .sorted( + Comparator.comparingLong((RoleEntity role) -> favoriteCountMap.getOrDefault(role.getId(), 0L)) + .reversed() + .thenComparing(RoleEntity::getCreatedAt, Comparator.nullsLast(Comparator.reverseOrder())) + ) + .collect(Collectors.toList()); + for (RoleEntity role : fallbackSorted) { + orderedRoleIds.add(role.getId()); + if (orderedRoleIds.size() >= safeLimit) { + break; + } + } + } + + List orderedRoles = new java.util.ArrayList<>(orderedRoleIds.size()); + for (Long roleId : orderedRoleIds) { + RoleEntity role = roleMap.get(roleId); + if (role != null) { + orderedRoles.add(role); + } + } + return orderedRoles; + } + + /** + * 批量查询岗位收藏数 + */ + public Map queryFavoriteCountMap(List roleIds) { + if (roleIds == null || roleIds.isEmpty()) { + return Map.of(); + } + + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.select("role_id AS roleId", "COUNT(1) AS favoriteCount") + .in("role_id", roleIds) + .groupBy("role_id"); + + List> rows = userFavoriteRoleMapper.selectMaps(wrapper); + Map favoriteCountMap = new HashMap<>(); + for (Map row : rows) { + Object roleIdValue = row.getOrDefault("roleId", row.get("role_id")); + if (!(roleIdValue instanceof Number roleIdNumber)) { + continue; + } + Long roleId = roleIdNumber.longValue(); + Object favoriteCountValue = row.getOrDefault("favoriteCount", row.get("favorite_count")); + long favoriteCount = favoriteCountValue instanceof Number countNumber ? countNumber.longValue() : 0L; + favoriteCountMap.put(roleId, favoriteCount); + } + return favoriteCountMap; + } + + public Map queryAllFavoriteCountMap() { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.select("role_id AS roleId", "COUNT(1) AS favoriteCount") + .groupBy("role_id"); + + List> rows = userFavoriteRoleMapper.selectMaps(wrapper); + Map favoriteCountMap = new HashMap<>(); + for (Map row : rows) { + Object roleIdValue = row.getOrDefault("roleId", row.get("role_id")); + if (!(roleIdValue instanceof Number roleIdNumber)) { + continue; + } + Long roleId = roleIdNumber.longValue(); + Object favoriteCountValue = row.getOrDefault("favoriteCount", row.get("favorite_count")); + long favoriteCount = favoriteCountValue instanceof Number countNumber ? countNumber.longValue() : 0L; + favoriteCountMap.put(roleId, favoriteCount); + } + return favoriteCountMap; + } + + private LambdaQueryWrapper buildRoleQueryWrapper( + String query, + String category, + String scope, + String status, + String currentUserId + ) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + boolean adminUser = adminAccessService.isAdmin(currentUserId); + + // 1. 基础筛选 + if (StringUtils.hasText(category)) { + wrapper.eq(RoleEntity::getCategory, category); + } + if (StringUtils.hasText(query)) { + wrapper.and(w -> w.like(RoleEntity::getName, query) + .or().like(RoleEntity::getDescription, query)); + } + if (StringUtils.hasText(status)) { + wrapper.eq(RoleEntity::getStatus, normalizeRoleStatus(status)); + } + + // 2. Scope 筛选 + if ("mine".equalsIgnoreCase(scope)) { + wrapper.eq(RoleEntity::getCreatorId, currentUserId); + } else if ("favorite".equalsIgnoreCase(scope)) { + if (!StringUtils.hasText(currentUserId)) { + wrapper.eq(RoleEntity::getId, -1L); + return wrapper; + } + // 查询用户收藏的 Role IDs + List favoriteRoleIds = userFavoriteRoleMapper.selectList( + new LambdaQueryWrapper() + .eq(UserFavoriteRoleEntity::getUserId, currentUserId) + ).stream().map(UserFavoriteRoleEntity::getRoleId).collect(Collectors.toList()); + + if (favoriteRoleIds.isEmpty()) { + wrapper.eq(RoleEntity::getId, -1L); + return wrapper; + } + wrapper.in(RoleEntity::getId, favoriteRoleIds); + } else { + // all: 管理员可见全部;非管理员可见公开的 OR 自己的 + if (adminUser) { + return wrapper; + } + if (StringUtils.hasText(currentUserId)) { + wrapper.and(w -> w.eq(RoleEntity::getIsPublic, true) + .or().eq(RoleEntity::getCreatorId, currentUserId)); + } else { + wrapper.eq(RoleEntity::getIsPublic, true); + } + } + + return wrapper; + } + + /** + * 创建岗位 + */ + public RoleEntity createRole(RoleEntity role, String userId, String username) { + assertRoleNameUnique(role.getName(), null); + role.setRoleNo("ROLE-" + System.currentTimeMillis()); + role.setCreatorId(userId); + role.setCreatorName(username); + role.setStatus(normalizeRoleStatusOrDefault(role.getStatus())); + role.setPrompt(normalizeRequiredRolePrompt(role.getPrompt())); + // 默认不公开 + if (role.getIsPublic() == null) { + role.setIsPublic(false); + } + if (role.getMaxEmployees() == null) { + role.setMaxEmployees(1); + } + + role.setConfigJson(normalizeRoleConfig(role.getConfigJson())); + + this.save(role); + return role; + } + + /** + * 更新岗位 + */ + public RoleEntity updateRole(Long id, RoleEntity updateInfo, String userId) { + RoleEntity exists = getRoleForWrite(id, userId); + String previousStatus = exists.getStatus(); + + // 更新字段 + if (StringUtils.hasText(updateInfo.getName())) { + String normalizedName = updateInfo.getName().trim(); + if (!normalizedName.equals(exists.getName())) { + assertRoleNameUnique(normalizedName, exists.getId()); + } + exists.setName(normalizedName); + } + if (StringUtils.hasText(updateInfo.getDescription())) exists.setDescription(updateInfo.getDescription()); + if (StringUtils.hasText(updateInfo.getCategory())) exists.setCategory(updateInfo.getCategory()); + if (StringUtils.hasText(updateInfo.getIcon())) exists.setIcon(updateInfo.getIcon()); + if (StringUtils.hasText(updateInfo.getImage())) exists.setImage(updateInfo.getImage()); + if (updateInfo.getPrompt() != null) exists.setPrompt(normalizeRequiredRolePrompt(updateInfo.getPrompt())); + if (updateInfo.getConfigJson() != null) exists.setConfigJson(normalizeRoleConfig(updateInfo.getConfigJson())); + if (updateInfo.getIsPublic() != null) exists.setIsPublic(updateInfo.getIsPublic()); + if (updateInfo.getMaxEmployees() != null) exists.setMaxEmployees(updateInfo.getMaxEmployees()); + if (StringUtils.hasText(updateInfo.getStatus())) { + exists.setStatus(normalizeRoleStatus(updateInfo.getStatus())); + } + exists.setPrompt(normalizeRequiredRolePrompt(exists.getPrompt())); + + exists.setUpdaterId(userId); + // exists.setUpdaterName(username); // Need username passed in + + this.updateById(exists); + + boolean previousActive = "active".equalsIgnoreCase(previousStatus); + boolean currentInactive = !StringUtils.hasText(exists.getStatus()) + || !"active".equalsIgnoreCase(exists.getStatus()); + boolean transitionedToInactive = previousActive && currentInactive; + if (transitionedToInactive) { + cronJobService.disableByRoleId(exists.getId(), "岗位状态变更为 " + exists.getStatus()); + } + return exists; + } + + private RoleEntity.RoleConfig normalizeRoleConfig(RoleEntity.RoleConfig config) { + if (config == null) { + return null; + } + + String deployMode = normalizeDeployMode(config.getDeployMode()); + if (!StringUtils.hasText(deployMode)) { + deployMode = DeployMode.K8S.name(); + } + config.setDeployMode(deployMode); + + String runtimeMode = normalizeRuntimeMode(config.getRuntimeMode()); + if (DeployMode.COMPOSE.name().equals(deployMode)) { + runtimeMode = PodMode.ALONE.name(); + } + config.setRuntimeMode(runtimeMode); + + String runnerImage = normalizeText(config.getRunnerImage()); + if (DeployMode.COMPOSE.name().equals(deployMode) || PodMode.ALONE.name().equals(runtimeMode)) { + runnerImage = null; + } + config.setRunnerImage(runnerImage); + config.setMemoryEnabled(Boolean.TRUE.equals(config.getMemoryEnabled())); + + return config; + } + + private String normalizeDeployMode(String deployModeRaw) { + String deployMode = normalizeText(deployModeRaw); + if (!StringUtils.hasText(deployMode)) { + return null; + } + + String normalizedMode = deployMode.toUpperCase(); + if (!SUPPORTED_DEPLOY_MODES.contains(normalizedMode)) { + throw new IllegalArgumentException("非法部署模式: " + deployMode + ",仅支持 K8S/COMPOSE"); + } + return normalizedMode; + } + + private String normalizeRuntimeMode(String runtimeModeRaw) { + String runtimeMode = normalizeText(runtimeModeRaw); + if (!StringUtils.hasText(runtimeMode)) { + return null; + } + + String normalizedMode = runtimeMode.toUpperCase(); + if (!SUPPORTED_RUNTIME_MODES.contains(normalizedMode)) { + throw new IllegalArgumentException("非法运行模式: " + runtimeMode + ",仅支持 SIDECAR/ALONE"); + } + return normalizedMode; + } + + private String normalizeText(String raw) { + if (!StringUtils.hasText(raw)) { + return null; + } + return raw.trim(); + } + + private String normalizeRoleStatusOrDefault(String rawStatus) { + String normalized = normalizeRoleStatus(rawStatus); + return StringUtils.hasText(normalized) ? normalized : "active"; + } + + private String normalizeRequiredRolePrompt(String rawPrompt) { + String normalized = normalizeText(rawPrompt); + if (!StringUtils.hasText(normalized)) { + throw new IllegalArgumentException("岗位自定义 Prompt 不能为空"); + } + return normalized; + } + + private String normalizeRoleStatus(String rawStatus) { + if (!StringUtils.hasText(rawStatus)) { + return null; + } + String normalized = rawStatus.trim().toLowerCase(); + if (!SUPPORTED_ROLE_STATUSES.contains(normalized)) { + throw new IllegalArgumentException("非法岗位状态: " + rawStatus + ",仅支持 active/maintenance/disabled"); + } + return normalized; + } + + private void assertRoleNameUnique(String roleName, Long excludeRoleId) { + if (!StringUtils.hasText(roleName)) { + throw new IllegalArgumentException("岗位名称不能为空"); + } + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(RoleEntity::getName, roleName.trim()); + if (excludeRoleId != null) { + wrapper.ne(RoleEntity::getId, excludeRoleId); + } + + long count = this.count(wrapper); + if (count > 0) { + throw new IllegalArgumentException("岗位名称已存在: " + roleName.trim()); + } + } + + /** + * 删除岗位 + */ + public void deleteRole(Long id, String userId) { + RoleEntity exists = getRoleForWrite(id, userId); + + cronJobService.disableByRoleId(exists.getId(), "岗位已删除"); + this.removeById(id); + } + + /** + * 检查是否收藏 + */ + public boolean isFavorite(Long roleId, String userId) { + if (roleId == null || !StringUtils.hasText(userId)) { + return false; + } + return userFavoriteRoleMapper.exists( + new LambdaQueryWrapper() + .eq(UserFavoriteRoleEntity::getRoleId, roleId) + .eq(UserFavoriteRoleEntity::getUserId, userId) + ); + } + + public RoleEntity getRoleForRead(Long id, String userId) { + RoleEntity role = this.getById(id); + if (role == null) { + throw new ResourceNotFoundException("岗位不存在: " + id); + } + if (!canRead(role, userId)) { + throw new ForbiddenOperationException("无权限访问该岗位"); + } + return role; + } + + public RoleEntity getRoleForWrite(Long id, String userId) { + RoleEntity role = this.getById(id); + if (role == null) { + throw new ResourceNotFoundException("岗位不存在: " + id); + } + if (!canWrite(role, userId)) { + throw new ForbiddenOperationException("仅岗位创建者或管理员可修改"); + } + return role; + } + + private boolean canRead(RoleEntity role, String userId) { + return adminAccessService.isAdmin(userId) + || Boolean.TRUE.equals(role.getIsPublic()) + || isOwner(role, userId); + } + + private boolean canWrite(RoleEntity role, String userId) { + return adminAccessService.isAdmin(userId) || isOwner(role, userId); + } + + private boolean isOwner(RoleEntity role, String userId) { + return StringUtils.hasText(userId) && userId.equals(role.getCreatorId()); + } + + /** + * 切换收藏状态 + */ + @Transactional + public boolean toggleFavorite(Long roleId, String userId, boolean isFavorite) { + RoleEntity role = getRoleForRead(roleId, userId); + if (isFavorite) { + // 添加收藏 + if (!isFavorite(roleId, userId)) { + UserFavoriteRoleEntity entity = new UserFavoriteRoleEntity(); + entity.setUserId(userId); + entity.setRoleId(roleId); + userFavoriteRoleMapper.insert(entity); + roleHotRankService.incrementFavoriteScore(role.getId(), 1D); + } + return true; + } else { + // 取消收藏 + int deletedCount = userFavoriteRoleMapper.delete( + new LambdaQueryWrapper() + .eq(UserFavoriteRoleEntity::getRoleId, roleId) + .eq(UserFavoriteRoleEntity::getUserId, userId) + ); + if (deletedCount > 0) { + roleHotRankService.incrementFavoriteScore(role.getId(), -1D); + } + return false; + } + } +} diff --git a/back/src/main/java/com/linkwork/service/RuntimeModeService.java b/back/src/main/java/com/linkwork/service/RuntimeModeService.java new file mode 100644 index 0000000..743ec52 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/RuntimeModeService.java @@ -0,0 +1,187 @@ +package com.linkwork.service; + +import com.linkwork.config.EnvConfig; +import com.linkwork.model.entity.BuildRecordEntity; +import com.linkwork.model.entity.RoleEntity; +import com.linkwork.model.enums.PodMode; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.Map; + +/** + * 运行模式解析服务 + */ +@Service +@RequiredArgsConstructor +public class RuntimeModeService { + + private final BuildRecordService buildRecordService; + private final RoleService roleService; + private final EnvConfig envConfig; + + /** + * 按岗位 ID 解析运行模式视图。 + */ + public RuntimeSnapshot resolveForRole(Long roleId) { + if (roleId == null) { + return resolveDefault(); + } + + RoleEntity role = roleService.getById(roleId); + if (role == null) { + return resolveDefault(); + } + return resolveForRole(role); + } + + /** + * 按岗位实体解析运行模式视图。 + */ + public RuntimeSnapshot resolveForRole(RoleEntity role) { + if (role == null || role.getId() == null) { + throw new IllegalArgumentException("岗位信息不完整,无法解析运行模式"); + } + + String context = "roleId=" + role.getId(); + RoleEntity.RoleConfig roleConfig = role.getConfigJson(); + + PodMode configPodMode = null; + String configRunnerImage = null; + if (roleConfig != null) { + configPodMode = parsePodMode(roleConfig.getRuntimeMode(), context + ", configJson.runtimeMode"); + configRunnerImage = asText(roleConfig.getRunnerImage()); + } + + BuildRecordEntity latestRecord = buildRecordService.getLatestByRoleId(role.getId()); + PodMode snapshotPodMode = null; + String snapshotRunnerImage = null; + if (latestRecord != null && latestRecord.getConfigSnapshot() != null) { + Map snapshot = latestRecord.getConfigSnapshot(); + Object runtimeRaw = snapshot.get("runtimeMode") != null + ? snapshot.get("runtimeMode") + : snapshot.get("podMode"); + snapshotPodMode = parsePodMode(runtimeRaw, context + ", buildNo=" + latestRecord.getBuildNo()); + + snapshotRunnerImage = asText(snapshot.get("runnerImage")); + if (!StringUtils.hasText(snapshotRunnerImage)) { + snapshotRunnerImage = asText(snapshot.get("runnerBaseImage")); + } + } + + PodMode podMode = configPodMode != null ? configPodMode : snapshotPodMode; + if (podMode == null) { + podMode = resolveDefaultPodMode(context); + } + + String runnerImage = StringUtils.hasText(configRunnerImage) ? configRunnerImage : snapshotRunnerImage; + if (podMode == PodMode.SIDECAR && !StringUtils.hasText(runnerImage)) { + runnerImage = resolveDefaultRunnerImage(role); + } + + return resolveFromPodMode(podMode, runnerImage, context); + } + + /** + * 使用系统默认配置解析运行模式(用于历史数据兼容)。 + */ + public RuntimeSnapshot resolveDefault() { + PodMode podMode = resolveDefaultPodMode("default"); + String runnerImage = podMode == PodMode.SIDECAR ? resolveDefaultRunnerImage() : null; + return resolveFromPodMode(podMode, runnerImage, "default"); + } + + /** + * 按原始模式值解析运行模式视图。 + */ + public RuntimeSnapshot resolveFromRaw(String runtimeMode, String runnerImage, String context) { + PodMode podMode = parsePodMode(runtimeMode, context); + if (podMode == null) { + throw new IllegalArgumentException("运行模式缺失: " + context); + } + return resolveFromPodMode(podMode, runnerImage, context); + } + + /** + * 按 PodMode 解析运行模式视图。 + */ + public RuntimeSnapshot resolveFromPodMode(PodMode podMode, String runnerImage, String context) { + if (podMode == null) { + throw new IllegalArgumentException("运行模式缺失: " + context); + } + + if (podMode == PodMode.SIDECAR) { + if (!StringUtils.hasText(runnerImage)) { + throw new IllegalStateException("SIDECAR 模式缺少 runnerImage: " + context); + } + return new RuntimeSnapshot(PodMode.SIDECAR.name(), "ssh", runnerImage); + } + + return new RuntimeSnapshot(PodMode.ALONE.name(), "local", null); + } + + private PodMode resolveDefaultPodMode(String context) { + if (envConfig.getPodModeRules() == null || envConfig.getPodModeRules().getDefaultMode() == null) { + throw new IllegalStateException("未配置默认运行模式: " + context); + } + return envConfig.getPodModeRules().getDefaultMode(); + } + + private String resolveDefaultRunnerImage(RoleEntity role) { + String defaultRunnerImage = resolveDefaultRunnerImage(); + if (StringUtils.hasText(defaultRunnerImage)) { + return defaultRunnerImage; + } + if (role != null && StringUtils.hasText(role.getImage())) { + return role.getImage(); + } + return null; + } + + private String resolveDefaultRunnerImage() { + if (envConfig.getImages() != null && StringUtils.hasText(envConfig.getImages().getRunner())) { + return envConfig.getImages().getRunner(); + } + return null; + } + + private PodMode parsePodMode(Object rawMode, String context) { + if (rawMode == null) { + return null; + } + + if (rawMode instanceof PodMode podMode) { + return podMode; + } + + String mode = String.valueOf(rawMode).trim(); + if (!StringUtils.hasText(mode)) { + return null; + } + + try { + return PodMode.valueOf(mode.toUpperCase()); + } catch (IllegalArgumentException ex) { + throw new IllegalArgumentException("非法运行模式: " + mode + ", " + context); + } + } + + private String asText(Object value) { + if (value == null) { + return null; + } + String text = String.valueOf(value).trim(); + return StringUtils.hasText(text) ? text : null; + } + + @Data + @AllArgsConstructor + public static class RuntimeSnapshot { + private String runtimeMode; + private String zzMode; + private String runnerImage; + } +} diff --git a/back/src/main/java/com/linkwork/service/ScheduleEventPublisher.java b/back/src/main/java/com/linkwork/service/ScheduleEventPublisher.java new file mode 100644 index 0000000..d94cd98 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/ScheduleEventPublisher.java @@ -0,0 +1,377 @@ +package com.linkwork.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.linkwork.model.dto.ScheduleEvent; +import com.linkwork.model.dto.event.*; +import com.linkwork.model.enums.ContainerEventType; +import com.linkwork.model.dto.event.BuildEventData; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.connection.stream.RecordId; +import org.springframework.data.redis.connection.stream.StreamRecords; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.Map; + +/** + * 调度事件发布服务 + * 遵循 data-format.md 规范,发布容器日志事件到 Redis Stream + * + * Stream Key: stream:task:{taskId} + */ +@Service +@Slf4j +@RequiredArgsConstructor +public class ScheduleEventPublisher { + + private final StringRedisTemplate redisTemplate; + private final ObjectMapper objectMapper; + + /** + * ISO 8601 时间戳格式化器,精确到微秒 + */ + private static final DateTimeFormatter TIMESTAMP_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSSXXX") + .withZone(ZoneOffset.UTC); + + /** + * Stream Key 前缀 + */ + private static final String STREAM_KEY_PREFIX = "stream:task:"; + + // ==================== 通用发布方法 ==================== + + /** + * 发布容器日志事件到 Redis Stream + * + * @param taskId 任务 ID(对应 serviceId) + * @param eventType 事件类型枚举 + * @param data 业务负载 + */ + public void publish(String taskId, ContainerEventType eventType, Object data) { + ScheduleEvent event = ScheduleEvent.builder() + .eventType(eventType.name()) + .timestamp(formatTimestamp(Instant.now())) + .taskId(taskId) + .data(data) + .build(); + + try { + String eventJson = objectMapper.writeValueAsString(event); + String streamKey = STREAM_KEY_PREFIX + taskId; + + // 写入 Stream(持久化,支持回溯) + // XADD stream:task:{taskId} * payload '{...}' + RecordId recordId = redisTemplate.opsForStream().add( + StreamRecords.string(Map.of("payload", eventJson)).withStreamKey(streamKey) + ); + + log.debug("Published event to {}: type={}, recordId={}", + streamKey, eventType, recordId); + + } catch (JsonProcessingException e) { + log.error("Failed to serialize event for task {}: {}", taskId, e.getMessage()); + } catch (Exception e) { + log.error("Failed to publish event for task {}: {}", taskId, e.getMessage()); + } + } + + // ==================== 便捷方法:调度阶段 ==================== + + /** + * 发布 POD_SCHEDULING 事件 + */ + public void publishPodScheduling(String taskId, String podName, int podIndex, String queueName) { + PodSchedulingData data = PodSchedulingData.builder() + .podName(podName) + .podIndex(podIndex) + .queueName(queueName) + .build(); + publish(taskId, ContainerEventType.POD_SCHEDULING, data); + } + + /** + * 发布 POD_SCHEDULED 事件 + */ + public void publishPodScheduled(String taskId, String podName, int podIndex, String nodeName) { + PodSchedulingData data = PodSchedulingData.builder() + .podName(podName) + .podIndex(podIndex) + .nodeName(nodeName) + .build(); + publish(taskId, ContainerEventType.POD_SCHEDULED, data); + } + + // ==================== 便捷方法:镜像阶段 ==================== + + /** + * 发布 IMAGE_PULLING 事件 + */ + public void publishImagePulling(String taskId, String podName, String containerName, String image) { + ImageEventData data = ImageEventData.builder() + .podName(podName) + .containerName(containerName) + .image(image) + .build(); + publish(taskId, ContainerEventType.IMAGE_PULLING, data); + } + + /** + * 发布 IMAGE_PULLED 事件 + */ + public void publishImagePulled(String taskId, String podName, String containerName, String image) { + ImageEventData data = ImageEventData.builder() + .podName(podName) + .containerName(containerName) + .image(image) + .build(); + publish(taskId, ContainerEventType.IMAGE_PULLED, data); + } + + // ==================== 便捷方法:容器阶段 ==================== + + /** + * 发布 CONTAINER_STARTING 事件 + */ + public void publishContainerStarting(String taskId, String podName, String containerName) { + ContainerEventData data = ContainerEventData.builder() + .podName(podName) + .containerName(containerName) + .build(); + publish(taskId, ContainerEventType.CONTAINER_STARTING, data); + } + + /** + * 发布 CONTAINER_READY 事件 + */ + public void publishContainerReady(String taskId, String podName, String containerName) { + ContainerEventData data = ContainerEventData.builder() + .podName(podName) + .containerName(containerName) + .ready(true) + .build(); + publish(taskId, ContainerEventType.CONTAINER_READY, data); + } + + // ==================== 便捷方法:环境阶段 ==================== + + /** + * 发布 ENV_SETUP 事件 + */ + public void publishEnvSetup(String taskId, String podName, String step, String message) { + EnvSetupData data = EnvSetupData.builder() + .podName(podName) + .step(step) + .message(message) + .build(); + publish(taskId, ContainerEventType.ENV_SETUP, data); + } + + /** + * 发布 WORKSPACE_INIT 事件 + */ + public void publishWorkspaceInit(String taskId, String podName, String step, String message) { + EnvSetupData data = EnvSetupData.builder() + .podName(podName) + .step(step) + .message(message) + .build(); + publish(taskId, ContainerEventType.WORKSPACE_INIT, data); + } + + // ==================== 便捷方法:完成阶段 ==================== + + /** + * 发布 INIT_COMPLETE 事件 + */ + public void publishInitComplete(String taskId, String podName, String podGroupName, + int readyPods, int totalPods) { + InitCompleteData data = InitCompleteData.builder() + .podName(podName) + .podGroupName(podGroupName) + .readyPods(readyPods) + .totalPods(totalPods) + .build(); + publish(taskId, ContainerEventType.INIT_COMPLETE, data); + } + + /** + * 发布 INIT_FAILED 事件 + */ + public void publishInitFailed(String taskId, String podName, String podGroupName, + String errorCode, String errorMessage) { + InitCompleteData data = InitCompleteData.builder() + .podName(podName) + .podGroupName(podGroupName) + .errorCode(errorCode) + .errorMessage(errorMessage) + .build(); + publish(taskId, ContainerEventType.INIT_FAILED, data); + } + + // ==================== 便捷方法:生命周期事件 ==================== + + /** + * 发布 SESSION_START 事件 + */ + public void publishSessionStart(String taskId, String podGroupName, int podCount, + String queueName, String nodeName) { + SessionEventData data = SessionEventData.builder() + .podGroupName(podGroupName) + .podCount(podCount) + .queueName(queueName) + .nodeName(nodeName) + .build(); + publish(taskId, ContainerEventType.SESSION_START, data); + } + + /** + * 发布 SESSION_END 事件 + */ + public void publishSessionEnd(String taskId, String podGroupName, int podCount, boolean graceful) { + SessionEventData data = SessionEventData.builder() + .podGroupName(podGroupName) + .podCount(podCount) + .graceful(graceful) + .build(); + publish(taskId, ContainerEventType.SESSION_END, data); + } + + // ==================== 便捷方法:构建阶段 ==================== + + /** + * 构建事件使用独立的 Stream Key + */ + private static final String BUILD_STREAM_KEY_PREFIX = "stream:build:"; + + /** + * 发布构建事件到 Redis Stream(使用 buildId 作为 key) + */ + public void publishBuildEvent(String buildId, ContainerEventType eventType, Object data) { + ScheduleEvent event = ScheduleEvent.builder() + .eventType(eventType.name()) + .timestamp(formatTimestamp(Instant.now())) + .taskId(buildId) + .data(data) + .build(); + + try { + String eventJson = objectMapper.writeValueAsString(event); + String streamKey = BUILD_STREAM_KEY_PREFIX + buildId; + + RecordId recordId = redisTemplate.opsForStream().add( + StreamRecords.string(Map.of("payload", eventJson)).withStreamKey(streamKey) + ); + + log.debug("Published build event to {}: type={}, recordId={}", + streamKey, eventType, recordId); + + } catch (JsonProcessingException e) { + log.error("Failed to serialize build event for {}: {}", buildId, e.getMessage()); + } catch (Exception e) { + log.error("Failed to publish build event for {}: {}", buildId, e.getMessage()); + } + } + + /** + * 发布 BUILD_STARTED 事件 + */ + public void publishBuildStarted(String buildId, String buildNo, Long roleId, + String roleName, String baseImage) { + BuildEventData data = BuildEventData.builder() + .buildNo(buildNo) + .roleId(roleId) + .roleName(roleName) + .baseImage(baseImage) + .build(); + publishBuildEvent(buildId, ContainerEventType.BUILD_STARTED, data); + } + + /** + * 发布 BUILD_PROGRESS 事件 + */ + public void publishBuildProgress(String buildId, String buildNo, String step, String message) { + BuildEventData data = BuildEventData.builder() + .buildNo(buildNo) + .step(step) + .message(message) + .build(); + publishBuildEvent(buildId, ContainerEventType.BUILD_PROGRESS, data); + } + + /** + * 发布 BUILD_LOG 事件(Docker 实时日志行) + */ + public void publishBuildLog(String buildId, String buildNo, String logLine, String logLevel) { + BuildEventData data = BuildEventData.builder() + .buildNo(buildNo) + .message(logLine) + .step(logLevel) // 使用 step 字段存储日志级别 (info, warn, error, debug) + .build(); + publishBuildEvent(buildId, ContainerEventType.BUILD_LOG, data); + } + + /** + * 发布 BUILD_COMPLETED 事件 + */ + public void publishBuildCompleted(String buildId, String buildNo, String imageTag, Long durationMs) { + BuildEventData data = BuildEventData.builder() + .buildNo(buildNo) + .imageTag(imageTag) + .durationMs(durationMs) + .build(); + publishBuildEvent(buildId, ContainerEventType.BUILD_COMPLETED, data); + } + + /** + * 发布 BUILD_FAILED 事件 + */ + public void publishBuildFailed(String buildId, String buildNo, String errorCode, + String errorMessage, Long durationMs) { + BuildEventData data = BuildEventData.builder() + .buildNo(buildNo) + .errorCode(errorCode) + .errorMessage(errorMessage) + .durationMs(durationMs) + .build(); + publishBuildEvent(buildId, ContainerEventType.BUILD_FAILED, data); + } + + /** + * 发布 BUILD_PUSHING 事件 + */ + public void publishBuildPushing(String buildId, String buildNo, String imageTag) { + BuildEventData data = BuildEventData.builder() + .buildNo(buildNo) + .imageTag(imageTag) + .message("Pushing image to registry...") + .build(); + publishBuildEvent(buildId, ContainerEventType.BUILD_PUSHING, data); + } + + /** + * 发布 BUILD_PUSHED 事件 + */ + public void publishBuildPushed(String buildId, String buildNo, String imageTag) { + BuildEventData data = BuildEventData.builder() + .buildNo(buildNo) + .imageTag(imageTag) + .message("Image pushed successfully") + .build(); + publishBuildEvent(buildId, ContainerEventType.BUILD_PUSHED, data); + } + + // ==================== 工具方法 ==================== + + /** + * 格式化时间戳为 ISO 8601 格式,精确到微秒 + */ + private String formatTimestamp(Instant instant) { + return TIMESTAMP_FORMATTER.format(instant); + } +} diff --git a/back/src/main/java/com/linkwork/service/SecurityPolicyService.java b/back/src/main/java/com/linkwork/service/SecurityPolicyService.java new file mode 100644 index 0000000..a9bc5a3 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/SecurityPolicyService.java @@ -0,0 +1,186 @@ +package com.linkwork.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.linkwork.mapper.SecurityPolicyMapper; +import com.linkwork.model.entity.SecurityPolicy; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 安全策略服务 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class SecurityPolicyService { + + private final SecurityPolicyMapper policyMapper; + private final ObjectMapper objectMapper; + + /** + * 获取所有安全策略 + */ + public List> listPolicies() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.orderByAsc(SecurityPolicy::getType) // system 排前面 + .orderByDesc(SecurityPolicy::getCreatedAt); + List policies = policyMapper.selectList(wrapper); + return policies.stream().map(this::toResponse).collect(Collectors.toList()); + } + + /** + * 获取单个策略 + */ + public Map getPolicy(Long id) { + SecurityPolicy policy = policyMapper.selectById(id); + if (policy == null) { + throw new IllegalArgumentException("策略不存在: " + id); + } + return toResponse(policy); + } + + /** + * 创建自定义策略 + */ + @Transactional + public Map createPolicy(Map request, String creatorId, String creatorName) { + SecurityPolicy policy = new SecurityPolicy(); + policy.setName((String) request.get("name")); + policy.setDescription((String) request.get("description")); + policy.setType("custom"); + policy.setEnabled(true); + policy.setCreatorId(creatorId); + policy.setCreatorName(creatorName); + policy.setCreatedAt(LocalDateTime.now()); + policy.setUpdatedAt(LocalDateTime.now()); + policy.setIsDeleted(0); + + // 序列化规则 + Object rules = request.get("rules"); + if (rules != null) { + try { + policy.setRulesJson(objectMapper.writeValueAsString(rules)); + } catch (JsonProcessingException e) { + log.error("序列化策略规则失败", e); + } + } else { + policy.setRulesJson("[]"); + } + + policyMapper.insert(policy); + log.info("安全策略创建成功: id={}, name={}", policy.getId(), policy.getName()); + return toResponse(policy); + } + + /** + * 更新策略 + */ + @Transactional + public Map updatePolicy(Long id, Map request) { + SecurityPolicy policy = policyMapper.selectById(id); + if (policy == null) { + throw new IllegalArgumentException("策略不存在: " + id); + } + if ("system".equals(policy.getType())) { + throw new IllegalArgumentException("系统策略不可编辑"); + } + + if (request.containsKey("name")) { + policy.setName((String) request.get("name")); + } + if (request.containsKey("description")) { + policy.setDescription((String) request.get("description")); + } + if (request.containsKey("enabled")) { + policy.setEnabled((Boolean) request.get("enabled")); + } + if (request.containsKey("rules")) { + try { + policy.setRulesJson(objectMapper.writeValueAsString(request.get("rules"))); + } catch (JsonProcessingException e) { + log.error("序列化策略规则失败", e); + } + } + + policy.setUpdatedAt(LocalDateTime.now()); + policyMapper.updateById(policy); + log.info("安全策略更新成功: id={}, name={}", policy.getId(), policy.getName()); + return toResponse(policy); + } + + /** + * 切换策略启用/禁用 + */ + @Transactional + public Map togglePolicy(Long id) { + SecurityPolicy policy = policyMapper.selectById(id); + if (policy == null) { + throw new IllegalArgumentException("策略不存在: " + id); + } + if ("system".equals(policy.getType()) && Boolean.TRUE.equals(policy.getEnabled())) { + throw new IllegalArgumentException("系统策略不能禁用"); + } + + policy.setEnabled(!policy.getEnabled()); + policy.setUpdatedAt(LocalDateTime.now()); + policyMapper.updateById(policy); + log.info("安全策略状态切换: id={}, enabled={}", policy.getId(), policy.getEnabled()); + return toResponse(policy); + } + + /** + * 删除策略 + */ + @Transactional + public void deletePolicy(Long id) { + SecurityPolicy policy = policyMapper.selectById(id); + if (policy == null) { + throw new IllegalArgumentException("策略不存在: " + id); + } + if ("system".equals(policy.getType())) { + throw new IllegalArgumentException("系统策略不可删除"); + } + policyMapper.deleteById(id); + log.info("安全策略删除: id={}, name={}", id, policy.getName()); + } + + /** + * 转换为响应格式 + */ + private Map toResponse(SecurityPolicy policy) { + Map map = new LinkedHashMap<>(); + map.put("id", policy.getId()); + map.put("name", policy.getName()); + map.put("description", policy.getDescription()); + map.put("type", policy.getType()); + map.put("enabled", policy.getEnabled()); + map.put("creatorName", policy.getCreatorName()); + map.put("createdAt", policy.getCreatedAt()); + map.put("updatedAt", policy.getUpdatedAt()); + + // 解析规则 JSON + if (policy.getRulesJson() != null) { + try { + List> rules = objectMapper.readValue( + policy.getRulesJson(), new TypeReference>>() {}); + map.put("rules", rules); + } catch (JsonProcessingException e) { + log.error("解析策略规则失败: id={}", policy.getId(), e); + map.put("rules", List.of()); + } + } else { + map.put("rules", List.of()); + } + + return map; + } +} diff --git a/back/src/main/java/com/linkwork/service/ServiceResumeService.java b/back/src/main/java/com/linkwork/service/ServiceResumeService.java new file mode 100644 index 0000000..0d5a8f1 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/ServiceResumeService.java @@ -0,0 +1,100 @@ +package com.linkwork.service; + +import com.linkwork.model.dto.ServiceBuildRequest; +import com.linkwork.model.dto.ServiceBuildResult; +import com.linkwork.model.dto.ServiceResumeResult; +import com.linkwork.model.dto.ServiceSnapshot; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +/** + * 服务快速恢复服务 + * + * 利用镜像缓存 + 节点亲和调度实现快速重启: + * 1. 查询服务快照 + * 2. 还原原始请求 + * 3. 设置 preferredNode(优先调度到原节点) + * 4. 调用 Build 服务 + * + * 效果:命中镜像缓存时,启动时间从 ~90s 降到 ~20s + */ +@Service +@Slf4j +public class ServiceResumeService { + + private final ServiceSnapshotService snapshotService; + private final ServiceScheduleService scheduleService; + + public ServiceResumeService(ServiceSnapshotService snapshotService, + ServiceScheduleService scheduleService) { + this.snapshotService = snapshotService; + this.scheduleService = scheduleService; + } + + /** + * 快速恢复 Service + * + * @param serviceId 服务 ID + * @return 恢复结果 + */ + public ServiceResumeResult resume(String serviceId) { + log.info("Resuming service {}", serviceId); + + // 1. 查询快照 + Optional snapshotOpt = snapshotService.findActiveSnapshot(serviceId); + + if (snapshotOpt.isEmpty()) { + log.info("No active snapshot for service {}, requires full build request", serviceId); + return ServiceResumeResult.snapshotNotFound(serviceId); + } + + ServiceSnapshot snapshot = snapshotOpt.get(); + String lastScheduledNode = snapshot.getLastScheduledNode(); + + // 2. 还原原始请求 + ServiceBuildRequest request = snapshotService.restoreRequest(snapshot); + if (request == null) { + return ServiceResumeResult.failed(serviceId, "RESTORE_FAILED", + "无法还原原始请求"); + } + + // 3. 设置优先调度节点(关键!用于命中镜像缓存) + request.setPreferredNode(lastScheduledNode); + log.info("Resume service {}: setting preferredNode={}", serviceId, lastScheduledNode); + + // 4. 调用 Build 服务 + try { + ServiceBuildResult buildResult = scheduleService.build(request); + + if (!buildResult.isSuccess()) { + return ServiceResumeResult.failed(serviceId, + buildResult.getErrorCode(), buildResult.getErrorMessage()); + } + + // 5. 更新快照 + snapshotService.onServiceResumed(serviceId, buildResult.getScheduledNode()); + + // 6. 判断是否命中缓存 + boolean imageCached = lastScheduledNode != null + && lastScheduledNode.equals(buildResult.getScheduledNode()); + + log.info("Service {} resumed successfully, scheduledNode={}, imageCached={}", + serviceId, buildResult.getScheduledNode(), imageCached); + + return ServiceResumeResult.success( + serviceId, + buildResult.getPodGroupName(), + buildResult.getPodNames(), + buildResult.getScheduledNode(), + imageCached + ); + + } catch (Exception e) { + log.error("Failed to resume service {}: {}", serviceId, e.getMessage(), e); + return ServiceResumeResult.failed(serviceId, "RESUME_FAILED", + "恢复失败: " + e.getMessage()); + } + } +} diff --git a/back/src/main/java/com/linkwork/service/ServiceScaleService.java b/back/src/main/java/com/linkwork/service/ServiceScaleService.java new file mode 100644 index 0000000..a2f0184 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/ServiceScaleService.java @@ -0,0 +1,506 @@ +package com.linkwork.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.linkwork.model.dto.*; +import com.linkwork.model.entity.BuildRecordEntity; +import com.linkwork.model.enums.DeployMode; +import com.linkwork.model.enums.PodMode; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +/** + * 服务动态伸缩服务 + */ +@Service +@Slf4j +public class ServiceScaleService { + + private final K8sOrchestrator k8sOrchestrator; + private final ServiceSnapshotService snapshotService; + private final ConfigMergeService configMergeService; + private final DistributedLockService lockService; + private final BuildRecordService buildRecordService; + private final ObjectMapper objectMapper; + + private static final int SCALE_UP_MAX_RETRIES = 3; + + public ServiceScaleService(K8sOrchestrator k8sOrchestrator, + ServiceSnapshotService snapshotService, + ConfigMergeService configMergeService, + DistributedLockService lockService, + BuildRecordService buildRecordService, + ObjectMapper objectMapper) { + this.k8sOrchestrator = k8sOrchestrator; + this.snapshotService = snapshotService; + this.configMergeService = configMergeService; + this.lockService = lockService; + this.buildRecordService = buildRecordService; + this.objectMapper = objectMapper; + } + + public ScaleResult scaleDown(String serviceId, String podName, String source) { + log.info("Scale down request for service {}, podName={}, source={}", + serviceId, podName, source); + + String lockValue = lockService.tryAcquireLock(serviceId); + if (lockValue == null) { + return ScaleResult.failed(serviceId, + "Failed to acquire lock, another scaling operation may be in progress"); + } + + try { + return doScaleDown(serviceId, podName); + } finally { + lockService.releaseLock(serviceId, lockValue); + } + } + + private ScaleResult doScaleDown(String serviceId, String podName) { + ServiceSnapshot snapshot = snapshotService.getSnapshot(serviceId); + if (snapshot == null) { + return ScaleResult.failed(serviceId, "Snapshot not found for service"); + } + + int previousCount = snapshot.getCurrentPodCount() != null ? snapshot.getCurrentPodCount() : 0; + int maxPodCount = snapshot.getMaxPodCount() != null ? snapshot.getMaxPodCount() : previousCount; + + ScaleResult k8sResult = k8sOrchestrator.scaleDown(serviceId, podName); + if (!k8sResult.isSuccess()) { + log.error("Failed to delete pod {} for service {}: {}", + podName, serviceId, k8sResult.getErrorMessage()); + return k8sResult; + } + + int currentCount = k8sResult.getCurrentPodCount(); + snapshot.setCurrentPodCount(currentCount); + + if (snapshot.getRunningPodNames() != null) { + snapshot.getRunningPodNames().remove(podName); + } + + snapshotService.updateSnapshot(snapshot); + + log.info("Scale down result for service {}: {} -> {} pods", + serviceId, previousCount, currentCount); + + return ScaleResult.success( + serviceId, + "SCALE_DOWN", + previousCount, + currentCount, + maxPodCount, + k8sResult.getRunningPods(), + null, + List.of(podName) + ); + } + + public ScaleResult scaleUp(String serviceId, Integer targetPodCount, String source) { + log.info("Scale up request for service {}, targetPodCount={}, source={}", + serviceId, targetPodCount, source); + + String lockValue = lockService.tryAcquireLock(serviceId); + if (lockValue == null) { + return ScaleResult.failed(serviceId, + "Failed to acquire lock, another scaling operation may be in progress"); + } + + try { + return doScaleUp(serviceId, targetPodCount); + } finally { + lockService.releaseLock(serviceId, lockValue); + } + } + + private ScaleResult doScaleUp(String serviceId, Integer targetPodCount) { + ServiceSnapshot snapshot = snapshotService.getSnapshot(serviceId); + if (snapshot == null) { + snapshot = tryRecoverSnapshotFromSources(serviceId); + } + if (snapshot == null) { + return ScaleResult.failed(serviceId, "Snapshot not found, cannot scale up"); + } + + int maxPodCount = snapshot.getMaxPodCount() != null ? snapshot.getMaxPodCount() : 1; + int target = targetPodCount != null ? targetPodCount : maxPodCount; + int currentCount = snapshot.getCurrentPodCount() != null ? snapshot.getCurrentPodCount() : 0; + + if (target > maxPodCount) { + log.warn("Target pod count {} exceeds max {}, using max", target, maxPodCount); + target = maxPodCount; + } + + if (currentCount >= target) { + List runningPods = k8sOrchestrator.getRunningPods(serviceId); + return ScaleResult.noChange(serviceId, currentCount, maxPodCount, runningPods); + } + + MergedConfig config = restoreConfig(snapshot); + if (config == null) { + return ScaleResult.failed(serviceId, "Failed to restore config from snapshot"); + } + + if (snapshot.getLastScheduledNode() != null) { + config.setPreferredNode(snapshot.getLastScheduledNode()); + } + + int toAdd = target - currentCount; + List addedPods = new ArrayList<>(); + + for (int i = 0; i < toAdd; i++) { + String newPodName = createPodWithRetry(serviceId, config, snapshot); + if (newPodName != null) { + addedPods.add(newPodName); + + if (snapshot.getRunningPodNames() == null) { + snapshot.setRunningPodNames(new HashSet<>()); + } + snapshot.getRunningPodNames().add(newPodName); + } else { + log.warn("Failed to create pod after {} retries, stopping scale-up", SCALE_UP_MAX_RETRIES); + break; + } + } + + int newCount = currentCount + addedPods.size(); + if (!addedPods.isEmpty()) { + snapshot.setCurrentPodCount(newCount); + snapshotService.updateSnapshot(snapshot); + } + + log.info("Scale up result for service {}: {} -> {} pods (target={}, max={}), added={}", + serviceId, currentCount, newCount, target, maxPodCount, addedPods); + + List runningPods = k8sOrchestrator.getRunningPods(serviceId); + return ScaleResult.builder() + .serviceId(serviceId) + .success(!addedPods.isEmpty()) + .scaleType("SCALE_UP") + .previousPodCount(currentCount) + .currentPodCount(newCount) + .maxPodCount(maxPodCount) + .runningPods(runningPods) + .addedPods(addedPods) + .errorMessage(addedPods.isEmpty() ? "Failed to create any pod" : null) + .build(); + } + + private String createPodWithRetry(String serviceId, MergedConfig config, ServiceSnapshot snapshot) { + for (int retry = 0; retry < SCALE_UP_MAX_RETRIES; retry++) { + try { + ScaleResult result = k8sOrchestrator.scaleUp( + serviceId, + (snapshot.getCurrentPodCount() != null ? snapshot.getCurrentPodCount() : 0) + 1, + config + ); + + if (result.isSuccess() && result.getAddedPods() != null && !result.getAddedPods().isEmpty()) { + return result.getAddedPods().get(0); + } + + log.warn("Create pod failed for service {}, retry {}/{}", + serviceId, retry + 1, SCALE_UP_MAX_RETRIES); + + } catch (Exception e) { + log.warn("Create pod exception for service {}, retry {}/{}: {}", + serviceId, retry + 1, SCALE_UP_MAX_RETRIES, e.getMessage()); + } + + if (retry < SCALE_UP_MAX_RETRIES - 1) { + try { + Thread.sleep(1000L * (retry + 1)); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + return null; + } + } + } + return null; + } + + public ScaleResult scale(String serviceId, int targetPodCount, String source) { + log.info("Scale request for service {}, targetPodCount={}, source={}", + serviceId, targetPodCount, source); + + String lockValue = lockService.tryAcquireLock(serviceId); + if (lockValue == null) { + return ScaleResult.failed(serviceId, + "Failed to acquire lock, another scaling operation may be in progress"); + } + + try { + return doScale(serviceId, targetPodCount, source); + } finally { + lockService.releaseLock(serviceId, lockValue); + } + } + + private ScaleResult doScale(String serviceId, int targetPodCount, String source) { + List runningPods = k8sOrchestrator.getRunningPods(serviceId); + int currentCount = runningPods.size(); + + if (targetPodCount < currentCount) { + int podsToRemove = currentCount - targetPodCount; + ScaleResult lastResult = null; + + for (int i = 0; i < podsToRemove; i++) { + List currentPods = k8sOrchestrator.getRunningPods(serviceId); + if (currentPods.isEmpty()) { + break; + } + String podToDelete = currentPods.get(currentPods.size() - 1); + + lastResult = doScaleDown(serviceId, podToDelete); + if (!lastResult.isSuccess()) { + return lastResult; + } + } + + return lastResult; + + } else if (targetPodCount > currentCount) { + return doScaleUp(serviceId, targetPodCount); + + } else { + ServiceSnapshot snapshot = snapshotService.getSnapshot(serviceId); + int maxPodCount = snapshot != null && snapshot.getMaxPodCount() != null + ? snapshot.getMaxPodCount() : currentCount; + return ScaleResult.noChange(serviceId, currentCount, maxPodCount, runningPods); + } + } + + public ScaleResult getScaleStatus(String serviceId) { + List runningPods = k8sOrchestrator.getRunningPods(serviceId); + ServiceSnapshot snapshot = snapshotService.getSnapshot(serviceId); + + int maxPodCount = snapshot != null && snapshot.getMaxPodCount() != null + ? snapshot.getMaxPodCount() : runningPods.size(); + + return ScaleResult.noChange(serviceId, runningPods.size(), maxPodCount, runningPods); + } + + /** + * 任务入队时确保岗位下有可用 Pod。 + * 如果快照存在且当前 Pod 数量为 0,按岗位配置的 maxPodCount 扩容。 + * + * @param serviceId 服务 ID(= roleId) + * @return 扩容结果;快照不存在或已有 Pod 时返回 null(无需扩容) + */ + public ScaleResult ensurePodsForRole(String serviceId) { + ServiceSnapshot snapshot = snapshotService.getSnapshot(serviceId); + if (snapshot == null) { + snapshot = tryRecoverSnapshotFromSources(serviceId); + if (snapshot == null) { + log.warn("ensurePodsForRole: 无法恢复 Snapshot,跳过自动扩容: serviceId={}", serviceId); + return null; + } + } + + int currentCount = snapshot.getCurrentPodCount() != null ? snapshot.getCurrentPodCount() : 0; + if (currentCount > 0) { + return null; + } + + // 二次确认:快照可能与 K8s 实际状态不同步 + List actualPods = k8sOrchestrator.getRunningPods(serviceId); + if (!actualPods.isEmpty()) { + snapshot.setCurrentPodCount(actualPods.size()); + snapshotService.updateSnapshot(snapshot); + log.info("ensurePodsForRole: snapshot was stale for service {}, actual pods={}", + serviceId, actualPods.size()); + return null; + } + + log.info("ensurePodsForRole: no pods for service {}, triggering scale-up to maxPodCount", serviceId); + return scaleUp(serviceId, null, "task_enqueue"); + } + + private MergedConfig restoreConfig(ServiceSnapshot snapshot) { + try { + String json = snapshot.getOriginalRequestJson(); + + if (json == null || json.isEmpty()) { + json = snapshotService.getOriginalRequestJsonFromRedis(snapshot.getServiceId()); + if (json != null) { + snapshot.setOriginalRequestJson(json); + snapshotService.updateSnapshot(snapshot); + } + } + + if (json == null || json.isEmpty()) { + json = rebuildRequestFromBuildRecord(snapshot); + if (json != null) { + snapshot.setOriginalRequestJson(json); + snapshotService.updateSnapshot(snapshot); + } + } + + if (json == null || json.isEmpty()) { + log.error("Original request JSON is empty for service {} and all fallbacks failed", + snapshot.getServiceId()); + return null; + } + + ServiceBuildRequest request = objectMapper.readValue(json, ServiceBuildRequest.class); + MergedConfig config = configMergeService.merge(request); + + if (snapshot.getAgentImage() != null && !snapshot.getAgentImage().isEmpty()) { + config.setAgentImage(snapshot.getAgentImage()); + log.info("Restored built agent image for service {}: {}", + snapshot.getServiceId(), snapshot.getAgentImage()); + } else { + String builtImage = resolveBuiltImageFromRecord(snapshot.getServiceId()); + if (builtImage != null) { + config.setAgentImage(builtImage); + snapshot.setAgentImage(builtImage); + snapshotService.updateSnapshot(snapshot); + log.info("Restored built agent image from DB for service {}: {}", + snapshot.getServiceId(), builtImage); + } + } + + return config; + + } catch (Exception e) { + log.error("Failed to restore config from snapshot: {}", e.getMessage(), e); + return null; + } + } + + private String resolveBuiltImageFromRecord(String serviceId) { + try { + Long roleId = Long.parseLong(serviceId); + BuildRecordEntity record = buildRecordService.getLatestByRoleId(roleId); + if (record != null && BuildRecordEntity.STATUS_SUCCESS.equals(record.getStatus()) + && record.getImageTag() != null) { + return record.getImageTag(); + } + } catch (Exception e) { + log.warn("Failed to resolve built image from DB for service {}: {}", serviceId, e.getMessage()); + } + return null; + } + + /** + * 当内存中无 Snapshot 时,尝试从 Redis + DB 恢复一个可用的 Snapshot。 + * 典型场景:后端重启后 Pod 已结束(Succeeded),SnapshotSyncTask 不会重建该服务的 Snapshot。 + */ + @SuppressWarnings("unchecked") + private ServiceSnapshot tryRecoverSnapshotFromSources(String serviceId) { + try { + Long roleId = Long.parseLong(serviceId); + BuildRecordEntity record = buildRecordService.getLatestByRoleId(roleId); + if (record == null) { + log.warn("tryRecoverSnapshot: 岗位无构建记录,无法恢复 Snapshot: serviceId={}", serviceId); + return null; + } + if (!BuildRecordEntity.STATUS_SUCCESS.equals(record.getStatus())) { + log.warn("tryRecoverSnapshot: 最新构建记录状态非 SUCCESS,无法恢复: serviceId={}, buildNo={}, status={}", + serviceId, record.getBuildNo(), record.getStatus()); + return null; + } + + String originalRequestJson = snapshotService.getOriginalRequestJsonFromRedis(serviceId); + + if (originalRequestJson == null) { + log.info("tryRecoverSnapshot: Redis 中无 originalRequestJson,尝试从 DB 构建记录重建: serviceId={}", serviceId); + originalRequestJson = rebuildRequestJsonFromRecord(serviceId, record); + } + if (originalRequestJson == null) { + log.warn("tryRecoverSnapshot: 从 Redis 和 DB 均无法恢复 originalRequestJson: serviceId={}, buildNo={}", + serviceId, record.getBuildNo()); + return null; + } + + Map config = record.getConfigSnapshot(); + String podMode = config != null && config.get("podMode") != null ? + (String) config.get("podMode") : "SIDECAR"; + Integer podCount = config != null && config.get("podCount") != null ? + ((Number) config.get("podCount")).intValue() : 1; + + ServiceSnapshot snapshot = ServiceSnapshot.builder() + .serviceId(serviceId) + .userId(record.getCreatorId()) + .originalRequestJson(originalRequestJson) + .agentImage(record.getImageTag()) + .podMode(podMode) + .maxPodCount(podCount) + .currentPodCount(0) + .runningPodNames(new HashSet<>()) + .nextPodIndex(0) + .createdAt(java.time.Instant.now()) + .lastActiveAt(java.time.Instant.now()) + .status(com.linkwork.model.enums.SnapshotStatus.ACTIVE) + .resumeCount(0) + .build(); + + snapshotService.updateSnapshot(snapshot); + log.info("Recovered snapshot from Redis/DB for service {}, maxPodCount={}", + serviceId, podCount); + return snapshot; + + } catch (Exception e) { + log.warn("Failed to recover snapshot for service {}: {}", serviceId, e.getMessage()); + return null; + } + } + + @SuppressWarnings("unchecked") + private String rebuildRequestJsonFromRecord(String serviceId, BuildRecordEntity record) { + try { + Map config = record.getConfigSnapshot(); + if (config == null) { + return null; + } + ServiceBuildRequest request = ServiceBuildRequest.builder() + .serviceId(serviceId) + .userId(record.getCreatorId()) + .roleId(record.getRoleId()) + .roleName(record.getRoleName()) + .deployMode(DeployMode.K8S) + .buildEnvVars(config.containsKey("buildEnvVars") ? + (Map) config.get("buildEnvVars") : Map.of()) + .podMode(config.containsKey("podMode") && config.get("podMode") != null ? + PodMode.valueOf((String) config.get("podMode")) : PodMode.SIDECAR) + .podCount(config.containsKey("podCount") && config.get("podCount") != null ? + ((Number) config.get("podCount")).intValue() : 1) + .runnerBaseImage(config.containsKey("runnerBaseImage") ? + (String) config.get("runnerBaseImage") : null) + .build(); + return objectMapper.writeValueAsString(request); + } catch (Exception e) { + log.error("Failed to build request JSON from record for service {}: {}", serviceId, e.getMessage()); + return null; + } + } + + private String rebuildRequestFromBuildRecord(ServiceSnapshot snapshot) { + String serviceId = snapshot.getServiceId(); + try { + Long roleId = Long.parseLong(serviceId); + BuildRecordEntity record = buildRecordService.getLatestByRoleId(roleId); + if (record == null || !BuildRecordEntity.STATUS_SUCCESS.equals(record.getStatus())) { + log.warn("No successful build record found for role {}", roleId); + return null; + } + String json = rebuildRequestJsonFromRecord(serviceId, record); + if (json != null) { + log.info("Rebuilt ServiceBuildRequest from DB build record for service {}, buildNo={}", + serviceId, record.getBuildNo()); + } + return json; + } catch (NumberFormatException e) { + log.warn("ServiceId {} is not a valid roleId, cannot reconstruct from build record", serviceId); + return null; + } catch (Exception e) { + log.error("Failed to rebuild request from build record for service {}: {}", + serviceId, e.getMessage(), e); + return null; + } + } +} diff --git a/back/src/main/java/com/linkwork/service/ServiceScheduleService.java b/back/src/main/java/com/linkwork/service/ServiceScheduleService.java new file mode 100644 index 0000000..f7d558f --- /dev/null +++ b/back/src/main/java/com/linkwork/service/ServiceScheduleService.java @@ -0,0 +1,953 @@ +package com.linkwork.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.linkwork.config.BuildQueueConfig; +import com.linkwork.config.ImageBuildConfig; +import com.linkwork.model.dto.BuildTask; +import com.linkwork.model.dto.GeneratedSpec; +import com.linkwork.model.dto.ImageBuildResult; +import com.linkwork.model.dto.MergedConfig; +import com.linkwork.model.dto.ServiceBuildRequest; +import com.linkwork.model.dto.ServiceBuildResult; +import com.linkwork.model.entity.McpServerEntity; +import com.linkwork.model.entity.RoleEntity; +import com.linkwork.model.entity.SkillEntity; +import com.linkwork.model.enums.DeployMode; +import com.linkwork.context.UserContext; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * 服务调度服务 + */ +@Service +@Slf4j +public class ServiceScheduleService { + + private final ConfigMergeService configMergeService; + private final K8sOrchestrator orchestrator; + private final DockerComposeGenerator composeGenerator; + private final ImageBuildService imageBuildService; + private final ImageBuildConfig imageBuildConfig; + private final BuildRecordService buildRecordService; + private final ScheduleEventPublisher eventPublisher; + private final GitLabAuthService gitLabAuthService; + private final BuildQueueService buildQueueService; + private final BuildQueueConfig buildQueueConfig; + private final McpServerService mcpServerService; + private final SkillService skillService; + private final RoleService roleService; + private final ObjectMapper objectMapper; + private final ServiceSnapshotService snapshotService; + + @Value("${robot.skills.repo-url:}") + private String skillsRepoUrl; + + @Value("${robot.skills.deploy-token:}") + private String skillsDeployToken; + + public ServiceScheduleService(ConfigMergeService configMergeService, + K8sOrchestrator orchestrator, + DockerComposeGenerator composeGenerator, + ImageBuildService imageBuildService, + ImageBuildConfig imageBuildConfig, + BuildRecordService buildRecordService, + ScheduleEventPublisher eventPublisher, + GitLabAuthService gitLabAuthService, + BuildQueueService buildQueueService, + BuildQueueConfig buildQueueConfig, + McpServerService mcpServerService, + SkillService skillService, + RoleService roleService, + ObjectMapper objectMapper, + ServiceSnapshotService snapshotService) { + this.configMergeService = configMergeService; + this.orchestrator = orchestrator; + this.composeGenerator = composeGenerator; + this.imageBuildService = imageBuildService; + this.imageBuildConfig = imageBuildConfig; + this.buildRecordService = buildRecordService; + this.eventPublisher = eventPublisher; + this.gitLabAuthService = gitLabAuthService; + this.buildQueueService = buildQueueService; + this.buildQueueConfig = buildQueueConfig; + this.mcpServerService = mcpServerService; + this.skillService = skillService; + this.roleService = roleService; + this.objectMapper = objectMapper; + this.snapshotService = snapshotService; + } + + /** + * 构建服务(同步方式,立即返回,实际构建异步执行) + * + * 如果启用了构建队列,任务会进入队列排队; + * 否则直接异步执行(兼容旧行为)。 + */ + public ServiceBuildResult build(ServiceBuildRequest request) { + String serviceId = request.getServiceId(); + + // 确保 buildId 存在(如果前端未传,则后端生成) + String buildId = request.getBuildId(); + if (!StringUtils.hasText(buildId)) { + buildId = UUID.randomUUID().toString(); + request.setBuildId(buildId); + } + + try { + // 0. 自动注入 GitLab token(从用户绑定的 GitLab 账户获取) + injectGitLabToken(request); + + // 0.5 自动注入 MCP 配置(从岗位 configJson.mcp 生成) + injectMcpConfig(request); + + // 0.6 自动注入 Skills 配置(从岗位 configJson.skills 生成) + injectSkillsConfig(request); + + // 1. 配置融合(快速验证) + MergedConfig config = configMergeService.merge(request); + + // 2. 计算 PodGroup 名称(提前返回给调用方) + String podGroupName = "svc-" + serviceId + "-pg"; + String queueName = config.getQueueName(); + + // 3. 创建构建记录 + if (request.getRoleId() != null) { + Map configSnapshot = createConfigSnapshot(request, config); + buildRecordService.createBuildRecord( + buildId, + request.getRoleId(), + request.getRoleName(), + configSnapshot, + request.getUserId(), + UserContext.getCurrentUserName() + ); + } + + // 4. 提交构建任务 + if (buildQueueConfig.isEnabled()) { + // 使用构建队列 + try { + BuildTask task = buildQueueService.submit(request, config); + int queuePosition = buildQueueService.getWaitingCount(); + log.info("Build task queued: serviceId={}, buildId={}, queuePosition={}", + serviceId, buildId, queuePosition); + + String message = queuePosition > 1 + ? String.format("Task queued at position %d, waiting for resources", queuePosition) + : "Task submitted, starting build..."; + + return ServiceBuildResult.building(serviceId, buildId, podGroupName, queueName, message); + } catch (IllegalStateException e) { + // 队列已满 + log.warn("Build queue full, rejecting request: serviceId={}", serviceId); + return ServiceBuildResult.failed(serviceId, "QUEUE_FULL", e.getMessage()); + } + } else { + // 直接异步执行(兼容旧行为) + buildAsync(request, config); + log.info("Build task submitted (direct async): serviceId={}, buildId={}", serviceId, buildId); + return ServiceBuildResult.building(serviceId, buildId, podGroupName, queueName, + "Task submitted, starting build..."); + } + + } catch (IllegalArgumentException e) { + log.error("Invalid request for service {}: {}", serviceId, e.getMessage()); + // 更新构建记录为失败 + if (request.getRoleId() != null) { + buildRecordService.markFailed(buildId, e.getMessage(), 0L); + } + return ServiceBuildResult.failed(serviceId, "INVALID_REQUEST", e.getMessage()); + } catch (Exception e) { + log.error("Failed to submit build task for service {}: {}", serviceId, e.getMessage(), e); + // 更新构建记录为失败 + if (request.getRoleId() != null) { + buildRecordService.markFailed(buildId, e.getMessage(), 0L); + } + return ServiceBuildResult.failed(serviceId, "INTERNAL_ERROR", e.getMessage()); + } + } + + /** + * 自动注入 GitLab token 到 buildEnvVars + * 从用户绑定的 GitLab 账户获取有效的 access token + */ + private void injectGitLabToken(ServiceBuildRequest request) { + String userId = request.getUserId(); + if (!StringUtils.hasText(userId)) { + log.debug("No userId provided, skipping GitLab token injection"); + return; + } + + // 获取用户的 GitLab token + String gitLabToken = gitLabAuthService.getAccessToken(userId); + if (!StringUtils.hasText(gitLabToken)) { + log.debug("No GitLab token found for userId: {}", userId); + return; + } + + // 注入到 buildEnvVars + Map envVars = request.getBuildEnvVars(); + if (envVars == null) { + envVars = new HashMap<>(); + request.setBuildEnvVars(envVars); + } + + // 覆盖前端传入的 GIT_TOKEN(前端传的可能是错误的登录 JWT) + envVars.put("GIT_TOKEN", gitLabToken); + log.info("Injected GitLab token for userId: {}", userId); + } + + /** + * 自动注入 MCP 配置到 buildEnvVars + * 从岗位 configJson.mcp 中获取 MCP ID 列表,生成 SDK 兼容的 mcp.json JSON 字符串, + * 写入 MCP_CONFIG 环境变量供 build.sh 使用。 + * + *

如果岗位未配置 MCP(configJson.mcp 为空或不存在),正常跳过不报错。 + * 如果岗位配置了 MCP 但注入失败(ID 无效、Server 不存在等),抛出异常中断构建。

+ */ + private void injectMcpConfig(ServiceBuildRequest request) { + Long roleId = request.getRoleId(); + if (roleId == null) { + log.debug("No roleId provided, skipping MCP config injection"); + return; + } + + RoleEntity role = roleService.getById(roleId); + if (role == null) { + log.debug("Role not found for roleId: {}, skipping MCP config injection", roleId); + return; + } + + // 从岗位 configJson.mcp 取 MCP 标识列表(兼容数字 ID 和名称字符串两种格式) + List mcpRefs = Collections.emptyList(); + if (role.getConfigJson() != null && role.getConfigJson().getMcp() != null) { + mcpRefs = role.getConfigJson().getMcp().stream() + .filter(s -> s != null && !s.isBlank()) + .collect(Collectors.toList()); + } + + // 岗位未配置 MCP → 正常跳过,不报错 + if (mcpRefs.isEmpty()) { + log.debug("No MCP refs configured for role {}, skipping MCP config injection", roleId); + return; + } + + // --- 以下:岗位配置了 MCP,注入失败则中断构建 --- + + // 分离数字 ID 和名称字符串 + List mcpIds = new java.util.ArrayList<>(); + List mcpNames = new java.util.ArrayList<>(); + for (String ref : mcpRefs) { + try { + mcpIds.add(Long.parseLong(ref)); + } catch (NumberFormatException e) { + mcpNames.add(ref); + } + } + + // 按名称查询 MCP Server ID(兼容前端 mock 名称格式) + if (!mcpNames.isEmpty()) { + List byNames = mcpServerService.list( + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .in(McpServerEntity::getName, mcpNames) + ); + for (McpServerEntity entity : byNames) { + mcpIds.add(entity.getId()); + } + if (byNames.size() < mcpNames.size()) { + List foundNames = byNames.stream() + .map(McpServerEntity::getName) + .collect(Collectors.toList()); + List missingNames = mcpNames.stream() + .filter(n -> !foundNames.contains(n)) + .collect(Collectors.toList()); + throw new IllegalArgumentException( + String.format("岗位 [%d] 配置的 MCP Server 名称不存在: %s", roleId, missingNames)); + } + } + + if (mcpIds.isEmpty()) { + throw new IllegalArgumentException( + String.format("岗位 [%d] 配置的 MCP 引用均无法解析为有效 ID: %s", roleId, mcpRefs)); + } + + // 生成 SDK 兼容的 mcp.json 配置 + try { + Map mcpConfig = mcpServerService.generateMcpConfig(mcpIds); + String mcpJsonString = objectMapper.writeValueAsString(mcpConfig); + + // 注入到 buildEnvVars + Map envVars = request.getBuildEnvVars(); + if (envVars == null) { + envVars = new HashMap<>(); + request.setBuildEnvVars(envVars); + } + envVars.put("MCP_CONFIG", mcpJsonString); + log.info("Injected MCP config for roleId: {} ({} servers, resolved from {} refs)", + roleId, mcpIds.size(), mcpRefs.size()); + } catch (IllegalArgumentException e) { + // 直接抛出,不包装 + throw e; + } catch (Exception e) { + throw new IllegalArgumentException( + String.format("岗位 [%d] 的 MCP 配置生成失败: %s", roleId, e.getMessage()), e); + } + } + + /** + * 自动注入 Skills 配置到 buildEnvVars + * 从岗位 configJson.skills 中获取 Skill ID 列表,查询 DB 获取 branchName + latestCommit, + * 生成 SKILLS_CONFIG JSON 字符串注入环境变量供 build.sh 使用。 + * + *

如果岗位未配置 Skills(configJson.skills 为空或不存在),正常跳过不报错。 + * 如果岗位配置了 Skills 但注入失败(ID 无效、Skill 不存在等),抛出异常中断构建。

+ */ + private void injectSkillsConfig(ServiceBuildRequest request) { + Long roleId = request.getRoleId(); + if (roleId == null) { + log.debug("No roleId provided, skipping Skills config injection"); + return; + } + + RoleEntity role = roleService.getById(roleId); + if (role == null) { + log.debug("Role not found for roleId: {}, skipping Skills config injection", roleId); + return; + } + + // 从岗位 configJson.skills 取 Skill 引用列表 + List skillRefs = Collections.emptyList(); + if (role.getConfigJson() != null && role.getConfigJson().getSkills() != null) { + skillRefs = role.getConfigJson().getSkills().stream() + .filter(s -> s != null && !s.isBlank()) + .collect(Collectors.toList()); + } + + // 岗位未配置 Skills → 正常跳过 + if (skillRefs.isEmpty()) { + log.debug("No Skill refs configured for role {}, skipping Skills config injection", roleId); + return; + } + + // --- 以下:岗位配置了 Skills,注入失败则中断构建 --- + + // 分离数字 ID 和名称字符串 + List skillIds = new java.util.ArrayList<>(); + List skillNames = new java.util.ArrayList<>(); + for (String ref : skillRefs) { + try { + skillIds.add(Long.parseLong(ref)); + } catch (NumberFormatException e) { + skillNames.add(ref); + } + } + + // 按名称查询 Skill ID + if (!skillNames.isEmpty()) { + List byNames = skillService.list( + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .in(SkillEntity::getName, skillNames) + ); + for (SkillEntity entity : byNames) { + skillIds.add(entity.getId()); + } + if (byNames.size() < skillNames.size()) { + List foundNames = byNames.stream() + .map(SkillEntity::getName) + .collect(Collectors.toList()); + List missingNames = skillNames.stream() + .filter(n -> !foundNames.contains(n)) + .collect(Collectors.toList()); + throw new IllegalArgumentException( + String.format("岗位 [%d] 配置的 Skill 名称不存在: %s", roleId, missingNames)); + } + } + + if (skillIds.isEmpty()) { + throw new IllegalArgumentException( + String.format("岗位 [%d] 配置的 Skill 引用均无法解析为有效 ID: %s", roleId, skillRefs)); + } + + // 查询所有 SkillEntity + List skills = skillService.listByIds(skillIds); + if (skills.size() < skillIds.size()) { + List foundIds = skills.stream().map(SkillEntity::getId).collect(Collectors.toList()); + List missingIds = skillIds.stream() + .filter(id -> !foundIds.contains(id)) + .collect(Collectors.toList()); + throw new IllegalArgumentException( + String.format("岗位 [%d] 配置的 Skill ID 不存在: %s", roleId, missingIds)); + } + + // 校验所有 Skill 都有 branchName + for (SkillEntity skill : skills) { + if (!StringUtils.hasText(skill.getBranchName())) { + throw new IllegalArgumentException( + String.format("Skill [%s] 尚未关联 Git 分支,请先同步", skill.getName())); + } + } + + // 生成 SKILLS_CONFIG JSON + try { + List> skillsList = new java.util.ArrayList<>(); + for (SkillEntity skill : skills) { + Map item = new java.util.LinkedHashMap<>(); + item.put("name", skill.getName()); + item.put("branch", skill.getBranchName()); + item.put("commit", skill.getLatestCommit()); + skillsList.add(item); + } + + Map skillsConfig = new java.util.LinkedHashMap<>(); + skillsConfig.put("repoUrl", skillsRepoUrl); + skillsConfig.put("token", skillsDeployToken); + skillsConfig.put("skills", skillsList); + + String skillsJsonString = objectMapper.writeValueAsString(skillsConfig); + + // 注入到 buildEnvVars + Map envVars = request.getBuildEnvVars(); + if (envVars == null) { + envVars = new HashMap<>(); + request.setBuildEnvVars(envVars); + } + envVars.put("SKILLS_CONFIG", skillsJsonString); + log.info("Injected Skills config for roleId: {} ({} skills, resolved from {} refs)", + roleId, skills.size(), skillRefs.size()); + } catch (IllegalArgumentException e) { + throw e; + } catch (Exception e) { + throw new IllegalArgumentException( + String.format("岗位 [%d] 的 Skills 配置生成失败: %s", roleId, e.getMessage()), e); + } + } + + /** + * 创建配置快照 + */ + private Map createConfigSnapshot(ServiceBuildRequest request, MergedConfig config) { + Map snapshot = new HashMap<>(); + snapshot.put("serviceId", request.getServiceId()); + snapshot.put("deployMode", request.getDeployMode() != null ? request.getDeployMode().name() : null); + snapshot.put("runnerBaseImage", request.getRunnerBaseImage()); + snapshot.put("agentImage", config.getAgentImage()); + snapshot.put("podMode", config.getPodMode() != null ? config.getPodMode().name() : null); + snapshot.put("runtimeMode", config.getPodMode() != null ? config.getPodMode().name() : null); + snapshot.put("zzMode", config.getPodMode() != null && config.getPodMode().name().equals("SIDECAR") ? "ssh" : "local"); + snapshot.put("runnerImage", config.getRunnerImage()); + snapshot.put("podCount", config.getPodCount()); + snapshot.put("buildEnvVars", request.getBuildEnvVars()); + return snapshot; + } + + /** + * 异步构建服务(镜像构建 + K8s 资源创建) + */ + @Async + public void buildAsync(ServiceBuildRequest request, MergedConfig config) { + String serviceId = request.getServiceId(); + String buildId = request.getBuildId(); + Long roleId = request.getRoleId(); + String roleName = request.getRoleName(); + long startTime = System.currentTimeMillis(); + + try { + log.info("Async build started for service {}, buildId: {}", serviceId, buildId); + + // 更新构建记录状态为 BUILDING + if (roleId != null && StringUtils.hasText(buildId)) { + buildRecordService.markBuilding(buildId); + } + + // 1. 镜像构建(如果启用,仅构建 Agent 镜像) + if (imageBuildConfig.isEnabled() && request.getDeployMode() == DeployMode.K8S) { + log.info("Image build enabled, starting image build for service {}", serviceId); + + // 发布 BUILD_STARTED 事件 + if (StringUtils.hasText(buildId)) { + eventPublisher.publishBuildStarted(buildId, buildId, roleId, roleName, + config.getAgentImage()); + eventPublisher.publishBuildProgress(buildId, buildId, "dockerfile", + "Generating Dockerfile..."); + } + + ImageBuildResult buildResult = imageBuildService.buildImages(request); + + if (!buildResult.isSuccess()) { + log.error("Image build failed for service {}: {}", serviceId, buildResult.getErrorMessage()); + + // 发布 BUILD_FAILED 事件并更新记录 + long durationMs = System.currentTimeMillis() - startTime; + if (StringUtils.hasText(buildId)) { + eventPublisher.publishBuildFailed(buildId, buildId, "BUILD_ERROR", + buildResult.getErrorMessage(), durationMs); + if (roleId != null) { + buildRecordService.markFailed(buildId, buildResult.getErrorMessage(), durationMs); + } + } + return; + } + + // 只更新 Agent 镜像地址(Runner 镜像保持 runnerBaseImage 不变) + config.setAgentImage(buildResult.getAgentImageTag()); + config.setImageBuildDurationMs(buildResult.getBuildDurationMs()); + + // 发布镜像推送事件(如果已推送) + if (buildResult.isPushed() && StringUtils.hasText(buildId)) { + eventPublisher.publishBuildPushed(buildId, buildId, buildResult.getAgentImageTag()); + } + + log.info("Image build completed for service {}, agentImage: {}, runnerImage: {}, duration: {}ms", + serviceId, buildResult.getAgentImageTag(), config.getRunnerImage(), buildResult.getBuildDurationMs()); + } else { + log.info("Image build disabled or not K8s mode, using base image for service {}", serviceId); + } + + // 2. 创建 K8s 资源 + ServiceBuildResult result = orchestrator.buildService(config); + + long durationMs = System.currentTimeMillis() - startTime; + + if (result.isSuccess()) { + log.info("Async build completed successfully for service {}", serviceId); + + // 保存 Snapshot 到内存 + Redis,确保后端重启后能恢复 + try { + snapshotService.saveSnapshot(request, result); + } catch (Exception snapshotErr) { + log.warn("异步构建后保存 Snapshot 失败(不影响构建结果): serviceId={}, error={}", + serviceId, snapshotErr.getMessage()); + } + + // 发布 BUILD_COMPLETED 事件并更新记录 + if (StringUtils.hasText(buildId)) { + eventPublisher.publishBuildCompleted(buildId, buildId, config.getAgentImage(), durationMs); + if (roleId != null) { + buildRecordService.markSuccess(buildId, config.getAgentImage(), durationMs); + } + } + } else { + log.error("Async build failed for service {}: {}", serviceId, result.getErrorMessage()); + + // 发布 BUILD_FAILED 事件并更新记录 + if (StringUtils.hasText(buildId)) { + eventPublisher.publishBuildFailed(buildId, buildId, "K8S_ERROR", + result.getErrorMessage(), durationMs); + if (roleId != null) { + buildRecordService.markFailed(buildId, result.getErrorMessage(), durationMs); + } + } + } + + } catch (Exception e) { + log.error("Async build error for service {}: {}", serviceId, e.getMessage(), e); + + long durationMs = System.currentTimeMillis() - startTime; + + // 发布 BUILD_FAILED 事件并更新记录 + if (StringUtils.hasText(buildId)) { + eventPublisher.publishBuildFailed(buildId, buildId, "INTERNAL_ERROR", + e.getMessage(), durationMs); + if (roleId != null) { + buildRecordService.markFailed(buildId, e.getMessage(), durationMs); + } + } + } + } + + /** + * 预览生成的 Spec(不实际创建) + */ + public GeneratedSpec preview(ServiceBuildRequest request) { + MergedConfig config = configMergeService.merge(request); + return orchestrator.previewSpec(config); + } + + /** + * 生成 Compose 构建包(tar.gz,包含完整的本地构建和部署材料) + * + * 不在服务端构建镜像,而是将构建所需的全部材料打包给用户, + * 用户解压后执行 start.sh 即可一键完成镜像构建和容器启动。 + * + * tar.gz 内容: + * docker-compose.yaml - Compose 编排文件(build: . 方式) + * Dockerfile - 动态生成(基于 Compose 专用基础镜像) + * build.sh - Dockerfile 内部执行的镜像构建脚本 + * config.json - Agent 默认配置 + * cedar-policies/ - Cedar 策略文件 + * start.sh - 一键构建部署脚本 + * README.md - 详细使用说明 + */ + public ServiceBuildResult generateComposePackage(ServiceBuildRequest request) { + String serviceId = request.getServiceId(); + + try { + log.info("Generating Compose package for service {}", serviceId); + + // 1. 注入凭证和配置 + injectGitLabToken(request); + injectMcpConfig(request); + injectSkillsConfig(request); + + // 2. 配置融合 + MergedConfig config = configMergeService.merge(request); + + // 3. 生成各文件内容 + String composeYaml = composeGenerator.generateComposeYaml(config); + String dockerfile = imageBuildService.generateDockerfile( + imageBuildConfig.getComposeBaseImage(), + request.getBuildEnvVars()); + String startScript = generateStartScript(serviceId); + String readme = generateReadme(config); + + // 4. 打包 tar.gz + byte[] tarBytes = buildComposeTar(serviceId, composeYaml, dockerfile, startScript, readme); + + log.info("Compose package generated for service {}, size: {} bytes", serviceId, tarBytes.length); + return ServiceBuildResult.successCompose(serviceId, tarBytes); + + } catch (Exception e) { + log.error("Compose package generation failed for service {}: {}", serviceId, e.getMessage(), e); + return ServiceBuildResult.failed(serviceId, "INTERNAL_ERROR", e.getMessage()); + } + } + + // ==================== Compose 打包内部方法 ==================== + + private byte[] buildComposeTar(String serviceId, String composeYaml, String dockerfile, + String startScript, String readme) throws java.io.IOException { + String prefix = "ai-worker-" + serviceId + "/"; + var baos = new java.io.ByteArrayOutputStream(); + try (var gzos = new java.util.zip.GZIPOutputStream(baos); + var tos = new org.apache.commons.compress.archivers.tar.TarArchiveOutputStream(gzos)) { + tos.setLongFileMode(org.apache.commons.compress.archivers.tar.TarArchiveOutputStream.LONGFILE_POSIX); + + addTarEntry(tos, prefix + "docker-compose.yaml", composeYaml); + addTarEntry(tos, prefix + "Dockerfile", dockerfile); + addTarEntry(tos, prefix + "README.md", readme); + addTarEntryExecutable(tos, prefix + "start.sh", startScript); + + addClasspathResourceToTar(tos, "scripts/build.sh", prefix + "build.sh"); + addClasspathResourceToTar(tos, "scripts/config.json", prefix + "config.json"); + addClasspathResourceToTar(tos, "scripts/00-platform.cedar", prefix + "cedar-policies/00-platform.cedar"); + + tos.finish(); + } + return baos.toByteArray(); + } + + private void addTarEntry(org.apache.commons.compress.archivers.tar.TarArchiveOutputStream tos, + String name, String content) throws java.io.IOException { + byte[] data = content.getBytes(java.nio.charset.StandardCharsets.UTF_8); + var entry = new org.apache.commons.compress.archivers.tar.TarArchiveEntry(name); + entry.setSize(data.length); + entry.setMode(0644); + tos.putArchiveEntry(entry); + tos.write(data); + tos.closeArchiveEntry(); + } + + private void addTarEntryExecutable(org.apache.commons.compress.archivers.tar.TarArchiveOutputStream tos, + String name, String content) throws java.io.IOException { + byte[] data = content.getBytes(java.nio.charset.StandardCharsets.UTF_8); + var entry = new org.apache.commons.compress.archivers.tar.TarArchiveEntry(name); + entry.setSize(data.length); + entry.setMode(0755); + tos.putArchiveEntry(entry); + tos.write(data); + tos.closeArchiveEntry(); + } + + private void addClasspathResourceToTar(org.apache.commons.compress.archivers.tar.TarArchiveOutputStream tos, + String classpathPath, String entryName) throws java.io.IOException { + var resource = new org.springframework.core.io.ClassPathResource(classpathPath); + if (resource.exists()) { + try (var is = resource.getInputStream()) { + byte[] data = is.readAllBytes(); + var entry = new org.apache.commons.compress.archivers.tar.TarArchiveEntry(entryName); + entry.setSize(data.length); + entry.setMode(0644); + tos.putArchiveEntry(entry); + tos.write(data); + tos.closeArchiveEntry(); + } + } else { + log.warn("Classpath resource not found: {}, skipping", classpathPath); + } + } + + /** + * 生成一键构建部署脚本 + */ + private String generateStartScript(String serviceId) { + return String.format(""" +#!/bin/bash +# ============================================================================= +# AI Worker 一键构建部署脚本 +# Service ID: %1$s +# +# 用法: +# ./start.sh 首次部署(构建镜像 + 启动容器) +# ./start.sh rebuild 强制重新构建镜像 +# ./start.sh stop 停止服务 +# ./start.sh logs 查看实时日志 +# ./start.sh status 查看容器状态 +# ./start.sh clean 停止服务并删除镜像和数据卷 +# ============================================================================= +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +cd "$SCRIPT_DIR" + +SERVICE_ID="%1$s" +CONTAINER_NAME="ai-worker-${SERVICE_ID}" + +log_info() { echo -e "\\033[32m[INFO]\\033[0m $(date '+%%H:%%M:%%S') $*"; } +log_warn() { echo -e "\\033[33m[WARN]\\033[0m $(date '+%%H:%%M:%%S') $*"; } +log_error() { echo -e "\\033[31m[ERROR]\\033[0m $(date '+%%H:%%M:%%S') $*" >&2; } + +check_docker() { + if ! command -v docker &>/dev/null; then + log_error "Docker 未安装,请先安装 Docker" + exit 1 + fi + if ! docker compose version &>/dev/null; then + log_error "Docker Compose V2 不可用,请升级 Docker" + exit 1 + fi +} + +do_build_and_start() { + log_info "开始构建镜像并启动服务(Service ID: ${SERVICE_ID})..." + log_info "首次构建需要拉取基础镜像和安装依赖,预计耗时 5-10 分钟" + echo "" + docker compose up --build -d + echo "" + log_info "服务已启动" + log_info "容器名称: ${CONTAINER_NAME}" + log_info "查看日志: ./start.sh logs" + log_info "停止服务: ./start.sh stop" +} + +do_rebuild() { + log_info "强制重新构建镜像..." + docker compose build --no-cache + docker compose up -d + log_info "重新构建完成,服务已启动" +} + +do_stop() { + log_info "停止服务..." + docker compose down + log_info "服务已停止" +} + +do_logs() { + docker compose logs -f agent +} + +do_status() { + docker compose ps +} + +do_clean() { + log_warn "将停止服务、删除容器、镜像和数据卷" + read -rp "确认? [y/N] " confirm + if [[ "$confirm" =~ ^[Yy]$ ]]; then + docker compose down -v --rmi local + log_info "清理完成" + else + log_info "已取消" + fi +} + +check_docker + +case "${1:-}" in + rebuild) do_rebuild ;; + stop) do_stop ;; + logs) do_logs ;; + status) do_status ;; + clean) do_clean ;; + "") do_build_and_start ;; + *) + echo "用法: $0 {rebuild|stop|logs|status|clean}" + echo " (无参数) 首次构建并启动" + echo " rebuild 强制重新构建镜像" + echo " stop 停止服务" + echo " logs 查看实时日志" + echo " status 查看容器状态" + echo " clean 停止并清理所有资源" + exit 1 + ;; +esac +""", serviceId); + } + + /** + * 生成详细使用说明文档 + */ + private String generateReadme(MergedConfig config) { + return String.format(""" +# AI Worker 本地部署包 + +## 概述 + +本压缩包包含在本地服务器上以 Docker Compose 方式部署 AI Worker 的全部文件。 +镜像在本地构建,无需从镜像仓库拉取构建好的镜像。 + +- **Service ID**: %s +- **User ID**: %s +- **基础镜像**: %s +- **运行模式**: Alone(单容器,ZZD_MODE=local) + +## 文件清单 + +| 文件 | 说明 | +|------|------| +| `docker-compose.yaml` | Docker Compose 编排文件 | +| `Dockerfile` | 镜像构建定义(基于基础镜像,安装 SDK、zzd 等) | +| `build.sh` | Dockerfile 内部执行的构建脚本(安装依赖、配置权限等) | +| `config.json` | Agent 配置文件(claude_settings、workspace 等) | +| `cedar-policies/` | Cedar 安全策略文件 | +| `start.sh` | **一键构建部署脚本** | +| `README.md` | 本说明文档 | + +## 环境要求 + +- Docker Engine 20.10+(需支持 Compose V2) +- 可访问 `docker.momo.com`(拉取基础镜像) +- 可访问 `git.wemomo.com`(构建时克隆 SDK 仓库) +- 建议内存 ≥ %s,CPU ≥ %s 核 + +## 快速开始 + +### 一键部署 + +```bash +# 解压 +tar xzf ai-worker-%s.tar.gz +cd ai-worker-%s + +# 首次部署(构建镜像 + 启动容器) +./start.sh +``` + +首次构建需要拉取基础镜像和安装依赖,预计耗时 **5-10 分钟**。 +后续启动(镜像已构建过)只需几秒。 + +### 常用操作 + +```bash +# 查看实时日志 +./start.sh logs + +# 查看容器状态 +./start.sh status + +# 停止服务 +./start.sh stop + +# 强制重新构建(修改配置后) +./start.sh rebuild + +# 清理所有资源(容器 + 镜像 + 数据卷) +./start.sh clean +``` + +### 手动操作(等效命令) + +```bash +# 构建并启动 +docker compose up --build -d + +# 查看日志 +docker compose logs -f agent + +# 停止 +docker compose down +``` + +## 运行时环境变量 + +以下环境变量已在 `docker-compose.yaml` 中配置: + +| 变量 | 值 | 说明 | +|------|-----|------| +| `WORKSTATION_ID` | %s | 工位 ID | +| `SERVICE_ID` | %s | 服务 ID | +| `REDIS_URL` | %s | Redis 连接地址 | +| `API_BASE_URL` | %s | API 网关地址 | +| `WS_BASE_URL` | %s | WebSocket 网关地址 | +| `LLM_GATEWAY_URL` | %s | LLM 网关地址 | +| `CONFIG_FILE` | /opt/agent/config.json | Agent 配置文件路径 | +| `IDLE_TIMEOUT` | 86400 | 空闲超时(秒) | + +## 资源配置 + +| 资源 | 请求 | 上限 | +|------|------|------| +| CPU | %s | %s | +| 内存 | %s | %s | + +如需调整,修改 `docker-compose.yaml` 中 `deploy.resources` 部分。 + +## 数据持久化 + +工作目录挂载为 Docker named volume `workspace`,容器重启后数据保留。 +执行 `./start.sh clean` 或 `docker compose down -v` 会删除数据卷。 + +## 故障排查 + +```bash +# 查看容器状态 +docker compose ps + +# 查看最近日志(不 follow) +docker compose logs --tail 100 agent + +# 进入容器调试 +docker compose exec agent bash + +# 查看资源使用 +docker stats ai-worker-%s +``` +""", + config.getServiceId(), + config.getUserId(), + imageBuildConfig.getComposeBaseImage(), + config.getAgentResources().getMemoryLimit(), + config.getAgentResources().getCpuLimit(), + config.getServiceId(), + config.getServiceId(), + config.getWorkstationId() != null ? config.getWorkstationId() : config.getServiceId(), + config.getServiceId(), + config.getRedisUrl() != null ? config.getRedisUrl() : "", + config.getApiBaseUrl() != null ? config.getApiBaseUrl() : "", + config.getWsBaseUrl() != null ? config.getWsBaseUrl() : "", + config.getLlmGatewayUrl() != null ? config.getLlmGatewayUrl() : "", + config.getAgentResources().getCpuRequest(), + config.getAgentResources().getCpuLimit(), + config.getAgentResources().getMemoryRequest(), + config.getAgentResources().getMemoryLimit(), + config.getServiceId()); + } + + /** + * 获取融合后的配置(用于调试) + */ + public MergedConfig getMergedConfig(ServiceBuildRequest request) { + return configMergeService.merge(request); + } +} diff --git a/back/src/main/java/com/linkwork/service/ServiceSnapshotService.java b/back/src/main/java/com/linkwork/service/ServiceSnapshotService.java new file mode 100644 index 0000000..1e74919 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/ServiceSnapshotService.java @@ -0,0 +1,364 @@ +package com.linkwork.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.linkwork.config.EnvConfig; +import com.linkwork.model.dto.ServiceBuildRequest; +import com.linkwork.model.dto.ServiceBuildResult; +import com.linkwork.model.dto.ServiceSnapshot; +import com.linkwork.model.enums.DeployMode; +import com.linkwork.model.enums.PodMode; +import com.linkwork.model.enums.SnapshotStatus; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; + +import java.time.Duration; +import java.time.Instant; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * 服务快照管理服务 + * + * 用于快速重启功能: + * 1. Service 创建成功后保存快照(原始请求、运行节点) + * 2. Service 关闭时更新快照(记录 shutdownAt) + * 3. Resume 时查询快照,获取原始请求和上次运行节点 + * + * 注:当前使用内存存储,生产环境建议使用 Redis 或 DB + */ +@Service +@Slf4j +public class ServiceSnapshotService { + + private final ObjectMapper objectMapper; + private final EnvConfig envConfig; + private final StringRedisTemplate redisTemplate; + + private static final String REDIS_KEY_PREFIX = "service:snapshot:request:"; + private static final Duration REDIS_TTL = Duration.ofDays(30); + + private final Map snapshotStore = new ConcurrentHashMap<>(); + + public ServiceSnapshotService(ObjectMapper objectMapper, EnvConfig envConfig, + StringRedisTemplate redisTemplate) { + this.objectMapper = objectMapper; + this.envConfig = envConfig; + this.redisTemplate = redisTemplate; + } + + /** + * Service 创建成功后保存快照 + * + * @param request 原始构建请求 + * @param result 构建结果 + */ + public void saveSnapshot(ServiceBuildRequest request, ServiceBuildResult result) { + try { + String originalRequestJson = objectMapper.writeValueAsString(request); + + // 获取初始 Pod 数量作为最大值 + int podCount = request.getPodCount() != null && request.getPodCount() > 0 + ? request.getPodCount() : 1; + + // 初始化 runningPodNames + Set runningPodNames = new HashSet<>(); + if (result.getPodNames() != null) { + runningPodNames.addAll(result.getPodNames()); + } + + ServiceSnapshot snapshot = ServiceSnapshot.builder() + .serviceId(request.getServiceId()) + .userId(request.getUserId()) + .originalRequestJson(originalRequestJson) + // 保存构建后的 Agent 镜像地址 + .agentImage(result.getBuiltAgentImage()) + .podMode(resolvePodMode(request).name()) + .lastScheduledNode(result.getScheduledNode()) + .maxPodCount(podCount) // 初始配置的最大 Pod 数量 + .currentPodCount(podCount) // 当前 Pod 数量 + .runningPodNames(runningPodNames) // 运行中的 Pod 名称列表 + .nextPodIndex(podCount) // 下一个 Pod 序号 + .createdAt(Instant.now()) + .lastActiveAt(Instant.now()) + .status(SnapshotStatus.ACTIVE) + .resumeCount(0) + .build(); + + snapshotStore.put(request.getServiceId(), snapshot); + + persistRequestToRedis(request.getServiceId(), originalRequestJson); + + log.info("Saved snapshot for service {}, scheduledNode={}, maxPodCount={}, pods={}", + request.getServiceId(), result.getScheduledNode(), podCount, runningPodNames); + + } catch (JsonProcessingException e) { + log.error("Failed to serialize request for service {}: {}", + request.getServiceId(), e.getMessage()); + } + } + + private PodMode resolvePodMode(ServiceBuildRequest request) { + if (request.getPodMode() != null) { + return request.getPodMode(); + } + if (request.getDeployMode() == DeployMode.COMPOSE) { + return PodMode.ALONE; + } + if (envConfig.getPodModeRules() == null || envConfig.getPodModeRules().getDefaultMode() == null) { + throw new IllegalStateException("未配置默认运行模式,无法保存服务快照"); + } + return envConfig.getPodModeRules().getDefaultMode(); + } + + /** + * Service 关闭时更新快照 + * + * @param serviceId 服务 ID + */ + public void onServiceShutdown(String serviceId) { + ServiceSnapshot snapshot = snapshotStore.get(serviceId); + if (snapshot != null) { + snapshot.setShutdownAt(Instant.now()); + log.info("Updated snapshot shutdown time for service {}", serviceId); + } + } + + /** + * 查询可用快照 + * + * @param serviceId 服务 ID + * @return 快照(如果存在且未过期) + */ + public Optional findActiveSnapshot(String serviceId) { + ServiceSnapshot snapshot = snapshotStore.get(serviceId); + + if (snapshot == null) { + log.debug("No snapshot found for service {}", serviceId); + return Optional.empty(); + } + + if (snapshot.getStatus() != SnapshotStatus.ACTIVE) { + log.debug("Snapshot for service {} is not active: {}", serviceId, snapshot.getStatus()); + return Optional.empty(); + } + + if (snapshot.isExpired()) { + log.info("Snapshot for service {} is expired", serviceId); + snapshot.setStatus(SnapshotStatus.EXPIRED); + return Optional.empty(); + } + + return Optional.of(snapshot); + } + + /** + * 从快照恢复原始请求 + * + * @param snapshot 快照 + * @return 原始请求 + */ + public ServiceBuildRequest restoreRequest(ServiceSnapshot snapshot) { + try { + return objectMapper.readValue(snapshot.getOriginalRequestJson(), ServiceBuildRequest.class); + } catch (JsonProcessingException e) { + log.error("Failed to deserialize request for service {}: {}", + snapshot.getServiceId(), e.getMessage()); + return null; + } + } + + /** + * 恢复后更新快照 + * + * @param serviceId 服务 ID + * @param newNode 新的调度节点 + */ + public void onServiceResumed(String serviceId, String newNode) { + ServiceSnapshot snapshot = snapshotStore.get(serviceId); + if (snapshot != null) { + snapshot.setLastScheduledNode(newNode); + snapshot.setLastActiveAt(Instant.now()); + snapshot.setShutdownAt(null); + snapshot.setResumeCount(snapshot.getResumeCount() + 1); + log.info("Updated snapshot for resumed service {}, newNode={}, resumeCount={}", + serviceId, newNode, snapshot.getResumeCount()); + } + } + + /** + * 删除快照 + * + * @param serviceId 服务 ID + */ + public void deleteSnapshot(String serviceId) { + snapshotStore.remove(serviceId); + log.info("Deleted snapshot for service {}", serviceId); + } + + /** + * 获取快照信息 + * + * @param serviceId 服务 ID + * @return 快照,不存在返回 null + */ + public ServiceSnapshot getSnapshot(String serviceId) { + return snapshotStore.get(serviceId); + } + + /** + * 更新快照 + * + * @param snapshot 快照 + */ + public void updateSnapshot(ServiceSnapshot snapshot) { + if (snapshot != null && snapshot.getServiceId() != null) { + snapshotStore.put(snapshot.getServiceId(), snapshot); + log.debug("Updated snapshot for service {}", snapshot.getServiceId()); + } + } + + /** + * 获取所有需要同步的快照 + * + * 包含: + * 1. 活跃且有运行中 Pod 的快照 + * 2. currentPodCount=0 但 shutdownAt 未设置的快照(需要标记过期时间) + * + * @return 需要同步的快照列表 + */ + public List findAllActive() { + return snapshotStore.values().stream() + .filter(s -> s.getStatus() == SnapshotStatus.ACTIVE) + .filter(s -> !s.isExpired()) + .filter(s -> { + int podCount = s.getCurrentPodCount() != null ? s.getCurrentPodCount() : 0; + // 有运行中的 Pod,需要同步 + if (podCount > 0) { + return true; + } + // Pod 数量为 0 但还没设置 shutdownAt,需要同步以标记过期时间 + return s.getShutdownAt() == null; + }) + .collect(Collectors.toList()); + } + + /** + * 获取并递增下一个 Pod 序号 + * + * @param serviceId 服务 ID + * @return 下一个 Pod 序号 + */ + public int getAndIncrementNextPodIndex(String serviceId) { + ServiceSnapshot snapshot = snapshotStore.get(serviceId); + if (snapshot == null) { + return 0; + } + int nextIndex = snapshot.getNextPodIndex() != null ? snapshot.getNextPodIndex() : 0; + snapshot.setNextPodIndex(nextIndex + 1); + return nextIndex; + } + + /** + * 从 K8s 运行状态反向重建 Snapshot(后端重启后恢复用) + * + *

尝试从 Redis 恢复 originalRequestJson;若 Redis 中没有则为 null。 + * + * @param serviceId 服务 ID + * @param runningPodNames 当前运行中的 Pod 名称列表 + * @param userId 用户 ID(从 Pod label 提取) + * @param podMode Pod 模式(从 Pod label 提取,如 "sidecar"/"alone") + * @param scheduledNode 调度节点(从 Pod spec 提取) + * @return 重建的 Snapshot + */ + public ServiceSnapshot rebuildFromK8s(String serviceId, Set runningPodNames, + String userId, String podMode, String scheduledNode) { + int podCount = runningPodNames.size(); + + int maxIndex = runningPodNames.stream() + .map(name -> { + String[] parts = name.split("-"); + try { + return Integer.parseInt(parts[parts.length - 1]); + } catch (NumberFormatException e) { + return -1; + } + }) + .filter(i -> i >= 0) + .max(Integer::compareTo) + .orElse(0); + + String originalRequestJson = loadRequestFromRedis(serviceId); + + String agentImage = null; + if (originalRequestJson != null) { + try { + ServiceBuildRequest req = objectMapper.readValue(originalRequestJson, ServiceBuildRequest.class); + agentImage = req.getBuildEnvVars() != null ? + (String) req.getBuildEnvVars().get("AGENT_IMAGE") : null; + } catch (Exception e) { + log.warn("Failed to parse restored request for service {}: {}", serviceId, e.getMessage()); + } + } + + ServiceSnapshot snapshot = ServiceSnapshot.builder() + .serviceId(serviceId) + .userId(userId) + .originalRequestJson(originalRequestJson) + .agentImage(agentImage) + .podMode(podMode) + .lastScheduledNode(scheduledNode) + .maxPodCount(podCount) + .currentPodCount(podCount) + .runningPodNames(runningPodNames) + .nextPodIndex(maxIndex + 1) + .createdAt(Instant.now()) + .lastActiveAt(Instant.now()) + .status(SnapshotStatus.ACTIVE) + .resumeCount(0) + .build(); + + snapshotStore.put(serviceId, snapshot); + log.info("Rebuilt snapshot from K8s for service {}: pods={}, node={}, podMode={}, hasOriginalRequest={}", + serviceId, runningPodNames, scheduledNode, podMode, originalRequestJson != null); + + return snapshot; + } + + /** + * 判断是否已有某个 serviceId 的 Snapshot + */ + public boolean hasSnapshot(String serviceId) { + return snapshotStore.containsKey(serviceId); + } + + /** + * 从 Redis 加载 originalRequestJson(供外部在 snapshot 缺失时使用) + */ + public String getOriginalRequestJsonFromRedis(String serviceId) { + return loadRequestFromRedis(serviceId); + } + + private void persistRequestToRedis(String serviceId, String requestJson) { + try { + redisTemplate.opsForValue().set(REDIS_KEY_PREFIX + serviceId, requestJson, REDIS_TTL); + } catch (Exception e) { + log.warn("Failed to persist request to Redis for service {}: {}", serviceId, e.getMessage()); + } + } + + private String loadRequestFromRedis(String serviceId) { + try { + String json = redisTemplate.opsForValue().get(REDIS_KEY_PREFIX + serviceId); + if (json != null) { + log.info("Recovered originalRequestJson from Redis for service {}", serviceId); + } + return json; + } catch (Exception e) { + log.warn("Failed to load request from Redis for service {}: {}", serviceId, e.getMessage()); + return null; + } + } +} diff --git a/back/src/main/java/com/linkwork/service/SkillGitLabService.java b/back/src/main/java/com/linkwork/service/SkillGitLabService.java new file mode 100644 index 0000000..963786b --- /dev/null +++ b/back/src/main/java/com/linkwork/service/SkillGitLabService.java @@ -0,0 +1,248 @@ +package com.linkwork.service; + +import com.linkwork.agent.skill.core.SkillClient; +import com.linkwork.agent.skill.core.SkillException; +import com.linkwork.agent.skill.core.model.CommitInfo; +import com.linkwork.agent.skill.core.model.FileNode; +import com.linkwork.agent.skill.core.model.SkillInfo; +import com.linkwork.common.ResourceNotFoundException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Skill Git provider adapter. + * + * 使用 linkwork-skill-starter 的 SkillClient 覆盖替换原有直连 GitLab RestTemplate 实现, + * 对上层 SkillService 继续提供原有 Map/List 兼容接口。 + */ +@Slf4j +@Service +public class SkillGitLabService { + + private final SkillClient skillClient; + + public SkillGitLabService(SkillClient skillClient) { + this.skillClient = skillClient; + } + + /** + * 返回兼容 GitLab branch API 结构。 + */ + public List> listBranches() { + List skills = skillClient.listSkills(); + List> branches = new ArrayList<>(); + for (SkillInfo skill : skills) { + Map branch = new LinkedHashMap<>(); + branch.put("name", skill.name()); + String commitId = skill.lastCommitId(); + if (StringUtils.hasText(commitId)) { + branch.put("id", commitId); + branch.put("commit_id", commitId); + branch.put("commit", Map.of("id", commitId)); + } + branches.add(branch); + } + return branches; + } + + /** + * 返回兼容 GitLab branch detail 结构。 + */ + public Map getBranchInfo(String branchName) { + String commitId = null; + try { + commitId = skillClient.getHeadCommitId(branchName); + } catch (RuntimeException ex) { + // 兼容 resolveDefaultBranch: main/master 在目录模型下可能并非真实 skill。 + if (!"main".equals(branchName) && !"master".equals(branchName)) { + throw new ResourceNotFoundException("Branch not found: " + branchName); + } + } + + Map branch = new LinkedHashMap<>(); + branch.put("name", branchName); + if (StringUtils.hasText(commitId)) { + branch.put("id", commitId); + branch.put("commit_id", commitId); + branch.put("commit", Map.of("id", commitId)); + } + return branch; + } + + public String getFileContent(String branchName, String filePath) { + try { + return skillClient.getFile(branchName, filePath); + } catch (RuntimeException ex) { + throw mapNotFound(ex, "File not found: " + filePath + " on branch " + branchName); + } + } + + /** + * 返回兼容 GitLab repository tree 结构。 + */ + public List> getTree(String branchName) { + List nodes = skillClient.getTree(branchName); + List> tree = new ArrayList<>(); + for (FileNode node : nodes) { + Map item = new LinkedHashMap<>(); + item.put("id", node.sha()); + item.put("name", node.name()); + item.put("path", node.path()); + item.put("type", node.type() == FileNode.NodeType.DIRECTORY ? "tree" : "blob"); + item.put("mode", node.type() == FileNode.NodeType.DIRECTORY ? "040000" : "100644"); + if (node.size() != null) { + item.put("size", node.size()); + } + tree.add(item); + } + return tree; + } + + /** + * 兼容旧接口:lastCommitId 用于乐观锁校验。 + */ + public Map commitFile(String branchName, String filePath, String content, + String commitMessage, String lastCommitId) { + if (StringUtils.hasText(lastCommitId)) { + String current = skillClient.getHeadCommitId(branchName); + if (StringUtils.hasText(current) && !lastCommitId.equals(current)) { + throw new IllegalStateException("Commit conflict: expected " + lastCommitId + " but current is " + current); + } + } + + CommitInfo commit = skillClient.upsertFile(branchName, filePath, content, commitMessage); + return commitToMap(commit); + } + + public Map createFile(String branchName, String filePath, String content, String commitMessage) { + CommitInfo commit = skillClient.upsertFile(branchName, filePath, content, commitMessage); + return commitToMap(commit); + } + + /** + * 兼容旧分支模型:通过扩展能力创建 skill 工作区。 + */ + public Map createBranch(String branchName, String ref) { + if (!skillClient.supportsExtendedOps()) { + return Map.of("name", branchName); + } + CommitInfo commit = skillClient.createSkillBranch(branchName, ref); + Map result = new LinkedHashMap<>(); + result.put("name", branchName); + if (commit != null && StringUtils.hasText(commit.id())) { + result.put("id", commit.id()); + result.put("commit", Map.of("id", commit.id())); + } + return result; + } + + public void deleteBranch(String branchName) { + if (!skillClient.supportsExtendedOps()) { + return; + } + skillClient.deleteSkillBranch(branchName); + } + + /** + * 返回兼容 GitLab commit 列表结构。 + */ + public List> getCommitHistory(String branchName) { + List commits = skillClient.listCommits(branchName, 1, 50); + List> result = new ArrayList<>(); + for (CommitInfo commit : commits) { + Map item = new LinkedHashMap<>(); + item.put("id", commit.id()); + item.put("short_id", shortSha(commit.id())); + item.put("title", commit.title()); + item.put("message", commit.message()); + item.put("author_name", commit.authorName()); + item.put("author_email", ""); + item.put("created_at", toIso(commit.authoredAt())); + item.put("web_url", commit.webUrl()); + result.add(item); + } + return result; + } + + /** + * 新增:显式 skillName,避免仅靠 commitSha 反查 skill 的不确定性。 + */ + public String getFileAtCommit(String skillName, String commitSha, String filePath) { + try { + return skillClient.getFileAtCommit(skillName, filePath, commitSha); + } catch (RuntimeException ex) { + throw mapNotFound(ex, "File not found: " + filePath + " at commit " + commitSha); + } + } + + /** + * 兼容旧签名:根据 commitSha 在技能列表中顺序尝试。 + */ + public String getFileAtCommit(String commitSha, String filePath) { + List skills; + try { + skills = skillClient.listSkills(); + } catch (RuntimeException ex) { + skills = Collections.emptyList(); + } + + for (SkillInfo skill : skills) { + try { + return skillClient.getFileAtCommit(skill.name(), filePath, commitSha); + } catch (RuntimeException ignored) { + } + } + throw new ResourceNotFoundException("File not found: " + filePath + " at commit " + commitSha); + } + + private Map commitToMap(CommitInfo commit) { + if (commit == null) { + return new LinkedHashMap<>(); + } + Map mapped = new LinkedHashMap<>(); + mapped.put("id", commit.id()); + mapped.put("commit_id", commit.id()); + mapped.put("short_id", shortSha(commit.id())); + mapped.put("title", commit.title()); + mapped.put("message", commit.message()); + mapped.put("author_name", commit.authorName()); + mapped.put("author_email", ""); + mapped.put("created_at", toIso(commit.authoredAt())); + mapped.put("web_url", commit.webUrl()); + mapped.put("commit", Map.of( + "id", commit.id() == null ? "" : commit.id(), + "message", commit.message() == null ? "" : commit.message() + )); + return mapped; + } + + private String shortSha(String sha) { + if (!StringUtils.hasText(sha)) { + return null; + } + return sha.substring(0, Math.min(8, sha.length())); + } + + private String toIso(Instant instant) { + return instant == null ? null : instant.toString(); + } + + private RuntimeException mapNotFound(RuntimeException ex, String message) { + String lower = ex.getMessage() == null ? "" : ex.getMessage().toLowerCase(); + if (lower.contains("404") || lower.contains("not found")) { + return new ResourceNotFoundException(message); + } + if (ex instanceof SkillException) { + return new RuntimeException(ex.getMessage(), ex); + } + return ex; + } +} diff --git a/back/src/main/java/com/linkwork/service/SkillService.java b/back/src/main/java/com/linkwork/service/SkillService.java new file mode 100644 index 0000000..08c89cf --- /dev/null +++ b/back/src/main/java/com/linkwork/service/SkillService.java @@ -0,0 +1,704 @@ +package com.linkwork.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.linkwork.common.ForbiddenOperationException; +import com.linkwork.common.ResourceNotFoundException; +import com.linkwork.mapper.SkillMapper; +import com.linkwork.model.entity.SkillEntity; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.time.LocalDateTime; +import java.util.*; +import java.util.regex.*; +import java.util.stream.Collectors; + +/** + * 技能 Service - 支持 Git 同步 + */ +@Slf4j +@Service +public class SkillService extends ServiceImpl { + + private static final Set SUPPORTED_SKILL_STATUSES = Set.of("draft", "ready", "disabled"); + private final SkillGitLabService skillGitLabService; + private final AdminAccessService adminAccessService; + + public SkillService(SkillGitLabService skillGitLabService, AdminAccessService adminAccessService) { + this.skillGitLabService = skillGitLabService; + this.adminAccessService = adminAccessService; + } + + // ==================== Git Sync Methods ==================== + + /** + * 从 Git 同步所有技能分支 + * + * @return 同步的技能数量 + */ + public int syncAllFromGit() { + List> branches = skillGitLabService.listBranches(); + int syncedCount = 0; + + // Collect all branch names for later disabling orphaned records + Set gitBranchNames = new HashSet<>(); + + for (Map branch : branches) { + String branchName = (String) branch.get("name"); + if (branchName == null) { + continue; + } + // Skip the default branch (main/master) + if ("main".equals(branchName) || "master".equals(branchName)) { + continue; + } + gitBranchNames.add(branchName); + + try { + String fileContent = skillGitLabService.getFileContent(branchName, "SKILL.md"); + Map frontmatter = parseFrontmatter(fileContent); + + String skillName = frontmatter.getOrDefault("name", branchName); + String displayName = frontmatter.getOrDefault("displayName", skillName); + String description = frontmatter.getOrDefault("description", ""); + + // Extract latest commit from branch info + String latestCommit = extractCommitSha(branch); + + // Find existing entity by branchName + SkillEntity entity = findByBranchName(branchName); + + if (entity != null) { + // Update existing + entity.setName(skillName); + entity.setDisplayName(displayName); + entity.setDescription(description); + entity.setImplementation(truncateContent(fileContent)); + entity.setLatestCommit(latestCommit); + entity.setLastSyncedAt(LocalDateTime.now()); + entity.setStatus("ready"); + this.updateById(entity); + } else { + // Create new + entity = new SkillEntity(); + entity.setSkillNo("SKL-" + System.currentTimeMillis()); + entity.setName(skillName); + entity.setDisplayName(displayName); + entity.setDescription(description); + entity.setImplementation(truncateContent(fileContent)); + entity.setBranchName(branchName); + entity.setLatestCommit(latestCommit); + entity.setLastSyncedAt(LocalDateTime.now()); + entity.setStatus("ready"); + // Git 全量同步导入的历史技能默认公开,避免迁移后不可见 + entity.setIsPublic(true); + this.save(entity); + } + + syncedCount++; + log.debug("Synced skill from branch: {}", branchName); + } catch (Exception e) { + log.warn("Failed to sync branch {}: {}", branchName, e.getMessage()); + } + } + + // Disable DB records whose branchName is not in the Git branches list + disableOrphanedSkills(gitBranchNames); + + log.info("Synced {} skills from Git ({} branches total)", syncedCount, gitBranchNames.size()); + return syncedCount; + } + + /** + * 同步单个技能 + */ + public SkillEntity syncSingle(String skillName) { + Map branchInfo = skillGitLabService.getBranchInfo(skillName); + String latestCommit = extractCommitSha(branchInfo); + + String fileContent = skillGitLabService.getFileContent(skillName, "SKILL.md"); + Map frontmatter = parseFrontmatter(fileContent); + + String name = frontmatter.getOrDefault("name", skillName); + String displayName = frontmatter.getOrDefault("displayName", name); + String description = frontmatter.getOrDefault("description", ""); + + SkillEntity entity = findByName(skillName); + if (entity == null) { + entity = findByBranchName(skillName); + } + + if (entity != null) { + entity.setName(name); + entity.setDisplayName(displayName); + entity.setDescription(description); + entity.setImplementation(truncateContent(fileContent)); + entity.setLatestCommit(latestCommit); + entity.setLastSyncedAt(LocalDateTime.now()); + entity.setStatus("ready"); + this.updateById(entity); + } else { + entity = new SkillEntity(); + entity.setSkillNo("SKL-" + System.currentTimeMillis()); + entity.setName(name); + entity.setDisplayName(displayName); + entity.setDescription(description); + entity.setImplementation(truncateContent(fileContent)); + entity.setBranchName(skillName); + entity.setLatestCommit(latestCommit); + entity.setLastSyncedAt(LocalDateTime.now()); + entity.setStatus("ready"); + entity.setIsPublic(true); + this.save(entity); + } + + log.info("Synced single skill: {}", skillName); + return entity; + } + + // ==================== CRUD Methods ==================== + + /** + * 创建技能(通过 Git 分支) + */ + public SkillEntity createSkill(String name, String description, Boolean isPublic, String userId, String userName) { + if (!StringUtils.hasText(userId)) { + throw new ForbiddenOperationException("用户未登录或登录态失效"); + } + if (!StringUtils.hasText(name)) { + throw new IllegalArgumentException("Skill 名称不能为空"); + } + // Validate name: alphanumeric + hyphens + underscores, must start with letter + if (!name.matches("^[a-zA-Z][a-zA-Z0-9_\\-]*$")) { + throw new IllegalArgumentException( + "Skill 名称只能包含英文字母、数字、连字符和下划线,且以字母开头: " + name); + } + + String skillDescription = description == null ? "" : description; + boolean publicVisible = Boolean.TRUE.equals(isPublic); + + // Create Git branch from main + // 从默认分支创建(兼容 main 和 master) + String defaultBranch = resolveDefaultBranch(); + skillGitLabService.createBranch(name, defaultBranch); + + // Generate SKILL.md template + String content = "---\n" + + "name: " + name + "\n" + + "displayName: " + name + "\n" + + "description: " + skillDescription + "\n" + + "---\n\n" + + "# " + name + "\n\n" + + skillDescription + "\n"; + + // Create SKILL.md in the new branch + Map fileResponse = skillGitLabService.createFile( + name, "SKILL.md", content, "Initialize skill: " + name); + + // Extract commit SHA from response + String latestCommit = null; + if (fileResponse != null) { + if (fileResponse.containsKey("commit_id")) { + latestCommit = (String) fileResponse.get("commit_id"); + } else if (fileResponse.containsKey("id")) { + latestCommit = (String) fileResponse.get("id"); + } + } + + // Create DB record + SkillEntity entity = new SkillEntity(); + entity.setSkillNo("SKL-" + System.currentTimeMillis()); + entity.setName(name); + entity.setDisplayName(name); + entity.setDescription(skillDescription); + entity.setImplementation(truncateContent(content)); + entity.setBranchName(name); + entity.setLatestCommit(latestCommit); + entity.setLastSyncedAt(LocalDateTime.now()); + entity.setStatus("ready"); + entity.setIsPublic(publicVisible); + entity.setCreatorId(userId); + entity.setCreatorName(userName); + this.save(entity); + + log.info("Created skill: {} (branch: {}) by user {}", entity.getSkillNo(), name, userId); + return entity; + } + + /** + * 删除技能(同时删除 Git 分支) + */ + public void deleteSkill(String name, String userId) { + SkillEntity entity = requireSkillForWrite(name, userId); + + // Delete Git branch + try { + skillGitLabService.deleteBranch(name); + } catch (Exception e) { + log.warn("Failed to delete Git branch for skill {}: {}", name, e.getMessage()); + } + + // Remove DB record + this.removeById(entity.getId()); + log.info("Deleted skill: {} (branch: {})", entity.getSkillNo(), name); + } + + /** + * 更新技能(直接 DB 更新,用于同步期间) + */ + public SkillEntity updateSkill(Long id, Map request, String userId, String userName) { + SkillEntity entity = this.getById(id); + if (entity == null) { + throw new ResourceNotFoundException("Skill not found: " + id); + } + + if (request.containsKey("name")) { + entity.setName((String) request.get("name")); + } + if (request.containsKey("displayName")) { + entity.setDisplayName((String) request.get("displayName")); + } + if (request.containsKey("description")) { + entity.setDescription((String) request.get("description")); + } + if (request.containsKey("implementation")) { + entity.setImplementation((String) request.get("implementation")); + } + if (request.containsKey("status")) { + entity.setStatus((String) request.get("status")); + } + + entity.setUpdaterId(userId); + entity.setUpdaterName(userName); + + this.updateById(entity); + log.info("Updated skill: {} by user {}", entity.getSkillNo(), userId); + return entity; + } + + /** + * 更新技能元数据(描述、公开性) + */ + public SkillEntity updateSkillMeta(String name, Map request, String userId, String userName) { + SkillEntity entity = requireSkillForWrite(name, userId); + + if (request.containsKey("description")) { + entity.setDescription((String) request.get("description")); + } + if (request.containsKey("isPublic")) { + Object value = request.get("isPublic"); + if (!(value instanceof Boolean)) { + throw new IllegalArgumentException("isPublic 必须为布尔值"); + } + entity.setIsPublic((Boolean) value); + } + if (request.containsKey("status")) { + entity.setStatus(normalizeSkillStatus(request.get("status"))); + } + + entity.setUpdaterId(userId); + entity.setUpdaterName(userName); + this.updateById(entity); + log.info("Updated skill metadata: {} by user {}", name, userId); + return entity; + } + + // ==================== Detail & File Operations ==================== + + /** + * 获取技能详情(含文件列表) + */ + public Map getSkillDetail(String name, String userId) { + SkillEntity entity = requireSkillForRead(name, userId); + + List> files = skillGitLabService.getTree(name); + + Map result = toResponseMap(entity); + result.remove("branchName"); + result.remove("lastSyncedAt"); + result.put("files", files); + return result; + } + + /** + * 获取文件内容 + */ + public String getFile(String name, String path, String userId) { + requireSkillForRead(name, userId); + return skillGitLabService.getFileContent(name, path); + } + + /** + * 获取技能最新 commit(用于前端乐观锁) + */ + public String getLatestCommitId(String name, String userId) { + // 优先从 DB 取缓存值 + SkillEntity entity = requireSkillForRead(name, userId); + if (entity != null && entity.getLatestCommit() != null) { + return entity.getLatestCommit(); + } + + // fallback: 从 GitLab 获取 + try { + Map branchInfo = skillGitLabService.getBranchInfo(name); + return extractCommitSha(branchInfo); + } catch (Exception e) { + log.warn("Failed to get latest commit for skill {}: {}", name, e.getMessage()); + return null; + } + } + + /** + * 提交文件变更 + */ + public Map commitFile(String name, String path, String content, + String commitMessage, String lastCommitId, String userId) { + requireSkillForWrite(name, userId); + Map response = skillGitLabService.commitFile( + name, path, content, commitMessage, lastCommitId); + + // Update DB record after successful commit + SkillEntity entity = findByName(name); + if (entity != null) { + String newCommit = null; + if (response != null) { + if (response.containsKey("id")) { + newCommit = (String) response.get("id"); + } else if (response.containsKey("commit_id")) { + newCommit = (String) response.get("commit_id"); + } + } + if (newCommit != null) { + entity.setLatestCommit(newCommit); + } + entity.setLastSyncedAt(LocalDateTime.now()); + this.updateById(entity); + } + + return response; + } + + // ==================== History & Revert ==================== + + /** + * 获取提交历史 + */ + public List> getHistory(String name, String userId) { + requireSkillForRead(name, userId); + List> raw = skillGitLabService.getCommitHistory(name); + return raw.stream().map(commit -> { + Map mapped = new LinkedHashMap<>(); + mapped.put("sha", commit.get("id")); + mapped.put("shortSha", commit.get("short_id")); + mapped.put("message", commit.get("message")); + mapped.put("authorName", commit.get("author_name")); + mapped.put("authorEmail", commit.get("author_email")); + mapped.put("createdAt", commit.get("created_at")); + return mapped; + }).collect(Collectors.toList()); + } + + /** + * 回退到指定 commit + */ + public void revertToCommit(String name, String commitSha, String userId) { + requireSkillForWrite(name, userId); + // Get SKILL.md content at the target commit + String oldContent = skillGitLabService.getFileAtCommit(name, commitSha, "SKILL.md"); + + // Get current branch info for lastCommitId + Map branchInfo = skillGitLabService.getBranchInfo(name); + String currentCommit = extractCommitSha(branchInfo); + + // Commit the old content as a new commit (revert) + String revertMessage = "Revert to " + commitSha.substring(0, Math.min(8, commitSha.length())); + skillGitLabService.commitFile(name, "SKILL.md", oldContent, revertMessage, currentCommit); + + // Sync to update DB + syncSingle(name); + log.info("Reverted skill {} to commit {}", name, commitSha.substring(0, Math.min(8, commitSha.length()))); + } + + // ==================== List Methods ==================== + + /** + * 获取技能列表(分页) + */ + public Map listSkills(int page, int pageSize, String status, String keyword, String userId) { + Page pageObj = new Page<>(page, pageSize); + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + applyVisibilityFilter(wrapper, userId); + if (StringUtils.hasText(status)) { + wrapper.eq(SkillEntity::getStatus, status); + } + if (StringUtils.hasText(keyword)) { + wrapper.and(w -> w.like(SkillEntity::getName, keyword) + .or().like(SkillEntity::getDisplayName, keyword) + .or().like(SkillEntity::getDescription, keyword)); + } + wrapper.orderByDesc(SkillEntity::getCreatedAt); + + Page result = this.page(pageObj, wrapper); + + List> items = result.getRecords().stream() + .map(this::toResponseMap) + .collect(Collectors.toList()); + + Map response = new HashMap<>(); + response.put("items", items); + response.put("pagination", Map.of( + "page", result.getCurrent(), + "pageSize", result.getSize(), + "total", result.getTotal(), + "totalPages", result.getPages() + )); + return response; + } + + /** + * 获取所有可用的技能(用于下拉选择 - 向后兼容) + */ + public List> listAllAvailable(String userId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + applyVisibilityFilter(wrapper, userId); + wrapper.eq(SkillEntity::getStatus, "ready"); + wrapper.orderByDesc(SkillEntity::getCreatedAt); + + return this.list(wrapper).stream() + .map(this::toSimpleMap) + .collect(Collectors.toList()); + } + + // ==================== Helper Methods ==================== + + /** + * 解析仓库默认分支(兼容 main / master) + */ + private String resolveDefaultBranch() { + try { + List> branches = skillGitLabService.listBranches(); + // listBranches 过滤了 main/master,所以直接查一下 + } catch (Exception e) { + // ignore + } + // 先尝试 main,失败再用 master + try { + skillGitLabService.getBranchInfo("main"); + return "main"; + } catch (Exception e) { + return "master"; + } + } + + /** + * 可见性过滤:自己创建 or 公开 + */ + private void applyVisibilityFilter(LambdaQueryWrapper wrapper, String userId) { + if (StringUtils.hasText(userId)) { + if (adminAccessService.isAdmin(userId)) { + return; + } + wrapper.and(w -> w.eq(SkillEntity::getCreatorId, userId) + .or().eq(SkillEntity::getIsPublic, true)); + return; + } + // 未登录仅可见公开资源 + wrapper.eq(SkillEntity::getIsPublic, true); + } + + private SkillEntity requireSkillForRead(String name, String userId) { + SkillEntity entity = findByName(name); + if (entity == null) { + throw new ResourceNotFoundException("Skill not found: " + name); + } + if (!canRead(entity, userId)) { + throw new ForbiddenOperationException("无权限访问该 Skill"); + } + return entity; + } + + private SkillEntity requireSkillForWrite(String name, String userId) { + SkillEntity entity = findByName(name); + if (entity == null) { + throw new ResourceNotFoundException("Skill not found: " + name); + } + if (!canWrite(entity, userId)) { + throw new ForbiddenOperationException("仅 Skill 创建者或管理员可执行该操作"); + } + return entity; + } + + private boolean canRead(SkillEntity entity, String userId) { + return adminAccessService.isAdmin(userId) + || Boolean.TRUE.equals(entity.getIsPublic()) + || isOwner(entity, userId); + } + + private boolean isOwner(SkillEntity entity, String userId) { + return StringUtils.hasText(userId) && userId.equals(entity.getCreatorId()); + } + + private boolean canWrite(SkillEntity entity, String userId) { + return adminAccessService.isAdmin(userId) || isOwner(entity, userId); + } + + private String normalizeSkillStatus(Object rawStatus) { + if (rawStatus == null || !StringUtils.hasText(String.valueOf(rawStatus))) { + throw new IllegalArgumentException("status 不能为空"); + } + String normalized = String.valueOf(rawStatus).trim().toLowerCase(Locale.ROOT); + if (!SUPPORTED_SKILL_STATUSES.contains(normalized)) { + throw new IllegalArgumentException("非法 Skill 状态: " + rawStatus + ",仅支持 draft/ready/disabled"); + } + return normalized; + } + + /** + * 解析 YAML frontmatter(--- 标记之间的内容) + */ + private Map parseFrontmatter(String content) { + Map result = new HashMap<>(); + if (content == null) { + return result; + } + + Pattern pattern = Pattern.compile("^---\\s*\\n(.*?)\\n---", Pattern.DOTALL); + Matcher matcher = pattern.matcher(content); + if (matcher.find()) { + String yaml = matcher.group(1); + for (String line : yaml.split("\\n")) { + line = line.trim(); + if (line.isEmpty() || !line.contains(":")) { + continue; + } + int colonIdx = line.indexOf(':'); + String key = line.substring(0, colonIdx).trim(); + String value = line.substring(colonIdx + 1).trim(); + // Remove surrounding quotes if present + if (value.length() >= 2 + && ((value.startsWith("\"") && value.endsWith("\"")) + || (value.startsWith("'") && value.endsWith("'")))) { + value = value.substring(1, value.length() - 1); + } + result.put(key, value); + } + } + + return result; + } + + /** + * 按 name 查找技能 + */ + private SkillEntity findByName(String name) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SkillEntity::getName, name); + return this.getOne(wrapper, false); + } + + /** + * 按 branchName 查找技能 + */ + private SkillEntity findByBranchName(String branchName) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SkillEntity::getBranchName, branchName); + return this.getOne(wrapper, false); + } + + /** + * 从分支信息中提取 commit SHA + */ + private String extractCommitSha(Map branchInfo) { + if (branchInfo == null) { + return null; + } + // GitLab branch API returns commit info nested under "commit" + Object commitObj = branchInfo.get("commit"); + if (commitObj instanceof Map) { + @SuppressWarnings("unchecked") + Map commitMap = (Map) commitObj; + return (String) commitMap.get("id"); + } + // Fallback: direct "id" or "commit_id" field + if (branchInfo.containsKey("commit_id")) { + return (String) branchInfo.get("commit_id"); + } + return (String) branchInfo.get("id"); + } + + /** + * 截断内容作为预览 + */ + private String truncateContent(String content) { + if (content == null) { + return null; + } + int maxLength = 2000; + if (content.length() <= maxLength) { + return content; + } + return content.substring(0, maxLength) + "..."; + } + + /** + * 禁用不在 Git 分支列表中的技能 + */ + private void disableOrphanedSkills(Set gitBranchNames) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.isNotNull(SkillEntity::getBranchName); + wrapper.ne(SkillEntity::getStatus, "disabled"); + + List allWithBranch = this.list(wrapper); + for (SkillEntity entity : allWithBranch) { + if (entity.getBranchName() != null && !gitBranchNames.contains(entity.getBranchName())) { + entity.setStatus("disabled"); + this.updateById(entity); + log.info("Disabled orphaned skill: {} (branch: {})", entity.getName(), entity.getBranchName()); + } + } + } + + /** + * 转换为响应 Map(含完整字段) + */ + private Map toResponseMap(SkillEntity entity) { + Map map = new HashMap<>(); + map.put("id", entity.getId().toString()); + map.put("skillNo", entity.getSkillNo()); + map.put("name", entity.getName()); + map.put("displayName", entity.getDisplayName()); + map.put("description", entity.getDescription()); + map.put("implementation", entity.getImplementation()); + map.put("status", entity.getStatus()); + map.put("isPublic", entity.getIsPublic()); + map.put("branchName", entity.getBranchName()); + map.put("latestCommit", entity.getLatestCommit()); + map.put("lastSyncedAt", entity.getLastSyncedAt()); + map.put("creatorId", entity.getCreatorId()); + map.put("creatorName", entity.getCreatorName()); + map.put("createdAt", entity.getCreatedAt()); + map.put("updatedAt", entity.getUpdatedAt()); + return map; + } + + /** + * 转换为简单 Map(用于下拉选择) + */ + private Map toSimpleMap(SkillEntity entity) { + Map map = new HashMap<>(); + map.put("id", entity.getId().toString()); + map.put("name", entity.getName()); + map.put("displayName", entity.getDisplayName()); + map.put("description", entity.getDescription()); + map.put("status", entity.getStatus()); + map.put("isPublic", entity.getIsPublic()); + map.put("skillNo", entity.getSkillNo()); + map.put("createdAt", entity.getCreatedAt()); + map.put("updatedAt", entity.getUpdatedAt()); + return map; + } +} diff --git a/back/src/main/java/com/linkwork/service/SnapshotSyncTask.java b/back/src/main/java/com/linkwork/service/SnapshotSyncTask.java new file mode 100644 index 0000000..041ad49 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/SnapshotSyncTask.java @@ -0,0 +1,233 @@ +package com.linkwork.service; + +import com.linkwork.config.EnvConfig; +import com.linkwork.model.dto.ServiceSnapshot; +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.client.KubernetesClient; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * Snapshot 与 K8s 状态定期同步任务 + * + * 包含两个方向的同步: + * 1. 正向同步:已有 Snapshot → 从 K8s 校验/更新 Pod 状态 + * 2. 反向发现:扫描 K8s 中运行的服务 → 为缺失 Snapshot 的服务自动重建 + * (解决后端重启后 Snapshot 丢失导致 scale-down/stop 等操作失败的问题) + */ +@Component +@Slf4j +public class SnapshotSyncTask { + + private final ServiceSnapshotService snapshotService; + private final K8sOrchestrator k8sOrchestrator; + private final KubernetesClient kubernetesClient; + private final EnvConfig envConfig; + + public SnapshotSyncTask(ServiceSnapshotService snapshotService, + K8sOrchestrator k8sOrchestrator, + KubernetesClient kubernetesClient, + EnvConfig envConfig) { + this.snapshotService = snapshotService; + this.k8sOrchestrator = k8sOrchestrator; + this.kubernetesClient = kubernetesClient; + this.envConfig = envConfig; + } + + /** + * 启动时立即执行一次反向发现,恢复所有 K8s 中运行的服务 + */ + @PostConstruct + public void onStartup() { + try { + log.info("SnapshotSyncTask startup: discovering running services from K8s..."); + int rebuilt = discoverAndRebuildSnapshots(); + if (rebuilt > 0) { + log.info("Startup discovery complete: rebuilt {} snapshots from K8s", rebuilt); + } else { + log.info("Startup discovery complete: no orphan services found in K8s"); + } + } catch (Exception e) { + log.error("Startup snapshot discovery failed (non-fatal): {}", e.getMessage(), e); + } + } + + @Scheduled(fixedRate = 60000) + public void syncSnapshotWithK8s() { + // 1. 反向发现:为 K8s 中存在但内存中没有 Snapshot 的服务重建 + try { + discoverAndRebuildSnapshots(); + } catch (Exception e) { + log.error("Snapshot reverse discovery failed: {}", e.getMessage()); + } + + // 2. 正向同步:已有 Snapshot → 校验 K8s 实际状态 + List snapshots = snapshotService.findAllActive(); + + if (snapshots.isEmpty()) { + return; + } + + log.debug("Starting snapshot sync task, checking {} active services", snapshots.size()); + + for (ServiceSnapshot snapshot : snapshots) { + try { + syncSingleService(snapshot); + } catch (Exception e) { + log.error("Failed to sync snapshot for service {}: {}", + snapshot.getServiceId(), e.getMessage()); + } + } + } + + /** + * 反向发现:扫描 K8s namespace 中所有运行的服务, + * 为没有 Snapshot 的服务从 Pod label/spec 中重建 Snapshot + * + * @return 本次重建的 Snapshot 数量 + */ + private int discoverAndRebuildSnapshots() { + List allServiceIds = k8sOrchestrator.listAllServiceIds(); + + if (allServiceIds.isEmpty()) { + return 0; + } + + int rebuiltCount = 0; + String namespace = envConfig.getCluster().getNamespace(); + + for (String serviceId : allServiceIds) { + if (snapshotService.hasSnapshot(serviceId)) { + continue; + } + + // 内存中没有这个 Snapshot,需要从 K8s 重建 + try { + rebuiltCount += rebuildSnapshotForService(serviceId, namespace) ? 1 : 0; + } catch (Exception e) { + log.error("Failed to rebuild snapshot for orphan service {}: {}", + serviceId, e.getMessage()); + } + } + + return rebuiltCount; + } + + /** + * 从 K8s Pod 信息重建单个服务的 Snapshot + */ + private boolean rebuildSnapshotForService(String serviceId, String namespace) { + List pods = kubernetesClient.pods() + .inNamespace(namespace) + .withLabel("service-id", serviceId) + .list() + .getItems() + .stream() + .filter(this::isReadyPod) + .collect(Collectors.toList()); + + if (pods.isEmpty()) { + log.debug("No running pods for orphan service {}, skip rebuild", serviceId); + return false; + } + + // 从第一个 Pod 的 label 和 spec 中提取信息 + Pod firstPod = pods.get(0); + Map labels = firstPod.getMetadata().getLabels(); + + String userId = labels != null ? labels.getOrDefault("user-id", "unknown") : "unknown"; + String podMode = labels != null ? labels.getOrDefault("pod-mode", "sidecar") : "sidecar"; + String scheduledNode = firstPod.getSpec() != null ? firstPod.getSpec().getNodeName() : null; + + Set runningPodNames = pods.stream() + .map(pod -> pod.getMetadata().getName()) + .collect(Collectors.toCollection(HashSet::new)); + + snapshotService.rebuildFromK8s(serviceId, runningPodNames, userId, podMode, scheduledNode); + + log.warn("Rebuilt snapshot for orphan service {} from K8s: pods={}, userId={}, node={}", + serviceId, runningPodNames, userId, scheduledNode); + + return true; + } + + private void syncSingleService(ServiceSnapshot snapshot) { + String serviceId = snapshot.getServiceId(); + + List actualPodNames = k8sOrchestrator.getRunningPods(serviceId); + int actualCount = actualPodNames.size(); + int snapshotCount = snapshot.getCurrentPodCount() != null ? snapshot.getCurrentPodCount() : 0; + + if (actualCount != snapshotCount) { + log.warn("State inconsistency detected for service {}: snapshot={}, actual={}", + serviceId, snapshotCount, actualCount); + + snapshot.setCurrentPodCount(actualCount); + snapshot.setRunningPodNames(new HashSet<>(actualPodNames)); + + if (actualCount == 0 && snapshot.getShutdownAt() == null) { + snapshot.setShutdownAt(java.time.Instant.now()); + log.info("Service {} has no running pods, marked for expiration in 24 hours", serviceId); + } + + snapshotService.updateSnapshot(snapshot); + + log.info("Snapshot synced for service {}: currentPodCount={}, pods={}", + serviceId, actualCount, actualPodNames); + return; + } + + Set snapshotPodNames = snapshot.getRunningPodNames(); + if (snapshotPodNames == null) { + snapshotPodNames = new HashSet<>(); + } + + Set actualPodSet = new HashSet<>(actualPodNames); + if (!snapshotPodNames.equals(actualPodSet)) { + log.warn("Pod names mismatch for service {}: snapshot={}, actual={}", + serviceId, snapshotPodNames, actualPodSet); + + snapshot.setRunningPodNames(actualPodSet); + snapshotService.updateSnapshot(snapshot); + + log.info("Snapshot pod names synced for service {}: {}", serviceId, actualPodSet); + } + } + + public void manualSync(String serviceId) { + if (serviceId != null && !serviceId.isEmpty()) { + ServiceSnapshot snapshot = snapshotService.getSnapshot(serviceId); + if (snapshot != null) { + syncSingleService(snapshot); + } else { + // 手动触发时,如果 Snapshot 不存在,尝试从 K8s 重建 + String namespace = envConfig.getCluster().getNamespace(); + rebuildSnapshotForService(serviceId, namespace); + } + } else { + syncSnapshotWithK8s(); + } + } + + private boolean isReadyPod(Pod pod) { + if (pod == null || pod.getMetadata() == null || pod.getMetadata().getDeletionTimestamp() != null) { + return false; + } + if (pod.getStatus() == null || pod.getStatus().getPhase() == null) { + return false; + } + if (!"Running".equals(pod.getStatus().getPhase())) { + return false; + } + if (pod.getStatus().getConditions() == null) { + return false; + } + return pod.getStatus().getConditions().stream() + .anyMatch(condition -> "Ready".equals(condition.getType()) && "True".equals(condition.getStatus())); + } +} diff --git a/back/src/main/java/com/linkwork/service/SystemResourceMonitor.java b/back/src/main/java/com/linkwork/service/SystemResourceMonitor.java new file mode 100644 index 0000000..723f1d5 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/SystemResourceMonitor.java @@ -0,0 +1,142 @@ +package com.linkwork.service; + +import com.linkwork.model.dto.ResourceStatus; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.lang.management.ManagementFactory; + +/** + * 系统资源监控服务 + * 使用 JDK 内置 OperatingSystemMXBean 获取系统 CPU 和内存使用率 + */ +@Service +@Slf4j +public class SystemResourceMonitor { + + private final com.sun.management.OperatingSystemMXBean osBean; + + public SystemResourceMonitor() { + this.osBean = (com.sun.management.OperatingSystemMXBean) + ManagementFactory.getOperatingSystemMXBean(); + log.info("SystemResourceMonitor initialized"); + } + + /** + * 获取系统 CPU 使用率 (0.0 ~ 1.0) + * 返回 -1 表示获取失败 + */ + public double getSystemCpuLoad() { + double cpuLoad = osBean.getCpuLoad(); + // getCpuLoad() 在某些情况下可能返回负值表示不可用 + if (cpuLoad < 0) { + // 尝试使用 getSystemLoadAverage 作为后备 + double loadAvg = osBean.getSystemLoadAverage(); + if (loadAvg >= 0) { + // 将 load average 转换为使用率(假设处理器数量) + int processors = osBean.getAvailableProcessors(); + cpuLoad = Math.min(loadAvg / processors, 1.0); + } else { + return -1; + } + } + return cpuLoad; + } + + /** + * 获取系统内存使用率 (0.0 ~ 1.0) + * 优先使用 /proc/meminfo 的 MemAvailable(更准确),否则回退到 JDK API + */ + public double getMemoryUsage() { + long total = osBean.getTotalMemorySize(); + if (total <= 0) { + return -1; + } + + // 尝试从 /proc/meminfo 读取 MemAvailable(Linux 专用,更准确) + try { + java.nio.file.Path meminfo = java.nio.file.Paths.get("/proc/meminfo"); + if (java.nio.file.Files.exists(meminfo)) { + for (String line : java.nio.file.Files.readAllLines(meminfo)) { + if (line.startsWith("MemAvailable:")) { + // 格式: "MemAvailable: 43681980 kB" + String[] parts = line.split("\\s+"); + if (parts.length >= 2) { + long availableKb = Long.parseLong(parts[1]); + long available = availableKb * 1024; + return (double) (total - available) / total; + } + } + } + } + } catch (Exception e) { + log.debug("Failed to read /proc/meminfo: {}", e.getMessage()); + } + + // 回退到 JDK API(在非 Linux 或读取失败时) + long free = osBean.getFreeMemorySize(); + return (double) (total - free) / total; + } + + /** + * 获取可用内存(字节) + */ + public long getFreeMemory() { + return osBean.getFreeMemorySize(); + } + + /** + * 获取总内存(字节) + */ + public long getTotalMemory() { + return osBean.getTotalMemorySize(); + } + + /** + * 获取可用处理器数量 + */ + public int getAvailableProcessors() { + return osBean.getAvailableProcessors(); + } + + /** + * 检查是否有足够资源执行新的构建任务 + * + * @param cpuThreshold CPU 使用率阈值 (0.0 ~ 1.0) + * @param memoryThreshold 内存使用率阈值 (0.0 ~ 1.0) + * @return true 如果资源充足 + */ + public boolean hasAvailableResources(double cpuThreshold, double memoryThreshold) { + double cpu = getSystemCpuLoad(); + double memory = getMemoryUsage(); + + // 如果无法获取资源信息,默认允许执行(容器环境可能获取不准确) + if (cpu < 0 || memory < 0) { + log.warn("无法获取系统资源信息: CPU={}, Memory={},默认允许执行", cpu, memory); + return true; + } + + boolean available = cpu < cpuThreshold && memory < memoryThreshold; + + if (!available) { + log.debug("资源不足: CPU={}% (阈值{}%), 内存={}% (阈值{}%)", + String.format("%.1f", cpu * 100), String.format("%.1f", cpuThreshold * 100), + String.format("%.1f", memory * 100), String.format("%.1f", memoryThreshold * 100)); + } + + return available; + } + + /** + * 获取当前资源状态(用于 API 返回) + */ + public ResourceStatus getStatus() { + return ResourceStatus.builder() + .cpuUsage(getSystemCpuLoad()) + .memoryUsage(getMemoryUsage()) + .totalMemory(getTotalMemory()) + .freeMemory(getFreeMemory()) + .availableProcessors(getAvailableProcessors()) + .build(); + } +} diff --git a/back/src/main/java/com/linkwork/service/TaskBillingUsageService.java b/back/src/main/java/com/linkwork/service/TaskBillingUsageService.java new file mode 100644 index 0000000..b9af129 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/TaskBillingUsageService.java @@ -0,0 +1,149 @@ +package com.linkwork.service; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestTemplate; + +import java.math.BigDecimal; +import java.time.Duration; +import java.util.Map; +import java.util.Optional; + +/** + * 任务终态对账:从计费网关拉取任务维度 token 使用量。 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class TaskBillingUsageService { + + @Value("${robot.billing.gateway-url-template:http://ai-agent-gateway.momo.com/v1/billing/tasks/{taskId}}") + private String gatewayUrlTemplate; + + @Value("${robot.billing.timeout-ms:3000}") + private long timeoutMs; + + @Value("${robot.billing.sync-retries:3}") + private int syncRetries; + + @Value("${robot.billing.retry-interval-ms:200}") + private long retryIntervalMs; + + private final RestTemplateBuilder restTemplateBuilder; + private final ObjectMapper objectMapper; + + public Optional fetchTaskUsage(String taskId) { + if (!StringUtils.hasText(taskId)) { + return Optional.empty(); + } + String url = gatewayUrlTemplate.replace("{taskId}", taskId.trim()); + RestTemplate restTemplate = restTemplateBuilder + .setConnectTimeout(Duration.ofMillis(timeoutMs)) + .setReadTimeout(Duration.ofMillis(timeoutMs)) + .build(); + + int maxAttempts = Math.max(1, syncRetries); + Exception lastError = null; + + for (int attempt = 1; attempt <= maxAttempts; attempt++) { + try { + ResponseEntity response = restTemplate.getForEntity(url, String.class); + if (!response.getStatusCode().is2xxSuccessful()) { + throw new IllegalStateException("billing response status=" + response.getStatusCode().value()); + } + String body = response.getBody(); + if (!StringUtils.hasText(body)) { + throw new IllegalStateException("billing response empty"); + } + + Map payload = objectMapper.readValue(body, new TypeReference>() { + }); + UsageSnapshot snapshot = UsageSnapshot.from(payload); + if (snapshot.tokenUsed() == null) { + throw new IllegalStateException("billing tokenUsed missing"); + } + return Optional.of(snapshot); + } catch (Exception ex) { + lastError = ex; + if (attempt < maxAttempts && retryIntervalMs > 0) { + try { + Thread.sleep(retryIntervalMs); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + break; + } + } + } + } + + log.warn("任务计费同步失败: taskId={}, url={}, attempts={}, error={}", + taskId, url, maxAttempts, lastError == null ? "unknown" : lastError.getMessage()); + return Optional.empty(); + } + + public record UsageSnapshot( + Integer tokenUsed, + Integer inputTokens, + Integer outputTokens, + Integer requestCount, + Long tokenLimit, + BigDecimal usagePercent + ) { + static UsageSnapshot from(Map source) { + return new UsageSnapshot( + parseInteger(source.get("tokenUsed")), + parseInteger(source.get("inputTokens")), + parseInteger(source.get("outputTokens")), + parseInteger(source.get("requestCount")), + parseLong(source.get("tokenLimit")), + parseBigDecimal(source.get("usagePercent")) + ); + } + + private static Integer parseInteger(Object value) { + if (value == null) { + return null; + } + if (value instanceof Number number) { + return number.intValue(); + } + try { + return Integer.parseInt(String.valueOf(value).trim()); + } catch (Exception ignore) { + return null; + } + } + + private static Long parseLong(Object value) { + if (value == null) { + return null; + } + if (value instanceof Number number) { + return number.longValue(); + } + try { + return Long.parseLong(String.valueOf(value).trim()); + } catch (Exception ignore) { + return null; + } + } + + private static BigDecimal parseBigDecimal(Object value) { + if (value == null) { + return null; + } + try { + return new BigDecimal(String.valueOf(value).trim()); + } catch (Exception ignore) { + return null; + } + } + } +} diff --git a/back/src/main/java/com/linkwork/service/TaskDispatchConsumer.java b/back/src/main/java/com/linkwork/service/TaskDispatchConsumer.java new file mode 100644 index 0000000..6ca3e75 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/TaskDispatchConsumer.java @@ -0,0 +1,198 @@ +package com.linkwork.service; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.linkwork.config.DispatchConfig; +import com.linkwork.model.enums.TaskStatus; + +import java.util.Map; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; + +import java.time.Duration; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * 任务调度队列消费者 + * 从 Redis 队列中消费任务,并分发给 Agent 执行器处理 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class TaskDispatchConsumer { + + private final StringRedisTemplate redisTemplate; + private final ObjectMapper objectMapper; + private final TaskService taskService; + private final DispatchConfig dispatchConfig; + + private final AtomicBoolean running = new AtomicBoolean(true); + private ExecutorService executorService; + + /** + * 队列阻塞等待超时时间(秒) + */ + private static final int QUEUE_TIMEOUT_SECONDS = 30; + + /** + * 消费前延迟时间(毫秒),便于观测队列消息 + * TODO: 生产环境设为 0 + */ + private static final int PRE_CONSUME_DELAY_MS = 5000; + + /** + * 是否启用后端消费者 + * 设为 false 时,消息留在队列供外部 momo-worker 消费 + */ + private static final boolean CONSUMER_ENABLED = false; + + @PostConstruct + public void startConsumer() { + if (!CONSUMER_ENABLED) { + log.info("后端消费者已禁用,消息将由外部 momo-worker 消费,队列: {}", dispatchConfig.getTaskQueueKey()); + return; + } + + executorService = Executors.newSingleThreadExecutor(r -> { + Thread thread = new Thread(r, "task-dispatch-consumer"); + thread.setDaemon(true); + return thread; + }); + + executorService.submit(this::consumeLoop); + log.info("任务调度消费者已启动,监听队列: {}", dispatchConfig.getTaskQueueKey()); + } + + @PreDestroy + public void stopConsumer() { + running.set(false); + if (executorService != null) { + executorService.shutdownNow(); + } + log.info("任务调度消费者已停止"); + } + + /** + * 消费循环 + */ + private void consumeLoop() { + while (running.get()) { + try { + // 每次循环开始时延迟,便于观测队列消息(生产环境应设为 0) + if (PRE_CONSUME_DELAY_MS > 0) { + Thread.sleep(PRE_CONSUME_DELAY_MS); + } + + // 非阻塞弹出,便于延迟生效 + String messageJson = redisTemplate.opsForList().rightPop(dispatchConfig.getTaskQueueKey()); + + if (messageJson != null) { + log.info("从队列取出消息,开始处理..."); + processTask(messageJson); + } + } catch (Exception e) { + if (running.get()) { + log.error("消费任务时发生异常", e); + // 避免异常风暴,等待一小段时间后重试 + try { + Thread.sleep(1000); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + break; + } + } + } + } + } + + /** + * 处理单个任务 + * 消息格式: {"task_id": "xxx", "content": "xxx"} + */ + private void processTask(String messageJson) { + try { + // 解析简化的消息格式 + Map message = objectMapper.readValue( + messageJson, new TypeReference>() {}); + + String taskId = message.get("task_id"); + String content = message.get("content"); + + log.info("收到任务调度消息: task_id={}, content={}", + taskId, content != null && content.length() > 50 + ? content.substring(0, 50) + "..." : content); + + // 1. 更新任务状态为 RUNNING + updateTaskStatus(taskId, TaskStatus.RUNNING); + + // 2. 发布状态变更事件 + publishEvent(taskId, "TASK_STARTED"); + + // 3. TODO: 调用 Agent 执行器 (gRPC 或 HTTP) + // 这里是占位逻辑,实际应该调用 Agent 执行器 + executeTask(taskId, content); + + log.info("任务调度完成: task_id={}", taskId); + } catch (Exception e) { + log.error("处理任务消息失败: {}", messageJson, e); + } + } + + /** + * 更新任务状态 + */ + private void updateTaskStatus(String taskNo, TaskStatus status) { + try { + taskService.updateStatus(taskNo, status); + } catch (Exception e) { + log.error("更新任务状态失败: taskNo={}", taskNo, e); + } + } + + /** + * 发布任务事件到 Redis Stream + * 统一使用 stream:task:{taskNo} 格式,与 momo-worker 保持一致 + */ + private void publishEvent(String taskNo, String eventType) { + Long roleId = null; + try { + roleId = taskService.getTaskByNo(taskNo).getRoleId(); + } catch (Exception e) { + log.debug("发布事件时未找到任务 roleId,回退默认 workstation: taskNo={}", taskNo); + } + String streamKey = dispatchConfig.getLogStreamKey(roleId, taskNo); + redisTemplate.opsForStream().add(streamKey, + Map.of("event_type", eventType, "task_no", taskNo)); + log.debug("发布事件: streamKey={}, eventType={}", streamKey, eventType); + } + + /** + * 执行任务 (占位实现) + * TODO: 对接真正的 Agent 执行器 + * + * @param taskId 任务 ID + * @param content 任务内容 + */ + private void executeTask(String taskId, String content) { + log.info("准备执行任务: task_id={}, content={}", taskId, + content != null && content.length() > 100 + ? content.substring(0, 100) + "..." + : content); + + // TODO: 实际调用 Agent 执行器 + // agentExecutor.execute(taskId, content); + } + + /** + * 获取当前队列长度(用于监控) + */ + public Long getQueueLength() { + return redisTemplate.opsForList().size(dispatchConfig.getTaskQueueKey()); + } +} diff --git a/back/src/main/java/com/linkwork/service/TaskEventBroadcaster.java b/back/src/main/java/com/linkwork/service/TaskEventBroadcaster.java new file mode 100644 index 0000000..a6996cb --- /dev/null +++ b/back/src/main/java/com/linkwork/service/TaskEventBroadcaster.java @@ -0,0 +1,55 @@ +package com.linkwork.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.connection.stream.MapRecord; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 进程内任务事件广播器。 + * + * 事件消费和 WebSocket 推送解耦: + * - 后端常驻消费者只负责消费并广播 + * - WebSocket handler 只负责订阅并推送给在线客户端 + */ +@Slf4j +@Component +public class TaskEventBroadcaster { + + @FunctionalInterface + public interface TaskEventListener { + void onEvent(String taskNo, MapRecord record); + } + + private final Map listeners = new ConcurrentHashMap<>(); + + public String register(TaskEventListener listener) { + String listenerId = UUID.randomUUID().toString(); + listeners.put(listenerId, listener); + return listenerId; + } + + public void unregister(String listenerId) { + if (listenerId == null || listenerId.isBlank()) { + return; + } + listeners.remove(listenerId); + } + + public void broadcast(String taskNo, MapRecord record) { + if (taskNo == null || taskNo.isBlank() || record == null || listeners.isEmpty()) { + return; + } + listeners.forEach((listenerId, listener) -> { + try { + listener.onEvent(taskNo, record); + } catch (Exception e) { + log.warn("TaskEventBroadcaster listener failed: listenerId={}, taskNo={}, err={}", + listenerId, taskNo, e.getMessage(), e); + } + }); + } +} diff --git a/back/src/main/java/com/linkwork/service/TaskEventConsumerService.java b/back/src/main/java/com/linkwork/service/TaskEventConsumerService.java new file mode 100644 index 0000000..dd957f0 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/TaskEventConsumerService.java @@ -0,0 +1,291 @@ +package com.linkwork.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.linkwork.config.DispatchConfig; +import com.linkwork.model.entity.Task; +import com.linkwork.model.enums.TaskStatus; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.connection.stream.*; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.*; +import java.util.concurrent.*; + +/** + * 常驻任务事件消费者。 + * + * 职责: + * 1) 消费 Redis Stream 任务事件(不依赖 WS 在线) + * 2) 同步任务状态 + * 3) 同步 TASK_OUTPUT_PATHLIST_READY 到文件索引 + * 4) 广播事件给 WebSocket 层做实时展示 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class TaskEventConsumerService { + + private static final String CONSUMER_GROUP = "backend-core"; + private static final int SCAN_PAGE_SIZE = 200; + private static final long LISTENER_RETAIN_MS = 300_000L; + + private final StringRedisTemplate redisTemplate; + private final TaskService taskService; + private final DispatchConfig dispatchConfig; + private final TaskStatusSyncService taskStatusSyncService; + private final TaskPathlistSyncService taskPathlistSyncService; + private final TaskEventBroadcaster taskEventBroadcaster; + + private final ObjectMapper objectMapper = new ObjectMapper(); + private final ExecutorService workerPool = Executors.newCachedThreadPool(); + private final Map listeners = new ConcurrentHashMap<>(); + + @PostConstruct + public void init() { + discoverAndMaintainListeners(); + } + + @PreDestroy + public void shutdown() { + listeners.values().forEach(listener -> listener.future().cancel(true)); + workerPool.shutdownNow(); + } + + @Scheduled(fixedDelayString = "${robot.task-event-consumer.scan-interval-ms:5000}") + public void discoverAndMaintainListeners() { + try { + long now = System.currentTimeMillis(); + Set activeTasks = new HashSet<>(); + + for (TaskStatus status : List.of(TaskStatus.PENDING, TaskStatus.RUNNING, TaskStatus.PENDING_AUTH)) { + long current = 1; + while (true) { + Page page = taskService.listTasks(null, status.name(), (int) current, SCAN_PAGE_SIZE); + List records = page.getRecords(); + if (records == null || records.isEmpty()) { + break; + } + for (Task task : records) { + String taskNo = task.getTaskNo(); + if (taskNo == null || taskNo.isBlank()) { + continue; + } + activeTasks.add(taskNo); + startListenerIfAbsent(task); + ListenerState existed = listeners.get(taskNo); + if (existed != null) { + existed.touch(now); + } + } + if (current >= page.getPages()) { + break; + } + current++; + } + } + + // 避免“短任务在一次扫描周期内已终态”导致漏消费: + // 额外追踪最近窗口内到达终态的任务,保留监听一段时间接收尾部事件。 + for (TaskStatus terminalStatus : List.of(TaskStatus.COMPLETED, TaskStatus.FAILED, TaskStatus.ABORTED)) { + Page page = taskService.listTasks(null, terminalStatus.name(), 1, SCAN_PAGE_SIZE); + List records = page.getRecords(); + if (records == null || records.isEmpty()) { + continue; + } + for (Task task : records) { + if (!isRecentlyUpdated(task, now, LISTENER_RETAIN_MS)) { + continue; + } + startListenerIfAbsent(task); + ListenerState existed = listeners.get(task.getTaskNo()); + if (existed != null) { + existed.touch(now); + } + } + } + + listeners.forEach((taskNo, state) -> { + if (activeTasks.contains(taskNo)) { + return; + } + if (now - state.lastActiveAt() < LISTENER_RETAIN_MS) { + return; + } + try { + Task task = taskService.getTaskByNo(taskNo); + if (task.getStatus() == TaskStatus.PENDING + || task.getStatus() == TaskStatus.RUNNING + || task.getStatus() == TaskStatus.PENDING_AUTH) { + state.touch(now); + return; + } + } catch (Exception e) { + log.debug("listener cleanup skip taskNo={} because task lookup failed: {}", taskNo, e.getMessage()); + } + + state.future().cancel(true); + listeners.remove(taskNo); + log.info("task event listener removed: taskNo={}", taskNo); + }); + } catch (Exception e) { + log.error("discover task listeners failed: {}", e.getMessage(), e); + } + } + + private boolean isRecentlyUpdated(Task task, long nowMillis, long thresholdMillis) { + LocalDateTime updatedAt = task.getUpdatedAt(); + if (updatedAt == null) { + return false; + } + long updatedMillis = updatedAt.toInstant(ZoneOffset.UTC).toEpochMilli(); + return nowMillis - updatedMillis <= thresholdMillis; + } + + private void startListenerIfAbsent(Task task) { + listeners.computeIfAbsent(task.getTaskNo(), taskNo -> { + List streamKeys = buildStreamKeys(task); + String consumerName = "core-" + taskNo; + for (String streamKey : streamKeys) { + try { + redisTemplate.opsForStream().createGroup(streamKey, ReadOffset.from("0"), CONSUMER_GROUP); + } catch (Exception ignored) { + // stream/group may already exist + } + } + Future future = workerPool.submit(() -> consumeLoop(taskNo, streamKeys, consumerName)); + log.info("task event listener started: taskNo={}, streamKeys={}", taskNo, streamKeys); + return new ListenerState(future, System.currentTimeMillis()); + }); + } + + private List buildStreamKeys(Task task) { + List keys = new ArrayList<>(); + keys.add(dispatchConfig.getLogStreamKey(task.getRoleId(), task.getTaskNo())); + keys.add("stream:task:" + task.getTaskNo()); + keys.add("stream:task:" + task.getTaskNo() + ":events"); + keys.add("stream:build:" + task.getTaskNo()); + return keys; + } + + private void consumeLoop(String taskNo, List streamKeys, String consumerName) { + while (!Thread.currentThread().isInterrupted()) { + try { + for (String streamKey : streamKeys) { + List> records; + try { + records = redisTemplate.opsForStream().read( + Consumer.from(CONSUMER_GROUP, consumerName), + StreamReadOptions.empty().count(20).block(Duration.ofMillis(500)), + StreamOffset.create(streamKey, ReadOffset.lastConsumed()) + ); + } catch (Exception e) { + continue; + } + if (records == null || records.isEmpty()) { + continue; + } + for (MapRecord record : records) { + processRecord(taskNo, record); + try { + redisTemplate.opsForStream().acknowledge(streamKey, CONSUMER_GROUP, record.getId()); + } catch (Exception e) { + log.debug("ack stream record failed: streamKey={}, recordId={}, err={}", + streamKey, record.getId(), e.getMessage()); + } + } + } + } catch (Exception e) { + if (!Thread.currentThread().isInterrupted()) { + log.warn("task event consume loop error: taskNo={}, err={}", taskNo, e.getMessage(), e); + try { + Thread.sleep(1000); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + break; + } + } + } + } + } + + private void processRecord(String taskNo, MapRecord record) { + Map eventData = extractEventData(record); + taskStatusSyncService.onEvent(taskNo, eventData); + taskPathlistSyncService.onEvent(taskNo, eventData); + taskEventBroadcaster.broadcast(taskNo, record); + } + + @SuppressWarnings("unchecked") + private Map extractEventData(MapRecord record) { + Map rawValues = record.getValue(); + + Object payloadObj = rawValues.get("payload"); + if (payloadObj instanceof String payloadStr && payloadStr.startsWith("{")) { + try { + Map parsed = objectMapper.readValue(payloadStr, Map.class); + Object innerData = parsed.get("data"); + if (innerData instanceof String dataStr && (dataStr.startsWith("{") || dataStr.startsWith("["))) { + try { + parsed.put("data", objectMapper.readValue(dataStr, Object.class)); + } catch (Exception ignored) { + } + } + Object dataObj = parsed.get("data"); + if (dataObj instanceof Map dataMap) { + dataMap.forEach((k, v) -> parsed.putIfAbsent(String.valueOf(k), v)); + } + return parsed; + } catch (Exception e) { + log.debug("parse payload failed, fallback flat event: {}", e.getMessage()); + } + } + + Map event = new HashMap<>(); + rawValues.forEach((k, v) -> event.put(String.valueOf(k), v)); + + Object dataObj = event.get("data"); + if (dataObj instanceof String dataStr && (dataStr.startsWith("{") || dataStr.startsWith("["))) { + try { + event.put("data", objectMapper.readValue(dataStr, Object.class)); + } catch (Exception ignored) { + } + } + + Object dataMapObj = event.get("data"); + if (dataMapObj instanceof Map dataMap) { + dataMap.forEach((k, v) -> event.putIfAbsent(String.valueOf(k), v)); + } + return event; + } + + private static final class ListenerState { + private final Future future; + private volatile long lastActiveAt; + + private ListenerState(Future future, long lastActiveAt) { + this.future = future; + this.lastActiveAt = lastActiveAt; + } + + private Future future() { + return future; + } + + private long lastActiveAt() { + return lastActiveAt; + } + + private void touch(long ts) { + this.lastActiveAt = ts; + } + } +} diff --git a/back/src/main/java/com/linkwork/service/TaskGitAuthService.java b/back/src/main/java/com/linkwork/service/TaskGitAuthService.java new file mode 100644 index 0000000..ef325ac --- /dev/null +++ b/back/src/main/java/com/linkwork/service/TaskGitAuthService.java @@ -0,0 +1,81 @@ +package com.linkwork.service; + +import com.linkwork.mapper.TaskGitAuthMapper; +import com.linkwork.model.entity.GitLabAuthEntity; +import com.linkwork.model.entity.TaskGitAuthEntity; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.time.LocalDateTime; + +/** + * 任务与 Git 认证映射服务 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class TaskGitAuthService { + + public static final String PROVIDER_GITLAB = "gitlab"; + + private final TaskGitAuthMapper taskGitAuthMapper; + private final GitLabAuthService gitLabAuthService; + + /** + * 在任务创建时绑定最新的 GitLab 认证记录。 + */ + public void bindTaskWithLatestGitAuth(String taskId, String userId) { + if (!StringUtils.hasText(taskId) || !StringUtils.hasText(userId)) { + return; + } + + GitLabAuthEntity latestAuth = gitLabAuthService.getLatestAuth(userId); + if (latestAuth == null) { + log.info("任务未绑定 Git 认证(用户暂无授权): taskId={}, userId={}", taskId, userId); + return; + } + + TaskGitAuthEntity entity = new TaskGitAuthEntity(); + entity.setTaskId(taskId); + entity.setUserId(userId); + entity.setProvider(PROVIDER_GITLAB); + entity.setGitlabAuthId(latestAuth.getId()); + entity.setExpiresAt(latestAuth.getExpiresAt()); + entity.setUpdatedAt(LocalDateTime.now()); + entity.setIsDeleted(false); + + TaskGitAuthEntity existing = taskGitAuthMapper.selectById(taskId); + if (existing == null) { + entity.setCreatedAt(LocalDateTime.now()); + taskGitAuthMapper.insert(entity); + } else { + entity.setCreatedAt(existing.getCreatedAt()); + taskGitAuthMapper.updateById(entity); + } + + log.info("任务绑定 Git 认证成功: taskId={}, userId={}, provider={}, gitlabAuthId={}", + taskId, userId, PROVIDER_GITLAB, latestAuth.getId()); + } + + public TaskGitAuthEntity getByTaskId(String taskId) { + if (!StringUtils.hasText(taskId)) { + return null; + } + return taskGitAuthMapper.selectById(taskId); + } + + public void updateExpiresAt(String taskId, LocalDateTime expiresAt) { + if (!StringUtils.hasText(taskId) || expiresAt == null) { + return; + } + TaskGitAuthEntity entity = taskGitAuthMapper.selectById(taskId); + if (entity == null) { + return; + } + entity.setExpiresAt(expiresAt); + entity.setUpdatedAt(LocalDateTime.now()); + taskGitAuthMapper.updateById(entity); + } +} diff --git a/back/src/main/java/com/linkwork/service/TaskGitTokenService.java b/back/src/main/java/com/linkwork/service/TaskGitTokenService.java new file mode 100644 index 0000000..7ef16ae --- /dev/null +++ b/back/src/main/java/com/linkwork/service/TaskGitTokenService.java @@ -0,0 +1,106 @@ +package com.linkwork.service; + +import com.linkwork.common.ForbiddenOperationException; +import com.linkwork.common.ResourceNotFoundException; +import com.linkwork.model.dto.TaskGitTokenResponse; +import com.linkwork.model.entity.TaskGitAuthEntity; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.time.Duration; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; + +/** + * 任务运行期 Git token 获取服务(供 zzd 调用) + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class TaskGitTokenService { + + private static final Duration REFRESH_AHEAD_WINDOW = Duration.ofMinutes(5); + private static final String SCOPE_READ_REPOSITORY = "read_repository"; + private static final String SCOPE_WRITE_REPOSITORY = "write_repository"; + private static final String SCOPE_API = "api"; + + private final TaskService taskService; + private final TaskGitAuthService taskGitAuthService; + private final GitLabAuthService gitLabAuthService; + + public TaskGitTokenResponse getTaskGitToken(String taskId) { + ensureTaskExists(taskId); + + TaskGitAuthEntity binding = taskGitAuthService.getByTaskId(taskId); + if (binding == null || binding.getGitlabAuthId() == null) { + throw new ResourceNotFoundException("任务未绑定 Git 认证: taskId=" + taskId); + } + + if (!TaskGitAuthService.PROVIDER_GITLAB.equalsIgnoreCase(binding.getProvider())) { + throw new ForbiddenOperationException("仅支持 gitlab provider,当前 provider=" + binding.getProvider()); + } + + GitLabAuthService.ValidToken token = + gitLabAuthService.getValidTokenByAuthId(binding.getGitlabAuthId(), REFRESH_AHEAD_WINDOW); + if (token == null) { + throw new ResourceNotFoundException("任务未绑定有效 Git 认证: taskId=" + taskId); + } + + if (!StringUtils.hasText(token.getToken())) { + throw new IllegalStateException("任务 Git token 不可用: taskId=" + taskId); + } + + ensureScopeSufficient(token.getScope(), taskId); + GitLabAuthService.CommitIdentity commitIdentity = gitLabAuthService.resolveCommitIdentity(token.getToken()); + + taskGitAuthService.updateExpiresAt(taskId, token.getExpiresAt()); + log.info("任务 Git token 发放: taskId={}, provider={}, gitlabAuthId={}, tokenType={}, scope={}, tokenAlias={}, commitUser={}", + taskId, + binding.getProvider(), + binding.getGitlabAuthId(), + token.getTokenType(), + token.getScope(), + token.getTokenAlias(), + commitIdentity.getUsername()); + + TaskGitTokenResponse response = new TaskGitTokenResponse(); + response.setProvider(TaskGitAuthService.PROVIDER_GITLAB); + response.setTokenType(token.getTokenType()); + response.setToken(token.getToken()); + response.setExpiresAt(DateTimeFormatter.ISO_INSTANT.format(token.getExpiresAt().atOffset(ZoneOffset.UTC).toInstant())); + response.setCommitUserName(commitIdentity.getUsername()); + response.setCommitUserEmail(commitIdentity.getEmail()); + return response; + } + + /** + * Scope fail-fast:发放前校验 scope 是否满足仓库访问要求。 + * 至少包含 read_repository / write_repository / api 之一。 + * GitLab 中 write_repository 隐含读能力,api 隐含仓库读写权限。 + * 旧授权若 scope 不足,直接拒绝并引导重新授权,避免任务在 GIT_PRE 阶段才失败。 + */ + private void ensureScopeSufficient(String scope, String taskId) { + if (!StringUtils.hasText(scope)) { + log.warn("Git token scope 为空,跳过校验: taskId={}", taskId); + return; + } + boolean hasRepoAccess = scope.contains(SCOPE_READ_REPOSITORY) + || scope.contains(SCOPE_WRITE_REPOSITORY) + || scope.contains(SCOPE_API); + if (!hasRepoAccess) { + log.warn("Git token scope 不满足仓库访问要求: taskId={}, scope={}", taskId, scope); + throw new ForbiddenOperationException( + "Git 授权 scope 不足(缺少 read_repository、write_repository 或 api),请删除旧授权后重新授权: taskId=" + taskId); + } + } + + private void ensureTaskExists(String taskId) { + try { + taskService.getTaskByNo(taskId); + } catch (IllegalArgumentException e) { + throw new ResourceNotFoundException("任务不存在: " + taskId); + } + } +} diff --git a/back/src/main/java/com/linkwork/service/TaskOutputWorkspaceSyncService.java b/back/src/main/java/com/linkwork/service/TaskOutputWorkspaceSyncService.java new file mode 100644 index 0000000..9d40804 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/TaskOutputWorkspaceSyncService.java @@ -0,0 +1,835 @@ +package com.linkwork.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.linkwork.config.DispatchConfig; +import com.linkwork.mapper.FileNodeMapper; +import com.linkwork.mapper.RobotFileMapper; +import com.linkwork.model.entity.FileNodeEntity; +import com.linkwork.model.entity.RobotFile; +import com.linkwork.model.entity.Task; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; + +/** + * 将 TASK_OUTPUT_READY 的 OSS 产物同步写入文件管理索引(WORKSTATION 空间)。 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class TaskOutputWorkspaceSyncService { + + private static final int NODE_NAME_MAX_LENGTH = 512; + private static final String DELETED_NAME_MARKER = "__deleted__"; + private static final String SPACE_TYPE_WORKSTATION = "WORKSTATION"; + private static final String SPACE_TYPE_USER = "USER"; + private static final String ROOT_DIR_NAME = "任务产出"; + private static final String ROOT_DIR_FALLBACK_NAME = "任务产出_系统"; + + private final TaskService taskService; + private final DispatchConfig dispatchConfig; + private final NfsStorageService nfsStorageService; + private final RobotFileMapper robotFileMapper; + private final FileNodeMapper fileNodeMapper; + + public record WorkspaceSyncContext(String workstationId, String parentNodeId, String taskNodeId) {} + + @Transactional(rollbackFor = Exception.class) + public void syncTaskPathListArtifacts( + String taskNo, + String userId, + String workstationId, + List> artifacts + ) { + if (artifacts == null || artifacts.isEmpty()) { + return; + } + for (Map artifact : artifacts) { + if (artifact == null) { + continue; + } + String relativePath = normalizeWorkspaceRelativePath(String.valueOf(artifact.getOrDefault("relative_path", ""))); + if (!StringUtils.hasText(relativePath)) { + continue; + } + + if (relativePath.startsWith("logs/")) { + continue; + } + if (relativePath.equalsIgnoreCase("user/MEMORY.md") + || relativePath.equalsIgnoreCase("workstation/MEMORY.md")) { + continue; + } + + String action = String.valueOf(artifact.getOrDefault("action", "upsert")).trim().toLowerCase(Locale.ROOT); + String objectName = normalizeOssPath(String.valueOf(artifact.getOrDefault("object_name", ""))); + if (!StringUtils.hasText(objectName)) { + objectName = buildObjectNameFromRelative(relativePath, userId, workstationId); + } + + if ("deleted".equals(action)) { + markDeletedByObjectName(userId, workstationId, objectName); + continue; + } + + upsertPathListArtifact(userId, workstationId, relativePath, objectName, extractSize(artifact.get("size"))); + } + } + + @Transactional(rollbackFor = Exception.class) + public Optional syncTaskOutput(String taskNo, Map outputData) { + if (outputData == null) { + return Optional.empty(); + } + + String outputType = String.valueOf(outputData.getOrDefault("output_type", "")).trim().toLowerCase(Locale.ROOT); + if (!"oss".equals(outputType)) { + return Optional.empty(); + } + + String ossPath = normalizeOssPath(String.valueOf(outputData.getOrDefault("oss_path", ""))); + if (!StringUtils.hasText(ossPath)) { + return Optional.empty(); + } + String resolvedOssPath = normalizeOssPath(String.valueOf(outputData.getOrDefault("oss_path_resolved", ""))); + + TaskContext context = resolveTaskContext(taskNo, ossPath); + if (!context.isValid()) { + log.warn("TASK_OUTPUT_READY 索引跳过:任务上下文缺失 taskNo={}, ossPath={}", taskNo, ossPath); + return Optional.empty(); + } + + FileNodeEntity outputRoot = ensureRootDirectory(context.userId(), context.workstationId()); + FileNodeEntity taskDirectory = ensureTaskDirectory(context.userId(), context.workstationId(), + outputRoot.getNodeId(), context.taskNo()); + + List objectNames = List.of(); + for (String prefix : buildCandidatePrefixes(context.workstationId(), ossPath, resolvedOssPath)) { + objectNames = nfsStorageService.listObjects(prefix); + if (!objectNames.isEmpty()) { + break; + } + } + for (String objectName : objectNames) { + String fileName = extractFileName(objectName); + if (!StringUtils.hasText(fileName)) { + continue; + } + RobotFile robotFile = ensureRobotFile( + context.userId(), + context.workstationId(), + objectName, + fileName, + resolveFileSize(objectName)); + ensureFileNode(context.userId(), context.workstationId(), taskDirectory.getNodeId(), robotFile, fileName); + } + + return Optional.of(new WorkspaceSyncContext( + context.workstationId(), + outputRoot.getNodeId(), + taskDirectory.getNodeId())); + } + + private TaskContext resolveTaskContext(String taskNo, String ossPath) { + String safeTaskNo = StringUtils.hasText(taskNo) ? taskNo : extractTaskNoFromPath(ossPath); + String workstationId = null; + String userId = null; + + if (StringUtils.hasText(safeTaskNo)) { + try { + Task task = taskService.getTaskByNo(safeTaskNo); + workstationId = dispatchConfig.resolveWorkstationId(task.getRoleId()); + userId = task.getCreatorId(); + } catch (Exception e) { + log.debug("TASK_OUTPUT_READY 任务上下文查询失败,回退路径解析: taskNo={}, error={}", + safeTaskNo, e.getMessage()); + } + } + + if (!StringUtils.hasText(workstationId)) { + workstationId = extractWorkstationIdFromPath(ossPath); + } + if (!StringUtils.hasText(userId)) { + userId = extractUserIdFromPath(ossPath); + } + + return new TaskContext(safeTaskNo, workstationId, userId); + } + + private FileNodeEntity ensureRootDirectory(String userId, String workstationId) { + FileNodeEntity primary = findNodeByName(userId, workstationId, null, ROOT_DIR_NAME); + if (primary != null) { + if ("DIR".equals(primary.getEntryType())) { + return primary; + } + FileNodeEntity fallback = findNodeByName(userId, workstationId, null, ROOT_DIR_FALLBACK_NAME); + if (fallback != null && "DIR".equals(fallback.getEntryType())) { + return fallback; + } + if (fallback == null) { + return createDirectory(userId, workstationId, null, ROOT_DIR_FALLBACK_NAME); + } + String uniqueName = resolveUniqueName(userId, workstationId, null, ROOT_DIR_FALLBACK_NAME); + return createDirectory(userId, workstationId, null, uniqueName); + } + return createDirectory(userId, workstationId, null, ROOT_DIR_NAME); + } + + private FileNodeEntity ensureTaskDirectory(String userId, String workstationId, String parentId, String taskNo) { + String safeTaskNo = StringUtils.hasText(taskNo) ? taskNo : "未命名任务"; + FileNodeEntity existed = findNodeByName(userId, workstationId, parentId, safeTaskNo); + if (existed != null) { + if ("DIR".equals(existed.getEntryType())) { + return existed; + } + String fallbackName = safeTaskNo + "_产出"; + FileNodeEntity fallback = findNodeByName(userId, workstationId, parentId, fallbackName); + if (fallback != null && "DIR".equals(fallback.getEntryType())) { + return fallback; + } + if (fallback == null) { + return createDirectory(userId, workstationId, parentId, fallbackName); + } + String uniqueName = resolveUniqueName(userId, workstationId, parentId, fallbackName); + return createDirectory(userId, workstationId, parentId, uniqueName); + } + return createDirectory(userId, workstationId, parentId, safeTaskNo); + } + + private RobotFile ensureRobotFile( + String userId, + String workstationId, + String objectName, + String fileName, + long fileSize) { + RobotFile existed = robotFileMapper.selectOne(new LambdaQueryWrapper() + .eq(RobotFile::getUserId, userId) + .eq(RobotFile::getSpaceType, SPACE_TYPE_WORKSTATION) + .eq(RobotFile::getWorkstationId, workstationId) + .eq(RobotFile::getOssPath, objectName) + .isNull(RobotFile::getDeletedAt) + .last("limit 1")); + if (existed != null) { + boolean changed = false; + if (!fileName.equals(existed.getFileName())) { + existed.setFileName(fileName); + changed = true; + } + String fileType = extractFileType(fileName); + if (!fileType.equals(existed.getFileType())) { + existed.setFileType(fileType); + changed = true; + } + if (!Long.valueOf(fileSize).equals(existed.getFileSize())) { + existed.setFileSize(fileSize); + changed = true; + } + if (changed) { + existed.setUpdatedAt(LocalDateTime.now()); + robotFileMapper.updateById(existed); + } + return existed; + } + + RobotFile created = new RobotFile(); + created.setFileId(UUID.randomUUID().toString().replace("-", "")); + created.setFileName(fileName); + created.setFileSize(fileSize); + created.setFileType(extractFileType(fileName)); + created.setSpaceType(SPACE_TYPE_WORKSTATION); + created.setWorkstationId(workstationId); + created.setUserId(userId); + created.setOssPath(objectName); + created.setParseStatus("SKIP"); + created.setMemoryIndexStatus("SKIP"); + created.setCreatedAt(LocalDateTime.now()); + created.setUpdatedAt(LocalDateTime.now()); + robotFileMapper.insert(created); + return created; + } + + private void ensureFileNode(String userId, String workstationId, String parentId, RobotFile robotFile, String fileName) { + FileNodeEntity existed = fileNodeMapper.selectOne(new LambdaQueryWrapper() + .eq(FileNodeEntity::getUserId, userId) + .eq(FileNodeEntity::getSpaceType, SPACE_TYPE_WORKSTATION) + .eq(FileNodeEntity::getWorkstationId, workstationId) + .eq(FileNodeEntity::getParentId, parentId) + .eq(FileNodeEntity::getFileId, robotFile.getFileId()) + .isNull(FileNodeEntity::getDeletedAt) + .last("limit 1")); + if (existed != null) { + if (!fileName.equals(existed.getName())) { + existed.setName(fileName); + existed.setUpdatedAt(LocalDateTime.now()); + fileNodeMapper.updateById(existed); + } + return; + } + + String finalName = resolveUniqueName(userId, workstationId, parentId, fileName); + + FileNodeEntity node = new FileNodeEntity(); + node.setNodeId(UUID.randomUUID().toString().replace("-", "")); + node.setParentId(parentId); + node.setEntryType("FILE"); + node.setName(finalName); + node.setSpaceType(SPACE_TYPE_WORKSTATION); + node.setWorkstationId(workstationId); + node.setUserId(userId); + node.setFileId(robotFile.getFileId()); + node.setCreatedAt(LocalDateTime.now()); + node.setUpdatedAt(LocalDateTime.now()); + fileNodeMapper.insert(node); + } + + private String resolveUniqueName(String userId, String workstationId, String parentId, String expectedName) { + FileNodeEntity conflict = findNodeByName(userId, workstationId, parentId, expectedName); + if (conflict == null) { + return expectedName; + } + String base; + String ext; + int dotIdx = expectedName.lastIndexOf('.'); + if (dotIdx > 0 && dotIdx < expectedName.length() - 1) { + base = expectedName.substring(0, dotIdx); + ext = expectedName.substring(dotIdx); + } else { + base = expectedName; + ext = ""; + } + for (int i = 1; i <= 100; i++) { + String candidate = base + " (" + i + ")" + ext; + if (findNodeByName(userId, workstationId, parentId, candidate) == null) { + return candidate; + } + } + return base + "-" + UUID.randomUUID().toString().substring(0, 8) + ext; + } + + private FileNodeEntity createDirectory(String userId, String workstationId, String parentId, String name) { + FileNodeEntity node = new FileNodeEntity(); + node.setNodeId(UUID.randomUUID().toString().replace("-", "")); + node.setParentId(parentId); + node.setEntryType("DIR"); + node.setName(name); + node.setSpaceType(SPACE_TYPE_WORKSTATION); + node.setWorkstationId(workstationId); + node.setUserId(userId); + node.setCreatedAt(LocalDateTime.now()); + node.setUpdatedAt(LocalDateTime.now()); + fileNodeMapper.insert(node); + return node; + } + + private FileNodeEntity findNodeByName(String userId, String workstationId, String parentId, String name) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(FileNodeEntity::getUserId, userId) + .eq(FileNodeEntity::getSpaceType, SPACE_TYPE_WORKSTATION) + .eq(FileNodeEntity::getWorkstationId, workstationId) + .eq(FileNodeEntity::getName, name) + .isNull(FileNodeEntity::getDeletedAt) + .last("limit 1"); + + if (StringUtils.hasText(parentId)) { + wrapper.eq(FileNodeEntity::getParentId, parentId); + } else { + wrapper.isNull(FileNodeEntity::getParentId); + } + return fileNodeMapper.selectOne(wrapper); + } + + private String extractFileName(String objectName) { + if (!StringUtils.hasText(objectName)) { + return ""; + } + int idx = objectName.lastIndexOf('/'); + if (idx < 0 || idx == objectName.length() - 1) { + return objectName; + } + return objectName.substring(idx + 1); + } + + private String extractFileType(String fileName) { + if (!StringUtils.hasText(fileName)) { + return ""; + } + int idx = fileName.lastIndexOf('.'); + if (idx < 0 || idx == fileName.length() - 1) { + return ""; + } + return fileName.substring(idx + 1).toLowerCase(Locale.ROOT); + } + + private String extractWorkstationIdFromPath(String ossPath) { + String[] parts = safeSplitPath(ossPath); + if (parts.length >= 4 && "logs".equals(parts[0])) { + return parts[1]; + } + return null; + } + + private String extractUserIdFromPath(String ossPath) { + String[] parts = safeSplitPath(ossPath); + if (parts.length >= 4 && "logs".equals(parts[0])) { + return parts[2]; + } + return null; + } + + private String extractTaskNoFromPath(String ossPath) { + String[] parts = safeSplitPath(ossPath); + if (parts.length >= 4 && "logs".equals(parts[0])) { + return parts[3]; + } + return null; + } + + private String normalizeOssPath(String rawOssPath) { + String normalized = rawOssPath == null ? "" : rawOssPath.trim(); + while (normalized.startsWith("/")) { + normalized = normalized.substring(1); + } + return normalized; + } + + private List buildCandidatePrefixes(String workstationId, String ossPath, String resolvedOssPath) { + LinkedHashSet candidates = new LinkedHashSet<>(); + if (StringUtils.hasText(resolvedOssPath)) { + candidates.add(resolvedOssPath); + } + if (StringUtils.hasText(ossPath)) { + candidates.add(ossPath); + if (!ossPath.startsWith("system/") && StringUtils.hasText(workstationId)) { + candidates.add("system/" + workstationId + "/" + ossPath); + } + } + return List.copyOf(candidates); + } + + private long resolveFileSize(String objectName) { + if (!nfsStorageService.isConfigured()) { + return 0L; + } + try { + Path path = nfsStorageService.getAbsolutePath(objectName); + return Files.exists(path) ? Files.size(path) : 0L; + } catch (Exception e) { + log.debug("读取任务产出文件大小失败: objectName={}, error={}", objectName, e.getMessage()); + return 0L; + } + } + + private String[] safeSplitPath(String ossPath) { + if (!StringUtils.hasText(ossPath)) { + return new String[0]; + } + return ossPath.split("/"); + } + + private void upsertPathListArtifact( + String userId, + String workstationId, + String relativePath, + String objectName, + long fileSize + ) { + SpaceResolution resolution = resolveSpace(relativePath, workstationId); + if (!resolution.valid()) { + return; + } + + String fileName = extractFileName(resolution.subPath()); + if (!StringUtils.hasText(fileName)) { + return; + } + String parentDir = extractParentPath(resolution.subPath()); + String parentId = ensureDirectoryChain(userId, resolution.workstationId(), resolution.spaceType(), parentDir); + + RobotFile robotFile = ensureRobotFileGeneric( + userId, + resolution.workstationId(), + resolution.spaceType(), + objectName, + fileName, + fileSize + ); + ensureFileNodeGeneric( + userId, + resolution.workstationId(), + resolution.spaceType(), + parentId, + robotFile, + fileName + ); + } + + private void markDeletedByObjectName(String userId, String workstationId, String objectName) { + if (!StringUtils.hasText(objectName)) { + return; + } + RobotFile existed = robotFileMapper.selectOne(new LambdaQueryWrapper() + .eq(RobotFile::getUserId, userId) + .eq(RobotFile::getOssPath, objectName) + .isNull(RobotFile::getDeletedAt) + .last("limit 1")); + if (existed == null) { + return; + } + LocalDateTime now = LocalDateTime.now(); + existed.setDeletedAt(now); + existed.setUpdatedAt(now); + robotFileMapper.updateById(existed); + + List nodes = fileNodeMapper.selectList(new LambdaQueryWrapper() + .eq(FileNodeEntity::getFileId, existed.getFileId()) + .isNull(FileNodeEntity::getDeletedAt)); + for (FileNodeEntity node : nodes) { + node.setDeletedAt(now); + node.setUpdatedAt(now); + node.setName(buildDeletedTombstoneName(node.getName(), node.getNodeId())); + fileNodeMapper.updateById(node); + } + } + + private String buildDeletedTombstoneName(String originalName, String nodeId) { + String baseName = StringUtils.hasText(originalName) ? originalName : "node"; + String safeNodeId = StringUtils.hasText(nodeId) + ? nodeId + : UUID.randomUUID().toString().replace("-", ""); + String suffix = DELETED_NAME_MARKER + safeNodeId; + int allowedBaseLength = NODE_NAME_MAX_LENGTH - suffix.length(); + if (allowedBaseLength <= 0) { + return suffix.substring(Math.max(0, suffix.length() - NODE_NAME_MAX_LENGTH)); + } + if (baseName.length() > allowedBaseLength) { + baseName = baseName.substring(0, allowedBaseLength); + } + return baseName + suffix; + } + + private RobotFile ensureRobotFileGeneric( + String userId, + String workstationId, + String spaceType, + String objectName, + String fileName, + long fileSize + ) { + LambdaQueryWrapper query = new LambdaQueryWrapper() + .eq(RobotFile::getUserId, userId) + .eq(RobotFile::getSpaceType, spaceType) + .eq(RobotFile::getOssPath, objectName) + .isNull(RobotFile::getDeletedAt) + .last("limit 1"); + if (SPACE_TYPE_WORKSTATION.equals(spaceType)) { + query.eq(RobotFile::getWorkstationId, workstationId); + } else { + query.isNull(RobotFile::getWorkstationId); + } + + RobotFile existed = robotFileMapper.selectOne(query); + if (existed != null) { + boolean changed = false; + String fileType = extractFileType(fileName); + if (!fileName.equals(existed.getFileName())) { + existed.setFileName(fileName); + changed = true; + } + if (!fileType.equals(existed.getFileType())) { + existed.setFileType(fileType); + changed = true; + } + if (!Long.valueOf(fileSize).equals(existed.getFileSize())) { + existed.setFileSize(fileSize); + changed = true; + } + if (changed) { + existed.setUpdatedAt(LocalDateTime.now()); + robotFileMapper.updateById(existed); + } + return existed; + } + + RobotFile created = new RobotFile(); + created.setFileId(UUID.randomUUID().toString().replace("-", "")); + created.setFileName(fileName); + created.setFileSize(fileSize); + created.setFileType(extractFileType(fileName)); + created.setSpaceType(spaceType); + created.setWorkstationId(SPACE_TYPE_WORKSTATION.equals(spaceType) ? workstationId : null); + created.setUserId(userId); + created.setOssPath(objectName); + created.setParseStatus("SKIP"); + created.setMemoryIndexStatus("SKIP"); + created.setCreatedAt(LocalDateTime.now()); + created.setUpdatedAt(LocalDateTime.now()); + robotFileMapper.insert(created); + return created; + } + + private void ensureFileNodeGeneric( + String userId, + String workstationId, + String spaceType, + String parentId, + RobotFile robotFile, + String fileName + ) { + String desiredName = fileName; + LambdaQueryWrapper query = new LambdaQueryWrapper() + .eq(FileNodeEntity::getUserId, userId) + .eq(FileNodeEntity::getSpaceType, spaceType) + .eq(FileNodeEntity::getFileId, robotFile.getFileId()) + .isNull(FileNodeEntity::getDeletedAt) + .last("limit 1"); + if (SPACE_TYPE_WORKSTATION.equals(spaceType)) { + query.eq(FileNodeEntity::getWorkstationId, workstationId); + } else { + query.isNull(FileNodeEntity::getWorkstationId); + } + + FileNodeEntity existed = fileNodeMapper.selectOne(query); + if (existed != null) { + if (!Objects.equals(existed.getParentId(), parentId)) { + desiredName = resolveUniqueNameGenericExceptNode( + userId, workstationId, spaceType, parentId, fileName, existed.getNodeId()); + } else if (!fileName.equals(existed.getName())) { + desiredName = resolveUniqueNameGenericExceptNode( + userId, workstationId, spaceType, parentId, fileName, existed.getNodeId()); + } + + boolean changed = false; + if (!Objects.equals(existed.getParentId(), parentId)) { + existed.setParentId(parentId); + changed = true; + } + if (!desiredName.equals(existed.getName())) { + existed.setName(desiredName); + changed = true; + } + if (changed) { + existed.setUpdatedAt(LocalDateTime.now()); + fileNodeMapper.updateById(existed); + } + return; + } + + String finalName = resolveUniqueNameGeneric(userId, workstationId, spaceType, parentId, desiredName); + FileNodeEntity node = new FileNodeEntity(); + node.setNodeId(UUID.randomUUID().toString().replace("-", "")); + node.setParentId(parentId); + node.setEntryType("FILE"); + node.setName(finalName); + node.setSpaceType(spaceType); + node.setWorkstationId(SPACE_TYPE_WORKSTATION.equals(spaceType) ? workstationId : null); + node.setUserId(userId); + node.setFileId(robotFile.getFileId()); + node.setCreatedAt(LocalDateTime.now()); + node.setUpdatedAt(LocalDateTime.now()); + fileNodeMapper.insert(node); + } + + private String ensureDirectoryChain( + String userId, + String workstationId, + String spaceType, + String parentPath + ) { + if (!StringUtils.hasText(parentPath)) { + return null; + } + String parentId = null; + for (String segment : parentPath.split("/")) { + if (!StringUtils.hasText(segment)) { + continue; + } + FileNodeEntity existed = findNodeByNameGeneric(userId, workstationId, spaceType, parentId, segment); + if (existed != null && "DIR".equals(existed.getEntryType())) { + parentId = existed.getNodeId(); + continue; + } + String dirName = resolveUniqueNameGeneric(userId, workstationId, spaceType, parentId, segment); + FileNodeEntity dir = createDirectoryGeneric(userId, workstationId, spaceType, parentId, dirName); + parentId = dir.getNodeId(); + } + return parentId; + } + + private FileNodeEntity findNodeByNameGeneric( + String userId, + String workstationId, + String spaceType, + String parentId, + String name + ) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(FileNodeEntity::getUserId, userId) + .eq(FileNodeEntity::getSpaceType, spaceType) + .eq(FileNodeEntity::getName, name) + .isNull(FileNodeEntity::getDeletedAt) + .last("limit 1"); + if (SPACE_TYPE_WORKSTATION.equals(spaceType)) { + wrapper.eq(FileNodeEntity::getWorkstationId, workstationId); + } else { + wrapper.isNull(FileNodeEntity::getWorkstationId); + } + if (StringUtils.hasText(parentId)) { + wrapper.eq(FileNodeEntity::getParentId, parentId); + } else { + wrapper.isNull(FileNodeEntity::getParentId); + } + return fileNodeMapper.selectOne(wrapper); + } + + private String resolveUniqueNameGeneric( + String userId, + String workstationId, + String spaceType, + String parentId, + String expectedName + ) { + return resolveUniqueNameGenericExceptNode( + userId, workstationId, spaceType, parentId, expectedName, null); + } + + private String resolveUniqueNameGenericExceptNode( + String userId, + String workstationId, + String spaceType, + String parentId, + String expectedName, + String ignoredNodeId + ) { + FileNodeEntity conflict = findNodeByNameGeneric(userId, workstationId, spaceType, parentId, expectedName); + if (conflict == null || Objects.equals(conflict.getNodeId(), ignoredNodeId)) { + return expectedName; + } + String base; + String ext; + int dotIdx = expectedName.lastIndexOf('.'); + if (dotIdx > 0 && dotIdx < expectedName.length() - 1) { + base = expectedName.substring(0, dotIdx); + ext = expectedName.substring(dotIdx); + } else { + base = expectedName; + ext = ""; + } + for (int i = 1; i <= 100; i++) { + String candidate = base + " (" + i + ")" + ext; + FileNodeEntity candidateConflict = findNodeByNameGeneric( + userId, workstationId, spaceType, parentId, candidate); + if (candidateConflict == null || Objects.equals(candidateConflict.getNodeId(), ignoredNodeId)) { + return candidate; + } + } + return base + "-" + UUID.randomUUID().toString().substring(0, 8) + ext; + } + + private FileNodeEntity createDirectoryGeneric( + String userId, + String workstationId, + String spaceType, + String parentId, + String name + ) { + FileNodeEntity node = new FileNodeEntity(); + node.setNodeId(UUID.randomUUID().toString().replace("-", "")); + node.setParentId(parentId); + node.setEntryType("DIR"); + node.setName(name); + node.setSpaceType(spaceType); + node.setWorkstationId(SPACE_TYPE_WORKSTATION.equals(spaceType) ? workstationId : null); + node.setUserId(userId); + node.setCreatedAt(LocalDateTime.now()); + node.setUpdatedAt(LocalDateTime.now()); + fileNodeMapper.insert(node); + return node; + } + + private String normalizeWorkspaceRelativePath(String path) { + String normalized = path == null ? "" : path.trim().replace('\\', '/'); + if (normalized.startsWith("/workspace/")) { + normalized = normalized.substring("/workspace/".length()); + } + while (normalized.startsWith("/")) { + normalized = normalized.substring(1); + } + while (normalized.endsWith("/")) { + normalized = normalized.substring(0, normalized.length() - 1); + } + return normalized; + } + + private String extractParentPath(String path) { + if (!StringUtils.hasText(path)) { + return ""; + } + int idx = path.lastIndexOf('/'); + if (idx <= 0) { + return ""; + } + return path.substring(0, idx); + } + + private long extractSize(Object value) { + if (value == null) { + return 0L; + } + try { + return Long.parseLong(String.valueOf(value).trim()); + } catch (Exception ignore) { + return 0L; + } + } + + private String buildObjectNameFromRelative(String relativePath, String userId, String workstationId) { + if (relativePath.startsWith("user/")) { + String subPath = relativePath.substring("user/".length()); + if (StringUtils.hasText(subPath) && StringUtils.hasText(userId)) { + return "user-files/" + userId + "/" + subPath; + } + } + if (relativePath.startsWith("workstation/")) { + String subPath = relativePath.substring("workstation/".length()); + if (StringUtils.hasText(subPath) && StringUtils.hasText(userId) && StringUtils.hasText(workstationId)) { + return "workstation/" + workstationId + "/" + userId + "/" + subPath; + } + } + return ""; + } + + private SpaceResolution resolveSpace(String relativePath, String workstationId) { + if (relativePath.startsWith("user/")) { + return new SpaceResolution(SPACE_TYPE_USER, null, relativePath.substring("user/".length())); + } + if (relativePath.startsWith("workstation/")) { + return new SpaceResolution(SPACE_TYPE_WORKSTATION, workstationId, relativePath.substring("workstation/".length())); + } + return new SpaceResolution("", "", ""); + } + + private record SpaceResolution(String spaceType, String workstationId, String subPath) { + private boolean valid() { + return StringUtils.hasText(spaceType) && StringUtils.hasText(subPath); + } + } + + private record TaskContext(String taskNo, String workstationId, String userId) { + private boolean isValid() { + return StringUtils.hasText(taskNo) && StringUtils.hasText(workstationId) && StringUtils.hasText(userId); + } + } +} diff --git a/back/src/main/java/com/linkwork/service/TaskPathlistSyncService.java b/back/src/main/java/com/linkwork/service/TaskPathlistSyncService.java new file mode 100644 index 0000000..d6d4b55 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/TaskPathlistSyncService.java @@ -0,0 +1,263 @@ +package com.linkwork.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 处理 TASK_OUTPUT_PATHLIST_READY 事件: + * - 统一构建前端可展示 artifacts + * - 同步写入 USER/WORKSTATION 文件索引 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class TaskPathlistSyncService { + + private final TaskOutputWorkspaceSyncService taskOutputWorkspaceSyncService; + private final TaskService taskService; + private final NfsStorageService nfsStorageService; + private final Map taskWorkstationCache = new ConcurrentHashMap<>(); + + public void onEvent(String taskNo, Map eventData) { + try { + PathlistContext context = buildContext(taskNo, eventData); + if (!context.validForSync()) { + return; + } + long upsertCount = context.artifacts().stream() + .filter(item -> !"deleted".equalsIgnoreCase(asText(item.get("action")))) + .count(); + long deletedCount = context.artifacts().size() - upsertCount; + taskOutputWorkspaceSyncService.syncTaskPathListArtifacts( + context.taskNo(), + context.userId(), + context.workstationId(), + context.artifacts() + ); + log.info("TASK_OUTPUT_PATHLIST_READY synced: taskNo={}, userId={}, workstationId={}, path_count={}, upsert_count={}, deleted_count={}, skipped_count={}", + context.taskNo(), + context.userId(), + context.workstationId(), + context.artifacts().size(), + upsertCount, + deletedCount, + context.skippedCount()); + } catch (Exception e) { + log.error("sync TASK_OUTPUT_PATHLIST_READY failed: taskNo={}, err={}", taskNo, e.getMessage(), e); + } + } + + public void enrichEventForDisplay(String taskNo, Map eventData) { + try { + PathlistContext context = buildContext(taskNo, eventData); + if (!context.validForDisplay()) { + return; + } + @SuppressWarnings("unchecked") + Map data = (Map) eventData.get("data"); + data.put("artifacts", context.artifacts()); + data.put("count", context.artifacts().size()); + data.put("artifacts_pending", Boolean.FALSE); + } catch (Exception e) { + log.warn("enrich TASK_OUTPUT_PATHLIST_READY failed: taskNo={}, err={}", taskNo, e.getMessage(), e); + } + } + + private PathlistContext buildContext(String taskNo, Map eventData) { + String eventType = asText(eventData.get("event_type")); + if (!"TASK_OUTPUT_PATHLIST_READY".equals(eventType)) { + return PathlistContext.empty(); + } + + Object dataObj = eventData.get("data"); + if (!(dataObj instanceof Map dataMap)) { + return PathlistContext.empty(); + } + + @SuppressWarnings("unchecked") + Map data = (Map) dataMap; + Object rawPathList = data.get("path_list"); + if (!(rawPathList instanceof List pathList) || pathList.isEmpty()) { + return PathlistContext.empty(); + } + + String safeTaskNo = firstNonBlank(taskNo, asText(eventData.get("task_no")), asText(eventData.get("task_id"))); + String userId = firstNonBlank( + asText(data.get("user_id")), + asText(eventData.get("user_id")) + ); + String workstationId = resolveWorkstationId(safeTaskNo); + + List> artifacts = new ArrayList<>(); + int skippedCount = 0; + for (Object itemObj : pathList) { + if (!(itemObj instanceof Map itemMap)) { + skippedCount++; + continue; + } + String relativePath = asText(itemMap.get("relative_path")); + if (!StringUtils.hasText(relativePath)) { + relativePath = normalizeWorkspaceRelativePath(asText(itemMap.get("path"))); + } + if (!StringUtils.hasText(relativePath)) { + skippedCount++; + continue; + } + + String action = asText(itemMap.get("action")); + if (!StringUtils.hasText(action)) { + action = "upsert"; + } + + Map artifact = new LinkedHashMap<>(); + artifact.put("name", extractFileName(relativePath)); + artifact.put("relative_path", relativePath); + artifact.put("path", "/workspace/" + relativePath); + artifact.put("category", asText(itemMap.get("category"))); + artifact.put("action", action); + artifact.put("size", itemMap.get("size")); + + String objectName = resolveObjectName(relativePath, safeTaskNo, userId, workstationId); + if (StringUtils.hasText(objectName)) { + artifact.put("object_name", objectName); + if (nfsStorageService.isConfigured() && !"deleted".equalsIgnoreCase(action)) { + artifact.put("download_url", nfsStorageService.buildTaskOutputDownloadUrl(objectName)); + artifact.put("nfs_path", nfsStorageService.getAbsolutePath(objectName).toString()); + } + } + artifacts.add(artifact); + } + + return new PathlistContext(safeTaskNo, userId, workstationId, artifacts, skippedCount); + } + + private String resolveWorkstationId(String taskNo) { + if (!StringUtils.hasText(taskNo)) { + return null; + } + String cached = taskWorkstationCache.get(taskNo); + if (StringUtils.hasText(cached)) { + return cached; + } + try { + Long roleId = taskService.getTaskByNo(taskNo).getRoleId(); + if (roleId == null) { + return null; + } + String workstationId = String.valueOf(roleId); + taskWorkstationCache.put(taskNo, workstationId); + return workstationId; + } catch (Exception e) { + log.debug("resolve workstation id failed: taskNo={}, err={}", taskNo, e.getMessage()); + return null; + } + } + + private String resolveObjectName(String relativePath, String taskNo, String userId, String workstationId) { + String normalized = normalizeWorkspaceRelativePath(relativePath); + if (!StringUtils.hasText(normalized)) { + return ""; + } + + if (normalized.startsWith("logs/")) { + String subPath = normalized.substring("logs/".length()); + if (!StringUtils.hasText(subPath) + || !StringUtils.hasText(taskNo) + || !StringUtils.hasText(userId) + || !StringUtils.hasText(workstationId)) { + return ""; + } + return String.format("system/%s/logs/%s/%s/%s", workstationId, userId, taskNo, subPath); + } + + if (normalized.startsWith("user/")) { + String subPath = normalized.substring("user/".length()); + if (!StringUtils.hasText(subPath) || !StringUtils.hasText(userId)) { + return ""; + } + return String.format("user-files/%s/%s", userId, subPath); + } + + if (normalized.startsWith("workstation/")) { + String subPath = normalized.substring("workstation/".length()); + if (!StringUtils.hasText(subPath) + || !StringUtils.hasText(userId) + || !StringUtils.hasText(workstationId)) { + return ""; + } + return String.format("workstation/%s/%s/%s", workstationId, userId, subPath); + } + + return ""; + } + + private String extractFileName(String relativePath) { + String normalized = normalizeWorkspaceRelativePath(relativePath); + int index = normalized.lastIndexOf('/'); + if (index < 0 || index >= normalized.length() - 1) { + return normalized; + } + return normalized.substring(index + 1); + } + + private String normalizeWorkspaceRelativePath(String path) { + String normalized = asText(path).replace('\\', '/'); + if (normalized.startsWith("/workspace/")) { + normalized = normalized.substring("/workspace/".length()); + } + while (normalized.startsWith("/")) { + normalized = normalized.substring(1); + } + while (normalized.endsWith("/")) { + normalized = normalized.substring(0, normalized.length() - 1); + } + return normalized; + } + + private String asText(Object value) { + if (value == null) { + return ""; + } + return String.valueOf(value).trim(); + } + + private String firstNonBlank(String... values) { + for (String value : values) { + if (StringUtils.hasText(value)) { + return value.trim(); + } + } + return null; + } + + private record PathlistContext( + String taskNo, + String userId, + String workstationId, + List> artifacts, + int skippedCount + ) { + private static PathlistContext empty() { + return new PathlistContext(null, null, null, List.of(), 0); + } + + private boolean validForDisplay() { + return artifacts != null && !artifacts.isEmpty(); + } + + private boolean validForSync() { + return validForDisplay() + && StringUtils.hasText(taskNo) + && StringUtils.hasText(userId) + && StringUtils.hasText(workstationId); + } + } +} diff --git a/back/src/main/java/com/linkwork/service/TaskService.java b/back/src/main/java/com/linkwork/service/TaskService.java new file mode 100644 index 0000000..79dd422 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/TaskService.java @@ -0,0 +1,1774 @@ +package com.linkwork.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.linkwork.common.ForbiddenOperationException; +import com.linkwork.common.SnowflakeIdGenerator; +import com.linkwork.config.DispatchConfig; +import com.linkwork.mapper.RobotFileMapper; +import com.linkwork.mapper.TaskMapper; +import com.linkwork.model.dto.ScaleResult; +import com.linkwork.model.dto.TaskCompleteRequest; +import com.linkwork.model.dto.TaskCreateRequest; +import com.linkwork.model.dto.TaskResponse; +import com.linkwork.model.entity.McpServerEntity; +import com.linkwork.model.entity.RobotFile; +import com.linkwork.model.entity.RoleEntity; +import com.linkwork.model.entity.SkillEntity; +import com.linkwork.model.entity.Task; +import com.linkwork.model.enums.TaskOutputType; +import com.linkwork.model.enums.TaskStatus; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.connection.stream.MapRecord; +import org.springframework.data.redis.connection.stream.StreamOffset; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.util.StringUtils; + +import java.time.LocalDateTime; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * 任务服务 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class TaskService { + private static final String DELIVERY_MODE_GIT = "git"; + private static final String DELIVERY_MODE_OSS = "oss"; + private static final String OUTPUT_CODE_GIT_BRANCH = TaskOutputType.GIT_BRANCH.getCode(); + private static final String OUTPUT_CODE_PULL_REQUEST = TaskOutputType.PULL_REQUEST.getCode(); + + private final TaskMapper taskMapper; + private final RobotFileMapper robotFileMapper; + private final StringRedisTemplate redisTemplate; + private final ObjectMapper objectMapper; + private final RoleService roleService; + private final RuntimeModeService runtimeModeService; + private final SnowflakeIdGenerator idGenerator; + private final DispatchConfig dispatchConfig; + private final TaskOutputEstimatorAgent taskOutputEstimatorAgent; + private final TaskGitAuthService taskGitAuthService; + private final CronJobService cronJobService; + private final UserSoulService userSoulService; + private final TaskBillingUsageService taskBillingUsageService; + private final AdminAccessService adminAccessService; + @Autowired + private ServiceScaleService serviceScaleService; + @Autowired + private McpServerService mcpServerService; + + @Autowired + private SkillService skillService; + + @Value("${robot.prompt.platform:遵循平台工程规范:禁止吞异常;优先给结论和可执行步骤;涉及风险时明确说明并给出回滚方案。}") + private String platformPrompt = "遵循平台工程规范:禁止吞异常;优先给结论和可执行步骤;涉及风险时明确说明并给出回滚方案。"; + + @Value("${robot.prompt.user-soul-default:默认用户偏好:请使用中文沟通;先给结论,再给可执行步骤;信息不足时明确说明假设与风险。}") + private String defaultUserSoulPrompt = "默认用户偏好:请使用中文沟通;先给结论,再给可执行步骤;信息不足时明确说明假设与风险。"; + + /** + * 创建任务 + */ + @Transactional + public Task createTask(TaskCreateRequest request, String creatorId, String creatorName) { + return createTask(request, creatorId, creatorName, null, true, "MANUAL", null); + } + + /** + * 创建任务(带创建触发 IP) + */ + @Transactional + public Task createTask(TaskCreateRequest request, String creatorId, String creatorName, String creatorIp) { + return createTask(request, creatorId, creatorName, creatorIp, true, "MANUAL", null); + } + + /** + * 创建任务 + * + * 简化版本:前端只传 prompt, roleId, modelId, fileIds + * 其他配置(image, mcp, skills 等)由后端根据岗位自动填充 + * + * @param creatorIp 任务创建执行触发来源 IP + * @param sendNotify 是否发送创建通知(当前实现忽略) + */ + @Transactional + public Task createTask(TaskCreateRequest request, String creatorId, String creatorName, String creatorIp, boolean sendNotify) { + return createTask(request, creatorId, creatorName, creatorIp, sendNotify, "MANUAL", null); + } + + @Transactional + public Task createTask(TaskCreateRequest request, String creatorId, String creatorName, String creatorIp, + boolean sendNotify, String source, Long cronJobId) { + // 使用雪花算法生成分布式唯一任务编号 + String taskNo = idGenerator.nextTaskNo(); + + // 查询岗位信息(岗位包含默认镜像、MCP、技能等配置) + RoleEntity role = roleService.getById(request.getRoleId()); + if (role == null) { + throw new IllegalArgumentException("指定的岗位不存在: roleId=" + request.getRoleId()); + } + PromptLayers promptLayers = buildPromptLayers(role, creatorId); + String systemPromptAppend = toSystemPromptAppend(promptLayers); + + List selectedFiles = loadSelectedFiles(request.getFileIds(), creatorId); + List taskInputFiles = buildTaskInputFiles(selectedFiles); + String resolvedContent = buildResolvedTaskContent(request.getPrompt(), taskInputFiles); + + // 构建任务实体 + Task task = new Task(); + task.setTaskNo(taskNo); + task.setRoleId(request.getRoleId()); + task.setRoleName(role.getName()); + task.setPrompt(request.getPrompt()); + task.setStatus(TaskStatus.PENDING); + task.setSource(normalizeTaskSource(source)); + task.setCronJobId("CRON".equals(task.getSource()) ? cronJobId : null); + // 从岗位获取默认镜像,如果岗位没配置则使用默认值 + task.setImage(role.getImage() != null ? role.getImage() : "ubuntu-22.04-python3.10"); + task.setSelectedModel(request.getModelId()); + task.setCreatorId(creatorId); + task.setCreatorName(creatorName); + task.setCreatorIp(creatorIp); + task.setTokensUsed(0); + task.setDurationMs(0L); + task.setCreatedAt(LocalDateTime.now()); + task.setUpdatedAt(LocalDateTime.now()); + task.setIsDeleted(0); + + // 任务运行模式快照(严格双态:SIDECAR / ALONE) + RuntimeModeService.RuntimeSnapshot runtimeSnapshot = runtimeModeService.resolveForRole(role); + + // 关键:先落库任务核心字段,避免网关按 taskId 反查模型时出现“任务不存在” + taskMapper.insert(task); + + // 执行前产出预估:优先 LLM Gateway,失败兜底规则 + TaskOutputEstimatorAgent.EstimateResult estimateResult = + taskOutputEstimatorAgent.estimateWithBranch(taskNo, request, role); + List estimatedOutput = estimateResult.estimatedOutput(); + String branchName = estimateResult.branchName(); + String deliveryMode = resolveDeliveryMode(estimatedOutput, hasValidRoleGitRepo(role), taskNo); + + // 序列化配置(包含 fileIds、岗位继承配置和预估产出) + Map configMap = new HashMap<>(); + configMap.put("modelId", request.getModelId()); + configMap.put("fileIds", selectedFiles.stream().map(RobotFile::getFileId).toList()); + configMap.put("systemPromptAppend", systemPromptAppend); + configMap.put("promptLayers", toPromptLayerMap(promptLayers)); + if (!selectedFiles.isEmpty()) { + List> fileDetails = new ArrayList<>(); + Map inputFileRefMap = new HashMap<>(); + Map aliasMap = new LinkedHashMap<>(); + for (TaskInputFileRef inputFile : taskInputFiles) { + inputFileRefMap.put(inputFile.fileId(), inputFile); + aliasMap.put(inputFile.runtimePath(), inputFile.realPath()); + } + for (RobotFile rf : selectedFiles) { + TaskInputFileRef ref = inputFileRefMap.get(rf.getFileId()); + fileDetails.add(Map.of( + "fileId", rf.getFileId(), + "fileName", rf.getFileName(), + "fileType", rf.getFileType(), + "ossPath", rf.getOssPath(), + "spaceType", rf.getSpaceType(), + "workstationId", rf.getWorkstationId() == null ? "" : rf.getWorkstationId(), + "taskInputObject", "", + "taskInputPath", ref != null ? ref.runtimePath() : "")); + } + configMap.put("files", fileDetails); + configMap.put("resolvedContent", resolvedContent); + configMap.put("aliasMap", aliasMap); + } + configMap.put("image", task.getImage()); + configMap.put("runtimeMode", runtimeSnapshot.getRuntimeMode()); + configMap.put("zzMode", runtimeSnapshot.getZzMode()); + configMap.put("runnerImage", runtimeSnapshot.getRunnerImage()); + configMap.put("estimatedOutput", estimatedOutput); + configMap.put("deliveryMode", deliveryMode); + if (StringUtils.hasText(branchName)) { + configMap.put("branchName", branchName); + } + // 从岗位 configJson 继承 MCP、技能等配置 + if (role.getConfigJson() != null) { + configMap.put("mcp", role.getConfigJson().getMcp()); + configMap.put("skills", role.getConfigJson().getSkills()); + configMap.put("knowledge", role.getConfigJson().getKnowledge()); + configMap.put("gitRepos", role.getConfigJson().getGitRepos()); + configMap.put("env", role.getConfigJson().getEnv()); + } + try { + task.setConfigJson(objectMapper.writeValueAsString(configMap)); + } catch (JsonProcessingException e) { + throw new IllegalStateException("序列化任务配置失败: taskNo=" + taskNo, e); + } + + // 回写完整任务配置(含预估产物、执行模式等) + int updated = taskMapper.updateById(task); + if (updated != 1) { + throw new IllegalStateException("更新任务配置失败: taskNo=" + taskNo); + } + + // 绑定任务与 Git 认证映射(用户未授权时跳过) + taskGitAuthService.bindTaskWithLatestGitAuth(taskNo, creatorId); + + // 创建 Redis Stream (用于 WebSocket 事件) + // 动态格式: logs:{workstationId}:{taskNo} + String streamKey = dispatchConfig.getLogStreamKey(task.getRoleId(), taskNo); + publishTaskEvent(streamKey, "TASK_CREATED", taskNo, Map.of("message", "任务已创建")); + + Map estimateData = new HashMap<>(); + estimateData.put("estimated_output", estimatedOutput); + estimateData.put("domains", resolveOutputDomains(estimatedOutput)); + estimateData.put("delivery_mode", deliveryMode); + if (StringUtils.hasText(branchName)) { + estimateData.put("branch_name", branchName); + } + estimateData.put("message", "已完成任务产物预估"); + publishTaskEvent(streamKey, "TASK_OUTPUT_ESTIMATED", taskNo, estimateData); + + // 关键:事务提交后再入队,避免消费者查不到任务 + final Task dispatchTask = task; + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + @Override + public void afterCommit() { + pushToDispatchQueue(dispatchTask); + } + }); + + log.info("任务创建成功: taskNo={}, roleId={}, roleName={}, modelId={}, deliveryMode={}, estimatedOutput={}, branchName={}, estimateSource={}", + taskNo, task.getRoleId(), role.getName(), request.getModelId(), deliveryMode, estimatedOutput, branchName, estimateResult.source()); + + // 预留 sendNotify 参数,当前不发送外部 IM 通知 + + return task; + } + + private String normalizeTaskSource(String source) { + if (!StringUtils.hasText(source)) { + return "MANUAL"; + } + String normalized = source.trim().toUpperCase(Locale.ROOT); + if (!"MANUAL".equals(normalized) && !"CRON".equals(normalized)) { + throw new IllegalArgumentException("非法任务来源: source=" + source); + } + return normalized; + } + + /** + * 将任务推入调度队列(统一消息契约供 worker 消费) + */ + private void pushToDispatchQueue(Task task) { + try { + String queueKey = dispatchConfig.getTaskQueueKey(task.getRoleId()); + + Map message = new HashMap<>(); + message.put("task_id", task.getTaskNo()); + + String userId = task.getCreatorId(); + if (!StringUtils.hasText(userId)) { + userId = "system"; + log.warn("任务创建人为空,使用兜底 user_id=system 下发: taskNo={}", task.getTaskNo()); + } + message.put("user_id", userId); + message.put("content", resolveDispatchContent(task)); + message.put("system_prompt_append", resolveDispatchSystemPromptAppend(task)); + message.put("prompt_layers", resolveDispatchPromptLayers(task)); + message.put("source", task.getSource()); + message.put("cron_job_id", task.getCronJobId()); + if (task.getRoleId() != null) { + message.put("role_id", String.valueOf(task.getRoleId())); + } + + List> gitConfig = buildDispatchGitConfig(task); + String deliveryMode = resolveDispatchDeliveryMode(task, gitConfig); + if (DELIVERY_MODE_GIT.equals(deliveryMode) && gitConfig.isEmpty()) { + log.warn("任务下发降级为 oss:delivery_mode=git 但 git_config 为空, taskNo={}", task.getTaskNo()); + deliveryMode = DELIVERY_MODE_OSS; + } + message.put("delivery_mode", deliveryMode); + if (DELIVERY_MODE_GIT.equals(deliveryMode)) { + message.put("git_config", gitConfig); + } + List> filePathMappings = parseDispatchFilePathMappings(task); + if (!filePathMappings.isEmpty()) { + message.put("file_path_mappings", filePathMappings); + } + + String messageJson = objectMapper.writeValueAsString(message); + Long queueLength = redisTemplate.opsForList().rightPush(queueKey, messageJson); + + log.info("任务入队成功: taskNo={}, queueKey={}, queueLength={}", + task.getTaskNo(), queueKey, queueLength); + + ensurePodsForRole(task.getRoleId(), task.getTaskNo()); + } catch (JsonProcessingException e) { + log.error("任务入队失败,序列化错误: taskNo={}", task.getTaskNo(), e); + } + } + + /** + * 任务入队后检查岗位下是否有存活 Pod,无则按岗位配置扩容。 + * 异步执行,扩容失败不影响任务入队。 + */ + private void ensurePodsForRole(Long roleId, String taskNo) { + if (roleId == null) { + return; + } + String serviceId = String.valueOf(roleId); + try { + ScaleResult result = serviceScaleService.ensurePodsForRole(serviceId); + if (result != null) { + if (result.isSuccess()) { + log.info("任务入队触发自动扩容: taskNo={}, serviceId={}, pods={}", + taskNo, serviceId, result.getCurrentPodCount()); + } else { + log.warn("任务入队触发自动扩容失败: taskNo={}, serviceId={}, error={}", + taskNo, serviceId, result.getErrorMessage()); + } + } + } catch (Exception e) { + log.warn("任务入队扩容检查异常(不影响任务下发): taskNo={}, serviceId={}, error={}", + taskNo, serviceId, e.getMessage()); + } + } + + /** + * 根据任务编号获取任务详情 + */ + public Task getTaskByNo(String taskNo) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(Task::getTaskNo, taskNo); + Task task = taskMapper.selectOne(wrapper); + if (task == null) { + throw new IllegalArgumentException("任务不存在: " + taskNo); + } + return task; + } + + /** + * 根据任务编号获取任务详情(按创建人隔离) + */ + public Task getTaskByNo(String taskNo, String creatorId) { + Task task = getTaskByNo(taskNo); + assertTaskOwner(task, creatorId); + return task; + } + + /** + * 根据ID获取任务详情 + */ + public Task getTask(Long taskId) { + Task task = taskMapper.selectById(taskId); + if (task == null) { + throw new IllegalArgumentException("任务不存在: " + taskId); + } + return task; + } + + /** + * 获取任务列表 + * + * @param roleId 按岗位 ID 筛选(可选) + * @param status 按状态筛选(可选) + * @param page 页码 + * @param pageSize 每页大小 + */ + public Page listTasks(Long roleId, String status, Integer page, Integer pageSize) { + return listTasks(roleId, status, page, pageSize, null); + } + + /** + * 获取任务列表(按创建人隔离) + */ + public Page listTasks(Long roleId, String status, Integer page, Integer pageSize, String creatorId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + + if (StringUtils.hasText(creatorId) && !isAdmin(creatorId)) { + wrapper.eq(Task::getCreatorId, creatorId); + } + if (roleId != null) { + wrapper.eq(Task::getRoleId, roleId); + } + if (StringUtils.hasText(status)) { + wrapper.eq(Task::getStatus, TaskStatus.valueOf(status.toUpperCase())); + } + wrapper.orderByDesc(Task::getCreatedAt); + + return taskMapper.selectPage(new Page<>(page, pageSize), wrapper); + } + + /** + * 更新任务状态 + */ + @Transactional + public Task updateStatus(String taskNo, TaskStatus status) { + return updateStatusWithUsage(taskNo, status, null, null); + } + + /** + * 更新任务状态(可选补齐 tokens/duration,并在终态补齐计费字段) + */ + @Transactional + public Task updateStatusWithUsage(String taskNo, TaskStatus status, Integer tokensUsed, Long durationMs) { + Task task = getTaskByNo(taskNo); + task.setStatus(status); + + if (tokensUsed != null && tokensUsed >= 0) { + Integer currentTokens = task.getTokensUsed(); + if (tokensUsed > 0 || currentTokens == null || currentTokens <= 0) { + task.setTokensUsed(tokensUsed); + } + } + if (durationMs != null && durationMs >= 0) { + Long currentDuration = task.getDurationMs(); + if (durationMs > 0 || currentDuration == null || currentDuration <= 0) { + task.setDurationMs(durationMs); + } + } + + if (isTerminalStatus(status)) { + TaskBillingUsageService.UsageSnapshot billingSnapshot = taskBillingUsageService.fetchTaskUsage(taskNo).orElse(null); + if (billingSnapshot != null) { + if (billingSnapshot.tokenUsed() != null) { + task.setTokensUsed(billingSnapshot.tokenUsed()); + } + task.setInputTokens(billingSnapshot.inputTokens()); + task.setOutputTokens(billingSnapshot.outputTokens()); + task.setRequestCount(billingSnapshot.requestCount()); + task.setTokenLimit(billingSnapshot.tokenLimit()); + task.setUsagePercent(billingSnapshot.usagePercent()); + } + } + + task.setUpdatedAt(LocalDateTime.now()); + taskMapper.updateById(task); + cronJobService.onTaskStatusChanged(task, status); + log.info("任务状态更新: taskNo={}, status={}, tokensUsed={}, inputTokens={}, outputTokens={}, requestCount={}, durationMs={}", + taskNo, status, task.getTokensUsed(), task.getInputTokens(), task.getOutputTokens(), task.getRequestCount(), task.getDurationMs()); + return task; + } + + private boolean isTerminalStatus(TaskStatus status) { + return status == TaskStatus.COMPLETED || status == TaskStatus.FAILED || status == TaskStatus.ABORTED; + } + + /** + * 任务完成回写 + * + * Worker 执行完毕后调用,回写 tokens_used, duration_ms, report_json, status + */ + @Transactional + public Task completeTask(String taskNo, TaskCompleteRequest request) { + Task task = getTaskByNo(taskNo); + + // 校验状态:只有 RUNNING 或 PENDING 的任务可以回写完成 + if (task.getStatus() != TaskStatus.RUNNING && task.getStatus() != TaskStatus.PENDING) { + throw new IllegalArgumentException("任务当前状态不允许回写完成: " + task.getStatus()); + } + + // 解析目标状态 + TaskStatus targetStatus; + try { + targetStatus = TaskStatus.valueOf(request.getStatus().toUpperCase()); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("无效的任务状态: " + request.getStatus() + ",允许值: COMPLETED, FAILED"); + } + if (targetStatus != TaskStatus.COMPLETED && targetStatus != TaskStatus.FAILED) { + throw new IllegalArgumentException("回写状态只能为 COMPLETED 或 FAILED,收到: " + request.getStatus()); + } + + int finalTokensUsed = request.getTokensUsed(); + TaskBillingUsageService.UsageSnapshot billingSnapshot = taskBillingUsageService.fetchTaskUsage(taskNo).orElse(null); + UsageFallback streamUsage = resolveUsageFromStream(task); + if (billingSnapshot != null && billingSnapshot.tokenUsed() != null) { + finalTokensUsed = billingSnapshot.tokenUsed(); + } else if (finalTokensUsed <= 0 && streamUsage.tokensUsed() != null && streamUsage.tokensUsed() > 0) { + finalTokensUsed = streamUsage.tokensUsed(); + } + Long finalDurationMs = request.getDurationMs(); + if ((finalDurationMs == null || finalDurationMs <= 0) + && streamUsage.durationMs() != null + && streamUsage.durationMs() > 0) { + finalDurationMs = streamUsage.durationMs(); + } + if ((finalDurationMs == null || finalDurationMs <= 0) && task.getCreatedAt() != null) { + finalDurationMs = Math.max(0, Duration.between(task.getCreatedAt(), LocalDateTime.now()).toMillis()); + } + + // 更新字段(优先使用计费网关真实值) + task.setStatus(targetStatus); + task.setTokensUsed(finalTokensUsed); + task.setInputTokens(billingSnapshot != null ? billingSnapshot.inputTokens() : null); + task.setOutputTokens(billingSnapshot != null ? billingSnapshot.outputTokens() : null); + task.setRequestCount(billingSnapshot != null ? billingSnapshot.requestCount() : null); + task.setTokenLimit(billingSnapshot != null ? billingSnapshot.tokenLimit() : null); + task.setUsagePercent(billingSnapshot != null ? billingSnapshot.usagePercent() : null); + task.setDurationMs(finalDurationMs); + task.setUpdatedAt(LocalDateTime.now()); + + Map reportMap = buildTaskReport(task, request, targetStatus, finalTokensUsed); + try { + task.setReportJson(objectMapper.writeValueAsString(reportMap)); + } catch (JsonProcessingException e) { + throw new IllegalStateException("序列化任务报告失败: taskNo=" + taskNo, e); + } + + taskMapper.updateById(task); + cronJobService.onTaskStatusChanged(task, targetStatus); + + // 发送完成事件到 Redis Stream: logs:{workstationId}:{taskNo} + String streamKey = dispatchConfig.getLogStreamKey(task.getRoleId(), taskNo); + Map completionData = new HashMap<>(); + completionData.put("tokens_used", finalTokensUsed); + completionData.put("input_tokens", task.getInputTokens()); + completionData.put("output_tokens", task.getOutputTokens()); + completionData.put("request_count", task.getRequestCount()); + completionData.put("duration_ms", finalDurationMs); + publishTaskEvent(streamKey, + targetStatus == TaskStatus.COMPLETED ? "TASK_COMPLETED" : "TASK_FAILED", + taskNo, + completionData); + + Map reportReadyData = new HashMap<>(); + reportReadyData.put("report", reportMap); + reportReadyData.put("message", "执行报告已生成"); + publishTaskEvent(streamKey, "TASK_REPORT_READY", taskNo, reportReadyData); + + log.info("任务完成回写: taskNo={}, status={}, tokensUsed={}, inputTokens={}, outputTokens={}, requestCount={}, durationMs={}", + taskNo, targetStatus, finalTokensUsed, task.getInputTokens(), task.getOutputTokens(), task.getRequestCount(), finalDurationMs); + + return task; + } + + private UsageFallback resolveUsageFromStream(Task task) { + if (task == null || !StringUtils.hasText(task.getTaskNo())) { + return new UsageFallback(null, null); + } + + List streamKeys = List.of( + dispatchConfig.getLogStreamKey(task.getRoleId(), task.getTaskNo()), + "stream:task:" + task.getTaskNo() + ":events", + "stream:task:" + task.getTaskNo() + ); + + Integer tokensUsed = null; + Long durationMs = null; + for (String streamKey : streamKeys) { + try { + List> records = redisTemplate.opsForStream() + .read(StreamOffset.fromStart(streamKey)); + if (records == null || records.isEmpty()) { + continue; + } + for (MapRecord record : records) { + Map eventData = extractEventData(record); + Integer eventTokens = resolveIntByKeys(eventData, "tokens_used", "tokensUsed", "token_usage"); + Long eventDuration = resolveLongByKeys(eventData, "duration_ms", "durationMs", "elapsed_ms"); + if (eventTokens != null && eventTokens >= 0) { + tokensUsed = eventTokens; + } + if (eventDuration != null && eventDuration > 0) { + durationMs = eventDuration; + } + } + } catch (Exception e) { + log.debug("从 Stream 提取 usage 失败: taskNo={}, streamKey={}, error={}", + task.getTaskNo(), streamKey, e.getMessage()); + } + } + return new UsageFallback(tokensUsed, durationMs); + } + + @SuppressWarnings("unchecked") + private Map extractEventData(MapRecord record) { + Map rawEvent = new HashMap<>(); + record.getValue().forEach((k, v) -> rawEvent.put(k.toString(), v)); + + Object payloadObj = rawEvent.get("payload"); + if (payloadObj instanceof String payloadStr && payloadStr.startsWith("{")) { + try { + Map payloadMap = objectMapper.readValue(payloadStr, Map.class); + Object innerData = payloadMap.get("data"); + if (innerData instanceof String dataStr && (dataStr.startsWith("{") || dataStr.startsWith("["))) { + try { + payloadMap.put("data", objectMapper.readValue(dataStr, Object.class)); + } catch (Exception ignored) { + } + } + Object payloadData = payloadMap.get("data"); + if (payloadData instanceof Map dataMap) { + dataMap.forEach((k, v) -> payloadMap.putIfAbsent(String.valueOf(k), v)); + } + return payloadMap; + } catch (Exception e) { + log.debug("解析 payload 失败,回退扁平事件: recordId={}, error={}", record.getId(), e.getMessage()); + } + } + + Object dataObj = rawEvent.get("data"); + if (dataObj instanceof String dataStr && (dataStr.startsWith("{") || dataStr.startsWith("["))) { + try { + rawEvent.put("data", objectMapper.readValue(dataStr, Object.class)); + } catch (Exception ignored) { + } + } + Object flatData = rawEvent.get("data"); + if (flatData instanceof Map dataMap) { + dataMap.forEach((k, v) -> rawEvent.putIfAbsent(String.valueOf(k), v)); + } + + return rawEvent; + } + + private Integer resolveIntByKeys(Map eventData, String... keys) { + Long value = resolveLongByKeys(eventData, keys); + if (value == null || value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) { + return null; + } + return value.intValue(); + } + + private Long resolveLongByKeys(Map eventData, String... keys) { + if (eventData == null || keys == null) { + return null; + } + for (String key : keys) { + Object raw = eventData.get(key); + if (raw == null) { + continue; + } + try { + return Long.parseLong(String.valueOf(raw)); + } catch (NumberFormatException ignored) { + } + } + return null; + } + + private record UsageFallback(Integer tokensUsed, Long durationMs) {} + + /** + * 终止任务 + * + * terminate 接口只负责受理并下发终止命令,不会直接把任务写为 ABORTED。 + */ + @Transactional + public Task abortTask(String taskNo, String updaterId, String updaterName) { + Task task = StringUtils.hasText(updaterId) ? getTaskByNo(taskNo, updaterId) : getTaskByNo(taskNo); + + // 只有运行中或等待中的任务可以终止 + if (task.getStatus() != TaskStatus.RUNNING && task.getStatus() != TaskStatus.PENDING) { + throw new IllegalArgumentException("任务当前状态不允许终止: " + task.getStatus()); + } + + task.setUpdaterId(updaterId); + task.setUpdaterName(updaterName); + task.setUpdatedAt(LocalDateTime.now()); + taskMapper.updateById(task); + + TerminateEnqueueResult enqueueResult = pushTerminateCommand(task, updaterName); + + // 发布终止入队事件到 Redis Stream: logs:{workstationId}:{taskNo} + String streamKey = dispatchConfig.getLogStreamKey(task.getRoleId(), taskNo); + Map enqueueData = new HashMap<>(); + enqueueData.put("queue_key", enqueueResult.queueKey()); + enqueueData.put("request_id", enqueueResult.requestId()); + enqueueData.put("message", "任务终止命令已入队"); + publishTaskEvent(streamKey, "TASK_TERMINATE_ENQUEUED", taskNo, enqueueData); + + log.info("任务终止请求已受理: taskNo={}, queueKey={}, requestId={}", + taskNo, enqueueResult.queueKey(), enqueueResult.requestId()); + + return task; + } + + private boolean isAdmin(String userId) { + return adminAccessService.isAdmin(userId); + } + + private void assertTaskOwner(Task task, String creatorId) { + if (!StringUtils.hasText(creatorId)) { + throw new ForbiddenOperationException("用户未登录或登录态失效"); + } + if (isAdmin(creatorId)) { + return; + } + if (!StringUtils.hasText(task.getCreatorId()) || !creatorId.equals(task.getCreatorId())) { + throw new ForbiddenOperationException("无权限访问该任务"); + } + } + + private TerminateEnqueueResult pushTerminateCommand(Task task, String updaterName) { + String taskNo = task.getTaskNo(); + String queueKey = dispatchConfig.getTaskControlQueueKey(task.getRoleId()); + String requestId = "TRQ-" + UUID.randomUUID(); + + Map message = new HashMap<>(); + message.put("type", "TASK_TERMINATE_REQUEST"); + message.put("request_id", requestId); + message.put("task_id", taskNo); + message.put("reason", "terminated_by_user"); + message.put("operator", StringUtils.hasText(updaterName) ? updaterName : "system"); + message.put("requested_at", LocalDateTime.now().toString()); + + final String messageJson; + try { + messageJson = objectMapper.writeValueAsString(message); + } catch (JsonProcessingException e) { + throw new IllegalStateException("序列化任务终止命令失败: taskNo=" + taskNo, e); + } + + Long queueLength = redisTemplate.opsForList().rightPush(queueKey, messageJson); + if (queueLength == null) { + throw new IllegalStateException("任务终止命令入队失败: taskNo=" + taskNo); + } + + log.info("任务终止命令入队成功: taskNo={}, queueKey={}, queueLength={}, requestId={}", + taskNo, queueKey, queueLength, requestId); + return new TerminateEnqueueResult(queueKey, requestId); + } + + + /** + * 转换为响应 DTO + */ + public TaskResponse toResponse(Task task) { + TaskResponse response = new TaskResponse(); + response.setTaskNo(task.getTaskNo()); + response.setRoleId(task.getRoleId()); + response.setRoleName(task.getRoleName()); + response.setPrompt(task.getPrompt()); + response.setStatus(task.getStatus()); + response.setSource(task.getSource()); + response.setImage(task.getImage()); + response.setSelectedModel(task.getSelectedModel()); + response.setCreator(task.getCreatorName()); + response.setCreatedAt(task.getCreatedAt()); + + // 构建 Usage + TaskResponse.Usage usage = new TaskResponse.Usage(); + usage.setTokensUsed(task.getTokensUsed()); + usage.setInputTokens(task.getInputTokens()); + usage.setOutputTokens(task.getOutputTokens()); + usage.setRequestCount(task.getRequestCount()); + usage.setTokenLimit(task.getTokenLimit()); + usage.setUsagePercent(task.getUsagePercent()); + usage.setDuration(formatDuration(task.getDurationMs())); + response.setUsage(usage); + + // 解析报告 + if (StringUtils.hasText(task.getReportJson())) { + try { + response.setReport(objectMapper.readValue(task.getReportJson(), TaskResponse.Report.class)); + } catch (JsonProcessingException e) { + log.error("解析任务报告失败", e); + } + } + + com.fasterxml.jackson.databind.JsonNode configNode = null; + if (StringUtils.hasText(task.getConfigJson())) { + try { + configNode = objectMapper.readTree(task.getConfigJson()); + } catch (Exception e) { + throw new IllegalStateException("解析任务配置失败: taskNo=" + task.getTaskNo(), e); + } + } + + // 运行模式字段强校验:仅 SIDECAR / ALONE,不允许第三态 + RuntimeModeService.RuntimeSnapshot runtimeSnapshot = resolveTaskRuntimeSnapshot(task, configNode); + response.setRuntimeMode(runtimeSnapshot.getRuntimeMode()); + response.setZzMode(runtimeSnapshot.getZzMode()); + response.setRunnerImage(runtimeSnapshot.getRunnerImage()); + + // 从 configJson 解析扩展字段 (repo/branch/mcp/skills/knowledge/env) + if (configNode != null) { + String branchName = null; + String gitRepoBranch = null; + + // estimatedOutput + var estimatedOutputNode = configNode.get("estimatedOutput"); + if (estimatedOutputNode != null && estimatedOutputNode.isArray()) { + response.setEstimatedOutput(objectMapper.convertValue(estimatedOutputNode, + objectMapper.getTypeFactory().constructCollectionType(List.class, String.class))); + } + + var branchNameNode = configNode.get("branchName"); + if (branchNameNode != null && branchNameNode.isTextual()) { + branchName = branchNameNode.asText(); + if (StringUtils.hasText(branchName)) { + branchName = branchName.trim(); + response.setBranchName(branchName); + } + } + var deliveryModeNode = configNode.get("deliveryMode"); + if (deliveryModeNode != null && deliveryModeNode.isTextual()) { + response.setDeliveryMode(normalizeDeliveryMode(deliveryModeNode.asText(), task.getTaskNo())); + } + + // gitRepos → repo + branch + var gitRepos = configNode.get("gitRepos"); + if (gitRepos != null && gitRepos.isArray() && !gitRepos.isEmpty()) { + var first = gitRepos.get(0); + if (first.has("url")) { + response.setRepo(first.get("url").asText()); + } + if (first.has("branch")) { + gitRepoBranch = first.get("branch").asText(); + if (StringUtils.hasText(gitRepoBranch)) { + gitRepoBranch = gitRepoBranch.trim(); + } + } + } + + // branch(兼容历史配置) + var branchNode = configNode.get("branch"); + if (!StringUtils.hasText(gitRepoBranch) && branchNode != null && branchNode.isTextual()) { + gitRepoBranch = branchNode.asText(); + if (StringUtils.hasText(gitRepoBranch)) { + gitRepoBranch = gitRepoBranch.trim(); + } + } + + if (StringUtils.hasText(branchName)) { + response.setBranch(branchName); + } else if (StringUtils.hasText(gitRepoBranch)) { + response.setBranch(gitRepoBranch); + } + + // mcp + var mcpNode = configNode.get("mcp"); + if (mcpNode != null && mcpNode.isArray()) { + response.setMcp(resolveMcpDisplayNames(mcpNode)); + } + + // skills + var skillsNode = configNode.get("skills"); + if (skillsNode != null && skillsNode.isArray()) { + response.setSkills(resolveSkillDisplayNames(skillsNode)); + } + + // knowledge + var knowledgeNode = configNode.get("knowledge"); + if (knowledgeNode != null && knowledgeNode.isArray()) { + response.setKnowledge(objectMapper.convertValue(knowledgeNode, + objectMapper.getTypeFactory().constructCollectionType(List.class, String.class))); + } + + // env: [{key, value}] → Map + var envNode = configNode.get("env"); + if (envNode != null && envNode.isArray()) { + var envMap = new LinkedHashMap(); + for (var item : envNode) { + String k = item.has("key") ? item.get("key").asText() : null; + String v = item.has("value") ? item.get("value").asText() : ""; + if (k != null && !k.isEmpty()) { + envMap.put(k, v); + } + } + if (!envMap.isEmpty()) { + response.setEnv(envMap); + } + } + } + + if (!StringUtils.hasText(response.getDeliveryMode())) { + response.setDeliveryMode(resolveDispatchDeliveryMode(task, buildDispatchGitConfig(task))); + } + + return response; + } + + /** + * 访客分享视图:移除敏感配置字段,仅保留任务执行详情。 + */ + public TaskResponse toShareResponse(Task task) { + TaskResponse response = toResponse(task); + response.setEnv(null); + response.setKnowledge(null); + return response; + } + + private List resolveMcpDisplayNames(com.fasterxml.jackson.databind.JsonNode refsNode) { + List refs = extractConfigRefs(refsNode); + if (refs.isEmpty()) { + return List.of(); + } + + Map displayByRef = new HashMap<>(); + List idRefs = new ArrayList<>(); + List nameRefs = new ArrayList<>(); + + for (String ref : refs) { + try { + idRefs.add(Long.parseLong(ref)); + } catch (NumberFormatException ex) { + nameRefs.add(ref); + } + } + + if (!idRefs.isEmpty()) { + List entities = mcpServerService.list( + new LambdaQueryWrapper().in(McpServerEntity::getId, idRefs)); + for (McpServerEntity entity : entities) { + String displayName = StringUtils.hasText(entity.getName()) + ? entity.getName() + : String.valueOf(entity.getId()); + displayByRef.put(String.valueOf(entity.getId()), displayName); + if (StringUtils.hasText(entity.getName())) { + displayByRef.put(entity.getName(), displayName); + } + } + } + + if (!nameRefs.isEmpty()) { + List entities = mcpServerService.list( + new LambdaQueryWrapper().in(McpServerEntity::getName, nameRefs)); + for (McpServerEntity entity : entities) { + String displayName = StringUtils.hasText(entity.getName()) + ? entity.getName() + : String.valueOf(entity.getId()); + displayByRef.put(entity.getName(), displayName); + displayByRef.put(String.valueOf(entity.getId()), displayName); + } + } + + List result = new ArrayList<>(); + for (String ref : refs) { + result.add(displayByRef.getOrDefault(ref, ref)); + } + return result; + } + + private List resolveSkillDisplayNames(com.fasterxml.jackson.databind.JsonNode refsNode) { + List refs = extractConfigRefs(refsNode); + if (refs.isEmpty()) { + return List.of(); + } + + Map displayByRef = new HashMap<>(); + List idRefs = new ArrayList<>(); + List nameRefs = new ArrayList<>(); + + for (String ref : refs) { + try { + idRefs.add(Long.parseLong(ref)); + } catch (NumberFormatException ex) { + nameRefs.add(ref); + } + } + + if (!idRefs.isEmpty()) { + List entities = skillService.list( + new LambdaQueryWrapper().in(SkillEntity::getId, idRefs)); + for (SkillEntity entity : entities) { + String displayName = resolveSkillDisplayName(entity); + displayByRef.put(String.valueOf(entity.getId()), displayName); + if (StringUtils.hasText(entity.getName())) { + displayByRef.put(entity.getName(), displayName); + } + } + } + + if (!nameRefs.isEmpty()) { + List entities = skillService.list( + new LambdaQueryWrapper().in(SkillEntity::getName, nameRefs)); + for (SkillEntity entity : entities) { + String displayName = resolveSkillDisplayName(entity); + displayByRef.put(entity.getName(), displayName); + displayByRef.put(String.valueOf(entity.getId()), displayName); + } + } + + List result = new ArrayList<>(); + for (String ref : refs) { + result.add(displayByRef.getOrDefault(ref, ref)); + } + return result; + } + + private String resolveSkillDisplayName(SkillEntity entity) { + if (StringUtils.hasText(entity.getDisplayName())) { + return entity.getDisplayName(); + } + if (StringUtils.hasText(entity.getName())) { + return entity.getName(); + } + return String.valueOf(entity.getId()); + } + + private List extractConfigRefs(com.fasterxml.jackson.databind.JsonNode refsNode) { + if (refsNode == null || !refsNode.isArray()) { + return List.of(); + } + + List refs = new ArrayList<>(); + for (var refNode : refsNode) { + String ref = extractConfigRef(refNode); + if (StringUtils.hasText(ref)) { + refs.add(ref.trim()); + } + } + return refs; + } + + private String extractConfigRef(com.fasterxml.jackson.databind.JsonNode refNode) { + if (refNode == null || refNode.isNull()) { + return null; + } + if (refNode.isTextual() || refNode.isNumber()) { + return refNode.asText(); + } + if (!refNode.isObject()) { + return null; + } + + String[] candidateKeys = {"id", "value", "name", "label", "key"}; + for (String key : candidateKeys) { + var valueNode = refNode.get(key); + if (valueNode == null || valueNode.isNull()) { + continue; + } + if (valueNode.isTextual() || valueNode.isNumber()) { + String value = valueNode.asText(); + if (StringUtils.hasText(value)) { + return value; + } + } + } + return null; + } + + /** + * 批量转换为响应 DTO + */ + public List toResponseList(List tasks) { + return tasks.stream().map(this::toResponse).collect(Collectors.toList()); + } + + private List loadSelectedFiles(List fileIds, String creatorId) { + if (fileIds == null || fileIds.isEmpty()) { + return Collections.emptyList(); + } + + LinkedHashSet uniqueIds = new LinkedHashSet<>(); + for (String fileId : fileIds) { + if (!StringUtils.hasText(fileId)) { + continue; + } + uniqueIds.add(fileId.trim()); + } + if (uniqueIds.isEmpty()) { + return Collections.emptyList(); + } + + List files = new ArrayList<>(); + for (String fileId : uniqueIds) { + RobotFile file = robotFileMapper.selectOne( + new LambdaQueryWrapper() + .eq(RobotFile::getFileId, fileId) + .isNull(RobotFile::getDeletedAt) + .last("limit 1")); + if (file == null) { + throw new IllegalArgumentException("文件不存在: " + fileId); + } + if (!creatorId.equals(file.getUserId())) { + throw new IllegalArgumentException("无权引用该文件: " + fileId); + } + files.add(file); + } + return files; + } + + private List buildTaskInputFiles(List files) { + if (files == null || files.isEmpty()) { + return Collections.emptyList(); + } + + Map nameCount = files.stream() + .collect(Collectors.groupingBy(RobotFile::getFileName, Collectors.counting())); + + Map nameSeq = new HashMap<>(); + + List refs = new ArrayList<>(); + for (RobotFile file : files) { + String displayName = file.getFileName(); + if (nameCount.getOrDefault(displayName, 0L) > 1) { + int seq = nameSeq.merge(displayName, 1, Integer::sum); + if (seq > 1) { + displayName = appendSequence(displayName, seq - 1); + } + } + String readablePath = buildReadableRuntimePath(file, displayName); + String realPath = buildRuntimeFilePath(file); + refs.add(new TaskInputFileRef(file.getFileId(), file.getFileName(), readablePath, realPath)); + } + return refs; + } + + private String appendSequence(String fileName, int seq) { + int dotIdx = fileName.lastIndexOf('.'); + if (dotIdx > 0) { + return fileName.substring(0, dotIdx) + " (" + seq + ")" + fileName.substring(dotIdx); + } + return fileName + " (" + seq + ")"; + } + + private String buildReadableRuntimePath(RobotFile file, String displayName) { + return buildWorkspaceFileBase(file) + displayName; + } + + private String buildResolvedTaskContent(String prompt, List refs) { + String basePrompt = prompt == null ? "" : prompt; + if (refs == null || refs.isEmpty()) { + return basePrompt; + } + + Map nameCount = refs.stream().collect( + Collectors.groupingBy(TaskInputFileRef::originalFileName, LinkedHashMap::new, Collectors.counting())); + + String resolved = basePrompt; + List sorted = refs.stream() + .sorted(Comparator.comparingInt( + (TaskInputFileRef ref) -> ref.originalFileName() == null ? 0 : ref.originalFileName().length()) + .reversed()) + .toList(); + + for (TaskInputFileRef ref : sorted) { + String originalName = ref.originalFileName(); + if (!StringUtils.hasText(originalName)) { + continue; + } + if (StringUtils.hasText(ref.fileId())) { + resolved = resolved.replace("@" + ref.fileId(), ref.runtimePath()); + } + if (nameCount.getOrDefault(originalName, 0L) != 1L) { + continue; + } + resolved = resolved.replace("@" + originalName, ref.runtimePath()); + } + + StringBuilder appendix = new StringBuilder(); + appendix.append("\n\n可用附件路径:"); + for (TaskInputFileRef ref : refs) { + appendix.append("\n- ") + .append(ref.originalFileName()) + .append(" => ") + .append(ref.runtimePath()); + } + return resolved + appendix; + } + + private String resolveDispatchContent(Task task) { + if (task == null) { + return ""; + } + String resolved = parseResolvedContentFromConfig(task); + if (StringUtils.hasText(resolved)) { + return resolved; + } + return task.getPrompt(); + } + + private String resolveDispatchSystemPromptAppend(Task task) { + if (task == null || !StringUtils.hasText(task.getConfigJson())) { + throw new IllegalStateException("任务缺少 systemPromptAppend: taskNo=" + (task == null ? "" : task.getTaskNo())); + } + try { + var configNode = objectMapper.readTree(task.getConfigJson()); + var node = configNode.get("systemPromptAppend"); + if (node != null && node.isTextual() && StringUtils.hasText(node.asText())) { + return node.asText(); + } + } catch (Exception e) { + throw new IllegalStateException("解析 systemPromptAppend 失败: taskNo=" + task.getTaskNo(), e); + } + throw new IllegalStateException("任务缺少 systemPromptAppend: taskNo=" + task.getTaskNo()); + } + + private Map resolveDispatchPromptLayers(Task task) { + if (task == null || !StringUtils.hasText(task.getConfigJson())) { + throw new IllegalStateException("任务缺少 promptLayers: taskNo=" + (task == null ? "" : task.getTaskNo())); + } + try { + var configNode = objectMapper.readTree(task.getConfigJson()); + var layersNode = configNode.get("promptLayers"); + if (layersNode == null || !layersNode.isObject()) { + throw new IllegalStateException("任务缺少 promptLayers: taskNo=" + task.getTaskNo()); + } + String rolePrompt = readRequiredLayer(layersNode, "rolePrompt", task.getTaskNo()); + String platformPromptText = readRequiredLayer(layersNode, "platformPrompt", task.getTaskNo()); + String userSoul = readRequiredLayer(layersNode, "userSoul", task.getTaskNo()); + + Map layers = new LinkedHashMap<>(); + layers.put("platform_prompt", platformPromptText); + layers.put("role_prompt", rolePrompt); + layers.put("user_soul", userSoul); + return layers; + } catch (IllegalStateException e) { + throw e; + } catch (Exception e) { + throw new IllegalStateException("解析 promptLayers 失败: taskNo=" + task.getTaskNo(), e); + } + } + + private String readRequiredLayer(com.fasterxml.jackson.databind.JsonNode layersNode, String field, String taskNo) { + var node = layersNode.get(field); + if (node == null || !node.isTextual() || !StringUtils.hasText(node.asText())) { + throw new IllegalStateException("任务 promptLayer 缺失: taskNo=" + taskNo + ", field=" + field); + } + return node.asText(); + } + + private PromptLayers buildPromptLayers(RoleEntity role, String creatorId) { + String rolePrompt = normalizePromptLayer(role == null ? null : role.getPrompt()); + if (!StringUtils.hasText(rolePrompt)) { + Long roleId = role == null ? null : role.getId(); + throw new IllegalArgumentException("ROLE_PROMPT_INVALID: 岗位 Prompt 不能为空, roleId=" + roleId); + } + + String platformPromptText = normalizePromptLayer(platformPrompt); + if (!StringUtils.hasText(platformPromptText)) { + throw new IllegalArgumentException("PLATFORM_PROMPT_INVALID: 平台 Prompt 不能为空"); + } + + String userSoul = normalizePromptLayer(userSoulService.getOptionalSoulContent(creatorId)); + if (!StringUtils.hasText(userSoul)) { + userSoul = normalizePromptLayer(defaultUserSoulPrompt); + log.info("用户 Soul 缺失,使用默认用户层 Prompt: userId={}", creatorId); + } + if (!StringUtils.hasText(userSoul)) { + throw new IllegalArgumentException("SOUL_CONTENT_INVALID: Soul 内容不能为空"); + } + + return new PromptLayers(rolePrompt, platformPromptText, userSoul); + } + + private String toSystemPromptAppend(PromptLayers promptLayers) { + return "\n" + + "\n" + + " \n" + + " \n" + + promptLayers.platformPrompt() + + "\n \n\n" + + " \n" + + " \n" + + promptLayers.rolePrompt() + + "\n \n\n" + + " \n" + + " \n" + + promptLayers.userSoul() + + "\n \n" + + ""; + } + + private Map toPromptLayerMap(PromptLayers promptLayers) { + Map map = new LinkedHashMap<>(); + map.put("platformPrompt", promptLayers.platformPrompt()); + map.put("rolePrompt", promptLayers.rolePrompt()); + map.put("userSoul", promptLayers.userSoul()); + return map; + } + + private String normalizePromptLayer(String text) { + return text == null ? "" : text.trim(); + } + + private String parseResolvedContentFromConfig(Task task) { + if (task == null || !StringUtils.hasText(task.getConfigJson())) { + return ""; + } + try { + var configNode = objectMapper.readTree(task.getConfigJson()); + var resolvedContentNode = configNode.get("resolvedContent"); + if (resolvedContentNode != null && resolvedContentNode.isTextual()) { + return resolvedContentNode.asText(); + } + } catch (Exception e) { + log.warn("解析 resolvedContent 失败: taskNo={}, error={}", task.getTaskNo(), e.getMessage()); + } + return ""; + } + + private List> parseDispatchFilePathMappings(Task task) { + if (task == null || !StringUtils.hasText(task.getConfigJson())) { + return Collections.emptyList(); + } + try { + var configNode = objectMapper.readTree(task.getConfigJson()); + var aliasNode = configNode.get("aliasMap"); + if (aliasNode == null || !aliasNode.isObject()) { + return Collections.emptyList(); + } + + Map aliasMap = objectMapper.convertValue( + aliasNode, + new TypeReference>() { + }); + if (aliasMap == null || aliasMap.isEmpty()) { + return Collections.emptyList(); + } + + List> mappings = new ArrayList<>(); + for (Map.Entry entry : aliasMap.entrySet()) { + String runtimePath = entry.getKey(); + String realPath = entry.getValue(); + if (!StringUtils.hasText(runtimePath) || !StringUtils.hasText(realPath)) { + continue; + } + mappings.add(Map.of( + "runtime_path", runtimePath, + "real_path", realPath)); + } + return mappings; + } catch (Exception e) { + log.warn("解析 file_path_mappings 失败: taskNo={}, error={}", task.getTaskNo(), e.getMessage()); + return Collections.emptyList(); + } + } + + private String buildRuntimeFilePath(RobotFile file) { + String fileId = file == null ? null : file.getFileId(); + if (!StringUtils.hasText(fileId)) { + throw new IllegalArgumentException("文件标识缺失,无法生成容器路径"); + } + String extension = normalizeFileExtension(file.getFileType()); + return buildWorkspaceFileBase(file) + fileId + extension; + } + + private String buildWorkspaceFileBase(RobotFile file) { + return "WORKSTATION".equalsIgnoreCase(file.getSpaceType()) + ? "/workspace/workstation/original/" + : "/workspace/user/original/"; + } + + private String normalizeFileExtension(String fileType) { + if (!StringUtils.hasText(fileType)) { + return ""; + } + String cleaned = fileType.trim().toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9]", ""); + if (!StringUtils.hasText(cleaned)) { + return ""; + } + return "." + cleaned; + } + + private RuntimeModeService.RuntimeSnapshot resolveTaskRuntimeSnapshot( + Task task, + com.fasterxml.jackson.databind.JsonNode configNode) { + + String runtimeModeRaw = readConfigText(configNode, "runtimeMode"); + if (!StringUtils.hasText(runtimeModeRaw)) { + runtimeModeRaw = readConfigText(configNode, "podMode"); + } + + String runnerImageRaw = readConfigText(configNode, "runnerImage"); + if (!StringUtils.hasText(runnerImageRaw)) { + runnerImageRaw = readConfigText(configNode, "runnerBaseImage"); + } + + RuntimeModeService.RuntimeSnapshot roleSnapshot = null; + RuntimeModeService.RuntimeSnapshot runtimeSnapshot; + if (StringUtils.hasText(runtimeModeRaw)) { + String effectiveRunner = runnerImageRaw; + if (!StringUtils.hasText(effectiveRunner) && "SIDECAR".equalsIgnoreCase(runtimeModeRaw)) { + roleSnapshot = runtimeModeService.resolveForRole(task.getRoleId()); + effectiveRunner = roleSnapshot.getRunnerImage(); + } + runtimeSnapshot = runtimeModeService.resolveFromRaw( + runtimeModeRaw, + effectiveRunner, + "taskNo=" + task.getTaskNo()); + } else { + roleSnapshot = runtimeModeService.resolveForRole(task.getRoleId()); + runtimeSnapshot = roleSnapshot; + } + + String zzModeRaw = readConfigText(configNode, "zzMode"); + if (StringUtils.hasText(zzModeRaw) + && !zzModeRaw.equalsIgnoreCase(runtimeSnapshot.getZzMode())) { + throw new IllegalStateException("任务运行模式与 zz 通道不一致: taskNo=" + task.getTaskNo()); + } + + return runtimeSnapshot; + } + + private String readConfigText(com.fasterxml.jackson.databind.JsonNode node, String key) { + if (node == null || key == null || !node.has(key) || node.get(key) == null) { + return null; + } + String value = node.get(key).asText(); + return StringUtils.hasText(value) ? value : null; + } + + private Map buildTaskReport(Task task, TaskCompleteRequest request, TaskStatus targetStatus, int finalTokensUsed) { + TaskCompleteRequest.Report workerReport = request.getReport(); + List estimatedOutput = parseEstimatedOutput(task); + + Map reportMap = new HashMap<>(); + reportMap.put("summary", resolveReportSummary(workerReport, targetStatus)); + reportMap.put("tokens", finalTokensUsed); + reportMap.put("duration", formatDuration(request.getDurationMs())); + reportMap.put("completion", resolveReportCompletion(workerReport, targetStatus)); + reportMap.put("audit", resolveReportAudit(workerReport, targetStatus)); + reportMap.put("artifacts", resolveReportArtifacts(workerReport, estimatedOutput)); + reportMap.put("branch", workerReport != null ? workerReport.getBranch() : null); + reportMap.put("commit", workerReport != null ? workerReport.getCommit() : null); + return reportMap; + } + + private List resolveReportArtifacts(TaskCompleteRequest.Report workerReport, List estimatedOutput) { + if (workerReport != null && workerReport.getArtifacts() != null && !workerReport.getArtifacts().isEmpty()) { + return new ArrayList<>(workerReport.getArtifacts()); + } + + LinkedHashSet uniqueOutputs = new LinkedHashSet<>(estimatedOutput); + List artifacts = new ArrayList<>(); + for (String outputCode : uniqueOutputs) { + if (!StringUtils.hasText(outputCode)) { + continue; + } + Map artifact = buildFallbackArtifact(outputCode); + if (!artifact.isEmpty()) { + artifacts.add(artifact); + } + } + + if (artifacts.isEmpty()) { + artifacts.add(buildFallbackArtifact(TaskOutputType.DIALOG_CONCLUSION.getCode())); + } + return artifacts; + } + + private Map buildFallbackArtifact(String outputCode) { + Map artifact = new HashMap<>(); + if (!StringUtils.hasText(outputCode)) { + artifact.put("name", "dialog-conclusion.md"); + artifact.put("url", "/workspace/dialog-conclusion.md"); + return artifact; + } + + switch (outputCode) { + case "git_branch" -> { + artifact.put("name", "git-branch.txt"); + artifact.put("url", "/workspace/git-branch.txt"); + } + case "pull_request" -> { + artifact.put("name", "pull-request.txt"); + artifact.put("url", "/workspace/pull-request.txt"); + } + case "python_file" -> { + artifact.put("name", "output.py"); + artifact.put("url", "/workspace/output.py"); + } + case "java_file" -> { + artifact.put("name", "Output.java"); + artifact.put("url", "/workspace/Output.java"); + } + case "javascript_file" -> { + artifact.put("name", "output.js"); + artifact.put("url", "/workspace/output.js"); + } + case "typescript_file" -> { + artifact.put("name", "output.ts"); + artifact.put("url", "/workspace/output.ts"); + } + case "sql_file" -> { + artifact.put("name", "output.sql"); + artifact.put("url", "/workspace/output.sql"); + } + case "shell_script" -> { + artifact.put("name", "output.sh"); + artifact.put("url", "/workspace/output.sh"); + } + case "config_file" -> { + artifact.put("name", "output.yaml"); + artifact.put("url", "/workspace/output.yaml"); + } + case "txt" -> { + artifact.put("name", "output.txt"); + artifact.put("url", "/workspace/output.txt"); + } + case "markdown" -> { + artifact.put("name", "output.md"); + artifact.put("url", "/workspace/output.md"); + } + case "word" -> { + artifact.put("name", "output.docx"); + artifact.put("url", "/workspace/output.docx"); + } + case "excel" -> { + artifact.put("name", "output.xlsx"); + artifact.put("url", "/workspace/output.xlsx"); + } + case "ppt" -> { + artifact.put("name", "output.pptx"); + artifact.put("url", "/workspace/output.pptx"); + } + case "pdf" -> { + artifact.put("name", "output.pdf"); + artifact.put("url", "/workspace/output.pdf"); + } + case "json" -> { + artifact.put("name", "output.json"); + artifact.put("url", "/workspace/output.json"); + } + case "csv" -> { + artifact.put("name", "output.csv"); + artifact.put("url", "/workspace/output.csv"); + } + case "png" -> { + artifact.put("name", "output.png"); + artifact.put("url", "/workspace/output.png"); + } + case "archive" -> { + artifact.put("name", "output.zip"); + artifact.put("url", "/workspace/output.zip"); + } + case "api_call_result" -> { + artifact.put("name", "api-result.json"); + artifact.put("url", "/workspace/api-result.json"); + } + case "dialog_conclusion" -> { + artifact.put("name", "dialog-conclusion.md"); + artifact.put("url", "/workspace/dialog-conclusion.md"); + } + default -> { + artifact.put("name", outputCode + ".txt"); + artifact.put("url", "/workspace/" + outputCode + ".txt"); + } + } + return artifact; + } + + private String resolveReportSummary(TaskCompleteRequest.Report workerReport, TaskStatus targetStatus) { + if (workerReport != null && StringUtils.hasText(workerReport.getSummary())) { + return workerReport.getSummary(); + } + return targetStatus == TaskStatus.COMPLETED ? "任务执行完成" : "任务执行失败"; + } + + private Integer resolveReportCompletion(TaskCompleteRequest.Report workerReport, TaskStatus targetStatus) { + if (workerReport != null && workerReport.getCompletion() != null) { + return workerReport.getCompletion(); + } + return targetStatus == TaskStatus.COMPLETED ? 100 : 0; + } + + private String resolveReportAudit(TaskCompleteRequest.Report workerReport, TaskStatus targetStatus) { + if (workerReport != null && StringUtils.hasText(workerReport.getAudit())) { + return workerReport.getAudit(); + } + return targetStatus == TaskStatus.COMPLETED ? "A" : "C"; + } + + private List parseEstimatedOutput(Task task) { + if (task == null || !StringUtils.hasText(task.getConfigJson())) { + return Collections.emptyList(); + } + try { + var configNode = objectMapper.readTree(task.getConfigJson()); + var estimatedOutputNode = configNode.get("estimatedOutput"); + if (estimatedOutputNode == null || !estimatedOutputNode.isArray()) { + return Collections.emptyList(); + } + return objectMapper.convertValue(estimatedOutputNode, + objectMapper.getTypeFactory().constructCollectionType(List.class, String.class)); + } catch (Exception e) { + log.warn("解析 estimatedOutput 失败: taskNo={}, error={}", task.getTaskNo(), e.getMessage()); + return Collections.emptyList(); + } + } + + private List resolveOutputDomains(List estimatedOutput) { + if (estimatedOutput == null || estimatedOutput.isEmpty()) { + return Collections.emptyList(); + } + + LinkedHashSet domains = new LinkedHashSet<>(); + for (String outputCode : estimatedOutput) { + TaskOutputType.fromCode(outputCode).map(TaskOutputType::getDomain).ifPresent(domains::add); + } + return new ArrayList<>(domains); + } + + private List> buildDispatchGitConfig(Task task) { + if (!StringUtils.hasText(task.getConfigJson())) { + return Collections.emptyList(); + } + + try { + var configNode = objectMapper.readTree(task.getConfigJson()); + var gitRepos = configNode.get("gitRepos"); + if (gitRepos == null || !gitRepos.isArray() || gitRepos.isEmpty()) { + return Collections.emptyList(); + } + + List> result = new ArrayList<>(); + for (var repoNode : gitRepos) { + String repo = repoNode.has("url") ? repoNode.get("url").asText("").trim() : ""; + String originBranch = repoNode.has("branch") ? repoNode.get("branch").asText("").trim() : ""; + if (!StringUtils.hasText(repo) || !StringUtils.hasText(originBranch)) { + log.warn("跳过无效 gitRepos 配置: taskNo={}, repo={}, branch={}", + task.getTaskNo(), repo, originBranch); + continue; + } + + Map gitConfig = new HashMap<>(); + gitConfig.put("repo", repo); + gitConfig.put("origin_branch", originBranch); + gitConfig.put("task_branch", "feat/" + task.getTaskNo()); + result.add(gitConfig); + } + return result; + } catch (Exception e) { + log.warn("解析任务 gitRepos 失败,忽略 git_config 下发: taskNo={}, error={}", + task.getTaskNo(), e.getMessage()); + return Collections.emptyList(); + } + } + + private String resolveDispatchDeliveryMode(Task task, List> gitConfig) { + String snapshotMode = parseDeliveryModeFromConfig(task); + if (StringUtils.hasText(snapshotMode)) { + return normalizeDeliveryMode(snapshotMode, task.getTaskNo()); + } + return resolveDeliveryMode(parseEstimatedOutput(task), !gitConfig.isEmpty(), task.getTaskNo()); + } + + private String parseDeliveryModeFromConfig(Task task) { + if (task == null || !StringUtils.hasText(task.getConfigJson())) { + return ""; + } + try { + var configNode = objectMapper.readTree(task.getConfigJson()); + var deliveryModeNode = configNode.get("deliveryMode"); + if (deliveryModeNode != null && deliveryModeNode.isTextual()) { + return deliveryModeNode.asText(); + } + } catch (Exception e) { + log.warn("解析 deliveryMode 失败: taskNo={}, error={}", task.getTaskNo(), e.getMessage()); + } + return ""; + } + + private String resolveDeliveryMode(List estimatedOutput, boolean hasGitConfig, String taskNo) { + if (!hasGitConfig) { + return DELIVERY_MODE_OSS; + } + + if (containsGitDeliverySignal(estimatedOutput)) { + return DELIVERY_MODE_GIT; + } + + if (estimatedOutput == null || estimatedOutput.isEmpty()) { + log.warn("estimatedOutput 为空但任务存在 git 配置,兼容回退为 git: taskNo={}", taskNo); + return DELIVERY_MODE_GIT; + } + + return DELIVERY_MODE_OSS; + } + + private boolean containsGitDeliverySignal(List estimatedOutput) { + if (estimatedOutput == null || estimatedOutput.isEmpty()) { + return false; + } + for (String outputCode : estimatedOutput) { + if (OUTPUT_CODE_GIT_BRANCH.equalsIgnoreCase(outputCode) + || OUTPUT_CODE_PULL_REQUEST.equalsIgnoreCase(outputCode)) { + return true; + } + } + return false; + } + + private boolean hasValidRoleGitRepo(RoleEntity role) { + if (role == null || role.getConfigJson() == null || role.getConfigJson().getGitRepos() == null) { + return false; + } + return role.getConfigJson().getGitRepos().stream().anyMatch( + gitRepo -> gitRepo != null + && StringUtils.hasText(gitRepo.getUrl()) + && StringUtils.hasText(gitRepo.getBranch())); + } + + private String normalizeDeliveryMode(String rawMode, String taskNo) { + if (!StringUtils.hasText(rawMode)) { + return DELIVERY_MODE_OSS; + } + String mode = rawMode.trim().toLowerCase(); + if (DELIVERY_MODE_GIT.equals(mode) || DELIVERY_MODE_OSS.equals(mode)) { + return mode; + } + log.warn("未知 deliveryMode,回退为 oss: taskNo={}, rawMode={}", taskNo, rawMode); + return DELIVERY_MODE_OSS; + } + + private record TaskInputFileRef( + String fileId, + String originalFileName, + String runtimePath, + String realPath) { + } + + private record PromptLayers( + String rolePrompt, + String platformPrompt, + String userSoul) { + } + + private record TerminateEnqueueResult(String queueKey, String requestId) {} + + private void publishTaskEvent(String streamKey, String eventType, String taskNo, Map data) { + Map fields = new HashMap<>(); + fields.put("event_type", eventType); + fields.put("task_no", taskNo); + fields.put("timestamp", LocalDateTime.now().toString()); + + if (data != null && !data.isEmpty()) { + try { + fields.put("data", objectMapper.writeValueAsString(data)); + } catch (JsonProcessingException e) { + throw new IllegalStateException("序列化任务事件失败: eventType=" + eventType + ", taskNo=" + taskNo, e); + } + } + + redisTemplate.opsForStream().add(streamKey, fields); + } + + /** + * 格式化时长 + */ + private String formatDuration(Long durationMs) { + if (durationMs == null || durationMs == 0) { + return "0s"; + } + long seconds = durationMs / 1000; + if (seconds < 60) { + return seconds + "s"; + } + long minutes = seconds / 60; + seconds = seconds % 60; + return minutes + "m " + seconds + "s"; + } +} diff --git a/back/src/main/java/com/linkwork/service/TaskShareLinkService.java b/back/src/main/java/com/linkwork/service/TaskShareLinkService.java new file mode 100644 index 0000000..b31f45e --- /dev/null +++ b/back/src/main/java/com/linkwork/service/TaskShareLinkService.java @@ -0,0 +1,166 @@ +package com.linkwork.service; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.linkwork.model.dto.TaskShareLinkResponse; +import com.linkwork.model.entity.Task; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.util.UriUtils; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.Base64; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; + +/** + * 任务临时分享链接服务(签名 token,无状态校验)。 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class TaskShareLinkService { + + @Value("${robot.task-share.secret:}") + private String shareSecret; + + @Value("${robot.task-share.base-url:}") + private String shareBaseUrl; + + @Value("${robot.task-share.default-expire-hours:24}") + private int defaultExpireHours; + + @Value("${robot.task-share.max-expire-hours:168}") + private int maxExpireHours; + + private final TaskService taskService; + private final ObjectMapper objectMapper; + + public TaskShareLinkResponse createShareLink(String taskNo, String creatorId, Integer expireHours) { + Task task = taskService.getTaskByNo(taskNo, creatorId); + int resolvedExpireHours = resolveExpireHours(expireHours); + Instant expiresAt = Instant.now().plusSeconds(resolvedExpireHours * 3600L); + + String token = buildToken(task.getTaskNo(), expiresAt.getEpochSecond()); + TaskShareLinkResponse response = new TaskShareLinkResponse(); + response.setTaskId(task.getTaskNo()); + response.setToken(token); + response.setShareUrl(buildShareUrl(task.getTaskNo(), token)); + response.setExpiresAt(LocalDateTime.ofInstant(expiresAt, ZoneOffset.UTC)); + return response; + } + + public void validateShareToken(String taskNo, String token) { + if (!StringUtils.hasText(taskNo) || !StringUtils.hasText(token)) { + throw new IllegalArgumentException("分享链接参数缺失"); + } + String[] parts = token.split("\\."); + if (parts.length != 2) { + throw new IllegalArgumentException("分享链接无效"); + } + + String payloadEncoded = parts[0]; + String expectedSignature = signPayload(payloadEncoded); + String actualSignature = parts[1]; + if (!MessageDigest.isEqual( + expectedSignature.getBytes(StandardCharsets.UTF_8), + actualSignature.getBytes(StandardCharsets.UTF_8))) { + throw new IllegalArgumentException("分享链接无效"); + } + + try { + String payloadJson = new String(Base64.getUrlDecoder().decode(payloadEncoded), StandardCharsets.UTF_8); + Map payload = objectMapper.readValue(payloadJson, new TypeReference>() { + }); + + String tokenTaskNo = String.valueOf(payload.get("taskNo")); + long exp = parseLong(payload.get("exp")); + if (!taskNo.equals(tokenTaskNo)) { + throw new IllegalArgumentException("分享链接与任务不匹配"); + } + if (exp <= Instant.now().getEpochSecond()) { + throw new IllegalArgumentException("分享链接已过期"); + } + } catch (IllegalArgumentException e) { + throw e; + } catch (Exception e) { + throw new IllegalArgumentException("分享链接无效"); + } + } + + private int resolveExpireHours(Integer expireHours) { + int resolved = expireHours == null ? defaultExpireHours : expireHours; + if (resolved <= 0) { + resolved = defaultExpireHours; + } + if (resolved > maxExpireHours) { + resolved = maxExpireHours; + } + return resolved; + } + + private String buildToken(String taskNo, long expEpochSecond) { + try { + Map payload = new LinkedHashMap<>(); + payload.put("taskNo", taskNo); + payload.put("exp", expEpochSecond); + payload.put("nonce", UUID.randomUUID().toString().replace("-", "")); + String payloadJson = objectMapper.writeValueAsString(payload); + String payloadEncoded = Base64.getUrlEncoder() + .withoutPadding() + .encodeToString(payloadJson.getBytes(StandardCharsets.UTF_8)); + return payloadEncoded + "." + signPayload(payloadEncoded); + } catch (Exception e) { + throw new IllegalStateException("生成任务分享链接失败: taskNo=" + taskNo, e); + } + } + + private String signPayload(String payloadEncoded) { + try { + Mac mac = Mac.getInstance("HmacSHA256"); + String secret = requireShareSecret(); + mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); + byte[] signature = mac.doFinal(payloadEncoded.getBytes(StandardCharsets.UTF_8)); + return Base64.getUrlEncoder().withoutPadding().encodeToString(signature); + } catch (Exception e) { + throw new IllegalStateException("签名分享链接失败", e); + } + } + + private String buildShareUrl(String taskNo, String token) { + String base = shareBaseUrl == null ? "" : shareBaseUrl.trim(); + if (base.endsWith("/")) { + base = base.substring(0, base.length() - 1); + } + String encodedTaskNo = UriUtils.encodePathSegment(taskNo, StandardCharsets.UTF_8); + String encodedToken = UriUtils.encodeQueryParam(token, StandardCharsets.UTF_8); + return String.format("%s/share/task/%s?token=%s", base, encodedTaskNo, encodedToken); + } + + private String requireShareSecret() { + if (!StringUtils.hasText(shareSecret)) { + throw new IllegalStateException("robot.task-share.secret 未配置"); + } + return shareSecret.trim(); + } + + private long parseLong(Object value) { + if (value == null) { + throw new IllegalArgumentException("分享链接无效"); + } + if (value instanceof Number number) { + return number.longValue(); + } + return Long.parseLong(String.valueOf(value)); + } +} diff --git a/back/src/main/java/com/linkwork/service/TaskStatusSyncService.java b/back/src/main/java/com/linkwork/service/TaskStatusSyncService.java new file mode 100644 index 0000000..1743411 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/TaskStatusSyncService.java @@ -0,0 +1,500 @@ +package com.linkwork.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.linkwork.config.DispatchConfig; +import com.linkwork.model.dto.TaskCompleteRequest; +import com.linkwork.model.dto.TaskResponse; +import com.linkwork.model.entity.Task; +import com.linkwork.model.enums.TaskStatus; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.connection.stream.MapRecord; +import org.springframework.data.redis.connection.stream.StreamOffset; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 任务状态同步服务 + * + * 监听 Redis Stream 中的关键事件,将任务状态同步到数据库。 + * + * 背景:momo-worker (外部 Agent 执行器) 执行任务时只向 Redis Stream 写入事件, + * 不会回调后端更新数据库。导致数据库中的任务状态永远停留在 PENDING。 + * + * 本服务既在 WebSocket 转发事件时实时同步,也通过后台定时扫描做兜底补偿, + * 避免无人订阅时终态无法落库。 + * + * 事件 → 状态映射: + * TASK_STARTED / SESSION_START → RUNNING + * TASK_COMPLETED / SESSION_END (exit_code=0) → COMPLETED + * TASK_FAILED / SESSION_END (exit_code!=0) → FAILED + * TASK_ABORTED → ABORTED + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class TaskStatusSyncService { + + private final TaskService taskService; + private final StringRedisTemplate redisTemplate; + private final DispatchConfig dispatchConfig; + private final ObjectMapper objectMapper = new ObjectMapper(); + + /** + * 启动时扫描数据库中 PENDING / RUNNING 状态的任务, + * 从 Redis Stream 中读取历史事件补齐状态。 + */ + @PostConstruct + public void syncHistoryOnStartup() { + Thread.ofVirtual().name("task-status-sync-init").start(() -> { + try { + // 等待 Spring 容器完全启动 + Thread.sleep(5000); + syncActiveTasks("startup"); + } catch (Exception e) { + log.error("启动时状态补齐异常", e); + } + }); + } + + /** + * 后台常驻扫描(不依赖 WebSocket 订阅): + * 周期性补齐 PENDING / RUNNING 任务状态,避免终态长期未落库。 + */ + @Scheduled(fixedDelayString = "${robot.task-status-sync.scan-interval-ms:15000}") + public void syncHistoryPeriodically() { + syncActiveTasks("schedule"); + } + + private void syncActiveTasks(String trigger) { + int scanned = 0; + int synced = 0; + + for (TaskStatus status : ACTIVE_STATES) { + long current = 1; + while (true) { + Page page = taskService.listTasks(null, status.name(), (int) current, TASK_SCAN_PAGE_SIZE); + List records = page.getRecords(); + if (records == null || records.isEmpty()) { + break; + } + scanned += records.size(); + for (Task task : records) { + if (syncSingleTask(task, trigger)) { + synced++; + } + } + if (current >= page.getPages()) { + break; + } + current++; + } + } + + if (synced > 0) { + log.info("任务状态补齐完成: trigger={}, synced={}, scanned={}", trigger, synced, scanned); + } else if ("startup".equals(trigger)) { + log.info("启动补齐完成: trigger={}, synced=0, scanned={}", trigger, scanned); + } + } + + private boolean syncSingleTask(Task task, String trigger) { + TaskStatus currentStatus = task.getStatus(); + TaskStatus resolved = resolveStatusFromStream(task.getTaskNo()); + if (resolved == null || resolved == currentStatus) { + return false; + } + try { + UsageSnapshot usageSnapshot = resolveUsageFromStream(task.getTaskNo()); + PersistResult persistResult = persistStatusWithUsage( + task.getTaskNo(), + resolved, + usageSnapshot.tokensUsed(), + usageSnapshot.durationMs(), + "sync-" + trigger); + Task updatedTask = persistResult.task(); + syncedStatus.put(task.getTaskNo(), resolved); + if (!persistResult.notifiedByCompleteFlow()) { + log.debug("任务终态由同步流程补齐: trigger={}, taskNo={}, status={}", + trigger, task.getTaskNo(), resolved); + } + log.info("任务状态补齐: trigger={}, taskNo={}, {} -> {}", + trigger, task.getTaskNo(), currentStatus, resolved); + return true; + } catch (Exception e) { + log.error("补齐任务状态失败: trigger={}, taskNo={}, from={}, to={}", + trigger, task.getTaskNo(), currentStatus, resolved, e); + return false; + } + } + + /** + * 从 Redis Stream 历史事件中推断任务的最终状态 + */ + private TaskStatus resolveStatusFromStream(String taskNo) { + Long roleId = resolveRoleId(taskNo); + List streamKeys = List.of( + dispatchConfig.getLogStreamKey(roleId, taskNo), + "stream:task:" + taskNo + ":events", + "stream:task:" + taskNo + ); + + TaskStatus best = null; + for (String streamKey : streamKeys) { + try { + List> records = redisTemplate.opsForStream() + .read(StreamOffset.fromStart(streamKey)); + if (records == null || records.isEmpty()) continue; + + for (MapRecord record : records) { + Map eventData = extractEventData(record); + + String eventType = String.valueOf(eventData.getOrDefault("event_type", "")); + TaskStatus status = resolveTargetStatus(eventType, eventData); + if (shouldUpdateStatus(best, status)) { + best = status; + } + } + } catch (Exception e) { + log.debug("读取 Stream {} 失败: {}", streamKey, e.getMessage()); + } + } + return best; + } + + @SuppressWarnings("unchecked") + private Map extractEventData(MapRecord record) { + Map rawEvent = new HashMap<>(); + record.getValue().forEach((k, v) -> rawEvent.put(k.toString(), v)); + + Object payloadObj = rawEvent.get("payload"); + if (payloadObj instanceof String payloadStr && payloadStr.startsWith("{")) { + try { + Map payloadMap = objectMapper.readValue(payloadStr, Map.class); + Object innerData = payloadMap.get("data"); + if (innerData instanceof String dataStr && (dataStr.startsWith("{") || dataStr.startsWith("["))) { + try { + payloadMap.put("data", objectMapper.readValue(dataStr, Object.class)); + } catch (Exception ignored) {} + } + Object payloadData = payloadMap.get("data"); + if (payloadData instanceof Map dataMap) { + dataMap.forEach((k, v) -> payloadMap.putIfAbsent(String.valueOf(k), v)); + } + return payloadMap; + } catch (Exception e) { + log.debug("解析 payload 失败,回退扁平事件: {}", e.getMessage()); + } + } + + Object dataObj = rawEvent.get("data"); + if (dataObj instanceof String dataStr && (dataStr.startsWith("{") || dataStr.startsWith("["))) { + try { + rawEvent.put("data", objectMapper.readValue(dataStr, Object.class)); + } catch (Exception ignored) {} + } + + Object flatData = rawEvent.get("data"); + if (flatData instanceof Map dataMap) { + dataMap.forEach((k, v) -> rawEvent.putIfAbsent(String.valueOf(k), v)); + } + return rawEvent; + } + + private Long resolveRoleId(String taskNo) { + try { + Task task = taskService.getTaskByNo(taskNo); + return task.getRoleId(); + } catch (Exception e) { + log.debug("解析任务 roleId 失败,使用空 roleId 路由: taskNo={}", taskNo); + return null; + } + } + + /** + * 记录每个任务已同步到的最终状态,避免重复更新和状态回退。 + * key=taskNo, value=已同步的最高优先级状态 + */ + private final ConcurrentHashMap syncedStatus = new ConcurrentHashMap<>(); + + /** + * 终态集合:任务到达这些状态后不再变更 + */ + private static final Set TERMINAL_STATES = Set.of( + TaskStatus.COMPLETED, TaskStatus.FAILED, TaskStatus.ABORTED + ); + private static final List ACTIVE_STATES = List.of(TaskStatus.PENDING, TaskStatus.RUNNING); + private static final int TASK_SCAN_PAGE_SIZE = 200; + + /** + * 处理一条 Redis Stream 事件,判断是否需要同步任务状态 + * + * @param taskNo 任务编号 + * @param eventData 事件数据(从 Redis Stream record 解析) + */ + public void onEvent(String taskNo, Map eventData) { + if (taskNo == null || taskNo.isEmpty() || eventData == null) return; + + String eventType = String.valueOf(eventData.getOrDefault("event_type", "")); + TaskStatus targetStatus = resolveTargetStatus(eventType, eventData); + + if (targetStatus == null) return; + + // 执行数据库更新 + try { + Integer tokensUsed = resolveTokensUsedFromEvent(eventData); + Long durationMs = resolveDurationMsFromEvent(eventData); + + Task currentTask = taskService.getTaskByNo(taskNo); + TaskStatus currentStatus = currentTask.getStatus(); + boolean shouldSyncStatus = shouldUpdateStatus(currentStatus, targetStatus); + boolean shouldBackfillUsage = shouldBackfillUsage(currentTask, tokensUsed, durationMs); + + // 历史任务详情反复打开会重放 Stream 事件,这里必须幂等避免重复通知。 + if (!shouldSyncStatus && !shouldBackfillUsage) { + return; + } + + TaskStatus statusToPersist = shouldSyncStatus ? targetStatus : currentStatus; + PersistResult persistResult = persistStatusWithUsage(taskNo, statusToPersist, tokensUsed, durationMs, "event"); + Task updatedTask = persistResult.task(); + syncedStatus.put(taskNo, statusToPersist); + if (shouldSyncStatus && !persistResult.notifiedByCompleteFlow()) { + log.debug("任务终态由事件同步补齐: taskNo={}, status={}", updatedTask.getTaskNo(), statusToPersist); + } + log.info("任务状态已同步: taskNo={}, event={}, status={}", taskNo, eventType, targetStatus); + + // 如果到达终态,延迟清理内存(避免内存泄漏) + if (TERMINAL_STATES.contains(statusToPersist)) { + // 保留 5 分钟后清理,防止短期内重复事件触发 + Thread.ofVirtual().start(() -> { + try { + Thread.sleep(300_000); // 5 min + } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); + } + syncedStatus.remove(taskNo); + }); + } + } catch (Exception e) { + log.error("同步任务状态失败: taskNo={}, targetStatus={}", taskNo, targetStatus, e); + } + } + + private PersistResult persistStatusWithUsage(String taskNo, + TaskStatus statusToPersist, + Integer tokensUsed, + Long durationMs, + String trigger) { + Integer safeTokens = tokensUsed != null && tokensUsed >= 0 ? tokensUsed : 0; + Long safeDurationMs = durationMs != null && durationMs >= 0 ? durationMs : 0L; + + if (statusToPersist == TaskStatus.COMPLETED || statusToPersist == TaskStatus.FAILED) { + try { + Task currentTask = taskService.getTaskByNo(taskNo); + if (currentTask.getStatus() == TaskStatus.PENDING + || currentTask.getStatus() == TaskStatus.RUNNING + || currentTask.getStatus() == TaskStatus.PENDING_AUTH) { + TaskCompleteRequest completeRequest = new TaskCompleteRequest(); + completeRequest.setStatus(statusToPersist.name()); + completeRequest.setTokensUsed(safeTokens); + completeRequest.setDurationMs(safeDurationMs); + Task completedTask = taskService.completeTask(taskNo, completeRequest); + log.info("状态同步触发 completeTask 补全: trigger={}, taskNo={}, status={}, tokens={}, durationMs={}", + trigger, taskNo, statusToPersist, safeTokens, safeDurationMs); + return new PersistResult(completedTask, true); + } + } catch (Exception ex) { + log.warn("状态同步 completeTask 补全失败,降级 updateStatusWithUsage: trigger={}, taskNo={}, status={}, err={}", + trigger, taskNo, statusToPersist, ex.getMessage()); + } + } + Task updatedTask = taskService.updateStatusWithUsage(taskNo, statusToPersist, safeTokens, safeDurationMs); + return new PersistResult(updatedTask, false); + } + + private boolean shouldBackfillUsage(Task task, Integer eventTokensUsed, Long eventDurationMs) { + if (task == null) { + return false; + } + if (TERMINAL_STATES.contains(task.getStatus()) && needsBillingBackfill(task)) { + return true; + } + boolean canBackfillTokens = eventTokensUsed != null + && eventTokensUsed >= 0 + && (task.getTokensUsed() == null || task.getTokensUsed() <= 0); + boolean canBackfillDuration = eventDurationMs != null + && eventDurationMs > 0 + && (task.getDurationMs() == null || task.getDurationMs() <= 0); + return canBackfillTokens || canBackfillDuration; + } + + private boolean needsBillingBackfill(Task task) { + boolean missingTokens = task.getTokensUsed() == null || task.getTokensUsed() <= 0; + boolean missingUsageFields = task.getInputTokens() == null + || task.getOutputTokens() == null + || task.getRequestCount() == null + || task.getTokenLimit() == null + || task.getUsagePercent() == null; + return missingTokens || missingUsageFields; + } + + private UsageSnapshot resolveUsageFromStream(String taskNo) { + Long roleId = resolveRoleId(taskNo); + List streamKeys = List.of( + dispatchConfig.getLogStreamKey(roleId, taskNo), + "stream:task:" + taskNo + ":events", + "stream:task:" + taskNo + ); + + Integer tokensUsed = null; + Long durationMs = null; + for (String streamKey : streamKeys) { + try { + List> records = redisTemplate.opsForStream() + .read(StreamOffset.fromStart(streamKey)); + if (records == null || records.isEmpty()) { + continue; + } + + for (MapRecord record : records) { + Map eventData = extractEventData(record); + Integer eventTokens = resolveTokensUsedFromEvent(eventData); + Long eventDuration = resolveDurationMsFromEvent(eventData); + if (eventTokens != null && eventTokens >= 0) { + tokensUsed = eventTokens; + } + if (eventDuration != null && eventDuration > 0) { + durationMs = eventDuration; + } + } + } catch (Exception e) { + log.debug("读取 Stream usage 失败: streamKey={}, error={}", streamKey, e.getMessage()); + } + } + + return new UsageSnapshot(tokensUsed, durationMs); + } + + private Integer resolveTokensUsedFromEvent(Map eventData) { + Long value = resolveLongByKeys(eventData, "tokens_used", "tokensUsed", "token_usage"); + if (value == null || value < 0 || value > Integer.MAX_VALUE) { + return null; + } + return value.intValue(); + } + + private Long resolveDurationMsFromEvent(Map eventData) { + return resolveLongByKeys(eventData, "duration_ms", "durationMs", "elapsed_ms"); + } + + private Long resolveLongByKeys(Map eventData, String... keys) { + if (eventData == null || keys == null) { + return null; + } + for (String key : keys) { + Object raw = eventData.get(key); + if (raw == null) { + continue; + } + try { + return Long.parseLong(String.valueOf(raw)); + } catch (NumberFormatException ignored) { + } + } + return null; + } + + private record UsageSnapshot(Integer tokensUsed, Long durationMs) {} + private record PersistResult(Task task, boolean notifiedByCompleteFlow) {} + + /** + * 根据事件类型和数据推断目标任务状态 + */ + private TaskStatus resolveTargetStatus(String eventType, Map eventData) { + String normalizedEventType = eventType == null ? "" : eventType.trim().toUpperCase(Locale.ROOT); + return switch (normalizedEventType) { + // → RUNNING + case "TASK_STARTED", "SESSION_START" -> TaskStatus.RUNNING; + + // → COMPLETED or FAILED(根据 exit_code 判断) + case "SESSION_END" -> { + Object exitCodeObj = eventData.get("exit_code"); + if (exitCodeObj != null) { + try { + int exitCode = Integer.parseInt(String.valueOf(exitCodeObj)); + yield exitCode == 0 ? TaskStatus.COMPLETED : TaskStatus.FAILED; + } catch (NumberFormatException e) { + yield null; + } + } + // SESSION_END 未携带 exit_code 时不推断终态,等待 TASK_COMPLETED/TASK_FAILED/WORKSPACE_ARCHIVED + yield null; + } + + // → COMPLETED + case "TASK_COMPLETED" -> TaskStatus.COMPLETED; + + // → FAILED + case "TASK_FAILED" -> TaskStatus.FAILED; + + // → ABORTED + case "TASK_ABORTED", "TASK_TERMINATED" -> TaskStatus.ABORTED; + + // WORKSPACE 归档事件,status=failed/completed/aborted + case "WORKSPACE_ARCHIVED" -> { + String archivedStatus = String.valueOf(eventData.getOrDefault("status", "")).toLowerCase(Locale.ROOT); + if ("failed".equals(archivedStatus)) { + yield TaskStatus.FAILED; + } + if ("completed".equals(archivedStatus) || "success".equals(archivedStatus)) { + yield TaskStatus.COMPLETED; + } + if ("aborted".equals(archivedStatus) || "cancelled".equals(archivedStatus) + || "canceled".equals(archivedStatus)) { + yield TaskStatus.ABORTED; + } + yield null; + } + + // → RUNNING(终止请求已被执行端接收,但尚未完成终止) + case "TASK_ABORT_ACK" -> TaskStatus.RUNNING; + + // 其他事件:如果是首次出现的工具调用/思考等事件,说明任务已开始执行 + case "TOOL_CALL", "TOOL_RESULT", "THINKING", "ASSISTANT_TEXT", + "SECURITY_ALLOW", "SECURITY_DENY" -> TaskStatus.RUNNING; + + // 不需要同步的事件 + default -> null; + }; + } + + private boolean shouldUpdateStatus(TaskStatus current, TaskStatus target) { + if (target == null) { + return false; + } + if (current == null) { + return true; + } + if (current == target) { + return false; + } + return statusPriority(target) > statusPriority(current); + } + + private int statusPriority(TaskStatus status) { + return switch (status) { + case PENDING -> 0; + case RUNNING, PENDING_AUTH -> 10; + case COMPLETED -> 20; + case FAILED -> 30; + case ABORTED -> 40; + }; + } +} diff --git a/back/src/main/java/com/linkwork/service/UserSoulService.java b/back/src/main/java/com/linkwork/service/UserSoulService.java new file mode 100644 index 0000000..994e36a --- /dev/null +++ b/back/src/main/java/com/linkwork/service/UserSoulService.java @@ -0,0 +1,173 @@ +package com.linkwork.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.linkwork.mapper.UserSoulMapper; +import com.linkwork.model.dto.UserSoulResponse; +import com.linkwork.model.dto.UserSoulUpsertRequest; +import com.linkwork.model.entity.UserSoulEntity; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +@Service +@RequiredArgsConstructor +public class UserSoulService { + + private static final int MAX_CONTENT_LENGTH = 8000; + private static final int MAX_PRESET_ID_LENGTH = 64; + private static final int MAX_OPERATOR_NAME_LENGTH = 100; + + private final UserSoulMapper userSoulMapper; + + public UserSoulResponse getCurrentUserSoul(String userId) { + UserSoulEntity entity = findByUserId(userId); + if (entity == null) { + throw new IllegalArgumentException("SOUL_NOT_FOUND: 当前用户尚未配置 Soul"); + } + return toResponse(entity); + } + + public String getRequiredSoulContent(String userId) { + UserSoulEntity entity = findByUserId(userId); + if (entity == null) { + throw new IllegalArgumentException("SOUL_NOT_FOUND: 当前用户尚未配置 Soul"); + } + String content = normalizeContent(entity.getContent()); + if (!StringUtils.hasText(content)) { + throw new IllegalArgumentException("SOUL_CONTENT_INVALID: Soul 内容不能为空"); + } + return content; + } + + public String getOptionalSoulContent(String userId) { + UserSoulEntity entity = findByUserId(userId); + if (entity == null) { + return ""; + } + return normalizeContent(entity.getContent()); + } + + @Transactional + public UserSoulResponse upsertCurrentUserSoul(String userId, String userName, UserSoulUpsertRequest request) { + String content = normalizeContent(request.getContent()); + validateContent(content); + String operatorName = normalizeOperatorName(userName, userId); + + UserSoulEntity existing = findByUserId(userId); + if (existing == null) { + if (request.getVersion() != 0L) { + throw new IllegalArgumentException("SOUL_VERSION_CONFLICT: 首次保存 version 必须为 0"); + } + UserSoulEntity entity = new UserSoulEntity(); + entity.setUserId(userId); + entity.setContent(content); + entity.setPresetId(normalizePresetId(request.getPresetId())); + entity.setVersion(1L); + entity.setCreatorId(userId); + entity.setCreatorName(operatorName); + entity.setUpdaterId(userId); + entity.setUpdaterName(operatorName); + int inserted = userSoulMapper.insert(entity); + if (inserted != 1) { + throw new IllegalStateException("创建用户 Soul 失败: userId=" + userId); + } + return toResponse(entity); + } + + Long storedVersion = existing.getVersion(); + Long currentVersion = normalizeVersion(storedVersion); + if (!currentVersion.equals(request.getVersion())) { + throw new IllegalArgumentException("SOUL_VERSION_CONFLICT: Soul 已被更新,请刷新后重试"); + } + + long nextVersion = currentVersion + 1; + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper() + .set(UserSoulEntity::getContent, content) + .set(UserSoulEntity::getPresetId, normalizePresetId(request.getPresetId())) + .set(UserSoulEntity::getUpdaterId, userId) + .set(UserSoulEntity::getUpdaterName, operatorName) + .set(UserSoulEntity::getVersion, nextVersion); + if (existing.getId() != null) { + wrapper.eq(UserSoulEntity::getId, existing.getId()); + } else { + wrapper.eq(UserSoulEntity::getUserId, userId); + } + if (storedVersion == null) { + wrapper.isNull(UserSoulEntity::getVersion); + } else { + wrapper.eq(UserSoulEntity::getVersion, storedVersion); + } + int updated = userSoulMapper.update(null, wrapper); + if (updated != 1) { + throw new IllegalArgumentException("SOUL_VERSION_CONFLICT: Soul 已被更新,请刷新后重试"); + } + UserSoulEntity refreshed = findByUserId(userId); + if (refreshed == null) { + throw new IllegalStateException("更新后读取用户 Soul 失败: userId=" + userId); + } + return toResponse(refreshed); + } + + private UserSoulEntity findByUserId(String userId) { + if (!StringUtils.hasText(userId)) { + throw new IllegalArgumentException("用户未登录或登录态失效"); + } + return userSoulMapper.selectOne(new LambdaQueryWrapper() + .eq(UserSoulEntity::getUserId, userId) + .orderByDesc(UserSoulEntity::getUpdatedAt) + .orderByDesc(UserSoulEntity::getId) + .last("limit 1")); + } + + private String normalizeContent(String content) { + return content == null ? "" : content.trim(); + } + + private String normalizePresetId(String presetId) { + if (!StringUtils.hasText(presetId)) { + return null; + } + String normalized = presetId.trim(); + if (normalized.length() > MAX_PRESET_ID_LENGTH) { + throw new IllegalArgumentException("SOUL_CONTENT_INVALID: presetId 长度不能超过 " + MAX_PRESET_ID_LENGTH); + } + return normalized; + } + + private String normalizeOperatorName(String userName, String fallbackUserId) { + String normalized = StringUtils.hasText(userName) ? userName.trim() : fallbackUserId; + if (!StringUtils.hasText(normalized)) { + return ""; + } + return normalized.length() <= MAX_OPERATOR_NAME_LENGTH + ? normalized + : normalized.substring(0, MAX_OPERATOR_NAME_LENGTH); + } + + private void validateContent(String content) { + if (!StringUtils.hasText(content)) { + throw new IllegalArgumentException("SOUL_CONTENT_INVALID: Soul 内容不能为空"); + } + if (content.length() > MAX_CONTENT_LENGTH) { + throw new IllegalArgumentException("SOUL_CONTENT_INVALID: Soul 内容长度不能超过 " + MAX_CONTENT_LENGTH); + } + } + + private UserSoulResponse toResponse(UserSoulEntity entity) { + UserSoulResponse response = new UserSoulResponse(); + response.setContent(entity.getContent()); + response.setPresetId(entity.getPresetId()); + response.setVersion(normalizeVersion(entity.getVersion())); + response.setUpdatedAt(entity.getUpdatedAt()); + return response; + } + + private Long normalizeVersion(Long version) { + if (version == null || version < 0) { + return 0L; + } + return version; + } +} diff --git a/back/src/main/java/com/linkwork/service/WorkspaceService.java b/back/src/main/java/com/linkwork/service/WorkspaceService.java new file mode 100644 index 0000000..2e86f59 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/WorkspaceService.java @@ -0,0 +1,54 @@ +package com.linkwork.service; + +import com.linkwork.model.FileNode; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +@Service +public class WorkspaceService { + + public List listFiles(String taskId) { + // 在真实场景中,这里会读取 /tmp/workspace/{taskId} 目录 + // 现在返回与前端一致的 Mock 数据以供联调 + List files = new ArrayList<>(); + + FileNode src = FileNode.builder() + .name("src") + .type("directory") + .children(new ArrayList<>()) + .build(); + + src.getChildren().add(FileNode.builder() + .name("main.py") + .type("file") + .content("import os\nimport time\n\ndef main():\n print(\"Robot Agent starting...\")\n time.sleep(1)\n print(\"Initialization complete.\")\n\nif __name__ == \"__main__\":\n main()") + .size("1.2KB") + .build()); + + src.getChildren().add(FileNode.builder() + .name("utils.py") + .type("file") + .content("def format_bytes(size):\n power = 2**10\n n = 0\n power_labels = {0 : '', 1: 'K', 2: 'M', 3: 'G'}\n while size > power:\n size /= power\n n += 1\n return f\"{size:.2f} {power_labels[n]}B\"") + .size("0.8KB") + .build()); + + files.add(src); + files.add(FileNode.builder() + .name("requirements.txt") + .type("file") + .content("anthropic>=0.40.0\nrequests>=2.31.0\npydantic>=2.5.0\nhttpx>=0.26.0") + .size("0.1KB") + .build()); + + files.add(FileNode.builder() + .name("README.md") + .type("file") + .content("# Robot Agent Project\n\nThis workspace is served from robot-web-service for task: " + taskId) + .size("0.5KB") + .build()); + + return files; + } +} diff --git a/back/src/main/java/com/linkwork/service/ZzdServiceAuthService.java b/back/src/main/java/com/linkwork/service/ZzdServiceAuthService.java new file mode 100644 index 0000000..e3ac982 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/ZzdServiceAuthService.java @@ -0,0 +1,42 @@ +package com.linkwork.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; + +/** + * zzd 服务身份鉴权 + */ +@Slf4j +@Service +public class ZzdServiceAuthService { + + @Value("${robot.zzd.api-server-token:}") + private String apiServerToken; + + public String extractBearerToken(String authorization) { + if (!StringUtils.hasText(authorization) || !authorization.startsWith("Bearer ")) { + return null; + } + String token = authorization.substring(7).trim(); + return StringUtils.hasText(token) ? token : null; + } + + public boolean validateToken(String token) { + if (!StringUtils.hasText(token)) { + return false; + } + if (!StringUtils.hasText(apiServerToken)) { + log.error("ZZD_API_SERVER_TOKEN 未配置,拒绝访问"); + return false; + } + return MessageDigest.isEqual( + apiServerToken.getBytes(StandardCharsets.UTF_8), + token.getBytes(StandardCharsets.UTF_8) + ); + } +} diff --git a/back/src/main/java/com/linkwork/service/memory/DocumentParserService.java b/back/src/main/java/com/linkwork/service/memory/DocumentParserService.java new file mode 100644 index 0000000..5a07709 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/memory/DocumentParserService.java @@ -0,0 +1,65 @@ +package com.linkwork.service.memory; + +import lombok.extern.slf4j.Slf4j; +import org.apache.tika.Tika; +import org.apache.tika.exception.TikaException; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Document parser using Apache Tika. + * Converts PDF, Word, HTML, and other formats to plain text. + */ +@Slf4j +@org.springframework.boot.autoconfigure.condition.ConditionalOnProperty(name = "memory.enabled", havingValue = "true", matchIfMissing = true) +@Service +public class DocumentParserService { + + private final Tika tika = new Tika(); + + /** + * Parse a file to plain text. + * For .md/.txt files, reads raw content directly (preserving markdown structure). + * For other formats (PDF, Word, HTML), uses Tika extraction. + */ + public String parseFile(Path filePath) throws IOException { + String fileName = filePath.getFileName().toString().toLowerCase(); + if (fileName.endsWith(".md") || fileName.endsWith(".markdown") || fileName.endsWith(".txt")) { + return Files.readString(filePath); + } + try (InputStream is = Files.newInputStream(filePath)) { + return tika.parseToString(is); + } catch (TikaException e) { + throw new IOException("Tika parsing failed for " + filePath, e); + } + } + + /** + * Detect file type from extension. + */ + public String detectFileType(Path filePath) { + String name = filePath.getFileName().toString().toLowerCase(); + if (name.endsWith(".md") || name.endsWith(".markdown")) return "markdown"; + if (name.endsWith(".txt")) return "text"; + if (name.endsWith(".pdf")) return "pdf"; + if (name.endsWith(".docx") || name.endsWith(".doc")) return "word"; + if (name.endsWith(".html") || name.endsWith(".htm")) return "html"; + if (name.endsWith(".pptx") || name.endsWith(".ppt")) return "ppt"; + if (name.endsWith(".xlsx") || name.endsWith(".xls")) return "excel"; + return "unknown"; + } + + /** + * Check if a file type is supported for memory indexing. + */ + public boolean isIndexable(String fileType) { + return switch (fileType) { + case "markdown", "text", "pdf", "word", "html" -> true; + default -> false; + }; + } +} diff --git a/back/src/main/java/com/linkwork/service/memory/EmbeddingService.java b/back/src/main/java/com/linkwork/service/memory/EmbeddingService.java new file mode 100644 index 0000000..4035284 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/memory/EmbeddingService.java @@ -0,0 +1,87 @@ +package com.linkwork.service.memory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.linkwork.config.MemoryConfig; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Embedding service that calls the LLM Gateway's OpenAI-compatible embedding endpoint. + */ +@Slf4j +@org.springframework.boot.autoconfigure.condition.ConditionalOnProperty(name = "memory.enabled", havingValue = "true", matchIfMissing = true) +@Service +@RequiredArgsConstructor +public class EmbeddingService { + + private final MemoryConfig memoryConfig; + private final ObjectMapper objectMapper; + private final RestTemplate restTemplate = new RestTemplate(); + + @Value("${schedule.network.llm-gateway-url:http://llm-gateway:8080}") + private String llmGatewayUrl; + + /** + * Generate embeddings for a batch of texts. + * Calls POST {llmGatewayUrl}/v1/embeddings with OpenAI-compatible format. + */ + public List> embed(List texts) { + if (texts == null || texts.isEmpty()) return List.of(); + + String url = llmGatewayUrl + "/v1/embeddings"; + Map body = Map.of( + "model", memoryConfig.getEmbedding().getModel(), + "input", texts + ); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity> entity = new HttpEntity<>(body, headers); + + try { + ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class); + return parseEmbeddingResponse(response.getBody()); + } catch (Exception e) { + log.error("Embedding API call failed: {}", e.getMessage(), e); + throw new RuntimeException("Embedding API call failed", e); + } + } + + /** + * Generate embedding for a single text. + */ + public List embedSingle(String text) { + List> results = embed(List.of(text)); + if (results.isEmpty()) throw new RuntimeException("Empty embedding result"); + return results.get(0); + } + + private List> parseEmbeddingResponse(String responseBody) { + try { + JsonNode root = objectMapper.readTree(responseBody); + JsonNode data = root.get("data"); + List> embeddings = new ArrayList<>(); + for (JsonNode item : data) { + JsonNode embNode = item.get("embedding"); + List embedding = new ArrayList<>(); + for (JsonNode val : embNode) { + embedding.add(val.floatValue()); + } + embeddings.add(embedding); + } + return embeddings; + } catch (Exception e) { + log.error("Failed to parse embedding response: {}", responseBody, e); + throw new RuntimeException("Failed to parse embedding response", e); + } + } +} diff --git a/back/src/main/java/com/linkwork/service/memory/MemoryIndexConsumer.java b/back/src/main/java/com/linkwork/service/memory/MemoryIndexConsumer.java new file mode 100644 index 0000000..049b661 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/memory/MemoryIndexConsumer.java @@ -0,0 +1,95 @@ +package com.linkwork.service.memory; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.linkwork.config.MemoryConfig; +import com.linkwork.model.dto.MemoryIndexJob; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; + +@Slf4j +@org.springframework.boot.autoconfigure.condition.ConditionalOnProperty(name = "memory.enabled", havingValue = "true", matchIfMissing = true) +@Service +@RequiredArgsConstructor +public class MemoryIndexConsumer { + + private final StringRedisTemplate redisTemplate; + private final ObjectMapper objectMapper; + private final MemoryConfig memoryConfig; + private final MemoryService memoryService; + + private final AtomicBoolean running = new AtomicBoolean(true); + private ExecutorService executorService; + + private static final int POLL_INTERVAL_MS = 1000; + + @PostConstruct + public void start() { + if (!memoryConfig.isEnabled()) { + log.info("Memory service disabled, MemoryIndexConsumer not started"); + return; + } + executorService = Executors.newSingleThreadExecutor(r -> { + Thread t = new Thread(r, "memory-index-consumer"); + t.setDaemon(true); + return t; + }); + executorService.submit(this::consumeLoop); + log.info("MemoryIndexConsumer started, queue: {}", memoryConfig.getIndex().getQueueKey()); + } + + @PreDestroy + public void stop() { + running.set(false); + if (executorService != null) { + executorService.shutdownNow(); + } + log.info("MemoryIndexConsumer stopped"); + } + + private void consumeLoop() { + String queueKey = memoryConfig.getIndex().getQueueKey(); + while (running.get()) { + try { + String json = redisTemplate.opsForList().rightPop(queueKey); + if (json != null) { + processJob(json); + } else { + Thread.sleep(POLL_INTERVAL_MS); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } catch (Exception e) { + if (running.get()) { + log.error("Error consuming memory index job", e); + try { + Thread.sleep(POLL_INTERVAL_MS * 2L); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + break; + } + } + } + } + } + + private void processJob(String json) { + try { + MemoryIndexJob job = objectMapper.readValue(json, MemoryIndexJob.class); + log.info("Processing index job: id={}, type={}, source={}", + job.getJobId(), job.getJobType(), job.getSource()); + memoryService.processIndexJob(job); + log.info("Completed index job: id={}", job.getJobId()); + } catch (Exception e) { + log.error("Failed to process index job: {}", json, e); + } + } +} diff --git a/back/src/main/java/com/linkwork/service/memory/MemoryService.java b/back/src/main/java/com/linkwork/service/memory/MemoryService.java new file mode 100644 index 0000000..ad5aeb3 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/memory/MemoryService.java @@ -0,0 +1,248 @@ +package com.linkwork.service.memory; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.linkwork.config.MemoryConfig; +import com.linkwork.model.dto.MemoryIndexJob; +import com.linkwork.mapper.RobotFileMapper; +import com.linkwork.model.dto.MemoryIndexJob.JobType; +import com.linkwork.model.entity.RobotFile; +import com.linkwork.service.NfsStorageService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; + +import java.nio.file.Path; +import java.util.*; + +@Slf4j +@org.springframework.boot.autoconfigure.condition.ConditionalOnProperty(name = "memory.enabled", havingValue = "true", matchIfMissing = true) +@Service +@RequiredArgsConstructor +public class MemoryService { + + private final MemoryConfig memoryConfig; + private final MilvusStoreService milvusStore; + private final TextChunkerService chunker; + private final DocumentParserService documentParser; + private final EmbeddingService embeddingService; + private final StringRedisTemplate redisTemplate; + private final ObjectMapper objectMapper; + + @Autowired(required = false) + private NfsStorageService nfsStorageService; + + @Autowired(required = false) + private RobotFileMapper robotFileMapper; + + public List> search(String workstationId, String userId, String query, int topK) { + String collection = memoryConfig.collectionName(workstationId, userId); + List embedding = embeddingService.embedSingle(query); + return milvusStore.search(collection, embedding, query, topK); + } + + public void ingest(String workstationId, String userId, String content, String source) { + MemoryIndexJob job = MemoryIndexJob.builder() + .jobId(UUID.randomUUID().toString()) + .workstationId(workstationId) + .userId(userId) + .jobType(JobType.SESSION_SUMMARY) + .content(content) + .source(source.isEmpty() ? "session-summary/" + java.time.LocalDate.now() : source) + .build(); + enqueueJob(job); + } + + public void triggerIndexFile(String workstationId, String userId, String filePath) { + Path path = Path.of(filePath); + String fileType = documentParser.detectFileType(path); + MemoryIndexJob job = MemoryIndexJob.builder() + .jobId(UUID.randomUUID().toString()) + .workstationId(workstationId) + .userId(userId) + .jobType(JobType.FILE_UPLOAD) + .filePath(filePath) + .source(filePath) + .fileType(fileType) + .build(); + enqueueJob(job); + } + + public int triggerBatchIndex(String workstationId, String userId, List filePaths) { + int count = 0; + for (String fp : filePaths) { + Path path = Path.of(fp); + String fileType = documentParser.detectFileType(path); + if (!documentParser.isIndexable(fileType)) { + log.debug("Skipping non-indexable file: {} (type={})", fp, fileType); + continue; + } + MemoryIndexJob job = MemoryIndexJob.builder() + .jobId(UUID.randomUUID().toString()) + .workstationId(workstationId) + .userId(userId) + .jobType(JobType.MEMORY_WRITEBACK) + .filePath(fp) + .source(fp) + .fileType(fileType) + .build(); + enqueueJob(job); + count++; + } + log.info("Enqueued batch index: {} files for ws={}, user={}", count, workstationId, userId); + return count; + } + + public List> recent(String workstationId, String userId, int limit) { + String collection = memoryConfig.collectionName(workstationId, userId); + return milvusStore.recent(collection, limit); + } + + public Map stats(String workstationId, String userId) { + String collection = memoryConfig.collectionName(workstationId, userId); + long count = milvusStore.count(collection); + return Map.of( + "collection", collection, + "chunkCount", count, + "workstationId", workstationId, + "userId", userId + ); + } + + public void deleteSource(String workstationId, String userId, String source) { + String collection = memoryConfig.collectionName(workstationId, userId); + milvusStore.deleteBySource(collection, source); + } + + /** + * Process an index job synchronously (called by MemoryIndexConsumer). + * Incremental: only new/changed chunks upserted, stale chunks deleted. + */ + public void processIndexJob(MemoryIndexJob job) { + String collection = job.getCollectionName() != null && !job.getCollectionName().isBlank() + ? job.getCollectionName() + : memoryConfig.collectionName(job.getWorkstationId(), job.getUserId()); + String model = memoryConfig.getEmbedding().getModel(); + + String text; + String source = job.getSource(); + + if (job.getJobType() == JobType.SESSION_SUMMARY) { + text = job.getContent(); + } else if (("NFS".equals(job.getStorageType()) || "OSS".equals(job.getStorageType())) && job.getObjectName() != null) { + if (nfsStorageService == null) { + log.warn("Skip NFS memory index job because NfsStorageService is unavailable: jobId={}", job.getJobId()); + return; + } + Path temp = null; + try { + temp = nfsStorageService.downloadToTempFile(job.getObjectName()); + text = documentParser.parseFile(temp); + source = job.getObjectName(); + } catch (Exception e) { + log.error("Failed to parse NFS object {}: {}", job.getObjectName(), e.getMessage(), e); + return; + } finally { + if (temp != null) { + try { + java.nio.file.Files.deleteIfExists(temp); + } catch (Exception e) { + log.warn("Failed to cleanup temp file {}: {}", temp, e.getMessage()); + } + } + } + } else { + try { + text = documentParser.parseFile(Path.of(job.getFilePath())); + } catch (Exception e) { + log.error("Failed to parse file {}: {}", job.getFilePath(), e.getMessage(), e); + return; + } + } + + List chunks = chunker.chunkMarkdown(text, source); + if (chunks.isEmpty()) { + milvusStore.deleteBySource(collection, source); + log.info("No chunks from source {}, removed stale data", source); + return; + } + + Map newChunkMap = new LinkedHashMap<>(); + for (TextChunkerService.Chunk c : chunks) { + String id = chunker.computeChunkId(c.source(), c.startLine(), c.endLine(), c.contentHash(), model); + newChunkMap.put(id, c); + } + + Set existingHashes = milvusStore.hashesBySource(collection, source); + + Set staleHashes = new HashSet<>(existingHashes); + staleHashes.removeAll(newChunkMap.keySet()); + if (!staleHashes.isEmpty()) { + milvusStore.deleteByHashes(collection, new ArrayList<>(staleHashes)); + log.info("Removed {} stale chunks from source {}", staleHashes.size(), source); + } + + List newIds = new ArrayList<>(); + List newChunks = new ArrayList<>(); + for (Map.Entry entry : newChunkMap.entrySet()) { + if (!existingHashes.contains(entry.getKey())) { + newIds.add(entry.getKey()); + newChunks.add(entry.getValue()); + } + } + + if (newChunks.isEmpty()) { + log.debug("All chunks already indexed for source {}", source); + return; + } + + List contents = newChunks.stream().map(TextChunkerService.Chunk::content).toList(); + List> embeddings = embeddingService.embed(contents); + long now = System.currentTimeMillis(); + + List> records = new ArrayList<>(); + for (int i = 0; i < newChunks.size(); i++) { + TextChunkerService.Chunk c = newChunks.get(i); + Map rec = new HashMap<>(); + rec.put("chunk_hash", newIds.get(i)); + rec.put("embedding", embeddings.get(i)); + rec.put("content", c.content()); + rec.put("source", c.source()); + rec.put("heading", c.heading()); + rec.put("heading_level", (long) c.headingLevel()); + rec.put("start_line", (long) c.startLine()); + rec.put("end_line", (long) c.endLine()); + rec.put("file_type", job.getFileType() != null ? job.getFileType() : ""); + rec.put("indexed_at", now); + records.add(rec); + } + + int upserted = milvusStore.upsert(collection, records); + log.info("Indexed {} new chunks from source {} into {}", upserted, source, collection); + + if (robotFileMapper != null && job.getObjectName() != null + && ("NFS".equals(job.getStorageType()) || "OSS".equals(job.getStorageType()))) { + RobotFile file = robotFileMapper.selectOne(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .and(w -> w.eq(RobotFile::getOssPath, job.getObjectName()) + .or().eq(RobotFile::getParsedOssPath, job.getObjectName())) + .isNull(RobotFile::getDeletedAt) + .last("limit 1")); + if (file != null) { + file.setMemoryIndexStatus("INDEXED"); + file.setUpdatedAt(java.time.LocalDateTime.now()); + robotFileMapper.updateById(file); + } + } + } + + private void enqueueJob(MemoryIndexJob job) { + try { + String json = objectMapper.writeValueAsString(job); + redisTemplate.opsForList().leftPush(memoryConfig.getIndex().getQueueKey(), json); + } catch (Exception e) { + log.error("Failed to enqueue index job: {}", e.getMessage(), e); + throw new RuntimeException("Failed to enqueue memory index job", e); + } + } +} diff --git a/back/src/main/java/com/linkwork/service/memory/MilvusStoreService.java b/back/src/main/java/com/linkwork/service/memory/MilvusStoreService.java new file mode 100644 index 0000000..512cf12 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/memory/MilvusStoreService.java @@ -0,0 +1,397 @@ +package com.linkwork.service.memory; + +import com.linkwork.config.MemoryConfig; +import io.milvus.common.clientenum.FunctionType; +import io.milvus.v2.client.ConnectConfig; +import io.milvus.v2.client.MilvusClientV2; +import io.milvus.v2.common.DataType; +import io.milvus.v2.common.IndexParam; +import io.milvus.v2.service.collection.request.AddFieldReq; +import io.milvus.v2.service.collection.request.CreateCollectionReq; +import io.milvus.v2.service.collection.request.HasCollectionReq; +import io.milvus.v2.service.vector.request.*; +import io.milvus.v2.service.vector.request.data.EmbeddedText; +import io.milvus.v2.service.vector.request.data.FloatVec; +import io.milvus.v2.service.vector.request.ranker.RRFRanker; +import io.milvus.v2.service.vector.response.DeleteResp; +import io.milvus.v2.service.vector.response.QueryResp; +import io.milvus.v2.service.vector.response.SearchResp; +import io.milvus.v2.service.vector.response.UpsertResp; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.ConcurrentHashMap; + +@Slf4j +@org.springframework.boot.autoconfigure.condition.ConditionalOnProperty(name = "memory.enabled", havingValue = "true", matchIfMissing = true) +@Service +@RequiredArgsConstructor +public class MilvusStoreService { + + private final MemoryConfig memoryConfig; + private MilvusClientV2 client; + private final Set knownCollections = ConcurrentHashMap.newKeySet(); + private volatile boolean available = false; + private final AtomicBoolean unavailableWarned = new AtomicBoolean(false); + + static final List QUERY_FIELDS = List.of( + "content", "source", "heading", "chunk_hash", + "heading_level", "start_line", "end_line", "file_type", "indexed_at" + ); + + @PostConstruct + public void init() { + if (!memoryConfig.isEnabled()) { + log.info("Memory service disabled, skipping Milvus connection"); + return; + } + try { + ConnectConfig.ConnectConfigBuilder builder = ConnectConfig.builder() + .uri(memoryConfig.getMilvus().getUri()); + String token = memoryConfig.getMilvus().getToken(); + if (token != null && !token.isBlank()) { + builder.token(token); + } + client = new MilvusClientV2(builder.build()); + available = true; + unavailableWarned.set(false); + log.info("Connected to Milvus at {}", memoryConfig.getMilvus().getUri()); + } catch (Exception e) { + available = false; + log.warn("Milvus unavailable at startup, memory features degraded: {}", e.getMessage()); + log.debug("Milvus init failure details", e); + } + } + + @PreDestroy + public void close() { + if (client != null) { + try { + client.close(); + } catch (Exception e) { + log.warn("Error closing Milvus client", e); + } + } + } + + public void ensureCollection(String collectionName) { + if (!isAvailable("ensureCollection")) { + return; + } + if (knownCollections.contains(collectionName)) { + return; + } + try { + boolean exists = client.hasCollection( + HasCollectionReq.builder().collectionName(collectionName).build()); + if (exists) { + knownCollections.add(collectionName); + return; + } + createCollection(collectionName); + knownCollections.add(collectionName); + } catch (Exception e) { + markUnavailable(); + log.error("Failed to ensure collection {}: {}", collectionName, e.getMessage(), e); + throw new RuntimeException("Milvus collection setup failed", e); + } + } + + private void createCollection(String collectionName) { + int dim = memoryConfig.getEmbedding().getDimension(); + + CreateCollectionReq.CollectionSchema schema = CreateCollectionReq.CollectionSchema.builder() + .enableDynamicField(true) + .build(); + + schema.addField(AddFieldReq.builder() + .fieldName("chunk_hash").dataType(DataType.VarChar).maxLength(64).isPrimaryKey(true).build()); + schema.addField(AddFieldReq.builder() + .fieldName("embedding").dataType(DataType.FloatVector).dimension(dim).build()); + schema.addField(AddFieldReq.builder() + .fieldName("content").dataType(DataType.VarChar).maxLength(65535) + .enableAnalyzer(true).build()); + schema.addField(AddFieldReq.builder() + .fieldName("sparse_vector").dataType(DataType.SparseFloatVector).build()); + schema.addField(AddFieldReq.builder() + .fieldName("source").dataType(DataType.VarChar).maxLength(1024).build()); + schema.addField(AddFieldReq.builder() + .fieldName("heading").dataType(DataType.VarChar).maxLength(1024).build()); + schema.addField(AddFieldReq.builder() + .fieldName("heading_level").dataType(DataType.Int64).build()); + schema.addField(AddFieldReq.builder() + .fieldName("start_line").dataType(DataType.Int64).build()); + schema.addField(AddFieldReq.builder() + .fieldName("end_line").dataType(DataType.Int64).build()); + schema.addField(AddFieldReq.builder() + .fieldName("file_type").dataType(DataType.VarChar).maxLength(32).build()); + schema.addField(AddFieldReq.builder() + .fieldName("indexed_at").dataType(DataType.Int64).build()); + + schema.addFunction(CreateCollectionReq.Function.builder() + .name("bm25_fn") + .functionType(FunctionType.BM25) + .inputFieldNames(Collections.singletonList("content")) + .outputFieldNames(Collections.singletonList("sparse_vector")) + .build()); + + List indexParams = new ArrayList<>(); + indexParams.add(IndexParam.builder() + .fieldName("embedding") + .indexType(IndexParam.IndexType.FLAT) + .metricType(IndexParam.MetricType.COSINE) + .build()); + indexParams.add(IndexParam.builder() + .fieldName("sparse_vector") + .indexType(IndexParam.IndexType.SPARSE_INVERTED_INDEX) + .metricType(IndexParam.MetricType.BM25) + .build()); + + client.createCollection(CreateCollectionReq.builder() + .collectionName(collectionName) + .collectionSchema(schema) + .indexParams(indexParams) + .build()); + + log.info("Created Milvus collection: {} (dim={})", collectionName, dim); + } + + public int upsert(String collectionName, List> records) { + if (records == null || records.isEmpty()) return 0; + if (!isAvailable("upsert")) return 0; + ensureCollection(collectionName); + if (!isAvailable("upsert")) return 0; + // Convert List to List for Milvus SDK + List jsonRecords = records.stream().map(record -> { + com.google.gson.JsonObject json = new com.google.gson.JsonObject(); + record.forEach((k, v) -> { + if (v instanceof String) json.addProperty(k, (String) v); + else if (v instanceof Number) json.addProperty(k, (Number) v); + else if (v instanceof Boolean) json.addProperty(k, (Boolean) v); + else if (v != null) json.addProperty(k, v.toString()); + }); + return json; + }).collect(java.util.stream.Collectors.toList()); + try { + UpsertResp resp = client.upsert(UpsertReq.builder() + .collectionName(collectionName) + .data(jsonRecords) + .build()); + return (int) resp.getUpsertCnt(); + } catch (Exception e) { + markUnavailable(); + log.warn("Milvus upsert degraded: collection={}, error={}", collectionName, e.getMessage()); + return 0; + } + } + + /** + * Hybrid search: dense cosine + BM25 sparse + RRF reranking. + */ + public List> search( + String collectionName, List queryEmbedding, String queryText, int topK) { + if (!isAvailable("search")) return Collections.emptyList(); + ensureCollection(collectionName); + if (!isAvailable("search")) return Collections.emptyList(); + + List searchRequests = new ArrayList<>(); + + searchRequests.add(AnnSearchReq.builder() + .vectorFieldName("embedding") + .vectors(Collections.singletonList(new FloatVec(queryEmbedding))) + .params("{\"metric_type\": \"COSINE\"}") + .topK(topK) + .build()); + + if (queryText != null && !queryText.isBlank()) { + searchRequests.add(AnnSearchReq.builder() + .vectorFieldName("sparse_vector") + .vectors(Collections.singletonList(new EmbeddedText(queryText))) + .topK(topK) + .build()); + } + + SearchResp resp; + try { + resp = client.hybridSearch(HybridSearchReq.builder() + .collectionName(collectionName) + .searchRequests(searchRequests) + .ranker(new RRFRanker(60)) + .topK(topK) + .outFields(QUERY_FIELDS) + .build()); + } catch (Exception e) { + markUnavailable(); + log.warn("Milvus search degraded: collection={}, error={}", collectionName, e.getMessage()); + return Collections.emptyList(); + } + + List> results = new ArrayList<>(); + if (resp.getSearchResults() != null && !resp.getSearchResults().isEmpty()) { + for (SearchResp.SearchResult hit : resp.getSearchResults().get(0)) { + Map row = new HashMap<>(hit.getEntity()); + row.put("score", hit.getScore()); + results.add(row); + } + } + return results; + } + + public List> query(String collectionName, String filterExpr) { + if (!isAvailable("query")) return Collections.emptyList(); + ensureCollection(collectionName); + if (!isAvailable("query")) return Collections.emptyList(); + String filter = (filterExpr != null && !filterExpr.isBlank()) ? filterExpr : "chunk_hash != \"\""; + QueryResp resp; + try { + resp = client.query(QueryReq.builder() + .collectionName(collectionName) + .filter(filter) + .outputFields(QUERY_FIELDS) + .build()); + } catch (Exception e) { + markUnavailable(); + log.warn("Milvus query degraded: collection={}, error={}", collectionName, e.getMessage()); + return Collections.emptyList(); + } + List> results = new ArrayList<>(); + if (resp.getQueryResults() != null) { + for (QueryResp.QueryResult r : resp.getQueryResults()) { + results.add(new HashMap<>(r.getEntity())); + } + } + return results; + } + + public Set hashesBySource(String collectionName, String source) { + if (!isAvailable("hashesBySource")) return Collections.emptySet(); + String filter = "source == \"" + source.replace("\"", "\\\"") + "\""; + QueryResp resp; + try { + resp = client.query(QueryReq.builder() + .collectionName(collectionName) + .filter(filter) + .outputFields(Collections.singletonList("chunk_hash")) + .build()); + } catch (Exception e) { + markUnavailable(); + log.warn("Milvus hashes query degraded: collection={}, error={}", collectionName, e.getMessage()); + return Collections.emptySet(); + } + Set hashes = new HashSet<>(); + if (resp.getQueryResults() != null) { + for (QueryResp.QueryResult r : resp.getQueryResults()) { + Object hash = r.getEntity().get("chunk_hash"); + if (hash != null) hashes.add(hash.toString()); + } + } + return hashes; + } + + public void deleteBySource(String collectionName, String source) { + if (!isAvailable("deleteBySource")) return; + String filter = "source == \"" + source.replace("\"", "\\\"") + "\""; + try { + client.delete(DeleteReq.builder() + .collectionName(collectionName) + .filter(filter) + .build()); + } catch (Exception e) { + markUnavailable(); + log.warn("Milvus deleteBySource degraded: collection={}, error={}", collectionName, e.getMessage()); + return; + } + log.info("Deleted chunks for source {} from {}", source, collectionName); + } + + public void deleteByHashes(String collectionName, List hashes) { + if (hashes == null || hashes.isEmpty()) return; + if (!isAvailable("deleteByHashes")) return; + try { + client.delete(DeleteReq.builder() + .collectionName(collectionName) + .ids(new ArrayList<>(hashes)) + .build()); + } catch (Exception e) { + markUnavailable(); + log.warn("Milvus deleteByHashes degraded: collection={}, error={}", collectionName, e.getMessage()); + } + } + + public long count(String collectionName) { + try { + if (!isAvailable("count")) return 0; + ensureCollection(collectionName); + if (!isAvailable("count")) return 0; + QueryResp resp = client.query(QueryReq.builder() + .collectionName(collectionName) + .filter("chunk_hash != \"\"") + .outputFields(Collections.singletonList("chunk_hash")) + .build()); + return resp.getQueryResults() != null ? resp.getQueryResults().size() : 0; + } catch (Exception e) { + markUnavailable(); + return 0; + } + } + + /** + * Query recent chunks ordered by indexed_at descending. + */ + public List> recent(String collectionName, int limit) { + if (!isAvailable("recent")) return Collections.emptyList(); + ensureCollection(collectionName); + if (!isAvailable("recent")) return Collections.emptyList(); + QueryResp resp; + try { + resp = client.query(QueryReq.builder() + .collectionName(collectionName) + .filter("chunk_hash != \"\"") + .outputFields(QUERY_FIELDS) + .build()); + } catch (Exception e) { + markUnavailable(); + log.warn("Milvus recent degraded: collection={}, error={}", collectionName, e.getMessage()); + return Collections.emptyList(); + } + List> results = new ArrayList<>(); + if (resp.getQueryResults() != null) { + for (QueryResp.QueryResult r : resp.getQueryResults()) { + results.add(new HashMap<>(r.getEntity())); + } + } + results.sort((a, b) -> { + long ta = a.get("indexed_at") != null ? ((Number) a.get("indexed_at")).longValue() : 0; + long tb = b.get("indexed_at") != null ? ((Number) b.get("indexed_at")).longValue() : 0; + return Long.compare(tb, ta); + }); + return results.subList(0, Math.min(limit, results.size())); + } + + private boolean isAvailable(String operation) { + if (!memoryConfig.isEnabled()) { + return false; + } + if (client != null && available) { + return true; + } + if (unavailableWarned.compareAndSet(false, true)) { + log.warn( + "Milvus unavailable, memory operation degraded: op={}, uri={}", + operation, + memoryConfig.getMilvus().getUri() + ); + } + return false; + } + + private void markUnavailable() { + available = false; + unavailableWarned.set(false); + } +} diff --git a/back/src/main/java/com/linkwork/service/memory/TextChunkerService.java b/back/src/main/java/com/linkwork/service/memory/TextChunkerService.java new file mode 100644 index 0000000..712dd80 --- /dev/null +++ b/back/src/main/java/com/linkwork/service/memory/TextChunkerService.java @@ -0,0 +1,148 @@ +package com.linkwork.service.memory; + +import com.linkwork.config.MemoryConfig; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.HexFormat; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Markdown/text chunking by headings, with SHA-256 content dedup. + * Ported from memsearch chunker.py. + */ +@Slf4j +@org.springframework.boot.autoconfigure.condition.ConditionalOnProperty(name = "memory.enabled", havingValue = "true", matchIfMissing = true) +@Service +@RequiredArgsConstructor +public class TextChunkerService { + + private final MemoryConfig memoryConfig; + private static final Pattern HEADING_RE = Pattern.compile("^(#{1,6})\\s+(.+)$", Pattern.MULTILINE); + + public record Chunk( + String content, + String source, + String heading, + int headingLevel, + int startLine, + int endLine, + String contentHash + ) {} + + public List chunkMarkdown(String text, String source) { + int maxChunkSize = memoryConfig.getIndex().getMaxChunkSize(); + int overlapLines = memoryConfig.getIndex().getOverlapLines(); + return chunkMarkdown(text, source, maxChunkSize, overlapLines); + } + + public List chunkMarkdown(String text, String source, int maxChunkSize, int overlapLines) { + String[] lines = text.split("\n", -1); + + List headingPositions = new ArrayList<>(); // [lineIdx, level] + List headingTitles = new ArrayList<>(); + Matcher m = HEADING_RE.matcher(text); + int lineStart = 0; + for (int i = 0; i < lines.length; i++) { + Matcher lineMatcher = HEADING_RE.matcher(lines[i]); + if (lineMatcher.matches()) { + headingPositions.add(new int[]{i, lineMatcher.group(1).length()}); + headingTitles.add(lineMatcher.group(2).trim()); + } + } + + // Build sections between headings + List sections = new ArrayList<>(); // [start, end, headingIdx] + if (headingPositions.isEmpty() || headingPositions.get(0)[0] > 0) { + int sectionEnd = headingPositions.isEmpty() ? lines.length : headingPositions.get(0)[0]; + sections.add(new int[]{0, sectionEnd, -1}); // preamble + } + for (int idx = 0; idx < headingPositions.size(); idx++) { + int lineIdx = headingPositions.get(idx)[0]; + int nextStart = (idx + 1 < headingPositions.size()) + ? headingPositions.get(idx + 1)[0] + : lines.length; + sections.add(new int[]{lineIdx, nextStart, idx}); + } + + List chunks = new ArrayList<>(); + for (int[] sec : sections) { + int start = sec[0], end = sec[1], hIdx = sec[2]; + String heading = hIdx >= 0 ? headingTitles.get(hIdx) : ""; + int level = hIdx >= 0 ? headingPositions.get(hIdx)[1] : 0; + + String sectionText = joinLines(lines, start, end).strip(); + if (sectionText.isEmpty()) continue; + + if (sectionText.length() <= maxChunkSize) { + chunks.add(new Chunk(sectionText, source, heading, level, + start + 1, end, sha256Short(sectionText))); + } else { + chunks.addAll(splitLargeSection(lines, start, end, source, + heading, level, maxChunkSize, overlapLines)); + } + } + return chunks; + } + + /** + * Compute composite chunk ID matching memsearch/OpenClaw format. + */ + public String computeChunkId(String source, int startLine, int endLine, String contentHash, String model) { + String raw = "markdown:" + source + ":" + startLine + ":" + endLine + ":" + contentHash + ":" + model; + return sha256Short(raw); + } + + private List splitLargeSection(String[] lines, int start, int end, String source, + String heading, int headingLevel, int maxSize, int overlap) { + List chunks = new ArrayList<>(); + List currentLines = new ArrayList<>(); + int currentStart = 0; + + for (int i = start; i < end; i++) { + currentLines.add(lines[i]); + String text = String.join("\n", currentLines); + boolean isParagraphBreak = lines[i].strip().isEmpty() && (i + 1 < end); + boolean isLastLine = (i == end - 1); + + if ((text.length() >= maxSize && isParagraphBreak) || isLastLine) { + String content = text.strip(); + if (!content.isEmpty()) { + chunks.add(new Chunk(content, source, heading, headingLevel, + start + currentStart + 1, start + i + 1, sha256Short(content))); + } + int overlapStart = Math.max(0, currentLines.size() - overlap); + currentLines = isLastLine ? new ArrayList<>() + : new ArrayList<>(currentLines.subList(overlapStart, currentLines.size())); + currentStart = i + 1 - currentLines.size() - start; + } + } + return chunks; + } + + private static String joinLines(String[] lines, int start, int end) { + StringBuilder sb = new StringBuilder(); + for (int i = start; i < end; i++) { + if (i > start) sb.append('\n'); + sb.append(lines[i]); + } + return sb.toString(); + } + + static String sha256Short(String input) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hash = digest.digest(input.getBytes(StandardCharsets.UTF_8)); + return HexFormat.of().formatHex(hash).substring(0, 16); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("SHA-256 not available", e); + } + } +} diff --git a/back/src/main/java/com/linkwork/websocket/TaskWebSocketHandler.java b/back/src/main/java/com/linkwork/websocket/TaskWebSocketHandler.java new file mode 100644 index 0000000..139517e --- /dev/null +++ b/back/src/main/java/com/linkwork/websocket/TaskWebSocketHandler.java @@ -0,0 +1,432 @@ +package com.linkwork.websocket; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.linkwork.config.DispatchConfig; +import com.linkwork.service.NfsStorageService; +import com.linkwork.service.TaskEventBroadcaster; +import com.linkwork.service.TaskOutputWorkspaceSyncService; +import com.linkwork.service.TaskPathlistSyncService; +import com.linkwork.service.TaskService; +import com.linkwork.service.TaskStatusSyncService; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.connection.stream.MapRecord; +import org.springframework.data.redis.connection.stream.StreamOffset; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import java.io.IOException; +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 任务 WebSocket 事件推送。 + * + * 说明: + * - Redis Stream 的实时消费职责已下沉到 TaskEventConsumerService + * - 本 Handler 仅负责:会话绑定、历史回放、实时广播推送 + */ +@Slf4j +@Component +public class TaskWebSocketHandler extends TextWebSocketHandler { + + private final ObjectMapper objectMapper = new ObjectMapper(); + private final StringRedisTemplate redisTemplate; + private final TaskStatusSyncService taskStatusSyncService; + private final TaskService taskService; + private final DispatchConfig dispatchConfig; + private final NfsStorageService nfsStorageService; + private final TaskOutputWorkspaceSyncService taskOutputWorkspaceSyncService; + private final TaskPathlistSyncService taskPathlistSyncService; + private final TaskEventBroadcaster taskEventBroadcaster; + + private final Map sessions = new ConcurrentHashMap<>(); + private final Map sessionTaskMap = new ConcurrentHashMap<>(); + private final Map taskWorkstationCache = new ConcurrentHashMap<>(); + + private String broadcasterListenerId; + + public TaskWebSocketHandler(StringRedisTemplate redisTemplate, + TaskStatusSyncService taskStatusSyncService, + TaskService taskService, + DispatchConfig dispatchConfig, + NfsStorageService nfsStorageService, + TaskOutputWorkspaceSyncService taskOutputWorkspaceSyncService, + TaskPathlistSyncService taskPathlistSyncService, + TaskEventBroadcaster taskEventBroadcaster) { + this.redisTemplate = redisTemplate; + this.taskStatusSyncService = taskStatusSyncService; + this.taskService = taskService; + this.dispatchConfig = dispatchConfig; + this.nfsStorageService = nfsStorageService; + this.taskOutputWorkspaceSyncService = taskOutputWorkspaceSyncService; + this.taskPathlistSyncService = taskPathlistSyncService; + this.taskEventBroadcaster = taskEventBroadcaster; + } + + @PostConstruct + public void registerBroadcaster() { + broadcasterListenerId = taskEventBroadcaster.register(this::broadcastToTask); + } + + @PreDestroy + public void unregisterBroadcaster() { + taskEventBroadcaster.unregister(broadcasterListenerId); + } + + @Override + public void afterConnectionEstablished(WebSocketSession session) { + sessions.put(session.getId(), session); + String taskId = extractTaskId(session); + log.info("WebSocket connected: {}, taskId: {}", session.getId(), taskId); + if (taskId != null) { + bindTask(session, taskId); + } + } + + private String extractTaskId(WebSocketSession session) { + URI uri = session.getUri(); + if (uri != null && uri.getQuery() != null) { + for (String param : uri.getQuery().split("&")) { + String[] kv = param.split("="); + if (kv.length == 2 && "taskId".equals(kv[0])) { + return kv[1]; + } + } + } + return null; + } + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { + Map request = objectMapper.readValue(message.getPayload(), Map.class); + String action = (String) request.get("action"); + String taskId = (String) request.get("taskId"); + log.info("Received message: action={}, taskId={}", action, taskId); + if ("bind".equals(action) && taskId != null) { + bindTask(session, taskId); + } + } + + private void bindTask(WebSocketSession session, String taskId) { + sessionTaskMap.put(session.getId(), taskId); + log.info("Session {} bound to task {}", session.getId(), taskId); + pushHistoryEvents(session, taskId); + } + + private List buildStreamKeys(String taskId) { + List keys = new ArrayList<>(); + Long roleId = resolveRoleId(taskId); + keys.add(dispatchConfig.getLogStreamKey(roleId, taskId)); + keys.add("stream:task:" + taskId); + keys.add("stream:task:" + taskId + ":events"); + keys.add("stream:build:" + taskId); + return keys; + } + + private Long resolveRoleId(String taskId) { + try { + return taskService.getTaskByNo(taskId).getRoleId(); + } catch (Exception e) { + log.debug("WebSocket resolve roleId failed, fallback null: taskId={}", taskId); + return null; + } + } + + private void pushHistoryEvents(WebSocketSession session, String taskId) { + try { + List streamKeys = buildStreamKeys(taskId); + Set sentIds = new HashSet<>(); + + for (String streamKey : streamKeys) { + try { + List> records = redisTemplate.opsForStream() + .read(StreamOffset.fromStart(streamKey)); + if (records == null || records.isEmpty()) { + continue; + } + log.info("Push {} history events for task {} from {}", records.size(), taskId, streamKey); + for (MapRecord record : records) { + String recordId = record.getId().getValue(); + if (sentIds.contains(recordId)) { + continue; + } + sentIds.add(recordId); + syncTaskStatus(taskId, record); + sendEvent(session, record); + } + } catch (Exception e) { + log.debug("history stream read skipped: streamKey={}, err={}", streamKey, e.getMessage()); + } + } + } catch (Exception e) { + log.error("push history events failed: taskId={}", taskId, e); + } + } + + private void broadcastToTask(String taskId, MapRecord record) { + sessionTaskMap.forEach((sessionId, tid) -> { + if (!taskId.equals(tid)) { + return; + } + WebSocketSession session = sessions.get(sessionId); + if (session != null && session.isOpen()) { + sendEvent(session, record); + } + }); + } + + private void syncTaskStatus(String taskId, MapRecord record) { + try { + Map eventData = extractEventData(record); + taskStatusSyncService.onEvent(taskId, eventData); + } catch (Exception e) { + log.debug("sync task status failed: taskId={}, err={}", taskId, e.getMessage()); + } + } + + private void sendEvent(WebSocketSession session, MapRecord record) { + try { + Map event = extractEventData(record); + String taskNo = firstNonBlank(event, "task_no", "task_id"); + if (!StringUtils.hasText(taskNo)) { + taskNo = sessionTaskMap.get(session.getId()); + } + enrichOutputReadyEvent(event); + taskPathlistSyncService.enrichEventForDisplay(taskNo, event); + event.put("_id", record.getId().getValue()); + String jsonMessage = objectMapper.writeValueAsString(event); + log.debug("Sending WebSocket event: {}", jsonMessage); + session.sendMessage(new TextMessage(jsonMessage)); + } catch (IOException e) { + log.error("send websocket event failed", e); + } + } + + @SuppressWarnings("unchecked") + private Map extractEventData(MapRecord record) { + Map rawValues = record.getValue(); + + Object payloadObj = rawValues.get("payload"); + if (payloadObj instanceof String payloadStr && payloadStr.startsWith("{")) { + try { + Map parsed = objectMapper.readValue(payloadStr, Map.class); + Object innerData = parsed.get("data"); + if (innerData instanceof String dataStr && (dataStr.startsWith("{") || dataStr.startsWith("["))) { + try { + parsed.put("data", objectMapper.readValue(dataStr, Object.class)); + } catch (Exception ignored) { + } + } + Object data = parsed.get("data"); + if (data instanceof Map dataMap) { + dataMap.forEach((k, v) -> parsed.putIfAbsent(k.toString(), v)); + } + return parsed; + } catch (Exception e) { + log.warn("parse payload json failed, fallback flat event: {}", e.getMessage()); + } + } + + Map event = new HashMap<>(); + rawValues.forEach((k, v) -> { + String key = k.toString(); + Object value = v; + if ("data".equals(key) && v instanceof String strVal) { + if (strVal.startsWith("{") || strVal.startsWith("[")) { + try { + value = objectMapper.readValue(strVal, Object.class); + } catch (Exception ignored) { + } + } + } + event.put(key, value); + }); + + Object data = event.get("data"); + if (data instanceof Map dataMap) { + dataMap.forEach((k, v) -> event.putIfAbsent(k.toString(), v)); + } + return event; + } + + @SuppressWarnings("unchecked") + private void enrichOutputReadyEvent(Map eventData) { + try { + String eventType = (String) eventData.get("event_type"); + if (!"TASK_OUTPUT_READY".equals(eventType)) { + return; + } + + Object dataObj = eventData.get("data"); + if (!(dataObj instanceof Map)) { + return; + } + + Map data = (Map) dataObj; + String outputType = (String) data.get("output_type"); + if (!"oss".equals(outputType)) { + return; + } + + String ossPath = normalizeOssPath((String) data.get("oss_path")); + if (!StringUtils.hasText(ossPath)) { + return; + } + String taskNo = firstNonBlank(eventData, "task_no", "task_id"); + + taskOutputWorkspaceSyncService.syncTaskOutput(taskNo, data).ifPresent(context -> { + data.put("workspace_space_type", "WORKSTATION"); + data.put("workspace_workstation_id", context.workstationId()); + data.put("workspace_parent_node_id", context.parentNodeId()); + data.put("workspace_node_id", context.taskNodeId()); + }); + + List candidatePrefixes = buildOssCandidatePrefixes(eventData, ossPath); + String resolvedOssPath = choosePreferredOssPath(candidatePrefixes); + data.put("oss_path", ossPath); + data.put("oss_path_resolved", resolvedOssPath); + data.put("nfs_path", "/data/oss/robot/" + ossPath); + data.put("artifacts_pending", Boolean.TRUE); + + if (!nfsStorageService.isConfigured()) { + log.debug("TASK_OUTPUT_READY: NFS not configured, only keep path contract: {}", resolvedOssPath); + return; + } + + List objectNames = List.of(); + String matchedPrefix = null; + for (String prefix : candidatePrefixes) { + objectNames = nfsStorageService.listObjects(prefix); + if (!objectNames.isEmpty()) { + matchedPrefix = prefix; + break; + } + } + + if (objectNames.isEmpty()) { + log.info("TASK_OUTPUT_READY path contract emitted but directory empty: candidates={}, oss_path={}", + candidatePrefixes, ossPath); + return; + } + + if (matchedPrefix != null && !matchedPrefix.equals(data.get("oss_path_resolved"))) { + data.put("oss_path_resolved", matchedPrefix); + } + + List> artifacts = new ArrayList<>(); + for (String objectName : objectNames) { + String fileName = objectName; + int lastSlash = objectName.lastIndexOf('/'); + if (lastSlash >= 0 && lastSlash < objectName.length() - 1) { + fileName = objectName.substring(lastSlash + 1); + } + String downloadUrl = nfsStorageService.buildTaskOutputDownloadUrl(objectName); + Map artifact = new LinkedHashMap<>(); + artifact.put("name", fileName); + artifact.put("download_url", downloadUrl); + artifacts.add(artifact); + } + + if (!artifacts.isEmpty()) { + data.put("artifacts", artifacts); + data.put("artifacts_pending", Boolean.FALSE); + log.info("TASK_OUTPUT_READY enriched: oss_path={}, resolved={}, artifacts={}", + ossPath, data.get("oss_path_resolved"), artifacts.size()); + } + } catch (Exception e) { + log.warn("enrich TASK_OUTPUT_READY failed (non-blocking): {}", e.getMessage()); + } + } + + private List buildOssCandidatePrefixes(Map eventData, String rawOssPath) { + String ossPath = normalizeOssPath(rawOssPath); + LinkedHashSet candidates = new LinkedHashSet<>(); + candidates.add(ossPath); + + if (!ossPath.startsWith("system/")) { + String workstationId = resolveWorkstationId(eventData); + if (StringUtils.hasText(workstationId)) { + candidates.add("system/" + workstationId + "/" + ossPath); + } + } + return new ArrayList<>(candidates); + } + + private String normalizeOssPath(String rawOssPath) { + String normalized = rawOssPath == null ? "" : rawOssPath.trim(); + while (normalized.startsWith("/")) { + normalized = normalized.substring(1); + } + return normalized; + } + + private String choosePreferredOssPath(List candidates) { + for (String candidate : candidates) { + if (candidate.startsWith("system/")) { + return candidate; + } + } + return candidates.isEmpty() ? "" : candidates.get(0); + } + + private String resolveWorkstationId(Map eventData) { + String taskNo = firstNonBlank(eventData, "task_no", "task_id"); + if (!StringUtils.hasText(taskNo)) { + return null; + } + + String cached = taskWorkstationCache.get(taskNo); + if (StringUtils.hasText(cached)) { + return cached; + } + + try { + Long roleId = taskService.getTaskByNo(taskNo).getRoleId(); + if (roleId == null) { + return null; + } + String workstationId = String.valueOf(roleId); + taskWorkstationCache.put(taskNo, workstationId); + return workstationId; + } catch (Exception e) { + log.debug("resolve workstationId failed: taskNo={}, err={}", taskNo, e.getMessage()); + return null; + } + } + + private String firstNonBlank(Map source, String... keys) { + for (String key : keys) { + Object value = source.get(key); + if (value == null) { + continue; + } + String text = String.valueOf(value).trim(); + if (!text.isEmpty()) { + return text; + } + } + return null; + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { + String taskId = sessionTaskMap.remove(session.getId()); + sessions.remove(session.getId()); + log.info("WebSocket closed: {}, taskId: {}", session.getId(), taskId); + } +} diff --git a/back/src/main/resources/scripts/00-platform.cedar b/back/src/main/resources/scripts/00-platform.cedar new file mode 100644 index 0000000..fd82fbf --- /dev/null +++ b/back/src/main/resources/scripts/00-platform.cedar @@ -0,0 +1,104 @@ +// ============================================================ +// 00-platform.cedar — 平台级安全策略(最高优先级) +// ============================================================ +// Cedar 原生语义:forbid 覆盖 permit,无匹配 permit → 隐式拒绝 +// 评估模型:所有 .cedar 文件合并为单个 PolicySet,一次评估 +// ============================================================ + +// ---- FORBID: 高危操作(覆盖所有 permit)---- + +// 文件系统破坏(精确匹配根目录,不误拦工作区路径) +forbid ( + principal, + action == Action::"execute", + resource +) +when { + context.command like "*rm -rf /" || + context.command like "*rm -rf / *" || + context.command like "*rm -rf ~*" || + context.command like "*mkfs*" || + context.command like "*dd if=*of=/dev/*" || + context.command like "*chmod -R 777 /" || + context.command like "*chmod -R 777 / *" || + context.command like "*chown -R * /" || + context.command like "*chown -R * / *" +}; + +// [暂时禁用] 网络服务启动 / 反弹 shell +// forbid ( +// principal, +// action == Action::"execute", +// resource +// ) +// when { +// context.command like "*python* -m http.server*" || +// context.command like "*python* -m SimpleHTTPServer*" || +// context.command like "*nc -l*" || +// context.command like "*ncat *" || +// context.command like "*socat *" || +// context.command like "*/bin/bash -i*" || +// context.command like "*/bin/sh -i*" +// }; + +// [暂时禁用] 外发网络请求 +// forbid ( +// principal, +// action == Action::"execute", +// resource +// ) +// when { +// context.command like "curl *" || +// context.command like "wget *" +// }; + +// 提权命令(危险 sudo 模式 + su) +forbid ( + principal, + action == Action::"execute", + resource +) +when { + context.command like "sudo *su*" || + context.command like "sudo */bin/bash*" || + context.command like "sudo *-i*" || + context.command like "su *" || + context.command == "su" +}; + +// Docker / K8s 操作(需要额外岗位授权) +forbid ( + principal, + action == Action::"execute", + resource +) +when { + context.command_type == "docker" || + context.command_type == "k8s" +}; + +// ---- PERMIT: 允许的命令类型 ---- + +// Shell 命令(ls, cat, grep, echo, whoami, mkdir, cp, mv, etc.) +permit ( + principal, + action == Action::"execute", + resource +) +when { context.command_type == "shell" }; + +// Git 操作(git, gh, gitlab) +permit ( + principal, + action == Action::"execute", + resource +) +when { context.command_type == "git" }; + +// 包管理器(npm, pip, go, cargo, etc.) +permit ( + principal, + action == Action::"execute", + resource +) +when { context.command_type == "package" }; diff --git a/back/src/main/resources/scripts/build.sh b/back/src/main/resources/scripts/build.sh new file mode 100755 index 0000000..a297f2c --- /dev/null +++ b/back/src/main/resources/scripts/build.sh @@ -0,0 +1,1584 @@ +#!/bin/bash +# +# K8s Agent 镜像构建脚本 +# +# 功能:在镜像构建阶段执行,用于初始化工作环境 +# 1. 克隆 Git 项目到 /workspace +# 2. 从 Git 仓库部署 Skills 到 /opt/agent/skills +# 3. 校验 Skills 依赖 (Python/Node.js/Go) +# 4. 部署 MCP 配置到 /opt/agent/mcp.json +# +# 构建时步骤: +# 1. 校验基础镜像内置依赖 (Python 3.12, Node.js, npm, git, Claude CLI, uv, uvx) +# 2. 创建基础目录 +# 3. 安装 zzd 二进制 (zzd, zz, gen-key, encrypt-key) +# 4. 安装 momo-agent-sdk (源码) +# 5. 创建 agent 用户 + passwordless sudo +# 6. 创建 workspace 目录结构 +# 6.1 部署 security.json/mcp.json/skills 到 /opt/agent/ (root:agent 只读) +# 7. 下载 Cedar 策略文件 (从 URL) +# 8. 创建 zzd 运行时目录 +# 9. 校验 git 可用性 +# 11. 克隆 Git 项目 (clone 后清除 token) +# 12. 下载 MCP 配置 (从 URL) +# 13. 下载 Skills (从 URL) +# 14. 校验 Skills 依赖 +# 15. 部署 start.sh 和 ai_employee.py 到 /opt/agent/ (root:root) +# 16. 最终权限设置 (/opt/agent/ config 0440 root:agent, /etc/zzd 0700 root:root) +# 17. 清理构建临时文件 +# +# 构建时环境变量(由构建系统通过 Dockerfile ARG 传入): +# GIT_TOKEN - Git 认证 Token +# GIT_REPOS - 待克隆的 Git 仓库列表 (JSON 数组格式) +# CEDAR_POLICIES_URL - Cedar 策略文件压缩包 URL +# MCP_CONFIG_URL - MCP 配置文件 URL (JSON) +# CONFIG_URL - [预留,未启用] Agent config.json URL(后续可支持构建期远端下载) +# SKILLS_URL - Skills 压缩包的 OSS 链接 +# 环境变量(由构建系统导出): +# GIT_TOKEN - Git 认证 Token +# GIT_REPOS - 待克隆的 Git 仓库列表 (JSON 数组格式) +# 示例: '[{"url":"https://git.example.com/repo1.git","branch":"main"}]' +# SKILLS_CONFIG - Skills Git 配置 JSON(优先级最高) +# 格式: {"repoUrl":"...","token":"...","skills":[{"name":"...","branch":"...","commit":"..."}]} +# SKILLS_URL - Skills 压缩包的 OSS 链接(SKILLS_CONFIG 为空时回退使用) +# MCP_CONFIG - MCP 配置 JSON 字符串(由后端从岗位 configJson.mcp 生成,优先级最高) +# MCP_CONFIG_URL - MCP 配置文件 URL(MCP_CONFIG 为空时回退使用) +# +# 构建输入目录(支持两种来源): +# 1) 固定目录(推荐):$BUILD_ASSETS_ROOT +# - zzd-binaries/ - zzd, zz, gen-key, encrypt-key +# - sdk-source/ - momo-agent-sdk 源码 (含 pyproject.toml + src/) +# - start-scripts/ - start-single.sh, start-dual.sh, ai_employee.py +# 2) 兼容旧路径(若固定目录不存在则回退):/tmp/* +# + +set -o pipefail + +# ============================================================================= +# 常量定义 +# ============================================================================= +readonly WORKSPACE_DIR="/workspace" +WORKSPACE_GROUP="${WORKSPACE_GROUP:-workspace}" +WORKSPACE_GID="${WORKSPACE_GID:-2000}" +readonly AGENT_CONFIG_DIR="/opt/agent" +readonly SKILLS_DIR="${AGENT_CONFIG_DIR}/skills" +readonly MCP_CONFIG_FILE="${AGENT_CONFIG_DIR}/mcp.json" +readonly SECURITY_FILE="${AGENT_CONFIG_DIR}/security.json" +readonly SKILLS_ARCHIVE="/tmp/skills.tar.gz" +readonly CEDAR_ARCHIVE="/tmp/cedar-policies.tar.gz" + +# zzd 相关路径 +readonly ZZD_CONFIG_DIR="/etc/zzd" +readonly ZZD_POLICY_DIR="${ZZD_CONFIG_DIR}/policies" +readonly ZZD_SOCKET_DIR="/var/run/zzd" +readonly ZZD_AUDIT_DIR="/var/log/zzd/audit" + +# 构建输入根目录(固定目录,可通过环境变量覆盖) +readonly BUILD_ASSETS_ROOT="${BUILD_ASSETS_ROOT:-/opt/momo-agent-build}" +readonly LEGACY_TMP_ROOT="/tmp" + +# 输入路径(优先固定目录,不存在时回退旧 /tmp 路径) +ZZD_BIN_SRC="${ZZD_BIN_SRC:-${BUILD_ASSETS_ROOT}/zzd-binaries}" +SDK_SRC="${SDK_SRC:-${BUILD_ASSETS_ROOT}/sdk-source}" +START_SCRIPTS_SRC="${START_SCRIPTS_SRC:-${BUILD_ASSETS_ROOT}/start-scripts}" + +if [[ ! -d "${ZZD_BIN_SRC}" && -d "${LEGACY_TMP_ROOT}/zzd-binaries" ]]; then + ZZD_BIN_SRC="${LEGACY_TMP_ROOT}/zzd-binaries" +fi +if [[ ! -d "${SDK_SRC}" && -d "${LEGACY_TMP_ROOT}/sdk-build" ]]; then + SDK_SRC="${LEGACY_TMP_ROOT}/sdk-build" +fi +if [[ ! -d "${START_SCRIPTS_SRC}" && -d "${LEGACY_TMP_ROOT}/start-scripts" ]]; then + START_SCRIPTS_SRC="${LEGACY_TMP_ROOT}/start-scripts" +fi + +readonly ZZD_BIN_SRC SDK_SRC START_SCRIPTS_SRC + +# 颜色输出 +readonly RED='\033[0;31m' +readonly GREEN='\033[0;32m' +readonly YELLOW='\033[1;33m' +readonly BLUE='\033[0;34m' +readonly NC='\033[0m' # No Color + +# ============================================================================= +# 日志函数 +# ============================================================================= +log_info() { + echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*" >&2 +} + +# ============================================================================= +# 工具函数 +# ============================================================================= + +# URL 脱敏 — 剥离 query string、fragment 和 userinfo,仅保留 scheme://host/path +# 用途: 日志输出 URL 时防止泄露签名、token 等敏感参数 +redact_url() { + local url="$1" + # 去掉 query string (?...) 和 fragment (#...) + url="${url%%\?*}" + url="${url%%#*}" + # 去掉 userinfo (oauth2:TOKEN@ 等) + url=$(echo "${url}" | sed -E 's|(https?://)([^@]+@)|\1|') + echo "${url}" +} + +# 检查命令是否存在(静默模式) +command_exists() { + command -v "$1" &> /dev/null +} + +# 安装系统依赖(禁用:基础镜像已预装) +install_system_dependencies() { + log_error "检测到缺失依赖,但当前模式禁用在线安装。请在基础镜像中预装依赖后重试。" + return 1 +} + +# 检查 Claude Code CLI(禁用在线安装) +install_claude_cli() { + if command_exists claude; then + log_success "Claude Code CLI 已安装" + return 0 + fi + log_error "claude 命令缺失。当前模式禁用在线安装,请在基础镜像中预装 Claude CLI。" + return 1 +} + +# 检查基础镜像内置依赖(不安装) +check_prerequisites() { + log_info "检查基础镜像内置依赖(不执行在线安装)..." + + local required_cmds=("curl" "jq" "python3.12" "node" "npm" "git" "claude" "uv" "uvx") + local missing=() + + for cmd in "${required_cmds[@]}"; do + if ! command_exists "$cmd"; then + missing+=("$cmd") + fi + done + + if [[ ${#missing[@]} -gt 0 ]]; then + log_error "缺少必需命令: ${missing[*]}" + log_error "请确保基础镜像已预装: Python 3.12 / Node.js v24.13.0 / npm 11.6.2 / git 2.43.5 / Claude CLI / uv / uvx" + return 1 + fi + + if ! python3.12 -m pip --version >/dev/null 2>&1; then + log_error "python3.12 -m pip 不可用,请确保基础镜像内置 pip" + return 1 + fi + + log_info "依赖版本信息:" + log_info " $(python3.12 --version 2>&1)" + log_info " $(node --version 2>&1)" + log_info " npm $(npm --version 2>&1)" + log_info " $(git --version 2>&1)" + log_info " $(claude --version 2>&1 | head -1)" + log_info " $(uv --version 2>&1)" + log_info " $(uvx --version 2>&1 | head -1)" + + # 版本提示(不强制中断) + local py_ver node_ver npm_ver git_ver + py_ver=$(python3.12 --version 2>&1 | awk '{print $2}') + node_ver=$(node --version 2>&1) + npm_ver=$(npm --version 2>&1) + git_ver=$(git --version 2>&1 | awk '{print $3}') + + [[ "${py_ver}" == 3.12* ]] || log_warn "Python 版本不是 3.12.x: ${py_ver}" + [[ "${node_ver}" == "v24.13.0" ]] || log_warn "Node.js 版本不是 v24.13.0: ${node_ver}" + [[ "${npm_ver}" == "11.6.2" ]] || log_warn "npm 版本不是 11.6.2: ${npm_ver}" + [[ "${git_ver}" == "2.43.5" ]] || log_warn "git 版本不是 2.43.5: ${git_ver}" + + # ★ 修复 python3 符号链接:确保 /usr/bin/python3 → python3.12 + # Rocky Linux alternatives 可能残留 python3 → python3.11(不存在), + # start-dual.sh 用 "sudo -u agent python3 ..." 需要 python3 可用 + if ! python3 --version &>/dev/null || ! python3 -c "import sys" &>/dev/null; then + log_warn "python3 不可用或指向错误目标,修复符号链接..." + alternatives --remove-all python3 2>/dev/null || true + rm -f /etc/alternatives/python3 /usr/bin/python3 /usr/bin/python + ln -s /usr/bin/python3.12 /usr/bin/python3 + ln -s /usr/bin/python3.12 /usr/bin/python + log_info "python3 -> python3.12 ($(python3 --version 2>&1))" + elif [[ "$(readlink -f /usr/bin/python3 2>/dev/null)" != "/usr/bin/python3.12" ]]; then + log_warn "python3 未指向 python3.12,修复..." + alternatives --remove-all python3 2>/dev/null || true + rm -f /etc/alternatives/python3 /usr/bin/python3 /usr/bin/python + ln -s /usr/bin/python3.12 /usr/bin/python3 + ln -s /usr/bin/python3.12 /usr/bin/python + log_info "python3 -> python3.12 ($(python3 --version 2>&1))" + else + log_success "python3 -> $(readlink -f /usr/bin/python3) (OK)" + fi + + log_success "依赖检查完成" + return 0 +} + +# 创建必要目录 +create_directories() { + log_info "创建工作目录..." + + mkdir -p "${WORKSPACE_DIR}" || { + log_error "创建 ${WORKSPACE_DIR} 失败" + return 1 + } + + mkdir -p "${AGENT_CONFIG_DIR}" || { + log_error "创建 ${AGENT_CONFIG_DIR} 失败" + return 1 + } + + mkdir -p "${SKILLS_DIR}" || { + log_error "创建 ${SKILLS_DIR} 失败" + return 1 + } + + # 确保 MCP 配置文件父目录存在 + mkdir -p "$(dirname "${MCP_CONFIG_FILE}")" || { + log_error "创建 $(dirname "${MCP_CONFIG_FILE}") 失败" + return 1 + } + + log_success "目录结构已创建" + return 0 +} + +# ============================================================================= +# Phase 2: zzd / SDK / 用户 +# ============================================================================= + +# 安装 zzd 二进制文件 +install_zzd_binaries() { + log_info "安装 zzd 二进制文件..." + + if [[ ! -d "${ZZD_BIN_SRC}" ]]; then + log_error "zzd 二进制目录不存在: ${ZZD_BIN_SRC}" + log_error "请将 zzd/zz/gen-key/encrypt-key 放置到固定目录,或通过 ZZD_BIN_SRC 指定路径" + return 1 + fi + + local binaries=("zzd" "zz" "gen-key" "encrypt-key") + + for bin in "${binaries[@]}"; do + if [[ ! -f "${ZZD_BIN_SRC}/${bin}" ]]; then + log_error "二进制文件不存在: ${ZZD_BIN_SRC}/${bin}" + return 1 + fi + done + + for bin in "${binaries[@]}"; do + cp "${ZZD_BIN_SRC}/${bin}" "/usr/local/bin/${bin}" + chmod +x "/usr/local/bin/${bin}" + # 创建 /usr/bin/ 符号链接,确保 agent 用户 PATH 可达 + ln -sf "/usr/local/bin/${bin}" "/usr/bin/${bin}" + done + + log_success "zzd 二进制安装完成 (${#binaries[@]}/${#binaries[@]})" + return 0 +} + +# 安装 momo-agent-sdk +install_sdk() { + log_info "安装 momo-agent-sdk..." + + if [[ ! -d "${SDK_SRC}" ]]; then + log_error "SDK 源码目录不存在: ${SDK_SRC}" + log_error "请将 momo-agent-sdk 源码放置到固定目录,或通过 SDK_SRC 指定路径" + return 1 + fi + + if [[ ! -f "${SDK_SRC}/pyproject.toml" ]]; then + log_error "${SDK_SRC}/pyproject.toml 不存在" + return 1 + fi + + # 固定使用 Python 3.12(基础镜像内置) + local python_cmd="python3.12" + if ! command_exists "${python_cmd}"; then + log_error "未找到 ${python_cmd}" + return 1 + fi + + log_info "使用 ${python_cmd} 安装 SDK..." + # --no-deps: 运行时依赖已在基础镜像中预装,不再下载 + # --no-build-isolation: 使用系统已装的 setuptools/wheel 构建,不联网 + ${python_cmd} -m pip install --no-cache-dir --no-deps --no-build-isolation "${SDK_SRC}" || { + log_error "SDK 安装失败" + return 1 + } + + log_success "momo-agent-sdk 安装完成" + return 0 +} + +# 创建 agent 用户 + 配置 sudoers +setup_agent_user() { + log_info "创建 agent 用户..." + + # 创建用户(如果不存在) + if id agent &>/dev/null; then + log_info "agent 用户已存在,跳过创建" + else + groupadd -g 1001 agent || { + log_error "创建 agent 用户组失败" + return 1 + } + + useradd -u 1001 -g agent -m -s /bin/bash agent || { + log_error "创建 agent 用户失败" + return 1 + } + + log_success "agent 用户创建完成 (uid=1001)" + fi + + # 创建 monitor 用户(专用于运行 zzd 守护进程,降低 zzd 运行权限) + if id monitor &>/dev/null; then + log_info "monitor 用户已存在,跳过创建" + else + groupadd -g 1002 monitor || { + log_error "创建 monitor 用户组失败" + return 1 + } + + useradd -u 1002 -g monitor -M -r -s /sbin/nologin monitor || { + log_error "创建 monitor 用户失败" + return 1 + } + + log_success "monitor 用户创建完成 (uid=1002, nologin)" + fi + + # 创建/复用 workspace 协作组(用于 agent/runner 跨容器共享写入) + if getent group "${WORKSPACE_GID}" >/dev/null 2>&1; then + local gid_group + gid_group=$(getent group "${WORKSPACE_GID}" | cut -d: -f1) + if [[ -n "${gid_group}" && "${gid_group}" != "${WORKSPACE_GROUP}" ]]; then + log_warn "WORKSPACE_GID=${WORKSPACE_GID} 已被组 ${gid_group} 占用,复用该组" + WORKSPACE_GROUP="${gid_group}" + fi + elif getent group "${WORKSPACE_GROUP}" >/dev/null 2>&1; then + local group_gid + group_gid=$(getent group "${WORKSPACE_GROUP}" | cut -d: -f3) + if [[ -n "${group_gid}" && "${group_gid}" != "${WORKSPACE_GID}" ]]; then + log_warn "组 ${WORKSPACE_GROUP} 已存在且 gid=${group_gid},将使用该 gid" + WORKSPACE_GID="${group_gid}" + fi + else + groupadd -g "${WORKSPACE_GID}" "${WORKSPACE_GROUP}" || { + log_error "创建 workspace 协作组失败 (${WORKSPACE_GROUP}:${WORKSPACE_GID})" + return 1 + } + fi + usermod -aG "${WORKSPACE_GROUP}" agent || { + log_error "将 agent 加入 workspace 协作组失败 (${WORKSPACE_GROUP})" + return 1 + } + log_info " -> workspace 协作组: ${WORKSPACE_GROUP}(gid=${WORKSPACE_GID}), agent 已加入" + + # zzd sudoers: 只允许 monitor(zzd) 以 agent 身份执行命令 + # 参考: docs/zzd/zzd.md §安全机制 — agent 用户无 sudo 权限, zzd 以 monitor 身份运行 + rm -f /etc/sudoers.d/agent 2>/dev/null || true # 清除可能存在的错误配置 + echo 'monitor ALL=(agent) NOPASSWD: ALL' > /etc/sudoers.d/zzd + chmod 0440 /etc/sudoers.d/zzd + chown root:root /etc/sudoers.d/zzd + log_info " -> sudoers 配置已更新 (/etc/sudoers.d/zzd: root -> agent)" + + # 写入 Claude CLI 配置,跳过 onboarding 认证流程 + local agent_home="/home/agent" + mkdir -p "${agent_home}/.claude" + echo '{"hasCompletedOnboarding": true}' > "${agent_home}/.claude.json" + chown agent:agent "${agent_home}/.claude.json" + chown -R agent:agent "${agent_home}/.claude" + log_info " -> 写入 .claude.json (hasCompletedOnboarding: true)" + + return 0 +} + +# ============================================================================= +# Phase 3: workspace + zzd 配置 +# ============================================================================= + +# 创建 workspace 目录结构 +setup_workspace() { + log_info "创建 workspace 目录结构..." + + mkdir -p "${WORKSPACE_DIR}/logs" \ + "${WORKSPACE_DIR}/user" \ + "${WORKSPACE_DIR}/workstation" \ + "${WORKSPACE_DIR}/task-logs" \ + "${WORKSPACE_DIR}/worker-logs" || { + log_error "workspace 目录创建失败" + return 1 + } + + log_success "workspace 目录结构创建完成" + return 0 +} + +# 部署 agent 配置文件到 /opt/agent/ (root:agent 只读) +setup_agent_config() { + log_info "部署 agent 配置到 ${AGENT_CONFIG_DIR}..." + + mkdir -p "${SKILLS_DIR}/default" || { + log_error "${SKILLS_DIR}/default 目录创建失败" + return 1 + } + + # 默认 security.json (SDK 需要此文件存在) + if [[ ! -f "${SECURITY_FILE}" ]]; then + echo '{"rules": []}' > "${SECURITY_FILE}" + log_info " -> 创建默认 security.json" + fi + + # 默认 mcp.json (SDK 需要 mcpServers 字段) + if [[ ! -f "${MCP_CONFIG_FILE}" ]]; then + echo '{"mcpServers": {}}' > "${MCP_CONFIG_FILE}" + log_info " -> 创建默认 mcp.json" + fi + + # 占位 SKILL.md (SDK 需要至少一个有效 skill,含 YAML frontmatter) + local default_skill="${SKILLS_DIR}/default/SKILL.md" + if [[ ! -f "${default_skill}" ]]; then + printf '%s\n' '---' 'name: default' 'description: Default placeholder skill' '---' '' '# Default Skill' '' 'Placeholder skill.' > "${default_skill}" + log_info " -> 创建占位 SKILL.md" + fi + + log_success "agent 配置部署完成" + return 0 +} + +# 下载 Cedar 策略文件(从 URL 下载,回退到 /tmp/cedar-policies/ COPY 方式) +download_cedar_policies() { + log_info "下载/部署 Cedar 策略文件..." + + mkdir -p "${ZZD_POLICY_DIR}" + + local policy_tmp="/tmp/cedar-policies-download" + + if [[ -n "${CEDAR_POLICIES_URL}" ]]; then + # 方式 1: 从 URL 下载 + log_info "从 $(redact_url "${CEDAR_POLICIES_URL}") 下载 Cedar 策略..." + + if ! curl -fsSL -o "${CEDAR_ARCHIVE}" "${CEDAR_POLICIES_URL}"; then + log_error "Cedar 策略下载失败" + return 1 + fi + + mkdir -p "${policy_tmp}" + + # 解压(支持 tar.gz 和 zip) + if file "${CEDAR_ARCHIVE}" | grep -q "gzip"; then + tar -xzf "${CEDAR_ARCHIVE}" -C "${policy_tmp}" --strip-components=0 || { + log_error "Cedar 策略解压失败 (tar.gz)" + return 1 + } + elif file "${CEDAR_ARCHIVE}" | grep -q "Zip"; then + unzip -o "${CEDAR_ARCHIVE}" -d "${policy_tmp}" || { + log_error "Cedar 策略解压失败 (zip)" + return 1 + } + else + tar -xzf "${CEDAR_ARCHIVE}" -C "${policy_tmp}" --strip-components=0 || { + log_error "Cedar 策略解压失败 (未知格式)" + return 1 + } + fi + + rm -f "${CEDAR_ARCHIVE}" + + # 部署 .cedar 文件 + local count=0 + for f in "${policy_tmp}"/*.cedar; do + [[ -f "$f" ]] || continue + cp "$f" "${ZZD_POLICY_DIR}/" + ((count++)) + done + + rm -rf "${policy_tmp}" + + if [[ ${count} -eq 0 ]]; then + log_warn "下载的压缩包中无 .cedar 文件" + return 0 + fi + + elif [[ -d "/tmp/cedar-policies" ]]; then + # 方式 2: 回退到 Dockerfile COPY 的文件 + log_info "使用 /tmp/cedar-policies/ 中的策略文件 (COPY 方式)..." + + local count=0 + for f in /tmp/cedar-policies/*.cedar; do + [[ -f "$f" ]] || continue + cp "$f" "${ZZD_POLICY_DIR}/" + ((count++)) + done + + if [[ ${count} -eq 0 ]]; then + log_warn "/tmp/cedar-policies/ 中无 .cedar 文件" + return 0 + fi + else + log_warn "CEDAR_POLICIES_URL 为空且 /tmp/cedar-policies/ 不存在,跳过 Cedar 策略部署" + return 0 + fi + + # 清理 macOS 资源 fork 文件 + find "${ZZD_POLICY_DIR}" -name '._*' -delete 2>/dev/null || true + + # 策略文件 root only, agent 不可读 + if ls "${ZZD_POLICY_DIR}"/*.cedar &>/dev/null; then + chmod 0400 "${ZZD_POLICY_DIR}"/*.cedar + chown root:root "${ZZD_POLICY_DIR}"/*.cedar + local final_count + final_count=$(ls -1 "${ZZD_POLICY_DIR}"/*.cedar 2>/dev/null | wc -l) + log_success "Cedar 策略部署完成 (${final_count} 个文件, 0400 root:root)" + fi + + return 0 +} + +# 创建 zzd 运行时目录 +setup_zzd_directories() { + log_info "创建 zzd 运行时目录..." + + mkdir -p "${ZZD_CONFIG_DIR}" \ + "${ZZD_POLICY_DIR}" \ + "${ZZD_SOCKET_DIR}" \ + "${ZZD_AUDIT_DIR}" || { + log_error "zzd 目录创建失败" + return 1 + } + + # /etc/zzd 及子目录 — root:monitor, monitor(zzd) 可读, agent 不可读不可改 + chmod 0750 "${ZZD_CONFIG_DIR}" + chmod 0750 "${ZZD_POLICY_DIR}" + chown -R root:monitor "${ZZD_CONFIG_DIR}" + + # socket 和 audit 目录归 monitor 用户(zzd 进程以 monitor 身份写入) + chown monitor:monitor "${ZZD_SOCKET_DIR}" + chmod 0755 "${ZZD_SOCKET_DIR}" + chown monitor:monitor "${ZZD_AUDIT_DIR}" + chmod 0700 "${ZZD_AUDIT_DIR}" + + log_success "zzd 运行时目录创建完成 (config: 0750 root:monitor, socket/audit: monitor:monitor)" + return 0 +} + +# ============================================================================= +# Phase 4: Git +# ============================================================================= + +# 校验 git(基础镜像应已预装) +install_git() { + log_info "校验 git..." + + if ! command_exists git; then + log_error "git 未安装。当前模式禁用在线安装,请在基础镜像中预装 git 2.43.5" + return 1 + fi + + log_success "git 已安装: $(git --version)" + return 0 +} + +# 克隆 Git 项目 +clone_git_repos() { + log_info "开始克隆 Git 仓库..." + + if [[ -z "${GIT_REPOS}" ]]; then + log_warn "GIT_REPOS 环境变量为空,跳过 Git 克隆" + return 0 + fi + + if [[ -z "${GIT_TOKEN}" ]]; then + log_warn "GIT_TOKEN 环境变量为空,将使用无认证方式克隆" + fi + + # 解析 JSON 数组 + local repo_count + repo_count=$(echo "${GIT_REPOS}" | jq -r 'length') + + if [[ "${repo_count}" == "null" ]] || [[ "${repo_count}" -eq 0 ]]; then + log_warn "GIT_REPOS 为空或格式错误,跳过 Git 克隆" + return 0 + fi + + log_info "共有 ${repo_count} 个仓库需要克隆" + + local success_count=0 + local fail_count=0 + + for ((i=0; i/dev/null 2>&1; then + # 安全: 清除 .git/config 中的 token URL,防止 agent 读取 + if [[ -n "${GIT_TOKEN}" ]]; then + git -C "${target_dir}" remote set-url origin "${repo_url}" 2>/dev/null || true + fi + log_success "仓库 ${repo_name} 克隆成功" + ((success_count++)) + else + log_error "仓库 ${repo_name} 克隆失败 (url=$(redact_url "${repo_url}"))" + ((fail_count++)) + fi + done + + log_info "Git 克隆完成: 成功 ${success_count},失败 ${fail_count}" + + if [[ ${fail_count} -gt 0 ]]; then + return 1 + fi + + return 0 +} + +# ============================================================================= +# Phase 5: MCP + Skills +# ============================================================================= +# wangfenghe 20260226 该代码未知定义 暂时注释 +# 下载 MCP 配置(从 URL 下载) +#download_mcp_config() { +# log_info "下载 MCP 配置..." +# +# if [[ -z "${MCP_CONFIG_URL}" ]]; then +# log_warn "MCP_CONFIG_URL 为空,使用默认 MCP 配置" +# return 0 +# fi +# +# log_info "从 $(redact_url "${MCP_CONFIG_URL}") 下载 MCP 配置..." +# +# local tmp_mcp="/tmp/mcp_download.json" +# +# if ! curl -fsSL -o "${tmp_mcp}" "${MCP_CONFIG_URL}"; then +# log_error "MCP 配置下载失败" +# return 1 +# fi +# +# # 验证 JSON 格式 +# if ! jq empty "${tmp_mcp}" 2>/dev/null; then +# log_error "下载的 MCP 配置不是有效的 JSON 格式" +# rm -f "${tmp_mcp}" +# return 1 +# fi +# +# # 写入配置文件(格式化) +# jq '.' "${tmp_mcp}" > "${MCP_CONFIG_FILE}" || { +# log_error "写入 MCP 配置文件失败" +# rm -f "${tmp_mcp}" +# return 1 +# } +# +# rm -f "${tmp_mcp}" +# +# # 设置权限 +# chmod 600 "${MCP_CONFIG_FILE}" +# +# log_success "MCP 配置已部署到 ${MCP_CONFIG_FILE}" +# return 0 +#} + +# 下载并部署 Skills +download_skills() { + log_info "开始下载 Skills..." + + if [[ -z "${SKILLS_URL}" ]]; then + log_warn "SKILLS_URL 环境变量为空,跳过 Skills 下载" + return 0 + fi + + log_info "从 $(redact_url "${SKILLS_URL}") 下载 Skills..." + + if ! curl -fsSL -o "${SKILLS_ARCHIVE}" "${SKILLS_URL}"; then + log_error "Skills 下载失败" + return 1 + fi + + log_success "Skills 下载完成" + + # 解压到 skills 目录 + log_info "解压 Skills 到 ${SKILLS_DIR}..." + + # 清空目标目录(保留 default skill) + find "${SKILLS_DIR}" -mindepth 1 -maxdepth 1 -type d ! -name default -exec rm -rf {} \; 2>/dev/null || true + + # 解压(支持 tar.gz 和 zip 格式) + if file "${SKILLS_ARCHIVE}" | grep -q "gzip"; then + tar -xzf "${SKILLS_ARCHIVE}" -C "${SKILLS_DIR}" --strip-components=0 || { + log_error "Skills 解压失败 (tar.gz)" + return 1 + } + elif file "${SKILLS_ARCHIVE}" | grep -q "Zip"; then + unzip -o "${SKILLS_ARCHIVE}" -d "${SKILLS_DIR}" || { + log_error "Skills 解压失败 (zip)" + return 1 + } + else + # 尝试 tar.gz 解压 + tar -xzf "${SKILLS_ARCHIVE}" -C "${SKILLS_DIR}" --strip-components=0 || { + log_error "Skills 解压失败 (未知格式)" + return 1 + } + fi + + # 清理临时文件 + rm -f "${SKILLS_ARCHIVE}" + + log_success "Skills 部署完成" + + # 列出已安装的 Skills + log_info "已安装的 Skills:" + find "${SKILLS_DIR}" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | while read -r skill; do + echo " - ${skill}" + done + + return 0 +} + +# 校验 Skills 依赖(支持 Python、Node.js、Go) +# 3. 从 Git 仓库部署 Skills(优先于 SKILLS_URL) +deploy_skills_from_git() { + log_info "开始从 Git 仓库部署 Skills..." + + if [[ -z "${SKILLS_CONFIG}" ]] || [[ "${SKILLS_CONFIG}" == "[]" ]]; then + log_warn "SKILLS_CONFIG 环境变量为空,跳过 Git Skills 部署" + return 0 + fi + + # 校验 JSON 格式 + if ! echo "${SKILLS_CONFIG}" | jq empty 2>/dev/null; then + log_error "SKILLS_CONFIG 不是有效的 JSON 格式" + return 1 + fi + + local repo_url token skill_count + repo_url=$(echo "${SKILLS_CONFIG}" | jq -r '.repoUrl // empty') + token=$(echo "${SKILLS_CONFIG}" | jq -r '.token // empty') + skill_count=$(echo "${SKILLS_CONFIG}" | jq -r '.skills | length') + + if [[ -z "${repo_url}" ]]; then + log_error "SKILLS_CONFIG 中缺少 repoUrl" + return 1 + fi + + if [[ "${skill_count}" == "null" ]] || [[ "${skill_count}" -eq 0 ]]; then + log_warn "SKILLS_CONFIG 中 skills 列表为空" + return 0 + fi + + # 构建认证 URL + local clone_url="${repo_url}" + if [[ -n "${token}" ]] && [[ "${repo_url}" == https://* ]]; then + clone_url=$(echo "${repo_url}" | sed "s|https://|https://oauth2:${token}@|") + fi + + log_info "共有 ${skill_count} 个 Skills 需要从 Git 部署" + + local success_count=0 + local fail_count=0 + + for ((i=0; i&1; then + log_error "Skill ${skill_name} clone 失败" + ((fail_count++)) + continue + fi + + # 如果指定了精确 commit,checkout 到该 commit + if [[ -n "${skill_commit}" ]]; then + (cd "${tmp_dir}" && git fetch --depth 1 origin "${skill_commit}" && git checkout "${skill_commit}") 2>&1 || { + log_warn "Skill ${skill_name} checkout commit ${skill_commit} 失败,使用分支最新版本" + } + fi + + # 校验 SKILL.md 存在 + if [[ ! -f "${tmp_dir}/SKILL.md" ]]; then + log_error "Skill ${skill_name} 缺少 SKILL.md 文件" + rm -rf "${tmp_dir}" + ((fail_count++)) + continue + fi + + # 复制到目标目录(排除 .git) + rm -rf "${target_dir}" + mkdir -p "${target_dir}" + if command_exists rsync; then + rsync -a --exclude='.git' "${tmp_dir}/" "${target_dir}/" + else + cp -a "${tmp_dir}/." "${target_dir}/" + rm -rf "${target_dir}/.git" + fi + + # 清理临时目录 + rm -rf "${tmp_dir}" + + log_success "Skill ${skill_name} 部署成功" + ((success_count++)) + done + + log_info "Git Skills 部署完成: 成功 ${success_count},失败 ${fail_count}" + + if [[ ${fail_count} -gt 0 ]]; then + return 1 + fi + + return 0 +} + +# 4. 校验 Skills 依赖(支持 Python、Node.js、Go) +check_skills_dependencies() { + log_info "开始检查 Skills 依赖(支持: Python, Node.js, Go)..." + + if [[ ! -d "${SKILLS_DIR}" ]]; then + log_warn "Skills 目录不存在,跳过依赖检查" + return 0 + fi + + local skill_count=0 + local dep_issues=0 + + for skill_dir in "${SKILLS_DIR}"/*/; do + [[ -d "${skill_dir}" ]] || continue + + local skill_name + skill_name=$(basename "${skill_dir}") + ((skill_count++)) + + local found_deps=false + + # === Python 依赖检查 === + local py_req_file="" + if [[ -f "${skill_dir}requirements.txt" ]]; then + py_req_file="${skill_dir}requirements.txt" + elif [[ -f "${skill_dir}requirement.txt" ]]; then + py_req_file="${skill_dir}requirement.txt" + fi + + if [[ -n "${py_req_file}" ]]; then + found_deps=true + log_info " [${skill_name}] Python 依赖: $(basename "${py_req_file}")" + local py_issues + py_issues=$(check_python_deps "${py_req_file}") + if [[ "${py_issues}" == "FATAL" ]]; then + return 1 + fi + dep_issues=$((dep_issues + py_issues)) + fi + + # === Node.js 依赖检查 === + if [[ -f "${skill_dir}package.json" ]]; then + found_deps=true + log_info " [${skill_name}] Node.js 依赖: package.json" + local node_issues + node_issues=$(check_nodejs_deps "${skill_dir}package.json") + if [[ "${node_issues}" == "FATAL" ]]; then + return 1 + fi + dep_issues=$((dep_issues + node_issues)) + fi + + # === Go 依赖检查 === + if [[ -f "${skill_dir}go.mod" ]]; then + found_deps=true + log_info " [${skill_name}] Go 依赖: go.mod" + local go_issues + go_issues=$(check_go_deps "${skill_dir}go.mod") + if [[ "${go_issues}" == "FATAL" ]]; then + return 1 + fi + dep_issues=$((dep_issues + go_issues)) + fi + + if [[ "${found_deps}" == "false" ]]; then + log_info " [${skill_name}] 无依赖文件,跳过检查" + fi + done + + log_info "依赖检查完成: 扫描 ${skill_count} 个 Skills" + + if [[ ${dep_issues} -gt 0 ]]; then + log_error "发现 ${dep_issues} 个依赖问题,构建失败" + return 1 + fi + + log_success "所有依赖校验通过" + return 0 +} + +# Python 依赖检查 +check_python_deps() { + local req_file="$1" + local issues=0 + + if ! python3.12 -m pip --version &> /dev/null; then + log_error " python3.12 -m pip 不可用,无法检查 Python 依赖" + echo "FATAL" + return + fi + + while IFS= read -r line || [[ -n "$line" ]]; do + # 跳过空行和注释 + [[ -z "${line}" ]] && continue + [[ "${line}" =~ ^# ]] && continue + [[ "${line}" =~ ^[[:space:]]*$ ]] && continue + # 跳过 -r、-e 等特殊行 + [[ "${line}" =~ ^- ]] && continue + + # 解析包名(支持 package==1.0, package>=1.0 等格式) + local pkg_name pkg_spec + pkg_name=$(echo "${line}" | sed -E 's/([a-zA-Z0-9_-]+).*/\1/' | tr '[:upper:]' '[:lower:]' | tr '_' '-') + pkg_spec="${line}" + + if python3.12 -m pip show "${pkg_name}" &> /dev/null; then + local installed_version + installed_version=$(python3.12 -m pip show "${pkg_name}" 2>/dev/null | grep "^Version:" | awk '{print $2}') + + if python3.12 -c "import pkg_resources; pkg_resources.require('${pkg_spec}')" 2>/dev/null; then + log_success " [Python][OK] ${pkg_name}==${installed_version}" + else + log_error " [Python][VERSION] ${pkg_name}==${installed_version} 不满足版本要求: ${pkg_spec}" + ((issues++)) + fi + else + log_error " [Python][MISSING] ${pkg_name} 未安装 (需要: ${pkg_spec})" + ((issues++)) + fi + done < "${req_file}" + + echo ${issues} +} + +# Node.js 依赖检查 +check_nodejs_deps() { + local pkg_json="$1" + local issues=0 + + # 检查 npm 是否可用 + if ! command -v npm &> /dev/null; then + log_error " npm 未安装,无法检查 Node.js 依赖" + echo "FATAL" + return + fi + + # 提取 dependencies 和 devDependencies + local deps + deps=$(jq -r '(.dependencies // {}) + (.devDependencies // {}) | to_entries[] | "\(.key)|\(.value)"' "${pkg_json}" 2>/dev/null) + + if [[ -z "${deps}" ]]; then + log_info " package.json 无依赖声明" + echo 0 + return + fi + + while IFS='|' read -r pkg_name version_spec; do + [[ -z "${pkg_name}" ]] && continue + + # 检查包是否全局安装 + local installed_version="" + installed_version=$(npm list -g "${pkg_name}" --depth=0 2>/dev/null | grep "${pkg_name}@" | sed -E 's/.*@([0-9.]+).*/\1/' | head -1) + + if [[ -n "${installed_version}" ]]; then + log_success " [Node][OK] ${pkg_name}@${installed_version} (全局)" + else + # 检查是否在常见全局模块中 + if npm list -g --depth=0 2>/dev/null | grep -q "${pkg_name}"; then + log_success " [Node][OK] ${pkg_name} (全局已安装)" + else + log_error " [Node][MISSING] ${pkg_name}@${version_spec} 未全局安装" + ((issues++)) + fi + fi + done <<< "${deps}" + + echo ${issues} +} + +# Go 依赖检查 +check_go_deps() { + local skill_dir="$1" + local go_mod="$1" + local issues=0 + + # 检查 go 是否可用 + if ! command -v go &> /dev/null; then + log_error " go 未安装,无法检查 Go 依赖" + echo "FATAL" + return + fi + + local go_mod="${skill_dir}/go.mod" + if [[ ! -f "${go_mod}" ]]; then + go_mod="${skill_dir}go.mod" + fi + + local required_go_version + required_go_version=$(grep "^go " "${go_mod}" | awk '{print $2}') + + if [[ -n "${required_go_version}" ]]; then + local current_go_version + current_go_version=$(go version | sed -E 's/go version go([0-9.]+).*/\1/') + + log_info " Go 版本要求: ${required_go_version}, 当前版本: ${current_go_version}" + + # 简单版本比较(主版本.次版本) + local req_major req_minor cur_major cur_minor + req_major=$(echo "${required_go_version}" | cut -d. -f1) + req_minor=$(echo "${required_go_version}" | cut -d. -f2) + cur_major=$(echo "${current_go_version}" | cut -d. -f1) + cur_minor=$(echo "${current_go_version}" | cut -d. -f2) + + if [[ "${cur_major}" -gt "${req_major}" ]] || \ + [[ "${cur_major}" -eq "${req_major}" && "${cur_minor}" -ge "${req_minor}" ]]; then + log_success " [Go][OK] Go 版本满足要求" + else + log_error " [Go][VERSION] Go ${current_go_version} 不满足版本要求 (需要 >= ${required_go_version})" + ((issues++)) + fi + fi + + # 提取 require 块中的依赖 +# local in_require=false +# while IFS= read -r line; do +# if [[ "${line}" =~ ^require[[:space:]]*\( ]]; then +# in_require=true +# continue +# fi +# if [[ "${in_require}" == "true" && "${line}" =~ ^\) ]]; then +# in_require=false +# continue +# fi +# if [[ "${line}" =~ ^require[[:space:]]+ && ! "${line}" =~ \( ]]; then +# local module_path module_version +# module_path=$(echo "${line}" | awk '{print $2}') +# module_version=$(echo "${line}" | awk '{print $3}') +# log_info " [Go][DEP] ${module_path} ${module_version}" +# continue +# fi +# if [[ "${in_require}" == "true" ]]; then +# line=$(echo "${line}" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//') +# [[ -z "${line}" ]] && continue +# [[ "${line}" =~ ^// ]] && continue +# local module_path module_version +# module_path=$(echo "${line}" | awk '{print $1}') +# module_version=$(echo "${line}" | awk '{print $2}') +# if [[ -n "${module_path}" ]]; then +# log_info " [Go][DEP] ${module_path} ${module_version}" +# fi +# fi +# done < "${go_mod}" + + log_info " [Go] 依赖将在构建时通过 go mod download 自动获取" + echo ${issues} +} + +# 4. 部署 MCP 配置(优先 MCP_CONFIG 环境变量,其次 MCP_CONFIG_URL 下载) +deploy_mcp_config() { + log_info "开始部署 MCP 配置..." + + # 优先级 1: MCP_CONFIG 环境变量(JSON 字符串,由构建系统从岗位配置生成) + if [[ -n "${MCP_CONFIG}" ]] && [[ "${MCP_CONFIG}" != "[]" ]]; then + log_info "检测到 MCP_CONFIG 环境变量,直接写入配置..." + + # 验证 JSON 格式 + if ! echo "${MCP_CONFIG}" | jq empty 2>/dev/null; then + log_error "MCP_CONFIG 不是有效的 JSON 格式" + return 1 + fi + + # 写入配置文件 + echo "${MCP_CONFIG}" | jq '.' > "${MCP_CONFIG_FILE}" || { + log_error "写入 MCP 配置文件失败" + return 1 + } + + # 设置权限 + chmod 0440 "${MCP_CONFIG_FILE}" + chown root:agent "${MCP_CONFIG_FILE}" 2>/dev/null || true + + log_success "MCP 配置已从 MCP_CONFIG 环境变量部署到 ${MCP_CONFIG_FILE}" + return 0 + fi + + # 优先级 2: MCP_CONFIG_URL 下载 + if [[ -n "${MCP_CONFIG_URL}" ]]; then + log_info "从 ${MCP_CONFIG_URL} 下载 MCP 配置..." + + local tmp_mcp="/tmp/mcp_download.json" + + if ! curl -fsSL -o "${tmp_mcp}" "${MCP_CONFIG_URL}"; then + log_error "MCP 配置下载失败" + rm -f "${tmp_mcp}" + return 1 + fi + + # 验证 JSON 格式 + if ! jq empty "${tmp_mcp}" 2>/dev/null; then + log_error "下载的 MCP 配置不是有效的 JSON 格式" + rm -f "${tmp_mcp}" + return 1 + fi + + # 写入配置文件(格式化) + jq '.' "${tmp_mcp}" > "${MCP_CONFIG_FILE}" || { + log_error "写入 MCP 配置文件失败" + rm -f "${tmp_mcp}" + return 1 + } + + rm -f "${tmp_mcp}" + chmod 0440 "${MCP_CONFIG_FILE}" + chown root:agent "${MCP_CONFIG_FILE}" 2>/dev/null || true + + log_success "MCP 配置已从 URL 部署到 ${MCP_CONFIG_FILE}" + return 0 + fi + + log_warn "MCP_CONFIG 和 MCP_CONFIG_URL 均为空,跳过 MCP 配置部署" + return 0 +} +# 部署 start 脚本和 ai_employee.py 到 /opt/agent/ (root:root, agent 不可改) +# 安全: 放在 /workspace 外防止 agent 篡改后 root 执行被劫持的脚本 +deploy_start_scripts() { + log_info "部署启动脚本到 /opt/agent/..." + + local deploy_dir="/opt/agent" + mkdir -p "${deploy_dir}" + + if [[ ! -d "${START_SCRIPTS_SRC}" ]]; then + log_error "启动脚本目录不存在: ${START_SCRIPTS_SRC}" + log_error "请将 start-single.sh/start-dual.sh/ai_employee.py 放置到固定目录,或通过 START_SCRIPTS_SRC 指定路径" + return 1 + fi + + local scripts=("start-single.sh" "start-dual.sh" "ai_employee.py") + local deployed=0 + + for script in "${scripts[@]}"; do + if [[ ! -f "${START_SCRIPTS_SRC}/${script}" ]]; then + log_error "脚本不存在: ${START_SCRIPTS_SRC}/${script}" + return 1 + fi + + cp "${START_SCRIPTS_SRC}/${script}" "${deploy_dir}/${script}" + chmod 0755 "${deploy_dir}/${script}" + chown root:root "${deploy_dir}/${script}" + ((deployed++)) + log_info " -> ${deploy_dir}/${script}" + done + + log_success "启动脚本部署完成 (${deployed}/${#scripts[@]}, root:root 0755)" + return 0 +} + +# 最终权限设置 +finalize_permissions() { + log_info "设置最终权限..." + + # workspace 归 agent + workspace 协作组,目录 setgid 保证跨容器共享组继承 + if id agent &>/dev/null; then + chown -R agent:"${WORKSPACE_GROUP}" "${WORKSPACE_DIR}" + chmod -R g+rwX "${WORKSPACE_DIR}" + for dir in "${WORKSPACE_DIR}" "${WORKSPACE_DIR}/logs" "${WORKSPACE_DIR}/user" "${WORKSPACE_DIR}/workstation" "${WORKSPACE_DIR}/task-logs" "${WORKSPACE_DIR}/worker-logs"; do + mkdir -p "${dir}" + chown agent:"${WORKSPACE_GROUP}" "${dir}" + chmod 2770 "${dir}" + done + log_info " -> ${WORKSPACE_DIR} owner=agent:${WORKSPACE_GROUP}, 共享目录=2770(setgid)" + + # /opt/agent/ 下配置文件:root:agent 0440(agent 只读不可改) + for cfg_file in "${SECURITY_FILE}" "${MCP_CONFIG_FILE}" "${AGENT_CONFIG_DIR}/config.json"; do + if [[ -f "${cfg_file}" ]]; then + chmod 0440 "${cfg_file}" + chown root:agent "${cfg_file}" + fi + done + log_info " -> config/security/mcp.json → 0440 root:agent" + + # skills 目录:root:agent 0750(agent 可读可进入,不可改) + if [[ -d "${SKILLS_DIR}" ]]; then + chown -R root:agent "${SKILLS_DIR}" + find "${SKILLS_DIR}" -type d -exec chmod 0750 {} \; + find "${SKILLS_DIR}" -type f -exec chmod 0440 {} \; + fi + log_info " -> skills/ → 0750/0440 root:agent" + + log_success "workspace + /opt/agent/ 权限设置完成" + else + log_warn "agent 用户不存在,跳过权限设置" + fi + + # /etc/zzd 加固校验 — monitor(zzd) 可读, agent 不可读不可改 + if [[ -d "${ZZD_CONFIG_DIR}" ]]; then + find "${ZZD_CONFIG_DIR}" -type d -exec chmod 0750 {} \; + find "${ZZD_CONFIG_DIR}" -type f -exec chmod 0440 {} \; + chown -R root:monitor "${ZZD_CONFIG_DIR}" + # socket 和 audit 目录归 monitor(zzd 进程需要写入) + if [[ -d "${ZZD_SOCKET_DIR}" ]]; then + chown monitor:monitor "${ZZD_SOCKET_DIR}" + chmod 0755 "${ZZD_SOCKET_DIR}" + fi + if [[ -d "${ZZD_AUDIT_DIR}" ]]; then + chown monitor:monitor "${ZZD_AUDIT_DIR}" + chmod 0700 "${ZZD_AUDIT_DIR}" + fi + log_success "/etc/zzd 权限加固完成 (config: 0750/0440 root:monitor; socket/audit: monitor:monitor)" + fi + + return 0 +} + +# ============================================================================= +# Phase 7: 网络隔离 (iptables) +# ============================================================================= + +# 配置 iptables 网络白名单 +# Gateway 模式下: MCP 访问全部走 Gateway (ClusterIP),不需要逐个开放 MCP 端点 +# 仅放行: loopback / DNS / RFC1918 / 运行时环境变量端点 / Skills 端点 +# 策略: 使用自定义链 AGENT_OUTPUT,不影响其他用户/进程的网络访问 +setup_network_firewall() { + log_info "配置 iptables 网络白名单..." + + if ! command_exists iptables; then + log_warn "iptables 未安装,跳过网络隔离配置" + return 0 + fi + + local allowed_hosts=() + + # 从 Skills 中提取可能声明的外部端点 + if [[ -d "${SKILLS_DIR}" ]]; then + for skill_meta in "${SKILLS_DIR}"/*/SKILL.md; do + [[ -f "${skill_meta}" ]] || continue + while IFS= read -r endpoint_host; do + [[ -n "${endpoint_host}" ]] && allowed_hosts+=("${endpoint_host}") + done < <(grep -oP 'https?://\K[^/:]+' "${skill_meta}" 2>/dev/null | sort -u) + done + fi + + # 去重 + local unique_hosts=() + local seen_map="" + for h in "${allowed_hosts[@]}"; do + if [[ "${seen_map}" != *"|${h}|"* ]]; then + unique_hosts+=("${h}") + seen_map="${seen_map}|${h}|" + fi + done + + local agent_uid=1001 + + local fw_script="/opt/agent/setup-firewall.sh" + + cat > "${fw_script}" <<'FWEOF' +#!/bin/bash +# Agent 网络隔离防火墙规则 (自动生成,请勿手动编辑) +# MCP 访问已统一通过 Gateway (K8s ClusterIP),无需逐个开放 MCP 端点 +set -e + +AGENT_UID=1001 + +iptables -N AGENT_OUTPUT 2>/dev/null || iptables -F AGENT_OUTPUT + +iptables -D OUTPUT -m owner --uid-owner ${AGENT_UID} -j AGENT_OUTPUT 2>/dev/null || true +iptables -A OUTPUT -m owner --uid-owner ${AGENT_UID} -j AGENT_OUTPUT + +# ── 基础规则 ── +iptables -A AGENT_OUTPUT -o lo -j ACCEPT +iptables -A AGENT_OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT + +# ── DNS (UDP/TCP 53) ── +iptables -A AGENT_OUTPUT -p udp --dport 53 -j ACCEPT +iptables -A AGENT_OUTPUT -p tcp --dport 53 -j ACCEPT + +# ── RFC1918 私有网络: 集群内部通信 (Redis, K8s Service, Pod CIDR, MCP Gateway) ── +iptables -A AGENT_OUTPUT -d 10.0.0.0/8 -j ACCEPT +iptables -A AGENT_OUTPUT -d 172.16.0.0/12 -j ACCEPT +iptables -A AGENT_OUTPUT -d 192.168.0.0/16 -j ACCEPT + +# ── 运行时服务端点 (从环境变量动态提取公网 host) ── +extract_host() { + local url="$1" + echo "${url}" | sed -E 's|^[a-zA-Z]+://||; s|/.*||; s|:[0-9]+$||' +} + +for env_url in "${REDIS_URL:-}" "${API_BASE_URL:-}" "${WS_BASE_URL:-}" "${LLM_GATEWAY_URL:-}" "${MAIN_PY_URL:-}"; do + [ -z "${env_url}" ] && continue + local_host=$(extract_host "${env_url}") + [ -z "${local_host}" ] && continue + case "${local_host}" in + 10.*|172.1[6-9].*|172.2[0-9].*|172.3[0-1].*|192.168.*|*.svc|*.svc.*|localhost) continue ;; + esac + iptables -A AGENT_OUTPUT -d "${local_host}" -j ACCEPT +done + +FWEOF + + # 追加 Skills 白名单 + if [[ ${#unique_hosts[@]} -gt 0 ]]; then + echo "# ── Skills 白名单 (构建时静态) ──" >> "${fw_script}" + for host in "${unique_hosts[@]}"; do + echo "iptables -A AGENT_OUTPUT -d \"${host}\" -j ACCEPT" >> "${fw_script}" + done + echo "" >> "${fw_script}" + fi + + cat >> "${fw_script}" <<'FWEOF2' +# ── 默认拒绝: agent 用户的所有其他出站连接 ── +iptables -A AGENT_OUTPUT -j REJECT --reject-with icmp-port-unreachable + +echo "[INFO] Agent 网络隔离防火墙已生效 (agent uid=${AGENT_UID})" +FWEOF2 + + chmod 0700 "${fw_script}" + chown root:root "${fw_script}" + + if [[ ${#unique_hosts[@]} -gt 0 ]]; then + log_info "白名单主机 (${#unique_hosts[@]}): ${unique_hosts[*]}" + else + log_info "MCP 访问通过 Gateway (K8s ClusterIP),无需额外白名单" + fi + + log_info "防火墙规则脚本已生成: ${fw_script}" + + if bash "${fw_script}" 2>/dev/null; then + log_success "iptables 网络白名单已在构建环境生效" + else + log_warn "构建环境无法应用 iptables(可能缺少 NET_ADMIN 权限),规则已保存到 ${fw_script},需在运行时由 start.sh 加载" + fi + + return 0 +} + +# ============================================================================= +# 主函数 +# ============================================================================= +main() { + log_info "==========================================" + log_info "K8s Agent 镜像构建脚本启动(生产版本 v2)" + log_info "参考: docs/docker.md §3.2" + log_info "==========================================" + + local exit_code=0 + + log_info "构建输入目录解析结果:" + log_info " BUILD_ASSETS_ROOT=${BUILD_ASSETS_ROOT}" + log_info " ZZD_BIN_SRC=${ZZD_BIN_SRC}" + log_info " SDK_SRC=${SDK_SRC}" + log_info " START_SCRIPTS_SRC=${START_SCRIPTS_SRC}" + + # ── Phase 1: 系统依赖 ── + if ! check_prerequisites; then + log_error "前置条件检查失败" + exit 1 + fi + + # 创建目录结构 + if ! create_directories; then + log_error "目录创建失败" + exit 1 + fi + + # ── Phase 2: zzd + SDK + 用户 ── + if ! install_zzd_binaries; then + log_error "zzd 二进制安装失败" + exit_code=1 + fi + + if ! install_sdk; then + log_error "SDK 安装失败" + exit 1 + fi + + if ! setup_agent_user; then + log_error "agent 用户创建失败" + exit 1 + fi + + # ── Phase 3: workspace + zzd 配置 ── + if ! setup_workspace; then + log_error "workspace 目录结构创建失败" + exit 1 + fi + + if ! setup_agent_config; then + log_error "agent 配置部署失败" + exit 1 + fi + + if ! download_cedar_policies; then + log_error "Cedar 策略下载/部署失败" + exit_code=1 + fi + + if ! setup_zzd_directories; then + log_error "zzd 目录创建失败" + exit 1 + fi + + # ── Phase 4: Git ── + if ! install_git; then + log_error "git 安装失败" + exit 1 + fi + + if ! clone_git_repos; then + log_error "Git 仓库克隆失败" + exit_code=1 + fi + + # 2 & 3. 部署 Skills(优先 SKILLS_CONFIG Git clone,其次 SKILLS_URL 下载) + if [[ -n "${SKILLS_CONFIG}" ]] && [[ "${SKILLS_CONFIG}" != "[]" ]]; then + if ! deploy_skills_from_git; then + log_error "Skills Git 部署失败" + exit_code=1 + fi + elif [[ -n "${SKILLS_URL}" ]]; then + if ! download_skills; then + log_error "Skills 下载失败" + exit_code=1 + fi + else + log_info "未配置 Skills(SKILLS_CONFIG 和 SKILLS_URL 均为空),跳过" + fi + + # 3. 检查依赖(失败则终止构建) + if ! check_skills_dependencies; then + log_error "Skills 依赖检查失败" + exit 1 + fi + + # ── Phase 6: 启动脚本 + 权限 ── + if ! deploy_start_scripts; then + log_error "启动脚本部署失败" + exit_code=1 + fi + + if ! finalize_permissions; then + log_error "权限设置失败" + exit_code=1 + fi + + # 5. 部署 MCP 配置 + if ! deploy_mcp_config; then + log_error "MCP 配置部署失败" + exit_code=1 + fi + + # ── Phase 7: 网络隔离 (生成防火墙脚本,运行时由 ENABLE_NETWORK_FIREWALL 控制是否加载) ── + if ! setup_network_firewall; then + log_error "网络隔离配置失败" + exit_code=1 + fi + + # ── 清理构建临时文件(避免策略/源码残留在镜像中被 agent 读取)── + log_info "清理构建临时文件..." + rm -rf /tmp/cedar-policies /tmp/cedar-policies-download + + # 仅清理 /tmp 回退路径,避免误删固定资产目录(如 /opt/momo-agent-build) + for cleanup_dir in "${ZZD_BIN_SRC}" "${SDK_SRC}" "${START_SCRIPTS_SRC}"; do + if [[ "${cleanup_dir}" == /tmp/* ]]; then + rm -rf "${cleanup_dir}" + fi + done + + log_success "构建临时文件已清理" + + # ── 输出结果 ── + log_info "==========================================" + if [[ ${exit_code} -eq 0 ]]; then + log_success "构建完成" + else + log_error "构建完成但存在错误" + fi + log_info "==========================================" + + # 输出目录结构 + log_info "工作目录结构:" + if command -v tree &> /dev/null; then + tree -L 3 "${WORKSPACE_DIR}" 2>/dev/null || ls -laR "${WORKSPACE_DIR}" + else + ls -laR "${WORKSPACE_DIR}" 2>/dev/null | head -40 + fi + + log_info "zzd 配置目录:" + ls -la "${ZZD_CONFIG_DIR}" 2>/dev/null || true + ls -la "${ZZD_POLICY_DIR}" 2>/dev/null || true + + log_info "Agent 组件目录:" + if command -v tree &> /dev/null; then + tree -L 2 /opt/agent/ 2>/dev/null || ls -la /opt/agent/ + else + ls -la /opt/agent/ 2>/dev/null || true + fi + + exit ${exit_code} +} + +# 执行主函数 +main "$@" diff --git a/back/src/main/resources/scripts/start-runner.sh b/back/src/main/resources/scripts/start-runner.sh new file mode 100755 index 0000000..0d14899 --- /dev/null +++ b/back/src/main/resources/scripts/start-runner.sh @@ -0,0 +1,241 @@ +#!/bin/bash +# ============================================================================= +# Runner 容器入口脚本(基于 Rocky Linux 基础镜像) +# +# 职责: 准备 sshd 环境 → 等待 Agent 公钥 → 配置 SSH 认证 → 前台启动 sshd +# +# 适用场景: +# - K8s Pod sidecar: 镜像应已预装 sshd(避免运行时安装) +# - Docker Compose: 纯 Rocky 基础镜像,脚本自动兜底安装 +# +# 运行时依赖: +# - 共享卷 /shared-keys (Agent 写入 zzd_pubkey.pub) +# - 共享卷 /workspace (工作目录) +# +# 重点: +# - Runner 不负责 zzd/worker,只负责"被 SSH 执行" +# - 这些步骤每次 Pod 重建都要重复(共享卷通常是 emptyDir,会清空) +# - Agent 的 start-dual.sh 会等待 Runner SSH 就绪,然后再启动 zzd+worker +# ============================================================================= +set -e + +readonly SHARED_KEY_DIR="/shared-keys" +readonly PUBKEY_FILE="${SHARED_KEY_DIR}/zzd_pubkey.pub" +readonly PUBKEY_TIMEOUT="${PUBKEY_TIMEOUT:-120}" +readonly WORKSPACE_GROUP="${WORKSPACE_GROUP:-workspace}" +readonly WORKSPACE_GID="${WORKSPACE_GID:-2000}" +readonly MOMOBOT_UID="${MOMOBOT_UID:-1001}" + +# ============================================================================= +# 日志 +# ============================================================================= +log_info() { echo "[Runner][INFO] $(date '+%H:%M:%S') $*"; } +log_error() { echo "[Runner][ERROR] $(date '+%H:%M:%S') $*" >&2; } +log_warn() { echo "[Runner][WARN] $(date '+%H:%M:%S') $*"; } + +configure_momobot_python_env() { + local bashrc="/home/momobot/.bashrc" + local python_bin="" + + if [[ -x /usr/bin/python3.12 ]]; then + python_bin="/usr/bin/python3.12" + ln -sf /usr/bin/python3.12 /usr/local/bin/python3 2>/dev/null || true + ln -sf /usr/bin/python3.12 /usr/local/bin/python 2>/dev/null || true + elif command -v python3.12 >/dev/null 2>&1; then + python_bin="$(command -v python3.12)" + elif command -v python3 >/dev/null 2>&1; then + python_bin="$(command -v python3)" + fi + + if [[ -z "${python_bin}" ]]; then + log_warn "未找到 python3.12/python3,跳过 momobot Python 环境变量注入" + return 0 + fi + + sed -i '/# >>> workspace-python >>>/,/# <<< workspace-python <</dev/null || true + cat >> "${bashrc}" <>> workspace-python >>> +export PYTHON_BIN="${python_bin}" +export PYTHON="${python_bin}" +export UV_PYTHON="${python_bin}" +export PATH="/usr/bin:/usr/local/bin:\$PATH" +# <<< workspace-python <<< +EOF + chown momobot:momobot "${bashrc}" + log_info "momobot python 默认解释器: ${python_bin} ($("${python_bin}" --version 2>&1))" +} + +setup_workspace_group_permissions() { + local resolved_group="${WORKSPACE_GROUP}" + + if getent group "${WORKSPACE_GID}" >/dev/null 2>&1; then + resolved_group=$(getent group "${WORKSPACE_GID}" | cut -d: -f1) + elif ! getent group "${WORKSPACE_GROUP}" >/dev/null 2>&1; then + groupadd -g "${WORKSPACE_GID}" "${WORKSPACE_GROUP}" || { + log_error "创建 workspace 协作组失败 (${WORKSPACE_GROUP}:${WORKSPACE_GID})" + return 1 + } + fi + + usermod -aG "${resolved_group}" momobot || { + log_error "将 momobot 加入 workspace 协作组失败 (${resolved_group})" + return 1 + } + + for dir in /workspace /workspace/logs /workspace/user /workspace/workstation /workspace/task-logs /workspace/worker-logs; do + mkdir -p "${dir}" + chgrp -R "${resolved_group}" "${dir}" + chmod -R g+rwX "${dir}" + find "${dir}" -type d -exec chmod g+s {} + + chmod 2770 "${dir}" + done + + log_info "/workspace 权限已对齐 (group=${resolved_group}, dirs=workspace/logs/user/workstation/task-logs/worker-logs, umask=0002)" +} + +# ============================================================================= +# 1. 启动 sshd 所需环境(镜像内应已带好,兜底运行时安装) +# ============================================================================= +log_info "================================================" +log_info " Runner 容器启动" +log_info " PUBKEY_TIMEOUT: ${PUBKEY_TIMEOUT}s" +log_info "================================================" + +if [ ! -x /usr/sbin/sshd ]; then + log_warn "sshd 未预装,运行时安装(生产镜像应预装以加速启动)..." + dnf install -y openssh-server openssh-clients sudo && dnf clean all + if [ ! -x /usr/sbin/sshd ]; then + log_error "sshd 安装失败" + exit 1 + fi + log_info "sshd 运行时安装完成" +else + log_info "sshd 已预装" +fi + +# 生成 SSH host keys(如果不存在) +if [ ! -f /etc/ssh/ssh_host_rsa_key ] && [ ! -f /etc/ssh/ssh_host_ed25519_key ]; then + log_info "生成 SSH host keys..." + ssh-keygen -A +fi + +# SSH 配置(幂等写入,多次执行不会重复追加) +log_info "配置 sshd_config..." +sed -i 's/^#*PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config +sed -i 's/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config +sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config +grep -q '^AuthorizedKeysFile' /etc/ssh/sshd_config \ + || echo 'AuthorizedKeysFile .ssh/authorized_keys' >> /etc/ssh/sshd_config + +# ============================================================================= +# 2. 确保有执行用户(通常 momobot,必要时 root 也可) +# ============================================================================= +if id momobot &>/dev/null; then + CURRENT_UID="$(id -u momobot)" + if [ "$CURRENT_UID" != "$MOMOBOT_UID" ]; then + log_info "校准用户 momobot UID: ${CURRENT_UID} -> ${MOMOBOT_UID}" + usermod -u "${MOMOBOT_UID}" momobot + chown -R momobot:momobot /home/momobot + else + log_info "用户 momobot 已存在 (uid=${CURRENT_UID})" + fi +else + log_info "创建用户 momobot (uid=${MOMOBOT_UID})..." + groupadd -g "${MOMOBOT_UID}" momobot 2>/dev/null || true + useradd -u "${MOMOBOT_UID}" -g momobot -m -s /bin/bash momobot 2>/dev/null || true + echo 'momobot ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/momobot + chmod 0440 /etc/sudoers.d/momobot + log_info "用户 momobot 已创建" +fi +log_info "momobot 最终 uid=$(id -u momobot)" + +if ! grep -q '^umask 0002$' /home/momobot/.bashrc 2>/dev/null; then + echo 'umask 0002' >> /home/momobot/.bashrc + chown momobot:momobot /home/momobot/.bashrc +fi +configure_momobot_python_env + +# ============================================================================= +# 3. 等待 Agent 写入共享卷公钥: /shared-keys/zzd_pubkey.pub +# ============================================================================= +log_info "等待 Agent 公钥: ${PUBKEY_FILE} ..." +WAIT=0 +while [ ! -f "$PUBKEY_FILE" ] && [ $WAIT -lt $PUBKEY_TIMEOUT ]; do + sleep 1 + WAIT=$((WAIT + 1)) + if [ $((WAIT % 10)) -eq 0 ]; then + log_info " 等待中... (${WAIT}/${PUBKEY_TIMEOUT}s)" + fi +done + +if [ ! -f "$PUBKEY_FILE" ]; then + log_error "超时 (${PUBKEY_TIMEOUT}s): 未收到 Agent 公钥 ${PUBKEY_FILE}" + log_error "请检查 Agent 容器是否正常启动并生成了密钥" + exit 1 +fi + +log_info "检测到公钥: ${PUBKEY_FILE}" + +# ============================================================================= +# 4. 把公钥写到 authorized_keys(momobot + root) +# ============================================================================= +log_info "配置 SSH authorized_keys..." + +# momobot 用户 +mkdir -p /home/momobot/.ssh +cp "$PUBKEY_FILE" /home/momobot/.ssh/authorized_keys +chown -R momobot:momobot /home/momobot/.ssh +chmod 700 /home/momobot/.ssh +chmod 600 /home/momobot/.ssh/authorized_keys +log_info " -> momobot authorized_keys 已配置" + +# root 用户(可选,调试用) +mkdir -p /root/.ssh +cp "$PUBKEY_FILE" /root/.ssh/authorized_keys +chmod 700 /root/.ssh +chmod 600 /root/.ssh/authorized_keys +log_info " -> root authorized_keys 已配置" + +# ============================================================================= +# 5. 准备 /workspace 权限 +# ============================================================================= +log_info "设置 /workspace 权限..." +mkdir -p /workspace +setup_workspace_group_permissions + +# ============================================================================= +# 6. 启动 SSH 服务并监控 Agent 退出信号 +# +# K8s 1.18 无原生 sidecar 支持,Runner 需要主动感知 Agent 退出: +# Agent 退出时写 /shared-keys/shutdown 标记 +# Runner 检测到标记后停止 sshd,容器退出,Pod 整体终止 +# ============================================================================= +readonly SHUTDOWN_MARKER="${SHARED_KEY_DIR}/shutdown" +readonly SHUTDOWN_CHECK_INTERVAL=5 + +log_info "================================================" +log_info " sshd 启动 (后台模式 + shutdown 监控)" +log_info "================================================" + +/usr/sbin/sshd -D -e & +SSHD_PID=$! + +shutdown_runner() { + log_info "正在停止 sshd (pid=$SSHD_PID)..." + kill -TERM "$SSHD_PID" 2>/dev/null || true + wait "$SSHD_PID" 2>/dev/null || true + log_info "Runner 已退出" +} +trap shutdown_runner EXIT SIGTERM SIGINT + +while kill -0 "$SSHD_PID" 2>/dev/null; do + if [ -f "$SHUTDOWN_MARKER" ]; then + log_info "检测到 Agent shutdown 标记: ${SHUTDOWN_MARKER}" + exit 0 + fi + sleep "$SHUTDOWN_CHECK_INTERVAL" +done + +SSHD_EXIT=$? +log_warn "sshd 意外退出 (code=$SSHD_EXIT)" +exit "$SSHD_EXIT" From 0cca3667cb17ff5c06c5aec4199e01cb9f3c5423 Mon Sep 17 00:00:00 2001 From: wangfenghe Date: Fri, 20 Mar 2026 14:05:43 +0800 Subject: [PATCH 04/21] fix(back): avoid bean name conflict with k8s starter generators --- .../main/java/com/linkwork/service/PodGroupSpecGenerator.java | 2 +- back/src/main/java/com/linkwork/service/PodSpecGenerator.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/back/src/main/java/com/linkwork/service/PodGroupSpecGenerator.java b/back/src/main/java/com/linkwork/service/PodGroupSpecGenerator.java index a7b124e..4922add 100644 --- a/back/src/main/java/com/linkwork/service/PodGroupSpecGenerator.java +++ b/back/src/main/java/com/linkwork/service/PodGroupSpecGenerator.java @@ -11,7 +11,7 @@ /** * PodGroup Spec 生成器 */ -@Component +@Component("legacyPodGroupSpecGenerator") @Slf4j public class PodGroupSpecGenerator { diff --git a/back/src/main/java/com/linkwork/service/PodSpecGenerator.java b/back/src/main/java/com/linkwork/service/PodSpecGenerator.java index b4b9c89..3258c80 100644 --- a/back/src/main/java/com/linkwork/service/PodSpecGenerator.java +++ b/back/src/main/java/com/linkwork/service/PodSpecGenerator.java @@ -33,7 +33,7 @@ * - svc-{serviceId}-agent-config: config.json → /opt/agent/config.json * - runner-start-script: start-runner.sh → /opt/runner/start-runner.sh */ -@Component +@Component("legacyPodSpecGenerator") @Slf4j public class PodSpecGenerator { From 58df01b7e9043fe5d31169a8b4c5c7fb57230da9 Mon Sep 17 00:00:00 2001 From: "li.zhiwen" Date: Fri, 20 Mar 2026 19:31:04 +0800 Subject: [PATCH 05/21] fix(task): keep workspace alias path aligned with general_agent --- back/src/main/java/com/linkwork/service/TaskService.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/back/src/main/java/com/linkwork/service/TaskService.java b/back/src/main/java/com/linkwork/service/TaskService.java index 79dd422..fcdee89 100644 --- a/back/src/main/java/com/linkwork/service/TaskService.java +++ b/back/src/main/java/com/linkwork/service/TaskService.java @@ -299,6 +299,9 @@ private void pushToDispatchQueue(Task task) { if (task.getRoleId() != null) { message.put("role_id", String.valueOf(task.getRoleId())); } + if (StringUtils.hasText(task.getSelectedModel())) { + message.put("selected_model", task.getSelectedModel().trim()); + } List> gitConfig = buildDispatchGitConfig(task); String deliveryMode = resolveDispatchDeliveryMode(task, gitConfig); @@ -764,7 +767,7 @@ public TaskResponse toResponse(Task task) { response.setImage(task.getImage()); response.setSelectedModel(task.getSelectedModel()); response.setCreator(task.getCreatorName()); - response.setCreatedAt(task.getCreatedAt()); + response.setCreatedAt(task.getCreatedAt() != null ? task.getCreatedAt().toString() : null); // 构建 Usage TaskResponse.Usage usage = new TaskResponse.Usage(); From 9654c3378dffd2226fa28c6d0cc782e017a71374 Mon Sep 17 00:00:00 2001 From: "li.zhiwen" Date: Fri, 20 Mar 2026 19:32:49 +0800 Subject: [PATCH 06/21] feat(dev_0.2): include afternoon backend and deploy chain updates --- back/pom.xml | 4 + .../java/com/linkwork/common/ApiResponse.java | 8 +- .../com/linkwork/config/ImageBuildConfig.java | 47 +- .../com/linkwork/config/JacksonConfig.java | 40 ++ .../com/linkwork/config/WebSocketConfig.java | 2 +- .../linkwork/controller/AuthController.java | 12 +- .../controller/BuildRecordController.java | 14 +- .../controller/ImageBuildController.java | 32 +- .../linkwork/controller/RoleController.java | 6 +- .../linkwork/controller/SkillController.java | 13 +- .../com/linkwork/mapper/UserSoulMapper.java | 20 + .../model/dto/ServiceBuildRequest.java | 2 +- .../model/dto/ServiceBuildResult.java | 17 +- .../com/linkwork/model/dto/TaskResponse.java | 5 +- .../linkwork/model/entity/UserSoulEntity.java | 5 + .../linkwork/service/BuildRecordService.java | 4 +- .../linkwork/service/ImageBuildService.java | 444 ++++++++++++++++-- .../linkwork/service/K8sOrchestratorImpl.java | 96 +++- .../linkwork/service/McpServerService.java | 16 +- .../linkwork/service/PodSpecGenerator.java | 22 +- .../com/linkwork/service/RoleService.java | 2 +- .../com/linkwork/service/SkillService.java | 14 +- .../com/linkwork/service/UserSoulService.java | 32 +- .../websocket/TaskWebSocketHandler.java | 37 +- back/src/main/resources/application-gg.yml | 20 + back/src/main/resources/application.yml | 34 +- back/src/main/resources/scripts/build.sh | 126 ++++- back/src/main/resources/scripts/config.json | 3 +- docs/guides/deployment.md | 25 +- docs/guides/deployment_zh-CN.md | 25 +- 30 files changed, 986 insertions(+), 141 deletions(-) create mode 100644 back/src/main/java/com/linkwork/config/JacksonConfig.java create mode 100644 back/src/main/resources/application-gg.yml diff --git a/back/pom.xml b/back/pom.xml index 212a8b7..36215f6 100644 --- a/back/pom.xml +++ b/back/pom.xml @@ -42,6 +42,10 @@ com.fasterxml.jackson.core jackson-databind + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + diff --git a/back/src/main/java/com/linkwork/common/ApiResponse.java b/back/src/main/java/com/linkwork/common/ApiResponse.java index 3890e0b..c6ff9aa 100644 --- a/back/src/main/java/com/linkwork/common/ApiResponse.java +++ b/back/src/main/java/com/linkwork/common/ApiResponse.java @@ -1,9 +1,8 @@ package com.linkwork.common; -import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; -import java.time.LocalDateTime; +import java.time.Instant; import java.util.UUID; /** @@ -17,15 +16,14 @@ public class ApiResponse { private T data; private String traceId; - @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'") - private LocalDateTime timestamp; + private String timestamp; private ApiResponse(Integer code, String msg, T data) { this.code = code; this.msg = msg; this.data = data; this.traceId = UUID.randomUUID().toString(); - this.timestamp = LocalDateTime.now(); + this.timestamp = Instant.now().toString(); } public static ApiResponse success(T data) { diff --git a/back/src/main/java/com/linkwork/config/ImageBuildConfig.java b/back/src/main/java/com/linkwork/config/ImageBuildConfig.java index c7acbc3..754b0b9 100644 --- a/back/src/main/java/com/linkwork/config/ImageBuildConfig.java +++ b/back/src/main/java/com/linkwork/config/ImageBuildConfig.java @@ -92,11 +92,52 @@ public class ImageBuildConfig { * 构建上下文临时目录 */ private String buildContextDir = "/tmp/docker-build"; + + /** + * 是否启用本地镜像自动同步到 Kind 节点(仅 K8s + 未配置镜像仓库时生效) + */ + private boolean autoLoadToKind = true; + + /** + * 指定 Kind 集群名;为空时自动发现所有 Kind 集群节点 + */ + private String kindClusterName = ""; + + /** + * Kind 节点镜像导入超时时间(秒) + */ + private int kindLoadTimeout = 600; + + /** + * 是否启用本地镜像定期清理 + */ + private boolean localCleanupEnabled = true; + + /** + * 本地构建镜像保留小时数(超过后尝试删除,运行中镜像会跳过) + */ + private int localImageRetentionHours = 24; + + /** + * 本地镜像清理 Cron(默认每小时第 40 分钟) + */ + private String localCleanupCron = "0 40 * * * *"; + + /** + * 是否在 Kind 节点执行未使用镜像 prune + */ + private boolean kindPruneEnabled = true; /** * SDK 仓库 HTTPS 地址(用于镜像构建阶段克隆) */ private String sdkRepoUrl = ""; + + /** + * 构建资产来源镜像(当 sdkRepoUrl 未配置时使用) + * 用于从已有镜像中复制 zzd/start-scripts/linkwork_agent_sdk + */ + private String assetSourceImage = ""; /** * SDK 仓库克隆分支(默认 test122) @@ -115,13 +156,13 @@ public class ImageBuildConfig { /** * SDK 源码在镜像中的目标路径 - * 从 SDK 仓库的 momo-agent-sdk/ 目录拷贝 + * 从 SDK 仓库的 linkwork-agent-sdk/ 目录拷贝 */ - private String sdkSourcePath = "/opt/momo-agent-build/sdk-source"; + private String sdkSourcePath = "/opt/linkwork-agent-build/sdk-source"; /** * zzd 二进制文件在镜像中的目标路径 * 从 SDK 仓库的 docker/agent/zzd/ 目录拷贝 */ - private String zzdBinariesPath = "/opt/momo-agent-build/zzd-binaries"; + private String zzdBinariesPath = "/opt/linkwork-agent-build/zzd-binaries"; } diff --git a/back/src/main/java/com/linkwork/config/JacksonConfig.java b/back/src/main/java/com/linkwork/config/JacksonConfig.java new file mode 100644 index 0000000..bed1b91 --- /dev/null +++ b/back/src/main/java/com/linkwork/config/JacksonConfig.java @@ -0,0 +1,40 @@ +package com.linkwork.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.boot.autoconfigure.jackson.JacksonProperties; +import org.springframework.boot.jackson.JsonComponentModule; +import org.springframework.boot.jackson.JsonMixinModule; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; + +@Configuration +public class JacksonConfig { + + @Bean + public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() { + return builder -> builder + .modulesToInstall(JavaTimeModule.class) + .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + } + + /** + * MCP starter 可能在自动配置阶段提前注册一个裸 ObjectMapper,导致 Web 层缺少 JavaTime 支持。 + * 这里显式提供主 ObjectMapper,确保 MVC 与业务注入都使用支持 LocalDateTime 的配置。 + */ + @Bean + @Primary + public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder, JacksonProperties properties) { + builder.modules(new JsonComponentModule(), new JsonMixinModule(), new JavaTimeModule()); + ObjectMapper objectMapper = builder.build(); + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + if (properties.getTimeZone() != null) { + objectMapper.setTimeZone(properties.getTimeZone()); + } + return objectMapper; + } +} diff --git a/back/src/main/java/com/linkwork/config/WebSocketConfig.java b/back/src/main/java/com/linkwork/config/WebSocketConfig.java index 46d014d..e64acc6 100644 --- a/back/src/main/java/com/linkwork/config/WebSocketConfig.java +++ b/back/src/main/java/com/linkwork/config/WebSocketConfig.java @@ -18,7 +18,7 @@ public WebSocketConfig(TaskWebSocketHandler taskWebSocketHandler) { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { - registry.addHandler(taskWebSocketHandler, "/api/v1/ws") + registry.addHandler(taskWebSocketHandler, "/api/v1/ws", "/ws", "/ws/") .setAllowedOrigins("*"); } } diff --git a/back/src/main/java/com/linkwork/controller/AuthController.java b/back/src/main/java/com/linkwork/controller/AuthController.java index 58aad70..2d21200 100644 --- a/back/src/main/java/com/linkwork/controller/AuthController.java +++ b/back/src/main/java/com/linkwork/controller/AuthController.java @@ -4,6 +4,7 @@ import com.linkwork.context.UserContext; import com.linkwork.context.UserInfo; import com.linkwork.service.AuthService; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; @@ -35,7 +36,7 @@ public class AuthController { * POST /api/v1/auth/login */ @PostMapping("/login") - public ApiResponse> login(@Valid @RequestBody LoginRequest request) { + public ApiResponse> login(@Valid @RequestBody LoginRequest request, HttpServletResponse response) { log.info("登录请求"); // 验证密码 @@ -47,6 +48,12 @@ public ApiResponse> login(@Valid @RequestBody LoginRequest r // 生成 Token String token = authService.generateToken("robot-user"); + Cookie cookie = new Cookie(COOKIE_NAME, token); + cookie.setPath("/"); + cookie.setHttpOnly(true); + cookie.setMaxAge(86400); + response.addCookie(cookie); + Map result = new HashMap<>(); result.put("token", token); result.put("expiresIn", 86400); // 24 小时(秒) @@ -128,7 +135,10 @@ public ApiResponse> logout(HttpServletResponse response) { } @Data + @JsonIgnoreProperties(ignoreUnknown = true) public static class LoginRequest { + private String username; + @NotBlank(message = "密码不能为空") private String password; } diff --git a/back/src/main/java/com/linkwork/controller/BuildRecordController.java b/back/src/main/java/com/linkwork/controller/BuildRecordController.java index b75ecd7..386fc88 100644 --- a/back/src/main/java/com/linkwork/controller/BuildRecordController.java +++ b/back/src/main/java/com/linkwork/controller/BuildRecordController.java @@ -42,7 +42,7 @@ public Map listBuildRecords( Map response = new HashMap<>(); response.put("code", 0); response.put("msg", "success"); - response.put("timestamp", LocalDateTime.now()); + response.put("timestamp", LocalDateTime.now().toString()); Map data; if (roleId != null) { @@ -66,13 +66,13 @@ public Map getBuildRecord(@PathVariable String buildNo) { if (entity == null) { response.put("code", 404); response.put("msg", "Build record not found: " + buildNo); - response.put("timestamp", LocalDateTime.now()); + response.put("timestamp", LocalDateTime.now().toString()); return response; } response.put("code", 0); response.put("msg", "success"); - response.put("timestamp", LocalDateTime.now()); + response.put("timestamp", LocalDateTime.now().toString()); Map data = new HashMap<>(); data.put("id", entity.getId().toString()); @@ -86,8 +86,8 @@ public Map getBuildRecord(@PathVariable String buildNo) { data.put("configSnapshot", entity.getConfigSnapshot()); data.put("creatorId", entity.getCreatorId()); data.put("creatorName", entity.getCreatorName()); - data.put("createdAt", entity.getCreatedAt()); - data.put("updatedAt", entity.getUpdatedAt()); + data.put("createdAt", entity.getCreatedAt() != null ? entity.getCreatedAt().toString() : null); + data.put("updatedAt", entity.getUpdatedAt() != null ? entity.getUpdatedAt().toString() : null); response.put("data", data); return response; @@ -108,14 +108,14 @@ public Map getLatestBuildRecord(@PathVariable Long roleId) { response.put("code", 0); response.put("msg", "success"); response.put("data", null); - response.put("timestamp", LocalDateTime.now()); + response.put("timestamp", LocalDateTime.now().toString()); return response; } response.put("code", 0); response.put("msg", "success"); response.put("data", items.get(0)); - response.put("timestamp", LocalDateTime.now()); + response.put("timestamp", LocalDateTime.now().toString()); return response; } } diff --git a/back/src/main/java/com/linkwork/controller/ImageBuildController.java b/back/src/main/java/com/linkwork/controller/ImageBuildController.java index 69eafd8..19a2076 100644 --- a/back/src/main/java/com/linkwork/controller/ImageBuildController.java +++ b/back/src/main/java/com/linkwork/controller/ImageBuildController.java @@ -1,11 +1,14 @@ package com.linkwork.controller; import com.linkwork.common.ApiResponse; +import com.linkwork.service.ImageBuildService; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.List; +import java.util.Map; /** * 镜像构建 Controller @@ -16,8 +19,11 @@ @RestController @RequestMapping({"/api/v1/build", "/api/v1/image-build"}) @CrossOrigin(origins = "*") +@RequiredArgsConstructor public class ImageBuildController { + private final ImageBuildService imageBuildService; + /** * 获取可选的构建基础镜像列表 * @@ -26,15 +32,37 @@ public class ImageBuildController { @GetMapping("/base-images") public ApiResponse> listBaseImages() { List images = new ArrayList<>(); + images.add(new BaseImageInfo( + "10.30.107.146/robot/rockylinux9-agent:v1.3", + "Rocky Linux 9 Agent v1.3", + "开发机默认基础镜像(优先使用本地缓存)", + true + )); + images.add(new BaseImageInfo( + "rockylinux/rockylinux:9.6", + "Rocky Linux 9.6", + "公共回退基础镜像(仅当 v1.3 不可用时使用)", + false + )); images.add(new BaseImageInfo( "10.30.107.146/robot/rockylinux9-agent@sha256:b49d75f52f6b3c55bbf90427f0df0e97bc8e3f3e03727721cafc2c9d775b8975", "Rocky Linux 9 Agent", - "基于 Rocky Linux 9 的 Agent 基础镜像,固定 digest 保证构建环境一致性", - true + "内网固定 digest 基础镜像(需内网仓库可达)", + false )); return ApiResponse.success(images); } + /** + * 手动触发一次本地镜像运维动作: + * - 清理过期 service-*-agent 本地镜像 + * - 可选对 Kind 节点执行 crictl prune + */ + @PostMapping("/ops/local-image-maintenance") + public ApiResponse> runLocalImageMaintenance() { + return ApiResponse.success(imageBuildService.runLocalImageMaintenance("manual")); + } + /** * 基础镜像信息 DTO * diff --git a/back/src/main/java/com/linkwork/controller/RoleController.java b/back/src/main/java/com/linkwork/controller/RoleController.java index 3e68202..5267c7b 100644 --- a/back/src/main/java/com/linkwork/controller/RoleController.java +++ b/back/src/main/java/com/linkwork/controller/RoleController.java @@ -118,7 +118,7 @@ public Map listRoles( data.put("pagination", pagination); response.put("data", data); - response.put("timestamp", LocalDateTime.now()); + response.put("timestamp", LocalDateTime.now().toString()); return response; } @@ -147,7 +147,7 @@ public Map listHotRoles(@RequestParam(defaultValue = "4") int li response.put("code", 0); response.put("msg", "success"); response.put("data", Map.of("items", items, "limit", Math.max(1, limit))); - response.put("timestamp", LocalDateTime.now()); + response.put("timestamp", LocalDateTime.now().toString()); return response; } @@ -179,7 +179,7 @@ public Map getRole(@PathVariable Long id) { data.put("isFavorite", roleService.isFavorite(id, userId)); data.put("isPublic", Boolean.TRUE.equals(role.getIsPublic())); data.put("maxEmployees", role.getMaxEmployees()); - data.put("createdAt", role.getCreatedAt()); + data.put("createdAt", role.getCreatedAt() != null ? role.getCreatedAt().toString() : null); if (role.getConfigJson() != null) { RoleEntity.RoleConfig config = role.getConfigJson(); diff --git a/back/src/main/java/com/linkwork/controller/SkillController.java b/back/src/main/java/com/linkwork/controller/SkillController.java index 15c4d6a..9039cce 100644 --- a/back/src/main/java/com/linkwork/controller/SkillController.java +++ b/back/src/main/java/com/linkwork/controller/SkillController.java @@ -260,6 +260,17 @@ private String extractFilePath(HttpServletRequest request, String name) { String uri = request.getRequestURI(); String prefix = "/api/v1/skills/" + name + "/files/"; String encodedPath = uri.substring(uri.indexOf(prefix) + prefix.length()); - return URLDecoder.decode(encodedPath, StandardCharsets.UTF_8); + String normalized = encodedPath == null ? "" : encodedPath.trim().replace('\\', '/'); + for (int i = 0; i < 3; i++) { + String decoded = URLDecoder.decode(normalized, StandardCharsets.UTF_8); + if (decoded.equals(normalized)) { + break; + } + normalized = decoded; + } + while (normalized.startsWith("/")) { + normalized = normalized.substring(1); + } + return normalized; } } diff --git a/back/src/main/java/com/linkwork/mapper/UserSoulMapper.java b/back/src/main/java/com/linkwork/mapper/UserSoulMapper.java index fb289d1..c495f9f 100644 --- a/back/src/main/java/com/linkwork/mapper/UserSoulMapper.java +++ b/back/src/main/java/com/linkwork/mapper/UserSoulMapper.java @@ -3,7 +3,27 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.linkwork.model.entity.UserSoulEntity; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; @Mapper public interface UserSoulMapper extends BaseMapper { + + @Select(""" + SELECT id, + user_id, + soul AS content, + template_id AS preset_id, + creator_id, + creator_name, + created_at, + updated_at, + is_deleted + FROM linkwork_user_soul + WHERE is_deleted = 0 + AND user_id = #{userId} + ORDER BY updated_at DESC, id DESC + LIMIT 1 + """) + UserSoulEntity selectLatestCompatByUserId(@Param("userId") String userId); } diff --git a/back/src/main/java/com/linkwork/model/dto/ServiceBuildRequest.java b/back/src/main/java/com/linkwork/model/dto/ServiceBuildRequest.java index 0ff238a..abdbe6f 100644 --- a/back/src/main/java/com/linkwork/model/dto/ServiceBuildRequest.java +++ b/back/src/main/java/com/linkwork/model/dto/ServiceBuildRequest.java @@ -83,7 +83,7 @@ public class ServiceBuildRequest { /** * 镜像仓库地址(可选) - * 为空时使用系统默认配置:image-build.registry + * 为空时仅本地构建,不推送远端仓库 * K8s 模式下构建的镜像会推送到此仓库 */ private String imageRegistry; diff --git a/back/src/main/java/com/linkwork/model/dto/ServiceBuildResult.java b/back/src/main/java/com/linkwork/model/dto/ServiceBuildResult.java index 0429e82..9116747 100644 --- a/back/src/main/java/com/linkwork/model/dto/ServiceBuildResult.java +++ b/back/src/main/java/com/linkwork/model/dto/ServiceBuildResult.java @@ -3,7 +3,6 @@ import lombok.Builder; import lombok.Data; -import java.time.Instant; import java.util.List; /** @@ -20,7 +19,7 @@ public class ServiceBuildResult { private String podGroupName; // 创建的 PodGroup 名称(仅 K8s 模式) private List podNames; // 创建的 Pod 名称列表(仅 K8s 模式,BUILDING 时为空) private String queueName; // 分配的队列名称(仅 K8s 模式) - private Instant createdAt; + private String createdAt; // ========== 构建追踪 ========== /** @@ -83,7 +82,7 @@ public static ServiceBuildResult successK8s(String serviceId, String podGroupNam .queueName(queueName) .builtAgentImage(builtAgentImage) .buildDurationMs(buildDurationMs) - .createdAt(Instant.now()) + .createdAt(java.time.Instant.now().toString()) .build(); } @@ -104,7 +103,7 @@ public static ServiceBuildResult successK8s(String serviceId, String podGroupNam .scheduledNode(scheduledNode) .builtAgentImage(builtAgentImage) .buildDurationMs(buildDurationMs) - .createdAt(Instant.now()) + .createdAt(java.time.Instant.now().toString()) .build(); } @@ -117,7 +116,7 @@ public static ServiceBuildResult successCompose(String serviceId, byte[] compose .success(true) .status("SUCCESS") .composeTar(composeTar) - .createdAt(Instant.now()) + .createdAt(java.time.Instant.now().toString()) .build(); } @@ -134,7 +133,7 @@ public static ServiceBuildResult success(String serviceId, String podGroupName, .podGroupName(podGroupName) .podNames(podNames) .queueName(queueName) - .createdAt(Instant.now()) + .createdAt(java.time.Instant.now().toString()) .build(); } @@ -153,7 +152,7 @@ public static ServiceBuildResult success(String serviceId, String podGroupName, .podNames(podNames) .queueName(queueName) .scheduledNode(scheduledNode) - .createdAt(Instant.now()) + .createdAt(java.time.Instant.now().toString()) .build(); } @@ -164,7 +163,7 @@ public static ServiceBuildResult failed(String serviceId, String errorCode, Stri .status("FAILED") .errorCode(errorCode) .errorMessage(errorMessage) - .createdAt(Instant.now()) + .createdAt(java.time.Instant.now().toString()) .build(); } @@ -187,7 +186,7 @@ public static ServiceBuildResult building(String serviceId, String buildId, .podGroupName(podGroupName) .queueName(queueName) .message(message) - .createdAt(Instant.now()) + .createdAt(java.time.Instant.now().toString()) .build(); } } diff --git a/back/src/main/java/com/linkwork/model/dto/TaskResponse.java b/back/src/main/java/com/linkwork/model/dto/TaskResponse.java index 0dc31b1..af5b884 100644 --- a/back/src/main/java/com/linkwork/model/dto/TaskResponse.java +++ b/back/src/main/java/com/linkwork/model/dto/TaskResponse.java @@ -1,11 +1,9 @@ package com.linkwork.model.dto; -import com.fasterxml.jackson.annotation.JsonFormat; import com.linkwork.model.enums.TaskStatus; import lombok.Data; import java.math.BigDecimal; -import java.time.LocalDateTime; import java.util.List; import java.util.Map; @@ -42,8 +40,7 @@ public class TaskResponse { private String creator; - @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'") - private LocalDateTime createdAt; + private String createdAt; private Usage usage; diff --git a/back/src/main/java/com/linkwork/model/entity/UserSoulEntity.java b/back/src/main/java/com/linkwork/model/entity/UserSoulEntity.java index 47a76d7..4cad8dd 100644 --- a/back/src/main/java/com/linkwork/model/entity/UserSoulEntity.java +++ b/back/src/main/java/com/linkwork/model/entity/UserSoulEntity.java @@ -19,18 +19,23 @@ public class UserSoulEntity { private String userId; + @TableField("soul") private String content; + @TableField("template_id") private String presetId; + @TableField(exist = false) private Long version; private String creatorId; private String creatorName; + @TableField(exist = false) private String updaterId; + @TableField(exist = false) private String updaterName; @TableField(fill = FieldFill.INSERT) diff --git a/back/src/main/java/com/linkwork/service/BuildRecordService.java b/back/src/main/java/com/linkwork/service/BuildRecordService.java index dead57b..8d9fbe9 100644 --- a/back/src/main/java/com/linkwork/service/BuildRecordService.java +++ b/back/src/main/java/com/linkwork/service/BuildRecordService.java @@ -211,8 +211,8 @@ private Map toResponseMap(BuildRecordEntity entity) { map.put("creatorId", entity.getCreatorId()); map.put("creatorName", entity.getCreatorName()); map.put("logUrl", entity.getLogUrl()); - map.put("createdAt", entity.getCreatedAt()); - map.put("updatedAt", entity.getUpdatedAt()); + map.put("createdAt", entity.getCreatedAt() != null ? entity.getCreatedAt().toString() : null); + map.put("updatedAt", entity.getUpdatedAt() != null ? entity.getUpdatedAt().toString() : null); return map; } } diff --git a/back/src/main/java/com/linkwork/service/ImageBuildService.java b/back/src/main/java/com/linkwork/service/ImageBuildService.java index 4721f62..2159fff 100644 --- a/back/src/main/java/com/linkwork/service/ImageBuildService.java +++ b/back/src/main/java/com/linkwork/service/ImageBuildService.java @@ -5,8 +5,14 @@ import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.api.async.ResultCallback; import com.github.dockerjava.api.command.BuildImageResultCallback; +import com.github.dockerjava.api.command.ExecCreateCmdResponse; +import com.github.dockerjava.api.command.InspectExecResponse; +import com.github.dockerjava.api.command.InspectImageResponse; import com.github.dockerjava.api.model.AuthConfig; import com.github.dockerjava.api.model.BuildResponseItem; +import com.github.dockerjava.api.model.Container; +import com.github.dockerjava.api.model.Frame; +import com.github.dockerjava.api.model.Image; import com.github.dockerjava.api.model.PullResponseItem; import com.github.dockerjava.api.model.PushResponseItem; import com.github.dockerjava.api.exception.NotFoundException; @@ -16,8 +22,6 @@ import com.github.dockerjava.zerodep.ZerodepDockerHttpClient; import com.github.dockerjava.transport.DockerHttpClient; import com.linkwork.config.BuildQueueConfig; -import com.linkwork.config.BuildQueueConfig; -import com.linkwork.config.BuildQueueConfig; import com.linkwork.config.ImageBuildConfig; import com.linkwork.model.dto.ImageBuildResult; import com.linkwork.model.dto.ServiceBuildRequest; @@ -26,23 +30,32 @@ import jakarta.annotation.PreDestroy; import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.ClassPathResource; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; +import java.io.ByteArrayOutputStream; import java.io.File; import java.nio.file.DirectoryStream; import java.io.IOException; -import java.nio.file.DirectoryStream; +import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; import java.time.Instant; +import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; import java.util.Base64; import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; /** * 镜像构建服务 @@ -62,6 +75,7 @@ public class ImageBuildService { private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(ZoneOffset.UTC); + private static final Pattern LOCAL_BUILD_REPO_PATTERN = Pattern.compile("^service-.*-agent$"); private final ImageBuildConfig config; private final BuildQueueConfig buildQueueConfig; @@ -206,6 +220,7 @@ public ImageBuildResult buildImages(ServiceBuildRequest request) { // 解析镜像仓库 String registry = resolveRegistry(request); + boolean shouldPush = shouldPushImage(request, registry); publishLog(buildId, "info", "镜像仓库: " + (StringUtils.hasText(registry) ? registry : "本地")); // 构建 Agent 镜像 @@ -220,20 +235,22 @@ public ImageBuildResult buildImages(ServiceBuildRequest request) { publishLog(buildId, "info", ""); publishLog(buildId, "info", "镜像构建成功: " + agentImageTag); - // K8s 模式推送镜像(如果启用) + // K8s 模式推送镜像(仅请求配置了镜像仓库时推送) boolean pushed = false; - if (config.isPushEnabled() && request.getDeployMode() == DeployMode.K8S && StringUtils.hasText(registry)) { + if (shouldPush) { publishLog(buildId, "info", ""); publishLog(buildId, "info", "=== 开始推送镜像 ==="); - pushImage(agentImageTag, buildId); + pushImage(agentImageTag, buildId, registry); pushed = true; publishLog(buildId, "info", "镜像推送成功"); // 推送成功后删除本地镜像(K8s 会从仓库拉取,不需要保留本地副本) removeLocalImage(agentImageTag, buildId); } else { - publishLog(buildId, "warn", "跳过镜像推送(未配置或非 K8S 模式)"); - log.info("Image push disabled or no registry configured, skipping push for: {}", agentImageTag); + publishLog(buildId, "warn", "跳过镜像推送(未配置镜像仓库或非 K8S 模式)"); + log.info("Image push skipped (deployMode={}, registry={}) for image: {}", + request.getDeployMode(), registry, agentImageTag); + syncLocalImageToKindIfNeeded(request, agentImageTag, buildId); } long duration = System.currentTimeMillis() - startTime; @@ -321,10 +338,147 @@ private void uploadBuildLogToOss(String buildId) { * 解析镜像仓库地址 */ private String resolveRegistry(ServiceBuildRequest request) { - if (StringUtils.hasText(request.getImageRegistry())) { - return request.getImageRegistry(); + if (!StringUtils.hasText(request.getImageRegistry())) { + return ""; } - return config.getRegistry(); + String registry = request.getImageRegistry().trim(); + while (registry.endsWith("/")) { + registry = registry.substring(0, registry.length() - 1); + } + return registry; + } + + /** + * 仅当 K8s 且请求显式配置了仓库时才推送 + */ + private boolean shouldPushImage(ServiceBuildRequest request, String registry) { + return request.getDeployMode() == DeployMode.K8S && StringUtils.hasText(registry); + } + + private void syncLocalImageToKindIfNeeded(ServiceBuildRequest request, String imageTag, String buildId) { + if (request.getDeployMode() != DeployMode.K8S) { + return; + } + if (!config.isAutoLoadToKind()) { + log.info("Auto kind image load disabled, skip local image sync: {}", imageTag); + return; + } + if (hasRegistryHost(imageTag)) { + return; + } + + List kindNodes = findKindNodeContainers(); + if (kindNodes.isEmpty()) { + throw new IllegalStateException("本地镜像模式下未发现 Kind 节点,无法自动分发镜像。" + + "请配置 imageRegistry 推送远程仓库,或检查 Kind 集群/节点标签配置。"); + } + + publishLog(buildId, "info", "检测到本地镜像,开始同步到 Kind 节点"); + for (Container node : kindNodes) { + String nodeName = resolveContainerName(node); + if (!StringUtils.hasText(nodeName)) { + continue; + } + publishLog(buildId, "debug", "同步镜像到节点: " + nodeName); + importImageIntoKindNode(imageTag, node, nodeName); + } + publishLog(buildId, "info", "Kind 节点镜像同步完成"); + } + + private List findKindNodeContainers() { + List all = dockerClient.listContainersCmd().withShowAll(false).exec(); + List nodes = new ArrayList<>(); + String expectedCluster = normalize(config.getKindClusterName()); + + for (Container c : all) { + Map labels = c.getLabels(); + if (labels == null) { + continue; + } + String cluster = normalize(labels.get("io.x-k8s.kind.cluster")); + String role = normalize(labels.get("io.x-k8s.kind.role")); + if (!StringUtils.hasText(cluster) || !StringUtils.hasText(role)) { + continue; + } + if (StringUtils.hasText(expectedCluster) && !expectedCluster.equals(cluster)) { + continue; + } + if (!"control-plane".equals(role) && !"worker".equals(role)) { + continue; + } + nodes.add(c); + } + return nodes; + } + + private void importImageIntoKindNode(String imageTag, Container node, String nodeName) { + ExecCreateCmdResponse exec = dockerClient.execCreateCmd(node.getId()) + .withAttachStdin(true) + .withAttachStdout(true) + .withAttachStderr(true) + .withCmd("ctr", "-n", "k8s.io", "images", "import", "-") + .exec(); + + ByteArrayOutputStream output = new ByteArrayOutputStream(); + try (InputStream tarStream = dockerClient.saveImageCmd(imageTag).exec()) { + ResultCallback.Adapter callback = new ResultCallback.Adapter<>() { + @Override + public void onNext(Frame item) { + try { + if (item != null && item.getPayload() != null) { + output.write(item.getPayload()); + } + } catch (IOException ignored) { + // 输出只用于排障,写失败不影响主流程 + } + super.onNext(item); + } + }; + + dockerClient.execStartCmd(exec.getId()) + .withStdIn(tarStream) + .exec(callback) + .awaitCompletion(config.getKindLoadTimeout(), TimeUnit.SECONDS); + + InspectExecResponse inspect = dockerClient.inspectExecCmd(exec.getId()).exec(); + Long exitCode = inspect != null ? inspect.getExitCodeLong() : null; + if (exitCode == null || exitCode != 0L) { + String details = output.toString(); + throw new IllegalStateException("Kind 节点导入失败: node=" + nodeName + + ", exitCode=" + exitCode + ", output=" + details); + } + log.info("Image imported to kind node successfully: node={}, image={}", nodeName, imageTag); + } catch (Exception e) { + throw new RuntimeException("同步镜像到 Kind 节点失败: node=" + nodeName + ", image=" + imageTag, e); + } + } + + private boolean hasRegistryHost(String image) { + if (!StringUtils.hasText(image)) { + return false; + } + String value = image.trim(); + int slash = value.indexOf('/'); + if (slash <= 0) { + return false; + } + String first = value.substring(0, slash); + return first.contains(".") || first.contains(":") || "localhost".equals(first); + } + + private String resolveContainerName(Container container) { + if (container == null || container.getNames() == null || container.getNames().length == 0) { + return ""; + } + String name = container.getNames()[0]; + if (name == null) { + return ""; + } + return name.startsWith("/") ? name.substring(1) : name; + } + + private String normalize(String value) { + return value == null ? "" : value.trim(); } /** @@ -516,9 +670,27 @@ private Path createBuildContext(String serviceId, String baseImage, */ String generateDockerfile(String baseImage, Map envVars) { StringBuilder sb = new StringBuilder(); + String sdkRepoUrl = config.getSdkRepoUrl(); + String assetSourceImage = config.getAssetSourceImage(); + boolean hasSdkRepo = StringUtils.hasText(sdkRepoUrl); + boolean hasAssetSourceImage = !hasSdkRepo && StringUtils.hasText(assetSourceImage); + + String sdkSourcePath = StringUtils.hasText(config.getSdkSourcePath()) + ? config.getSdkSourcePath().trim() + : "/opt/linkwork-agent-build/sdk-source"; + String zzdBinariesPath = StringUtils.hasText(config.getZzdBinariesPath()) + ? config.getZzdBinariesPath().trim() + : "/opt/linkwork-agent-build/zzd-binaries"; + String buildAssetsRoot = sdkSourcePath.contains("/") + ? sdkSourcePath.substring(0, sdkSourcePath.lastIndexOf('/')) + : "/opt/linkwork-agent-build"; + String startScriptsPath = buildAssetsRoot + "/start-scripts"; // FROM 指令 sb.append("# Auto-generated Dockerfile\n"); + if (hasAssetSourceImage) { + sb.append("FROM ").append(assetSourceImage.trim()).append(" AS linkwork_assets\n\n"); + } sb.append("FROM ").append(baseImage).append("\n\n"); // ENV 指令(注入环境变量) @@ -549,8 +721,7 @@ String generateDockerfile(String baseImage, Map envVars) { } // Clone SDK repo and prepare sdk-source + zzd-binaries (before build.sh) - String sdkRepoUrl = config.getSdkRepoUrl(); - if (StringUtils.hasText(sdkRepoUrl)) { + if (hasSdkRepo) { // 使用 GIT_TOKEN 环境变量认证(已在 ENV 指令中注入),不再用硬编码的 sdkRepoUsername/Password 合并分支暂时注释,这是init时拉取sdk 仓库的节点过程,该节点是不需要用户的业务级仓库token的 // String cloneCmd = sdkRepoUrl.replace("https://", "https://oauth2:${GIT_TOKEN}@"); @@ -569,22 +740,46 @@ String generateDockerfile(String baseImage, Map envVars) { sb.append("RUN set -e \\\n"); sb.append(" && git clone --depth 1 --single-branch -b ").append(branch).append(" ").append(cloneUrl).append(" /tmp/_sdk_repo \\\n"); // BUILD_ASSETS_ROOT directory structure (build.sh expects these sub-directories) - sb.append(" && mkdir -p /opt/momo-agent-build/zzd-binaries \\\n"); - sb.append(" && mkdir -p /opt/momo-agent-build/sdk-source \\\n"); - sb.append(" && mkdir -p /opt/momo-agent-build/start-scripts \\\n"); + sb.append(" && mkdir -p ").append(zzdBinariesPath).append(" \\\n"); + sb.append(" && mkdir -p ").append(sdkSourcePath).append(" \\\n"); + sb.append(" && mkdir -p ").append(startScriptsPath).append(" \\\n"); // zzd binaries sb.append(" && for bin in zzd zz gen-key encrypt-key; do \\\n"); - sb.append(" cp /tmp/_sdk_repo/docker/agent/zzd/$bin /opt/momo-agent-build/zzd-binaries/; \\\n"); + sb.append(" cp /tmp/_sdk_repo/docker/agent/zzd/$bin ").append(zzdBinariesPath).append("/; \\\n"); sb.append(" done \\\n"); - // SDK source - sb.append(" && cp -a /tmp/_sdk_repo/momo-agent-sdk/. /opt/momo-agent-build/sdk-source/ \\\n"); + // SDK source (linkwork-agent-sdk, fallback agent-sdk for backward compatibility) + sb.append(" && if [ -d /tmp/_sdk_repo/linkwork-agent-sdk ]; then \\\n"); + sb.append(" cp -a /tmp/_sdk_repo/linkwork-agent-sdk/. ").append(sdkSourcePath).append("/; \\\n"); + sb.append(" elif [ -d /tmp/_sdk_repo/agent-sdk ]; then \\\n"); + sb.append(" cp -a /tmp/_sdk_repo/agent-sdk/. ").append(sdkSourcePath).append("/; \\\n"); + sb.append(" else \\\n"); + sb.append(" echo 'SDK source directory not found in repo'; exit 1; \\\n"); + sb.append(" fi \\\n"); // start scripts - sb.append(" && cp /tmp/_sdk_repo/docker/agent/start-single.sh /opt/momo-agent-build/start-scripts/ \\\n"); - sb.append(" && cp /tmp/_sdk_repo/docker/agent/start-dual.sh /opt/momo-agent-build/start-scripts/ \\\n"); - sb.append(" && cp /tmp/_sdk_repo/docker/agent/ai_employee.py /opt/momo-agent-build/start-scripts/ \\\n"); + sb.append(" && cp /tmp/_sdk_repo/docker/agent/start-single.sh ").append(startScriptsPath).append("/ \\\n"); + sb.append(" && cp /tmp/_sdk_repo/docker/agent/start-dual.sh ").append(startScriptsPath).append("/ \\\n"); + sb.append(" && cp /tmp/_sdk_repo/docker/agent/ai_employee.py ").append(startScriptsPath).append("/ \\\n"); // cleanup sb.append(" && rm -rf /tmp/_sdk_repo\n\n"); + // Cedar 策略文件 → /tmp/cedar-policies/ (build.sh download_cedar_policies 会从这里读取) + sb.append("# Cedar policy files for build.sh to deploy\n"); + sb.append("COPY cedar-policies/ /tmp/cedar-policies/\n\n"); + } else if (hasAssetSourceImage) { + sb.append("# Fallback assets copied from local image\n"); + sb.append("RUN mkdir -p ").append(zzdBinariesPath).append(" \\\n"); + sb.append(" && mkdir -p ").append(sdkSourcePath).append(" \\\n"); + sb.append(" && mkdir -p ").append(startScriptsPath).append("\n"); + sb.append("COPY --from=linkwork_assets /usr/local/bin/zzd ").append(zzdBinariesPath).append("/zzd\n"); + sb.append("COPY --from=linkwork_assets /usr/local/bin/zz ").append(zzdBinariesPath).append("/zz\n"); + sb.append("COPY --from=linkwork_assets /usr/local/bin/gen-key ").append(zzdBinariesPath).append("/gen-key\n"); + sb.append("COPY --from=linkwork_assets /usr/local/bin/encrypt-key ").append(zzdBinariesPath).append("/encrypt-key\n"); + sb.append("COPY --from=linkwork_assets /opt/agent/start-single.sh ").append(startScriptsPath).append("/start-single.sh\n"); + sb.append("COPY --from=linkwork_assets /opt/agent/start-dual.sh ").append(startScriptsPath).append("/start-dual.sh\n"); + sb.append("COPY --from=linkwork_assets /opt/agent/ai_employee.py ").append(startScriptsPath).append("/ai_employee.py\n"); + sb.append("COPY --from=linkwork_assets /usr/local/lib/python3.12/site-packages/linkwork_agent_sdk /usr/local/lib/python3.12/site-packages/linkwork_agent_sdk\n"); + sb.append("COPY --from=linkwork_assets /usr/local/lib/python3.12/site-packages/linkwork_agent_sdk-*.dist-info /usr/local/lib/python3.12/site-packages/\n\n"); + // Cedar 策略文件 → /tmp/cedar-policies/ (build.sh download_cedar_policies 会从这里读取) sb.append("# Cedar policy files for build.sh to deploy\n"); sb.append("COPY cedar-policies/ /tmp/cedar-policies/\n\n"); @@ -684,7 +879,7 @@ public void onNext(BuildResponseItem item) { /** * 推送镜像到仓库(带重试机制) */ - private void pushImage(String imageTag, String buildId) throws Exception { + private void pushImage(String imageTag, String buildId, String registry) throws Exception { log.info("Pushing image: {}", imageTag); publishLog(buildId, "info", "正在推送镜像: " + imageTag); @@ -693,7 +888,7 @@ private void pushImage(String imageTag, String buildId) throws Exception { for (int attempt = 1; attempt <= maxRetries; attempt++) { try { - doPushImage(imageTag, buildId, attempt); + doPushImage(imageTag, buildId, registry, attempt); // 推送成功 publishLog(buildId, "info", "镜像推送完成: " + imageTag); log.info("Image pushed successfully: {}", imageTag); @@ -711,11 +906,10 @@ private void pushImage(String imageTag, String buildId) throws Exception { attempt, attempt * 5, errorMsg); // 重新写入凭据文件并刷新认证 - String registryHost = config.getRegistry(); - if (registryHost.contains("/")) { - registryHost = registryHost.substring(0, registryHost.indexOf("/")); + String registryHost = extractRegistryHost(registry); + if (StringUtils.hasText(registryHost) && StringUtils.hasText(config.getRegistryUsername())) { + writeDockerConfigJson(registryHost, config.getRegistryUsername(), config.getRegistryPassword()); } - writeDockerConfigJson(registryHost, config.getRegistryUsername(), config.getRegistryPassword()); Thread.sleep(attempt * 5000L); } else if (!isAuthError && attempt < maxRetries) { @@ -737,15 +931,12 @@ private void pushImage(String imageTag, String buildId) throws Exception { /** * 执行单次镜像推送 */ - private void doPushImage(String imageTag, String buildId, int attempt) throws Exception { + private void doPushImage(String imageTag, String buildId, String registry, int attempt) throws Exception { // 构建认证配置 AuthConfig authConfig = null; if (StringUtils.hasText(config.getRegistryUsername()) && StringUtils.hasText(config.getRegistryPassword())) { - String registryAddress = config.getRegistry(); - if (StringUtils.hasText(registryAddress) && registryAddress.contains("/")) { - registryAddress = registryAddress.substring(0, registryAddress.indexOf("/")); - } + String registryAddress = extractRegistryHost(registry); // 使用 http:// 前缀明确标识为 HTTP 仓库,避免 daemon 默认走 HTTPS token 交换 authConfig = new AuthConfig() .withRegistryAddress("http://" + registryAddress) @@ -838,6 +1029,23 @@ public void onNext(PushResponseItem item) { publishLog(buildId, "info", "镜像推送完成: " + imageTag); log.info("Image pushed successfully: {}", imageTag); } + + private String extractRegistryHost(String registry) { + if (!StringUtils.hasText(registry)) { + return ""; + } + String value = registry.trim(); + if (value.startsWith("http://")) { + value = value.substring("http://".length()); + } else if (value.startsWith("https://")) { + value = value.substring("https://".length()); + } + int slash = value.indexOf("/"); + if (slash > 0) { + return value.substring(0, slash); + } + return value; + } /** * 删除本地镜像(推送成功后清理,避免磁盘堆积) @@ -853,6 +1061,176 @@ private void removeLocalImage(String imageTag, String buildId) { log.warn("Failed to remove local image {}: {}", imageTag, e.getMessage()); } } + + /** + * 周期清理本地构建镜像,并在 Kind 节点触发未使用镜像清理。 + */ + @Scheduled(cron = "${image-build.local-cleanup-cron:0 40 * * * *}") + public void periodicLocalImageCleanup() { + try { + Map result = runLocalImageMaintenance("scheduled"); + log.info("Local image cleanup finished: {}", result); + } catch (Exception e) { + log.warn("Periodic local image cleanup failed: {}", e.getMessage(), e); + } + } + + /** + * 立即执行一次本地镜像维护(供运维手动触发)。 + */ + public synchronized Map runLocalImageMaintenance(String triggerSource) { + Map result = new LinkedHashMap<>(); + result.put("triggerSource", StringUtils.hasText(triggerSource) ? triggerSource : "unknown"); + result.put("cleanupEnabled", config.isLocalCleanupEnabled()); + result.put("kindPruneEnabled", config.isKindPruneEnabled()); + result.put("retentionHours", Math.max(config.getLocalImageRetentionHours(), 1)); + result.put("kindClusterName", normalize(config.getKindClusterName())); + + if (!config.isLocalCleanupEnabled()) { + result.put("removedLocalImages", 0); + result.put("prunedKindNodes", 0); + result.put("skipped", "image-build.local-cleanup-enabled=false"); + return result; + } + + int removed = cleanupExpiredLocalBuildImages(); + result.put("removedLocalImages", removed); + + if (config.isKindPruneEnabled()) { + int prunedNodes = pruneKindNodeImages(); + result.put("prunedKindNodes", prunedNodes); + } else { + result.put("prunedKindNodes", 0); + } + return result; + } + + private int cleanupExpiredLocalBuildImages() { + long nowMs = System.currentTimeMillis(); + long retentionMs = TimeUnit.HOURS.toMillis(Math.max(config.getLocalImageRetentionHours(), 1)); + Set activeImageIds = collectActiveContainerImageIds(); + int removed = 0; + + List images = dockerClient.listImagesCmd().withShowAll(true).exec(); + for (Image image : images) { + String imageId = image.getId(); + if (StringUtils.hasText(imageId) && activeImageIds.contains(imageId)) { + continue; + } + long createdMs = resolveImageCreatedMillis(image); + if (createdMs <= 0 || nowMs - createdMs < retentionMs) { + continue; + } + String[] repoTags = image.getRepoTags(); + if (repoTags == null || repoTags.length == 0) { + continue; + } + for (String tag : repoTags) { + if (!shouldCleanupLocalTag(tag)) { + continue; + } + try { + dockerClient.removeImageCmd(tag).withForce(false).withNoPrune(true).exec(); + removed++; + log.info("Removed expired local build image: {}", tag); + } catch (Exception e) { + log.debug("Skip removing image {}: {}", tag, e.getMessage()); + } + } + } + return removed; + } + + private Set collectActiveContainerImageIds() { + Set result = new HashSet<>(); + List running = dockerClient.listContainersCmd().withShowAll(false).exec(); + for (Container c : running) { + if (StringUtils.hasText(c.getImageId())) { + result.add(c.getImageId()); + } + } + return result; + } + + private long resolveImageCreatedMillis(Image image) { + try { + if (image.getCreated() != null && image.getCreated() > 0) { + return image.getCreated() * 1000; + } + if (!StringUtils.hasText(image.getId())) { + return -1; + } + InspectImageResponse inspect = dockerClient.inspectImageCmd(image.getId()).exec(); + if (inspect != null && StringUtils.hasText(inspect.getCreated())) { + return OffsetDateTime.parse(inspect.getCreated()).toInstant().toEpochMilli(); + } + } catch (Exception e) { + log.debug("Resolve image created time failed: image={}, err={}", image.getId(), e.getMessage()); + } + return -1; + } + + private boolean shouldCleanupLocalTag(String tag) { + if (!StringUtils.hasText(tag) || ":".equals(tag)) { + return false; + } + int idx = tag.lastIndexOf(':'); + String repo = idx > 0 ? tag.substring(0, idx) : tag; + if (repo.startsWith("docker.io/library/")) { + repo = repo.substring("docker.io/library/".length()); + } + return LOCAL_BUILD_REPO_PATTERN.matcher(repo).matches(); + } + + private int pruneKindNodeImages() { + List kindNodes = findKindNodeContainers(); + int prunedNodes = 0; + for (Container node : kindNodes) { + String nodeName = resolveContainerName(node); + if (!StringUtils.hasText(nodeName)) { + continue; + } + try { + execInNode(node, "crictl", "rmi", "--prune"); + prunedNodes++; + log.info("Pruned unused images on kind node: {}", nodeName); + } catch (Exception e) { + log.warn("Kind node image prune failed on {}: {}", nodeName, e.getMessage()); + } + } + return prunedNodes; + } + + private void execInNode(Container node, String... cmd) throws InterruptedException { + ExecCreateCmdResponse exec = dockerClient.execCreateCmd(node.getId()) + .withAttachStdout(true) + .withAttachStderr(true) + .withCmd(cmd) + .exec(); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + ResultCallback.Adapter callback = new ResultCallback.Adapter<>() { + @Override + public void onNext(Frame item) { + try { + if (item != null && item.getPayload() != null) { + output.write(item.getPayload()); + } + } catch (IOException ignored) { + // 仅用于日志 + } + super.onNext(item); + } + }; + dockerClient.execStartCmd(exec.getId()) + .exec(callback) + .awaitCompletion(Math.max(config.getKindLoadTimeout(), 60), TimeUnit.SECONDS); + InspectExecResponse inspect = dockerClient.inspectExecCmd(exec.getId()).exec(); + Long exit = inspect != null ? inspect.getExitCodeLong() : null; + if (exit == null || exit != 0L) { + throw new IllegalStateException("exec failed: cmd=" + String.join(" ", cmd) + + ", exitCode=" + exit + ", output=" + output); + } + } /** * 清理构建上下文目录 diff --git a/back/src/main/java/com/linkwork/service/K8sOrchestratorImpl.java b/back/src/main/java/com/linkwork/service/K8sOrchestratorImpl.java index 783bb91..2bcd18b 100644 --- a/back/src/main/java/com/linkwork/service/K8sOrchestratorImpl.java +++ b/back/src/main/java/com/linkwork/service/K8sOrchestratorImpl.java @@ -12,6 +12,7 @@ import io.fabric8.kubernetes.client.KubernetesClientException; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; import java.io.BufferedReader; import java.io.InputStream; @@ -246,35 +247,33 @@ private void createImagePullSecret(MergedConfig config) { return; } - String registry = imageBuildConfig.getRegistry(); + String registryHost = resolveRegistryHostForPull(config); + if (!StringUtils.hasText(registryHost)) { + log.info("Agent image appears local/no-registry, skipping imagePullSecret creation"); + return; + } String username = imageBuildConfig.getRegistryUsername(); String password = imageBuildConfig.getRegistryPassword(); - if (registry == null || registry.isEmpty() || - username == null || username.isEmpty() || - password == null || password.isEmpty()) { + if (!StringUtils.hasText(username) || !StringUtils.hasText(password)) { log.info("Registry credentials not configured, skipping imagePullSecret creation"); return; } - - // 提取 host 部分作为 registry address(如 10.30.107.146/robot → 10.30.107.146:80) - // 容器运行时(containerd/Docker)按主机名匹配 auths,不含路径 - String registryHost = registry; - if (registryHost.contains("/")) { - registryHost = registryHost.substring(0, registryHost.indexOf("/")); - } - + String namespace = config.getNamespace(); - // 检查 Secret 是否已存在 + // 检查 Secret 是否已存在(存在则覆盖,确保 registry host 与凭证保持最新) Secret existing = kubernetesClient.secrets() .inNamespace(namespace) .withName(secretName) .get(); if (existing != null) { - log.info("ImagePullSecret {} already exists in namespace {}", secretName, namespace); - return; + kubernetesClient.secrets() + .inNamespace(namespace) + .withName(secretName) + .delete(); + log.info("ImagePullSecret {} already exists in namespace {}, recreating", secretName, namespace); } // 构建 dockerconfigjson 格式的凭证 @@ -302,6 +301,64 @@ private void createImagePullSecret(MergedConfig config) { log.info("Created ImagePullSecret {} in namespace {}", secretName, namespace); } + + private String resolveRegistryHostForPull(MergedConfig config) { + String registryHostFromImage = extractRegistryHostFromImage(config.getAgentImage()); + if (StringUtils.hasText(registryHostFromImage)) { + return registryHostFromImage; + } + + if (StringUtils.hasText(config.getImageRegistry())) { + return extractRegistryHost(config.getImageRegistry()); + } + + if (StringUtils.hasText(imageBuildConfig.getRegistry())) { + return extractRegistryHost(imageBuildConfig.getRegistry()); + } + + return ""; + } + + /** + * 从镜像地址提取 registry host: + * 仅当首段符合 registry 形式(含 '.'、':' 或 localhost)时认为包含 registry。 + * 例如: + * - 10.30.107.146/robot/a:b -> 10.30.107.146 + * - docker.io/library/nginx:latest -> docker.io + * - service-123-agent:tag -> "" + */ + private String extractRegistryHostFromImage(String image) { + if (!StringUtils.hasText(image)) { + return ""; + } + String value = image.trim(); + int slash = value.indexOf('/'); + if (slash <= 0) { + return ""; + } + String first = value.substring(0, slash); + if (first.contains(".") || first.contains(":") || "localhost".equals(first)) { + return first; + } + return ""; + } + + private String extractRegistryHost(String registry) { + if (!StringUtils.hasText(registry)) { + return ""; + } + String value = registry.trim(); + if (value.startsWith("http://")) { + value = value.substring("http://".length()); + } else if (value.startsWith("https://")) { + value = value.substring("https://".length()); + } + int slash = value.indexOf("/"); + if (slash > 0) { + return value.substring(0, slash); + } + return value; + } private void createTokenSecret(MergedConfig config) { String secretName = "svc-" + config.getServiceId() + "-token"; @@ -1493,6 +1550,15 @@ public ScaleResult scaleUp(String serviceId, int targetPodCount, MergedConfig co List runningPods = getRunningPods(serviceId); int previousCount = runningPods.size(); + + // 当服务当前无存活 Pod 时,先刷新依赖资源,避免使用到过期 ConfigMap/Secret。 + if (previousCount == 0) { + createImagePullSecret(config); + createTokenSecret(config); + createAgentConfigMap(config); + ensureRunnerScriptsConfigMap(config.getNamespace()); + createPodGroup(config); + } if (targetPodCount <= previousCount) { log.info("Service {} already has {} pods, target is {}, no scaling needed", diff --git a/back/src/main/java/com/linkwork/service/McpServerService.java b/back/src/main/java/com/linkwork/service/McpServerService.java index f070fa6..c0a1235 100644 --- a/back/src/main/java/com/linkwork/service/McpServerService.java +++ b/back/src/main/java/com/linkwork/service/McpServerService.java @@ -236,7 +236,7 @@ public Map generateMcpConfig(List mcpIds) { */ public Map getHealthStatus(String userId) { if (!StringUtils.hasText(userId)) { - return Map.of("items", List.of(), "checkedAt", LocalDateTime.now()); + return Map.of("items", List.of(), "checkedAt", LocalDateTime.now().toString()); } LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); if (!adminAccessService.isAdmin(userId)) { @@ -249,7 +249,7 @@ public Map getHealthStatus(String userId) { Map result = new LinkedHashMap<>(); result.put("items", items); - result.put("checkedAt", LocalDateTime.now()); + result.put("checkedAt", LocalDateTime.now().toString()); return result; } @@ -508,12 +508,12 @@ private Map toResponseMap(McpServerEntity entity, String userId) map.put("consecutiveFailures", entity.getConsecutiveFailures()); map.put("version", entity.getVersion()); map.put("tags", entity.getTags()); - map.put("lastHealthAt", entity.getLastHealthAt()); + map.put("lastHealthAt", formatDateTime(entity.getLastHealthAt())); map.put("configJson", entity.getConfigJson()); map.put("creatorId", entity.getCreatorId()); map.put("creatorName", entity.getCreatorName()); - map.put("createdAt", entity.getCreatedAt()); - map.put("updatedAt", entity.getUpdatedAt()); + map.put("createdAt", formatDateTime(entity.getCreatedAt())); + map.put("updatedAt", formatDateTime(entity.getUpdatedAt())); return map; } @@ -635,9 +635,13 @@ private Map toHealthMap(McpServerEntity entity) { map.put("type", entity.getType()); map.put("status", coerceStatus(entity.getStatus())); map.put("latencyMs", entity.getHealthLatencyMs()); - map.put("lastHealthAt", entity.getLastHealthAt()); + map.put("lastHealthAt", formatDateTime(entity.getLastHealthAt())); map.put("consecutiveFailures", entity.getConsecutiveFailures()); map.put("healthMessage", entity.getHealthMessage()); return map; } + + private String formatDateTime(LocalDateTime value) { + return value == null ? null : value.toString(); + } } diff --git a/back/src/main/java/com/linkwork/service/PodSpecGenerator.java b/back/src/main/java/com/linkwork/service/PodSpecGenerator.java index 3258c80..35d1e0f 100644 --- a/back/src/main/java/com/linkwork/service/PodSpecGenerator.java +++ b/back/src/main/java/com/linkwork/service/PodSpecGenerator.java @@ -732,13 +732,33 @@ private void addOssVolumeMount(ContainerBuilder builder, MergedConfig config) { // ==================== 镜像拉取凭证 ==================== private List buildImagePullSecrets(MergedConfig config) { - if (StringUtils.hasText(config.getImagePullSecret())) { + if (StringUtils.hasText(config.getImagePullSecret()) && requiresImagePullSecret(config)) { return Collections.singletonList( new LocalObjectReference(config.getImagePullSecret()) ); } return Collections.emptyList(); } + + private boolean requiresImagePullSecret(MergedConfig config) { + if (hasRegistryHost(config.getAgentImage())) { + return true; + } + return hasRegistryHost(config.getRunnerImage()); + } + + private boolean hasRegistryHost(String image) { + if (!StringUtils.hasText(image)) { + return false; + } + String value = image.trim(); + int slash = value.indexOf('/'); + if (slash <= 0) { + return false; + } + String first = value.substring(0, slash); + return first.contains(".") || first.contains(":") || "localhost".equals(first); + } // ==================== 节点亲和配置 ==================== diff --git a/back/src/main/java/com/linkwork/service/RoleService.java b/back/src/main/java/com/linkwork/service/RoleService.java index 3192e2b..fe85409 100644 --- a/back/src/main/java/com/linkwork/service/RoleService.java +++ b/back/src/main/java/com/linkwork/service/RoleService.java @@ -366,7 +366,7 @@ private String normalizeRoleStatusOrDefault(String rawStatus) { private String normalizeRequiredRolePrompt(String rawPrompt) { String normalized = normalizeText(rawPrompt); if (!StringUtils.hasText(normalized)) { - throw new IllegalArgumentException("岗位自定义 Prompt 不能为空"); + return "你是一个智能岗位助手,请根据用户需求高质量完成任务。"; } return normalized; } diff --git a/back/src/main/java/com/linkwork/service/SkillService.java b/back/src/main/java/com/linkwork/service/SkillService.java index 08c89cf..0936167 100644 --- a/back/src/main/java/com/linkwork/service/SkillService.java +++ b/back/src/main/java/com/linkwork/service/SkillService.java @@ -677,11 +677,11 @@ private Map toResponseMap(SkillEntity entity) { map.put("isPublic", entity.getIsPublic()); map.put("branchName", entity.getBranchName()); map.put("latestCommit", entity.getLatestCommit()); - map.put("lastSyncedAt", entity.getLastSyncedAt()); + map.put("lastSyncedAt", formatDateTime(entity.getLastSyncedAt())); map.put("creatorId", entity.getCreatorId()); map.put("creatorName", entity.getCreatorName()); - map.put("createdAt", entity.getCreatedAt()); - map.put("updatedAt", entity.getUpdatedAt()); + map.put("createdAt", formatDateTime(entity.getCreatedAt())); + map.put("updatedAt", formatDateTime(entity.getUpdatedAt())); return map; } @@ -697,8 +697,12 @@ private Map toSimpleMap(SkillEntity entity) { map.put("status", entity.getStatus()); map.put("isPublic", entity.getIsPublic()); map.put("skillNo", entity.getSkillNo()); - map.put("createdAt", entity.getCreatedAt()); - map.put("updatedAt", entity.getUpdatedAt()); + map.put("createdAt", formatDateTime(entity.getCreatedAt())); + map.put("updatedAt", formatDateTime(entity.getUpdatedAt())); return map; } + + private String formatDateTime(LocalDateTime value) { + return value == null ? null : value.toString(); + } } diff --git a/back/src/main/java/com/linkwork/service/UserSoulService.java b/back/src/main/java/com/linkwork/service/UserSoulService.java index 994e36a..93a5af0 100644 --- a/back/src/main/java/com/linkwork/service/UserSoulService.java +++ b/back/src/main/java/com/linkwork/service/UserSoulService.java @@ -6,13 +6,16 @@ import com.linkwork.model.dto.UserSoulResponse; import com.linkwork.model.dto.UserSoulUpsertRequest; import com.linkwork.model.entity.UserSoulEntity; +import lombok.extern.slf4j.Slf4j; import lombok.RequiredArgsConstructor; +import org.springframework.jdbc.BadSqlGrammarException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; @Service @RequiredArgsConstructor +@Slf4j public class UserSoulService { private static final int MAX_CONTENT_LENGTH = 8000; @@ -114,11 +117,30 @@ private UserSoulEntity findByUserId(String userId) { if (!StringUtils.hasText(userId)) { throw new IllegalArgumentException("用户未登录或登录态失效"); } - return userSoulMapper.selectOne(new LambdaQueryWrapper() - .eq(UserSoulEntity::getUserId, userId) - .orderByDesc(UserSoulEntity::getUpdatedAt) - .orderByDesc(UserSoulEntity::getId) - .last("limit 1")); + try { + return userSoulMapper.selectOne(new LambdaQueryWrapper() + .eq(UserSoulEntity::getUserId, userId) + .orderByDesc(UserSoulEntity::getUpdatedAt) + .orderByDesc(UserSoulEntity::getId) + .last("limit 1")); + } catch (BadSqlGrammarException ex) { + if (isCompatColumnMismatch(ex)) { + log.warn("UserSoul schema mismatch detected, fallback to compat query: userId={}, err={}", + userId, ex.getMessage()); + return userSoulMapper.selectLatestCompatByUserId(userId); + } + throw ex; + } + } + + private boolean isCompatColumnMismatch(BadSqlGrammarException ex) { + String msg = ex.getMessage(); + return msg != null + && (msg.contains("Unknown column 'content'") + || msg.contains("Unknown column 'preset_id'") + || msg.contains("Unknown column 'version'") + || msg.contains("Unknown column 'updater_id'") + || msg.contains("Unknown column 'updater_name'")); } private String normalizeContent(String content) { diff --git a/back/src/main/java/com/linkwork/websocket/TaskWebSocketHandler.java b/back/src/main/java/com/linkwork/websocket/TaskWebSocketHandler.java index 139517e..6d7eb4b 100644 --- a/back/src/main/java/com/linkwork/websocket/TaskWebSocketHandler.java +++ b/back/src/main/java/com/linkwork/websocket/TaskWebSocketHandler.java @@ -32,6 +32,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; /** * 任务 WebSocket 事件推送。 @@ -43,6 +44,7 @@ @Slf4j @Component public class TaskWebSocketHandler extends TextWebSocketHandler { + private static final Pattern NUMERIC_ID_PATTERN = Pattern.compile("^\\d+$"); private final ObjectMapper objectMapper = new ObjectMapper(); private final StringRedisTemplate redisTemplate; @@ -115,7 +117,7 @@ private String extractTaskId(WebSocketSession session) { protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { Map request = objectMapper.readValue(message.getPayload(), Map.class); String action = (String) request.get("action"); - String taskId = (String) request.get("taskId"); + String taskId = firstNonBlank(request, "taskId", "taskNo", "task_id"); log.info("Received message: action={}, taskId={}", action, taskId); if ("bind".equals(action) && taskId != null) { bindTask(session, taskId); @@ -123,9 +125,14 @@ protected void handleTextMessage(WebSocketSession session, TextMessage message) } private void bindTask(WebSocketSession session, String taskId) { - sessionTaskMap.put(session.getId(), taskId); - log.info("Session {} bound to task {}", session.getId(), taskId); - pushHistoryEvents(session, taskId); + String taskNo = normalizeTaskNo(taskId); + sessionTaskMap.put(session.getId(), taskNo); + if (!taskNo.equals(taskId)) { + log.info("Session {} bound task normalized: rawTaskId={}, taskNo={}", session.getId(), taskId, taskNo); + } else { + log.info("Session {} bound to task {}", session.getId(), taskNo); + } + pushHistoryEvents(session, taskNo); } private List buildStreamKeys(String taskId) { @@ -147,6 +154,28 @@ private Long resolveRoleId(String taskId) { } } + private String normalizeTaskNo(String rawTaskId) { + if (!StringUtils.hasText(rawTaskId)) { + return rawTaskId; + } + String candidate = rawTaskId.trim(); + try { + return taskService.getTaskByNo(candidate).getTaskNo(); + } catch (Exception ignored) { + // ignore and continue resolving by numeric id + } + + if (NUMERIC_ID_PATTERN.matcher(candidate).matches()) { + try { + return taskService.getTask(Long.parseLong(candidate)).getTaskNo(); + } catch (Exception e) { + log.debug("WebSocket normalize taskId by numeric id failed: taskId={}, err={}", + candidate, e.getMessage()); + } + } + return candidate; + } + private void pushHistoryEvents(WebSocketSession session, String taskId) { try { List streamKeys = buildStreamKeys(taskId); diff --git a/back/src/main/resources/application-gg.yml b/back/src/main/resources/application-gg.yml new file mode 100644 index 0000000..f7dafcf --- /dev/null +++ b/back/src/main/resources/application-gg.yml @@ -0,0 +1,20 @@ +schedule: + images: + agent: ${SCHEDULE_AGENT_IMAGE:10.30.107.146/robot/rockylinux9-agent:v1.3} + runner: ${RUNNER_IMAGE:10.30.107.146/robot/rockylinux9-agent:v1.3} + +image-build: + enabled: ${IMAGE_BUILD_ENABLED:true} + push-enabled: ${IMAGE_PUSH_ENABLED:false} + default-agent-base-image: ${DEFAULT_AGENT_BASE_IMAGE:10.30.107.146/robot/rockylinux9-agent:v1.3} + asset-source-image: ${IMAGE_BUILD_ASSET_SOURCE_IMAGE:service-3-agent:3-20260319104738} + registry: ${IMAGE_REGISTRY:} + registry-username: ${IMAGE_REGISTRY_USERNAME:} + registry-password: ${IMAGE_REGISTRY_PASSWORD:} + auto-load-to-kind: ${LINKWORK_BUILD_LOCAL_LOAD_ENABLED:true} + kind-cluster-name: ${LINKWORK_BUILD_KIND_CLUSTER_NAME:} + kind-load-timeout: ${KIND_LOAD_TIMEOUT:600} + local-cleanup-enabled: ${IMAGE_LOCAL_CLEANUP_ENABLED:true} + local-image-retention-hours: ${IMAGE_LOCAL_RETENTION_HOURS:24} + local-cleanup-cron: ${IMAGE_LOCAL_CLEANUP_CRON:0 40 * * * *} + kind-prune-enabled: ${IMAGE_KIND_PRUNE_ENABLED:true} diff --git a/back/src/main/resources/application.yml b/back/src/main/resources/application.yml index 9118ce9..f2ab303 100644 --- a/back/src/main/resources/application.yml +++ b/back/src/main/resources/application.yml @@ -13,9 +13,9 @@ spring: # MySQL 数据库配置 datasource: - url: jdbc:mysql://host.docker.internal:3307/linkwork?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8 - username: robot - password: ${MYSQL_PASSWORD:} + url: ${LINKWORK_DB_URL:jdbc:mysql://host.docker.internal:3307/linkwork?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8} + username: ${LINKWORK_DB_USERNAME:robot} + password: ${LINKWORK_DB_PASSWORD:${MYSQL_PASSWORD:}} driver-class-name: com.mysql.cj.jdbc.Driver hikari: minimum-idle: 5 @@ -26,8 +26,9 @@ spring: # Redis 配置(通过 Docker 网络直连 momo-redis 容器) data: redis: - host: momo-redis - port: 6379 + host: ${LINKWORK_REDIS_HOST:momo-redis} + port: ${LINKWORK_REDIS_PORT:6379} + password: ${LINKWORK_REDIS_PASSWORD:} # MyBatis Plus 配置 mybatis-plus: @@ -166,7 +167,8 @@ schedule: api-base-url: http://api-gateway.ai-worker.svc:8080 ws-base-url: ws://ws-gateway.ai-worker.svc:8080 llm-gateway-url: http://llm-gateway.ai-worker.svc:8080 - redis-url: ${REDIS_URL:redis://10.136.55.3:6379} + # 默认与 spring.data.redis 同源,避免后端入队和 worker 消费落到不同 Redis + redis-url: ${REDIS_URL:redis://${LINKWORK_REDIS_HOST:momo-redis}:${LINKWORK_REDIS_PORT:6379}} ssh: port: 22 @@ -249,14 +251,28 @@ image-build: # 构建上下文临时目录 build-context-dir: ${BUILD_CONTEXT_DIR:/tmp/docker-build} + + # 本地镜像同步(K8s + imageRegistry 为空时,将镜像导入 Kind 节点) + auto-load-to-kind: ${LINKWORK_BUILD_LOCAL_LOAD_ENABLED:${IMAGE_AUTO_LOAD_TO_KIND:true}} + # Kind 集群名(为空则自动发现) + kind-cluster-name: ${LINKWORK_BUILD_KIND_CLUSTER_NAME:${KIND_CLUSTER_NAME:}} + # Kind 导入超时(秒) + kind-load-timeout: ${KIND_LOAD_TIMEOUT:600} + + # 定期清理 + local-cleanup-enabled: ${IMAGE_LOCAL_CLEANUP_ENABLED:true} + local-image-retention-hours: ${IMAGE_LOCAL_RETENTION_HOURS:24} + local-cleanup-cron: ${IMAGE_LOCAL_CLEANUP_CRON:0 40 * * * *} + kind-prune-enabled: ${IMAGE_KIND_PRUNE_ENABLED:true} - # SDK 仓库配置(镜像构建阶段克隆,拷贝 momo-agent-sdk 和 docker/agent/zzd 到镜像中) + # SDK 仓库配置(镜像构建阶段克隆,拷贝 linkwork-agent-sdk 和 docker/agent/zzd 到镜像中) sdk-repo-url: ${SDK_REPO_URL:} + asset-source-image: ${IMAGE_BUILD_ASSET_SOURCE_IMAGE:} sdk-repo-branch: ${SDK_REPO_BRANCH:} sdk-repo-username: ${SDK_REPO_USERNAME:} sdk-repo-password: ${SDK_REPO_PASSWORD:} - sdk-source-path: ${SDK_SOURCE_PATH:/opt/momo-agent-build/sdk-source} - zzd-binaries-path: ${ZZD_BINARIES_PATH:/opt/momo-agent-build/zzd-binaries} + sdk-source-path: ${SDK_SOURCE_PATH:/opt/linkwork-agent-build/sdk-source} + zzd-binaries-path: ${ZZD_BINARIES_PATH:/opt/linkwork-agent-build/zzd-binaries} # NFS 存储配置(替代原 OSS) # 后续更换 NFS 服务器地址只需重新 mount + 修改 NFS_STORAGE_BASE_PATH 环境变量,无需改代码 diff --git a/back/src/main/resources/scripts/build.sh b/back/src/main/resources/scripts/build.sh index a297f2c..be250b9 100755 --- a/back/src/main/resources/scripts/build.sh +++ b/back/src/main/resources/scripts/build.sh @@ -12,7 +12,7 @@ # 1. 校验基础镜像内置依赖 (Python 3.12, Node.js, npm, git, Claude CLI, uv, uvx) # 2. 创建基础目录 # 3. 安装 zzd 二进制 (zzd, zz, gen-key, encrypt-key) -# 4. 安装 momo-agent-sdk (源码) +# 4. 安装 linkwork-agent-sdk (源码) # 5. 创建 agent 用户 + passwordless sudo # 6. 创建 workspace 目录结构 # 6.1 部署 security.json/mcp.json/skills 到 /opt/agent/ (root:agent 只读) @@ -47,7 +47,7 @@ # 构建输入目录(支持两种来源): # 1) 固定目录(推荐):$BUILD_ASSETS_ROOT # - zzd-binaries/ - zzd, zz, gen-key, encrypt-key -# - sdk-source/ - momo-agent-sdk 源码 (含 pyproject.toml + src/) +# - sdk-source/ - linkwork-agent-sdk 源码 (含 pyproject.toml + src/) # - start-scripts/ - start-single.sh, start-dual.sh, ai_employee.py # 2) 兼容旧路径(若固定目录不存在则回退):/tmp/* # @@ -74,7 +74,7 @@ readonly ZZD_SOCKET_DIR="/var/run/zzd" readonly ZZD_AUDIT_DIR="/var/log/zzd/audit" # 构建输入根目录(固定目录,可通过环境变量覆盖) -readonly BUILD_ASSETS_ROOT="${BUILD_ASSETS_ROOT:-/opt/momo-agent-build}" +readonly BUILD_ASSETS_ROOT="${BUILD_ASSETS_ROOT:-/opt/linkwork-agent-build}" readonly LEGACY_TMP_ROOT="/tmp" # 输入路径(优先固定目录,不存在时回退旧 /tmp 路径) @@ -88,6 +88,9 @@ fi if [[ ! -d "${SDK_SRC}" && -d "${LEGACY_TMP_ROOT}/sdk-build" ]]; then SDK_SRC="${LEGACY_TMP_ROOT}/sdk-build" fi +if [[ ! -d "${SDK_SRC}" && -d "${LEGACY_TMP_ROOT}/linkwork-agent-sdk" ]]; then + SDK_SRC="${LEGACY_TMP_ROOT}/linkwork-agent-sdk" +fi if [[ ! -d "${START_SCRIPTS_SRC}" && -d "${LEGACY_TMP_ROOT}/start-scripts" ]]; then START_SCRIPTS_SRC="${LEGACY_TMP_ROOT}/start-scripts" fi @@ -264,14 +267,25 @@ create_directories() { install_zzd_binaries() { log_info "安装 zzd 二进制文件..." + local binaries=("zzd" "zz" "gen-key" "encrypt-key") + if [[ ! -d "${ZZD_BIN_SRC}" ]]; then + local missing=() + for bin in "${binaries[@]}"; do + if ! command_exists "${bin}"; then + missing+=("${bin}") + fi + done + if [[ ${#missing[@]} -eq 0 ]]; then + log_warn "未找到 ${ZZD_BIN_SRC},但基础镜像已内置 zzd 二进制,跳过复制" + return 0 + fi + log_error "zzd 二进制目录不存在: ${ZZD_BIN_SRC}" log_error "请将 zzd/zz/gen-key/encrypt-key 放置到固定目录,或通过 ZZD_BIN_SRC 指定路径" return 1 fi - local binaries=("zzd" "zz" "gen-key" "encrypt-key") - for bin in "${binaries[@]}"; do if [[ ! -f "${ZZD_BIN_SRC}/${bin}" ]]; then log_error "二进制文件不存在: ${ZZD_BIN_SRC}/${bin}" @@ -290,25 +304,35 @@ install_zzd_binaries() { return 0 } -# 安装 momo-agent-sdk +# 安装 linkwork-agent-sdk install_sdk() { - log_info "安装 momo-agent-sdk..." + log_info "安装 linkwork-agent-sdk..." + + # 固定使用 Python 3.12(基础镜像内置) + local python_cmd="python3.12" + if ! command_exists "${python_cmd}"; then + log_error "未找到 ${python_cmd}" + return 1 + fi if [[ ! -d "${SDK_SRC}" ]]; then + if ${python_cmd} -c "import linkwork_agent_sdk" >/dev/null 2>&1; then + log_warn "未找到 SDK 源码目录,检测到基础镜像已安装 linkwork_agent_sdk,跳过安装" + return 0 + fi + log_error "SDK 源码目录不存在: ${SDK_SRC}" - log_error "请将 momo-agent-sdk 源码放置到固定目录,或通过 SDK_SRC 指定路径" + log_error "请将 linkwork-agent-sdk 源码放置到固定目录,或通过 SDK_SRC 指定路径" return 1 fi if [[ ! -f "${SDK_SRC}/pyproject.toml" ]]; then - log_error "${SDK_SRC}/pyproject.toml 不存在" - return 1 - fi + if ${python_cmd} -c "import linkwork_agent_sdk" >/dev/null 2>&1; then + log_warn "${SDK_SRC}/pyproject.toml 不存在,但基础镜像已安装 linkwork_agent_sdk,跳过安装" + return 0 + fi - # 固定使用 Python 3.12(基础镜像内置) - local python_cmd="python3.12" - if ! command_exists "${python_cmd}"; then - log_error "未找到 ${python_cmd}" + log_error "${SDK_SRC}/pyproject.toml 不存在" return 1 fi @@ -320,7 +344,55 @@ install_sdk() { return 1 } - log_success "momo-agent-sdk 安装完成" + log_success "linkwork-agent-sdk 安装完成" + return 0 +} + +# 修补 linkwork_agent_sdk 运行时模型选择: +# 当 config.json/env 提供 ANTHROPIC_MODEL 时,优先使用该完整模型名, +# 避免 claude-sdk 将 "sonnet" 展开为不被 LiteLLM 识别的别名。 +patch_sdk_runtime_model_override() { + local python_cmd="python3.12" + + if ! command_exists "${python_cmd}"; then + log_warn "未找到 ${python_cmd},跳过 SDK runtime model 补丁" + return 0 + fi + + local engine_file + engine_file="$(${python_cmd} - <<'PY' +import importlib +try: + m = importlib.import_module("linkwork_agent_sdk.engine.agent_engine") + print(getattr(m, "__file__", "")) +except Exception: + print("") +PY +)" + + if [[ -z "${engine_file}" || ! -f "${engine_file}" ]]; then + log_warn "未定位到 linkwork_agent_sdk.engine.agent_engine,跳过补丁" + return 0 + fi + + if grep -q 'resolved_env.get("ANTHROPIC_MODEL")' "${engine_file}"; then + log_info "SDK runtime model 补丁已存在,跳过" + return 0 + fi + + if ! grep -q '"model": self._config.claude_settings.model,' "${engine_file}"; then + log_warn "未匹配到预期模型配置行,跳过 SDK runtime model 补丁: ${engine_file}" + return 0 + fi + + sed -i \ + 's/"model": self._config.claude_settings.model,/"model": (resolved_env.get("ANTHROPIC_MODEL") if resolved_env and resolved_env.get("ANTHROPIC_MODEL") else self._config.claude_settings.model),/' \ + "${engine_file}" || { + log_error "应用 SDK runtime model 补丁失败: ${engine_file}" + return 1 + } + + log_success "SDK runtime model 补丁已应用: ${engine_file}" return 0 } @@ -1209,14 +1281,27 @@ deploy_start_scripts() { local deploy_dir="/opt/agent" mkdir -p "${deploy_dir}" + local scripts=("start-single.sh" "start-dual.sh" "ai_employee.py") if [[ ! -d "${START_SCRIPTS_SRC}" ]]; then + local existing=0 + for script in "${scripts[@]}"; do + if [[ -f "${deploy_dir}/${script}" ]]; then + chmod 0755 "${deploy_dir}/${script}" || true + chown root:root "${deploy_dir}/${script}" || true + ((existing++)) + fi + done + if [[ ${existing} -eq ${#scripts[@]} ]]; then + log_warn "未找到 ${START_SCRIPTS_SRC},使用基础镜像已存在的启动脚本" + return 0 + fi + log_error "启动脚本目录不存在: ${START_SCRIPTS_SRC}" log_error "请将 start-single.sh/start-dual.sh/ai_employee.py 放置到固定目录,或通过 START_SCRIPTS_SRC 指定路径" return 1 fi - local scripts=("start-single.sh" "start-dual.sh" "ai_employee.py") local deployed=0 for script in "${scripts[@]}"; do @@ -1455,6 +1540,11 @@ main() { exit 1 fi + if ! patch_sdk_runtime_model_override; then + log_error "SDK runtime model 补丁失败" + exit 1 + fi + if ! setup_agent_user; then log_error "agent 用户创建失败" exit 1 @@ -1540,7 +1630,7 @@ main() { log_info "清理构建临时文件..." rm -rf /tmp/cedar-policies /tmp/cedar-policies-download - # 仅清理 /tmp 回退路径,避免误删固定资产目录(如 /opt/momo-agent-build) + # 仅清理 /tmp 回退路径,避免误删固定资产目录(如 /opt/linkwork-agent-build) for cleanup_dir in "${ZZD_BIN_SRC}" "${SDK_SRC}" "${START_SCRIPTS_SRC}"; do if [[ "${cleanup_dir}" == /tmp/* ]]; then rm -rf "${cleanup_dir}" diff --git a/back/src/main/resources/scripts/config.json b/back/src/main/resources/scripts/config.json index edc0658..1c48beb 100644 --- a/back/src/main/resources/scripts/config.json +++ b/back/src/main/resources/scripts/config.json @@ -1,11 +1,12 @@ { "claude_settings": { - "model": "openrouter/anthropic/claude-sonnet-4.5", + "model": "sonnet", "language": "Chinese", "env": { "ANTHROPIC_BASE_URL": "http://172.18.228.32:4000", "ANTHROPIC_AUTH_TOKEN": "sk-48sbQpOd2cCeXW1q3H4TBQ", "ANTHROPIC_API_KEY": "sk-48sbQpOd2cCeXW1q3H4TBQ", + "ANTHROPIC_MODEL": "openrouter/anthropic/claude-sonnet-4.5", "LITELLM_BASE_URL": "http://172.18.228.32:4000", "LITELLM_API_KEY": "sk-48sbQpOd2cCeXW1q3H4TBQ", "API_TIMEOUT_MS": "600000", diff --git a/docs/guides/deployment.md b/docs/guides/deployment.md index ad558b1..ca67282 100644 --- a/docs/guides/deployment.md +++ b/docs/guides/deployment.md @@ -154,8 +154,10 @@ LinkWork uses a "One Role, One Image" mechanism. The role build process: 1. **Admin configures the role** — Select Skills, MCP tools, security policies, resource quotas 2. **Trigger image build** — Server dynamically generates a Dockerfile, executes `docker build` -3. **Push to Harbor** — Auto-push to the configured image registry after build -4. **K8s pulls and runs** — During task scheduling, K8s pulls the role image from Harbor to create Pods +3. **Image distribution (choose one)** + - `imageRegistry` configured: push the built image to remote registry + - `imageRegistry` empty: keep image local and auto-sync only the current built image to Kind nodes (no bulk load of all host images) +4. **K8s pulls and runs** — During task scheduling, K8s pulls the role image from the configured source Image naming convention: `{registry}/service-{serviceId}-agent:{serviceId}-{timestamp}` @@ -165,6 +167,25 @@ Base image built on Rocky Linux 9, pre-installed with: - git, curl, jq, and other common tools - Claude CLI, uv/uvx, and other AI development tools +### Local Image Ops (Kind) + +In local-image mode, backend can auto-run image sync and cleanup. Recommended env vars: + +```bash +LINKWORK_BUILD_LOCAL_LOAD_ENABLED=true +LINKWORK_BUILD_KIND_CLUSTER_NAME=shared-dev # optional, auto-discover if empty +IMAGE_LOCAL_CLEANUP_ENABLED=true +IMAGE_LOCAL_RETENTION_HOURS=24 +IMAGE_LOCAL_CLEANUP_CRON="0 40 * * * *" # minute 40 of every hour +IMAGE_KIND_PRUNE_ENABLED=true +``` + +You can also trigger one maintenance run manually (without waiting for cron): + +```bash +curl -X POST http:///api/v1/build/ops/local-image-maintenance +``` + --- ## Horizontal Scaling diff --git a/docs/guides/deployment_zh-CN.md b/docs/guides/deployment_zh-CN.md index ec3e5e3..e540d54 100644 --- a/docs/guides/deployment_zh-CN.md +++ b/docs/guides/deployment_zh-CN.md @@ -154,8 +154,10 @@ LinkWork 采用「一岗位一镜像」机制,岗位构建流程: 1. **管理员配置岗位** — 选择 Skills、MCP 工具、安全策略、资源配额 2. **触发镜像构建** — server 动态生成 Dockerfile,执行 `docker build` -3. **推送到 Harbor** — 构建完成后自动推送到配置的镜像仓库 -4. **K8s 拉取运行** — 任务调度时,K8s 从 Harbor 拉取对应岗位镜像创建 Pod +3. **镜像分发(二选一)** + - 配置了 `imageRegistry`:构建完成后推送到远程仓库 + - 未配置 `imageRegistry`:仅保留本地镜像,并自动把“当前构建镜像”同步到 Kind 节点(不会全量同步主机所有镜像) +4. **K8s 拉取运行** — 任务调度时,从配置的镜像来源拉取对应岗位镜像创建 Pod 镜像命名规则:`{registry}/service-{serviceId}-agent:{serviceId}-{timestamp}` @@ -165,6 +167,25 @@ LinkWork 采用「一岗位一镜像」机制,岗位构建流程: - git、curl、jq 等常用工具 - Claude CLI、uv/uvx 等 AI 开发工具 +### 本地镜像运维动作(Kind) + +后端在本地镜像模式下会自动执行镜像同步与清理,建议在环境变量中显式配置: + +```bash +LINKWORK_BUILD_LOCAL_LOAD_ENABLED=true +LINKWORK_BUILD_KIND_CLUSTER_NAME=shared-dev # 可选,不填则自动发现 +IMAGE_LOCAL_CLEANUP_ENABLED=true +IMAGE_LOCAL_RETENTION_HOURS=24 +IMAGE_LOCAL_CLEANUP_CRON="0 40 * * * *" # 每小时第 40 分钟 +IMAGE_KIND_PRUNE_ENABLED=true +``` + +并支持手动触发一次运维动作(立即执行,不等 cron): + +```bash +curl -X POST http:///api/v1/build/ops/local-image-maintenance +``` + --- ## 横向扩展 From 2b520569491667ed8a6129ed686601f60d2393c4 Mon Sep 17 00:00:00 2001 From: wangfenghe Date: Mon, 23 Mar 2026 11:42:10 +0800 Subject: [PATCH 07/21] feat(runtime): align pi runtime wiring with backend build prerequisites --- back/src/main/resources/scripts/build.sh | 20 +++++++++++++++++--- back/src/main/resources/scripts/config.json | 13 +++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/back/src/main/resources/scripts/build.sh b/back/src/main/resources/scripts/build.sh index be250b9..b472232 100755 --- a/back/src/main/resources/scripts/build.sh +++ b/back/src/main/resources/scripts/build.sh @@ -9,7 +9,7 @@ # 4. 部署 MCP 配置到 /opt/agent/mcp.json # # 构建时步骤: -# 1. 校验基础镜像内置依赖 (Python 3.12, Node.js, npm, git, Claude CLI, uv, uvx) +# 1. 校验基础镜像内置依赖 (Python 3.12, Node.js, npm, git, Claude CLI, Pi CLI, uv, uvx) # 2. 创建基础目录 # 3. 安装 zzd 二进制 (zzd, zz, gen-key, encrypt-key) # 4. 安装 linkwork-agent-sdk (源码) @@ -160,11 +160,21 @@ install_claude_cli() { return 1 } +# 检查 Pi CLI(禁用在线安装) +install_pi_cli() { + if command_exists pi; then + log_success "Pi CLI 已安装" + return 0 + fi + log_error "pi 命令缺失。当前模式禁用在线安装,请在基础镜像中预装 Pi CLI。" + return 1 +} + # 检查基础镜像内置依赖(不安装) check_prerequisites() { log_info "检查基础镜像内置依赖(不执行在线安装)..." - local required_cmds=("curl" "jq" "python3.12" "node" "npm" "git" "claude" "uv" "uvx") + local required_cmds=("curl" "jq" "python3.12" "node" "npm" "git" "claude" "pi" "uv" "uvx") local missing=() for cmd in "${required_cmds[@]}"; do @@ -175,7 +185,7 @@ check_prerequisites() { if [[ ${#missing[@]} -gt 0 ]]; then log_error "缺少必需命令: ${missing[*]}" - log_error "请确保基础镜像已预装: Python 3.12 / Node.js v24.13.0 / npm 11.6.2 / git 2.43.5 / Claude CLI / uv / uvx" + log_error "请确保基础镜像已预装: Python 3.12 / Node.js v24.13.0 / npm 11.6.2 / git 2.43.5 / Claude CLI / Pi CLI / uv / uvx" return 1 fi @@ -190,6 +200,7 @@ check_prerequisites() { log_info " npm $(npm --version 2>&1)" log_info " $(git --version 2>&1)" log_info " $(claude --version 2>&1 | head -1)" + log_info " $(pi --version 2>&1 | head -1)" log_info " $(uv --version 2>&1)" log_info " $(uvx --version 2>&1 | head -1)" @@ -473,9 +484,12 @@ setup_agent_user() { local agent_home="/home/agent" mkdir -p "${agent_home}/.claude" echo '{"hasCompletedOnboarding": true}' > "${agent_home}/.claude.json" + mkdir -p "${agent_home}/.pi/agent" chown agent:agent "${agent_home}/.claude.json" chown -R agent:agent "${agent_home}/.claude" + chown -R agent:agent "${agent_home}/.pi" log_info " -> 写入 .claude.json (hasCompletedOnboarding: true)" + log_info " -> 初始化 .pi/agent 目录" return 0 } diff --git a/back/src/main/resources/scripts/config.json b/back/src/main/resources/scripts/config.json index 1c48beb..8ad1b3d 100644 --- a/back/src/main/resources/scripts/config.json +++ b/back/src/main/resources/scripts/config.json @@ -1,4 +1,17 @@ { + "runtime": { + "provider": "pi" + }, + "pi_settings": { + "model": "openai/gpt-5.2-codex", + "language": "Chinese", + "env": { + "OPENAI_BASE_URL": "http://172.18.228.32:4000/v1", + "OPENAI_API_KEY": "sk-48sbQpOd2cCeXW1q3H4TBQ", + "LITELLM_BASE_URL": "http://172.18.228.32:4000", + "LITELLM_API_KEY": "sk-48sbQpOd2cCeXW1q3H4TBQ" + } + }, "claude_settings": { "model": "sonnet", "language": "Chinese", From 37554b4e2f74114803991ea71230d611b8c0f702 Mon Sep 17 00:00:00 2001 From: "li.zhiwen" Date: Mon, 23 Mar 2026 14:10:59 +0800 Subject: [PATCH 08/21] refactor(image-build): remove sdkRepoUrl/assetSourceImage and bundle build assets --- .../com/linkwork/config/ImageBuildConfig.java | 30 +- .../linkwork/service/ImageBuildService.java | 133 +-- back/src/main/resources/application-gg.yml | 1 - back/src/main/resources/application.yml | 7 +- .../main/resources/build-assets/manifest.txt | 26 + .../build-assets/sdk-source/pyproject.toml | 31 + .../src/linkwork_agent_sdk/__init__.py | 5 + .../src/linkwork_agent_sdk/config/__init__.py | 17 + .../src/linkwork_agent_sdk/config/loader.py | 72 ++ .../src/linkwork_agent_sdk/config/models.py | 55 + .../src/linkwork_agent_sdk/constants.py | 100 ++ .../src/linkwork_agent_sdk/engine/__init__.py | 5 + .../linkwork_agent_sdk/engine/agent_engine.py | 1042 +++++++++++++++++ .../src/linkwork_agent_sdk/exceptions.py | 67 ++ .../src/linkwork_agent_sdk/logger/__init__.py | 14 + .../src/linkwork_agent_sdk/logger/recorder.py | 157 +++ .../linkwork_agent_sdk/logger/transport.py | 165 +++ .../src/linkwork_agent_sdk/mcp/__init__.py | 5 + .../src/linkwork_agent_sdk/mcp/provider.py | 194 +++ .../src/linkwork_agent_sdk/redis.py | 135 +++ .../linkwork_agent_sdk/security/__init__.py | 10 + .../linkwork_agent_sdk/security/enforcer.py | 193 +++ .../src/linkwork_agent_sdk/skills/__init__.py | 5 + .../src/linkwork_agent_sdk/skills/provider.py | 169 +++ .../build-assets/start-scripts/ai_employee.py | 123 ++ .../build-assets/start-scripts/start-dual.sh | 398 +++++++ .../start-scripts/start-single.sh | 305 +++++ .../build-assets/zzd-binaries/encrypt-key | Bin 0 -> 2807760 bytes .../build-assets/zzd-binaries/gen-key | Bin 0 -> 2533982 bytes .../resources/build-assets/zzd-binaries/zz | Bin 0 -> 4089005 bytes .../resources/build-assets/zzd-binaries/zzd | Bin 0 -> 13416918 bytes 31 files changed, 3358 insertions(+), 106 deletions(-) create mode 100644 back/src/main/resources/build-assets/manifest.txt create mode 100644 back/src/main/resources/build-assets/sdk-source/pyproject.toml create mode 100644 back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/__init__.py create mode 100644 back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/config/__init__.py create mode 100644 back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/config/loader.py create mode 100644 back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/config/models.py create mode 100644 back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/constants.py create mode 100644 back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/engine/__init__.py create mode 100644 back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/engine/agent_engine.py create mode 100644 back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/exceptions.py create mode 100644 back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/logger/__init__.py create mode 100644 back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/logger/recorder.py create mode 100644 back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/logger/transport.py create mode 100644 back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/mcp/__init__.py create mode 100644 back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/mcp/provider.py create mode 100644 back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/redis.py create mode 100644 back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/security/__init__.py create mode 100644 back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/security/enforcer.py create mode 100644 back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/skills/__init__.py create mode 100644 back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/skills/provider.py create mode 100644 back/src/main/resources/build-assets/start-scripts/ai_employee.py create mode 100755 back/src/main/resources/build-assets/start-scripts/start-dual.sh create mode 100755 back/src/main/resources/build-assets/start-scripts/start-single.sh create mode 100755 back/src/main/resources/build-assets/zzd-binaries/encrypt-key create mode 100755 back/src/main/resources/build-assets/zzd-binaries/gen-key create mode 100755 back/src/main/resources/build-assets/zzd-binaries/zz create mode 100755 back/src/main/resources/build-assets/zzd-binaries/zzd diff --git a/back/src/main/java/com/linkwork/config/ImageBuildConfig.java b/back/src/main/java/com/linkwork/config/ImageBuildConfig.java index 754b0b9..148f548 100644 --- a/back/src/main/java/com/linkwork/config/ImageBuildConfig.java +++ b/back/src/main/java/com/linkwork/config/ImageBuildConfig.java @@ -128,41 +128,15 @@ public class ImageBuildConfig { */ private boolean kindPruneEnabled = true; - /** - * SDK 仓库 HTTPS 地址(用于镜像构建阶段克隆) - */ - private String sdkRepoUrl = ""; - - /** - * 构建资产来源镜像(当 sdkRepoUrl 未配置时使用) - * 用于从已有镜像中复制 zzd/start-scripts/linkwork_agent_sdk - */ - private String assetSourceImage = ""; - - /** - * SDK 仓库克隆分支(默认 test122) - */ - private String sdkRepoBranch = "test122"; - - /** - * SDK 仓库克隆用户名 - */ - private String sdkRepoUsername = ""; - - /** - * SDK 仓库克隆密码 - */ - private String sdkRepoPassword = ""; - /** * SDK 源码在镜像中的目标路径 - * 从 SDK 仓库的 linkwork-agent-sdk/ 目录拷贝 + * 从项目内置 build-assets/sdk-source/ 目录拷贝 */ private String sdkSourcePath = "/opt/linkwork-agent-build/sdk-source"; /** * zzd 二进制文件在镜像中的目标路径 - * 从 SDK 仓库的 docker/agent/zzd/ 目录拷贝 + * 从项目内置 build-assets/zzd-binaries/ 目录拷贝 */ private String zzdBinariesPath = "/opt/linkwork-agent-build/zzd-binaries"; } diff --git a/back/src/main/java/com/linkwork/service/ImageBuildService.java b/back/src/main/java/com/linkwork/service/ImageBuildService.java index 2159fff..38afd60 100644 --- a/back/src/main/java/com/linkwork/service/ImageBuildService.java +++ b/back/src/main/java/com/linkwork/service/ImageBuildService.java @@ -35,12 +35,16 @@ import org.springframework.util.StringUtils; import java.io.ByteArrayOutputStream; +import java.io.BufferedReader; import java.io.File; import java.nio.file.DirectoryStream; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.time.Duration; import java.time.Instant; import java.time.OffsetDateTime; @@ -592,7 +596,7 @@ private Path createBuildContext(String serviceId, String baseImage, Path.of(config.getBuildContextDir()), String.format("build-%s-", serviceId) ); - + // 生成 Dockerfile String dockerfile = generateDockerfile(baseImage, envVars); Files.writeString(contextDir.resolve("Dockerfile"), dockerfile); @@ -660,9 +664,57 @@ private Path createBuildContext(String serviceId, String baseImage, log.warn("Failed to load default config.json: {}", e.getMessage()); } + int copied = copyBundledBuildAssets(contextDir); + if (copied <= 0) { + throw new IOException("Bundled build-assets are required but missing or empty"); + } + log.debug("Bundled build assets copied into context: {} files", copied); + log.debug("Build context created at: {}", contextDir); return contextDir; } + + private boolean hasBundledBuildAssets() { + return new ClassPathResource("build-assets/manifest.txt").exists(); + } + + private int copyBundledBuildAssets(Path contextDir) throws IOException { + ClassPathResource manifestResource = new ClassPathResource("build-assets/manifest.txt"); + if (!manifestResource.exists()) { + return 0; + } + + Path assetsRoot = contextDir.resolve("build-assets").toAbsolutePath().normalize(); + int copied = 0; + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(manifestResource.getInputStream(), StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + String relative = line.trim(); + if (relative.isEmpty() || relative.startsWith("#")) { + continue; + } + ClassPathResource resource = new ClassPathResource("build-assets/" + relative); + if (!resource.exists()) { + log.warn("Bundled build asset listed but missing: {}", relative); + continue; + } + + Path target = assetsRoot.resolve(relative).normalize(); + if (!target.startsWith(assetsRoot)) { + throw new IOException("Illegal bundled build asset path: " + relative); + } + if (target.getParent() != null) { + Files.createDirectories(target.getParent()); + } + try (InputStream inputStream = resource.getInputStream()) { + Files.copy(inputStream, target, StandardCopyOption.REPLACE_EXISTING); + } + copied++; + } + } + return copied; + } /** * 生成 Dockerfile 内容 @@ -670,10 +722,6 @@ private Path createBuildContext(String serviceId, String baseImage, */ String generateDockerfile(String baseImage, Map envVars) { StringBuilder sb = new StringBuilder(); - String sdkRepoUrl = config.getSdkRepoUrl(); - String assetSourceImage = config.getAssetSourceImage(); - boolean hasSdkRepo = StringUtils.hasText(sdkRepoUrl); - boolean hasAssetSourceImage = !hasSdkRepo && StringUtils.hasText(assetSourceImage); String sdkSourcePath = StringUtils.hasText(config.getSdkSourcePath()) ? config.getSdkSourcePath().trim() @@ -688,9 +736,6 @@ String generateDockerfile(String baseImage, Map envVars) { // FROM 指令 sb.append("# Auto-generated Dockerfile\n"); - if (hasAssetSourceImage) { - sb.append("FROM ").append(assetSourceImage.trim()).append(" AS linkwork_assets\n\n"); - } sb.append("FROM ").append(baseImage).append("\n\n"); // ENV 指令(注入环境变量) @@ -720,70 +765,16 @@ String generateDockerfile(String baseImage, Map envVars) { sb.append("\n"); } - // Clone SDK repo and prepare sdk-source + zzd-binaries (before build.sh) - if (hasSdkRepo) { - // 使用 GIT_TOKEN 环境变量认证(已在 ENV 指令中注入),不再用硬编码的 sdkRepoUsername/Password 合并分支暂时注释,这是init时拉取sdk 仓库的节点过程,该节点是不需要用户的业务级仓库token的 -// String cloneCmd = sdkRepoUrl.replace("https://", "https://oauth2:${GIT_TOKEN}@"); - - // Build authenticated clone URL: https://user:pass@host/path.git - String cloneUrl = sdkRepoUrl; - if (StringUtils.hasText(config.getSdkRepoUsername()) - && StringUtils.hasText(config.getSdkRepoPassword())) { - cloneUrl = sdkRepoUrl.replace("https://", - "https://" + config.getSdkRepoUsername() + ":" + config.getSdkRepoPassword() + "@"); - } - - sb.append("# Cache-bust: force re-clone on every build to pick up latest binaries/SDK\n"); - sb.append("ARG CACHEBUST=").append(System.currentTimeMillis()).append("\n"); - sb.append("# Clone repo and organize into BUILD_ASSETS_ROOT for build.sh (production v2)\n"); - String branch = StringUtils.hasText(config.getSdkRepoBranch()) ? config.getSdkRepoBranch() : "test122"; - sb.append("RUN set -e \\\n"); - sb.append(" && git clone --depth 1 --single-branch -b ").append(branch).append(" ").append(cloneUrl).append(" /tmp/_sdk_repo \\\n"); - // BUILD_ASSETS_ROOT directory structure (build.sh expects these sub-directories) - sb.append(" && mkdir -p ").append(zzdBinariesPath).append(" \\\n"); - sb.append(" && mkdir -p ").append(sdkSourcePath).append(" \\\n"); - sb.append(" && mkdir -p ").append(startScriptsPath).append(" \\\n"); - // zzd binaries - sb.append(" && for bin in zzd zz gen-key encrypt-key; do \\\n"); - sb.append(" cp /tmp/_sdk_repo/docker/agent/zzd/$bin ").append(zzdBinariesPath).append("/; \\\n"); - sb.append(" done \\\n"); - // SDK source (linkwork-agent-sdk, fallback agent-sdk for backward compatibility) - sb.append(" && if [ -d /tmp/_sdk_repo/linkwork-agent-sdk ]; then \\\n"); - sb.append(" cp -a /tmp/_sdk_repo/linkwork-agent-sdk/. ").append(sdkSourcePath).append("/; \\\n"); - sb.append(" elif [ -d /tmp/_sdk_repo/agent-sdk ]; then \\\n"); - sb.append(" cp -a /tmp/_sdk_repo/agent-sdk/. ").append(sdkSourcePath).append("/; \\\n"); - sb.append(" else \\\n"); - sb.append(" echo 'SDK source directory not found in repo'; exit 1; \\\n"); - sb.append(" fi \\\n"); - // start scripts - sb.append(" && cp /tmp/_sdk_repo/docker/agent/start-single.sh ").append(startScriptsPath).append("/ \\\n"); - sb.append(" && cp /tmp/_sdk_repo/docker/agent/start-dual.sh ").append(startScriptsPath).append("/ \\\n"); - sb.append(" && cp /tmp/_sdk_repo/docker/agent/ai_employee.py ").append(startScriptsPath).append("/ \\\n"); - // cleanup - sb.append(" && rm -rf /tmp/_sdk_repo\n\n"); - - // Cedar 策略文件 → /tmp/cedar-policies/ (build.sh download_cedar_policies 会从这里读取) - sb.append("# Cedar policy files for build.sh to deploy\n"); - sb.append("COPY cedar-policies/ /tmp/cedar-policies/\n\n"); - } else if (hasAssetSourceImage) { - sb.append("# Fallback assets copied from local image\n"); - sb.append("RUN mkdir -p ").append(zzdBinariesPath).append(" \\\n"); - sb.append(" && mkdir -p ").append(sdkSourcePath).append(" \\\n"); - sb.append(" && mkdir -p ").append(startScriptsPath).append("\n"); - sb.append("COPY --from=linkwork_assets /usr/local/bin/zzd ").append(zzdBinariesPath).append("/zzd\n"); - sb.append("COPY --from=linkwork_assets /usr/local/bin/zz ").append(zzdBinariesPath).append("/zz\n"); - sb.append("COPY --from=linkwork_assets /usr/local/bin/gen-key ").append(zzdBinariesPath).append("/gen-key\n"); - sb.append("COPY --from=linkwork_assets /usr/local/bin/encrypt-key ").append(zzdBinariesPath).append("/encrypt-key\n"); - sb.append("COPY --from=linkwork_assets /opt/agent/start-single.sh ").append(startScriptsPath).append("/start-single.sh\n"); - sb.append("COPY --from=linkwork_assets /opt/agent/start-dual.sh ").append(startScriptsPath).append("/start-dual.sh\n"); - sb.append("COPY --from=linkwork_assets /opt/agent/ai_employee.py ").append(startScriptsPath).append("/ai_employee.py\n"); - sb.append("COPY --from=linkwork_assets /usr/local/lib/python3.12/site-packages/linkwork_agent_sdk /usr/local/lib/python3.12/site-packages/linkwork_agent_sdk\n"); - sb.append("COPY --from=linkwork_assets /usr/local/lib/python3.12/site-packages/linkwork_agent_sdk-*.dist-info /usr/local/lib/python3.12/site-packages/\n\n"); + // Bundled assets from project resources + sb.append("# Bundled build assets from project resources\n"); + sb.append("RUN mkdir -p ").append(zzdBinariesPath).append(" \\\n"); + sb.append(" && mkdir -p ").append(sdkSourcePath).append(" \\\n"); + sb.append(" && mkdir -p ").append(startScriptsPath).append("\n"); + sb.append("COPY build-assets/ ").append(buildAssetsRoot).append("/\n\n"); - // Cedar 策略文件 → /tmp/cedar-policies/ (build.sh download_cedar_policies 会从这里读取) - sb.append("# Cedar policy files for build.sh to deploy\n"); - sb.append("COPY cedar-policies/ /tmp/cedar-policies/\n\n"); - } + // Cedar 策略文件 → /tmp/cedar-policies/ (build.sh download_cedar_policies 会从这里读取) + sb.append("# Cedar policy files for build.sh to deploy\n"); + sb.append("COPY cedar-policies/ /tmp/cedar-policies/\n\n"); // 默认 config.json → /opt/agent/config.json(build.sh finalize_permissions 会设置权限) sb.append("# Default agent config (overridden at runtime by ConfigMap mount)\n"); diff --git a/back/src/main/resources/application-gg.yml b/back/src/main/resources/application-gg.yml index f7dafcf..271e571 100644 --- a/back/src/main/resources/application-gg.yml +++ b/back/src/main/resources/application-gg.yml @@ -7,7 +7,6 @@ image-build: enabled: ${IMAGE_BUILD_ENABLED:true} push-enabled: ${IMAGE_PUSH_ENABLED:false} default-agent-base-image: ${DEFAULT_AGENT_BASE_IMAGE:10.30.107.146/robot/rockylinux9-agent:v1.3} - asset-source-image: ${IMAGE_BUILD_ASSET_SOURCE_IMAGE:service-3-agent:3-20260319104738} registry: ${IMAGE_REGISTRY:} registry-username: ${IMAGE_REGISTRY_USERNAME:} registry-password: ${IMAGE_REGISTRY_PASSWORD:} diff --git a/back/src/main/resources/application.yml b/back/src/main/resources/application.yml index f2ab303..3aecd76 100644 --- a/back/src/main/resources/application.yml +++ b/back/src/main/resources/application.yml @@ -265,12 +265,7 @@ image-build: local-cleanup-cron: ${IMAGE_LOCAL_CLEANUP_CRON:0 40 * * * *} kind-prune-enabled: ${IMAGE_KIND_PRUNE_ENABLED:true} - # SDK 仓库配置(镜像构建阶段克隆,拷贝 linkwork-agent-sdk 和 docker/agent/zzd 到镜像中) - sdk-repo-url: ${SDK_REPO_URL:} - asset-source-image: ${IMAGE_BUILD_ASSET_SOURCE_IMAGE:} - sdk-repo-branch: ${SDK_REPO_BRANCH:} - sdk-repo-username: ${SDK_REPO_USERNAME:} - sdk-repo-password: ${SDK_REPO_PASSWORD:} + # 构建资产路径(来自项目内置 build-assets) sdk-source-path: ${SDK_SOURCE_PATH:/opt/linkwork-agent-build/sdk-source} zzd-binaries-path: ${ZZD_BINARIES_PATH:/opt/linkwork-agent-build/zzd-binaries} diff --git a/back/src/main/resources/build-assets/manifest.txt b/back/src/main/resources/build-assets/manifest.txt new file mode 100644 index 0000000..3b795cb --- /dev/null +++ b/back/src/main/resources/build-assets/manifest.txt @@ -0,0 +1,26 @@ +sdk-source/pyproject.toml +sdk-source/src/linkwork_agent_sdk/__init__.py +sdk-source/src/linkwork_agent_sdk/config/__init__.py +sdk-source/src/linkwork_agent_sdk/config/loader.py +sdk-source/src/linkwork_agent_sdk/config/models.py +sdk-source/src/linkwork_agent_sdk/constants.py +sdk-source/src/linkwork_agent_sdk/engine/__init__.py +sdk-source/src/linkwork_agent_sdk/engine/agent_engine.py +sdk-source/src/linkwork_agent_sdk/exceptions.py +sdk-source/src/linkwork_agent_sdk/logger/__init__.py +sdk-source/src/linkwork_agent_sdk/logger/recorder.py +sdk-source/src/linkwork_agent_sdk/logger/transport.py +sdk-source/src/linkwork_agent_sdk/mcp/__init__.py +sdk-source/src/linkwork_agent_sdk/mcp/provider.py +sdk-source/src/linkwork_agent_sdk/redis.py +sdk-source/src/linkwork_agent_sdk/security/__init__.py +sdk-source/src/linkwork_agent_sdk/security/enforcer.py +sdk-source/src/linkwork_agent_sdk/skills/__init__.py +sdk-source/src/linkwork_agent_sdk/skills/provider.py +start-scripts/ai_employee.py +start-scripts/start-dual.sh +start-scripts/start-single.sh +zzd-binaries/encrypt-key +zzd-binaries/gen-key +zzd-binaries/zz +zzd-binaries/zzd diff --git a/back/src/main/resources/build-assets/sdk-source/pyproject.toml b/back/src/main/resources/build-assets/sdk-source/pyproject.toml new file mode 100644 index 0000000..5f53d63 --- /dev/null +++ b/back/src/main/resources/build-assets/sdk-source/pyproject.toml @@ -0,0 +1,31 @@ +[project] +name = "linkwork-agent-sdk" +version = "0.1.0" +description = "LinkWork Agent SDK" +readme = "README.md" +requires-python = ">=3.11" +dependencies = [ + "pydantic>=2.0", + "redis>=5.0", + "PyYAML>=6.0", + "claude-agent-sdk>=0.1.21", +] + +[project.optional-dependencies] +dev = [ + "pytest>=8.0", + "pytest-asyncio>=0.23", +] + +[build-system] +requires = ["setuptools>=68", "wheel"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.pytest.ini_options] +asyncio_mode = "auto" +testpaths = ["tests"] +python_files = ["test_*.py"] + diff --git a/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/__init__.py b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/__init__.py new file mode 100644 index 0000000..8f41d32 --- /dev/null +++ b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/__init__.py @@ -0,0 +1,5 @@ +"""LinkWork Agent SDK package.""" + +from .engine.agent_engine import AgentEngine + +__all__ = ["AgentEngine"] diff --git a/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/config/__init__.py b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/config/__init__.py new file mode 100644 index 0000000..5d5606b --- /dev/null +++ b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/config/__init__.py @@ -0,0 +1,17 @@ +"""Config package.""" + +from .loader import ConfigLoader +from .models import ( + AgentConfig, + ClaudeSettingsConfig, + LinkWorkAgentSDKConfig, + SystemPromptConfig, +) + +__all__ = [ + "AgentConfig", + "ClaudeSettingsConfig", + "ConfigLoader", + "LinkWorkAgentSDKConfig", + "SystemPromptConfig", +] diff --git a/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/config/loader.py b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/config/loader.py new file mode 100644 index 0000000..9c3be72 --- /dev/null +++ b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/config/loader.py @@ -0,0 +1,72 @@ +"""Config loader for local JSON file.""" + +from __future__ import annotations + +import json +from pathlib import Path + +from pydantic import ValidationError + +from ..exceptions import ( + ConfigNotFoundError, + ConfigNullError, + ConfigParseError, + ConfigPermissionError, + ConfigValidationError, +) +from .models import LinkWorkAgentSDKConfig + + +class ConfigLoader: + """Load and validate SDK config from local JSON file.""" + + def __init__(self, config_file: str | Path) -> None: + self._config_file = Path(config_file) + self._config: LinkWorkAgentSDKConfig | None = None + + @property + def config_file(self) -> Path: + return self._config_file + + @property + def config(self) -> LinkWorkAgentSDKConfig: + if self._config is None: + raise ConfigValidationError("Config not loaded") + return self._config + + def load(self) -> LinkWorkAgentSDKConfig: + """Load JSON config and validate with Pydantic.""" + if not self._config_file.exists(): + raise ConfigNotFoundError(f"Config file not found: {self._config_file}") + if not self._config_file.is_file(): + raise ConfigNotFoundError(f"Config path is not file: {self._config_file}") + + try: + content = self._config_file.read_text(encoding="utf-8-sig") + except PermissionError as error: + raise ConfigPermissionError( + f"Config file permission denied: {self._config_file}", + ) from error + except OSError as error: + raise ConfigParseError(f"Config file read failed: {error}") from error + + if not content.strip(): + raise ConfigNullError(f"Config file is empty: {self._config_file}") + + try: + raw_config = json.loads(content) + except json.JSONDecodeError as error: + raise ConfigParseError( + f"Config JSON parse failed at line {error.lineno}, col {error.colno}: {error.msg}", + ) from error + + try: + self._config = LinkWorkAgentSDKConfig.model_validate(raw_config) + except ValidationError as error: + details = "; ".join( + f"{'.'.join(str(part) for part in item['loc'])}: {item['msg']}" + for item in error.errors() + ) + raise ConfigValidationError(f"Config validation failed: {details}") from error + + return self._config diff --git a/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/config/models.py b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/config/models.py new file mode 100644 index 0000000..b8d43e9 --- /dev/null +++ b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/config/models.py @@ -0,0 +1,55 @@ +"""Config models for LinkWork Agent SDK.""" + +from __future__ import annotations + +from typing import Literal + +from pydantic import BaseModel, ConfigDict, Field, StringConstraints +from typing_extensions import Annotated + +WorkerName = Annotated[str, StringConstraints(pattern=r"^[a-z0-9-]+$")] + + +class ClaudeSettingsConfig(BaseModel): + """Claude runtime settings.""" + + model_config = ConfigDict(extra="forbid", strict=True) + + env: dict[str, str] = Field(default_factory=dict) + model: str = "openrouter/anthropic/claude-sonnet-4.5" + language: str = "Chinese" + + +class AgentConfig(BaseModel): + """Agent behavior settings.""" + + model_config = ConfigDict(extra="forbid", strict=True) + + name: WorkerName = "demo-worker" + max_turns: int = Field(default=100, ge=1) + max_thinking_tokens: int = Field(default=10_000, ge=0) + permission_mode: Literal["default", "acceptEdits", "bypassPermissions"] = "default" + allowed_tools: list[str] = Field(default_factory=list) + disallowed_tools: list[str] = Field(default_factory=list) + can_use_tools: list[str] = Field(default_factory=list) + zz_enabled: bool = Field(default=False, description="启用 zz 安全执行代理(Bash 命令经 zzd 安全审计)") + + +class SystemPromptConfig(BaseModel): + """System prompt settings.""" + + model_config = ConfigDict(extra="forbid", strict=True) + + use_preset: bool = True + preset: Literal["claude_code"] = "claude_code" + append: str = "" + + +class LinkWorkAgentSDKConfig(BaseModel): + """Top-level SDK config.""" + + model_config = ConfigDict(extra="forbid", strict=True) + + claude_settings: ClaudeSettingsConfig = Field(default_factory=ClaudeSettingsConfig) + agent: AgentConfig = Field(default_factory=AgentConfig) + system_prompt: SystemPromptConfig = Field(default_factory=SystemPromptConfig) diff --git a/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/constants.py b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/constants.py new file mode 100644 index 0000000..decd2e5 --- /dev/null +++ b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/constants.py @@ -0,0 +1,100 @@ +"""LinkWork Agent SDK constants.""" + +from __future__ import annotations + +import os + +SKILLS_DIR = "/opt/agent/skills/" +MCP_CONFIG_FILE = "/opt/agent/mcp.json" +SECURITY_RULES_FILE = "/opt/agent/security.json" +LOG_FALLBACK_DIR = "/workspace/task-logs/" +WORKER_LOG_FALLBACK_DIR = "/workspace/worker-logs/" + +REDIS_URL_DEFAULT = "redis://redis:6379" +BLPOP_TIMEOUT_SECONDS = 5 +IDLE_TIMEOUT_SECONDS = 600 +TASK_RUNTIME_IDLE_TIMEOUT_SECONDS = 600 +LOG_RETENTION_DAYS = 7 +SECURITY_CHECK_TIMEOUT_SECONDS = 2 + +TASK_QUEUE_KEY_TEMPLATE = "workstation:{workstation_id}:tasks" +CONTROL_QUEUE_KEY_TEMPLATE = "workstation:{workstation_id}:control" +LOG_STREAM_KEY_TEMPLATE = "logs:{workstation_id}:{task_id}" + +WORKSPACE_LOGS_ROOT = "/workspace/logs" +WORKSPACE_USER_ROOT = "/workspace/user" +WORKSPACE_WORKSTATION_ROOT = "/workspace/workstation" +OSS_INPUT_PATH_TEMPLATE = "tasks/{task_id}/input" +OSS_OUTPUT_REPORT_PATH_TEMPLATE = "logs/{user_id}/{task_id}" + +WORKSPACE_LOGS_PATH = "/workspace/logs" +DOC_ROOT_PATH = "/workspace" +DOC_USER_PATH = "/workspace/user" +DOC_JOB_PATH = "/workspace/workstation" + +ZZ_ACTION_FS_PREPARE = "fs_prepare" +ZZ_ACTION_FS_CLEANUP = "fs_cleanup" + +ENV_WORKSTATION_ID = "WORKSTATION_ID" +ENV_TASK_ID = "TASK_ID" +ENV_USER_ID = "USER_ID" +ENV_REDIS_URL = "REDIS_URL" +ENV_IDLE_TIMEOUT = "IDLE_TIMEOUT" +ENV_TASK_RUNTIME_IDLE_TIMEOUT = "TASK_RUNTIME_IDLE_TIMEOUT" +ENV_WORKER_DESTROY_API_BASE = "WORKER_DESTROY_API_BASE" +ENV_WORKER_DESTROY_API_PASSWORD = "WORKER_DESTROY_API_PASSWORD" +ENV_POD_NAME = "POD_NAME" +ENV_SERVICE_ID = "SERVICE_ID" +ENV_OSS_MOUNT_REQUIRED = "OSS_MOUNT_REQUIRED" + + +def get_redis_url() -> str: + """Get Redis URL from env with default fallback.""" + value = os.getenv(ENV_REDIS_URL, "").strip() + return value or REDIS_URL_DEFAULT + + +def get_idle_timeout_seconds() -> int: + """Get idle timeout seconds from env with safe default fallback.""" + raw = os.getenv(ENV_IDLE_TIMEOUT, "").strip() + if not raw: + return IDLE_TIMEOUT_SECONDS + try: + parsed = int(raw) + except ValueError: + return IDLE_TIMEOUT_SECONDS + if parsed <= 0: + return IDLE_TIMEOUT_SECONDS + return parsed + + +def get_task_runtime_idle_timeout_seconds() -> int: + """Get per-task runtime idle timeout from env with safe default fallback.""" + raw = os.getenv(ENV_TASK_RUNTIME_IDLE_TIMEOUT, "").strip() + if not raw: + return TASK_RUNTIME_IDLE_TIMEOUT_SECONDS + try: + parsed = int(raw) + except ValueError: + return TASK_RUNTIME_IDLE_TIMEOUT_SECONDS + if parsed <= 0: + return TASK_RUNTIME_IDLE_TIMEOUT_SECONDS + return parsed + + +def build_task_queue_key(workstation_id: str) -> str: + """Build Redis task queue key.""" + return TASK_QUEUE_KEY_TEMPLATE.format(workstation_id=workstation_id) + + +def build_control_queue_key(workstation_id: str) -> str: + """Build Redis control queue key.""" + return CONTROL_QUEUE_KEY_TEMPLATE.format(workstation_id=workstation_id) + + +def build_log_stream_key(workstation_id: str, task_id: str) -> str: + """Build Redis log stream key.""" + return LOG_STREAM_KEY_TEMPLATE.format( + workstation_id=workstation_id, + task_id=task_id, + ) diff --git a/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/engine/__init__.py b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/engine/__init__.py new file mode 100644 index 0000000..71af68f --- /dev/null +++ b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/engine/__init__.py @@ -0,0 +1,5 @@ +"""Engine package.""" + +from .agent_engine import AgentEngine + +__all__ = ["AgentEngine"] diff --git a/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/engine/agent_engine.py b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/engine/agent_engine.py new file mode 100644 index 0000000..d978b88 --- /dev/null +++ b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/engine/agent_engine.py @@ -0,0 +1,1042 @@ +"""Agent engine orchestration for one task session.""" + +from __future__ import annotations + +import asyncio +import base64 +import inspect +import json +import logging +import os +import shlex +import shutil +import traceback +import uuid +from collections.abc import AsyncGenerator +from pathlib import Path +from typing import Any + +from ..config import ConfigLoader, LinkWorkAgentSDKConfig +from ..constants import ( + ENV_TASK_ID, + ENV_WORKSTATION_ID, + LOG_FALLBACK_DIR, + SECURITY_CHECK_TIMEOUT_SECONDS, +) +from ..exceptions import ConcurrentExecutionError, RuntimeInitError, RuntimeProtocolError +from ..logger import ( + CompositeTransport, + FileLocalFallback, + LogEventType, + LogRecorder, + RedisStreamTransport, +) +from ..mcp import MCPProvider +from ..redis import RedisClient +from ..security import SecurityAction, SecurityDecision, SecurityEnforcer +from ..skills import Skill, SkillsProvider + +_logger = logging.getLogger(__name__) + + +class AgentEngine: + """Single-task orchestration engine with strict initialization order.""" + + def __init__( + self, + config_file: str | Path, + task_id: str | None = None, + workstation_id: str | None = None, + cwd: str | Path | None = None, + redis_client: RedisClient | None = None, + session_id: str | None = None, + runtime_system_prompt_append: str | None = None, + runtime_model_override: str | None = None, + ) -> None: + self._config_file = Path(config_file) + self._task_id = task_id or os.getenv(ENV_TASK_ID) or f"task-{uuid.uuid4().hex[:8]}" + self._workstation_id = workstation_id or os.getenv(ENV_WORKSTATION_ID) or "unknown" + self._cwd = Path(cwd).resolve() if cwd is not None else None + + self._config_loader = ConfigLoader(self._config_file) + self._config: LinkWorkAgentSDKConfig | None = None + + self._redis_client = redis_client + self._own_redis_client = redis_client is None + self._logger: LogRecorder | None = None + self._security = SecurityEnforcer() + self._skills = SkillsProvider() + self._mcp = MCPProvider() + + self._session_id = session_id or f"session-{uuid.uuid4().hex[:10]}" + self._runtime_client: Any = None + self._runtime_entered = False + self._runtime_symbols: dict[str, Any] = {} + self._runtime_system_prompt_append = ( + runtime_system_prompt_append.strip() if runtime_system_prompt_append else "" + ) + self._runtime_model_override = ( + runtime_model_override.strip() if runtime_model_override else "" + ) + + self._running_lock = asyncio.Lock() + self._entered = False + self._zz_enabled = False + self._selected_skills_for_run: dict[str, Skill] = {} + self._referenced_skills_for_run: set[str] = set() + self._referenced_skill_reads_for_run: set[tuple[str, str]] = set() + self._referenced_skill_commands_for_run: set[str] = set() + self._runtime_skills_dir: Path | None = None + + @property + def task_id(self) -> str: + return self._task_id + + @property + def workstation_id(self) -> str: + return self._workstation_id + + @property + def session_id(self) -> str: + return self._session_id + + @property + def cwd(self) -> Path: + if self._cwd is not None: + return self._cwd + return Path.cwd() + + async def __aenter__(self) -> "AgentEngine": + if self._entered: + return self + + try: + # 1) Config + self._config = self._config_loader.load() + + # 2) Logger + if self._redis_client is None: + self._redis_client = RedisClient() + if self._own_redis_client: + await self._redis_client.connect() + + primary_transport = RedisStreamTransport( + redis_client=self._redis_client, + workstation_id=self._workstation_id, + task_id=self._task_id, + ) + fallback_transport = FileLocalFallback( + session_id=self._session_id, + output_dir=LOG_FALLBACK_DIR, + ) + transport = CompositeTransport(primary=primary_transport, fallback=fallback_transport) + self._logger = LogRecorder( + output_dir=LOG_FALLBACK_DIR, + session_id=self._session_id, + transport=transport, + ) + await self._logger.start() + await self._logger.record( + LogEventType.CONFIG_LOADED, + {"config_path": str(self._config_file)}, + ) + + # 3) Security + self._security.load() + self._security.set_can_use_tools(self._config.agent.can_use_tools) + await self._logger.record( + LogEventType.SECURITY_LOADED, + {"rules_count": self._security.rule_count}, + ) + + # 3.1) zz_enabled validation + self._zz_enabled = self._config.agent.zz_enabled + if self._zz_enabled: + zz_path = shutil.which("zz") + if zz_path is None: + from ..exceptions import SecurityLoadError + raise SecurityLoadError("zz_enabled=true but 'zz' binary not found in PATH") + _logger.info("zz security proxy enabled: %s", zz_path) + + # 4) Skills + skills = self._skills.load() + self._runtime_skills_dir = self._skills.sync_to_claude_project_dir(self.cwd) + await self._logger.record( + LogEventType.SKILLS_LOADED, + { + "skills_count": len(skills), + "skills_names": self._skills.get_skill_names(), + "integration_mode": "setting_sources_project", + "runtime_skills_dir": ( + str(self._runtime_skills_dir) if self._runtime_skills_dir else None + ), + }, + ) + + # 5) MCP + mcp_servers = self._mcp.load() + self._security.set_available_mcp_servers(set(mcp_servers.keys())) + await self._logger.record( + LogEventType.MCP_LOADED, + { + "servers_count": len(mcp_servers), + "servers": { + name: { + "type": cfg.type, + "url": cfg.url if cfg.url else None, + } + for name, cfg in mcp_servers.items() + }, + }, + ) + + # 6) Runtime + self._runtime_symbols = self._load_runtime_symbols() + self._runtime_client = self._build_runtime_client() + if hasattr(self._runtime_client, "__aenter__"): + self._runtime_client = await self._runtime_client.__aenter__() + self._runtime_entered = True + + self._entered = True + return self + except Exception: + await self._cleanup_on_enter_error() + raise + + async def _cleanup_on_enter_error(self) -> None: + if self._runtime_client is not None and self._runtime_entered and hasattr(self._runtime_client, "__aexit__"): + try: + await self._runtime_client.__aexit__(None, None, None) + except Exception: + _logger.warning("failed to close runtime client after __aenter__ error", exc_info=True) + + self._runtime_client = None + self._runtime_entered = False + + if self._logger is not None: + try: + await self._logger.stop() + except Exception: + _logger.warning("failed to stop logger after __aenter__ error", exc_info=True) + self._logger = None + + if self._redis_client is not None and self._own_redis_client: + try: + await self._redis_client.close() + except Exception: + _logger.warning("failed to close redis after __aenter__ error", exc_info=True) + self._redis_client = None + + self._config = None + self._zz_enabled = False + self._entered = False + self._runtime_skills_dir = None + + async def __aexit__(self, exc_type: object, exc: object, tb: object) -> None: + cleanup_error: Exception | None = None + + def capture_cleanup_error(err: Exception) -> None: + nonlocal cleanup_error + if cleanup_error is None: + cleanup_error = err + + if self._runtime_client is not None and self._runtime_entered: + try: + await self._runtime_client.__aexit__(exc_type, exc, tb) + except Exception as error: + _logger.warning("failed to close runtime client in __aexit__", exc_info=True) + capture_cleanup_error(error) + finally: + self._runtime_client = None + self._runtime_entered = False + + logger = self._logger + self._logger = None + if logger is not None: + if exc is not None and not isinstance(exc, asyncio.CancelledError): + try: + await logger.record( + LogEventType.ERROR, + { + "error_type": type(exc).__name__, + "error_message": str(exc), + "stack_trace": "".join(traceback.format_exception(exc)), + }, + ) + except Exception: + _logger.warning("failed to record error event in __aexit__", exc_info=True) + try: + await logger.stop() + except Exception as error: + _logger.warning("failed to stop logger in __aexit__", exc_info=True) + capture_cleanup_error(error) + + if self._redis_client is not None and self._own_redis_client: + try: + await self._redis_client.close() + except Exception as error: + _logger.warning("failed to close redis client in __aexit__", exc_info=True) + capture_cleanup_error(error) + finally: + self._redis_client = None + + self._config = None + self._zz_enabled = False + self._entered = False + self._runtime_skills_dir = None + + if exc is None and cleanup_error is not None: + raise cleanup_error + + async def run(self, task: str) -> AsyncGenerator[Any, None]: + """Run one task and yield streaming messages from Claude SDK client.""" + if self._runtime_client is None: + raise RuntimeInitError("Runtime is not initialized") + if self._running_lock.locked(): + raise ConcurrentExecutionError("Concurrent run() is not allowed") + + async with self._running_lock: + self._reset_required_skill_state() + try: + guarded_task = self._build_task_with_skill_guard(task) + await self._record_skill_selected_event() + await self._runtime_client.query(guarded_task) + async for message in self._runtime_client.receive_response(): + await self._record_runtime_message(message) + yield message + finally: + await self._record_skill_usage_summary_event() + self._reset_required_skill_state() + + def _load_runtime_symbols(self) -> dict[str, Any]: + try: + import claude_agent_sdk as sdk # type: ignore + except ImportError as error: + raise RuntimeInitError("claude-agent-sdk is required for runtime") from error + + symbols: dict[str, Any] = {"sdk": sdk} + for name in ( + "ClaudeSDKClient", + "ClaudeAgentOptions", + "HookMatcher", + "PermissionResultAllow", + "PermissionResultDeny", + ): + symbols[name] = getattr(sdk, name, None) + return symbols + + def _build_runtime_client(self) -> Any: + sdk = self._runtime_symbols["sdk"] + options = self._build_runtime_options() + + client_class = self._runtime_symbols.get("ClaudeSDKClient") + if client_class is not None: + return client_class(options=options) + + query_func = getattr(sdk, "query", None) + if query_func is not None: + return _QueryRuntimeClient(query_func=query_func, options=options) + + raise RuntimeInitError("No supported runtime client found in claude-agent-sdk") + + def _build_runtime_options(self) -> Any: + if self._config is None: + raise RuntimeInitError("Config must be loaded before options build") + + system_prompt = self._build_system_prompt(self._config) + resolved_env = _resolve_env_placeholders( + self._config.claude_settings.env, + workstation_id=self._workstation_id, + task_id=self._task_id, + ) + + hooks = self._build_hooks() + allowed_tools = self._build_allowed_tools(self._config.agent.allowed_tools) + runtime_model = self._runtime_model_override or self._config.claude_settings.model + options_dict: dict[str, Any] = { + "system_prompt": system_prompt, + "allowed_tools": allowed_tools, + "disallowed_tools": self._config.agent.disallowed_tools, + "permission_mode": self._config.agent.permission_mode, + "max_turns": self._config.agent.max_turns, + "max_thinking_tokens": self._config.agent.max_thinking_tokens, + "model": runtime_model, + "cwd": str(self.cwd), + "mcp_servers": self._mcp.get_mcp_servers_config(), + "hooks": hooks, + "can_use_tool": self._can_use_tool, + } + # Legacy plugins-based skill wiring is intentionally disabled. + # Reason: enforce Claude official skill path via setting_sources + Skill tool + # to avoid runtime "loaded but not recognized" semantic drift. + # options_dict["plugins"] = self._skills.get_plugins_config() + + options_class = self._runtime_symbols.get("ClaudeAgentOptions") + supported_option_keys = self._get_runtime_option_keys(options_class) + self._inject_setting_sources_option( + options_dict=options_dict, + supported_option_keys=supported_option_keys, + ) + if resolved_env: + options_dict["env"] = resolved_env + + if options_class is not None: + return options_class(**options_dict) + return options_dict + + def _build_system_prompt( + self, config: LinkWorkAgentSDKConfig, + ) -> dict[str, Any] | str: + """构建 system prompt。 + + 当 use_preset=True 时,使用 Claude Code 内建预设 + append 追加自定义指令, + 保留 CLI 内建的工作目录感知、文件路径规范等关键上下文。 + 当 use_preset=False 时,返回纯字符串自定义 prompt。 + """ + # 收集追加内容 + append_parts: list[str] = [] + + # Legacy prompt-based skill exposure is intentionally disabled. + # Official chain is setting_sources=["project"] with Skill tool. + # skills_summary = self._skills.get_skill_summary() + # if skills_summary: + # append_parts.append(skills_summary) + + language = config.claude_settings.language.strip() + if language: + append_parts.append( + f"Please respond in {language} unless the user explicitly asks for another language.", + ) + + user_append = config.system_prompt.append.strip() + if user_append: + append_parts.append(user_append) + + runtime_append = self._runtime_system_prompt_append.strip() + if runtime_append: + append_parts.append(runtime_append) + + append_text = "\n\n".join(p for p in append_parts if p) + + if config.system_prompt.use_preset: + # 使用官方 SystemPromptPreset 格式,保留 Claude Code 内建 prompt + preset: dict[str, Any] = { + "type": "preset", + "preset": config.system_prompt.preset, + } + if append_text: + preset["append"] = append_text + return preset + + # use_preset=False: 纯自定义 prompt + parts: list[str] = [] + if append_text: + parts.append(append_text) + return "\n\n".join(parts) if parts else "" + + def _build_allowed_tools(self, configured_tools: list[str]) -> list[str]: + if not configured_tools: + return configured_tools + + deduped: list[str] = [] + seen: set[str] = set() + for tool in configured_tools: + normalised = tool.strip() + if not normalised: + continue + lowered = normalised.casefold() + if lowered in seen: + continue + deduped.append(normalised) + seen.add(lowered) + + if "skill" not in seen: + deduped.append("Skill") + return deduped + + def _get_runtime_option_keys(self, options_class: Any) -> set[str] | None: + if options_class is None: + return None + + model_fields = getattr(options_class, "model_fields", None) + if isinstance(model_fields, dict) and model_fields: + return set(model_fields.keys()) + + dataclass_fields = getattr(options_class, "__dataclass_fields__", None) + if isinstance(dataclass_fields, dict) and dataclass_fields: + return set(dataclass_fields.keys()) + + try: + signature = inspect.signature(options_class) + except (TypeError, ValueError): + return None + + keys: set[str] = set() + for name, parameter in signature.parameters.items(): + if name == "self": + continue + if parameter.kind == inspect.Parameter.VAR_KEYWORD: + return None + if parameter.kind in ( + inspect.Parameter.POSITIONAL_OR_KEYWORD, + inspect.Parameter.KEYWORD_ONLY, + ): + keys.add(name) + return keys or None + + def _inject_setting_sources_option( + self, + options_dict: dict[str, Any], + supported_option_keys: set[str] | None, + ) -> None: + setting_sources = self._skills.get_setting_sources_config() + + if supported_option_keys is None: + options_dict["setting_sources"] = setting_sources + return + + if "setting_sources" in supported_option_keys: + options_dict["setting_sources"] = setting_sources + return + if "settingSources" in supported_option_keys: + options_dict["settingSources"] = setting_sources + return + + raise RuntimeInitError( + "claude-agent-sdk must support setting_sources/settingSources for standard skills integration", + ) + + def _build_hooks(self) -> dict[str, list[Any]]: + hook_matcher = self._runtime_symbols.get("HookMatcher") + if hook_matcher is None: + return { + "PreToolUse": [self._pre_tool_use], + "PostToolUse": [self._post_tool_use], + "PostToolUseFailure": [self._post_tool_use_failure], + } + + return { + "PreToolUse": [hook_matcher(hooks=[self._pre_tool_use])], + "PostToolUse": [hook_matcher(hooks=[self._post_tool_use])], + "PostToolUseFailure": [hook_matcher(hooks=[self._post_tool_use_failure])], + } + + def _reset_required_skill_state(self) -> None: + self._selected_skills_for_run = {} + self._referenced_skills_for_run = set() + self._referenced_skill_reads_for_run = set() + self._referenced_skill_commands_for_run = set() + + def _resolve_skill_by_name(self, requested_name: str) -> Skill | None: + skill = self._skills.get_skill(requested_name) + if skill is not None: + return skill + lowered = requested_name.casefold() + for known_name in self._skills.get_skill_names(): + if known_name.casefold() == lowered: + return self._skills.get_skill(known_name) + return None + + def _build_task_with_skill_guard(self, task: str) -> str: + # 强绑定 guard 已下线,回归 Claude 原生 Skill 生态。 + # 仅保留观测事件,不再改写任务文本。 + self._selected_skills_for_run = {} + return task + + def _resolve_tool_path(self, tool_input: dict[str, Any]) -> Path | None: + raw_path = str( + tool_input.get("file_path") + or tool_input.get("path") + or "", + ).strip() + if not raw_path: + return None + + path = Path(raw_path) + if not path.is_absolute(): + path = self.cwd / path + return path.resolve(strict=False) + + def _resolve_skill_from_path(self, path: Path) -> Skill | None: + for skill in self._skills.get_skills(): + skill_file = skill.path.resolve(strict=False) + source_dir = skill_file.parent + runtime_dir = ( + (self._runtime_skills_dir / source_dir.name).resolve(strict=False) + if self._runtime_skills_dir is not None + else None + ) + if path == skill_file or path.is_relative_to(source_dir): + return skill + if runtime_dir is not None and (path == runtime_dir or path.is_relative_to(runtime_dir)): + return skill + return None + + async def _mark_required_skill_usage_from_tool( + self, + tool_name: str, + tool_input: dict[str, Any], + ) -> None: + if tool_name == "Skill": + command_name = str( + tool_input.get("commandName") + or tool_input.get("name") + or tool_input.get("skill_name") + or "", + ).strip() + if not command_name: + return + + skill = self._resolve_skill_by_name(command_name) + if skill is None: + return + + self._referenced_skills_for_run.add(skill.name) + + if skill.name in self._referenced_skill_commands_for_run: + return + self._referenced_skill_commands_for_run.add(skill.name) + + if self._logger is None: + return + + await self._logger.record( + LogEventType.SKILL_REFERENCED, + { + "skill_name": skill.name, + "reference_type": "skill_tool", + "command_name": command_name, + "source": "loaded", + }, + ) + return + + if tool_name != "Read": + return + + path = self._resolve_tool_path(tool_input) + if path is None: + return + skill = self._resolve_skill_from_path(path) + if skill is None: + return + + self._referenced_skills_for_run.add(skill.name) + + read_marker = (skill.name, str(path)) + if read_marker in self._referenced_skill_reads_for_run: + return + self._referenced_skill_reads_for_run.add(read_marker) + + if self._logger is None: + return + + await self._logger.record( + LogEventType.SKILL_REFERENCED, + { + "skill_name": skill.name, + "reference_type": "read", + "file_path": str(path), + "source": "loaded", + }, + ) + + async def _record_skill_selected_event(self) -> None: + if self._logger is None: + return + mode = "claude_native" + await self._logger.record( + LogEventType.SKILL_SELECTED, + { + "mode": mode, + "loaded_skills_count": len(self._skills.get_skill_names()), + "loaded_skills": self._skills.get_skill_names(), + # Claude 原生模式下不再有 SDK 侧“已选”概念,保留兼容字段为空。 + "selected_skills": [], + "required_skills": [], + "suggested_skills": [], + }, + ) + + async def _record_skill_usage_summary_event(self) -> None: + if self._logger is None: + return + + mode = "claude_native" + referenced_names = sorted(self._referenced_skills_for_run) + await self._logger.record( + LogEventType.SKILL_USAGE_SUMMARY, + { + "mode": mode, + # Claude 原生模式下不再统计 SDK 侧“已选/缺失”,避免与强绑定语义混淆。 + "selected_skills": [], + "referenced_skills": referenced_names, + "missing_selected_skills": [], + "missing_required_skills": [], + }, + ) + + async def _pre_tool_use(self, input_data: dict[str, Any], *_: Any, **__: Any) -> dict[str, Any]: + tool_name = str(input_data.get("tool_name", "")).strip() + tool_input = _safe_dict(input_data.get("tool_input")) + await self._mark_required_skill_usage_from_tool(tool_name=tool_name, tool_input=tool_input) + + if self._logger is not None: + event_data: dict[str, Any] = {"tool_name": tool_name, "tool_input": tool_input} + # 标记 MCP 工具来源,前端可据此区分展示 + if tool_name.startswith("mcp__"): + parts = tool_name.split("__", 2) + if len(parts) == 3: + event_data["source"] = "mcp" + event_data["mcp_server"] = parts[1] + event_data["mcp_tool"] = parts[2] + await self._logger.record( + LogEventType.TOOL_CALL, + event_data, + ) + + # 安全主入口:PreToolUse 在 bypassPermissions 模式下依然会执行。 + if not tool_name: + return self._build_pre_tool_use_deny("Tool name could not be determined") + + decision = await self._evaluate_security_decision(tool_name=tool_name, tool_input=tool_input) + if decision.action == SecurityAction.DENY: + message = decision.message or "Denied by security policy" + return self._build_pre_tool_use_deny(message) + + # zz 拦截:zz_enabled + Bash 工具 → updatedInput 透明改写 + if self._zz_enabled and tool_name == "Bash": + command = self._extract_bash_command(tool_input) + if self._is_manual_zz_invocation(command): + return self._build_pre_tool_use_deny("入口由系统自动路由,不允许手动调用 zz/zzd") + return await self._build_zz_updated_input(tool_input) + + return {} + + def _build_pre_tool_use_deny(self, message: str) -> dict[str, Any]: + return { + "hookSpecificOutput": { + "hookEventName": "PreToolUse", + "permissionDecision": "deny", + "permissionDecisionReason": message, + }, + } + + async def _evaluate_security_decision( + self, + tool_name: str, + tool_input: dict[str, Any], + ) -> SecurityDecision: + try: + decision = await asyncio.wait_for( + asyncio.to_thread( + self._security.check, + tool_name=tool_name, + tool_input=tool_input, + ), + timeout=SECURITY_CHECK_TIMEOUT_SECONDS, + ) + except TimeoutError: + decision = SecurityDecision( + action=SecurityAction.DENY, + rule_id="security_timeout", + message=( + "Security check timeout after " + f"{SECURITY_CHECK_TIMEOUT_SECONDS}s (fail-closed DENY)" + ), + ) + _logger.warning( + "security check timeout: tool=%s timeout=%ss", + tool_name, + SECURITY_CHECK_TIMEOUT_SECONDS, + ) + + if self._logger is not None: + event_type = ( + LogEventType.SECURITY_ALLOW + if decision.action == SecurityAction.ALLOW + else LogEventType.SECURITY_DENY + ) + await self._logger.record( + event_type, + { + "tool_name": tool_name, + "rule_id": decision.rule_id, + "reason": decision.message, + }, + ) + + return decision + + @staticmethod + def _extract_bash_command(tool_input: Any) -> str: + if isinstance(tool_input, dict): + return str(tool_input.get("command", "") or "") + if isinstance(tool_input, str): + return tool_input + return "" + + @staticmethod + def _first_command_token(tokens: list[str]) -> str: + """Return the first executable token while skipping env-style assignments.""" + for token in tokens: + if token == "env": + continue + if ( + "=" in token + and not token.startswith("-") + and token.split("=", 1)[0] + and "/" not in token.split("=", 1)[0] + ): + continue + return token + return "" + + def _is_manual_zz_invocation(self, command: str) -> bool: + stripped = command.strip() + if not stripped: + return False + + first_segment = stripped + for separator in ("||", "&&", "|", ";"): + if separator in first_segment: + first_segment = first_segment.split(separator, 1)[0] + first_segment = first_segment.strip() + + try: + tokens = shlex.split(first_segment, posix=True) + except ValueError: + tokens = first_segment.split() + if not tokens: + return False + + token = self._first_command_token(tokens) + if not token: + return False + return os.path.basename(token) in {"zz", "zzd"} + + async def _build_zz_updated_input(self, tool_input: Any) -> dict[str, Any]: + """构造 updatedInput,将 Bash 命令改写为 zz --stdin --raw 管道。 + + 改写后 claude-code CLI 原生执行管道命令,zzd 成为真正的命令执行者。 + Agent(大模型)不感知改写,只看到原始命令和最终结果。 + """ + command = self._extract_bash_command(tool_input) + if not command: + _logger.warning("zz: empty command, skipping rewrite") + return {} + + # 构造 stdin JSON + stdin_payload = json.dumps({ + "command": command, + "task_id": self._task_id, + "work_dir": str(self.cwd), + "timeout": 300, # 5 min default + }) + + # base64 编码(避免 shell 转义问题) + b64_payload = base64.b64encode(stdin_payload.encode("utf-8")).decode("ascii") + + # 改写命令:echo | base64 -d | zz --stdin --raw + rewritten_command = f"echo {b64_payload} | base64 -d | zz --stdin --raw" + + # 记录 ZZ_RESULT(改写发生在执行前,此时没有 request_id) + await self._log_zz_result(command) + + # 返回 hookSpecificOutput.updatedInput,claude-code CLI 将执行改写后的命令 + # 不设置 permissionDecision,让 can_use_tool 继续处理权限 + return { + "hookSpecificOutput": { + "hookEventName": "PreToolUse", + "updatedInput": {"command": rewritten_command}, + } + } + + async def _log_zz_result(self, command: str) -> None: + """记录 ZZ_RESULT 桥接事件(执行前记录,仅含 command + task_id).""" + if self._logger is None: + return + await self._logger.record( + LogEventType.ZZ_RESULT, + { + "task_id": self._task_id, + "command": command, + }, + ) + + async def _post_tool_use(self, input_data: dict[str, Any], *_: Any, **__: Any) -> dict[str, Any]: + tool_name = str(input_data.get("tool_name", "")).strip() + data: dict[str, Any] = { + "tool_name": tool_name, + "response": input_data.get("tool_response"), + } + # 标记 MCP 工具来源 + if tool_name.startswith("mcp__"): + parts = tool_name.split("__", 2) + if len(parts) == 3: + data["source"] = "mcp" + data["mcp_server"] = parts[1] + data["mcp_tool"] = parts[2] + + if self._logger is not None: + await self._logger.record(LogEventType.TOOL_RESULT, data) + return {"continue_": True} + + async def _post_tool_use_failure(self, input_data: dict[str, Any], *_: Any, **__: Any) -> dict[str, Any]: + """记录工具执行失败(含 zzd DENY)的审计日志。 + + PostToolUseFailure 在工具执行出错时触发,典型场景: + - zz --raw 返回 DENY(exit 1 + stderr) + - 命令超时或异常退出 + """ + data: dict[str, Any] = { + "tool_name": input_data.get("tool_name"), + "tool_input": input_data.get("tool_input"), + "error": input_data.get("error"), + } + + if self._logger is not None: + await self._logger.record(LogEventType.TOOL_ERROR, data) + return {"continue_": True} + + async def _can_use_tool(self, *args: Any, **kwargs: Any) -> Any: + tool_name, tool_input = _extract_tool_context(args, kwargs) + + # Security: empty tool_name means extraction failed — deny by default + if not tool_name: + _logger.warning("_can_use_tool: empty tool_name, denying by default") + permission_deny = self._runtime_symbols.get("PermissionResultDeny") + if permission_deny is not None: + return permission_deny(message="Tool name could not be determined") + return False + + decision = await self._evaluate_security_decision(tool_name=tool_name, tool_input=tool_input) + + permission_allow = self._runtime_symbols.get("PermissionResultAllow") + permission_deny = self._runtime_symbols.get("PermissionResultDeny") + + if decision.action == SecurityAction.DENY: + if permission_deny is not None: + return permission_deny(message=decision.message or "Denied by security policy") + return False + + if permission_allow is not None: + return permission_allow() + return True + + async def _record_runtime_message(self, message: Any) -> None: + if self._logger is None: + return + blocks = getattr(message, "content", None) + if not isinstance(blocks, list): + return + + for block in blocks: + thinking = getattr(block, "thinking", None) + if isinstance(thinking, str) and thinking: + await self._logger.record(LogEventType.THINKING, {"thinking": thinking}) + + text = getattr(block, "text", None) + if isinstance(text, str) and text: + await self._logger.record(LogEventType.ASSISTANT_TEXT, {"text": text}) + api_error = _extract_runtime_api_error(text) + if api_error is not None: + raise RuntimeProtocolError(api_error) + +class _QueryRuntimeClient: + """Fallback adapter when SDK only exposes query() coroutine-generator API.""" + + def __init__(self, query_func: Any, options: Any) -> None: + self._query_func = query_func + self._options = options + self._iterator: Any = None + + async def query(self, task: str) -> None: + self._iterator = self._query_func(prompt=task, options=self._options) + + async def receive_response(self): + if self._iterator is None: + return + async for message in self._iterator: + yield message + + +def _extract_runtime_api_error(text: str) -> str | None: + normalized = text.strip() + if not normalized: + return None + if not normalized.lower().startswith("api error:"): + return None + return normalized + + +def _extract_tool_context(args: tuple[Any, ...], kwargs: dict[str, Any]) -> tuple[str, dict[str, Any]]: + """Extract tool_name and tool_input from can_use_tool callback arguments. + + Expected SDK signature: can_use_tool(tool_name, tool_input, context) + Falls back to dict-based extraction for forward compatibility. + Returns ("", {}) only when extraction truly fails; callers MUST treat + empty tool_name as a security-deny condition. + """ + # Primary path: positional (tool_name: str, tool_input: dict, context) + if args and isinstance(args[0], str): + tool_name = args[0] + tool_input = args[1] if len(args) > 1 and isinstance(args[1], dict) else {} + return tool_name, tool_input + + # Alt path: single dict argument + if args and isinstance(args[0], dict): + input_data = args[0] + return str(input_data.get("tool_name", "")), _safe_dict(input_data.get("tool_input")) + + # Alt path: keyword arguments + if "tool_name" in kwargs: + return str(kwargs.get("tool_name", "")), _safe_dict(kwargs.get("tool_input")) + + input_data = kwargs.get("input_data", {}) + if isinstance(input_data, dict): + return str(input_data.get("tool_name", "")), _safe_dict(input_data.get("tool_input")) + + # Extraction failed — log warning + _logger.warning("_extract_tool_context: unable to extract tool info from args=%r kwargs_keys=%r", args, list(kwargs.keys())) + return "", {} + + +def _safe_dict(value: Any) -> dict[str, Any]: + """Coerce value to dict safely; non-dict values degrade to {} with a warning.""" + if value is None: + return {} + if isinstance(value, dict): + return value + _logger.warning("_safe_dict: expected dict for tool_input, got %s; degrading to {}", type(value).__name__) + return {} + + +def _resolve_env_placeholders( + env: dict[str, str], + workstation_id: str, + task_id: str, +) -> dict[str, str]: + """Replace {workstationid}, {taskid}, {userid} placeholders in env values. + + Per config-system.md §6.3, these placeholders are resolved before + passing env to the runtime. + """ + if not env: + return {} + user_id = os.getenv("USER_ID", "") + resolved: dict[str, str] = {} + for key, value in env.items(): + resolved[key] = ( + value.replace("{workstationid}", workstation_id) + .replace("{taskid}", task_id) + .replace("{userid}", user_id) + ) + return resolved + + +def _preset_prompt(preset: str) -> str: + """Fallback text when use_preset=False and no custom prompt is given.""" + if preset == "claude_code": + return "You are a reliable software engineering assistant running in LinkWork Agent SDK." + return "" diff --git a/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/exceptions.py b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/exceptions.py new file mode 100644 index 0000000..1624fcd --- /dev/null +++ b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/exceptions.py @@ -0,0 +1,67 @@ +"""LinkWork Agent SDK exception hierarchy.""" + +from __future__ import annotations + + +class LinkWorkAgentSDKError(Exception): + """Base error for SDK.""" + + +class ConfigError(LinkWorkAgentSDKError): + """Base config error.""" + + +class ConfigNotFoundError(ConfigError): + """Config file does not exist.""" + + +class ConfigNullError(ConfigError): + """Config file content is empty.""" + + +class ConfigParseError(ConfigError): + """Config file JSON parsing failed.""" + + +class ConfigValidationError(ConfigError): + """Config model validation failed.""" + + +class ConfigPermissionError(ConfigError): + """Config file permission denied.""" + + +class RedisClientError(LinkWorkAgentSDKError): + """Redis client operation failed.""" + + +class SecurityLoadError(LinkWorkAgentSDKError): + """Security rules loading failed.""" + + +class SecurityExecuteError(LinkWorkAgentSDKError): + """Security check execution failed.""" + + +class SkillLoadError(LinkWorkAgentSDKError): + """Skills loading failed.""" + + +class MCPLoadError(LinkWorkAgentSDKError): + """MCP config loading failed.""" + + +class ConcurrentExecutionError(LinkWorkAgentSDKError): + """Concurrent run() is not allowed.""" + + +class RuntimeInitError(LinkWorkAgentSDKError): + """AI runtime initialization failed.""" + + +class RuntimeProtocolError(LinkWorkAgentSDKError): + """AI runtime returned protocol-invalid response.""" + + +class WorkerLifecycleError(LinkWorkAgentSDKError): + """Worker lifecycle operation failed.""" diff --git a/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/logger/__init__.py b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/logger/__init__.py new file mode 100644 index 0000000..929dd2c --- /dev/null +++ b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/logger/__init__.py @@ -0,0 +1,14 @@ +"""Logger package.""" + +from .recorder import LogEvent, LogEventType, LogRecorder +from .transport import CompositeTransport, FileLocalFallback, LogTransport, RedisStreamTransport + +__all__ = [ + "CompositeTransport", + "FileLocalFallback", + "LogEvent", + "LogEventType", + "LogRecorder", + "LogTransport", + "RedisStreamTransport", +] diff --git a/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/logger/recorder.py b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/logger/recorder.py new file mode 100644 index 0000000..af470e7 --- /dev/null +++ b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/logger/recorder.py @@ -0,0 +1,157 @@ +"""Log recorder and event model.""" + +from __future__ import annotations + +import json +import logging +import time +from dataclasses import dataclass, field +from datetime import UTC, datetime +from enum import Enum +from pathlib import Path +from typing import Any + +from ..constants import LOG_FALLBACK_DIR +from .transport import FileLocalFallback, LogTransport + +_logger = logging.getLogger(__name__) + + +class LogEventType(str, Enum): + SESSION_START = "SESSION_START" + SESSION_END = "SESSION_END" + TASK_ASSIGNED = "TASK_ASSIGNED" + TASK_COMPLETED = "TASK_COMPLETED" + TASK_FAILED = "TASK_FAILED" + TASK_ABORTED = "TASK_ABORTED" + TASK_ABORT_ACK = "TASK_ABORT_ACK" + TASK_OUTPUT_READY = "TASK_OUTPUT_READY" + TASK_OUTPUT_PATHLIST_READY = "TASK_OUTPUT_PATHLIST_READY" + TOOL_CALL = "TOOL_CALL" + TOOL_RESULT = "TOOL_RESULT" + TOOL_ERROR = "TOOL_ERROR" + SECURITY_ALLOW = "SECURITY_ALLOW" + SECURITY_DENY = "SECURITY_DENY" + CONFIG_LOADED = "CONFIG_LOADED" + SECURITY_LOADED = "SECURITY_LOADED" + SKILLS_LOADED = "SKILLS_LOADED" + SKILL_SELECTED = "SKILL_SELECTED" + SKILL_REFERENCED = "SKILL_REFERENCED" + SKILL_USAGE_SUMMARY = "SKILL_USAGE_SUMMARY" + MCP_LOADED = "MCP_LOADED" + MCP_CONNECTED = "MCP_CONNECTED" # 预留:MCP Server 连接成功 + MCP_DISCONNECTED = "MCP_DISCONNECTED" # 预留:MCP Server 连接断开 + MCP_HEALTH = "MCP_HEALTH" # 预留:MCP 健康状态汇报 + MCP_ERROR = "MCP_ERROR" # 预留:MCP 调用异常 + ERROR = "ERROR" + THINKING = "THINKING" + ASSISTANT_TEXT = "ASSISTANT_TEXT" + WORKER_IDLE_TIMEOUT = "WORKER_IDLE_TIMEOUT" + WORKER_STOP = "WORKER_STOP" + WORKSPACE_INITIALIZED = "WORKSPACE_INITIALIZED" + WORKSPACE_PREPARED = "WORKSPACE_PREPARED" + WORKSPACE_ARCHIVED = "WORKSPACE_ARCHIVED" + WORKSPACE_CLEANED = "WORKSPACE_CLEANED" + GIT_PRE_START = "GIT_PRE_START" + GIT_PRE_DONE = "GIT_PRE_DONE" + GIT_PRE_FAILED = "GIT_PRE_FAILED" + GIT_POST_START = "GIT_POST_START" + GIT_POST_DONE = "GIT_POST_DONE" + GIT_POST_FAILED = "GIT_POST_FAILED" + ZZ_RESULT = "ZZ_RESULT" + + +@dataclass(slots=True) +class LogEvent: + event_type: LogEventType + session_id: str + data: dict[str, Any] = field(default_factory=dict) + timestamp: str = field( + default_factory=lambda: datetime.now(UTC).isoformat(), + ) + + def to_dict(self) -> dict[str, Any]: + return { + "event_type": self.event_type.value, + "timestamp": self.timestamp, + "session_id": self.session_id, + "data": self.data, + } + + def to_json(self) -> str: + return json.dumps(self.to_dict(), ensure_ascii=False) + + +class LogRecorder: + """Record events and send through transport; never block main flow.""" + + def __init__( + self, + output_dir: str | Path, + session_id: str, + transport: LogTransport | None = None, + ) -> None: + self._output_dir = Path(output_dir) + self._session_id = session_id + self._transport = transport or FileLocalFallback( + session_id=session_id, + output_dir=LOG_FALLBACK_DIR, + ) + self._started_at: float | None = None + + @property + def session_id(self) -> str: + return self._session_id + + async def start(self) -> None: + self._started_at = time.monotonic() + try: + await self._transport.connect() + finally: + await self.record(LogEventType.SESSION_START, {}) + + async def stop(self) -> None: + duration_ms = None + if self._started_at is not None: + duration_ms = int((time.monotonic() - self._started_at) * 1000) + await self.record( + LogEventType.SESSION_END, + { + "duration_ms": duration_ms, + }, + ) + try: + await self._transport.close() + except Exception: + _logger.warning("LogRecorder.stop: transport close failed", exc_info=True) + + async def record( + self, + event_type: LogEventType, + data: dict[str, Any] | None = None, + ) -> None: + payload = _truncate_payload(data or {}) + event = LogEvent( + event_type=event_type, + session_id=self._session_id, + data=payload, + ) + try: + await self._transport.send(event) + except Exception: + _logger.warning( + "LogRecorder.record: transport send failed for event %s", + event_type.value, + exc_info=True, + ) + return + + +def _truncate_payload(data: dict[str, Any], max_chars: int = 20_000) -> dict[str, Any]: + serialized = json.dumps(data, ensure_ascii=False) + if len(serialized) <= max_chars: + return data + return { + "_truncated": serialized[:max_chars], + "_original_size": len(serialized), + } diff --git a/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/logger/transport.py b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/logger/transport.py new file mode 100644 index 0000000..10f7b5c --- /dev/null +++ b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/logger/transport.py @@ -0,0 +1,165 @@ +"""Transport layer for log delivery.""" + +from __future__ import annotations + +import asyncio +import json +import logging +from abc import ABC, abstractmethod +from datetime import UTC, datetime +from pathlib import Path +from typing import TYPE_CHECKING + +from ..constants import LOG_FALLBACK_DIR, build_log_stream_key + +if TYPE_CHECKING: + from ..redis import RedisClient + from .recorder import LogEvent + +_logger = logging.getLogger(__name__) + + +class LogTransport(ABC): + """Abstract log transport interface.""" + + @abstractmethod + async def connect(self) -> None: + """Connect transport backend.""" + + @abstractmethod + async def close(self) -> None: + """Close transport backend.""" + + @abstractmethod + async def send(self, event: "LogEvent") -> bool: + """Send one log event.""" + + @abstractmethod + def is_connected(self) -> bool: + """Whether backend is connected.""" + + +class RedisStreamTransport(LogTransport): + """Redis XADD transport.""" + + def __init__( + self, + redis_client: "RedisClient", + workstation_id: str, + task_id: str, + maxlen: int | None = None, + ) -> None: + self._redis_client = redis_client + self._workstation_id = workstation_id + self._task_id = task_id + self._maxlen = maxlen + self._connected = False + + @property + def stream_key(self) -> str: + return build_log_stream_key(self._workstation_id, self._task_id) + + async def connect(self) -> None: + await self._redis_client.connect() + self._connected = True + + async def close(self) -> None: + self._connected = False + + async def send(self, event: "LogEvent") -> bool: + if not self._connected: + return False + fields = { + "event_type": event.event_type.value, + "timestamp": event.timestamp, + "session_id": event.session_id, + "data": json.dumps(event.data, ensure_ascii=False), + } + try: + await self._redis_client.xadd(self.stream_key, fields, maxlen=self._maxlen) + return True + except Exception: + self._connected = False + return False + + def is_connected(self) -> bool: + return self._connected + + +class FileLocalFallback(LogTransport): + """File-based JSONL fallback transport.""" + + def __init__(self, session_id: str, output_dir: str | Path | None = None) -> None: + self._session_id = session_id + self._output_dir = Path(output_dir or LOG_FALLBACK_DIR) + self._log_file: Path | None = None + self._connected = False + + async def connect(self) -> None: + try: + self._output_dir.mkdir(parents=True, exist_ok=True) + timestamp = datetime.now(UTC).strftime("%Y%m%dT%H%M%SZ") + self._log_file = self._output_dir / f"{self._session_id}_{timestamp}.jsonl" + self._connected = True + except OSError: + self._connected = False + + async def close(self) -> None: + self._connected = False + + async def send(self, event: "LogEvent") -> bool: + if not self._connected or self._log_file is None: + return False + line = event.to_json() + "\n" + try: + await asyncio.to_thread(self._write_line, line) + return True + except OSError: + return False + + def _write_line(self, line: str) -> None: + """Synchronous file write, executed in thread pool to avoid blocking event loop.""" + if self._log_file is None: + return + with self._log_file.open("a", encoding="utf-8") as handle: + handle.write(line) + + def is_connected(self) -> bool: + return self._connected + + +class CompositeTransport(LogTransport): + """Primary transport with fallback transport.""" + + def __init__(self, primary: LogTransport, fallback: LogTransport) -> None: + self._primary = primary + self._fallback = fallback + self._connected = False + + async def connect(self) -> None: + try: + await self._primary.connect() + except Exception: + _logger.warning( + "CompositeTransport: primary transport connect failed, using fallback", + exc_info=True, + ) + await self._fallback.connect() + self._connected = self._primary.is_connected() or self._fallback.is_connected() + + async def close(self) -> None: + try: + await self._primary.close() + finally: + await self._fallback.close() + self._connected = False + + async def send(self, event: "LogEvent") -> bool: + if self._primary.is_connected() and await self._primary.send(event): + return True + if not self._fallback.is_connected(): + await self._fallback.connect() + return await self._fallback.send(event) + + def is_connected(self) -> bool: + return self._connected diff --git a/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/mcp/__init__.py b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/mcp/__init__.py new file mode 100644 index 0000000..52705fd --- /dev/null +++ b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/mcp/__init__.py @@ -0,0 +1,5 @@ +"""MCP package.""" + +from .provider import MCPProvider, MCPServerConfig + +__all__ = ["MCPProvider", "MCPServerConfig"] diff --git a/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/mcp/provider.py b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/mcp/provider.py new file mode 100644 index 0000000..6aab787 --- /dev/null +++ b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/mcp/provider.py @@ -0,0 +1,194 @@ +"""MCP provider for local mcp.json.""" + +from __future__ import annotations + +import json +import os +from dataclasses import dataclass +from pathlib import Path +from typing import Any + +from ..constants import MCP_CONFIG_FILE +from ..exceptions import MCPLoadError + + +@dataclass(slots=True) +class MCPServerConfig: + name: str + type: str = "stdio" + command: str | None = None + args: list[str] | None = None + env: dict[str, str] | None = None + url: str | None = None + headers: dict[str, str] | None = None + + +class MCPProvider: + """Load MCP server configs and convert to Claude SDK format. + + MCP configuration is baked into the container image at build time. + At startup: + - mcp.json missing → no MCP configured, normal startup + - mcp.json present, valid → load servers (empty mcpServers is OK) + - mcp.json present, broken → raise MCPLoadError to block startup + """ + + def __init__(self, config_file: str | Path | None = None) -> None: + self._config_file = Path(config_file or MCP_CONFIG_FILE) + self._servers: dict[str, MCPServerConfig] = {} + self._global_headers: dict[str, str] = {} + + def load(self) -> dict[str, MCPServerConfig]: + config_path = self._config_file + + if not config_path.exists() or not config_path.is_file(): + # No mcp.json → role has no MCP configured; normal startup. + self._servers = {} + return {} + + try: + raw_text = config_path.read_text(encoding="utf-8") + except OSError as error: + raise MCPLoadError( + f"MCP config file exists but cannot be read: {config_path}: {error}" + ) from error + + try: + raw_data = json.loads(raw_text) + except json.JSONDecodeError as error: + raise MCPLoadError( + f"MCP config file exists but contains invalid JSON: {config_path}: {error}" + ) from error + + if not isinstance(raw_data, dict): + raise MCPLoadError( + f"MCP config must be a JSON object, got {type(raw_data).__name__}" + ) + + raw_global_headers = raw_data.get("globalHeaders") + if isinstance(raw_global_headers, dict): + self._global_headers = {str(k): str(v) for k, v in raw_global_headers.items()} + + raw_servers = raw_data.get("mcpServers") + if raw_servers is None: + self._servers = {} + return {} + + if not isinstance(raw_servers, dict): + raise MCPLoadError("mcpServers must be object") + + parsed: dict[str, MCPServerConfig] = {} + for name, value in raw_servers.items(): + if not isinstance(value, dict): + raise MCPLoadError(f"MCP server '{name}' config must be object") + + server_type = str(value.get("type", "stdio")) + if server_type not in {"stdio", "sse", "http"}: + raise MCPLoadError(f"MCP server '{name}' has unknown type: {server_type}") + + parsed[name] = MCPServerConfig( + name=name, + type=server_type, + command=_maybe_str(value.get("command")), + args=_maybe_list_str(value.get("args")), + env=_maybe_dict_str(value.get("env")), + url=_maybe_str(value.get("url")), + headers=_maybe_dict_str(value.get("headers")), + ) + + self._servers = parsed + return dict(self._servers) + + def get_mcp_servers_config(self) -> dict[str, dict[str, Any]]: + resolved_globals = _resolve_placeholders(self._global_headers) + + config: dict[str, dict[str, Any]] = {} + for name, server in self._servers.items(): + if server.type == "stdio": + payload: dict[str, Any] = {} + if server.command is not None: + payload["command"] = server.command + if server.args is not None: + payload["args"] = server.args + if server.env is not None: + payload["env"] = server.env + config[name] = payload + continue + + payload = { + "type": server.type, + "url": server.url, + } + merged_headers: dict[str, str] = {} + if resolved_globals: + merged_headers.update(resolved_globals) + if server.headers is not None: + merged_headers.update(server.headers) + if merged_headers: + payload["headers"] = merged_headers + config[name] = payload + + return config + + def get_server(self, name: str) -> MCPServerConfig | None: + return self._servers.get(name) + + async def probe_servers(self) -> dict[str, dict[str, Any]]: + """ + Phase 2 预留:探活所有已加载的 MCP Server (http/sse) + + Returns: + {server_name: {"status": "online"|"offline", "latency_ms": int, "error": str|None}} + """ + raise NotImplementedError("MCP probe_servers is reserved for Phase 2") + + async def report_health(self, logger: Any) -> None: + """ + Phase 2 预留:探活后通过 EventLogger 上报 MCP_HEALTH 事件到 Redis Stream + """ + raise NotImplementedError("MCP report_health is reserved for Phase 2") + + +def _maybe_str(value: Any) -> str | None: + if value is None: + return None + return str(value) + + +def _maybe_list_str(value: Any) -> list[str] | None: + if value is None: + return None + if not isinstance(value, list): + raise MCPLoadError("MCP args must be array") + return [str(item) for item in value] + + +def _maybe_dict_str(value: Any) -> dict[str, str] | None: + if value is None: + return None + if not isinstance(value, dict): + raise MCPLoadError("MCP env/headers must be object") + return {str(key): str(val) for key, val in value.items()} + + +_PLACEHOLDER_VARIANTS = [ + ("{taskid}", "TASK_ID"), + ("{TASKID}", "TASK_ID"), + ("{TaskId}", "TASK_ID"), + ("{userid}", "USER_ID"), + ("{USERID}", "USER_ID"), + ("{UserId}", "USER_ID"), +] + + +def _resolve_placeholders(headers: dict[str, str]) -> dict[str, str]: + """Replace {taskid}/{userid} placeholders with environment variable values.""" + if not headers: + return {} + resolved: dict[str, str] = {} + for key, value in headers.items(): + for placeholder, env_var in _PLACEHOLDER_VARIANTS: + if placeholder in value: + value = value.replace(placeholder, os.environ.get(env_var, "")) + resolved[key] = value + return resolved diff --git a/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/redis.py b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/redis.py new file mode 100644 index 0000000..99b1099 --- /dev/null +++ b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/redis.py @@ -0,0 +1,135 @@ +"""Redis client wrapper for List/Stream operations.""" + +from __future__ import annotations + +from collections.abc import Awaitable, Callable +from typing import Any + +import redis.asyncio as redis +from redis.exceptions import RedisError + +from .constants import get_redis_url +from .exceptions import RedisClientError + + +class RedisClient: + """Async Redis wrapper with reconnect-on-demand behavior.""" + + def __init__(self, url: str | None = None) -> None: + self._url = (url or get_redis_url()).strip() + self._client: redis.Redis | None = None + + @property + def url(self) -> str: + return self._url + + async def connect(self) -> None: + if self._client is not None: + return + try: + self._client = redis.from_url(self._url, decode_responses=True) + await self._client.ping() + except Exception as error: + self._client = None + raise RedisClientError(f"Redis connect failed: {error}") from error + + async def close(self) -> None: + if self._client is None: + return + await self._client.aclose() + self._client = None + + async def ping(self) -> bool: + try: + await self._call(lambda client: client.ping()) + return True + except RedisClientError: + return False + + async def blpop(self, key: str, timeout: int) -> tuple[str, str] | None: + # Worker idle consume path: fail-fast on Redis disconnect to let supervisor restart. + result = await self._call( + lambda client: client.blpop(key, timeout=timeout), + retry_on_disconnect=False, + ) + if result is None: + return None + queue_key, payload = result + return str(queue_key), str(payload) + + async def rpush(self, key: str, value: str) -> int: + result = await self._call(lambda client: client.rpush(key, value)) + return int(result) + + async def xadd( + self, + stream_key: str, + fields: dict[str, Any], + maxlen: int | None = None, + ) -> str: + kwargs: dict[str, Any] = {"fields": fields} + if maxlen is not None: + kwargs["maxlen"] = maxlen + kwargs["approximate"] = True + result = await self._call( + lambda client: client.xadd(stream_key, **kwargs), + ) + return str(result) + + async def xread( + self, + streams: dict[str, str], + block_ms: int | None = None, + count: int | None = None, + ) -> list[tuple[str, list[tuple[str, dict[str, Any]]]]]: + kwargs: dict[str, Any] = {"streams": streams} + if block_ms is not None: + kwargs["block"] = block_ms + if count is not None: + kwargs["count"] = count + result = await self._call( + lambda client: client.xread(**kwargs), + retry_on_disconnect=False, + ) + if result is None: + return [] + return list(result) + + async def _call( + self, + operation: Callable[[redis.Redis], Awaitable[Any]], + retry_on_disconnect: bool = True, + ) -> Any: + if self._client is None: + await self.connect() + if self._client is None: + raise RedisClientError("Redis client unavailable") + + try: + return await operation(self._client) + except RedisError as error: + if not retry_on_disconnect: + raise RedisClientError( + f"Redis operation failed (no retry): {error}", + ) from error + + await self._reset_connection() + try: + return await operation(self._client) + except Exception as retry_error: + raise RedisClientError( + f"Redis operation failed after retry: {retry_error}", + ) from retry_error + except Exception as error: + raise RedisClientError(f"Redis operation failed: {error}") from error + + async def _reset_connection(self) -> None: + await self.close() + await self.connect() + + async def __aenter__(self) -> "RedisClient": + await self.connect() + return self + + async def __aexit__(self, *_: object) -> None: + await self.close() diff --git a/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/security/__init__.py b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/security/__init__.py new file mode 100644 index 0000000..dc953b8 --- /dev/null +++ b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/security/__init__.py @@ -0,0 +1,10 @@ +"""Security package.""" + +from .enforcer import SecurityAction, SecurityDecision, SecurityEnforcer, SecurityRule + +__all__ = [ + "SecurityAction", + "SecurityDecision", + "SecurityEnforcer", + "SecurityRule", +] diff --git a/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/security/enforcer.py b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/security/enforcer.py new file mode 100644 index 0000000..ebde27c --- /dev/null +++ b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/security/enforcer.py @@ -0,0 +1,193 @@ +"""Security enforcer for tool permission checks.""" + +from __future__ import annotations + +import json +import re +from dataclasses import dataclass +from enum import Enum +from pathlib import Path + +from ..constants import SECURITY_RULES_FILE +from ..exceptions import SecurityExecuteError, SecurityLoadError + + +class SecurityAction(str, Enum): + ALLOW = "ALLOW" + DENY = "DENY" + + +@dataclass(slots=True) +class SecurityRule: + id: str + pattern: str + action: SecurityAction + message: str = "" + compiled_pattern: re.Pattern[str] | None = None + + +@dataclass(slots=True) +class SecurityDecision: + action: SecurityAction + rule_id: str | None = None + message: str = "" + + +class SecurityEnforcer: + """Load local security rules and perform allow/deny checks.""" + + def __init__(self, rules_file: str | Path | None = None) -> None: + self._rules_file = Path(rules_file or SECURITY_RULES_FILE) + self._rules: list[SecurityRule] = [] + self._can_use_tools: list[str] = [] + self._available_mcp_servers: set[str] = set() + self._normalised_mcp_servers: set[str] = set() + + @property + def rules(self) -> list[SecurityRule]: + return list(self._rules) + + @property + def rule_count(self) -> int: + return len(self._rules) + + def load(self) -> list[SecurityRule]: + if not self._rules_file.exists() or not self._rules_file.is_file(): + raise SecurityLoadError(f"Security file not found: {self._rules_file}") + + try: + content = self._rules_file.read_text(encoding="utf-8") + raw_data = json.loads(content) + except json.JSONDecodeError as error: + raise SecurityLoadError( + f"Security JSON parse failed: {error}", + ) from error + except OSError as error: + raise SecurityLoadError(f"Security file read failed: {error}") from error + + raw_rules = raw_data.get("rules") + if not isinstance(raw_rules, list): + raise SecurityLoadError("Security rules must be an array") + + parsed_rules: list[SecurityRule] = [] + for index, raw_rule in enumerate(raw_rules): + if not isinstance(raw_rule, dict): + raise SecurityLoadError(f"Rule at index {index} must be object") + try: + rule_id = str(raw_rule["id"]) + pattern = str(raw_rule["pattern"]) + action_str = str(raw_rule["action"]) + except KeyError as error: + raise SecurityLoadError( + f"Rule at index {index} missing field: {error}", + ) from error + + if action_str not in (SecurityAction.ALLOW.value, SecurityAction.DENY.value): + raise SecurityLoadError( + f"Rule {rule_id} action invalid: {action_str}", + ) + + try: + compiled = re.compile(pattern) + except re.error as error: + raise SecurityLoadError( + f"Rule {rule_id} regex invalid: {error}", + ) from error + + parsed_rules.append( + SecurityRule( + id=rule_id, + pattern=pattern, + action=SecurityAction(action_str), + message=str(raw_rule.get("message", "")), + compiled_pattern=compiled, + ), + ) + + self._rules = parsed_rules + return list(self._rules) + + def set_can_use_tools(self, tools: list[str]) -> None: + self._can_use_tools = [item for item in tools if item] + + def set_available_mcp_servers(self, server_names: set[str] | list[str]) -> None: + self._available_mcp_servers = {name for name in server_names if name} + # Claude SDK normalises MCP server names: spaces → underscores. + # Build a lookup that maps *normalised* names back so the capability + # check succeeds regardless of whether the tool name uses the + # original or normalised form. + self._normalised_mcp_servers: set[str] = { + name.replace(" ", "_") for name in self._available_mcp_servers + } | self._available_mcp_servers + + def check(self, tool_name: str, tool_input: dict) -> SecurityDecision: + mcp_check = self._check_mcp_capability(tool_name) + if mcp_check is not None: + return mcp_check + + if self._can_use_tools and tool_name not in self._can_use_tools: + return SecurityDecision( + action=SecurityAction.DENY, + rule_id="task_whitelist", + message=f"Tool '{tool_name}' denied by task can_use_tools", + ) + + payload = json.dumps(tool_input, ensure_ascii=False, sort_keys=True) + match_target = f"{tool_name}:{payload}" + + allow_rule: SecurityRule | None = None + deny_rule: SecurityRule | None = None + + for rule in self._rules: + if rule.compiled_pattern is None: + raise SecurityExecuteError(f"Rule {rule.id} has no compiled regex") + if not rule.compiled_pattern.search(match_target): + continue + if rule.action == SecurityAction.DENY: + deny_rule = rule + break + if rule.action == SecurityAction.ALLOW and allow_rule is None: + allow_rule = rule + + if deny_rule is not None: + return SecurityDecision( + action=SecurityAction.DENY, + rule_id=deny_rule.id, + message=deny_rule.message or "Tool use denied", + ) + + if allow_rule is not None: + return SecurityDecision( + action=SecurityAction.ALLOW, + rule_id=allow_rule.id, + message="", + ) + + return SecurityDecision(action=SecurityAction.ALLOW) + + def _check_mcp_capability(self, tool_name: str) -> SecurityDecision | None: + if not tool_name.startswith("mcp__"): + return None + if not self._available_mcp_servers: + return SecurityDecision( + action=SecurityAction.DENY, + rule_id="capability_pool", + message="No MCP servers loaded", + ) + + parts = tool_name.split("__") + if len(parts) < 3: + return SecurityDecision( + action=SecurityAction.DENY, + rule_id="capability_pool", + message=f"Invalid MCP tool format: {tool_name}", + ) + + server_name = parts[1] + if server_name not in self._normalised_mcp_servers: + return SecurityDecision( + action=SecurityAction.DENY, + rule_id="capability_pool", + message=f"MCP server '{server_name}' not loaded", + ) + return None diff --git a/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/skills/__init__.py b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/skills/__init__.py new file mode 100644 index 0000000..2ef1671 --- /dev/null +++ b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/skills/__init__.py @@ -0,0 +1,5 @@ +"""Skills package.""" + +from .provider import Skill, SkillsProvider + +__all__ = ["Skill", "SkillsProvider"] diff --git a/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/skills/provider.py b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/skills/provider.py new file mode 100644 index 0000000..1c97761 --- /dev/null +++ b/back/src/main/resources/build-assets/sdk-source/src/linkwork_agent_sdk/skills/provider.py @@ -0,0 +1,169 @@ +"""Skills provider for loading local SKILL.md files.""" + +from __future__ import annotations + +import re +import shutil +from dataclasses import dataclass +from pathlib import Path + +import yaml + +from ..constants import SKILLS_DIR +from ..exceptions import SkillLoadError + +_FRONTMATTER_RE = re.compile(r"^---\n(.*?)\n---\n?(.*)$", re.DOTALL) + + +@dataclass(slots=True) +class Skill: + name: str + description: str + content: str + path: Path + + +class SkillsProvider: + """Load all skills from fixed local directory.""" + + def __init__(self, skills_dir: str | Path | None = None) -> None: + self._skills_dir = Path(skills_dir or SKILLS_DIR) + self._skills: list[Skill] = [] + self._skill_map: dict[str, Skill] = {} + + @property + def skills_dir(self) -> Path: + return self._skills_dir + + def load(self) -> list[Skill]: + if not self._skills_dir.exists() or not self._skills_dir.is_dir(): + self._skills = [] + self._skill_map = {} + return [] + + loaded: list[Skill] = [] + for child in sorted(self._skills_dir.iterdir()): + if not child.is_dir(): + continue + skill_file = child / "SKILL.md" + if not skill_file.exists(): + continue + loaded.append(self._load_single(skill_file)) + + if not loaded: + self._skills = [] + self._skill_map = {} + return [] + + self._skills = loaded + self._skill_map = {skill.name: skill for skill in loaded} + return list(self._skills) + + def get_skill_summary(self) -> str: + if not self._skills: + return "" + skill_names = self.get_skill_names() + lines = ["## Available Skills"] + if skill_names: + lines.append( + "Canonical skill names (exact match): " + + ", ".join(skill_names), + ) + lines.append( + "If a requested skill exactly matches one of the names above, " + "it is available.", + ) + for skill in self._skills: + lines.append(f"- {skill.name}: {skill.description}") + return "\n".join(lines) + + def get_skill_names(self) -> list[str]: + # Stable, deduplicated names for logging and prompt diagnostics. + return sorted({skill.name for skill in self._skills}) + + def get_skills(self) -> list[Skill]: + return list(self._skills) + + def get_plugins_config(self) -> list[dict[str, str]]: + return [ + { + "type": "local", + "path": str(skill.path.parent), + } + for skill in self._skills + ] + + def get_setting_sources_config(self) -> list[str]: + """Return Claude official setting sources for skills discovery.""" + return ["project"] + + def sync_to_claude_project_dir(self, cwd: str | Path) -> Path | None: + """Mirror loaded skills into /.claude/skills for standard runtime lookup.""" + if not self._skills: + return None + + project_skills_dir = Path(cwd) / ".claude" / "skills" + try: + if project_skills_dir.exists(): + shutil.rmtree(project_skills_dir) + project_skills_dir.mkdir(parents=True, exist_ok=True) + + for skill in self._skills: + source_dir = skill.path.parent + target_dir = project_skills_dir / source_dir.name + shutil.copytree(source_dir, target_dir, dirs_exist_ok=True) + self._ensure_script_exec_permissions(target_dir) + except OSError as error: + raise SkillLoadError( + f"Failed to sync skills to Claude project dir: {project_skills_dir}", + ) from error + return project_skills_dir + + def get_skill(self, name: str) -> Skill | None: + return self._skill_map.get(name) + + def _ensure_script_exec_permissions(self, skill_dir: Path) -> None: + for script_file in skill_dir.rglob("*.sh"): + if not script_file.is_file(): + continue + current_mode = script_file.stat().st_mode + # Mirror read bits to execute bits, keeping existing mode as-is otherwise. + executable_mode = current_mode + if current_mode & 0o400: + executable_mode |= 0o100 + if current_mode & 0o040: + executable_mode |= 0o010 + if current_mode & 0o004: + executable_mode |= 0o001 + if executable_mode != current_mode: + script_file.chmod(executable_mode) + + def _load_single(self, skill_file: Path) -> Skill: + try: + content = skill_file.read_text(encoding="utf-8") + except OSError as error: + raise SkillLoadError(f"Failed to read skill file: {skill_file}") from error + + matched = _FRONTMATTER_RE.match(content) + if matched is None: + raise SkillLoadError(f"Skill file missing YAML frontmatter: {skill_file}") + + frontmatter_raw, body = matched.group(1), matched.group(2) + try: + frontmatter = yaml.safe_load(frontmatter_raw) or {} + except yaml.YAMLError as error: + raise SkillLoadError(f"Skill frontmatter parse failed: {skill_file}") from error + + name = frontmatter.get("name") + description = frontmatter.get("description") + if not isinstance(name, str) or not name.strip(): + raise SkillLoadError(f"Skill name missing in file: {skill_file}") + if not isinstance(description, str) or not description.strip(): + raise SkillLoadError(f"Skill description missing in file: {skill_file}") + + return Skill( + name=name.strip(), + description=description.strip(), + content=body, + path=skill_file, + ) diff --git a/back/src/main/resources/build-assets/start-scripts/ai_employee.py b/back/src/main/resources/build-assets/start-scripts/ai_employee.py new file mode 100644 index 0000000..f3f25a3 --- /dev/null +++ b/back/src/main/resources/build-assets/start-scripts/ai_employee.py @@ -0,0 +1,123 @@ +"""AI 员工启动脚本(生产版本)。 + +职责:创建 momo-agent-sdk Worker 并启动任务消费循环。 +不负责 zzd 生命周期管理(由 start.sh 负责)。 + +环境变量: + WORKSTATION_ID - 必填,工位 ID + REDIS_URL - 可选,默认 redis://redis:6379 + CONFIG_FILE - 可选,默认 /opt/agent/config.json + LOG_LEVEL - 可选,默认 INFO +""" + +from __future__ import annotations + +import asyncio +import logging +import os +import signal +import sys + +from momo_agent_sdk.work import Worker + +# --------------------------------------------------------------------------- +# 日志配置 +# --------------------------------------------------------------------------- +_log_level = os.getenv("LOG_LEVEL", "INFO").upper() +logging.basicConfig( + level=getattr(logging, _log_level, logging.INFO), + format="%(asctime)s [%(levelname)s] %(name)s - %(message)s", + stream=sys.stdout, +) +logger = logging.getLogger("ai_employee") + + +# --------------------------------------------------------------------------- +# 信号处理 +# --------------------------------------------------------------------------- +_shutdown_event: asyncio.Event | None = None + + +def _handle_signal(signum: int, _frame: object) -> None: + """SIGTERM / SIGINT → 设置 shutdown event,让 asyncio 循环优雅退出。""" + sig_name = signal.Signals(signum).name + logger.info("收到信号 %s,准备优雅关闭...", sig_name) + if _shutdown_event is not None: + _shutdown_event.set() + + +# --------------------------------------------------------------------------- +# 主逻辑 +# --------------------------------------------------------------------------- +async def main() -> None: + global _shutdown_event + _shutdown_event = asyncio.Event() + + config_file = os.getenv("CONFIG_FILE", "/opt/agent/config.json") + workstation_id = os.getenv("WORKSTATION_ID", "") + + logger.info("========== AI 员工启动 ==========") + logger.info("工位 ID: %s", workstation_id or "未设置") + logger.info("Redis: %s", os.getenv("REDIS_URL", "redis://redis:6379 (默认)")) + logger.info("配置文件: %s", config_file) + logger.info("日志级别: %s", _log_level) + logger.info("==================================") + + if not workstation_id: + logger.error("WORKSTATION_ID 未设置,无法启动") + sys.exit(1) + + if not os.path.isfile(config_file): + logger.error("配置文件不存在: %s", config_file) + sys.exit(1) + + worker = Worker(config_file) + + # 将 Worker 循环和 shutdown 事件并行等待 + worker_task = asyncio.create_task(_run_worker(worker)) + shutdown_task = asyncio.create_task(_shutdown_event.wait()) + + done, pending = await asyncio.wait( + {worker_task, shutdown_task}, + return_when=asyncio.FIRST_COMPLETED, + ) + + # 取消尚未完成的任务 + for task in pending: + task.cancel() + try: + await task + except asyncio.CancelledError: + pass + + # 检查 worker 是否异常退出 + if worker_task in done: + exc = worker_task.exception() + if exc is not None: + logger.exception("Worker 异常退出: %s", exc) + sys.exit(1) + + logger.info("AI 员工已停止") + + +async def _run_worker(worker: Worker) -> None: + """运行 Worker 消费循环。""" + await worker.run_forever() + + +# --------------------------------------------------------------------------- +# 入口 +# --------------------------------------------------------------------------- +if __name__ == "__main__": + # 注册信号处理(在 asyncio 之前) + signal.signal(signal.SIGTERM, _handle_signal) + signal.signal(signal.SIGINT, _handle_signal) + + try: + asyncio.run(main()) + except KeyboardInterrupt: + logger.info("AI 员工已停止 (KeyboardInterrupt)") + sys.exit(0) + except Exception: + logger.exception("AI 员工异常退出") + sys.exit(1) diff --git a/back/src/main/resources/build-assets/start-scripts/start-dual.sh b/back/src/main/resources/build-assets/start-scripts/start-dual.sh new file mode 100755 index 0000000..fca8d60 --- /dev/null +++ b/back/src/main/resources/build-assets/start-scripts/start-dual.sh @@ -0,0 +1,398 @@ +#!/bin/bash +# ============================================================================= +# AI 员工双容器入口脚本(生产版本) +# +# 职责:SSH 密钥管理 → 启动 zzd (sandbox 模式) → AI 员工 Worker +# 参考:docs/docker.md §4.2 +# +# 必填环境变量: +# WORKSTATION_ID - 工位 ID +# REDIS_URL - Redis 连接地址 +# +# 可选环境变量: +# ZZD_RUNNER_HOST - Runner SSH 主机 (默认: localhost,同 Pod) +# ZZD_ENABLE_APPROVAL - 启用审批队列 +# ZZD_REDIS_ADDR - 审批队列 Redis 地址 +# LOG_LEVEL - 日志级别 (默认: INFO) +# ENABLE_NETWORK_FIREWALL - iptables 网络白名单开关 (true/1=启用, 默认关闭) +# ============================================================================= +set -e + +readonly CONFIG_FILE="/opt/agent/config.json" +readonly SOCKET_PATH="/var/run/zzd/zzd.sock" +readonly SOCKET_TIMEOUT=20 +readonly KEY_DIR="/etc/zzd/keys" +readonly SHARED_KEY_DIR="/shared-keys" +readonly RUNNER_HOST="${ZZD_RUNNER_HOST:-localhost}" +readonly RUNNER_SSH_TIMEOUT=60 +readonly WORKSPACE_GROUP="${WORKSPACE_GROUP:-workspace}" +readonly WORKSPACE_GID="${WORKSPACE_GID:-2000}" +WORKSPACE_EFFECTIVE_GROUP="${WORKSPACE_GROUP}" +readonly ZZD_FORCE_GIT_LOCAL_ROUTE="${ZZD_FORCE_GIT_LOCAL_ROUTE:-true}" +readonly RUNTIME_GIT_API_FALLBACK_URL="${RUNTIME_GIT_API_FALLBACK_URL:-http://tjwq-robot-web-002.tjwq.prod.momo.momo.com:18081}" + +# ============================================================================= +# 日志 +# ============================================================================= +log_info() { echo "[INFO] $(date '+%H:%M:%S') $*"; } +log_error() { echo "[ERROR] $(date '+%H:%M:%S') $*" >&2; } +log_warn() { echo "[WARN] $(date '+%H:%M:%S') $*"; } + +configure_runtime_git_token() { + if [[ -z "${ZZD_API_SERVER_URL:-}" && -n "${API_BASE_URL:-}" ]]; then + export ZZD_API_SERVER_URL="${API_BASE_URL}" + log_info "ZZD_API_SERVER_URL 未设置,回退使用 API_BASE_URL" + fi + + if [[ "${ZZD_API_SERVER_URL:-}" == *"api-gateway.ai-worker.svc"* ]]; then + log_warn "检测到不可达 API 地址 (${ZZD_API_SERVER_URL}),强制改写为 ${RUNTIME_GIT_API_FALLBACK_URL}" + export ZZD_API_SERVER_URL="${RUNTIME_GIT_API_FALLBACK_URL}" + fi + + if [[ -z "${ZZD_ENABLE_GIT_TOKEN:-}" && -n "${ZZD_API_SERVER_URL:-}" ]]; then + export ZZD_ENABLE_GIT_TOKEN="true" + fi + + local token_set="false" + if [[ -n "${ZZD_API_SERVER_TOKEN:-}" ]]; then + token_set="true" + fi + log_info "runtime git token: enabled=${ZZD_ENABLE_GIT_TOKEN:-false}, api=${ZZD_API_SERVER_URL:-未设置}, token_set=${token_set}" +} + +write_git_local_routing_config() { + mkdir -p /etc/zzd + local routing_file="/etc/zzd/routing.yaml" + cat > "${routing_file}" <<'YAML' +rules: + - match: ["git"] + target: local + reason: "git commands stay in agent container for runtime token injection" +YAML + chmod 0400 "${routing_file}" + chown root:root "${routing_file}" + log_info "zzd 路由已写入: ${routing_file} (git -> local)" +} + +resolve_python_bin() { + local configured="${PYTHON_BIN:-/usr/bin/python3.12}" + if [[ "${configured}" == */* ]]; then + if [[ -x "${configured}" ]]; then + echo "${configured}" + return 0 + fi + elif command -v "${configured}" >/dev/null 2>&1; then + command -v "${configured}" + return 0 + fi + + if command -v python3.12 >/dev/null 2>&1; then + command -v python3.12 + return 0 + fi + if command -v python3 >/dev/null 2>&1; then + command -v python3 + return 0 + fi + + log_error "未找到可用 Python 解释器(尝试 PYTHON_BIN/python3.12/python3)" + return 1 +} + +# ============================================================================= +# workspace 共享权限 (保持 agent/runner UID 隔离,通过共享 GID 协作) +# ============================================================================= +setup_workspace_group_permissions() { + local resolved_group="${WORKSPACE_GROUP}" + + if getent group "${WORKSPACE_GID}" >/dev/null 2>&1; then + resolved_group=$(getent group "${WORKSPACE_GID}" | cut -d: -f1) + elif ! getent group "${WORKSPACE_GROUP}" >/dev/null 2>&1; then + groupadd -g "${WORKSPACE_GID}" "${WORKSPACE_GROUP}" || { + log_error "创建 workspace 协作组失败 (${WORKSPACE_GROUP}:${WORKSPACE_GID})" + return 1 + } + fi + + usermod -aG "${resolved_group}" agent || { + log_error "将 agent 加入 workspace 协作组失败 (${resolved_group})" + return 1 + } + + for dir in /workspace /workspace/logs /workspace/user /workspace/workstation /workspace/task-logs /workspace/worker-logs; do + mkdir -p "${dir}" + chgrp -R "${resolved_group}" "${dir}" + chmod -R g+rwX "${dir}" + find "${dir}" -type d -exec chmod g+s {} + + chmod 2770 "${dir}" + done + WORKSPACE_EFFECTIVE_GROUP="${resolved_group}" + export WORKSPACE_EFFECTIVE_GROUP + + log_info "workspace 权限已对齐 (group=${resolved_group}, dirs=workspace/logs/user/workstation/task-logs/worker-logs, umask=0002)" + log_info "agent groups: $(id -nG agent 2>/dev/null || echo 'unknown')" +} + +# ============================================================================= +# 1. 启动前校验 +# ============================================================================= +log_info "================================================" +log_info " AI 员工 - 双容器模式 (ZZD_MODE=sandbox)" +log_info " WORKSTATION_ID: ${WORKSTATION_ID:-未设置}" +log_info " REDIS_URL: ${REDIS_URL:-未设置}" +log_info " ZZD_RUNNER_HOST: ${RUNNER_HOST}" +log_info " ZZD_ENABLE_APPROVAL: ${ZZD_ENABLE_APPROVAL:-未设置}" +log_info " ZZD_REDIS_ADDR: ${ZZD_REDIS_ADDR:-未设置}" +log_info " ZZD_FORCE_GIT_LOCAL_ROUTE: ${ZZD_FORCE_GIT_LOCAL_ROUTE}" +log_info " CONFIG_FILE: ${CONFIG_FILE}" +log_info "================================================" +log_info "Git wrapper 已禁用;runtime workspace 策略由 zzd fs_prepare/fs_cleanup 管控" + +if [ -z "$WORKSTATION_ID" ]; then + log_error "WORKSTATION_ID 未设置" + exit 1 +fi + +for cmd in zz zzd gen-key encrypt-key ssh-keygen ssh-keyscan; do + if ! command -v "$cmd" >/dev/null 2>&1; then + log_error "必需命令未找到: $cmd" + exit 1 + fi +done + +configure_runtime_git_token +if [[ "${ZZD_FORCE_GIT_LOCAL_ROUTE}" == "true" ]]; then + write_git_local_routing_config +else + log_warn "已关闭 Git 本地路由强制(ZZD_FORCE_GIT_LOCAL_ROUTE=${ZZD_FORCE_GIT_LOCAL_ROUTE})" +fi + +if [ ! -f "$CONFIG_FILE" ]; then + log_error "配置文件不存在: $CONFIG_FILE" + exit 1 +fi + +# ============================================================================= +# 1.5. 加载网络隔离防火墙(build.sh 生成的 iptables 规则,默认关闭) +# ============================================================================= +readonly FIREWALL_SCRIPT="/opt/agent/setup-firewall.sh" +if [[ "${ENABLE_NETWORK_FIREWALL}" == "true" || "${ENABLE_NETWORK_FIREWALL}" == "1" ]]; then + if [[ -x "${FIREWALL_SCRIPT}" ]]; then + log_info "加载网络隔离防火墙..." + if bash "${FIREWALL_SCRIPT}"; then + log_info "iptables 网络白名单已生效" + else + log_warn "iptables 加载失败(可能缺少 NET_ADMIN),继续启动" + fi + else + log_warn "ENABLE_NETWORK_FIREWALL 已启用但防火墙脚本不存在: ${FIREWALL_SCRIPT}" + fi +else + log_info "ENABLE_NETWORK_FIREWALL 未启用,跳过网络隔离 (agent 可访问全部网络)" +fi + +# ============================================================================= +# 2. SSH 密钥管理 +# ============================================================================= +log_info "SSH 密钥管理..." +mkdir -p "$KEY_DIR" + +# 生成 ZZD_MASTER_KEY(不全局 export,仅在需要的命令前内联注入) +log_info "gen-key → 生成 master key..." +GEN_KEY_OUTPUT=$(gen-key) +MASTER_KEY=$(echo "$GEN_KEY_OUTPUT" | sed -n '2p') +log_info "ZZD_MASTER_KEY 已生成 (长度=${#MASTER_KEY})" + +# 生成 SSH 密钥对 +log_info "生成 SSH ed25519 密钥对..." +rm -f /tmp/zzd_id_ed25519 /tmp/zzd_id_ed25519.pub +ssh-keygen -t ed25519 -f /tmp/zzd_id_ed25519 -N "" -q + +# 加密密钥(内联注入 MASTER_KEY,不污染全局环境) +log_info "encrypt-key → momobot_key.enc..." +ZZD_MASTER_KEY="$MASTER_KEY" encrypt-key -input /tmp/zzd_id_ed25519 -output "$KEY_DIR/momobot_key.enc" + +log_info "复制 momobot_key.enc → root_key.enc (相同密钥)" +cp "$KEY_DIR/momobot_key.enc" "$KEY_DIR/root_key.enc" + +# 导出公钥到共享卷供 Runner 读取 +mkdir -p "$SHARED_KEY_DIR" +cp /tmp/zzd_id_ed25519.pub "$SHARED_KEY_DIR/zzd_pubkey.pub" +chmod 644 "$SHARED_KEY_DIR/zzd_pubkey.pub" +log_info "公钥已导出: $SHARED_KEY_DIR/zzd_pubkey.pub" + +# 清理明文私钥 +rm -f /tmp/zzd_id_ed25519 + +# ============================================================================= +# 3. 等待 Runner SSH 就绪 +# ============================================================================= +log_info "等待 Runner SSH 就绪 ($RUNNER_HOST)..." +WAIT=0 +while ! ssh-keyscan -T 2 "$RUNNER_HOST" 2>/dev/null | grep -q .; do + WAIT=$((WAIT + 2)) + if [ $WAIT -ge $RUNNER_SSH_TIMEOUT ]; then + log_error "Runner SSH 未就绪 (${RUNNER_SSH_TIMEOUT}s 超时)" + exit 1 + fi + log_info "等待 Runner... (${WAIT}s)" + sleep 2 +done + +# 获取 Runner host key +log_info "ssh-keyscan → known_hosts..." +ssh-keyscan -H "$RUNNER_HOST" >> /etc/zzd/known_hosts 2>/dev/null + +# agent 用户也需要 known_hosts(调试用) +mkdir -p /root/.ssh /home/agent/.ssh +cp /etc/zzd/known_hosts /root/.ssh/known_hosts +cp /etc/zzd/known_hosts /home/agent/.ssh/known_hosts +chown -R agent:agent /home/agent/.ssh +log_info "known_hosts 已更新" + +# ============================================================================= +# 3.5. 对齐 workspace 协作权限(双容器共享卷写入) +# ============================================================================= +setup_workspace_group_permissions + +# ============================================================================= +# 4. 启动 zzd (sandbox 模式, root 运行, 内联注入 MASTER_KEY) +# ============================================================================= +log_info "启动 zzd (sandbox 模式)..." +ZZD_MODE=sandbox ZZD_MASTER_KEY="$MASTER_KEY" zzd & +ZZD_PID=$! + +# MASTER_KEY 已传递给 zzd 进程,从 shell 变量中清除 +unset MASTER_KEY + +# 等待 socket 就绪(sleep 1 × SOCKET_TIMEOUT 次 = 实际超时秒数) +WAIT=0 +while [ ! -S "$SOCKET_PATH" ] && [ $WAIT -lt $SOCKET_TIMEOUT ]; do + sleep 1 + WAIT=$((WAIT + 1)) +done + +if [ ! -S "$SOCKET_PATH" ]; then + log_error "zzd socket 未就绪 (${SOCKET_TIMEOUT}s 超时)" + kill $ZZD_PID 2>/dev/null + exit 1 +fi + +# socket 权限:仅 root + agent 组可读写 (docs/zzd/zzd.md §安全机制) +chown root:agent /var/run/zzd +chmod 0750 /var/run/zzd +chown root:agent "$SOCKET_PATH" +chmod 0660 "$SOCKET_PATH" +log_info "zzd 就绪 (pid=$ZZD_PID, socket=$SOCKET_PATH, 0660 root:agent)" + +# ============================================================================= +# 5. 启动 AI 员工 (agent 用户, 白名单环境变量) +# ============================================================================= +log_info "启动 AI 员工 (user=agent)..." +log_info "================================================" + +PYTHON_BIN_PATH="$(resolve_python_bin)" +log_info "worker python: ${PYTHON_BIN_PATH} ($("${PYTHON_BIN_PATH}" --version 2>&1))" + +# 安全: 不使用 sudo -E,仅传递 ai_employee.py + SDK 所需的非敏感变量 +# ZZD_MASTER_KEY 已 unset,不会泄露给 agent 进程 +sudo -u agent -g "${WORKSPACE_EFFECTIVE_GROUP}" \ + env \ + WORKSTATION_ID="${WORKSTATION_ID}" \ + REDIS_URL="${REDIS_URL:-}" \ + CONFIG_FILE="${CONFIG_FILE}" \ + LOG_LEVEL="${LOG_LEVEL:-INFO}" \ + IDLE_TIMEOUT="${IDLE_TIMEOUT:-}" \ + WORKER_DESTROY_API_BASE="${WORKER_DESTROY_API_BASE:-}" \ + WORKER_DESTROY_API_PASSWORD="${WORKER_DESTROY_API_PASSWORD:-}" \ + POD_NAME="${POD_NAME:-}" \ + SERVICE_ID="${SERVICE_ID:-}" \ + PYTHON_BIN="${PYTHON_BIN_PATH}" \ + PYTHON="${PYTHON_BIN_PATH}" \ + UV_PYTHON="${PYTHON_BIN_PATH}" \ + PATH="${PATH}" \ + HOME="/home/agent" \ + bash -lc 'umask 0002; exec "${PYTHON_BIN}" /opt/agent/ai_employee.py' & +WORKER_PID=$! + +# 终止 worker 进程树(WORKER_PID 指向 sudo 包装进程,不一定等于实际 Python 进程) +terminate_worker_tree() { + local wait_sec=0 + local child_pids + child_pids=$(ps -o pid= --ppid "$WORKER_PID" 2>/dev/null | tr '\n' ' ') + + # 优先终止 sudo 子进程(ai_employee.py),再终止 sudo 自身 + if [[ -n "${child_pids}" ]]; then + kill -TERM ${child_pids} 2>/dev/null || true + fi + kill -TERM -- "-$WORKER_PID" 2>/dev/null || kill -TERM "$WORKER_PID" 2>/dev/null || true + + while [ $wait_sec -lt 5 ]; do + local alive=0 + if kill -0 "$WORKER_PID" 2>/dev/null; then + alive=1 + fi + for pid in ${child_pids}; do + if kill -0 "$pid" 2>/dev/null; then + alive=1 + break + fi + done + if [ $alive -eq 0 ]; then + break + fi + sleep 1 + wait_sec=$((wait_sec + 1)) + done + + if kill -0 "$WORKER_PID" 2>/dev/null; then + log_warn "Worker 未在 5s 内退出,发送 SIGKILL..." + if [[ -n "${child_pids}" ]]; then + kill -KILL ${child_pids} 2>/dev/null || true + fi + kill -KILL -- "-$WORKER_PID" 2>/dev/null || kill -KILL "$WORKER_PID" 2>/dev/null || true + sleep 1 + fi + + wait "$WORKER_PID" 2>/dev/null || true +} + +# ============================================================================= +# 6. 信号处理 + 进程监控 +# ============================================================================= +cleanup() { + log_info "收到停止信号,清理子进程..." + terminate_worker_tree + kill $ZZD_PID 2>/dev/null || true + wait $ZZD_PID 2>/dev/null || true + # 写 shutdown 标记,通知 Runner 容器退出(K8s 1.18 无原生 sidecar 支持) + echo "agent_exit" > "${SHARED_KEY_DIR}/shutdown" 2>/dev/null || true + log_info "清理完成,已写入 shutdown 标记" +} +trap cleanup EXIT SIGTERM SIGINT + +# 同时监控 zzd 和 worker(wait -n 等待任一子进程退出,bash 4.3+) +# - zzd 先退出 → 错误,终止 worker,容器非零退出 +# - worker 先退出 → 正常语义,返回 worker 退出码 +# 注意: 整个监控块保持 set +e,避免对已回收 PID 再 wait 时触发 set -e +set +e +wait -n $WORKER_PID $ZZD_PID 2>/dev/null +FIRST_EXIT=$? + +if ! kill -0 $ZZD_PID 2>/dev/null; then + # zzd 先退出 — 异常(FIRST_EXIT 是 zzd 的退出码,或两者几乎同时退出) + log_error "zzd 进程已退出 (code=$FIRST_EXIT),终止 Worker..." + terminate_worker_tree + exit 1 +fi + +if ! kill -0 $WORKER_PID 2>/dev/null; then + # worker 先退出 — 正常语义(FIRST_EXIT 是 worker 的退出码) + log_info "Worker 退出 (code=$FIRST_EXIT)" + # cleanup trap 会在 exit 时自动触发,写入 shutdown 标记通知 Runner + exit $FIRST_EXIT +fi + +# 兜底: wait -n 返回但两个进程都还活着(理论上不应发生) +log_error "进程监控状态异常 (FIRST_EXIT=$FIRST_EXIT, zzd_pid=$ZZD_PID, worker_pid=$WORKER_PID)" +exit 1 diff --git a/back/src/main/resources/build-assets/start-scripts/start-single.sh b/back/src/main/resources/build-assets/start-scripts/start-single.sh new file mode 100755 index 0000000..70efbbf --- /dev/null +++ b/back/src/main/resources/build-assets/start-scripts/start-single.sh @@ -0,0 +1,305 @@ +#!/bin/bash +# ============================================================================= +# AI 员工单容器入口脚本(生产版本) +# +# 职责:准备 workspace 基线目录 → 启动 zzd (local 模式) → AI 员工 Worker +# 参考:docs/docker.md §4.1 +# +# 必填环境变量: +# WORKSTATION_ID - 工位 ID +# REDIS_URL - Redis 连接地址 +# +# 可选环境变量: +# LOG_LEVEL - 日志级别 (默认: INFO) +# ENABLE_NETWORK_FIREWALL - iptables 网络白名单开关 (true/1=启用, 默认关闭) +# ============================================================================= +set -e + +readonly CONFIG_FILE="/opt/agent/config.json" +readonly SOCKET_PATH="/var/run/zzd/zzd.sock" +readonly SOCKET_TIMEOUT=20 +readonly WORKSPACE_GROUP="${WORKSPACE_GROUP:-workspace}" +readonly WORKSPACE_GID="${WORKSPACE_GID:-2000}" +WORKSPACE_EFFECTIVE_GROUP="${WORKSPACE_GROUP}" +readonly RUNTIME_GIT_API_FALLBACK_URL="${RUNTIME_GIT_API_FALLBACK_URL:-http://tjwq-robot-web-002.tjwq.prod.momo.momo.com:18081}" + +# ============================================================================= +# 日志 +# ============================================================================= +log_info() { echo "[INFO] $(date '+%H:%M:%S') $*"; } +log_error() { echo "[ERROR] $(date '+%H:%M:%S') $*" >&2; } +log_warn() { echo "[WARN] $(date '+%H:%M:%S') $*"; } + +configure_runtime_git_token() { + if [[ -z "${ZZD_API_SERVER_URL:-}" && -n "${API_BASE_URL:-}" ]]; then + export ZZD_API_SERVER_URL="${API_BASE_URL}" + log_info "ZZD_API_SERVER_URL 未设置,回退使用 API_BASE_URL" + fi + + if [[ "${ZZD_API_SERVER_URL:-}" == *"api-gateway.ai-worker.svc"* ]]; then + log_warn "检测到不可达 API 地址 (${ZZD_API_SERVER_URL}),强制改写为 ${RUNTIME_GIT_API_FALLBACK_URL}" + export ZZD_API_SERVER_URL="${RUNTIME_GIT_API_FALLBACK_URL}" + fi + + if [[ -z "${ZZD_ENABLE_GIT_TOKEN:-}" && -n "${ZZD_API_SERVER_URL:-}" ]]; then + export ZZD_ENABLE_GIT_TOKEN="true" + fi + + local token_set="false" + if [[ -n "${ZZD_API_SERVER_TOKEN:-}" ]]; then + token_set="true" + fi + log_info "runtime git token: enabled=${ZZD_ENABLE_GIT_TOKEN:-false}, api=${ZZD_API_SERVER_URL:-未设置}, token_set=${token_set}" +} + +resolve_python_bin() { + local configured="${PYTHON_BIN:-/usr/bin/python3.12}" + if [[ "${configured}" == */* ]]; then + if [[ -x "${configured}" ]]; then + echo "${configured}" + return 0 + fi + elif command -v "${configured}" >/dev/null 2>&1; then + command -v "${configured}" + return 0 + fi + + if command -v python3.12 >/dev/null 2>&1; then + command -v python3.12 + return 0 + fi + if command -v python3 >/dev/null 2>&1; then + command -v python3 + return 0 + fi + + log_error "未找到可用 Python 解释器(尝试 PYTHON_BIN/python3.12/python3)" + return 1 +} + +# ============================================================================= +# workspace 共享权限与目录基线 +# ============================================================================= +setup_workspace_group_permissions() { + local resolved_group="${WORKSPACE_GROUP}" + + if getent group "${WORKSPACE_GID}" >/dev/null 2>&1; then + resolved_group=$(getent group "${WORKSPACE_GID}" | cut -d: -f1) + elif ! getent group "${WORKSPACE_GROUP}" >/dev/null 2>&1; then + groupadd -g "${WORKSPACE_GID}" "${WORKSPACE_GROUP}" || { + log_error "创建 workspace 协作组失败 (${WORKSPACE_GROUP}:${WORKSPACE_GID})" + return 1 + } + fi + + usermod -aG "${resolved_group}" agent || { + log_error "将 agent 加入 workspace 协作组失败 (${resolved_group})" + return 1 + } + + for dir in /workspace /workspace/logs /workspace/user /workspace/workstation /workspace/task-logs /workspace/worker-logs; do + mkdir -p "${dir}" + chgrp -R "${resolved_group}" "${dir}" + chmod -R g+rwX "${dir}" + find "${dir}" -type d -exec chmod g+s {} + + chmod 2770 "${dir}" + done + WORKSPACE_EFFECTIVE_GROUP="${resolved_group}" + export WORKSPACE_EFFECTIVE_GROUP + + log_info "workspace 权限已对齐 (group=${resolved_group}, dirs=workspace/logs/user/workstation/task-logs/worker-logs, umask=0002)" + log_info "agent groups: $(id -nG agent 2>/dev/null || echo 'unknown')" +} + +# ============================================================================= +# 1. 启动前校验 +# ============================================================================= +log_info "================================================" +log_info " AI 员工 - 单容器模式 (ZZD_MODE=local)" +log_info " WORKSTATION_ID: ${WORKSTATION_ID:-未设置}" +log_info " REDIS_URL: ${REDIS_URL:-未设置}" +log_info " CONFIG_FILE: ${CONFIG_FILE}" +log_info "================================================" +log_info "Git wrapper 已禁用;runtime workspace 策略由 zzd fs_prepare/fs_cleanup 管控" + +if [ -z "$WORKSTATION_ID" ]; then + log_error "WORKSTATION_ID 未设置" + exit 1 +fi + +if ! command -v zz >/dev/null 2>&1; then + log_error "zz 二进制未找到" + exit 1 +fi + +if ! command -v zzd >/dev/null 2>&1; then + log_error "zzd 二进制未找到" + exit 1 +fi + +configure_runtime_git_token + +if [ ! -f "$CONFIG_FILE" ]; then + log_error "配置文件不存在: $CONFIG_FILE" + exit 1 +fi + +# ============================================================================= +# 1.5. 加载网络隔离防火墙(build.sh 生成的 iptables 规则,默认关闭) +# ============================================================================= +readonly FIREWALL_SCRIPT="/opt/agent/setup-firewall.sh" +if [[ "${ENABLE_NETWORK_FIREWALL}" == "true" || "${ENABLE_NETWORK_FIREWALL}" == "1" ]]; then + if [[ -x "${FIREWALL_SCRIPT}" ]]; then + log_info "加载网络隔离防火墙..." + if bash "${FIREWALL_SCRIPT}"; then + log_info "iptables 网络白名单已生效" + else + log_warn "iptables 加载失败(可能缺少 NET_ADMIN),继续启动" + fi + else + log_warn "ENABLE_NETWORK_FIREWALL 已启用但防火墙脚本不存在: ${FIREWALL_SCRIPT}" + fi +else + log_info "ENABLE_NETWORK_FIREWALL 未启用,跳过网络隔离 (agent 可访问全部网络)" +fi + +# ============================================================================= +# 1.6. 对齐 workspace 协作权限(目录基线) +# ============================================================================= +setup_workspace_group_permissions + +# ============================================================================= +# 2. 启动 zzd (local 模式, root 运行) +# ============================================================================= +log_info "启动 zzd (local 模式)..." +export ZZD_MODE=local +zzd & +ZZD_PID=$! + +# 等待 socket 就绪(sleep 1 × SOCKET_TIMEOUT 次 = 实际超时秒数) +WAIT=0 +while [ ! -S "$SOCKET_PATH" ] && [ $WAIT -lt $SOCKET_TIMEOUT ]; do + sleep 1 + WAIT=$((WAIT + 1)) +done + +if [ ! -S "$SOCKET_PATH" ]; then + log_error "zzd socket 未就绪 (${SOCKET_TIMEOUT}s 超时)" + kill $ZZD_PID 2>/dev/null + exit 1 +fi + +# socket 权限:仅 root + agent 组可读写 (docs/zzd/zzd.md §安全机制) +chown root:agent /var/run/zzd +chmod 0750 /var/run/zzd +chown root:agent "$SOCKET_PATH" +chmod 0660 "$SOCKET_PATH" +log_info "zzd 就绪 (pid=$ZZD_PID, socket=$SOCKET_PATH, 0660 root:agent)" + +# ============================================================================= +# 3. 启动 AI 员工 (agent 用户, 白名单环境变量) +# ============================================================================= +log_info "启动 AI 员工 (user=agent)..." +log_info "================================================" + +PYTHON_BIN_PATH="$(resolve_python_bin)" +log_info "worker python: ${PYTHON_BIN_PATH} ($("${PYTHON_BIN_PATH}" --version 2>&1))" + +# 安全: 不使用 sudo -E,仅传递 ai_employee.py + SDK 所需的非敏感变量 +sudo -u agent -g "${WORKSPACE_EFFECTIVE_GROUP}" \ + env \ + WORKSTATION_ID="${WORKSTATION_ID}" \ + REDIS_URL="${REDIS_URL:-}" \ + CONFIG_FILE="${CONFIG_FILE}" \ + LOG_LEVEL="${LOG_LEVEL:-INFO}" \ + IDLE_TIMEOUT="${IDLE_TIMEOUT:-}" \ + WORKER_DESTROY_API_BASE="${WORKER_DESTROY_API_BASE:-}" \ + WORKER_DESTROY_API_PASSWORD="${WORKER_DESTROY_API_PASSWORD:-}" \ + POD_NAME="${POD_NAME:-}" \ + SERVICE_ID="${SERVICE_ID:-}" \ + PYTHON_BIN="${PYTHON_BIN_PATH}" \ + PYTHON="${PYTHON_BIN_PATH}" \ + UV_PYTHON="${PYTHON_BIN_PATH}" \ + PATH="${PATH}" \ + HOME="/home/agent" \ + bash -lc 'umask 0002; exec "${PYTHON_BIN}" /opt/agent/ai_employee.py' & +WORKER_PID=$! + +# 终止 worker 进程树(WORKER_PID 指向 sudo 包装进程,不一定等于实际 Python 进程) +terminate_worker_tree() { + local wait_sec=0 + local child_pids + child_pids=$(ps -o pid= --ppid "$WORKER_PID" 2>/dev/null | tr '\n' ' ') + + # 优先终止 sudo 子进程(ai_employee.py),再终止 sudo 自身 + if [[ -n "${child_pids}" ]]; then + kill -TERM ${child_pids} 2>/dev/null || true + fi + kill -TERM -- "-$WORKER_PID" 2>/dev/null || kill -TERM "$WORKER_PID" 2>/dev/null || true + + while [ $wait_sec -lt 5 ]; do + local alive=0 + if kill -0 "$WORKER_PID" 2>/dev/null; then + alive=1 + fi + for pid in ${child_pids}; do + if kill -0 "$pid" 2>/dev/null; then + alive=1 + break + fi + done + if [ $alive -eq 0 ]; then + break + fi + sleep 1 + wait_sec=$((wait_sec + 1)) + done + + if kill -0 "$WORKER_PID" 2>/dev/null; then + log_warn "Worker 未在 5s 内退出,发送 SIGKILL..." + if [[ -n "${child_pids}" ]]; then + kill -KILL ${child_pids} 2>/dev/null || true + fi + kill -KILL -- "-$WORKER_PID" 2>/dev/null || kill -KILL "$WORKER_PID" 2>/dev/null || true + sleep 1 + fi + + wait "$WORKER_PID" 2>/dev/null || true +} + +# ============================================================================= +# 4. 信号处理 + 进程监控 +# ============================================================================= +cleanup() { + log_info "收到停止信号,清理子进程..." + terminate_worker_tree + kill $ZZD_PID 2>/dev/null || true + wait $ZZD_PID 2>/dev/null || true + log_info "清理完成" +} +trap cleanup EXIT SIGTERM SIGINT + +# 同时监控 zzd 和 worker(wait -n 等待任一子进程退出,bash 4.3+) +# - zzd 先退出 → 错误,终止 worker,容器非零退出 +# - worker 先退出 → 正常语义,返回 worker 退出码 +# 注意: 整个监控块保持 set +e,避免对已回收 PID 再 wait 时触发 set -e +set +e +wait -n $WORKER_PID $ZZD_PID 2>/dev/null +FIRST_EXIT=$? + +if ! kill -0 $ZZD_PID 2>/dev/null; then + # zzd 先退出 — 异常(FIRST_EXIT 是 zzd 的退出码,或两者几乎同时退出) + log_error "zzd 进程已退出 (code=$FIRST_EXIT),终止 Worker..." + terminate_worker_tree + exit 1 +fi + +if ! kill -0 $WORKER_PID 2>/dev/null; then + # worker 先退出 — 正常语义(FIRST_EXIT 是 worker 的退出码) + log_info "Worker 退出 (code=$FIRST_EXIT)" + exit $FIRST_EXIT +fi + +# 兜底: wait -n 返回但两个进程都还活着(理论上不应发生) +log_error "进程监控状态异常 (FIRST_EXIT=$FIRST_EXIT, zzd_pid=$ZZD_PID, worker_pid=$WORKER_PID)" +exit 1 diff --git a/back/src/main/resources/build-assets/zzd-binaries/encrypt-key b/back/src/main/resources/build-assets/zzd-binaries/encrypt-key new file mode 100755 index 0000000000000000000000000000000000000000..df05d440046d909cae84ac0e7c960c9161e86d1a GIT binary patch literal 2807760 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%0D6hF7J+1 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 z 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%0A^S8AROIe@`Z((cM>iQZ-{NKDS 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�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

uUG+jm*n-AEn#w9)WAecDP5G+D$; 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

IR+oZlCdQcDoYy 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 z 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

uZr1DC3o^Jw#-_GdX?ir zwN@LDw~{f8l~`UJ>lNltO9Up%s^eo+z5FR%&;2Cr z>|?x6pSq7Osi;ILPpg{W9fPD zf!6tNQH80!(n91~<^&Nr#oBxf)pIyD zJIkFJOs*k(=J-&#N1ztV{p2hmx2OD&_uL_@GX930(P`aHXYW)QcE#HPUtbfGQ z8RWO!Z&)MyhgoD{X3+689fh5xXUmm6rLx7%&GE!2RErAX+mz9bKvZ(&JSX2ht=BMv?8 z&E_5^gI|Xs3F=lE^_)?X^%9x>_3l0V>2-5Y+KdP#vW-X}!gHCvPkQXm|FnxL#s=h4 zTp=O4W1w#Z?bzKGl^^Mzr5o}@R(T;Y`ZGM*u~M=RqL*`webQCQqAz4cF&A=1lro?h zR>8k@ZMAm5XR_!mb^;=|%|)U0tsl?`Zb1Y*^F|2pREY8*X9160wn@4AUm_X z(y~d0chYZU5#$HySx8d&1i8*?@ShMxnXbvTU1Tz=8gSaQ%yFOXQ74E$fTD*RyGn^` z@(l>313a}wsRr`3I#AXlL!K^1dqyxjtP78MB2<*03l{rBZvudYDw)lOylc zb@{ZrPE{3Dw1GwxCY#da#0n%Kn zob!fE_R2XcA(Pqi<@TV`AirF8y}&L>@* z2Wq3Be=<~C;;m1Xal#j(O3#OUzh|ee$r}_!VUfbp$BE7wd0P>|lZeQ--jK0T`I#%q z!Juc${7t$cQ=?D^|3M+;m{PhaYWZ|oyg$-cWsq+2L)zkO0GoDQm;ZA~cZs&*kD^3R zzeh-!qlDpKBFT6h8>KZLufWu%SleQ6+*!IFqFyX~OCdLOsD1GdhIb5yooeF@C;{vx zo#ep{$Ua8DSVqVBA((>KgFJ@@CedP@r}%d|xH8;g@WQ5zM-mU@F`K^$KsF}pJkabz*)JvXns;*vA8Yhun}`3wf`7r`(jwRk;~HoTuyZXeX~mZW->?b@^ixpC8L9 z*R$o(9(LJJ#VfyUFMw>!tu=;0UTZIw|4`+LzyxldKkni7bJO5*S<#d)_vEq@suv`o%qrv}a)xTx^jbr&qAk;8$TdUqkdVh)^JUv$FHDWb@~#}M z-pL~09b%nj_c)C2vkE>pIC{o724SEi zVO=}P$Q~n;RAb3x7>%n?wh$3qWkvfS$*<-?i2eL<(&DKO$$r1eeycT0)+N;9@PND4 zVczZ?q&Da0+6r(AS;o5tm6Mr+{Hl#g1x&hwS>%;A>JIb$Hue4+>)m36elTdPRcdX* zo`-)q!pn&w&$U&;scbV#LY~c3b~oQ;rv*tjV{l@L^(Xlpok>*sX@WJh*dBiXWz?Xt*W1CJtIFFOuHF}1$T91I$R!PZ|b_NOYmyj zfNF*Z|G_&jnDl`hz#dMYo|V{$QJT%f_x3?}lMvuj4hJXLJ3?k8P<$PL@z7EI4*(UN zPVFm(+B59et#R2j*bDhpYd+-e!8P#7ORfD*5vJTRcwc4>adkNmUbP^!L*5uXR!O@@Fkfbn7g>AQUFiZv zHrX?TeJjIQOg^?xmurVwb9bRNcQ3am@B2e)C?d&p$F6RNAn%W`r{{;wYI>bDXSXIvy?1~$v2k6Khmm=f~R{F zMn_unetBP>?~lwt=e3w3#Pt+csL?juOX(M5n0U_6v*j1@QXU6%LzWK>(Pyf3`60tY zJaA99&Koqpj(hO|^d!BU9#8zbAA+4ql+2yqOO$AO7!*&`z< zSgYpPR7BAsx5qiI49LUD0<|1+E50l3w}#2Bs%naTOvulhDpji=9vRB)XR?4pB_Z!L zkrH9Kc4Q4jA-;^9uRiS%;^U(%LeTv+z2Gdemb70t`gKD#9m##^r;Ca9w!GRxgbVzN zMQs38Eq-R1aXiD**`b2O`sGdqtB&rgU;@HtPDi1hExTHIF7WBPyuFk{3j^}gBfZ?Q z-)t@5+Ybka+F@c7!f1qF)e$}?bF*LZZ3q0v7I<-YLVmZ9s!MY7(O$W|JzrkS9eW0< za3aieafW=aTWtexXSGN#5eDN^=xmgK+MY4UU5^--^5>pm5CN9hrab4`x{$5sI z?sK>ohWs_PpOswcZXwHr0JQJAKjYl~$5BXNR7N_s{1T_^DzLhj)D4c@d7 z3#O1kF5xM6I;5v=E|Z$-DkN_`?&ewI!VN0L&lG7}BbRm8 z$v?89XG*#lw5jBxH4*07>lz@Jb{0PC(98HNmFcY5Hf`0q8jIh_Vbgn;C(qOs=#eh;EX0jw6E`%&MxVx!&*rn z>K=aA6*eghdCZQ_&sD97-JnJfx8@ykc{|r#*`xSq`Gt!Ui+hXQpPnVpa7(7iHsdEY zvDdq}x+W-lT174}L`BbsiE;I@oxt_l0 zsv$eCyXAw6@)^b;<4JOPCL{4QsGCzmS6Xs!x|pvw4Jjwzq`T#vizx9lAm1C}C9-c_ zL^_&C;$Do(;ObguS9^G9e;T2nu>#1o%?hCWc?2!#-H>(7cJnihSZ+hpb@>?0+qB+vgOlQKjGfaEjNoij0 z+WUgRU>X0_2Q&X^$GY6^F+(ksM#y1JlX3=xl4HxUpt+6yZ~mR;{5z&24f7fYRIcsc zG}^HZ4F>GvYku>Rf#8>DY8;?TFo*E{jVi2>arEKrV%Rw)3*-H)S{y?cIh;|9mDph$ z#T@$Pl+;L9LUkVWwuX6@TgI%6F^OXUlcm1(8Ng)O#EpE>0OpVn{S9Ca`EZBT$d`n0j?esqThW273r25Y)5fS#2!g$&Ztj z+yI`}cx60F*W~`BU(c3jv?v;NO*>V#B=_TM4EXC2z1R2|euuTF$69Tdd(YHqQ}vOM z7OGMiK3$U|DP5MfryWM`dTUwIHTkL1+htP>d3r>YbwfK|9%$ar);%+#h}+rY&6Hpc z3F#u6Mnvz@HF==fFJo#XTE|>ZjaYBV@gHb@S{GHw2RS>OOWm%;z^3>pR)!YYK>vR#lQ`cy}PHh>zTp=e~jV|(#o8&tYz6qGBcm}HO zDQZ4NH5p#JQ8D+aRI&0suoz*`y^lbClH0;?uIkc6gWTJ$6qKINEyC%x6!`9TcF@jT zo@ebo+oY0y-9PCL7L-?1QZ+9l?rB$?^3QUkWBzpo@DP`sxnujr#lNmY9|5_deU?S* zBU|(l^mm|C=thQkhR{V;jBH_?&k=FjJ$omhK@p6?>HG6N9YWAliqGNaxRvJ)N*3VVel`5;9boiRBYF3RT z(`{$m(^Q~~T-v|v7iuOkUT!M3h;cNDv55F;ko*iWF3w%wN8Zod>#TZK=JK{j;r>~> zvVk~|RHx|F1H%ay0LK)n`t0)$k z$0EAOx)C9(xBuL1Egf>*h!)cGPinE1_l~4QL>_3Q@MA2s_oD16%HwqK8bbQ6A%1>j z4GARIHCC!MXx)f?1Bb0*79I{Z=}6eKsJ?5Wa4}oJQ|L5F?bk)-sE%Dfg06bL(KiC# zZJtFOzdNFcSE|+|`hBxsA0e?y(5IPc(Q7p6b)ikKqZGZ+D5(NSK3ByEEb`8>$y>tg z->|cvq_WF}F^cTQwjjXl%Ja|786;=y=ILiKbqVFp!PM6Nbt`u>xpOePF`{csP?7so zUqU;FC3KPc7~~B@2_9de-QM>*wm~koZV`6C7V64D<0&4CcJPlp-?5FqJ8UmMueUQm zls4g@gz`1pMNq{?XSceP>M`(w{4$NFGUbhKoZ^B&9B3d!OU#0^79Y;2lK`jorbH5tglPn{59}e6v+KO>HJeWiBL8eis z{}8a3IA9CDh~?u7?2i>#-iuHGVPpBTHa||MEEQ_w*fwZ|O*N2oFm~vg{G`nx+4kEV zl9f@A(+Fc70(b=hoC~eEf6Tl8uyf|g(=-nFCRG{6?8T|@a#KrF*QiY%r5+~a=UPY} zRF58nQ?2r}()KZ_aCoz_Hx0rk>8+p5yNmaqCx9j))Sh?^k&6%1--X36<^JR>?KJs? zwokSs3Eb57y5$*dpKMLm&6zewp4FoKQeOe;H)=m}tGtJP+9Dz z`(}qEe4KKgas1h@3bb!gvp0SmD_)m)}!Ax4vP{ z-08SLIoI$lzo-75`njN<%WU|Nh9qLrxasqWn1cvbm%%)@L@UdH+@e5=7Sc{TK*G>{ zhW2Ckf%>`iFs{Ku>DeYtYYb}lMHqMjMCi=VgZ0p^qO< zp8$`1oLW-|t&BA`T9+W&;sZhL*aJbMH3+%N{W|31R18|^0OT^a7xItPN;~6%13}Fr zm$>)Ir>QzaE_D~lKT>rBE`Lw?gV3S}P{rA=MGgeDA}42stagWF53?Au$~_hn&rx<) z_sVkjST@gcjB~(_W$^>|VUNcc*zq_%kW*l+wKd+F7)Xi40KI20|G(pa(RV(8%!8aAbbb^22TEgXdy{g;v$&16y|C0j;9*#-tU82 z^0&j}?PXF@*1Fm?$P$p;q#bqy7y}yt8Pd&bOvL85{a1FbQc0>l*nK7JwI5~=yn31DoFL4}GWMztFEoqIsZz?M??>`n?x#R~bVhThmqVYOXB8;oH1Tuz(13t6ydfuqV9;&kOkP%80~0r6 zGUkmJDVH27w0sB|R3vT1GOv;vaFkP+s8*1Dm}%J!;M?$R!s)0oM*lFVoq9md_xr^R zNuoq`>15+w0Wh#T(`X;w=6Qe^dYV#-(&Lkb5SG4rO z`2ATVf=>}ip0b@1TJPKWWvcqJm<+F#%3P~5&u((Xc6y>;o|B$}N1|P_tV{AkQrm^3 zoR{DK7OVFer?-EgcHMG(hA`UnJo!;NpUN1zX4~@e&c=sx5SRvf?ej2UGOF}Exg$+u z!e6;UiU$eM{`mW8<$P7?3`$!}6kjTzxO$b2brlNP_;qAr+h!5e0asM6OcxyTWv8na zjKD?%Q7?`Us^@u-Kf6LY+nGGEu^08M`|0jqS#YGtFiN9)zWmtw`m z1XyCPRs@F2Uy=69=Tyby!(Nw{3K%8=a#gxiK6b5F>gtlBbBG86a!tBaK6I_%Q5tI@ z>(YLa6%(gIK5#`Rql#CthFq5J5?LXCc6oWkT1&5GMzYVCw)c65*slpU6*;jSGJ6BU z2+9d#QH9mK`7v(S-FT@nR0f zpIeUuXoPk(c{^wk>O^Ww)!)gx;XAh;+R5texZQF?x=!A6l_MfIq&LaCu24N6pq;5Q zjHg0v5ax$-5uAv|TdVy>=c?M3zMLd;2ZNDt9FBfEAJ! zw6HwsDwl`T#qy+UEUilJNyn&H8b+W5@-mCxkbYW}qdh6&nmQ2)oI3F&PRm=ru9ObF*`iNmtd0KZ zs>qi*1@gUgX@AYRac?@q|BkGF4)!bh(%)55QdI1>@ja|@?-7*8PZjoElr7kQ~WU(b@yTw@WqO-a&6(du2N z-4P9#>?p>|cR9H2&dP&lGvxzHgi()xxYw)KYDi^)>Iiy@^gzzaN2dQz>(d%>y=B2t zJ|ukPJd{f4<=GbVz4Sko`Cb^0#^$vTC|(hn;(@TX%>QpN(GEw;@&l>QDQGdf|5j0( zEQx=su)~5MHt?+tntZK0-eO&@^|9b>UDg?|?_}4`J`ANEx3Pp}h{jNTEdtG^!rw~IfjKgyCE2C&zqBZQM`q7c(F#mB>MulhGJJvMO zHMyQb(K2|5%BQKVhoJJZOX0F9O=N!KA~rXsV_X6L;PO(Yb3BzZ_PaX~=AOb2^m0Ez z37O8}EL}!GLW|+s{_9Qi9XtffcMviEm)95g_m|mqU!wll&en6}&h&<Ij8RCAOjNsRHtH365iA{1j=z0th+)D>5 z_fEIIeU_q5=qg>4OWj=a*UI8gGQz&)e{zu6tzX{~z5;=B zF2Tm5Gv(?(aLRp{DQ8gLG8I!`j6vX>A7Y1-yW&G9_a8_*ovxj>>e<$wRE-I`CgVaG z!u0m#zJwwl5eDNoBA9X#Ly#+d$>+&vHNvMAeK!4NkWpWY3gg2c;&z0w5#%yfX`^;S z1!k6uXdvT-Eb-5fBfL0E+A0+@;Tk3AAHBRnbLMt8Yyea8T}ach0MrGx5x& zban?S7aI}j%Fi(T%Rh2jwTaFyY;VM~m9DjkxHi#JEpmNq<;y+P$FQBMXUVy<-Qlni2gg>e1lBNcW1#);HJtL0PGdxI^BbU^jCnRDGt@D-a(cR}H zHoG5+20Mxf7Q9-Fo`RC?%F)K% zf3D>LSENWq;_&gfjfaj_YZ8s{`ZAc*M7OQbdi#=3Q{_CX=hjbFAyDeaWy-o9aDbO9 zUMxVMQay@`F-as*a=I%BQngn3xPoPED+m<7oFm zUH?VXc;i5elL*BHVL7%YvwJOn(81uBWCzJW&Gd`ci%~Me4>=^l(nF9`F{HE;K8B(% z4Jm0(0P=NZOe)1SL^}g|*F!dFO$sg93B^dO;n7D@6`xzynxr4TahFly*%19>96&%G z&+$SYUg}o@!yP#xd1NUqfKNaG-$!63waosgb>d`lSGp#bC$}nip|%P_ygW%x_H14| zXYiy*lT71|aTu(uU)F?bqjVj|t3Q6de&4k20ak3|@?E73#F+v;y^REudvO38PO+~wY zwCqh4Tlxa4-LKQUNS1D8eM4&acWEVk22&bD3AJ{-45qki=_V@UYqQ9s%Mo<`D$z=q zlG7=zU9#*wNxVnm7}KWWZAwOA;+urlRf(J6(~h?H`E3qac33&!}mp(RL3QhJiXWK?Ia#j_k@(W?|{~ROLQOB94Qs z@21k0K_`@j@;6Is;)?Dy#BL_BTjDr;-nHGq>~Dq@zfYUAuTB@)-%&?5ZAMfiDjjug zbFrwI?ix2hv=#7(J2V$`U29yU49p`+&*r8M9z;Gcs7m>K$FKS4gW zql{@wL&JDs?q@=4>MOSys(ly9O7Pwsdlik+(Yh(7tI$bo-bP2wZ>b@@XG9UfC_=h{WJ*!M+(?KY zS`e@4sG|iHtu&eXo)Nl=nQyd{wzBVF%p|JdFrPWalMKt+~i>_cy zS(~O#56@v#%KUD}Cc=H!s_l=5($DIuH?40U*u-0v^D#y0y2zjU)9*dzlK0t&zP|oO zrUQtx-PacNq%Fp@A9Se2G1I)?8KHZSkX9>R%d}w@^EoQ(u*~NX17kk_>w^a;x?27> ziGT*qJ~_+~z1$Ck?@Ijps_OkNeFqxH+2S$=f~V6!5+~jo{WlE&;t5r?b=hnlj$e$m zEOkP=D)w$cd&~Tig>jb_>x|9s3-Rh+&=jA)xZitAtc}qm`lV1eLY2%ghZ<8ZYPYo| z`ow1TU}j#o{oIE`CZVaZrM**Sarnu8Qnv0xvh}fl?rzoKgjKv>dbF`M)}q96?$(Yz zh^mp97`yIna z(S?oCe#IE@cYbqX;o*`TbiS&MPGRPZ#M>^k8TPiu=2)a9zMx^T;(XP6qOG-kzJ(%b z*3#Z=RoS03)&;&Cj>XO51Y;{;%^W^vAA%QC42$S|%y9-R-*Wpt407bC_C(@$jS4V- zwNCxHH|d(>QGH&ZjgpT2ScQL(iQqOQos{pupy{$ey4ALswVq0rVqGI-mFt7$*EuwK zGpZ1#(bW}8D>JUW>i=d{qKgcrs-X{y$Sw1NKY*F%(0!@2Rc2&e+xv14W9}qqJ8no8 z_aHJY)GSCy$JR`NioZMHhqC}V7{yb!A~>6T!Sm#j-9_gx9A~!0O>NJV(PUH}rK$m{ zKZbEHd}xGuTSZ$1&LW$mkzZ48)5b_@Kcf?*DsGORHHmKrSlC$eepKCGzAQbv~Jb3lX5IqB_~E zg9r0qV#fLK;UNU+$dUv#u|OG^(S&B7UFRsF33eI6^zr&Z4viVZ`Y5?>sh{lrFg;g} z8p{+&Be@ch7MRLK>pM9VXs*&n$@iA}CE*N1*M1xFAUV>0ti};x!7M^Ulm9Jy}6nwEFle0{8$P%&=nQL6U1S z(!=@#HcG?fW!3%uC_Gun%IKhoaY%FH@MJv~bNlCed!Hk%Nfw#zdKhJ)jWWRqSrZ70EmKb`Kk9@6SoQzIB>i-$%)nR%@@$r2IXp z>XXa0FEr5hfwWw6YJ8e^y^59(L$&bPWp)ei*6kXfUuMJqk#5)c;xZeDdowA&UZ!e< z+^MsLjMYMi`^H;+LXPF)RJxOuD!mK(rVr4mwKbZ4vdnJoW0}f-vTTFZxSwXK+Loz& zdnV-xt8#fn@88@`r$3kLPR-Y5tNx_sv}*0QyY1HQ&9Ym&vfFOkXIXY@FYaaxnqiQA znUu@A?biMyi>4l?CMh0%dI$kdn@2n{st@OfZA-e_rWf^y^m)qd^iLCXBqI1W>R)!QXaP|ggl1+ z?M=X!8N;q@r^*-TR+uB|39HH{JMAj(%eLG2RHxm>2eR!p{-V<+@zzYr3!SQskj>fs zs?_Y8u+P`Xd(*J%`+kQ#q~FM-+}@$;lUI%YG)?uX zRvC5!b=j9i)v6D$sGd@i6KJa7hhT*arP7Td`CGX1MPSIOA9H2qZeVOy)*^I(8-aGx zUNjm~^R%SuK)3Dzgo6lIY~I2!MJkV81i7pG6Re_H(92m=0@O^oaDRq>s2F6UGgwZ^ z%k`DPli4=v`@T%c%k@o}rorXrp1Hj519yBMWKg-Z?}SebUdjCf0o)cuKpSRdcOBu> z@R}Y&jSe;JF|DN)p*Ix3kHL;XU`5678X_)E-DF*2`1|^%!C>y|in0+Q3 zND-&!c;akm8c@$UW$nADf!;Ir-4`lv76pfstSpGPHaA!&Z);ep18<@!7E`wVZhOEn zRWfJe|HXT?d5yHjV9D&ZjK>0lbA?In?s!YAv1Ng}y>nh7t+9saA)l4>K&4oiMp_rl zFD)&dqLLC_iN5b`jq?|))C}av!gy;ut`Nw?yv*t^NXoHCfCC~Y*4Y+oZ6T1{!-*LN zQ8q!T+98Y4!gwDfwMbbh);&C*kI38)4rc93;)zC;C6`hKTWP5;pjgEx7K`7PvV54e z%;}oktW?s&inT0+S+QBP$0SuZ7eNNs_F*+-D2#Ykw^xh1{-PKJgnrR`n16yTT!BteYlmUgh&AxAiMDnu?@E^R;qo2dvL?sI!j;2Wo%toNq#M z${|bh#i+p)c+keU-9AVT?^Dv1(&3lEA*x>c=RZ6b48m@5XRn4&mc>MGk!1yv-GY;O zXJksUlIKCZ2+y~ryADa|b}-nU?_hAVo|pR^!t=2NVJt-e%Pl@vde%U)soGsl=?2YX zjcWJ~)WZw&xQow{bOA&u1#QH1Fyuia9Y0e02LV3u8p;?ii(>tKI4(cRsrV6{>{ytV zqLSPM7479zFLj*XS0U(1V9NT`^^iZRU@EsEOntQ*nNIsj-cI~Bsp3Go>D5@$bz@M) zs6Gj-W|##^7H-ve3u4Gy$znvX8NR6vlJmNB)Xjxah_G>Ey*5o4H*P;pKm;CIyl7)~ z!$cwo9}OiRwx28NVXQmM^M&>LU>NdxkBasAY*wwV$?M8g!4rh9!uom@)+2%-QU0iF z78(%7JId#zkGe?-$PuaqaX(~O)EGS%+F0Viuw2@AsIRYmYY$GMcL5&^8Nu-S28UY! z0r^%88`-n=5wU)(L*P%Xp2d%;eM}1+qYUHfVce%Q)}}EaJUuaG`bxTDnE~2c-~hSQ zKJw8!rDpDd4^=StLM!}?BagV8pQigZaatrF&&2N_y{c4yfxC{~eaxC=6M; zU-h^l^SelvV>;Pg^NV#&j`i|!9*sdxyuy&3u8$#8Iu&33m((Ui|U)6{-MT#hPY{)KgNG%bN4i#TvoG!IxW zkEZKnhbzkR`e3;`NmnVWh0tYERF3pglb!Cq2SaXie@snCc{4SOuf1-sdcB~SZ-tyZ z#%P35jRfB2O}2cWH-hZ=H(jDs)bScqo^W~R^Wydaev?&)`_!|kPL|X`>PRIS zRRdX23CeY=WN9hnQ&%~a;e6`C+edHM(2m;;(~!i|a#y-eJX_f22GW|5Xc_ZOUUNlJ zPqPh!vYTp{cg-{8x^(Fwncr|l<<@kayy=S0h)S}K9l~ipUyK$>)Nw7A?*B@> zFL6g9O?5IN1fM=w3X3>s-$=2;{=SS}>*U$28eNkh*O@9v$p2NKpkSGhHWph1pO%pD zzo>ZW{@+QgEhBOM)6XfWIH9MsFmJ8T78l_q*E@C zZ@Z&Xr*b2T86?rL$t1^U#wt5I|ErpyT5u9Cdr`%Kb-8j0FcXcC9a)Svq|r-&K3IOP zMKKL?gOCTi{g&99bo;xuHlhAx+WWwaSlX!Nm&IN z1Af(TQ{8rLx)=dz?}7}jIR{m^)vEg=R}D|ava#nAFg{Bas%29a9rE0QiXh|#g))V! z8suGt*I$$s3X8m9n?>4a|6bdo50+ajwC~EISC>mMH^`FrC)v+Q$Vs7pj^_GwCHK=R z%s>@*hf>YCblgjsQrS_N&$*-zmQC)cuE{S`b#i>P9TA$mK??o_{O_lWr!f#6P99}D_gZ^^y``SbEf+i95n*&N*7B5Zx&9T z1XD9*^1gqTUsk7!5wb{i>c4=zhRuhJ@ml>RwbzpnGdNq@CV>Kf$^EAwxEO;YWFy@b z|1-28o72T~c9n#T{1-XzAzD&b4Dr=K&aQ-5PKhl-|!4+>rERK396} zBnF4>kEJs>{+6nfd3AD3v>J0+=9Z+N$jMWwZF0XmiVHzI@(9WlRu8IpMVdBQHp-WA z7!sA1IvF0-&KB=ie>KFrUIsoAXbmAOItbyXIP|po7J&R5`7WrjlRGKPfXZYT2Xxu9p`B6yU(2V^=S{!rSZBk zIr#UsvzMOxM1}Np;R9Lf1lX>qvI#I;j!;KrO8gB;Z{qlXyrXv_p$~`Lx@-d?YMvYn zl6k7;u}nQtKOrzp&0RyDR9nhzF!kZ`U{@&z%g^D3B{!IDd1&~qGK;VPdwgn$T@kMT@K3FbRP7xI+I$ZATDjvO5-DjJ;BcOC3T`UXY zkWo}c=DD^I=G1Fmy_$5#c$kv*hhCe?E5HD>V|V~HW+Q?Rr7ot;vLqK3=h-yTr9>%G z^U@=5ypkK#D_P9-7==-cfjQhN(c4$z1MB*nyjB3`PzCy&OfFZA1NT{4`KY(8QoV2C_%Rxw}apA(y7e02JCc>XNR@K(Cl( z*F8dxqfKA%9Qw@8c>7-7_jP<;uDZ;B5&^72;8bxJeV;UxtQJFhVmuh*Vf`pMpcE>T z`UrX@AgAwEPBhaimCms;6qFW(F$w{F1f+YL%CjDxIS5FbBcs{%H5;$N@>W)!d{5~+ z3u?*mbQjarSs3!Ht5ZK#{+2A&2g?g?DuBpPUOH97qaO=HZcfEGd@k3@>3Dtgn9*{7 zI$uUH;)N8KL#UV9w3UZAPib}F3qb>i2gUB8wh-k!8 zn70mj}_o7$WQfNgykp8Vvrp=y{ha^GzF*ixO_IO5C z(i?W5Uk+-;H=-S8ucQh8wq%ho_~baaiFY#r+g35qa2pU7*kTE5RLBOlci*IU`kIZ81uk{yxd+Y8*^)6GA00BznCftel~M;mO_qr z9Uj_`YXy6h3{$?fDRX^TU+Yliok66dZyJ7xFpTHbV&p-ZR;%ecZ0w6VE}t#y6NAle38Hwl4XL2~I)!Mg1&m-bnZLEhs|hY#1%QI3e$kS_lu z6_~@MKR6!>_vGbmwtVv)-T6~AU|xUl2akG zzMlJ(+|fP@33)lU9Jk5y?X#5PQt^=ppVC6thSH$oL=-(%#<+KMs_4Tic_g=d9QQ(b zR>6`l6!4+1fG1M`%9r5nZFN}1f$=C?&f&77ZH<+3FHJVe~Nb>hB;31 zYIy500j43}sFRb*86=W3$%kNY3eJ)V3UBr8PIW zMN)smrS0>b2E4;i^Kx{#G?qe+9NQP`L7F+vlmY~0{T}dEf`hk8jns8DOk%K8g);~5 zI+$l8l3BZZqfQ-H(v2d*TyySGenZby-k`z=U?R_1%8@G0SL%+Rh#s{T4$erF;9P2m zfSkx9tSN{|C&*DXGQ~wU9%uY`E2dP zJ&^QH1f;7NV$k~Bm%%KPoLWet03O~LkU(0&Zbm|z$f?g7C+vmiE@|6I$0x?5GjTh7 zxQ*))Jc^74xn}Fd14*pODCUf7a_YR)zObujBtV$Tj9&CDFuC}!n+svy!==ho4xz&+ z6LNWaB?7Y7^%!Jdis)VJ-a`D0#Z#w+uQ1WKxIzE z`!RTAx9hQqI0>}AGUS$YrTozqnks3(++aQPjG!>0Rxur9`q-2WB+Z{)wBM6;=}n-O zzm3#Y>0*A|YkgdmW*DET*U&+41~F_w;e<)f z>+7y97M@e48ggg4PF}b1+?cN9^WpHkA-z?0S$N!<-hhDo&Q%Vt8eBK072ElNYXzxy zZF&u(I=QZZVanf9`TfMwG-{Uzmiieb&Guo_i7si><=wXS*Dq!|S=`7y{jo7DcU zj&pM2Q>0nA3jry4Os4rEW25lMs3OSp0#q4t#!G4(Tr+2}lH_v%>>C30T#)B%lQC)l zM7OeJ)FyR9zD!Wb-(p1HbrTjLU~5df333td__Wf!JOqxO(7>5pwx1Oaed#+VsVdK( z^A4-*1!H=$&VHSI_6DkynvnB0!6PLtcAeFwa)zpNy6RYXT5UBiDOAI)j&x3Kl9CmW zudC*jymS&0Fpcx)yg+H@aZ!#gtyl33Um997H^_+!e0VFQC^M$M=9kk{>bZ8MxSTtF z!Bg-_T|r3FYow?Ja>1H$s4`we;e<(5m*RxU&-{Goz1Jr_wn~sAwzc zf^U%BdDHOfLm(SE;(Ys^;f1LWfgu|@^7X;;JEL|I0?cr0#|C(09HoIi9+;T>XDln* zJBrC@!6)sNO#NwAxvUWt`{|n`@y9(Y;US1;yZx4?`=eP!FlD9m>OZpf~ld`M0?0lnP4Qy(nbjB*>d$2w*~S_))%ISS#! z6oj=bm)q^i`G3fJ_wcByvu}82|90-#IUuM60g<4Inph`bqE65ZWCBjqP=!aMEm0;R zGr(Xn6EX?OBy*sk#RE}6geq3UQ?;$wdO}-IfTxPJ9;$t+t%u^Fh{Xe*HQ4f9zqM!Y z35wVI#P9mve?Bjn?7j9n-0NOz-D|D;sMVx0XdH=RbxKy&6~@iht#=^%eY<0HUJ^^n z6l2&&3keC(zc3>%AWkVi>6&&7oP%m7@d&iiBGSRmq63s+5w|+2c|<$bDs(arN9r6= zq;8PtE#gJ;SSgm5S`DB4N3dJ91Sub<;ftPJC4i zoJN`IEbH6!YHUdtF_$wa9&I8{9h9?3rq3%8kNJax=SuixL3Nun2BS*O`338`E{!R&>?ffT%`9JKXHBqaN{hKmyjq_F3yfZaNhU8uE z3}Wa0d6`vJ`_}%|UhfRzt^Ko#f$J!<7VOC8#m}8V?D!V6V&Lb_7w~E}Bf;6|^yAh2 zD>aaMO7Kf(5HIha_ZH_|yu5#2?MQ5N4&p^eh2Xd8MhTqJkM4dr*Ws2YjmI3Nd{k3|j0gHk4akX{7M)wA0h6}#o-F~YBlS^i>=rzls>3$QE4HTo zPm>G}P#fW?)Ev%^`<#lJ-=FegJB$A=RhS*^!~BdqVa`#vXyY+@P~37he&wvj=9E`f z{-3f^2!h+4v+-AY>;AQ~8eMzUDP89!R-1@_sBn9sxu&-Yp(_9bSH8!T|@DKK7Amtsh-uxslW?wv_ z{lHvryaM0_stNIsMX*-G86+z@KM2h~eV!MWr8nU-M^KXLoadIi#V2I1mWVH-niep| z3a(3e@uCTwK|WfB9*o#7ZfK>v!XG1w`zOY!5+Gw$w40n?)k;9=CG$o{|5v7Tf_gW% zi08g3uTQB`$0gq>;^qZS3{LH;sp7#w5oO}!5gZbffV@W4E%Qb-OHP)>;y%}?HL#=z z2LaMUSuYTy%ET@dSU$PC3Yb(5+k>%@3N>|olCYwhCwAo+h#j0-tUX4Q)OOlcv% z2eM8%>g2joEN;O9FQ>-1xk43~Vlg~s5Qc%|CRwSE6m0_jp$*da)maGb<$9a&!uatB z(~$(ma&p#ZYBLCw5-wPE{LHH3aJ*yIaW>j$NU2Rm;ULV|stiXXJVtp0+Z=w>774ti zvX<{Ng92V`m!Y*#iI^h9#N~(y?(Cm)8aVQ&+@+Y{GFx} zXU2)SYL#B2GEZ0OGvX}e5&X`qdb>jT%`+C~I28+zFF&4rx_%0^ zSj{DGd(;@%XxHAK(a1PlAg;VR)JoOkZs;@d7~iVp{`LL_dC zx*7*>M`SAweif0pOyeBUmP-Q(hPt@3OhA0bZK{y0LcIUYt0CJF-wIi$j}-$Cr0R&X zT5;RUGCNC=+AFt8y?z)zNYtqk`?R+IAZIy}jq}AgQRr)!WKfZ0N)H58?}6_W;e z`=nkzmj<<^E|%g&wL&fyy6dD<>KQ*tZZK1i|D06uSxFktDPtGo8ioQ6i?Nz4BOF#L zNilA~qB<#FICEa@@uHphIgPH-RcDzb4g4kp{6YALOx7xCphxJE8l`aCAkh7?Qf81v zYBEYIzQHK%B!5>s@(Vcw`O77SCAPKnvO6+WYj}ceM#af0R=g@oNpVuO;LJL~zZ}(4 zFPKlZ)hxBI^IosH)$auLd+e7=_2IVkUX^_oPyK7tuL+K*rsH}>JoAY0ym*Y>DY9E) z+zW6-wbWyNqbNyJoLVr|IE@jVRU^fpv#H82xeH)fvjsVU8PHcLD!%*Q9MuT35#BS5&m@ML6+Aquy4ajYw8^SZ*`6s< z8xz!dt)4*J%q}M56proYw-F@ts?J;Z`e1q^wN6PTmVN8T;O0;+G41d#q@2^?C&W%C zpVG{Hm(>heYGxAhZsQmHkm0aZmB0W~E*H0A1Q|HWjAG~(<5%J~er48-G2}9-0^j_> z8FBF#S4}2~FnXmz!bV!k$>klfoPWhYB{kb;#S`d=C*JLS7oO$2cI`5b!{T=mI4k}y z^uF^i9JL4OcM|d6zMBvO&pS#_JDwx*bU6i=rZ?eJN44M= zq-pqTYL4)X{om^CFVrZ{sUQ&B_03q@7vHsO*Dl#@ajlnaF{f-^gRWs~L4`g7Unafc z)187pb@kgW2CQtB5nDEh?xd}m8fe8fE+hSaJD>5p$?zu1S=sfSjY`l^FFv_L+?Iv> z4y*LHE3Vm=so4DFA=xe=0W{pY6O9am>$8I3Zp-EkD0A#=xY|jNnANC5lZPTHtM5&j zFUl1pZMd_Awq-tX@K8qhoTJ+%O$31Xf>P;Wt>z_?&@4HERT`VVYOnTyFJ?4 zIvnsahE&5TLNpI2zYW`PXKOe*dBL!-5bNw{iAI(V2RPH~%y3F%P4k8^t%n)#m~9NF zdK<+H+BzBH_j{G?io}{1gcd3PoDOAnSbre1!}gAF#?KMGFO@Hh4sl%0lqKKx$F#J$ zwM&@}4)NyM+#ZO9T0LuIR%;QjV&Vy9d8SBUbt9?Ssvik!xG{t<5 zwpXiP$gIf zq=&Yc5x7S$>56u$W)E)ej4ogvKB(Ak2e$@8J*r!UR)u30SWRSI#pZ;qaNIdVmn^F~ zjE1R)f71-rk#9S%>{p6|NqNIM!PQvb#?uqkGXia{7)WgrpEOa~z=)BlXG|8_4Z10H z66FRoQ6vreG@+xc@i=HMAD0yB;|+|fMxv48Y3g`4P!^CNf5`Y;0 zFKHQT)S83OG`;4G$H}9`tr;c0tf>vzl#I`WWRf ziyA6tiCdcGB+BiOtm~OByx4p$KI6<<=)ve|wKe2@c;ppv8%=_aA{;dPWT}^9c=)_j zs6g}#;m`OKsW~(Q{}{YPm$GW`PSYm{HfyE&7{T_W*L13>2gRe05$!4`5_|OtxKE2H zWftF=)M3*m0E5iGjgD}oTs&u*dMLUk^UQ)jb22^(8CFQoX_A1hN!t9f+WA$c!sBU_ zg2YpR|JE9^y2PiC!S*Cg8=XOY45t{UI= zW?L(qa1QDdaHlr=cya4v@Ivw_mEEtN*PG8xHSEg^$$n9NIoIwBozW{aT;W7Mxp-!O zyiN4UDd=f5zy8#T5rbqCWDH9k<846qnu=P~&}&e4nxpwUgHE%?o1@;8&3v&^kOCD@ zQ;JFHB%^a_m3X2vsR_l4{}`%|Pn(Z~7wHwqS%rgp1WUYRj}Ic|)hFOaCp)(z<*mWt zr3iXuO_jJg#y2{bOEG);W-3Ii32ve+gef~xbMy(==q#0bzP_$r)t!*9uckKX6L5?5 z_3!HIFRj;?m0X3cl_rRRyOc1lgvb^=lZwhwZ2CSGwlvJjcBZ^E&hGY74XajaIL%X4 z;$C+GVr5hfsx6mFa;rcO!&1WU#t~K0AkWIwqor1+s4s5w%c=Z)9)HfK0yiHFTH7`W zQgwYu$L;_7I1QZ3^)ZC1Pm+k(a`bIS*yFWT4FM&dEejwCRBYW3(&#CN9}SgQ8bx~}X&aQDOUYEBR+dMqDCSV$vAP=s!Aijd zN=`53Jw3;d+-edo588^9lmqQv%CiNrIzd4N_18T`asrh_J&H>s6>^N!%8BK2yi%)m z%TJ|BaL{eymNTX5JvsVJIi4B_klTdXx*j>78Hp%*3!s#@Uh459ub`B(ZI-yH7C_D@ z4jsVaGNEVXv&6&dZB7Ep5rI`V_ zF=T((~U@kE(-A0$Vm!6vxpn#X5|L?2f}dTOt}L! zh1yx>YOE=?r^0iZsYpzo$phjk5k|Ri%f*yHx}~IC+(!!MX+2U~YfVL7qYHiB+8J>A zkE@kA;+`f`XaVj+U!lAt<18}VRAM*sW~LFYQ)=C@nZrwK1uaxCalcoC!v_%bcGgIf zYH$kqly0;XRY($JCR!Ay=4iXdU8W3arc5V_leMR78Lj_q**qU)T78S$9+^Xr5T!SX zn}bU(-q9?1inv!u=?wAUC_mcg;KDNK>!?6>@=l!L6^x}F{^R1Fj}d-~caz@fIHwUs z!Mla}l?=`7A<5S!;NU@)JEsw)L8LZK`!&Rmu6~SME@<&)=EDrZVY3BYQNckKBxt@& z70+=~&!0QA1}>DOcQN2cU%!I*eDUD?7g1Va;QSRRU4GhTbTOM3=@45>^}|?BvU7eB zj;X+pi%?vF3yRc4DOPjw8ga`q@n~lty*C4@z8PJ0$n#?<>sU+cU@`zb42wc|lXV|o z*iQ&nV_u;)*PK8*jULj}99>>J}LNx9;-iG#17@Zg6-cu|m{#bmZ+PA062;p;%3clTy zE;6b53yvNsV#+;Ac}`LF=N*1AMxn3Hn&ZpdcFijI<5UXZwB=GSm2x6`^@i%zXZjfY zLUn4TsLq;5*sYJjy(&35197|3nsU3+T~xg!DXWr`<=5iV>jc{qbiHwlJ_f%BiNVV} zJ)OW)Ldgka>cSY{pQ%++d}bAGQ*dmx;74&OMq8O++&X53b1p)hm9_el5c{S!AfpW zM-oO?n;l@OrJ_c{7@;9my+RvPL6N7Yeez*dql>nS4=nWwq70inYCRT_t z+r{_+^fdZaKt~@ni>OCLYF>O8-=tWkks(jD9`Vp)o)$)8FOCS##5|tJc`iSQ4lif7 z2(8uB`40J|NLPyIRJbZQhFH>wrCxDs;qNNBaI0XfUl4DkVtA<_UPtA~rpe-&aiLZQ zeGtjU7Nq)@Nv%v4Q?tiY(*)-hq#K*Bq?1(b#4m$LY=zH@bU%_SWHpk7)=7NX$(fm; zVKujGo(~>4?;?GS3U<(-*u^WWkSwHxU)P}2KY1K}Y^){BndGsn*uW|n07*smGu66i zh1aYS0!kx3uuKAL7(SqhTF+G6i`61pXL$#lRAtw;owbPvH>SOUHyu6rIguyYB~HBM z;3;y6b)v1AY8NuYSydY^N-+;IT-`qYYU4id5 zg|6X|&O-4p*4CG~rMiZdODjtl!p>YSrt6lLosF-PmEyrZ z=W;AdZPZ=bak%-A!D+gN*W0{;i?xVgZ_>*aHXhO=9-5WD6AkGuY#2qEhYFPOcGN5Q zBDqllxKw);UnIS_RP$&4uEQ6}GSMPm;S#MvFqkY9?KrBY;isA(sXFXc5L~RSVnd?& zafvo~x;>yZHV(c}9l$5aIf8!eHhi3%Lq&rtX-xD{vQVF(m0-0->*NoTbHu<^+ID>c z*LR5m+rVeZD2eRlT9Cc?EV)9TfK?iM;bX!lNv~+*_F@&U3!f0o$qLLXH5%fC^#c6N zRV_ZotXtph)eG>&p%k9tvrZoVHNmc)N{TGsqtgXkqrHmvl2KfvZO3~=K;kQ0O$2<% zn#)zqtIe7PA0)kk)mpGtcj3j{YF)$aOUq{IE?hRMS^S}lm3$s*!z19A5@bItjR@7HN_ z1v~lf$MxFac~}zCCYXz4$7%LJtm4LIp1_Ocn)B)W`Y1C~jo;3+o|&XtNcgMDNnh)^ zFcu?Sr@r9C>2MFTbX1r^>%T~ z(07`HGzeKc#e$rj=*}u@oMAWgJR3TtT#BzRr+F-{Q)_0YRw14ES+Y>v_$P-f8P|@* zE^QF&ll|DGMX;U|YnRr;fTlB4gpzt$iK{786ztJf39jO3*We>*`bvUy(OU{L1AZc;bbP~E)FyaW3yrC3>WPgNQ}>LjCvOCg*{KVLl3LJU(AT76TO z=hLl%9{-UF2kWq;PM(os?T8E@vur@C|1bC!Xj}WMPC?d%>QlH z{EIY?FQ3sXmV-Pfwtdlhabx^!!GcYk@p}>sAc?CH6nvN{#BUJ93yDplUGG%-MghUc z39nLz#s`T)?FU$cLA;-6lq8FCJMC*Y`|a( zb|mIt10r}k(TELLCCKp;CPjnlbptn((jquVij^@o{xD`PejhH>4IDj@i~m9uTK5aO zbymt@StQqWoV&9BnulVqGq6`}lyoZ-4vK9nyC5ps@4Y&<&C%oEm1n^qy zNUXpH3=lxU&(VW76I{U9$d0_u{?+0N3_|=-@!{P>nNoJ44~)mzkzJWO_Yo-u*1?Z= z6Px%j@ihE1;S~cv!*=X8EBzTOeTS7!W2Muyv&`|XImaIFn@lC8yV?1>6B|ud+B2v) zS$E-s+=;p-ShKWH3?5ADX&TZX7*!#~h`xqV6^O=p`8itLh^lK46>1DDDO5Ty0VLvr zr%hNMCM=I83bo1DhCysic-bVkJheA$T(VVsOnKv=Qt|OvDY$WoSMYFxyWXRC6%Qw( zcof_5aH34K#q@55$2cnmtZh5r#@Z9a+IbStU7{U>>ks8gmp;^R zPq@EU+}yz?mg+8Ek=^uT&V-V=?B0cyaw6B@lVj8(es7UlGh0Vg%Q)itfu)t^Y2#BD z{TH1rKE;AKT-R`MPZ_a`n-V-v?7%AAl<9M}Db~!Rvj65)d9E zC-Yi?54ikRCknM=>0bm_B>Kg`huE%9z^y69L9JE0Ah+;JeS$!3;dsrtPM?7PPW2PC z&yr--ncF$Jrrgf)9tLoYsw}YrdpH_bB?@`VO?VA_itVDB3WzpoA3j2b;7VeDg;R~* z$EcQZ=ucFNb~Zl2;CbSc4dlz?%7i+3e9ZCcXNd}eEk3ynOG4(k}>wgf_@@u!lXF`?~?zSuVvjY=-3r%uo}w zo1hEA*teP($Y%IO4o}21?(nQhTsn{8rxZ(qwx(`r6*pbX8RQ4o%N>|x#`af-?T>}$ z=d0sox(k0$zSr*Wog=4n1^p$5Cyjuv;f`LeYh$sv$)P^g2nluB$4wBoBypaX*SQ7> zmt(;kOrR6+7;O_Vp9Y2e!0ndKAb3~lEkvG5q>J=obb4np2YH_>XqnLbx8A?SssUKI=;L1GgQQVHc~ zk76_*edSm@2V>M@2^P)4MD>`_aDN6(-dc8>75b**_)=*gc0N0}np^5U;jRB0I>dGT zyiV2hXJ+aye0Xq#$har${ofW|ug?{?1ZwpHys9$}wjuV}o&2Ee+5M)zN-n=O=niFA z%PUD}&qq9@O)*z<(_(vSeVIeO@CLcdq_Icf@4|b>EowsCr6$C)y>oOI9?IbrA*pM4 zeE-?dB*@z==$eCMW3!aBs;lsCrOBLMFZMQaJKZ{>!lb*ObWHVbPc!U1ff?* zaw`7WOI<%~$O#JG?k!Y|jq7s;Ph;UX)HkgOH{}e@FtOjCi0T?XM}_&iGRJ?46w4VT zIJ$-}d&@XPm*kYnDYKZbw^E9ka(NDkG_!o#TdBLSDyKA4 zD$AoP3yVBN54x=6Vf{9>Kj85`8gdnzTlK>_xkvjYttJs3dr4>*XcIf!TT1E368aC*NvC3Y9gHv4#O zjJNOi3wStCx9>G<^r_x^C4l>KUd2$1{hJ%wkv;FEja#240KP>I}b-<$_n^h1e)T zydK}H9gCY~5U<1=akI?EEAdT&o25qrVvLuF80g!GbG+qJd}g`s65Ls^Pt=j&ud$7~ z3%4uOs(6Pl#=Lagt0eIp{15x0M33NVy;^r+vrCPGR435|G7a zfrs_w>`I?k*YKdejzK#8x(nAA1hJ~G5ufPXP`-+B999%?-hLfx6na2a`_1}-<>sn+ zS3!7P7Q&qz{LkV%gkCPM;CO!MV zw7(=F)sDd1IfK~H+bG(R_*>2(ZtE=)k9GofZ zSu*ip%qyA?1HF5RxE(pG2zDX#`}v2R&TfSGqXoKI&;S=HOrPIIfkT z@UVh+<8|WEytt0DVn{aZjL*rA=6dPDuDBvy8)UBF?RcSR({PFKc1R<5;`AvA(DRqR z3cKT*bQkU#!8YIOX(WUjTtR~OmICiamvU?S&z?r?jyK|J3DT3M9&9|J(S+&)1ywPp zH0cO4t(7g<9p7tUjl3W}d}KnihS0oEXetPeQi(ARrIG&epL4Jil9UG|AkXM7ykpXq zuMPD~;&rhibH{ndPFku?L=(L8N}A!y<*_8 zoSoRl?Lw=;Lpg(ZthbRz3_RAmN$^NckD7l0(Z=IJb8bD6vs~~133Cm$=kOkg-*Gw% zp30dkxQ|~d_yxaXwSuRayqU=-FKBK*n`=p9GN+kT$$kbeFE~sS0yrMm`_oW`FweTtKoB4!+WX9{uQ$e zewrwgS;vx&32rQyZO@4v<{S_V6j=4Vu7>y*1#IS3^?b8EW&Tb#DTMOY0`iVIXa%{J}L^xyGQK z92sikC_d*oeO;edJj?J;y;RPJRw~6v(o1-;%oLAY#T?f&hip`ak8VNYDRNBoxq^4} zQaM*j=-kNCQfs=xY7(z9B$T=hxaCc8C*<$Scw>|l%bntrp9>@L`OvpsHjLFWv38cN zvWvf~isL8oO2IyfU|F1egbCV_Hp9MB3@mf;!hdaFq1hc>Qy2cIdExhSXZkD|(!a&C zH;{M-KXFwH{?k)vUhYLZ1It~5brP0h?ND=)PP)TJQ2>SI;IbfooiYc&}%Zfooh5 zKHz3oMLErF=Zk?4Qe|+?*2iE|4-LS{jQOj}51zKs`IKN3gS)4Tq=NFzS<T56=*8MNAV00%Y;BI?+pnq5pvk5z;nOM4C2e~Mr<;I z8He}u_fabzq(TDCPkoJ*>B9Ng)6#BI!9DV;HF^xLprKP-Xr)ckFK&Qvk4fC zwz5*V3ls}}no_N=BoGZon zI;UFRoYf>g?2CJ`CuccThOX_6e&EsKh{1khI~TG_s=5&pnNn?&B0 zJ35bh4|NCNq*>riieE>JWB7jj~G3EYRptPR7AXiU)X@% zp8G=g?kQSvciQ7{9sky~$FE1tmUXw*TW}kMX3Cp(3H+?#$j?Q}qm@5GTBiCWcCh}zu z4l3txOE^5r^NVw~xRI#CI6peQSXwE_?U7;%>7bDec1mE1LFZ3sqLnE*!6kTu4kfE_ zY3en6;jF;Alvj|?V)_&#SgG^?~f5eBg;uzN>l;?P4 z7Lw%F)#9*-P)7TLbDdmTejG7RDlm}hrwBPJaIF%&?)1wu_)oIXQu}b+W@1*Kjup0n zu$!I4FR74(ph?W0Q@PUw;EBGLeW}5a>$*-6y zwKd{tk`vArw>-%)`UA&TH}y_ELPeuuL9oumm4W|By=Fpjn={BSI``T--Z~B?nWVKU zofP{752hLk?c+{0z7M8C`gFdqP^w=%GK~axmhQvjsWN7K&{@i2kE@Ii(ggt0Ui55| z`z;JE`>oxLpD2wL51qm+Ae!8A$<&joq)O<<^#<{r*;qSMj6*0|aC7xH?UewED#&4I z=HtW)Z32RO#dt$JYHLW9@wjs|Z!*f6NwL|fMRnGhd)u9M<9BH0O{|vcwZ*zm41Ag- zPj$NB3TV*8`ckL8?^EQ(K)uMPQ_qdtn85yG@_`>dW1++{cZ zUSE#ehByKP=OyV|meHrUOTtZuR%F`m(V1T;za zJjC-feY#B1eR85ceafua)SJIOJ3V$}YZbFrlG$Qum?g)aE}j~mmRJe$W^*vQ$~ZQ8 zL47*9=>G9BtJ_WYn$YEKpm|TzKDk5ON_DPFcQ|q!hGRAy4yV)kbzVOII2mWu;dC1L z!?KJHM~7()N;5XcFibQ<9Uak**lqq5Y=o{tyq^c;LhTT<$w^PxP3~28h+)xc_Gwm# zgzVRUJ4a_t?Y3po^EPDIM&|AZu{~r?;yU9!_(&4sfm;WAwVAymvL#LF%c;11QFmcAYK4d{@ObbM!k?wFL z80}U|K`p32SHujl>dbVV1(96>*(Ic#lp)7#p4r5atL?~42wlS~*2b@P)`n%=&M1%- zDrZ$Ge#h9%DYTV0Q)7ymQPSKeGwjZa%uK33@J!12elw{v6zgn_4lAH=EFNhY4r#Wh zz;FvPzZFATjcM+I#x&FuHmP9N&blQU333>+2G?kfE@X<6X6k&`8O! z>KgKSSh_=Mb>OgxdJYOEO0hucPtRFF>ea-bKiZUQyIpd`=)9N?lx@*-@U>&2_-4s5 zIzz&FwTxNpfR$NrJoS92LPC*K{zPe@SN<+abZJrmlt630TP?-1SUi%hvwXu&dMeY7 zpfXdm>wrb!`bOc|nS;-Nv;)!~{rkF8H;;J-u+Sn(auF7D(^K_=ALtu=~+kGB|gQFxS{#LS(s*v2v{8ULNLm@?mF7JS| z#Ql?y&}6og`fiNa5J2C(vYKHGd_x$9f67!phLkPF%Ndn|sdD!PD^4>MtJ^yEOnN4h zdtxTHPmCMXP85*4RCY5S@q34wXPTl z_x^u_Seobc;dGBHYB>2jOy5?+DPkj{d4BkNmCLmG|7P&4Dq`_rVaEo&Wl?j>qUI_R z-(huOSl4X3H9B({Rx{SUpryUbJSim(v`Kz*$7u~mx_T51jz(HS)*Vsf3sW`w7-erX z*&~+yq<_~&lN0Lbh(xpdNM<*+6J&upqYXKVyB0;G7X?~cqoJOVI-GsS#w=DADUqLCvs-}6FFn;zgyiu6aQ)U{;q+3P+cQTC%Ses)wXRTlOE_wwBgVbEHeT8OpV`>HSYUIUU{h zxM-Xfc6IVyu;qkeEzRu;=gO%SF_Cv5A75r-cQ~d_6nV`&*f4W$hFF}SUO4wvhWt!& zg-ULlAMR{xHbHM&6v{kB!s>ci(ALu2vPiX@%kxx6bL65xbb)fQl-s6`d#XxRxmn%9 zmbT`Oivp48!j4cV(iv)As8&F3+rpMucPL~w)E{jfEFR znzL&+H)@@hhcJS2+nQTjGtITNH!lojk)U4p`^uaa=Sg+4rFm&6vM_@Up3O}RWlLsz zmD{qY*#yv9T)A9youQao8M&r2H-*>Ej^>t7X5g|3tiH>vUu6g_4MoiSZ3|mk!)+$= z_@c_REo|wIc9;lm>z+Tq%N)p-h0&JgR&xv@(auoze5(dM5Ni)dGUJfj*4|-y*fJZm zs!*-loyamF0B}*r*Wy6meyQKihcjRi@GRMul4LWdKwXBrSC@-c!O7EdQ zpcr(X(Vzpx%DndiYJJ-;8%;i9r|mX**|VABE38ZM8gdOG1L9MHf9mvmXo=)9nShSx z>;fn9R-Kf`dAsLn+D&&xJk;MI7~dA% z>!~7hj070^7yeo-^)lSck!-%Bjz}?PDwboe;3}+77ZIr$d>%uu zHOc{xTP~G)!5fZhUY~wQfY+xRRdsJWsIlVFMw)mV$kw?-)k#@?SDkgua{Q0o3PD~6k4@D7Dlx+ z@I4k14|5gJ-Y8GdwZeSQ%e?o7B%qH)4)tf;a*vt~onUo&8j#VMon zl;60u9J}NFryR-a^Eq`=Opi#X2!2>Zt&_+ceJq}GluCn&^GJ>s&Q^haDk$5>5Bc06 zZdt(As)D?3u3?WbvraaMPa9{VbJiUxXH?CHEOdT8i zkKLnPw7|1neA1-6ZuG^KSksZKP5QfCD}#S-s1hG5p5Jd)a*UcgKQ5G+tl`+8 z_^3AGqdpC(tmj7(Ln{NU7YP2VP9F_7$BTq`q1=+~yyF$Sm6guXdC%yDf~Or@^wDBq zbGlJ{X2NZZPk`U2H(qSMmr8&Y|2b8u7vi^0&bQHm&FPQCEoYI|R#Dd<+s*lSPnwD= z4f<&O$w3XJW2p90#b2r&ghiY1wS;M?vRA?eM$Fdpad40oMwE8egSsEj^m$1?eCLo0 zf`{}T!4rLDj61#sse6f-(;FqgGM>4!^s)GTnqqaIiK{0abH%OaV}z>oR=r2h$KRAu zZqm%~OT9-L^nASB7u84Om%3k{XyESNGJQ1GI)g~p=@YRhRb;;E6R|r*r%Y}(x7Nwn zDBL5osaUYrniJbzxAA%fX@RVk4Hmz6(m(8PZ)zEwUy;67;rvra(8T!_>8%RqUpiJP zoL`YHGI73D;e4Bg^G_Yr=2JLdV`BU5B*S0(^wHQrZAvlTB#KM-Dipg`N#H`M5udy) z4M)ocasOflO<#9Xr|J5n7YF(A7wtB|&yqCoq2dLH(MN^I0A6Lhy<3xI;@N`8M>QA` z#L^<<`icK{=m>-u`I+V~(Z}KyzFQ-3vqn|Wj!+E_*^DK1^orFHsu7IbZ2G7>WQ(}z z2LT6HNWGqqKN8u3%k@D$AD?lkSo0P7PR=ddkql7;dE(dN6Ri{phE~=}>BQ5goyFIc z29~ZEs#iAfwvFfny>`PISSYSSN115D)Yf5%le9hD{U$f9xyNN8=v7RD2IhOrMCS z`V_6WPoJny6#S98@%m`otNZnd_#-EzV6(o5m{c>vz{7p(Nyqfj1|HTctXVa%)9%K< z^>23Z9rIh>IJt$|7qVK(OG^$n`F;AR1M>SO42$0v9To$I=lbDrLYV*i@QI?s)aV#K zqb+ldA5Ld%w$5-_qr6dkuZD=eVHF$}e=Bnj8&(4rXY2cjkFB!2<$SM37ld2Cmzdpd znOD+bN>}Z&nF%wQJeT1>Z%UzuQ{sCm++^Y81#{u|R}8TFN$yT)cpmd@82*3AS+*uO z7e=%r_&q~aSXxh$qwSrO7e(7b^E*P_lV^llTbny3FN{t;p}gXx@{=dGhUbTRLM@YH z9W9gXmThM9!_EV7n}_;9&+->-o-#R(OS(c`A;lOVmyHilgq2TDQ>jAdv{3Kri(($+ zFfEW3TV`fZmNm$AtIDuiRm908I|JF5Tvp8E8tT-ZFZ^J#eq9Ha3yam>|AHgT3ctY- zc9A->Ycsc_Ew+OTw_+YrVrJIQ!kmO)U%}-r+-rxO;N?SzfqA+95#6XDR4b{ZAc+_fQs82&#-1%zS zT8LK??0_l8R$0|oyQHXBE3a>DOsJ!NDfE-?PshD#-+8&D7<)|>wZB|b;C)Exg zvKiXL?V*YxuNi)^v!l7YB^ps&R%b`3ExI(++}aw*=u31&qeBKb``H3lWl)W&oCIlT z$t<5!<>4I2cHCUE*{Wil;g*Zyfk?D-e$4DiES@PB&oH6QTH-?rnW80EcIst`g(YYB zPJPA#9v4{Ui0@J<5TcMd4$5d1jWl~VYJatoQCW3`QRb%1G6x?>$zoKNou6+hZyx+j z<;{b?t-M(zvyYfR>hsDv!}Lxe2( zwvZ)1BV;)&D`d&Hc;6P6Jb#FgMO9At^1n;S!Vj_TjF2TiD`d$ZB3V(j43Vsiu~tgH zEm_IWN>+wE7TS`P!mMN^-=jQZ2o$_5{TH674vn!uR|p(s%I_D zwqzwgD_I#*GAmihwiyb|yjYrd#3IqFe~E;6H)P2gHKB`anITe3n(Y{?2= zRGA@?mHez^C4Y!yC4Ya(iW=#ml9ha0vSK!vk*t_6b|8rH=cxTsvfIcgljdhB-YBjui*$;8o?{*3EDdn|TjEq#cZVr- zq?|o!9*oAfwG5TrVk)Bq``{=0zgQY%<$0(wlgI#7 zNvqF;$4oli%5}ef0v6Aam0~bRGqteD*JO5|K1B>VHPKHHY)a43rwIPySSKF+1Z+xI zny7OFLE9F>efIfiUs9uVfQeGCm-Pxm$giyhDgn@*2ruQ^` zq+cI_bdf&NFE2As$~$eQ_q1vHNa+01S0pqyqMqMlGEWOT+fpp>yZ=Bv5eAP-1G zn;2uH!MKrP7@yFIl2*S@i8PZ`6{g=e`e@9$E1&d<4cTilS~<49MSSumZcP+2$}9d3 z^3_Wutc}CXiM@i?KnHck;pC9Uklbr=3736p*Yu{0&Wq`#xHJBavh7j>{wMLvV=~T) zDYPS-{T*iKe4i=Ba*lYUZ|L-@`OL2O6X8NZezhRA3C`*ttC=xF`P*O!PLd0s7~?qr z%3<6rabtcFjtHvXSM_GW)pXUtLEM8N->*t|>12h2{T@p7W~@$lXXqoeY1n~5M9VNS zs1l?42<(6#hM&+)H+o<-Lk;4R2=v+ zQ}GO{gT};DOFthMYt%x&L!W`K)xh^@Zga6t`YMyX)YNA+$4C04L9Vh^{4y)gNc>q5 z1`jbLB~pB@{F;v4YNv>Au|8jLpE92vDcJ6yrFw6rO-f<*^7gM9~TLZi5@kpw0JhMFFoXsJ7G)1x8xlGw& zraYli&Sc7iD#bWm8t_P}Odp9yNSLOnxa^}!1zj}$W`9hIveNW$pwAZrSEuF>Ho?E0 z6nw^rY~xu{jAol+)cnWp(e0Wka-|4(b&BoW)P!}Ac zd^$w9O*YW!Bwc3m-%5QXJ;h2)7E8lX(n<4DY3Ncr=PlyX>b_bm9nv5kk~DP1)m$2F zqIk!?3_w$Gt&`3kck7LU|Is26U|H^{o0D75<0@#81~Jy^rFhmck?FUkmG9tN(+vDS zMYM9f;uw?$1NvN|`DsT`pMty6h2oR9#Vz-XkI8>>Y?cPP=zB8tnmz^pa?o8}F%zy$ z7tNOo#Lc>QJ7()HCAVLxPtnS;%P}Z13Fu+EvG{w6VjoUsX8o0OwjRdIsUp7o(izlU zV&Ix|oFxg=`vgj~5`dm-=^aFy@FjQ26EJtLwR<~s)5=tzf?uf3aEO$s$@+FW3P$HO zRFJTa{~Re>-(y=gL)B=iWc%RSBEA`&h?_bXX8)FQN zG46By2!S^!4fu03THte*O1b_o}?S0J-FJA3QQ57RRVXPB=tGxL{b$g|F;6AtqS z3+fmO#+ITtxiWn!106+>WG$1G%3H((YVS0U2X}GXqQXXEqcq4P)EGNtF>V4hBB zyz9u-nlj1C9faW_&L`T2d5&DOj5?|^vYJ+Ff7_P%Mx~aO;*mfmkTOjg^Xy{Zaxt+i z`)#%O#3P?+Cp(54n5#NZ&dY-YJ`Sl;nL*+LjBO@at zugJ^;lO%cIfd?M&$&#H1K6JnR_dU=hM<()jV9FnB_`gXKdRf>x8M?KwGo$bBTK!nT zi2uX?O_E&bh&TO(-f{GgrS~QMG5jm>UC&=iKalhb`BzGJ7trTn^q(Y2(nmtBS^6JN z&%(~NB+*W{lK)vsKO251_;>KH#BXKTxsHKu1OKx&^wvdu{wVOjdmO)!f$oF+&j!Y; zp7H?v>HB~n^wxx(E5dsJTrUb*?`{h{2dQE{qsM`6C&)~{ZncngPvI2ROa6G^|A~Jk z{2BbE>4QC_k0kw>Ohxq2;6KR!R}KRI7s>yJ-Xzs`2jXn8d~(4ZVz?TADW8F)ujOAU zALi4Y;bNU+dP|;w{0`d}K-Sx2*y{i47@38w{?AK7??fagN6WwI!JvEZ{`etKeNKj& zKW2ZSt2OlF=;8+(s_JX>Oz?Rk*d(F1nZGnWc^1+i$-h!MT7srmB>lmbzKp+A52>Wj z@vlVBdgv^$Ok1LT3hr75pp>WW?_a{mEVSJPp?9L@+dmX?H2_aS&z(-6nw}hn^as(8 zobNI8kEK^6-Se+RKfzy0pGvxza}s(-71DD_e+d6d>C5>`>HB*~A4q!Hew2PB=}%>@ z#Q$Xe()7yVDBt0xFMk+)YI=SI(hp!NqUUP2^uDA&+|rNbFQp$y`q!55&q?U{uhOTc z_n!;?&p>h#dfWI*(}$A2j)*vaKSiIKUU?qq7qh!m(iIZe5@S5K0yl$az* z|E1vfCaTPcull#DAE<5+%IXRF;XbBc$Loi7%T;$}=xt9D^sY%#Nx^@As_e+WT@}K% zQ*YT;3ERhNnsRSXF;Dt?DgB4~;|zR1MgJ4|&NvlhW_`ejA%3h5y>&{TOo0A7L>%eo zoC-Fl^zmb9=xtZ}ktN-$T{=`pp3a_!}Q< zc1zkI8akSok$#qf|9=(r-f*7~(uVSApsUS7!`&Eq4sJX0Sq4707@sv{Ph0&Gm2>MZ z;kb2U@q@=6RT75mU?_J6ydx~g-?u5kb zVze6hyvXDg`~RE})O93TU#h>X4!!NGNJx@%4d_2=^}_Vi{vWlx(@mk9Rtt$A?n|mc z9Y>C)!0)X6h&%ZkuIHtGavx!{^kn^5sUPkN{elrqgr)9Q{pl6@fwU~h$Ao^^FIiaX zRzN*gpZR9Ub7oOLU3adq?Wz=AH>p4Mv{ca_kG6nMxxZKYPPQWb6Gc7N{R67U`joJ* zqaYKZzD$3yB>UJFdK*cD>VJpR;Qs>pk@cE|ohPu@=Z?$dI0?Afq8xMnD3xOl#hlQO z>~DRzkBDef7?Gs$CK@oL+?zp&!h_cLyG!uyAMbP?XCKOioh_} z`nAe7(7&Ka-*i70vL?t{q?9DKi+mPzCA*&WPf$I~`sdmwYJ_;GzALAKzDrD5-`p>D z9h~(R)yF_^=N9Fw`L$q5vDbPin7R&KBkoYposzf;*-tcgvkB`L*0z`Kw9ViuIT50RI(> z)4iSPNcm%Qc@uQo7N`3p(_uJ}s6QK>0lEhj?Q_oE&gd-6sT-VgU)G;crUa3H&>YFKpG=K9Sqdw?aNS^Y^;Cuzfl#`bx>6I2dZ1E`!$vfn?Hd>()!L{gM8Q-bA59= zYHyKj+K_IW;)jCYw`fFv)}# zh4iSi1Swq>I%-GqOD&dn@HX(hq+H&|s+Cp!Ay(dM=$%Hj9_jOUfxcCwula{5ea-Ku z^tBv~F^T0Gy%+SaFP4j}4x{i~^_8P@%*KB4&(=%?)r$4-e(-%yneWe(ui8hhCk#jPECRTv|kup2KwuYa#gLpRgq&Bs;7&j zRli($OX67lL(%Fx6WIQ%~o&26$1>6gZeskJ=K?I!E zfyT7f_aLKTYxry&3*e{7}+M_1%SPR~fD{L_Tk2YO|w`YjHW(8$ZE))o@bp1K`vy2cHMO z(zvDW&SqQ|>Mjet^=8L|F93gowFB&5_^bA10;DD;Z~j&Mve^2!$sxE`7WF>ww?OY$ zZH#;?H-YcrRz7Y|-A$pllzvh@i9@j z_Ol?J43EB}A0K=ja(|SF$LW`IM%iNYDrf!;(3kfC(DR&VxZxgfFQv?BJ>X{W`xE_7 zl&9sik+gK#zlC{Z7FxSPZ(7$iE8hgYxQ!%~*2PmvFR$k-eJ<&Xu6s-R{v;gBhZdsr z14&mBp$m#3oA4^YeLHQQjSF1?+p-e^P$IgVxmrHuIQ}h5@ zp94w9E(g)G9d}M-bJ$|IHFV?JpX^`)z7Bi0sVW5`FEWze08y0s_kC=#7O9-jE?oy)e&8HD;$90)9)gm zvx|PE8P~g-r_jcu*2nNx;7j~FaEFst{>t}&`=ZIgesIR=^3{y?g){T4Qot>r@O^m^FZ;`rrk7lBN19$({#hOBflHKk z@I(Bf`-TDXwj7E%sdkav2HamQf9BgnKbD22`#h^73r$P_0XMxSx8n!iwBYu@c3F4O zVixMT(APnaZE!2sNa&9zVy0)kw$VLip)FLZuk=UA?-l|xp7W#4K^hk~wH_*W09RUP zX!>`ff67AhEZS8yzIOuuBx1^ZSr2XWCuySd)m(7jvi_9wuezO_e-^4vCuddvIL)i- zKd1wys?%lKs*WYAE)TjcWAgkQ@-ifX58!er!35@;*W_iW4j?U+G=L}4Qe?v z{s7b|`5=|6@+Zi(Cp*a%R~m2oAAu)U$Hx4}|A6mederO$VM#I#}($lY5ZQN+M=@$6b=vdoJngi|wW6_bKW@ z@xyz;?_OfY{7~<k1##+{>o;5CHQAy^DNn`>S6R3;J-`EnLpci%hkmu zS{N~8p@n(AEFh`=hxdcN>_1(~^ycR)e+BMR%7pXbax`t7t%;h*^aF64uZ@KMz~XvF z-QslW{?}T>DqS z_L}SizOOXC>~}iijVE;+(-+(&hGTs<75YS6= z(Dv0rXLOR`MuIEZi{Q3dJ4z1#KXGF=Pf$JPlKunRM_fnoUawj3FV&62*DUyzUOP6e z*EE3d!;Ahr_jkab%k<0e`Qrp{`EY&B_&XlNx+vrOPX_*RMg7;^9SqMxeK~derjNnF zz~63q=W<-S;6LRANi5ao3{tKa})aDN}AoT%UE?ke~rv7V6Wq)}r{qp~x?e zPZ{5l-7H$xT|wGb`3Hvqk7zE^GaQNXOAbeRY2Rwjy-l*8bML3EUFFw*4)B=8GCy9g z(0Uz7`aNbp+-}$Vb=qv#`xWXK*XN-p8^J2i=%2v<+oTNU&*f`4H1I4m=BapQufyj7 z|8W9GeAl&Uwij{zBZ0e~KoQq*uPWlwe+CZGTU0*yHMcZyq@VROf;)|vMV$6CTNIZ* z|8MeZV>+v+m%kAC1?z92KR>U08t%l<+pg`X z|6<@{TpQzSzNz?{wxsxR9J>tu(|XHa3O+9^=ldq*({Lw+-WjTw!OMV;ai`Gp6sEtX z=aJxgoZDD^U?O7s+WE`DPv<28{5t5yCPHTl^{4WV1Ye3HlW{*aqqQ`xHhez?|B}CH zxerS_X-x$h{A)YtXTX2b>XZBRrp0+RZ3n99F!A9LhRT;71$wNq#&YXuxqCb!02MzJ zd?`MyBjcVdbT(4=8vCoG!5^zV#GmDB+xkTt!$}ruY(I7+bhp!wOvm=pQE%UI7Zuy* zF~~>dlzI{OJZ1KuECue##eT5nFrdgn%|9AF;zX!9bO~9g@!ALdsa;i8fX`z2>F>GV zzHRlxao`j8T^)Bw zI*#Z|`eo)nMXon%J8Jp=SrU3Wo>zNI*MaXQ8yAS*-AA>y-mk@H7S_8fbO%D|2b)2E zKPfZTYu);Ry7tF)tQ2!E7VT-U1@v(|&2o>$tz#WlhU>8&wu0_YrFyWTUCaG0^m5Zr zJ`Mbri}g@dJP^kmTOSJdmAf1mbCbEi>VDL$_Pe`FC~^S3L$RwFN{ z_`!DIHx==7{yxRe)yMJ4X~6F&mSfHz2|Ued>Cf!qbl^*IqaCLXp@a2C9sg;2NE@Jg zQCYwDvcBqy>+b-rv_96w`omh|Hxk@?NjSC>_S)V`$Mi+^2fdK(Jv zN5qWzvE8)YMa(bL*ivmjgEPSQ3059|5PfPnhApJWaS#bJX)j#L+;D3$$&XI7Tdyc0>Bq{L=G4d0A1;c|WJ^cAg{glwa~1;LH1D8jn$t^cNdHZpR%B z%j(F+2aOzc4%*R?b7vioCFxK7Pkuh~dx6zA^Q-$mF5#|O%Q3hBxHnk7T%S$ua2aVu{IuDovU{+;b?h4LE-4!t(jH~ZtRtaC0`edKQezvV@}HT`Fx589zQDfG4~ ze)#Xef5!B|<*NH*6<H{Mi@^WPwjc5cgPLqXFo@iJx^{z2jcXdd+ykb7CBy z4c`ZRX&*x8tC^1X*-`e^SUsoj2VH4Dt1a<2t5wgH4*;j*7H!`;ZKvi{L81o@WMFFOvW9@eYh>R$@nWi~$Mb*=MuS%-DrF8}@q z1>LY^r$^^BJ`TEVMLFlr6fx)gKr3ucN67I5yHi=1tLebNgv$5xR~J;pew~~H)Y&M&E$sh@#yeM;5S=)=J~@Wf0r8J zrnAY3sa%z7fIqI-PTJNFw52E6M5T-4fc|HM4nJ!@-O+J?>SrW4oUR9yeZ zYU9UxY-7KBr^!1KTs*%EKN`1lv9F;1RPB27S@8d1shs{Ct@idQq({}u@N>X_+T>w> z5YJ4>Kr|CNEl2-)(3Sefw(O4DI!AH?aHajAHqO6cxJ(mD<16^rnmnwZx8_@Uq%MZGlLIbw}XcS-1-VdMYw3*i5B*)R-ffA{sAwxs)d{^Z9A z@qN8hLXW#%?VtO%gT8!zT;&-^`mx1!+VK}@J#}0&^v+hjBtHf}>{gL|v0mmO&7|os z7aWac=uiCGJgglgfQ_MwFnE;RX_p8@wZ z*>DJGkIVc?I*DJV>w(MMP}H`}@6ggNb0bo}>8J8r@VktdbG|FX&dD(6&Jx{DDP$OWJ=HTDJ4lIwSPf zS^fVWbdMw9S)PsbkI6fb^m4n9xcjL8KY&kZoYRr{*a|IIau;x=b0+hceJl&};+Pbl z?wf#Z9Nba+)cz>H8*<&nrKfrUown0?EG%bX9*!~#^VlaH-%&x| z!BZTyxBS1r_s*idP55W2MNRmR#m_@wg%e@Iy@e~9nT`4R^d9glokwaooTjt-7z!@# zC$OE#YSD75-^#t<7xytH!_G6P#o$l%+85l;azA#3^<(b+EHN~z_MY4i{v|)zbO`)8 zPH_XlmD*9$VcfM!ars}tZ%^eK3a&KnZR@yK%auL=e%Ffw6#x12UH&T%=i+4a{lO)4 zmW}>Yj^sdSY%%_d>kCfv6aUqMsYHJ`6kKV%rRNi^{QXBF-)E9AY^PkW^N3nsVfhRN zx1sD0+ES{P^w6j-Yj2eR{(EYlRzAUh!p7TjPPV^Kpjc>a$LZt2_q9cN>i&2&embDN z%H$s%1pE=km)qlv-=z2%{|QGUC;aJpSmnu}JEYiOHDjE1(}u#$sYtJK<`vNY7HoC= z-*(rF-rB3VaTxw7z?aVbHT^coseVA^OP&V&{iX*VFLfn=Aq!oLNAJ4-kUYE7mS6I8 z&__Gr`tQj2Eu+X!IwPX;4F$hjT(Nqu4iB?T@BL?h|68p5;Ez4tjEh8BfR@eBP`bH8|%hi8A=-x@njDDx7Cs3O*F;Mv`F981VVm`And!h9boX9WvpZtZO z+h+X?*Uz;3rN}q!SGf?!*Q1vJ{|OR`FJ&3QdWYF9|tc5{wp?4gujw;N*1=N z!ngXP&>3f8tF`{Ea^5QoTQMuu@q^k)e**lUMZ&N=+O`N#LGctgn`x^JyW%%HqQ$ zKuPO6T?YDBm+7&eoQ0O|xI<%FzR_~f?OR+wxOO3id0o5UuhJr3yWpQi)o=94TF}3w zGqZDmv7Keg+09q3mR3;KkM!ay%QDRKOXqfdChi=yKA3Bj#2T0 z6M#RqsFykO*K_XEbk0`s$rj)bF#Csn#)DH^*?%g&G7WsGzpMMNp#MPt+u3Fhc@BJO z+|m$xTC4rlNN~GM4%SQ4KNNf?Let$NCKyBdwCyx0%#oLr)IRbm^4+(n=c+$mtG4P- z0c&1A*7EghpnJ0EPx?ze_ut*Y^{MtgI2HKP`GZ;M^le=~a&{L6}ZnThu_%;*rCPjD-Jr4725 z7V~LKJz7Bf_=ov833ktDF|daPQ1y@bE5P@dVtMD>{ZiR;IEX;|b9y9WPL!L4Ic)P$ zBF@ETJpY}G^T}Zs{QqKlhko2m%-_~chOY#!6vx)J^GCGsd0OO~&jVlbUv2jX=36uS zI2*VaHwOPK>o#k3UR>D;+~p>x<1R++c_irzMg2G38#%u$j71Y&H#XnS<^Hb+B3}+U zQ-7E*Am39h9rEcsw5#K`F8Uvx2bo+#UmAb3b-g$qM0ZQ6e^#xo~4`Jgw$O`%7N~ z{Kdqa`A^b1P{(0od6U-fNN~p#`>SfaccJQD&qd2Zbt?2U?#lFW0rJ6r&2Pbw`j zvK3Y@eZk#ah8a|=oE$iPF@?NKh;C}4$znOc{+0c$7zbIyc4+6 zzC|1Den;Hj5!~fg4sNGii&yDNO=O{~Gp?@gv3Fah_x#<+@4rb{wj*v2Eq93&rnQy_ zOWJ<&_W<9ue1Z1|s~%eGsPNhzNACqbo|}coReqHL(qnunm!s|8Ao;g((nIkh!S6N? z)FDU5HRv)3!@m#wzd?nI>GMp)t?MM5B_nTh}&yJ?mLU?iC_5pIIX|*i@?Qo z71ndd=JTB?8cvws8wvh_Vn3Jr+puV-_B6T?e4kUqHQlvhhE1QsvJio%{-yFY(7gfb zqkCC7qeq+bXAo5glBC))}%^8KDj zPIoHGsfIY-uiOm!Qh(64^@_Fz3eiI21}jhUP0*F@>zEh+vq9w>39eM{^E&>UN8n+m z$3JvFq4hkt1^JZj&lwy4yF00(RnLQO17GUr+wilijBoNC;NpEGY(HJKaB7vb^`mm+ zdx=*&8VbI&Pt~;}VqM+0p`4alKIwNMSLwWY+k%a#8rDU7843Pwvpa5AWBIt(kbYFI z(XHUG_o)Qn<(vZDHjD^B%)(;(8GH|PCAk`|&(>x8Y3cWY`&`i;>pGd{i>-PmTDwVp z2z=Z>ay-OUz4s;kfg-<_{{dQ|+KZO2e>?cS)I%XszC50z&vog78?f5x@W-Hgoy~iB z{c^RtfXkPK)&3^rNT1(Zg4KkmpZuqwFU{W@4jahZ)jpG-0f*B?YV>yD=jYE`evFM+=U zYE`{PyTQPa3}^+-;Z?6Ap?h+1zj0$+pW5i}(V_CjlR~eJ476Oy?~!jQ4!q@$!g@y* zTC<_&?40B1F5vGd`tRI(#pg1l*^@TdJl|IeU9uB1Q5q03s%k))U6e_BqLzgzC7QCDiC)VCTW@7a?68v+D_S*7yXnSaFqyfB+ODYF||6Rp;Z1}?!-_Vxcz@Bjy z8XBQOtG42iP2PutUunN@-o~@@CsX%i_>th_Iv4A!Dg8`FJ3KTlSN?-Xfd4f`J7~LK zv3|)PT|lEA#pjO%{)%GxX5zZ<%t@pVwa?0KWpa}W_2Vp>*^uKefA#%x=Y6Q zbykEm{@JR>HO69%e}m)TnIw+O(kFsGo)=z2=ZrVU%LX_5%a)L&_)~wI9}Kz=<7f2? zW0wf)21vYMcy;KVX!TzK{WiHkU*Zq0T=1J!@GBSm%{YXdg)0~A{O^?u6oVyh)$~0$ z6#Pyt>aQi|NGJ~IMrt$K?ka}^e@U@^8vc3acWf5Yz>*`iyB`t{`qO$F9RWV2`$2Pm zv=$@xPaM;q+DHF+po{U2+>dtri#2~-e4zNzk-(Sk51qCBfY~W>lZLN6ANbdqU9f%U z{&V|?!RGEQp|@T64_^ZOzQy*N`wdnOw>|XQ>PJQ|1D+pEiGD=m3+>+Y{>y^g}u>)o&#Qed)Y^$L=kZYfptN+Gi$o#rmz=d}X}HIMvL_>|<~g=npRHW7a=d z>u1*Y?NcuYo26KB9@1(%87>8%xPHU++;tjYDLbTD=xT%tkD!cx67**j?YeDeE!tRU z&Bo4y%#NMMO_ziI;YB?(ZQr}8-nD7>f;QPR;ZMgE`AYDK=gR$eNhFmsJr?PuacKwZ zvvxae7CI=VTvKcOM?xR(U2}gV2{ixFYVeQqRlg7AulAU%L3$}(T<$sAX6>RcxZUmx z)U^c~@SZr>CyAkW{ zntwV4dCKQn{eP=5*8HRG({_z&X7{y|0&U)D`^qkihRoea9tzDLKuM2$fPxLJs|8)J=- zx5PF7(G2*M#``sQmFO{s6&z3VFV+9(6wt-xG9k%c+6>-a$d88R_f{iUaY&q**N^)n5LkIq74Dc@wE_<`W#`$Vko zW&R4)-?EjV=hi8HbUOHRWk-E<^)?goOQb8+N8SMbhXiIkx9@m=mjsk%9FTp6`Qda2 z=>DCEn2z<{k$du|wSA2Q7uWTqU$h164(mG$T^;pzX^let;IN5&Vt)boZ12Y&hUw~d zeOwY>7yEnF&+rWJdr@(`H0vL$t<>dT3=KnfN+21IBDELzQ zoYniA%j1{+E5Sd$p9KCI&n>re(eXfB<1Y(Tmft++OY8G(o8PqE8>xM1eJ4ACFU1{n z>`{;o;-DlO%V!{T?;<6P=|TIu4#oYgi~W4M3;9ShPJ*0}QMq$TkLUi`E@ad|#YGRV z_=>z67W*&YE$YAH_-K&glLh3@!Ims9@bhAA9b9K){lq}<90b95$6I&WkK}#u3zW#| zSZUY%`jRfkmh^nmyq@WtuX4tzwF|+&q1f*<+^ZOul~+=DusL==Q2!jrqw5%<=QwMX zcO>Z?RKxN*zFOmHC#%v*hQ3-(#i1`8?-jn74K2 zdB1%<1dl6rCpO$*9(py6De8T7g;7jqy zvl3Sk??t!}xRPJjILWnYSII@dmG)m+YKIb_PzbzT8hXcSKQ|KkyKFwh`*wHiw7{P` zcG?=^9XtIab(nO=PWSoHJ6`?F=naq~#;I}ptLy4UT{eWIn>GJ5cq8cIz8BNYS=`s0 zLq98oYA@-VK!?>pYJY48dcCH_fer;1?}^$%>mYyHVHb$~X$S6l%fg>_Z04~H{b~IU z-i&05!9VSw6kQ{q!Iir=M$Ngd~@-yI6wTZ{goF6TvPG^$-h z-S!g4hN>4c*DTaG1b*>P<aV_Gb4c4h6?fmGcJJ#d690>=|uO^0-s=)far}+-Q>)pJ4xI_ z14F^7Uz7g3dxkW1?I$Xq0KZb)Ow-nBnzB4Aj#2F`EPZEClU*0Biim=MfPzR95s)q_ zy#z%?KtM%|)F_B_LhmFLm6sZdh|&=dX+etgBynAPUP2A&$<24~oym_gPcrk& z$!Ytnz1G@I!hQ$3@}6MZM9@xuyY;SsZ+pfG?fr`Gh33bvLC%>645tepxeoJY-;!lk z1lG69Z_&NWchdermY#VXEOMS$`^UyeplEqJ8pYaDXbTKuxEoTuy=$i&tS&pweS73! z0^>RYNGSdh63GVC;g$?k&Ha88epv z#%|Q+{Mlc6j89sRfk7{;Ht`rfO8L1AJ?6=^`L*(O)2vVBz@2VMwFomC1E)}rfgJ5> z=%smk>I7X*e_w9KcaUL&e^D4MP%rj1j3;P@YxoHhjI{p+ zzTGgN)!0!pVs#}X>nXjz;eNwhFq`d^OOs2cm`f-;jEy)@Ve1pRSVR!_IE0M}cK`5j zIDiyd-;eouszz-pkj-qOoAi>bgE2a~AKQ5?i<=#{`U+K%(OfA#u^JKbSo&OOJyMmb zO9ac0{YLBx{B9VDN=_%lFe4_XU80k#R(fKkyq_sI!e*{>Uv&h5Y&2ED=H+j{ysOTe z^HGp-C|P7rHz!l2^I?Slj7Waz@=0^F`^M@ozcT8=fporOKLQu6FAOPnbi1|}i7}#V zI}rYw3wIr^Saf~Bl@Mi!Urxl_Oto;C&|=re(hZhBH$OV6?AV)p$P7>o#d|M%dh3yt zt{aqs+zg?n%>Da&kmRJnp&SNQ^57w5yZLigchRy>dwP$~(KV%?_`6I9HZQ}pJr%?R z3-?aNNx#d8scx)7>*aPn`Kla@3FukV3XR?7;IbQ;&Bz|Gbbx?wHVRl$99vusp&1|f zZ(e70jqo23imJw*dHX z3+ocmMNX2D_o4;kO{4j)z+WXA53x6)SK9PbCH)Sj_SdA+qsq$bw4ac1Ks2uOSYq4b z&Q3Nv!toejH~|ECm5jpT3Tc;It};uDu~CgEkfZbF*6$l%dU=Ch@8AT^TLK{@CJc^7 zJb#Rgk$+H57rWT>4sf%j7Ha4I)G{5icsqcEIbchzs``(lr5t zfEhO8Sct1N*5%O-Gz~v@6;0G9 z@I;x=E2=}ks^lFPwfhtm>Y&WXWH{Bu$WKts zdzWp}6}d5>W&om`qHb$>$pTQeIw;23+?bSjXw~XCj+&48H@pI4L;5N8|2rwsN?)wM z*Ti*(zIEXLyYKcktTRl4qqh0B=5`MSAsq?7r~8}6{u^uK6}AzL?>}tV43AnhoX;@M zy$D$i#d&in7qTsf_%RC~7b_9RG&113fWT>U<;v{z=yt}!&9ejTkRk_K_W_DIcTZ&D_Vn>XF4!rWvMTOP%McA*x3J=IxIQ~1Yj8$v&+#p_HODR8 z$u1~4^-!f&$hc||5tMi$NkZ1>*}mF@E7bvZ{b z4l4d3A_Y@YIBc`smCiY};E8q0`BAsJ_VOF?Cq&jD5-uG0Af+8kZ}7S21-qlA7d-FO zgc00*%@mge`qg=M=6xbbdnmS=p2rpC=k}?N*ID zVuDifW`9ety0?7%-9x6TRU(rp$l_&sw!GhMi2{GM54YbHM7Q5l(krKNzPf>G`abVl zZ>qfxLfkw~vIuij*)I-Qs75$8H@Mll1N7T7dKe;DGol3#K$D9as0F^U_v-NNx0{B# zPZ)-&?;81u(Kp-lmnW2L_Cb*UHdZ=VEeIbuaC0Yx7QG)i4xW=a1cvTKoZ|#FVs>>% z90K!+=k_UuCQipkFzj#x#>sOlS<&#ZMOb=|j7Oy;JE8qdl#O$HM*fJBu&4uV-dTt^ z53GY#w=8UiD10AyU@>Q8VQTrVVD`FnPM7-DXeowJW3RRGurMV$LU8wXG&ttQbdtDY z^`dM^q*=n8BVa7u|4`n~Qp7vYX8!;gP=EAgaKP%$(Rl@1v*d%yWMoMxeC_?;ng`z zPW{1yauY5C+G`YNSZhf{ufox5eYE}RRdyWW5mE)pPxNsR+Xfxgwh^Wug)N}?Z#Wg; zxIrBTSBPe-ee3s2AlLOoHMDur9D?V$8e|_LjOG&&(pDNm)Mfg`jH>t_%~Rw^8!v!N zzm{>#cK)tqb855=7o#-Hg`1jZQMdiW?3w8#6Rty@3ft7af)IE-BV68C$8BTi87ygE zxr!E;&x`X>qm}*?Xim-&z561VvK~=2v|fb5b+pxvq{moY0q};|z}8Pxx7jctQMHRh zEQ~Q~q-RO~;Y<_dc(ScKNXI23*E7i`v4IoERe$L#T4(yzxcmI>Q760h7$xr3*BCsB0xS;<(pT6Su=kWg> z{kbMF4Nu&uuY)CsoAjy)Fki1z&xyxb0c_F?{GMnEi^-o$qs9rOLA6K*Pj|q?KBax= zw}@NIAM_b!n(0;p!n{1o$*BPU8F_^$kb394Zu38Y(0B}{NYGUSZ|ijA)`yA)$t(`z z2o@TYD9TH3U5lfWFlP^~1yl*mjI6kyxW1&0+{+bJeO2RSHmvl~XoK=vapkLM8X=0_ z{YYmmpxg}yRJ@?m0=gA0wqG1tI!mv1hGF;aHQMcfEUMDEBajnN|LR~dd?ttD5TH>k z8DE?Js!G2-$MIzNXp&3XS22`9Ha_4XN2_ORKO+Eo;$-=HRaY=X$RdgUyA8zA zm^-lh&cQ1Ll5&|yjvO$U| z>{<1m|NQa_QX={_nnj6+Bk(EQW_Pf$!VnDGE@>N)D0V00?*mTn0?VIG zVn>DlHQS9a&Jd3Z9>Up9xD~X3(=Xr(&4A#Xel+98hMn8VfL{~9KTi)|L6rls;*j4N z72Tl*Zu9|fE#{d{(%k6ZqYfaP`O(^!=3@+rw-}zZLjTXp`!A7Hp+D=(-$mdD4C-~& zuksZKF>gwf8bs?vB++a+WMvnDqu1a;KmFky34t&fQI8?9V$RScC7Usb+cC?&(r#zn zO5;?JwwCIUo6&F;9~wV`|qvvOLGj-_+X(-x(E)FB@x zBlVX%_q z=m_u1-~PEaUTRrkHc-yks*<3g&|)c}koPT1D0qk3vadB~NZvFq!*uTvosFN8W*HM2 zQgeHo!6e?y1Zgoi_8G=Tj))rJqk1kKR5W(^cNmG*mN7iohd3p7IfHtoaV!W#5X+E* zD>aU_c>8NifDgh*ESQEl9rdkbt;U@P!!Q}f%}9~-jWtQDfVAKG?dd0u)&Y>$M6)1ig+d;l}o>-lm@L>mo@xoNhdiLG-b1C z&MwMQPKoWUu7!Ahvwe1N>d=|6iEKmL9xBz9a-!z_Gh*z*G8_R+oT62gsxkZL8ePzs z=psoUCzlGJYLEFdY-H&E<}H)h;|x8;w~w2l2{+!ENwN^BReSQaA-|$myT9@Cu8pA0;wMt z-QFFoM`9LTsg3m68U}dPOe~SP_h0barEzAqR|*XCgHf=4 z<)9lx>aSY{bLjl0hT^%QGqx9s*GoPlp|>|Ou54Pb=ol(r=604lz{Ov_F#w%id~C)_ zE1D6X22O`6uvv7)ellac-P?cPV8#adcHq>quyO2zP250P%Uifl_uai8n1$DRsZ$Ox zGTm{3>m^skhQns-RVD(ohewI4D<6BG9i#@%(W2c;EarffuJ2wHg zW8x;obqhFF7@;)Sp!o{xp!8?XTI^2Dcddxux)g(GvrQX=Ik8x#_qwEUWc-2Y4Ro+g zd}%I=*m=bv=v4#n=RbGr7gt7KiS(Rqn)R@@Hn#38I-8bh`|`{E+@r?&K`tzfXrEk$ zF2G=EKzX3?)P>rSj-lu;Y9KDU*&TY?+x4wRuAgC)sL&+Rh5CjcfJ;4+<2Ie#zzDDm zu0+yX#L`(j>DlU4_*vxbs;?InfL3|Kz|#T*@uIMQ7AAcLqfA|eTl`_tfU#r#fipR*%1cwC{Q#|vcxk+!-V0dlZXCmN+N|F}OP@9y|8#J&HP)4gZ_C&zSi<_dZa)hRy-*s( zs0)kcyAKM1HcIWyyy$K8V-f%>Nt~u<=&@7Wzv%B%yXP(8<})wR77!p zK++Xe6SMcpqUdRVVb@Yx5NOQ!pwGm%-m^Kh(f;;q;;x?`J5je#Jt=qJtZmtfUur&( zDQg}qVH#Zsv@8@74LRjoecJa>c|%uU(v5`t#%v`EVi?mW=|;^->p|MPudEOo#5cDZ zwTyOW_sXY6EaKyam4B{bW3+NB6}H~CmA^G?LcH8Hjg~c;23iM?DXuX7o6Nu#d;HE@ zNq+)_cCCJ!a8$I8na`)$C=#vXPVOci-Qas`b!M#g_O9Px&}`nX4`NVZYmkv6&GFkU ztHlM|rM(Gy(8t_XtNUQEx3mU41{ystiPGUWU`}*13tYQK8```ARz{J{vgR+lX8HQ} z4{MPY)YEOKmm<;TNyjhR5&4a6_L1qNTZpJvp5(Kx$#**Kl?7tG{b{Y&h@epz4=6n) zahz^LlCC<9JjNN}7Kc`qTfd-uboU9ozH7s~kR4m{)uk9k*}^vq3$t%me`9HN6x)q@mcdG&!VVVC zi7MSY_Dug&CuR9Vg|=43wqU<*@OO{HbC>>Ej-+DOz$8V0+q)9P(i`=2#wR)ao;llo zRtnZSj3p@FrTKH5U^@Ld=vvpM#eU)^pwb&$^5pn3hU72HpYZRW%82bPk+AjuEV+5T z9@j2gH=f#BkJ&q&D}ZRSnHSXxG4!d(SoOO&eQ1d0(9A)_q2n{Yix1(Pl z1Iuq-`%*9D?--VzaWz)HB5ujHoZ5X$ng3=*YsQ15F{D%Hr=UcW`37g569x-$jFw8K zH?d|VK`Vq>hE-{om@z`nkb3x@@29FIRT5e39I|d`_Cf1ckrsF7;M9SLj8)!Aw=V2S zWbI|NKr30TG=~4dd+FoX>zh{SK7%%f_7T=y%oe|BtMzH#ziztRFB%r*siw1CsmJNj zcJF$&N;ADz54hF4?GvlRdIf5x{UqFYLuGPrHSg4d{kCPC(0MNB@r>;9cyEusRXrQC zoGlENT)~RHYW~i&Utl#E$r&fAHOP%`22^ z+v9l@);q<9C1sRtRMzSo?}QonNN&tMA~#)y>8(${ugIpAM=-c~HMfd)y>vBk+aS*Q z+6!Ik#0y>irag!3itteHU@hgB-!jd^1U!#j_7w&}nsl*|*}KRsN5p0cLl9%U{fU!t zQ-pADg*|-sdufa)e6#j&Esq5;1*^;8dXVIxaW0CPhMb)U<@QRUga{Eh!Kcu?kRcRs zfG0!W|LZlt#+v__>#oQh^lO1$HBGt+ie0xiedP=OkGuOL7DZVO56;&mg^3~D zu~WksH%wq`4CKs?42)p7>glpE=+ZI>K1Vl?kh5FJtn36LFW8jY26#YoJ$d(Xgm-cL z?E?6@`aski{khe+2SMh>DnOxkV=f=?5h~d<0;$v03UYE}YPhLdZ5iZmKM;G94_R_X z2<<=GU*m5R6*mNqKvEf)04=jameQ3I&6D^zGb9`GK-8oTT*A@ky!c6s9n#>lQJ7#7 zxE5q%$`l;J=%4ZLy#KR8A~8RnxqI~5%BOV)X!Fj`JPNItX@Xq~{?Tam1^tQYpk=2| z$f$%_w`^B?DMc1a4a4*r<}~i_@MbM=;!fdA9tFRrx%?6pQF39VE?#JDWQ#My+;Bs( z7*6}2J8-(AVdvJkB8@R;de}MVERmVqHFI2l>%d7%Di1N8Jgz|iyQ`h(?)(q)y*}-v zznpb3gCR{!U(_hoY@I=m9j*ablLQ{wYq|dU-99&AY7jN&fwoeDluBHFNaBK?5J;{q zPoZ4%)oZYSx=AO1$27+!=TIx!hDW<%@+)$Cl^)b-6!w=aQOC%fv>;ik3G5j@2P`lY zHlWXo@~TfX3-?+a?_KV`hEE7BUra70Z!$!~j0mG$<{r4fj{yKG%f2LK6ZxPf**JOH zMPs}r0+l8kyB$k=I7fXbq~pnNe|?~A#BDRE)<594A-~QJ zs*(6Meh><5N)cX26@np8-CD+W9MHpfW0U%5^C(mgq#2_Yz>t0AdM7+Jaaap)`l9J5 z^gbxg{AU)bbl?NbAl3n!O3?$x4Oa~`Z=S1c=x3a109DhSXzK<13b5sIN+g=q|0mRW zlWHo(ul1773>-qWDhQ|(>W6C^pwO{zX|QL_@A_U0i4Vy&+E$#Divxc&JdTqXKj_f& ze7(oNK8eC@wofG>tHxD&L4+_{L3(q8%yDM6TPv{1=hVsw<^G0;Lex)Pa;lxxUL`p& zLLohtevxr|KYrbFCigv+5;5Fy5wKGRDPo*w1}$qN(JfKC!^_}9mdN<^t*Z@&%PxUs zc1H$h1a_lnPS$g_LPz%nS9^{E(wPCYM>pihrk`0^>28$^2hc>zbNU0fkAWqPi-l++ z1o$h2_20lr+jPkL_0?HG(|aRu|Gfx3hE~67OchStQUaVOvRUg^?3EE4Tqcbf7urN@ zx>O|LF5(QA)#=Ik2a`vrUy-t@(n_-=(cG*>S^egIdhGya7MpR-kE<-`|3iVTxS#RWymjHvlVtbh1ERVx; ziY!*&S&w?W@FpwwP*;CIMe}SJwuL1DJfq2`b5WTer4pvPziwV&-*o)yue_gGVP0hz zsh}-mxkAf+FF+||^)}`^=&eR`fLKsqT!VXsC^s8j-8#bo%AnaoYRU6^dxxN3=e>WY z(U0_Erryxvay?<#E{RCFZnNJv??A?SRHDk6ES~vs`la9TjW9OzB9YX+n&-_|ypbub zr${0%DjG5wnNvb|@&P^JxVx=4NWPxX{1E@1`nT5Fa~0ltmZ;{v`??ALyeczE;uCWP zFQ!!gVImXg?dfW2GNcLLQa%l zldOT_=0R?tBG_UG{a02|=UP}0U&`tm8<$d}x9t0;tAS42v_e1Nb=U36rv`?h_VT`fx%pK_= z=cZJPw%t;jCQQI(yvpt>f^pdB>nBeXHSbzKp#2xd>EZrUc+tbd_X4@8iD7C({lzBG(?D~ZgfHMmQQ`}o zb)1}h%O#0AoZX2GH)!FcQxds zPXXhmD-DH%Kbw2yNxc6H+pQM&epatIhQ0{#K{VFKNZGUW%qC(yrQfIIA=#2Ye$mS8 znX#fT@$W0-WK_*TymN3r>|AKQZ{+rSXVr6iFQDeHpeL!Tii}aou&d&N+c2|pwtcX^ zbVWY$bwBgfEe`s2*Uc}5o_nv;EVHi?ERF>CL2t1FxdtA`j5=@-HZR0qy2QO$Q^=-H5)Tq}! z;37njcc{7N$Awu7rH!>5WsKpdP&BWwD@_kI3Lip5T|k-&f(K`nJMJT zqy;UB>K4n53N8l%?MKxaTN}U<8K)V}c+P*VcBK2428XTt&4Sa;^mO0$b<`8V3hw%W z<`(nWw<2pcNo=LrY|Yvfc}ITVwy!e)bYv{!bVx}fTluGK#Zo>~GNLl1CeTPj%E z<33V8?BK>zK*^rK-)}d1t6D?qSk<@4R*W_?E*twX&nR7w&iGCUJNozz$_C?DV zj4wzoD$-gWD6$~uvCaUA!`)~T3m^T?h(cchs@U?z)b9_KkL-ffF*-b&p86XeFA!_s zQwt3ztbiu9ZE`LJ+(#hi0xaeZ@j9pu^Pq(8=n@7O8O)Ep#?+s4dv{qW*HP zTs6urft@W0kZT-L^-*=8g^=(6X^@75GxrqkMQOvwQ7xmhY?K^0LSXK9bf0Z)&PIRT zpr5~P6^T>H*2rFZSnj}ru{arXy_rj^?_&hvZi3mN_ECndcRqyG}EXH<7v#Lg3vpX?= zX^Iys96kkd&8xfEb|J3ZW`JR>3!eM3Q6tkC&fnNgEQR>AEBweq7Af(A3P_x+CHO11 z6|)TH4TLkKrtibm#jppH?{%z^--e#Sou<{g_u1 z#?CnZzi|>)Rs7SPR0VPeA*FBYpQ zX)l$UF!Wxpofu73&(%Of`|L;b;QdLTc-w!vAJzbu=W`e3o*+*_iW^6RWnh)P1lESv z!B3%4@h6-nOrz1ULYvpsqlz9LeDr8c0bqz$K)HxmPfQCOwzzPUnD6<2?_lyruO3Nn2v$NQ!>h^0NIGG) z>IpfrM~r}%WxN~{^VmE;{|xonxH&1h3)*KBR(>FO9{0Q_hFNckPJT9~0uo%DVZd&N zkNQAZR&6ST({+kUQt9ex+-m>*)NU$Ln&P6QBEN?Hf47);rcX?`GAT9dIlX&Bj@b!9 za;|YmD~vtmVHwlkPU#Ed*;t7QDyc*0Z;~wf12ujoWoFK9`CMqM(5@uhX8@gHhEL|; zD!h12T3yu-571|JYsikx)C2OWXP*WZN2{nRyj1oG(OrOWX5UN5<(ntd@5#<)dYeR}Xi#%b$)4QPH%b3p8fKGh}!R zfsg9zsIUo6`)G;Cd|NN{E{ItVP5;mD&i?N0vqv=y`vX~)_U=$SLFv7sZem6XhUmjl z55T<3`9~~n5JwYn7qFVI;V>$h*=zQsM4h#{R8rLf@L7`xQvzi>)Zloqqfn7u9xeqq zV>bsoRyv`78)BHZr%$P^F6_jPjBrlL-@ENU_#j$UaP@}9z@F&}o6H~6CLOqd$oVht*mm zdeFY_3g`>p>dgDqAwtA>h~Rlsnt(yg(@#IfBRU#6V=Ux(p5U|SXAYg4_{YH@FC{6* zg!G2K!o7(dCOqF8A1x4U#hWoaTx;LD_<|XLr$Hm%ykAFsu=y&P3K7ZLc>6gx^(Lw6 z=EC7yEpS(Z?6s0)zDb`X2xr(*_|$G)XCk1x7E-VDcn+R0Ms1m_81~fUJ}@XM@{4W0D-R?9$LIVra_%Vb_wA ziHzgd(28mJ?$PE@`NEUfl+39C43kTfsitz=YQ%pn%}H>Oy#S;F6%D03Vq^CB!CLR|%-L)+Fjk0lzQiZRE41JR-s46cTHRc#j<8NgD- ziv>I;M1mK30u!ppF>kY~*1*XEe~UA?eEh*n%=#E1ysf#HTqC~(y#i($!cQ!NPIEk5X7rz5KL4cNo4CjEAr5Q&mkqgAcD`3ZW0^8&zhvZJ1m%y4~`p&^DLgRN8eA zGk&Y*au_&j41;1O(Z($q;f?BWq4V&2=rnJm$$lMP;aAZWkPo1)Vs0h|-@$n{@T{QO z%5*!8?Kv_9!=Gk`HxE947|y2b0Qtp1)R;Z4IJmC29eRtn&inPi27RP{j;}YR?=(he z-Lma11+e_0eZlplhcr!9$^9$ePddLZ*R9Zfpp5Wn*1@qzv7Ld!-JQ?wSI#z7modKD z-Fs1-FYhyE_!qpdUZ7N;Q61>85TMEOGsKYlj4s^Ry9EcA{9$-p)r7IvmdF6^oPPW9 z4t?%P9FX3}*9NWju09DZ#fXaYU5{O7g0P^?j|Y<+bg`4A`(~*(l}u7^8m8WaZmlqS z=Nft}U%e!F1{^#)n{KWABq1b0>7LQD9M7ZLz`NrM#~1a7pN{`MK0e+F9vQFm7+&&H z_ubgkaEkw~efhp}kHH^RyJSo})7$<|qzIqM_jBiww`D~O^KZz?%5MFc2URkx%1;(` zdYPhgLfAJQTgGqK$ubLz^0HSJ8p_erF2Uk};k+F2>tplK(Gl_8n?d|5p~;QvS|vV% zn+S1VpU@5A_r$PY4_CNyImt%ac;Q~QUPaTH@ZEnnIO8wM%;*Tzm!Ptcfy9BE2`s0#LRFTg;R)N;D>N0?V zs%@4VW?hX&^Y3%8i)`+;4-|>f+?V^+#SoWCzpOGUz>?XD0dE(IzkMVRsW0rIQt8(= zfHww9de!%oMvkA1yD3Er)2J!`jR!j0e4fGSJ_3;B^cZ5M>NSC>5~HCfX}`AVio~7t zwq6|UgP8ZhB|1I!IRS`foGlUI1;m8o{ta2|U%=8@Y3{vHUgBG=*xhsK(oU^VC--Pu z;c80D`F-JfO3Op>!)ZI#U|AxINW0j^=Y1D3s$KVT3H{rXy~{z-@IyxyJw@4x~? zl^%pPB)6Ikk+0$YL%Ow7j#sBi;9xJ|$+;sb)Y^0mT1iLnA(^EjXiga&m}k%3cYCbL z?{6%i8~j_}rOji*a=%?Lrc3S0ut*%HRv){5`G1WPj9G!csp{QRU{?R)PDEo5LgR>jocnZ0 zyvT-`HYAWIFWcXlst7|{bQu)J^neeD3Xj4(b_)&~LO+T&BD4y~7pFw7L4W0sM2o=o z@SOseL%gbb*S9JAO1F+NqOjo}GKPa)r*RHDUQ7AEtpRM>fi5xX0k`M3XMh`;CEUoL zBhjXX#8}4jxse!n(d!aR>IcPt|6uT+Z%W=$KftFm;6I&9T$m5@HZnlZmrSw53%2#8 zVz$o@OplmN%^4W{^=bIIeZJ;*zJTTzkDN`;+uoam!EJb6@Qo==*GIsc&BQl(N?MTr zXz$LZ9)B3m*;|LyN9WuW%zI%Yv*XmPnV1L z-_R!4k4>2pKn{ItOu2)dkuXkc<_%c9iRj&=Ewe{eCJzsT1`eL9m=Y*L>Z7t$K2vCM ze@v*Pqz?AD#8vaXQEINQrr9)$bu3`VI|S93@kVH6-Qj%XYTKJ7khPSKm6VR9l#Z>G z4zmrCLQG5B_Kd&L>PO4M=aT+;2RnV0+Wr{)GDLBIm?{4X{Z8WOMNxuZ4yoIjdL2Dr z10qho7;^uJ+_P31C^8r*k{&23$zC+0yMH?l`*yr|XS3b(GYUjkG2NTSNtgoiv~JBQ z`x9gWa)u)q+kUjaML6@ZG>sxCaJOTAPgf;eU<@_Lh1&hf2*|Ss8gguk`G1ScsrbdA ze=-XXO>;Y)b$WJqa<+}Itt4TZ=ll0RG&^CLG_R@lQE65LOUBRXm_p+m9)x8RR(9_+ zp7(j8;m)Qt`U3agI0bKTp@gu_ZEgzab2KGk9~LBV2$oRrz`809=b7$?Dp4#s{qAc$ z4f-F)2PAiY2GPfh`ET`n7^i7-Fg;_qnM*G|WV&JbljmA-hA zA2u2CIi%d1Y|<|lauKZ`m$16PHwZhJqpBp!XAg_Jy>fgw1( z=t)08*K2X~m=RYKtcMm05y=DSVbf{W^7+%K+w%=#tA2lZZFMm0aw!t4ek;pk1qMw7 zgX3`r8~=r;#EE9ie#xyLTWss=2wUj{K4g7#n$jWT^Ejouc0HRuooV(2$gKLB`XCA7 z^FA9#5c}C`GC#hyx}yvSF@#&WNH2HI@$H-HCQJDCy=$(muX-B6&cRP|0~*bCuF{kD zO_UxZ>5HuE!1_PJIZjfIv)hqJ>vaw-jVW)6yxJub3ITE1IGvh*ip?teE?^C8D`HGD zn1xW(Rj+KX<>uk=bbnMRnX$C&+taD#PrgA{h-J8OYSI-J8WI#wGkEDL^|jrKsipD4 z8d!{l9Qx#DX<&|g-cMau@L3QF_84F7rMMKXWp)+lx~_ky2=8@FkgEi6&YVl);onL0 z4yXN7yAOA5o^VJUry+lr>!J6aQ2x6*4KwT59QfiUn7uOXV50&s?9pQq6`oX)9N34*CCJ zDJ*}Y!$ZV?=?c+&O(6-}>m{tf8-PWri_8g}@BoB8c4~Z(2H_>Zen>Od7fPlCJCTWn z9q!M0XLW(t7uhvry-5m4cHcJteNQogasdcP+o@TSV0~Rtj$cy{Rb(@2@uvzDF2&sO z)2I3ppU9S$d>(gF5n0_z{fx|wDo+{q!3!#T#^ydt8oUuW)CdXSZ{59pWPMJWRGbhR z`-M+v|9edKxXizlPF z$?(2Qf_?t@S^BN8M+iAgU zrh?Sp2@uPY?-o&i6C%L=i#KT&U7HZGyqyA`Da(;_p6QR=F5E*Fp4jb&XIVcL55Kff z0lnwPFYZ;H^XN^myOZ*hW-ZZ%+ktufrvHXS!Q7_@>Z%q-xlpXQK zf|Ar}uD=7XRn1*{Kl_Daer`FcR*BD=>r4_y)up$@I1iz@)M^jo34fvWuQ$~)Gm_W}xS&&b#SxafyAt8GBN`K5&pyI>h)RwOXTVAFP zsP*@;g&e_5Q#czoU8R@0MU2P9>-QHnWq}A+*bKgn5^mqoFey6cb7nC8P5fHg!l~wd zoi>lA1gZL69Yu*0ITTM2H1|{g^aJ6%7A0qioVzIjsgO%Sj9kruj~tZj+jNZ)eB=;Y zEq5etTw9M4R3|jD)EtOB?%4^q^_5(Hx{zlgl6<u+eL&9f7jq!jawk>(ycFU}>95p0{I6@rNKi^TQ=XF@qJ7PPmo1#?nzNcv8`FUl34CIky3MhI68JM$lIqz6hnI zij}$0v*RV&)x3M^ zAzxYmdRrllH9w;21$axMJu`v0nOGb!@Mp1ObHN3 zaUFfpe62&tuJ-@?3#0Y?A}icL14lAwHGOA28)y3aupZMDA!{O8NAc(5)1 z#M%TQ!u1oBN;^ZU*8K9f*4K2e1wF7yI~j-uMFD{8skFmu%n*0_e;cwo?e*AqEG)S2 zpLzdgI>dFz{Sn}~4)7U8W#vj}{myXv&h!?Vug7B6AbxndDbljX5_!OL*X1C-5KYB} zoz1v2w71@g(vElaPphBK_GYrS+40!grHl(#P5Zs? z-19Q372CgO&6JUcx-a6FImka;WU>!fL}Gr-{Ws(pG( z{pU{)YX%CV{hCUAWgGaZ>Ph?Xzd%xUuscm+z~SCozLuXNk^Whq>c^i8#HhYSmeR7- zclgdl8ZZ^=jPJbu^2VPCb4>8ExR~RNWsC zU_^UlAf6}Y7HS`I0H?;LS=9U1Plfv3+xXI2F2BdEvRY)6wx}~oO**}>;MkI*28!ICT8oU-lG4F-lcCn2wJCYVKM zqm{T^_NK+ngpykVuK-jC3$~0DH{U+qRsw3Z?b@Vm@R?S@d*ibOOO&LYj{W{TA2c{l z^I}F7DL(kC=qj7vvwK5I%g6oH;`?8-_4gLzCW;kZ#t`{UE6DZOrWW zWvJ?gQVFUirzzaW^O78KPXo)W5L}M+(Fu3&?G6_;5Q6OzU9HX(3Dsy-p|2!bw7wryA?sJlV@3Z}-ykkG#1zK)+z3b5pM}70bg*N<>8MR>~0U zLEt>UT==wd*4zB*UH|v1_kIqS@J9~yu8%ag`2{HeyN)%>T&f2|u{@LQ0U9fdUjmZ8 zqk3<~wF776VA~-$Q+nI*MSCB4&oC}>Hb&GpJV-lT07p3 zI`rfNKsTtlm6E~Vue1S&YSl0qJ@u!Z0xFO7HL&QXR{ms;DzCg3$aj)N3mSZj9#_Yh zy}p0>;X~>l#xcoZQ?;Ht)&glmOojQb`GT5Iu9<=PDqXlIASa`_=eh}D(8}H1on3Pg z^xLp92Rj9rTUZ&Norjk1BH-`v83fF!@Z?v7#fbQz^i@_*Nj5vHX%us0bk{fGAz!%O zk7?7NG{FrA(zBTe3vGC_a@5xW*L>B(V3hdF+`-GwlG}VM!>qmB9$%W>r;#7}?If=V zmesOG`QDb*vd?25>KmAt-I*FPtQtj6``l8?F;OVLq}H+fvVL4$=Nr@O&r!pADW+7s znN;^d^>_BxCo@osRMB>sMgV&FdM;cG{=FfF$ed9p@hN4grMdzbd!uCrmhWij! z@Od|n-o3ygP7J_i2)KM(*e=OPU}zrn-%#W2Jy<5jJdx!^lRBaXJl#><|61|cKbe4W z2dbf~(o3{LxOD5sCDkv|v)M&K`k!(;ybzTX^PGx~X}4cOKFEO!W_Y9BveG@Us&^7e zJL76l^UjE$Hn$u+*Y=@?QB$Gn;oRQW=a834pYHXa16YY;A9?ZYgA}F>?o!m{k3DCZFGB_%lZ@+pPGWBxXDUS9E`Z!2u7v&nwAPZR?V=m;gP>u7nMC&WKWJ=tU>&#@gNl zk3aj6jYOP&YDbgHR`Y{hooS&6gj#eS8YmT776Ysf-%soGw0-}3H(QA`0Y94qFw?atJt3cmki=&Vh%BF{Q6;X)kmYa1MAHhFXcC5aJ5yMFo_fiTCFOYnkE;p5U=^ zz4eocdDwO_*v1fcpzerSDG0NwX&n2m{`pH!>_cEQR0io7wQPOs-U}uHMTLGL%jhy> z%F@ZYaLg{MAyUcq@L6&v1uOrbvHZXL{eHR--n+iv)K;<=m5?u2I2V(s#pqhNXpFl# ze+-G(aAP+JN+SNr_SS_y>@f`GhTli>#yDw-Z$3Xx^&-nnB=wPKd+~jX7m&cC`&SM@20OoIl-_mnhTf4^0RI!Nid|bJ3oiDVy!l71raupui0N!F zE~EFkzxW!ErfVUlbZw1wsPVc{P`Q_RfghrS@qYlMKwH1b7`Gw0(%FIZFVS@{jr5TXEhp zVn@v{rA|i8GmY(pY;fY&M>*);UiKH}b?BQ9zsBX5UoC|Q0-gW${t5IS({fOMI}zT; zR1@$}2JA$5z5Ge5cEUf0I1x59xust?W)jwJrr2dKQYlhd;XQaJmcLMh(WjlDdV?KHe^MAOr$tzIk zdS3zEsl0*9_iMGy`=z?uX5*16mi8aDZ-TBGk0)|RoPJ8VUK`;7l1A4l(;oQv{x_th zX6>iGh4g2*!c+On@y-#aSJzENj)c=I{(`(exL(>_k8#f{%R8g*Kg~309Ypk2`!?jN ztXs_UH4w3#$9zWmY0Zb}D*M89y&SuK9&O!-pOt?H`Mjy@zf9?V>6Ck16b*e!`mU*j zd{FmOJ$x5@UgG{pdZd1Z?=|=LxMhsj7|E9J%ci;Q@h4qXd0KdfF)_K4-n!5`B=t1D6pJP8{$-b%JGsfSk zAM6R6io6GXgnx~4n&u+7wGt};CzX1QE%62tljNoTv>NFE#QWV=6iK?E#Zou|} zypMoi^;{gzYx+0~kvC)fa`qzl;8)=P#sELoyS$ek+xao-|8!hq{Plp#xY3g7X-l>- zT3F{2KVR%2`8D$U%QF2u&%Z~ceuLit_wh2Wp>Z?~4AxVeF8p(*gV*ciqyDY|`}OTK zIspEace@zp*R!QR$r*oH*$!H|-sGO;M0eZ@FOx4>{Agi9KjZzTi>A>#CywTCXeT?ma{TV5*f1mys z;40_ZB;P5?H)mWwy~w;Rdkpw3c{f`ZsBh`&-SD?`#n-&0s|)kDbVD>1_tq(v6G!~K zFM1sKKSj$$`&RRa&h+Q{62|%aVSs~c;BhJAB<_}G(nEHJ{ZhT(EjSMO`u!o|H^a8= z&qXuthbkY9qjNk>Lr_K`o+~>Zd~w@?YoD*N!HT7RX7G66s{Wt!TM?%(x9%YQNyhkp zE4R0%nU{XKIcPgGwn?XE{C(f)6CsDc-K zqMz7%@-)!BNadq^#=@@>)uQ(QPY3RP#Zi4v^F7oq-d?tHrZFaz*B7#9fc`qpnCGAO zcg)+LLOI3m`lsw8aWf-y z&x|B z@DKD6!#w+{LG{x_s4hooogk^av_<@6qY%6&?`q+>|0>|3_N z&r%)gc$J+E+;huvw>Sz-alLWiaNCv3kNkx;zZ2>APtF1E=ViZc+P^z9ZLUO?>TLDRZ2?%$dUA7{P z+qSsnZp%;3p=LY4=L%itqIvJFT^{IS_xS}D=hm+Ixm3=rT{x-^Af5fKT?xZq?rW9Q zqqOU27IIYH(Qk;`-QbIyHU7bca-buQZ9_`tL!hC!DP1S2&4K?*wVc#nZqU`L4Q89v z;|6%O19pRHRQ?v*4N_P}#dd>v;Ssj8)mdMlaUse1$nQhCjz@ak*EPoq%Y9w*3jfWH ziaSZBca!hyN_f$}!rSlry5^Ds9b79%XBPdR&66=`NdHsY19^PAGsn6nDD5HQa|E<# z?)!^*2;lqgjB&e){f}931dSkPP$&-lWIwr5M0N>_?L@w`*U2~_D_r|W-?!UDoxUp-Fk)`SzyXI>iVqWFHLsUp?Z|sKB0B`Ju zSCg_DUhXu59H&!lX#c-ZU9XY;ugCbmE90B$!#6=mi(iHQJbNGV*;DpkM$H)_^Qif- zD0dX6IAS}BPFX(|bzH7p4}RV+-Q@NG@7}E&j(6`i4+`(QcWac)-Md39(O~8*<9~1? z_${Xg=~olRpXM3|>5ayx9p)&>bBB4Fe)21C?(vyRsRnf2C;Kq+{aUe|4B5IrFIOLT z>no>mdiqh|AK;6$sXYzZwz0<$<5-sbe)k8i&5Er*&#w062ZT^|to)yJu#m6`@95z# z155rA7Y>amxwuM9^}B@yXLn{x;y^t;!k**8>2EQawwV%pYKL!-{wuu=`YW9m*&w%# z_;!&qPS$TM{GuiMw9+nWw}bCLI%DwmFst6-?4a$OV`q7KrFPe2y5;f_JB@PWQ`zU8 zmw0jKa;~-Wqf*|S@s)K&UZ(Qy)0g9uGp@S-Dtv-ZA)h~Id!}^? z^Nb^`oAej-AN@0MmHFz7nM0!*urn0#Abe||27b{oA^npdJI&`ZH7+w_T=l*};gfSZ zU5-wCs9m>se?!k#rk_E6%gw)K-q>TjkKcqJCg;`L8i?crRosh&VqfXMBEO1U4P8dz z$W^&-LG9(=KqvDVwqtHne*IL+pD`Y_uj4^$k$M-u{$2Zb@a>0_{4>T?{cdsDxw)Pz z^3^^IzK<>1$B+weaZ^;C$2qTV+mb&a!jFa?(R=hc@LQ?;$iKR{8@`*#FZN^Q#V%eG zlBoQj2YqFozlq^Iw#{K;qx(a({{Y_K`$u*-t8rjF-;woq(R)0p|y9w?Do`R2keb&{D9@}7JF(^G zGhuTu{khV88T>18j4k#Y<3hY@$AuOO zUHTQ!`F2M6wZeCBeyutAaJM$TM(BF?fX=@sMfTk0xQ|%e32M}Zb=35$p!54EMAzhK z2Cetne*st7cb4;BvAlHb?xb`-wEiFnz6QF#SO1RYffHsL<479b0IvH~ zT*SCj9f)vb2ONFm=c6g(j?(%h`)rzTLwB;jsQP&T`B(g`y86VGezx`l;N`p&`87kf z9=@KGq32Ai<8zPc4!5689z;HsIM*ik%R{2S9^+&^i~8ds+s2@|#p~~f;8%%fpXYV4 zb>_b#%Hh*$b?cq(LvQwwT4_)7Bb?!U$9DFJ-PeAGd?r;sj$^7bdNO>gd_lKmyH6hm zz4R+w4-4CgXxJ`r1xdMjj4!=oJS6AY2kZhCtkwxW`Z@TImCH3{EbKbAQ)}Fkpy(m{ zCGfv-jfL6^wS#%>r&$fOh+F7yvqwOOSIFGBP3^349+l&@Du3-)z*X+CT)=)^Y!@(v zTPyOUjQ4&m@vY0!TAkfIi96A9{~G!HR_lezQ8yn(eabvQ@`(-rUpe-WoU?S|hVO}b@L z?Zj`@{nA@yXH?1`9EqOfaQoAQ@sNt^Z^8R#!Z&AJEC_Xw0a#Z3y+U8$S zOnL;ja9%b!2KX=57?UiA;7$AF%P#U<~VB%Hq7{ieZV!RI|r z2$F;BPTu3!IK7N<)p#N87dic9jtTK;x_`8P4UR=VVvpSZC2nGq=sjhe=!Ze2&a^9rlJo#;7x<5N+9xpxP zSa_anFKE8OiJ<>Uxj)?&eu>R;o4FU3jUGwH+u#pC=lAttPq5~~yHAXpG^9@P6K2eQ zk!mKKx1G&0{^U;u-`8sWQG1yWA7FXr&80#)Z{F$dV4@@HqQ~f&p!fIQ5k24Q?WWOh zV@8cJTkuztXMyeu5L4vz@j>AnZK6y0qh|w;+vyyh%HKqs)-J7|oN<3pF3%40RcJM~ zJIv)$pdI0m?S$l-i#~eKM?RH)JPGgRVkGQnxC`ViwOmWSJ@#Gz`pdLjkdxPmH2x)h zA#k!kVWGEXcsb=W#oc`nodTSXKPG#-t!u840^QaXlN+$Nb>Wx9_BP!OyRB;;)E21y z!JmQeRVpXdTj4z8dbcdB`mg;t@V^T1V|iQQf0Dc!?^Js^aFzXIxu>Kd`A4S$_oQx3m#nnzbJY4W9;3N)2`b|uZc4<3E z8TTP=*YM-y9eH=ac!5*}M^p6>f&W{Mg23E=PMEWC4SjXO_|D17^`z7k;y)Z9X2a^1=Veowb;z~UBdURb4$c zd{x)9gx6JF@n^5)eYsH<*T~k-K2Vl2*0DBd-0-()JJVQ@~?~^>aBWFV6M<*~-g!k?5UrlBq z$D8S@Si@xadG_y)!(2w>2X}{K{Ir; z?Su%wRk^bFf$qDiFX|Vj%=;i0deZ{_{lHh|Q#;Jrz+;ulk89C&z)L&j{$2fN*^kW` z_v&)LvB9hp8`yw;Beoml;M|6A+R9B_qKDu{kQ2n>H5x4bvn|8Rtm#gU{|KE35hF`m9EJw-Lh{yGyT?KK%&j|Fk-u-3C3zc2a_`Cqa)H z{R>EnAjoe5y}zH6%lU4R7dsw;&;Aea|H<^c@7&UThgL{cB>41Z;AgaaWEX|_?zPsP z>QlXc40!K9usqj@JlM65ZJQ!!B%dDBRo^d~60#8U3K<`hk0YN-oOFx#(Y6Y%whuUr z60SanET6j_br&9L`)3??drRh{8K;-_2^Vly+x?^*!6%SkWu3YnUd#olkGf?X#rGIr znZLGmpQ*i*f)%)b3@p_-moCXxlu?^6YB&<9LqJIGW zEjrJEzr~Buv7J1R3pQ!=Xvn1O-A{%;rqqGLpL`mYR5guUKN*LD+mY{`<#KLg8DqOm z_-x~?wbGwtQpu=>#(VT~@ZG2945@z{*Yb=Ty_s>`oQO_mz>b@v+~Lo&v>vj52Os}V zGq(%)XR)1LQ}XN6&jMeHw|cGZ$UIBzu*bOSd|T>2_#E?5y^#EC%+sa6ShLCe^%*WALew>$$Gr>56NA? zU#R^Z2wBD{_aEVdKj8(p~tAAJ${mF0Sl%zMRW zh{C4rY!E$VUkCn|t}#{C7~2KM zi>=l8hTwkSE>b;`KfSN3%jNsJ;iDvz3rWPPcKOR#soH(Ts7D`1yRRF5Mmd;f0sUY4 z=Wy14o@G1h#h%h1LB8YFE~wsG;SG%U_wt&#THISavnkSP&)wn7nj>((_F3e=etwy;-4jt0)M5B2bABu zp7)y9zL(GJxV!Q{?AEi$!o}W#klgP=KS2H1gn3Bvoq)R&+X-wOVW~poPLBlrz2*9A zm=_Cuqv@Ux5PWhp@Rf6FEe)4zg&(z@aIN5b#{mE7GXEWBjqu;$93Q=ZH-9Yf&nfGX zmhv#)m>0)q-W_A7#jryA4UPkS$xqSu{f70tt=MD2_)1(ri|;YsF5^jZJopZ3ISg&> zOF8q$Bi*kba6Ryw7qOifbF0ddZ}vps{k<*J&$Re{6DQ`rH9#RWBnSXJA=qGa<-Cx?H z(hhozf41tC+X3#Mb@%L|2zrtp$4Fk|lu_76sXYn#ZY=Ye34cZfi|vdoG|ouC$;@E! zJooQ|9=gPCEfG-c%IuFotbNWNt-%@*7?bo$dhp*S!Xkomqfp6vh`W@lF zkeG{NhrMS2ccYez{i&ylzc4vk?ANkq0e_9!56LqoLCZL`=H-Z<13WLgi5;{;%&PTV zRn9oESGJcHFQW}f{}MeH{QA9PoN#(&-<;2L`TNy##`(Alwqr5>CL%1^{q*hjWaM9o z?`TLIt-J1Nt!<2oyutH;_vcXIkxTzpxvxdOq^I zPwSKViH5mA>a!89vz=9vfAm7&`{|1&=gxm&+~d>^iEo|nRf%nVGZ|KF>k{Hv7pt$c z(YZUhq*JM*V~%azJXS_H&tivc->Ba67eP+HPD1(5>Z;1@8MLn?>r?rQfxnL%mW7>; z>pXPajEY9aC1`%Yj`Mlz*p4HTAhzS78$V?pE_%yf4*vbd8);v`slY8~FVbJ7jIZ3| zH_PYzUAO@{I`<_yoMo>f@#k-&S0dl_Y6sL#$HO;JHMx??Qpb3BGyTmK6_e`={Jo{w ztH9@b&IqV~CcV$8*EJ_K$`^Eo@KB}v*{eanUi~+cW3{PasvFzYXS#{2=(RQgd_}MP z0Gp3HPZ-C?pO(D)nR5DLoX{jU>lZ#5r&sQ+ zY2g%?zqh8xxY9Y@AhR)|cFW?}k8m z!Ga?@;Ymn;nV$)I>33`=q^nA#e9;=DSNt-*N7d^oWgJg;^c@}&-^3oKlGbUF|MsoS)~C-Cf)D8=}{CT_iSn$G*O^i@^V{s`8&AqinLn$s>A< z7J)xk$7hm1G5e)oN#<-P)ce}<*8xw5-o5;@p>)4|3#=?uiT$P*gYJZ~p0?>W;5Lk3 zRr}3e54ua)F|wVF&VEPCM$8ukul9Qv@S<<)E+Z=V!6~ONXJ0wvCdztjn$jlKdp|Lzy{Uy-SaT+u~N zF3G3&Rp764jhO0-^4Z_kuF2ND+5t+*P~Qd+lNHU!(e_c0FyLBLvf>1}FMXe-64zoc*l1MC@z! zweH}w*jM%l@Sk**Kz2g;%+|8c&ZiW8^$vnxzxF76Yh03hT_H)YR3A;g_b7Jvw!5Y- z_}~bX{4NJ3Jk`TI$J51j{%j|P;(LdQ7k%d;_+O%aE6Gzgi!$%1hu2XK_z7Kd4CwrO zn?%EL0{513y-ECA zZ0BU!J?CB^(fO+AJ3j&RKP}Tw`E&GBEw@%L`Q;Y)|JVGICxQNL+Rsq?*+o>BWpJ`ykUbmxj#IlMJ>0Ws z8q#szv$)H;ArFdjEAqY1U7C!GFgXifn&_JaSN zaXPzuy3Q%|Ij8r{|M|$@*F(6k$UpCsF8x^g$DsG`BF1*n+;xQIkzC|RUI?7;$HH$E z^NCJD`kC5}%xd>3^x2;v-M^D%p6^Ue(t9sL`T?yk^Beam_St(G(wAE=l5!<~27GC~ zWY+R(hwo=H#`$#vYF~UF+51(&pMxJ(DP27gu8rQMux`(|e*Kp8ry1k?yqE0rfkkt% zG`I&A&$N2)uh6BZLXHnO<&qpk_r1OT?92D|o=Kr9^b>tVuLNCXel{idGD`neI}Ny> zD1Yj&6Ms%Jk^R6VT;X=4@KgF%gWkVWLH*mXkBb=&f5Xjg7>h5olq_!XFmyJ6enfML;FEe3M6GdjYP-u8FRPetGRyXXaI3L|!xo({Sfm-W$> zYtac@u%~|cbxCjMJRzxf%!Rn9XMqUCs}A+~L7q+$I=+wBPGtLurPzhD*8EBf0O-a&F; zGdb{na!|xw5x-dM;|7TDgul&u!twR@Y)pXf_ltJn?$_6I(4)?w*eG(OlfYl=3QgrE zebqy312v_dYny@d@1s!ry1r|EgaY!yrQp*o!25k0#3!ClW)a(VNMUG-j}qYjQ01m} z(BL?n*fwTuXOHjKldZu2%n40$6Ml~GdDZ(TlWoAQ)OsPgzr5%x=F5vP{Mde(y@S<$ z{Vd-BdcXe?{?O$o?IY)#IDV>f8|qp$eJAK+^~X)*K1ENpIix@T_Z(N(&(V6}{;qk% z+Qry*MuH#+g5duK00960$!d4Z0WWWGbT4RcZDnF|Wp^(_Wo&F=axZ6ZFEK7MH7+(U zY-wU;cx7WRb8=%Za&>NWX>DaMa&>NWX>DaKXJle7aCra$000000001=!wUca00001 z000000001Z0o46@eA89-KLEc!Nw+}Q1Q7uh%F?FV(3VXFM@w4-p-@`{Tp>+9ZDZ4< zBq?nXwd|XK%AzPJ?)wU;fQ~4x=%}NP+vup@5kVctb!OasUhi}7CrL{;f6qV9$XlR5#7&XH|Phdoazc!LPDrZEb$}Gj34Wtn`%o5vlTwL>hqP_BgvUvkLu{ zW+duz*P7v)x~K`G*hu#QQ_f98r}Nf_{mki%^!W0Fz7oqw zpEr!H$pB^;**;(ft(AV^r~q&xtscM}*A@VS#cA3XD_)FJtgTpa_X&3&xsL&mT?_cw zOn;&a24VQvc5sVv`^35(Hv`Di0{C2S0GI~A^>+X>`SDKx6w>ttfZ!jJO}y+(GiZkW zUf5S8DH%I}%cpfLpx zC0qK)W2cM^FGlj?DT>W%AJWwxE!BRVwYA%T_xZtUe@a4DyQ`5{?W7bys@=~(Lbbc6 zu+k9Dv`s2>tZ?p^q7|CX4{|<RZhLK|>d3gDV* z1JX#d$`{iQNZ>K2btJHTXmKv8ER399bHlRw&vC`Ne-O+Z|t3Fo&)e8e7>97zT$d>0g$l0@?GR z5^GWccNm6YEMP--ZI`FA@UltyBSBY4=?wK0)9dwA3%$`9dgYd6e?n)-wHuQ@seO9- z@RYpKV}$n34rCGHQKL>4;ww8tJUTZ|h*x%ocP^jJ08*KC?D5f*o)u>@&me2}Sv}wPw(Fjz1V3nFnKpVNDy- z=L1=doPNY+SZ!wuwUX_R3ovFBjFHVaG2%x@0x;WZqm_JZNWd9l-smxIZzve4t1+#r zM`9e>4C#+ho|U&pLf%%@Wf7Jq5O!WX)-bN(KPDdY_D(3ZP4P(BkU-dhc+<>!7@G`Z zun(J!&u6tW_dP;uC3R8ig9Q$W6-awi&D}=9ZLy)*p3sa1kPRHOBs$NRvDtlDI2T5~4CWn57B5G25V{!q4-QpMk(5)!a<+4zEC-5M z%KfoIC8-N7l0q{t3+K24W)LBcLvWnf4S3Q3DEAbXuNyF`3*ZWL13X;-M_o7IEd%HZ zAjfrmH(&wrw{!#U=)!JMIDDx&Ocf6MNI?!?S{#yWi5yb80>WV=xhf6=Y)Xb=;V_jH zCB}2XDFkLuQk%AmH zSsd0i1p*fr_9F~%T93HYb zeBO-1OT<%#AG*aj73Cs2fG2= zN%3Vj;4kFx-)_JZ&0&=5xbDEG#1HEZ^zK0pf558G3F-k_8+OufHf7l78eAVamxg^( z4>j36mxevLhZu7XjB~}9CEmPHOM5;X?(TqBC#xJ)-GQJ^MIEWZPn3|%>x?8Pr+$}txiW%MAvb^(0s-PC;((XuBeD76!?-jl; z(z0uTd(~W$r38a8?p1}jWqIyiwfgK_m;_`5@u1*tXX`rGD9~;J?IP$8RGDi_j^FG` zsuz@0FNjoc^#roHF8YFq^+`|5<5U`%z8t({ESsGMWXkH!%f@9_(9k=tZ*P_9t)>KB zp4Ae(p(J=iBuMUM75Ro@ox$PiO@o2SalL?yDBh98a3=IZ&Br3`h+Y)X&#eJ`rZQ(n zYijY9@frn|m4utMDgK`!Ycqf9r57pg);2p-)@CO4VqfMs*aLV}XZXT!4e1G#GQ&wd zfhTo_L9SUnfeYdpUe+1@r7~1l8QyG`p*Ei3W1Znkm0^LE;j?BLmc}#uS7-RQ%COGL z@LRJC*Tyq+O;+8&S1QBJR)*fms=rWmxHq0*kk0V6%J7JlA+uSAeen#Vb%t+Lh67fH z)0$;?GoIlro#8u`;U89p(qXRARLxMykY}k{G2D&q-pmN_;AbF)ESLo2btrR;JloL_%V|a-WK-3&dmyP9wB#=#}qv=H*Hy=ZGcP^aeYR#)x$3eR^ zIJ!M(s2l@zOAlmApLn!t>49wNkrbh$zDdfrmfpHQWK%PN?#-6o+Fouu%=*Wc+X`5q zgM-sg$y{gvj)kn68Jx$DjsMKm?FyUNBgyj68UwcgjUJhYJt?Jcf8i_dLmhqJXgqw=2rL!{v<;JqCxcj>uoly4lNuS1K~Q@4b* z)qkN&8_P!4YTukGaM<_FxIjh)j)_J`IqD*2{><5VaM%yNb&+J_;(uqdl}*Z@HW?24 zr-}LSq>X*DKD`oKhKn=?z36+#Q9#rG&XiJmaWkjm<0{N|m;+ohR_@HK;<~8W;Bqr8 zDGP+^fmO+Bq?%)7*8p=4zL1J-94hNQ{%Bd$6OPXC*O-_)adisrAv_ZGgy(aa+rtX) zBgj)9sy0#1^g*WmQ8NrLd()qo3JqYQ zi%*^K5e?a%H59W0irE2SwxlnR&1&F)aN9(2O^VCvzACW$SpvE?Z-QV!l|SGMo587} zO4YF@wM8L)QO~xp#h!>7B6_r-;JKrQsUdH*=@Sk{Yxr5AYEuz^3lK+rZ`JMozpHUgy z{-94}A$O$8Q){{-(QutNI_Q!?ViF138>b*avmq*9mS7f#7=1yN z#~&;TL@(4iYQyIIPUaYN>4h%KTuzg=ypwGt<}&wnt5vO%Gyi{4dgWWE#)SX>1BJIV zsDhaA%ATlsF=_SHiaohAvr1>_5xLar;lf6GxE`4JW-11U(ZEr!r7|XSKh~vu=^`n0 z=-*OfuEZQtt_(sg^V0Q4=2@=4K{>%+q&E|j=4n|^x!;|cHKVcCbh(Q?wful_o!v-} z0`KsQgLHhIv8NbIG4o)^XAU~86=2+tBtwPAYYw{P(vxD|l?c6XTnh$1)8lLADD16U z64lhT$R%oTNyQE#7#)-)Rpi5iv`D+fQ>LKDEf2Yqk&mJ`EC+m7l3;F(-#$nu+WTY$Q04IG+ z?uk{%j9HZ)vo^=gO7%h<9gy!)__@0Zld)NNS}-x6iCF z!}VcLZ6223Bx9@LczihUh|X2)dT%(;OW3uaJr_$cmD#DTCv^5HqO$(SXq7#qg;XjV zB3xs7D3NHTq%MkO__JYbYvRHmUUo zu@du?syHdB$(MtKL@pDY za%jmvR0)y%pnNY-!X6f3H!>lu|Fs}*WLjDu-x%M$l8VP!8@hObuFXGwH?CjDGh{Ph01;%D{O75DWgt>weYPjtoOeLyZ&$DMetX8DOBUL5ML zy|_{fpuD(2_-c)E-OZhYHON$YywhfAOv|9SE>bm#5%?82WEfky<uM}ro@i*8Nfmf_h&sKHs6MD(jsp*RF(gx)7KbM z=bb{xI=`*rWm*7{X`u-5O8v{|iXD&um;OmjV6w=2_p z-!QVh!28DPS5xuv0BGZUU+VTXtKq{3#z!xwqU%7lS|iKmUy6H=Ajp@8?}T8&K)`WP zCh)ly@Dsz;%LSY>kgZL`4;t~kLYOf`N}4k2|0zTR5v^80+v}CMNQwWm68~orf6YK? zrW_bQi}-VJw$=hDUdG@n*?M#4sebl zC{&GWg#Os$pzQ7%;gxGW+j*P1M#_&C6|DGRue zA2((JQSy=&;^sGdth;SlY&tINNv);#%f;2mFt+jl(P#rH_h$hcBzQcK1zbvry=lOZ zH?n}`!twnq;NmoZ2ZkE5j1zf5sN0FadSYYkTRKZZf0sL~W?V90ZNw$UR_0q|7}=bD zEiyO(yY@KV&;S<6ROvSp@W)%r}}IB zYH7HsiCdnMC0s9y$Hkt8Qq$v$ZK1{qxb1LeI8;|Vp{~36j&ao8}@D?;HKPgED_L0kEn z+kDGaiBQDt^M@w~D?%3nU78umUA1XgZ5as@S2s5jJC)()9~f`wlP_J3j2cfQYKEtn zjYYxv=zj4(QgGezK!#r~4LOo?zchd+aGY5;jt7u+xBPvCe<1{LD1vTn5jZqK z_tpsZj|ci|f*x?ZIUdN>$)mCkvXrbZ%jb%6kkk8MVJ7&y6m8q&+_D)e#tBk#c5+meR&5T=-o z#h#$2(hLK)+12^0K1OykD?}f<oH3e&~cmu)5LN zYY>n@v+K`X+XgW*Xj8b%thj70cTrIEFvVNz_oLD!nzx?ionKJf?!7px0D4H=ktM z>#wac!%>zrmIWQ14mejiG?$U&G9jH#^~~wOW^FwqU4J_r2&V(C4^9WJNe8kHoz5ui ztJ8rmN#9OewWW?3p^5&03EN>qDP@jQ${Z@?xFOAzGKaDb6QCzr1G4 zawD;FbD3fK5M8gi#NRmtaNc57RqmlbKSYLrCf;MQ_UO%^H{|mND@T-jBIcM;=zA~= zI3r6L{@tB2e4R6mj@5!4Emz(*km2JDBVF*cVb*A{i6VO_4TJqStM4LiR|+~V4XsI} zbI7|*o<@&xGzL) zv@AC>%aYpVE;PL%pXuWbUt?XeE}CMe2i5-Mk%jsavRs{PScK`d9$!I~C)}2-o=C*3 zDG#t2j1Q8G^k6Q)3&iQcJnLhm^)br&7;Sxwu|CFHA1CV%Ur=pFriZGnZNQclwu&B^ z2jefDEShY&P7!BoZ^@PuAmCuKHQ;6pH-lcxrS`s|wZ@Y_4*?ucOaOLjFRUN(#RMRE zC@|!^2|%BrbW;*Ms|hXis6=i@3T`Cx?{i3t|}D719qz53d$nl-!EjFnP$}= z5W+*l0Y|g|$nK(on)~gp?FGOw8GvI)0Wea-*Yo)25jL3Ef~pQkI@nb;-SQ+Y{qiJx zC=J6htfpI@M0rOsElsD=TomLB70Z(-w!TBKJW16i;>VQ&{~`l$o=$c9g4mG6rMuEh zz!@bd=nr~vlh9p|$?n*>hG1V1_a@PqDoDu-UW)P&p{!MuZ=W<;g5{q6uUZ4! zl3X9IH?TAlkYn~Y2*u|GK%cHkcN>yiT?&CaNzuCyc$gmp3xQ|(F{}`Hl^^ax;9Y)< zEd>6_k24B^|MFvUA%{Z1RayvianUW!LSVQq;NB$HvO-`0Db^JN!}xJcA#gH3wif~u z_;FVuFjX@1tpCY6^TSE5={MErI46b9QLnYS2IWI;7HnYN~b2! z{?IOMsi(dyn#dY&w8y&CQ{S9>S|T|-9CfW2gt1^)g74Zo^zD%gY(Mr4`UXn6b}I6& zt-~3NJ`x#myW4D-(8wpKmfGY^B6dmXUNtBV_G6iJjtN(QnA8*r&_~?l4&3O|*sh`8;N-4TrpD zBr?4=>JJ4YFz)T5a_+No?vtFmUA)lHmHjlHk(J}CvKZk7J>>xtZyH<<-A+h_-wgP$ zUo!0>pxSK2>yq@Gs0NNYQv2$KTMYwin4L}0tImpY3$(#Qyy^UVNGqeCn? zNv0wVlMB^ViA_wK-iBQGsQUYv3`NT7YHLH`Xe58;Y&22_&mRHj0}Km@Iz*IN-Y^#H zBveVwrUHGjbT6GQYKZ3IHX#|14LIMYHcU?gE@i>PxHRYBLu1gp;LllCl_wri62)as0&Me97E2sJV=)fU>fpQMn4= zHbI^yXuSp{(@P4b&Cn#R%aC7GX5kZ#-^OK}UN*Zde`b+I(|S2{Ge$dHxolNg6N)FR zs-$&o)}*SY`8-|D{H^L?Lh)o>%T|?KR9dWS(5jB}XVTLr3j4NHP&iwoTP?qAl5VA@ zrq(4;MNF8bnY2}k2r_eOjH*>tOe`xc7K&CIqqJ~FSwV^5o0^*Tp-VGm7MkUCmDYga zuUC_%R zi_557ll6y!aM-85eUXgS>?vCmF#F^&T)qBC5p=uf20SksN*h5hPPX zppN-7V9{lQB2Il-{$@m9&$Z1joWuwzD<2lIu)*=nF4+yb4M6_#xT zm#iAXxLr@KvW@I8kbPn>4d1XAEC;eVFZq$ZU=T(&9ZOF`vSk+H*j@&BoGR9L16{PV zA_bQu%`hDH?QbfZIJrC3w4YDuSZaz@i$PW2Z%zR|%22UbWvBGSYz^G@O=P6{0@^hO zr~#vk(IPTvs5GPH^RcL@3B44_(Je_T&FGoa^NS0|j8ZYf;-)4z6zy>au%C@%Rinle ztr}78uSDNlPvvw)HFa&Ls=#blvI-UxX+FecHw<)*q2{O#DlGw`I*WY?L^Ym<_zi>c zanfS?R;~E_@4x^4dujs4A1u-s_Du~57*9h<*!0FS^@yVunBfrqT9bx^qAM71nF2N9 zl>T6afNXKljLlaY)0rj7R)RO{75-59IA=o?9I!n z^hEvh&B?(E$(WRgED1&YiOA$Qa)Qa*KHY+8xI7LlYVdfY0!*lQetBrVDZm6#D*_=; z^vqECOfwu|@5Pmn1hhZc4zk$OFu@a1&s_Z}UU-cHm(_Rz0cn(>aa{Pw!n(vPr^*xI z=;W{;NbJn~|w%01QLIm59c1!uT* zc*w*gat_B|oNJX`S=8gLKC{jf_7&F!qW;=|-wcbQ6QYp>^|%Yo3Qt`iT2j>*De+XQ zVUThsMgWQQB?V{e6+Ke5!E`c}lOOO`28AkNd{1Om><>;1durlkCd8hJbW)`bx%nfd z9uHu(6=3E?{b#lev(hM^^du3XU<*NUsK#vrkJIbw0o&jjoyJ10^&-SGRhk@8~q!&#&F3oW#co_IR3iH>keb)5`-pDq9fous;<+d9`?QC>vDK3n3di{%&! zsSn|g8RfDL;qSE>>2<&(D^p?%G3=ER;)f$R=!+%l6n_)!##TNtJHW{H0|Q=Pk%G6_ z3+dwv1Gx0?A(fJH=mKC7l8mjcJ{JOq-GFQ0g}^sN#^?@Er>>0*jkQxpcmye+hSkao|g1+@dz`o(C2Y9qog6N!w zux$Bd3h?E_1J;HMvoCX z>vBC|BTrUz)?IMIxDLf4Q>_w;3rEKavEEy7mrgDzDlW_&lOwd&a{$b*uD_E*7mEX5 zT%BW;Z(Sl+P)h>q{=R}*GIUGeFgjP=#V6n6rwza z=+zQo33OCTge6mN3sK`~;2A*?sE?krFhHs@IXlHWYP$0Ht(>k*cO)EeQf)iJtNP=< zPfY~&9it3rU*`-+<@}oFsF}rlq(cANg2#-EsPxvb0X9%W>`9PVD_?94E9FD_(*mmV_740<- z{kq_QQ_WEa9l<-OcrU_Qn;Kjv#CcC3@6qh^64X9{Dw#@PO!opZV=_!gkYR$zkl{rE zoA0s_^3KoH#Z_szhTq(FoJx|LM*t$}U$vwY;aK1S-qFdauA4o;8zTVM10LXC{CLs> zB`LIU;R~eUxtNfLr6NZ(cME=HrYC@<_}Za(eXV$X zExcZ(62wNo7N0oGw5SRiQj^>KAdRBCg;0;r=jV!*Cm<*8H0se@ONWXS8N;>vV2O>c z^Kvd>lXU7srud5?KZ^k0GRZx4l{_gA$c%a_vD!9|S3CG!%;yc3sI=o14{)qj?azkm zYY$MG2e^Lp0B`fdCrsz_qL#STL44U>SeY6z{1r{xuemGXAOI zRvG__rKOBt$v~Cylg9jAm63L`E@K6M>Z28P=GR55umT(P@xo7RhSJz4qOl)O2H5DI zh$`|=0oWmleQVeh!c;dQNA7gS^9gS0eu`(~DT(%QP|86@84kgowU*50}utVI>w=fn+v! zxY(sNo~naxCRUur@`aBU_YL0fSksSe!b=_gl!7 zuvB7k4OLV`%;@Zs$4v4>xbK$-ZpFOW0cAe-%}BVv146wINYYo@B%L~a`m{60jGE+$ zOf8!&w27O2%_U8s&DX?FwaJ(pFAUvhDz2}!Nmx=+Amx`#pH)&3Cey|M%<9@uo))AYp(+J=D{i3=Or>tpubBHTyczmx#` zd{d>f7}Y7e=V{~;+I$DR>GJtzWTHRlk5tLjnCI#E;~b9o)=*o5>C8NOBKsxZ+Leu- z-s!9o0WEH7!c%D~>upA-^A-iYSklylyE9eRA8ZD&J#ncaCbw=3^evqUY#*UkivH9o zMOs;FMVXw?l!ttcqNx{kbu(IXbBPFapTfPQ;Y!G(4PbBkGFHq2_T?%WC;cHAn>HzGyxBMn>v$ngOE|a13Tmmj7z>5JKb1TOI8Fj&23?+SN zob-b@z15R@F`*kHUfBry{d{2VDM}klJEsku^@Y~8p|I|v+E5@c%j1voXOdLR(BH^5 zYb$&}f3D=#j{~yJ`DQR00s7Bgor22=sqr+J^EJus1n`Dl4M~{k{&DKuwIpOiJs5(~ zpMxqgObq77BUFbu9|PPCDR`2JaIV5UPu0X7rG+ls(`(HjjEj`8BaCcyr(?uX3sUeJ zC8^=hKVk$&sCSsIHN!Q)h^)pGd_=h_d@uwX6Kn+KL8h<5hg@a@Mad)ZFy%7U_t))< z#nS+=)hV&Y8QIj}ICA?zgaF2IB?#k{W&{%xU>wV$W+w0>V?2;in}hMx%m^mJxe7Cu z_ru!OObL%Yz`jg3Y`_bWVxTB^)_wm0xM0McM2aSgW)} zN<^MZ=&1NP1yiRN%`Pes8JgPIfsIQ$r(nj^Ir)W3@g1p3hzpCyo=3Ip*ljFit%u zf7Co6%sv||+s+)In{XB4Tu>{~jOLrPHF+PgbYVA0H? zX)`7kD&m_v;6A;iXj;A!eNhLbC8b6Ag?cx?SNrWkN>N`stwOV(HhtQJsnZM8+$-_G zXv6NgtbcD}&D|wwCeUmN%_GziE>6BDD=hwWLe*|00 zl?wtP-j*L@q;nkR2$*h$B?_U=2Ademe{m!O#a;dAN5VprajwB%f#&F$E>BAd&$jai z&3c}kW>8>;`S5UDj)*txiF&KlPEfiG+1R(1*ENr*jTR;|E8q`mUM@Gk<){R2K_6;2 zV?-VrXm)m^hg{PQAsAtnACjpP({##1aZ6S;-A-~;J|5sbBBuF zml-Um-=D}o5A#^S8Ybq|g`%E`g@_QchY)3A5t`QDnXWa%{*Z5CAuc7!zXZe^$YB=v zU;eg) z@e6^MwZ=`?Sqp)oX8>8H(nxKqp0-+0a*leS!?sdyP29>IHQ-heCvb-7I|s(C##vYJ z<>t%yINPm;%!S;Sh4YA^{@{F@6%!TV4udh~duK4L6^-B_fqYE}Rep!$`9b=oHm#DL zGBbBtT}`8q<*9Kb{@3py`2Xo_vO4@tGgFy*ntA6MGlMyMO0QPmkD-H zOyn^kzLOEuH;wxhCiG*^3uO*L!8|-KOipEr9<)z#78U~H{vT`if5;~OzqK&t|APKX zqaRl2LveK5wyx`3Y6j$_aS8jev{!Zg-?Qrfp45MI5fHDxg4GpCEpuSJC!({;xl9zS zNs&Yz6RQ3dKD;kP*A~Sb^?e(=)rSZO;-FynGGTS|2Zhn!ihxY%5&tfdK(upE_+XwLSKQLZ1^yx2+xN2?|P@tH<%E-gf`araGzbMC{;1 zAe$}=d}rgxUr&H+rhX?vH!&SE9|hx49JOPkm@ttG<2$J}6M0N1^#!q9E`q;45$Xpt zYolS@YjA}`rM;e7+$%1w(p(?aX=j%nvW9h|a7aBvw&5iBk9fTZu5%(3dCg}{kg-%ISSl?#ExlL6ON z3xRL=apOY3#sg|*92f`e z(!DtOQ;ivZyezd|eHLp_7Y^ffA>Y6>&+_99N%xl%QEm3d+Ea?T%w7`m2czhEukP~> z*>yj0NEj7NN%Rv1+dsvU?T`q=w#|icNE(1$!*>GiJr$sz{7X{VOsSswUv~P*#Hp4a zd?6(%Klo0VluXsV#B8n2!*=N1J0$&1Jt7cKNM@--7fDE444Ke57;SB8l1^=O)!dQB>7bd zTP509YvKa|{Pk=;sascThNss?@r%HMrGWD`Rmfvf?DeGpoR1RZGXwq_ye5RV5e)H> zH?%fyOcEb?pj7+FWBj0xyhvK{k@qy|&ZKrUT^BSXUQey+#=Cu|o#gstZJyUBGeUj6 zRR6f5O8%)#)RqI|`eeqlT8Gyst6nEm7xm+2!JJ>lJ|t)*{bhjj8`jQ?Ta&pKzK{f= zx~LaBCB2jszmmd_UBY4$AzgaX5%Gx7+*rm*29WZ}LZEw!vDJ0drNCV=p?Yu$znv;K zpGEPP7trH@WOZ@6RBncqayIoyjA5y&S8dpw&qw_OX4J&_>@HrUYy&A<5K(I4AgiO? zFG}xAnL$yL+6vb)vl1p|F1O6j&~vPQ7oo@H>r&8jhNw3OR<({?l+eB1oRa(*ljaoX z>x-EQ_cE;&08Ln?=AE%q5l!1$Sryf^9NpuDZSzz{sMrcz7GjoNYmE|m5otb$;V2fYHY*AcMI6N&>pq3;Y_OCCw zfb9pif5H{a7J1BYxY!fmb9$z4BHyBN*s-va87HfkCyK-e;js7Yd`54mMof{*3tS57 zY)-vQGtuJ@m_9h{Ii1Z|G<_mpMNA~eJ4Yw1?R2m?kG_TgZvDub92$zf z#Az%tjC8L)l`N+u*(IjS88?2l%Tmraw>+J^-1vj1lUJw^Ex8>9TeAB@LEhByR++xK zfC*z-O!juOi=6S}&gZpx+WGv>RoTnm%GF1aCP#`IYE5sHKbDhgbSy^LWGJuR4K<34 zbicL6)=u@6{QP0x!cbi~D_$yA5U5T=x=(|~R;Pg`!IwzN*nf8{i?Ea*C7K%N*9hU; zaczH01lWz}OkUkl)qdMYl0J8MeBev zfivR_4Ie5Q%dL!25_Dd0>U#;+Co6F?Wu-Hd;x3;7@aOL`Y5bdGBXXuXjog3}QI%?Ppzg4 zdB@t+_Y9Wd1|3YEAjj5E;8lQXis#km@=xGJft7^Ffo2H@Z~}Yjs|bvp$`{aetcM&_ z6h=DaJb@!K9K85&skZK+D!EiA2X{i^%&apDiW86AFGrf1aEukv$5QlLVgih%h@EFE zC+}B?Gui;0E-X(c%X?W(8BtEBVLi^Ir~(S`5djHrdJ0wYOunV&iF!2Au`@*(IWPnb z$KSq9!&+VYGdeG3#PkI6E23t2a?t1ZdZM8)R^m3j_+BVN7gA`ql+S1Q3MurNOlqM8 z@G=1jLJQC~D<-t0`_8h2mLxKG_J2D0oj42Ngg~V(nFTn`TL)~^6`Jl^w+^^x7T~&O z9gs5{aNW2Ln9Ps6*8wZe1%~We2RwQ%y^n|cU(k6=Ts+(V;<@^4|BvVDllx!w1o%7j z>TG|v^8nZJP6tjqkFlSe{{K~TD3hc8XIm7Dgu>p@u*BZjor_gCU7ZOT)``B9dCn{- z#%e6rhd9Pb6O3aMY(5Wg>|Y0*&y6FmcV};M6^p!w2R(L%V zy266pPC$Z13-BufTOPg_4Bv&xoP_RDub2*WGU}l zc9F!YW&HP3a^*TnE-&2lyCCr*hJXeWq-&Z>J)C#};GEkl@$SHQ!U4w(JplgnzZ1Y| zuJ?NYs@2S=L$7G#n#npmtXrr^*6E5kNJJ^9EbJ6foFz<3 z{4L4PnY3&TMXc=E7xEVhJdtWl6lIHc7isNIcAd5k;4ZjQvt?(t^}u>fbCzqwdZ6|~ zz){s5*rvf3$W4)ZbcK5Yj)Ogbofg(Lq$kkCOeggOp4Mc8|!QL;yg;VkdkDdlB82jlwTLEvdn5d zKGQ$b;?d6IVQai}p}N1~q2f=wDDnD=fX*aHjrl4Fkz8{LZRtOPx$L4u`}490WR{^q zHDvL6p#M?IP0Ar=Ib0`wVwP85q|Gu%S6Z3n_43A%(4VU5O`*TlqAzMjf1l8ItfMn0 z<(EyumAJt$wx$5Q3@1Zx;B|>w z_)qJB2Lw*~7d8B3;q$S;kG>Lk-GVEt_`6P?k@Y4;6x;Y|j<%)Gv}{AO_zWzhqf4Gk zhZc+aMd!_zTs`ONT*w|(c5PHaBfD8UA$U)98ivmWvKxV5 zpgIjXbAgNn2(i6RoeMa7_m(D;QwmoSa6AF^p|B5Oo;$vgFc)DLqmk`;KLMi%sEO1= zUZ{COiQEaH>uPZg6&^sWWro&m9~Uj^Gmo?dGkA4|t5t?Txy}%6mVs9SJ5w)djB})N z*og0Rdt7%!wXTzD-N1*$8iDJiTK6%fBK7N}Qcsj~Q=l#o(0aOFmUX@=2b{}#(-%Eq zK6)xx+-OLL*9qM-R&?jME9!#YTx`O#NovY|l%eKJN68#1qa1@dl>S~0WU`0qO*O0C zpuwc*;+2RkD1G!XoF^i)G8Dxynf7JYdxB96kZ*ZwWo>UD6~N#uzO@24%B}=9Y2Yx| zvMYgcUjAstmB0wEky3E&JmR4`=wU?o_ zdwg9x?1jB;-6QPMQC2e?p3ZaZ4*P@p@i;by(P6%b^;&CD8RrQO`*Uxp5sT|u9cLH2 zE;ZTp`$Iw1`#bC(>E{YBeJ&LS-c3AC*hW1uy=uHP81r3;|+Tk?Cz}=vwFwh;%nKglD9}+_4UDFe>PD;$ZO@biCt z===QwVD1>TYW-xVYA5+}lF{xmpR5kfD1~vfVJ!=$`{%3EfL!Y9!JjW{Z_Se{Ey7^n zkqp|-NZH)}pf_yRn87HFSw{MNy;@%7$7~J_g99O)D<58*$A8=A<}oAg(okK{HzTCF zEn~DSI4>$s!{D%4=DM`FIiK6syHML9M$#qeLy-9IZRQ zqveP0GQ8+}sxJ*^v+HRyrizuUc)4?7J5O;t#>kGCrVlt~urCegd4XZWy*Or?nTE9T zPV&rweWaRtwzrllGQ^jSk@T!x_2zCqejN>mY8wGJrKt52L!79CZ^+m27MDwh z8A!fEYo$omXp2})_;`duTB(To>F%Qn)KX&!b<)?zS0v1uT1=9mFLMgck`Hd!qJ%mb zUcQg}F%oItuM><&=Chut2|2aLtkoh!HHEl`C5GyvKsAkH zHy!z(CA z2YRCnkSlbIggQp8h?iZt{*gC$xo^0hkJs`6q# zm7a?({HgN}WNa1ycYRr}AGL^oy~w3}lPaKt*%PTx72}SSPsiMzz~_C`+QR6pTCNFa zHSn*BR=?G#G3`XFRD`=)J(Vao0|vDjJPK)BE&-s!!JqncCLFY~ zUfEhGsFChMQ_eg1fOC~0_A|1lPR}m{`gP^7T)|gmG%=2)ZC29W@F$)huwLP48OKH% zBVl>Edg)T&qh3>#OB~%9p{=afGSK5sPsX2GoDd2HTy7o~@`TO&ps%c+ueJ`9E#%zi zQ;{AD_y9U#deE%bUy*uGZ7n+y!ja5(Y2BGwGyFAX*u^&&YhlbY(xbpd?$$PewK(p2 zPdF&C7&SFkX{vWCE@aWu!(n`&H|x@+uSusv@JP0^+tK{pZwzOdk5iq_K7^RzFkBom zpJf*_Sc7k($|W(={6>H5h>pRC;HWqC52D`N}%R8;<*L z>xv3p!PBz#-qw|@j_4t*x>WeXk?1)Q9y^-xwwiV}Jxs1C2a) zBn{)RznC~9YcsEI%<23ELWh0jyQ;it2ihx7=h3IJPS!t>^WO=4;Jz&xb)KSd7!Lbu ziIZ{c@Fbjaij7;=F|Zm>Z3KM>tN}1bZmuBEss2Oqy}-_+RhDiKpG7{Wwj@-a2V;%R zNDo3@XdOHs>uoZLY9pi1tc_O92&t#hjUNmnT_BKWx!cN=nN{qmb-8{1u<4D4!j1V+ z7=N=F={3N+>r#;J2Y8~fgY03mHsJA^YC(aWN-Tdc3e31N1%uf``^^YGk~8pw{rFfu zI$#`=i-&iNcaIb-RzL@g&WV^l7_00t$vU8q6+D?wdBAujp=h2rYF{l?c`*KL80j65 zd86Tg{H(FN#uN2cP0E9@#W2#@O6M!WKN`es^Y8S-n3JDfZOP zXsk6|d?-YWiN{rpaYlMgR55Ey;kp{qxxmQ%f2e>NFl%7k(wQ3E`bTO|Bz*tWaTPMN zVlC-%NBDzRt5q|uqdK(<@i5`TymuLQ-`3PO%`|;e%tpQV%ypHH6qtELi&B~hjBDZ= zNhmb2{}Rds3oNRsjW$jX7n%W6&h0&;szGaPiv!R7@G5Ua{StNDb^>OwGFqkjuC_9_ zRGgF#V}mM|+Ur!#N~(NuJd>sJwldcR>B64!fJu{;^Vd8A916o|N;UX9roUa?wg&}5 z!AkYC&DnK+Z*>u0A@WBXXPA*FjAHpQZU#eK-Q&N58*nzC%oyCL{%uRB%&aISaxrc{ zGaRaqR!uc|t~DybAN$a@bBaqx5`0x=cYk;vg*+}Qo=+aFIBk5amQKBu4>SERCpnm7WD2x?O8)(s&H*57k$m>cun4V}*AhazHZ3fPYShd1^eh{L%QZIPYUA(uy*4 z)dKXogtA)eZTU=gvA?n^TH*=%y)aHr=s3{)f`|+(uw>sYR2P08WARqx_S#7`{#UlKs^?8W;1P+NRUbz^^mTB`L8~eZy zm=3UG)PU~^2(t+-NY`pkkAhJNH1eUtRlfsxiWHac z0A8yGhOFEH9OB0{JAiMi0mn@{fPOm5KW(npb^u8M!1c}!pf^80+5sFNpajBxxTamM z*pDU&*%t;hdoMr8J{ZvKN9in@eG{{g{d#_o{dK~=Eo;~9RcS_N`J+{g=@w(PO)cVX zup2b&4R#v#?f{PfF>>5srysn^Gw^!_`6$r^F9-8E$=r`3R~Y$xz1px#vY}NJ)#VMjirn|qKq)pQY-9{dMqYDw6jOF zv!+HAk^@7fwRYl(JYr;pC`0B7c+$=&|N0vGI`-O=u1>*|HGs3SFBOKTrI-UmM*<-U z#-EXr{z0N8BqCfw+yjF6Myl_avlF;VYv@V4>(ZUTFT^;O?*y)mBX;Zrl7oP2?@pk5 zkm`SCCvdZ-_^aLZ%}(GLLHx86xK|^dvAag@0>(4zX}f@_LNQ|(uvb$&Yj-W$1=Epw+$`lW6SkFs<5mV-kDx!vskCZcL)}e-o4@ z&4HoPRx55vVk;UUX|uhOHHV3aZnGyOccBH6=6Y6O7ZO&9O-XF|`Vip!f{p6OHsP|E zXp75@l4CQ8dK|;8fIx>FE$h1kJ3(}}9)VDO}XcM|M8kx-M+=LPyCp<*SYj!}Kx zmL#!~1A?HHjEf^^C2xeZmAo$$w2~rC@j#MT$(Mqd_6RUtBVJB&EqDYtrWSB5egxof z4zZ)Tn&QSJ7wu>aDQHI%YiWYCqj{QQdy?4Ed4ixFU9J(^lEjWK7Q}`}fR!3?Ym)2f zM}Uojpn+}Hh&z+Sz;0z>G_ajQK?A#0Q{0s#2KIs=UVa34SR-~N!IAbTurHQ&7-ye@7sbDrfUuZx@b z<^hhg9tDomg}jmE3Ox$c%;PUZKT0cftaud2(CBwNDT9O&SdL|SfB1bR#QP$|{Fo5$ zixA7^f$>#2K1iBzWePSi8IO&ANMdYs2Z5FNFp19f*gQUp-A`QfpWJNP4;wM?<+dpQgaW;rmPOyP1hocB_3emtKNxjY5m zh1nW-L2~ws@WB>2JNIHr_J$N>MgZrdr1#*ZlqnlhaB_qZ>Yo+p^^~_aq+kv~aK5Bq zpQHpfreHZUeoTlT2UFN1-$Te(gm4q%Q<3E_k`vBf369`jDOYby!FMEd9LpFo9L6_7 z*e@!Lci997jyYC2&i)kFwh0c56imu*j{+-EU~F}bd<>W!wd&lI$|Zm{)v2Sa#35&* zClVc*hgEpiW^8o;;HkqJ{=xGbItkj5Is*KwOLGd`kq59L9p%CX~bJL>4KhTR<_+oLO~1Kq$xhMsV?Ydo&Lfm zUC?Jj$TA+(h)+}*-wT4`{aGVERpNDT1Vp@yMjAM!_^YP)Oi7VP3Q94B6dhQQULgq8 zarls+kRut723&#VN$SpWFPTF1;y^aD(Xygm9LMG`DK^uK17;PEJ6^`1p%*O(8C-mg zpfA^TFK^T{7!Fg%5Pho!k%h5-9PJMiBt|)o$sd@k#8I>tV*%iNkJfQ925~#xNx&}z z_%WD(VGG1esIxu;AZOT*krvG%LPEF!8h)CZ3Z?7hcrm6^gpAo;8vZBAK0)B)9|LkU zJlz$344AS2a4mQYm?s3xSfB~=B;!I7(2|yspo4~J8BDMV)?%}6g7*z+gZCwxxN?Ej z2=7ZYaTC*`HM}o_`3?f4S>89;E{`v;T={(&(=Av2SfYt{7l;%7C|T1kNob~@g}S!| z5|{i2a9sj8&mJIL4hol1L`QUoa7c1YBvFV&gbS-L1aa;q(rgScF4gM$ zSOpk9V#K?b#u4=HnoG2I&ld`M_c~4SZxvvy6a<@On?@W`Blu0s$~M^{6!g4@HN`IncP#O7eN{Z>EpcEcbv}J8UvtCWR*5ROjaq=u9 zTNVJ%qDKc7^5UtrfQ_>lOI$)2kA^n@XVKHv5Rka9a26kixPGB!WoL1QhKwkt^MfzI z$x8Hqf$DJ4d9o1w%tE~o{g?LSjcxmcP43XH=Npl7GrvNnE)^``9f~7Ws9+TiF&VEy z#s6KBg8wb#f*Tjt<$(rNvRI#m(Tz3UIm(wu0j4(@6yll9*23sfy_HCv{yr{aDnSx;F5!8s+ROiu1p6oo< z2nE*{7HW#W8nQ%oBN1)&Dlwa^3(8wUrmsXT$KooytS$3bn^@+rvOV+A<(6gsD$(eR zOe@R$Rf56)BS0+kSBU|?U|QB`G~hp%(|*i(SZ=2cc5ecltB;EfZL4LSeL|Di;7VC% zpGuN9NaDv8vd%t>XxbWmXq_ylS6d)4zfA(0-vla}*4OuVWf05lVttE<5$iiHj-d6e zZqnAbK`3Z_Lp8+;yI9{=LD0zD8nMzYM)rJ@HnNX|f`)%gQ54 z#`URcQ250zHuj61z2Ae2EF1epc5|L&x&}BEvF zA5Sni_Z=^T!15%H0=C6~v&#uF16!HIX^?BN7}yF~ahyq#*(A9bt7Y}DoM>9{6|%Tk z!=zaGwSsP9hNa9vL*9}^LtaDJO@!6p1}Wo(#WX*zpY0^rj|WAdH%RyqAvM?~ga?Jt znL0?senP0eCe=_vYVfpB*_MdNPl?FsO8{s7Al@>?Ga?iJbNsv;m_A6E`mc6Z)*isk zW8z}3Gc{sGlGy8MO8^l%#EuG?G zA*6F$su4R?VQa0zwpoSUDWvq0HJWsnTB_e6i0VDS7L9l$$#wM};Lm~}^LsVoDaHIn zi}|}2^G}47%y((hrxo+B1i@Z=zeYT(_C$UX1RdDv55@Dhs*o_U+ zh%HIdjh!tB4x+9&g5B5!l!Ak3rBJXN%ZpQR5Dg20$~;3Owkc)ax>Q@kPNARTNj9>1qGnAp&(s`PYI*pYWJZ4f$8-&tT9<+^mIB-& z;8C2b<QzyoFtev(}EtebS}bF}h*N|vRH#mjWJzE&vMd)H`+pLwmLtz#fc z9n<(e*(QADRIRc;MYYQM6t>DX3UUbrsm6`6h<_girDATEy@ltQ6d@m!^kHVWml;CU zcsPZX@39vLeyVFr2YytY&knC=Zlgv?Bk==-=uIRl0$^^HNttIq)|fq&8h6h zPazutvQh33Sb#9Ao$gF!Gu0C$mQu_kzBru9-hT0Nz&Rm3ZdSjha5{81iPgjkzol^K z*+s$Fy!?;z`M-k;MtLRn&P(< zIWqEzAlPXw(1-_8Wi0z@g&xa(A`i~Yl2@{W=8&~glRliPw*Rh*rDglC=Ssc(H$ZYQ zI=fC&>`Im8py8xobe1a=?78pM6py4TCw)YxUr3c;e7q3SH=ogn7gN<3{1=`6PRV^H zS#w5Nx>EPL7fM#j{gx*EUn*ow{xFu7F}X?z|8EcQrAGXc>gur+(odx6V@g5$~0 znqp;|#Eh#1!I~xwRV{FJnpAM3AXvepG-5-VOib<<1P8N08gW;eiw9123xZ|1G-8J; z;|)PDN1jGJt#bTB5DZw)(1^WhqO>0bL8VR8h{w~^TFWe*KA>11waRMRxf*d(ifUSa zEG1Apkb+HHEEKGHlcu;MMK$d@oqjY$`rk_C;DjqIIVkQ{ zP4Sq{akozIPZ7c^VuX)r#51k_8jzfJ%(PA=hfRq$H{*QORsSQ+;Fj+{(%4PizRDUu z|B=S=^8u!7z|^`FJhn9FSEG{i_uuK0@pgcghOn7uz4P>bEgUbcbpMV-Z zBDTuG&gZ1nfb*%L5(=+zu*t#1#&X{O1#L;xHiNVyu;1m4sYCNVVCB4GwmJ zi&tA?>a~*I#I$I2o8-BZY1RD)Oxz&S>{!j-32+SB3oIF?yknKab=F?sUE-woxl$vp za7gd-`D&~8S*^YJXkJqiFW#a_*Ey7l-W*Gdi4Iz$P4q;`K@+`4Q*3aEiJndhn&@<) zpo#ujQ*3mIb9x0qk9t`nu62m5Mwpehx8nGWC0Ya+HMm#PJop*)R@J~s)h-qo~=OoYUD|91I z+5xN^t{V9jha4gGUIDmx#xrt-KI6HJA3Wo^{tA7@^M>K-Ou|3a5z?EOg=ah;;s?)o zJ}w1_sobsE?^34n3Q5@E|ASmPQ2j_+F_j&f^igdpkH^wtDoJa#sq|P&1Er}vt0^8= zrjkwyno6Ef&{W>h6i+BqDG~%t09HHo?7p7&aY2{kTtG==gbuSZaDD>hF#Qt@y#L+ph_MN1WMzI_C zeJy(cUxWJPILA6!mOh!iP#=DC-aU@f?i1F@9C#$z!TBoQ`bPv{*7wDrC;R zB&jAzjN?SYpA(MiKy5SwawemntJhB8uf)hGe~E?1MB^ z>q)>s>IO}4vWq9FOG&^pxb;H7ndQBjV4O@W7Ym#d%RMnT z=apA5Bj=UZ2m$An`!&IMnO5E`@HwoKhMz8LO>YXEk=utFewuWh|5&ekOxu+dl`FG{ zHNok!Fx%})y=Bx-2%g>z{8tl9g~S)X>*Qn!NQMX*HP$UtX}C}{<|ZF%tW*dXu?*4# z6C^0QP~aEr1zZ~Lc3rU-s1o?5y+EFZyIlJiW)a?sKjc~da2>8qQvNVg`@>8QnwMRv z{9z{h$D5fDf0#)F-gl+t4>M^mdX71hp|w7x-AIf5_m$c=7Bck%Q&C(>->_c=h;KB> z0)O|b#5o$s8_sc=`e8I&OhcY2wAwkUSzd(&5$6by`TVN@=PYIqB)$A9cA?I> z#3<)jDw_wZNq-fgeylLa;ckm5-D8z(3+y7%T_lPzT_cEn!c{rQ!&=)Hii5mFj5x?{ z4Zm0%$wKVs)S2cH#0~)(f9Hj5n+CfHLO{=4Wysrr^6$kkf3Fsgf2mu}B zb4}2s_Ae?0K@a&Mj-ZRoV^+GzLZP6G7%pXri`4$b3PI3EdT7K_wSV!9Am}8=X~bf+ zfAPxI+DpC_3VO+KO|ew%U;N5axqp$ypC=VJ8Ko(%GL)a3rqk=xHbrkCq~@k*#45c_ zu|cn3O%Mut%0-%Dh1!iM76e_TQX?YbDzgPnSE<$Tpty>c@aC=(iTEpnmZw~gb*YAo z9QzIJD*I*ME4V@V%6{2CU&@p?%YHd)cl`$OkQ^BM#d@@h{8h~E;SKZzt3&@n2%gy> z?(mxAhO^OSg$W-B`XI%0t{{{H&sW0dck+qiYuT6UvXLIdtG|Wn$|g^);|C!eN*sr! zZ^iWrnM@>=%X9dj?5Zu=2snRZLCSZ25#PCvS^T)#Mz^__iAww|^nW3}CyQQ>CP9C^ zk?YD!2x2AaTld@OnExU|j|#VCDZBg15Uc;+Ms4-oH<5yN_Ohn<*^u6N&?aqXV}yc! z@t&sm+7QRc-=y#0%oYkd#+RDnTjdyDLC`UNjw9$8b<9e~Xc7uqUebw5!28t^nKh(f zAAOTh&~=W|6hA4~*((UT&LEBWMY+zGf}mrJj3apD;CJ$*V|3ZfN^?1LqNZ49Q`5`o zI(@Y|!E?M2@}Tf+jo4t5^T94ba7ydZh*j!H(kMYpdjtq+#13`x;B-N-rk85OVpY>~ zH)|gY2?c#@jizW)QY;VzBejhhu~eNBTE?u5)UJQPBTw!IcaMkNW^Ub+TwzRZlIlu=1Swl}YlQR#2Q zD9@6R?L(3qCs8^Tvi(j#9|B_eB|z(PE%U4K=eU#95keK99ZigkKSMQK1!$)VoGt9u za222xT-$v78LhD@K&!k~d*T(6oqje+6Q}^~8WM2)d0YtSW-~Q`3ecVxIK9jlgVV`g zXGS{N2SPv>s?h{0Kszk(IXi(%G+YH}{kPCP*e9&ga222p+oHRK$wEL6yIK>d0Byz= z9iYt-0tRT?HGvM$?$SvWpj|9v)YzjMt^%|=@}cu@5dylzvzkB!XtxTSqs}WDt^%|N z1kO?CZ4Fle+8)ANvHm<6cE*gtHmpff*552Zdv=SmehJV%W1`w@1ZL7)Q?IkEzuf?B z^mVi~UxNzJ&R|LhXjAx2Pf!8c%Yk7$wN3KF2*Ahg;QBtX00f`}_ffcEF> z0Ov2vNJ?=93DBOuj;8EP&W>$-ssQbE(&rLt1!&(AQb zDReZpRuJ@y-!wu+W7i6tev#@{ji;iqTL^FN7x8Fp%qZL-zemE8>@A|P`?o5`P|?^k zOo?agmxJqn-)gx=E791uTaS#!lD3KMzb3ii?CEYEjSbtz2+I5)8%HD;)2>^)|&qOpH5VMSxzZUCG=klu>M z2H!wg`;X9-j7MW95uu~8@gr0#=xA*04ch9bkw)yyt0{CeHunZ?XY+-Eelbr|=xA&a zdC)Pg6$(1WQca% z1wq$&R3mgWmVF~DO~*J8M=%;Y{YLE=X9)$Pu@5wb6^(tW(>fZvNC+8?eWejP8uJT+ z(bx|fp`)>=AQ+9M-9LPuj;C>3k^fKbrK(lv#S#`Xw;(b$O^p`)>9 znU&Gl-(wV~XbK&TeQHtsLW;Kijz1cFBfd>hBhvRgbj91<-^($pzT1`SeJ@u6PGU-2 z?|X^)3$|OXr_y>Nv{=qcowr?ErHl<*`j34c^0I+?>;*h zCrWPO2#Wo}uAGTV+$;2pNk1%C&<_cE^G%fXOoBLq(fi($n%zW%_C8;(vW4yHp8CBv zY46)l8u7m6n&Mt{oa=8lY47_&DCm6~HN{QpCi{QMgWlKeW|l(lyIE7*qK=vM69m2Q z!8n56mvyr~W;R+V=v%ur#Xahn*%^YMZ#}OO_o-7`mkNTu^W!f{eRf|6X>{#;|(1BdL)f3P=G8zLKZw@8)*cRE!j9`3Bi^Z9K6M{frKA%u4b-A z*P6|9XJm8%rfmEe#wHSzNH5Fe<0d;E+Q*eWhV5?eg zLLGj&ocyKYPBsuPXyiK)g2?w22;$h^K>SDJm_!Kb=Rt%h*S^4B$Svft3@216`PN&~ zI3bes*L5z}s+Xj{?kFZi^OB6uzj(Q)cuDTPjovK~f57L#_klPKq32-MDP@wBmrcn`gh0`4+X>&Rm97aYfGNgORMf~#5YdV)Au zA}!v=VoxDX5D&+F1bT)*%$Et_Bq?~FEf`a8U5gG+&xqsgb(V;adA-R$OnVb5E;1Sv z<=f4o9~%wC;o4U)iV)O~Hi4jighy)JM`>TdU!>rVw*Wu$3#cDCR^{sSH-Vsj{MtY) z(w>0hrGWaet0`ElJpqqN0nN63Ou^CGYD6(?|g8Fc=fmouwYjasJ zfTZeu3nle@oNFZY0?%J|f%wb*aj}-w3&dY`4pSnm7szdrl^1)`dVvhOaW7w>0{-RO zM0BbyF|xZ_lG`&`z#SsHQ6?z6I);2#CcAK}v~a{FK<(8dJ>k7xnmOSTO7G3g)zYiO z%+F>;FUV!=xGA&FeVG7Q$~DmDy2nyw--Q)?;2C^xE9~26i|GhG6g1veKgyk_|6sxitjrH!i1{-l?CPE z>z16ol->*Q;TC$HJ<2n3juIoMaH*Ewqr}KLhAENWqe7e*&%aazb_CQ>V)U5F_Ed3) zK6PnODy|lQH<{OkQ>6CRmjSi^rR+#d)cFGO%gZR4pZ5gXDfutx4;zRxNSo)>i6TrTI_cL@A` zmjkt565hc*QaqQ%o<7_sXFfw6m9`HnU~8+hN9z&V#2v0q6T zk+Zo5;*OB`fFHQr$k{r9pkn;pK&%Xj4|oG{P%&@?TcKhsHV`+4AXbNBj9kr10afP#Q?N!~D!oGrs2KnD z3uqZVMVz#Z)(Hd^<9!41Oi17J`n;5mjlBu@SO6*A_^qB#o)5+N7|u6RK$EG)6uh9F z!-K9QFQ`gCF$Irn=kQOZfF{#0Q}CsB4%bNmSuoNR+^1PE?n>hvK0qKy#l8mO9!*6` z3TW>9$rL=KUB3%hmFCWAC5WVf*r*{c^B~p`qFhhKAZo#Ktg2E=w*rtihESe{(7&(L z>Y1k@w2=wX&O9xYt*`QwQ<7$7$p;pe#j=u&zRKv@X_B18WC5qk!ZMQy>eE?b6xmk+ zwZ-3ZEy?GxQ1LN~i>{)?FOp2g$}W-V{~=b)c43JuWX}rBX@t?bcCHvpuMjASrD81o z_-df`34%B{Unu$Y)d4M9E@rxxVhPqS=rd>^sn|7C}G5VWi z3-l`VCl<*nG{_X3rdOfd)kYnU5(p~WFAc=$dKEf@5LC9S1%k>JGZ1HKBkEQuptAke zFQBqL#Hxq$>XksOy$#sgKrB?T4{rlD5MtwPK=>M-uZV0=8Np zsHZCo#C2u?yS9{$@eo%Bz^R;2NrxY+W=3`TEnZ9B*hXv)TBugGH#FtxWO_NSu^V-TYVo3EZgCxz zWh>@#8}h!?9(tXuZXbxo4QC>_WXTr(a{>GV%czYXi8dyg41(&_?K4rQ`>z9Pm$MrFqnZY`8`t2K}%8h5B<@5hJ!YWLNZ^_$3Z ziSAV9Pzj0Q3IV;mFNP~6`woKNMDVV(oi_HtQ-XF4b8Y6{B%te<%l|}O*Y2+M_q>KiIz=F;(6xK$RzA`Sy}uMt zp?B~LsL)-kN`>wg2#V+s196F77*3V~E}FZTf-khXo+|}hG$)&aue7?pF9lS_|MLsD zXohYu%J`!j*jFlJ)xqD=~j^T(#(Mvc>v00+vr(=+}#{TN1_swHYuI|(8Cv&PmyWliB8`mR*}h?=d)UC#`k|rMiQv2NWc)ylHZ#BMUsE%4F!-)55sIlOZN< zEtjD!`q9`lHkCq6#e#+UquT$sd=M1&Re`Tj^!=KJ`LN<}%XYEdyK8{IHtH`I?)HN^ z$ZFfLZ=1o(ag-0txXokTG`~TFb*7^=1AJ#DK#^awg*6v0Sb#@Dnnja-Xw6ZqdFX-# zxV%!=JVX7^s$*F7umub7b+xW~dPo6xRRq{s8UeSEcQQ#g`w4*utJ`}Ewv|dYwH6C};u>`4|Yr zd;lmuW!+ZDADGNGxt&(dZqiL21T{oOT{{OnczPHS&e9ml0L+MkVZ;^5W#^+_a@=|5 zhl3c40+IL{n{Y;V0Jz!Q+WVDibb()SDql-EcZ==xtLU83H+hr&uLNUVd<3Skqv!IW z$9OZQGjS~w4rUPK9{w)ikNnMrp{~PnQZ8mP{o|W~+Npc;#`OpcXU&G2nP|ocCVt0+ zgJy!x;O_!j_7hgoIEBlXEk$ByNp>`%x2aD%&NVQ zIdjp@oF|!6^N;Nmp4(GL?MGwhZ>R9$%|PtZ?Gz^8!iAXtFB^c_0?>Ag0Z5eq-ZB7V z1mK?@z!?H?-F6Be8i4V^Vew>SGCxx?(`t7z`&#+j077tYrCM46V1gRll(X_yx>@8w zhHk=7f1z+WpOg?ek>o44c;hDQKwT#$Q%1JFmA@^w({(Y48B>`-5t5XBE2G}YB1b>* z^7mWG*_z*pr7h#TggG_OZKp8!cK~Uc6bo;!aQCgikZrbC_=rD#xxK2et^U*K<3aD>CgtMJY zVf3h2lNb(aWk^Mfz>SLr{1QT6D%uL!ybbkBzwvG>RkWcS?SI$G>txf+Vg@+g+@~3% z8spi74Xqmc-v-o7+(BXY-vJ>2yEPoVg8~{3m4A=rzo&!R*5142& z_m2JQAi$Pk>u*E`3Gu%K)&ez@UjieZX$H^O*v}3EvbO;ZLOv*Y5)Kt`)FJO&4dh<0X?=EU8mYI(_ub}a;nHDBx-FTA8K&#{nX&Lqd_WVcDS>f|9NIUY4T{YK$XliWKt{Wl7yi5UB{ z#en3^v^$(s3hDtv;4C3<774tF^}n~e^dPj}RL@EE9IM}O8yzwHr0+cP_wL((+Qrl| z2YD{Kk1%5eGkQ?q2IMuir#o_&>Bu25cH})4)WmeH z{$?cDQ~k}WDbuG2dVK9)PB?Eu-1tl0#7ypW%qiHmhsIWWd}AwSjX5r}Oz0nu5^ zbP2%VfdTTGZDd0wcmb~Th0Brx2Ks>Q9(@p4c9+(n*ZyZ6>fx4PAjD1WoXKPlaL1O~ zsQ#@2Zv4Splr;|lYwGnD<-H$77q=)2NY5O*Cue06bGq!_In6C&goucy;ok}Xl}YFi^>!39@fP_qR3vY^c)jLN=W_1! zU|c7euv4yGfclM!%Jplc&kf6miYV;?Z0>mZzTD&vD{ez=p`rzV?~VsRGEQM^DmivS z8`NYM&j5+0_tDBjTlYXse%PeJSNzTD62YMsb~tAbV=VLe%WQFmFbcVrh$m& znSnFU3IZ218q?vS0Wj%8A6Ik$n7Hkvpe*6mAC4vJl8WfBGx2+Dv8EYW$#!LR=|8*{ zJN>JjwQ4g+zw>;EIYJj5phWVCL zm=|kG*z<~Z(T4ggtoR*;<`=?V)RFf%}(JeC12P&gcO*q0;M?O;%dbW_4oB^ zjC`V)dk%hnuL!INVA2RYX2(0NOnb2d@@>R8WGJ{ty7`>okR-GGVs0j$4Q|<%(`6)P~(e=tlfIj8f$w7n4ONQ^*za<|~aKE8WIa zf!A%7fot4$I#=k0dfi7p08lQQgZjb;i6nFQayL@HIUbtbqu%TNth8ibmWijGlO8Fwk;htut?TOj^jJ&K_pH-=-jIDSdBNjfd zq606|?)`|HA3nnzcbYW~>L(%!oW7%u3w^v&8Z5`1oauo2bqOX|G?5Fl_Ih?eF!-e% zsjO8PJ&MN*P@@K@ZU8#+*|Y}QU8XK>0DLU@F;M%JR)#>nZ6#)BKoE_A@9WZvrw_@Sx&<5+uKys|XQyDb(}z5`v&( zW%C6)0rg6yc-u$)y^QF1!RfZAW)sB}KO6c@g^J|xZN=vw1%Yx_#)&7g1-3r{>ZA}q zhT3gwD>XXb;y1C$%cvtJto-(Ak9XQuPCOv$HKiiy99~zP;V2C6%H1SNgZ{PY6(Mf9 z#)^U(XL!pm>S%8yc{>o7+lx*rK^ttW>)QE3s}=9GW$tK(I+>qHLG$(e$kXaCyfJB4^>8LDr}P{l3Ro}(A`GPK|JL7VDu3s$^qh86FU4_#-( zM_IcujA)!JrJVUh+0qL`y854DZ9>9w6$X3j_R_B=V3lo$}ddb(J!duHVMmFy|m z`BSWdor(8C9XXH|5a90j}%2Zon+@Yr{E$d$@B=Bz4FegpvcrW zq^D%#T{H684ku;Dn(Ryh>g*~N&E>Nlz(jV^Em--2ULH0*?&6T9`&f1cDX}P49395M z8v~)d%}VD|c560MurmcGn}J#zQW5Gl9t?3KA-_kq;L%XI4N;QRjQ;v|hF|g8T(3mg zp*|_=7nl71R&8@xu%fr%H*ro-XNOdj>wP)PTsv*ex4P_N4(eQ`Aw*BQah`rqp)?)P z%$R9qx|;O*4RxMUWgDE*Okiw|GRHc(Y0Rn_K0l3BMgI5=$pSMw2lbx*Za+ty1_=N9 zQ1uNRPLGvJW#h4?j=XI{b;^3T^%r6J&>gii(v7(jz6|oj%G()Bj(MQosg%X39Ie~y z?o&+KGpwX7mJC1K)5HCQ7JI*ax7VGP^+DT3RyPSc({}BAw+*$d{}wgcowhh}8Y3cy z^Ci-&tf`54+n(MAbyolCKAaVwS9J1r8tT85Vg!C#SuPPtyD(Me5U2$zFxa^q4pleY z97ZIL!_<*wN|m=$w&fD(SCxS($sE4!gPq@6CY=*4w*a-gTKv4+sz(Z)_Bf}23+3TW zdM>zIzqp!jhDA7bPND}ZB_b8LYIB0XnZC^~aKfFI<-}ptT zny?C%%uYJ#PrvjmXlFu%t_gv;S5{h|i~x3drPgcU;UCgKr)`lM?7CGo0<8^*eTtQ* z$UXf-@CFD^#@U}|s<@u`cx9_Mmde2anUG;bQ9?AlFN_u$sM}QEqEfBX`W11OwewBc zE~u}RiWXA9v0DY5Rnq=UEjbWRhX)g3^x>iq2WE5gFLz?H#grL%d4+88FCrT~-%L-L zTBn?3W_)&j2I{|?la-`Cr%4vAJf9X<$5sS-ow4U<+vnQx0@RZ~Qofw{ETe?$i5}{P ztpZi~SXwHZY=PQJI4pX{_Sj7ZOfo*cI2JQwMiYGw2cw4E-RYGvBuR?5cq zlIAz4e<29C=;@z63H1$`Y+-U{AKS{sv=8T`ZDbm&T1yMHw_v+k&iyKGuRGi2@VE7w zqoTRIowM@XBWZ?~=5Shb_1syH%_nEt$#G60%@!xox0n#)`{k{x#r06WM=Ck&vh8!n ziO=rYkc#>O8x>EIY0WGYWtB8*)D8OM^_)KFqSDm$gHJf!PQo514?&Cis*3{k%$yyW zhf8b95s)5nRW=!~r202Ki*cZNX;7|wIGRA(o2i~15Hx8QCbdqr+%BjC#H=pYNZC6% z;x~c1b4wYrpBzldz+GFG6Gw~Jn4PYO+(emBpQfhefdAX3ntRh1R5{~G>hun%RkRB< zgCltdcU3($s2Y(}3m&a{qN*B^P76;~jS2^~wZ0)u->sa}jm!4L*cYf}6*A11F{H_t z3o``%+Rjdm@ebPmCVEQul`nod<9Ek@8ut3 zPjfg)ogg%rrpthvp$;npK1y|$0dxMQ9=4?%I=Zjr*yXH@h^9!D$fn4SPBPY-gRPSh z@jG89Z}CUkMF|Afh`kR|-;j)xf>;yhhaA)m+sV-WcKJYtjg?3=LmfG&gwRLi7TY*`rDEbfZkuoC3gF&74y^hy7t8s!olDv!W4JUg86hZ_zmYAT z0N&}l?0mB81I^{Ee8I827RQRydcYPjU#mj`HccE;5m-Q$NeTh5|V-AUhHHyNL5<-7LJ=DWsrTTY6LKhy)YGN82bf}An? z!I2Ng7J<+~_CO^!?4Tus*0DEbW-{WK`>NxRV6X~(+=57nij+4i|w zlcGlUIO*x5De;X*EC(r>qQo>unp0C`em0-rd+d46jJjn1Y(4>(X`g&LewM~e3S9EIM1zYE5l9DH5FW~;=nQ6RU?{=&j((X z9&TC!^;v-SK3}3IBOV3#WY+s!76!UG4H9M1& z&3mij_DTCHeV~GsOxg*vd^F|s8IS~BaP1iTi{N0@H^gcB^Xve37hJ^=d8uj5lw0{tV8f5c#+SVFxXl5?QTx38A@N-t%bX7TKdYjt>wV*fa? z?7iT|Q{6Rw#bj9HK36F$BH`eL|Mu)1uhqeC(g2&>v5e=jG#=&&&gU~($nQuV=T=h-BfP+;e+DV46gWK(b$Xy*#%ECOMX|)yHlbxQ)SVTo-0SN32EV_lv2++6 z`+aRWnc1$LA4^X{YbQfZ)aiP$6SlZGqncK$&lJatg|?iPfx3jIkapHa zxJS+5hR|IvQPWD4YAc-1WNvR`O_SaIth{3-oH*1u^#1|79=`b+8Dsez<%36xJp5fG zMvYHqiG?jtYrmI=eJPt>ESQE)3^bH27Fx5#OaUiV%4T00KFu_|$zDz?rl%JR5VbD# z(3`JayD-toSSjaVJ1_3G9`4tEY`n7~%eg|H?9$^NjpuEvV9#jvmV;s@?j`)rb0W<5 z$GhYw1}}%!ho;xe+c?u!)q9$QF6Bu0bf%5#1nMDL3p_#Z15@7+&lRaJZmh|Q$BXG= z$|~5CT4z{A*PbN@mgj0W%7sI#5ltA!Wi)N|%*bcsE=0Q{@mvw-RdaJ0B(X55bw;P` zz&JJc^mQvHbw`RbC4bFwd)79WLFw6vUhY^rZ?d)=vanp}uArLtB6>X<5V9bH)Fxl7ZnJBM+A zt-bM-EgOBG-ueb9m!f^Lsf3-6H947h-cH+$E#clN4#euJ5y|E4Zagp6S|l#0UeJjQ zMd?NP?IU&yS!=x04fW7g1D+B_6LuW3enlKSycHL+h{L=`Mm3Qsw?Q>n`^KA1^v=PJvb@Au39+u#ozX%mHOI`ehc|OV<&n}zc%<5byv zHkGpT#AnB3gElG4W9Ex>bjkS~q$G1Lkx$tKNlQ6to|sMOwljrTllJAy@u3YI)>v(h z*b--RUz44WK91#J?Y4rI&T*>^bvR8v;OMi1>dSzpDS_4m9wNhZxNWkHWPE(yax&B` z3csD7oS?A2G)M)FNl*IwK~Kzcw~@_|A0Pa{I^*qx6Ssu(>w{II4B$`A$lJ6v%prJ3 z%1XL;W6;%Qovm+3$`F@S^_<;gkiTpUDs(cHZO<1o^K!*PyL^U8t=m;a_0K+_K0=0+ zjI-ihyo{x8@zF--wE%CF<1cd*d|<*#TKYH&$e(U+H^xZL0@RC~Um?4(%_$r$3UMl$ zgNQHnN-u6jV^x&T^Ysm~4;4>QtVz}gs6Wc&^z)`5Yq-O1oiW=8jFId@zL${ICwVY*rACr05)AugNJ(?um!O+L2oF(+}DSrjEJlUU@Rw6+~ z(re;UeSS!OM$MA={nf|1uHzOog3~sKAQ?AHB6WCXHd}CIpVTs0u7hoIaDBiiKjo_Cb-IT>>i86iYhecZ2x%z>I%Qs0L)5eb5_2~(-UsgcrFPE$Sk)==&7=28XIK;Z4}AbL;##m zJJedA0-kd{XadlT&NVr1TW8+MbhYsyz0K;dp{^WoU>Rgt>`jv zX%f^4HDVFbfC>t5F*D!E7!$@RfWc2i;HST(L&6K@(-S$?mc{+m2dbKowAZvljFp2; zNDu&R2s2liXoG~^19g?w|B=5fpW*se>2NZM>E^uTA-@XUVw^=I*^Ul8Eb~@`PM}9Z zZ|^z)LXAN}31`}I`gr7slr_K#(L6AI(-n1{SW`029Te26EtFRy!ej5-w-8#=*#uT^ z!OfM~Y2Bd{45EHOK!VKi?AAiQcamM0-ac15;Lq{x*U0?#Vh7F*r5_ARGrhgjJ7#+y zU50uo;ET5$yez)>fm+HH{buf0A{6vGX*Ng?q`?PNd4r|H7Dpct=cn28Q@wKTr|p4L z1E(PrDHG%y(Z0AdYktBL2#FIvv!*=IloJRYelgxv&q3YR-(@V5VGeygdcpps$&y&f zdpI$hH^)0$$|A~rGW)1Abw@R6wLWFFeF-y2b)TUYY_^Yw2l{w;pFYw_$v$#V$;qCk z?7ahdHFH+d78-fL@`XRBQSGx-^jSs)DAA++^ODl1K_s7}|K$Lq-B78?ZQ+$oz3@(Sps#{7UkHuVH9lFZ^55G zXeBU~(<*-{@azkoSoR^PuR+8QI?v7}EJiP$5LkjsMn<3deVM}<6TrzRJm57I1Gwp)qa3@Hs z#&4j$fdS953+57Xh#W~?dnb45z!!IgDZ5GF)rG+ctW?R^)t^`S)#i8dG=|-Y=Ip$a zP4wMexzE#VM+ZZgn^Lyx&g!HK z?_^4GGjR7sVMJ3c`iEbE-A$#kW+G*z3)CASIeUAWoppelX_#d6U%w)61&&QQ$&8)w z1pMZ(L>AEKqAP=c(SJYS_PR||Y%Aepl7F`IS(j0|XR1<7zlHh+y>*zb`xI-U$2kvm zy>{dOZqI7H^SD8A$o+bHG_;|RmJ=l|PXkb*(VLx#?7FmXo&7IDHeEi}e$;5#bpc|! z@sf}dnQwVVP%rlhoA$7@#jS4f%#ODTmgiKurbb2GIMBXlP`HY6m{L{&>RAJ-dF&~h z8^qe=c4mvI1S{Hdtb6qK^km*yGInC_9EK??K`l_Q*;;i?Gr zq|f>&bL<2~U$4=e4*ismQDlRWb4EUT0`lQ;(dz~|7Z@X#5zJ6Gnyzz@qArJv{PbHW z@r)zeq|d=#Hg(D&y>`lb=dJS31$w6)v*eiHPJO@8ac%Ryk2>6pbl>4A(PqZewF}d- z88fCwZ=zDp@mIN+H|-w2r7YC5`iy_j7oi=czn7(^+!f(NHM#QHSFUI-@Clt_ZY~bR z5SM6&+}bK%l*-*5MYB@3mKuq=(->1uOc8f_DuIdo8RS}-b4DoPXTa5?LH|rZ5+n{> z7D8z z&C$!-%3|@O-)|09+`{Vh9|Bs;q?}CG*i3?MYN&VoI+1uWk9W6y;=XCsAQJT~HSnjlJo9PbJM_bGI(3FmdIZ6e@$6HgNX?A|HwVbYZTKMnQv{QpD zG?uhS$HZ)YoE#W1(i?(u0O-<+=3DcZ4Jy$P^@{ZzAbMqN`pXCTM*Zc3XciBEJZMCe- zO$;yh(zNRC*8?3tgY zd5JdbLw&}cFT@iZ>iq3hG@}F2`oPtDBvIOg9QvaTez0k28I=Lby%cUHEom`+>y-kCa}?|kk7>~1@yj{$YOo{U6P9IoE) zEF@wx+Ue*YK3Z>^K%*w6=T}j}>nbF|>a#t{<&5&kp*JTI0{R!(RCJE-3+eAZBk|LYoa=p8*&q$}TdZ4LbGU5>%*Gt^2k86{pwm=ojNez%F*i42d zQF0_U*gUb>EwhH`yUCd^ITE}ppL_WMM2s}zNR84>48*X*WEv^sn9lNBpx(*s)Ff5~ z?}b{L^FEAG;HHGbtDwh`nVWWG(DOFVDF{xw6c$v-3P?N4b6!MpIJ|-)66wMb^22Y7 zZ1)A0^Ora#KK2E6U?KN(LPUV={_F>9g|tUgSX$cHza?#`_6?sTrCzJ9t>2en!+Z5> zm^Z`N|MFgXbw!IMP>{6~|6^@2P=C>{Qh8H8LpeIlP@m~dt$3DP+sel~>5dj=$g~VrU3YVajafUpJ(hiNVpQ~y!N73cMn&PLJmfe3G)Vc~kPSYGq)AsdgvFEw8 zE+8(!9^fyJhY{`7eZ>d^1)` zwv8n?u}UOD=m7!HK|v46@3PkHv_5*0h$9;P34J-w0zRKayF&jhtHTH;f%hVm*f!Ma zfoY3$+`V$N*^$#Hhdg;x3xdV<4e3}@r{%ULv(tIx$IHJD4)U+_ z`DAwFX7QPjcDW-gq4E>tr6_MgzqgWsUxoe=bPo5v_QbrEw!Q83ekxkza~S)STO}HD zcIlAQ&Bqzx2vrd2_~#$0QK}aypy#4L1nVOBtv{p9+8Z#D5Q7(I}`B1L%x4m7`${6MG~F)lioX3J8q9M8vZho*?K4 zBP_j47|X3($GjE8)~|x_y6|R}49@|_-AtQZXv>kkIvm{#C}6a*3%oeJoTu6?Ibc2M zxt>cd)kn^(%nM?-36f1uN~Y-pgAe-_fE zYdR<{o*JCyvolyBD38Xtrl}7;EI^4rg{%?F2snIdkfB~SH!zFLR^u2GkzvNZ2fn0$k`ntY2Zx` zry%G08Z(c|bR5JcTiMyPB7Zm6igclnf*PixcCI5~@Md%7M=3|x3oP5Oy1Yv^c4Aw; zc$8nhDp*N*M`@soR|O|+yE&U1X1g%Wwc}7p73JGPiT!yciY}>Rf<}t#By;&}Tw-<9 zy;NbwHw#l_l9Fd0dCVuje3H`Iajw?v99;bK$?iEi9*Ad1Oah$;==sn$LdQu9M_Jw(Mqxc<)g@aR3#sB$~k&@%OP@J{H4goak57?_or9 zd7D|%*Z_1{a$eF@{{vQdx>yJE!AXBz&(nuTld?~~n;UGRk$u=)ynkLtW-x2l$9ymz z0S#{O7PmPGzJBAEa-A65Q0l$`^Lk+ni33fxseOQw(P?i+Aa}M<4<&U-kOh%^3%u8<+I!3T@AgNhPs?DgC)#uu{^G*5$e;it1CH%m!-jH^ecB!Llmb9M%UIc&N))hpUm)al`zTClMD43 zPt)xF&r7ChC&|MBGn`Du&eJmvwN&p8c(CE)EFEWL?D;|9Q#T9j+GUuR`M|WoHrPQ$ ztZ&Gbh=?_1J9Km_)FK_>L}2>M$Wb}O%;D&ee<*r%h{GoZ`&lU*Bgr87Ift8f-kNb- z=|DoBH=`@+>W+SNn9yF2{siQy)NTlDR2EeZ!oUs4r2VNX;r8L9>0+wjJgFU z&m_!olc=V$ibpYTcKkpp9Jj3In?60kri=)bSTAR0YKcEkK5#BCw_&D2eQ4gpD!={2 zD_2P`T>rLeKU_aj=ycp9R#j76eSC2%Goys^ya7=fKZQKI2lcQ%W1L&&)0j_VPwclX z#OYW5uA$u7D&6FMUy1c!?l;p1;=xr@F7FagB%i~NUrh4$Z4N)Vv+PyQL{>j?_|zWS z_L%UdJUjUG9>=~Jv_100%!D4NuunGI-enjIw7xdzM@J7!T^)(rq}FU zJ9ZsCx_!)u(Ic#mT@&$+miG3MExYcvTgQmRZX*)AwU3UE7|{|>j2g9TM@viduDi95 z8Eq#zM(t|1jI?%5SoT4EFNaSP$0MKp$N|G0?}GT5bG>zdzcxpro`B85JTUf+0k@`@ zU>qE6mhso-up7L-TjKu%3@9@c*kyl=if+obb8|A;IZ1iewMfge&sJ3r&e&9H5&(`s zalh0SOn86sWC1BBzE$O+GQ=H~+&v(<9@BQev; zbm<4Tc^O2K@`fTW)X6*CQ4v!g%*UP1@;yqf>D_kT>F9-erc#;f0?$_R?tl*~cOi7} z!V0A#4zAFLG7hehA3kJ#LohZ%XkNOBz=(LRr~y8!Qc*k8?U}rumFGH1moH$dCu(F0 zpvmtyl+4>)sv7Q|%o3OuXis$EqzgBD-T@*KwTc>R`g7v;0Ea1tNPaVNDx-82wThHo_JbKvdd{nUny!TH$uI>=70>Td?$XRB|Z z6zPClS;LQ`>5&8W%~y-TQXb(^%%q71^y&G1NzbtIT~I5xR%~N(Z!62mer|Bu$rTF|dJ1`K zD%3~Y4s@20cAaG2b_-dmz<(Gf>LkTlLHTDBvTmxG z_;iw?WjF0A7prgGwcNJg-B}Zf^~q@c3Kj<=!Yv`bkk8pf#CQ0m{+%yoB=+#D(dE;p z@SiRvJ0E!8XLWUeY!k@u#+vxJr@r*~Y_(qGBX@`CaqB7U&xrmP<=udYMECzke*0qJ zUHFwwy}4@uKf5Bi$Bgki!X1I4s6?Gir=52Sx?P!5q$9_U;bq=PCr%whMT!(~n*Nc@ z;jA&N>9<|qkTe_c7}XMDi*m~$&&qM@*>{x{9b|A-nCtPi5F_QAM%mQ(OkP7&!%mj zkz|DMtIB5B&9=k4wz?fl+Tr~d-44An!FKXiCXr1~&&$n19o?ZW+(Siq=?=#gwD*St z)4$S$Ofem6l0j70?7`Kkm`p#o1b< zrF;(I{V)xASV*@cCYC^!WAF{R4J3nKj$2Iccc3Bbv@~>Uh1-k9;?Cy_MjF3Vpvz%nhhh{Wl@x*+5R1Q7g?L zVe}F3b}JDt^c0|8=YvU}tsrce(Bp8eYSYeZs8fPAQVGINlmN~Q0^}L7$gl2HEBb*% z4qUumeRH3?w@L2~v3y12g56{$`I4_vaNa8Ke+jOWWgLzJdnT*JRT27lJt^*@|9nnfIM$3tP zbX<7ON8xbNX!$KVesUij*9eT^bjBu8oEboIRv#1r>6T?#JvpF+^B^uA=fmz$DG7xt ze>B;f3(aI}jF_{^CR^>8mkBrG;G8|mXWnEy0d>nB^0*jF7>L8aKD+80_^i1eDeru> zAQJjEKRH&?07A*Wj0Y0>H70eq62PC8Nw5&qR&0kvgY{_A7=1HPyYgCRr{zxXw(}i) zvQ{E5+kt&w4EG~afEiovL-X-)18q2=*i4iXr5}`u@N>O_dRhO7rF>Q+bu#9SA0P8d z9R@`@{F|jdvK#v#BPz`vE0GZPxMMR3xeX^5c_5ekdLYb-a(ie(^LV_|#|2V8Byz5N zh%lNXqKy`^Tu;~h<&RQ%Rb#XE_?2abFRx;yQoQ2_l`A=oB33+(dE!OvL7smD&WHBi zg86*oxCt|g1)c#?!~apLO|RTe5*p+1y94~FgPBG*@z^BL^>5%Cy<;6!0H;t@eNFV~HGz zzkxbmAFGfa`i1#z+#~);+BQAlecWgU(Q6eF@+A*9nuBNUG4nbZ*uHz)>ca-!xo2EE zZ5#u7fnkJ`esC?)jf>t+zfPP%fU zzFDC59`WC87AD>UrEjWiy0JtL)DE(MZ3f1Xk0EciE!Tni)%W0O&q4kAdtlt0d^KmY zZHJd)pF{vMc75{R&QJiCJDYMX^| z^ml+Z3)3NC6x^6@4lM3MHEy2j4i~Cv^B|Pw;hP7c!OR#<@Y5iMBJ+x`7iSvH+io#k zdc`r~<}kC|D-uv4*DlNvNg7pZmkYRF+6GWLfhXTaZU#PS7hER^#dkCvZ5u^g3yUf@ zUK%ubTqij>;}-ZNRCXULlStWl9T?2YVo5~`xI&FRJgELs$tg@uX0m*XUmc~7u4oWN z94s<(SJoXahqo!hXROp5$Adqi4E{s|@AZ`5N*e3-X85iZ{ZEi5`TOrlf*f>`yeS+j z@uO_!^rka9q;ms{k?SNU+J$)MB&a3&u0Z+xV%&0*bcf7l@0=}F)GYv)9bPR*!{=^U zz88{PyJnvPiA47N$ez?h;@l72rx!cdE;w8y^SHlKoKTXO6PKtZJRo_S>X+bABL?Wd zrdV&2CT8g-eVw;o`=6w9tFD1f_l`E*+x-ad6w2%9G1Ae9TX1tYQ7*Ikbh&n+&Easn z^4!!6s1tZx0*%HymiOT^Sq*L)eSBcz%Ex+QKAWEHQc{oC`)v-E=H&_1G#q^N{VTTF z@pv|!gS>)kv{%Sk`imhqDRJ?0Hj7Bk&3%rLED3?k>DXzgS1b8&^IVBz#AB4DZu<9W zH}MYVYn8)Sgz36^y^<(>qBR<~n8*Fh7LqSCuF(iXYUfFm<#G zxce%8Z^XpAtb#7*KJX)=<-y^`g(t;rUSyHHmJ zk(H6{*GOhvUR`6Hq8y3ysV+=8vKrBjj1HiQ(j3!2xsv?2oOJ1F+RDjZPhC!NDKHIu zMO#L- z-wu6{pTiv?jP%O=hr(go{+ z5?cZ!IAdU;UJc2`3&Ke+-w2VB^4Qv&As(2T)6=(fLhi!7D=>Z+a&fWyTj@*L*Y1US zi^mjYFZc(6IVLi?P!9*&~h?PgQmcImuXO_j}!+-K?U?F#H{9G6b$$(gp^ zOuS&b1*l7EI>Kulzb~%2ic~J~f+gIvfhz8C&>AQ-O!3 zBt-ly^EjE$_kxq=8y*?kPPlTUc!~c^RfM^?w2JF}zrMI$hi_zuUG!m==`qR7$fJ_# z)&b$9eR5vz1cMO((ZYOS_C5Yf3H)8wJ}*^`D|Q$cG0m`E2;=7g6ORiTgY^vpEKc~D zwwo>HWdQH%eO`NxRAPm-`@kyI=&iF`BaK%9}j6*e4|V`J`IAjRQF$1%CF-C zZKiD<6t%zQ&SF;*P^$)N-bW?v@e&W8SP+kz#V^CpmV?~#xV#eRguMPg5A_wEO-&-t_UK-i=x2@^QhHP;Cv|s9X$E*$SwJ{ zf(ZFkMX`hN>Aailg{-?eOR)R>)PlyDWmf3yqH*})0>6}&OsO_Qm*l#F@$ec~@!_Xe zmt4hHE#-2zDQ5nViyZ&YXmS|-zd4#^#~B>Ve)>O7=Yynk(EbL){--AH3Bw zFwyIe4AFp$HEE;lR)(+W5BvCnqd!z%%*3mnS&gLgP^d}w^u|9{iMXXJy-XRr2;K743gTGARKJ zP}l8DG}rG;YnmRhl{+(P$md`oTd-24P+%_7)2vL^g}Owkh!kQ)iH6G!4W*GXgbzvSuO@Sw;BLhXeQaZ!Q zKrP)}qL0qmogR1c{@mTmc~8jM8Q%I)*J#*l1va3qZf_>uWY{w&k!3U^)T=ae`!P@7 z7#L`+=XPm;x*aK$FoZi}|-y5Gaef)%RvnSCP z8z9)w`opLRL?x6H!>|O8m<6{5i4jFi+i7_#SAQumkucZi{BiT3#KY1jyMtfgfcl2v z)rMLP?^$H>>W^QE%apjR#V36}KZR}2pTd`jMcA^bM5~q4?nPd@C)5RElSdNSVtY!e zEtd=*r{m7Z&bRZ^J7&$#Lahr)OiKBdn9g4R`a}-u`M}jd;{<#mOplg zJ)CNx9<5T*bOIo7$!iwj)yc>71r$azJfm+vI(R|fe02Fm!xyTo^e}ISb*Ks zQUyS%#vGuYJtK^tt^yk8nZ&PF0ZrWWiX#dY-rz~F{X%>Z@~YSTZHU4)djY!u_@-v) z$wL%SbKVez-PYP5hq5!G)Esz0&r z-X&C$L|qU5%*$t!R|U~_jBFuV9D&P~#updT;>0)8;ENOAfhByBL|q3Gqt6Q?TaIt^ zD8Y9mt}ytrf-g&aJqBNv_!gG%Ns{<-e>gUbQ_Jwh4ZfrBM@`<}1l`|=?p%ZJZ$x)R z37sU_-9!FxUKlr&qib#%BlIou@GZ~+v_KK#-3H?Vaos&pLaEclxlkQ(UKlTwAr&zd ztViR&rq@Skv_}Zqw+z}N1noy9v^tGC3rDIo=Y{d*Dxj`lwc{xD+!0|^+|DExD=s~? zw*xh6hbWBL3!n&{tYWVXQP}o&1jjRNr)|CtGy=yUtT}dQNLY5L99{h8cC8~iRd+j} z#qH2gA3gC9jlp8nXhcV7L`Mjs5eAV??N&l`#HJ86kH)dsLStH_F)b2I;|wO9+P8#h z(WWqsY{79Dq%kelm=+7BSq76%9aO@!cvF~0kHQj{9u&orJ>G@erg&oKs%PMuuBb4ouvqep2x zCt_QT=X8zdbiuRK;L)jzN_bB1izn{kISD&!JZEVvc7mkUp1yv zy=h>%#2k+)O#gN%ll%=IH>@#(>!D=G_n@$N*r@kv8ThSMp-C@OTdmc{5 zb!HN|GNc*{z?C6R0E1QojXg|prus2cy!At@SnRLB#MU$dXJMvVIvl_wDzH4)d$-qJyTA|(d91j#vzY0cm7OR-5@)l0 zsrXD-HM+nvVIH5H!M^cO#>IEx=PnB4Dt7M|LlyqI7r=HlgllFFRY>jy{1U*El?|OF z5vZ9rR3T%a-Veu)9jb5(X|6eSs6x>%xO}L>W2=GK^+Oe&BmPxG6%N@8I0nE8D)zxp zg;!V;QgWE7`bAhu-jV~a` z(OBAkn8L|>0TTe6p&CXo6Q~(KOyMj8ds20cHB8}r19e0Qf$>Rmdv*@)R%+=S06VHi zZKm%?QLDTIXv_dRa+UuP6S+LUki8>!m)kL|BS>_{j*^iusd}bpx?)EjEjQocsfl3b z($@^7@7mU`*bAuVrG5RRP#^LPXU;`Z(^#j5?L^=@#(E6e{SIK5&|FWFx0%dz2K6NQ z=QEiQn(ImO-~*ZNNa$33;*PSWiW&0aJS;K1Xz_T_Ld0qICj+z)arXc<4VlaEdrjsj zrO6t_+JpYdmNOW|+S@EDtIj~5c)`hLa6VRs)Y88K*j6=4RMocR-HFmP-)-5}y)=xo zn3PL^lDz&7p!OPSXcCRw!9IKkQ0L~*%(h;S>4nl*%9fP-|~;|+huGk|)~ z8zg)u5;L9=!P|VwG>Mr&T7Ld5lO#rxgR9pBn}{2WS?@#JdU=_rjfad_j&GG(+6v%J zr5YK@_NMq6wpjx-GOp`QvDbEzq(p!T9twR%`ldaTtZN zPWHo(i?F{&JzF(q_ri=%TVgDpH`vD-_7GBRZ{r%P~TrCPc>fPH1D-1m;>!}yAn@OTE;my1Q^onD{!y z-JxYlHO7Huiqu?rC(tNlW0`CzZe;z^#O_tcrFeWxUHuwe{Tiu$hg8o2*GT2pn3R}2 z=5P?UxC>~crr3D~%3LFY-+Y(Hw`&9lb%halUL13~-$ez;;m{D3J;7AE*G=NE5aZSs zF>O9hetL*8G?(86)P6{Ax=E~81iJAq0JZA(K;66?-WS5|WM0kBc2fA+?=_E~QL&wN zQg~PjxcEd(!8#S=;`8BMfUG|||K3}FVqAZIdN=j2X82ADqfCwUYRK%J6h_|-4EgI$ z3S;jEYUb{wFvS2IU)```Cj~TI$A8TNHro__qhta5i)n@nSk3J_DI`t#iJ{nQ#6|4y z?WB+;0X3iRq);>vr-d3C>J%_^H2l98jHQraT)9Mrs zz8i?0Tc>a?DW;^IWdIhd7$xjdwx!qG<)&n#CijXUxm=HLFy*i4^3{Rz{^DdN<;!UQ zQ{dlHYFHd^DN5v^l_Z8J?>8vlQn3SuDcsI{uJwW>QB8bue%1Gr|bp!x~+V08#0;yGKjtzeGZJ zx(BGaq)y=(L+rXxY*n4YZubDOd+QXY+(UUODHMroO6o^64HvFJi(1+M;N=Q6ESbYA z6%_qJ``-f$lT#~c{S`4!xA`m4m9R;?5_L4eFO%a++@N%)-_V_YLptr;wNqWy0RW$X4G3;KTj^`6gBsq%BmpGTVOOq_GcnzV`oazDMQXDMD;Rq4elMJk)K;3Vt8%^#jxjIpgt*I;aOQB-%YOl@!qs+ zlDJu#TSD~79PSobIG@;Sn+(LG0&#_bcvc|p5QvF{NapaWpAb@3K`pH z?ln`}frjs9Y}c~_YM!lAXg4+1R>Z!jQ}~oMD(V$-djk~JcXo@#>J@OG3@InWUQ^@d z3YiSIyw6OA+udg-!xIfazsZp8on;DN)a^xBiS4Z@Z*LdBy=zU4M|FF@_1YW9s+@f9 zF#!GAu#*R=w(2iKBm{s~=k_0)K2J z1^z6lo&%1pq-w5a(%1>d%59DHB+a#XF3Si+oe8R`?Q{onXer!V$@ed6@7MG2HNf48BAr`vlOamDg2c=ZZRIWUDt$>cHIxu{&=ibyceY2KkujUup{&4PkaIPM9Hj<{UJ`K{bO1X@GCwtqyi_SRg2jB=2w#`z6&qnLsR}egQOAwA+}QZ5t3*~;S5a2o z2h6gvx{8u?D3cW3)m0STlbG~vgeuwymoQIOX{?rGtD7FsviWKiW%K?AfZB?2+8%ve zu)IKeWC(vTPK(szRWgMC^@gxwtr@~P17L=5tF=|YJ1q*_f<5&FbwXGM{e&Aq4PdSu$jm+N-(j514i&8A48FcMxAEjKE@KYw zsbDT+g>GF7)Mm#4^__`4is^sTu^>~@5#s=2OO@?fqzA{EqIsyfsb1j}lROwTkJKw% zV3L1t2*r4Wx*4LUWiykqxE)_8P5CkUSnC)?+FvYDdW<5e>p9MIcT%|AG;pFSCykBY zWXjG{-{1O#o*&m+BH;N2bW1Oa`Zti5RZF&yH3N@ zr?PILt5U}yQuOLL zfFrhVz*V}zs6iiB>4ye%mgvgo21phWTK?af;{5}ds#>)|^}$rzsvA8(Fl}dm1QRFY zUz_4-QoK{3*zat@ZZ9U~z1aMI%~(Tc61(w*Qd(cWP}lv3u9O_UlwXs{?~?FKZH5%J z@>`j>6BjGBbe=i)j+?XY14{ggFxDG&^rw#CPwPRTc6cin{G4{jrigx1_{|b@7pDqo zrCANzDHlhx_-2a_0@_#GJa=6f+Z^#AP*=?1&vaUy%0$LW+Ze-!mpus7Y#meBqZJ^% zBT+LdrZCPV$DpPyrZCkc_aBHPe)0x)^Nc}gNvXz!UOb(^|?bVF|#sOgC*9B4?GUvqLy!EV*|{ud2Hu8t{u#%|Cq z&zk%v8}vTlA$k~YjVbh+f~Oi{FUJ(NU;!X?78uNTE2;BS7D=5Yrr;hWb%wHFfFWW3 z&4}EC^Fo>nf0Hfq0&$lP8vYP4ti%9)w!Y2pnOWl0p^9wz`993t$!Ead==~jhz(YV? z#-5)v2s@d!@<|s9)q)LS^e~;y;b`IEp%0mkoo?tqMR)8t(k~sm$P}EaJ9dr~kbPH~ zf@QkK3Kndl$IZAGy}GYYDaGM@S~`0(X%X6<5!xPLI^hY#vx?n(f_dbJlSxj(vr_K` zkLsHY4gb+JY}gD9Ew~RC8AUrX6qLIoLu_Xw+ew(VCt^{EMv;0Ls4r#ihoeKw9t{Ka z*)AL_u7^$a*t=eGKc?z_9~V-?cFK0)xX}4WhB2u2VW9R-DlwhA$A?~B7{;~_(<&-W z6Ul7SF7Tbz2XIkHEp;h^syxiMeJ& zOkrIsKulZ5RD;659tL7H4GQ}`Qr2|61htNJT|3WHj|usFHXmz}KUj++s@2kJ0K2G0 z8`$Nlx5GH?5s#K#*dFTS;1`WdT=WQoI9xO{v5twji(Q#$d=#jChjgahB!0`Re?Ce= z!ar$Hc#sFiu@Mal_dV)iKaem$c((?HhdAxV{?efEpHg{;DNi;iJj@V}*x?NdRgZb~ z57y-+9_u3s>6q!>Bly?Wx_fsk>EGQd{9YJ8Eupwu?%trTBVE8f@_XcyKwUc?lxwgZ zPXhJ-$d72ET_q0ozRB7z*<%L}%TgS98mRr_c%UwhN92`q_i3`A=C}rhTgC(A^uJW> zk_Lq(PXn>58x-1~1sZN@pu6-Q{#)~SgThZHXy7|k!z;{;z0;sjcorD)X@kO%&z7*Z z8o*~X)~la2SkHeB80zCY-GG(hyZ$+Y@80K#&*x;0x4|S=#aopoG@!nNJMNqw`<(t6#@tUjEwGQIJlTgavv&WFdaWGl_C zQ$woJ0Zx?(;o*PtRG~L#BB^n@wD20yl_XgB?qm{nh9>^wjAjw#yeA}w@%KoT?Op_G zcPC8_?h^;_{x5R)d@NSRiOq_EfN&fslnr6H!$hhWFJGD_UX`+_q=R#s~ zw2^oKp9}(OzHLzWbE!P`tDO~oybc&rx3fY$e~j2!VITh3ZD)nfb!AII*xl2LsT6*}>u0F7*qnnMXt^$02h`4E22HSE(lEMm9jDuTK9kky8ws+| z0~tn;$EDT4uy>@g**u=YnMy5Xh~z$s0VDfR*gjt8%}@>)|B7gr5N!fenNj%? zC!V|&FX;4-ULy6aJ1d+z5g^vdvDrH-Y$y52ofVdr@_Tny*zqNx=FpuLmYeL~#7-yA z2p%9colSJ$X{sSckR<`lF9D1Yf|4YfNQ{@L>%rfsbi0#E8;9I{P8j>L#brAy zTxnYT8*1*{S>YCwJX8hS)v=%x&6zZw!3`B^>3n_Io&&b!o_4>N=-IIC1h6gF?Y~Ld zB+e&&vSpPr1`lpWpvy^8yZ(LqOF->ijSkX;m_FH`)a;v56fP(ygf zr1r1*=GO}DBptYTA&+$h`e0MOlQgrl*UUnbt(zI;HS;^&Oye#Jiwwk&*q*y6Ok~9g zyC|GwvfE9VHY|Xf}tj+OxS`)^K?-+>P1pZr-(&JORPjGzLjcxbQVz8TZ z<0X%*w~3hD_>@1S8?R7Hd6-mhs_ZUj;9E*Q8*VoFdrHIOO-eUBv#eo4A85*dC(s#_ z($I(WgFeEPHw!nmd&NlW875nEBl3!o)?HXmdAzuUsNvVY;ZQ(J<<+KMvowB}N$JKP zFV!J5dGx#~Zx$Z?_7yYKykkp^leYbNUkwx8U05C)s_7HJ^v zHIN}*Hqgr-Y~b)x1HUs>MoI%SOiDNKmwpW-P5DS^;CQcrvxt`sT*M!2;2LRwqkOQb zGD=4IHs<>)t(oISwv=YMXX(e}e9|h8uU1ZS_q|e@iOG3hm6W824aHr=@YL{UlWvls(Q6t0nhf}8>p~aTeo@@5a8{7 z;C{UvJ*w2pe_^Da%J-38?!#UVeznxgeb~#`t6nc9sRix=)A(eO-Z^wE{Rqll&hZHs_**^rKrcHz;CVi<-^=<2o?Y+bxP@#o zZ>3}OQuDR)=kRy6v=@M*cz3~B^qJ9jHPCwEkiQOn;CV=Za!bX2#VYpgE()J>oWhL?)vp0Ve%7ea`WicN`VI>CgYMe#3X=Y* zNuOxa5tBYy1$t68L{n|Kgr<4v;suFuBezy~uDccLt`F$CYsaPX>;GN@Y8(Cl)Xl|} zLVAih&CJn5r*G$7Bk%SWNh@l{u++g#0{;)fk7v$Y^LEy8%$d!cAck87!+EcjWMXnA zVfWxpL2{WO4s@%&!6{hnv-q%(zJSV0PWU&*sij*2=#mhFuJ1kwX5^5+^}{ee zBy<|x#PWsETN7ziIOGoiNy^1`Z&V1q4#XxlDs1^Whn4f?VFn-{=pH-A$8%o9`jGC? z;fz8A4wuUxKYN|K3>>G}!Qa0Q)E+}Z3N1KUvi>5-|JbN-xj}xG8d7Xj$h{5>`A4I| zVf=Aaqr!y(dSau(-3IhL6;1}+s^ zA0(~9(<>DD_7u~d=FP1aUk7R*ChZQclH&Eud6qdjT&>90Pgo({f@=ib*Fu(X_BBKB zl}cD!wcfCHtMwjh-!>3e2Q`BIkO<3l*jTN}e^@j3VQFFe^+02XlXnZedB5VGWd?I5>`NlJKgK#~^>9{o z#9{ri(0IONaC7>Kq`zRbQ&^36vhk+kCG7m0K<#_X%ylJaScOi!Ex^Yzzi|rN=*Zv$ zq4uRWxxA3O7musKgeh9GA5*cl<7)8sn?TJ!#?|2OQ-G}i+#8CWGp+`eZ%K`ZhAA9w zYWz!yZSWhG)ZEdiaFQu_OT}JjR2cIX5PP*zVWvR5*Qjujfmo+v+cYU0Bn7N`l__{h zNsUe^V2zth!3Gr@)TH1NC#xPO5Uje!K)kD4J%bQz^*Vw0Ws|~M1Mz-e0dje5059PQ zr6uUFkdl2o_x0aYJpB>9q@Ek>8wvYD?Z3#(bOJ}pgKM|H1=POAEC)x)EsFcttUej~ zuW9#)P~br4t10~sbS5?_gr@3YI5ITEYEpQDSmY?D#uO~p5n4ZjLHehI4MID zUWKhk;)0Me1Y963%xAq)wRkquJqxd)O9Ma3dn~}C0(k^U?PiMB*~uF`g@07i+}}SZ zzb@#HF_Y!MzwXiSqDK3opuN_heNigkQ9`TJMEjPMJX(g9jMg4#tj8%8n%t)<#H;mG zh5d0D{~_K2@RY>5y<5WjR0UTceOK^l`K{gt>awXsE8eR3?Bg(YWIAKdZ^cKFZg?B0 zxwJ_kVg}*0iXm&76vn>|40*6gVcOfk(5IWY4-&X$1NXLu`;!Nk2*ABW1P1Q2ir6;8 z6|!#wu^omh9Q=0KShg3PRD#R6?y$Tzh&OO@rSA1t6^geBT7j>G+*95L^aB2sbp0Gj z^ET2~(%l>1_C`*_qb`H*L7{a#ktFm<>BV*3wQxbPidh&5c{ z>UTJU60Tt2mRH6Icjr3>^aT&}6N38ED`pHYW3|n9cxLz})*8vUwo)YH+RB^$8%EW; zWDsy|B{guXcRhAoE1fnsTdu9tViLDpdk$`pqG9hExw%ma|HP!o&7+n3eHz9*rbTX^ zlk_1>v%ZV#D&PMsjMLr)YLA-AH3PQ@#+L;Iw^VYK{PtaToPu_e>G=JXB4{=58A1Em zd*y<5k%4<$!|mw7H3Z-&Xg3?U|5S>g?fRY(w7uW!FKCnb^#q@ZTH4;i%|_bRS9;R6 zUOM^b_sGoN9I#&48E2ZEp2Hj(^M^1;#$0MLL2Fl+CfxPNHChYms@QI{v zH))Z-&qe+oH(9OND9!(y>45ybQCa*vj5nAYkiRb_cf|XgOouBxWZ1E|s^JWgzpI8T z9QHmiWt48^P3x58@=V5&FKCf}{acKO!c4HV@ zexUh;9q3Tq;RB%dD@qlw@%)NeLq1>^?i#L8H4Pxb9bzvGSJ;L5KAEWwBHhnQISH#^ z;~gY)>tj?S3xF|fZ4cuA$uyv@-EwX0HnAs+$wVX#{K_=2r!+8=`F;a@Piwx_WfyaJ z7bhsS^e_NBNZ7{?4I9JwD?37}f7#F*hQrFGrHFOqgP(X$e9f#nM5+#9)qk>TGKZl& z`9GD3jEj2io6qfsrXYCc$n=o0Qg|QNR%lxG6oh*c;Z+}y$u6)bk=*`)C!~AQyp z-?6Ee*;K-|5-C3M*GAZn$ekcESL0u-R3llqSPtPjN=VJKh72q?RssupKLl#` zCd<09MBcDnMBG08j9b{x=9f&TiuQku7nE8$0l;5W&4XDij10?t;GE(v1=yPeEosRR_PVXAu!E|L@2 z=b4gcd}RZ4mGt#h$>a+&*UA>;mmdLj^W~#>xKR!+cVUiIm}NOBlk*qm&^jcZylZlg zW^OlblI`B*9|5&-^2xz1f_RIMd0UVRVf9UIiFa6b`1FswEh)@lq-dKexq z)zBE8DWwI&dclA&!?He=(dQMK4_TW5lj{uDBXq#z7Xn?<4;|jxg3s_og&u#thL8dl zhdBPBPqfM(D>q<%!j$IW68Wt85N2@X+BkX33tPuaz(*?!>`Y>o6Yp|yLWsIG`xBsc zJ%^ziXUK7BkvU&8N5=3{$vo{7uEjOmHY;o~Ll4V|p&?_M70&+z7&5+D;j&MFnzm+z z-xz==L$R)Ag_X>w-Ow}xI1j)@A+Z})vmy=D2R4t3)N>2u|O zfjS*#%k$~;D;~HujNSgrn1>mjFn=qMfBG+l`7g|sh4CZ-O%QYeGiALxSu(Rkn!(9p z(k>#z83IAgI8`7{X6EJ0jQ7S68BZ`n8eA*jKl&7?{gzlMMYu+Qe$LG8{^<4dI>~JQloaSuj{Q-K z!MczP?jCb(&Xt)51dqSZ4^!ATXO)%z0zwYr@@sMTo! zy|P)s_CfD&R_Kue&I}n-aIwa5m{)a)K(P5k48)}s()^iHK%B>zf{QBz=LJ&mM6<#f zrr?o^*oJ0>E2V%n&Nl_u=^8h)0O7Bj6*l5PwKVpN7KOV%1x@x5Q0IwbCa{R4tyC%- z7gxj=xU5<&9R=X?kg88R8Llc5v-zn4vX5;g?EGYxnXU5F^Y!}nNS*5(taEI4c5YU7 z>VOr;=I!YjTfPpsQtCa-p}mYl>)-~F!*wLR_IjQ5jAVT%S*vu`A~ATsV!eBrlf%(s z@BaETpsv%&6sFK$eqrU@jbV)N^01=v*o|Sdeny$88P=lk)E@zM@{7ub16sHr^;NmP zA>Cy9N~@FkwxO7moKm7=?Tan<@S!TtY0ZrXdi>sP?!ax&w#}v_^_C?*TWs*FVB-idV({v8cRIwwgC3iRG z=J2urKF+?jTM4{W#rT=`1Y#~B@(#YLdTdZNMtly`UdF6c3%;&;qN*B+&w;u;P7RL= zSK}O(zs+(XXjz!_JVJ|BJHkj&Hi^ z{y!&alco*B?en-TP*Md2St@&j>>)!LY5Qpjv}sII%1~s=o`OsPp@56ruo?+UeXP$@DiVJz~CE| zCUmgY-=`5Kt)iu8&TO=qp-<-(NuCsk*(Q$CYD+tv(6(-5`K(?~9aGUMe;3H#ZDg7G0&^ z?8ciua56sg|G)nhS+q2cQova3SB%FA=%#ZYMjcQw4MK5+*y3v?D4T zro*m#i{mn>YxZMI)wE^~BqXnVjOiR0T^k-_YNUiAF35j{2J7@=Om36pF+Ch~lp^FkG8bYloAxjA_8$x3toPUfdsDzm=kE;?>Yh-qFC8l)Y zxTg}+K;?MN<*8bU$q$ZNm6-bCNApTdL-FI8N=)M=4Wy3KGzW~-8Q_4_x4`k&YHy4B zZ;=~H_xkBN4R^3Kuyd|R?VppHT5~y4`{!J!{f&VAmX*=Z3g!~n`#EUG<%mn1lZwzD z%t#QI_{{~E_~J5L0yh#1t9@`HB>W;28j=WSabCgYG+#d}U8XKW<*k?d1sO=E(TQjc zj~dYHr!k4}lP3Z9rm>0elV<_+p^1s`le{ZL?pekuHYLV5#R9e0Z!U3)kylXDaEg^m z7?LPX@$waQins6sPO(Z@dsSjOsH`s~dWKYD`aDWFs)P}VBw?U_Pd} zZn{R~F5O;wfmum06<%Hqv?Ea}y!16#4i#QQ2}ewYKXOegyfJ>D!dnX~D!hrZeq|~= zR|x+p#neg(Uz-XZevK%#J|cWcHFs(Mx7D1XiH@3To{RiY&2Qods(Cejpqe*HMw2Ts z1vSktiJn!Jn08$w^1NS(=}Y|BRf*{ge(bNrlyIFW`P)iNcU?zG;UnWU@$@9|kuu;w zA5jY&|KcO+A%pmUddU2wh=I>*5rT63(m73v!9U9DM;>eB0fo^m$b(!^)Z=f3v z)ERK$Kr_${pXkX9M|zZapgYE^j0Ewj!ESg}KW2PkGS&^>nE^ni6cgO=i^YKZ&=ird zo|)X9r=_#_&>hoRRMg@Pc1y+YL8+wTYbjx@TPpq_%8e<*kNAO#KPRjR0DeG$zYg`3 zvQBVIbSS3{wnMnTHcFV{mT-S}2mwZYMhP3;(mp*lHR|KS(W)|&PdN^`J;N$9)weNu z##d%)2i6&tnKFeHv!3Ub^-EKP-a>$0qm*#K=ru?PaO#(paLANxB4na$1^9uoEfrR1 zI!jr~=7 zAyq~0#ZMEZnDx{TlH}|SndOGUMROa<@7j#N4J{^LAPg-AzqyXAtZXX>T?*0AV(`G* z?2-J37J~;qY-jmcBPT%b$tFL!mqP&w@I#}EjVbP=I(Ag)>y?>4QhSUlMy_3znRY5X zx|r)^Wu|>k6ICao_29QvVLGDpF~vOhR$+Qp1eLAAbW923ih1f*Vam5NxjIx~I;#>! z*iuJVk;OEs;gVv<*~I#jAckDisxT#VFb#KFvDBr)Usx#_Rz_ZWxK1eK3x(?itqdP7 zT6QsZnpZ5+F6W6=UbQo&c!jkQ6%;GE*g*@CN#QcW%1O@-$|NGtD%Xe8){unEv@+-= zo}-n$J}P#-h+PU}_aRy@^|2P2^rrWPv`GM|nT>+jVP|sR)B&F`VF|HGkcWW03rIh0 z70e~5MXrrim>$xye^iXf@l_S3;ar_(d48+HWRHt@)n}4*j9Ks)kuTel9W)xjhgJ20 z;w%9H3yPy_amO)1@q*%Tqz3_ss#H)MPV{)3^gIiSqvvTA$0Y5lqUy7txbzuWaXMJM zfFEdAFU7$xu2*5IuB?lT$7q)64TY^ffuPr)K_d}WSLW&;LNUwhC;G5BR=@M2Mf|$n~rhNyHE@p@69x~{l#TV_L&Yvwq$ogJ?yvdi(}FY zA>_KVDpOBQ_hoTUxvETGgRyc|rXTR5PF1F}_|dE?(`Ed4wkne&o+&xID$`B)(Yq?s z-SJHFwaoJ!i1LW&<|4IW1>v8TKWr*w6sw0`FCOvG>tgbhc&5lMi@`h!OsH=N;o*s{ z7caQvpvU7Q^?qGyj>-mTUz;{cN-4~c=9hS-gp*tjVCe(le$2t-9)ebt-XqW7 zD@3a$>xK@vC&rX9TJiHr$gtR22vZY&|A z+%pax#y{!sGLSW$?)jsx66QH7mi z*;E{-dxNqu?I~|Ocq~uT!RSH>IdHmA;{6g%I`3f8KIDQ}+vO~DXoI;Km}o^`kcz$w zxad~mg%a3GEbWXq`Gpebp0IoPhsdnuWOBPY0@J{sL01I%G>{Jf>8I<0$#Kfa0+V<; zn(^ZgQw1+NwF*XnMK~T)jw>a^ToZ+`swz_rB@8O**j+2AYJeVB4@a7^ZRjEYG@?h~_fm9^72U>n(?2g&ZL9aLaK( zzA(U{QylaQ_rh<5n@EAiz4<(z#`VbEd`d{y^C_Fo(@FN$_f!(l9mig>NA^cdCDN6}MZJz&BW10a%q2a@Q1M-MY zdUlXr=9WhBA8}{0Z-D4W8;n^1Jp$n%V0iAD1U7jNP|-ixB+s2iMMtApBag`Jqh%&d z=0VJ}9wnmQTu$cx)EjgEuaS)eXQtlhvws0>?9m(W)r3d`>iujbv%=$PD(SNfMWncP=*q3`Tajp>y_Lhou!Pa)-iYD}Gx za(FeSSCwJ4%ky?Mrk-F}U5%-)iz)eoYD|M6E4GFFL;2hL(|cIyn1M~(^>bZz^Frdv1E1>31RA^*GZzN?6FA3Xd~g6T%aZGi^}9BKEX> zoayF7rsQWHXSyp9om8}4|AP&1!IWO%OydSCqNQIUr7x8jne3a!Tn@0Y#tN}Uoy3SW zR;V?!yz8aB?Gu@#ydSHQYfX8(LN_VaZY8WY#mW)_igic{@0()vj}&VtI0}mu>(S-q z?0qV4x0%wMN2?DIofp-{LicQ64m!T`qI@}bM0vx3MRNsWLnQaNXx?eQaEQj);M%p^ za8cC78P#o!jYQY0QCKYBR2Z$tnH-%=Be=kx5sx#qcQYkVM4f@-9}0n4Y{T4OzBiab z)A(eZDa%ZB1OcL%HaOi5Hxr^L%@oCE7iWr;%wQH3kCe>tQ%V>c3t@Uy5^OBanaF)-6uVQfUjQpBr3bVuD{Y>oRhYhs63QxJwITc<1eoa&C48V}svCvH zQ_mJxPrVXN(MmbC+G4DemY3bDt~VI;S;1C8OWshV>HM+77-p!QO;DMa zO2AIf6{4pI`m~_?0WA$=m8f$O0y9G4KJ>2rk=Gp*S5l^t>+HQtI_Zv*kTf-#WP>Dm z5G<19LrGE&a44AGhdvUxHsAnlu}>)Jq_z;@n)x`>QZ?OZyJzX+OkGPdC9ip$X*7Q9 ze4J@=Nj4|c?{+e~teefb?yC?%u7>5AHmZ#AcKK*x2Pm%nk27so!YsQBZvcO$5?-_G z!wiR`bm`3ZNy17`FdbLI0=uWh6HG^i(D4bTUzD)Lp4|Tlrk{l{>Dk#2Gd*mv-{Eve%Wq(|4vD4RH${BKz!d}*UonU$ zfDm6fB)-z~Ch?W;#8<)q!U6h8eC5TPnB0{+6PemSD;Z1yi&Wk3Qgw3y2k3&-{t6U8 zs;;?~>^oC+AKau>cMLyJb*FC9s_W3XNY(ug5hRB0sxrPchW_u(=&NAQEG1kp_Dlw$ zu;+=63b-zV*UIanLzu}g8iOHHYKZ8H6uA7&Ozxp5Vj5kuBYa&|V8#LCBl9gm3lZ<0 z23#5qi33*$;A9k-qq?$N6iw#J7JAkKN29 z`F^fxHQ!T6+x1DN-|+(qT*HsyPck`gK_mE^>Wr97jxQVL2J2u3^D1xBS3s^JA8pbAeF5>SON-NGcM9FHF`L-~7X&x&RJfA2mljj!6^YpEecDY4zqm)o)(6W5;Rrtq(in|Sil;6U>J#7_KHK4qvET1OeQ<0Xy zr1`=ov%Lw}80`vN&chv{2MmvbAFdju-3u$TvhGW<69#^j=;UxhRQq{8y0c{6my zNw4u5oz2Sq3EYc-9cSLgR4xDrv+Tuyf>x$ z^J_WukGnlGi7@bO0?Q=AAX0$98n9(%X2=SYqN@Upb_E31hP7{Jl5Uc|5y4r2k&eEV zlm5W%Oz!O{v5$@m$9Q0z&muMyW|(IZ(fXDESr^CvolWdr(n*_cM_siCO6DTJ3EwV= z576(4m`0uei_RqSpEd3ASfWhX&VnTU-~c5YP4p~ylIhazOr9lAGTHCIfQg=Sm~xyj z9Bv_C5;8^!XA@;oce@ZU4J=T??};)Ed;o;PYQW+S1ApDZ>Y(}jhgQUxB;9BlBWLLk z-x1kp8Y5@zbpfMWR5(*$sTjjB>JH4s!$Gm})FjlwVxXi_rYGStEz;8Br)6zAQT!d5X{YLm#girxi{2{3it~h`@cxya-b7^ zA!QzMCmdMp@`V&}>Yb53=8L39esB=J)Y~VNxhPj3+Mk4gXv>ggbbBF~=WkT6RWLV; z7V!5@(_5a6=5$ta+J&4j1Sq^On$cMiPq3-k#ylqp9{3NFsgB=;Ar-J+>gR$WS^*KD zKZSb^fG{4)hR@I&a@_UBf0*3ux=0~jlGD&{z?M#n1-JqLPO{#O`hW1h7&|jFX}lzE z{9h*bR!N+OE`F?}X$$NrV0|=BFwY_m3At@u@zqpjCe3lLebqsJVBPmckZ%fdpdhOP z8SsX~R?tryMaCp#?T_F#32rNL>IL33Ixd)d?_x^vr_uKUJ$e^h%RLDknL%$BO%kKUNOb^J-D_q!S=9Om|1c#k~g!KAnn zB|N5tNrvFO8+{=%udalthHx_oh0R5dI?NqcTF7|ZraTdK&`{AGa4bq?)2XBUa5v~C z)3RjI{$6f*b1b)r(^!Oe;?iZ(sne@BIVqvc-E1!VdR0c}nB%A( z-TrbMg)ro~%KEIw=3%NVYTy8=x8l3V$eF|lW2LFG8JPviY`=ah1Uw-5x)PR~2P7|n zP*|OX<&%mvw|L&*ckQN(Uzsv~C1s4i2W6E0_A4nuaUlHWiYZw8IjHnKkuLZvDLSEN z$g)E8t)!@akC^3@(Ao$P_J%`rP70rW51Wfp%T?!}*@HWF`M|d+#q?e`Ga33UP6I)0 zR>nV>T#eA+6~4}%=g`jYfm35j{)G}om`(fXApD&bf>yTW4Ow-vvuQD(P%DfzPdAOV zp{7@e*6CiqOtWdcd?mJ7kXbazhQaqUKp9wCHS_hh=w)H~9!OK#)izW$;?lXlf&=(y zvYf;u+{@&?0!)ae*f7I<0GQ|R(>j5kxnHM$pFDv2{R>uws@ESouo zYp?KYHaUlDBdpO8u+RqrK`Y=5;ya_Z`IZ%+H+c^mN7!skuvtvdMe@#~9a7o=pv>&N zP>)8IpLPoFrF)rNgQ_!i)tqeZETU_^hi_awR ztj0nYgma5ycho0pMg zg{c?1rTv1W97wU>2cu@ubEw6-0I)`ZRGHMj&sZll2flDw@qr*a0x8*1$1%m7lz!ih zmy5~{2*zABDjUisf47^RMDAQvZ*DId02K?6E<^)dAd&k8rA?Aga+ez7Bo?ZB0*Fx1 ztc6|?Mb_O1H*n7f&FHg&q0koK#W~*VP6T7NlXIGH1;48@(`LnYlnYI}x>L~$O`j{d zliX+eNs{3{(;>w_BiEVi_v>}0?=^kV>rAH=tJj(Cz8}iER##`bqGE!c!_}Em?q~8G ztIkvd9Io@#nG(H5?cS-k)nKCJdulK>Ms9Gkd%Q#yiRcwJ?`4!xcm{j$%wJoG!%Kf% zAE~%vgXt}(*trJNW^lNA)?gZ}4CB}{t_IWY`cuxLAO<5TBY;?Q_2#O5Y_sj5*C?KKKX#w>t2;mw3LSw ztEJ3>m>c@nNW0GSr+d?Tn3lbSKVj3dcbkD`H=}I_A7H}n?QR(+`afVA#}nEZp<|Y95C1G;3(Q3MzqZe=H}De zoNu(eY_z;AS}uPe(gR%;M7!LvRTr`fRix#31xz$-#pQC-dJ?rlU;8uaOM-0} z*!Cb(IV>j(co)DNS^Q9|5w|$0(t{DpwL+gqGPem(H!NftKFAuguN&@ww|J* z$nAq8BcoQ~Ablox46worIMLadinerbWA>=t_7Y$;K}y+EgEIR z-Nuf(1^|8uREH%qIP{C%9M`$dswmQbNnN|}Clk@R9~1PTx>;=Kh9 z`lM8((JnIeRk{r73SSo*r&z>~yzZccUrI5RE)(^PMSS~hPAcgx4Xjl6BF38`cRnVV zhyyKR&uvb+zfEcEoAeD^0op3}FrEWK_PID9mkL7YB8NjVRF4ti|J?vNwlq0&(vB>mw=vE zi|MqY>w7+`#T1WMWrG5n zVGmPs`8rHjg;i=N)6~v4cB!3Ocv#2vL>;D^mGHgY)1?j*9vm_iaF-H(Hx&>})e68v zM5bOJQ;svHUdN|0c~Gx&Q`u}jG*pJ~&E~_K$keGo$u{s z(YySq%~aaJIH|OOaj3M_sW_Da!?3v2z7E;~*y@8<1`Xm;hq( z5*8Z5146)t=Z8v|Z61)_rK(}aQ=WX@r}Q`DJa}UI;fL5f6L&0=VxO3<3W4UWF$%h)V{vDh8|aV8&ezLOu!sT;gO)|_aVwVm`%1Wv zzL4-5GV>Glg)&P4M3w0aWj=`HaEs-G7-x=jJ4}NYREij}y&t>VE!)xu ze86tHfFile z8G38bk45OMK|c+8QDs6FRN265w8))Al}<=|QB~;#RT9cExtkiv&wzSoIVQ>Z8AyJh z9GYr$W)bQeYOE1UTDGN)xM&7@W{L1DBu_18?zUz@_+t>>2Ex;7w8)K7XcPB0h7XHEVJOsH6$&FiAJWH` zrlB`4JKVA@ZRSyGsM*E?XNw00HSug&8Vo9ji+pvMda6=$*z;u_rita4Jm1w}nhWto zFux5Y?MASv( z9$UEP-ayN?^bw!78LPZyGJZ?AOO!ViWg^($3n-fLTat14^6ClZk%aaD1GGd^XP1v{ zWVn8)?=a97UhpW-x`T*nu=_dGy^k8fm4+aRd#GUinYr|}Jv7xpQ-F2dUzcfY77>zl zPpw`Tdz+2wGR=!pUr}mS4E6meb&gW~G1LQ5>g!6)i=m#6QkN_B`50<(M3@z=)+u!e z$B;cy@`p+u6-%xkCGS%5#8`5-D0!cfr^S-{Maf?)c}^^ON|bz3$qQr2%cA68mAoXD zyfaF^sN|Kg<{l84%2+~Kz< z`B^28jwP3<5V4x2DtSdLxqp;AQpvkw$*)Ao zla%~LEO~j9JX6VsW68Us0~g`K*#>#FF<$ z$(NM;S}gfglx+8zX7;+t-uakj??KGB!p=NBHz$x6wjQOAxuoe_PujWsfWV_fC+)tw zoJ8ev^TGh|R!=1)jt&IRVeOw8+5V)hygQC(-AP0imkSVC7$j>vrdt3Ob^zJ}@&~bN zYhl#v2{_Z|4-;AVj%^U&TtJAe!R*3WSu0KUrk<5`h{CBjE2C5#l*I~*8qh>OrS06>NW_daD}YIA$;A%Z z0@H{YbPoFoqW{VBh0Dy2p_HntX1AOr{_U5KF-h^PXz@JQMMhXM3Wj6JnLA+KTBck$FA1gf%wCrfSzbbvdNqy)IK3rH`_icJ#Qy<89iGTwCfg)m7|7Tk4-Tw63;RI9k87i?T+H z509;2Af2WOj@(c6g7-k?5P9kWF(KUP2I)IUx@zC5}M2`^BG^c#`)g26L9%t%_ANY7E3{LFDFX?5j z{pfdJFCq^jSLJ$4D>av$c2B2zOe2xjTaRfncwG2kb+dAOW*;$<2^HMr)&x-PmYy9l99H6|C{SZb6_v2QH(LIQFfsmIhxQ;dl7{8o=?N;M`A)n|GK zEG}1lrmo5{AfP`U>GnLL%sN@tt;+-?Z6YpFAgt+ev@vr;e1|m01Gvd%n%EDnvN8NX}Bzkn~ z<4mq0^_dD($=Pw9g8EEfqJVGLXZjXD*4JnH89#Q`XS)19^shjla3`bwcXp_MOd4K7 z{1Z&BgY}u-(yUI{Jy+^8-TDMf7@dfeP0jT6ksEd%SuxV5Jj}+ko+YAcTrQKST8WDe zy5|XwM2e_}$wwK$nRvR4sM=kZ9P|hf-e49{HB4O|2Z%dhMAfh*R1cZ{ZJv@f?coWU zX;aCxDKc&TL}ZOEyDxamyeowFfG`IF1@GlMc$Vk&icEu_VDc=h$Ta#1rsTC1nI?lP znq`&=Ui}&NB5;*p1+#laHo6kpC=;Pq!7dlU)@762HdxE4HtbKwI8Ro(3m$aRLdb}VL^+Kxm(R-04|)R?=?kvMv-lqwD$qW|5LG&mo1NVduTcAP z{od>jxxrpmut6VRnD$F_BV@nQ#%3b3QljQlia$4>*2q1))lV>`WLVi@FRc~C1|R~o zUJi}60SH;yneBWz7QHVFpTTAD)W#+?!J62!+pKgA^IAS^g~IeDpJ5gqeu$H>7(c`z zh#5Y}GaCI6Cs!!kJ7MOqMuUEfj3d|3 zvP`EHKC4^KzbpJ_oOz=1n!;ydKPbyeH#gr8@YltRszc(rTtpQb5)XwtKWUCoM#OJh z?x0x$;enV{@hLMMv>LFvt@cs;nBq=4_T*o#&%xx!$zv3ncE>UphfeX;ic$~D{R-MV+5SYv?+7*v|$os)%j#@h97~dQbpNw#_ z4}l`bQRzw;8!yLEm7;{_lrYK=aL1fn&IU{`Dq(Sa>U|B+B?xweV&5-4)Nio%N>Qc=^Ov^1^CN?7hN zI@F88qQm^^Ov$qwFug4)P|DWIvB_boy0gNY9a7crA_d~#?UocjHed=W$5w~2Q-6hb zIEwUT(l}%ctV9R7k7*$*9bCE$JbI<0bh48Y4phhW2)Ol1#}mm;DtQvwM7t<$Vj`s? z9pXkt%E@>xEs0LXBT>SIe@s+?4s%_T=#_XbofV*|(lip{YcQ2g%S%hkwrFbnJ0Cfy zWN}=tjLEvWH`q(=&>rC{4kP0a2TK>Y+KKjfM-4Vj@?EDi$+AH!Bq4ovn>f|t@@g?FjkK2)9^#ZMX|oi>l{AsEKoO&y5h>SEi;A7(OntDSG&0Cuqu9kx z;}aVdUhOnKv0dTKPVtEvD7Ga0ObJ7su2-F=xjbY!tqra=%iADc3ZluZ+Up8tTNj zEBenlKSp26%nLbxf}A6@^NADpa1uf_nB0lEa{ltE6P@D_vBbj-nQqq{_x?-CD$+L* ze_RLgaN>2QU@t?^-*{5w7D_8IO=UWYFFjI;=);m+I%s)AxqjML^66I{lrRtendztf zC7&+sq>^vKR9OM~s3bc3b$=tjofXXS`Mns->0AEb0pqY^@hNIJpJ;3XTy#6ch>yy+ zJShQgT(YJaq9#jt^HPA)^MYt93COG(5U8augu5BQkd;pR1ndsbZ(!U%>jgN^@9Wbd zH=p(kOK+e;*)+@5?Pezp0%V4~4G9?X)&h}W_1+Ct6+J$o6UJK1LEiaRPbnQ*Tkl6+)d~`8mP@wQ+69wKQ z;xL4+QNo4<8A7+@-Y|rIqV$akGK5x=gcw4ds(F0CwB#=mOjlG~;gboe12Ec2ht#*Ww(p@3B+sSXt$gPa^tlwlmR9Wf zBG~eZy%5FXnnG2@UQLME>#7}y?4YFLjsnJD>_0Y9-hnRJM`@#6vX9cgA=5KTALnvS zZ^)DpAbCLRcU$tVhD_t3{(*)}Gw|d4hD-}!KI}l|DCa@lf$XjDSLU|V+zsKA}B$e?kqA=7%ro^qu+ z8{w(Xn;J2ltj&~q7r^AwjhHS%i0jElOkXJXMVEoUQuva>CvJexD16yvrcQqn?|Q-{qSSefUD zS`tyq)WX<^k346pIVPZ?>sTYEYAR!PqUVmrOwH;rc}g{A>W=IlYs?geRJ6dSo-_A{ zj^q0)xKk%hx*##eozgU)8@5h+Devov;!a`Z5_hWEnCS(jznz%;Tw|serHmL~UsA%# z#MF9d9cGjsuh>;lEJ9CCQ|ww3B!3i&fN9%oNV{%-l&5ei|CN_ zP93vhYlb8ok^gHpdH(dr=onWrMr;x3MgN!R3iLgv7{%r#aOuJUa=r{;2|wPTUfnFraoS?8 zXO_(&I>GyFM#9I5oRSluFXf?tb1)L(r1Yf&iEAHnQes^u_uJ2zpx#G>=Wgt>%2l_I zRq-!PApfdDK<(~P!a-BJwd=BpN#0R75Wn=0avU*9(}aMeKPcgAlQbuibObnJcMJYn zDBW79Kqqh5Iml9GQ+WD;m%2>ZCns?^Q(BswgyOzj*Eq?PBupjdq3{<__&|UTCt=RD z0t|%)Kaqr?b2pHfBlK$K%khP+V1wS4KTJO)Vc`D(#0)FlpYQdB=@U6)1V2{L-pYTt(q|1q=Z-8p1w_( zDhXj|6Q=(tVVK+VRuiV0^_fa-hv~@mWfP_cl|066PI(?uc&s~SXs8r2Lr~1naJ30j z4dor@c9m_)^i;^)H~hA;t4mX+P71#NIF!fCBu)9pyJaZprBY=mfvRDpPj;(@H}-t= z6E96LHavhZ_2|N6DL@CLm9&QKUI@ve=yx$^XJ94(gRA*NiXFYhNiV=Qo3Czv<~QPuZmWuMxg2bh(W+wD zCwrrQWUqErG4uiC-iDk40s2HT-vEIw49xpAr}f2RtpAnTX>@HR(VetS-1sYB0v+#j2G7cFzy~MCNtjraFH4Bj`~b8G119&Ru=!$R zP;q>*Q3j|vK=ts_;NtjVqk@@2!8FUYxYuG$v-a;zvq5W^m>TX@`taDe z_PApo4CLl!hv*j`rk&Qj;+zu7qIZff*x;Zi8_36Y#1{Wc&QR z6wwC-+K&PS=nV<)&*E}SFE?OHU5SfscTWFG?l_of45@3-h2~Hv<)y%yIUiGUyyni8Ix)!jxf!l;L=^3@fAzR~m7V%2+Ab+Z*G{5T8FIH@_W%F3G_E zu8{5n$s5AM6oP$Uuyuj;Wn|04=k%fA`T%EyG^!`O-wXcj#!T+baDtow?cr_99rP|x ziM=tI2+&@}Ea3~F8Uq#X8=%iPW2S??fyC%+{WFn_rIVE|xiB4`C2?sLYtQ`A{%r+qWkLKr1PtVKA%SP)t&%@PMN7%$yN7&%2U79eJ z%gGCS!?{7RJ&mxT1!e({7cXP%xO_YR#o3QyL zX{V|?%@*^W-M-#t$G`BCB$+6EqrB5?63b*Xisr({dMA{yC|1b%zXF&QP64snAN}_|Qo!p{W=x3kRn0}Z3>~EK z>vj*8p$V85yS6{YG(rhW?4GkvG2PUZ$#d~3rvEl&O13v+N)}dh1yhuDrCpY*4-4U@ zW=yk`u*&Xf*^H?&WOQoAR1+Mrepb{xp>S@1F7P><=}R{l+iVcqG-?`&pua(E(F(BG zYJ=FSXH(4F!rq(!ZIq&ifQZ^_r!YO=R2Sv*Rl&__jU@_yXsq!P^pujktAwqlB;%Vh zrJ^KcjI~k8JB_iXp`b9tRqN^FNhv&}9C7L=^WiZBR4EvL|-j z&Ps1(Vn<-YkN6QG`hz#vdDedkR~y`keGqHV8}OFYbFvYC%;(({St%V+xePcfDA0ZQ%bAaqpajHe>=_W3fzlYE0(FE`I0wt}>fUmxk9x1KV`e&qw{W_ZsU)QfvyA$pmhHw$_i(3xqpigBlS_ft&nH-Sp` zhiSFk9!76P}!^Nx8tICLa%1fsG$0E15COtXmp zPLLS~rQ~L2(saq;PZTMQ%v&AYoGC@+(`=ER2$Y}Z2=gt?*~GYhM9VtcCQp!hnj>&i zX(rg=ZwiSob{n+~d((S`=qfMKDp_LVl(5&AO>1q~41E$gJx3qeu;rQ7Jn|j)GFyuc z_>Q|uUvp{fU18qb9Gjn5*Vj>1mfB+W2pab_tN$C;^(~e69dph6Y30JY-c_+IQx~PL zu#pSfm{|(HYa>_t=1hKtKeBlO&6&Q9Dv_^*t>%9FKou-IUj3Rgja2$JTk2d%gzeW! z>KJDq*IV#2m210EuRx_hy}vq6+kn>}qHFxU!}M*R+B5{or{V>tn&V=s@q$mq3*uT} z^v|NB67H`UAg_;(Ntloa0QK~B5lq#7;JapBLk|8b^w`2HLhJ8OM6p&hp97yLPnF3&_&Kz-MJ(4q(zW%nh~+dead&b#P$Ior!4o~e;h4{&qjp>c z3JbbUA$mp}&=UpSsSv$f&~FR+xkB`5LGJ|G^+t21aavPH>@Hkqo7&$@L7$P^Y;*gY zrQGZAb=Q&ROm8UrMZ2tH-cg2C@N>}LQ~D)C-yTcHKE-EB9}#E9*ux4>it}Lm;k@Xz zt1{DZB`lAVYdPooQ_+Z%mzDlboLtMfCJAvZC+>OUlWXGSTFzaq^;*u&&l?FvujSmO zSiP20t+kXCH#j{i=U|)(30YC$8AhSnns7Z6xezR|iPC2ph3Y|)DAYy?3yebDBMNm_ z2}Kq1DOMGF0b+~-`6}nMQDB%dMim&R^fN|*5lATtOjW`~qrkF=0t-|^Q3c*stSWE- zVvGXoRnAenRLKryj4JTC(!aG!l^jD#QQ(LYezr@MP#dk1pHxCo1;UmGjeYMZq|N1N_79Sn_)$L7K_ zZ7^g;Pa5Y7vKNriQ~ZT;6MYzP=$MzsqhFp2Fgm+@N0Pk>T=W!wlO+29I4saV6_&#Q zGa`rodj$78aDf2rl}qr{Rxg_J3#L8Ik0&`_zggje_?YwcTfvQujnXfe{$rXh<;zFm z*z@)3iY;=!UcN1w(#dCu8brx*zTOxF)BUv2%-6?DZXI4Q{gZS*2wK@)>D%JvyxsSL zIjNEJc3c(+D*e-V^FqNeh4;ow$gQ@>N#_3Jl<;NjQz}{M9lX7)I@#IAXF@d8<};ph zG@i?5BNoti@iK>$`QlWS@ojv}d~x;*kpVw?o$js{Obe9vyZGd~EttBBA~h0GM)LHJ4u%MIb}DB+|MRv5x25J<*{ z-!zY_@g};^RfPvSQgP=*r>{u^j86`Bh*54GU~CGbxCXaix<~0RIZ_v4K9{;u{{0yL zn%Pwus7zHorn0d4l%u>e z95S#v+M#h_Ef7}1JO{bHY{4{G;rWi_OD&j62qUU{v=ZKRNOk|Wod$fGJfKK*mzD&m z?s-bk>V7mzSf&K6?k7PoCH_G3*l0=syg)<0yfwDOD8(M7Z#Jc9h?LSS4l3az;{o5P z5#RxZ^@qv`VJ%3*ZNrM2mU_Tp`r-pp7p>bdnQ86;2fEX=c9He#0jWMp2?gk!0}JFy z?X-IRR)#!U^{1d;13Exg95}hz(GIt%Wi>IvnN`tAR5p)hIs;XlMDCyA1sPP}#M$*e zl*Da+5$8U%*@+Ckg(U1rTfyGmu&=j8o1JL3r+`O1-H)9=Rd&)J0E4u{S--NAZ0(s+ zveL1YM+co)qZb1vjSh*(JKLkyu~dHKMblyqI%GpDxxH?BRC&= z80*I}#}v@g*Q6Mb*UxOA%#D7dLC}YG_pNYa;;WOlf}AEwPN~3IX-oIo@>Rl z5SsMIjFenMTQOZ$@+eT*4n`2?}U?6;unLgz+80 zekRxs!1`!o!tH|`^c4h#fx)imR$===Go?)l^(#B+ynvaRv^nA8Fb7=&nC1;x^r3LO zIxvPCgyfy5mK~fBW5BHR`nWkqBW-IFjNvA_bYEnm3r4xM17nxhi|4f`x|Ys#&|@9Y zS_3rAg>j%Apg@48yU=xH0?I5h+{|>L`S%0C=l6xN`jj>4Y`G6S6?h5eE690*{3VK{ z`K}XHom8(QlY1uYkVc2(C9jn(mUx&u}3eZ^e|Tgd;AGt2NW4j!eln zwPso_9BAYZE61m%kyjoR)5y_p*Hrp77YW=*;Yo?+tEpBBFE{Bs#iTEEQMGUj8f6=% zzPTt-O}r=(CSKnWo*A7=FOn}szU&z3!wqcq&}AgIGU#pLIo;9J#5;+o31=rJciJFC zYN2)V2H72*U?i-MUsO$3Buc?w8f50TW_>(1R&O!gWMyBUD5k5~Nln*D1j00LDaVn- zn4+Q#CQ8g`+isn`T}m|jvzJ7pj83{gYrs5!k^NJ;Bu}jk2Toe$C9%$Qz@cz2&>ySn zq&GV;rKHg{k@r3TKV28#6Bu9>4B(?d@&wgUU^W6H8*oG92C<_vllv1Od(wy`G_!kv zISfpIMoYpvz=Z?! zUedEAopc6@(_m~_r)67V+9Eu&pFtmVC(z!Ej36Bp?1pEUPyjk9;DKkD+)sc9Wj1ek z9RrJ$`GRP38gR7ChUX%Llk48rO!Wqvwzw?G^JHtLxM!J?v4v3VS%lWB$n^AJv!i&_ zJa2savoM$JwRO>?tCHmT+513|-MI`UY)F#bIgb$Ves_)%4kdZ;es?`&hWERB3dgV2 znEETn^&}78YtIt`cKn7a;Zq}Xpb%QLW*V!6kCQxs)=V>=W%BfC&9nl-2e)S04c4NS zTQApNIN2F%!^KuCmq)v$ibuOK2po78eoqSh2q^0Iqvdt8%fN}-k9NZelDeQ%FvHbo zHyr<-E==wPs2?9K7O9T_vk92q-fVrUtZtWxm+p1LOS=H}W%Qv#lB5Sf)bCLNLy%B> zi0NH2=q*7G0FN}7C2l-WFuV(DiCp7bGnE=*YWT3*v%WRcgf2{;k6JU$?821%Rcoe& zU6@=ywPva~gy>Ns^vjMmOv}MpybaTO{CKbp(^hcev-sLWj8m+?T^{573=D^#Wco_; z?NE)WiROFA?de^O=?Cx(sK#_2LQth`G`rO<&zMq7#k->G8(xj+nL@(b)tLU%mC3WZ z8dIsROvxWqV|oOvv31(Rn_pc zrgAdkD`Er?m=P%a8ema~u82l!fj}YXij?9Y;HZY0>Jrq>5ib~23@=*PyD_=*P*yqV zpqa&R^x^KtloIe}Sxqwf&`f#G?v4nmxi{2{W)_1BJ=hJ^QN9h+94+vmVxD$wn96ly z@;uvysYN%YWNa06z>oYkOx|wlNpB1xs(@oyaim^I0Y}QikK5ZY4Zsh0)Ci=2N3AI8 zQIo&`kD3JrJl*)dGOWH`Uddbt^)OEw(uV0H)o@HP5B8;&A@S>NnD!zuJbRaV)KHfw zbO;Pk=r|aV{Xvzk4*iRfE^mB%tHP%g^W0gJ=?{=xpR{2*ql9V2Jg2cA*Bu6fWB;jy znZ?AhT|)Sy4HFMFO>t&1Psz4Ssoj}8ceZ7!j31@iGS$Y9ifx&m>dus0r!CXd-L)US zcc^J{mrOtGg;0_nFh!-lT}*nwY{) zGp+ctwBoOUh_vF%!t-MUC9U{!F|^{}V21K-nBG#Ie>08vx>p;qD~(Mfz8OEzi0?|% zM!b4xY$Gn6#w2Z~Jbs`NSH%xB;@U`oR>p*eyA* zH%Vl-KSYyk@90_R@Z#*gX_#!rj4F=h*oibI_kVy%r_sgnrOt&k9l*vH$1KB<&g3oy z1{okG7Dpp1na(mV)QN3zw95ac8{ZJ8b!Mg&WM>%UAj6ds=Fx&B|K)}pv;T3e?2O8LX)+1{3^Geqre z%ajgLIM#bg8CE2ct8_)CHVUsw^x)!pwut&4CXmyQdVSNgEt+hb=`e;EY!^cew!;v4 zQe-s@hSLOK!a-kFmK7u#Y)71-8K|BxZgsa0`?7ugy!gJL zQLZA2VEi5a;%Am=T*Qek@NQDN$U~Z76NYWqR4xQi* z0eZ_04?35QelrcI9+o#C+3d8J8RVd=5a5*)@HS9^c8Q3D3=CYbUY=TSh&}d8{^u1t zvb~1*_QRN<> zBcj?^IbNS1*B;&x;$aYxcOhY;5Ke$#ybCHs?n8VU2M<46NGx(6VvkU-WH7mZg2!5Y z!i^(NJ3+)cEnJoN1~bHwX9(_Q3m%KEN7FLv{pZQM(y?>Vn%*++A=j$cyF8w zN09AbGLHAYIfAT$d<U$9|rFx|=kBW85{QAwum>qicEzZMZgK|!aby&iol zH9i(-e7q9VA<{V^y$({u`)HCJ5w8O-Jueuv{9!EK(M8hB@$jrIAmwIfMA0(@ebmC* z=Ose8*;(;W{W2`%9s-m!wKw9?Cpt3WN~r3YYTv_g(ns8#sa5@uaA2^RryR$%D&J7} zWSnuXcNG3j;rA4t6mRGs-2m^7f#FK~6@FRyzft&gg@3pKJ{tog{iPdV`*713kREim z!f(Wrgz30V;kV+G5vHS3riSTA9{$%b9S;wW9CfBXU)oG8{-?9yU>aS46{M-Q^)};h z@5YP6y&DgQtCfk~FhED*u^rkNP)^?__33Cly2aL+Ozz6Vi4a223emSxk6vI~0PD-3 z?}RacgaJAckMVCX>dP2BTMM`@o?LA!FohKUF5c6t0@GL&_>J;R!-f;hA)*Dg|@GwE_bnb8GQJFq}Mw>jpgotcZPfI<`wne8C=S->3NvYq;WGWX3 z5SHi8zQ1SW?1x!wFc8uS!}*0-!t4?ec0|MxeD>M!g3YD(~jw+CcGA(`ZNwBJzd)|r6D=m*y-VB zpL-(oz`J3;EBh;s)YgddB(H}Bv-I__7x3e`T1+D#0I!E7jG&m;!(Ij&uZP{PX!Cm5 zY!s8oG0$?WjX|kYgc6^uM&1SQ-U8 zsRxrh>pNa06urSTO|g1|sYVZJsQAc12eKhaA#I4>{0A8~4C&Ro}2B zy)OFvLk{$ut$Re>?U=Y*=N>n@n+qe_LM_4(hg_=3gHmGPB}(|pVHj6NVPPB)WqeNw zUpq{~%?f{`3AaaKNjO{*!t8sLaLkzfkeX*bd<$3SzE%2hWA+J>5N7{D2`7x%-;9|3 zf=VcA_UnpOvwsXR|EqVzYQppqZSlF&4BK?I{jbLMzl!bm!uBTC!mke7Oa~nTB6@#{ zew9Xh4C>*Cr`Sm2%ID3kg;P*O2)8QXH%BUNJ0dv7qfB=x_JU#mJ<9%|68AL8BPZWy@XarlqccUA*JtOh?GJJHf6Ry|T z7j?ZsPV{uGeW+4Yc}-P)kW*S(5fZG9=GmP#M&l&80PN<#R;vq~7^lqNVp2=Ik; zCA{R6COFlnO)#LMi~2%e#i}nXgBX&WhpU{iCg=Br^6y5)G#k_2Cb*33_h!>9+Y2n# zUFwt;y3~ml`k9ZZd={3Iyj1K%<#T*~jo2H-xhKGiek&gmX{9h8^)b2U0w2n^tU!HV zC>%+)P8fa^EoJtwSoL4-H1KkT*Tsfm{M)fHX0sqVxqsEROdb9!Q0_v?*i+rmV+Mw`v3V(b9 zyj$U8PSdY_p>RR$asS_1G_t=i+jd>2RX-<)L;RcohiKa~GK2V8G}IY{0;W8tgp!8x z#5jw=J`b$s(JVLL3f9fd^JmZ~7tB`xVve^@#8IXR_HAG_3C$7IZlHpB{(Ns=nk$$K zuoPyZhqa`0UH_|J(bqlZ*siO)Y;s9KH@Q$y>V?nSLK2}yzB&WBpMzYzFt3cPvQg3E z^k0Y;H}_(4e+Q?Ja?m~(CY%rSLN|gz>x?S6-Ic0wWuW#2#eU*4Bi7X@ECWxfBp+Iq zDQ=YMi1%qoy(ESAD|~YdjO_lS@K;9AVK-_;eE4?~Y`@TW&*8N^URuOfs8~ z8uQa14XTPmWHn&3nt$rr(y3ZF}qZ<`acndIB%o3i&q<1<<-pT-OLFtQXS2dGjblZ=w1 z6`qhJN>+|2SraM#hfPC>@6)HKk|8Rv?N%*k8ZBpvmW{Fz`oP-hrqh7O|jeAV(*)q0u21xD)^BU(>EibAbpx8CaI=H&Row7~X$7j1d$^jkXIi5gtxj^Kw`clbl!-;P!!8#Fw=^K+|uCWgVlexxmC2UTTxyI>iook#?2}S1`e=1hz z8qOS10BaY=Xw&~}O)6rrR^D{0ZMwI&<;UjqLfd#d&#FdvOP!1E=r3YyGv1uTR4!^V zS|slk-3?^6H&ocGDxpjc24NXb}eZaMlve~HmVD9{Qv9rW!Auj5EJj!yF)#ja=RG<(8s zuJP@eN-5zeOMu*R3QpDWy-Fc$#Me^9i6Y_~EA|H?J|yCiZ)+ud9APhX=CY}wNWEx0 zA~~2@WZR-ublk?}LPW=H4^(r~fE=c>nRLo_akB%V>Dx1PR@rB4sk?B6)AL1prpd_q z-!ch!gT3f=TfT9F`S>u8XuchJtcFdmlw#_kk_+q->MVD(*$A43p%QoAdn^47d+J~B zy#HI~`FIBT4cmA&xvw<2uaw+B%3*T9Fq-tG;}7lfbpwe)qcp=+MxlwZ3hge-G)?Ji z?Ws0-#6zADo}<{0?4FA$OrN88_OeV1M^o%G!bikpa@Bi@rr2segj}Qf6us*G*NO{e zTULM;+qT4+a_u$c+AHPy9<2vE`B+EqmE)J+;MbB!4winB9s4`|I3mxs0%1BOh_ZgB zvc3$<8+{dorptCIrKAV)8tO|S^qSzBAT`Eu*_>_XXL4^EZI0K2VLC4YyZLcUbPVV) zjf+Ej4+147KC4Cl%$WF3ExOb>J`TCA^q0&-%8oI{Ctw;EHjj#pG3^lB@ZYp&x=Z2T z?4HZ*nO^WSdF)R!jqo$27RN1G^Zi6A<@@vRd`TpiQ;y4aqiGd|uNqBjsU}fP8!LUN z(X_SFq3OT*W_t8gr=i!EZDr7#wz{S|SH?+0Tp5Q(^0J?e8l5$pRdLctyqZl^jUGy0 zW7No1`u|cRnCGXrYzwpx*l4oeC|SSeXDT1GvMp~2kI8M6M~0}p`W9~#M<93aF+?dr zIwa&}@Qdi~!6B*i_o9_PT6NrH$}mZFjFw@#(zlp0%vE~PGNfgN(#-%vOQKDCWW3C1 zM#kf%oR8s-i$tx6_7mc9V7(98L4Ron6J#6zYtgPl6{h8y`;>Uk=qgMnL5QuvfHwHlTP~8k7Ewm(jt8E*2h8DfeQ7=&CAY^{TP}b zk6X+oa+%6y;-~`{Oo~hAMrM%nMQJJ#EWpmZO-Df!;%1qjKw z=k=y!e1A{iwB&D!x@AL9CaOyy==0;kckgzxkG#ybTYc}ncrF*ozHS!Ldy@Kj*e8N+ z;Ki&$DWgFV%Vhus)6LA)ar9%Y%FXd+-MmNP597@+d?*UbFnnCW$T0kKA#Bbu&M9J- zImdVt`Jk7=ErgatMZ%4{#zwlTzdgr*y<}&h(2H97En05d;WYJd#5mFsG5Kl~GTldX zL`=Q~V94?Y(|f??bVQ844_KKS9}$~3!tgshCdcPDxL*VpA7FCd zJQg>_y&;P(h~(P?SO-AmkYXx;tqs`Z((q+T+XBcDKx)m8lifEjQscr^h$cA>EO1ab zfDz#pFs9K=$A=3X^de9??9Lf$`t)=0p5mpMCIvK-z(Td?G!sc+rX+FQ{WQ}OO>sWn zgAX+dKylT6n&~|yT!{CiJA+O0kfUV>rdiv*3DC~nWec9P{vVB>8D}&y#&C(8Njf2be zAzCA652-<{HvyqPeKHv7lMR(zJvuPuY3_R*p5Yys8U&eA$D40!Kz>olpBZw?8_1)S z{DmQRx`8}R$@>jC{RZ+vB_A;4oEyk1m3+vM^9#v^Rw$bqkoWQMiJeD|7b7dTdR~8q zF9VT7R@k;XOt*B+!R2y@t~o9|>7+rhPE0>^O{_E(*c_kVmy;))820804(s-70xyeN z(xlOiLj_WNG|`Ep^zVX9DSc?3v&@)L|wQ6VcJq4;2i)he};mM0ET@1 zELtaIpTPL~oZb^eID|lz;|SuXi1?R6=#>rw)sx?eZhPhk3x+3;QgCUPf@$Bvda z%L=u!f{pXCEOeo?-HD=pfUJ5bu1;ERZ;N(FmfIn%B2q@Eb*=2&eA*+_FGRA-s>sw} zylM2~obu}AHz6j^;EGH?fWcI48l+Z;9uR9H6rozh%qdknS2zlfx zVTV&h7Z<{`DolNpu*d0HQ-$e2AjCRLWY#4&0QEV?m9{Oqwwi;NhKS}Q+*#d8rNW4z zhj;vd@8_U$K!o~+u<``1;F5!?h9hlij(n9yt{oki2B{Wv6Ou1lvvWOSJkfnb zw2Dd2N+&7&F3VYI)3BbEwiUHS)N~c~ni2JEMAUpu8WFWbVHIUXMEOP3znCB^n43?l zY&oWZEH)NcEEdQ|fn^}2#ZtO~Ku8qA2xcTOQJX9ln@oz@#H1pO(%4+aXum`R%?vZS zSHhVf9{9HfUjS`gwvJ5iX}RA{@cgGE(-I*(*pX?A5|$*8ge%{r@X`d&cTJgAgYjRA ziwg*}+O|x)%hjg5tEIdfP#Bqv5eWSpv@Kec)d`rTYC~V0fHq9HY7@#0(wc-C)t$6A zjJ2ptYwBcV1nE5~(P3b5mtb>3YF`I^4=~5)rw>KmX@I6-etc*sYrG!vtg6yA=M& zm^N85nB9@-K_zTUaA8xuvVzN9`0eeznaYOJ>6e7l z)tz`Xvm?_{RsI)K^(PgKRQ=f~ELDGBZ<)N*?acJ2QhrNt4eZRs6OE6q(W9TlC@g!3 zWhLRiHP4`B(OTP|x*ol3N_JUF_E>K%*<~qN6Mz`Sh%QUHx&sj@szK%VW-@d9LGp3! z1TFfNn4;gQDqb-~zfZwP(LGUEiat|HAXDi_l`_aBK3o8b*yM2~Tr)Olq+rA*t)j5l zwxm&#F(~@S?~yD9Bm{V5ebVsGfh;& z7?oP`V=19F=g;1fo8N1pih+eydNok>#g%Z8cdo#izJrqi2c>W9C(eMPK#0 zW;fL+?8sMzS7~hvvce5=0^z>2&i1yp zj`>Ex`J&)G`RYUSUFgrt^+g4V7=OB;BB1MkHS=YO|CVJYn&W!gF3oY3=D137%oOox zfgz$*l4Gu*G{;qv%;3F2007JfED!RW<>ZFLQti0M12coEk`#r=^eQaVKVViQ{tB$2D-GfaL>b2-ry ze1_>~NE1T75`Hp--$5wiUI@L?Hr?yTgx2zu59Ebgg@g2g4X>MzA)*~DO?wC9Bi-v_ zRv@uVNa%xh6#IQJxcfQizCO5zZh1546Yf*PNo4`!A&k#>NJS@A>%;%5LzX2vY_QF+ zvFPxN(cu@-p>rRm@&V#sLeh6aI|IGZGtl5J&%={;c& z^hLAqW%Y<42KOymk2yGT^hon<8}RxY<_6`m32nBWvGJ^mMAV(j^&sm0{UryDg*I|~ zsXM;PBD^T$Mej7LFO&OSm?@M_Y3FA;=q+Hr7YtcoUWbgy)&Ci$q?gV4a$4$)XQXNM ztig1%BC|ZN)L_~LZrA*0nC?-0x@Y4vOh*L&Pz|OO#rr&+YcPE;_^+Q~dQ|a#kMmik z-}~ZiglCzq^<|TJW2HAVU~2^%6G`&U3WrjWoRX2YWIs)tu0&0ntzhG#4(Uw~`+8eq zMh`wopbuq}2ezEYT+T~0kNe#0Bu_tlPoE92+s#g@*bg`QUxrn~*!)-|A+4VRb`-F3 zKXAF62e<1dW8isbebsZm!f%>aJ-hT{^1M@qDHBO#SX`_e%k@loxx(+nI!2~E z#v+&Iy5-PEw!>_k;Uvp5Lf1vNTLbo0?bao!gtmwTUO+OAUnU{Q0Af}dh-gX;TI z5rb_~e^VtP{NR`phSE(I zA47<2PsUF+t~c41icMZ}VQR%6jMcQ4;vD(B8E)|tSB=;)H zDSeGi?p0J#Q14aLQqbJ1K#q+S^Wop-D4UeZ@t)1>O=^xs#ijm~imY}D6IM}yN z`g3`{phX|shHFLcGw)gKvz@Bpq=Vv#MEfLu?|1zogYG_?>t=L018_fopG|t{!n%-5 za{U%@_X(R`zd9iuE8+6~Nn6Rq4*I!Y%wE0_uL&l%d2Z0cr6&4A{UInT{#pvMu@fXPKr)bU3AWybaT{4AbulZ#AvCAfm%kNFvuLbZpA9-tNNX z0Ou4lSA9EjIUuIO%BJSvn_6^Ap}Zh^s6d#u+16=CzsDTp?~ypg>-yt{VSv7n7@Z%A zIkV}A-PB6SDduk5H)8DFLZxri*zS8lMNZg0`Pq&E;s6wuN~k!+w7?_wm`XSnF+g3V zMk=9+!deNZA_n*!@`@Bqyir^}!(&{Nw%bMI_X+}e4m031A@IR zAmK3y&quuePl3gEFH7kkcpe)>G%yb1*h2!~HZe*!JPxI6Ahz>V=iWmmAJt(fe88V7{MEHpVxHKmgjH0sa3W}gkyIr zF|ss+5pg%qa?oebqZxRCj(%MKmgIi~!UMEKfRg}B6pgpz5JltM^Gxo~k*tR|)X?Xz z4e;`xQY~(Kci)4X2=TD3K5g=0+ai}SMcppPJmX%?JaOn=@+2E6F>&& zBVns5s1648g`nyJMdZ!}(kSw?DAN1|rW9|QFC3y%g6M=iJ^-7Kevz~mFbPv-`}~Yx zh67U*7-P<}V$OLc4{|>ZijQW?LE#2q-GhNLhT9Sk!yOXz<_J1OL*=;edqM9hL@yHb zuRu$fgey~xCy$7eFbei*F|8k0^%JKV`q((RC3okv8*a%xp!9JjuJC0MT($~RsuCuc zxWc6e=qy zDd9@IgeQ7Y2y35ZnyQ4waU|gu7AX97oWx9dd4PsnSTcY~qAaY1(m%CkS}`p)%EE_W zL}Zdr@B_gW4uTQE6*j5;G`PZXFd(?X87M&}R>=;{c4?dku}Ut1>a%^tlpZy{AML!X*4h3Fk`?xn8f#^x$;k0YAo(Yj+o>hZO!f&U3B{ zlO-uHcVViegi~>z;(6V{k+0z7c@fBz9}4xvJd?AXG^$ zRqTv-&*x7vEq#$mOu7y{lDK10j(tMt)RoDrgoW{*eqEW4!U}`CGF`%t@m-nj7|4`7 zt1Ht(__3fXQ}cmLF1%aQSM^<*WHK49@ViN(M#q7wM*2W;RRph`pxM0>Z`#&$g}1~@ z+nT3kl##Vjb*49!ek|VeTy>@akmOp`m1(6Ceu_{1MhsXxg=w=UpKebLryx)Bk=Y*Q z{n1>xnJ7Zga1JVAq(d6cEFmD<@02jjA=xex!s_Zwzbavd!*i@U(>fvi(3R<;5?*$A zN_1n|ErdqZnQSwRZ)`Q9P6`2;Cn;fx$^4QK&}wg0!Ul)QcRBxV)JRvLo>{2{REcsG(t)e^(tx7O-8*ophz9oP{QnZQ-=)|o?~1o8+o90 z=1U5+-3a_g0e$Z_CH!Xk-albN zOy%}#W<~qnZxpM2?`?zOTvFCwRL+Qa(`j5*_zTnJ-VaXcsa!LSAMA@G>1pp!c#p!R z72X(U1U;tkM@G;SA_!ez9VP74F0iS>AIJTVc@pM@W)}cc6zl@X<*dhOhiw7#th;be z#m%Kdbi$1ZP(r)GOr?9!&u(l9l+1tv(fd`gOOMDr>>uP;q@8~+KU_rq(ElypJVmw3 zHcHnvr{kgR*?7@*@?gEf*ir5ATWmz`ZZrR~^usbMQ+fZ0mt|Ita^1Mh(&=>23giaE zopW>2@D=xN+x2)e>%?cKA)-SLDDwJXTpyR&%~uZ0yVd|+9>Oywurl(}!?M$&&xd?sdpph;e}a?C zVGSbTIEW_5B?%(B9?(usS>e*$f$tFOrCok>G)ob1a4vYM9p z6{n1mb!Wx&cj&~LD1EB=q_g@^3<&7NS}9?=`J}V`P&NbJGb*8ICzhdD?Zo^LW44Nd zDrdISzylSYqwq+DS1CM6;dM?^fSC$!GzEA=Gm3tn{EpH$nF91h&QgH)l<=V`z?4V< zwyT7q1^7&{T7cyc6XPXW=DQHow-uyMZ4hn|J1E^-R>GZR3A4H`Jwu!%e$f8r?5(E#kq7sY){vu+I zL2nnn$2qITKj@TwqdyhYeWU9NntdZ2_&Z)TjVbbBW-*0-c9OYBpztXt$%h(uDSW}{ z!PkGkAmi9RJS!bLGJ4BqkF6Qcs!c@WnakzlKZNP}17*%n%gd}t6FFyxgW`w9?jPc- zP9r0b7jBu>llIE)H^j^`VrGe$TZXZQ+M6Xu0}l>kN|9*IRtCMw=-w&mCPdMwS%zbGev=!d^GoUzhB=ycF4DxGseylhHsM?3WF(ek4=%@wn;s_Z9_dwgnsp)pD4XlWEDV`d>G&KRU+uI+xE8=oaW6IKi(%J zF&Ot7;rm7SI}qMBD*TWL-v;4@nr2yHgeC7w`)wE1<}>WN3pm4$l2J+;&6HnzUt)51 zd6lSah+da%CUQ@Kcpq)FW2fZy;Y{xJ!1%5FG} zlhG1C5GSMaa3+b9@%^i@aWX8R5hvrkqD`EPfQrV=)hjA`u{~yM*gpGj=LdYC-fw@L`U8&ZWzQXoY=FEhi;RM53tI1@g2m57=55U;E6?hzk9g zRK#2u5jl6zoIf`?zX2gdq-5r0XVbxmkRwLO5fSnZgcNjR@~V)p?4BLnnBFTYBpmFU zWf?h#Z2gTg-$(rFdyxdcIw8M4f=swaFO_-19urJuH+& z3UpRTH;=&fIGvLadu2w%cBKLHHZ5WVphr#1%M8&G+pn6zbvuj4ja9`4#>IH=Wc8&O zkNdnE(@fR;C=b#!<1C5IVOes0JL5t=J z&EThC&U)@Q_Vl7s3^>S<((L3u55p$@Fb)4aZLrSrBpvF{WU}XGnNi{JjXQr zxv`6?BBj*!_I1<+ro=IhL0*xkvpS4ma+jDxnBz~Ch+|p6+ye||t29kEnO}eh#Mm)t zrRN4Sgy9?8`*yP^D~RKAy?YQ9#Fg*kpvfb!sU3k9!=HN4f;g6!l}Y~r4Kf1 zsvTOs=$h(trE8p@E|MMWc0>uo%$lli#BM*SgraskuUNI)6o~n|hNCVnx83NNZJf*a zIac32u&_o0nmd}QoH{8@aKcO904ktC+I4Y$5;%VZ`g3%o|1e8;O7TBR(_{^mI0iiv z2E+`SE||N4F)Qj_vZC%X#vCKhaGC>4evK%_>!Uf&PZOLp8tgs1G}noM@vi_9^{^VF z`OdYkI%pk`K3TXGI1dhS&=w#N)ym4C1%kP0ER(w#1p8=-e7F$?X0Bk6vb|sV-*!IR z*Fob!{SDNfv_djj3{0hY(l@ShVwY$=Fg=0E?M18QnX0X0Q50J3l%s6-a1f=_2+3tR8c(nrGIEkrUSS3hN2$iLrXjeMY2;%YCQ)iQa?R_!g!gU?F|VQhs4i~i}?3xsd=o4&Y55Tl&4ND zzPCMR8rgX#m+nD-NWKaE#xa#vzxYG)EjbRTG9fze#9aT*c>oLbxF~AezZ^zy(w?c6 z8sUQ3bm%bO_=#>hbX8C`9sWtd=3alEH=9n_o;C`OPY}zDPe3oQ1!hnMX}o-6@d;4j zz5&23XF2H0s7=Nvpf!+dTX!Z)6`PRY`Kde8QK|RnL?k-chAg?k7NV0f%|y*iGx<-G z{C`Ad_-ch{n&f^S2vI14xdKcG5OQ~n$Cu4y9>^*Q!0V$K37;l7>A!$_cxh%rC5Mwz z$1}OJ=c9|FSt9x=U|s;mN3RN|(|GY#OlGEQd1fTYWF{RHAEv~_g9nVzWZ&?{4hnL9>S93stYEH{Q5UjTiRhKUN)IZ?)q>m=MK<#xc+Gafeu+X!X}4%; z-!Y~A5rls#ZF8JTn9CjXqwR>EN{n!cdyR0xz0RXBayM~=3$;fnfdGwmA!4e10%P_$ zUGR5LU~=z55q6EIZc;jVqE(rA~b#S=`mLHH+?pb`CKo9#BX-)3~#Cb~44 z5Yq{4b75xJ5h!u>Z7#Ta55R?uk+!+eDfF4ZR6az!O;nXDC_7xZJ~aYGklNfS;wA#d zH*pGo2skU4n;-5$yM*KS2~1^CxvhHi4PjubgHKLrugff~0s-1D#_>$V<5dWM*)F$j zB(%Y@MCkkkNSV38!k{{|Ty9>s2LTaXzm#xR0Rcq@{V6F&1A{vjAsUv1Z?ES}{L8HL z24&B=_&&$?ZjxB->TOlu&>xuHsVfNcI~iGP!Rr zAS&zg=Y=dE4mbyPd(&GdRg-ZVNS+zCB!C}s{=%><>F+{Ba!v;^I{5oYk(Zi23=5HA0ZX{!?U zINe1&OfLxm!_^)o>~+d;cWA_Y!`(@x?{~_udr9ds+?DY#-B$V$XZczlrZ;;qmH*Jg z^kGzkbEL89ywOl(C$Aexx}Q7r9`e zLoQ5m=}I@{-=B}1-@nE5tHKBJC69fJiQH4)V*0WN8*Ss&y2z)Pr$^!BgWx9TskfLG zC^pZhm;>TrO#W9y$+89C76h}7*{I5o<&*Wo&!C8vyOeM;pV`|zuJGA>Zd`ggKp3Y# z$8IVgw#LB~Dx>17Z+LOsuXB4LJz7|;She9+dVa5PE^@s7U zZ$Xlwk*+0RvM>T7A}gP3B4bhnB_FoAmVm{=9F&&nPcQZ0=ma;WKg)!Gqb?&ynKl}i z5OdUJrXpjGy5L~fQbkWP-<{hgLRLP{v`z_&6Uh06hiQ8Z3?&B?J`$_stRiESv`1c1 z^u`3UH)9)Z3_O=0doxExDYmrpE8&_UTouCgx=e|=ghKV0;(D?9fK(y;c(fi<8T_bP zkEvoW{eaYyz4Sv!Pifg(W{>FQUWgqh&ThP{lzR!%qiPA|?-NK2pLE8uR|BfEO9HC1 z9CP@jS7byd{bF8&UXl5``JSG>n$+24##el3tfvbx)3_H?QaC&8$*g6@=iY+u1T=0A z=qKQKfPR@i^%ukpAezuh0oDTug+1XAZFONhJ=BZIRdX~JwN2<(7d8OTg2#g>q+g}b z4e)rtBdbvw;;PunPDOfSJjw=Icqa&Nx!z2!?*u7Yrizv)z)K`=?zdMb_H?;hRAqVz zBq98yg#L!`MsFs0x4(9jqnC0lH5{KrI8uZI8G4v<3~`wobAG!Io|X&$L&IVJRhJ%T zEmL$d+$Fy>UzhyI2U)#MKIkGJr1}<+!0)E=EOl_D^4Y? zS6jN~H>q)(!awJi)OcX@eW~%7(!1q%KkzKmbP-hPS*Ei}=#k%D{aL2A&Qc!TK?!W3c`gXCk%gZ2O$$u5Mip zF%BIf4!r~yhKPoUL#=&q;{w)kL&TxQ`{bs{#q#a0Bw$Qvt;ml6ez6ae>*+BF;Goy< zp{erKubNzvQ7jg$#H6aB*!oMV8)J5m3^so8=hZwc~cd{F0XX`hKiV+U%Gy} z6hsPYtc0oNJ6YL6KniM>OF#;0)kjlMXZ%14>VqFhK|}j!3L1~S6s+ueX<1D{Ga=NZ z!a+*eV$>}LMe^%NC2Y?hlfobJh0nYb@g`B_z&iuRSH2nQTemI=n93POoZiWngE2*7#Ef6X9!Gru`hLv&Lb z{<*$PI%eLK)_NU?$e3wRZ)4hpq2#n`>{1}6s#g`6Th%;9NF@3~VYgl?OBXChwSx~W zz?Ja@4x*t2h7Pk+U8si9Kwt%!jw;30C|8)zAMysnbkTB4my&a$!sdvuX1T)Vh_F_9 zgn81^LIH28&yz`)EWg@}bM6+DJJ{|PL^kd&0*LMw#L4&}fMg{F3`U5_eIvuiU4ijI ze2<^*6@=xB`r@oufI1YyV}KoiveV`8eWyYgQjP=D$mdU^&V|q)t^<~r2f7tH`zr2W zVM_8g^M%48>Q)HqiuGfX9s7Ig?+%4z$36*rT(YQejx|~2r$X)m6_}nt0lXYlcx)t{ zxSy!N^g0TV^SpDy2^$_YWydv%%3Jg=r&x^h;sdgCE0En2y0Dgzht1Q>Qbx18m^e-f-ua4FQsZSn5e5UjPh2$|re1E3$cnndpKl({DcFcvwkrux!hfA=( zG@_8S_=rMi@$&tdDodl#2x*n7{Uc30q7Z^Wn|Nd)G;#I*rimv=6MqIsn|QJ`u@9J_ zHD7O}Ya=0Fgj$ztE(|;UP5Xb^Jg}ItL&Z<0yR}Ej+J~9HlQdK#$Tth95Xe z|15ssD1Eg7dX#>w7P(|v@;#A-mYkvlIZBU~oU4chrX`nZ6{0POIC`Gejt5a@ir0Ff^5 zt)DEkvxS`|0Ff>9&)eE*{(wlcpOmhLt*6t43g&mvx&chCJ!A3QAA4svMBKgs*qlP& zzafEf0&rGi@dGfHSvSo+Pg1GV40EgZ0~ zhH`W)EFt%v>XfdE5vsn@I~A4@>LWfH7;Ju|mSugaaA z=$qXNOW*8P7=1H55KgAwvZk7KejGyTs;s1MxsV9Q>$xzzZBg zZ4Gd#L9Zw5ty2R?1mqqdaRxdUbsDHtqv-87z=)~;ZR3SKg=-ggP{JUSfDRYN7*l2t zn~XS6YjM=HY?VRUvegDfB5%BM?2h%?RIP1{*U*M@ly|T3$ztV#PbBhID*bR_8I9h9 zlHA74_?xq?Th8i$eKO*#lj4I<2H{|DZiP=uh0_N`yQ0)tGw_+h$iPkD0P|4%f>3_| zLX+@CkvbR{Nx7E_V_Y3Gh{?5m9OTf|!pP~fVX+f~(gCf?#lo`KT`B|&lgE^Bsjv)_ z>jr5~-453xl^(zkq|$RJlUsdMCwO19+^`(gl-08c|r%H0Bf`z%`EFO#T^ML|ijh2uKnIlrXG_%sF=dqE~Jf|1FZoWX^F51!}wZ(q_Xca$_)_dbEvG8S zj@VkxQR`xAi8?G+-d&~+E0qg%kU-m@^t~p~GN2^4av79e*G2R$m3g&jOe_4Unq%xMQ=93y@?I-izItt@0Q8ID z7nN|Ms070eEe^wT?ZdSWbmRqOngD}5#_yATr|&#Xxba63^_3dXX?_ry;LlVw=s$#!k8d}jQ#pc8}TkcB$c@Vt~_w-cSR&Cx{1t2vFA&knA4D~&?P3B5Rwo>8hJ?RKN`CGetK1<-!dWkQVcyd zXqtsJYN}x`xn>|cwE^9=jMw?h)DrA^V-gMxctT;CQv!|tu$Tz9y{{^!m5AxVAE}}k zx53;mly|8yH$}Nnomg|4iW%>Fn%DOuD(RkOvYN80guWTGO01yB4$o=mLzcT)+;7%q`2)oAJOEci5 z;Y_X$Xz>*4WW_!IaU;-Ydjpk9J*_B{ID*Nw9GD>WwPM0m2~6gh*5OI!e*Uu-lG~|I zDSXc2p7bnJb#OXy+wnyu+_t!J+wr?mf@QLipoA}lfO|6wDWQ|qUG@{EEFs{A`UjLS z*6PMb1HK=j9}W0v1e1Il;BjR*XmR7)0R2#sa;qSn^E7Spf` ztrAKLtq7$VBXgaG;)|3VDZVNf z{ui(11mrgRHr9~|#!n}VpH7IM9vg{yJ~jmDguDdzG7ynxCJc$W9l*)SRve z;2MQ4a|QHJPF9vD*qrWInH*GN6jM^u2HBZjYGVVU-zX;6L%&PAc91tpHlZxJssI-$ z+)H@(g0~Lvsnpwsepqod!qF83E|UgH`R7M7x%vwxJENvQ9Q4VJ_p)B4VYXSn*s1<# zB!%pZ_dU&UxGhA(1?@9v8YObR1JuB$(2tP13{rhGR_G&vIRi{6CyRcw!3mpDCbfYt zGcD-#(>SqupV&yI%NLkpdiGhX8(qG{7z}m@jN(xONr!$&;d`b-KRiY|bk#9z0`GZc zIB1b>`vxSLqSci0lTC8L$C08Lk)od|gBI zqm)rL_d}^n=TP+VRHlTnOzs+~OvUlzy;P=$#-h^5T0^uOPZ__|(bbTmsTr8VLW^gCx%DB6_Kj&KtkJ-cQN?^)YS>uQeHKetjtAsTRcHaAkdKx~kX;1ijX<_>QqI$amJ0RxaZIk& zQ*g^Xt^lSLHhiqGiB0Y}KNKYd^AfHI0qxgT3F}Pz-5sYvTJSe*zoE*Y?f39+=&n+9oKjYp zqEG#%Mc;@NU7-wG^kY-><5Z@NO4(|2wn=3=JeBA4#%u- z!yZh*>*Zw8&N$@S{1cGGJt3fQgY7P1d*oLn$(_33H^`Pi zimSHUVNYkkhDqG9Td;#C;G2@ZU?_|ihiFDzT1^L$%MZ6@(q7R#ZUU35@N}Xi?|p;L zexc8UNHXs?#ZwvLoiO3JW7G8y`0ap2REDj@R>O>~jQs^~i zqi$zK*o=ut8=FCDNOuIa7|d_Xzy@1R7X2lTJPes3>L^aSi!$UI50-{B&kpB1Co#G9 z0fR#G?eKoNNeFNvSAm%-Y=ht{0q@M0#`MHY)0fW1xgSeodP)e$8m}s0jrp3=tCO@t zygx}h#D~hD9pY0diLC#5E&-h^6$En5{Fo_KDckLGE59EUA!I6Hk0CS{LX9-093^bD zyZ@KQ)K&<-G^U?&32oDueij10*flVhfG>6p5W-JsOe2)g!S0@z#xxQYnv=#fSva6< zo^o_F%GL=1G7l@Ew_RkO5CW{YsD$BmvBEkTVH#6Zii)$0zb6zlv z(n29X#}By#=vWQH|B>XX;F;u;)WLdEmj}z@Snu6lCb?nTWSq)F&!AX}hd=PTP?ii~JrrBpM<*Em`OJ-lL;hwrH?-i1#ko6;D0tF7Ra7$+Ol!&5Mf`0$d^ zB;k2-3X>}b{z{?o4tVt?+-~jq0Vp3$bzoq5AEg35nkKJeeku%o!4OKN*}{+G4!JjTL|5ROA6H81gG&wnOH3m7;`V zc?mBH;epSY9#+B}hs^KZh!Xyjm+-L=kg2OHVTnUl691d3nY!^*&D5VOgJ$Z^C`s~l zy;P+vakxMDglQy-;_G^o@#E`HnAU(-v}7vl0;A;sIIs%15Yf_B8B|NYX%eaUhTl(0 zSzxq0I*m!R{0BdvvV($y4!=fgf1U5Jp=+d+k2TRDP@_%J=V+g%5)}~ zLq9p!*m=g{9+ArQmMMV{cu`BNG*;A`t|5?yqEJ>>8Psh*nWAW%FO;&EN?KwTTk{1a1UN5>jusH-SIJ?9PHkAkH>Up+jOSNG1I6z-XP|L)Lp&? zRAxqGqT4+l(-hLJzCJ>&FayU{vO{5o{{t9Xe}??}3?^6FT$y(EjmOOA9blRO6Y^1i zk?|$UWJMq^Aa4Z9o*|Jr#mm%7E8i#Hz0u3`11LiHRSEqK;YT6t@-mHA!qj;86)#iI z8BA_UXZjUC@})D41#7Oy@@$fZnup%@`@+=OI@-d6$`Vm6$%D0!2B*!y32SdC3~UQK ztwgQnpjMgm4yw5cwFouvrHAPq%#<&IVFMTf921;P33Q%u(s|3PwXY!SLYeu}BeOs3@j0xX~T6w?b}{`czD z!WZkhSi2^e>ZM8b(onrOW=2=v6rqMj2B)`|2_I6q`d84AY(8fcCqTiFz!r*5{@HB)wjI`4dsfDw6I@9iX#uvTW z{c<`J&tfWHC!MLxEGF49xuXnz*)gd&OE*ehpT(4njS_6orZYV--^j^!_e*Db7i=Ta znY@rUBb_M#dH4+Clk-hNn8vcv{0%tqJ;e62kQvZCujO)}eY%4{4vZY-%j!02;Mqd!m4HlQ&%O7VRu#rQ-Rq`7RlD`&6@)!8kp2wmSX0*?FD5R@scxe*_zxJ`D2U^a+p$ryt36DR%c~ffJSg z1J+CB&*pN#>FYsIr(aVoZH?1+p)j0&89(6kyU+)xJLfQo(~Hev^H{;Tz=VZy`h#=O z#&BX0#T(})3m$D>M)BGPk1A+<{S4TM#MzaV@|$t?>(C5me~uq;wihNKW?olLH9s(i zNzCtrA27c+iopCK_<@-D4Q1iC3@vNSpMer^+hQ0Z_U0&q+S^7!Q?0GShU#`vys5_l z;7ug-R)R*tDOi9=u+7!(H$fRpBov&hk>H-2HxlM5n~8*{!G=hvJ(o!$;WNlXjkYYv z)BV!Gi7I8!Mbn@O_U3XR5?X)&uaC}PI;v_WvU^DeQ#(k-BOlj7lCOt8M$R<|bOoOv zl436}G*K|s6q|rz;CtsjzE72Jn&I0P;q%NUlG1g3?E(-ZI#Q1*F-DK)7M0w zzz@78`qDhTKf2RGqE1Bmn&_K&>(iLtQ$ z{IpS|te?k<@-(AOf;tWqMh0+g7SzQk%F~Rt2&%|@rX<uqtk3NX{_N%ZQyn5e#Fn# z4$Om|A)*RgIhCjau2d+oAZDRl0sZHp1rgf>q$=`VAM@L_f00a@lfmd-fZYW=(s&G~ zh^zkp8Bm+LSw~uo1uuvNFTjGAVl8+9g6l;s5Kt{Jzmt6QB4(~%L3tM5pM4d|GXRP$ zuVKp93U0fBWV?t6HGa`utN~M(1x)U;4VcC*U@HG;1E#6?QMm!ry#K>`x%-v6TSwZA z@wLVH+CyJ=&}tYTAgYb|=^hx7>Z6aaac~wuz(@6A?cD`95N&+-84v{*V#oH$#W<5h z8Gnql)3d-haihZhrpWHvN7)kF)WbU1Zd#?O zEEJleA#N>Z6v7R;jQklAWm)Uwg?xuL**F0N0L&s)Gslf1FSG}X5TtfOE*lf!L`k=)^yuawZA#V-XGYooldSQ{m0 zNuEY|10~I##P?cs5X)xH2cS5gYrxbYm+*N5rq3X%Q3IyIOH3+p#h3hQgiMozj455(>h& zgg>Ij^=!a22b#?Oefd9(D?911_IMPQ>Grb4Ov#vJkumuhC3i60>Un1V_l$?m#7rehFve*{+dg+p$f%{EV&ds^|a zRy>n@KrX<2purmsy11CBvXAHi1kh~&eg$kx7<;o7LCC*^$<^`?qNGMt1&G8Y2$nB= zOkLEnN8C+)Ocj?fxqt96JpqP)Hb~w9F(&o0UbL{7^p=?P7EF3!i81LdDEbh9pA7K1 zF{us+sY{ropAS$2-jltoOc;P}-uB-O2xJFR{pyt7)~+^VK|`^iAuRX_N_|8Pp=c@q zzXJ1?n9GcW0{3KtWf|Nv#<(X3i1mM0j%=U}Vz!g;DJ|<(t5M#TMSEM``jUgrLy27b z)Rtp+i%c)S2`9*Ik%8m?K$6G_D){0bOs-|H#YaCfhL?K4YyhSib>xbz?Bw}_sZyA_ zO4gvHO!`@VHT(mEvTGMueKbZ2hfw%9FwJPJ6mAE70h%R6wgYHHvjzAIytlyHkTwaX z;8I8=XTMLFN-Q;<>1TE?{)DMCNV3E9fD(qY`-+#T{8HUvdK^En!}M&F^>Jn0Ygk_c z2R5VL!Vj?418d%}%XK`SQd3{+05(29VSIiTrVz-WZzT5-V_EofAld`Q`=9md#sPC6-@r!$JA*lQ~AX{rk?n*+Q&2$W&W>9 z)XVZ@X4a;D)+<_-E*1%&E*6B(IM^L|oZUrE7fu5zrYc=5h|&eosu)!34%m|AF)#o7mU!Hr<6U^||rvBD; zns=re&rKE2r7nvJf~n%UETCdMH&v`|x-1d|23F50ptYSQtSwR75PG|Te+Hb#mj*v& z8Ix-eJm;h7QgSgcGl6MFGsKk}P&P!%guGMgDqh&FURZ3ra2OOJ99P0>LpTG%-`(?u zC+yK_w@J$btQn?@jI(m(ETVB%Og^r{6+S=+p1Xkj5h(w1Cg)Zk(^b_t-s-kAWGcIy z$(^qu(__og&e187-`ljhc~fYhwGZ>4azu25xpEj6ZXDsC2ifVx12VLQBgIWEQM-0NY!)|HIpK0 z@~N7xM$N3eYC_>g#%na#`fI#V($k_1)YF0nT8svg$flmsS2hX)4c620-;H+q6R_l8 zaA1p{Rz#Yxr&vl!DGjKXr9&+T6qPlUIO5wDHgQ*4**hVrIhFdz>}GYPyf?8 zOKK?x{jvflT$bT=hEy6U<;Sd$nJ{oEG))Rl71Y-TwMbC=fO0N+o#|WkMK6nav#E{3 zeJn8t=sPVVs!2rsa^FEi+3w2Q&qB_k4Vn5W{Hw)XuOU-`m6$2vr2SAO3^Cui9jD^j zS+MK+I@468PqUEoe+`)yD?Hue&S}V0Y9*8VyM|2lS2C4v*N`cIA6*+VeIq&`W51SJ zq!qcS@B;HS-&?8^C+S6pb-9VP#YRW_h>j%?4ITUN13FGbbtGz;?H1!ax57It?#r(; zT^7co4VkJcVVA}IZbPQK5Du3(SHUIVcu6^S8;-K8n1mxqIN-E5lw+@P+WX5*8?=Md zK>t|jOAP(%7YGUb4?)yqslX?@LKb+ z+wW1qDHWN1Mg>i*wN#e&vB$(aXgy+L4M)nmDsKm?8yA}NTcs}-_ldI0%f;i2boghm zmgr~>j*n5ssQ-RfdM87l6GQ(;|KSx1ZyF7?&QdRSw{m5lIE}iC*XF^E@-9*@E3(nD zRZOlwmZPszZ^5hwW)m=30qSE#COm2^MhG8Ki+Wkf>1f1sTH%4#zghIYNiM^zyR0Td zO}BF8tN;#O;$86bt8m)@AmrDgtD`4#rdzRble`+U8sw{mR^*v#fK62vNlo4bB88TS z(G37(1?dm5_uJJ>&X*c7T~+HAS>3)yOnp`}mCtF!G!zVo2wq`&7`6zc4dWHD+_d3% zFqWvmRCt96?S7^W7p}&DDQ#F}5TK3XhWO2^ znJT2vMyXYyJx~)JDz&5F)BYo@E478UneTmUlLAL_y|_)hcou#!UfV8S`wNJy0PPe< z+1IF}9@k22H;yW|MjcfV3~^LtIm@Z$i%JZ*5O*q4mPeV*&syJ!G=$oeX@qBauA=bSH%C;L`JU;a{iQ@KQvxH^@aNOYvGh=45kM@3VKq#DW23ubv(ha&y!iRS-|5@qtVuOHdE;(Hr5I) z5tF*F#qBl$q9yX#kWByssnv7Rfx%4V&e}-JnF9Fo)gMyy`E@vFrP{&h^QDJ#sBVxt^A9thNBpzy7|IyGpEphuv5PC9L~Equ#C(*>e!wxVx4Xd_BI zwi0v6blPES_>P12u0vJHd8iRnos}jr?6$cJWHMa<$yp|oDX4_)HunpeOt*zlBNM~C zDY3)mZk)-KxE{x3GMPHAB$`Y_O`HYYOg*5Hh!#4q`!p_-X`q(dX>+g1WO{NvjEMTX zQgRlOI)+GJrWccBPcTeltj&$Dd*P&XFgu;7SKOO7?DX>bh%0-=p_h=e-jhrtR7LMN z3GUxknocm%bb^}enWPicULWlQB=^5g*K&R0VuJmCi_Di|>iaXZQybA(>qt%ZqobCM z7Rx?akNZ+Epoa*kPImg=dVG5e4haOkO=y}}+#JZ=5oEp8?4XzC##L-(r#9=ElEMvq zA%mPJ$gbip>FT%D+PS8d39C#kwX64!)N)vEEyWw9^YZ-`;hT}mXXIC2Wg2*%k^g$$ za*HG7*5;NI`StSh?TGLl%H{h%yqoF?gmZ%4dSOp$BN}JDrTK7W)VnLiyJz8>i~t>s zD>Tkdr8h7og#vU)&bU6hfvHj;l@1F;l_XpIDJQ%C9~J9}^_%sEsy`CP6#}VrOeDrv z-oR8L6i~3xa{w~{!j_WN09F*GN8%ETJLsWzfsvGVT&$?{6%Z1}$E7w;WW(efIvN*v z7X9S^=ndIHZ@tXyrZnEV+hV%J&8TZ`im)HxjI03NjYGcd3rLJqoU?b=Ltb~4c$+^WgYR;_;j@(*fc#${JYGQaXp)kSvyViH0J$GmvC>B1y zF*-CFL@gjNm7;-mB-~E`>ku>0jv=POMp>W_vSUuzOn?CWVlPz7L9Otx7}MKd)Y`%J zm}S?9Rgrm7a_j|QzbWrz^G%s)%9VGl4+opqmCsDo$_{4dgnfRmrnyh5(?sj%ra6C1 z;K~_+Cy9PcKsa@WYohV+uM%jIwVfuS9uWaOM8E*lJ&>A$;ipFeyfGG-=00y`8c~mg zg8l5YZX=UxKAKn()1ZWjgY0wwUdam3#013A3E1JR`aaWHt!mE%_q*>iF`lJk#&l&B zQ8E!tcM>_bVW?2XK?y=HxZ0$XuKCDZGE-g=6B8s%3T;A|6wG3JMhTM=lIvzMQSz7g zD=V1SmDDF+@|cPsVfli%%gADSONqZHBoCDm>nbsQsK_4qk}YlwH6)?^a*vd|Y z`T}JP{Zs5LIRtf&#u|EI22HU}&}q(67gr9X5-pYMj8~$Xp+E%u6JUmbh}xR~gCPF) zo`Z^P&UMLB7eesS%~bTh}u#uG}BTP2i}f?J8h zG|jq`jfR)GvWMtpl-dLFDGEHcB_djAByULcUu_P8ykfxdxgeg@&QS0*b8vl;g4GPT zIxo0M!5T4!R!WYV8q&LCr(64~p?6s* z&m-#Pq^8mgE53sY*LJjsyp9&gOMyHnC+aBMT8*OB?Lz@EPN^hohi@ zA5(ao#a;Fjrr8ja8`giXG-9fj-1rx)*%)8fnJG^);D8y%0W-t_f8;t~hB#nz)By(8 zefJq=b1}(BGo>Dfwy>$j3u@S0i~C|0(`n!@r!m!3{4?${UZ&fySAylkC?54l*ecn} z8qD#BeOcc7D&k2?qgmG3`Ao!aGkeF5vKx(!p(;G_l zhTh0->ZikE{m*#_^#gQ7&;z!zIhOXdnzq*>mbO$-&)*GI@ChR4Us+6J6h3S*@b3yA zu^4G{6z*b`(@`rGd?GgSaBalK`S93L{tyA`t5)_5Y8VX#~cTgk{u3bXBosj zAVLA3-}|1|PoIG&-!`TsStHZuZQpm$i`$r7+t*WhJMP^D#eD4Fwy@*4Kv$4ae?gs-Nj=MYM1a_A|}d#%7B-OVQ)NH^$HOjUqa+ zyLvX$)(B(UwdOM;18uUcdfZfM{5woNv_xlf%Ip>>ve`6H30+v~H&!bk^@GJzl>U`i zJXayH_=9YwrHU}*&8okvjmUX4o9Ry_cV~C$08`v{tic@KyX zEf-0jY-e&^N7!IvQ=|;wTJ;>%Shx^2h{Iv>D5@Qdca{$@6<=o}X8=30j4LE=ZM8UJ;2l#!cAPhsFY!Bv{h4hn$h-_ zf>CWBC}O(NHsqhQeXKmHZLHC@Ai(sMQhsOmxd79Y?M&_)0j7Cq!ou${HByGl7WW75 zF)atfXYVm>G%~D>nVKle@9ci0G1G3aRBp_47%UQ9-zmo;mhft?U^KjbR>U$x|7D$t zZ;7uL8Z-T-^yP*=O~JlaBB3u)#2Q0iqoB|cpIa5Nk&_Xhrn~J^VsHQ398k=Trr0^Zn3_%5>&D>lOe;In4 z^)bzi`TI{wZ)qWcdnw%BLedBW74B+@X@p@a<-SH3tGwMU(g^?A!6c0^S@F4z;2hPM zX{MrkS>&5Xe<=8qRX=!Y4)JbKbZ<*CPWRG3HJ~iGV^0avd~1I;d2O+|PHM5GS3L(k zzax^@7F&>-$h8v<8KNZuR)e3N&%VZVNVP7pl>hQIrgwHQ$w$KL;Rikv{v|5+-D^xI z*AulMqGWg9*OUBe?uX#KrhCHKS-#Y_yq>g=Jibgl^X~Jf0-^=hL27<0 zE8|nCt*pL!4r&@Tzm*j^lad;QskQuU3B@=jeM>EGZH;_}7P*f%nEuhuD!H$t*b|*p z`24m67e2+%00L~)=#1qziIEtPmcl~-d*SLLIlmB6FQ zfMO@w-MD)6>77hYPX(r?T8VZRH!g{LMdajZl|W9kDN=K?ax(GE<`3&6HFm5+ntQBc z#G?*+b0?ZRyD8CF2O7K%;EYt%d8c&vxP&h$q;nS_my(BalNAq(Dl9 z@P;?dm+A?7(?FXjv_ORRX3&!w_GSA$nIKLQ;`b5a+i96zpeGBu3(%odPm^3l3xqH* zLa3er?uCLJ38%bNi|KoH)^rC6+)m*c3U^U>roufHo~7^rg%>D1RN;jRk5PENBaf?_ zrc}>Kck9Tr)LN7WRU^U?^Aw`m8wxpSl2{$aBZLnywVs96iuNEWZl1$0vwms{k79Q2 zYRvTe1|kTIY>jxfG9 za2;SEJ48q2Op#?5Q&J!t4ABZX_w&>)Cf8g9E^e*gAQFDuh0WJLBFJNc><-53z-6a2 zq|1UE1Yww{(q)-7js~2a(ooW*@=O8F6ZHB$)Y9^_-9ht#z{LSLR@YKixho?m`8-fd z%a_gVv<0}V6kH;DlYcDgp#89loK@;FRoiGhcaz;8)nz)li^=_EU8XC$n94V*%XAMv zn$~5?x0}g%>O-cFHWEFFds5u?x=h7)Gr0@YWqJrd66-QOh98gAWqKARX1v6dvC&+6 zIWN(@S%cwto;9Eu` zb~BZ)R*z{COvFv+bJaxsmeCvtCHX4EQkB}yB434ACX{FtR!Rfo=q#PpYfoFiKssUG7bOl|HwfbcVod1h5<77+6A~7jTYbxjKNB9-9{pm9ibnw z-m36uh4(0oOUD{kWICkq7;}{57OIK8SnD1*`X6l#dBd`yUo#k_mDZ54b+XzzS!{Lf zVM6DfEN}l52ZWSOlO>`b*u#|6jHZa?NdnaKHm0cpzqp6Vb!H<`k|1XbvIeY?5$2}a zJ4KJe&?Zygna2M2_UK2s>qkrEQx3d%8?BGu8;0|1dHvp?FIC=UT7@lFxNbMIs4A=l z_pn)mmsEH^$2>=Pa8qO~j6O%eKv+?E59qY7vcd;h=iV|lJ+0WoW>x%>%EGER*HjZG zH9V;eyl!&=$ZG3gHeMfVk=Jm>T8e$_p#Q?nh?lVz#7h<&o1O;0PnHDmU8Ew_Rqc3- zjO*=z$GHBs>eq3-4_s(w+I6(VWQ&=IexmRci@atw3Y=1-|0-dIIXLB2iKvlGDKeEl z%OV;>N|%icjMgobKG&3QrF4|f^@uiJ(CbNSPHU`vEXEn~v$Jsh z%vx)bX|?V)JSQXSZiC-5_cB$^z$o0^R;8Jpa`r}=sJl5FlN6*L^6Pu(ZWLs)ZZ;X9 zuPyl}@j2##`4l-mPA9Z~wSXc1i>p$e97Fm*$rApCovUP z1;5zb^OBhQ!zIg-n1)H!@6R);t4id5bKlc?YgHbEM{R#v@sNiOO6bM^wwI|wuI7Vs zjI7W!2)9hdH1+txzHIPdM!GTT;1L4j zz)pZh+C~;|(C)ps7cVo7mdK%$%X^tzB{$>pJenbxyRgYQAjtHnx?_ROJu}GU*vI5v z6l5y2kE#5cAXDjmOwOG_rW(rG%I3ZnWU2r>GG=YX|BP-!A*Rn1(c0#IAjDK<9|%by zrW7T#v$;PEF})y!&qGX&atZB1Om7IGYltbVgw8higb>pQAeg@Rol+*)+`s*g>GOR| z$>@}k9w{BRtujqEOrgIqp_g}7rWrQ(2cI%^fez=45K|8&EU~!{hnPm9^yv`#R&H}v zlBs~^cys)4qZMB}!gw?}PLllOI3)Q6`yw4;ava*2k^*V8Qhu)6$K)D<_Q~>Q(SkVi zpS{4%0M17X<1lqT3d~wy0%^2J3SNh;dT#W9TKjvPBM3hKWjwP{&bu`6eF;1JT{jEIc zeIhE(N#0=4pG|T-ed2zmO5P^)@a;}^BIk_|)1zC6z*xflT$riuex~v@!c2|uT zynfnj?ajvEC%JNZ8qt$*{KfrvdI|{bc58p69fe3q;Q&2dt(1eT2TZN1np(kv57dHZ z#RAs>Cb1y#09=UJ`%D?0{<}e`nb{#PZNWoUIOYXoki19y(1C~v0*WtR%+>lo75b8B zbw{*Th1S2-yLu2Gj@fEmrWLQjm9qWbdU!$Q9b_H?NN-ncWhZhq+(I}i_Z|o@L7dD) zb5vi=ye9(gPkFS>dfs8Yo642byqTWnM5$PGzZ-zKPI>D^g#J9H#o} z+u5A_OAbOruwN?n4<_f7944>Acr7RDs;EKgs6E!k#;Q52^)`yB0&JQqHpLe|$W$RK zKy$_7LJu5d_+_qGm~=2UkV4Hvo?u3Z_FDVI8Mzm5z9E>AN_0W^pE!sse1jQHfN1ck zgI<6%KV1}RbpW26W=#Tghrj>SLGJ>Qo<{8~@cb8m0v=y5M58P?htlLAlgkhP8|+=# zlW7H;T;B+qI?GE-ol(*o3{qz)HwvJiewM}3?_hVX$YJWJF1y0+{W(lq4l=n<<}mFQ z4!ptDO*!tcjB!Un$n`F&pVO=f?UMl=;XeUS^?{-Z@M2o65U6vj291&$omDw4&$)5pXi9Z ze^Bl)Q>BoHj#_VwMV+fOVVbISJ|6qZh!GkJdr}+GVQfC4fo~hhw?*<}D(a3AmD_GO z)8or9Te@__+RT_d5U0@b@q&Riq_`Jgm*3lzXrL`L)=sqmV@|GcojlbK`7->T%=hW2 zHOEMsV>5YWjvTuG424Db966?*k+-ln{80n6MU6W3^+02gj#+OwjHoL%uH2vn(G^)r zG&#(KQ-xPWa2G(xP^02*wzJb9sj|08fQH3=_nCvJ5>C1VeQ7jQ=;WG%h>jp{iNx6v z8FvNT3)`I5*O=C--*4LF%jH|Pn#Xpyjo*2_BGYcA-?lyaY!aqKuO~4bQLd?+90pQP zl%G@HU*p`3o@Baln9X75Lfg!tYduWN-1Jf7n zST6tP2(s>zbX~5{eM_LK3VjL4E7N5|4gevU_?mtG=MHLngh@QwP%XS7=zjiv{m+Wh8QX z2Ri>$o$o{EIh2mtpOIY$H86YUr>wo$6mQIxn-Mj}=y4tPH&?)ZG&Z@!7OqdHqotcl z>87)Ou~XtvCX{XpQ5F8FbiGVps<&FE*H34n0)7wyKcLJrDnNiY{!an*>128E{mum`osF3a=a_GMb zsZJN70+oJS9zXDJo>75i(MW zomU|Sy8G`!YS87VgvCa}Vv$hbn28Gml{@wiVI_f4BirA^8w}HxsF*cI%o-6>S;ZLW zwPpJb=^j^UMRuP2ShTl-G7(-(l{@X}&|3Nh(rv%*pG?NPffmg>laYRGnypSbh;T z%Lvd$*{8b&5z)jF$PT;#@6k=`vN%(=hwdl#ko`pKai*j+>S==y^8?5V&`=pY9|RN# z&@dYwJXSr<2;`0PUMdP z#rgD$OhuG1%;v8BBGVKhTz-nFj1uOVx9H{{*JJb>g#(8Ho>z`3wq(2{OtL*xZF^*U zDEjmRyZG-Y?{u4N5`C&%*d)sJn7BOX4d(>?bPMl=VL1+uGe~8sa>5MTySawokOv4M=SO zsZFSD-0jYGdgKJ2#5OUg_Hh=EgT4leavL{B>>cZLixK;CoZPnca~#ADLWyXBO35imr*{D&Gg5AX*bw(&LZ=$4?NiiNoj>qqkD>*HRgG74z)- zuJy9&9&2vXA8Uv1-%em>DV1of9V75`fH=}W){fQVf)nN~%yBYfSb2iU^#ke}q8aw7 z9tZ7#<+6_HuFW>z?#5o-(G$8?cRtE7Ksgo|j$089+eyS;O!Xsn<3a_~FYd{UEokHI z#`f)Ed%=^Dnf-PDbUiyX9&1DLWR{&z{61QMx77t_2%h zlfjZkZ5`-lbx~*@FzJ5k?122Qz(vko&6rkeWp3Hs+?**Olz*win?X;Oms;A|X-XVq zZr&f{fd5;d4%q;aKyJT{hzH3b4~qSbd@q(FURU3(Y-hERhdCJUqm7W_ z@m)?bRS3{nQCX-Dpo-azXqaPYdpngJ16P>$C5Fq*{&P;s+ZbaU@-~KR3%rmOi9)4XYKAissEwQZKoMDc3lgwXqu4ZYfU35(G+LVEcWQX{0}Dyt9D)3LX))sfuXPIdE;>V$)| z%~7OsSIn=}0aj-Q(|jmD3D&6$43kCDxp=0Z9a{H1r8{5sieYA=N>a@KP*J*^53=KnJuQ#1Ck7o@NrQhCBaL&~aR_^~2-`~%cki9XW+Yvf zMy?OZQNiH`!R)3QwQX$~EWLYCJXZ+Ol6a)D`2XhNPxpj9nbgjPJ42BOHpg@25YcAg zZH;O!uEiAA%Iu3TzpED0AmA~mwBAAV0TF!=^QF&DJIweK^M%}8^F*DR*FIw2b}?_K zsEi+#$2`0c+{7DfP91EKA^cLjc{q6~9zA%qx*{H(ewP5LbR`}+_avZDK%q;3(j!pZ z8F=ssx+>)o0pXo2^egF7IA!LgtMSNq6+z9&4rkX%rMn{exif4+tB3mKZoE0OH%Q@u z&d4LqXv0X&rFG~>TiCSAIVV>R6P6A>>G9OCC2hSi=rBcg$kY1=Yz)(QJl08tNNzIu?Y6aTnyT8$7 z(4$Vap(?&dKCYY<$_Nql$OqpKQ}cU@0x`dr{QOPL?=6N*16x{lYS0sEKz;JTza*N+ ztLAC{tXZqq*@mYX2-N=hxPs^(AnN1iqec}YlKbaF1}?M?$cz9@5ZxvB0Su(l1TpNP zvj9V&!Y7sff}trnDJhv=8k29zQaj~8ixWE4n5L?6L-YNuU1b&^&iOivy4cR>II%jP zZu6}+bwuY~W2}e>oufhzM1;zm?hnQOnJ;Eew^sX9?j{ zi8>201Tf-vgX(aWsnYksC#v9R-Gdk#42Tu>gIH0MYugUQ2)4$uywo}YnW@KFr2jvf zGaXeQv`!%Bq2^5I74DPZzS*2<&RHh6r3KUKvrNeeEttq%tOe6<5%53@roXg6=LGk& zEtpQ8WpY<29*A08ZIU!n| zfEk8aG5w(RnU`Q*D{imw67yPdPqbW-rj>}3+Ffj^d^k?9U{e+2UTZHv1P8{5e;s| z*A`orMQqsshFDty>bxAWrI+nI8(Y2*TfXoux6^K@e-`R}^fj{O$-F|0h+5t-rry16 zcT|FpEBlCim(v_{FWQ_wxSNu^)NuU64vN3Pl;p+Di;Z@+uv6g+Os<+x)X3*gqilb3 zJC(kGG#FQd=|ini&|SU;Q~3)_DdcRffAnUqjCi5m@@Vt( z3kWHnA27GDlkY;LGxQc1^9_%m%-ev?QO&FX4U@;9{QyPJW{+aT`&i(NAF;flaeUJ2 zplOP&7shJ^lemAngZ9Bk4R_%rMl)Y19HJ#gpNWQM>Z4&S;cyBybAIwXQ)?xRVt140 znJ!;oa)19kQ;~~I&JNEr4cv3q(8rf)!TE@;8DLkZ*AeW(S~03n=i z!E{Oq6WCqw8>ZQ?r^Gi*JE5$5Wu_a-Fx286SDEQD7^YQbx(gBHeCQh{`)<=8C$amD zZ9l(Hd>MtsTo>T8}2_7YKe)kPi4b}HMlXJ{B zOz$Z?kCV52gD$ZD8>ae-Jj2P>Z<#1LKmL|4`z_NiBFyRjmg&FBHI5%b9FEQ@@@%U+(yirl;91ssO37JxZRmYD{Q<%QGf~0|Lovvk8$KEKLwa4_Km?=AYT3C74nxpmKDwp)MloY16O5bd8 z|B%9z4s9J$m~!x=UkX!8{1}$P)EOq@b(+3=OjbNqP+q6$BX(gF9-@TP1!KqFs58^N zvCbT5>t!_oy(j9-J>txfS7MyGN1Qq7N@OtJBd1j;DdBZ*O1NB5kJ|20;m2be?}#=_OyhN~%5+wFPgqDA z^R~h#O=H^jnkGkM<_2#lClv7d(`wNmTek!rRGSDBH|+ZP3XQW<)vFO#`i5rUK4)y! zf9CUMrcnq7_};q8xUf1KA7G~@m~r0vST*G$73qHO7FcZp8e^uHd?+B#;>Gyims)0aTW$x4r60Sl5-p)MAFr^C=BLQKN_Ut|)IHsW- zLVsl+<=%Y-@*7J2#{KU5OnvdDd8?P1(qj35XsdiUX9xHvg>f~^%t}ms6h6x2T>LH5 zScOlpd&jp-6CfC`W6j)0^f(b+w*6*0MLmO&$a-XU0e1}x;6!5VAlHz5B3dY-tSYQ}MBN%P_jQ}vl1YXb^ z%E|K9qF-#kTTR@xM$eY1F15BG+KOFga^>HT$uxDeBsaHH=j-Sx(DvYd(d)HKD zqJ^=$Fv)^GGzUgUM+-bIsqzdBwoPWkJKw@d;cV=BFO2*=YFv#EAkwCui)6-g@5X~}^{0Af?kfN?kQk=S5_TJT1sTc0*Lu!?-V%J9l6 z_>2XesMHOnq?`bClQ)_k0c7?UQ>mL(<_tjjv1c~iiaoRc-iX<0TkFWX)wb4w9!#!J z(WL2V)K{(rUUCC%TKGGrM%rfAEbip*nAQma7mMa7p_P^7$<6l)_qEECo9#FB$<0Ch zz>}LZQWQ6rc2U;hR%e?`rUCnjaxW|$4nv|BmOlL*)A%U6yY_cX*F_GF8_iL|2&gRo8sBMqTRl(0t$qYPoU5b*rxq!Rv!JtK$*3&FHEvxD+bn1MbVMb=d5q$KlGafw%{RpDF{<_!eeW60l_skUR=WCV67=h=Nbc1CzG5*o+a6 zD?0Xp$x{a+14T04dY6CE@Ri5ha*xL_+b1j*R%0w~t0P{1f?P~GUR1en#%vQ-Q@Odr z7TNJIXSl5v4-&J6qB!OuZ)x#Zvp&$`G0$or(jBzsgcI-+)1f^$Yd%`rvHrr;nn+tdIce>c=soU5op^kFG z=hNFva*n(M&%>bX`uYH_wxGcb*2cFnLIUHXA;RAVs>s#E;C>TaZ*a8*CPd=}Gg`Rr zt9WWQZmVuaW96wf;&CxEK8m-PpE+x%DJYZIRXIpf<7~Ux)L|J*C6>us3~NyfAFU95 zyCbz&&P#D-_&zxh^PBB`BW1IZvRTIFlYdv*z}t+*+fJy?y@q?Q=)7Ze9uS=c@2KbY zt4@2)cT5w|dhVaUV|x6~-x>w=!N$)-Fu?&Au@gjeQC4lw{JlUZ9Q64!XcEQ(@Ly$F zfn8)0gU{$?}8=f^Hf?HyKg=H3Y%<{wnAW zOAUU=E2x3DSz1n3;FIiNT8MtPEz`)tS9=0PU1X@}bcd-jKKiY2f55na0*G(}2aSc- zB9Bil#pg}xm*l(PR0#!e0=%nb_yN4YGYqliqwb<&_8lhIOki+AeTd+;i=~p^w`d(k zS;+acn`y7Yqb=sK=BXGM8RLe+<1LalPQn*R8}>g*a$aoODER09k<#=683O5e8<#Q;nnOu{2kNRe=(Im^Bq$slo5`f zl%u6ZIC@7o76}JtKSPvbrrvrqR^de!a$ft6X^O&2Ey;zxmm%n6O{Teuov)>qDm=#$ z6Y6LJ-H@4Td&IQEdgclhWBk2dwp-%Yz@BKfk%`o^G|kq?6xtX2x_}U$qv^IvhIk^D zn4TR>^=70t@MZ?QL7HJ}XE97?EG%>6b!z8r{gla8g)hV|!uDuf<&I`f>uXGhl=q@V zj!mEZlPHcT_aOQOrC+hcobYxoSxsU7LxLr^(;t_)BDmi)Y$O z^PpUxWMqd@8+g;+POqL5YEH9kPn(MMw#sz0j}^n-Rs>huN=ykVzJKgI#FN?>R|?h3 zNz2Zl*|x25M#3>Go41>eSw}9kBkBWmUOsiP?=L(_3D5;A9!%T<6rxr(aFx2tW?Q<5 zYC3O~ZRzrNF?o9WMWzRpaKS2P4IdW*<_{H>a4GiFB$W@Et{mfXJnDE_c{`YMiO<~C z=Pq?c8uCId<+x?O&QM3?w6nxc<@K&1Q}a2tzqICE@U8_>7pc)#cg;CD^RcnFmk9gI_p=AR z>6zZtu&9`Ct89XBxh-aP1)14Fue?>0@dPcv@C-ZF+BhkLHp_Y1n)i#1DIq!MNm^*T zz()EO8&}Antx_<)_P>{uLjsw!$o8-)cfiKZvfndpQosFai^*=GroM1$gO9Rn;Cx9v zIXAr6hF2nB>NR!QH95EX-+Oon?WOBtLB>5M=d0f{?NqThY{_epTHV{fX9|FN@O!31 z2TgvNVoOf`0p|{${(-4Ej3?)7KQNut(syifYWjk*%c*G?eO>8yZRA|^1JhlFTgQ>m z?T1VXY7-YzDNkl*2JWC;V%wvcb7GuXicX9}hG`A!u-GoBr6eF$K-nVkU37eN2)O&ZWQzCtBT#R=6$J(?(06O%Ddl~(@?qo*_^tbR4*;H zeQP4=s9JPXELx6uz|8Yl-2Q9_Z3UR=^=E_|&@p*S{|HQO43mBMwm>sGT>+*QFc|?l zA(e0vlAQC%4@_OvvSV@O@BF}2ge+7(UrVM(z~W47$<$jpPQNGBVRjti`3{(Q!1!piV0r+v9hd-(v18IXi7b>9qOl@kvDjo@zr3J!54XGV`ei=0 zko#l~Q&Gl!rz>)pt}DkF!%;TEQBgRsj=!rMzu9Gh{~sZ|*^Muf0Lr8Wb>MsXqufbSn%a%-)v=-~^2L6x2 z>+R;Yo@W(4VApFSoqJm{y`tEY_VTw{GO-2zjBmyCrV>us%RktPsjS69e*II z-5?k(|C`bul7=59a>;qE71L}L)YIWE(wb=kN|bHQv_kQH;z{6b3iplIZTrKD=;MgV zXJ-!oEuUeB_Nwysb&#_{Ri?X9*!^i$rbV#FnNXj}al`~-e~0@&^_kY7BG1)lI))#w z)n~dWtQfK-pp~|IrhDvku$fElbYQDJmKzuUWc#H%t+KV# z8Q4*Wc|Y~216xdSRtr`3r4b#K(#5P6ImmTP4kABl{r~STlF?sQW6#DmPz`lcOh$jP zHPbuFd)?u#)0(NW6`cShQ9UKJj+dv3FAL#~3QS)rp-sHF->Y)reu*o;(mTdWT)k_x zm^f;s_()v+tT5s#*Qb?|v)oleeor9OAUj+wGc!P|ZJEZkbK{Mx=ElQSpIa@+z)uj* zjnA27kM*?3kk4U5Ks^;kYi#csLCaOpauJk@nnh=Gc>T9It+n;g_SqM22LF9>06G)x z6UKM6%$-R4Bx`*GES~rf*jw1C2U-fd{W&4pACJ9#!c_X8=o^bh6&Z9QzDofIEduxV z!6%}32}oANZKiW&8^-M2|Dx{NA1~95eQ+zL8zU4i(+$k5e^bQ4cv+8}LG_$#pJAG% zgcI@3Bq&RnCQYIm+;&%|gQRAz0+WM%+=81rEo{ z1Dh|wfd@8?pc0d=qROBTY<>V9UMQ=0@d6ktC_*1~bd4}pQ9Kx9!Y)1N^|tVa8+e;$ z2Q$-Xo$aV*jJxsddhRIUK7t`m?6M9MWAR?|8!GBve9UV1-J@pC8nfC3Z(Zdb?u_C6 z;%~gUahU1}r3bUKzOEHY_4qZE*4xfIOa#w!ny8#7r_|@zEQD9$n|Lyb<~b2>tAO%* zo5rAa*)YM#Nlo>JLNworI6VokG_1&!rsl12y2n&xx@@zMTP{J2Lpu1pHB%$y*y5C? zY;u&Syj*nbe442!*h~wyQofB&X~A-F7W2sM@i>f~?=$_Z4BCRP1CJK$r+74swBS%h zXbXN2VH~G;FdCgx74wJDnHtdAxgM01a2srStAPs~kgYb_W@w0A(+XUZ3fSy1 z6}To9C<;`h0tQvyZXtQ}Z62bx&4(fIDZ7PSg^o!V`Y|86P-9@;2BsPH5=;*}l7uTA zI6SAGB_$^T>uMfBE)(P&LH3CtHwto@Ag4x<+XT50Nb%RUVU7ljXSMMnZRm3n<)E;Svh>&qvNFt(hKDcwRpD_SQ_dMbXSkOl~DC z%SZBsq^b%p&*zSRlF9BshXdnFN|>b*-%xmskytp&_`VW0swp2Uye;TF<=BZ4rkFxclcfC}PpSIdMX>D$*c{jzpCNL)(-yXXu=KTOzE-EqbkFX7W zF{CE^l~4NOcgJJ&;+xmRjdcqiwcd)V9ugxV^I+ zZXWgmvX{MEo&`jCxg``A4na zY3r-oK6ewia#NzaV(58?MW%4wT$p2$N7}Z(OMEQO+olE$aQ%bx zJi({~YSo}Uwl9q5hPuQ9Lq*JIsPkhrnf9ur;jZKw|3@=?;tMyZ=gsoaUfZO6M&70v zTQ`ZVSy5Xzi4s}uoRKfDeH)MI3QVJ`E{r36fWYAJbAEv10H*u=zEA@iBbWuiNQsYp zSza0|$jt_s9SHdR88zzE7t%B#?E}e7#E-i$5x)mSiYJYx=f?(54X0Ue<<84phtW#{ z0lMeH^MJ)p3z4hf3G?nk_x!-^5nMUN%@*7>;A9H^#0fKu9CyhS{FM{;O~Kz#`U#gz z!8sq|2~+TQm2k=>Q}8nRbPE2lO87C)6#Q$&>J{YDR$8YL`HOncL(>lL6tT`xR{ADGy&ria5N8wI}adeb1-$~OXKj$|Eiz?hFzuAE; ztMGvQPV6=$pESOb6WFlg4pl zu>8Vfj8BxlDt}DV{7==zG!1xt%DXzhyqge=auGQ*+Aw{i^r89xE!jl7=RVuyIMY23 z=jY1kUvyZy$B}#%LIOOTA6X<{LL{pgs$|1s`H>B)Ctwp$_WI7{$6jAuK|D#9CHlVt zBxk^+@AW8v-rGlzSV&X90_b}~6aLoO+MXmzGB;745V`Zglj#eE=|q0Hh>~2L!4sgn zqG?@%gsNyqF04rWq4M3ujG&E zRo65)Vpfp|p?k`E)r3&|shALgtiq>EEMJS2Ra&`XWMLbpyz<^OasBwIn7GE%%V(5+ zFTXs!bh(g1UxItuqJV3OZw3im4@$uHy7eaSDN z^!^2;FFzuJkt~`kVPFAC7O%QA`E^hUd6Pvq#cHzn9AeC)#9vj;@B%U@dtEvx=Lk!5 zLXah6$P=_*js;;Nv#x+VVqRYWd)_Tw7D~zvnde;_3t%1H-DM%y_)|ocLb&jCcL6-F zh+SbBkx$Zr=mWXyM&u0L;x-9G#zGV1!!48^oupnxBwah@}=BM1seq&bO=hFo7ym~P*?3M zXd-&O!WRq1B&Qu3Gcn0&T_vVJmG{R&u~+?{RBY!$l1DFTS;?dL^2%+ccP(TVMz+%? zmb(^`h0zr;4Y@DB68aXB+?ROT1a9;O{Rfpkz$mJubWsHJo>cm{sCkh3qGGqkR9- z`Cf4Jm!N73s)L}aQkTMz^S}HSa!m!wB0Ax0prq$y!wVM#0J@@rZNs)W30Ftogu{b%R^x1_a54J0yd9b&rjOM{$j=!EK-JHyR(@%$O19h#rqA>2LS}9R5 z1eHRQUQ(eO3&%9+O;vqg2cokkV73_paD|G-bG?gTFp2*P zW=5k4C*HE?nC+a^%r^fl!nlbQ2@k(p*eSiB$#1vCojHKyG|}xMp#BIr6rgTJF}U^w zWKz`7qDWD{7PMgW&B*eE8qt*^=2~bR0=c49I#Gn=3G8i!uNIL9uhw&s2d`viDGBFH zYz{9f9xZjwxJ*_q71}aAqVzFElRs|DXm8|z#}xZpQAskZQFX}yPb*hcfZ0cg@8=u)0S7V`y_#wzb*Q905X67@LJ`GXQp6_q2M?SwGzQKlc2aJp#n`A5YrIJvuG zuSJ}k>w~mlb|8yR+B(@x9Q{&E%oGCfgPS4lM#f2p`$ zwL;E`ZJFLv^u=PyE6|OkGuA(EI^%w4?Bp3QWJuW4DcgD-g60+H%6?yFb|X)70?@)v zuNRJ_{CUL@3zX#Zrv_J zA~wtMvF?gpU!3HtDE$=PR9rq|6fSHb{fv=wdt0UXpv6fk+)IHHM zU;<7RR55dNlE_j6&SY#9V^3E~$tJ39P&m3+In zbofz_EM{&~!X0Dgq`YSCRcc-{4=Yy9oDX3!X8OHNv*@htXB}B4CYo9E#6(OCmKU~= zOdBR9VuHH?Am#}Z6EROXAQcGE+(d-T2}H>$L`g<~79=8va1qR!@Lavv1=D$EC(4kX zAOtLkA5g-)L|G6Q69T$hB_*s*jOlJqUie#ggTG!>-VKT3uSZ1)`e6+vY)zDY_?{M* zeuzmzeWmYAlu5!XQV@hxCG0hXw?N2Eqwv|2bk255JJf+h)2;`^5%r2#$kq4)QBpWW z2NUOn9Ml-)o%l>vD;0b+QIh<(Amj>`#q&qrtU#tG>@|0I(|Ox6RdXj%vUQshaCf^R zx{dy$N^Da?T-z5Cr4GH7(5{43Zb%WG0ZoZ=3|5YghGTAoV;wkhtqSKg=z{Hv8q}`@ zyC$Hba0m?*yLO`X8w|2pm_`cniU^c;-KRSHmxyWCBibM=iOLmY0eBx!-f<;lwU89$B63c6 zlIaPh4=rKlfG;RK+~}^Q{GuEDb(B7`gnPx4Oz}lATt%lScutW;7i~Rs)-b(Up+ofA6f0i(-jz3EveY{fCLY1Q;Kx|>BFpwCf8sOD& zMYb)9)EJkDN4lT-l|%-aVofsb?7c5fV*(IeP9WAQ;=&DG2s2$U8@8U;JJEo7JK;)OvGJRs@KYA(B6O#4yP4Pu#ZW;c4;XY4}OL}F&@~jm~g;&AcwsQC}LSDH}19=0!A!~%PJm+y_{dY!<4Lum8ING z-(mVqR2}@5>8V`8t#6rTpbc?caWzEY;MvQ{pa;+P0}mB76)!5lSX&W#@a$5A@e{>^ zF^|_gL3vRfZo3xUnA=jyoS)euezPZ<5ol{E3^@e>1p>5B+*LkN^ZWl)!PZie-ycsz z+CU1+P{RIFlER(=A-CBwQqgc(bUpfh;Zf6M$3*q3s_e2Ts|97~6MmI>qLlQBoGUSX z0?Ynym43cdWSNa#*G92dO2zcLPFMcc>kxz8mG@dH6NCK~zFkUUewda*%;#3d+svDq z6ZX;#+j?C>{8-vJ^2gE$z)uo!D%;<{8}yl@1)yWY3bo7%P{-16a1%fcTF~Iqcm~=Y zklA|}Q5t&>-4pNMp?6~E;WssKL}}T1n0>`$U)g!U#?lg{k1Q>F3=_}{vd6GS38PEP z9>Xg57<&x6R6^c8hC_BaRK5l%lj3`NJ>Iv10OxzZf1@31Q_he?~p6|Y12Is&R0Pjo7^ak*m z_vqp8TV+aw9rSof3z@Gj<)z*hYEH@fo9OSyvawcVH1|KyVvcO1;Quq|x^RVLgL;Gb zc|l1Fxk_Bc;!7ToZ<6@gRT48V*VDjf1ZbbII!mEG@aaDBX|Ymx%2Qo=P72S1g8N!= zmjw5e;Ib6gstlt1Rmh5M;a1}Nw}rK(vi>NnACIf-Bad8Dh~Mprz6?${>)A z0dA_mMF4(p(@(O4jgB?WbXs_Sf|5PTuvr+!0reM983EcV zTq}&kttydRrLRXD|591F=a4a^8t`fI#m{c#Q1v2ZECfW@L%)`T;~xbg5TJ48V9YyZ zEM%JD*K(#A=0ie8fTol~RRZ8P6`o!WdfJwe3LgO%x~4Z+Cp%L+=pUkS8aO1GU%GC_ z(}krancut~lg#n(k8T+Hg3|KvZ=n=C{tS~t3F}Ln324D6EOJZUFey+cpby{3l}j@3 zNml;jrOo93DTPmzju{_bzVWy5;aoeWn#y~!v~x#orcV_-_e}X)wV77JMl*1vE9F#a z891_490m?&e0!#5N^e!hU9LUTmNFJ{S8UI82|xbRo++WMh00fJ&yg*5_J#rj~i> z9hE*Rj?A38N0d&^#12dYqj2(D9i+Y1mS!5E*k8&d-!p59s+E`~DE8MfW<4@j;olWr z8Uxqsz_d=`sb$>Z4op)}Nz9VAD}Hi(WR`?u4F?qad%Uh#$ocGROeYk*u1xZWuQ5^i zFJEK&0|A)+8q<3GX#5(}E(rhbHKyA)iCV~u9erP8Is){t*O;8Q4EhZ>zNmX%0?;&T zkz1x&@0N+_ePwP%8aMfg3UG@_8ILOOy)u$AD&NZO+Oh(9R_QIvN~e2GrAVj4isvn* zw<&94@7n=L&Z#D;~QA>*29f#pm@{reb3}hGkHWqPrU-zK=Bm^bShzXXxEx z=}^{R>5I!ong^5EVT#>YR-*qmE!7XvkE%^m`lhnR74sE7XtXR>esKkYV}sI+n$F#a0 zb39>0qi*@{nC2f=M#l9rckZj(qe>r9M%FHI4&pejgc#HH=c=4-rt2@gW8zri6scmJ(syar z+ph3FQ|mL(i&`I0eBN50P;5-C5hfQEJ*Aw4$zKqHFuAArXqd$RWg2~2ISG@(59)K? z!hhunljk0kISRt0w3e7&P8?e4ulvH}Nu|#$Cl37zCB>mHDq(gxap+G_1&6++67o9q zL&d5?=R=H%?5|bML}T(w$U$T`$ZPUBQv!*xv6fh35@QQxlq12|i)*9wKgyXV>!NTw z(`3E>y06K8ReHy=W=CnX!W+xTgs#{_=o#?UWF>4iU21{WDH?n$l)lw;sRyMXy3|G` zY&Tu%gNL+B?NZ>0hhH^JVF!h%O)UD|Ij2p}8d+i&`Vf9T(0op2Sj7|?( z$aVFv=uz!mvJUC_P-Nw?%d9+1hmF5$f@f1X^IV{y!rRKpbAeG0>2rZe_<`pFv+*OR z9n&8$QQD@Imf2ZO2Cawgnz)m;!4xhjFTIM=Wza&~Jrkvq6KVeyg?E)R(ekFkdrY*f zd&ol0R@In3xJ&f#|Hs&S$46B?|HE@`vUjtvD)vqbkla8530+h~q>5Bg{8%^1CM+bo z%kG8%3WD_Bd+5FQqJSVxumB=T69GX56%-T!L3m#8IkTG$;P3nJN7yN6X3m^BbILvU zT%vFuJOP8K&}YGr+ohd$E%qOIhw{vma-#kWubuuZZBRo0m%$p3Do`iJTp8qV3b@Z&tVmOnZUi z_4vS&4F{QY8CK*AV3gG+(ZG_3o4d*yWSfpwmc%|?#drf(UF6)dPe&_DVx!I# zA3n^pvLtpg$oYuEr!F&TRY}C~`|$?hDDo<%4v06%`7{{(ep<_@k!&3|!OK#U)w(8& z!WH-kEc{q4qmToSC%1tEkJY}w52T@k_<=O^1DC;s-YaF+l#E*U-#ryo)`24j$%D%opA4q+RUd zN6HzLT$o;H7kAzo<-(5FFga(#ubxcW&5mzcPCI@#d$~B|EY#gsSnhRJ!QXebf&Do(_k6qlrSr%EQ_!KHpmd0nZ(r<91J z7(@57E75UO+_)`{u$aGJc>9%#TFg7k8^nwGhw%f8`LZwxi}^B!idxJk1C7OeWkKu3 ze68|mF+Go|B?>+$#erH+%!mYPQ=wnef!gL)`Wr$Yrpvo;rF(@wP}B3H=!MOh@9OAo z?haw+(H)?h;Wu5!l~T$4Oq|A*!Z1_6yg|;MhN9bcDhrt24E5qD8YEiBm(uV^feT7S zX$`o8?oi}pM^BqqKLS}$il0O-EQO(}ZFxMwC_F;4tQ7WK--2H;;hrFJmz9#0e_@?G z-VW}JP&RLg_p;&3fe55eN~vUxqfbgzIcici6og!!9NNVSdzQz6RXprNc+=rh4f62P zwBJv^mO@&b3x2O3k2~mZHf9$vA>PB@T?$*eM+fSddDGyY@qlHb3qbrQ6#i-AL^3ruyUmqx*R-F=OY%OIqPgL|O z6EMa7MF_h~@p9lVLls^_gB8a$p?_A2=f-|6h$*fSqkGE!QoJ1KT0y3`_s3`nMW?t= z2v(-Js~|?d3YsKxzAR|~131OKgG>#B931NjMv;jd+(?1u=v4F&E~I;!k2P1KR;P@G!YFpNlLgjYo^21dOqMT3Q-?d@!JFKdmo~ z6f_o4^lW*3Y0Rsa0v&l0t8t%jXw+3ST#VXWn&+$&Vx#7)Xq*{B|1{bm3&r#(hk$pL z@a`(jn`|2+Tttos2P)bj^!=r+$^#W0h(y31A$%LHvLGYg! z!gr-P_{~HK{>y@w7%!C=4t|?W$CAW&tq9?MAxL*=1%e&}%LwI|Zo(v6R5)R>R#oUn zH2vu)dSMg$U4f2ye4qB)8PwbMtR75mme$7#ZgNw0NHmC#4HDffjZXT@?_o7||{ zo(7_?XBn$qc}4G`&Uh2OSwyk*UU@}*xdufocsrN+*rwUE^1fxX^1fvdN5c{geo)`9 z3?gm{Ai4cxSQ*^@u>=@BjtnS+ab#2C|NU@0YV^2uFll2`gyW4f%N*Nh(xF6yoPB`u zb);Ek5W%N_S`L)fI*+xU*R|q-W4}0JKp7r5j@xwFkL->7B=pf`IIlluAqX&VTo%HV zGCXipw`Ab3nOZ{8f#Yt$%D~YQV#rZ@prR*C9Zb{9#5WtL=yjGeP*IZLua!>fJP;k> zBTG>&Q<0O1<{C*S_yid_dJR<6Pl$alRIYJ%AuQE39>tc{P&8O16s_?X!AgxkhM1^G3g5e(>(8OSwlfk=8zh`I za5(L@3`+Lny*WSbr2(~@Nrx;weZk4CO=WO$i==foa_jyEdeON_(K!gW`%J0KM(u@_ zVl8_CXQw_C`ld1#&T%CrVMp?5McV|Avr|02e<_Hq+FiAi5Fm>NDmpBLPs><)1}e&D z!l8kRP6%Ol8SCmmMQfOVkUArTJ=(#4LMI&jw-{H{!8Zjf4oSwE@Q|tHA7ry7da0s=C?2Ab6$3bIh58v@uFs=yV z-!eMzZwfrLEa%eaQ4oP&EKYZbB7y&4+#LhIwD1-Qd`ozv10Tzdn!-G_ERP-y;#A?X z1C#&eLZ4QaM~`NekQ~s}-w?v=vOIcpU`uN#>L3z|cJ-iOrK=Bs7~=1K^b$F}%IW|f zF7RJEfJax70G>je+SA_3eL-?)1_mj>D9 z%P!rUgm75< z>nE;f4Mic5P}E=V3Re7e6Jl;NzX}aOcEYa4afzkl97~Rr=ntY~XS#!-fUheJwf!Y| z>a?(*X7>A1^t|-PvY3~a0fb8~I1p1)4085!;G>KgE^qySJJS=QUh&urdLCLFO$I5N zC3QPn*6KS*(Mu@?S;Gb?dMm{s2i`bd>d^ai1$GUu6?nYe!W+o#AxW=hH;bsBbc6;# z;f33md2}Oe+JRg_=a6f z8kxecIfcPV0MjGjEVN3tpO(cVSuBRR1%nh-5fiq?M@2`#?Z&+(!LC96+{JJkzB!I4 z`Cb%$&kEN-1HR~|!R-K{P=g0V;R#lF8j#;l1?7;PE&3BRwGOB!QXTi93Yj&zB z#vYI7;hp%is@G7YrJr7K11kJ%kjDS zS0V*F31NmV*e$AHVJ;}@F1}--Dvhv(Z=mc~PH_kBEwTob<5}lWvA$@(9V1xjw=P)n z|5lYoBW<5a4!Rkya4%@ja%khYjwl?NaNNPlR2mg2dQ}(whg&NyRHSHhG`t<`8qD>1 zX|(NsItaIvQ#?tXD%Ja>T+-b*KE_)ObBf(QNv*4BvG8uyZe11SR=yMKBcX3A7j?hZ zC&G2homjhscV{^VR@i$*Bj%iil`m3*>!BP;|E}UX^os`1LxPpy*$0#KDym#@ooV*zo^ccuR*)=&qguhAVFh(W zR8a*l2v!snufl609209Ta*mbL3f~d<2d(fSaPm%!M+iUa<_Z+o0rKC?m7e3v=qR0P ztnIQy`nd9{e7c{;mq+G{OQ~WI0)KpY1b&HUp)C^l(QRKSERDuRgpDY#$_M>4QVVM& z!bWOgum1lKmP+GoVao=xuv8kPrM&}bL4TUSCHxw$D)1vc0cZPZ5Nj(v;{UDfZFvom zCPd~Pv&)C)9dpVfKqpqYHI;Ity(@m7Q=TKOue2mb8s;5Cgg&=C=gyU|gCk_L5ayTX z5d6G~%+scdgrX5LN3aqhry(Y~A9nGCWLV)QO{0mn*18iODX+@s_=2I9bi6$FAs#u8 zvW2a9OUYE4WJ}N`zb?;N?*Mx+?%)3^mgNeDXtHgwbo%iX6mHw~6OFHcG098~Z{&@y z0R7=zkEs>l)kjlr_3AAHlyiA;MTx$Sv{aYYUjJB}H?;zLeV2GA;`O~kpI?E!-aHi} z7CQd7LRe6Nz1}-jy#9koDC+fJ1uI@(4>7tQUKKgBw1RD*umZ!WGs0}G;7V9Q3FnrRlqDHdjE8e=E(<)lHCEeNy4St&7qihEBD$)rRh&0z zg+Ai_f7+=Z;7zlQ7ZdkYkmuI>bO2zsv!5d?AJ$35KVARNfA@XVA z{kj5AS?W8ZrYyKNvZ>GyR7grnP()D!Wh>#mWuR;)B@R~Lf%0w9drRgm>}FMHx^0;} z>iDsOUTXfxp6L(g`~7r@t2Yi1lC(RMu5#U%1EZVkCvL6J-~o(xSz^N(-5-JyR@@&} z4pP)j=s#EBIeRamk2Pe@PL2cEQxUFTG}mz9npDVzj5bcVe$}CW0j}=6*+%a;3Ys)mNQV%KEW4R0(|eh;}YOE;VrTRxFoz$ zO8}0an7eeJ{VO_toOj(8KX@GTpz!`(fuHHLh1ncfC56yCfdebyu3G}DiqQKc+!k21 zg*Pg&u;uu?l(<&G!j@wu{D3XT7X>d{jzQqD<@mDTWy|pcZCawDbRo!=;GPIDniTZk+xrwjuG3ioOo&4sp#2Zx&O7v&Y{6x%! zqVk?d5rbyi(zUDwiK={-mlm?9ID0icby~=>N|XTT&!B}Y?2$*S;qZhzKU?T!0sH;5 zI1xi;#cI*p1fnjL=Gay_w5s)D_IlP;t(rl}SW&tI*?yw+tg(I}5=+_jthWu2cpT@> z1RsXu44^P?muul^eIh<45Udt%H7$;xk8MiawZf#{V(Z6j>u5k+lg+GmHsGM2_OimS z7z*)2kDG~j=8wHPzK+gC(1aUg?wvPGr?HvIRj=u}#ujJPE>gEs?i){MBWPrx)srEuJcO7v18iEwF6y41sleQuVxux{=6} zs+M=_84C|0ko20+2Utni93p)$Te@I}TY9R{QQ#34Prb%NJ`WEeA&jzk>a`69V$Jn| z=qWlp3>K^m55Gf9R3F5KO+9ahI~ei>f;87Q$EJJ6JWEA&g?Sb-&Og<|u{_Tze$Ye) z@@E8Ty@kV*C2F82(sB#AsvH1USgfBuVg3|gb^j_kk(oE<>MXUNIuHW^5=zUp6w%<4(zh@%_qAoq=Ei54N69j-o@3JU6ZeV z(8={n3rT5pO@o{h?^cxP^|iq_M_bWxOP^%oOxTLP*P!#j-TLV84=id^O&)B~O@5PB z*==z**;P}z$)AGfZt`t{qJIQ&)UvK7C^`g23@&l^=%D^ppPV_PMdLM!dxi3Y#jjCZ z0mbq7U`3^baN4r!4ptOXOHM8%3x2j1b$2ava_Fcdgt?mVCJO4w^*2$f`P|xfLjTLs@TptiU!q`4#4iYZDcXfs zMCt?;IoE>dxGB7SlQ=pCqvdeq%~?Xn6Tf7Gz-J(D=_W`hwy5ifWNyk)4Be$A_r zP_F90`v?@)`IHc@X~GT=qI+YuTd%RwXhHax%A_P-x=dzg#_dCisO}pskV*?}jdTVY zstXL$1&-fRz@Hb$cGDu;WqH=LJW2QXT_gU-e!bYre%4OgIm z8|!~kxiv|;vMzzwY6q*@=uPN;?S-&DiMwC1+S2`kf|ovXKNo1QAse1l|1>3T0DLRQFp|;*Q;Kqws#56xIE92$DM%y5Al_ zY>wtSC`jf)_d6koExHe!6^%u^pFY0SqK(|)x7gNIH};&0yu&%SBD&ghsC|>ciY|)C zMHS<-2P3=CGZ)b@xfaNyTD$4IRx$~{9ud+$kZulE zw2DdiRZ2)-fs`;r(P}2)R|O&MuNc*rf}U)Dz?Vg9YzN}Bs}5Dv4^<9T#K`qpZF#7o z!S?~?1W3Sv05S3%V#kgKnCk}>6VQ)p^N`)A62|XSwfR|oN9tXvItJIoJo=!LzlNR2 znS8IJgp6!oi>xf_$IQ;BaBjCL@F7=r%TRzmtdu<3q~xb~;tzv2S3*KB z`K=U>*At?(wkwhz{;s6c!{4lV!BYla6CwJ$($&5uee{$;jyH!W>LcRDBwL{&ioSTt zAnUy$ihg>^p!mK+6kWlOkwX;4)Ik!MIz-W2DKkFVS~o<|-E|DIHV;u$6&!`Z%0`8s zQ?9dxzo{@aS=%)=86Ig+#~|m2@M^Z7<|e~|uRx6B&6g8Z`Uz&>ewgv zZ>n<^Y7ab;%`w4C>gdFRK=@e*k~+G9Kw8InAuZB6dP5d6-9LgC9YcYKDDytub415@ zh+@J6LJ%D@Kp=9&4OR4{kai|prG_e6#H2{4j`mYZ7S4WUxH8(id5xGx>uqnTf)pak zRpm3t+i#;uacd#qKg#EMGg9fp$hvxCvMTTQ(`{@T}yT)t|>>?I>KzECa$-Dr3;DM+GAa?#9 z7y6&cytFgxBAH-Uy{r%}CG*m*QeD~kt1J?VF70XwR+e_HA%^&AS3{9=HCfLIUK6-) zN>s3>KXAuj?I^rcQaD&c!i8YvZ#Z`s`n(itVX~q(>l(x#bM`|$ZzYTnjzuZj9TNp! zo5JpxCuZ|{817gu^mQrhj=?PG&_G2W3SmPEpOoCd1o&yI5H_W-pMDMdX`hHL>Zikk z6+b=x972X0>SvL&Q(JaP7$cV55c;RuvI;242G}3eExcPB@ceVqP!B$+o4Tk0B?T)6 zyah4DS)`K4Ih?|eqpLp{ZW7r&d{*ekbYnCUIyVOH?rbUapHuiHL(g;au5$=K@RH&C z_<@%U2SKSLaj2rVq|EOr*2_Z`jRnU$$i%2bhoOoF3B%Mn*1JO$ZFqWZsz{=>-a!#>Y*zZ$;y*$~@G~VTEz4q0y5`vnyj< z*aQI)*9Y7=ZdcIlr;lw*r0~khDy0X$g1(ZAhjZyFcKrT3JN{uYa+P*`nTK>oh&Vn` z=&QBke}xU~_{u_9s~sQvyg2?TkxR(e0IK?t3}l%5c+W+d`QEZVcCH zqtq$ePn);_5}(%xDz-8q75cFu+Dz=)S~*I-$D4uSOP(fevAwQ`=`Sm*mk* z4-zm%Xd@E#RF2BJ|9dFh#UrbDOc7kd`*mdpPKmS^E_C$5+VShqnONz3Vw)qE{2s2X zFo0K~3#_~aDig@_(%%fe_Pjxf*%;t)J9W=0nB|bu_mH9le(iB$6$C`)^9DJm1L^nE zlq!f(ALQivX?7J%+`F-xonHvcI%eq$q@&ljU0H9J zToQP6l_b14Zd0)OW@0-GVy>e8&vu@+PLxXew zPNl83Kc$QIs-how_Nsy$GWU6d%2$ZAbFV57J!q%6HE6>i^{s;E1v?Ox*r@c05q+!h zM&%yh(TJS|&l{ESLU%z7i>_I}|A)15@@kuHkUU;FP(|_AcCvlBt}c2OdxQh-7%aq z!;j!dr#>Nwt3>JFDtJt@3;g_Ti5p!0JMccsyg6>SpZc(bpX(#7INt$2(~X-D7o?(V zU#xG)-G>WOn{PIW$QcArh>oR}yxUH9y?2B;P6(3>_Ddeoq5MsisC=;c5gmdP zispk2!h5I+caE*Xg?zw`vs>tAQ<=UmnvU~M2ZesMihip4qrm@E;iszSABlRZ+9y%b z$NI8F>yQ^wo-0gyHDuXwYTM5)YDn3rr?v>c zx>BN-u3=-bfIWch+DhmnQrW`)34H=A!~uj1p^v;>W6tdwahY?Tlo*w&ecVIfiQ0nx zq9J0z2%#6GMz!Ze;fiWcw8u>0otCO!7hfar+*INVXg(HrUMhFSPajo<-wXZdWkvgh zK0np^`(;IyUNFd_wu)-LfVBgzoH;BU3sd!#Gd~EtCYA40YWe~mNYqpGtB78#zcA{C z0G+n~k`iB~>X`Xg;A5#AGv;GaZHyMU=P?~4-$&Q~k;lT35*gpn8fAp{`0cHcByywL zAKTScrNqfp-mdNm`?2TnwBUcO&F>@+*7hS}8VKc-Zm;HI3EHbLr3Ty~S4TH(xBVke zx~`yLXOyuQAYio8h)Ub>o!adQqj_p6WbVzFl$Fq?o`1dQKLz0xplo_fKc zgdhz_L)m{B=o%EA1JjU!9%_KKC<^K1w=50Q*~A9OIJ)oEdtCeVdbD2~KYp8Eu>fcx zymQic0Z_kzECAlb4=e!M;|CT1L1s?*g&R&pNNE_Q^C&! zD^tPM5ThTR9uhh0wSvu{u!569_)sf25?1iLh$^b!ieN>-C5X{2WImx=Xk!}Zo(G-? zw@vgSK;EEo<>b%~TW38{+?6IX>s=fRln`(SX}A8}&>-iNPbfk#ea6A`XhQ@=WTq|x zeO@>1v@MW{!0|M_v^dTs$~Fu~?{O^@bGQ>}n8Q_U$a}MNIt??1Y7Gr?eDa>6B&o_@ zY1YB_6g>lchYGk@R#7D)x@;7^+_li0U`Bg)rjFrHZNu-^6}+CNk~7`7Z30IluCoyh zp&3K=ziAkr+oGbiP*D#Js)~M;1qQ#L2318D8U*@_ptq;NRnd=TgC3$G415G~dysSe zG`uRRwvYWp&d$L4{WOo|90k_-K5)5yTE(0{q3|f6{C--^Z1*%W$Tmeo3;sW@2Yw-{8vkN zC2-%WoP{qu5tW5O|EDND?#U=Rdh5MU>R4S?m3#YRPwH6Z-i~!nywF!y<%1t7s5Td@ zB!t7dVD+eig+Y+%&T{4Dgc`MYsb0I5ty|P@)7GEq3b`BTTix z)ukoA@7>53@a2U(UN`Nw{V9vJb=CBl{b$95ud8uCYw)DDoyU08t(nljsm66{BXq7C zMyUS@eM7aVcFGX0s1X`|%n{y=)!5H@(SF1T)@QqVW7h}G6^#(yE!C3d zHOE&#R`cJT_^TsmqHyetwru9@mZ6r5gm;&&<%hzBS{62ho_uTX7q$j{W?LgZJz7mA zWaI?`^g}gFWotJwC_&H3e_=!u^nlw3Yqbr!1N6D=LuuST)m5@v_c{FTW>P0KXYZ0&EYXnaXz`w~hTSqz zl=ZD1)h(ur$|$$|+eFbk;T>9?FDqFopj=k6Nx)5!t7f(fW>|IZl3$9{NJYOD`e;o* zek=Wq(EDrpg=jj`;w5Km^Q^dJ{R#(YhqCeS-s~gFEi+@(fC_yQmNc~xz z4;V!KbQUYchYM1vXAQ6&f-=7L<`J>ty1pI$6ccb7wY(5|)!-N4&%tcWcdH6s&X!)` z0wB~Af*ezgX^fQs2rYyl$5ihIK`Vb#C_QV$<7)axnNm2vz~UR8++zzLk6BPd&n_0! zz{p&#u|dfg$Z0_hjO4W%hi4laCZ3XZ6eWvm@RYPsW0{h^jvttkX5t5?q`A<~%cDFg zv%H30io7TA${OUrQ`UY0ud2aMSvxnDr>r9&YS$1&lknrf5JhuXCZ6Wblrn2-@YCGo zD1)cD-+~oSb5G*O$RUb;<1*Mj+AL+()!^-;%S^y-)0aY6Un9KR)T6PYZw32d4Qo|n zMa7%QwQa{sDXNPr`(nuPabrbiB5=~l#+W2Jerc@ef?y}dSPwT*^k5S-;LmRm{ZmMI z;!UDbAVh^Kw-b&r?X~?VA-c1MEMj(Y!z48cH_T3MI7;b(OX@U{Cz!h#-xvmC5)e~b z_l?~(coL8ejodKBgz#kz{fPWtf%j^s`kTlj@_|j{5&03 zEo^@fV0)r1Y+`pnp)l0HX*jsH)I`-N!AskjZv^V4v*#x@t9P-}#HRdNlVdeGKRUah zmLA&8%yVF*^L>pv%&1jBag|3&&5r0Qf6N4o=#zzTqGr_4HM_K)H{3FGEfL<6HF@aT z5C-)vX}bk19J=-k=7*X*bR89`7`kqArmp4JwvIaT&Wbb7vNLzXU!91~*8C^gPW$1N z_H>S`coaa0&NFZtO@A65$oA6(cKk)IDNaFN5Nm#^Y5mni(KRL@9IgoAqV`Sfi-;4H zxG97`bcwq(p}8VQ8Qr@t)U+-(SM<<}266r`1&*Rs%M7?YUOHe~EQw`SEgj{vY9Y!K zUo)%%i|$`)NxpbgTE+V2=H=Ql~lk>n8nQEWDEzqp2n2xwUvS9r2>f(5eVt z2F-~O$X+*c^ho z`O`?i$pE=TVO4w8Mpb7=s@khIf{GGC^g(Te=aObPKSRA4*aRTkPXm}~C!qE;gh~5g zd}L_I^fmE%LaB7n_PadyI9x{|T^_D8n?UG^^!peS)M>J)G*JRK^NOjiVQCY& zEZwMc&jgcZ1Mqjurki!9^fGDLOE}nu7mJN1%rZeTDFsCfr~G_Oyoy;h#F>*q5ZJK;XVlTfZhLax^z6{!)^n$D1QVfn&9B z^wS*4VUD`sDD-Gz5{?i)pOWaqXXF+CR(KKXyisZJ|{ zoaN#buHEWLn~W*%;~^wD)4=DUzgSYkRt6>bGw3e^CxlzILc+)ZgD)qOuCbMGfW_<1 zr|S&*!Q+S>rf85jbDd!&4pa1QD_PM@7yLv#UP~ROXt5wR7}hJp6wPR5kkw(BqEA~H z6rVRt(TQ-;?ZP=-5#M36U*H)69~F49z^4UXqR4S?GezeFUZAWN%@kdPrq`M&`WHs) zj&@C0J~PPCZ;~1`JcWZ-^9vIFm$x_1=br-&W4XeR$MF+swVz{C+kbLL7n&S() z>8P!_;+!!khI7WC803sU;T&v5_Y<*$WAq1%hYCC_=C<`wbboVobm#dSbO{Aq^quWZ ztzfKHFqRcuZ*7osOgX*eq46=uG`2Pvd)@gofx)}mz~9IVGeqh57~;Av5_n<^*Y)8x z260_Wqr@HS%I^jovwbQ3e~C6_37cZIF(^3~_p;NHn6xP-J=X?PTy#UVBnI7`QbMlm zAfWv_O`;UHFAG2cQ--<%^5*5f0KvOrkcayK#mV3r z!&3lzL#}M<8w>h;z?mKz#n381ArFmaXh$1^oGal#F0g?G92Ay~44nYWez16G6J!2h z2RTmxhtsz7Weie^@iIR57!1+ZT;01~4mIe4u`tXHVn&RAj-rG~ zOu(>KB0?a3!2YDb7h`nht|0KP7#;rA1pYKeC!<#c{w#)*(X5xH)4nbENHS_Kh%aL} z8GZD!B%`lhmSprxxM)w|3@4+30*8~)D1jGCGMX%KI2p|rSd!6oXhMFlw>2=GMXy;Y zEJq|@Hwb)62kd=qC19UK5f16?!f{S>B!)SvG6!7zrEv6(W#2y=A$(hu(1Z!_{qYDv zr;;-QPl@GJ@)~p?m3Z(2sU*Zj|2JIG1!3J5V|_4O(fc42cC3KQo5^oid~X|Js)A`m zR6!*M-R?}P*wQ=A$UXCckR)AyUa7B4SZj^K!QmmhcD~f#u zJtw22qTWJn7(c3{qI(z*#(zcZ+J0JNCRemVNkx{Z+!{Tn54b~t zt}VO`>NTp@q&c0k4OgmQ1`*w@lC!)-cVBqQPU~JVuya|3)ZKWDL(ZB|5DIwQLAnR6 zx)WOK0qG6|ygn)cwx6Mx99!O3)I32Egg#&$dSB5WOo*&r6O%%ojE=#VyZYVo*3@aX zB9@3s@iMkl`L1@l`ih|&zHNdc(4uc#e?#0C)$mv!W(aE;-O?QdjOT{#BxQ=aJTWQT zm+4N=%WmS$uu__MGn)InFX%_i^rP)ZrP!`HN+o9#%~5N*+9~c;gAzR644SKkceB$I zuNvg+l7JlJ&Trz)p!sS}H#^mO6`k{s;fltJ8FQ6m%X5kf67(~}VX;Jxnj;j=68d~i zUlL6pJVMbrp|4O$M@J|k>&Fp_8oz3gBkMUuTZH|i*rY|zfx6~7MeU%RSo7ozNOG#IJqn$SPibR)6QTZQ)K zV};cq5a8L(&$f4UE1gj)nI}?oM%~xVq@Ax~kq!v)c>p$M`{_4@J(yE4(UFMj+%xHS zg>}nsY-6vd6_rTTA&2#L*>;K^7Wnt*p5zK-2k8tBWVa-u`YJhts6G^3g4)hR_1m|z z(+voX#D2DqOP%H1iegQavm}i0P;(Tuw?oi;ny08rq9QP~u)fbzRH2shV-6_UI9K@kK5Zh_;#Mx(}jMqO*IrNYoijp^eXgpYG;u1d9CoLm|+JP3M~k^gQ0pMU#=&kMm74?_D761p+nTE3_e^=zu7*s zYua$75Hj_ zkSkts1%h7wl+Re%pYEGQ;ZuLZ@B6Dc9fPYe0yDu|EtiNvHper{gK-Ni}?;20bI zX$z`?iA2;;B?t0}8eZ|)Y3G~RANfGhF-sAMFY=v0r!7VC81DH>puG^RuOvP%Y|WEi z>4Uw7|JIk1#jo1_WgDlo+Lo$(AfH-6YuuqX4RT&W+vL(~IFfM&D)@1pk)#c2#pk=6 zN#V&F=_$kgLT<}bhR1}ADFdJAC?n+8Eqo2*3QK*VyrM+Gzrn>SM;F66?;1jW>pv=_ zZxx_Rwp~&gxAvi%Yx9;t&S#SpB?Kr7arO`pZGgxpFKSonEu<@civ5mg$`S`MGFy-x z!$>^W^a?VU9g!y{PY65WJs}6UM*W1$j`-gPibe=I&+%tfMU#_sdnWPyNr`MuVbft@ z(e+hNs6AE}^tWxAQU!B}=zWzOBzk|&W|Pw1!lr1zmEq?733?y1%;(-R$T2TL(cC0O zD9~G38xs`0{FXu1js!(-vZ6>x7SWTE?PFNtZKwPJ{3@Tr! z_arBP65Sv-A~xvGb!BwQ^90^d)Mh$X2Sv~@(n@O91 zz*7ZJjvKhqQ%pJl9BP;qaOJv#K%=6+0v*1^Ym|Crib=-X24S)?nkOrd0t(VB1z(i~ z5b*gzG+VXpWv6O@Lse*w!rpPCw+(Vk9HnqS2|I0~vR02$)c$RQ;x~^{H1lm_n%$!m zeI#z3t&+};!p!l)C`F5y3;|yWdA%0!V^~0`cOU=<`_Buc*QGVH_ib?-X$pOcD5WktUcc*q?M0td42|ZrV*k?_+RKE_O-Nu3%V%$*gY>$Xn~?eJ3(TTijA7h!7`qEjsGI+Xe8M+VFcgA%&ZPYjd* zkWN1{@L0xwwXfFM+LPTrME^#V-i#={$x6#*7?j+V=q5``foMPViiKlp0q8;>Fz`Hp zbn4AOvm#1^?$9eQ)obp`b<+*oE<+W}C8CEE?#T>ifH$L-AywI_NA6DBV( zUKFB-FyHDA1@1tAN`X2p1CPeL23hLkOFys^Iqn~=D6Ntrh=14-Q&&-Kf$rmT;m=jl zE8zK@VR3M{k@kF_X+R?-pD|idzVP2?Z5*xWQkcJ|@IT7@v6;wSjvb>F4HW+StrMda-JfX?+cQe|A7lQq zn*Z!*MU#d90V{TlqSP?|Y~g>L`JdDLca2fBMED=H62~ZN5$0bj{7*3dTbe(0jH1oL z|B%&ejH28y|1RNwlKJ1${B6f5`bzj8w)|rh4G!}k5&n|QKS}d<9i!-!@IPXW9;0YM znEyB7FU9=pHGjbvMVEyCQETlOMW2TGZwP;B=KognZyuw_o~+yVG3&?}ML&l5OC;;| zEyMhOY5r4V6g?{Zk6Sm!D2j25|H}%0S?0gb&Fy6$tEi&zKVg*}tEfzvznbvJGk+D$ zUvaFWXN3PrtKnEhb;JA(g})s0H`n|v#wuzd{3WdpV->v_=5Htb<(WT6^9RQ&atVJa zYuH#t?}qt12!93UAEf!mk5v>D{?gXUv5F>!`QH=%B<7#5`8SMJ)K~b+Sck_dS{LRY zCj6C{f2ZdEVXUI@!e7?9K335;VgBjDpUnKHHNS0~q6NYqZ7xNUQY(~Gtb4nQoM&4*@)nMAs5P&`YA33Kxt^QG}#e zjh%W!7yNvjqI*(wCws;EWt^hBvJJ8>j#KnRwzS4$Qm`r)tdNb?BQ9A+N;cz?)x#xU z2$xJMQnH0E8Cj*^0U27Rm-$(YD!_Ym0hR0zxPd1MU;_JAwn5ISDZKm90~==9km0yC zPSKkwir>BKr>uL%D;l0{P`QW4^MF%!ydt-d2PmulcttC@+^gdibr-@gWd+A8`X<|; z_;<%Ex&n?u<8Y^($M2>O%tqR_FAe4BJ6_QUDZj@^ngw<7i^eO8X%DAFYL@A1Fr zaJzH;A?j^jR$9b2hQ5#B8w0`TY>%6iLtR2d-xvthvh59Wjz?vE9qBO7OhfW&3Hvq#g0RU_4Rl~4cZ&}!*KIc6g4LT{>7IRy$(D!h?k`({)RfPE+EIT z@ru@pgu{mQ&v-?hz=@FGCar35$oB#of%l~}rVja$Fa+~a2JQ_; zWgXPIH{9znh+s z+fJQ<$Ov@-1Lkx6JO(ejSM&D3I6y&nh%e~H&OtYm)_Dv{%no|8y{;UMKOVDcok`@d zCMbGd>{}f}jwTZnH4*q+Onh*HqR&x5M~?}LUKPSeG4X3ADEbx-IXXenJCzkRC8C{p zIE1+J2;+tr4yS-P)Zzx~Deg<+;F+jspm6^bW0jex=p^(N_9S$}fG?}L&)djHea!;t zNxfsmNxfs?q(9&j?WEqZaMD#E*h#%(;UuF2x>jX8@S=gd5nH?i);xU@6-^TZ`o!vb z&J}n-EZ6g~4hC^OmqgX`&IY!uPyNgWx)~?M>KiC0#bP*#hyK--|6Ux)F0|M zh`PzKQBqx*9rA*qHc?I1-@F*BB`*rseG%8aQU@H)@e?hI#l~87fM{&p(zGacVIMm+ z=n&pgT@;IupoE~G7PDz>J76gl3i@dk!|gRpD`G43wNns=Z-wK6A==KU9_+bF6BT_U zrmu;$T2E9oxPw9QZ%kA)89%ZnDw@Zv!HJ5#7uFrI*2Ia5Rx$xHeip(`En^dkLdKW) z0U3wETIlcFJd;KP%*_%OUx!`uHM{0Cd=c~$ea$ud6CgbGHG3+iBW4D{7d$~%I`+H1 zX3yN;(ICgHiHiOZ!w$w;A5T;i-w~-LlHr4%Z0xn`XZ)Tvgk?Gqn2q$bVMNI3h_0+VD)na69H4Uj)W?R#T?Q!Ao#X22&eXv{ zBW-BUT_9xTxUz$^#)b{oAArblcXsE{bw=EuW03P2A}}9s?q&M2v9;;T3%%sY2>60D z)P%{D;0e|bxPtBF?%Clcq?bh5I#sa1_T-`rEnx1n9DFj+)rCuVyxFwSgzgq#ZwK}a zTE>`8IdGn{BXH6DYnl2U#AMJq2F5e1WA{Wwy{qVa`k{^c-;gRg&y29k_%&AOTWy?Y zrdH7zU+0;{RdjymJOgQKg!@x1ZGW^h(2olJuuW?}E%0&8e=eFI^MJo2bPMx<4|5DX z6*!IxIE+b(9I4v=Q(D4(soMStush2`NJO+eVI(-4xXn_`1!i z(M?fIu0cu9cT+@3&G0wAT{lG!<{A|5>ZYi4E)pB^a8oIEQ~T%5)M)>7OpWpn2JuiK zH^%M{gu4>y`z;0wxu>b88DkdH=DXNgB zU7R8jT3N_n>lUabWF4Um)1q1c18fW79%r*AOiPWp|D!W8%%O6LkkML zS)8_0;AH~u6?l;}!l5V_jc~FME=;glu8=E7QsE%;7gJ~0Pff|NpK=1~pNlCp(WfTn z&*K0gn|^8{o6ZG@bpNS|+_^J1JOpYoIe$sx`M@a?BlNeqdMWq|Lw^EFr{B$XeeGoT z8sxNB{r=+EC46UmB#t1t%$g@5)RiR};4~`7A@U{uLGCI;w^Eau^p*X3|p*U2h ztq+p}KhdE$n2`w(v2-X7u@r*PNM#G_Im{d(Gm-OQap$>wAC%7^IvhNb=Q46#3GqZg(+{By9@Y*1U7&piP<7Ppco>&ZWU}EtJ5Io%-2y)jOXSUW(o?o0Fs?RSzvag+d;=BdL z;k+(z9+nTKMeM@jynOiJHnBmx=QrN`%1{OKh^P^ZY=qz#><{;aMuP z2y)g2hCTZ#kW+wk;Gu2PYKkE4rSO6t$X0@E6^(2s$k!ssrxm#bnMsZg6%=(4uzius zqL>sElCLNU>fRGlH<3){nMsN!n8~VuO#5U-6ZvN(8|At>x`R2M40ndl-<2krXAM;_ zkk6kdyF)}34s|nWuHT^KkS_;kmntB5mI09!@Z}bwJ`AIXDj=t9jrfs4N(lLK-MN&2 zjB*sM27M9CO<+Pks)?cxbu!4=w;DdJ+1VY)!YlR`w*@(-l~**Tnj%|ZRVFF5= z6T&fFq7?{*%^Rs7?)|3%v#(U|52cd35dFdW-v}5KuIwLNS$BZzbAjvABVdrDWGh8~ zi>eFCs?$o*_<%w2^;;>L4u((C6x~%_(c?sP!e$*#Q?!6>aeP}_(F4`B`4<&WIUg6s z88M{mT~_E~T-n<`g^xJ6ChNy~+H_&Qd zp&|gTKvBQkP*V-G+8MxnntvK^kW$bfXO-%T61vb#=6ED%$a(yy#HQ(nb$5A1Wr7Ad z9xkt_UUhx!VuG@8{38_wbS)Z7iCG4>UOiCwnSTo*%rp3~Q9F^)7wvw>TIS&XXqw7H z2I66fuI3W1X47C{doAH=z7sT1gB4tZd;na>m0ZXEsG|#Y>`H5xV>}y%Gg3Y=Y?ZF# z!f+km6$_<~AEAJ*;|Ee=jjrQopl}@r3*jSO$1x(|zv}pQMvmJRplK#9|3DSD=qhgE zDjtK)Q(&|1gxk5A7l2&@Y!}+eH7OY~$hi-guC$9WbwhBfHtDdKv`3qCBFZGL{m(+* zp=;kdBrSYi7^U`J7RkNsl9c#V*M1OFingH6B-71*OdX+z48H2~gm;2hzY9252W${L_Ncx4RV!C8w1CDu^n5W?yh>-idrUhXV+a6Vj9ODK9pW@*976`6jB(czjRa(cyZ zLV6c+kdSKD410tV(lA{Db$?Du^wxE6E{t4v3?8ovy?+cRtQjcDgm;86SQ8e5a7&!? zh%&?MYu91E7Y`zETfp8J1opbl*qrp!K7P({8c=8YDh7dlUp}5v(S8oD+WEIdxu+(! z4sleHe&V2V<>Lr*SDzrl43n8N6u%iH~rbuqA>Mig8UPy8rRa9!Y^bQGAibQ?yZ;I3La3Vzm6 zkOD~@1rG?}td4>vT_g(1iiDz3P*JcF1(^^-JPOwqIpbn^6!vwIQMelfViayxtH>za zR~N-7+*XQ?*Bd`?2_uif=(E{EpBu}4b_7JD&(4Gh$Jla;@`U4fjJ2qoqD@^4vQ}Yw z0_7XaDe7HI(I6rkp)7oe)7h0BiJ@kQ6rC3v)tSfE3Wsi_Gb3)N3h#ng?z8iRi{lov zq2)rK5X*h`VQ66`9}3}y?jm)%N=Dx*5+tKHM*%%Xd@d#WYE=h>kyW9K925G;SneV& zE*Z)A`uT$2=kbQzfgsH^e~}5+5Nz&exV(O%A-s8?-_@Yx^v(d6_O(+_fUe9AdR;!m zwzsdHMgW%uj?HIGn%LE#gj|o;PeW};!=Eth>f)zi41W$--=sdA5#MyxT`9#M3ed$k zydT70tWUu#{bC#@?&SQn7H>?A=Mu-e8sschTTwzfO|hX*73*e@vn4PcXd3f8+6_Bh zewtycJl~{vhG-U(YJmA&F#G*^wA_|F+9Y>3G&qrCbR9)~YU{o*#bzz3qo@mLJXaqk zgc&xTtoLFuFqwlw(80pm;WKo@K;RJT*bck#YNRu*04hblDyrn`b}`je&91MH{E z3^W6fox$&W-LRqYUWLSap5pep$vo*$OXSF#tmw(7v~Q2ttPztHh2AyDnmSq0y&z%6}*A7sroYwE@tVCu6C19Q=`I=*Hm}6OlqnmIn&>ZW- z9G^ru`UuBD&GA{7<7;pfRxsGro1yzgFfY@W9i+MDQA5{qpVV?6*YZ2mGB1et9ry7L z^{+rgve7=S_OW~sIM-_z9tH!V=qzOEk>G@|Ts5tB z>lW+j?0r3-B(9vld<;r(glhFpg58W z6qOdjD!cVufucS=42o}4ps1jSLGf=FC|ZLbo&rUma+#RAic+M^EITjuYt|`zzQnEe zoX{8Axz+YV0vhWV*p1xQL>Sha+(wrmnH<3aMXw5FtKAw}pord==5-5xg)Z^P`&_fk z7K(yG*=x53wonxRzClT&TJT=-v=)lqi;%S=`U-qZJE9H?ilj0$B5y~|H-CIsH}z$^ zk~Q2FZtR!dN5=qDcAhJcS%qfvmVP#){j`~(?tpwbnH&+nFlq@<%bZ62-+##U-w6PRb7I?UbdPd-$ zTGXSE08!=e1EMP92c9%P1({K?;mN@pd$homh5tlH)Z>6`Uohkf@au?KURr2&($O=` zffq&5zBJ9zte>44^fD+pt24mY_n6eYmq7_xUYgDiYF_PSkYi4PqK0DRT!*!(K#?0J zeOjQ%iy!+66y^6aDE?@HqMk*K47Lw=yd60_X_5I|jJD!ySb>8t{6uFUbowMX|8ijN zIRmJ0a9(26;$E03K8<~SI>^tN)y`;!3Mjv$WQNzzs8SH!K zM7ORDT~-)RIIMCl6rBPi^2$KLf9&AAGUDl|yn?Nq@j~D2&c#|&|=ED3)R-E)8YJp<^B&0avlMew;x9q z!#;6MAb$fgNTZ5j4f+Z&SAg-lyM$;oBfTFWeGxf}Kf|-X87#3ESRVx@NHdvl5ipg2 z;li^R`K>O@>!_!m(fy>L7>{K2o(Yd+vZK&g=p%~pj>6dwWVzHr7-hK>+Z*=q0_zni zF}+ySD(rBz|K$x+WmEsCyKi@Uv4$@MynJVZ)M2#b>j>cGYxO#_YO^wlU z{#(pTE`Jj&8ojO@T4wIGX=!goq`d`c9U#pW05hq#usorZ_O4)ScUF!&L!a7gk=}vS zTl{<^u}7dv%B_pPTj8k1AoEAT&S7v*uE*P^CFQ{S9&ll9XX>jw{p>UlZs8pI;WLU* zcehTV+XU>P!LI}yq%`+&fd>nGPT==7`?UyoXP0No@zj@Gep+tsa%x9SQ4vo~QE=7l z-UcP}8~+4iHny*lMe+J*T zF^8{bxCjgn&}*5aR3F@v5^fIqRzYpEKL4={zqKX)FF2}YZf&Xm3wAo5MPkM*#FI&z z+4xssY%tU@*X5@z>SjMXb?<{oL&XRyC`Xwxo++<_;-^aryLKP;G05oy#!HvE_D8^^ z+pzz$Iw0mMaun;TXr#bfRD6T3ihk^4P<+d-ivGZlH@YgihO!(>(}Z=aBFB#fidG8z zg|coIC^GvRWI3lOD&5!6n+h9X0}-t(#+wQaIi0Z9?NaoT*4nDCXng}epfwx3%z9E- zk80KcIACCR`~d3!W`#k&3+owe(CDy1Yr+OyDQeJBl+@cCs;+j+6~)^eKY_xPE+&K@ zq)R*^@G*fa2)sM`I+kFlqc6P!P9D$-e$0<9IKWT|2&AF>sK?&Vz|1g{h8bIrn^dBo zLC$J*6(t~u{WP4hmHHw5KVDl=9Z^$Y5bwx05qP5Bk$(z|g`G0P=gn~8GAoZaJ4h?d zD>15IArU>Hl6eC2M1FrewdseYJkb-ae=+IJe&J;J1m>QU;K}SlPhz)ZBMi-_W2ZZa-#Q*SRRa$Ls5Yf+z=;tBY%fhMwhS!Mr+KUMNvah)DVhRv9Kyc4WVcwL#YCN%}|;^zc5r)peqrzT<|~C zR;RV*NJG1>sicF;PbFxrX$v zA$Q0Z3?XW1oq1L2-&ctxeHBdcLnQhU_2r9|x&rnBLgY+|sOZbLKT(38rgK*OY@k8T zj}T;jn#h~ zBy7+THpnp;2ik~^unG4K4!ih>wj&{(j`FX1&=9Gt$1CZ9K8noILM@n5F8}6$Up>8f|u4BaJV^yyZvGVwZ3PFp)0;mD!#%Yrdqk{ZK=1>CrTPoYoDn-L6j{zi&Y%c6tAWEq zU+@#>KN%N^wJeV})0>x*Lz~Po(y2~rH=K;QubWA>VPQAuJ5MOVmz9N6e^*%A-NUdm z5(>~&ekNHCV9=H2Zszuqz9p%_Fx()fE%<<+3cfR}NMNPAGJ{=%A$Km%v*=^9j4EhN zM2!@m)_mzXMF1N|wP0dWL%tWqO5x8rY&O3tp$agWd0QoCc!=JfG0;vOhhYlQ|>!uw%r(880 z+Y`9|Wd|pUkHN<~G)KWnL|$^#&rx(*zyZq9J4eyw=M*g`qRz}q^%Q}3GmeTns&!Z7 z5X^jKHSezIFpIsBr09u4!V?u01xFYZpHNXzZ~UlIQPBeYs9jOf-VvDPV6&*al=(!F zV_}Y>6oIEGay*`+=xKo`E30;nqDx#5MmG>bfwDT~D5@~hAhy0uy=d#-5H7a9Fh1Q~ z{4Q^1{cN9ZaoS@37^~ZQp`qXPT4{M}7^sN!OzRbKFcaW!qpO_;={`gpxAQYMqd-;M%HAflbbi>ZvOqv*rsbM3a0zhHy zjHga*kPN5st4|rDWKI2in>LJ|!RXFVPR@5x%0tV!PYeWWZYC{fsbj$57(PYOKrj)} ztXOON6h+fU$(Ho^dU{KGrk$S;FGK-yj7(NET}rHvOK7V_|aghq7WCx4(m6Cg?*Ny?jRJ_$Dg6^ zuiIhnyh~T*oSmz3&W@+x{opoiXwx}<1)$UzoJ9>U)ae{QD`^2FW;YojppYZmjS0K< z9KQ%4qoC%egp7`KgNqIrgK;3>?(EN?rM$1bjWM)14s(x#WA3n+_h--&X1#qK-Wj_& zaX2MU&hJp~TtD^XmjD)yGst-ZC=U(bhw-a`x%YWR2`Dv!OMN^J=48+amcM%(nx35H zz~t*lE8VUeH;lc-tJ$%u$7=Qf|MlLL)Gu3awc(wuajY$9Hjhq3#m+gqY|P z_>{oi1^zjXSi@j}$7v03hBahCLt)S(S@=6<`(2^-wA0+7bl_i&fm}o|o|hgXhB&5O|TC19N$l|I}PBwhnZ#tpgpfwdMrOSUfpyua9V;1L2=OA#DCY zeJi7L1+;?+y?Ho4G3W?2Z0lwaiD-aXU_Znv4Pn|Yr$avvL+I+61kzf!!9ozhri zWGj;Pu}!TehTXYO+m#TY1N`fM6Af~_KUL9XG2np1nm$!g1}rUfS(GKI^qHBZO*y1Z zImD*qXvJ*GAvUGA#)M5d#HI`ZGMsD=u~)`MtUJUu!MgAG*VKt(-3_tsJ8j*(|FkZR zJ~uP9btkoTC)v8?P#eNe1De zv7eNdKqN#9LEzotG6{06|Eq3MGeb?;<@980Dua!LjI6|;6s zRTMwjAnVJiiU#Ayp{a_-O*Sb0hpCEY;K#316)nb(D^nGHj2~*6qTQ1b$q<(y;)ZE) z2OtIF5(^Asaa9XM+_U%raZT_8;$FuOhQ+joJaKf`%UfCufU+>yey*Eu>dJ32uC`Dx^unof}U}^$WeTnqUK@Hsy$870@zt^ znxf6O+JFNwxTg8}y!7-Ox0RC79=CjF2Sctf-^Hkc@3Y86i(+H|@g}8E}HHu#tRdq#^JU4wlZ7n)uldOQoU_KdcdQ zfc>yh$nZm9cD&tyOn2CTugs5?4wKG>7IcOMlOoOb9-3`I*r08qF`o_ECmJIL9TakR zHt3{~VGuXW86m&V4f7!!_5aJzTbg74ottC#9h+l6v_yvc+r5$N>YDC;)z#76%$L#8 zok?F~EJLI8;cY|Rb^0hc<>-`f|Lwy!yHbJ|54J}s>_wfPVvzF>@N1n$if+81s2mY}XD5bj4K#cV`vwu~L$2&>cVXZs)8R;ED1LO5Ha>?!Hz0s0o$L=bCmC+sDV%x8cDD8QQ%*WoGaMbMLRC8 z_y`286jx>@*Wp|9FR8=U7?m6f5^arvm800%fKa6wunqI=S4Ha17~(p-CGb}er+NSJ2qZez zB`WGJs!qp{8aLAu~yv_hXDI zAEKYwy10u|4Jr@Ieqzf?T!U$m@QwsdFy!;oVRM(b`+~0F1(p#zEgZ`iVlco^g5Ql# z3}xdgq;4kNKh4lLR!oyhT#V6U%3Oi}(&y)s!H3ahNu+js0<*?6jP~rF)l%ZJc28ZD zfO|d`ytrpaL;wN`VfKYE^o$LM8Lrg$MzAAd$?@wnMc)fNCf16ZuBbW0KbN5BH^C1u ztWbiYH(?MTdHF{O1NA#!F%5OIAiZh^XG5Je#>Mh6&-Re$DzE6l2qz!&3_>C~?wziv zq?8yRYb8%t)B`2ZB`OIX8-}+vP|!-rbmzE3?w34XI>Kj#(6eX6>RvG;7QJHVG)x<4 zRxCQj8~}U|?|j}`TM9ks*wsbPidgOx>wrh6s4w`U9lM!erK{}K^#Ffcp{$OTL~xtY z7Pb^USzYOgm{PuVecw;OQ`X4~}-2sm;6FflKr(-b)=;~IJ zN&<2PvH}0l%cSangYHg%k#*mg9*)$Fv4~94^Pq1T2u{Z$O(ORoqs9W|%FLwS7~C=4 zAm>{RaURm+rBiIc&(ra|p)-)_dT6>Di;?Lf@El|l#1Y$Kb<|H2cxNmhgZg*6q0b%9 zm5`8QQ09zC)Gv_|yJI;i+%rQy<+EP!67^dm0>V+hTNu8NL_J2f{enFeOB~%t1^zjf zqq{s5J8Dco$jX_nsLl+7;`65~Y7I*u^15(NDn^cvURGplq{Dt{ zG3)P_6}^d))K*b@$R5A?amfvEnnbQ@$519+6D;f(Hc3eB*7;e+O($Z|6TI}!FIfOy?CCB9>7D%=MfXnhwc)}r*2aD9P^9DpAxyAww>=M4=(aN=BDmYC znP?-rJzeJd5ZID)V8k1c+6RYfbAux`4dLqa%gv%a3L zXv0hi*W-d;r#Q?1B)-H{RcH5e!f@2aasGL@;3dIJoc~*-AY#eZScltj8^_X-nFetz zon@P_Yj?kJoVM|<-Cy9quHDU&czxR2 z*zL-s?;|mI6EPShy2*j|-YoqJ<_G-F$KY88ITtlnxb}Rei6AWiJv^^mVj}x41JfzZ zG?tmRv3@>pTfB*GQrwQEo>Fw5!1GM)-Nyx9YH~8T24BFtDS{X8-i;X~JLPF1h<6(_ ziS{n0Ud@GJvB}Z*=xhnwR|PN8_l^`S3|o&djMD`xg$o7*F9qK%QV^4pKEg05axWMr zAwvZ_Ro5&N;!v~6f|r^VL~6D`2vW0^qK79TcWTTKy8m%=fXwAKn0k`1!9>ECH`}1( zU?778UJvzOJf>)aX!+8V?-KDSzg@6OFq>etq8BQI81?JR&xzi{?OttfJX$F)sVWJSG*~9Ke-FU4NY|y(2DtQ9$Vc1LuWD^52CZ9pLXG#-qpn zD-0XsbdQZ~raQzZaokbw5>PtoL(QVSj4oM97{1hoBnvpe&|_U4fkpodQohi6FA2l0 zJ2=lu(cjOi=gn-L=cQBT9J|iE-@%DQ-^C#jj)Kq7Gw3_^;bg#JAAZL^oIMZcb?9;& zmS&p(U@3N$A2#m==*3>$KV19=z?rx`maZ}40uWfrU5Ud=E_S{_&R3c#!d~7Iesiee zeB^77;EpryS>QO8y(cvs>Yj&NwMVFHhiaKXFd1v|mP39t2hLobx(2wvLz zbykLiw@L`&?~O(Lji}!t4D%fv>|YBQ4)*T^mSF#}NO@%L^TM#p!E44WScILD%fcyZ z#!j$E*Nk4$5w#~$GZ^X+>`@1=Yu>;y{_r6RR}867(&u~zMxn{;zN9F@@2ADZ z@Dj}epjrTxPpg<|rM474dP6iXihfxrBVu6WWg+bTb`aiG#dvnValY*S?tnw;G*Z+_ z7{Up)i@?WlK`uP`z981=*Np~<9`0hdF5!Y!O3;%PYU`&Td2<*$9lMI@jI^s5GScDs zxJjL87r%S;!+hK|iA~zw#SU&W=>n=f64mz8e&%p2K&r9LP&7yE__~-L5|;?PSKze* z7r8#KFipa)7kv3%`pG;XqvQ#(>;zkO-vVvf3D*1gg0N*L*|J&-403LUWjXGwQ2q9v zoJ=~;oCCm_L4PtZ8bCf>EY^0rNpn!<1j=-vznJ_3YC~KdTXP+@mx@_QGZdWzh2!dO zA^a-!dRXAo(bobN*30Adgm{biXEQ@*ul~ig!}}M9!%HqS$XU8MzwkD+IOckl7Ghd) zYtvbgP2T$LU0yoF5C74A(;$v$T5%-9h6@c!&H%J#i%D%4;?b%vpB5EIP`eizdP$Qc zCeJRelYTXU7Z&Gfnh$&kN^ z;b9?1;DLI#GEW3^%>ExWm#vJmc+P|BuPu(PjCEZ5xSl0KD3$`9oot@f$ z2Y`qF1_)mKH%h9h{fB98fiQesJUljF6Jd^Ezbnq02&WgyCcYeRGz5^Y9`A%j#d)H>F_% zobkJe{8c-n3h;2oRl$ohs6})z!5Q`zy2t$;aR!FKy9IkiSM@oFLr6U$co_m)EFNmS5;gZ9GnP6)mV1e>?00p)adpG-;{V|ib-+liwr8C<)v>+yi(pyB|0Jovi-EZ1fB(VLo57; zhCFXZH9C*`OyRP_vD&3ac{@CAVA!Q&q#8$}uS=RNm5p-g;--pb3h&Wa>xZU_CO|p+ zYJm{G)0Z19m*Vi%|HHbf^t*Y@P;6fBSXI74?#sT0IeEeM)H}9gz)o@VP<6z?I5xV( zx!(5d4TaM{T>;A7#ZTk-DaSSV4eyVQi*3E%q^;hPWDjrr|_?gwUXXf6aux{ z`v{MyswO6JAWPm4vyw**ZfyIUz7Z+ecuQ>b%(1W@s*gA@9OBr;XGbb zSSbMM5|Ssn(mY&mPBbs>e%(^C}8sQS;c(e2gZL(#G%d*2}@g!Vt^7c@!ze?Q=$*33LP**=u zw?o}b8VSAmGZnc-@4MExnTlpWuVdy+ML7}t?wN{K0RMlP!>;(p?4dpJzBcB481o5~ zYh&JrF?++>^F>Qf#p=KRZ#9j40bgFoUKuNfj%7o0V3#&@EF0PlWOc%MhqA1NK_}=PdC`e$$(H@8fd)U%cMCBQ%?hD z_jjO@ce*$v$L)>W8o3$O&Iz;(1?U3LfBr#r__IX2q@G(04Sy@}0)re?XDK=@@J_>O zHcOFhg+bQKvlPXzFepA_mZFpua3PM=Uli8)G5X~TYOP)Rn_)Gz6xCi~5WD*B*7^YV zpK(@2OVP^^@E^YA{mh%@kK)kvG3?OwF$f~x3jKU^15bN~thmFQKJZJlA0CG0Okc}@ z#~Y&Ee6(r_v^(Bxs;IV@yCH_J!P?9OgvN71*e3OAB=G(iCch1#MEst{og*a600=9 zEqpr*|6@gX_kBCh`SrQ%t|EI!?0<;N$oAED<>a^nLHfY{z0!ie(t^KY!8cYIl+hVUtqfATV;^*Qy}xQ=&x9uYtLSV43kHH#p31LVPK#(ituvorPvNl?KEi>>Op)f zUHvvX@_&!DK5n6C4oLj2Y6~I!70d6ce#iv8tJ+Qomty%{)dQ>KUDd2M`s93P4mBS% z66tqU`DNHHQtt0q>+>8%ze8YQbodeI^#a*J>SO;tPJ42MP2o+$5qwGQKdW$sLm=Bv zG{S~MwT{)?oB)xSlUCyd7mc*l9A&3!0CGG?by+?d$NX(0xXdmzp5Kbh2a0b?YoXuR zp1@brj*6h^Lp~_87zn?IHuF^eV?ea0zpbLC#*NvX8bgCJ~t9zy!O|K}I!Q z1GhVHYfZ|_+Up~2@wm07?Haj1<}Lidr7+pxW!7hfb)07Pg9G=+bj1&_e!#4`vlKNJ z*26Yy&@4rR*BE4tnWbn6eoUFAXg$g-nx$wzeypCQ=(jZnIkwJHG+xTCwpqt#DY}dz zXJ;vTbge=0muD%ex)#avMny&63F`uz+dfXZAU_)O(r^3GWzoc*fXjA-_E6K9#*|p)$te0u> zK29N!MqwbKgRIKhtwmR%pSfMLQNzeQl>-tlz`_XXXa7;u{u@#IuMMj~KSa3b0~0_G z0OE5>TjPT()-Ensr~pQ%_T_iXV3tCw>Ea29yjnxvY$S- zZ#|Ar3L)*kAPyX4lB0Kyq8AI{TPhUMnMwof{bO}M8*Xwx8*XBheqfz+tKqD)GQez~ zCzEKnS)rFnb=ToC2anfHllZ%cEdcYR-k(9^xJ(96c>j(ju~&MpGxRXqTI?EWve|F9 z4UevpjDDok7;dXBDl-k58Xf^usy?B}Vk_!dhVJ@nW#R zQ=(6nu$jo%p@7Rz1MS^){g;}G-?duG*8hzf2mK`AO@KLpFc`PqAfDeXYUhC@dyzf`93ifMhSARfNNde40kZ(3(#Eq=7T1UgRQ*Nvss+8#gv^M#_kgAwg?uR zEnf+CmubEDlA>Ab4dPvwBW?e4*99I<U^b{F7Mhg! zJ3#Hbes;L7%9ZtaG(-JajrtH_>#0`6Rwhu@$Y!CX~=bmw?7-87OB&wdF1 zr_pfT0EN9P()4LG#D1lOuJUp_Pnwo((&VL9t~7YN*bDsZ0< z4NCEn($pe9bp;}nq9CT_+u0ezP>me^=;{tT-kF_&YGBHVdr7{q-`C9~jw8%8cG`KS z!DP%xzhI*n)|n^7i3fDN8jMQ@LXb($GYGo8Ec(ojX~-HV_2kiKc1%OAe`t{Nf3GM? zz_Q2frZ4Q>dzs|;2+iQ}y6G^hc>r)Qi$1qwqEZSFl+YnN((7x0Ik6mOc;H6{IX6M5 zKS2B0n>)e6FDz*ItB@^H$Y(#53jfF*K%4?NGN{$lkPA@CZIE^pU1U31fY zIi7CrO!}K;-?f4F@O*x{Y**b(se4Dpo|n2o^x;f+#2;ARPn*l&09ghKgNOP`>AP zX5O+1g#YK`Lw4qz`JFRo&h+vEm40>O#f+N84$3-zkRUYKzq8AZL{uq9CFoBEnPh^< z@btwP6%c9v8YU<{NcNno-TXn-1&fdILDemb9rA;!dytX`-cPAytK_q|pEF@{e?iYF z!df?v?tWkbULu*J2pilyx)V##j$(ATP!Tq|d31Nx5;eNpGRQu<+lwrjqdUB#@`Xyb z%gyho)H2{jQw05?2;1D=|4tD!Z;6Avg;NAAVU7V9E?h4&z1!^_HAT=`a6CLkkYlNX zyc4Dfx_YUDT1}ZE=$fTa6U}{VmF8==Z0^`6x#PJ@HV>Zz=-h(#!>7~c$jP{yVJjiS))us1SqcN8AUM{&PG6!}B@(h``@V z)v#d>>(t^cqh7zl8ha7un)?dBB+v!nYMSdo+c4rShkU!|UZu}G4`*lG^>&||WXlb) z<4jfTuXxyB9(qgp%kH<7zwCbt{sQ-WU9ryduzUW^1US+vMOdJoS=gq+ui1~VaFdlt zsxIwO_bxSBTkjF*o;GrpJuQ|w2;GwmHgAPsFismi=#S3ML9lI<)_dSyEiXfm{p}q# zP`loUi2JcW^^OOhVec?#8TuO9;E7{F$SR;vL8qzk7|oli5!3;UR#}CT1w-SpL`ui8J|2FJbAP0kb{^J@ ziPXaydfAEjKJ^z~iI#@PHcK^>(4B|~#?{sPWm@|>7d?u&T3;wt5lhfE4JUIZB1$o> zX8jkebWqk6H(1wUe$a3l)V<1~dgmT0UrRME!qZkcvLqQ5mbPAo^3gcbuE=I-ap3=nBpK5Y2^u}tV30TqW<~kQq8>z)gR>Jc(%nL2nGI#ThX_9!4Af-plZ5*2D zKBPLTovPq=ayh+++*E{y>Hz{}n2YwAf_FAUSdLq^C=2gtCg+|zRQRiAq+wrsuL}Rv zjCUlzxWTrs-Nh^Ddqw}HnQSZnGQ-^T{0#LiOYzE2elTW@*`9HaQjWJVOUMo*ktEuf z_1b0^wOE5y+E^qKQr+5)td39OR=+P1q@7uq8MIsDpe)yonEcSDERwCO;fvl2nXLl62q>zXIsRJ+C~*Uz)q zyjkZ!g?NI#;_%ot#tcV>$3rKr*Kk4J8)bL%bCwKuP~ktaY&xSW%@4-RiBp1}b?ef- zMxHFBA|p?RB9kEq>~!zRZ#?Oym)EEU@(P7J>SQ}^FqR*S;t@V9wNG;687u67jXPQ3 z>*6V*aVNv7^N?MPplIC57(%^iN_m>4e2*~9G3n%<m(@22dvhQXPc zA5$nP*zTg4aM5*Z9hB7@r2z$R#-8fUYti%kd#a!tZxjT%C42-l zaHF8!MD!mb?fR*LqKbN>clcC6K~6Phs-V#-{wD9MQw7zobx^CtQw80FA8V!x8iyZS zrwW>ECYhj;+`>s_fB{LC;0Kay#t$UfjU<-0rWq@mtWN0koV&L))(FAY8X;h={V-6( zH?)*DlSl9?#GZAAktB~=xg8aP%Y*Q?m-ghQ6bv2t))kW?;hu0|Pg*44ZO=ekc-Bs(!Cb+v^;rYg`c{#b32)6*L9=0H_PL{qutE|RnS|4B zlCJu;B&=itys^C^tPNe!cnygliCjAI)6vpsW{e;^T6Anf}%>?9!vUq8C@fY3}Jt$`6K=A6UveK;>h2 z^3>x8uulJJf@Z569dUSvO%wDD(7DfFs0hQI+~>b_ldaD`)K<`HMIYg`9nRgPxQ=@` zw=@-tm*~Dge(?Z$iKo|Z5w6qlSAX8WNf3fXq>Y*;=tC8}p?xntLD%kaLHm?|7c$xh z9_N#9%z8kv-e_a}u@S3PMBJPjdc5r3k|kJ=8g*2$2A$oX>y1{kMz)iBtb*$BQ`_L6 zY^>E%UGZKQorhq`7fe#!wlywlz0rXu2(i>#>v+LUl=VBxua;(T8}i#mbWEX$o=o{7 zak`J23-JyXoZ_JX-Ou4R2=lJcLmVzdI0E`u4woSuO7^eO`K$URx<8f((Bs^K4*@Nj zOjXf~-1`3yVQygc0L^BW7Z8<9`TSLdJ?S+@oGqiO=wX38EqVu)8=YUjn`NQQ6S85t zO@$W;;>w>?;Wq?#Y5U(%4aTXP9K?0L6@F};DkygoFwtNlEx9uv; zQkmwA%_=`_@dIft#}A~*N16scm0vtS*2L-Vi1ev7LOInMKHpTd*+E%f+{}~U3R)-7 z1Pp2dNVRe^+(`+yUWhgrfo!4c`lEeT#I`9YP+=y;z066r>I6Ca9|le!Qds{(cRP6nZ_Aj>;MD6F}U(!ln7W9;&jdk!T z;OB0UE$hR$7O|@&Y^Y>8x(>%a>`?7Ic%X>?&&U3{~sc1C(jwS!!g5~w_qMxsb?GkX41??~>Mm#@Cp%+?pmU9QHdiZ^Y_>=@SKxU; zS1Q^oq7}~BFW=%IK4)L5;$IhXd$mG^t>g8i-7-T^N=a%@+UOaA?zDn9egB{eUc%~6 zQW&g0)SP9KQL3*g^0Cz1q-aXbjfR?^sCYxogDTum%_B-uj#M+VT$(kfVd)oOsw9hD zx&X~~uM@KHJBu8hs56Ri2g(`Krq0VJxM)8@a@y1x)28pB9gK6j64OOWJ;w|l+vk!g zUm{hUPjlQ)D3STn-}2#aXTFP}jGrhU{paQH8a|be$(9XK4Uqcz;@;eNb&BS?4?9Ht zn?#J}8%&h{=L>Gi0~~j=l#flt!3fJkvMgY+PuHLJ--4^7#XzGNl!h8{1zi$+DS zEs6znD(o}WO-m3Hp=QgQV zdiL}F{9i#mIetlNLH$*HdHe5MbMu1Z`h!)Z&wJ6`f&yC|)V@7V>wCN0E$CiO3(UA; z3o_;-h_{SyH&s-xEyFffKVO{l@(Pzb)JcjQHd_*Ao4eIP%4|uPZ4tuKY)NEq9m2>l z$?z_OasDLPc7%(*+G>~%|Sept5ew?Bh*Mv`|@U@;VSk4(f+(!aJQ?GDRAW^7UlER-drCa zxu5Tz=@9jc<@2vTqKhw_=BA=;c!z-KV&r+yHX~OTXRgOy{9xRaq^^Ixh;Q)@rT=hj z)_o}S1YL$CNo0_}r8T}>5Wp|-4(lZ7e;n_;TF|p9z5@@Cr``;;ii^vZF5l88!~@P-wX|SLcLvS)`Kra>?xPMKW=n#K0a0CUNRqFsbC+@$4*AU zHLdSe{53p=_)DQ+48g_ix=kAKHZR8QUve^BcsoPI-^};je&Tq*ov-434A=HHsTAOv zwpt(&i_-%4Tp=Ceb^(XD9rki;cTm=)w_%a6I+4J7(-2gTQ@5jXA8IS;N+q(&HlZoF zO_r)Pp<&kkinZFtdRt>wxWN#`Iz-xGs0t>P*IT*aISKmq!e}T(uepV&zlw-17vy@m ztDx829#Nmm_mw8`AfCwePFFz--RBtbi}}nx@rY>DZGw<|iT%5CEF#Z8q&k$)3!=E> z6QD)zkxJ5fAte5KiG$mI@A4?_ob76u_8kw?T5Wg8SIQnzLf#epO4%iB%8RZSG+q(j zSL3f|RrnKg=mf4QD)u|YHB*JZH@SYs+h;2F2f^=Yb=|JMR`4HF+7iXFQxLfv-2}bi z&Jy+Be{~bod%IEb4;w~n*G=!G^9n=$F^W%5$j1cgou4P) zgnr9iC*6FJ0$RjCdz(mEiZKW+b1zeQUa3;9~Wt2wz5-p+x+}B38g&@YpUY_kT%`-38HXmMaxuxU)&K^p?3})2w!HR*v(K zQ;uC9a-u?hszi@nUW{S^4b6RB?@a~{?j#XCviPGa27Q0~~p8_Vaz(6r8tujWCe7gf?1 zIceVe7%l0wL82G=5{-@sjOAbCtHQMi@=ybNi7)bfnKAD>C|j?gsZLBWo_cZ7bjMFQu^fH9vahm5m zxX?vgpb^@&Iwk8>+}i0bXru}+kY)M_%yvAx6+{EH!7%p{C3^|W{t@YpWA0M4k{fwj z6UN@C5^m&#M`28(Y1rsIWs-}|-l1ILA*I_pC$dYiFHuGBxbeLdNZ76!;iC^aC;uBetUHoL!6%6zAk`Hq!Y z0_jlZJ67gB4&uS%@7Se3f-YDBcw5On=;RCdUm||QRf678o@*`!?64S1W(fKLj93=< zNHHF3S>Ss`dqwbb0e|dJ&jmPks%4C$ia|XWaMDf;N3jf~4VD$B;X=V#gJtJ|hrY3( z(Z&iN*Bk4SIKfU^%+~l1WvM^dQfKUxE&E|^`!0fNs3)Bj@?lv|I_}V~asVN!kVt!S| zri(=^Iy|dd(U0X>_bJ*da$RgcMBx32i7MV$aC%vV8~V}fO4Lgka_wi8EsR3mroyjh z9LKM4&ztArw9Q>3ZTqIO?VD`d?_o^b5+iyu1B(lP9k-x>)jQ@3({}e{9mNQ)ET@KRz(?`(fO4a2V?L&P8QL7<+1ohd4nuLexzE$Lv$IFS)JsQRS zo_ncM;$5Z0yR5`@yRe`dCwiAJW>!Kzy1bv2fbAJvbN^Bnx4OJzduQ)hPd%AR#Oe&W z@4K&)cHFDP?q#t z3S)xf_RBWp^-N>Y8nLIS*mXv1Q$p3s#7_4HCHl=w(Iyh7r7YR=(k=&Oo#W??%O!mM zYawFVAO=syEakZEP!QW0OEa+%@&z&~7bUtXT^47yKcx*$G~&UAtNd~q!1ibk)(Y|! z?;81z&^3xHeX)QC@1@xDqFwGy9#Q`W5w#Smd*ZK<|7a9%%jPp&r0wR9iVP-d+4Uti zox9tZNw$P5^X$0wx_K_j*^Tu@Y-$wX3}*>~p@7=htKV&I23{yP1FwSx)Vat@aCY*g z-SAVKh^z7of>xLC$+=Ct)sdZAzwJ!pgepNJ6<;sX7F7v)$Ow9ORSEi-;hvo-=t&h{ z<$YtOpaZ)d*nxW~Ow$TFWKUb=!u@=t=e@6@HQzn zb0@PRAMSBb)+Ur;A{I;0^8zcQ-#`Uk?b)LY^1P6Z>Zd9^#l8#!h1d+q65UT9xHn`; z&0ZEFCyG^SdYNH=fpWD7;bhNW2$|Z@%dF1W4-C7!%uKl4JX^pIw|#&ILc`TbZ2P{! ziywOt#RxQ=Q-6gBK6#j=IpWTj-Q@hxL0Jcpy%23=oc|!^qyU!9_yxju1Xj&%_z;6c z-XLxlkY2k)Z4h6n!pqeLaghqIkQ=Ij4;}KDR180`p*jLTu%Y@8Ks9U-SEw{^%QR1c z0~@!~@dIfV;|J2LG1J_o(yWzfwt@p`_TdN8{E8n)Lwn)iSezTL(!8T|eN}~5NsN>B zDqY**2QWJ02QdD_7|``&m1ea}QwR>oy$L^%CW0SGGt^9TNTpdL(~JZM(maVDNb?eY zAkBQFvD`c2tJ2F7KEKW<{FC&dds3!!o}Ji+#pre?JMz-KMg`i*Mg7iR!%=o}K~uIr zM7ssPQvLT{cnVIzCTSNFPyNV2S^v-lW%IYHKVa{<=Ob8c=S)H8>$1dm3-6CJ1&#m6 zL9LF?6toT;$J3Sey1!R7@Eb!_x$FDEM-IxZAo@n&6WPXM<~Krr8K*p1G5VH6-+ly5 zqx8MNIr_UlMhKs*{*A-aKejK{RK(>IBn3n9^=A6W{g_J{>N$tV2_#cQ&pB>=*F}3i zW<#NdtC;_MVwmbV2Wk@P)C~TW`UIzeeaTd>>Zrf4C(U%+Il@JA5zDijS-gnx(I*bd zY6I&fbZnL_=GfCdbx_vbh{dtY;(S`d(QQA)D`OS3)`2^H-8n>S9k{l04N|N}iev?? zci_Uuz)w-_Nb}4R^s6d`6%KFvS%Pi{i3w*_$l`fN67FIGt}*vfgtxE^6&7@z3a@kU zCABfkh)ZfWD#CgPUs9X!sk)?ASs@6Y{HtNOAr->*v2T2jdzebM!NHd)rUTIO2eos=nOtpc?O!^FF(b&k1uJT z(RPH3F8BilFM!=?u&QE=NqnTn+-*Fc;+^mfPRLo+Od3|IElM3$j_O zk&1FI10``;pFVZ(Qi^tBMLR)JA0%Kl(+QKAClLxoOUnikb%FwuO=-+c=nRnQZkR<) z|I9(W=RK}MPCPsF6l9tT9cv2milV70$O0f>3bI_q8&i-iDr}#EV8F9i#TW2o=!*(# zoMU-S6Lz4_+-rm^tQ*+|H^K%>k!u{4q8m|GpCM=z&W*_EPlyRcdBKzDMi}O>X&8pE zi8rQqWwA0eyQZJ)b1zh)lekjAW~3y>bIteR%Ss8NBp#P+vCk;rBpidXQ^6!v<7caV z4$^Y_3;JGJVYKkx)L&4}KGgBX1DZrA8nlg@_Pf^#QNM(UTJX53#p;*cbQxrGFVF(x z{ay$riCUn!9B69NqU{LmfhLo5?utn+IhHyPaglxaM13Hj2k1PQ3-_o@ zDXlAKzAMDdM{YUuT_J7-f~ccik+rP|C5gJi6rUoW#@Og;#3>uRUk^`Ttpd{zZ;CU+8TMl6^Rlz=99KvnAUyUTX;0Jp6Uig6?{<{6> zS22?4p^_AGlAFPRK|vHhkYqT1AjyNA+u6g{77OsV0tMluRGwrUKVwge3>_bl5Q9MLe=jZm8`JM(R`IgEpOie z>kjI@%1Xr=Wk-uCrgTSB^{K2buG`w_Z`>uKekl>1!&T{=w`aKN&M&aEfVU3M!CQx; zzcBiUbKon4x$On`^$7CVu#VaAF_ENi-P44W(3T~%g@mUeA&AP_7I}W@3vS-B;eM}t z;h?OVN6aRl*Gqx8 z2@2E_T?&!=%nWjnvXS==$a=w-7{jYs>Xm4T2OuU@7w2bzI-<3{?Mnw`Jr0FKbS(zt z;}Nj{5pqh}7c+*8m&cde*Dm5456f4gwSk1i5l8!mhyij1<8bP;|u z|Jp&?vK&FvLxPazBJXE8g0A=)g=KoSgpXGRI*n+{47B$l$Zi-S`UrZsZe|Et{xhF84`2~5%FzAR_dUl_hO|60L4-& zsHd={o2=>|?Wsq6(E$D6{!z%B;}un+AUyasQ?VdF^ODWah(_U(v9BGJbqIRKsT#z) z5s`U24u50on@00geG<#ns=5d|?{;aW(ZV~ji=gRDG0oISBh}3P=x#67x?iAo9V4KZ zXa3g?Y7?h10+O3A29$|3IF!n-P9$RqIjW+c+&Do3lh+H8GnlAeY@F$)RbQJ&3F{f= z14Q_vDKMa2Irz1Mvg0&~iN7Ndr5BjuIN%^&*?p0Nryp>T_QaKfTB#gP65iQY3OfG) z>JJF*6=8<(PQ6l4+XD`24-S&)Ix6xjl6+|c^5u%WSdy=>$hJ&Zhr$6_r1Z0Uty9`; zty~UV%SOvTfDB`CU@aRUc)&qfc_=U+?G@p1E^7S^Hoa*-vloBkpsYb4hv;+8r~ex` zcGm5POVR=H_&660N0PyzV6TuK4$wDZ-<>X+1wQS~S%StW&HobKU9$vz`;CLVpUe_; z^0%-I{Ps!3@qzF@+e4#&GGTHXLG_AoP)Mk$D*U^I>hZ0D@^cCZo1-|slpIlm<3Vs( zmQwGSq6zL_Q9-}BKhKcHdI=NtRH882FBYf{>qIZ{ynOt(4$6sDR3Mx^%te#GHClt0 z99{0j41?d0oypVtdEYv4=P*fcaN4y9$~O<*;16Q&`PM;M3t_-WfMz-H7}9r$T7#$r zRw^0hFsq!k4YB+-=u&3Q_zq_KW|p8&R9+W3_>|*UDmc+$>+OHIy5rh(Yk{;%B0L>2KZAs*vO>&rd&OC_w;}0ShoFkoR&s%}>Im9vNeNOmhH*iWz z%~9FA$H^Y|nhK^_YKh8oLrbkx%=bFkvJ)ey8xP`n>52-P&cVPz2epg(BJ8(woT$Q-^${`}?nE=0 zr3Ts53iCDkR;l)~v(=sX8jU#UpjM;vHF^|39?92elF52lvCedQlldCG1P-K`j~__0 z6hDw=3#akU7IaEj*0s4#Z@<}s_8fGOc5=Q(XN6_&y*I<#DPN<{k-%2@;4|S+v?__` zrs;S0yC=!I_qtPHG^z7}_cxvW=DO(zWS&27v6M^jr0-F+xIs(O66bk8x#;Te@dcwW z7Qh4a7IQ@qbvdF^VOrtD4DbO&m2(um?L?J*5>TmdD4C-5&dj-PdgXiW4@NLkgmyT4 zedVHWk!~DEg=sfKxqom_)=L~kdl>3m#Jq_Zzb_gH1$-%;K46L;lucnl?Q(t9saPr! zFYj_0edyeMv71CaADSnvSFWHBlqr`uTlsPY4gUeUm}3A~ELkw5+83rj+?&sm<~oXV zAw)-6(1SlX2v<>MXd*&M9dnAK&Y5%FG~)-uy^ivEFv_k9MFTW8BUQM$C58F9;=4^jHS!b&DU-JJhI9yB{6=5FWq&b6-Z+Q*d1W zuEep!k2Rp%!(ly2_hrEKCEl^Zk2RpHaMpelLad5L@PU-ep^|C%jvczthzx0YE4G^% zM_E50wHn%{=$Q;u#2(0`imRb|j=TFuv^Xb61ZDeD^l}CU8&kpOtD)%}TEHCVg2SJp zSsBZAyJ!_S{53S2L))37H8?6#^h!q8dtI~-92GS*k3&BplnO=ZwG6nY<0l7Y*OOO6EmA|Dap-CAWNigUJVjq;+<32xUIs_Jh7NFOA#>~j zM>0j4oCtf7M(+QA(A6vvMl7~*}QIDX>LugJ_{Foft2&R*s((2eILr$=S` z_GE^4eGiQ~{o)`VBSFKFs74r;X}U(f=GM}xdtiGL)+ z+df~TWq?4-ycrP07@N~Eb{iO98yKIaWBg)ZWcM|*RKxJ2J{rvDkcs4c!qaaPeEQla|Svp!bF*Y%YStcXP`GY zh{3z3gO?cKgAMRO3PXWMkd(o7MNr^(8sKBWLF5`aThO2G98piMsj~$g=AUNU9QQ>- z{_1GbSD`1vA-|61>WDjA)GsHZTSSwL9qBK3Pste&e1tcEsoK*Jd|Uz1P#6!a{cXqy zH6cUtcBG?*n79-ZhnVNn!~}GIETAWY$t|qN~s{ z@BGz4xd{?ip>L&Jyon%~VC&}fs0ld2^QuDvR`SbAmAgl}=u-eDh$?}x=r^N-ssuh| zMPqy(>2_?>toscvW*Vd>kc{Et4UYGf)+IyHidX@z9^d2n&@Ji*5kU%Ki7MT%xM}Ne z4$1)oRStjEO&|P*iExU-7$@#WfTuJAc`+c4b*+Pl4Aezw$h-w^I)YFo?kL_bZhy^9 zryar#n`pHys?#B?(@BjJpWWf2l0#@~?~*ZN`EZ60JkKB|u09-+1QTV=L0oijwZBSF z(F}nM-vtqa!?9ST6mP@K;>a;z&su2bp3j7F;Ewt$eW55WKh?QtGFa0M)+-bZ4Ac|J zP%KI#J$S(zhFT(oveXg*Q_VPpVT7I_S|Z+g%}s9{GKyu1KrvBvK(C+`V)=YGtv}=- zZDSij8)AY0wn=zDXd~zs2-omX(pE)SExdz=3p(fbM#Wg{OC)vqMpBaQ_3X(Mtk!l@ zt?jH<``@KntUYaKRj&BmL0P+C4Vm*F*cLq?BkL%llXa28Xh0uIN0<@=|HW~k?wgKq zz)%yx-oR3Joz$w$D9@h`so5?ea!g>_#mG~kT@^yj7P@K9?}o{C@mUON!>+iSGvYbx z4+piWQRT3oGt%7q2cDUzi_;!)bDfK>LU1sKXCH95;136B8)ggoPh8r0kMQoBEvV=Z zOrHpqQ;x4+ant`Apa%6+!WRw1BB+{lpXVVV>R0iWXS=vB;fo~cUu_n;(~NB< z>8;}$MoOpqJvRskN7wB@)Qpw$KU0xEt58B)eZKBl?>InKXFc~(Eb<~%9pTAPi z3x|SHJwTm6Uj{l~Kn0yTBknWA#i=V=fy4N%Clb^ZjnA2X;*_9X+anzFSJ9PVZuzH! zvXYP)qN@>6{3m>w=Z+)df{@WmZHpE6$K|qvHFe~v;v6u0|PLz(}+6)bfMNTA6RE(pJxBsbjPF_`@18nLoRnQI8TcubBvHVSn3F=w$ z8@&jps>lN#kKl*Mc4Ud%c!IKiP_YZ0oP8Q1 zi!eR=jnwW+4|#B_tdxjGX9_;iF**}RI)=lIQenC;6UXJB_)|`gB5_*G7d1)4l~DH( znZ^4u`6}pHL*!xKCD^#eA@qMK`u&;Oh^qv(8zRqpECn5p0(4UJF`3@~%@DK=Zpl{- zx|;;jaE-E$3O|_X#a+r%4?E-?;ekqG`u)jr75lJ*?@tyVhPRX111J5JdVo#AMc0wW zde%Dyf3kB!XZalEhR!&cd(&Z=Yn~L2)Udp5P3lH!sBM%L8NyG6_^b3lgtm2N?}b1< zcTzJ%o;2CmnNQkQLvL8(P8I)}M)LL0dsW27&fZ5K7j!QWERAbJ3Eg<-@nKK4)V2On z+t4W##b?lhSP)Q|dc<>BNXFe4@KVk03;r18qOpe^l=U!lj>PGM3!v`HhcTpx(?=Je z;1@x(HmS3q8A_)QE@-u&v!E4BIMQ0sLPhxS0-I;Qr995&*;vtBt$4qeB5VmIj1R6vrnL;p>J7KPmdB65(?L;a4D7HuU?FK{T?}vRmv# zj~a%2pjnzBL#fJW4FK$K;WWL>f}6!iI1p_q|_Ct;Fnh-gNJEeExOjc30&2OVj= z=VDQxM??$k8BNyedOQ|2D%De-2ZX5aL`1WkB0Hgnbzf2s^a#=`ycpW=2$o+|Y=JMR zCuklM1|CsO|LF-qNIu)%ex8$%?V8QYaKZaR#ruj=KDg6Ph37eCJ8`KBFLd%_0zp_p z!%c*4im*r$YMB79`?n&zA=}fQN-q4?GGV*+Nx!c~j|TMw?g{B${sx`0($k)5QGYoR zee2+P$hQs@)$k)2V8uHk8eK8v-WBh}fd^c6ziI-y6HV~o*#TgWk;|}S*mTb^cA^6b z8K&I35*;}JYz#3HDSE((&|Ee@J;>>nA)rSC^bpgwpak%R@@o@E}!E{XoO- z>piE7`W8fV=w#WK96A|&$!9Q**#!mtJ%UI$D3-u=cRYLEk)HJoktr9RByxg83r|AA zZ;s%?Y%HM15rgsGFJ>_>JP8v%$_AMhopk6jH~n?QL0J!?+DGE_=1FMZU4LPy{c~qQ zPbLIGwGAh6WBZT4RAYNKIPBU%w=z8u2}ONo^Ux``BTe$0EmfT75xIB8I}pwDd~$l) zxHx#s#9e5z=QknKZq(#JW21)t=EA=mq@6ZL&}60D`&#>}=iqKt;T%C7{x6z!$?Ztb zc?L=J&6;QvkJGyh?zs}^=(83n^evjL&swHxwXHQl-Cv`4w`v+jx|n6&<@+#f<;z1W(537{S5%gX{5IApcb^9DaKKMg&j-cD2 zg{4MA5uIo%GKqvf%)p6!kZ|W;*k-Iw5&0lwG=$tWN6-gKh~N9f96^sXjoq218Q7H) zyAs$>o7k1We%`?TOkv;7*fW~O?m{ma*fEJ61NN&Xb`01{4D7EJ_7KKi-86PrdfC9P zk=QlB-fUvm0DGr_{e!|D%GjSYjopo=8`yVB>^p(|wTXQvuzxbJ4=L=s8T)Y4*q6}^ z1N&ZHx4`F3fu8ZVQJVJ(z@Pj#){o~1`dh(|7L5PTzcIQtUACdGTuw6${09~OgN%QH ziT@zuw=?iFlhP;0O8kyZ<6l9u4E)Cx{^N|_-Nb*K@$(J*EQLQ#;`ePDzheiQZ3vj4 z1WaH7155!ESir4@fYX(LXQY7O2?TVcIfj7elz``0K-?7Y91Ey31e~J;ydVYKa{>XK zXs#jPB_-e`7Vw}c;3XC?&Jb{+5-?o~c=iMWI@2qja|E{sbCiHNEMS@`U=9nI3!vk= zO2;lV51l94mDd!`YmBqV#CeTzRy2*%mF62ba)}TNg7E1LCe9MZ+1@lxH+t2;S*i3{ z$v7XHI4c?F^QLhwqXh=e28FYMaSoa|8yM$M(>RyYYX;6%g|n4$=%`Uww=#}))P6y! zVU_H71-)+IU_~$##p)mIbDD{>i*e3v5~ouKTIiYTlm-8>Qs`p_YHb32%s`!w;!<@e znW9fbyYF3;f7C%)?UK0b7psn@XurTI)Pdj%(SEt&4B6Vdmk8>nyzvv^{reI@;iC@n zx^e{l@2G=XoslDG5m=A!SDiZ2A|t=wC^f%fHMg2-e#2_+0nl;%vr{K}!@&7T;rzrn z`%Ijl80XujaXQnRrrrKfKz}gMuO`qR43t4m%4WO$$#y$~oRrnevfJNmw`_2Q=x?@L zCvuXsTbZ)kpVDssCa1JpKXOXDjR9*D`RziBjr@*s$c|@}1Ag)XIn{7tlmmUkOaL9< zZe3}Kf%A~Ud5Ccqm^cqH&eEoFy3tb4>C)YvP&iL8&KeWv3C7vdG|px8mVq-t;Y?th zT_(;1#`%<-q+QutP(YdTnWpMhr^{)X=S->JWQ9AKaSxcdlNtAC;Nro6Duw%elekyV zazo!~3U?ag9x-vJF>a=CHc_6PJJ1RPXSTwb%{ZqBqdaFb&RI?4bfmXE&1F6pD4Ye1 z)6&FQz&PHfaXQgT&lwWuO)RirKJg|4DerodQ922y{8~stS!+pC*6Q4uR(YkhOlO?` znmE%Lr?6?9E9gB#pZQoiLYp?9fmE9|pHWJhMCsar-uK)pwOM4KEV834Vw9U4PST#8 zBdAWbjBh$y&6^`A;BZo_H|Ge7fB_xN$fVqec=clY5>~^JVb_kd!)QC-GUUExm-`mW zt!Y~OPPEfNS!tlGw443upK$~HU7Hb$A)G)g!6z*8bib*F){(~h!}QC>u$t)3(30adEIovjYc5i|>> zdT5TI*T8^b$zw@DST%lG$YILUDm+7lC#&!*Y`9_WKFvhrx%*qt*BZF-z4`n~-Cq^) zC8~@~hz~t42~mG05w#aNz7SFSEgm%ei7uY zq;2cc(M`~5MgLCl^R9Pu!d+toZC8XJh4+s!f@UxQdz2q4!mq-MJ<9i;YVYBIf==I~ z{8`05EcYlWgFm`|#TY>%B~?BoTJ;?x=%ftbj1lyYl&r#sTehzmg93fiE9m@`pe!Pq z=4k(km!ELeMvM`3vEn__vi)<+>ue{ei{ic4(casRdF2s+9!lWv_AfYGn`*St;(S0* znd1FJc+Y!4&_AJEagm@~l)w#Ly+exxwLv^&R;c*7S9l?_3$n?Clp@TNgnW~5k0QJ( z2_+y9@w)7zO8B3G*JURt6|pYMW%`1mACYDHN~%GbzM<%U%QE!=nv3pjMHuejq8khC zu{-pxLP#&V-6~cU-5Y?h)uJ?4=cU_D^pWRvhxGI*%G0N?SFg%&Qg&3Y4bn6YZ%0^q z`3&~*{TV0nayavsO8*%$IrXdamd*E{MhqvQQw&e zpEd{)bq=j@(S1RS`h zdzxC_e^0~4A~q5`BG$I6_#S^C)dn1h@&0moB$u6Ru+Lv5KQ5~L^fQl3o`X4LfFI(| zg<>bM1BpZOTd|g+$xyJ;7Y&5<#4!Oo(!T#sz-TBOil+1gA5Yxx`C5qj>xsD5#PJ9F zUyo)wDW?)4WOYKOQ)xGa!<6l#sbbcJZh9dTT?S1PIK@2^uC>0Epy#Uv0dkJ;?rSAz z0m|6Q5bi3l?9V+*Go)-R*`%`TQWA%E>)`oa?v^|#7*>2MsETIM|AqHb;{Aq1#E>$#5)17hd|Zi*7*>Pv~9rXmdBk-A>Z3?=I-<8bLt1sMQ_a1=T?yzv+3g z5$`SPF6bV_latFf>I+|*sOFCsUn(`iF{^}L9Ah$l>A74gcZHD3T><4DG?lvo%1wg& z$8rR9QgXU`XXFT)?{<=RL5`r+EYS6FuAr|x*NS?t>rz3xL2Z7ipl|WxoJ$3f$0)NN zTxH3btc&^$wMtVqwZ8UTE!7&J)EWS_&hQvE8vwPkJ(xYEVbuEKM$Qfx5I8TLN)QDA zais|n07Q|g5(gu7Y@_69(*l5A1B3x$f<}Djpj-)4M6zRr3x=Gkw)_gQGUSi z34xvH?H(uLq+&EiZ!m42$4TvKL(ybil4YzCXTRsd-6Cf~uL$e@RIb0$7sa9U?>v`? z`tC&ZzDwlz@#nSWF8a-s^}dUbV_Ci))KejRtK^^uwZFa64b||&>II= z=@K*$XKGH$@%y3`zEBvv!(4QUW;n+syGG%1X@t7tN4~}l%YTo4@1miaQ;wpps1XDy zy=?o-|E*~>i0au!P;bTBhler6iiw9YAm5QNCwrGZ!aR_wIl2Ob{|P-tOkqFY>*m*^Nb$=uRfoDPGj~zi zDT4@~hYkAaXU}&IQQw7#eshZ)`SqFK+-Qzn&7365_cu2RxD~=tT@MV?6ES8V=As_W zj3WNcjaot3L#f__U%qDW)vz96%x67-e5#p~vQjkyTw|E)t!8M{ha<|z53={B4g87x zwIJ~2=`l5epx%iu2~@n4+SdlDK)qQ6c?U3B?+B4@TaloI^M{hpgA1qOx9p3bNi)iK#yYV)~h4>Q7-qSQ_I|!-y|lo`n0* z?^y6TgNQCwUUup3hh0RPW3Hg7HG)9u(8@bk&_HNMuFi7>{oxrX>btVXB9o+?p)9}$fg$}Z!1u<|(U!jBA$=azaZ!pVdmjIhibCpo2y zzFH#)=_d+y(M>GN<)16)2yEoNbFQFIkjwk#3i=v99-AxZC$Jr-%+mDx3n0M9?i;tb z->`m%p%3f#4eOJUbdt|2 z#^MNC^w0p70dOOp*yOW^50YTc1D+GO|Fc!)0$A7OFpT~T6NNYjAnRM?%tp&MH24T?Ug8ov3 z)y=#$mkVlnvXi`bT`uT~lbxj9cex-}t<>&y%{%9EK|R3m`sIRdI@w9B-nv{+1+Z*I zU85&L6?H|iRACfvj>FIH(Sp*_o1~>Tu?dHqZ1~tFHEo17{-NY;mDU&rF|fu+{D3tc zf@ZKri`q16JgM~Hu{4I-!5FVd>Y##`!9*Ay=c;O5Zm3lpi-q&Ux-VKCr+c*?@^IXQQ$$V$(S%d}@wAJ6K+epH zz#nGN@r^59EcPOr<}asP~>zoRpowZJndcaMmek zV^|{zHd@1^6KHEN(o|?`TtLe{1r1bHC{DD7$D18Yv<=MA{S+r@cUK9zqE-+vHoCOw zxq|xE3Ytu~k<@B=uAux=oYZOq;!kl>>-Tao`)9tJ)AbAJ?OFzqZFAxqrdf9wpDy(c4ckeDfFaQJI_9 z0XmZSg-;q#c0548a(o@^P#dH}oN5e0i69;3&=Uyh2@LulGjtUe^who)wRa3*^8&q)}V;wx$gg|6o-Tik5u8qD*S*74|kA;yE2cd@I4OWt_)%) zsM!0`VqZ|P516qSjL%fD56c%>7AVy)Shw|kjjWmXdQVCwkf)B%i zBmDCBO76xo{$Uib4D<91H(zNL(@*|~leCEmK^v9yRSxgMgrI-^!%3|+CInpohFu9k zyA;ES4DW9VLG2Jf;wnKOtN2H-WKBsyU#N(CGQ9sv3hMX|w87a)LEkCDs0{CQNkQG2 zaAQ)?FBV}`Qcyk<9!?56tOyTgcxNXC^#wr|ykn?zt@UPMqTn?ZyU~bkst5=9LMeKX zdq0t|VTMSvmSW&#YEuL#PJ8$poeevP7hBGrA* zP>Tix$(NKxs$l@nm9DA@rtu<+R-6`o>wWhIKAg41PyQQq*i4KL60r z>IdrmEXB4XgS6Cbf?ikQeHq?YZxi%fb0@W5dKVbSn?r|7%o1(@Gdw$SitRS7{y75x*ZcWgq?cgX*UJVBm1S@ULPdKcsg`knJ{ zb=wvGXevx&wN8RB8sV!>=x%ngWu2#D4JHyG70yD~mk1i+Gf#C=ZbDCyko=S@`xPUY zouJ)3+Wpt5n7k*9VD_o#0xb;OWwrVo5|lZ-kXVq^bSZHjMEpH@P)?^ ziqpNWeREw@f2xzRPOTG^T@j@RT^N*12R&9r54y0NumFO1!~L8(LAbc}aVD`@E>z+B zTat4t zsFCtPnestSx#^fGD?;&P$1WXI%Eye9kIIyfa>@@lr729=@KTDNpB=DkF+>yjCRby;O28WzGwkGnO>ROM~@_SVHFyOFgAsA}{-Gb%-2&XegD> zz3NtO>@PpfN!o9B3hG=Z_p!D)yv^?tlm|eoF2E5JRrIumZ>!;&;bN!Aky}0$M2nq! z-*wS7r#UGHn?Q)bq`eFg#y8y-JMr}Qt%%|cof6%bj77ov^|LOjG~?x%buo{C6Q?-| zmxGo$*UfX${}2ksX*suiqY=XNtU5_6obBse^eD1|+o0W5R+c$i;Wp?5WJT-LPf$-q zSmES5t}h}1I)H0cJl}EsxwD`W6|uqT)w&3peVUW_e(pj*Syrj_Rq1h>puOpk27auC zfV$7NfVyu(!FZe|w|H%li?%@)j(|Qkd1io`7do^o;hsQzrYRwSb5G+8XWQm8UzYUEEK9GYDlT<8+jN z7M<>-?7?vw-UjI9r#mTYYn=c?M;gs>ad0H#o#;Lej(`F-4TgqNhQ{^Uw;59QiZ(3! z?KY785CCG~K%6$TLGe$4E_Wr4e*`3J4>aJKK|9+#bc&l6La%1S_{ugu{)y^CvD4=luZsWaljGziow66;U zMHFFpTcZr8yUNS4*7>yJIp;+PiZF)u{A~y0`j#uAsZla8j%9 z=L)(H66A(h8sdOxJQSr#+Q|}gX?LOhIakn~!=&Zj>fY+qR|GvOQ=2U`_W*`seqXc{ zAd|JI1lihMVB38wt2N;aV^XrUJBo+0?n8;iX-{_)`wKwkYClei+}T}*pRtAiJxb6E zD!iw=w``Q4naJ5KqXaF$kI*PVOYx(6l%O@hB-eSb2zpM-7WI~2qHm?=wUDUC1pYap z4EP+MyeG6wQw3V4EoV3>+fUQPj;CGp5enJ=FF~`0$%+3g;eFs=g1&>i$Nweh2+Omq zqYNYaRTlXI=I;=>&=9#$ibRR? zY8gili)-$5(? z!{h;p7c#u~oO><=+ei8~(M5V~Vbos~i}utjeAVF;y`}zoi=T(N=`yL# zQ)22cHx)qa^REb6rPP@yNZa&^pv@{gL3l5mC#VQ~Q1AV8^#&>RjM9_~Z>4&wEaCI> zX$yK;d&VK^i-~A%hR6x)HF_97F}%ACs^B#}^t(gPbW+xbaQi5&&Ol{-0OVK|t>)JO zpM)swr)oi;4-*8u)fwJ1Y6LxZrjuHoS0iWnUUahZ6sax?Tu>ilj%lAh!QKO+tU^YNVI&40>M zOZ;%gU(WY_bg73k{JuE-#iI~Dt;eq;s&LjpWHu6@r!w){Y28`q67jg?8F$G1jmzZ6 zCC7na<)*<4!ZWl^GDp+#nn}v`fbjH8jGmr73#XK^emy-C&tXhE%c)L}NP1)Bvbs3G zRyxx_UYsd%_~lrl#f&-E#9Yjni%d*OC+)i&LFcM$EXnkGas{n8%Sqnmxq>zvGe2ee zP=1tlmLYY6C3OQ!-EKqKsUAtlmo{j=_HUW!1|!i8 zNYw9aC*@S)0W4IF8({i@h{C*qzl)Y6x&e()IinxDQ@3>MSDn&_(yLm3DeG24)~%3L z3E*(3B1Uv81PwvhF6dSW8peVeW<%w@WDrXzp#_Erzahd85hDR9Mff4&A%yKB{1EXZ zi!k%j(9VX*Uehj>*{CsO)Ii20xaP$Lf=ccXgkq@m<`)Qh37}-5Hq3#6`npjNcNnO5 z0QD7UY8J#DQ2q@>*_FQo%D)Xc>Gp}nQlW~vzM-T%!%Yjdi9%|=CR5}jhY_vG#Mpcz zCpGh(reV|vl!#7?w2m_EX2ZodXJQM4_+^kARGznF5|1yEp9-L>A}#O~UZJJNR7N?Kq%POR>1CFOakZUiJ&_jN^h){WM&noqj% z*@GmXkXfwZgIZ&VNEbPJib&rz%ta$wV4{x=YW<%hT=W2f=q_dOi55mbrK1~@M<*&! zn)NN5l(p;*ydbSdcK?&*_JIWYzySKf0J=sckU+niK&2`kK)=irbh85bNJ!w@ zRro6j>^#p&tl=r=Ipv2z9#a4kxW#$MGc=s2;sK1TzNmnHkU*^spk4;hYL!3&%`|~F zt9SryY$Ir=0vhY!_cvb$5RR^{M+#d*iVvCi!ol2rzAC*ab|cPkE!9RjrNchx#8^5? z^dJu|zc|mREf4b1GlD13BTm$pzo8&<@QD)qh?5KJ)bo`oyyq)Z{HZjRIk^6O!xZ99 zS%}C1Dw%etEUdBUw$17uOX&OsMnAo!og?ZmCZczn*;>V5Or5q{rcD;2eg+Z!ge@lg zIc=DW;^!NA_{o9U0p8UA*@0Si_xVnI_T~^rKZ(F#I^@6+yR-h~B(3yWL8sj*2#ALq z-rJuQwCZ0@@>V}9Xb*ne`K+LuFK|+;QO^p}TB1QSk6Ri?ik55t5%ncRG){<|R1AMn zZT{8LNjVkxFg=aqh-^ee5rSi+F1ilEXax>%{>-zVn-CERMd^O-!|vvwZzzr7;6x6FqH#Yxz!7se z!Z$STrw2LW?UuGF$xj|wSWZE3(#9S}6a140c9wKoIIHEIe4hIk)@VmdCuQ|TOhrdJ zl(Fn97l{ioe(glRbEt(3@zIJuc!t;ULMLV2i6pnv-wcwt5ay?>M-eIKS(SLKcYJ2f zmo6F)s@BCTXcFi|bU4F%jaSf%CSjH${3QvCO~NWgI4TL-L9ogO2Y~SE6uqsDaLOWi z0uK@SiJowyD8ISTaK9(q$QotK;u_~hY5jt%#r+uPPjt7hbJ5?37)p~lt>+>v1=3Uw zoQ6P%rg7kW1S%>9)64G3OWf2B!BAi*O=pnK7hw-=zMySN_i=7-m-&JUE^<<|D1MBeFX$fpm^ok2SVlqH z^OZt*+1^pZEa6a8??@}PZ-unYWR1_EOx9qE$rs^ZsV@=KQ?S=$zHzY?k;zmdshi=32OfzJku$r@MTT;L+~$0!^p1}z$+DI7eC%>TnH=trg0^O~)^c=-aU2SjT_ z(SV+yRoXHk>R%zEeAX==s{NytllUd>e3aq&t&B3vKfTaRnCNySk&o~HQg$dBMu}2S z)ax#-oE%e@&~@r_X`!3`jhJdGM8aOJobv0pzuzf8%iFV6L7kxMS~;mzah;$W@#E$? zLH<@KpOYpF%DhW*UgOQ1EGUe4a;8EN-B`3Liq+C;ZIHama;nJ1*AB(0Ji^S4$^aWF zH-ZlzDoR$?T>>9_8az0x(`oZpxoG4ukbTh@t+QO*mOVi=U7(fsTCR_H=ljbthomwK$zZkB31=j5-H zJ;$t`)<)Sr#|N=&zQW%xrS-7D>qPi)DRx-9!XwUY4gVndZg6V^qC{O`=o}M|gElx` zKF+(51f=*a7 zoxz(=L%=S#o8&i=X-UT1@ostw@zqh59O#{-uN=jXxaps5uyIuu>lLm}R?<(5a8VoC ztVhg;X`9{R+S!4C>=bF{ypfcKm(x?a%tDt4!Xw8hU0^4u-Co^g8L ze_qfBNYBatsR+}ZMsjTHU7%uTIC(eB-PTDGCr1(9k~pU^Vb(Q*{%aBN{_ua8fUS!{ zMc6C%_X^vpy{TK61NRgw6vuie@1dmb7IYQiaVC=YFz;0KcbvRcQrT8*l{~2!)3-_{ zsaUmD@-)QAy_(k*&U;eMD#gfqHQ18br06@OoEHr_A1KCjIr~+tlCxaOxwt^kPYUNF zbqD0I3jgS|?I30hZ@4VQX-^(5%W028?j8P9h5wZ2EiO>0uq?%IDYsMfw=%pfUl6nr zI*+(Y(0_*u!dJxkQRUGy1QjX9^%=w^e6tFFnZYG|27y%FT9CtC`OWx+2>tQ$DF=zAiXrPfoe1ouEmIut>CbsO!k^j9Cisn+)Ff><_@x zx(K?F6T-JwDURleD7+f_A9z=*;#P_2L?UQutWKj?I*g{*?+pmgz-3 z(OC$5{YOPun#q=kGXV%0BV<`@&m`9OWEEbO$@<=5=sOxItU(m6Ea{0PZPY#z^4jDo z93QB{`OsB7gnTR;?X4fT=4h4Y)^}a>Ot#T(OAhXm*7B8++1VJ^$7wa+HCdSLZ@~%q!L-8x>(+FiCU^hI{{NXB@=2g-6P{$?AAKmPqLV61Siu;6^@-Hm!R* zCuN;G0z1Je9J}R@cNDh6us1{LpWCi|IOeY^@kOh=xhe~rwa1017dZ)Ew2El5czv0h zN+9*FL-=|xtk`N z;1v@5iuQu~7~p9Z7#ypP29j(zL(}crV5R9w5nS%3mrb;lvREt&Ms$pbOLSjAPvrVz zk$81V#edkm4wucI8azkqR&U%;5x&Pm$B`GWczI(c``7qq;clf0kK7qq@%&T-R^ zKZT6l8ZUMtAmx3W>Jd2+qUi$n_I4qeIX&ggIXp>`j~6d!2bOu+j|7jPFT{B1Q!{Jw zYqQ5ljR!4gCyrRC#DP#U?o0VAiNb(5kS0{^#GvKit2?`JxJf1CJ1l<)RMnr`ISF@I zi2mSd_7Q}WxVa<4E-wzEhlc53fRyHlNa(1be~7#8bUW+m+;1~?PD5^+LFwb;iI1tZm|zF`J zoR)p3b+TXrL_95f5h%vAY!(8>wCr^g&zP2>o9~{h4>2avxxEs;h)xH!Md&Yy2<;b< zxOa@3mIG>t3Ty#Xf`t684>bm|EkAv)jZH^;V7Z$lJF}9(KLSgRC=qJsU zhIh8lO)U*P1iUB0vvrR2v*t}hL(kaBKttfl6QPyMU9w*q+J46z7xgrd5GXkjQpAss z2+^&Pvni`4?O? z%D_V4;S*s6{XIgdB>j#(c$>dp_M(fPGJp_x?nFSzxb6?(qomq}X)0}<;-cvW5(4v2 zgcRhg{n^0JJAB}x#Rd)nt4@Sd>96!f>995=O&_coZ8C5W*l{A9co`OD_>&ZLM8or4 zHbc!qRW{%dICvsBUWY05SL%ThJsGP`_!abDT5X!56CZKY-v%-QSr?z6Vn<`@dL;eb zpfYBzchR{QtICZ)>l0yxLy=HQPZaQ#z@u8TG}T7txGBegL!jG<;DY`^u|yROZ+3ec z5WaSBwE=`czY_t~#u8PS0FG#uUK=ADTy(R6gn)h`B)_~KDZlJ>PqTC{xpb3@q6QoS zwf{eGeRzmYBb%i+Vq2EGX}AH3z?c((Mr^KkZ-X5FvD{6M8)yhTb0ReMydKpRG^znq z$u<{FF`y8beIlqx|56&=taDmnp0~nHuNx=`EIScO(3ZXXnkCcF1}5FK&Ok$8$BEE* z8kja=qx+i;OasMq?Q;VZfrBRk#fUtmCsRFjUjWl;+1ZV0fP2{rHytu?5ui&>P*BGCFopz%h|teGpUjZ#U%_cnDl}JiMDa-C|6g>n5Ca@sN@PkZ3=u)vP%z5TmH_VacvKEE{b2FODlme-{_<{pe963l*E9L;V&E`5B-zNIoFO|j%D9-Aaz&CpVFg$*8I**u}gBe9xfaYC=q6A3-Q zWQSRP5#P}K;sM2pSVd_H6Eq~Y36CdY6+Nme2E`JAe13@^^x?MhR}5vopq*}s58z?X zzOkV62${MuRjx+$MGq}`nwtt500W{`(O9kYj>Z5zVzF>pqFU$FR_=_%AoucxlalUi z1YQ`Ga;^Gdq#O`Zq|8Q%`slJ=xf)S$FbY|9H=@XlGSPBzLsot)noQX^wBsc2Z=867 zjAo6Piu0LpQX>NQO?cCf=~<2FvXke!2IR(OEUk;mZ;XO`nwh@5ps=W{gd;5jmKWsp z&MPe9SWA`if};NY3i@%Zr9gRcN&m9)qW(n%Ot7klB$O27^<;uo?vgO5q_9jTv2>M` zg6j*U6>JLi%PTI=E9x&*YH4YY_~Menfq9LwKg;%P;E6FRHW@+QXq?P`0(L2*zGTN%I zh%W3aC8nv;yR0NnB3tzo%y~VONvukW=pF+~Sq-a#BDPntG_0+}N(*~mYY4QeBccbC zmULtcs{$grw4nDuj<)%HX(6~fakS0pOAC7!7b=k_r&%elZ;7lTHuoqk>|I)RZLhx4 zBsNzpE$p4&r?2$TQ`2Db`zyQJsw%_um!>*Bjk~P0Oh%uP9$ls?@tF;x`w!p@{WC3k zKuK?oJu5Bt`ut+ae0F+tuhJWt+wRGQy$6<=`9CL(zqq7-FXlftEqYL4QGOqeKQAr5 zP`d2-X_3W)q?i9IEw=Oq=?@pA#g>%yQ+c;lh+f47<%LCMJqu+q+ZD{AWMC!UXoIZbLV+D$q1^Yot=tP z*PvQ?wNZO8$ug^r)Y-(f%$i0vfGrQVlO?j{<91p;OSQaQ((+@`8an&syezGNyK z3l>#Jcy$m17)xM9IOa=r>(bAcltZJVR==xArIn!yIYjJYHW7-{Bjl5_u%nhHk*+LZ z%amVUJhJMEwN?{UfvvJ@L(xD%EX*vHi3+#Hv%Fs3ciMobm;ELLsNe zkYkxU^E}`GVI~>(zQ7U6j>br+mSlzLbweS-o}27wl0g8(gce1}mmj%xm@4 zoWE$u&(W4ENu4m&VuIC$r+h)4HJAD#@vtyK%L|tGRuo31VKMq0K79D^M=g)X{(oUKl(H;o3C9<% zYxMFY?wiBCj!7x^t7)J$ZiwI#Ti+Ny?9S;dO_)*|*7bNOD(8BZIV3F}ibf5Zt@gUlA=3EPG!4YFP4 z3xx;ys`O%c{1pbZ939JzIK^YkdjnM<7IHkijN$)OgA-XrTEnOARo3H zq!6p143<284VYUZ2C>S^;3!QcV$oo)L@Y8$_f=V(Ruwfk^X0m~#bUK~1`BJ%f~?Q(-WJ)&3a_d_K7p9}WlIY6uNFH+m5X-`GhChU$Jv zvl>GqWq!OG3r~z|O>PWoaV#9>wvp-9EXPjosVAkEShF324EMzvCD&kvu%k3o~!k`}e*K?W7sk~C{FWYBmIjZv)`kwMD~hl5PA=0rA<)YF<58Kfff&L^j} zCP)TDX>}kbajij$LF4k14s5jqX+#FKCQSydNKfImELvRVu%=H22M;k|z6%>w9`5Hs zx~LJ!x;`$oYSoAXD^%FrV1U+*$ODYkaf}S4d$ei9#LHyZRDu$1k4=`VbW)4#Ml8KT zviY()FeBgC=PuP#Xo#hFYhmAD>6eV-^%J@G3>Dfp!egIwN-gA0>sR~(zhCNN6c+8{DAz=u{i{gsW<8+&|AzN!(q z#OK$0g`y!Q|GN>HwT_e&;;|?kd4m`Hrx6!7XlXoGH{#(gCXL57*EKSNLc-{P<$Psz znGxU6vQV_n9FXrR4i=~w1i{%AaOKdnJ$5zxpwN7XiM#LAW2_^LbQR^B4ENT@o z{0BpVWKTU#Ytv>W$+~13#j2nFcy$`ZVP+}lPa{|pk!ZDCljy0(lPt%|k30&GrJ)|a z0PRcD!J@%mx^-c)rHDoDB^R=oZmH9Vj=6s6y^TjvbW_`6`^QwKon=2 zL%c#0p!Rk4l|<0<6S@y~JV3G31Er*zC%o303s@`-!Gie`M_V?L(N#J}TW(d}6PwDt z^BWXm60@#;A$Ikw^!M{6s&EcWPZY&c@&`GdvD?MY(^KcCeDpxSSU_h!OPHOnR|1O?v8XR>G9FuK zVIZuVELJ%N{XKmCDm-->m7=m6sKnD`P6&!!38t_Um}I-?vP38t)Dvi5s7C`(MZ>It zX?9pe%g1y1Otgdc2-?jU&`b2djj^cJL0RQwKf_lPi|Q0e)4@(=)2Py6HcK5l8(8FA zoS7`O>`ca@gN@NLww)0|8OQQrJK5|Hj-47KdLDeV#hD)UpVc4OkxEm(NSrM&Cd~pB zp`m&JlPj~P$u{3Ip~aEV_1@gqOBTyMCW~5FkaWv57Tr7&Vr3{1i>26q>|MRpl|+0) z`wtH5DLd7w*@1*F6y^IiSk+Bd;&WuE!OxAg7FmpL9D{b`lo@OtL(tz7CWIZ~f3~gX=wG8JBomqwnK@hT0(yn7I-EiU>=%lLBC02WxHhxI9GZ*U#zpfTXEiXvPcA^BWrZFzCb?%W+ zRJ91qVT>xQDJaJLHVA{knxiDuI-p&=<-rL(g3To#m4#{l|wQXosAUl)l%oxUU{xl9;f=kbu&4q0y>LFhkfzsA$ zyu5e9z9Ty~On2-}w`4#gt0ycDt80w&`T+yT#sv5_546!`a;DooIUEZX9}C1%GM&SM zvF1w2>PWE|I_jBr&@CJbV%Meti?w)PkaiRS%>>uI z&R7ay<6EODli00Gk46hQ>De|7XU}y|Zq~|jN3L!COho5RV^otz>h0qgeu&+uK_FQb zk$3d$B&+%juJ%{;@g*zG#&GiAR&Rl!Lsdvt5~~`)@Wu(@o_ai0$+@?+@2nsX?)kbi zCU?V!54To_g2pKUz=Xnj-ryw040F;Nu!c;9(c(lbh|B8CXthKQ8B2YsP_iPV2bj+? z0vf$iU5-WzqNzkkHMaHw$6C9d!cGOR$>Xs?u2vQb-;F*_^4h`ud_xtjSWn=#I48G! z7W*JYF?oLhz&4kOg>lgkUxG5Wf~_nnBF2faYUym2Mv7PzOJS$WY#Xg2jt7cDVS*dk z#07B8zK&yUasoW<25UM#_W-7g*t%2*Z$f9>t|~}0>=%%zU}!v)z!foH0NrS~@tF}D z7eDut#+GiC3@I5v|lJ%Bo~w{vn4~p zVm*P`h%ZoD$M34M*bB`uNO`XTWd+w8Cy2{?_05w2!(ysLYb|~DjLq@=BYD=wn3Sq7MGRek&fl69>L+QZM0%e58 zDwh_NmGxDYu(_>DEVW`Lb=WU$o};+(`xN%=S)SjwAg{z?Yn;UaMQk1;8;$cJaiEbY z>=-3b*1x#e0eo(z>od_&9mJqxs<_L4}3f_hCl+3u*Nh2;ZE)k$=#QX{&| zJl8$Us>-D|l$MtkmdFIo?;!i1IJ&D(dcBS>RCJnpcOgl!p zl4^dIn8}tvoLkbZ4J@0bC8b2KD8)DNx#K{mVhx|ld&fe7d_J+pub-eGEq93}arr4$ zTcRiPu^(8dRunA5VemFgFHObxU=1h7G}Ic&CsVb?xuW0F@|#T6_RhyTUzwhWFwM#@ z(|Go460O`OQ?-KcZ<73o7Lc{K#_kv4rQ zZDSLlSRpx?IhhDk@H+U86I|?d3LF7>sH5c*9_4=ra zTGT85&v&hTX74kZr1k&#h0mwV*?aA^*Is+=wbx#I?Y-BO3nebAXIYf3v1=H@Feigu zwGiPd1SD$a_`$AK`Fs@?I}Vv045J63AMC;mUJYvmhDhsItXfBD;H|cA2fTZ5ZZXGD zS_DUnUdC1q!*KTb6YZH{Np|b(RwhOzKssP$!0f5!zQ8urD zqT^Q6nV@nv7iAwN1kmyd$C*>WZu6j%*#Vn#z|It1i9nos&)r&h`Dio_d-Zv^Fc!m2 zqp4=D%8NNur<~r^)R{x;oveyb)gyCgy;pYKsu`+lkqk5fwVPPfGcug62h`B|U6vDs z@S5{O>kCEC87aHjp|V@{)fq-TdWP0xhmn!#;WD(|b}vDouUSTqm!b7|C7f@M;evXY z46QH8eMPDsJxGQmPz8-KdQ?&)EZD94Tjz%Dy0SLYWX7J_Op_y5)n-NseXEh(U$39~ zL1ajV(M|m(1x7dZ=Q6m#lN)UGQh$}(x{LU1A>`GF`_wEA@)*M^>{oXYl0%1}UMoV; zZxSCCIIZQ3Gq&2Ycc;ncWM0JpT$5jzeH&?Lz{yEu9GB6!knuWa#+X3zAU{rsDs-!n zF5x#w*X>368gbNfj#e_rSzqMWlTgIyu>mKCz%cmjIm?(^k$0ZUt9{Ng?VkZBw^5y$ zHdX|X8o8H_FG4>*qGWXD`aaodz~l(I0GwuqhTFJ>`HO7LU!C1IX1QDKjP2~SH#>QJ zF{l=qC@3OAO*qX@4TtI0aXILNap}RXVb)vT2plIk&Cjn5F{Bo+IUun!!qD@pwx5RP z9<4T5_o{{%u=92%U<6t_)Mjrk_E~W6f@^v*)lebxt2OCq)dXVWqza>_)Ci2)45@(n^ zf%Vfdj8BL38X$)u|OkdFzZwE-yUco33KOBSTdxp|((I`~-z)TfTuW#xC zR^fEaZxjyfT<;ajx~KKRNJ9?d+8(h%*nqI5qfV+L#6Y;N=&kQ#uN*f-(@F zU~?b=1L+9inLsMg6YSH$!WvB3PKqcUF{~lj@?s|$aOk*U4WSM7FyaD`T1{$5tOTGU z2FL6Rie6qgp}jI1khgge@6T4E&j@vBv4&&jR!wjO@RS8uM;X@u1E~S0!;Li@j8aC- zRB+udtJ7P3;W~mLbj-0vw7w7fDTBoUJ{5r8WzZ#ckU`cF1HH8@I!;F+Yjhz@6_JiZ zo(WW|IdnjBeb}l7(P7Cm0Sz6hLz8EMfxzg%(3`{>TCJ)8%xuxI%HA$^g>SOPW!xxd zNF=(@XLZh0mv1#4?2@t7&g|q2J@-jQIK`uop6qO4my=WDkxd4zpL{3Bsut#l#^O6UnHyjR zAS;3itO+wTK?TbVMx4O!?xF*?Hjxgkw#Q3Gx}MfX91Flezk9qYxUS`PcbOqC|1=M7 zxuelc^Hi5>=c1XF$`w_#A$6S{Or2(cqE4mj=^fN{itlN+M&nv;XGgf9@h@#IIPR!U z|EErO?VL_GoPE5~7~q>QnE_NKlyf#Y5?f|0B{|B5?ZGxKQ8!DjATT!Lbt!2|-Kxzpq-n$x#*YjYM*RUTWp-SzHE82n48pWr-ZkC zAwTe_`r3bKl!s+j1>5M37B!SXI-uZ_+3319qC5*~N3x^JnV)#+_Z$~H zZAH(g??mPi$scg?b_76kNdN{ttHdRBVD|+@$+vEHiPICTcJK#Ls zJdIn~7(iXG>gs|30GbO%`3YAD)5}kt39ASbBbRQ9b|6y*0Ja@~UaK;VL5y8lf-_Jp zui*(_FDpRKHwHH(iStG8Rqa-WtLYJjQ`Uej^y*1jV`BBn);S2)D@_f-F_r84*1B1r zVe~Rn!^k=e81w>D=hiV}0jigp8WhZj8Os2M#zV{37h(Q~qSJhv=|eREigKb705!5B z7&KD=qD3fQ%sCk=&$I7>6h$YZb#`5=B&W3S_pS(QP!F2iofT+rZWSv4U91*p@BxNf!GLB z25-bPm^MgSxqSTQS@i(F-T6&3zCX+*hc_(ZWCF38fOHU?O9FhkllJT)| zL375tnvB}qOs>ceL?q+Gj|n-a?KS~2IO$Rv$auW5oQ(G_9%5<^)=&fg+LqT*GXC*d zC;QvRZn^3y8J||od2)9xOlL<3b7)qW?yeAqE|t2Xc<@}Mn+suD>cimb zun;6&7eq+L(g_s*NNU1+9=ewR(%GRvo}Lw?yGwx_stY3YDv-nVK|rp8poi*_f|qJ0 z03nx8i*FzqKcp1G=$}naGcccP$K6{4yPP017M7S)s`J6Wm?iSX%#O{x?n}m}E*Ao} z{>FqTnFkt@@o^U+ zRR+m;i)vn9WQ^3DCdNSDr)t9`{kk^hoID?qjQ9Rj81eQGnT#m=Hb><1$;@Q@Gm5CF zVlj^i_Xi$_cP8WeRh_3s)ORkU{KOz@TGjdOR#OLC>Yy-386@M4aUl!=&6;*OPwQh- zijwifTrg@Z|7Ts=6&~PFqWI#IOkxo5#)m}08?Tb_^dmyhHzKM7-V2=R)o{Ia;YJ-7 zx3XblBN;!WXnOqtlj)<53lXJcd{Xf+s>gr=y{`yv`B+WiODGw?Q$w`h6N}S^!8f z{=>hh!J{wlPsXQzbumlHi(YQx!Era+&7oOSN<%#rX;uipr2Ir!nLj&%0*B$Cl@3bo zwU?S~DU8`xX^4_>dXrd1U;LLzB%k`irl1{a`RTe^UJe8Cg%-@YpGsqHi`13JITU52 z4bK;99$ahE$`GMmpPU`!T$~KT_5I2CbX_P!o^5n}+Y6K#^Pn0Z(cup;b|)#QgR??a zF9uSO({n+}^*{=;P4(M0Qp$@P%_?A z7odQhIoVK8#z{43&l0mkI7$axa`Lu^_bij~rxjpSS@E`H6!1`8z)jWTKU)Rguo@j$ ziRwuy>-Y%4+r%e(L8p_)mR&L)Q{d5gv-S)P6pPY2KZx?(akq?)!r>3&SK+$%4wLgZ z(_IBA)df-KY*=*qahp>zept2k(16Lm0>2c;-8}sl855tl+>`^?uHG%CYq;CzXkpKi?#%r0!Ks#@p&zx1}tMMW_oUGEvLcE{&r01&S-r zUTrc?(a48p)&VS`ni4g<6d3NM8*niDChE7vD`r-e$9F--#Pl@g`9+KK(mkZo`L6rucTI?8yZZ$@u3Kny7&J6aL_aez-FF ztpGzRU6-b^XSvy8VJK^Z?%ujErP>2H$v7#$#aG@CAU=UjHqCju!n`KO7caBb>-J0Yg3T{Zvx2`*2#EU5m=)FaV?B$!cqkhuUvRTxM3%-d|o$ zUn)cFfMV9jqb3tFmY>5L)n$KGewQ`j4|>=mO~${VplkFWAJ_1Ut~TqZ-zAJGMw*Qf z>oAB72Ch90@6WU{f`0H)lg3?$bVC>smSAc~)vi8N_VIPI?eV~4XY8=z(ZfG7n<&}m z+HQ_j_VIO9jQ}CmO4LbfT_`?>UQko?v@&n+E}5;tVod?0)uJE@;M%(ixIzFIHYMYe z3duVn>~q|8MQ?|l?HhCQm_ol;w>MyD!G2M#-cvCphfjXwG9|gPI2qroSQi}w{C02P z6fjCk3QrWGT*_L39r}hT8Q-V$V^r@87*49gg^ckqsRY{DZIYFd_pn1i#i?;sb#!R> zfo z!lQPEIEoZV<*AE#nfA6w+hAY-pjcREIeFGlU#i=20gkyq<1319i@3~fcPBEZ6&X=S z8s0&MyASPsR7PGDAXnrWLwoQmb@jahJCA!8LZle8?@F_`6x3Q5c1%fuB=*z+*=+CH z=w>2Y2jdFTb%ynxpeX356j#gM|V2x_r#SfZ8nY$XXCPSfp+p5GmsxUO1&7_~K zMspO(@^YB)(<(D+##&wubW~I$@Vc}%IaW({+%jR>YI<6LI)^1k&-L(;PRquILSv{gUlwF*wr+ z0Y3R{CR)cw6y?ThAT5SnT|g_JFQDy;Irm4_thu70El#b=T(?~4>pzbt@-n;ifkz)?AOe0cL= zvwyj+I&%EHGvZku_C~)l3xwrm#+;qd6kn|ikr}gG-*&Slbl07#eNkHlAfBDs$sl*t z1@Y}dfCWFg{N)$1iaqg4Q&X3`0XsN<)2j1~TQ8IGuPKtErp@TCtgI|6|084Uy%Fgd z!!VI9{9l47_JG;9QLyuPL!zf{ zXIa@dm3`Qyt_s!Z*yv^*&(1&?_N%$~)a%V*%PLijyjLomYxbJ8LmLe^xd6d&$@o4s zYCjP{qF~BCdx`fen5%9wxsRpjgugN87CoNbdR1-9C(POaWe2PBd7KCnvJR-;zA(CM z70#xNhJ)J|0Xr1s zq@%8y-Yr!}*I5iNVSlv+hTkRyet)gmB*(4ZKa`ArU8%HajD%;8;v^vMa%L+{(3~D= zGNN|QEm{cW_n?>_47)Yb<`8PF3%mN@>@bd79#=CNs#G>l=RGFnKsGP|4!+Kw0A-U3 zY{M;PX8>%y9f${RV4?sUtA53~5Q;;bp&lNWroM|24k3sK>kCXqKb9QnQqBqZg{C1N z&Iq_GWmgBnx>5jTuuYSg@%oD#u`1 zq3lZD-kEv6lauR#pXJ5155y0qnuoMe2PnjfR?`K+_k$*NG=WPG@8 zec`G#^v0IzLW$Et?xsk_-&nV{Fer}xDDB$)QIllU<2hNIuXlj%QVjdq$4n?Srl7)$ zqgiW0hE>8l70GgWMaRk&-RGn|J7N)i1#fUCl zP_N!E-4mx_RqxQhRc{aK%@uP+uBc1iuz!D?D30;5)edNP0FXd$zokMim-K(mBZ}vW zzJ+(W_{T7|MYwY!V1otfI+(L;MH z72-$wHE_quZhC8_LPJp;E89Ehx|zxCZ5>_owwdW2ZCyQd{mk^PwzIqGhDwDpQS?3S zXLr!sXQsDzclLDAjWaVkJI?Cvp?xzmx>v63?51~w8Sx=6XdAL{5{TYesZcZOC0ZEM zq6L6lk=|9Q(6bGa{ehT~Z}|m?9^F)_irqDFBOg8P1@!Jpg_@-Jb!Ik8(t9cuS|ph_ znVAKM6uq}np^GK=<7RFhf!eR-<|ng&()%hEdbg5~CnG>Cl!L`SxWCy@ZKL;BDs;Bg z{lP}FRYlKcQKp+K722cF-CGxA$Sr1q@qlirR7lC~A5B2NSyt&`H%W}JaRr#KaWSxos|kbII}t)K_9K3 zC!^?N^)qE8eY}3AjHXZ2&us5(Z#%1lK3PAvqr0QMtBXEWKX+wES4U3|eY$>bds|n} z%CoxZuKL*>ZD*g=zOsYvuAkl2es))9TNiz%es)i1PfvGS5B+ET?Dp=n&N{23y_-H; zKfkkc<;t$E&JOxq{rv8;+S_`1y1VF}`uXjhT^;Rboz>AppRW(l(cRP4xw30zHyx-C zu(Gqe=d8A~+dJqB^#R)3&gwj?tG%bAi@sQes7|HuGKuN=rAmbs$PoHalQGUp<&nIT zvG9yUIOlv>E5DDMpq5`yG4Dfp%gs7&u4HBDE0qe(6T}DWL(^9)75W&9G0|U47)+7$ zwMvB+3WC2U49cvLq8HFqr9zF8du`IlWqp;w)yVYqN`+1lkQ+>pOsOn>`fpS!^ilzM zp9xT~3mjN;Z>2)VN&e@|d_V9w|Gr9vPLlj7GhdbO?4WN}D)b70xNU(!rGn`0qHk3y zbdErL+JxXHtQy!v->y{Xg#z%T36Lw=<4!>LS1L4LvTu2wQ3L!^*oJST?^G)Ee1Z71 z2@$m@+UdKM3LPtOH#QrXqHrDbJq`EH+2K0r`x@@uv%_`K4>a8V+2K~w4>jD)v%_`M z0~+qO+2PKj2Q}OWXNT*dA8EMTO}MJXb$$muRH4;)J%g<_qtmy8fSJ$$74y<)J7V*6sTm+T^T zM$TQWUmn>-vAstN&RkoK;2(djo1s{ese0v5f?kqwb`ov7dOp1*>j%Vp)i3eAjQpCL z2m|XHJ43PO{XfBKWwWq;nHTvY(P_Jz=p|#0`Y|s5e{qK15kt=06mGV6QLH!i5**j# zrX3NyZ*|{Vmm6k#6gytG2yD9K>iI{7VU1nI@Y*^U;+$K5aBv_3g)V?tjG8iAn<#c} z?3QKk+1f^F+#KvG7+2}r!$yk8xgwjmTH}xi~8Fswh?}TDv0wu|TN(RSV1MBmaT2y{ z1@@QWli9+bo6m?Jp@OW)wN#J@D|iD}@CH&s2v}7^G7Fj z;NvNL?4WZXfemNIX-UvVuSCX6_@iAuw(&=Yd|b;Po$~R4Gvl;$dL`$C`=mQ=exhZ*M00!R{D6kBC}Ga(}FH+m_$l zOhgRxLy&+oE_g4`WriMp~wNz|_uw8-K0Xqw2~6#lF(e1>cw(9o`gULA)sjNqndyoU(6Sw_3_YLdjMEZ#2fdHKPVS7; zg7tf1v?QS0Vvwzub;gMn6p-Z5#~J8#0J;<@Z=z3f${Ue#EmFqmQ(UZoVoI~!q#FP3 zn8?)a>ig!{GH9>MJL96jKBw5wvJ49BTE>ukY-)nz33 z;}m}`V17P>WbpHp2tRvzz@+4l2byS^g1>m#F@sI?mYz5*JASZDeJa$H33m;FI||h9+&mCpU>duQ?m@1qTQeFrcUKUGnUm zeqmeF1?=lxU^Uf)K8)X=Uo+}rH)Ixbe{Z_k$l}% zF#-oSEIW8?l3xFsII#$R_%(525qw8g1Q#zz(yx(^v3+(g%i^-ZCc0H&*tG2RgH3c< zZ=9CBVX%qbiyxW6Cb}D-4EZqyc>zOy2>^h6qzdpE1#mF~{2>H5b`{J4)b&LLFvqhwDE!{pOCl(U@eRZ6YU%oL$$6p;|6aCa>zra^toR06}+b1qfHNLP1<3m0Su4RPvENRY`B8lE;vbN*dRolB48i zJ01V|SnQT#iReV;=80ft*P4)-Cqn1E5}(XV{#=7kVdaS!5a-G_ILY6GAYg9o7j|X` zoe)-k3Riy$s(&L8@=!iy!Nh#Z;*+cA&pbY*`cqJSAm6B-zu$87)pyb2u=*uj{Ss7v z6%cauOS&=%x*nfgJ%8SWPpN(hs=r0PQ9Xa(e)QG%(1~I7XL9vtqWVt&Ay5OS{}j2 z&P4&+VxWM7ZCmDqwQRq=qeoGEl<;SFnDDJf!r!s7+k{Ua3BJ9fXDj^Qa*y~iM+85+ z!vJ4!B=C-v-3EB`oZzKmUY;+xx#5_(819!#rJ@)3tJV%sGJanqSaGg$rQ)t}1mA9z zU^t9`*qjCYk>WVcw@#@NKXVWmH&+!X(T-ze15(G4O9Xs^ws)Jr@4x*>H3T;B_rCGy z;Ss9YJACx;orrnO9yNRx9O@lAdU&QkfAsK7f9dGqnf}0lr_1YZzO_Car{TP8zTJHF zzq9$)+GT9M73@ML?`9hdFs`Lj0n&|`5*k4w#8 z__u0qL(MuwZzzwLajsSI7?r+yp0Ssy!n|m4a4=52s?u-#Tb1^pQiW#)VkNvH-Z5KW zyuh74R`&WOJZ$I|qKAB!w?Ba&bbH<33Z8H)`aSeST~P@t?1f+;Jn9qj|F& z2CAC$Rx|R^vmD~ z)qRhc`L5*_K~p;k1jZxmG%Y}%Fo6)p z*q18~R3qpSe%d8~pEm)0CpWq)%i*Ullk9tIvIEa5lsE)c3H1Xe0FJQmDY8O|15v-E z8g^wQa@5Irc2M@*A%k?pqN<>wMHVh&j2#4dpFUWrP>kpK$DamJoWHko+6W zkXH`$$0-kMbQG(K364Zn;u#5!)hA!tIV5#luzho0jOc@LqH1XVY-SEeUC`p}&*D`1 zi^PL`|ETn;6dYbT$AWmQU}X>husv5?;g4A*d&P2Gq}g*3(bY|{TN;Vz&R8tHqGWki zp^g4|QG)1%9O47GD=`1A8zQmrjL2A(#`$9~b z^~qT5jIjdIXPCTI>*93AX^Is3WbA|2G|@)XH0xUa2o?=lnTAe_t1;<$Zrn5G{Wl{uP*$ z70fnD(F8Sm5F*I^bjFLpOn22Uw5wOIsIN$c6(AXn5|$yV!~SC7I(2t^E0Kynqzd_D-m&T zXSe~crWcf5-x{@7C_9&izsT`mbUa3BDe!21V!`wQNqZ5S!mgD+(;9JB@Ree1sme<9 zGf^|v%R|J+hp4$f@c0!DN){ zi7IKaB)!gYvqN;E%g}2tDO-7JeTYBS_wlnG>E~YxL<50GOPBD^{=ge74vslKoxTAH zbjnP^-!R@JZS3rz`Hja+eqNe*d=!!?ogYi2zoN3il^&!_-lI|?1wbrv>93M|A{;?W zqbxo>9sORG9>HaEiQ}f8l|l<~#*Y7K5tVhy(PO!OqKS@Yd=0T!TJ_~gqH_6M=wiA| zYKan#iy*g*j?%owdBb5_BP~O3D}YG_5EX}2xbz)Rv>_!mpsOcJHqmi7Pu{#XZx^Ty zH_uV?076x%M?TKAUFu~qsp%+<(z&#WPFg;LAjZ6SdYwpMlpefn!qxX4nVdC_M?Dw5 zxru5is3Ljx(xw`YAq(Z^C8Tzu8Uogp-3$}ZlbZZm4EfZ|-%NF^d7~?LLbN@Jikr8T z1NrXY?;2KAkzM*`CAaO}G`}I4R&pC{I|cwa-;*MqKM2%1xy09o(LA;@0XV~QL_c{C))Gr`9#f#!A@ZJ zikcBW-NHX>8Op>eT)OF8H%VIuCrXr%F|tK=*IMcg6BE<>F01A6Mx2tx&EJa2!&7x6 zqK3(v_@V-zdc4bLj)^G3*yS_Xv{5cPpw<(o?gE2|p1EAJXxAUnk4M9z5KkhN4#im5 zBWfP(X{@KDi3U>Rrx_m>J{-pb8L?`OjXcO&cbv}{iqb`xES#FHu(mgEu*6}ET5PKeH!E0 zyM_f30>64i(`+-Id! zSp_FAGm)X2F`MCgSlKei17UX;%}*Sg4wE8dee1d*To+-ZP)KVO0y-J2!Gu4p=ERv0 z8`mH|{Z+y7LO<|uhe}IpS{V?{e&8uJWDE*wWnv&i={H|SORj99qQ#X9WCYXzg~q~)w*aQEF3eUt$H#x4yQ+y6_b!#n zqTCKa_BEu^hFOez1N`7d!&%=3j-pZn*60ur-mw@a%f&>TS3{gsRhmpY90tzk<`kbm1cQN zfKvQrw8+4`;ux=>8 z^97^9t6HENuLZ-Z@)TxUL9yUuASaz2d^_0U-VX_`neB+cHm2w%0c>`QeA5=ab1#P3 zPAgxwiEmA;M^6OFb9LLLKti;s^l?&Aoz_J`b9?7%<9UvY0onQ1CR#lCu%Mi^kqS7G zO5Z1tHLV;X+VfVUxrp+ZgFuDuslE03Q8mJX2cx@-C{;OPgG1O0-a1!{H*$ktdR-GO zo<53R?e3yPYWUuxYA^2Tnxnl^^CNE+)2h3R<|Ue@l`=6GL!lV@6MS{hinX|-e(g@% z4KO*Wzkgh(t%b?+k7ivo2igVNq~Usebw00|eL#(PM5kW6>JWJMb(jizSkcQ&n^=_o zmC($}IKi6KX2T++D3kMw|h)(Fu- z%$3bqTm@Ha4@!Y1Am&@hMUUOHiWXNQrWjv6s( zp$^ykmc?Y!gKTWu?Ad#1yOH%Mvf25@{d8#3F^$SZsjch&6D zQeL1pyY?=ip*H!ni=)(oS*o63sZIvGs`h8shpQjQ4NgyQmu4y}Mc;g|m{+ScUTW+YUK4#K1ctaEWv-$6Y?H#iC6sbQU%KH=Og)?Rug;3Tk9F0XS zt;5Q<*<63&V%QutqHCf#`|Ys4l?epHB|6;rCa+B3<*{~R;wfUyhnN{|lU1TG-iX;@ zKy1*NL$V6jmKt6zK+XJomP`Q6zHMhiS9R9$U{AKdvLk%KKiP`DqUIbwFSf=m1!I(k z9Tz6a^ENM*By8Gak@ph$42p#jKPb9(f6@1yk-SYWWvOb$%SGOReF-g{Z5pc5-Ed>1 z$KijEEWL*+Mn|8#{i2x`1EZ2VIJN6)aGzGuU2`nx>U64sui+i2{4C+?EX|A@ay1L5abxq-WlW^5>v-UVCFB@xTpIZEP)Hllv zVz1a&Z!Tv5!LrpJwLRO-U>QC9F43&btSL!EBF|UP^G(8uIXkWv-%H;i^J*PgA8`W2 zz|T)0SeXB2;UnYJBqsEE zeo2w;d6&^nO?Vwh!}S6B6|G{rgwyX_HSK&CD2+gDF1j`3Y`AGwa%KV>6!Ah*83b%h z)um`Aig`E9AWCo8eECv?7()iY9Pw8(BYq3lfszyxETZ38yo_OqLB4oc%^jNWkuoK( zIATM!8Wvc=@^^#^zbO868NW)Gw9Gk}s~CTO6YNJZf;v0+mWY<fI56iS$>!@%b+( zhTdf|#tj+D$&(2F3ce-Xe=oTCUJ)YA%_vZBsE-M! zrKQbV@>yCcIa^8=LP*3KY~ENXlx03UnZ-Jr80eVM5=)`c7W+uA7c5IaNue@4wyGV+bfti4JHKZ!f3Rp8DUg+}oVaTxgS815!4~Hfj*k9Z1((US3 zhVGPKMpw_oPVa+eQ0pJ%L##9`nlhI#SOJOdPyp#g^c(3aUO_b))-c}&E&uN9Sg{t% zYu1|cV^y12o6a@B<5*D^uppnGz)=wXn++N_PV;Jv1*CA6EM!RcJhCw8n-ZqT3PHCa=@7J9}QD zVfsr7SD2m4;000rdMN|dQj^PU0-?r|-Y7rlIk}weg*6C%(hzy4fZKuV_f1HK2{aHO z+|sb~s!+Mi8qacE>?rwlslltDePd;Jhk%%U71qShg7)Pt-$zi(3~KempY=^7}j3m_mmLfRIhz_xzZK^uFLD9S1bpPYxjFaJWL50v+K4Z81ss| zgowst(zSi=hi_WdZ6YGR<+`v)g=G##^+5c zU)M>?w|88`KCsRX%9_=mU}SWEpb6Xf>QC`X7wzt%57aH%-9@jxzNyxBY%o|Vo;bMp z3>$fSdpCQQy1VFaQM@_>wpb+=H78n*G(m?Q_hvXx)w+4i|8v^4c$6v7*M;v7(sRW2-MU&U2%Hfw-&MQ5cLwmGCa|uv81)XIOuawBLf(VD_^q^L4J#we2i|NGU5ex`U@@yj7m5%*oghvC1wOZ0*kpMjDk`7NpGPfMnjAb!&% z6rsH32TFvDI;-Ws@u4QIFLRl^ao@wIAZFO;+VyWI&D?C2Yi^1O-@~fcl0hX!{K#kVWM28jK z1FGiS++(RROxU_eov3bcY71XWO>RGWKhosEY{p^w7s@) zY8WT!@K=pSsR4QC?WVja>7;Y+z*KMz3X<-cFt;@|`4dI$5Jj|&$ZxpzXL_4=IBu3& z6lcO_ho}yj)LYrCx7Bj7UQA6LQZoANRm-ok8!Y{rt-lx{^8y5BI@~BT~NeLWxf=xrwWy;CG%7BwYH-k?5Fpp;^65H;M|brU=~ADw)dzP?8Tda(T_g8dm)4jwWnl$v%@> zg-tdtqNhZjcm`i%=mQaCD8t1SW!M~HtqV)86kt`|UK8wnPI_C1vFPqhJ6Kzvlit=< z{k5|CtD7Nt>D{JXL6N2RnRkLy+uFnJiftX?RJpB_(s%-qnzwb)sp{8CTB?3^)9E}1 zso-!ZLyU*Ty;leq=5ArsX?ji??UVq$^kt^YEkdhUW1tl^(GjYEH=+}qX-_jh`FBUU zYNq(%`MBVm^tR4=>FLX8=`3v9@X?TM7s;;+hZc9Eig$n1)Eha%bH|IP;x(!wM+f4m-m8^%kW+1R@29{b{>YUrFWXkF zHn1|1)E>&(5Z~}j=|{QDP!>BVwDfPE28IH)-G4WJ@PRh!Ipy>GF~NZYekrsq5>+Hlw3&~L(cYNImH4V)E|q=S7sDGpC}U-I@RN}@@nvb$ zoGUt6+RvBNkdOphfF@%dOcz#nzJtx5J7c2TkmA{RB$@7toqsEcFL}0ICOF?V#W%J9vxH&{R!7#;^V4nAavYZU*5Lda>=Kk zs3zlC!Y5SkV`tWe<8PXe6B05r#jM>%hm}zI4kxrzr9p|ElPhItulg!kp6v$fAw>Hc zPTLI{bH!4I_N#o$&DefW^n99ZIQB7Q^BSfE2cqBuZ!^I--KD-EU)U@@&~V4s0H4Vf z`|za7R0H11rRKafV&{E2*dSt#V_hCiH{AVo0P`8|3&w1ho^B9vRsIJ&x_+oZ20^W7 zB}>mVoO%$&(eaj-qr(lG_8dat;$5OOesWZ#VmBOtPv^DW0_ZpA-)<-{umT?#L_l3(!{v7D1rNVK)L~Zgb z?@^DCI$EM$N%L%U?gmL_#fRk|0tosgAlJ_q1ODC?7Rcb0E;~!ZvRLzMZV+Axup~u2 z@-QM_j8x8GQ-+f?e^hgyi86>w(4M;xa=Tk5pz8diGTTy>#fm|&j)PL z29;k@ebQXYgcHE1ORWp{gK^BnwCMRU+se|?_ud8Pz$xK$sA@amE_C2*YZumqwzoZ6 z=puT--IzfCL-p{#TRM^I-6Tbu%S5+g3YvZ8GaCty-c>JHK&F}%=W!&kDhfOPJ*vJi z0x}X<$-O^21yN-EcY|ReLkjeTd(f9-g1_3A1=`=7O21R}W3G*;+CEPv(&@(UNi1(aI(~U8V`eYQ~B7O6&)m zCC^`tVqO$+$dtVt9f%34j8)E!;Z_8iQgdsG?vMSROS<-WP@)IbS23S0(Lwd$WNBJW z23QpoGtiAs$HZ#o`40)djU8m~h?c#V>{8mR zQhc~<0CIf;53OcCL2hU12KDXQy9S(`=%Kf(w36qz0qUU}8)SBL+)S=W`&7Q;^Gkm8 zj_UW?@xZe-x_$J{+9W(z{4SL^>e==MmJ`rTN^`^C!>9_hU%}+sxO-M*GG)&SYHk1!QG(acrPoM*8_Cf+pDQ;lyvGRDg5>Fc6r>M}W zbbrI=eh4%$F6hCAS3Q6aE1zdYaInEJ^f+a@L8fu=%FgEK=?1Z~Y;`$j74D9 zjt6w8Q6yDTHo5^lqf!ds4*QD^H-3ss6=2*ck2>w0x8^|*gl=Xo(bPPdL@~*hT_7B# zgY$mIP)I`xP0!=8$q9p)o(Fa~C3;eOfQ%wi^p?UwqmY_0H11GbCo|z|PqZhgL zhvx}DxNE4=6#NLp<((1ffTpK@Yla2Gni1YIZID3IE|GIxa@DR^I1gF9ah zEoH{&S_JL=_n3}BwTOjZu%b35tJ+qnN)fE3sr2KbjbScicKmzmrRq{f9N7j<(kW8M zmd~@Fg1!CW`n+us{ZwXjc2gL%mkipcluvWkNfYiu>~*Ai->q6vcg|vgo@35p=LH>m zfRBzhbUetuM_{M>;iYF1}s_orr$0YAwo{nIbOvP%nLRrZ(Y&ex`T-=ajo$*Q9Y zU_VxTN<;9#OUNw43r-%I>f!-4WH{+~ZX1#cMVC`rU(ZX90=0?KDrCg_e+?&>rsGCfVpJ6^nTqzMNsI36S3LdpRvvbh3izu+qQP6x+>GB`yQTE4!Dl zJ+(J3WFjH3oIG7`B+$P2?-?srKG;p!cbp7?9DHH4|G3S(pzzDtVooZ(^EgWYgJWBhQ{q6hs^2%;?#l6m@J2whT;VJO3O#Q@0(T4$Uo$~3f zjrZP*VhGN4v5q{n@ys8X39m2PWjjlUH%ea>tZ^Q$*h^Z&{@BWErR`Y0ySA*12C(6> z_q9^Q%wVF_PWC%N^ z9+q}BUKnN<7LW-=w76Y3N&LUMR->BVe92Hf1w-|aQayS=t8p~^l_pv|d~?mHR+$Hq zso@oBXh(eZ16HAww?jX5!7gB~-wMM=MgD@9>^crtC-6mwekuG`-hR=voWkI~nh#*T z6f@KodgzqG;AvD zy)imrd)GywWzY4A!Qcm!{Av>pRG9FMS^-93TT;_+5njrUDaqz1Z|up=h9)#x@`0}y zl7szJ8RM;~;ky+=+0)?ZvRRm+;v^A)QVo*~+n`aL`qk(}pjLmaCr*7tW&^Ago7HS^ z!W`k|e2AnvBPcW|TxnR8enN2Z{D)|&j+Nb2epV-ND8fq1qKXMzY0%8fu@%{ps$8bp z%<7hl!6|g9O$lB=XeLUDlfM>iA}d#FJ+X49Pz4`O*us5R9?g6w7(O(Gqlhzhw4l0a zTWWHH8XLKAiO2drf!eU5vv*R9{I(Gss;nO?b4l6pZ0NOcqegA?aMDs4@X!W!AP@6U zv*Qogc{>wOmnb2>oXOa}Pb=jU#<1=A;U>9w!>C8q5jWJMcf0@ zzHfZZT z@P)@lt89X2aP4B2PF~J834tFTrNyTPP(C!jly|V3ecR2BQ=6dl?99$_>d_yUQ11uW zSq896s9!#mBW+mxXfSnY3>!#>JV{#~nljriblmw3?1i5uO{}(E@`h8>TO^~oP$u4# zqGk(onN11aK86kpY)Z-^(DB**dyI6SR)a5A&{_` zCAHNzN=u*p22>?{X>H<5^X*I21wPGzPa9s$^C$R$#ToT;!&$Oqsj+PhM|4&dh_vj7 zUpJ0V+2QR5r}GwADez8DbDStbTPx}wgoUV`Z;n{VqI?P*S2#E|N9iwpV+Ox{!i2wg z>P+Ell;J-0rY2969E0On;!xr-ZfM?iDa}(u^QFbYh-3S7Dz}iW#J*rWoM{S<3s)^~ zt+wHGCxuCyidkpWv9r{}$u+F;={5JV)(JH(GYqJx^~7GKc+R^|8`>Yaes;wZZ5|_O zNlkx52AUcOBm;qkK+J*~in!Q|?la~QjQvZfC6&HhVN$^vGV&!jgMVvGQY3$$*&*=k z2eu!q7Dx1Zz6lTDErPO2c@5CMSc+aTqlcJBJ|)n%gyq5pvAu93T0e_RvsWwzv`xHK zAjbA+SiTCD&dXdlf2BqJe4y7S)yBHhiR1-$Zqv`DtjBJmjFQdJQ z)YJvSO%_|eu3RFa6R(1WUrP!XWJ9KsSk(J7nd5lot;7ryuf%ar^wn=65J3eER=Z1O z_NIm}RaJZTs893|D#Y&r>}pc$Vk{~&pH#7Hzr~_oW?yUQr-eyzyAXqgemLQt@h&JP zj7Mc08kaAfuzh7M5)@s!X!0k5h+8$!AbSvtJcL=;I6iQUlCXeNVOejr{bJe6C=hD9 zAI^}%zy#aa8`p#ivtUgO?6U0=Eft}0T=`rkpZ+ep6i}NOTb`ZCTTTIrfD;UE7}UJ_ z+s43~jh=LBDjbTK9m+l<$P%5$i#4e1mK*R>*D)KaUws=Q+NrN+P?A^mgNqvgDsaJ~ zNmEF0o>xu-t1yd}Mf7un-G)I{!trQ7u-$;ob81NqU!lZB%+Yz@LFX1nM`xI~wM9R9 zzo}B(>U9sBz5{JiyZNp2Db@QLRb{va(ah0cr+vp@e`E=pNcHwA1b%=pZ|cNd0~$#L zI&0#|Ww7F<2OCqV=J&vnQs7~=+wx!hH8K&{ zJ{Hh+mRe6^?;GYSEKa$Ca2C&({V_YsNlGpY*2HF8l5DRkodK?1X*(4QY26G(>?gis z@_AR$+W~{?f$ze5Q){_}xs{ew`ZJ10A(NX`=2+GvdcpTFjEW;~qSnjMB3-M3qU*nF z@LwgOXlrV+qAQW#Df9mq(>f+!%vgEED6&rh7lPD_hciJw^gZx=dwW}l z93p{V@L%79t*jhwCq(n0Mk>9F(!W4s)XgZBgoHn-VY~u%F^By_N}%w3djI#E5E3

fJ2>p|^<3ZjfsTXyFeb%BqM@=`*o)qUlr_T!aRGU^L&%QOMG%;h(Cis>4Q!9m{zP zehglGwTQRMrB+_s)UQur{lX8xr0zCtd%%R=Y2|7A51VK@zG&KH)+-HOg>aKUgzW7V z>8i3R%uS?vS1GDTUDbV>pb+ZPA3~->X*715Vh=!->=ESEBMJrpcWJ|-iZVHP3Qy|_ z49LfRXziF@rdUNj(7mr~_S98ERP6J1sP+3ajnb`%^8c=}D!m^!kixF zeL1L-#IyX9$I(*Ryz^`u7iikq&_hzpXL%c1!mz|mV(`nTEAgYbf(H~66RGJrw_w29 zBW%GyFmLz~B(>wB?Hzh7vpomfV97&GlwLIaUxv^&V5X`Ay7eJGkrD+xsmkvv$}c?x zQ~z_q%BuWVvn!Rx)bM8|S1YVATWKF!_G2hW%kwCm!Ct6lV+cv!LqN=rc9lH=kBT39vAt?+rPmSqu5s&n z?Ivc3`MMt)eA?x>VLGMKEy5$+TqFBgvsh&7cW*pZxl&5_-nv8QTgnE7knfWG>06IJ4i(ofci`!~v6&f5^}be7TBh)T%A6(I9!1x;w^ciS3_JhD%#MxjXp#Q-XcJAw z7FEn%KSuXq1hyrG$DZ-WE>u1UraI8W@!`b#-roe}7@7`|!k>Kk&crRJJsg{&)Oa}rl$6&%I4bA4;wres>dG#2SZ52iqh8) zVhMef@U7Zf&Pu1~uc~4MTy}7v*C-_pb7X0uL@hF*V$QX*i1QHxRV5(IN4)Wx1B}-> z82W`Hq%5Pval`isAqzQrRouTdKtDcc^39l>)2XS|id*SG6zg9<=HRA9!^L*C%)wqdO+Dk_|IuZ66?=j4AlvB22^z;s0 zaS34yWAT(amS)_$p|I9*`z)?B$h0;)G%FJ*vBka1h3~V&!Zzw*U9)()Ai#4UN2xYA z$45~d7iT|=n_nWwP+(FLA48D|OgCS)_z*R*nAFP8$PCS1NcQ61KA|hT&s+Jo5{tqf z;-!JNP z^*_UhTP|>{Asu`*x2`!~^aDwme)VbW4?@bS0N01o(~_h`+@RspKQ%``W+a%BC;zM- z>0$2Pn5Nq!YG;ymQ9^zo?0Ga_+(j+y7OQ@+ZsY z@L}mSa1w^AH9_>FZL?1Z@W!;srOvR@F(XgCIw~ViY^(KKQdE-uR+qnn%rYITJs}0WMkEZEL5NH~kzF?@6i%-=?#; z@)qHZ9x_8%;nTifU={y@n#M@p>8bR&qB)xNwY-D_;xV>5agR|Lz2aI0eu1P>j5W){ z;VAjdp;jtHUhQUqXBS#V`{Q88#7B8wYZ^QJqap7Bp8Z&of41lK~e^H z8c}-QQ}_VGkk=!5r3^m!VSlhQ@5=Q@W0E_H%t~yPD(Pl{9cla3p_+o@mNGOMep%x< z52L>LdE>j(hs|FH)R$W<1s*qkN~PF3>3)^O`46fOVbwwPHRAgSCLc$GYeu%t?obhk$Y!V475!h$ZmpPW5HWXL$O@ zD>D5DRBo;a*3p#u1UGy;!?%Xt-|#5&7v=MgyThmHhF!lx(;RdLryOqh45yU9{C)Ai z%SXPPbKDZ%P`NWMG4iDyxiQNhvwb=czu~_bhwB7^#1KvW~NZG|L{u;klOPu)$9$a-d;623Rcd^gleT^W!p*7Ub3Za@Lz*ebxEC4z!oV4WFYyDNs$$HTQ;vq|| zC-&YU+WqTKHC2)P^`1GNfC$M7Q6y5+yF_`3X2(t*lSIrSd`e8B?(?wdHBWJaLX2QB zMg_vI6jj!*G*yot4OJRPYWU-dterTiA1*L4ywwUC`;VNZ&Mj9tUPW^9ub4pD2W>sE zq8N_JDP*{uIG&Z^QKO%)WNbZgO119b)w*?GJcLm+E1r-?&;V<35g@#@{~=ws^S?Ek z&m9euG?GNBGOoA;Sq@*oz`dK{3p_JxUiIJ1l`um_Fti86Nzru1uQg5AP0emdRmI7` zvNV6^D;ri75eu@%KN{b0oC0j6X)+0i9)hL5*9K_v`ttVC#+1DFn z-!L{i+1eJWB6;%Hu%OkZMzvYh>_jR(p&EtFS34Xd?x^d}7$DVBpZK-0%pGxpopuHX zW?NEIAu>1gEn$b4)o6mxCu$DpVuyd-M5%&R+8$YnVgOq}q`wuqWpuPgRFO(IJ(-{r zdfSEW4VO0YU98o%VHmU5r6pBKC>1QW5LJE-4=~6nQKClUSBvd^`iOR{>RGk^(^hTt zt_3!C`8tKAdYv-M+ECQ=wvJGZZ|kHqD@~(M3#uaC+*#bKu8Prpp0!8HIUK0M^V0kb z${+p*oqwZ@$0~zn!nLG&uMuuyYo=rcnXynYA%fd+bL&YKvbz6fV)^L*9FcK&iDM22 z!3VE_oqzK7i)3wp5umnwFv>#R#zScqopi$X?yxY53`Z9~=0pxjTIKt;het}pS+eeb zvAc+e&*Ae3JGlH< z9u{f^nFd?y)OPSU#%SZK&ET4}eP7*h)|#69nWCoP=ZG6l&2^`cI;BVdw@KEG#u6bL zWm^`zH)j6v(fs3Q_)9|G4W+kBIli96NL8DhJ?YEf$y0yWy1Dqbm~e)O?!XT^L6_&{ zEJbT3IzjFjts>-0hh{tja7r!I6~8rjYFr{ot*NPhDQ1q7y02y;w7qa#3LPotW(Hv& zi^@@_u!-x7zip!9GesOJ81L?)cw%8%UG)~Gws-VUyrpGWCF8*#CHfGHeKmKgC2`N2 z+#}ONr@jqiTm7B1e0#V(bnEYobOirHYY1B!qs%jd5ioacaQvW{^Q;1`{JnvrIuN2T z`;O5{jl-gSqW<5b^U7}47SBuN7;&?NeiGU%30jLpasJouSQfk6VB6S{7cne$A+fKn zyNd&zDm5=wO=2ZGir1UeRdI8?k>&r+dxm{dQfBBGBpcuTWa!Z zMOu~l?5mP%FMVqL#vhZ~6ML1y#a)4+!o8lmwZ0Lkd5d-7 zAhz|P!l-zto(M%@_NBE5vF}ZWxU?uVhjG8Og9XoZhawG%qB`{uBxhxNM~%I}rY#KG zN{vq7P@FnuFkp|y@4Z!ffg_a6GP`Z1#BWV-SfA2I+dDg&-o_V5kFm z<{u$W(G2wl<}-NxvG*pyR2{h<7QN=>{oF-tHpR4v2-MAtylUczp$e0UU@%o#bJFtd z7u5qg`A$W0lwz(32q!4<3ILd+mD{f$y1M9cz zFSB-FEvV6f+*YFV|AKlq_gTK}ho*ykw0z;OGkdcZ;V++=!KNAzspr9?rcvMxZCh8; z{syvZ`mt`s*Z!rBxYdJ{&bi^QO;mH)e5^+1W-n}T*AP5#Gvq7_p)8<|f_Ne6tiMVN zW)T9&7(pq_*kFT3t1mcQM#~{ehjMt`9Nv!_%{DtDz{Oby#q7m2ML-Zz^qRjxLT4Kp zp~}vW@=;DY^fzNLRIh;!bB(>LlZ>Jq*^HRz#Z%Xcy3x5D?f9w(;6PQeoH{QO0*DJX z7Ek`CSUM-^Sqax-SsQSH_4S7h+B6moaM%Q>&WJkS5U^^fE3ciDj>{HzRkuOeSKNXs z4MR_(`s85@+>m>t&bXDwVc+jQYmiu_pqg#GUvC;gqmG9wW?%4k^b}6NsiTE_VR7qP zPR`(Zb`{Cd?8?_t_+6>lmAd8D#b(DX`*!H(g`BnCXS5+uCGE^-!(fPV zF9UFctQ9i|D!CJDCo?h)6be*E5MUR@A!K?mPwxR22r)cxMPT!qMIS$BY!v1 z`xE{qn{jpMwLB{F#mB{iVq0!{nfO0kE+F!)IQJ7Isxt5 zqX-(J{d*eU!u0Zx@af>5#+!}r4dtB8_0aSlwPOQ@@qN;(c%(eNr?HekgBbzFw@-Vo zYUBYYX|(Su1yU{Q(_L3JzLkqruZm`zKHVQ?a2r+PbeM?iv{l;SFfCJ-`>ko;)s4Sp zoEa=YtUOI#-MA(R+R!&LxF&xz4`UlKS-6Epnh>V>a<%oS<63#=Qrna4yB<{u5f5SXbSM-dDM@0= z+Ml%d;JiN`3+}7K3b^|3;5@`dz-x5i;5-pLr%)(*fn|{9)8T{je!Bog%TR(@syse# z;q&mehh7^)mZ;$$hD!(|Nr*XcWuqR>ez{Pvya}R*dF-;r^=W(q0l#lsUSTx_J$u4*t&0b<0%cu~6fAznCV3G1C4k8`tv%#r}kk z&^wyEvQd<`qGpp-D%shsww0ZrJFjdMS2B~PN8DXksu361en5AtFUrl(C$ck_KcNd}EJ4>ckxuC9wOoV&n+>nrF5qw8Z7y+*+Y_`5c5&hU{#EDoef>?(1kiQwkL+)8`h4fqsZ*y;ojP^uRLSY#Sg1sL%d<7D+HvXo z?3K#}YcbGbqBG-?!T!!wT|3W^|A|86JYknp3Xa}U8&+)qCsA&h(By-ZvO1IMAfei+ zO@KU52ife^GA_oV=J*^*F#<3W4K<~xiRaMgq)r3ZW5QhmgXbB<|E83EUgE$2H^-1@ zjsxJ%`kEpD@;;Z&)?8KF+my1q9dilN5I9+T%&4ISZ9#;_(7nJ2q=Z&7oAR4vY#IpH z&7KdMOEb))7JmoY-AyT%Ys?piYY^XGE34U!GjmP-`()0=U`)+eRG1Ln!t9&nz%OzH z^40l_(dHFnxR%C<)mpbX-^=^KY|S$J-E?Y<6H(KA3`E0Oou|#RmFp6QOgp&HPD5k} zwAl(Hp1hFg6b3t1C{Y_XMT{WIs6QpPI;MD;lvpXXI!_(H=C-zMT{+15)Gx(|6tn)~ zjWi($Eii+0IU=pPsO2bGNJP2+(Lv<`TXw5VP}S=tKKo62WxcyF(?VJrrsu~7@y%rZ)%%Hic`Qoo5X8bgt^@?&z_| zVYg$whweT$Re!|AUBb*w{&;Leq&BG8vj9@H7IRas)oO0e8H>vaDF?8bXZ!uyNoOB6 zYBlH_5-m@Grr5+pORm-CUu=r-J~^b?0jFrIC}$znu60VchyK=zSK3f27E0WToCfe$ZC>Ubns!LKUNMVs4XULT&PQ%dHpF7E=O|6JS$cUUk67 z)Y-$p)dhAVt9ZH0pn9B*%hZ23E=9Gwnxd-AfuuMd8rD$&pK^2`-JEFTX`p7s%h4@O zcSA?F6K^Wr;(hmOH@}mKH;?uy2U1rV! zZL43Uw*rn0<}BNU>@+HPv}PvRX_A_q{`5NNaJ${+6^g9yxI5zb z47*{p!RwyRMD1}djboPEwgi=nmkOWO?WLG0v%GrgxEFXO?QnI~`Ykg!OU2J(We`#k5ngk);mJIuOMze22EY;6Lsb;9a+Nw>eiiEonvd!_ zTKNp1YtMO1Oh`N!Q;ezEY{hwBt^)z=IwW71} z9PTmo-STrOEKg)2v7{NR5XlQKsHIAdnz6c#E>2^ZMzK=lh^hJfD!X;pcg4~?%kv+z zg_|fX{GtJl78Opz6Y~R8T{^bKC>KY3!m0})PVwqNcE%kMpD;O8IhJsP0!OSd%tI&V8n!B6A2IoLBq{FS^hEnysAiu@WuP(5k`0cL;xMhlNdZf5-R>CcG zw|pP*0@ap}W1pHh$(5}J3@^_%@PflV5qQV9U_#ZK2E225|LYuUET=+LQ$d9vM80VB zSmeN#!b#DP?5=g{R2hP$hy?aPP}kP!_sd5!K121b#X{D17laBoDdB^XRSd(WF)071 zB{gTxlVG4x_O$J9Np#V7>y%oD%?8g~Tz-Q!D`tHUQG$ym3B zbBe%4h)*0z%HcJ)@_VBlA@d;wB$P=*B=P7l$f!DzD-qNuc`#aEujxdGL^xjXsl&OJ zf(vbhi2Y|}r8LXC0MR{U*;uYJAkIHArI0hEl2A$!+@?=VY250nU4NoWXK73bdfZw& zK|E`h4EEZW*MVOt2h}frvWtp~Q*`jSs20{`*~wOPhFJ*1aX-{S;;^nosw4h-3#Zbv zW3&LCzj)R~ql5v=Xb7`&GDz$NS5!&ml{kb36575?YhA*!x{KQW2Rv8VYj-)yP4dz2qsS-A?F*?O~ zDZ6=%el}962E0>u#Kd!Q|F2#$>(uDgpa94e=|Yw+nf)%0L_{3(1n-nMM1$tKr2?le zSLm_UMnsbYPVR|dcD1XJqYM+wvSh5!gROe;DfTc%`U=eKIQJOiGjX6pwA4PYS# zw-95E#H2Y|s3)J~E@d_E5(n%nZAs*bRC(Arzwxn*eF7wu+V&EX)Hcfv5qQzw-$%0m(Tk-r8Y%x zpN$HpINWHjd@Zk=fRe4X8$?%ew*Cs|MKBu{d{`8m6ur_Q@XkuV&LgyiwT*-1lQV=I9or;r0BsrS5*nZmV3erM>n> zsg=(8m5}ILOTk3&uO3H$t8deR4}(I* zo0savYzT+1=jHQ(pZBW1vzDb>_AtgayGv#HTv#|cMGJ3$BdzEGM+fN0Qg`)6`le$} zNm1*-u`&p|B~(3&A6@tK_Guk1!!L#72r#AS2O}j^;gFi|IRyk&7dRmuKJG_Bjt zd%YKqqLxa0o6l}6gjp1H2%K_t)DQ4mCA`HrE+~6NlZ7k0h9kCd+P?MDMwjsgP>wcp zsU=1IP=D`ILbHfViGlNlB`yEZOHNCl3OxHi-}Pff zV@xsPX=-L;?&AWLOCet@4|_!pk*V2@7Y^4EcC-E0_2iuvv9DvDL*85SIwZf|HD1zS z*RcBSNTI}jEA+8UlKNpjPoHK_ibgT>R1Pp&k%2%eAtpz&{GBaTNrjakqQ!U~vz`fSoQAv(s6i4`oub83gnW?8A4tfjQvAr$bO%>T`X1-W85{WEP`Wq^GE&&cOIo zrL zjCg0`FF6kO^{~Kd9eAyDZ)d}Rq_00KX))OE&h=T~bCzKGH@-Uwk)jCMf65ismz5A4 zDU=G~s8g;)VW{e@*}R=ak|bZStHbs<_^i}yMAEDw;gHPFXz2OO%+#_Wk~o3IT)Q(k zHsE_revP7$tk{ZL2c9Y7voRCswg~OBg^A7#0oB>6ah2r_1}J%)!45Bb4%VTVj0{p0 zXo|uUL<^C_EUqM0mQ&AjTotfb*yCeA;d^}csGx5@Cy8|uD!Nwmu;P`J0WZBz2`kG5 z360*2m)(5)lt*?mbox$vDm-`qZbFG02rlROw`Mjj5tE=XiE|r4| zg5W#zRoCLB&vTh3pIuWfRf958ZbGB?>D1>ZbzEQXz%h0c&Sy9K0nEijl6Pdx$Nl_; zg|R}Fn%PZ;)sc;bQekX-Olup#cxkUUp#yJa%O#E*PZ{wvM#_#gx@_6G&xiOl9l>## z_1E=$Ru_Zu+Co^}SSW1^3i-Sr&}R8mm7ji&cCZG^fKB^dQU+xSj?gqJjqscGpo;tk zGHAnP`=kxZOyV`FiPZp}R<`hfDs)w?YgHM#fFFvVGuhx#gdR95{$!I7&ggAidW$M` zAaeOZRX&^t0Og2%NELdVZJnk1lq$+;{TL|lm&5AINPW7zZ>dj(T#=n%xc`bN0X|6bz(ju(k=6Z8@Pxo z9aJUatRfD#hg2DR#7(K}xI^@XdF+sEMQQz-`r7j#NqfxOTOb~Lq&nYuCZ%MNw*08) z!|rX2z2Y~T*9?tbiDl0^k@h*+PrtjbnNFBin$ZuQpR)J-`-$48{~#jXkz)Dm>OA-f zGvfyrm2+@Y!;}YSLa4+aGS65Yv74`weK=iTiRi~?!n3yGBHD>!o!$@qZ;m#tGO$A~cu<;Cdhz;<)daRRmGu215) z8zKZ%3!k7II!N=IUl7??r0|z7z{IP6cDv)7JI?q(553npaM&98?3&Hnc?0p)##B1X z0cCNotLHqPV!N^Ih-trsXAL<5 z62FGDHa-t^q{yyt)~wNdMhe9upRD?N&M}2qui|A3)d}?>s&1i$HY4bYBvypkcv&K$ zwpd*u!nS-{HX8^fvGj6ab&!vi_6z5(i3Sdd^kbKYI)-&o8{cv|9H*{aE;dhC==9Dc zj;pWdoWP7ZLe`2R2l5 zGhh~)QA4(J8Ux4aoWJPw)6nAlV2@WV?D5sJc}{H5c4Zfq4eo#t8Z{$7v&ea{O#Wt> z)A8Q!6q5Yo(S5)Cjr2Jo4d7(1GN!HnRd1AvRWHkyEkW(BPFE1|M{G#w_FuaaXB6#q zm72q=b~c?F>yaerl5XrkNbHl@1B|USbgR`gEA-d%t#qqX=d7(I>3fr-D9Zpo}T)tGL`8I z-EsPuZvb>O6k=wHQ78gzMGt7y*KLk)4h87>y{S5K{dJ7_1zI9fh#jb0Rd|b3&o%>U zUJF)D7yXecwJ_VJgF=3^N^Sfj5nuXhPaOkG-&_1_ImpqSy|c`fs^@SrBgp%OLQwuj z>MrE#Qn_YTxJ*=*AhwD4Dd=OpGhoTYtQR2TrM>V@3EWpOBY`*O6a8l&7JZ)9ivC`+v zqE&pDD{+PsVU~9#mRn1W38XW$cH(9-mu|sB=aVa?QHNgcq(A)%9?Vy$bkb!c^{SrTdU*4R2k%|dX%*9 zqw;&SJT6vcsx|#`C7|WYh+G_$vmqU5`Wxu~f-~6rjE^zOz&1Xj9EiZH>qWLCG z(K2B$rWQl^^$VS0IP`ZHGo(!G)U!ljI{rN@{fCmrjq9;vtQ+*ExE5!*2? z2H^T~{L>Th;xoPS#vO|I+)AWHZCA>y%P8(WC$po&vOixuL{|jAc+c#2EfbMr9sJ11PewcN5@5w(R{9sUf~90U3N63N(IU6>A@Fa(S#%}g^rjNze;KLz87JR z&C*fd2%dR{-Y)(4#fCKf_KOmUIOYZ7?0dj<0iA_{1_35v^ynU;7r!%g*@PPq$Y}6w z+!x#t8+5?i<}4sAwy?UhdF{?!2ZN~53* zPj@GFtUfxsfa#7;j&%NcU}UdYZzwV0~j zvgf(@sbQ(OBD86B0|MzvNhdo6N0BEzOl^#;?Oj#SFrL0T0SgnrgzjsEhppydqTp); zIMKR1F--?LE()~^j~k1#YGeVvBMB`Dd_tG5X+RrQ$3e(UrRqduHaw0nDutXfKGq?l zAU$u5OQ3vqb)g#a?Jzx>h=*IFR&%Zv4IVJx=*K$3PuHvgdkw?ix8)?n^03Ib;6Ap7 z58u7LUG{B&e5WgJSU|sh?3k#j;7E%u58~>cy-#{9s&xh@UbJUDy-Gup%t0T&t?~d)s!QS2ZfECdl zH{n#wgQaHv0e5BgW+rHjb(i;c7RLNBWgB?$Iu}(UQ1dGI771_pT+g3|@Px)ByTo@@BBC&%XX(OQ2S&C= zq!4GF0xiuWEgd_Uu!3T#H;Zz%&Mn-hTs|ud7RX1l9rC|9FBs-PlFO3gV2Jnsz4(Y-m z3rn6$vz%A;D#U?*ZI3b+bvp9Xv4i$TvQZOlBK=u*;(Ete#q;9OZ%@+UKo z+P}LjzvB9~o1Gi^$X+Pne|znBH>_m?4@VJN@Dx*jby1tT&+4WO-=Oh9UW}#e;rK9K zdD{UtO0oIudy(K^wWz&~*QOyW2G*&@)0L6rbd8M>3g1p@QCN0S8z&*H>rzi$a+&}y z2r`2`yctP{j>Ja@93%*UWs~UY3$g5$*vkCGIJ0QrPO;qZRs4MSHcY9N0o&MDun&)t z)a@Ic6=>t}%-p~c2n8;^nCACl}4wkB0V)iyC(4PD@dO(Xy9EQ@T<{xIAiM`*8ADqz`({ zMcDp(`rYSWM`F6`5%zcNv~ftpkhKv(E%dvrS8YmBLi~9%BMD8M02GzF=o1O`&)!r| zjhRA>RET)U?S9@>Pwr_tc*Y8)q8F%{2a3ljwb$F|&W#iJ(tyap&Pr z+}Y!kVt-zb^ z$TplAVWK~HG7Il?sk|9xUpjO$zZSN*B|$uU8>G2C%)ubZSgqwJhj?*#@Z{(j$UV(f z%EjVZKg`mj+f&p!^(xtU4FS#PTjsS-UZSqtj46eKTGNkt03#;M*+?$*FC`UNN+xv! zlGeA$=z_eC;u>oYmm+i*Uyr5tqJmD3y0=P)nxx8J_{>U`&@YbAdi&iupYR}6FfJ7aJ)t)N+xs#noGG9&1yl2%CYm42zjg99oCx7&u;P5w zKYx#3s?sA@JC2D<20Od@@q{7jIWb6vl+ZJmDfkus|FXN74;;_4W63_x7kq_J|uYxx=B&l7tF4Th(f{ z`m=Mb)m3 zwXKl%nHinPjJj-H!7t`QqCZ{&b$p20+Fl;jkagShE=ho>T018d!Hfw3C!u_)tPkO( zalhugBrdZAt@yz<2E0|4C1);N zZ>4`VfWZJ$Mrp5;)DhY1($6WqZe_+xdp-I-!|PSI8ELOi`|t#v)GttiE+qr)a!W*_ z|K(VEJg-RAr1r|W>yX%C_!7~VdF zlat%8P$$?d5tU`K+Za7qcPXuRLXrO6E1X4S&%vr@9Kg)F)PHLtI7-lyp}+sPro}G? zu^RrroM=kza+&@A&oMf;#^6IDQ4ftoq9!Q_D_X`{Z^@AowJp33x+0_vW&ZFn{0-Sc z%l_^l43G^@Q|K)w?o6k#^1n1ONcX%lMQuvv!DLY=<^6zGUbBD}PpQYlb`y~xH-11r z+l8f~n{D<=h3rLzq9mCe^$@P*l-ThhT8G*P&Xo!{LR{%Zw)$w*b3JUe9X70V^m7~9 zE*kh}QDWE*3ROy@7J7U9+@^|OTI-K!XTo!bf#BPug;9HwYPPjcA5v%re$`&E@D_v{ zT9rCpZP{V5T-8Nl-gZcrfXI)%RGr0m26rMcQk!87RX}p~tm8Oub9yZDy^n&YVXWq0 zZnJbPbDi1mDpe8p8-y~N4TwQl@P~dI3m`ft%tfmK=KR+zn43-wl{$q=LNCo4K&jh7 zsYm~v+3$*y$e0xgJE?_9$SA4XfNrgtSvSPS=LUmFT^Ir9q!{U4f(M8nvuRx^luAA) zsLFgvSbAaMe=VqgV6UoOo5RW-?A;kXwovO=ob#c|**4;3{miTP)6Xu)x61{3<3ZUO z?z97BzpMtF#FNS=A^2|1HoY@W==kaOV z2qxD-Qi|X0j=?pZu``&>fU0)BoWC+K@ zieJiIAox18$P>x>%K2W7#3N5)d%zWv_j9L{2$aD*HjYlYUz4+aI%>#ByTsr)w|%3D z1mJJZ;ZnuGl@ok1@wI`yLgL2%o=>TTtJrrr^Kn1Lf+4mv@!X0FcC6}#QOvHH-a@HR z-3E5J=6)?nD~S&ve^BfWin z!~Gq79o|T9E<4gSJlx&YyK2=)M{ZR|Zq;yKwxgpfo9pT69qH=o>|Hh7-{dV2k? zZm&1z`8#R(tK9jT^Fj8Bcl^Z8O;5c44;H`+Mvs4@0O@UImyk~$+(S-W4P|A&gVhlJ zkstShT!+$?oT5%8Khm;;Q%3gPA=bNSnJDLD)WL?cbeKi#&HmHmO}E%s!U~Owq*n!% zEL9=}!xh@63aY!S5%$>2S^rX_!_Z6!2PpTmBvOa8a zflr@ly8S)SM@!{wz6@*iu$SFUpH&sBftN)u^to8UR{v7^ya-8CX=~LRtI(99D-y0OFKA!(hH~rvB3@a?;v*n!MK@TgYQU#q(qCzl=Z`EIS6TT-qXL&Js?U+1z|h{9%!4b?xTb1H~0&W{}0W(>GPBJq*qp7tG(AtfGeZ#Ci%0zu9kI6}ezwXH6@NQV?r-9rI63;) zdGNqNg8QJ1l&%lY+juYf78hTt9m6UtPn=H&j@kSJe9UKsZRG9dEyu{c!5*)e4+^Cm zb0D%I9>c>Vdk%lXLhtvlf)KTP8>X3+AR`;%8__)gX^(W!6cDh&@ zoF`9Xoctq!8OKxznC}EBYONKdp^m_T09wYkH?T9uzMAxMs2qLx?UH`@a z%5qmEG0t?;qDl2=k?kteO|uZhE+yQ1Ia+*#6uTJ%ON#t9NG{R*ArM-pZxh7LptAY0 z+|01Za~1JCk!-KkA|5yGND=3bkxbHdA;bVogef{jb33# z&nAyF7El9JZk)`_mZ)nu49VOUzXOp)ouMaAHV%~{EXy50{)n+>maZ&F?tus00DCJy znNo8FjytwJ9&B2Ur3F29u{^DFD2)tA!neYXrF!pD8n?49wZ_5z2A}^v#MG5?lH{Bi zT7Y3!zV?_!@`XfFbkUN*J~?HZqex&bc9VHybhH+%-{KSyYI#px{U2x)IS1VHexSYi5f<(A>gA{(dE>iZL^ z+3%0BT$e51`S=V6y76-^!^q?pExay5e=ZfH{c?4MP7k>Q7Wo{j(R^a<7qXNVVyrDq zfU2Z*tm)zkI^@y9ba2F6ZW@LC6@qvW#K@E+$HjncLb=Esk2l+u1Dee3a!OE-UFe5RpX z{aVbB7Vx>@{L8$6E`6O#@z_V_7~~34ams30|3Mu4#jZwe_q-9ma{dTqL;*57sOst2 zuS55SzJe8f?e8MuFOJ@V2zuhcH|AAVEmfzf%+y(1`+y$YZhwN*js|p8JiAEx8YW2XpPOmPRF{why8^CR6ba4XX1J$T zCWi)!*287c(;ojqf6UubE?4Q$K0AA}t1ZBUTA^k!ag|{6s(<*RdlTSZ&j$;I!qH5hC-O0$pnrY@`IpMCKWs`EDUAKIiK`X&B74gqAB&cu2Tj7rYm7K z>FkiRj$AU1MpJ`~` zN}qssVScor zPtj(291y>IePo8pxGlh)Rcoyd^}rv2v3;Xo9WCcL)fW9E5yOOvF87p-12hw=`J=B- z!N)krbH4A`#3jIb#~WOf8OWGdA@7Yz)H0u)U{oc;yTgU5UTHXr;*~Tt-|!Qhl?|~B zgwx*W_FEmRAg1T1-Cm8DRv*DAndrjHSiq{%-@Xy6wF^5mE=I7m#oElHC4=4W_l_vc zWB3iO(m>EY_E=y(Eg9@}zsKb7vEX|9lv3XCmkv1OL_jOOL&@h;-jt$)O^c@8qH=p$;OgjEXIB04C+LT^bDEgF9|iE zmfeLqUdqx--V_1KVGlr))|&E2vMZWDhSYX-idv@@2yELYT84C_seN)U zIR?z;HEIn@G|!MSXI>5Zam>xFRoqX`SR9@vEg#DaG4*R7v$XwChLkgf_ZGllM#aJs$VK=d4!|eMX+MD$yc8zQpH0W z!$6j?1W5B*REdPlTCD56e6>3b0A45+>78$Oc-Nq=zxG?>ce#Yrw=k|FcsW=~d1CRd zrkIikqnHx;x~YRt1P)X7aDhcr_jJaaf!$!gSvwUNN5>@^*7>C>eeOHXabb?iEMJLg z8*X2tu+(yC3VZ50wn`m!2UaVcC}pdDSe2khnfA(GY=<)-#dN@BVB2j|5b}1BvdwYz_{d9o__ndt}p=d!T`ESnX#1+cHG7nT zR$0J?yCYSWwNJMz!JDsWXEpW(k%;o?F}x-cYViKHVXd;l;wM~6GH3A1MI2p}JIJ%e z8>Ha6R6GZVFlU?*B%0rvn(Or9hQPTnaKZ3Y!?xS_?m*obbW+QGZ-HcV?+ymd`f~x( z=C?VM?1c4=RI*oaAiEZo(nqqWw6n%@wlzEI=eGD^c|6Ff`^1jJxVBKq?V)h4GzfAN zh}8#OHaZQCwu5}0jRYNZ9GG%H^UZ?`{Qp_UjnD7Kxw?(I+t zZEi%tHOMpZViz_|5%nxmvn4ub^Dt+fD?8O)$xHvnAuD)He}LZlp%`aoXAm|vG7H2Fbkh!QL3^YTBZaDCToRpYh3)dpGFS-DWJ{)d$uDTie-*N^y2t*lyWZ=x%Lk#d%RZ|+8|T#D`UU5i zv3J(XWfEL^!HqU`3>^h&a#;Gg5n;e^9HtH@9(fp#I|v1MIU>Y zBQn*4l({&S8w<5YB`|;4TWxE9-P`bMSp|Xzvn9SS-ma8n-I;Cx=hHbi^2;L)>%tXJ zcQBd@!op>~mFoHfPH`%FnVSiz>g7WeM;4PEesGb$&zh$}AP!<~E9e2O9v=ai*l?k= z8VpM1s*i`&kG{v5xw+rPJvDLv)f4xGZwsSwVV_!fSTTRbh4-R&V~=$&N}rk#E6M)s zl90klR}elmFJAR8Vuzvw&YR7E*u#y|d#M?&Ze(*Iwn%zZy#r!P*V%pj@(5+*5ro>1 zqsG+DKVsF)%ZIf79=yg)^F~X%79+)z0SUEjRO(_UrCQUu--NNuzHxYJQ~a;p6zPKN z**wrCAu^3OYs@f!kXYgXVkT?`2MJb;SC5a(@f-%7o;{D{%*{3f7Mwab^frP#G;_M( zG@`GmB-cZL224~i5{?6Sjgd(#W?@3{J>y20* zk_iEPDJ~GRBtYOHZ}ciisZ57&nvsIKiW4i#Jh9J?7K*vRFA@C=bdkg#$_Q1+(K4(K zeYM3e^6FWOyD#2^0rb=d!xQ(k728>VQ{hyZn`bkqI+P+XwJ~cPjf8N3u%xj5ZA?7p zGfyhp%|L&NuF>t(FO{nsN=WwJz>;?QKd^18lUh#fIP!9n>)(RAptp#P zDk}M;LZ&_Q6S-+~uHbP$xnZro3&Q>8hPBG4NNzaWm{DWQDF7GWQg5`8FV<`WthCwY z_dIuVyDF)|141UG_k#^4#8#VfM7t|$Veo2}Z3+|Bf*m409=E#(Z<+H?M}6ovbmUv&rJTRA4BFH%@UmfvCPay)ffh|^cE4nvWdF{ zXb`0arZZL=U~bgr#!kog2iTeP~Aa;W|W=C$ZKtk+0|fewf{f zCx0oTiJjzTbSIt*K>x#rB)T7VFP=-=w)O!{9CkOp{3BR&;%@u{z7#Z~(-46Zl*Jp8QcmlE|#hrER8tKof^&O*|5K)|!v~7)WzU z(lFrN^nT$w%$eSYdefNm!tU?`Mg`E3V5J@$NUeGv9*fDxDM{Y^z;3W z8wW$LV^e4G&ih_7Eg4X*?ch!DWE?sI_CPApm9KGNTa9KKd5f2%-pa>`4G6Jyv*+a5azN`@0-vblwndE{1lN=mobrR9G@g<@;|<(QhB)f3aKd zfBY`~?`c*(DDcj15LSoSTa5Vv{xd`DygMJ4==`%>9rXh`*8C1fOSyftKvJKERTZvS zIp%~_<-p8JfE~T+w|py)y|c5cw|~{DRULi(JzafW-RhL{%#TL~Jwq)Z!ef0bK3ZK0 zH-X;1u2ns&R`quGbai%jif2G)AH6=>#SFDDwnqh8jG$8ZI)`!n;>gQn#tWZt&^Hp< zeqZdcZf*!nQz(~oQrSH@7Z1dvZhG_&)W> z9l!YODUl;0GobMN>Blh%dPV!ik@H7$0ln{k&VW6Cb%IO#LxmiU{p?>(X`eR`Y7s>J zp)oHz3fI>Ybb%hI1h7c^L*g^3)I@)%GKysK8D^NYKNRv$b=33~Q95Mh`9uCFF6yX9 z1Fwwwr79Qo*&>7p=+{3(Q{!w~gc>ijKf`JOV!wz>A|&I%f8Y}!*x<^J?!Nw&Jw2T( zSN8RF8`mMfII?!4L_cxqw`7*eFS~7~6)Oz3Fv33o@0b>HLsc(NB<^oHtUtB-BABDm z>9;ZBUHzRsD|=S0>|5E{(bp@9`ntR5?YB8Iz5sGSjlX23TA=(N1*^oh@Yl{-fQ);Y zAAql4;!jZb?M??~LBuLKqDXTJJAt!UWhooyZaeE1oQ{k|2*_EmpSaz9 z_q4TenZIdd6FYQsP!FSlY#j3{n}c#SQXo!8T1n9>J@7+6*yHCigPkIAIRuTfvg>-$ zn}Y5{hu6Sw?n8?p4VsSs$tR)NEU{fM1NB55xt;+D-DmxZv4A;g@7Aox;T1!A_FWM* zfgki^Kz%*5;gG{tOWZGzA+?rhU$uF(RIJE2&GLD}Wsfa}JY#+}C}g+zUe3ok@QB-s zI(i%(gt0>n8FR*-TiOR+DXN-f$Su^o>riiJiT7cwx2tBUV6T`v4|ggSUT+ znL^6D9S^()rV87w`YU3On+qzM^lU;A-yxPHaNlYWau?o3JYGo@OiS%;GSgc0Q1kAZ zcK(yY-?8WA@w)c0Ill(y|2Q{YiSU>I6Ltl2bL!MFF%G6@MZr{!VW5K@Gz9-)#$d2B z9xwM(f5*V7hyJ+t(9GP*-;7(oMcGA@UkS3_1PgF1Y z47By+6O}!ej1)>aP)HpJj{U5Iq1h2)l^jK{klUw)XgEGBKth_2r+LmIjkW*sXD~sA z+X`bou1N#B0-aAkQ#|8M=k|N0J#$E;Ksu|kwWSfzREKy)&45~vo5yu&Gw@e`HZ{v) zv=;L*=6tmvGczxC4ot<1ombr~@GU$?--+zXM*)Q~oWXm(0mj?|V|>QjAaUm|o|GJz zNujWS`Pg_AfE8KzLc;I`{As({?ia}&>gL*X?s)2n$U{^(&6+QiqZ>NQ0>)|%kB8`Z zt-Fp9wzJEoQuGQX30;5n5Aa;zCt!%KM2Z!7=-D~*W@C#)H zjP62g@5%SY`^gr4uQXoa3nX=&x9}SfO9$e>#vEYPyBAPv?M-!6Hf^QTzK2h5j(^fm zn%4SW4wE4x`^PV&sCCx@LCQ#oii+y0;!3I~P)0vZhLZb2iWb{A zBR;>eEf%W&3cPftRP*tIIk`@YPOSjeU}|o!c_P-*Y$RgSR}(o&59^vd^o10)C3egZ zIN8GVw`G7c0OE@4z6fEP2uN^y?iW+kIz1@pEj@#|k{)?8nEEv`HdPo#IQFDFaJBWH zQsY!^6|K zXxmGB1Fy1dd4^$XXj6&M>gw*F5!&F+2)td5;n~`E-jSlk9Ns%z5tf6hpIcujRq5Df zp+D!L2&+N1T-viD=VuFJUXc!d$=#IWUk@#?c9$H{m%oH>p!ZsXAP!hQxXG2`=jVv-uFaJPjATP~}Uu7+OHbeOb06F%5rD(B%jm@*Ok5coF1NY$HDW+!INdte660yR~rMo!J-CYjOu$5>a4Z~a- z_jOF?WJD$IjgVZJs{AH zGQlV*kGC6c!>_uVF+Skc^NRDru$(P;9H}5GNGD1ftHX@d`O+=ZP@&u0zUxg=3X=Wy zt039fIPowP%~Z|nzp7|uS!ZC8G!@$F@_WAu+HmKa5WM}OuYn@Zma5V>mml9g{ZCS8 zyXL2m%=(DIJ0ECk=-g)M*HW`6*3vQ0MT1u~S7D%Yf#fd92Jkwu{;(Z@1DjrlRz!U;JHOOQkRJ_fqEL zcj83D>sUH#xzk#Nm{~<{*bj@+=R`8LG$Bl{W{bmh1DD3gD zav1k?IO+v{j#}=zD*>iyf9_o_>@|EJ*wQ|rVahZ4X@F4A@vli>w`aFQ2Cfl?_Yf*Wp=zrZfi!o z48KIptUaW&UPWssGF)lh(oPzY>V!@!4;)PFE2pd9>apl`ro09RxsE$*gc!_xO^tAf z=w?g~{xDk~%twk~bAa}OZ$QKA*eUYD?MZlG(d0LT25g6d7x5|`c?nmPyB6(w(D}X# zT8F_|7K-&a!;;a8!<|^8GufpWLQXj2qC%0c+FR+X(vi^3`MUC(5l++-3WjRUD9N)l zHa@}{X&qQ6gp{vwY8my#I7KjCppok194P_Q@?UWXroLv{ovKA8@b}EI1)jw%y&JMd z{bMGJ_w<*8tMd|Ui0HF-V<#P=IkD$o&LFmbORb8(6qIP!Jt?B-(E=OoUkrYSzU{K; zUkqAKVozt?&br(D#o+(&Jw~=|xtO&%y7N0OrCG%2PtKWoFR`*qX~_z*)gQCBn7I2x zfuzKMT`cFTOd&1L_%0T#lPP2|mnf;LI~x9u+IzJe+EBu2Xuh>UxJzDcmRW8`>;LFG zdUVlpbm{nm4|euQltf%8d*5}2za;VrX5QLy(M59YtPwF-)r%f68fdt`z-h|$`{D_} zt@pN|fM*9*iZNhl5}8-;KhtoOTe_3J`#ongv18KX2I??9E=K$u@i|qlNj8qtd>qaw z&97`)?z$HfzT2Nbchq*{efYV@E1rMpxL2gizw+3!w_7 zTp{qY)hC3de!G%Epi9tSFPym&uMl!nK6Q)3+C_B7-u};E0C)K|P$-|8Dqpi%9Yg&o zrt0+X�F$%RjY7O4&aF;pxZ!dyd-IQ?uYHarfl2KeheD-7KGyJ%3KwL)STvxUd5g z-9HVB4fai%4=X*vV%jXag=~S;a^_F5n`mC3wo87-)|aQ|?$0`RulSLhW(H~lD^Yg) zX0g>+U!tb=sf)y1l2%HDdX*5@2*Djk2l|?6oC6pMBR?LOxAalAb670%6M0Yeg=|s>`X9+G{TW?15%47zwZnlXS>;?Q?z?J_daY(%J**8 zZ7J5cseNFRqLTVzR@G6W@zBpxbW(J<@bX!Q3;yO42Np{&9OZ5yPRcEkT{gy(rpcxE z)q(SIBvkMwBk*uU&>{SyDA+br_od{Ol^GY;R8nVWVkuqu?Pgjc(?j%{U!?f|+mh=( z?}`Pqn3l*mof~tv8@l)x5H%2iM|*O@;(zMc`Q*qQ7`0(|aEv^d61J`Lt%13%!c|%8 zfVyVmM|$EMyz`9lQUO^vR&4P~dG#py;cwuLUaxQf+`L|YKW00j=5Ov#QR~3FgtJaf zsL_ zd$;t%Mt4hb5DVHfUr-ocSQgg~O0O^Ir&Vwi%=zkRRSdPxKDzKoJzf~xNdpS6XFg!@ z`s|$X>h8jQwu=|MMHsL7@|VI(UUkLr>h7ZZj-+U@@`GF94Hvdn{mM%Ur5vvGr*9LI zneY4|)ts9Aee47Bi?1yNXfzQ2raks1zc17?(6;)>oS~WtKpjvtQQ(}JNh}tJHP>St za*O|?MI>CU9}mns9soPDJTrO{#8R?Ioyq11y}fnn12RJA1uL&WeztaW@T&g;olC)s z>0FNkTt|E6h|Ekx-_o(PsCni8!BWI);}%esFIC5V%J7f)e)c#Jc<5I%7$&2HKYGZ} zi878(^fx?^IqF1Dyz@{0i_(&wC@8w&S1^YR#PudH(9!OyO6k$|NLEN&r<4Le*L&IM zfUqK-{Eb@kU&KbNt*%i79tUT{O~||biDc;#ugH1vlB;5q{kPvhH&y@mvgl|h=qYNl zKQdD@YAHYGcaTGKT)t+j__JPW{P$$qxBXfPs;7wN+#fYu=~VkYM6z1wa*m(NQTD0T ztyeca=l{B*@+sDvGwV6m&0r^OAk(sER)y^;QqP&xpMD4jfWba_j{$dx%D*{WyTtth z8_#qsyZ#GjP^qH&i!qdPv05Q#x<7XAq>KN66LcV&dZK2*4a_-ZPC%R6ciklrv9I3n zhojhFA4hKw{F~Zmj=C-V2B1 zpmx$3|B-?ne3v_+GuXk85^{6<fb?+4kv~g4rH^nqfY~+ zMdXgWo|R|moSCk5-~}?!WSauhEtw^5d?ZD!nO{rm*dma_RD;GH?Yr)mmTLQv1z@+I zHGc%n*uR>2lLJUgPZLWE#w zoiupP4pE3|bQ&Ij=K93aojVB@tyVHdkC|keX+FalQi)SzOTlHkLyeeKl}>yNPjfC4 za-ACg+R~WJp$4TZJMSR3I$A>v`uN#hNRF+Ey>O~ zJ15(ov(I7gecA-AMntFzL+AI73W`h}N5$KSju)J9G~i`AUYQv$cp0sZ7t}JMjLOV7 z`hLD^J$vtGpIcId!Tm>ap4)oXv!1oqv!3x_k!fxU4899Me+^BX_G9w`k>ZbixmzWWVRQ zbbl-zLq}QmfSKi(2Vx(64en|!lsrG`LlPBX9Y*rq6xWC}VtLogqun2i#i{wkAB5Tg zjl%R)k`5~dvei-1M8@z8it`!au%fWYtuehSIWnJUh*R_MONB~7HImkz?A)xX@QBXE zGo2w=$@245iYy)jLxPzo0=!F>i(s zfd2r9k~+p8Agbr+OrCg;LN{+5GO@1pxNt(`h4F-=xT1xbBBWph=>J3;uHz5%o2OnToDj-WpHbZ5 znGSugv6?XXspFv}<%zhxi7^Z1T8_RES2_ia_e;FLQSSmKhTK@OXBJq6r0GEqXc=0P zokt`!Ay2ofyAl#Rp?jP5LHCCBX$6~SX|xuBm)lLplEX_BFY!~(ydFx4{zKV*!yEIT zO=jhgYMl)s+-qd1eHH&y;@gQiBqes(8FQR^MW=Sz1$!c2D7iGuR|tey>9uEP#i_;T z4X%W*s-UX`rLYjE{eD)QF7R^;7$DQsa_VeF6xVY|UCA6O%aD>eW1>*T{{oxm|&*4!vVqz=R8iB8X9ISaB~G$&qeB4g*ifBGO| zZ|H%87||CB`Dbt}0MmPd(G1{!^o$V-)_V{sjeqUSK?_oMC;S4$@cb^EEC zl2iVe)UOIqThuJ@vmT9rR~l*Kyf`&?mJh2cu|GLUQtwRF4F(x%7qzN13@jt9*$VWx zBgn8qgg$IO^?TukP_O&T{>7hSK3S2>YLgXt^YH5hS#7F1n)h%OOWRI!LNd-fD^AUm z$EA{RR$7s-Jvntu=?G{bu7DB(qlMe2O)@J=e_>vn&Sw_Ggw9VMfX3sf7(1uXI3U%OYUb>)HZ4B+c_VaLf^Pzclfpu#hzH=*;JC)F|k^ACi(5YHS`C@=u( z3#xf`Z9#3Z3T<)|t5`>!N$E9O8YV1FK+y+cI%2tI+H31Gy!4pq(AN@BQ9qYrp^7TY zwLCfe-;xEF504aFQ+ZqyH4KdBaXuv7_38!jfH)T}1oY}!tfDmnCz<+3)s(#99wNEaCLz~F@dY=y zrMHyxNL@czfh~i%azT8WH48;TZaI8)C%&wz9U@xV1hmi8R~Lo&)&+4|9C6m_>9hw` zEKL1eXvWqKIK7;pH%IW(DCcuL20GA4U9A;=ro9~>zZa&*}j@I1qM!4h2s zKGViu)qd_DFiA7rBdxE@Q#2e`&8*(O5Oe+K3Dkg;9J*HjJr&=M8?ub}VT`Kif)}?a&bRlFMUO&(aif`LFni07; zk}^IA3l*n6FSKFKXsWTQSE(1v`A0Y*741=Ni;-$gzQntpDo(rl$tnM74%OG#dGye^ zS`&DdQ|>Z;PSJ~Q<=MCB3?%wF#e3el;K}+&9aOhGYn@AvgIrM=fOdKg2}2h*>+JSi zVsbnc&g`TM>TBBP64&v?;HGtQQNG+?_=0G^z5tfj2H9`0iN32-d2twztAIKdPX1C; zN*#eM;A_s%K*GwKZy-IHnOpNdC3S@%Zemit^9SELCn)*uI=j-v>ArD2Dpf`Cl7wIB z3ooi&X<%upu`O6ZSt1wg2bQ7s{K9~C*gspym?NcZ+o0)rmW{jGo03x_G6aMd6c14R z;ue@w+UQnRlJ0JaQ}e0MNYQX?E6=t(dE&E@C-OE!0hi7-;nq34oyxdS>Z2`jc#eZ{ zpd8k&77Rrd$*wBjlZ2)5Ch6u%jQniNlfyq$81bSE(MOU{uy(=jF-MJ3&f{C`;(}Zl zOD(wM5bmkb0;Rq#PS%>pe79vfBhGIfBLORJ_$vynA)+5MN@r@tm4O^dLV((}HTOsc zI~PYT6EqT{AVJ_*~T*pXQ+m z_EY*pwcHPxI!X(kuc_Di??hAw@W$5oGy((%8! z2&_|Q3AHNOHJX`{r_Pm}Rj3!udgt@&HfDeOH0nU)hctDtzdh6kIwAZG=5+=Mzv*BG zyzXb&S-nRxh#F?!xBz-%hC=#(L8QMwUkUuP zUm5LP0_EW|3!C~-9{&e5?^&v|!v8^q+RatqAT>KHS8D$UhSKWDh+KV;5vV>O2JWPdx^J2sQC0)G$<`MsioEMuG!t7L>7P%8H*6Z80I*wg{ z18u5blvP1Rqk5S@4N3{`kd2=`sAld4h!)-2c*vPq4OKLcRbUaaYs zq3$7mtzgz{)q~6tqIdQgmWQW9N-jO`g&O98`q47Xopn|vfPs>0WKHV6L@ONfv?Lza z?xML4FZ&R^?h^F+AP1Tx&}WQ0u1hIQ{3x-51Itw)gOqj^a1q!ffj-L~&3V+RLdHuG z>Xp!L_lQxXehKWbNAMyE?U&%5k_3k(*ymh>Zu64jVbWs|U?A@l=-**SThz;tVwe;}_X?i7w@;N58D| z8l+sRxz2NKBX81yW!3t`Pra=%WVpczDM7_$=$74ujL8tXBs^f|Ov5$lmY9UqYl`$~ z74B27Yu?%*={<$KVc87fvl10H$glo8mq0oq(dv%5^UT_U5?^;>B9rEzU*5U?r=@zC=)-L$Z0}Rou_pU(mhiLwj{G;E>ki1^W zgjbJGqOB#DT7=j`bpB_4jwQ-~7Fu1)R)8M-GIZ)?DYl$|_qvYFsczgg>A#_=jWe9d zM+uo|7FPskJveY(2j6-+D{8&t88?4m6$nlW8^aC>-4{W7we=NjZ%jAOBzm7|9?BRK zl?gp1$54+Rpn?2J5vP2(BYV@Z0xs)zN4EcYaCF4!hD{SMF*-Z(A4eHB>A9QRB+L!G!X1Y0T6 zV`?9<()DS?Y_&7Kvghy(05!aNb% zN}l+*v@y~YwG+FEWhh~}J%f8my}c-ODuJ(Z3OVlB+{}CfY%a8q2W( zJ_4aBIsAQv)R8gM_QxEHw+hDL4+c)Im!WxLe_L+JOVe2#&=3@8Usx6NE@KTUHIv$U zgJ*_$WlS8@+oCeHt-^X{Oovhjq!7QJ=|`>W5J1s)EQg40JdMf25YapEL&=XZ;trbW zf}L^oJq)eFh0M*J;K0O>OZCO8rxS@2V(2_jy*KiUeSXpMO#oR`tMlryJJy#SGlifkI0e;7{t7} zGZ3ha7^C@D)>)}LeH0}P+oh#LlO1ttp4ukKOUhH*lP5kQNr^p0V!}`Yh5fzOTOEf|cv24z zX08^g0{S~^(PBWO3)Z$xbBMW+3fi;Oe27Dmiy`a6Pp% z97Y=f1z+_cKbST$81Z7Iy2mn>qaN;Eu{pKf^i2D37~WGf?Jxw}%n!jG@bEgV`|G;{ zp0PSLV{V9O^?Im)Gxak+W5{e+hsle+s{agurn}Lwr~s4xHuE!8&;AWs7goKX?~h@T zat?o{S&*JuAJ-oER5tpWl5LH*Z8yCE!_E})cncmLRY(TD#Rob9HCm}wRk=uGm#4~!=at0IX_~Cz*L|%7OOW# zk07v@K!~N|p-C7cS8cjfqdFbTw>Ls%1=BNjhWZ}r@Zux5;x|hoT)^^c=Kg9`)G@Jl zFbXwwl)iQu)@AFvvZI9*yk{Pj_T;Hw$bbN!_a`?&ZK0OBK5u_d3sPqshFehzw3J(L~nB2FE} zsjGJ>Vn)33J1zrR_pha2E$ zyf_EHYi!x(*73AyX3UHj1Kt&JYM$CIn1yPgP6^YqrWvi}j8?*O z*}sNz-}2Rw6Bd-NB*qwr%U7ZJTus$sOn8 z6Hj*bD$-en!)Dqg>b;VE2vOGX^Z)N=bk%`_N*%ZDKq1Or2^P6mgzo(E)RyxmRnoVw z45#;Zd<4y}F};t1Z%UrHOCh-hj#_o}ZWms?V~0)XM-zPBb7e(?(P}=X8&6L8Emqqo zy1jAY16>ug3kvc%S_i_nuJRAcs8_2ge(s4+ zNsmWh_{m*YSCY(h98Fa{ae17YJC$NtRB+qy&Qr#6cC|!l8Y|S{ z=vNQeGe6wdt1WeuHYGbB7tV$m@Nb=vY!Gf{WyiBbx`V44D<|Hmx;$d6JaIKTKQvZa zSITqSn+Q6mc`7(2RdAsju{(rGPT${xG30WAxuiTbl^iZf5{6QgBIVP7AESwN7%eR? z_1-PeK?U)s(CThD=(D1yQ3I3)9tKD&$nX#mUD3^(QvFhAcPkj2I>j*lS6B})ICg(> za*lva6h||}27_{sZ+a`1i^ z`?vZ7sS=Eep?dPSLfBfPboFwt+ZsJfi^Dscu(YB+EP}vaZ%p|1Q+3-b^k%4f=m!DD zAPG3oALHZaPJKndFM!lU?11vTS8jtGRpZ*geG&}U1-Gs2kWayF8!3&Zd}Xp35#SZ9 zhH?PRf&d(Nw`>RZsjt2R@0qGn^DYb#o!$;!o}siohUiGpEEv#-ILgu3CwA6z))WGp%PE2TH}1!Ufu z(Zri-5_#V80WUrO=5?|ZmINu`PPnuQ+e<`|LWCLY-|PSi{$vX!cpn8eZZvnQxk`1f zJLH((#|Yk%COnPaJX{nkTUUN*)6m#D}`Vo6#pQPA>4FWU)$F`TU0 z7KzQ0B~xANlNil=)cHNm;+O>|W8z);2h=&?Y@v|pa-Edid!AWr4MTnMRs|(^k9rHm!YK!-CM4S)A{&4QW_mK9o)KgQnbsAm8w?y zs-1Bm1e#S$p7^CuObBs$CkQc`_u$@jDSqEBxC@S088r~%8TzzZ^-EZ`hv9Ph1G`{A za~**B`km-itwvC{xl>s=+y_Ag^OD0aR2^}|%Je&?%V%-f;NZIaC`G2cWtUw4PisHK3Mbx!U0}$c0q@2+@yvv(79*f zsV@oD+2ZI?Jf?cG;Lrtqm`^qfIk}l(S}K|HBq5P^v%c+!QyISPi0Z@qkMHtn7{s|W zi3+BwR^Za|GX(P{2I_Z6>tM_J1NDOs*FG6`exQ)}f(t-U zAEx*|gnC!Hg(^)Te5KECG}s6N*F3pNxMT4FeGzs}bO~opg(U@IM)y3Zn3-;Qz-0vL zJThO^G}R)#2Xo3#c;D{2b|5^wsI^$+>5FBn^~u!BFrVo_#JY~uQGEeEFcD$g<*tD! zVkW~owG;b0C?&h9S^#!i!O0sQ?%!erJJ0hGj3(~b6PNtk3wUeB&YF%Cn2Hq8QYZWp z(fpkO1yDhd_TD|{vjc0_vZUa&_J8ithR?-%3QlY9k9OVj;I0c83HxZnbCLV|LvLNN zj<;@6q`dC9adTGMblb)YjyqZxn4^3v54Ce5Ee-2< z^8jQI7)uTj)HXp%Wt5a(4@6rc4Wf^H2A4!=tw0s3-z19^&kn|^d1|8ozJP1glbc$G zKP(NcvGwNs13K+m&YOI*WCziFMgG`c^hWhLn?4k1G!8|1rhxPjK?C%yL4VE~i26#N zUuw4tNyG}MRo&hEuY2Rff5hFa{B$z|bM@XhwU%XNQy|z965=b>(??KbrG`3M9%L6k zd6*h;j3cK)PtVbC-L#{Oe@WEdiK5A4%}SQ5&8htzsSs3jmtSP4`S5kKd>AS>l85%M{> z%@>$!>kU~WxQ3S(9QG7xcC1n44XCA^iK16n!{&l*Ii?A)F~;WuoqNhvmfnfl(D6v9o5T>Cp-T~wPcLwf&J*( zaZ25YO`r*ToQ7e2%0A*TUYtuLdElTDI8VGu350o1kWBrnxb!olM9BPx(_!Nl}v43i^Lqc+w=PpJ&9 zhwP8V&UrBp-F4fl7~&N#23b4WWiPPM^<-?}Dg7#qlQCL+;Bq!MCBwWZS?5hhox$F& zom=V0H-PtMNn`8r-a1v_SVNNhi{vuI#KjcY@_5aho`_+Ap+1B8px$!BG%~VbZHG^i z_ZleE|I> zVzO~PpI+!(c*y|8TPN0-=1OP&6|Bc4G^_Te!c$_ohSb zHy#9yE!#F7>Xbm z<5q^!R6!a;OT&b%2jkRIOWYWoLXA~DTcuNhRXXEx`GdYurmcD^GbnzpbTCm<3}d3Q zYbrDT>%nQQn*-~Zd5RwPC^d!Wpdv&3U2nnwwfMk#n9g#vn(w|(m2B4-HT|PKn^Rlu zOj%s&D1G3@ICVz(X%^4%&KQ!0AJZW~)0_ym%+HQDO#Ma( zSzFJIw!BW3qFn)(SHF|Hw{&l{xo1p=-fn0U z{knY}DfX0hDs_aIV<=%ArpFp@dd=K=gBNHKf%Iad-Vmm;_n1DugCl2vZ&>V)#W#dB zZ)940_YTm4bex)}Rto#nC+pq;nw*tP{Ydia)XWv=u|^qFt;1s$J)r{Xh}u&Utj~7< z{{W+WKpByn2qtbYS;3K!4jFhi5yo{bg^8|BA$$`Ty%JF{b6I2fHv-kZ&D|d zcw>Mo=c%#mn#1NVZ9z4{i}D%)YG?m>#Tr4~#zq$p>SjITbBeW~8K%1?o6fg&!%}^H zc7_TwEOKmC*bjknKeX0Y?ukPk^wbRWd4%=C;T0v6>6#xy~IdsS*_gm8$VcbPP^&?~T%`nbb zUc+QVa>}>+icTR@N_%^GCP4pYK}_qJe^mkQvq1We4f+z@WW&O#pNU%FdEg`t0cm+q zRUrQGy!pF*FsmwaA~vwAQO{83YiWd6&QA}$aRB9e<4Rj#_hG)PPPL|uqT1+%MXHSl zr(^SMI?qqZY!ycwU3MsrMoJUa8^cZ0vr9P`Z6wVVju20w@vu&IOx>$7qwa`c_QRLX zL~P3uL3@G@fpu}_B)R9Mn)v$@P?3kWuB}}?GS%$7RwOHEn4aT54uw-!6~Bcqmf=}y z=Z@+{CCl=h)^1f8tu%7E5hHzwzMsS7KPMo3xvr8gl|1!H$rK3l&K$TW6y~O=EVpp5 zyg_>1cjhYEl}+cnaKk*eZ0ZuJu?h^%MESTsag@pb+&JuY{wgMvBLyr@wmF8K!C4J6 zdaztEdWA7KJKoUwYX%tV$%;(3<#7qCI<~whZ+K&CMyzZbv=`0gCx7@0QwA^vv+OKy zB(0jGE1fPmMtY*E8ysz5H@WH1t8AEe%Y;yV|LGZ;lAU=;#i|@GR#nZXf3|hC@>gylnt$e^%O~&on4c zI2X6nZ_#A#7^YInhLN=0vnT`YOBV|sRc&WV6wrqBL^7*F*7d zUNV;`!GI{m0jzUezR}-UBQD+(Wmo-7o|r|84{U;S4!aPrj6GTADU7&uhl;dua*2+skmvG)pm#UOmQDMJVR>{yCEBn*2MPtxrTZIrL`&FP z1_veP0Y3&vOVoow6kf2DxG5C}?FMjkQUOQAmcc>#q80`ymmbk$0Li7VYEkfYHZau{5C;S*t?S2&~8YS1!}^Lx4`#T(uZVxlay@b6GmAx_d=+5~w>J#|V;{U(Lk3Ws@P?uzO+kaRjA?YQhThtnDE{NsF#XP~0=!=o)&YV?Rij=7@=|>F zr?8?@SquBG{qaq414;+b%euQt)0T%ClcD+VF98h2Ww^PCnrgMvEn0ThsAoC@MmB3^ zwiu3MnT`yxu-+6_{`kG6P{@Vc<(RB7%uG+o+*|0l?DEUkabr?PsUbwGdKE+?Uk8^} zfuZ&%SD{xn-ETU0VQ727$kjwru9gqc%$xawGqDSI)}B#0QBHfUyjioV9kz!nXcd*( zFg40Lu+pb56=lmv%ZS#!7PKitoZ=E4;zdmty$%_p^->GRYC;R0hdZ2f{~j}lvPZHM zhb+R(buctGi4|p}Jt*r%C(nUwg6@uylLr-Bo`kB`1U{(Z;+vnA5(^bXdW+?`Y;lEM zXq)5SG+ELm$1&~caupr(uvI9zRe?>X7w_l>9e@ip$!}FDwMfv1PALwvtHtLH7GB$9%AJky5; zI9FIl&KNOsC=^`)P93FAP0V38@a7)gvI@pgCkO;ZiZfT@;ToVoP2YGq26ll>%m3wb zP?7Suidi2qZ3Id7%?-R#@7;yosWE~Cz2Nc~d{RRkL%590bjMzRV9UrI!a6D5ocw?r+c^*?SIm7ja zvgsjQ3Pevl|B_dsO13nHyn<)shHRIfibUHkO`gBwL0~KSaLMH4Dv7a6`D~gduN2eF zF6Bqr7|W*VRuz{`52cHxp-~4cRj}#Um6#$>fQW6_1<%Txbo@#|1{lJ4+hJelnsm1o zJD4_Xq27rrFa9DBH~F!@Y?`JzS&f8BLJ@z>QOr*^MBK}h<&&IQ)SSjWf!NPTvwsq& zb55v>U;s3{xI=ZUKS24{j;iRkEDq0N!h$bRB6{lw!Dr!(7%F3(@_%>*&|Q0(KH)lE zHk(>lMTVhZgOP!yKa?ojJd)SaEE`)g!emS(EIZ4M*JBTb#-3g9%rI%pIBf1U^TnLu znR~|^)5s|8QCp<}1&UjrK$ofde7%3fIAl`GRbN3;C1iE!aFrIz?bEvo=|eU35yoa+ zr;XiQGwTl9<1-!Fam=s+BC+_#Zl+m1$IzvLh?g+bnSI81zf(xN+rgZ;92N|Kulm4e zWpXApiAf?ehDGe?37NMl*mGWWrh^?;L&K{$!ybfQ((H`%n2{bcHnDe&TM*{NT#7j( zb(BtNb{6=}AJ9ACRq)o?jjB3T)F~_nG@t9i}naP-*Veyyq)vm_=`_91qM%aIbUGv@@pdc~%~y zUPYg=_YsCbC;`x8j6->DcchX5nKMT%+sIiro0T2GaJUjMJ^+dVTBeS8JPI-3gUPnA z#7y#Yhr+0G*scjyIjYr?I!ed1`xt!<;a_v8X$G3b!&IU|BT!rtz$CgRp!q63&;b4o z_mB$^Ae4MH1|~$}{R{6kNvHD#SXPs8l7p3rY?)+}dYm8|k!V`(L*QPZRT2y(nA#;q zmt0yeae>6rCO;yORJu}&64;#*!-0roQjesR*mCeCl6obUJ1&$&+9lDt1X91mRZ1T1 zl@zX2lQ`P1#YZHKUZSN`kuVyT^r&P}Mq;Z<6pcw@o|&9-u%KnTJ)=cMNrvc=Fv$X5r&}c&v%6rLpBc$sZ}3rR_04%@IFX%W~F_EF2s4aCqUj6m4@ddw3x zZaApSc|M|htZZJ>g%uvjqV~MrS~ivFQd|B2WnITAw&RPQvnuphto^(!}d%V-rPHS3SIzl zswX*S`fxR1Wwv^m1}~rDWK1k0Vy&*G(l0N9iy2Jr4LK8SC6b95T1xTspTUVD zt)oQ}QZ1sC1knmwCBGSS+9Y$Q5HY$^Vz^#X({>z56y_mtSJ?)zMqy;tc5ugDV`MMA z;nulycUe~OympqrFJgISI0t4Mp;##7c6eq!?9QRof%URO9D5_5cUR>$p;=Ytp=SDr$ zAXAy=0D3odVBLm*^1-T4>L@*^Ey@6hKOICNieGUYJJJ?cElgPFd7w;LU4-3SK}C%D zo{^WDX-^!4^o0#`%de-7LCNDC!@#~VMl3$>x@UA$d&}03)qHu|9t&54n(LhRtXxJx zQ)|%+z7QCdf%@SgY)ff0WHOl86ZJA+DLQqaBcOy2f|r*jVhPbU?aZFh?WS$YV?Wh1Wz-@t$RJ7~ z1i*~3*BC*Xk0g_brv|(oQqK%cSUtB&$!XcWiK1C8GqwwqK~5J-y9!xsEdnAz0GdE$ zzkkC{igb7AM5;@3eT)#$po|IF0dK3;)^Z-Ist<;($0BgfG>aj(9Wf4>n2K|BB;&3^ z7HjOOD_5uC2zAx{r$-~1wvxFH<;DTVSICZjM`UXn#?!tu{+R@XG7F3I#&4DEf8 ztM=6j4L_>FN52Jl!TaF|bJH(XV%D_jsrf?hDA&^D*_aDZ!e>@GcD8h#8DU4d z<7}C*F~8CAvt_lXjL+z96|eCZoj98@h?JWA#G{il zY)&s5X2UUr?s(T+I#o7Xs#-$vNzSAV@19GOjnh^Vj3#v6du!DZs92@dY3Fz5lv_W7 z&6y4lwEWHSN=VZ>=Y6Uxe4eI%yaLJ(?uk=#`Itk${Gs+p*EcVn%NcmwRU?FYrn&wC+HlkJJ>Ebl{2>*If( zpXO%2WZ4>cILPEJ`!HvVE|P;Aco{2IT@j-?-&dEXrk}lqm5F5^rk3Qru-B8yn#QKk z$oXr!`61kBv{tt0!asxK(VRV+QBa^?{R6IXS})m=6-CYuH@hCrxP`%q{74~(eZJwz zkF!1&I%a9j4v`PEBN`hvJ2|+ zjRz0N*{zN1>CTgLXmVdv1wJ?%@+%W-bq7Sb*epW(}lxRq|c;`5r6D5v)$8{2P^PRibLbF80V3$J4@T z{7cYw?Z$&8+e#NQ=9-c>x~a`br_CZ>amt$XgTIWI4|q+qq-OSpu$OZMBDwix%p$o1 zhL+YW`0l@yEtOay;**?O$Kar*-!XUB=@?cM(q^a!^8OCGq+OH)x&fOot6sP4-!LnW zz(+=%A);@dfP$FeZOP@J%o8AS`@!HATd9LSeqoiniWeVPyKWsM5N0eeex&TBtk=Xe zl@yH0o_gZ0>`v;6^utEZx1r~U9R=r(+;fh{vEyO_RV7mez#TYNjgz+JBy0JrGay+# zuy?#ok*{_pdSgcwQi_yXwin(79(Z)Y2N6Nhvi1EHGh$F(x>#x}6g})HYB_J}g978& zZJ}G|dARzj#_+M&zae(*M*6|a=FqXSal9wYp^V4!O*qK0wnM+o=v8GfUWL1FI@ar1 z{;Mp@f3XKEXi~PoMz7n(_q~S-(0xXpoP7HR#0L6K|S{~sTN#Mu%WmLxlmjpjB+ zgN%wX%}fwCCX;`sWZy(Z-VL8nOW<@Y_J(((SF>;((fzUi;gDO(6tZXpJra{9a}00$ zq(@`#z?S|*#vC;r&U8v;;utJs&||URegJ5qHFhJ`sSi~F1Ho{Gto92;B^N56}~ zBFBcULXlT`CL6BeJ$UYfn=i=uqGJuJ5Jz#Y<9;q<)MRhoHk)PKx zu%~;FPRb55g*g-Gkp`h63hpfolx%vmVZ(obKGPpK7O6P|lZjzrfQwtioQjur8cd2{n;jVE0Ni zx%QS(hNVukBve^yk;5&bj@3kORVq3IT?PAUT?bz%yRJiDx5iv+0Vbmhz?w}l{8f5; zX0ZPJJv!Zk5e=ruP&C+$r}lSQw%KoFHm7Kg=l_G^Lbqk)b{(uTDi+sY%h z>?@DCepeW?rEkaDNJs;?b)Zi}W82_BUzl@SUso8|Gq``S>-w!>M1S|LzUy{f+jFfJ zwWq&(_ueo((6e{2yFUyM_G$nJ2e-<39j3-$d2s7`4d=nZt?PZHXG-MMQF?>6i)n~R zj%0$`TL;xLGlzQiQy1IP9J+o#ZP7y=)U5|We(EWU*|LwemPK8^i?#((=mFZU2Ro=Y z2%-;YM-W6O&^2MOuZwmDA=M4ED~JjDf%?KY?g-i)L`YB2o*-hdx6Aj7_Zpz@I^Pk_ z!NINTNZyK)@YeNfX)r8|?x4LA9-`}*u$lof(=OtkkH|rP-*vk*&K>OS(z&3w%Vz>0 z+1s^YZ8WU$Q};mkIxVql@1DLL-5RHN_g}Z8C*;hY9X{LcaCe)9!=~fP4v5V&ZcOSZ zy?#dg#%hUy9W1bLJm|knlmmM z(FGZe?1GI?_95E(M^WX5u7Pg9X<*&G+jeQ1bXU)gK}}0;-O;Bh;ht@J(%x6wH1`t z3&kp^9hXYf2u}*EGD4UsLZ&Cxq&qb;2Ie$GwJo{eDfiU%!8=rrOa5 znr7!t&@?;uLZxO`Gj#P7!65Gi=-JmbuuY%syPiYpJ>77Is#&VMFs7IN#oywBj*7`R zrr^P+J%X!{D04T!Z4rE}D>b(yoRPuTy0Xlh3?Fjv@f#s!-fVc#gReDMe~YeDV09w+ zT34#Y64{XLZ!)T}^#~h|g0D4vVNKn;%XjVipO%b=Rq&ij@U;g1N_nyP+gIYI6?Lyq zD?_6P-#Uky>6KEZb*1v{whb6|)?C*?7yfiEHPaibCa>K{uX}7RHBY_4PiDuj311>z z@MWYN_fudqEB{&Z=CO^@WsmJ;UoUi}a6g#(^sJwXO`xgHho3 zJn-dcdY~;p@W9WBMOAWN8OiEnQuK_Gt+wY((BU7?q2|e3q_Pkd&m8w$GwqS_qdC;v zS&~bB5U$S(QWgv0f+V;kDwZ9RG;aM6*BbC zw`f_^9iMrl*oHITMcti#GM1dWPMqn9wCCjfyU5%eZUl*6{2*5LmdYieRRO&EV{uw6 z>2lSJT#9qZoWMpP%g%02nK?5LMV&fI0}sY&Dz@;{*QKd^NnG5AKZEi={8-#?j=#;Q zR#h#@PQPJ?%n4%8J0*aNy2y)`Ff|6?8NorJD@EWbJx>6cnl{laI7|5r-CERmoOKFEys=&8s7JFT zCRa!sxgMNUp*gZ1gVQPF6o(g_P@q(Fsp&01nwI2^Vx-#k(AOr_%5Dldi-(CWTux5E z=TOtY-@)OOu^e1lE;pVfvbn;DUwcYwm#D!pytniMi8|u&ej>V1f?gp%;(7(!>@T>k zg)I*k$sm-@nT9iH-bBk83u;|iNvE?uuF(pqF04n4m>H|2) z%jY=PGIg-FWDNX0P^yrOs-XTqtXT=0l=FBre<&-w692ltkJDsqVd^nySbb@!3n~8h z)#avcp{8V~PXLx6ct4Z`$(fxB35tS)B(3YHB1@}20jA2C)5}s6pz4|L>V2)GDw@%+ z`Pr7Qe`6+K`KpdU`^+WnbN>LMJQtEy9ra;!lK%xVS3U1rfV=;-vh-EY`0qN<3OQuK zE8Dlia=5>?9DeJQRpqcMsONvO=2*q~mH0(bPaUOmKE-m_`D1BfeL1WP>G&tB%VFI@ zP03E504#^>dX$k&y)IBbk}h_pOQn)SW)w{Er%u= zB63(Af%cip;qHe(l;=VYtE2uHw@rPmE7j;&J?}-p-Tzuy4y$K8GzF?ma zIM$Ht9IP#XeGdav#X#ut9L|UFcI*JFJ_1TcC3+G_9i>-1%qM6jKQB1e7da_a4&tnb ztIJyT+^OW$fNCTQ87~`)kTvn|3Lz6XtK?r5={o1rV5FHymw@nT^%K1ll~_rRpsr_I zo<7fg%HvvD&FwQ6q~o7K`=1LzD#Q6j=m`G{B&aM?4%qr%D~nE9j=$Hz)RLL-bvjI~ zI(?R^GPSz%v-RXggYyl%2RQ@T35%qT(!-zSiB{^&Wj)#nCt3iU8c@K6CyID*WNyJxjOxipv7}2 zS32HzajVow71`Y89iB z4q-E1i>)qKl1&XLIHHp}N{N3GBk;5|qW*|f4ZwXTtIJZgyiLieA=Nk@jd*G-qmjRt zugrNNP*wriD)MvRKY?dvB0mC0Xnd53s6Hh*lCJ7`w#T9UKwD%cQg8drrDf3<(DLU( zS|S+#6SqWtft*CL{Oa>yS@pHDghbN2_4Eqa5G{Hz>agX5hZu;a48=`VL}mFGQE!E) z)B(8f3pM9aQc6iq6=YIJY4nRcG@bZ`G_}4U%>dq`U#KouGbq}U?DPr4V#bD^h*{HX zegfBXxtIyiRaIJ6Md}uP3A{HGsS~hOd+&ZQu>UC`l9ZCI5U?K2nA z&wUwWdM-q?9vye1^ZYN6*m@=2fOhy_D+_SF?BCEUsF3I2xnXwiV+keovZDv9#$tx% zS6fZMFMXt{fLDc8qlRT)sVVVQ!6*e#H7#|NZhnMkcRtefRX|lB(^T;JuT<9wPY0GJ zP^=_g{v&Kyl!XNTU7;kZ*0QQ%|AmNW}-E!Lafn)ul)*`Kz&M$R4rc%PmK(+ zXdo*QY2z7MtN0;vDeILp!mB;xeZfm?rD}^Ao&O5TJ{OASj9T;tFo6FBn&*r_{Qh6G z-fm@8bVlXZ=;h1DK(!>|6mC9ua{{>)(gbSbd6p zEAs1jJlpL0Y4DP;Yh66rXU?8q`5GwoT(D=|7VJd_`d`3~b&I?eE%CorX1}`mzpGbK z&TcS**LAEqP~}pVD%$hd*FnUYu$#a=SPd@y?ERJOCi!dN@ockO2bv}9Ru_-I>MdZjt$DiT|}SyVcF#_YF-umb05O*=kIre}bxJ#9jhNCepJf zE7?iXRXxu(`@9Wk3;RUsZJ#;29Q@XE#V!$y&-td-cL94uvTO#X{@2Rv5J|s72T@^o zOV)$FzRH`PW2O03n_~^hsROm=(e}52S*>|=6?iAURnw>zB$Sa`Su}N&-urDfp@)AW z4XtlNPY-VCx2qe>)0a#oJAI-YD&J>wS1b6whJ;l%tBN|`_Z@KEOw_qxT8++6==`8; z2gzL(hi6+B8_}eQELNjL`^+VC_IE*}=R%R!q2h6FrTWU{v|59|_HPg$^|k6!TP=5s zp1VSZdCrVCBe2g8+xu#4tb`MJUu}tg$@hR#g+$L3dgi+|<+~aPCGFAud_X>Rl-~6{ zc0NyjQ|MD)+RqgDAHG{%_RkbfQ*!EhMNHNNy*J~~98Z+vkK%WQCa6}!s!9S>(F=uB z;Q5*8g=%$FF(v6?NRj_3Em5s(Rs5c9opCmrD>`GwIJM7QgIxCmQ1iLaATw^wN0|cZ z3-rm1i=T%U`d=$+ml>Dc^?j%T^)2DUwa|PrGrgMjs}$_JBz`K45`2g_E=O%|_?dt=F1dd#r(*L&RQwq zns#QK+65#op&h49Vc;g}{37=kvfV`e5*R6sU=Qo?hdu*{d~SG*j}24n%8K?}2%7kx zVj!7coH_5jjc=y-ljR-7BGS4_?nwPOoHu!hI7+SH^Pb#@XOa4=1 zkw$KlQX3MOo<+3wycp&EPw@#KPB?R|(YzRI7Jup-!i}rsjq83E@&^A7c!LY{{G^?MQb5VzYne+lo8;Z~SYXAdXQy+PXHxU0f7nR$rtMtByDdg- zw7rl@=QC^4g?!PA^6*@`urNAGvRpawOh_Gn)i|>{x(oTD;oz7G#qNC3 zdWA57=vDNAnAG&a2CnIY4d|D(y?#v+re&r_AL67ddK+oplRuBsYgAPqYFKg3={R-w zHqu3{r{lB>KQ2BUr|a;e?R1=O#E(s<yI~pKDPu|f4rga@LZyKeW&B}mkQ6nYgl2Qj??S8iaAG4 z$LZCyFLv*Wx15gCUj!w;B=1;X^!C$ndMmK}o6~W6rvmZi21fb;h4kA&+&SMq9j9Aq zUhLi#a~_Y=I|E4f$aB&(t7>;IU&+l`p>xS==(5z4k=Ao{S-4-KE zv18MMW>Vcog9_-!8vyjd0H`9;bO=IJZl|LP$d?$%Lje#)uX&*7Cwc#N#`hV{uAi|F=MTU1&|F$tJKrECjuQiWIA5xE zzODHYGo!Li)u1F0++k-dhk;GTYD2?q-;$}Jb|!U36%Cl2HC;A2Q;1`YXVuQC@IOWL zSfe&JmfxN>Y8BnJ>)Y3i<&88o|LUL&P19IB{p0msBF4$eg zqEk3*W+--LjA~_PMB0|G?CtC7UeUJC!O$Js8M|pw5FDFy;uXgUtb zrK62C5D zERa7A()@qFEk?_LTPH3=J+h;bXx`U;6{jbZ?5~&LZ&dJdqB*mkj8nsb*u5*(JsGEa zcQn$9%}>VZuXi-k%I+uQL@Reb38SulGkvuAf^xnQw{e}|-p{!A1NRT_@JF+L(2!;b zbHH>>k7oVvYymjH00#i@6FVAdS>ABmF(ZfZeTQwE4!!gp2jlelvZOsF&veK}(u=NX zq-B;WMlR@hC!#<$7i9YbLIpp=&B)NSvytZi-Y4!}2Nn=WA*9mm7o$ z7R1DQSO74zY>Zmbn30`8%Py8YYC@IX&PG~}M|x-xf;2yIdyJN43m#p7@SmgldFZ^< z?J)vxJ)e8}|DUUU0QEMHZUHtOb|ZZ*R4+cQeBTN+~* zT0}pc6{mKxpet1pFMZ* z=FW`3?k+{bq{(!BTo zCQf^A7aMWyiZA~rP7fg(cG3;Eiz)VMW-mtFZL?fz{C`RSHYgM}0EMS?6gcLbz5pq$ zYH>@h>hqhp0jSXYVer&AU2)*kb7^y5BhmaW)O_%XMmqI98A(1iBc4+wQyc5(BgxY?0_S4a_4_+$ za&nR$dA_jPycp5#I*t}pO>_1?8)@o3;r@qagb9~h4m|Kn@$)rONi0TZacs6=j$2;8 zVO#0zja&)jI`uLk7o;IZx1D_kl*e_K1n*2V6fO+;tc8C_M0g9)A7qMryNdiapLo z1hqkkVvma*G5@QOAe(;4Y4i3#82??O{+pv(5p@Q5!GxIglSXOYtqqLTtqo}4sy)hx zxU~U8$rXEitsr4qjyrbgwGB7oy4W+QF^vZxQSI#4&X#KLlxpwfY7YeU+$o@!;l`o6 z8n8>$+tWzPJo!;0@LAi8f)BhCB7T`_v{_82{nh%R37le=PcKU0wl&@n0SE-COKzd-z$Uf>g9 zfyJu8{Zim_et`=j1s(|ttW*WwEd{>i7wCu-_+eP!MXJDiq`-gp1-3^DJQWt$s|vhV z3N-YqM!zIdV17SzG&kB(1>PqGn*9PdMGBkv3X##Jwg(|fLMr1|$B zi_tQJ9Mt;}B#y}Muj6-Ge*Z6iXXN*#1C6xIl;0!xJu1Ini{DxK{Wkm_li!aHG}8Q& zsKlZS%;!0S&=B8~7z;7y4K~vJ(}*~{mR@?vtLD)1!A4s4|5NuL@KIIi{{Z~GxoIbi zU;}kml%j-Tmq@X9s%Dk41QpxrBy*DtnaqTlNeHebD7{Ekq)AZ(6hyIiKvA%xViy4` z2q=OLMc&W%Ip^M)3_)4{zt87=Uq8EKo_e11oagj&&q;Jd1+6-($VPrmCxi*~Adu*U zN&*9@3clSG4Ix2Y#njbwv;{H^Ce!sI(Gk@IMu^0p7atq+f{w^JPmkRX{qqJ|3LLpN zXn~_lK_A6}o?fI2`Y0CkJX-K)jkF~1j=$B<8M>QjY{uV>IRHUhJ?3sgrDni=g#7k( zWcE4WKA!M{Oi=aRlZ$SnEwH<34jCn1WWeKPl>BZ{{ZVp}p|$`T{YihxJyRLP49({J z{~)+Kbvjw*zB(N=Cc$n>`~w zeD0ASR~~;b$($yZ{j%!knUn5%=(~>&Dw&SgxW)YL;$5$Qz2wX9b{&*lCr|F?Cw`yy z*$+$iHa{2>J9}bpJooI%)$@ON{i}mY?qpAH(ad!lrfq-u(Iy9z+$o;es>ze@d0@v! zJAXQ;i&y?)-oBzKx8HgCa)w|=nxiP480j97T!dFqvq8Xt^U z$7|fmKJwmO58bt3%xjwussuWFa!)LO;_(SHf1TF!V3Ir86MOLXy5IM`x_0`(#n)+` z+}(4w{PfW+Z+-aEpPFgbaGapmK)q_^{Ykre%|%f5ic9pAirxF4N>s-knG`Dft9Sn+ zGh6GrY*?6`Iw|~vR6C3}{EdG(^f(oakBv8?_)!k^YVoB#tTIs%huGN~`;Au#0D*vgvKO?{VSI-zjq>%iV zw4UzGq#GGJtiPOJ4=ETdR;hADgfeNc5pOHO2v9 z0^YDv>dGH0L9-c6Gch-+O&s#Sn0Ut@Evp2=p#UEha%ux5i9R(>EF7qUb&S=j)@)7; z(SHxT$)PoLi45_i57}S(&s;R1Pe1J<47c?t{V=P2kOvMA<2d#u+`r90xiSw`2MY`zV zid0L^{llme9zBSbK7iwzww|gtx7t@#lmX4Tre1ofjrKgUV&Izy z2WkQ#*}egq^GM}_OKqrWL4~JB>y3@${7Zrg4_|7d-NQizsKUm1SY=B3JpWaV^Y9vp z!Nz&g``SDib7p}vs35(bijDlosdOTVfV?_p-DYaJWpSm>;%iM`(yl?!z^+Se6vSXz z?A3w&#t+n25D140E2~oU#<`CDz8*!KwICN3#%KR|3lemy;(Uh$&8|8=6W1Q1i)PJ1 z6-`N1HC8lKGG?#U)vdc|yqKY5g)7VHeoB|<(y!O4C+j=Vq3l;u+wOyw?aN0ZpzK%w zk~D8qhq7P$tHPXDEBp1oD&85&e&c@@+%5Wf!hiOiRTiaX)~p?xb*Cp;$5&zITo zeaux|nL#d+Br^tZGt7R9uJx~pCCHC=#tupN zh5w8|`Djr}B2+h|AsK`KS8urEeP(3t>|RzudX? z^8Ni3sH$=zL6)wUl0DXx@+ezU8f9}*aQo#trBOC-O#awQNmJq5rBfPXGo^1NrAGDQ zh-vhQ#iB8pQSRMm?@umr!e$-8s!!|{sHM0_?%Vf#n&wOT_v{s@6=G(6xp;Lf7A*@T zoL(WlbW!&}TCzzFqha53X&i};qB$+igibHrP_uBoBF+9pXVI}S9j)j1-=(8Z5$>PU z(2RJU>Us5ZNrGNoIDUVEPPxhc1f6zHeL}1{;&k*}k(Or?J9)%3l_Yldi0P#pj+PWl zrqUf&9x{aI(sEm?v$``4Z5D;nT^84clukqVGHwos)ET)ML@vddAADSHbi*gUVjfM9*113FRV< zCFR|6&Tdv@YtEWEoZYT! zY83#^dVyabxRk1xQyQu*j9=`;Z&ScLMToree|}TiIve{N9PP!4`xah$e5(N`Rv$%nT7R!L-~wX z2Et)jXUTcO`Z)>3CFgxg`wx!7T5U-ZnuTR>5}jYrKNbk_$>S@WSTrtrYRxCIS@r7I zrGaordP-PRGp$xJ=(hvBLJ7qSBNxO1Rpp_Qi$n1QtW`;_mZfwiUdoge2v4Lq?gU~b zNsHwQ4SbEo>Ib?%fj?RR5Z)0Ym?G}{gJlv zum9gx`vCB7HX3_42B`u5%_y<2u(|$t3G?6V3L9 zV~8^G%lUgSs`xv~-x0*gGnBvCH)DiFc=HuDd?(T}o_rZAxb3vTcaDpkD#)LS6odO3 zgXdaev3;cs;9ReTol;})%@~oX4d!vJ05Cp-CLeO8HhFvo({FaAjr^b!P6TkPv~ewn zh+&EV?FbCUQ~^#V5WzH&OI&Hg_aB-#s5*)nf}2lnP9BOZ z6!3ZSs8^0vm|=1?A;Z7rScTWGwBg@*tinfE+GzdDu?oKmD@$yiVSOw^9H6eM=YW*3 z(FJ!47FgO3Pi08SJtclP{3`8-rzCSNsl;<)iVo=+FbsE^$lI)SY!)58sDmBLW>Iq4 zRW_8}Tw$59@XHMUam^J1SK08lX|7PgR8)uN3eOsjuQL37n=4e4V?c9-o366aI?!BU zG=B_it}tC>63rFfFfv;*{I@q(xSKL}HdnZx$x_d5v0wQY zlbT6LI-H9r_#(k*1!7dXlRz+37RR84t8-vqW5c(bVHZbVE>AbPhDWM0##*{L7UG1uBDIVg?BDg%k`A@RmY+@Gnl zvDVGTTFJ)kOw=n;Qm@O#BSebnYbBv)X^A9+wUUxG%LL6@$@)v=m#nXqtZyK{e0Ql6 zfvLg*!#9|jst(~XVYG^E_?8n@88S`K5ydw0!y!B;495wC6{5gTpb{?#a2A1TtP!9W zfoMgL!_GJ5rTuF(rn(AW2t2Tu&XNY?YZJOVbOTadY(pB5sl~SLT7Dd>`$E%z%+aE3 zuQJE!O0ZM6SI<(E?bRp6roH-_KiFRF5h1TBEv-!Sz-atr>cw839A>@PD@zqwC2rX7 z6=U}XZScVM-VBClw&T?rJpez62nn6NlKV3pH|VuGurM$~#yH&_8~G*W7-qBSy3DcR zJ9eCf{1CUAde}B?q5fW~jegjs z(K%(F(GQF64rQ*9S{%N1w4)MF3;E14woc~|6~Y=p^(wRB>q|^YIo68(5u(b88i<#L zX%yvBoA=VnL|2T(8yVW@^{&zD#po$CON?GGM)xfD>ce`QO==$^80=Ut`b!8)eOPa^ zff-ZoWy-DoANeEc?K0p?Zr0HJYb;Pt8A0J4J0h4Z4=Y3C(L?A z0h%^~2r*47c#g1`woOc1H_$arrzquqcP7jI!+|^m?9^gZCc6S(g$-W~-Kfu&?6Elr zI=+GpOl=|-z^{^^zk+3QBl$v@Ei<8$iBg|8SGdU#c0Je{vGD1dS7#eb>l2B*Z+h%;W&j440x8m z+i?n4gl+gQI!+-Rw$ZxiIEBF!Y}kpXo{U?uFVhkyULj7rf=(PA_MCVHgJd3IapDy; zzz*ekoCs|1|deqZ}?j~aWGx;JKvaxHz)!}J3w7VP(4s4sSJIWhLb2~DLWx*)q=Xon z_bxH6#T30R8ykt!{#_{j=PqWeGEj>}qGc~ZF7Q-3K`a)I?1)UxUXb}rQvx-3QJDKi zZ1_%R{sXvAFvTPn8z3BEb-al5nvPf_3^in^>13c$89--@0a z2@S#z0?Z{44C5D3@~}W92Krc_Y6E3fG5W$Cut$pFWC2TDaG-$IE;yHg$Cw|d<} zNHpG=!GQjNNY?2R%oD`tE}|Oq1@SEr)zJzplpc8x!IJ7KJRsoT2C-M|faeAJ=O7!t z|FB$w;m)1#o5gnGG_nLsJoqAkt3CLdL5%IIS^Z5ge{1<`k5d>p$cF!x;}mA_$N1wE z?&6PW$0;n~kGqajc!xh8JWk&i3(Ps)(GQWP_F<%XE4 zE2Bh|?bEY{b*g5a;c~p-acngl(=^AsE{7ejH*$y#sWyY?N%$g5$J`E%d4L@mtZPl< zHrnf@G3*eBpFoVeKxS|S#8t~OnZcY+Oi2geTk(8X0jsNAxG2uSV|fVQ$*xW?Zo}uh z)#75v&CW1gL4F87X6c&&qgewhU zrzR}(2$hELy(TIa-LTLvL#B=}`oEF|sId_i?1s-(A5tLH&jAlQDy z*5GZi=PCjbyd#uI!iMj9Iyk5_nWf8#rLI=wh# zte=YE8QS_WnPUBzOg1|o(Og}q#NdJM;%yi2G#fApxfp?t?H zEWOnNb^P6b-0=$6)=y0L+FiLaaXS_$o!S612^qizg%H!L1_sc(9}v)q4`AXy6P5w> z0Cv?GH8$GoJ>6@dQN9`*1wmM%3k7jl185y*N7qn8vl<(11NhGut8)O~(+uD0I7Uj( zU0uWNcA`RYj26_)8v4rj3z2$YJyY=a)Nr{6v!thckU%A73*{+F_>P$%LmoY1SWUeh zi5Z0ZMdA%ILFFH>P&h%mYr5s{a=gNaq^N%#uTW$NGcAAV@d}@lkgQvQD(+Y1 zf|rF7<(P&Axz?;E0FJlXb8#5*71$2CC=MJ?FZ@(%qx~St0LOC@Q8dIxd)aZ1M#4iV zdR;agBFpsBexD?I@DR7)MKOF=GDnfF)um|7f*U=A4MGW#gXK1ww)FR}Lu~k_6B&b_ zd+BYjqemOg2zR|DGk0LGr89G;$;_Ed;>_#xuKbx)w1j}k%$dy08X{6Mb0#zMK9Th@ zBk1kdxtS5gP|@QRHcYTU2cGR8d%Qx%e`%;UbJh;;5ibc;IbtZL<6WONvb!;|JB>W* zzuL&|G;$sRV`O(4xrK-nBfHbc?}@Buq@aKOuWO_zhHne~7)O|n{rmNFclJb!HE<9y z?TJxSk5@QuqRvPk|AWUXoOitq_0sVQ9S!=WO#i;)75ZFnqxI1(6t3WpmMs**{L!w3 z!Z@meI=O{Hp%Loq|91<8Y1iBEU*19?`vwk9=(WoXLz#H38SzQ4bqhqg21+Wl2WH@1 zQ>yRiGb!)LnUpp+xY>M1MuYHO%Ulf(1@W$oE3d!7Mt%(M$^nuQ^%E$J_l<=j3CzUS zEL|9Eo?~o*>HmqIZnuq%#jq%oqq#?@HH5{|Za+()l*?^+P#WVkM1+j;MVVQjXX9;+ z>K#q!Bo}4g@L~?O-eAL5NrOX}E4s37v|)7JExHaR!qqiLr2ei^o-U2;bR(V0q8eqK zI#(A}uNzHK1^I(TRl^@Fs@p}TxP`)WBeOWuKcj`hyc=!!=eJOJoU9MEP*}+yPq$Eb zlRs9sP}nZQOmMLgeo!a)GdUO=N8DsX5OZFE}DCSsUN(!_{u=7-UngupY5T84sRxQg0dGH?^D{}iS!CZ zdel~R>>!+X-|&a5f!ZYNM;RV#q^L3w4(o4pK1ZHwL$oRpstjGnTP-qdeY}7b+VU8< zYgJP21QI-f(4tPW+%lVI69;y3B}w5q z^<={XbLyc!4}E&-$yG8gQ}+2jt*1-9v`oZyIEI zR24;o16BM6sVWh}P~^CW+1m$#!5C0<(xPlUNgWYqFi_O9HXG01WTPM&4#E;J%_MzU zj?*O)Ox1idnxttCQS+veB6-g40;p8kn8EG0xY`m7wX`b37l!Hcf9Pa=E9C@(HPCXeTkXO&L)7RvnxvPvooBGP|&(v>MyekDm+>mVc_Ia5& zzx9O@g&!PL6p9>92a=bT5>;>s|Alq9)k?g~_05(X>NWe~n{1wnIEpP{%RxAX6eO?j zq*y^Ajlrkpv}2y7ODc~dCowm&QI*GGB2-2fh0iAHuUUNlzjaYKtw+!{ z{p)PBKDCR2Q^($b{JRW)W8sh1=O5?t&muoid`!yeE6-nn;t-l`J3kXR^n7tw#-xL) zw8bIh*7Fu`V7(|53A*<{TH`FM6;CtB5vSDjygOzub|SE*Wi)g}E48(4$Ge{Vq7~Lk z>(BTuiIhYuE29xuEB`>J%cXVqsI7|f%BfyXqCCp8NI&Xi8r_ne8VWQg5Da$X46I>nD?S11voVZoxQ98}gEEwNC&q*-ZF-FP)d{GjGlY@~abAV>47X)!9(@bX7RQ zq`JoTukEU^m3Ugy$>3LOYj%k>>aDH{XBf(>wm-9*!f%u~q?k-`){QNPYo5E?5c75MT()$P(vN8t}c${bGqW6)>hp;5%d2o!YKD z#jcoV*PWhS^Tn?J4&}0EC)_2_Fk1OZH-&^rYfhGS*Nq0AudSXyV`=pr{6VYl5v%Fh z@rLy-&AN;nWPO4^$ht;YnZX%`^=_TPyA0S+2A#poBSvC-t}gI}I)4i#KW`7UdEsCg z41CnhHnz?S?mOZj)#oj?1suh@f7kCfwGI8$i+?s4~kE2xS2~ZTuFLJ z>iHzX{8rO&Js@05hz;Xm0Ujezj1^g&r@wr&jkbm#_h&u9Mz*a^1j{t&TQbQ)exeww zMWP?CI8>S46<#+Uc_6D@tL_T$|FgToEw|WceR6k&TRpxn4BvyPd=I#MPmwR>CS%nW?4LlH_ke8ST;Vn#dV8myc8R^jL8{z*w?=DiJ*y)nY|;GE z!Hq^Is!IUVGENzmjW0cQZfnKX>~L3|v=+xqcH1uOZ*1R0EUA4=iuNmpW#iihwYR9J zJwH`@a)4K_i%#t&F&6pthGuN>e!j(q3>pbo!wxWLtf#Y|Q`RL`7RNZ8QBU2C!?KY# z%%~%9#6hVm>>nt`c=+q-dVhE}j&*erII+I2G`GsynEbxDPj=oYPx=*3v}nD(DA3R8pHdIf4P za)$84zqY+D<(g+^O{dAJb=avqFk8{!P(0o%P<25pT3uBXjwa%=P~3oR`*R9cnsF}I zpP84!_s0g7Bx2znl~su$l3IE$*%mp8i;KDnb?D8*MopZ%BAv-itDC2a>)drN_f7T7 zz~%1kIKhjYA(vDI1AL-kXg1P3>@r>$h?n=`(O$W5Qw&MDO`B!mau)JZZl@%1k)v~y za+`KTY;=sMOY?ERp4W8m)4K=G7@Cc=fbQ4xntnaHbiZ6^Y4-N(c}?Fgy$ibuEzQDy zJ+J98;KDAK6!q`XUFd0n*{|m{y$Xwp3VUB*bkHAZ=JRy>rGcpnu>uP90G8Y?)D4^%KAsEdd32k4zAq4 z&|IFPzL#-aD=cZga9O&GSIDBa*(Uev?CcUzfu4TOfv5L#0u?i<9j5!Ue(-+YSLs~GnnxbV=R0cv3(R0{=*m{N{fpF+LJ|f2`I{ZL< z-NH^FQe71nt|J=ciigU?cwsrRVU{8BghLmYIBWW6T-U@0hY}^_qB7-O2R3%v7njAj z0H#AI<;eyzaHznx1Lv zOm9w7a^>-)Hzz4P+C`;N(UsnyxO@x*Pgz=Hl2W#^z(SkSI_(q|{Zp83tRE_xBIVGJ zH9eNlUqasSXKeaw$m{=%txKXuBoWhH`+w^*_bTkotcq0))cO8_Vm&%!zzb7)-&Po@ z;m2W+)wKG$WnGu}v>4&>0;1C*qA-#;UGQm9P#EEk0tM2_yD-AFUJ}v@xiG?YTN2Vr zmCM6|PcO)jn|4}nKtjh;MI@~wAf*Hq?~%PP}16aQn=zz%hH>Ay_X}T^cJ2Z zqcE*)=dyp;sWrs3M%@rQcG6x=E8Q-9i%OD9Ys0(oLsVMWK$|W2v{t$>l5k?B0rR{~ zTKn89P$e>H4YGu~6BANe^IH^d;H4ZkyXGnbT9)!cj-gcV|1u6KDHY44_P6>W%K3Ho z5Qn~nUKY{@Z)B09g-ki+p&aV(&rzK_BgHvG_8W=|Ll2(0E=mMq!jTsB!og*epit97 zT&Vq=vg&Xk*2}4k#)b%2T9gXc1>I<6pGbG7G7!JbULJbMqs(>G3A7YAZxx)uHPK)gj&PDv;b7KPuXG4%@6_D#h2S!K$!`Ov(2OBofRNRZLQ z9p-C?>YdzvTX~`9Q)p#wGZhUK`S4enOoEcdblvGyf$KY{uT^$;0h9fPY^Fchwgl20l z$i&>P`r@(E4=NX>A$iLQ6S-&fM^}lJMP&}cZ=tIlSWT@~TrYGtRkYFZf^N}BA`pti zyF`Y->Spn?Wv(%|Dk%@d%m@7kyrZxn6bU7Okj}Jaso+%#UCo+UGxfHehYKfL;BtF2 zt}Y+I+JbIfiu%KP-E8iiXtmddo=8IqxCB)SoOtcIxwv2$zmJH;(Eiu4*|>C=4Rz;L z3cXE|ZTyd3r7&xljdssorNFJgqw~<;P*3`kj1+W0#ut_~D-*zBR(=f*XZnvY5$5Zv z$@-<3!zEwE2a~UU@+g+~B9`4rfA4y-g#%sPTM&u_LnVPkl#jSL3CI@EpELIigyW7d zTYt{nKUS@IGx82pAfHs>5l_mL08Zt5-1MKlQS=+UVL=GL?k+) z>@}&}n4Vf~%s2gL*7f2{oZmw4LYE&@HE~5BU}ZOeiB}!~H?J1CcZC#G=@F%O*+)TD zU$Ei!ZYe3K>MaQGB9VfsB0=OPbzC8cLz9S$s|C{{1*2Qa6)7oK2~S;0(rn$bF);;m zu^#hpOcA@7?W%~TV0#_l@|}WyPE|Ni;_xEIZsmdf?)9Odoa+;X`;y794+|th5k0=& zmo(`To`Ub@EbN9zN;TUV&N<>s|ArwTmh-sIKmXWD4O>#0kXQ z>uU~oFIFk&84U-!1rkmf2OY{j^lcrqeZS)IA~ym`qv7DiQ1;`snmT-cYM2p!U@|Mj zngf%odHdi2$-Q_{-GQkQ9*b&_%*+*)#6nd}{^$c{t()GqSN7KpYGLj#h&fILl>OZS zISM1?PAsHfkNP>43$2@{bjf@i5zS8Nm5I4)|0M?=Vh2?-MecF-Ui?+iwjIZ$MNYzB z(5E`V2idG;wp_6?;orG{W|Bi#-JQ}vb(j~lEX=Zy73aV^UQiT`CAb8VdND^k(M-Mn z^e*RU$5UgcUk&Rxxml!j|6H^xsjLEq_s`|+9LKY|%XJ^2NLk#iFkN_E$z3YhFUm2b z*+a9@N=_j0m$5M=BYNn6X&?q`N4A$2Uf?ls10}hnDx$4wL1RrI4E!XgWVUeYQjUoIWO3wpAccC{ z`eu&vGXVUqb^b0oG2AxqQ2#DET^(_@KjA=G9Jk=Z3~kI@+iFkW#DTf?CH-=tJmIO+!h zI8cVQlJI6GY&3`BW?QP72UWf^gY_AiI;X&lOq%))wVFn4hJ4$-hgpwSz?z{?&7}U8 z*Bz}HnJkmmBW&bfhdG)5$;ib?1QJ84aA)S>(U5o4C4ct{&%`G!et|D_&@H_2m?oOHev!dnIjXwBe2z!;G#_H z#5OcwQRc&!>jMg#d|2wy@Ct+HZg?|o{~dt9Y)&lZ6{_R zo)OHsBW(b_Ux+KM!ONLPT$+pCMEY{3Fr;emR%Vl;T-+|WBMk0c!5ue>!rD10y;=Rv zcN9J{KKhi#1D|FxobDOL2UU|E?GcN`u`zRMuUtGbikCbvC5Oo2SwyZRvb#ZIWEMl< zkkL$ET7|$AeQslfZWTI{B7{MPFiI1;lJF-co5CH9)#gy#CK}Et$0qUmzelrh(~aMh zIr`FEK!xrM`CV3cd{PSg||Ewl?m_5dZ zI=*opmQAs60)RpO^Bd=3DfyuuEm2ry1isJozh0v7%orQ(-er^0ZX^GNXx(L)zS3Iv zjIrVG)+7&kV~zSlr>3gEeykMauZQHJ^;BJu-(>omHO|90QbgrH4Pl#BIm^}5y`H8e zRZX4^Qo zFWi&O5iRno@T>gF8E->AJ`mjxBZk|7XOxMw^|X#X5?pgj(^=V>>8pCj4(K8M+Re7yd!q1pky z&tb#2mKdqC_&$djZgi^+H+ve>^;UiZ#RlTnoT2@4Q9(EmjyZwgkVLr?bMRXZ&;H$X zs|{byZ5Hw?<7N0=w2isd27FD4tcu1lB9}=#O{5waRA_yht`56%{6m8ZKi+CX{kz6O z!EF{=0~np@Pt;gwKY=-wcNxww5>RRcCvqaJHCu(BXWM z$p$%1JS=b65FPjvaJG)Fd$k?+iXER(WIxlZ0Xbmw|WqBwo!?UD+!Xsg65wJeAA-pbfFn3al6D zj)cksaTyfjWx<_ITpW){etQ#;fPY@#DTH+ee?<^KOtRrCX2gc@s-*Cb$qeF~h$^kY zhFlIJE}U$`cMnk<1#J{#-xj`SUF7G2Y&?a+FS*F?1zAXB168*#eT7LFZ^jful|U>yd!XsVmlh5E(- z1xKNkv1pO)Khja?Kh=i+SVy5^stt9bqu{^Y!ruWL>c7xYxOu7#e_uyox>yW5t`|b)fPLnM5lCb?hK(Cwc8 z6dPbCfw1hXX=;btcs&GHV9xm{q2SzRBY$up-Zc;oL`s|>y09p#4YqF}mIx(6(TEd7 zR}$vkX2aLvc8kv%mnOK+R?Q^eC4bn7lqJg1lZc%}#CWLCNVKN^=R1g6<&eRQe9ApU4ksxXC0k^$iQ^nf97JBH`#5EyB> zG0;I`0B#IO2v{ZrMq2A?6dswzY730CnCnCD&=IX6KcOn|y}@)QM#5mE^=Xa5Id^ah ze4$n2QfhC4!KCt#JZc+9)qd_;REJ(8kT43{4PDH z{4TXt4pz9HIS2qt#fbL^6}!-PgmhL6eGf5`-=)?YgB3ET*UPU~!aOY#b3!x&yc7=$t)&P&s?vnpvyRZMrVW=f#wvgo2CIvg-IDTXH170e z=p3;8rpv^d6>E5g8?T)tUNcO*c9wXxO}q}z7&=6u+YFt{;TeqA8bZZPftV=3D+DSqNq}Ew*l4TQ z?(|K+Q2tVd?@cA2mf^puRN;u3HvDC!3O)JbzoiO&XR`s0~>4|QxAFG?kL_)`kssH8OGZWWqf;` zLYrCI+Yd?hnZnkZNI~n0ewD5c6pm5%uHbnRY z{-E$#{6XPW@H+mW@YfXfJYF1g0u_A&iG&l2#1H8B znY=?(!u-2~s?6V6<<~1VA?iBtWb8Pqm z(=6myMuXMi>Ns}GjWNCF*k~IIM1s*u?6L;+%>{fz$QBCL;ztp^jG|MCi*ojap9EVv zhoJ^Ft6L*1Hh$Y}`5)`n2!lvctGhMALx%8^mGn`$6R7&bmH1g$ZQ`5CGb+($6ALY=9*M#5HZE-k|UZJ)@s*$S3b==06;T+!urV@(}z)uV zDdZQ-lW(2IbR^4f#CRni50xgmM+YCcw#~*prf)jZ&D9@u#YbLsqeA1ka;q627gkl<)Ls;f35x6T$W*`jBisG z3{%%X+Lu=1PITiDk>m$bBEW@e%mI&F=-qhvjGoh;Uug-yd?%1M&qm6FfQ>|!n^&Po zwbLgeZ*j=hlOgq}4JA_1mX?rDN>cF|d7`ODEOwz2u7a{LL~jXmPGM=HUcpiJwPqKz z$sO4)i94-k$PZ=DU8iL^kGAmM)j$@kTyBb5F{d=_lqAq>ij#>tpFYE|bK1ciSc)0Z z$IswKPQ42q%z?z@re{3CGlebw+rryXqd-9@-h(G;TNFp5;TAVs?Ju}E8VJHV(`r=< zY~PlX`i-&(v~3rO2A!l{QOBc(E&hAMKfTS=RP5D$IkmzwiLlmY$=BHZw^B0|n(HhI z+8(sYOuxlBY55@Xe=_Q1N41ykXY;itGICbZUKD+qUCJQ~hHhr&plrf3lgS7D~u=czFW&U_F(mI_Lk}B^Go7 z!Fr{|8hnQj5(1OS9U!0>Qwi+ zvE_xq!PU_HN`2ZF|G!o+|9@JQisbei>l|tNSeJPC0;v1`ulsku*0o+5Y+iL5k9@{7 z>PKJOcK>@j<1YwDqr5}jdOp)?D~Q(p>t6>+)%)^9;3npG22%s4N2O89U|164NUdv~3qC4i!0r`~~u@ znR$%HdRjm7)OKGETE#=xIRMWZvLxCSV(a?z@ohjDpgv}FjRnsLyUDxGweQx)$Sjp_Abt5Ocwh_^x}?KmP7 zZs|bY%t5*v3f*tLyM_{#fhv6v!@5Eq0^8Um2d<+11kGpbetv?Xe|O>aQP#gKzG-yh zuxzx7@vRM}-YO8&Ux7=Jv^4(e&GJd+O7b9gp1`{82#aqm@$^8Fp;9iR5jK8{*)1A$ z`X*wqhFMmMb>=MhNtJ=BctP8Cg$XC-FW{%e*0Lv_w(JzGGH{GVVO=g~yT)y) zSGyb3CSM?1k2R=H%quA53VtXEYlAN(BVGhHsI@Q>N`&OI#UQM^{y=$ER20FArs#Gn zl4Pf{I-cn2z0PwBF*963Mbb!S*@&OD7dn1 z+r<(?svLhob%d+tykqE1eaF!2958!)`lL_KXbjfOCJog7M3h%Iz`DO_3Q;o2ah$HN zMKUJt8FIowe?brTjt*-{reyJ!Wh}P%kO&8-y%@)OY~0amRS7i8&NVITW*+vFaT??n zomSBb=oK!{*oe7E#CpV*;V<|6S|xaMi2QQQ)ku)*^;#uw3z6kZPfN^+N5eHvKRxQ_ zM-i~*rQRl#Zfl>I>2JCo*ko;fpd?Wp2pjvu(Lj&^V^XhSrg$<|V2|Svf zQhfnqk%a7CoeTC04H=biY7>1*HS4olYm#;A&8g1zGHhgEJ*fqf8Lls2%oQ_+>MSO8 z*S9#Hh{l|LB{eZv6LP##;<-{@$JH`{i8&N)8KU{geIS>rOdMf8nukUjC z62SIVf{s`^oHq|k-5o>q0&0Tn%>we(4_k`12@8~ zIStXBI4=l}!&=!~MnkkcUAjmglC*a4B0r?{w^fN)|0t~Oc~U1CJn7HU?v}S? z2@h*j13Ydw^{TGZ^|?wTH63AnpC|6(#k{S;d{0ttb!%CSU3najC6!9D()!$fh}A02 zOK+kP9iY0M;I+Rg_k)=h|Nmi*&+kaX7k_WNYlj^>caS;%%}GQ@{Bu(dZb>3K z;i#=S7?wnImW7?+NyN!A0~ukp=MuvA_8pTI{x;7B?_dAkUOZXBPiP2xVBmZE$;k?5 za6M1TVlqQJun72L8qN|5B$giJU59s6vS$+>-f!>e_xhc#feMZfW0cPx`jg>Ehcu z&rzMu5LXV>j@oYTo}w@##WmaQtf>l1=h;w~-K-Ec7VohA_uj0qlK7+l0mKddctAaT zv%+--{+3&vQU^Hr`F{=?r?x5(3F2lfFe}ary={imI!VqhG}~#Xb(R1qbitD)g#P7% zr%1e9O-#7462L95(Pjl8Ey<_&CoL`Kc$%?EZWgJVn4RE~o>dn+u*UOD5f2@C=Eb z9tJ$EgM?a77wjnD1rkgHzP(-Hyt{0)m$i@qzu&IVo1h-HW56NP6dWSVNqb8$b$8h) za7V-PCPO;NgH`3Yg26WKF4@n$^4Mt#bNM?Ncvn3>I~y+$7Y|1hxcVQ{6x!U)kivh? zoThLCe|u3r!77}lu#`A2$fsEYrYU@Rw+-KJ23UE3m!a^p6In(?3CHR?N$xe>LRj1sLzD$Kcu*VL0Ym{@;fyJV}o1 z5egeTj^2i2hUVBpLdrdc|Icl4N`2$LniCrw3siN)FkEcSvc!V{W5IaKe`FirkcBqH zmCY8itNcH%`WTKo&F1R)Zk<2nG^z7vn3{RY=~Bthbip$OJj;Nm>QTlyF4##b`MFY! z&;M+O!to2;YJC3pGZYFI+R(csu+Fz;D(DYL&S!-%)w3(BXOD$aJ-Z(|Q=vD1C#z=< zt|x^S+VGXnkf)alkDsX!r+cKTj5H4HB30$ag{G=ZGHAD|++pDEI-=(ouu#{WMF#9G z!8d-P3BHBY#^8I(&>4LHm+`>^jYZ+;;MCw4kqnNtCfR(6jdgBpe8|kFY{)W9Ptw=N zdm9(^`wVZhM9`e!a6>Q^xcX|0ME#q`r~H_M&sd(^%q6gmr5S^z*B(*~5JleL z1`s&!KnDDI_Wv~TacQ}~ac>STbQ34=?*k<+S@LqJ{p{_<3DM1OgV?JvKE2$z*>Z)O zHi7a3rTym_|KIJo8n>qT;?2D|80^|Zpzc8SNOnD6c(tp+j33Ay6VmD%=Zd4<)Co*J zQ0fPYLofM*+Zn~gv_L%N_Z-Z0Eg*3Bfh=gKJpR|zC#89xyL(IB)CoMAmb$wt!xkEo z`J|{h=K_zEvQrccc8Qf?3ZBi&2Sl7v1Z6&fw3 z#DI|sKO_nN9jWm5#e58Mq(ZyJHq@Mv3b`|NTF+$omycBFK}m?t!whADB|7_(A|iiF z5-4&l2?ubBtWHkFedc?~1(vvFfknSmEVj`;#^p;akVUoY7Q55D1(rCc;6{E}u|W4W z{A6O0{G7Cy2f!0C1K!1~JU>#QwXtil<^Ou5!n3qX%{kG+sY$}B6D_P40=;vdAv|V@ zceX9I(T?6hyIV&p=-ggrc#MO7CC`(j@_Pt3NRp8dbIQzC=54s&r-R`|o$VJT+u8T> zT5{k;$@8&<%YIcrPm~9B}SpKG?6x!Zr z!{2Zj{0$_t|K7$|%0ban2}(kVrB@pG-rd(?`!{r-qZ zl#>{9xa$#j5_nXQw-6bM;C7p>*PVo-)d@T!;Jr(2$cM13s3S8^X`F*3JShd{hq5E( z<7Z7LJjuYJNI$1cY@A|0Iy@U_8?8>bs3$eb*N*OsCn|~(P8II3$Nipz$7o9^&b{J0 zWR-H&Qr4NS6HGTT~rK{0xo9z4j6Na^Sz8Gc)*> z6C1({qVClDdEU~A;{{PyO|cKi;Hq0K>P8d4Q{%B#)J@gu)CHpyGH2`7c&6SkdtIo{}Iq46>siVtdBFk4@R zH6c?T)Hr6D4S%O@3hkCLUrKei(AVgA!S;9PZsC+=HdhP#qkNGq9m|cRNXIfhNnpn^T?oTRDco)d_uBpkM=30& zIZupI_>4bRj#AhmtaC;xEH|JN{yn188~#&9D?G#>=Z#i) zdOvIQe%W--mW|q$jbh7crX#j&6kA>=?AfwWYy~iV)T9_f%dhwdMYpwRtD&Sgfy=Oki&wqhyc`T&+gKnE9Ao5GY zDCHj9{0CX}IFKkO-y;M|qCp3hZ0z17SXzTYgEDgQC4oS3D2OUHi+dh4jg))>ciRz% za|>De%){@rKx@jNt{km!i~%d4t{<(CZ@|iw(yolwIK2WD4llCl64R~LOgv>-lB>Hc zt9?nLHUZpa4YYHSv)q<%BjZFc6b@U?5902$kRQU`a>D-a%WX&lb+-%)PFQXOYRhRB z&NHbmwEV}MZs8;nl2KO~sNxBjBDvsv7Ot}7n~=vXdB5jz>w^i|IGu@Cg3ztm=t796 zWFEJ8PUc@!=M8L(w%$%{0<-Z?t!%YriNIzaRGQ(1_Ql<*{|SsIL2hKPfIMfq2`TN=y#_jml6)k&}DVG zfyZXZ_~r)Mquv^=aBY(CB9KXv-Lx< z^=Z%656y_WH!1b8;3n) zLp2_&@R3PzL#Ds|ScSh4e@|D1FARQNhBPsmb96VnI)hhTzSUKs(Hw2+=1f0dz-moF zcGyh~r+ESEpAWG z|L|?OxZoiht^c+y7k&BTgl)OFoIeV-<)WBBPT!V`3jXN2Ef)zY;k#ZZ85{TNxV}Mb zRHu$rIMt~CGRxmYhpCVKW>%H7B zGIO#1AsfE#b1dZZMA+tBCjTi@^7SGzo(LpDCDkEb0UpQJ+=9n*ko7PpsQ4zA?bx9Y zv)$$`M^_qiKhE_J9INnmA;iZj6dS^ZTs2^+g$m=!rQD_Ydx3=^hW=@8N~!b8u8RXh zIO1N2J%{LAt!*l~THBPff)gI*=%S=12CQw$U^w?-8wI7YK&2xsm8G!;)i)Xq6ZO-} zIY4{qBdxVf*-H*!b}H~@)7!IhQB0r~ABw_71mgIrDV_2>fp8*(T}^rLO*OpCz^^rxK4By|)jeYs9yNq@P1`*`RssIk#wyHv#D@Qau?n9E zAIFi;8NQ88W!(6+5E#yD3}H)C3FloN;a!iQV}v)r>6Nw68k(`O%%yi-FL|h_)`}_O&@oArt4V!kniAzN2g>X@wg4&v-AzC z-f)X;(oMv@MqCL-3TDdV;x8_vy)#$$=XE@JkX+!BF_L_)=jT^<(HQQ-hzv{2c)+sS zJHP|hZaWwAJwqPQH|;5Q(;JM|hb`%qzczXPfXRBP)bO56-2=<{*(G=+Q>XB_WdiYW ztuwz(RSBN3+T`To(Z_k+4_mc3p0ehR&cTbsMDU~xqdy=ZWISyhJ30qDnfY&-`O0X_ z!85YR_B)Y36B*(4Z7U=d^#rX`3&$yB&(ltM(rWiKtC|0$aSA7quHL)P!l8!zl;z)f zpM{H_u+e(oeHJE@Bbh((p*QZ~2T@pxCoG-CJ2Rv(@01bdf+slG9iwox(L5)0_@wpl z&doSC4{<-fwzZOFTFEj|vg!#pB$j3H)j{~$%#$6|=j7vrf+yK3tQ)6rkx7Ju)Lq}? z;xYq2u16{Z=joBk-VC#%ILM<*>w1PL`fi-U5JPw>1ET0=125N#=ASL3*uCs5F9MIqj!pw)3Hx2w z-;k6zr@%rFQq+y(6}B3}FWLSj;}!Z))nnrob{SP8Z2u?Y6|SHF)Ysz`elrq7b0p2I z`8v(tS%w@`G@h>`dT5R$A({&QoTp&)x=)$@%7G#7WD+skb4pM4u-?M?%jC9+PC=BTh53#lR}Te&b>L zF|k#W)~`ewaq^1h=OxKTPcs1I;}jvu*y;Hk4xNrqBgrD_v$^c;I+G-6cj;P)l{p++ z^&n1bBJz1b_Ddqgo;5l9hGpZLr)@oCVmqF?qRJtZSwZ!kKi}fgE?&vuN@e(IE_;>{ z7s4kwtOi4^kk0{apE}mH;8oMMN z-!nFRCB#+=cDG=UA$EEaTbE0l+7P>%SXoFKn#}pi^Nd+X{#g5fi>!4<$A%nPxc!PcxN!SDe{hj? zFL{M^wqgBLvo?9wtjBid53-*7EZf=lZ&i52u>P9k-+8M-UytytA^eu(-*T(MmCxD` zjtb%6_Rvd)N(dt#~7KRI{71?Gfp@`I4+)`kZ(A~;!r`D54wg&xn@P!lI8 zoN|v|*8Q2)(;KGg;YSZHe?5pN%*f+8CBv8J6g%O*&$%NH4I{q-E7WCMa?ovs4d1!< zSjex%^TK=83N}*L5XF{ltzv@^U139R5ZpxPt3snGAp@7eCMPSDu2i=e z_!Xt6f#VJQnUe9vR1ti9g2L^F@VtWhW`e@q9_-JYs4!=Rjn+*kD%{5($4yjtbcL-| zE;XF<^ls`(QOR-hqlPeFZ$H0Ef?nEt&QMmXl);U5qF$C*&YedYNq?ig_>Fkxy%l;{ z;v2a~_L!BfPc>EhXSevL)k=$2Q}gcMSZK_Rcvg z8A2Qzd}Ii}D98fE7Y5#=WPxJaJ-Xt{0tF*}m!S{O+b@H94E7L~nlSk)Pr~G@Jcdcn zl{$l8<*{qBSGgI~RQUEVC=x@eup^IA;9F%QKdL{y{WgzXYExo(Q}~aP)8h!z4S5Wc z{8bFBCJQ;C98!gw8&O9GvQXldM)b{D1WUsh)`${4MdBZnsKPj5__tvgFAP_aK_c(m zg}P>bou~KG|7GBB^Q2)Z6Gr;x--hsGp49CsA#k)fzz}}Qlh_<01TI@&;}Nv#kb!S$ zB&u%~#9v!zxm-V8?P+R~%(ZEX^ zOApb}fMx;wbPv?r=NWjJ=Dyf~hP%H9>KW7327b7)1j*<{x~h3ougM-=3U00#$vN9h zLwKYyAP?FHQ7*&G`oY!e}cr@ON6?8O(!G3lHibI_1iHc`2-K=pf(mEE2jypUZi;iHW z;DW)!N3dZ!{6$U;<9O}}t__^`qN#P)8FL>w0y2pnYT&1j@K3l^p@Mvz&yQNHr|CEA z`F!F<)(VlBU?g5RLKvrcjQeNclH?n}P7Kdsf$4k494T%Xb0pnx!;5ZljX9Em$4DJ} zBqQ}7FWK?boR=7sjJ>7Cim^xP*n8N(cONOSx59uXT3+%%$iDgO2A+4M zj=l8;zSU^kVBkBnwrvJ9+J5lBlp^D+7FZ$Ika0ou$D>SR_2W@2+nz7Enf&o6W*Y44 ze?5u~*FxsnZOZ210h zuf;IlcQnJef=T$gY1Fu5h#EnZVn2SdG3TeFB_=L4AiHg;n^zme-lH{KXW$XXX!uqG zPdx@Icap*^1CKWJ1qR-Cbc(y%wv&}Rzq?cTJO=AshE;lhc9rbU&hzrPN}hB;ejLxs zuVr*DSKl7GS68v;GyN6g72aQCLzdybWd4wS?j(g5kZaBIUp`6UTTkCdnekF5;Hyl2*lew3?P9tsjcH$IO5%A!D4wLS#t^>F z^pBaOa0Cg+o-#?{MJDOLf0DwnYu)1AlEn~&dTf%y8%A<#mj9he3MW!hb|c?S5(bV{ zC?Fx(dwSmPDIfp7gxwiBXWO$Z?$~V4;%@k9Yi;>hczYHD z3fJ0@9DZU_-H|0Z?00}v`HlM;gmseRvn9##*-Y|EFOl)GKr4w%%I0QK&`soXlgO>v zl1SwN6476~uf^I-ozBWUNoQpq)2Z>&St)Z5?l@H8qrCs*=3>TLn{oA89=AQ05~vDc zOCGmfwy(9}`-a6Et-zW*R`MM&SYOEVuOF}QcOh^$ z@nl1IJ5M$d&wIs&Y$Eo4#fH0y$PLSL4Bsbter{OaMDhVPEPHyaP@hav=wsk7^8DXU zQh1J{KTT3R=s*hw81fcv!8+H1ZRDHI{gsA7F{Ooj8A_Mgoz~D*FgU3HhiyAq8twkx!3EAqrF58#7aR0$kb_xneMJSggwk{WTV@C zlNG{CbnrZSh`-fjh2^iCY2tMTKkX2i!mSq>*+(8_D32Z@o83Q?BB~}C!YZxGe#5Am zWAH{*n>W}~iK?ZB^1N16C=_o=N!C^iy{Elj#ZoiudZCf@U@tUcFIxJBjrQ?CsRQD# z%f`kx+!BAG5v!jbb3VF6ci(R{f-LqtW8ilh`8Q8c*rJnWNSs;V3@UKj29= zpv0ZgmzP+O8&u^G)46Zj=G>C;*9ZFC(gknQlc{N$lQUj&Z=i_VEOKV1ROOzEZ6j`pMlub(5%(z5*uM%%SoMhUbijL@d`Vneax*ZG2CSf(L4W{j>78UP z+Fu#Z2y;uX8_&(YpOX73Kn~Hq<*FNK=i;M3RVVv(Vj(`jm7XuDjeC-xyV?kBudl6< z+eYvjzJycNH4p?p)!2_v#IGuZWTnd#M)>K1&@$3#?y3TH+Udle;b_1U&Ge{+k(vN+ zoReXxhm}JGL?zQNj3fk;OtmnQI9*W5v|r^82s^a~-y|L1 zv6Hs=P>;|h77GlK1P}9YY<2~8xQAkp3hD?CmF&uB?vWmzXI+VD>S5fXC$^b~Es}*x z;*Lt<^fC#NN2emIOLX}j<00!W08rp>9*rNx5R*)+pHouPJrn~ES|+2W$cY49Y%+4X zMib>OHW@rT&da{wvQRKl4i0&e0prqo1>zNQ)!gx(DI^soLM0VL`t=b~i+Uuv^3PCK zBqN+sazl%V)Fmw@L2jhvEW0jgGx4Tx7fLd~la!+JP-#LePeviBCi#uYv6@*Ez8$SFx47ap!-;rNP}pfv7VTKj>6C&~&uS5K$^yk9?`ro^XQzw?QaYG!fp8dgb#=InBS2u?p(*Kx zILzhe+@+O?cr=zkvw|6!xOS>Gz<*E%xS|u!PPv{;`ZhK({p4;dYlLkj?^KPmMbYnV z7bUn%#8xZ$8TFM{r<=tqR7GR*lk&LS$l2n*EiQ6~v~WXMc+P4ef9fM!+;BCl6D{6V zza!6multckskab{lkW_P?c#Y!eelcAstOr!8c@N4S{{hUSN2(GruQUpbdHj1I^XdgzpHf&` zkMgc%JU}wKmGe@0d0MuVIsYCg6;n4ta%EHK0566AmEg~_%udZj-%X;FHyoSg$Jo0Wo@MO0B8o8s@zA{iH zRR-wDsrru`K+9aWV@+NzkRQS692op<9oNpHv4j)E8Kj)L&ep5KotNsfCY>NNyYmcq zx=a`^TB^VK;>=Fnw?UzwA)g8L<_3kJ0nhbc^r=GeTQ=GieaiKw225wG9*8-xCjDJo zf1%Yrz}^BE((*gsveCXc39ov~hR(@_%mDHO(lHhOEh`sazGWl7I0-j>+nmu${h+Wd zUsC7Db;se}cMk-&%C3Wu6GN)5!PcYL7Ykev;ydfN9l`!vBf?GuI}R5EsE=b4Y?X}+ z_y#StkRQNhr)i8TZScZj}7DlLfv!qC${o{N|G9SRzVLL!PHK&j}iJ6Hzac z=U*CeJrQr3bgyT+ABuqNCR}#Ej*CLQ`4BQ-FNurc_v^UeLV|G5GUP(2|9q-&zX7in z?$w5ylWift<&vRp`c&a_1NPBi)-s)PpVoJh__nQ=m)b1TY0hl~s0Tk)C~*1w>lkzI z@MDrs74qKUQtZ#4Dx75){tW>GF4+3$jS5G;WAu(Rdfzz$Q2*GdFu~>XU%XMF1NHXb zsBos}jc-(#Wf(4jfcLv#>!}+Ry1RN;7`-3xb@e+pD!k_M`B!XIxR!d?Y*ZLXfv+|y ze7?-W;Q;*poJ|S|Nsjn!2H#HbHxdtZ=q809x!T~PG@NoOJ*}}kewswZ0x}gR&5(;;V zR!W%Y-t8<8(S4xy~q9}94)Dk zspXr()S1Wt)(LPafnozpdC!Kg?m@mYhmAsh|2+oTwoMA-AGAQ@-p=q}xLINIdp7(< zn-#L&x8c8Xv%+!wQL)lqFB9ZLwf7153Bd@K2=FC=xZDP?R1n`1 z5i>xm4{i9~W_g8hvydx4lzpdL1eiwhMXh4mEdu~RSV87uUIq@Nc^9aR`%pZ zy0qR*UivKc#Jv-!GHd<$Xrf}oOtP&KSWQ0G^!gI(<;kEoh;g9%(!aH1HeBuf(H^LjVaLZ3j$`|nm zh5y4J6t4WlqF2pVyI1+9YiWU?(PKCZKgDN~!x9V8Ti6jJRxqLk$K|y5Xg`)fj zvZy>7OI#KUCAijge!T|>Qy98dmzFv)F5T_N(WfVreeHheSnQIBQ(Gmkw`iWKR35Bp za5E}jVla|#axp37u1;yBT$7O~Dd(FyMdi`zaIkNT%l+~& zNF{JV<6N`~1%Wn0`R>IZNLNx#KPS#bQ0tQndGn;6bjuJnNa3_B!KWFm4&yq}AL!H8 z^oX$Yg!Nhj3Xv86Ute7`nT|OnP9)Ld%8s2*zxv9Pujb0dsVAS+BHfzGYI)&qzgO#w zmdX~DfvPLD@YM}I;TSrcnf(7nE6sqeOC(q{I8X(vn=A#<-d53Y5ZHcXE?PyL!R8ki zDyovN(xf}8ZM$A9eFjmlK&--v^@)Usz*>_mH0&qSrK-w_1T{OXv6)t@V4N=<-jbaR zxRz`U#%C^QmdogwppRGWrzs$sdWOOdpZJG0#!l5$8VWnmFa3l=5l64lw5-N>oA-iC z3X|=1jdGa66y0j?mqKZzhAV}qo(yYEmes0UR|hV9vRbqZVXd5yYEaN@>sTx53-Ulp z%Mex`Sdb}p?gVRHeZh7^vMKo1fdyebSzm-V)uoJWP5APFBOX3NH^eKsjT?}cje(W& zdGy6>Ze@vQrNxSDM(`^wX>ju^@uHxoZs0eYy58@Q(fv|libIJwzP0F|*9q$(ihkYr z2O*pu!qS^`D>G@NH#d9>VuBb9kQ^>mgZ@*ljT{IU8;GS`ib$bZJt$ zY~-i(Bi!AMd#kR!`f3eNHUb4|f`@z6syUF3XDB0-F zWfseIbI5NKPgLMO`yYqr;sz$|j>g6jC=SFOEVsG1vy^Z-9=B=KOEmjJnjON!5@}m$ zrtb=(f_T#Ib7(GhlW8h3A-pC`O*b(w@|pU)hxEK}iQO*wnfmdhtDnD9SYpUe*!~lC zDxA5=hX0>C6)xOlL!G));XT9fL59D0r$Pl82JTcCvdKp4_)djc)CDzSr@|J)`;P5@ zXs5zb@;i zrcvKZZ}p9!hG^98Om;ax2x_QC{g}x&h4u+*v_}1$$tGTHq54T0wI{RZm~0#&sOcK@ zn;6<$Q1dkE_e?f9`GQ)kQFU3|-R-!A*&(JDL$lcPTuLVRo+qvb!?GCb|0T}%p+=3$ z%0e#YZn5F}f|y#2mJVknnc(}5xEhShI{&C#yh5(M#MEMZ7PI)B=E{3i{53I)ry}=| z>nLJsF)53=K7K0;&36KEHJF-}^?5e@TW$C{5>t!YvgnB3n(JKRYH&x^bw}l50J$zA zrWVt)=$jif*8t*bFe|I?C)pTDE{B*}%+6v7FVS2v;%YEAi?!)da@|BsE#_s>gC7YO zRP)^m;~v$G+^$Uj*}E0K*lNSyeYe67TWz$yc(+3KHw@lOcPmUctlwt(t9L6L^^Fbx z4Z9Wczp>GJ_-=)Zg_TsYEb&#f?>=g3OetR*X5^P$HYGOS}X>o+dz4=!u=4%WT>8qBusssgdNBS-w)dw{YU>E?4; zmc?6umSy!kEEh-ZaNEvh(x5cZE4OVIbb{4Y{(`vUM38-dhE}sJOD=xfmc`M|@jG-! zw@s>Ndr#@MEbbB^Uqp8baP|%xD&7XDF?rjbrKYq2ZhA~F5*NrDH@zia>WveDQHF0< zmfCqDFy%1|{~y-=1iDR8Z{6EP?yV}S z>h?o%%1-x9(vb9RyE~HjU84yQKprZBq7V>Ne2s5>F@ocaj^Yy(6>!wS*Es5k@=#PB z`LF#rb@r*caa`Z^b1g&P^V#Q|efHUBpMCau6tvlW#?wwx>g7n+O8|oDW_NLMzgqcP zV*C+e{1Ie)Co}#CGTzM@e*}3|Yp3yq%%jUcar}Tf=e|C*c6ygOe+Af}*WTBsR$ccg z45mGXHuX~AW!1YNX`ecTL~ny6?DY3@D=t`pF(T& z8dzhUhlIuVZO{TZtO-BvfCiYun#U9if#dESSpPr^Zv5g!H{5pkJ%8L*BAZ{B-P756 z3R2y4-hl%LPE=1`C8J1lOC3BKv$H$X*}iwDTK%<8_N!O?h=Lh6ed;fXDe0_Fd7)fy zaq7s)>yysX((K&Mbt7=jNjck{;YFpM`IP^sg}5#izV3M<9Xt?G0*#WhA--Qv2_w8F zE`2lpl&1kRxh$AktW3x?bgu6+L6mcpf|ZC^Vd~mNi`#QE-RZsX8EsgqpCwwJSH`&( zN1EL(eyTiihVe-(T9EGUsy4*Q*6f;9WBA7&+FD)%;pa(k9sVDpE?nvV2VzX%o2ZJv z5)X2uPlKj^Ye`hs2P<6X<+6dqJj!~09e*RxSwt}(vtmFE?@+37;()XZx-21Pe{MSH z&glbx2jCvlGNsghylS!^RPAoCRVTv-xOnuwdfnfjq|~nStot=k_e1yb83j0lu>DE!-;S8bKexmha9b@d4+PV0P9ns*m7bm;a_ut$NC*9sJhC2QgIg{@=g@ zM(Tt3>FS%H%2g);!EX8p^i4?g3nID+i5>!?LAWdSB4+r5j(Fa07aZ%EDjwog4{b>EPB9sF@Z-;nws{Bd&MkoqC~v9WJR-H(}H&^M%B z#+g6ntiP=fdIB$a&2Z{X4nA19_I`e_ax46C>7|3}W$*{w^t$x^-g*U}T^ZS^4yi5F zS@$?j4`I*2M<3;2yFh)4m-j!+#r{JS`&IXA1^l7>x8$-$DH+_WdpQ_o=}<`-apW z=K4RKb^p^hq`r0(*7nZNOf7ZPh3X34>;8q~Y?$fnQ0f<0)BX4NsSTaESLxrYA7IC1 ziOc_`v;N|f6n}#6HJ^6W3Z>M@diVq%r2e6ANL|FF4>$^cU(UZDg-02S#+o@o1>dYL zQg^cm9?eDYD5}P$2jKdY;$O)FS|E?27O7{=%+9FCoO=(fR0Ed-&(ObDbH$JA{_W8V zBoM5AlX@LqiH7n2KwkX=SpC&pJ^lJ(Ui|~2`iH3c2OsEDt8M^ApPk#$RS!Dfeai~< z=mVfC{YPv4Oeg=m-o*+(uLn-vhnfFhIqUxWSxPN`uul!%|170G$zmsO@3;q{=pIc& zZ&zKSZs);#=`v>nh)G?#4CHkDgCx;Qm%+?;@`HV}EBmHpU`MO?^Ofof{Pmg#`_!si zu}SJmO!+vZ+zu&d9zfq}= zF3X-epcHIweiS#|-uRGKn~!4c9QFMuZL{_0o|BEoWlfA5cI;7?sVexL{q_zB1h%)H zvJO9<2vo0E!_HxT|ML?VF;;_5U#rv!H}g%ZbuV12)TgnjW%UsC>xmHxeFIh9!!On| z>LNLc>W%6}z@*Zjssp3_uMhEkK=|<$5Q%Y)|4fmu!YT6d!qq6~);o29@ZX&`C zU!C~L(~-fGZvIPF13K$RU^T!$MowN|**m*?2DYR0?#qY+$8tXqLA5=|;Ci34<c{$o$0fcb7BuldI`&UY!KaUuouINd$%txEs$%i@@kQ=vVWDrFF0zT$-AlTU5>+D;+3CMNmwsdLcu!9a@m{dDCHC17U~7ie}lh+&uRC_vU8z(4!eJlrGi;1kmX6gqa#oisBg{hI6Pj3py&FdmDYgL z&R5qs4wkkJOWOveUB{(ugVNsfyFN8|!f``tT41hT zkFNT9=#N+Y9_BRGOicOg?{$Z)$Ew!Osm<`m6CUnUt3sHHX4NT>u>N82DVM$I7&XPy z(1vvcg;c^P*!^garO^Lu!EuPFZ*6aYO2rKrnOMkh&HK44XI<;=Oa=ZGCzr zO--*wR$mLO-uE!PaIe&BAzJ*rKWHvr3%saR2Z*ynxJg*~1MEVoLk^I>9Nsfrbt&Mx z;k?P4kYpE-%&IH!$07K!V`_HyqPh|jJ_`v8i|TUp@qYe?KDFw-P(W8*h4>Ty2=VyO zK+;+2I!t;hCVhjFK8i^jAW02&PZ(4`VhLR0th?ldL3J7+gA2zEssCo+JZIfS#| zk8nn4wWIFm^nL4YSna4wfu1t_iGiyq!(BQ<-#?QtKpF0*3?EzVs8tNS$60s#YDXRO zCrdX_=3+fIO_l;2g zs;B+4qfVY$RCl3WQmZxq$*j5?b9@L$)T$Ry+P#?exj#WzOx=rRd=Cf)-*()Psxk9- zIqPmZZb<#?Pkn0LzZ^HDHaya&p7G7&hSX{B$M=pKQl~!x>iE;+hE&9P?s3*V;R!>k z4S>^KqdR*0BG4O9)XAJch4yPeF%Qz(FejmfWH9+z5CK5 zS{8)DbG%avOX{LQK#zG8jElNp@bPFs9elJ;t@;OO@*Z^rGhg;7DA9gMI!j%GIo^s% zABH5@5x)WxKMTd2rLM#uUj~Z7r#xXu{RcPm^1*f0Ck(0odX#O?!~aaS=aq`+hCV}z z@fMcR=U+ zcaaZ_#obJI$sk&bYaZ)UXe~bR7<4eS{8vnIDV6knUDCiUd5hFBdQO-I<5%4aU$evG zTmw0>XKEi@HG`fyqW`DV@DD_Ya&iCSG~Di6efMqs>Lm|QWb&!E{6)S9XH_qPxp)8E z^hh{2jrZ`BbBqJAC0LlQnNi0MtwBEoNKRfKkGJ+Nb@r_riJM(;E~?J(!Z}LSzr7NR zL3TzOou!4{vwLQjlyljTGrZ7Q?CxEd=F=?004a6z@PHbI&1wf{iNE;VQFZUaiF@%2 zCoed$HMNib#;~OoZ@UZ1f7R4(rH+3l@lU)h8s4t_%Xy=`-onCMcf?;*&OT?jqtwEQ z18R6_YGG$*Nh#e7A_bQzT3VUoPgs4|-N|Jr%hNi*uNp{Riq9!HOcn~9MTtvHlzWRp5q{a?zs z8Z)lOj3fVh#u12!{Uc>P9373>og z{J2m-cQKoWRY!Gqcdir7&g__xm0bvKw_}uUri)7yL#!@R7dX!0ex=m6RyZ5xp#b=E z^nEMTuRxyjvop&1)(Y78#v`?JUHA#(Q0Q{fSqi#y+u@#odYyW=L&vLUoeeu?l)`Ie z4?$b_#j0PeyLrOj}~T05mqfy8s6!ra!3Pt7i! zqApcuIfsu`N98kAHT6Z39v|<|2eGSqU_AXBC z>;T#NXAP+D04wKUH$R3n+#!$Q0U&iU7y5YLH^8bJhR8JIRhVJ;7n?W-Emo`Uf&N)k^YH8E&^EQ=wZ{*s*Q?j{IfvI>dHj%i9Qxq!@k8oZryoSJ z5zId9P?%b~^PRb=CG`gNvt`cVCn}}RbT-V)E`Xk$2@O2O=~o-(rBA2|!E+A*UXM7ix}Qb%+)sqN5|S3CV`Z8pDAy&U#S4#BU5 zjcPlLiPt;*uz#{?1n&w|0jJ-1g|iAIYViHX52<@^aTM_T59@9_en`F7=~vIV)t^gag>OJJ9^U zUMBV6cIWLU4XD3`v|JB}ZY`u~J-FQgJ=nOcU$Gt#4a?$oau=Y*cR0|V=Pm13AeuWI zkWIX-Um@E=w^9S|bdc{eZN96!yNexlIobvg!#9?ZaiH_~JD_eDy3RK!kO<}9z6{Fm z>hhn)<$rS-mcP#~f5-IP(r)!8^(BYo@?rRn;EYlqMwLDY&7Izp|GpT0LztEEE1-UG zT^yq4rhk}*05tt0cwX}`H~k}hz}iikwP!PHH}xTFx7e)BgZnkRv`Jl|e#YG0zMMzz z?YP7L@4$}n*(mWFkT};lhuk`K`*JXJcR?a9M(Fpwz&Hx-_T{gUYaJzKZ@*LJS}x9{OW8{_aak4A8Sj5^?oC zo2xy0=k`p^tE<$U z{&xpJ_b=_1GYEL!+N;)nkLe-#=*nZe;!r3?Y9ziNEgR zNWqm-SF7umlXrAr1#dwdSb=qn;MB%H)`AH>7oDVetPz$J>Rfb_F75AECzD@wa0P^) z)T&S3>d+zEOR%hK`}@_ZFWu_EE5R^Py=es;lFVn`%T^roEc9Jg-3c_fC3fqIE7n2U z;IgL-sqf$F;B~*V(YNFg35#b3r!02C+_U;$OeVUz_>Y z5&X4%OH%KvJ4zrxD#*Wci; z&5C|K1AlGdU(d#0r|_>+@z<&R>qYo$EB|U}HnB`%lmA3EUyN*mi#XR+F99y1<;v&6R<=peubud7Gyi%O{@TL74rxX(A2GszA|pOBVkUL2c&oZjUExsG znXWp6s;|W2H!1q{PW-i*f8B(?w(zgduJ}U|aMv9_#O0g=789 z6<~|<_1Vem(T~-K8LwCPie^WtxhJh3P&=S<9O!f4Op0|~p3h%YH>lhCoWtn(?t@%i zXoU_^>;o?M<>l-{e|8WY-{0S=_bN`95410)dJk+oRnG`c;nSZ9JV7CVX3i!H{MQlV@s_Kym3FO z>b#|PLgvJaHC}3A;EhLyw^9S4i#*-AgI;)g-D1(|xs#n+4GEbd>-8eZzPxv{@8Dh^QMr42@Y zSweK4UzCv8)43k#f$4nr_rMaJ@!?*e&iOk7;`KJLvPVqXG+fbvG$EGGzS*S?U1T|6=|wqUpUXNV6zTQa5dr&KZgCnVs9axJkiu@Y>u6WXWWbEq0c6E_CZ@eBR}J;pe6mc3$pK3-J?GXl-}AiF zHg4W@>M4jlQ|9n>ImUcLj=)Q|BJq$#98F-Nv2{Qj*IZ0J-XcwE)Sh80wOt)UT4T7~ zdbZ7*#Kb2^(q@zT=5MUTuL->x1No=LF7lo;{TDw=s_}Q z660tzu4Qh`jc7KxNT?cPICOdBiowWeT-8b{#t~yYvM&=I#z;y%DaHo_nnf{=390b7 zys%VQb<_>1^MqZYOjSRr(o}d*3=77RrcZPG!BU$MqcL_~39QwsEy{SPRA{YPC8ZX_ zg{l));q|2^cwRcn&OoU;1BuS(GTZ)EG8<1V_Gn#?#mh^T)vI;468oe$SM-1*8pUtw z1yRG_ScYmfYWM|ZC}vOd+!8ceV?$Ezo>4!FsKiUl*~Yb+ys;!Mu&0~0Jdea8hB!$K z(9<^3q*f}H!A+acEZuN)Jd0vKAq{@VpP`D~njfYviR_(6C1lJ0u0KPNMWbXuZaf-M zmXa@deVHJr`+;B4YY$=EStL=dMl8OroHuI3WJfM8V|r?NLkViSQ3YjqerbTobJj4E!G?Em>CxaFm>U*%~)Olj1Tz{{ryVGkJ#18T_QM@pt8@>{V-PRk{wDkD~T zv4-Fxqd1>oO3jSj@m#krD5H8_d3{NJ<0KxVWy|>`Eb-Y!mzYepu_-&R3~4JToNjIa zKxE|3E!CB_gTNy@aG+Esft2-N2^|aBV?R}=C^3z+hzJ(m00Hi@#)ks zoAz|?3}n{k;B@vzZ4ORvP?y_6IGyJoGv8rl3Hw0>CTK9(JT!Yru~{oR8BlD~BVSRh zWiXj1_j~PvTkh#$tXF*4Ofzdiww62L2T4F(E0&aqaa^HTq}Lh`wSQ&{t>p)QzBWIh zY0mg2SP}DneSx<&DZDN0fK5Z8{UO=gf~;fi0y8L}zL@L#tu3pB`qmx_NlKkytQ3@x zw0mi7%C5CtY&t<&S73M?d&4HBs*Hly(yYRso@qfX&8nl+TEl8B&1ya+nn71fv$|g= zhs%hzmS#=%=*@zqmS)=KTGb~Z8-KH;s-;;w*cOE2q)(;=wKN+aB^esW+Ka+w(~F5U zQ#2(LF$6sJ^xDrD_7?Xh#nu*YOYzi!}FdpZ*#4LfivT8H*CU})0V?d0i%}kr* zf|N4AYCMM&8i!abBqL-royrWtpwiT8Afj$wI+{hj5#L zQPArtns+lu!H5Ul&qtfz- z$u(sT(pKH&b?Eu!+R|1l3ds(9O*qM1uAYXA60AfdzawTTO*!(g$iqb;RXH?b#w+It zgES6FN@j)2Ses4X1)nPxXBJn+08yrzR>E=~-=zV0s0;;eRwE`3_5@>^AI|L!(ttg$ z3??L6v+N=s#UjrvJs8q?zW0fu($uT0H5a_8*dcZH>#i&zxqE3=_*9Wz#qhG?_eBW< z9d|Cq0ziyK%_M7)mfTjV5z#iGUn-%I zOV;4^rSTcLZ4ND6RL*50SHCNEUKB-n`-;{gilVHh9iE?*i;bd;{YEh@qMZRK;f<@G zmNRN)DOygUC`BmNHtiE~Lcz+CDvg~BOTC*kLvm7HSK=WFco}V6UMem1$T7P6D@9S| zk+iRrq@TvF-{g49(~Kj4?4>cxBRp3W&EU8jfUr>gj7v!w$E#HWK3n2mo7+RC%jz|2 zW;M*$Yq05&_%9{Z5k5vetF;a=4K8D& zFtzEqtgi=U*$q!G+7#3!?F zqO)}{0kqzc%?iTQuTuc!TviHDC295NN`$FKT2}WZxwOpO zM)X=*rF0q(M%9>noTXa;v~*<~6AzNLbWK{Rǻ~SMs8a5gAqS&jt zDZ^%^NAPIa^4wZ2_7Ym48f7Fr2x}b6G>j4wMycQOla#}%{kcLA9C>le4f6f?BGU;t z4mCF>9WA1%*NVdU=xC6-sXs>cLU{d9v)+6$isIK{i>}#fH&a@72(yk@I}KZ2%_k!* zH>Lrt#GzZOy4>GV2=%PyRoXNlW$Wu%Z8C6MESCdfI)L(EPR`?fva{-0%T072i%|rb zkoTMk5kvqm2=&xsNx2S2fXqI1t?YULGmbVFii1-iXSBUGNms77pt2~0jdJ`KEtJ%k zdKQIon(zXslrs+V;6ym$^w|C zu}|~4P@y2JHr*s~v(b?C?#PiNLZNe9E4%FY9Mrp12M!$ggRz1FPe%PZ^`ddzHC&N( z8!KnvylOd`xbBLm`_Uuix+|He+Y%zSyDI9oM9FC0u*Z+|D7-2Q7Xu7<>%Fiih-dqP zD8j_cf^a?lXT~N6ncoP)m}dcN38UlI6 zXbd3Sp`DOIIuB{#4p&s!P-C=0-pB5k(1x56Y>7FqEFuya=I%Idr@ zXif^5*jU8zVN1McWZ{?>*G=zOn%fRqVOyU~lP!y)I84LLrNH|Z(_su$LterBk8vL~ zR@45s*?!NVBn>0BIDcO%)N7F6E}F&Fc|nzz9X~GD=OvVFbE&|3>^53@u77q3)>~@N zDIud_l=E4x(AV3}LYB-ZK)eEfeyLQSxA#mGCePMjaSDahB~s&ha1k*i21cHTd01EC zp_Nj3-!B)K@65fRlq>Qnl=<^=F1;vSRmv2*1oHT@QE z6H0wHc&=b#8q;vJE%gJA-%FL?+|f1|Mt;b8yLF(VwRv3&% zmZyk5BrKQXRvYr8#H(ge*z~L9&6_h;SmWs-I6A>(?!P|O&X2e`cUq7{({snXOlCLn z3oM=pO#VW9TPbanig_K{(@NVIIs&7O9-|4#5e};b;25Mr{Q^|&Q% zfjNojN4@M`2~WayT=j}Z%p9fkqpSiU6(&THn1i=|l-0bt+is@4#-9{z54tp^6{1bX zzPR+~qMnA<9Awo5z1woK(2uMhdtSxovlL>b6v67#4|vTg2WZ*e%FcmxnK&RS52W}A z3dzzykoGofaceCX6%HL}*+}b}wUWA@hoz(ol(jgV^f@_YgVDwQI!;QUWQpZ`Cwh^s ztZ=#|3hNXsTTVVAx8`5E%2`;ix7>t6qDNjVnS$V!p7pIJgt6bKB`M7ASe+%k$W4-j zH|^wXVVQI3qyQkRlCwZ=hOr(?D7k^|F$GAFt+yQ92JUVC4c^oEqXpKs{TL}RFwMD# zARQ$_{+!q#YwUp_NF$|FgJ8<1UCp^^p8+1R(kF%!DLEr{QLNl@l^T@|zjWj>UA1Ks z(%20Wy4-`EBj@#8)8xFIwP}N&EdpjT$L;;7WHya2L3c}HuM&o7R&CX?s#^oa#WGCK z_9mlX*l(`+@Yjvjl_*EuCN<-o=ic%gBgqoow)|AY=P&-z`^~5*5k}#-7w0^F|FH|- z_GgG=FG*dVa({mDWo87Fie*YV)9~#Jzj~P&6D8VYJWJY1@gL7SV3fzAZn+J= zO6Olcc^BYDI|+YK*r`QV)~!-S1Tp`WnM{M4uxf-Ns@cy6Uhx@)uq z!63BCH8Ny0Zt*pVLZPu6)c9ztkq739y_OCV8I3?B@y=*_LF#jfM&^jN1DjFP=?IVe z&1NndGpV-RDBH5R!mCIz_W|GrED|&G=;X;(*d~)@V>K_BG*Lkjk%EjcirQ%|N)der zG`}(leSu*PC$fVSRC`(d(GFskNgqpx1MiVHMt=i10 z?KswPC{a|knbq5An@@I&%7c_|R$^H;;7vq6=MbX^gRpUxDi>8GjjW3QSV3k>d_WQg zb)S!`Nxc9ljoSfD_QDJ#4GjF^>I8WdtS~n2_E6lUk$EAxD8h5aBf!hAeXDn-)BS|Q8H>hh$K1!3Yh0v@WCvLuZx3`sR@Ax54@4w>kTqygT_1T8qk z07x2H%o`+f7$uD?nWU*tL~bG3h*%8Rq>-iZddX?JCqXw^KRxoP_ z4cGk&ENi#%2DS0>3_A=|axBceAgr`maAg7+ zAuNlZThvydF!hFbis4 z(kEd`{S^5u6+u0d!W-jE`+S6pTYgwmHV)(Vox$+wpsC%in;`+mO+6eWAy|E6ALZB2# zBEDBu${tT-vCds;JiMfW=QYV86GDysFpC;|Cr7jh)8jBd*CvDk5KLz%B+<$^OfkZN z>7oIUUIBiMl{y+)0Bf`k{=C6QB{&yoYf<*)y{%2ihT~;TO|;j_>Fo* z#<`FgY@RU2z0jJUxRoaR3l{fv%C0X3OB)--8ubHOjah370|#Lcd)1I=3ale@;TT1& z?T*)_!!c!__eh0e=XlK5HRN1k&}2OG0%#oesj&Knq|L!#X|AIfj@;uUo@cfyZ+I#9 z9Tsf7bk2#rf{fYfz^yEzumr*)%}l`gqMGNXqY3Mu)y2SQ z;#KqWVrHX`^Ww8CUd884mZuhLg++LrG_~(!USr|>so&D3#ViOk#+Q!GZa)L<0R2d8 zjW9@Y)~b5hHlOblnoT)@(7Roj(^6HPQ*YoRg(Zrd!gFKZrMA>D#~XY-*6bO|`tv|$ z@9Op4bE{~yEbW$2CTRi@B^!cFo4vz`Fr!Rnv1cGwCbL*#$WkWbRxp|?lUal@Vk=Xr z8T1Svb-<6lcj&8D_dz?Qwwc2cTzEm-%$Xg=3)*H*j2T>On^|6KjJ3@iJzzj;+X|`Q zshezr8wqtjiLEUmc7r*P9mQozVO2~31_*IDm`RnxUY4#f4YV~$U0x?xf=-jFtf7P@ zek1W3WKu01=cXQgy3hoP->C2=g(v_Y3xpfGqgfPJc^6d-dB)S!<@Ja#MTEg^R}`Nx zUxd(8g@~DLOvW%K080aYn?%@J%1i{XM}h^2;bY?*-Vwbl@qBAc2vp+c70HrJhyf%p z>s+DxR7}H5kx++#4vJ%vhNK?FUW2?Xp~QI!^=Kl;O`O5tIVgsFo*6(kU+4F%JngX{5wM$^MHWypwdMkGO!dbC~; zejekKNmWN-#bP1{y%sr$VxEy$MhxWTmlOwyTQo|{^b+Gy-OMHF8KyYBs<-4!mun;< zc1lI%I%JfSv()p;xkfV?3c@^WVkQE{%*doFD4k|2z!lo+h~;f?}!aG_Yvo_SUB%0v@I=~<#3V<8=h+mpPIIuKb$ zUX5>um>>9Q-ZZmP$gx;A+e{&dHN(=qUrg5*vkK(;9CU&Gn4-hl*AjXJe8*`B;aL=$ z8K4Z2ttzxMPSY`$43#lOm?25v31&Y2Vr&g!J>PXiWZVyGd`Dg=QF^qfI3QX8X2OWm zYMsN22|s^f-Hsw0Zb$hd1SvvvNT7Eltk47^EjOW8M68WegaUrq!PxBRv>`qIauV+ZrH~T> z$MLv6o*=^9P#Z+W%q;?I`#LcxkOl$Be#F%9eMYS$uJY z_Fy&FTNpNdYnIFsN-K1c;NMDBnyxo@7C5KrdgG#MNO!#nR52pDetogwvg^$SKgg(O z$lGoH0&iT567wu0WC2X~kBM6e#PrW?rBZW2+oKYm^sm7tLdZ`sf1#AqNZLe@xKf0) zq%D)#f|7+AnyYM_)%1e7v%!$*1>?$VNcV!A0D3@$zXq@_(F^8s2j`N#U~a}D7-x02 znBcw(W7knBLr_< zl(tzI5uS#e3xd(Co<&|f<|g{74msn&<#9ShA$?xl>8bd96mOMKX+rdlwe$rMsf|`? zc_yVuNPvAxc1}96h=Kz`keuNVf-`b#_$A|rwb+-CzB~d!N|Kk(8Uisb{H23U>7H;p zonnKDrrD-xQ&l^`q!A+XpeX>3w$!Rdn<9U!NHW%?G-*l&#T z4vA?50QUOs>3K(4m|jMidEJ8f8nLdK}@+CziZMpoVokInvrr(>nvj}+2oQ;D8Hfo-Df#+l8jLgQR zC1gw*%3pl0prEv)9e+cRjy|E?FwPbt`q>lX3sQ28Dlz^NCMVt* zq9_xaBruIV=1okjVKZ}iTlaO6p~mRA`(s)6a;PNy+gaX<(FN6TtDV{fh@e`PE^By+%gfMN$>!v z7o|C9mdJqNjxyYG(=Xkb#cmSf4VW~nd1F3burg5)K&_|vq;_SMqy{HnIM`~O-Xs){ z^vSmJF16&uoQQ#3VeZYEw+ApC9(mTELy~GF4nIrP!{ZrlQ!jyx|DS{qlx*+`3$rju zVBeHIFS9&N;K(Vx{eok`IPXaaH>rFG_fn`%4imhRUX6K)W7b9qA^Nhqi7No$d2yb_ z#I+d74VYPwGolMK`&~eO_MHiY z5!ejJSpiHyhY)by=`6O1T>#>>6MX#=MJlyBiznbguR6tX%%VOC>0}Fhw9+_l*KG%pKNRs(={pAMvKsu!B zk2lCbGo+gr-GnK~{QL_JBDN06`Wp@h#C>l_Z_#vg;9kkSUynW7Uz8UyfE=tXD%gPo zR)+kHsR*GanIG$exT5beKi1#$6SgJuW4(7@C}MnqSUJrsQ!_svpP(@yGzL>?KlpPN zbNN>x#ePVV`S~R}5!!|1G5&^!-EZT%DZ=n%{{?n_&pGoClmN}kX~3k#9? z6S`=dRu>`EC$0MRW}9~-&Fq_2{m`Tv2GgoYHif6v2o6FuyaYx!cCs<#1hOSJ!iD6T zSBJNTsxVPvE;B6voS)kY14|^}}kl!hQMP_lmjz z&@aVYTL3)$=Io^fprJ3ee(b%Mivb3O!HxxX$h(Sp{AwO4_(2hCR=1^b%jb7ezFUNI zAO8&{B&E*$NSTp1Ipwrdk(Cb>b0uD^A7Hqygz>kq-d6&`dRpbP0dFgU<9@(zo?lUb z{DAh>uPLOPTX=^HNUOp-kXMxfT4ukc2An8GZ;^vRVkw8DRI> zqK&qt8QS@L61GK_<7QeCc92W=Lw$0#(4mk!Fjd>q0x-#+ys8o-*B22lhQK4=7PzyF!p97_NAKy0n!xe*df-VKxB0@}-96xOpTD{G?J}5nqU`_Z zf#;dOQwHIAcv1(u(_l{w`}&>5JS>7wzV~uDf%suHu?^>4#f-V(d}9eo^;`WPEthIc z=ZA~=gz0>H2@@9b>LQp@1piINbYnTMlu$dMwYJ#*|IYjVRW`pJ)L=tiQhc{a(eTo` zJYIME`>gq@4FrCj)@tACfr)j>ojqW9W_`34v*o|5NC6$1Ke7Mi_gS6S2DFiXt^ON5 zP|es^dSH1-@asLmAgt;KR&VP8hxK|h%uj;?n1C8L@@N6)@+5;CN`*sgF zoe(|x%^n1{Oz*ZR^49|3{E@LQxIypMo*{Th)VOx-cpZzA{;Gtqc%Z4A@I(FM<~2Vv5V@C^Z|`bUz! z0h^?dYJ7XoZNfA(iOCNL;)+`%cFYL|NUI~ESu$W+4HLg^=Z5PEP?tC|1H>^VnzvOk z?Tjj>nk6HoCe0L6&6}rQr4H{3LaFAlQLoZYhLVs`P6y@oe4)_xxtUjr+Ew z45Bz};HP4skOwU&C8aXP!1LjCBBV57G0zV%#jHa;_+WVuVCpYriTF(dsK>;kJUP+);9eLoTY%|Z`sL7 zmBjc>nH=b0WzKVXa7i8uFo8`EzJSwi!FeM@9u;M@@Q6QDXhN1=3ujPBH1S@ql{C<3 zL-O}z32^uop1KA=oDcILnw*AVGYlGtnSi$!7=!z;C@S!bii<}IA>9n_|FSbk)&H4< z@`jnd1PWAw-^8~6iCgH6E6t!z7a>fya2y))TZ(Ch*b#ebV4B}Py*Bm z^@%xH=)q}YQfo0KdqEg&Bhr7hO2y4 z3rL)#>D%lYctMy#G#F|!N3S5Ys*TI9EZ$Ox1p~F&5CCeg6{RF)v+D!t+U)$^E(Gtp zdof-ehr-Si<|PPIjQm3S-F7y3>Ju3#R%-i3)Wt>D(kk4nO4)sWsSvpwv7O#G6l6H{&f z=m$kOw?h9Rkk}0xREOEE@?x)59VOip=X)VJOo>>W!R2HY-5+JyFr~+&+24`rb-V!y zauc`9yg00r0-C)|4-RMLbFcVViw8=O+714ps3OTlXU>#)Nn9PpT20v4OX5~Q9R9n^ zp1MtH=tEX&oEQ6Dkq((*yA%BxL&*3 z^jw;QekX`wDa!3JNAF?lEhZ`6C1}UPcC$v8@qi!UBu{Aj`6OdhC+h7cxftec`^0a! zl{i(Go;V`;6Gp6AFVzY^f#&LqgfcQ7F0wjh=Xw(GLh|t)9#BZR^LPchYkoXN$2uL-H9!L$j zeyi9h4(mv3oJ*h`k1^3`ed0IHX#2#1QB{3u9V#;&2&w6|dXuQlIswze2&v7~2&t0^ zCK~@D@f)efooK8)q{c24VEjr*O;SoVHXc$_KfrR0Ux}$<8&i$1n)nTV%?%o2anX(& zm}uMvAWq^Ml*gAAHv|(Q6x7rW8ya^;L;{=VH_|>ttT(V zR5N2FlSu|^NeqLTVFk(K`8Ujj{!J!X{y8h5Y?BEb@hLsV0_kuf8A}%mJDFtlJY96b zWRf-WbfKb?@U|wu{UOF3lI!PR#mJdVvX(xkB~%)cVK&i^h6%9))DCnWp@Wl2rsJrh zKR{?Hyp-UY>nUQCgRwO5gy|f+EhKkV7$wY*Th2n!4ptJbXroz?$g8jvi zuq{xR2T_OB2Up1PGMP{hk0Bs5eiGiI&7W`*N)MIz{Fac=`pE=NRcoON4F{k)>eaGI z-{Z)xP)87X>}iqP8ri~i6EG~49WwYqeot9g8bI9In0^dMj09j|G~cWdCW`<*{UPiV zrRy-g7{ikZd|McKGA;|JZpLJ@gokAF`D){RKziT?Vbb*YZn%ja0M_tQA7)(IsxeC& zz}%+G&p;c;9nupkk8yATYj|no)6h2FDFBHV)Nm;SA~tI}gu<#m>Lm`7!T6>JPBDw| z@BxAF7+=USAproO8h(e;#ApD3cggDdyM1P93=rgLB@_}g>tjG5lSa%U8IatSg65cI zD?spUEG8RA0+O44lJXS+vk(KI;h_@_%S_B>mh+6lVW%85VKzWXbdl>*|Cw+Zf>bc9 zZh%YSeoQ91Rt=hUB!DK?YC>(0p1)FIVr`IY-ko@U?)N}C#0L2Ou!+MF!0%z0B@-k= znDrFo)htRO9i0Vef^i8Uxt$BeEXN={@$%s(mVk`##+6tFGEn6gW`r}2$Y`rZ8YtGO z0JM^Z-jNl7RYsD4R6w}FjKo#4ej-}Ph-N^nL;$H*1LjNwB@n>CABaW7>=4LJ*;nl8 zwGPiGn-0ff^4Ucj5NVuiz4b(};Y!byNsM`3jf+}}}P z(jX#81X*GX#b6#0RRW~7O&2DjPyoa&4TvBi0Qg)g7A4kF0AN@G*b-|JNN>a8MLHuT z)&hVelN4uyOkxoY5X^#kfg%PASar0;&$);w6aaODh_xjk)lvNw1rezLpl%ScPz9uE z5V4{Iq@8;SVjT`Z!%JaC2fd=t%6N*D@Q2>}sCi$@;u|WY4 z)JW$ZM8FIHdWQr{6Jaqx8eZDwsYb-o07)hj{Sbl(yaUqk(gx=d!F50qQ<{jLF{1N` z;4dKX!jfh|gl;sXbBUEUK#7+|8WQVOfME92)2CSGYDk|a6FUX~ft}|@iGVsIan7L# zsOOmGL+t1P24afCc0@fQg6IGy9_?uoD3(044}(hD10O zK+35>5m;w17pRDs00i&<_!AIv z+~HkkeGFB^{|y0*ff~$#sO=)wZ%FW~82K0yI|YVBep5(<%YXm_3~7>outbCc0d4~W zSholy0npkOu&Fv^uglu-&^TRPzraRd2RVd?6;nF-cA*Wo^Cg$~o+?xY>HKK=>+CGJ zdSmEf8%ib<1}?Dy7*FzW_oX%rBT>Sa6<}OR0FkS2g}lBO1=NyLyrCBbj8G`7-DF4m zjlHNq=`5#Jw$aERgZFmz9qemt6f{MG;e zPEU&S^}B4`e4XP)8)zd!E6I1;P%`dD{7I?z^g#GDjX28#W0yVvhmCz-A(NNFzHjcY zzrO_2U6!2SpKJm@jOc9e2TJJ>Yl9K+K^sB;&I4u9FCu)%CV(fcB>Z6;=I`9M!aV;+ zYyx=8mTupJ14wh>M{SZb+Fm>gYl970>c7}n(*nhJ{Xuu@7Wl7)oPHvh{kH;Wqo23Yx{ZFpM(GoB zMh^dO!_6Z8MH{ad@h{nUnlGGhD*%4W!%_2PJH6gc*_3_725?@2K6C4J7&$blf3+7; z!?TILRv_}q?;U>KCh_3{Q_9x=8zrpfC21TI2e+3{@X5#W<>(zHJZV?-(&d{akd6bP zWWHsCFx=^cn0boyPMf3|!gp}8S~U!}`6S+N+Y}z`WUy(%O1{$*Pn+;`DH43Q7Xi44 zImKNz1%@}_#-NwMHx$V(-EGq}yc8Gg9Jsy5MzMqZ8b0vm@h5V>XOr+&q-9M0!zM|* z7ARSb!!6&p(K>}rnSmeJAmodslz!L?=UES>^q;*5&@Qo>-+L4S*mc$)X!%hu8kz>M zk{{c66?_|ui=Xr&kX-!qCLY8`>^lm2RfI$>JQsT6oKSy^5#7j>0%6$(Mt;Lz+fX|mliPCWl3}!FSEfY@n8dP zD+=!Q1q=)i0&lQ^c7UJ#L+Fh*1ePEUEA1r3of0Iu+$I5AP$ZGBK7b^mroNUhA0e4c zvect$ue8gA#THGMban?u)thaSv6d+HEj9*U02UMNRW^z`5@-Rw)dtBrM=;Q_*09YjTdf#->0Qh%+Cyp}N~_hIyd>S{u|Q%h~n1-XwBnd%H~ndK2+Z z&h<8=n?>6^H`pL^O)JuO6fn>!ZZUld`<*sHtsQMaLB7jQhy7zJ@kSfah6(&xD)HSm zK?DmJ+O2s{0l|%Lg5GOGahjzKJagi;O{DI9Hc{m1&pe>fLbl&;li=2)UgQ0fjn&_Y}zoPBktyCYg!K%UG22TD(Nbr+TpDdzC!DBYa6pd8Ka z+)m6;ED8Yle*ui9mUgwK_F<~L>W*wB-RZNZW@Z-rxy}+PkT|JOq9D^GcIGV#cWysc zA|vJ&L3?hN*pPvEi=sI@clPe7{T-5(IL=^E1)ZhcvvX%-W#T*s(llr1c5-$jAAQc6>4nZr&$`oUlouv89=}?(#&NP9Mkjnb0ozd3F!Hi?;E#DRADuHpBgYdzC1**5 zjdBngqQFfOetr%@lovta-{)Wuj*ogVl`MRFm@(mWde-}i%`_uayqzRgAUP~weYZKI z?Dg=06Xv1%LZ0_6H(e=`Ol)%WRcsu_Qf8gi-` zevk@J!Roi@%>V?%8Z~AJ66Do&)n*I=(!wX=XmpxUhzQ(Wa3^Tw`%$P?CFgTh30Wd~T3Na9UCB+yN%$yWvV3}em(4b&$rAULU0w0oXbVzZ=N128zFeuYm+NWlgWMC2#trBH)RuQQEVi_^e~AEuV{I9DFDutZ28m^W;aB|^kfdK=S{AOe0gb_&=c{ra{gM?=wy^H<3%X&MSc zUqAYPg!b);$E!A8UAzt=2_B@spc|(jv*LYBa zjrCX`%`%zv*Ct;-#ZP{tocI zbeTnN{2lJh%z{Qum+4|#%!`qtMuN`1rCr^5RK#Mwq&t1K@D+Q)^9x;SW{IV_?$Ydz z{oboOykao~nVV+ynm(^~*gWJ60ByDC(`4deI)EI2!Ovhcn>@ZnbP8zbN;lcsLUz2- zmF3n}tb4|MxU>qk){O8myv_h=rVHVM7tiNrNRQ>)KV~S8c_GaL$eWSqyp8Jc5Bk4Hkr;QBk3XGY){oJzqoGv>xTept_pva#4RUzRmWF;?@Fs{Xi$ z@zTb|y;!%}IB8?ez~gU}nYjk)q9B0jZod)ebM{7%m3SLmYys&SLHdm#;zxmu*djM> z@ncIy1N|W4FCK|{k~HQgG|jTc51Lg9m5U>)ek0&Sarnot#z7Mq5(?$Ft4%V-LY4dp z-m(|3Blr_;KwD+vH8g)h@8Ann^IPtO{)mWtl+JIb_0y<+!BrS(@7>5QY*yRE)Uw>? z4D)sx$%T1~jNrl`n0aMw^Nv7qMO**F;E|pEE zF`XGMmCdJcni(&ZQ#73!ES2Z!I?PC^YVM z)r5j8rmw2Wb1|3csA@u5Bg;v{h)#=%zJrq*6y+3s7rC()5UJ4m$c@7q)0)m0ZAVqo z2%-0|J;y6~q4Ko8w>VRHTaSE%gW_~m3hzcy^11JIh8FiPPEYOLJ<^$bRkJ&Nwi@h;O7qZeWWMcSy=GsA)CRzC;ne5;E_vY?La~Ae+>4WcvK2{{$Q|gIS9Ma^(zw+#T*Mwfcm6`qlNv`jDvlKI^Cv)##o$y=Ue*)7?Gu zv%5Pd?(A+F*?j8A))$<((Ahb)eOBqLPkgabef(o&Vd|W*9o>bq6Vd{BlSoZChaIKV z6C8_-DT1!Iu+Uvl2M-)jYl!e9N2xP?!Q|5Ho{n05`#t^YB|jQP4v+ndIK+G>zF4W{ z@Yl%H{CsC_#-Cdn*|B$SdXsYA-{%bPRqE5%uFxbivpZ*(7MqaSBi%Dm?J2$g9GBdS*Q8|C=cZR37FDiBOh5=oMAe!C{1H}3H z3dc|l@9OSdP!6cm@SfQ@{%f%_O@A>X>Fl1)NH8_mUBvp7b8w|Iyrk4i{w}ZQdr0g8cCKGX6JS)=UdC1 z;XO({XZ?U$vwiOl_1$InoVP-)nNxQy1E!h59eb8w*Tp$J;0yzm0;1tpDRt7p6>7~n z>e>N8JhgqXHno3obb5 z-CgVey4C?Av=u6^A?{4gDCd(7VNm<fmye=k$!abU8F&&8}H>9sRYt%YUim zd#09lJ!kvu(&9*K@9r&|mGjj;XLuhsc|ZSm*Q|1&h~eE``eV(~o_Y1PKB#~@b!z)! zl^7e@j~xBSawlh1304)dde+ojy|Z1ZKaAu`IkkOpa%O73ZuO1J9d3|<1`Y2~P8}M# z2ipj8Tr;CSj_ollU;$jVbF*E-nzMGO&n&kqB=y|`O01mEFGuagTDXi}{E?7v`<~I?Ee_EvGnT2 z&Qh> zP5&Pc<(6S~u?d>syy;kH7)J#byJpYS;@Rq|V<88(8Vs60r<_AWsDfZXMFv4s>Fnq( zbd>YJU{1JZYUV8U;GifCJR68d;p{nr|0-vlGrU8oUmBe}dHu}P(v%;r8`(a)^AzQH zXfjXOIH15V;c(9zIIrr^|ICQVxq!y)`7$RpabCUTC>cO{+w%--7Swc4!tMK)I?9>I z34y0G!3+zYCPhx5*VE9fqfP9ZS#_2rm^s$1pDuHTU)`PSEGqTr-$9Fe>)G(^BJ7AL z?&Y81?6Kz6>SxRLJv$%VJ7H7bIk(Rl-cE*{#k8}tv{^Y9%k+i4bLeg~LcHnW{MRyP z&7%7GGEgU-tGm$cqW&h`y$jPFXshLZeVsU9|VQF!Ec4=3$ zdroJeI)#PRW! zt5&GtS?=R$b=6Af63)E1G*e%gnkFwtIlt<2hNsO$I!Bp?r|G|{Z)yL0=lLU@SB@<1 zpPPQp)KYiP?DR;~ot<0iEW8Z=otxVIob9uVo#~~Kna<4K`IoB$YKYuG$JsEilyk-U z0d?PsezoD1{Nv#j{c2=EIXEo;;!1XQXFA*W?i_j5)b72Vm#c$?TE4!1K&h2n3;Y;d zS*Qg(uH1uE=c;e7bPg|5O1-1c*{~h{1B&`2C}JP{0*!p)K)>2>&g=|09q+)DXAbnM zk=HD2RL(n)Uaj4$-q{BZ=<^2p)!I3A6Ox??KW5c!_@g_}uU4)3tfSWM-K;)^#IGLc zS8COZAZ>nXM%{vRZ^OiwLEISg;@^At(v~35p$u%uX^gnF%u! z2RV+GbI!1OlCs4+eCiYFN0&CPFn3f#KkzASMV_&CnHoE;*!x zU_}^<#qduESGC9ELfs@#J3JxKenK3HKPfTFuuQ2tEY)J=Wf5)XXuN-vvhy`%=WAl; ze=R3quZeZY;l$>3lQWzBmWdqc``!>MO|OX@HQ;)~WIuNd5ggMgdNx6s=d2R`j)bd% z;hMOuF;@_y=QfJxW@2dnn__>|Yr?yW!Qlwx3zOr76OUt-jQPU@^Ho*a-!Ogi|M1{q zAw2en2S;ndD%1DoA08APXTta4A0G7L$AACuU?4xfXN8e8+yC(3G@a&i(|!C-4{}~L za1?;MG6DCwJ3VNp>0g-Hr8_;C$b_l+-y;&O^2ad-PwB#XNX7M##PyluOyr0B!?~J( zLlRnV3sW}|3PujcD0j(luxkqwIfF1Vodx~c7ACwGy=owrff<#4^TZ6!j#J8BH<|Kk z3&!FHT>_`1O9`Bk&Jwt^g$cG`Q$*%ALXJIC(pe;19PikpSQ>e!nnbPP9yIh+WoM4k zFh?|;bG*_pM>LcWax}~l4e2L38Wan1^gys)oc`cMQx&)RbQZUt334=rx#^rA{z+Jh z-+WB#0>4R^1H=Xae<#R6^+EwpIGL>j zhZ6N-d;gP7Ap{Qda#-lX&A23gq0c6FkG(%O2a@y$~665WyoiZ4LRf(GEb*# zWQgZZa7^#_KTLcE!DAdN8U&! zkB*tElw%(&$DSia6djc$P|>@M6>V@7owG_su#uuojv`kp8m=3oAvKY>jd57*R?Wkv z3?n}VY|3E!(y^7P+dBnA+WBuX*u>q~%7nMqDg!w|d?&@`kJct~%J5wV=U^wcG2tyI zSq-y(5tj3bT}bS$*eRHvZNwODjdXaa4OhP&MdzM-?nM}n-!qN+;{ddf2C@a)$MQC+ z^=d(TMiG#)1$jnfU|}1_c%kbO-k#Oq*)}GehU|DEekFq4=J9OCknQxEElE6|m0tt;7dez20wY*Xj?IIzH3EcxpQM#{^9vuM|2XFA58MZ=K83@@{BHi;|FG~xZ{ zYC~DxjtnoK$vz;5xGJ1SrXSAKo}EYfZ=}0#-{nD(cI`ahS#!+IG2vdm%Y&5KAryLX6`*(Rz%nx(72PI6Bz1xFIon&6Ruj_6P29V+U-5%VM!}{Yc-|az6 zv;3UytK03tt&}>Oc(S+`@AhDn=J+w)_wjBI?x2v3yFIu^IDXvi!353mQ+oE%dpwvf zgp>AoaIYr(lJ2{Bj|X!}(51u)CWlyDe8mjBp#%C`BA~xXKo=(i`kM}@474055yBl3 z!lzO~_`8JgMaF|6oU85Gp+dNt&N77W(RkrTq7>74Qm%QYb_|J@mEc*f+K@P5TQrpIIVNbmPF)wwtA@nEwy@$L+_xz~eT z8hqJ6BXdHLGImqCHy1ASXrf1OD z|2oFb6k~tNHI-4WHx21MW-6mvXz*~OxZI2TF+v4sw?{16?V&~clNRmvuxU+NwA;hB zMkzX7OWxxV@7rqda7Aj;q}Rx2VrRP2_-{)z{@bF)Y@Zqw{9D${uk@L0z1zW1FoE_2 z*s?iefZo9{UbKdvlZ9V>ri0{K{!7-gvoqk%Gm$gYj>fnh_p-%>h-{+RuYX`Mcf04A z@aDg1IBD^^Mc;1DW7*|Q^iu86tCq|}D}})J@iI+#$AWm?SHmAzkp8<;!yj5eM)qD0 zX5j(DsP}c<>%q`G$G0EFSMA%6%D3#x_j>Sro{8+M_Ij|J8dcY#`fy*t)#0i}dkzm^ zLWYXx4i(QG7B?kNILkyn*Q$XXqUQXw9JM=?rbKM^NNlRkGLbXPAC7ag_Mik}C{em3 zPVzD|BZ*-b_PfZeKg)!7C9%5%UOZYxJiy>sB#_34xv*5mIuLn~#Jk#qNy4Qp6 z&N7jA$6gQM9=F$n2Q>9d!`HCagS4|v72gFK|DEAGd9Mep&So2~%HU&~G|TJTzsG~l zPLdZi{$-WqMoA(jysjw^C=*7UZNf(rW-%$PcvmxMD;_4EwfifL*H*kHB9+X|nlwkr z+~~+W$h4HXT{CEzS^3&C*IS0vRV}k^zVeKr^J>yvUI_dT4ezv^wk0=Z2dBc0V>ZG@ z{m}q!F!Dpd4QwR3=bJ2H?O+H9cWztYw6_e9@0PqyZ2{zU=fCdWZGqgk3~4E!+y&iPeoOk+O-|NA$d=tL+_ImJ1zKQJ5_j<5d zLhe+u&m@WQq2~WY=B^`By1224OQ?mft9Um z#ncxCBYoFear;f`YBriZoNY$T!&`N zj?uWif=fS#jm>v^J(#Ss`e(zJwaqYHF?M&nZV)34`i?%`7dlMIn2U2m@YFr<+#XD0>C?hfui#I|(OyqDLF!)fIz7?1w zaRu)dn1%Txu^_EIC(^ng2&N@5f}our3$C@o1xmPdcfZ8(e4+xecnT`JgUN+9e>C2& zCS>D{wEv!yh3AO0!+{jkst%@Fx|*f)@WV8jRDaZg4Wg=@l7}LDKHhSveB7?`al7dM zqJz`cDVPmkd!zsz2uS?B53vjdu~Q;)hz3QmGmUE?C!Na*^*HHK-0dRjjB`ycnA|P< zLg$d4YLX82Ls;QaMon=^;hN%N;p%a&nJg$%L;?!bhPYx-Wnqd-?8*t-6__F4L(gN~ zkJLnCm@CO96Twba?R`B!uqqgi;ZY&4A{?{JVt7vA56&~;K4qT=FYA!bbNLGPdGPsp zCh{)W=K*|I?DOEX^G*2r@AKd?ehl2_!P4_hWLNI<;5mK_+2_GW=d+4Y))p=6F(vDZ z^Bc)J`T{MhBR?ps<^nCNjvtgY`vOPSN&iv3(DO=G!v&3Gy-N|aE3Kmmk#%lIEvqv> zDC@e8hm{T~Q46s2c$E>Ks*LzlG9u8?DIK4>SPEG>+93sKPk>oQN(TZeOCp2urF?e3 zC^62&c-d)*5);K|BJf`qneblm9|JkJvT|*dTo`u|OH(beRgo|@3-VqjIjVjcZX{9yE}F4hsNC61I*E#;t+^8F=^@^E@5E$c;oP}ZhS zTGlW8psdVG9a%M6);1-p#iiP>zWiWG7{?Dv`T9~V(Z3d7ss6_NFP7dF1b+v9Zs-&bGy@ z!9Zk~-o*P(K)fTYyC`ixr3LZ6V2we)H#rwdgSIpG~m}Xqy88GC9)7% z!oxTBFliV@33qW-ds?FEZny*b@>tIFt|oHA?a_z8T>5t6eh)^zV}PRi_}cCFU~yLy zz6G;HDbR9--u5lcA@6$D`MT< zwAkajH4=MTHxnZE1*rIOCLmi7=5r4oLq4tPIs`_8w-5>X3Ax7t_m<)A> z)NVmi!a444?3ksbG50!y+!5wV#hN?-mGgZE`<;V=!g(lY-UQHw4f@3V66u_{`c3HT#2TKMXG-kHTLK%U0Ew}MwSZ9!4O%z4RZ2B|7{i{dH z9_7W2wQa*#^Zai!}*ffAm&RREECjDqGE%C z)p%O4ulF$FZT+rn1-&Mi_s9qDMGALNa2v^W&$|Y4BZKj$&i_Feo5lr|pHzY`RDyaL zOYLNW7HbTizL_g!<)Jf%6d z8QBl-_u!Ba*j~S?33X{Hq3m&m4&^7Bni|T`6)alrrTabjNt5qM%Ui=%JMUxuoBbsl zc1OfP&2wE!ZoHioaqRml=o8a?6tVLPGgV6FmD-SO&6X;q&6Q%v5Boj1K$EAW`S$Ji zpyWyu*=YwnSV$J}v%BVaFxAhe9PtA*HPz2GObfSlz=K;gd2O2Sv;!XOxzdC$?|=vS zy-fJdKj1+nKMD_caC*Y#m@SzeA01*{?{o$eZeM-pa}#v?B83F`dRGzM_(G9@uBu)ryyGq3Ap_SJm~PDst}L3 zeANd$IEy4%th_`M8eG2PPW0dkAuvUEO<3&8V+t*&x5h4U!9Dzd2iItLkxPjisNu(z zxX}r5m74G$Wxz@yP{L46c-Q5-=YR*9SFwcCfKi&TMx|&W1g4m-3Gb;C|4b0(Yr^}A zkeeVpp$Q)-!ns1Aj+ZpyL#3m$5V)rMjwXDf{OBnJDqp7wJ6tNTKWO+DC1F&8@mEdQ zsw1^W!`pPEjE_`~Y*!k-m4sC7(S%)!(5fHXBa@&QTv1aev^8o>Kc~LmBj*rxg}cX; z+Lt2S8TR5a?9b9wJt2OEh+mi#9|LBXyym!)@<+g!AtxG9P|g*|;%Jl@Q6WdoSS@~# z;RU=Wf5f9<%E)^~?v{Qgyd8<=2th{N_ldEtQr!yNC$f5#GNj&qjX39vh_4*Z`NC=T zXT7?Wj9Sy9!uC{u6W(WtPH1{eG_C2cH9aQj|8X@nxu+fQ;5VHokDA#H2Rv9ziu=g} z9_&jHd~YA{;C&&mAY`poCFFTis%DF8C`;vH)bc+(qz+FagB_(|06Th83fqgtQ4+g1fzBwZ;9-#159-k z^G$O_yDY%{$pH_J)8@Zx`o2Hl!E*yl;O6GaLlzP zfJ3;pBCLf=vATV+b2pDsyNxPsvzZ}9j-cmgkF81=^jIMv`#^9&Aa z=UmG<#|WIkxmd%sCcK^3a_t^DoK^isDqYWEu#UfuC8C$2cOd<=>vWcPApJt2U#;k! zNH22eok+ilbZu5*&qd$Md=mfFtEIyX@68lZ8O3nUJSv$=c1fz&s`Y^pvN&4-xYwF_iMQ;EubV$JTgPVzt1hUN2ed-h04< zuhy!Acw_M2Bad)w)*N3cj$RH&xp16y(1Y!oV^x~3&*u}curU=4|?#YW_>%&S9j2Z*U38Jpa<`fLkEvV1y3Q@xr(97p0lf)I4!;$ zHyQP^FaDazecVs4cdDB{CVp7L#uw0@#><9I{-bOIZ%QO@YO~qRt!eK%13a!^$ky{u zbdOulz8e@%)jv2^>g~T32^$xPjlFJgY}7~Y6UjIVOI)h*>04FLAVwR@0pFf>S-3ip z+Q)hVH&=y@cZ`yip_|1PSK{`I^f+uG@hk>95tYj=+z_j|L0>x|a0jI~b-5)Wqkq&N zj^Vjv)`+0t?Xob|5kz2WbAoPYe^Wvho?5iNtZ`z)ZK+Rk-SMD36go# zEm~1p87xx?9_I2w(p@^sMQ zt@76n@P|U+#`d83i6Nq6wUiRBwN@tWub?1Kp~l!H^(y%zFEW3z)8axuPa)3!LG=Aw|zraJQO@npj*o zbZkrQ>bPAs$c_rqt=RgM#Ul>NqfjN0p-DDN;kw&pNt{>VN^8Q*SH6j$+t=Qa1V+2s zzs8O^Oxn`{b|9QYYH#~j)-*=J0?v^tgjtSX>vL#3zIZ9 zUs4pNI)x^Y%xO{U9@bitBsSTSs&3~QN@Bo2oT|=i*N#n4Oo&hI)B&zy9`>({)+D0E zqP<-E&5J9cnz)@zxbn-@TUP3{&IMIcC7BMOld6BDXE<*23ab>;x-m088+}(@UEQ6^ zE+={aNVh>TQKQ)Q{jzZ1!9#}*=?vnH{=+40d+VjmT3J$xy!p-gP&fVgH)h?Tlq79Q zl4reJKd&xnhf>hkhAk_;-NYATwGG4kq0r$rq*Sh|V7Plt)GrT(49v^oqb}-Cds^kM zZA7PJ`P1S{)LqO-I3UTjXT5`EQRn>la3^J5VBKTLe-Ui zDGAE`WQ-$Kc7>vd-~7`WxgC2&O(^IOV|ZScQ!9z>-sd_&X@iN9@e4~hD*)NG;wpbF zr@0)OI_eV@!ya79<0^vJI%BpyxTq!?bzYIw8i+0q2ZS@tLti=iYF7)lYkrn`A&7fQ zh9ZR`;fn75;XJ<*gWL6Wo#NmkMGA*l?C@aZ5j=7Thxre;YtN@Q^LnC`VJ+h7{zcSY ztI~0rs0lUkvR;ugxLsYRDB6!_@qs8YnoBDoKD~5gGC9|&61MvMh=fAz!&?e&*X8Lv6FS3Tf0Z3bOh@T9^b=HN^AhThVAB$>JyIeUez~w*Ts6oJ1nhv`|8%>a*AG%{ zN{Y3k(Lz7FYd_qs?IV=&oGR;xP*(rMQg#0kvc2bat<;jPd@x~Pb;uttk3_3@V7gQm zhUiJF)XYKsr9e9LBT(g4akLzGY&yE0qMlbed7Xj({Qwk`ZSTrwa6-W{J62E{iKaX& zaKlZ(aQy5F=aqqAIDQk1V~sYoz;Pv6d@$%ZHpNJ<2?seZKBp#Leqlksny_6IsR_qn z%yK24cnDYH0a*ra9mZ_+QHFXKt8Q}wdWQA%&uHLNP3kd(Hu^apq?cF43v%-Y)s&a> zqS3HukWW!m$ny@rT$z=4zo$t?NATbzyO!UCF}?Ws&}(T%n<&7uS%+`N$U+0sWASKNBs^4~ zYrjga;u&`vMw>t^uC*o1Z-Tdfq+4lG&z>-*{_P}mCz0&sM#&O>HKB>gU_AbJ_W7#Ad%9hOF=PB|S*s!c>FYL|!jFh>93)pnn97!P_3 zzRSToXh?U)1?Tt&1rr?04Wo@8!qM6;_m|mt+IV&bpQ=eIMdelT(pnhLx)OFd+21A* ztO&*vWubAJ!de(Frlc99i%#P-j?LHbnT>)Xe^`b{Vv4ccGTICUZcfVr4r5H=TAAc( z6NKEjJDi(26@#%TEkz!UjYlHA{Lu;<#>O;hXqfXV=+*vc%$5g!zE6`^PPlf{MtUbz zB-(I8UJ|6d0~Cx&5-yIm3ERVhkOe=a>jp1s54EE)yPrQ?VfTp!?5G{M;jjje6<=Cv zwCtM&W)5v)@c4i0AYPYB+WJVd$KXs_0cXBdXwa#iI~amuu{9Cu--Wz?Sx zprw(|e%H?;#q#w5b@_pof40oRkJKec#apshM@}_P2wjl{>qee9#>q@@8o#;5ehOJ` zxY2|+Z=HdhVQBSXuPnTGBNyEZiLs;67;^6IorT}Xbvz#$6iPV=jtvkl+NTPfUj#bKgN%CGyerFYT-tpZAoTSI0(=8;EPhEd>RSRR zG`KEY$R7Bfo4IQb6tYCmyqTLu_Aqw-mlN>>5!LxMejl>^*wic4ZbmBvh^C zUro%gfhN40nQK8@LlI96Ovwx}s@fmGJy@2hY`IBnxrw&CG*I16yNL&^)(kYEug4Ff zoL;w>aKCn`g?;M`(1}65FE6!Faf^xUUoW+=ne?>I7OamAoC@$9c3Xme6TX7Z7B2A9 z5O>GU7Eb$Ei5TeX)7iqcnNq#4SE;kH30M8f1j)S*kB zr_04#CJz$3rol_rn7|?5)6Yi!)?OwwZrgxf?>F3s1=5j113a5e&BG2%B$jqkx0n) zhcTXSq%tc;8sgDNgD&+Cn8+UrB1dH4CJIOmNO@KKYBhAm1bmU9A~IeH7%u|KDWC=z zZ?qkmftrAc92pT!m2PY_;UMM+FpEGWBZkSZS~#YzK&Bn7$%%s2v?d1jpN(iEsuft7XFv`+xZzSO$eidLg0Y>LtiB4vP3GWBV=zJ(lT`JgMe?zn* zdZTE&N(eubz|Il7#KQowjbh}C_%R9dbd$3_O^V3+G!~Iag{tW5>hdNsrQfb1M*~LO#rHSlgx>#7oj}yCCSR)D#E7^rH zOo6S`ZBy#DiMns8ZV<3dJpPSo4@+7)s1{Q(TNkK5RN_A*@ufj!>mOq4jf4gPe~74m z6AI`7uwrO=YoZxBB(dpDgfsCRN1WcdH5SWsGhR>PAe-V8-?^ zd#}PYeD6}OzV4FDeci>(tsbnbeO*q4-8tA)52O6Y#a6vrNFV9tS&+BHIu-awb>QD{ zN#L&v>A>F-(t&@R9}@Tw>u#Ga7T(Zl*0_9^bg}RQ(-e2HuuC{fx>)!?bG+m7g}Ycd zrHZ%TyI44@stJ#ZFdgG`Sy`(*S}Pv)tum2c2CS7=Wy`8eo?n=)^kn0J8Y4F$HjID@sq%=q94n+Wz_|3`#s)V7Ju|0F`1CTtUq{4kre znkxJuPIL~N@cz!C=d?R_nQR?T8NvqX013LNPmyyi=L|8CQ|XU&x66W6{!m{&(TI7H zq>iMRe^^N*Ji?Afkwm~%zf4F?;YT@yy37@B{g_rnoYF7)Z`H|Dr!UiUCU(nUUtvbyb)7P)7 zg^lF6zN>{D!odN_U7F*4Q^pfV#q@ZhRg8+I=7sHHeWNmc#(kJ-s1g25XM{h~;Gi7t zvaS~H+n~;6)En;cT`kmW*`1TEV|BH!pgd~xU4p$@(nE&q$Uh`<22-tQ%@y7Ovd%OU zWAsyS?u70q)$ALC_foES?Lq!-BB1{rKb-;t zA%GactqY2whRG&=OB7CtJC)}L6TtRJPCMHRSF>PT)*HAbf~i!ZKmtXHB#Va!Pg ze?kd=LWEyYV}hZ1LWBpj@F!A*KamjrqzE6A5ME)&dj#!J0QI<4g@zsYP+)x;7eel* z>>wf5^F@>`QBhRn4E2YwK{%Jym~h`Z)s3fgu78r|{%E2b&u=i031IfofIDlN8?Pql zx-h7sR}&r_jtmQPF$r_AG+i0i{#&W-Oiyf+OphTfBaW#nrQWjDj>`;SjlecGoPvo*Z4v899jfonK`n}ka{QKYImWY^+he4!uV+@LgU5Dgtf1hBz%{?-g!K}c!XAQ~nTp)_m| z4Gpy>ysbVpzz)7|s3yiUlOr(G%|hmF9Q}5)(Bd`|d3DM+ zt{!aGd?QV%ow>I=)qbBQjL|LCQJ<;Wdbg=E>l6)7G2tGw$&GV0{E+FJv&oIv?Izqm zPjREuXR5K^c0%@PQ{A|W_&W;SxI*JO`F>-p8>Jdvz>|{V!i`$u9Oc5K+qDa`5;oeJ zW4g9!tcLH`Hs7b=ahm>|h9@0Smt^>8N3jrFvJ6#YO=)EsOtvyl{EJz}4FaISWH0!# zU?kfbbt24L_|t*qwkS zdwSf#>|wqg^SR276`HY)hM(20p0DA5H+HqmA1|xy>kkLZun6yYl&kM5SKk#^@4tgX z2fiKnuE~9y2ExkGcg4|_MDXTJBmlfCC)Pi_BV}e+C)FR`qR;i6mNg>h=Q~VzAN-uR z97BN~vd{Eib6ziww-1tYC_a?H7T?KXuPRgj_^)#ELsh13xl@;^fBx4gQ$BO58@JtQ zBKsdx-5A3Uma=K&WGOpSb3SDF<`%k9PvKJB3N&GX;oCCSjYT9tjaE7)wca(=jpdZZ zVM|w?L^6-VmYzw**3;a0kJ>v-b7KpINiOx%93PpG@wlzwFHJ=cYj~rny8n?H{@Rr8 ze;-w|`+q>=Rrl|yV~9k-Q<|_z3tXq+Z&Z0XnViD-gC=~d%f}xY-rTr+RQkh#klovk zS4ILcEXE!^oY|_1$W|#L=hv||NZH>iMWVRQgiacOu~lYTMovM`ekpR%7mqn z3&|S?=V1w#ERDaxdM+OQyR04X*xzMskEM#$m9$kRA>E=CcpOjaNH0m3c4tXC+nr4# zoyxc*ot1GfVHNo$>0E=j@h%hI7~@fnRq5z|VEFa7e?yr2D?;X5mRm$nGlZOOm1*N12eOuw4ct(R$n)|xh&Z3JXa&{u1HG2OSVtA(RRn~=%Dbze4` z98@!jOb!Mm2#wbbq`1mHIiA4SOI7djrz)_2N`bw7G@Az1QU00Ej&hPVrT!^pYWry2eaN=+ea9zn`v>FxLAdXD78ZC1e<|0-qH#NbWf?;~Sx6gW!n>d7 zAfC!-IWhy?$C!L7{HYALCksQyFb3ZCU&(ULTf%S$lME$>C#UeCyaqW(G0q@5jx8A{ zr)S|CGJZqMpy6>lhMzLHhS_GU32&Q?B5;QYTr`$S;4M|ik(o5(VhhWl#xi3~L+sMPTNnG*UTn^Hng zc_TD^R;F@wriQmTHWTqO%U$5ah4d1TeahY(xJ=KkSHT;M+ zY_5h^Y59*OUjQF~--c&ZLvOv86;`h6N6o2hES!y5iQBm2a!-B{1b1BZX_e5>;O z&rbLug<6rx-aAN?8kE1M&0d-*UDx{WR9JM^6(6rV z%sctPuIrxhy2JcZbLy^ZIz>v?^_?c@u4@hndMv&*p-jf&k5CpfW4}%!Z6Dh*^LrKC zCo+BaZ*=2T%8*8_wI-~{^d0-P8=ngyv(Q43CcKjAJE_pZP7+i;$jK`eGhS*~hBbPX zVNIrySc6`Z$?*y%m?#K_19mNpHIg4F7#0j9fyiKfOHO#N`d<2tb;8$Sf(dUKF_FPo zFC1Mba7ToPw$=+vPr_>JS|R#PU|-{IQ)nTotzVz%JFn2f4HHb{bt<#~-{plCCQUHm z>sM&uetz6oXyI{wR2EuzV}c3s|6f|dPif-+46ItmK(zyHC|2MKA=HLz#E#d zBePNDtAepuFkHcg;Au?Ii*GxXjXTB0jY%7KijCWvvvGq~u}j;yS;M=Pjej~e?$LN< z>wuOkHv$yTd=LBsMn|m;XoF1 z?JUAEZb=>x`ZZHbcz0|zkW+^Hvv{7Vatf;xyxChs_2cu z78QqkvZSZ`LI~Vs`&AQWWGTiy8eW~Hdb(qOR0&79Rn_gL;g7PU>K--KgpXCXD?eCi zZ{PgcUyGkq^cu8=JPj{$OEx?xDLLRM)P%)u752UwUaAw`tl>R8mzkV`>vD`| zuvu5MWo~KXmbs;ko5nT{SmtK`%aul+uNkV>W}kAiW$!rMgm=hKMxr<`my%RDT@`RF zmy$G=T$9KZjOpZ0OY)beOL)0^^Srk4X%((dxv519^EoSd$dwKx>L+x z>@$8=ne@7jeH#tGNh`RPeKCJhkwNEKMmds+=Wu(Y%hxWcwlY`72{Z@OI%7}&csmBj=;bd()fk7HL z^A>@aoxQZ^Wz>#UMnVD9 zlCzYgs$f7J6f7s?4@GT%V0fxEaauEi#E@McPuMZ}J`>)nXdfj+C~xt7QvTwF76LyT zVDN78Ehx0`^nE6Lj~7~4&yQycE$q6_ME1*t7EZX|ME0A77B0M>Me)Nz3%BdEVc*Y% z7Oo&3i9B<{)F;~1ne!L$wM*sNI3tm5l52*}wQQ0squ%J4%xr4%}^nGvYC)QY4nR)*v7E?_bxxtWdsmn(35?cQoNSMd(37 zV<-Kgp5d4sWd-6@tfpOk5tDW4Slx`PtBp%z7OtW@ejcJ^sg4kmBb9`P(=j=wusV^| z?+E3tnI^pJSRro3yMmcT%wC20LNJSHx!3y(-yO#Hf?7_L^8buqRHa#?++0P96pMUK z_(2gqm}$cId!dET6C9UljxQC*_YTK)awI}kRvC%J?7nO(nvT#*SZ1oiFgDGpKOLn0 zr0M!IHf>u5wXTC&NFG#YAlR+ zK(#o}hzAXX20=qB2$ea|r-YQdr2iumn;vk^wyM-{_bRgRFKzp?X})-og`a8rokbSX zXPfYiEwXSlKc*B}IAOM~VB>U>$J3z6S``V@ z@TkEqgWD3n&o+@8^@jtID(p0d@P>}}L$U<}wb&y`4>0LZ#6{wjb`2Io$O{ z7XH*e?=pPP6c;@zAygUE&^kiOZbkl* zjzlAoI8LB7n`!WcG_R~O7z*GdA~X53eA2G=(}Dh{REK{i@YiX4E5Wy|*UfEvG9-Kp z{_hUwHPotH@iy(Bcj<5oo$6Jtcxl^p1O`C?gX&FqD`FQLI$SYJ%Z-bv&Aa_E1`Nb&sfR!|j@X*Nt*J{$KBA z!az3@$@|&C$T{UPv>e_$9eBN`a829#w{B?1kW~b@L_h&=wxJHsr8zSNhrqW5dPf4b z^I+CU{728;-Z6~)D&QSsLHjH`#$ZUd|Bf8aM9wh%iuS+j^}}~-j5{V~0Ny#wpCI0s zR^X?4K7EGw4K5@cVqSP3Q}oY-?w%|3&xD>m*W@;tvN$)duRmTXGaR3~Zil-#(Mba^ zGR@$vvyo{px6eYsT&H&!DM$MgJ~xt}Z=T)Fn`abpw-Rx;i0G6Mako0mnNnbsH3?fV z3XeA~IJBX7u71ahKz~Y0PPa@YAveznwa~3rmO|}sS5-%%{^;-o)kvY14zCIY!-E~#%0{Nx zLxW`rp{-I<@imA*-(Youcx(!>G*UL$jyn=leJHi#HNk`rsVDLf3&e7 zjm(b+%LY51H8Q?uG-_AWg#1o4Q_b%`JYo6#y{QKFwWC#H_#u}^Wl3=~+A}=V9|{KG zcJ=*O)nA^hFp>;R#RbSn7;e`sP4g7j24iu!UDZvKOCoWHqpoR=qEIAe(}=05bYvN+ zjfj4$<{Un9SM85ihJtY$Eq7d%j<4TTjmisuuTcqxlIbs$=A|jo1%+}|t6s16Z!(O0 zY6Uh)<586;=L+V$=oe`Yo?#1MH7&E$d2vcw;B%Vc>J^N|8*^Ta18GggB5udxp-2T< zcIuywy0=sq-uPQ#aFTW{0+P4!HMXEvq=IK}44=_DSXCWD-u)f2c+Yuwxa=H%JW>@b zW7EWI7IyT8)H~z0T$XQjTdShVKrosci${au3g0EIF0mIi_#|pO=x=wdE%4q} zwKmlvZ@l~l+l;orx~&FW01EP+CJuo4m&yuKUh6e_8TE$ygEN88wB+M`Th0VtnQOxR z+nK;dExcRyscnHbSvJ_XJ0;F>Mui7y6-IpqfDX<>z8$!)^!{8E-tXDvFt5)Y+93?wb>KJ{vw6cpFWfxId+B_59-Hx)0sqDmg zoNH0pK`r%SQC2X|geY@uQ#MeU=Lp}bdBV`iD7ULpzcbVJp)58uhesl$N}k*G^;*>+ zeM>%k>_*Cf!bubx9xL;QLg(;}D71`TlaBdst4iSBX5fE60VC;dY19U!iY(|+7LJDi z{WE{Zt6vvBo};!4=J6_6RR9X?OpqG|3hXh@>Cc6k<*+@BVI}!iKu|&x{_2g_g zqTp*7=VU=|WXWZAyq}t@7~|5^d(0;p+9%uBZ&A=Q2c9iy=5p9))2IhC~w!rb*)RV@o z;eM$taFPb|QcHFt>#wwL}AEC7<{QK3GwnsjjiSc zPtug3K?S@u&qS`AD~jPCU!9)?K`u&g$c+nM=BX@&H

LdW z*tBLi`P1Pkd?TCVi`xFcVKOH)$6GZx5R9e>y{kFK^0LaRNFarC;$Lt^hNT)hsyWWX zj2zb-UrY@hEkO;@$cW>{r~o z&o#x}d-fB~zmivaUGH8!ORgMP(x*qCUcLGZPzjnd^y>aSdlwJv)4OMX;rsK@p+l}_ ztSFDg{qYp%$22Fi-@rasiZrtsNdp{)<|JI}Fc{59D2eolghG*F#nOne+T}LGd0D@b z?gLBuloa;rQ*E5Te@HJ;#_;2fu+HAd2LUvDcGWn}egMx8z{i-?p z1{U?|KCt-Op8bov%Y$8i$#-SXUcHJ{v1(4I$m@P}@4lL^xqKJC{{6c3br_q=R$(md z-zUMC-b|hdV^N=y{yj^s*2*)QVJ(Y9_0xjQxf_c|s;h0a&~d1_)~zfOjt9dv4qLNj zyuyyFmBU0c_)2r(D~nVQ=d8J(KU~3=rd6x?W^?S->@iCD3#Gw|us@WLzqq-uOM*Vt zb~wShyg63YB6mnIKGPgy-+_I5szT9RG=-yw;&CO1e2uz_M3AlyCr24>*X5tw%rQ3V z-r_=0{s~Qu>#8H65L&)@eL7aYr^a)a{;lzxq`QYR8qQP-__Cp#P)<%!21O#Fn_v_e zd~WTP3$xHVfOhLUa&nVsdMTyAjz&xDIA^B8a0REz9GIMFw6THh7ahr-n^zv_T@$x! zeFXu#ENYj-C^ygR)_PHMZ!bv zsQN*TxX~ucAKu_lu1zq;G6onUGLA@OM`eP|mw${r<1blr^XPkbJ67f^DC49YMmwWT zbpYscX;VIz4i~-jITnvbhQnwfuanuAW}!`aK)tDTxD%;LxC#@8anWBX>CxRMgM?m@ zvcWLEFbt90_L8OyN}69CwX6M6yPxe3z_>gq|1bRyR)iy*uom|$>EEws9~fV!8*R$N zz^R>69BNao-i~V>#y4_iy*1yw`bI9L{FSpb&Oaz*gAX+}A5^lbEEwhw#+3ut{!IsR zC0cf*ToxT+9GeVQo3P4`HZ)e2d5*M|p_ZqjRIIoC{}*fZI)KruSzC4Upq~*i-qzx! z1nZbOFIcakE^(3|UFhE-Cjm>IpT(4Dxf?-uh9TS=^O^GV)u%B-!D=Eu4C^ujE- z)DrJSOQu`EnQa5OsAtD4WZlAvLvl{@chwZM$6c6UC`}g|ly|Yni~PUJD`=0q@h>It z5)pWb2)wg7f$G7$Q8-VXhHAWul%po8Em%$`a~f9__nUt6@ev*aJILfHcTgF(c z=$y#qlq15Po7Y`Uc(}A}+W|WuRn^%a7(*$%q~A%l2Ra>haTew$=4zdq5qVfa?H1-c z_%Rz}F*?nt{|A7}jC^)Rm$5fml8|?q^cUW1IXRAm0_e$7yqqqwn<^pnB^^9GQ&W1o z=&%sm<$Lgg)>NZ3)lkz9GVf#)j2f!inb0gCa;n&676#)ON?m_av!FNwR=8v5^AZo) zm0+EsoGyJ<-V&YN$-TkbeAN*<)Tl6>^!9s#eOul8md)ucwQ)P%*EaX4V8 zur1&QQVp=cpgZCDCi0a#3*_X;u=&p9U%@F3JuE8gXn|NS9x3Fw1X5g5$QcQw`1FWD zr{>bBp^j5eiO8j7q*Kod_-xZou_f07k@1+Ky;;XsO#;9=gYDf13FFtPhSvSUBnx+M zGr$7#v6215BnzL;H<7)4l7&sp>tRH$R*{>4VimX_w5lIO)lW=(Si_(iV(D#k;)zb; zJv#9oNxY}Y#B5wtFBuF6V##=jt2F_jEgk;HLr!kBm6kWB zEMALTE@Jo|N-WOE#`?JrnQ-T}1tx7XK%qH#owzof*PZ{$8raNj23VYm0Qde$7UpTr zJYUPn7A}2=v?DxD8m)&Xn2ZO~l-*sG-Cb$-Wwg7BzqW*~Y<2n)cE)&JS(C0y_|jGR z!m(g?^48M!(psJtkXNN_zjv7T(tM;l3f0Exaoh|CPz?Y%v8~ zykkiEDtr6V-k)fnvbQg7_?@t0Z(rJbAYpIcq`m#gd;9_uV(*9Ao~y;)`~}+H%Sfla zn>Ah8d-DQDSRVb{rV0I;FrW3HCkx0_JZKp8{|BI>TE^|jB2lw|uU^`5pd*{sdk8X< zWSEAIw0y||r_^**mUH}6$j{|8;fOq@#(*9UPs9CMX>X;pH%L z4a%$=XjY4d9Y=4VEy}DQKU+U+!u{Q33zpVnLvrLF8oa5Qz{}Wm2AXi*|YNX#+Fa{m~Q)|IvvaG2l*{YT+Xd zHyFMSQ!TV#NZ-0nweYznJZkv-Q!R8_Xu@~vR0~(~BRbW>HDpcM{$f?o4K?#Jb?56| z@OLVkE@cDcnXvotr$=S{mNZq&wxqF~4Oyr%ev7r!#+ViWFPrdp`hNxn(<9hz$4jm0K>S<@_h z!jEI8SvY-(3Ezp+EEF$El!`laB0klWJ|SCb`;BQ9 z@F;VZy~=Bv<6Y+9`_nA^M~fPjp1pgT1^1(Tdw05p4ciTHGv1jlA;Zsyr`D=?(R~Gw4YQh1qKdt-v(4ox3 zxjB9=V%x8HjHc-QWYSL&DT|Xz8Iuo4kW&_RljMH3I#!m|q-eWbhPo8Kr_r)%iU1iQ zlcTOKb>kw1BO#-%uI|90Lrr=yAXQ9VUFwcTQqq7_Np*E~e;zt?c!0@V!u7a_I;_zY zy*RnDM!OHUha!rj(f+|IJ5r;5;pXtjYL=2nyl*6=Mje!tvM@^y=5{~5L(@cXV%7Ws zlafeykj!2v;BY-v{@POED&-wGa@5tWOxdd!Ex}l$ecH>4&p#J#*A~4G|3gRq(oEyY zlO2r~ce|`x&l0#@U2nS){A0y7n&9hMa!t2hJZSVk_)7W|m-L6*b<1Dy6-T37?_`PQ zvZmWr{nyMT5ogj3x2x{2c^%H;S~Wd~+covCQ>5me+cp0$m}7Qys2!E@sM}Sm4??J) z@M#=adS>^i3G*j}mg(OKiZ9550rx2>3+9m+T0R?2N7v0g8G8J<{qLN5h{SMhcOX3y zD=4vtF_17u>$Nz}hT3pDO8wvr(wkdI7kkV7WjyO6D;T-HRxvs4)9PP|1@aE)?b@iD@;iy zQ%e~6veYlq|2myr9jvy+Fc?45CjK(BN@zO^rjeLBJ7bJ?8I8h}qF3?`#_+={s2rkY?leqr)Ah6KC`#v|$nf7%aemJelv_VRfz(N5S{1TsX^8&$W z&v1DpvE7ktvu5Xdbg{aKBq=w2xq0WrZK+|j zmQxvz8&6+AajLqNsv}EMWndH;ZL}6u?q%6qn$YHQT+XK?Hwa<4jW)6e2vv7uz{FN+ zfjJz3N9`iX?~chY>>0m%jW#jTr>>s?GVKOcn!= z>chz#R910plsug5Vj<@ge2z{0GInFJYr$oqbBn1 zeS{0+LN$I<)Sg1U_b{qQQ?C%}gDKSck61YVHv{beOgHjA7l{dznp$5Mf17JKrSNpX zUHr}F_CV^od-rb1b@$efT6*1`E@nN-(X11O|3{s&WWLleM6F(g7vV4be%U8~`YYMaQsgvq5IaZ1>xXP%%5P)su z54MK`Hdk{tvz*9TbOHIwNd@tEj7P7e#ZIk_&PaSL1{w8ylK28yg1=y5VHQq!%;fa5 z9gGV^fZ{gC#?xi@&yQHh*F|}f z;mc^SFiQw0HdyGW2~!Q<*$oz!K4!vqUW0{|{OHtRVGBRHH(1!iG*AIl9>|Ol<1yBy zT#$ukt~1kx6PGF%)`<%ROL?ml#=2A&^a6B_jZegdp-WA8yE!g=EVxIPa>_;*dTUER zRxUg*ggy-x257=3hA+@y;r*p1e4z#l-|-{fVBze?P55qauyFq46gIZOLb*<}(eTY~ zun;6iLxY7o$stbia0T7O1U|DaSv1?_oSB_1@3B7pIMi?A*Ze1YOpX~r`wX@`=Y@@CrQUZao@C0^m;oQ!4woKZ?Y|8tn*-RhLKOjGP%ND zkvr_k#9q<{~l6SZ+d>@kN;|<3-Dz&@K`SnEi`0 znf<+%>!2vFMuM2h@hPUG+^8=CaI|wH z<7k?f{&b=W%3iBKJ6GMWR`*UFpkdelqKICU>wb;A*VcmV;I|lr?t(; z5Z{rH@SeQOgNz*p_k3`S@9bS340xKijdytv=11W!4~Frh_bw0W_;JlH55_&sFlW%S z8R%iui&IMy=BQKQ;$K+J#`x}+GcmhYZZ;ePV1Qaz8^9swayqAGX#?o|n+a(H7;ZWd z#I^KdE4^UL6vIt~jTO8l3P0J7<2MV;P4w^-z8;*r(85tW)ah=&uiruoXRM(0EF{`G z+{YKJ#iS|hsRFLhAv-m*g<C}pya_#W=XB_8tisicG?G%mt0hvD~UG_7QG54o2XD%gRLru*tf5wFS=7kpe>5REM zE&E>!Ez}V|ZlQ&nb{NP7P?zRjxX?nCW*e2}dtsr4=btg*du^eG|B(Nkg%-wW{!4w| zF0}B)Gt6K>@_wpLdw-f_{x3pf=I7>dI_yl7X3D!UjK^{=?aM@YdD_KASvZjJcDb5Y zN_bz8?-tLR@IJth%e?5>G)}X6KFjyz9wyetzlHBwqTIhMwD6cV=h?J8dyxhBB8x1n z6!M5g7M|DS=hA%B7g_j#1hFIInF8L`VtzD==OxXx&(Wt(9pr0*oKB>B&LRt&cBuJ6gW-E}k%c#& zGm-tmA`5GTgE_rLb1XF^r`JDc!o6ydge9~mlJNlCKP|Fg{;mWrGJMA@ws7q8 zY)MHtN)uKnLPsH-yx78-n((~g>%Q1R^m!9`eHU9m_O**GOcp*0I9Kz%paeWX0@yz% zBb?epKcH5;SztILD`j~)0wrL%uGi`Y=6k_G$ zwJi)aM0^pxY$DB3nrub(qaqOB}9C)*g|iL z_+_z$0m4Bu)pb~2h4sfV7wZk9-Ur|q8EqWHdhCC}sr$!pY{4Se9)@%%$Z1<1!|`-t z*bBPE{U6)mv6QdGvtls@z5*Fp%tN|Se-(feR0K|7;^heyC-fN4yJN|F;*l65DH8A? zcVG2cF_~2}?TatySv7&TDJYtJVJSYmTD{by#b7>bP^t*l_fH)W1UCPL2))pPhj4x) zA#4M7J3-C zL7Z-JsMVH9!kkmFKsKIW!HUFW6c`9bc}W;^JM#zelgRGDXmL0m9WDeTg}}1RRVE>% zksvGelBv{Fw^C$tHNB)y>2;;xs!dz+5!J6-$z{cYYU7|v`t&aDEhsNh9DCNp`do?U z9g7sEdn9ZNPK;5wes;+A$LxUM#g^(|;OcP9j*BT`NOdrfV55C^Qc0hI{kr!l>2;l8 zgssoO0sRsfQ9+&o>h6h%?lZ7k-@e5q>YR98mJ(Ccuee+PViBYK?lZ9X+M-^9sMBIf zuPZ51i1`jr|9)3HIFo`(i;J%uSX!(uq=-F&DY{ze7eRt?^ovCUo4m;qi4}01;taYt zRuPFrCZ>PHid16z2CHqsh*V;DHC!;Rgt!Pdbp$6C^8$Y3d%fq7mFOm}{E`%nMB=^u zwWaDtk$R6oqR3U)#<%GsF+m=2m0shUx<{``OV3>OAO{e13^GB?~T`&AtLi%g-*@;UEp!0lS8CEU`Pw}d0Hf^N}@7;{0J z+3%+e7|2d}0CYf$zabz^7FNUl&^eKq9gT9^_J3ahc36|!ZyIx0?%u9qpmO(cMdWRq z*Z-3}k@NqPGqoK2Pv&H)NM*$ZuH>F!GEC8$a6DLLpHoqWzY(d5G9-8QlSvC2zX{VM zOK*QD6e+vh_6O`}qR-1}nl%_EM~_G}H4)eerJe~$(cjl!k+P}HXEL1qgcPRU!SLm_ zzq(r}SP>Rs&1m=6mR9*gA(|j;jgAkb$V+f_4-OT^rsXBrdi!gIjW32YVV?ZzKb1P- zQg5-Ps4c0fB3Dt!AB)+lH(1ql$R#S3!E)89AJufM{k44t-5Tp_M@y^yDlBzb$-oaEG1F<4otT4=o1OLAO00960hRWN`0RR9100000 zPPzmD000000RR9100000cmd?S37D0|u{Yk;UGF*X42vYW*O=TS8h7Iwm&7G1F+qtj zdX||u45;J1q9mHcT#d%W$S^Q4ti$23&oB(gu*)*+B8tc;JID?qqGkX=HbGW_|MRPU z-#PC&GlTfW?>_hW{ys9NySlo%y1Kf%y1TmHdtYVcC|BV>B36H)2>)duQ8^E!Aw-cz zfST2LNW^beJn6=gZf??55^orO-ft}Yyz`uR(oMR2-fu!SqawrVc8c)rt*D$^RU0bj zYBR?GA1NvNe0x0mAt@}8mvlM)CI!;oq=nQp(IM$3B1xORklx9FyhGJG9j98q=0RF} zxn9$2bkjsZ(yfE6(ZwbmQfzvdK+!!+FXIYdxk;CMnSLgWDk9R)RGRb>E6sf4iU^QC z-_)eCLK4fUkM?WcuuPcMtrX!~XB#S45(}_$>-|j=ZqiM(NVGBHtL4SJb$`4fE@ zVv{6c=vFlMSI81(b$wPO>9NAs-;q_GbisGh#RcQ3xg}^fGd#MO{`=@M6a_j{Y{=YZZw1x3$^9 zmT+19HY-=u1HJIKwb|L|tceRyl#*m>1F%k9=SA~v^ky&G7EzO!uMq$31fmE}pxSJm zC;;GnnxrX;&>%bffY=e`r3b(_Y}Ea->32ojxH;Ws$Qh1SNHOYN%(} zjsU1u&+y1_cG8u$Z<%y?WOz;>bx!#8u-DgOke?f%n4(>e`6*dbvceHaBI!!RwYn~` znOOlD&&-+=NSu>3zb>DmELK+qP_O#2D@`hlWU=~jO8knlxf@kMgjJN#I?(};nKY9e z$-*<~O$u2a_)S_$jxYzL`q}D3&Vo=ARyRYu@*9^)^t%+{@5V3;8dj|-!mW>Hd9h!6 zGR1iQ>-)1(gZusQKqUTGbl?^cBmXr)!AT%N{V9OipN0gZL+Ew%059w7v^XvAVtPL< z{yuFBX5L1%!DuaYgU)%hy6Lex$j4YcMF;qpqE`oySL=gm$OrX_Ao2-43}NM&|5a%TI?=DJ zTAF`j=wKjrFw|RmWuDF1J3JtqZW9a$&&o=N%*r~N6(}86N)0U*%fX`pwZO<{X}(Xj znEWIHL0y^-$@ilVYTJyd?ElnWm+9Q8Jn3#5^@T$TvV% zYn^DxtMxjMm#>G)YP~hc%T|3bzzcLZ$je8%o1r$`%?vXp@mfb7W-5&p%-2L_m>DN# zswbIo=JkN(iB1ya<`uIV+^jY`?PR&r?Dn|%n#k-lhl1Q3GTk|)Gw6juZjPE^Ol^1= zPh*pq-jS#AOt!N5n#fG!Hy9(SC!05TZGane^B^~G@lJ5FlTWcpOzFs{xRYbK`I^X_ z;vP)+|PzAZ! zYt?(5$>NgC<``>`C6zjtCkeucWDzA$eUitr^r6jO4H*a<%nr6QjGtu(?@OCz_wktED~ZlI@3VzV2nB7!V5P66GQA%v=hK=?ZX9A-1H3k zmTlT+wowz=P${sBhHdnU->}i7%deOX;a&HslPYZDRia2M0wno1`*}*Hd^IS9;%IBj_*AS9z4iVP`nuM|^ z(G2`-qX|rHc>;se`z)tz!NMe(P1KHO(;F78Hbm_JpG^x$=H&&n*ajBUHXGPR-9z*c zcMlB-2?nZ-0J1HhWn>`0Wr8h|+{p9@Z{+SFN$4Je9IZ%;O>aZF+}jM6HEa|9-1KGu zcras8+l0%_XhRVmZF;jwjFlo@H#?>4HsJ+knV}djlf+Sy_)k;J=*~@eKY#8}TmD>x zp2s85HW9G6!PzFmX%jAV5)MT;;h-WDJ1T0%>Ruq>H`QenHRt8D++Qs&r;T;1#f?;3 zw^}?aq;tG&Rr!r8Qmihb2w&7)#&pFOU8)lWd}$lX&j;+6V+ZR@g|_c7#0tS+&?-?;=Sn$?vQ;VbW; za=t?(tXW-0alY;jopAY%2Pn>eFC+Aw`U#Kzj^c_6l}EEsVRDQs>ZDkG2c_6RTv3sg zrVadx;{2;P$j2v=m-@?J0l%~2W>+E5Me%=J;{`6e1~|ov)s28iz}vp&8~fTB8w)$` zORtlcpn!JcpMl?1vHBiG_%}Ao+jt8|c7q{z^Ig*Up4+XG%)TbW?oCZ#=I4`d5#u zzjpTfg5)lv5#;2G5j28!Qz$nH^IMXR%hpST6MCekIFHnCX=vSBdbuc1H^s-PNPA>w z>)EN`TiUC+(8*-DFYP9ZaxXmrJ3Blc{DoZw$3WvHCtm`1|)!Io}sYyOk*Z z{!L!sio0Q8mMFgF4h${9KpoD_cL2G&^7e8*W5~a|;^FiO%#Kg!gu(cH!W2md>8^M@ zts>CvuDDho(G=k$`h;DW&F9Y;+nLHwbdxZJVGSSAU+AVuH!-`T_lLjGLrpUrh@?P6 z&2ZBko6!N-aC4#_>@}Xx>BLZS8bpDG0IQAHrsf=gZSNMZ7?_VQXZtYV)bv3es9H3 zJmpvm_Ni_Z$`R$`Zqx%qH}+vmX@L(`K>xvoAlX~-)i-#7TkiD&^^@u#bvXCt0J)D} zejmmE`GgY{?SJBLp{U)T_}hz)4ZJvoeApDKAs0rM?Y4cCN4$^Xn_77^ZE<}RU-Ja2 z+aOM>2Y_5^2Q9vFwb;Ul-r@`Smeod<9vHe5k}kf5qI^p$ig2r0gt1^2y^a3!Hmx=? zCX_1voVsg@ad$n+h%sKOcqg5L9-ktBItZlz5s5cIC^J`I#p+vVs=kW9@hyt-w=U3$ z4t&A&6y@uGj^NK9MveL^zWz4!H3Y8r#)RvC9^(bK|`f<$EWSET9sKZxrZV_aZF-zBi6i5{mD=*DK}ThrCkwp~t!Z$ z0I53C1;R-qq@R2i6-`=B6$0OtxiIO^U;P!UYbnCl%3&B{H)0AJp!kjly+C(bB2(8u z#p)`G@Kr;kg$F7gLkrLz3+O%Uaw&i=i&BiE1u(n|o67P7LlSTm4Cw3(XYMOyY^1FUdIFfv>AXDfRI-VXDI{h7A6Z<<89IJ~k%G4!3{E{jY_RgS?aLFYWi7&a$$GGhd zkKF6}Ep1bQ*{W5Ycv`i>yduY(-0F$Ifyxt4xgs(Q&`t0kzhYYes4(` z<}Xc|pk z@@RThtS^9HrH_4#Qr#c&&BYi&7GDmLcr?B1TS)vatp`5lxvsjOsHn)WpYE^av{hpL z^%|~=f%E=)v`$$7qxA$GJd&HBSL-I2|8YKxGrHA!vu=t8Kn(wQvxYpd731N6YLb&& zrv-?l^dQM=IHd_d-mEukPkT4(z1r8_y}BRt8+uX5#Q{ZrRllh{fxW5MX-E{cUI#Lw zRUrC*r(R!;Rn6*B3`zJ+DVUtlUFsVUm+mAvxlLS-*42FJrFC+<^wN~nF8x_bX8f}| zkziH+&R^Ey52mt|_H!BS#7>i7p~y=w^}52P*Z5uGnjiaJLG!PE<>}O~@_h}?f7*(Z zdsC^WTIf@6m@#6DDW1sPWKdN61DW${9hngoCt=mcI7#bd`%Cd58UiD9hvax78*Bm_;s_bnX z&eij@3@qqO%k*b*b`cZ%X#>BH{dpVN$&(#_lawbrHL_Q2lWLsR!F@zt?HqA}E*(dl z6SC3mm3YGG8GYN zo0A{en_4?#`@WoJiA7>K@^u$Wab1@y0&km-V?j6#X)a4^EPO_~NFaJB+R zCQ;duN6{!h&_`30`{)ghBy4c@IIt1-I5R^O<(Z*QS%`MZs>z}V*JPc->B_0B0ofGc z*Rxk-BXLFcwrq;>=h;1*Kw+4-n@(&)r?|p_Eyp}3yfJKHW8HbKg`LV8ES4Vgd?@@w zJV@A>(vd&Uu5CgOf#npfDqUqPY^pw~sSTeL?BN}Owm{D`*fnKdq30QBBJ*aw&`>+1 zZr2kGJ;D>rc4HF5B*T8a(O52y>!HSS_L13cC>khod1zqR7P%>O1bePWLY=ZqqCzO_ z3XKWdA`gXndUcx;o)e~Mpt7AqZ(uj{4aqmQBd^G=&9)SMfjHj4DZg~PkxEZ`x}v8;*NcB;b6-?BBuxSR${s}OHPyXW(maB-GNly>CetTkB}$tYOaLNTMz ziwo&kUHBMvGoBQNpe8VcVCzdwvc%?*=$BLlWY=eXoMlaM-kr7A4<5@Z1mi#`%q~U{ z)hW)Nn2oB5w-NCQT8kkDE$}go_wD@gray;Z(j{FU6JC?$hu37imt~D9$l@fGz;bMx zbh$()F|8!}c1*gQ)VnpHHnv;$=PbB;NEG?{Lr7v8ZwnQMZ784{bz(sShaiZg6^b7| zgG4-mrngyTs4_$`t_;l%S!)2zKrs=!Neu2N5=Q{c%@57T;4(k7Q5?)PZ^V=o;T~rA zH^czZ{ASbDO)0{wo0fh=PYhv^Bir_ z-7B`D`P*JOjWW<^LMrdsh8iW6V$u&8R=0s|!|DzUAIz#d7QP97RSpeoJT7rriW95r zkpaJfN$mR3>2nnfMHPY@+7BtlH{M1u{&kF^oZF6K{K(@JmBPk5Qm;lMlYhGraAY!~GQFZd5@!(Sj?ex7LXk zenKwyl7#cTg!3ffru)%4lF-3R=pYHzG)HWqPKv8xq}Ww>HN8=1ySzbfdIk$Gp=B6j zWW?#D8YlV{`GaVF=U4Q1f3)@IN~%I(kjXYG<&t7~o zOX)qr+9!d*=RMj+A?wG?+vuS7!w2yXL1cdH3I4&{N&&0v>Bt zxq;{^_dPchmnD$od_&h@K0?NoSl(NK^fRkmZ~hXj{}qxRf_=<(B4QjZTgIxy5l8C7 zRb&<7AsdB4G>a$Q#8{l3;E(784m=Q%KfH=o)q|92Iblg5mFY5_o~=wz)fu#>>Nf%) zkv*ECIVNLEOJHM7XU>Fm=3ez+y?B+IiMPt#?q-y_y`JjUEwQ1wbrC489}&r&UBg#F z<_~!?A8TcPb~zi;@c)6F89k_2VND*9qgyqCD9$5jHOah*q#LsAP62jrZ+OtE5yB6S zCOb4BGMXmYHDC@lgo+hUqDj8pJ&Bfxaf+dH37iIG8&HaSFdqjwI4OSIA8;Rk(jQv+ z$?=XKR)X|ofR!glQd3N){%F7>X*b!c61dfbjo96ECMYBMLo`X8WMlSxdy<}^ zGcac8S2IDc>hVrGmRC5ng>dX$h^`6@P=rU)c2Gx6W}A{1=o@)Q2&>{GI*Lv0Z_;P=ZiiECrOWjw?3UWI z5+0=|Vs+^47I#w|ZHskHH^q4mV?PeeeNEK`SFqduH5TDkA#Ojp*iu7G&I@y8uvM%s z_ea6YudrsPtVpiF!c6hEE}$H~;0m||DL(fCiXtM*ICW!coYbst6f;z@y4ojq^|h89 z|Iyzaf5toL;lKXPOBzoTeFJw2&G&V_D?e#VIXn%=<2iNFC;m*i{O4)n5Tp2p zpHP&4QYwBNitm(zdL=l>U{_&^)%k%{*!e%QEr=2MM>mC~H8G2z0=67=GxnSmx9aQd z%%A@wEN?787X0?mmRMbl1j0HjhK1Gj58L$XA4Xh*QUu$(=?q+!V&JmtQw(Un{Vz7# z`ImvL#3Qm#DtDL68OU6hB4cpVHwW2nFBbldN6!c#B7(UnpyU@_;>w8-c33X?Nm#;3 zmv8??c0|H(k>Fpn%1T9A{WcK!?X#k#g7vpf!QS49x(3c#x>8R&XUhVLSp6oz!*2?3 zdCy+7;)3E(AXXeI4FxbuL&-1@rBKMcU?^szp)|^YH`6F*G&~B|JH;XKoFFb(D<}BG zssR05+Pi@w>e4{-=dHuJ=z2+)TNgl6#E}Il1qGfxUNETc6mk%~4W)%bb>h+3q(g9$ zGu82MmpHXJJ_fWfR2;JID4$WJ@ z@LGm**jnBoo9Y&^fl6%PKqWTtZVs}sn`Z^NoaO9G<8t4ZaQXH02hHm0KySJF`c&V( zy=AaNw@mBME6`DswiBdryk!ZMO#KK$V`&|I%2w1KlcCEHbEcNgKC_NxH zUd20u8V;_~@WEaHA15i>>3~%TEP)Qt5p3|FjSxLT-Lb*rN4x8pbvN6QJX6ot*_bzy zuEgi-BU&yu_!*AqLX$dnL>7oK9Rh)-Wg5v-I6xIw&eh|AwtReFP;vIrp`fy_)N6%B zOfL}cT75u9R&TZmgH-BP`Q!C%*F%(@Yxj$AICF@Wz<$ITVbbL#daX|724jy`V(zM> zb#f%Vjy8yjhIu;8lSvDqxgPBC$Gz>z$Nz!dB5?PQZvNinbeit_$4sZ?1a}Y2Hp^+H zKQp~gANa_yxh*a5@V`_zE~^&;t@gr8qKHrxX!1*iR*o20XD(x1ph9wdyC25@;3?L`|6*W)$pi}4z$5nPwrLp(9DPa5PhKayAtoDmpn5=88E8t(?sZg|W4_zqqL-3xVikM|kC`I&+y(^* z$3rgS5+Aq?m;x-};q`&Tc_E*ZsKlma3UeXP3fBY93a<_a3zE|3yESe-%o=yOTMu)& zySg6GuOmiSzl!5cQ#n@k>j%kNpGu6KQYfVC(ch2S2@n#`zg>vZ>~e;O(s71|szMorszT#4kj95*h10V^>(U^oQmNAq2BO`qu5zZPyq=~y#i4BB z&HA?&hsr&Iq7qm^ruV$`{^E7TY*ycau6owgcP>m#W|%K7d?b+g$ccc54N_0g$>3ad zk~(8@@kn*nJu_Yh>Z#|XLvXe7vGuYh%;pO(^e0J>zVH#NU6@rKc`TUn*m`?I5sd`Q z^>l)Q>0m0A7d*+HHVAdrT~qTp@^;m8>L&a=NGzMbccGgrJv-@gPfA!*A}aR~-X(t# zhD&3rGFW#UkEKlrRWv3YFos5^%?Lcwj4{wi%o*Trj476fC1h&nV(uaOiYQWh@I+ZR zNvf1Tkpi>HL>iesn^^LJsoy7mK@wF1`BZk$E0&9-yjZW4d1a+Oj9;Woq+%?&iUpF# zhjmx6`?Uht)m6X`+rTc zXOLfmf^90t5E11C;$9#yVUD09&C_=OeU!RQc>qS>zGs9d(UImRvFIg$Mw(Js_ockr zHHmF6@#onmn@Qkgv%=<>>6-F1n(fbYvt_2kNQkqGb#$b8w!~zl2e|`!Sa7BrW)7Qk z#2rNj_rylbbcf9;bB@f77FYsTmQ$t|*8}$AerZ5Vs@#vq*9VU0YJd8}EMhUMd1HOx zMt;Sg6H}P4gtvu*rAtBE!kFpgKre+kIIAqHUX9AKD(V9(vTEuBYqCnRF@SoOKuPw8 z+4ZP>nElV}ATuJLe`XgpsRt}<(z!mcbCXw_h-)O6hOzc)lSxhMWu4S?R#R`n2cE6* ztft+X1u;bo-I|SPRu6MTvscqFg~wN$t*H-O)9k%8pwN7;S#5oqwas?b2kvTiGA+9l zbh24-^Lk<{Zr-Clut)QL^?=aRVFsgP-;NmitX7URhfOnqU>@-l6$$_(nLsTN3eZ%5 zIIf32mu1ytKx(o|vh9q6d7>nHZPWA|Yn#?K_4pTKO~}+XMNKnGLodwWs<3&F=IP{6 z$utOR*p2(K%yU?j_T!PB=_BGF$<;EIB)xX8=51VOXb8qOJ{7Q71av9{1Ix3I1Tvu6 zh-Mktn$>1N(5NZX2id^fFn5G2%wbL#;l2vj3C~J+)t&F6huA2PduP9%Z3n;4o175m z()95#eTTe+rvWr_@6*Pf{tL(MVVdHf5w96EJ1Q zq@A-gV|8t6>f~!DYVJ!kHs~!GkS)3@u!RF7u*xho z+3}<-ebIrJniaB_?Y)4p!mJ5K*O*eV!wN8#h?j(tH#?_mdAP5OLOjkAzgZ6XMWm07 z#=9}2@$^DD&!E>t0&p+54fDGV#4P%4lxxqh#f1}RcoB+lq}mpMcy2&Sxz)Nk4&B9R zDX~aF`WC0on+Q0W4oCJZvRVgiRv(se*okAhg;>}xq*Cy}rTVBS5E9-Ax>vnJOGqT? zIZ!R3mDqjrqbsQtyKjE9R8K*Ni$RdQ(6J%ys>1avU#edgLlvyNth+Gooygu_7oP3N4z*af-i`h<2K5dnN*%GhZq!B==PTc~C9e}jyl zSxMAZWQ)yZh!g(y5ZUvoW_#Xp+Rip2w4n+5m3PHV)B%?|{P;>*nGr8dzqeMZOLZ_q zsUD`&C=SyvrvRAwUe<4$It&2bHeI~^S(FM0TFowUeF$1EEz?40vE!GEEQ_6uY4MHD z{!|7y$?vbHy)xaQiJ`o85$LWTk+%J+y72r;Jk7F__B$GHy8NGLQv+)MRk`?@Xk>J? z9!iEvMLiX(Nx{YSB-%;MaKoi;pyQo1J(K~O9_kuS*#MTzPCQ`)T$5O&1OtOB3fM`h z@ql;ITQV}i9}~S42>UXI2Mf#K8P69yl`52}N z&@sSX$0DjXq7_kbePA)I;7m+QZiPKJfeqLKH3Z`;IHNXrH{m+iUI>hpr9DD71Xi9m z+-HYqIKACBDBuA%h=#+k65ld~Z1+6?QTJ9}0B<7PMG?NMnyFmbXkZ$AeiddsOZ9b&rT&Oll^SVsK@wwSB^tD`{&`5w_BY0pBom?GNdn z8IXTUF##f8F_e$Wi5E~ql}%4Rf!7oAC1)U5PeSRLI+-rwx7A101@W{)A05!ZK01*; zfpbC+%zzA(*O>!u#JpF39whR)J{~ZCkL%NzsAXGK-gQuA8f}ZRD>UVBh2E=m;%45f zPiuI3AmNWGJDvYnrE&V?IYA-yleDccA^OCh;mlJi&N5Mf{P9`X#ihy%^o4^EMfmax zu}%@aI-o&US7csh=&Sga2Pwi2!ZW{v!b=V6RanHYn#H1ocr>C&)EuF!18RQtU}Fsv z==oqX6#fgIY9|yg*M)}STxf=h`J7PvikWKsSEG5VnPFf_ipK`eFz=Ww`|?K)dR{{D zbUdvU=Qjus-2}-Dq7`@~?0wpamBBuo=d=;NPf=gk4Cokr^-vvpNc=x|sNN-ETu0rd z3vu(hjaNn)jl%uWY5H|dk8q7%tmzRUwci{RSBiuR2EQ<;eW)Mor&yJXor=(CkyAg# z-?|NNmOw7I{oD~BQ2zOYp?n*9@EIh6oo6~C1hkHw$%%?#)0uk6gC|JufeZAKNeR_& zQH;MeT-F)=6kl)+`32N3)@l*1I+-P}?ub<+MSn0?E8uqEIa*ZJc4{KaRalWYB zzHKD&K$~6^v(rA#nsKKzmKje5x7d<2+#6ne{NXeu12Tn{2eT}va$Pq%3VWAdm0sjk znMG#C|DR;xKZ|_@D5eq||3;ysN!3UK-aV0r91luAx6wG?iEGlXlK8EGX1(>!w4G4k zr2!jPJnX*tJ&N)7@})p#^&^V#k80$t7Utgm)8iwIPITZArb;%{na5Kx#;0O~ zgaRd~y?yR_)A%~>#+p~q*@dMu&}*b1ht+Q>!oM9O{5Y(lScE#fk_v?pgjV{+uB7+t zihZ93`aYD?3upscoHx*Rxl_MgAJBjw(D2sn!2QhuJpGAMhU%5FV-x22 zx<+Fu?y~wrmUTFUKK$YN;A)Yde=#lkVxP2?H22ZT;Cu?VU2jtrJDRfX$D`nV5lv#p z4h5{$AJoVBK<}y#+@-Ojj;*->wNs`i2dMSrJ~&K@$fV7ExUbzE6oqm54}G-31O~Lqs34yc>5}eC{Qfo3r?vmtdZ6!UN1iDC|VDj78HG zD|yCV-UCv6zC1nGMv{otm}?C~eD+VX5uesZX+3jOF%s!F+g!AI^%_*WuO zg)3>|I>%KsyY7y~Y`oVmM|k`JZO3?n(~C+y5;Lgmq!DlTydbc;`<#xG=NCB=NS5MA zJy!ef!(;XPfxJI|QYY@`C*RjPaX)(ce_RHW*|54bP`z7!o~9o^zdJ2@cWzoVH*E>= zCvauk?N5J^)gsj6FTR27Pe+whAM$2!)s*@;j-O>A`5(veA@9&9ls-j$Lc4=RcGI4e z^mz{*q&kicQk7223m0EJ@@d7f47U^QZt5~xUpIuT#|t?Y{4srk))AfLjC1Gw|g$mBu5x#o6n4XAUPjS9} zJ8R5pHXh^aZ=wj_bSK65PNZ~DUP`XTJWt|<^s1&fzp7V=m(U7*5YaL-R$OAo8sy9q z%>FczD9RsE4eaz9$q?ZcdKaR-Btyi@5RnWY;q62l+)JmWDGQb4xArd#x*A<0+cFR@ zIuOXp5tI)A{S-Qiu{VhPba0pkk)n(AESZxF6?em@pkh47>=j?ey?9$9CP=nI5?n^_ ziPL>2#p)i4@IA}K3`F!^iu1k8U${; zVEL3tmTCWOY%b__3a%*hPx_Q@LE#0B*yC8-%6^G@R~} z#gWE+=uS2e=lfXafA5KM9A=|&ZZpw7dw}>2s2KwCfHD%!lT(`q+u&K`Lc*)6 zT!<^IIlPJ{X^BtLGciEEfw3b;(6{JfgPreU^PzohzKdeHl#tKGW(jPdC1#Us&U8_H z()44>;ePxkL!58&hwPEL8VnQ&#SyehEU0$E8+}pRu-r`8KbU$FjSq25YR^q)WG)uIKMAAE663{uz-T@|-PT7jh9a}UM%p1J7gbLqI~ z(s6mFRZTgx8B_kJAezx3*wG44U*Wvb|=_`jc}DEC1;`jyK18d)mW<1j$*I9zOs@jLPvqt?6{0*DDyqwI+y zmO1a}cl>9vf#|JZ1naD1Im^uQUr(B47MQP=AIE{;!mxx_uhDY7$UF94ga%whrDEVA zbpkxqC(t4+`gsxTow$;OAEHjcn1Z95DS~kxFM) zVnX9yFe0HwYg1RuSHTU`qZ3Vm`ECnOt8Al126BHX4o=)`GmSy}jKTO&&P%ADG!s(s zPCiehCDKew=(HGCNDyfuFcr--RWQ!uCA36N26`xNITg)h6CUOzu*sqd30r|kC9w!>(GfV|g@z*YX$r>pL=n@n;Z`?LEfkDsVP7pWn9 z;U&V`=rE#(X{tPG*h_IsECil?4HNupf>dCUViqY!4w`U>Z-IGRe&06BL6|v=i8aRE^if>7?P*Ho5x!o^g9uJth3W{@L55({ zOiprmoSvoqqo7%Oop$nZ?jr;y;vlN61nY8k(|cso6^G2j;9)e32FmUT07EfD3?+Qs z#2zTCo6w_?R7D|skBX~kAiD|gBvX!^^QtgnR?%n;(|fd@fZN2g^lJR9*8842MBf)FIKnDrFP=!-DOWNWUVIx1!0~H9;a6MYOCHV9i zzCIT{skh>rD$$sgQmNL0{+|u7D+%@MU%&W9GY4vQHgqOo=fxJ=xTNY6I%6q*B96kZ z#=cjr?Diun`(CvqcA1UGSmG_@JX~Fm-7ch`C-Lh`p&F$$RQB?R(g?9w`zX$R1`5$f zaS_=E7jUvHkK&ifP7tCuQ=D%ugDogW%Iac@@x_R@koe`dgG4C~gyy4Z=F>8<{E+p` zl3o{>d66&X9<&z|?9n&J!Z7 zew~$iQRmn92O{^ckV}1VUqpsfisVbuP#i12Zcmq2$me=mb0J+0*jw7bZ0r30o>E0eWVjE@1shY`;l-t17BE6Ac3BU4{YS| zWCAQt5KC!2;Yr?v;u09+QGwR6ce(6FB^3Yg884|JZW>{t(c0gAFT86Kiev3C>m*d; zs6`F%+$h~tts6yYkX~J5tRT_bdd;<4ecX7RHj-M&sPBEG%OO;s-H$ z9V9?nh(pB@D#Vmms6WNer@E(f@}6d(%zXV67Zl)yiGo3xE(g&8`G(v9`W(@13_2}^ zPCx9B=I}P&#Bk^f+!4}`s>xYeN(CHu)D|N5`>RW5(8tBj$sLVjK)YW0z3~>F9J#Q4ItosR>6pgch18}^hKLcAdqN2 zMFkx7z#YVU#F_%1iU9+66fs~vCFy*KUkir?#9IjZPyZ7(c7Me`Z4VX@h@r^-n1<%a zVB8x!zT4zOxfe4)aYgouY-;1B zbl`WJ9Bx8w_;8a6O~Jwd#VfKqHK91qYVuAK>>V|E7qJh47PV+GWzqI#*9CXV{hPYX zf^g4YyyPi*H9FF2vtO1Pi+QW$4Cb#eg_|P%Mmim^#;*H zK%FHHMR|!cHAGQf5Za4J)c1zBxH^%?Tij~-hVKBypHe630R!+siYi$z3{cz&--68* zn(yNwX&anjHsEUs8_YKB$nW404rYM?iZ_^ALvh{&h^>&t5dt@!s&u zF2(s%pxW||IDw&IkTc&ZOvL@4-cs;Rkq()Nki7)wPrVYfE7l=vV-!DPK;Co$i{is;h&4}JbalWhA zdKDo4D~j>25N{!$_qyhO%H{iu*(4@+6u|v$DZ*`mV88$MXPgM|hXhD#!RY6|(1lz$ zQW}Jls!3uoOoDM2<6Ro)Ed_lEt-xNKgk?^SbD=(rUHQWr6ksb1QanMQ(A1VsNX|UL zUJg(@57a?GC-gY6bO$Mq(dRG%KKG$m@c4{24&}B7n7t{pewhNn%dD_`8sPj*s*&%Q z3{u<^th5mp8e%?^=-J?WtA}TY_BD-Wn{Bnx%<%qXX62A&cF<9|W zH{+;evqn-xlIGKVk=1-!0V`>RB(;{LEd;az3D;6BCdFFXfCk$jSS=-`FU}kCd5eJZ z1T+<(wjQX1fVRLF#g>@KTz#JY`!$&X2g7hI#3Wrv*zFQRnYnvmEuAY#^$BIl%nl0 z&67Mw@%fi_5-c`@4JOnfiu)mG>v2{>y5k-Uqmb6uCH9mc&x(P7v{O0f;BhTmi)u81 zmD2uT6WGt2K-OoFdy3-ORIjVun2GaQP?pQnks4^=!FvbO1m=m^vu2dGErxTrhA$LXZ6?kA~S)E(e*GXm<4 z;3PA}3rab$wv@y3&0twW4pscyw)ij*;!#Pzzb%Hap|D3k63ri~c$QuSUz5#xho(H< zp~qlVI>yYD&A6e8i%d89qR~*rrDlw1{!qo`2%e3g?zjh?ZK&dZUFIn%p1Eztq-31R z@+!UVx4|?B-U=j5@F(j8e|FMN5kH%{_0WAp--jwr=-1>!U@%D(6L^k3wiTKryc0u_%OreNpIx5c(a!t>Cl$JA)ErqGy?DLJ|w{v&RPfk4zg`n`ezV(5qt6#X?kH1Kz&Z8 z@*C_)cL0CHl*b?Oq7XLpB*PME0||b_M|{ja8BNk4cMaDj&>8_MUy9o>LL;InSFB#5 z2)|SVYpMn(IT8MXMq*wWY39jz3mmAxV=ti7y-Jumz3rJ)IlkdOQ|6a)e88th%&BrEHk2dx2gQU!+t|7M z3x9t3f=Z>=09}NS;j%yX)REt&}V7ivX2CYQMeOHqs^o^;9{BDObEpgfGyu zp%-YGK+Zt(`@vy~RY!_&$Dq1*(vzU>lRU#3IDex!{|%a4r|6T+3NfRQ`W(ghIi%L< z`F`r8q2C~d-${5#IHrK@i)L6#&xfV-e3;^+bOO?bWrSPJ&~t=OQ77$91D$k#(Q|jvA)8zZvf5Ij%aRVr?#yNS+6|VyjxkKtY4@;{c4x}Flke?!mO>6t zKbz|H(fvfxhbbPQ=ZpO`OmUfbN=u{c6Ol{sZ0Py+(=eDIXe`MYXmmeVp;+BY5x#XD z?5B01gJM4cdMCyC&UIoxtqbiJ`w3vNp8#&b>q6T?P~Hl~f9#Fbb#FpKYX!*L&LY@P zWue)ypKzprt>*=ybz(nNDBee1v5nGI4;A}qsJ0$g6^cLAhs1uWP<%+&K+|f>cCnwf zo7rMNRVW@~YG6MdHockJa&PVpyQViU68ot_@mx~_rH7$(%5=gNQv^G4m%53(3-|Wz zC*(talKb5iiVvF}O!?dcRNF}^?57IFE8uPZ2$!OAk4QZ|J}ieVdArZgY_Xpz6c0AN z#eSO25Ke()n&?b*U_VtT-sEf(`w6mbSmgC(+i;5w;2{kAsc}jH!PX~w+B^2HP<&LE z8`w{v)Sz-mrP~H7eOTz8Nkxd0B*tKLsO-n4OjeR5A?GhgoM@#kQ028Dar?&-soq&LxV!t!yObFFa2z| z;x@p|6Ws0a^^WjKN=TPW=n-Nu4_CZR@02b#T=7obTl&~=#bw6(%I^Y0ZFvEH9^nP% zBzoLQJ|LnWu6z$^^si}Vrr+yknmIBV0X)Yn@ExBQm`cB^aiy~aU&@ePH(c=qY@y}z z8z`q8FEG7B=yk&tcjkfU-3w9GN2Dg1oQ-v;Esu5TSn6dK$O+mWgW4BJo)39Dqtgvn z+{c+IoeqL-SpM||-Ei|Q!F1nF=r=+sAlmvw59%X+6MU-sN~b%6K&{I-1bPN3)-U7$ zR)bb<>&!^>r;!Y?v=p{`h;Su1l4nWB8;*hQq#qPnw-tnjWe^bTid42Q#cdejOH1<+ zDiz~>kq8&zW%P31L?57U%+!75x)PHAL2>@aCRv7TqK~i)snUJz?Ik3?0N)!tBlSWT zlCP)2*O9x>ra){H?GWcLpjGLaI)#UV>S786yTBno!t;3nLvbsy3-+2USSl621}>xA zi@`ud@L$o>bhf=}KDJ}h7FF1RZdzw$52wSIwLqIj$HS~1}&74OH^ zdalTGv)(T!B9)4hX0r74O2x||BO2}%Z|Vx^6-chvGD^T-wKq)B zZw7h;3JCOu5z3d^JG@wW!-!OG7*W?725^NV{)!_M&(^P}^@i8=n`!YkwY4G88+vo! zwBFE{E7E(z2vye`MkwBa_n;zD&0hG>N-n={2B0?#;P-8Bc%LhzH;jPs1D-kv_9sAV zC&AAUY%4jWH;hocn%|e+a0a0^ENf4p0-|XI(bxJ0#p!GPh&ru@IIY%;JpVQMHNa#M z2H5oB7VQp4nh}cM(;tY|k5GI79o2il@u z?vaXB9z{5>BAd#&B73$t*#P%cvsTBnEd>fX$N+Sn#H-O<5G>O<70rb2O_Y8z)pH zo4`P8$y0+(BRm8G7tRj+g)WuF@<_!iaXJ$fq(Nq)K|AsWGX=)o6h^5y+8?R7oTso! zRCeSkT;xcg$SDa^K9_`nRcU#f!1ykXdtROQ87~S zhi+LGwmZ=F4XecX1{%0wof_d($s)6HT7jl)i0r3kzu)NFOacy)32ERG+jI?=ie{&L zo{|#WZW&ics;|AR8{&Dfv%#bCPGX@~vJaaj$FRGuCPw0~$^2!`iK z#q-f9_0(cYV^wNc1`V}#Lxj&hcf**C$@*pgQRSZS6Yr@9VgqBneoaO|B;JdsF?!-c z1ozZ~Ws?%%2Pno50NjFm;+QDLD;eLQYr$*zHTypE%ZmSu5BYFs?kw&4vf`1nM6Ab` zF?Ftl)4@gq^a2IYa=k`p+Zzj0U_GwU>(k=v^+s8L!QB`60=SZYw63n()2i0;4I*q7 z;)N#rz>i?k-K=`<$bhUH#QppD=K2 zu>jDrt?PR-oC>i1#m*GfJ_#pCWvJHSB=u>}6sH02-$_$E(E-sLoz2b^yS6 z6yrRATX1#u9P!c}g(IgbO!ie&?M?Qh6u%MP6sBDM#69U!8$Rhy%c2NR%i5DgZFo;s zuWV|=y|T-*bt1y$+11(9Mj=;!992Yks$MEPE=%#@&4@xug?Uph3ye~{U7yep?P#+c zp8U%lFw|18c9@=a=IqJKF;5~5bG{^gs88s?Jk6WUX%mc}HpLu_7jw_FIB3EKzz6}t z0bHSP`6}Qerl;&+_GJHexkGIvi`W386xZ@+*c2M*EO+Q3UhWLAp932Z`aDE!`SZ}G zFpL4-6z=KLLqdGL5Y6LNjN0Z2Y({8K2rfyZ6u*g_d6H8)TRI5Fk>mi2kO%KDM~no( ztOR=Ud`N7*vo$R7t>K=om>fObJy{3{fzQEF)PuoT7^ND7^>pXCUd!3Gm)wo0RZ#=_ z)39ES=SlL`H*~RK6%FV~LuBWgH^i3k)W{@CI`U-bQLeNP;=-wI83}&5+I%%NX^5>g z*rRPL#n~p>`I6T%J73}%TMC@G;~ z(FT-hZq}+S98`=_q9>H!h%z)x=wD%?VTuhyp~7F0wFU@P;RD#v)Y102KqY}oF^7*) zm;ob70`~)=SpA+N{QJpr`$DmLg5vxHZkfp!xz*n>JisdW`%?110?tdRi>`}w(LL*| zh@QH?XF70yJ;*!JE7>`|SNtT?t~j zNta*K@A<*Kdb&yBPdBxJSgo1ev@Q+h)KVAtg4LSput|*R2+%%&+IXN*0y>}%XsmY* z=vo{=)tb5XhSpq8VvJ5Y+vO(4_Rv9b?BIi;>M#}6l(w1w@!4N#E z1-C5T;(2jFAEg+BIG^-I^jd!&o{E^v(*eC%` zit~*R0qdbBfYnv;xiM zMnI4e5ahv%|HH?zx#}Q86yJ3Z)bpMK{4ID26ox2%`Y)Ie{!)UQLR>=DKf6rfP00v9 zun?OU3-wYtcq~&qTE2O#5L6E|gb;fibjuX~S~RXqaqi=&Mw#M1m||k$fA!5vD8`q> zB}vO@6>?n7Q2;7Zp(;8#ehRDACBD5z5LH?Ic|*q)cqXd%!Kr-1!vob1tz^ZfC> zgqWAm!Y0HN5^nz`Ce^$9$xh4&#sAe$uGNfG{DW2$~uOk)eA5jRL+X`7nD*kRO zFobOaad7$&o|8wtqXsI(c^eK7&$xDdIiu%0a82l|t2eEbAzkpIkDZZ~g zMYw$*(uo{Nxb!{<71rOkf#C)C+do4Lw%~Ut!rxgb;~S#i3w+(-d*|DyS7BD2e~G+7 zoSSsTa=avu9_8Qvksjq9*#3Cb0w4B>Uh*(%I9dsswJR?`(ip|6KWf*Xro!!$r(&5B z7mRaTQj}ZP$nkuQSucy=F^XH3VmvOTjRZfTjkHBh^v2*7QRpvMNJ+*;v6Yt%#1)P6_xN_Axpqq5;Dx-DshpScswNmV zfg@W^JLPP7Ctm-I^J2y);9S9y+sil6akJ?{7%-pG7^I|+2T60Fx~;X5s%#n7cy!X z@oE^6a?S!}lwRkTKNVlWJ?lCYhStc9*t6tzR@Pg7<=)DAM}p%Oe^W;9@ruur!M`pr zL9uE{5pMY>D(64_4cZM*D~fX~-28Ht7msnPKj}mZ{!>RNq{Yd#IOq8|IW~TQ#HZ0* zsQg?4nMh-x$$FdEz!MeThmR-(zIh3n>=osGq2tF?&Og3^B7ApyD(Chy2s-+T;-BNw zcHOBj>BJMpE{DZ*F(SSLDgUm8I89U#da&L-J@7NoI4bJ}^lwJShilETwc z5x(hpFooFneDHM?)_pYWmU;Vs{rjr%hyP3}+6Ji%569Sh}j4yjmGEG+e zy=y7T*FGnirYO&&eXvXAJlGvBM#U7oK2(d&R7Y%(Sir3@TbVQ){x1k8P8SKh=^%PYv9-tWr7MUtB*r&i3e4FtK4U`ZFe&l>1 zPFqtH?{nshag@%2&&^a{7pBUz6IWENSY1I8zCylVDP#UL#p;{j-3H={rYlxZiAXiv zn5)ff+~Xh5lQFbS=I+fPY~bWloO7#X+^)uvZJc{?6~maU;_l64Xaib4#W){mEw~!5 z^v3u&_rPfe_i%vLLP~sFufTY{0(a!2T&t_?$>s!Sm}?TVJMu8Mdou(Cd5D9v_dKp} zwqr08ScNkfH~j~@0JRp-dUvlFfzuV2WY=UoR;&tOwiCmnpNVzOX z=lMuEHhzJ`U(*ZGbPF};LDh{veEk7|tDwO319R+3MR|d^qE{6j4U6#2cT+jv9Y+V4tN8mr zN8mNZ>N<+>b!+7bGeqS=|5{js%Ekj9zqxCz&iL5PJjHhXAc24d^>3GE`&$P|moGg= zfvBR_6)X9gYpJx)>xzHc9!l3fDNm0l>17$>-%t$mi7!|qBlvv9-@S$+e9eP0zP)KZ zwG{$46ydtL1TXw=Og9S@pL-$2_`<#+L0+Kv!mB98S0P@Y5MSkNmW9^>dm-H0At|I? z;+cz0zFj;V?wAM;cSbq>#>rbsmbD6jf4>-Z$i>TX$zK3NXtJo&TZ%94LiDfzQ=uE= z{p`0?;~5t!zDSgHIQH8V;^($Pl@^KtoyU?8k%X(n;s#;|iMNVUgrkV(N&L5Oh|#!E z@hh+&a|D=))L2rD;%~*VZi>Gkry4bizbg-I)+qkI=uwU0AO3=3{EHVv#u?GK6{{aZ z%idOeSrr;q(L18cF$r99i4`bc(lTVfe4}WQVpUvslcShQ$lnp=5;~BI9H66hSVyT4 z`=0Wxl|_m#xYhgq*R8ku-vqn$XZBK#(2vBOr1(t5iwJuw}?LSwzq_o0x4)myqLndN!cFb;yPN;{1h$3rqHq!;xhUu75Rv^Q^=#Roj$D# ze~McBqz|Om^k0(HVukN6MEJI1dF5-d;#RG^_GlIH+JhqzuUACcW5XF$ZC@Prj>Q(e zgeB2p#qHaB8Qb@$Ym^>TO4*6Rj{bMdOKDmt7@ro}5b{ge5ZZ<(xugLXD=wu{|DIYY z?Wx;L-=ja*+2Xq+AC>3N^;QnXxAK5GiUUIO$d^yC*c5v^q+D#q8^7G~rrKmDcD|H! z6Z2n6x?FA6aWKA)xB5eYq9quM{G1nDX62%2sbY0r;Oj8wUEo?>PP%--WntN&g_xG9 z;K#L>%W|-(#f&8>H%Z)|_Ru#{4oQjRKa!Lr-a~`TIrx+byq0|6AT!i7Pr7Nap=MY; z*f6uV9&E2EcIpu~W@8%yk8QTUc>~z{n;&dmPYef}A8wvmwZqL% z)PtR9eyV=vQ_VY_lbN~GIbG|)x}MYXoODI*c}|~mnuy|in%(D|f#+lkz-s!yb1Ksj zD$m&vL{R7-x8LQ+bH04nB;!BwJIQoCCo6$(p;n^5c0DI~j+Z1^_RxOHYMaDX4ZQgF z(^wsdj@8?AleS4WG5aO^Im>POv~K1D>s3(bZsq;IHv=#F5w>G zRJI=B%5Yt_%J9T+DqD$L;-+GA-PLZO@VBx*&PFt9i}*PEPhOu%ZWPd1o zWs^+U@@9LQ1+wjFcB)w>%Bg0XnrFc3nvSBppC%cKimXgx@JoEcR5C@mlFJ=~gYt5x z#+5*gyV|8FkI&xUT;ltipJ+}|KGD4EIauf{S9n^)&?6qH9WU|;(_QXwELZ>Q6r-54 zJe^Cr|I>PKFkB=p?=^LiTO!e1+w$pJQsT~)L?1V6K$fWRSg%73$QqxW1{k0HRyL{z zgrv)FWiL%nUz&Z$0-#suvJGj~$|hTyq*2|{WP2Jw6sXWGa^d&t&UGnQAU4QFS);N% z$PV{77S6}rqAZgrd5I@x&4SO;{H)J#Yz(rnR{vjBkdK`MPM~)laE?gSYw7f1JmoO!~H)TWO_HPY$^>o#nYF{rqi3H0j4)w-VFKzgrv*Mo2^SvU)SuE1^D{X zz;MEwns0BOMs<7h{po=H&AXnHs*WhjKIec#xl(UWSs((+<=#yvH}xQ&H7RaN5iV}p zyQxX^eTio^twDTw)6-2UUrK@ctWd0?zMUA&w{{{IQ7?aiIfrWe;HPxh502Cmy+sW7 zHVIrAgt0xz%=UxBcnt3B0{XGD$6uBX4pn)kGcOIT^Ml7i#r}c+*l@KMRR7Cai{?{= z^Z#TL$&SJ*L`D(rMOZrK2?#=>2v5}bv`R!k5FthQFqPW9%oR$6MiHKERvL}7ffY&w zf~E@(^gQKO;E~$Bh92d;rU#ouxh-=O_P`$HT76vGTNo=8ujI|_JeuSr^JcDPH_^Xi zk{?Yn*K!XhTVjd99g{5VR5;@t=TXefJkFW!xN>>z(WJ}Mot5bqa#kqY(DJz@PMCql zQyQW3zg2}+DpnVJjPk`-SnXB-UvZ6Bm229CVWL4%+ja1)HMnDU=+itEQ?yF4`i@WO zJD0m+Ulb%=zPy-Hkz%T@TT)ijAYGRt1b%Q4HjOU29LGGD7t1->EZlsH@j-l*B%p$SWEaR0@OHLgUkIWpF4NF{AB{Wz7> zkNbrJvC#gIPebS*F()y6!JW1nuX60ZjHLXuCH50gg_fv_5Bv4Xtm%?RHxdzH(f>Mc1z6j5u*TtjYb;6TZ1w2rfY2;m_Sp5=5 z!)p{j{|_(ltEc=xJNi$Bq&l38z*>dZJ0DdDJUfgi#xt<391}n{d|e@`Xr1S(c#Mkh zpj(l+FK}N|X(*2?%?$Bd0@8<431H!Eu0?pL=On(goMW-#mQ)wnv|OjS)PBQtG`?V9 ziy4gv19MVn{5cKL6yshfBqs3iXcW@P;FScfG_T09&N}76i@{XH7MCmdaI*ZL3cM;i z^itY9T25X~-lk=;2ft47G(at10=1ki1Jq_K(iPII>r^Ab4Rp+>yOnm3M{F0qGiE95 zqW!WFn@%CnN(~2J*XuMq7uPBI>Ma}-*C}47cgnle65Q%@Hco#i52mbByb%cbQsbSt zPH%bGiMtK9ZqxN4x#qA=@orry-;rFWSiY)ZagXR?Uw?eg)+@IDMF?CEwLu^Y6dMdQ zJs-NUUh%o#^KfuBsQ~%j6&~c;>rvhY#Y(OvAaDaHBTxsa7ie|?C~r{wZShmup!oaO zd33)2BT(L;__`Ya*{E2_>*xr`HjzJDPDrl{lp|S6Z;203P&+KC0+i} zPqHxM+0pDL?VBKi&o|>muJ&t9Q$LOkX`$7A4D6iD@-{2J^c01~h)BAMHY+Z85~Xie zd`AIlwOR4gPg9Ja9wj||v*PXa2~m_kfqzX@vMc~3Pl8Jj%u)27!s}QO8@LNn0pu=_ zeot}eF64Yq@i}*S$mgF=xqSXzQ2qB5Uv>@1y{Gt^ySzXKjV-=6Vi}xD%H-uC`}w4D zo`Clf%lS3D(Hc=ez4{4?@)Kn^Lf|quSVegiy{6^hd5b~{MR=(|@+3n)D#O=bRtu=L zfF={pWq30D8)LkPtRu-z`c%ZSMe!W?Y~%{=6J==x-?yK9R$d@3fzUmW#rulSy$7;* zU-6}vQVw5w59LVujrXAD?<>CY9#DN>aaIctvDtqBXRBhh7Uisk$jkW?S{0^pUKKtn zj(uKooY&G8s2gvgRrml0uL`dVgNLn(JFbNT?OI7_ZFA-+B>Z44s*ePmB-@l6NqKQC zKJ#N!rg$m2l2W95Ym+$o68F|?@Kp91y_qGxnfEcZ<$YWemUvBgRTx9jR>fAyVDtgORWX*QXxM9ZWrXDGZ$3a*QO!PA{U>U0MM`>9fJ;~FqKG&^MNcAgzt zSa)QyFtj-2@v$bfCKcRQhp{iz&09)xw{WkpMV7*Ln01VcQ_+{QU-TqhgPGu4?R zE-70TFNM;@g^`{5Gr41WMn+aToBXVe(_a``A^s*GD1PW^Q2zke2QV%u1h2grTD48V z*)_tKPm=R#M8#->hwyxBf0-f?l}EeogLlk*?~4a#t>QcHqliRf{61C4 zm{Fmp$RU5N;vujwqYAr;d`(Ih2OCe+qv2TjgtC+ZDfx z7ltFeKoBj>)wGp7@>}V+AH3ieSH?Fih;I1>zQ~WW3@mzo@vA1$q&;21(%CSAUzrHIduwyY9q`OzvfKG+;PRO75mImP%y zoGo%Z?8Q8RZbUFD!Ou`X3mvE{ea64M4m*7lGax-wn8D|M+fkgwjQ=14FuB-g>&u&o3)>djE^8D^QN zH)Qnn32(dJ4PD)wS8!aHe?;`?(chGg;b*{fK^D8jL+GN9XNoMRaMx&6}==cnhwJB;UQc(NBLpnMaz z9FRrkguRL%hBN*PK+F?_fw))@QMAvxW%_}*1ipC%7};-=_=*UXbL2r_TTq;@h+sB} zi2v+SY6rbCJ=wa?bN9Ee#(QtPc93N7A>#xw64k_+IN%W>9 zb-@D=T})93eqlwSJ|P&weL}P3cUI^N94dSf0-=@)8UO2f%Hb{sfYt&KN^wroXl&I? z(p~VFau>59WD+wv@&-_d^TF`ZFyg5kdA9644|SG@n#V^4byfg1-=gMo7t@VRVrEC~ z#?|u1#v#S|&r{U$(3X?<3QCj@;yZn9`E%-ykNXKnQBIgScmr{cS&b9h4@@_#5rj^Z ztNE0@9D0gN;7hzLv_T&K^ySpXGSEhN#TKsAs*SKJi@HOK*E@T0TYax{!oetcNO84H z@ca&@{B~0D9oSZhO0cn(^SG_#YRgduYm&DF9}Kj?a_41v)#Z?~EarMmW811%c&M)t zbq$S%v#D#sA4=-yzGl@04l7o1itr(QOvcK?iob1d(YhFrIsh>3>A=bU}%{VzXID$8y-_~9hkFU1`!XM-N2D!WsJ3qNxhn~jm2gJ{l z^moHM!W88l;aM)V#K`I2!QPh*O34R>=W!rh$D9FyqVkw0_#hbY1$ zLZk3g6Ivz@^d47ykSj4VR|;Z@ExgjH6<_M(il;lX9Vec|Cg*IY*1;|De6Lz)DJgQX zxBGq^yV=FieUD8^P3K%dqup6@d*!%lsHM?%(46Cn_q&H(^p}Pa8*jH0%DXI#z{M2j zi}86MS4mtP#}RK~!wJO~%VlMhS6~wwF7L(?vEj};FMxD2m{7pOo!!sOb-RIp?F#;q9l2>OdqEd|F9U=MX{;DTYHlB z%WBANr!F=ytZYi2q_obVTwIyY<){CSVVJhav~@~xG2U{D*%D&>worRO@@%1E*lU6i zw+Oi&f@NhZ)HB~AM0h9;k@F?zSThfEH_sDl9RyZ}1*pw;?i}&`Z?Fp2-v$jmrTDhn zDd$TnBy>@;^gw;N%t6@?3zn^v1oy&G`KmtQP&uD)PRS4p_&m&x^YjS^>!K6RblIvbB>rIyEc6;l zD6k1pp;QCMmKfJUn`2yTkntH(Cwb zHihK%oW@vhOm2-<$yP+T7=`6UOoG$Ur7XYiP(NwILgKmDHn7mf%4xKHME>a_T86DfxZ z?G=gR6^5H?_?2l)VVb7UL@{qlDK$fWs#%Jw{|djaE=O?=EZ&tl5`1>OF{@D-IB4pR;P z5FU%R^rW4@dD9^92>IIy9%#m=-T%(dt-a6=AdDyWQvc=*aXcRzBbFcSUg>BU)iFo$ z#H%Jz7q52L?CLSCV%3cZ5Jb;VpUrky=)k#ZFas{4V1D9-$odZj=~qypP@LK5d}a2);&79!fv`c8Pk@QQ)Z=NGHE1 zznIU;$QO5@M(isR!z;Z08FFa4*AaNHNYtV0I&{<{_l4T4$(`-c5k>1qa)jsxXZz%{ ze=m`0qjnZ}btH^ans6lUEXa72Q9290;GXso9F(3$9`T4deTKG4XThJHBOk#-L&PUO z`jj8<-+fez-&v4DOT?H)%_Eqp;5@zy{Fs7^WrfJ&o^%ntkmrCXqY$G{`F9nZ(O4pA ze1j@7y9(+xW=^{b>Z@y=u7Xn=vu?Txyg~ee2XP=P!gx-=VGOqic&GgUeo(~;xpci= z!hK4KjMok0c)I3{VE_ixp*sfSfc_pp8`YUGQi({^=dX+LW+u(RMp^RJ#Q}5(_*r;A=9ITd@1DeeF5t;j-G}oXW>h`K>cYh&?K{z^K&!GN(M&RFF8Xf>POov znf<3qba~xPj*`}1N@%Gum+{R#mxG^EW{tDje6AXiZi01`^h=_57kIBq7_U~*xf9!j z4NIUA>xw`-olyyO&~KdgAUa4_38?*|M@r*oC4y(?@uPDJ{D`8X>P(;LOw5zzx9rhHsL1MV!eh!lNt0xT-n@AK*?&lDB|HH`$w@3zV zx!)vC!TnEgjwj!p633kfox`(Uf`8J*nUi|0FWyJx3q19vcAXUAIvo&+I!|Ia?>eS^ z-EGV>afh)L2s}LxZ=<_s2G5ol&ZeF-uUH4Js@W|Tf@hSewg&bf`=zo zm3;R3%yxm`;>!pW3cThlIe~{IhKHZw1@C86c}9IPi&`k$uZJ*_ZVF-^d5=4bU9M1Y z;bjuRbq`BKV`l4HpAltw<~4~cP{CUP+>pZm+TrZazQzXZE$ZOJQ$W=pH!~|YhnWEq zuaGE+tBafO@*~`Q8)fwt9CwlrR2Q>?>$dyJ)kokpsJO4F7Q_u2*3NgwkDOJ}@Go_e z_i`QLFZcWWe)#_T-R;I?@U`QjdJ^x4U(1!_^=LDbQdMNG6di#plA%&H;!v3+wbdZd zmo!OMDT~?+sGwDnFjX>}F!`pclzh`$F{**nNWD!zQ$=n+Gl1u*71HzUkp zbuhw=GRNr^&ZwWcQD&^mN)j>FjISbPyqQrYJj1+sxYRezGE=2)J#C*{P7%4X%&4Qn zK7u7Qs?Aj5Bx$2FYac=`#bEN{$@>E7(S<+Y? zGKF)QVJwp7}@Z zh#qh|;y+%iD)zNj49D}6a$Ds|75+S#r3ebtMxG@r_1!j)u}vT4&_!UDNG4{GVSN(x9yV&(fdRy)Cd0`CBqW(T+oEJ7bMklLMrW+B}VE;L=J!J_63`Mcplo?KwWhUu}s8knel>ePBJo<5o zN-G)_($Vxuevy9tEi`3^VY;KFYNcz;-jvK&{RQ6yO4R*WKS2o==mc{UA6ZUWWB^T zWkvjwR_P&qRm)$s+RAu8!{t6DUn#}gz(*}3p7a-Z&r2A)B!`;1E^MU;KID@?5wx+L zRGm(yyCwTQ$eD(MNOJ*j{=@N&YF?s{IAIJTT7GB)3aW z)izXY4&M^y3-p3Fn2%;yZRmGDOQ|2ocA?ILGJj5v|9ron*k51sfu9xa%B%e>B-0z< zR$?B33odX*rnuk_{&gTlKdBDo1_+uq4d|ecrV*cx$o(#gGS)kdA83)l)2o@O!0{(? z8B`=VF$o-rsfcy%&1}0O!P!^%0R{Y=*~hm3;Ay146a ztOJM3P~0iFE5@`22_AgXmrMXT?qIXO9j%rJg9N>KE*0|#mdHlE!ZJuu#A{g&+lhsX zDnl1uBx;Ug#*Qmyx=dDu`XIr4zQ*FBv^A4e`94U{j-caRLqiD%dY2!W(dtTm*MljU z_hhiZJBcmlfS6)Xhq)UpNY`a(vB0}S!f2V#VLjjU*Hi0aLH(9g3FQcFvvTB3w%eN= zHex83?-cu;DuK|^l;~=sSa51fiBpnEJdJ#!;~vph+$b4%GDj5=#e!RIlMMW8hc} zE_{lHpXas92aqeHjY&L!aytVi@qm(2B@Y#NzV0%ic4a;yhEhq;V$oB~jS|I;4A*tx zGbFgE{@#AG7^`g;G z!P$4QD-9K7un@|WA~YFwn83SJ!nm`XN229+r%nhC8^fK?^Ba7A8y5!KWTC$jSZ##w z5oye*^3+wHsLxXu3&}$Vy2_J*WoEVEMtQXTtw#+mDNr_SJMthwQWbMzxv z&l|)Z!x!G3=5Oh6`+8cUx|OvjB?7N5M*s)JP`8mZN27skkpeS}$8y7X#uh`aok2^Y z8T@I18GM|(IZEyLfW)vUFfk}GTBHmNu4m`^Tm&RO`DE!PMXkT3OBrey+|ON(8ISS{`O<>MmE> zN^vZs(m##^b^zC=Q!$G_rP#=y?ty&1Y*8E>6XdvCB3i@5#&Hq|CqfZu7%Tx8Tc&l6TMQT z#&Cu2!)sIyR7HLmh3sIiHC&6S94)2mSSerujqm(w(3(D+cP(w?T`OzHbqoqNSKdv$ zB;h11VyE&McxT2%%Sr2)?Al|~)~UPB62aKY4xhx15d8ID%=jBZFTPPUdQ^4XZwPLR zam*SizO_gn=}sMXcNC}+nE3weGyR2T#v%N`$2npi34a{SzBX71S(szqKbz?VD(3LNuMpV!k>88{7NWn^WVA{emwM?ZB<7nhktT8J| zo>8K1e?0{rkQg4|dzT^nU^-dKAYHYZMAyX+w1tZ*_cbjfj22JO+T{rn=>?kOYau_Y zLis{xp)bsS&FCmWf8WfeW{JJc8@j-#S7(+MtO6QfYI2go~C#r*oBQ#OW2wBb6 zT`ga!37q1($ueD&juw>ha&^>`F|JxNE8PEQc zMDX}iJfKo+wqkF;pK``Jl{2ngMQ6#YLTlBaY^>nM+bMIb;CuR6k77ZnG~`EEg4Oz1 zD^~NduQ2wbEMPgyl?B>!5NsdJPh;3)^gU9(n9@0C2qzd4VATUu;QmX3z(h1V7bbYOG+mnQrKHfx@Pnt~Tg2 zoErJ0lljHekPYRiE`LfGA~W?Yr^XgYDLS3C_|1_5e$Q_=uELI%$D`5qn67GJ@N+W3 zvx6i1NlGq{$?!{><(Kew4-F6T#A}@Bk+Y}3(`?Ccf^&4Ino6n(GoCtfR`YnlmB}j> z*$78{+zD6m1DGJV>P})N2-@-XL)eo^0xw&_cuzj#`R-@ZP4^W-%`7F8C*6~1ZZt=~fg~7neAOq|P4YMi^s0=LLre_4IDNaQ zx3=o=RJD3iYmCK_ItIR zCI#=+K2Y0@6O?nH_K&q|=@61oH#tAnK2#?qtdT!RE7x1s|I_@Wl&iG%n%e&xSxXji zsSe*k`;fnU0+4$eAcqZ}OQaQ7M_qcJ|BC%&}!Twr0MREG3`% zvSc`N*pc~mIP*YH?SgMPwMLZlTQgDj$5RCfyGGS6EZWVykbAVk@8yyrk}9c;n0Ttd z9yA$nDHe18=29#*E7gcQtq!NY%(Ol+1#T%34Byy#ou6VpU7dDLGuQzo03&{}ar z#W$naEKGDd*NU4(TKHUirF^vlOUnF%-~x;z7(bi!`i~R+1+*-V8U8;B<7Lf+zDKmO^_YdBr+9qRnpTDQQ3-Wat}9b6S!ep zXZ9M&z+UqWmnYw-z$_;)LwO6RbaS##dxEpe48b3-;(R-UYl3lVBr!uU*{n)}vj{fR z*uyIsQb0{w>BlTj3M=mvR@TIk1>Gy%)mUtXq-MHm$vl}U@OYI3Ys}^#wO%s?_x3W< z9KFnDzG0{=k7i2@+3UEWUuO=|8)<7Cw7KA|zFfSazi(hH^!mqGyw8#25H<%H_66u| zlI%^~&u=oM(AUIF!K2xFzao1n>wT$GdM4w@gUFu7?ff+JA-f?yG<@fd@7d+K3v79K z!vRd4OUJQT$rl|y<)hFMObAZZ`yEq*o7EnBrl33bcr7$j#qyz%2l1i#$Sqty!l!i5 zq0A2|$GMNPiDybhHa^lsKB`gru|<9&IVPlcU>>4xG4r8%<$Xvo_|SaIgJn0N@cs+F zHHDVm86AUWDp@7R%1fEe*Yl)mTl9-S*PvgvBijD}dq9N0jX)n=@XnM9a8^=Vv_{8? zS%R#Q`h64Xsp|Gwf-B-2^JWQ}Jk2j+mY^p$*Ab84nHNYH7p&n!$JK^YSV#Jcl39Wu zWPmF4st0F_e_7)`c3p-V^5Z0MDi`NGI6bvdC{Z^*lsF|nghuyOz#h5&d>@BFvj0xP za8l-o|I9*|G8sBYVmODFZaob)=FCfcLkXi=uDPOjkkP24u6!80Kw`L{Bj--f4@Z3< zd3)x9j-r`NG~@zrUh?pPc9Nb3PYuTO+!m*v8KVDumI`O?t$dYI*)9)D4B<{{9o2~o z(O424##$-o+6(2fm%pCO7x2lmFnY;IPWmHxiW{!Lb9p#Yht!dFs->j)f+lxM1b4d! z&*lqC)TU0Ol$fd7R`UfrO~U$ucJdxz%#%08f7yG6R*DP8aQh5bDQ^mHe~wWIJj@6S z1n#9h0xf;aMc0#Of#8yx*sm4{&N$b{oN);;3&nprk$|2aEfRQVNf>8!(S#YiL}Iw4 zi%Xc{qmUPi|89c=Vz{ip#bfx)WG6fG_u3rV$$noT6#IO? z;WHHi_cY3G{jgjxh9Fqz1OmsZHdrMoMiz*V*;PfH9>^V}f|qs??}h_?hy zUXd_fAvznQ{q;$yV3JJYyg!K!pTZt7-TqL13CCy+d`3lJ6kpgp>ge*&>r(m?Qqn4{ zBW@iI@aKS%wmZ_)z^s5oF)OfzKU)HfRpv+ogF}N7#n50We@cTSZE~azXFqizh+9!-H-C03siz~2JAU%<62;`>H}Ge}@r>2U zkqUm(?>7=fzu(N|&)naTl;cQ0{-$kRiK1=Y68@CbCF$Vn+9;pZ{erkJ>gJswQRJOK z(q2beb;3I0)}3&GKL?bw-I1o&n^lkPRc{M_w$x*+GDjNt+o8W@d;PYQKc&AVX_F&$ zII-)AY_Ai?^Jn~tB&~I%T_^4*ZvTmeC$YUwB5A23tv~5~;@&^$2mbt^qy>&Nul}O? zY_Iy|{3)-`STh}I#K~h$W_z8ynm?;gCTXH04Qw#90dv@(ls}~nNE+!#9Zv0fDsy=1 zc>at(m84=v+I8xF;`X0fNHfI3(@5&;NMn9C@plr%#NWNkpLc&pQcp+fbw-~vB#J&~ z%;wMRGf3*>NI#y@_DnX)nI-%wIg_LuNBZo{FNpi%%)Ewdl!hc7{ExQRs)p-`Ti5Ua ze-0>VuOm%8Yt~t8l(V+*XUkcPwcU{ho;{Stq(jeM#GgfHlT_wNht6(yjzrP!oMHSK zb`D9K9O=L3d`jG>=d}O5MA82DB&~I%4Zq(++@{~>Hmz^geGo zf2N;D(nLq<*0@JwiK0j2<@{OR_`oGLG4ek+aLJFC_^A(UJiIYee_PL)VxBDV_o+Ag zThtD!|04DdiQ$f)bgufzcGVLShmJZl_oh8nr|r-g4t)xKB09r~zC+>;-O!=qiXKZn zYed1f><2onElV2m5;%o9I}%tSc(s5^mjdahTIqgf9@nz-OnYud(B8gbC4x6>8P&)r zvm3xm%{a)#^Fh25lnCAlewh){N^dG|PNB4C9aYAD%)Fyhq?xe<4s!jBL z9c6X}*TXBYf$r=6gAEuJu!)2J!KlD;F3(@b^58oTU}75Q%CkHDO9y;e3G}SR z;;s-hx}59n%X{+8NRNmCR9!|fQ_96Aa$ZNd9AxoFn+Zn46U;ng6P;f7Guz3}Y~0Um z4l~=c7Fku?LbJx$#NPj4jcIQc=tWOQnRr9F__?eQjAo@qD@1Bc)^Lpx=3)bHes6#) znnozIWo(LyN*|rZjm_w^EorR$EoqZO62XAdYwDHH;MBmmJf84a=I5)49U%*+;s{~<8 z)~DGtm2x>`*IYfK65XD3@5+7Qz4hR}{$3hb!0zZQG^uZ*+N z&gM~}OQ&aR%t|Xsw+Zg*Rtg5C4M~#>3`v{CpIK>dhv-I8vwGBcD+3$UR&S-C6Gdcd zl;`MbSD_iS9*HPeiF~Q6ZcSG59B5(^tR(N(Lkx{GzxKk~JlL&0JXz`e%xFCq`jwUN z)vu`Rb#{ghQ7h%>b@6|q=Nz^2hX-csX~?gvi#EZcAcyo{8U5E(WlEB+t7$7I!Bv7w z@0Mn`d$ijJtrDDhw`96-md9}8yP4xvf{X6v^sq`$|89vpj!fmaSvNwI5Z3S`^wOr# z)494gUL{BH(1k#TC##dkyUh%fTP?WiMt@eUjDlK2j&lRoCc#JcBTf<@ z+3)!Cot@_%;rSt0n5K{J6sCPthk!@sIar6}bg&L%0%__ol^gp_f+h();@c!hZ~$tl zF@6g4SHp@;f}X+tYO1kGaB&l#toc&LO6aJG9F^E3Fecy&KA6^BPgOSw_OR40H^Xa< zti@K|Bp6xi$0S%?wT!5}y*4AgFF5TgiQuZ2^>pWbLA|RaQ-fKaY)*D~zDMuL2ZH0( z)!PSx6V;9K7Qql*T;T&d(Yaj73FZb_`^XLcpnylNv3Oe-eIE)vpM`NgxAEB?!%zH! zUH3!54L39I9|~^P)9jA~^{51xFLsm@6Kh_|mqghs`Ju#i{@eRXeh3|9{#n&h zR7_7f#D&r!nSYi|EO=RQLv?44orYK-$~Pu3lSkz<0|y)0#7q}= zX5dXVB-$#N>D!}h6_f^kOk#`DKd%*tTw8GPQB_Yw1WiLGav)|QHWu}HzQs3j> zKVIT^|M*o6Y+|X)^}gS%sVfl-Ibq`o62Zn3a_iZ|-dB)YuS-47>stlAPw01oP2{|S zekTlZfHH@D<{Y1yE@cJF>yoGJWz3+!44STP;%PR~ z=@l*W5aJ?mxJ@qhz6Pb-jyhal$5(h-|I_?3bY+9L)YwO@`=b)`Py%1b^57nwhqnqg z1b3(GitTq)FG$x5N4}j+v#7Q?pJViU)kHg`|NkDuaA?% zz{gT$82DJK3q1IFgojhCPYaX zofMPp5mO+%O`eGbyrUb%J5oybz)HI4US{hk(jINA^U7y}X^h=cV;x0cF;%xMJ=res zvV@O-q*LX+UGV!SxB+@%Lb^T~w4EpQ>u3eJ&P)$+*EgM}IL$QXMze+|^lR+KG)o`m z8+qDIXQ+Y!b|9z9!S)b;4k<@|YsV4M$dgElVRKq}n(leZ)24?AC~+tjQpLCsl*}P; zyI?X${|Mez9EU(A6DSQzCe{SYgEmpn8s$MoC~!iKD9LDAQn^A;lj&+fv0bo7))^oB zoqVs0neFs+QEZM~%%jvmciUGtU)u$Jg0uA7V`Wkc+R~HH1!wYI{>!`2=uX4+FPFFr zXKMH%U5$M%c-~#s{anzMw!C5V7g{Z6d$L2|wP20!5R8-s+Q(K)Iahw=GL&5n`^*Ho zB~o6!#~M~}fq6?k-|Y~*W!_DKNro@P_tn~thKB9X1&sEE;A``f9%z3dSZMdCKI99* zV$R-CkKk6jPcPt9Wk_Oo3NF9NpDAvNrt!igojgZ(R6ca%k!dh*?8FrOYmQp;?-KN* zZ{sLN@F#*1JRy#HBuwCq?}&mu*=kV~^CyC0_FTs^$}G|y-7Z1+9}>qu+Ox7WPL=R( zfp<>DIx>WFo(t*4nrMCq&%IW0HS)Cy$@OFi3(O{4ah>v0`(s7GQ)!oR*}hv4D-?;K zpG?ypwwr!eia7!l6BBm4|Enoqe2qj zYoQ{T$rq<0S~kVFVos#8C=)aJ6UI#0q_;A53*8JEW@nH1Z>bZwmhR!*t@lUSCy2X= ztxDiTirg#y>C$~tg!`UPqV#h4eI^mYeJ?TMUcounFgN6{hUlq*R}~qL{f7zdOJ;|_ z4HQK{lTHQheuB*V#lMBuiQ1_m%rk{*L__>8ewf|Oa-Q#CxhZs}U;72;T+V#$7hE-0 zB#uSAloCfTvs^7cmDMI_KCxeL=Y0~vBKedi3*XBSJsTflmh%XGxf!c&!N<}lIRoXE zDKzyM>#@IStOLBI;9@m#u`>NwOO{m zo>H=zov#IFoXwJdEqJk9bDfH+pj7}B(H?#7h-j4pej)UY;1;JB`9{#$zvuRipv6mE z1b!npM?Kfi3ceo6LCfil&$eJ5CA{;Mas z-wEcL$<|N4`C~rNUxw*SQk9&{^Y?=K*%C(fM4mr*3^#g7GVl_^*&05d{;f4kubYCi z=zm+oO_hH8OOolrw`;fx{fDFqWJ_cNoLD$H0%sO$F|M~X3LvLBHxs8=5S7x|MW8w#Y@%Y zLdk`GP7VpY?`cT(z3CN{BJ|=ZI6Y#jiAl^O!xHI0&psV&uOQdYio2t^AP2t_<}G z4h~kbs;qT(z4Y=;C>2(~~6yG^Go zY{Nd#=IRg^nq{=PS`a7=*u;p|C=F1_Gr~#gMMu+7Y_JRF>X*r>^M6rG?8grlRhGficIFzFG$uU?M+7jaN7GrJ+%}q~B^wgM`o4!6h z`4nZ2!614$Y<6sUM@?)44uKQ?v2uLW;(VHxqqhQnLFRvMAxqNdru+FjYIPr#xVKg} z)u*;GsIPjUHikF%>3GlhO4GK+SDLmn&aJ@n&UDiSV+`tq*3O`l?BaB@OFjtjI?@M$ z&jTDN+8Mk>N*w-Il|MS3R_mQ_V$T3~?`cylEE~bFvSAk*tOpmC&<};2xZ30~a zHZiv~x&%H@K#AYk36K~jU_Llv&8L@(m{M{v6@JoP)M7LnZ{SDwnI1KxH5XwNJ*~%8 z`q$QNPum-ubH4UF;QYU^XGW;l&o#JuwQ%ZKE$y8J;{PV9gMrsn!dS=II_fcet!jMY zTqfs8N4LN4U@%%bY1R5Mlr;tSOTc7e9Syw6Y^2F1SDnE7h!`fDpD+ zHv?U2b4S#r_ORM)oQ?+n{J3w}^sYtJG|T`-jG;MzNzo|4@0zPh>Vh$GkXH7UJ;L(O_w<@>;C3js}YeI)-XX z#B=Q`Mf{&4Qt~+^G@FO~Ytq~5$#+LnLw3tph)gYI=UU@o=C}koeVC1Rx;7c%r_=+H|86@ zKXbQ7!nJge*INm9`GmWa@a&83^ts+y!_Kf`yUTc8hjubpK!?B%yWRe%+OJLq>j_4ccdh+Y=c`Tzn}}`hNoP~x z4`CJ0Fjq+@-aG6R>Y?4pC(}E^I^E)|lTIP_;7*|e?OdG=E-2+VSE>X$l{6TljtjDV zT(+h%NB4196nr<07Z&htTBi^JC0u)g+ z#KV{!_*lo)j|01SHF;McpBENq1}T;6mCgo#`@1y5tED2%P%0mD@uZ@gq0}67tD}Qx ztG6E8s_dptcBabahTjQq=1ZjlptHf_k4q*V|GQ+WXnJQ@PF)t3YXxL!dHUEAx*RW2 zn_kSKjY}A1_AAxRbv7vEJEL)BGf-x85X5X==!$qGTz8Aa9e=ilKb74gQS6q3Tqxz5 z864E~@^u*7cp)RIY~C$3=3pu%WO&lmz#By&qhytC4T%^l62n+oBdQ6%rx|xs8QgjYg3(XZQrE)jygYK41SA$Z%a}xEWn}Ks`NMJt8JYVMNxIZtj zOt+QY3?B2nkMIn;xHaYl-qNGNZU#?IVr%g`;gDY{-&_86Mm}gEh&9gDayNr5d{7{c zEz+J>I@+7ThRcY>mhImJ8!RI>(d;aj{o;Er^Sx;8m#@Y5T&8;yd@Y?#RhGeK{b88( z<~{RUw`1d8PS>yMJncQRxq8B8Q&v5p%#5?uiPG#&;;BFc8_|cxQhfpw_2Zlvn5QeJ zZbmaJ&CuBlX2}QB5lu6VSwdeZ4lLu1v#?^z%t774ck@Tg!`kSI`}qyBv6}l4UgS8W zQEP6dE%NnYb;>GCty2}iiuQB#KK1hv#nvpg#tu_+ZER;fWq8uvz?;c$Zl;XV8H|WI z?0<7)l$*ndxkRG4gyFg_3d@R81z~rC5{}~$?2-dI z>KzdFx-8{BAS-o+)!ksA>7o0e?gj@4MwH>zR-C`P8-)MqW2%p`h_4eg*C?adur79r z%<35?(Dpv6(Ng68ybPyA$TPk5kR_!Uc}BgmdIYY!o4qE_;DWo^YY5!RWfp-un0%f= zgEN@t9tPe5j-(4r33U)9!LfSq>*HgJ=VV$EoTS3VKO^dwG+ zlg#_-Cg*+5BpDcoDbUf93{&|#4mJ=ZWF@-|4slb!}%tE5#^tK3jp zQNCV8a+Jp;ipO$8x;j-($sm*Vjh+TCwvq_LRP1PkFX%BohCLXjvr zrcF|fEpv8QPH(u|FI;ogc2~nuHSY-nZ=af;5xRh%UBb{aM$9A7T^4AcOBfV!J>syF z31%omW#k)g_o*4#%tFE-I#EwL69#|C@KHaD=@zwCOqfcbGL?!fR-!Jr)=#)Xcm+_& z^vFoceY?} z>TS?g+KW7bK2pNAD6#YXVWzjiDp@1)7}n9V*kc-Nm%O9J>us>uykq1sw6`UC+{%cC zJ-fHTQj#7;7bP|ENljEhnJL!oS8s#i!I42;xWmXGhqmI@%HUm8r92lk@yq!k)s|sT z`WSfk`oIMl)He1p?l}07xyB?OSB@w2)aMNyqxu*um8D$NFO_w|w&`Q=G2cgY(q8=! z8?=u>4smgfKaq;Hh$npw)C;1gh((NIryi0jG6nnjb>DKqq%h!I1L4 z!{;F53VJtJo00n}^Pp@K=w84TXrB2e`cmsYGl@;%?a0giVHDeOR^12m zHPxYd(W6s_rn4IF>bY-UPD4MxK)XapPtx zd^Yp_6?(Z?*Cssjw3Y|UyPU=V8^jOWwh6ssT)X4H{g> z${%RZ_)?$ex(uIm`QLoZjV)RJfd&~ZB#I8wTX>UB+_XH<_!v(H8GTO%r~jnNjzI>e zt5wldSt`_dFO{F1&YKiLP$@8u`LbB`2GvQVh^Qxn4ZQnV8w9Rr0S6moJt`4AL(_X@IQu-Gv%&9t z;H3)Mck+{tYsCf)8@hW|Xn1w9*I#|RLwTg%uFHa-!}uUGRBZehLkzN>WzHNB_6Rg; z?1M5jBoKY%mCB_R@PO=@{e^Dw5V{1t-B6 z*2p(*6<1<#nB%f+u86!}M;OAxgO;9*FyLA&?4b6#svwTOz7=jEBWclHYKH6*2}>c{$k938Lq)0rhc8N+3T}lpj*6 zLwYjO_`a-O;x|4j8S0@P1x6X%9$|_V!06-z!_5rlnx`W{KivhUu`)&|_o< zu4ecYkMygbkPIk)wuU25NR(l>AD(1TY{qei@}^l~s7YO6Ho5~@B~3FD!&|1n(nbUY zcB5}BGRa`4`PxVvUn_r8m3@d8qhsiRAE4`&NnCU7(f(0=gomDB%}p|uz)X2dw`VNL zF$#WoMI0-9ba8LnmB|`U0l%~*dNXp8!FM*H3&}|a30`f8Xp!3mI;#qMl1T-tsheXY z|50FjQqHe!kYl7!8eE~v=t(9O{3`fe5}O1k8+hZ{CEhk`)i|37r%NV8lJ_g1DM_P& z-T^5>@4x^pFR{<@t06!qgpV$x`p>HjKgtjE(P#MonddozJ>NUPA4(u{K%g?lbI;QZ zp|h?9I`eEWhVer7unIiHLjwnn;7+rdVm~!I*^$3Bhp1*Osn|WOqJd8UF82DVrV_YD9ji|@c>EPVUIp-+J~A~;N2IXl1F<9!O#>G`yNc zn`ZF)tC@t0<(M62x`FoyL4as*V<#U>?D!f|c~jIE&3zZO`e{22_CW^Lf;$Co_@<`t!NNnJ){qw-Ku!$9yi= zFkkj-^Ai1{AL}aJ8q6>l%_pE^p3F4<`*}0@h{8-WM^h#Cb&28inVd;6(?4b+`d<>o zzlg4jnP!332GO}Z-_8{hPI3GhD*OvR=j+nB`F1utnX4DQS*B$t>O9IpDZ!B#LWpWO}m8jQ+!$2=&NEf<8o7cqC5b)S{__pIric5Tkn@Ro_nkTx6LuYuhz`(mi!nouEjqHLk26chs!ciBltL%*~;o|Fl3FH1EZVrodbC`{# z%uWO=#J$_GfNF5p_YW7c(CBS6~k5B1po7O$v|5f z$ev6_iDIEF(V8P4eaxK4=ykD>3z(=!^i_qdrb3n5R51hT2RH1=BICcyu}4e2$l%Ni zC4vk0xc%}XgYz%sVl~XpxrpbERH{K;@w@1U^EXMd)=2nJ{>y9+cgQ=`DjRACxE_d{a!~ zL66~E|3;%jwYN&uRX{(n==9x9ro3AlbX&<5-(|Kzqf_dfxs-8bk%AHE- zF0=J3T4K)A?i#p%1!C5m0?olmrhuU<#z6Q`WW>@G8yCI3}-HR1da z{wp6#nwnYUhw!niHOYVqNoXn)TWjEPevk@nvd$i8nL#Hk2WReOH1w*#76gXsf^(U{ zx0n==<|se4$7wdPpcQ(Yw(E2PDrnKEi%ykhSafQ+-rHSf(EjuVr~4JO>(q{?`QX}M z9uN35&X)>jD!iorlKK+HlKSgUmN3?zJgk9D%xr~W4Mv{oSO3V9*Pkp=pVWz@@p`Lx znL+L;C8q?NIop(yQ-(FDG>BeikQ2(K&$+cH70_QZ41Nj}seg>4ejhapt(metn3v{PUkX^1Hadwd zN!ySn&9EWu`!q|3<=>~}hPcdKW-vZ&Mw;K-YfhjzG(2PzOIu-hXmdz1usO8qB!;yj zdl47)aXAh*oIdQs7#`XXk}x)e`lQ>$$X4i+en9WuvqyziB(pF%G%E=nB|ORr6ifb-S4V2{uXP!@A zD0Dp#_86{DrwL~=oaxDm!{{qaRr*SUdRI#nSO0?>`hT=$t*&y*51jc^fEuq=2CL;K zcTT;^V7tPZ9^v+;htY$~9yHmEVupD?pyBreJJe;@DuZz(MSRl8)*9~+Uzv!at0ntj zhZ}>LwDy<@;s!a~e)Lk)yCZYt9Pk(qbfTIt~PK_V=Waa!cyM-4`a2ASGBAo z#&G9SU4kn$rc_rCgjWHERuzGuN8&Fs$I!2D^C^D&jftN8#k|Dk zX#07)qNOKm47|qtBG(u+?y064gu3gRY>mNHJ@tTjjls=5bxmH0TkFm%aN<4eO=}Ie z4`Ez#52d|h;6)^iNUpBJ8N6R&xIfqJJ>D_Mm5yBT52fxp!y~3)6N%u7HeA@ZAu*1Q zbaouU02#`Kk+Q};S!dwgAYt6lLWU>>ZVh%=Ul^V zY&1CU9toqX>BrjbXXx!R(>bQ3W5#8AJAIkiNJl2vXnxe+A5B{;Q7pB))gEG_K|k8a z@OXm^P4J`1QH|S1b9h{jxPf*-l03$~os==%E>ca@Mw7~r!aI-ew?$P#GU@jW>RrrA zx>IdnHW_vQ?NtKrCovVkW`o;apqR}D4fO5&%?9VGAFIs5h2)ry#(!I-qZw_IhpVH_bRVr;*D*q- zCuIiSbu4h1!QEOXshE0t##&}@$!)BzRFY%fYIvzQjBDsX)?;vieNTJO$4;k&C+D*c z<|jMjTs0SS#2AU(swp*wx9D-|9>f z^@!J)aThbLm~K)c!MKh^D3RVK$lEyiA#@822qddyKww|Mf6Gea3^b!mnnuYFVU(F> zYBkRfsd0RM2-D1be{`DJ+HGW*fjT;Z5tC@b%by<`9HgOVSOtD)J~vc-eoRBIZY)$c zTV$mLifoNVyyZ#G3`Z$zo8a7_&r(AnHSMF(%4jLpQrS|nxQCa0A5?51M(XL-$L6RN zcC^ae5ZI@7HJ=!G>QP>Sl+pa$Jj)aQC{MhRXY*~XNr;{26N9ONzPz>7H`qO76YsS`_fXFeKZs8Z-VVG=4aK{R++5}42M4OP z+b0GCgL9JLJHah#B>#!QANAlQ6=?D}cNo98X?|HlE<`!_iF|6{-NxJ#$fl0O0Z~uN z4bHhk!nh-YGeU;iyp$W%yMwN?GdLe)sEt^;L4Eav{ZE|`%MH%FgZ{EJOyU8uWO(vf zQrG8zSP~OW0k#=XhaUD6Quh!VY5{1B3<;DYr8=^|BPn4K^|49 zEgZ3hN6e)zmCv0yGkWvNRFTJU!=@5JS5a3N7$95Rr2Vi2Pd}c$Ien;|DXwY#K>Vh%6I_NkU4faE4!|bY^e>x%?h; z1CxW&0+WN?{Obfe48A8R;z$vn)Umaeb8*_|d`9B)wB63j-0rlm(^!=|3>JsJ3P}uK zg_hUmmQ~r`Ngty&TRRLE@SagvvE%rpY)rwm_*fNMI}CdAPF-`wjtqiJcK_M+e{S(wOT`F0H|7Z>ISPyi;p?t>jF`Vlg?F3Kjql4xFdU1tB6b=~q?$jB7(Z=sYR;H*9CX8^>!K@;(D-|#@J&z>ppOo@jbdtR?UJ7#&G*&F5^8v9ChKk8gBkP zSK*QT5T0Kz6^fT#=2sBL^Nqxop6oX8z84AOdpV@L2_n7_iQx-56x6|*n1v!d%p|6+ zM%u%U8AGm_;*?Ii4W=5mDc)@`P~O$KX}7^x+UL>3p22dS@@8uEN1yd^N2c{Gt$CH{ zm{K}Zosyxvm9dVL_%jN)$R5&-7R!2M>5qlkW8kT89Rg|>P2dy`R(lP+yCjUKUXmib zRIV*eTo+bx7b$mbz0bhYB@cmTC5C6eqt?%p{cgdJa|fwXkU^gLUO32U0U74|Dd#X; zSJ^MrH>k_?W^}pTsXceUL4*g?il$3EgQs4QC|)32rbc_I16#O*5c3zs43UmHgzq=l zAUpXb?$ig7?^Eg>E!cbZ8{BY*L~#eBWh>$Ea1Hr6cKW^O0U&aWYz*g9-}L`yYD#Ff zc}rb3?l%}j<5W8QGnj8SB@0(W1`n^W4tBXdJN%{bAMA2^ItI^`7|vbo6c1k-oU3LB zgZ1W3(unsUJBBDa3FI>CBh`X^Y3MAxrLtYE zrkv!-t~Ojl>W8Oxc&6J3)uZTHnPNU|6f!i88f5k}!*uBT(%^v1*IQO!@?!IJt=gYo z6QA*6^_HG|mDJ_+*Urx1H4?)${at5w;iwDOb?nzjh6|sP498u#p@wf))fMAsYxo

tyZC(m{qnt-V=ph;iX4G+fv7 z!x=7oN-`XG;f5M^*<}1|4fD!|M6gtd%Bl%fCbQIA@z(~UeC_@>#=nHJOSl_w!5A9t za)aDA290!E_zf?5br3p9lg=V>bd$IFvs`xRbn=bCmAA0QzcIMCGY72Bq(nU;9It{E z(kg(8XiaXBDDKVC&D1vr;g==m$-!j)%C+OUU<{4;NN-5Vjr0hhgK&w`LAEiA+l1t% zO1?8fg*3@o*kU@-SOlFc=~*7dJ^H*v@I2{Ztk8I2#jlWc99Y*GI;6m`conTc$yGw;!g2=5tYGF#;w zi++qcsHu#tJ22}#)bL8v>BT$9aRrYdvcDOw27Lz&4$52l1s>!uK34~rW0&|?&9c)B z&`9Sx^xFaFlxvYRA~o45teR`NqnNfb{poKZ2e>Ky|Er{;v;1h_WpPLSqrq8su-E)(a7UI8{H{be>i%d@VkYTk z??;1N^oK;z9}Bs0^N4E4<2QO4HRw&xGf{kv+yIq#xq+ViRjy)QGLPVGe8#NJW6Ti(}p^2VA~x~#9l$!$tRlgs55 zS_h^StRm?$yH_o=e>B+Z-|9VN&|dm#>OUDg{knwldKc=)(1qr|VNY@_yr;O}|DFeu z-*cPfW1o>2p81|ro5bzNs6AVn`jq|;i<_19_=`~ z>Ki5p45#=u7B}gLLIo_uZqZdz zTZ>z|i#m{PYq4H7@+5z&>=jFG-(Kl$6zFZX=q94A#b-R#4tvth!Yk)Gpj=ASX(|zX z@!gRcvmJ@rp^9Wex}tegc8Ezdzz&&V`8&gQgT&E2^ht=p()4xdoEzI&G~Gc}!wxB_ ztyUfFES}jxHLr`=#Kp`+_fTobB$}W!M8+nX`WI-=M{YL+@`5(8trhZu6~Ne_L@_qF zH;n{k-ozY4&<$^cFGS%W&Bh{1BF-=CXjvfvK2Jq!@C~9@N@*FgL3T*Gp4%PdDGoE2 z5*!~Y4W$H2L!YDubJAy~r%-36m!<|;6)r{1%Ubg;&lB@i{qeEcXVStvjmZyTpXqLC z6WW|%bhm{zNz#bD(@XTxmv$DNrI!TqL-G6&ddXnn2~A=zv%NVzw^q8^PG~T<*1Xi< zyjmr-E2%2<$i^wh7qC<-e}N4Fn!^QH$5cMHvnUFz49LT9IBHkhS&Snt!^h>&L#f3A z;+km++f`n1Yb+)%)8{Kr<->-+f1RW5|7uJ3ZH>I(grGFT1a08Hj=6i<0#)O-v)C2f z8%$$0@iy?@;Mc*Bo>VdPbuc$AO+)!1KQ*FL=0A6XYiVvdOPv-|ST zW;`N^BXe;i=BTY!S>~VHE=S}cw^m^-zd@@3+go^TxB+Nm@1@i~yA0)2p~lfQusEQHB#Q$Z(iGT` zmZ!(2?fFr3HqzXe(p07N;tIpL6P5_}i95Se%s$@lTo76k z;>l`gH|?T#hYHd;;})bB*Wqt*ojG;5C{q41>MP1*mgQXm%(8Q7t+3 zbyF=c!7%xp*7zYbwZ0k@Oy%?CEi_f;P2w1qPSVU)jucnYSG=s9i8gkYl}wBYjS1Pr z+Ey5&Quyk92aAF9p4MqxK3FeKLO>4U`(h}x->&4<(SwC#dTKK z;YjFbGmsx@ksrbYvzD`Zg6~{nt(j#x<+#ufp_S?BYImW;mFZLKrvzCdCquKo*-TQu zy&Y^a`^BMsA<4wP5OY}83RCLN)O!vcY_d7uuiB0(=gah-wIx%*(%*JC$tE_nLWh%v z>V4M^7Q;>&dlEZZ2a7?0Hv)dOr?kLfea!VJ6?aUq58l-Kz*S|ZB$EQBq}W@EMb~Zn zLV4+ui9D_F?XHXY9k;rY{yI{z`yd8}cDt(ryF=_oOZ{$?;g@uQPEhR3M|?`hh^e33 zQF&I@J6KOz;9$L3zvZl=DW)__HDE`pr!)G1E4M2B>*kA1p#HN^~nmt};q0li`*|c*E@|jxIaw9u4lXCGJSL zlSMCjt&d<8&DkRO+%Q6>Ce)XsM<&XoK#w4o+ckQMv&Qb`1{u5YJ;ZUA$!^^Ycd{tc zi+LKqRaZ4lVP<|-mJiQDXA7@8=iu&UfO?W3q9>OlJb3f)Yc~D z2;Kz7gCBioi$a1?jqzfkkxUetMXD$L36w>z&=%dLkn5L1vr6r9SMl;t9GwH@YFJht z9FWGLsoG+NsFk^Ba3z<=4cg>LOZ;MYp#u&Dcwz>YCDcz6Q8k?qFS5W z#kzw0ZzSK(mRL?gM^FAI#pKrLgbDR13 z=50>EF7UQlM$5*Sn(bnlSyMrBQr4&vq>Dwy5W|s(5FgjZ2zf(Q@8{l34UY8{7*c?9liDN(fCFS_s9Pm|Ip4$2$4K~jd6k1~5*Epq9-EP|1;-)V%p zS`-rHl<=q=`M+<#ZdPr;^uHK$sAQ3i268rK?|N|5f^Ee2pvvKZAf)SOH{cj>MP z1_nn5X_n)P&tkH-mv@nT4vPAoo6MAa$CZEnX(ny z$=YU2#ov2XB-{9A=9rXYr+-9-X4AkWu4xRSC1nc*tH=Ht`N@p;vhZGzFkTo;8-l@s zAw0v_7c_w{h6g>t@cw^?$waGcxh z9u4{t_t^djX%aM%?T_iGov)6W^-h^)GZmrP+GD2bR;HK53t#E_t_t*v>qLd_W+gbV zQs_z|A5?k@SV{kcOVfzbe?h7#`L7-vZa-4ZSTCE(&>sU;mymDat=D1`I)NWDfqVH0 zS;+Bqh%q_y%)*|-JlZRkWvXWqtpV6>u;r0Vn%bw zuPI8Qh1Zr0ZDkx~j+03Yy=#_G5wnE$m~_5w;Wc6k1oVOHLW`@KFoi;kk&GI{M4EfX zu*vZHZWuqB&T8D<+q!3AJVL*zClS;epeotkmccmcY69wiC>L`ls z6lzJQ*!B91i;EFMy?gmqY?fpq%UvAmZBehDL{aZwEPZc_hWAQ_CYY(_6}>Ig1$q=q zWxM!1>zQH(a(Sbz1-h6Xe3rC_>18C2ZRSgczBJ$R@aJ2;cmE)|*wNJLj%KD>V2WML zukUTGZMqYkw_!6{aNZ)P@p8lly(nyVU7$Ukh)#c3eWU zbb=uSa`m0Lgu$tDf8Q98(YOWG1Q-11S5_e7L(pn*_xk_10UOJgiCeY7Q37QGtuxhi6BpU^7y|ewW|@l%j%72)cOr2t})^o1Rei0 z&@Z^3#iiG9&hKY2NG56xr{Wy3zx5p_I3R|`PZ}yiT`;P_x}G1bi8{c-Ya(GZ>8-|b z3^tS)8uq5Wi^uTAmq;8ps4K_;7JvCGtDwlLHHJr^;rUfC1FhOwdj!tX=WQ!6gDkuY z$UMm6f=hkCVFp`xeC)F6AU#iIP(R2)?le`y>Cya9hO+{=cTlKSqHcbOE){s=Sm}~U zyxxKxY}Lm#GXE}7XQHYSy*E&-tqnT$vT@Y&%WJ)ULMv4f(EVlhb@~a@x!^$EW2!9Yui`5h z&bW+g-pfeIQp!1(`%!DAyqe3-mn#&Tt&tAH46*P|tN#II};>tW-W9RX* zQ`95+$~+x9hgx`ll`#I=nKNo2PmLyo4Gv+qg;tJP8(`bEeBzf?Tj!abMmfEdnG4?7;icRavC z47WJ%NeSc0fm&|^Ww4fLxb;0Z47WJ@Jd=pKm~oByY@N+X|p*NdW9D&Y%TtC^T+9rOdCO?EYaFeVY?OeH zy=E&Z;``KOYFX&7lq{u;<~8Fm*6?;&7$5S&O#~y@Il>ra$8zu)%N`U~?waL%Z430q z_#19+LgVKojOV7S{rz-V$%$~K@F@UnklhUJmVCp*_m%d$CkzV=?m*9%jx^7b)D z>};%*4XmsUGKUWfVGfV-VxF>|n=&_?+c;%vK;u_aL$Br{C8%*5t8dO%C4yI1isgn| z(}`e%Y;)mlGDi&(RqsR2yok$Z){?GYrMnb^5#nKZVPmOaC9k0Ng$uDSDJY; z(&|GW3e?NucQn%C8eI;KvhZGHn-eJKfnvG&mS?c|mS;pUkA%z0RS!JM;!WA7y?>O& z6guFHDE;2oec<)hK9DENb$vF{>vm2 zmyw~dhOfMiB^_sR-Jd0lKa-H{$#|;|G5u@^6sQM#%mzCRnkzbxaz9Lr?5UaKL2KGV=s33aiIXKqnX z34=L943=?Jt~$((m5JfT7bZsZ9%+~qp z?g!(|YI_)JwH*hF&?0Ke>&!OJ_}k2C9ZEf!V&PrSsv&SQ`!Rv*lMBY{Zw}=1^vjc} zoP(3-8=5#Yp6kSMlgDG7q^mwOMWg1flBj?a(pO>f&VXZ(l_=m*N zhOe-=SiZCwst*aH(N~(j3&t4KP2CKOMopN0H3aHWQW>j+XIi}>f~>*mQiQ=Yst$XK zy(H*9LtK&-aOcQbvuag1h?r@Swc1cKgjsq|0kZ;y>KOwQLX#aZIaKa|^3V>t63t2P zq#i_PS_~2Aihm}p^S{-xZI*?nUKcW@2$^mrOWdsz!>yTG%Gnm)*%HRtCF(4U!SlG% zE8!Z}WB7{OdA0r?D(!JkiRG{2cX2|BD0u7byp4a4Ni@YhS^S^%oW#(YtM;Z?E*sTE zWh0k3G36gf_ibS;RA+KoC^rZ7o11Ns&zU8THS)0zHXqAg;iZ_p!du>%_)fa9g1VW3 z+#3$m_+b?}L>6;`S}fhTxKY#^y?=06++$Fx=1iHZL5(&mbOx@bwyM(|b<;4`iw{H z)i?Tl4p?mAP0>^c?GsKpo0hY*D9?L~*gIY8P9a)JP(@_zGrjvBl|)B#w(!KfKuD zoJNv?izjHKF1EOJg7%Na7WYn2+mN>`yx%hEw=B|*r}(!myuV2pf7{RP(tacYB)@HO zc1sSEZ(E%IoJ8^5e!aA$^r%PlXw25V)Y}$))oD74Id#UcOm{h%D8MvG3?Hh_BvW-J z`h1a!tZ;Pq+;E;i@Fq!q2*WWB>5=?oa2#f*24`b`3NN#gl%g5>+jD--C(#fluDEyP z1FhD#E#8qGx{p-QU59(yVjo8@$J>TGsAd|iKfx9XzEjCty@Wh7S3k_RExMR4mYamP zE%r)Y5}P97n7rT0Qe9QOZBxM%301&~0!=e{dTa0J6lg7NmR&JZqD{7M>CL?*7Tz_i z$0Zi&)Qi@?Mk2VT8N1{Xi>vs`N0Zyx1(w)K;7(OFFSWX+^%Qu4F^Eyqo23?YPnI|? zx=}LF{1J&OQ^Q6+@Z{fpfOI9@*peUqa`#2yjKMlJGkCtlaQ-rW*$PqjxphOd28qIgr5=;%zwWQ=#^8<7aw znzpLRTw#5T#;V|I&!dzmW^(rsRg%Lf=rk&RqKaYa3RIAozG57((!$d{I)U?;`IQ#+ zRMWf4t)V;x^y&CiOg%k#UTsmo5u0|kMLj(LT4UjzLkxjW^y?tx4pr7zoT8=tMt;&s z_$Oa)yT+oU>8;&#jm0xBv&S>qk&@a{snN`=fH zOBBuNPr_sHc71hbtwm3sC2+AnL_5P;ixGU(K3XRro zNLTfh+m)uMG%{VS!PPuzi(-@MXLWc?#(mdpQhF7F?rDz3;Zp2r3N>z_OG7i1HC#u+ zckEGks^(Oq9gbRO{bw@>T*FSd&f>;q>_zJ={y{?%F9mwn(zRCDBhXaM2H&+lu-+o7 zYMD}}@7-YGJx|sRR9Oz?2ewDr8Jj420qxA5Gy)!p>$$=WqodhkZKC)EY_a=kxcLJ1 z2Y*s!{{}974|3P`UGOI!1>mRPo;0o94Hjd~cq1{4Hyt=Vb-;Jdi21uf_n^d35-bmL zZHw~Ye)R{r!D2jPL|u%iA7kVT_%6_ucSBu_kuOwONN=!MfoZ(CH7!8l!(F!b(a0vQ zC1@9z7Ld4-4aX~%n5gDTO<_!e8@U%_-8dlT`2c+%D=F&9dkTbcLqZ2j7mO+X=BF8V zlf`L`xY40!Vo%<;c;Xd~bnjbqqu=3(;(GJ8b_~;riFvZw!aWT+KsN@PEzY@)ha}ex zpk;^B^TgM9^N~w*i$zD;kA#)$_8jfmTPzOp>8mhy+8`J?4=O|~*A zDW>2jb|*hI?6f1P+Z~AoT$o@1{;Nld%wjTU4fL(53e1NVUPGEw`1`kpjna$qLv$RQ zA3~$XDS^hbOl8Pp#EYBINllZTI(~d;@j|h#%|5cY|7i*1={>r<+(S(s9Y9-jmqKo3 z3WXH5BbAuPNTsrt)V2Ns=Oc@mQp!DbjC*S3C4|b?%EkFC* z!{5I`Dzl6gvPw-%{F1JcwGvXr09n_{A*wBO@9I(PQ~%-{WeeSh{IS&~rKiB-62s$v zWBY!R+-GM}9`I=ys=yGtI z#cz6uv_uaX7i51J7o4u{l($(F$Rf7rA{iItEh>+=etdQ-h7v;@FO+TLcCp0pghFv; zj_)GUd4*}at7OjsNj*cOd7`~EQJ0-#yG3TMQ2(~w;)Yj)VQp8u+S+MqbgSb)Pd>Ns zuJJiERR0=A++pGUPQv(IfsQo{{+@nV3*5r&i>hP>Q^`B4k{L`T@2W~>FqOQ!Dw)Al z@}8<>22;s( zti5zMTz&2i@R8t_o_uG~;1zdc?mLU@S18c|F;BjCYIa$9WlrgFyse;s3;3A&; z_~tG-z#pDqgS7$9=%SNq8=z?yE+D(eE}ois#GKoO7MKo`t(dE>k!HBQ3(tuhI#bbS zU&T#JTfobdF!Jb~uL#r4LLT`qM!or7E&XGA&r zQtoh7(6j}bJR%WfFJV^I*Jnf}Pm+0BjJCiGnM0$sIc6bylgeZzwFN#iBdpJV&P6`p ztoYgj7d7!QO(#;5H#0!2&AIbLlE3=k8N86w+uu4AWk@!x&WSEH@ zDk7s8r8#Qqaun4RRxz(}Kd)g&i7VwDuH!NlR3?`rKdpA4-kc6%09?X`bU@UT_CUSU ze4CZi8mS8vK4sVF$&j9eWU)ST0qxrD2DkA*UnY-6vt(R~q1o zqa$!yT%w3`B+K>~zAw&Pb_6bp>kc7asyhdzT^6UlTbY|G;_%2f$0bg>P0VmHUY*Wa ze7Y&NJcTc|`&EO}3GgmrGM#|>Pq3mo0d=0B@F!k0iKd=(1{y~tf~Z;>Xt;hU1;y1+M9>jKnyn6=sksP{1Ixhrt3uDH1a%k&s_4^1(gt>H5pOBjtA&eCw> z%Os4;7!GUrvTG%bYZ-2;;a)OIM~7~}$>*6wHqP7WMw9MSKxVQ zhBsuQ_!N!e-a|QR4#La<`$KnNoh`ExKQbRGD<$~>%w0>xmJp1f1SS5<@UEPxQh!M% z@}P@mj{t2kJ?R19LMGM&_)S+mji>@$c>58=9Of*LP^Z%b8;r~YxI#1!sSX>C!>(7Z6c-5_Yc}Dm|j- znUy@=S!p^@>8z-0=cu$^z|HNogY^O~$?q$~NA z6@ur65syF^HHDaQQeGJkb1*k4UX3Fh`}o)hAT$9d-1)2EAEys zn!RQd2V3E_Z=t{6B1aYYy@7weCQ*l<*xDmvpc!U4>-Pqx@=etUHaSho*OKF&e9NK1 zb{GXTeTZnBMTUZDG}9aS>uao-W2fAS24^&ZK4u=faj_IdfiM-ibn1=NY(Df?iQ=!{ zLI=LyI3j$E8u*`)tFIibmA&%4Hh*s%jVq-ZQ>uP8TftTC41$>|aS_3|f+^94n2~DM z+#5ASOW`dsi*$?G8>wJQ61!;s?ph@UTY!P8^{B4HEXH<7OHcX$XR5xr4^U5Mr#?Xa zb0ms$Zqc1~AK<);*tPls_0RBuda7*h3!I_K=6(R59QFvD`V2GD54hqw7P=oWo;K%U zl*$3o#!ls~h&EEjtEQ(PaM7iH#0p{sP>rZfYC5S&S3|9csqf*0J?Rg4Pe>R~Y*e){ zgHQ8qZysQWl>Bd9@wZnKx@_e0er!Yu7hcG9?uAcF6w02Z*kedLS8rie8}o8K8)Xiv zX6RGdt;@Ooz)Bj-$26VowB&MND*ph$bIy?oH7Caa;H=I%3=(xD@$|DKlEU%(Z$;wx zR_1Ur#~kyXcC{)|=V%LhL>9}Zx^EZ&yvdbcM6rGtl&NVV#X! z`vJHD`2cHz4K3g89ob#)FL1uW!!#r3zh`A6fXi6 znOrLoOvh3+xg zKiJrH<_#zR@?Ba^soQrh9DK|q}g**`A4TkCl+(1>UCmrkT2){|nu>#D;Iq3%>` z^i{n@2qnVndm#)C3=1$pGPv~sp%UHP6$1@lVDlFPr81rCndzoYz$9)%n}B;xLn*V^ z^=2t0j5dMXfW)vMz^l7q%|mCq+$MK7%k6sWc0Nt>Ls)OiQbJ|6Lxs_Q2Xx|gGo~l% zE0XsA8}UIByB)iHtftY$6zlh#n&;1XfD%`7d*j|mDh3YnsU(N}%6xCQ0gmJY-y6N@ z5TONaG5)XQ`Ts_}{2xza2;iOTx;&KwI5*eU_P4K2RXYSImSSp`iaE-MJz{dqAS2B% z$WURErKmgq#tC&OQ12#*;-=f!uWwUp`C&l4Kan^b@UBit>*~F!>i)g*hN&caw23^xfmC|IKF1&x4#5dsE<%|T4J|Dp<+IT&Jbv&GS1pDM|Bah&1GZ6Bq zN2ag%BySjBkr2k)W~7d6BY{zT*Z(n$rDEnWWgSaQ6l1AxiYn$y-ua8-OEbv&m^H*i zJQ)ReX9ge14?UV6!kM`#`hwBm*?kyIo3o-v1GhJoD4Mz@`xr)(FwVS8ig4MLBhk~z8RkcCIH^q$*=qD3j(T$_lQ2_Zk?kh z0^X&S?Se~7rB=Qh0#x}i5$M5J)WV)j0^Gx|rMkOv!5GdjX2>0ELW@SR?lL#`pEiSmDGdx0rU2F_DGbz#VGl$s1o!!+jwU>bHp!dMa9 z7j)D2WS}ru?7UzW2gf;JTyT*d)J_K4@pfKI?=Y|zHMC*Hek-{q;GSvBHANk_;tgV}M;{`O&SiBxfu6DY{Zn z=(wAFac7)C?cK5DPL_A|1DjpBWzOZb^4vf--Q3LvBE6Fw&n%%?_7b7naGEs&uP;%1 z4TsA1$iCHlY9xYB%}%Z@c3Slsx6gKlM9>-BU}fQ3{KVi-e0eQHV|~h48Tiz6rcU1R zW#U`jvWwz}O1=&%Poup2I+roCQBAfMo2{xum;-nxv7YB9f%(91&Sed~3Dnm)nC70@ zo-9o2I~D?=%gAvgun209fGerx#xz=Eg}oZEi=j6zaC)Kc=@$ccXOR0X_~(iLp{nOC z;G*ldq+WJXoB=29dW2qM5-YE zKHs&^nR#X?Vsh_&fA8n>{xS2cwf5R;m$U2IYp-3a5!*$hw>YfK0q%c%HrfMnP~2D^51EfhYuk(QvP#4v*Cm%o6ql$sCu+zWDeY_~XILSPToatvQktzL$j@S!CQ-Q}?<^4weMu z+Y{{|stUUDcGg4@aN{jpBoqPlb99eU1T@hZstEscsg*0pT;Sox{7Q`%R?x9?F3`BS z#L=8@+6`%Lk?Mn=!`4h!xe}bwE=K?SPEhzZknyZ?bYp3Q5D;Q_nQ*@}9iy*p6 zN>T(ZA%R}PSDI^~zfdJx3&Zrcz3p2dI%>CM1^=a*P}XS&XKbCC+JjckZW92+7- z(j<-{Y5D0+@`L8cPamBgWM>08qSz4mR^L6H3%r{)JdIywE^rJzXnE4Z9Z$O$&ErOI zzYfN8f#crs@QUbh@01tJOv-i28x+>nDQ~pTmOc--^$8YfLD&U3N?D5DCJEfeAt|DG zbyY*2f$wgKA|+Z1x)89Qqx4Uq?!hcs1X#b5D1O&PHDU%$;CFnIoX*$w+02z8#|^nM z7N~iZM!xfRl4;0|uz=i|N`56*c>RkVEms!U5%r*|&nX*GsR>@Mdh?fwVZhj@bl zhwO2VWa2xI(rRh!KfkTo1V)cxxg4?;RmPYyeCd6st1o=#<@sEOAwtxx?u)_G?(&9d{mQP>K@b-mI0Tj>TenFlNubI zmH`*m;NoIM_%#fWuoSqRJfxHW`o&05;~)YWw9?eL6#xkXT*>Gw!Q-!nmaIZxNJ~H+ zE)n}27qAAXaxqJ{7O-k(MDxU$MP=nhP`hD; z6x1Tip9{HTE0iAWtV(%df{I;>GlVUm%io5UtP77Uo??Go&pkq!lq}g`4hM15=ybAd z0A|Q$S`%%S+;lD>a?`t~Q`83FBwd-t(9OYZQlT__FDLoKns6O z8V?XOk?meCJ5&FRX>;nNYd@*;|Af-C$Jz8BBa1;zT`R-(W*nR_k zWW*nrjCGTgw6l!}Je8cHQO7Bpcfx9+z}W_lV%soAr(<&6pOK)-kNtwDulGN4L2`KW zJmD@aOP8;YjBzWLkVvr@ujV5XV~|?tZUoxXWGJRV1-gL3T3xO-!a+GIoXd{NDC&Yo z*~{6*z{lAnjzbt@rp1laK^5p>*G6ETv)T#U=xXN!czi>j>*tNYBlRVY`hRnhBbsY^ zMzDgN>?vx!vJtpimoH~vFCRU}ZmU)k8<8sMTp2i5SZ92o=Pw&^S`~eaZ5p%*sQ3nJ zQY;owmZFDFH5y0=g4YXjlDTOW`&Lp*G`V#0uu%RWzK$VM_`R(C6y-$tMw*y^ig%-td*+;dDMN%*MSWlg{19Qb( z6y6RDu|KvsVX$$0Y|pjzZW)*Om}~EIN-&l1zs38pv(aIBb^w>(ObhpVx_I9K)Ov&$ z10Q*Y|DUPHqV5E0sB&!;7s?j#JKM5Hv=vgCoP|;*&MqM1Zi(UUcI=rJ@!rl@RfO&W z?t3a644;aXlpnEnC_yyF|18@o+O;=S!DX_x8?Y)%6qP^I71(Z|^2K)YRa|WT>v;5; z*vVHdhF$v*r-j`JfIYyqzhb5B0e?|RDqvNSl<<2VW3;{D zdXm7clEAH<%)Mvgsy*Ke+{IH;bQ0c0Xo7qiCPpm=M7!$ZZZB}zT`VIbl@oH-xcepF z4stNmUZvnZ!1}#JF_iwt`eF=kGxo(Yod_+4)_9+np%r}aQAXYm{5+R7gtJW*TBCz=t)8cVMrrOz6lo=b)+xq@D{|YbJ*192%mUpg!efPMDeZD zm)6AS%i}h_^9)#z@TU3auFbK_;BhFPred$x6$QijR5Z{@7I3e%KsIWgHu9cGrgC;s zoiY0As}i-9w9x)s=bmqXD|F)j2B!(k$A>!qo>TUFRLz@YT1%Od1x3>27T{a8!1xAO zN{}x|18WGH%$F}rYv+9(PR@|6rOw_k=xiw-0<3YYuswaIh)hp7a?IGZM zyyx;Fq&wD3=sz6eikH|g4*@ld{p}&(rg3@<`onRztJ>KjrvXj+nJ7vBFi`DjX^5v6 z>cV6p)@#)t2J*!`eSa8uI?kqX82GQhb0_q7@@88k^xy$nOu?|z9tH~RnYwhGX_K>w z1`o5TN{V8E()gD2dGZ|z&A^TTm+IqVFLH(RqI$4A0{mLlWDLBN{k4R06sV%l!XE|d zz05Z-SFwFsa!i|e6pxrU(}~uU1ZsX}hNWY`mD<9I8}=N#qc+-OzzBP~9S-r+?WE)J ztcdH~WB-2GVLPb}>p1Z9TO^8GdR;@sff3861pBEY6$S9;uKs9x10L|Ub{f>b(PxI8Nh|$0B62q z6b#3KmguRbyvKn9UI*1HE11-!I+ON4@g<5+{3AZs>=S;v@~3*#cpO+7X_+Q*biHK$ zB~J3tTbO^zfS(f3#QI(`{}T2*62tcQ^(Cz?4ZB0DOWR+Xp4{0iFOqD-Vzj?>z)w?f zmC+@Aq?#lh2fC#1PN#NB!TqXWS8z=_M`;E3r*niq4&+^3aCHDKxO(H&%*Dp5OMAiI ztG~ZGtjzDPey2uyatm{j!(6;mqt`VlxaMNX&yW6GqB#2V;$PUDmyQFoDlg!qjl}zi)I{u7+M$7$4|DRBua~T7tG!Fu4Y;+vhU+GvSiK5%jdjHI!-Ba(M zt-6AM#vJgodA!jz?`Ny7;I@9~RYR{*7uCms^_LyEEUdu;mmR(=J-L>7KlWB$1cxu{ z__GvTMfIxsK~>4Zn5sopgL*2ex~Hndv8O6K<$<@*@yfARIt0dE`R>mNXuRbYZo5#T z*mfbgIycAW>N~3IV`)38lZ#J-9tSpF(dJ6lZksDNR}a7O*emB=8Rl^Am7iXjp5zl~ z$)2p;Pp{luJq6d4`u%*^&n2eduYVo1*^P7x8aK3ak7!H0WgO&8)5Xz?b1w-#{DIOI z+}#^To^jp`&q=mvh8f-xonMawD=t2&%0vY>T^z(s5p#~X=LkCN?qSVt=)g&_qzP0neA4L|uteK)c3RULYv#4%JqZZM9Y#{Xl7oFB{I z^WxDL3*VE+8Hb8Ebug^_*n$7Y@^7p0Z4H*-TLeyQzXdu-Ax*am zWxm+S`W8vos)O5afm+5H?6<&U9aLZPE%3BC**kO0^1@D-OCY%64#~nDO(cpPJh{q3 zj(ymXEF5-5Q$O^cTkP_Q5x2jp&ii{qJjuc|Z=s?Ky;UCPh7-UqZs2Hs!?)(r_6gvQ z!_@a3cHVQHAJyeYo&Pw|)QGU5bq^BtVZ?pVjl zIN&T-*U`)UReHE_0$4zgl?`y%S?(JJ@ChKtIZP87rcPr5Ek#U)6IH*{F^i0*m;u;T zUm!gJ9Cqk0R;gY5DL!{_Cx9+~Z(Zb^fEI%-;{@=tUj(9l^H#w3mu{+`JpufMRvId% zyEBGfq_guP7~@QE+`zTp380@d+o6}DGu7cbIy1;dOTVKp*=Nbh`&g$>8k_*W@LGl$ z{KDJg1=%sNH5VzoDPT8ujO*r^ytxzHl)UwF*Q!?V1dz{$P|uQ+Kw0MrukpOcth8Sn zTH;Rt`{g6sSKTg`sEqZCw5&WHD6kHGF;pT33qr5Cjg$6C;I3zw?UTTm&q@YmCEo$og=yxxJdc+y%+)tJOc8RW zdr27#YqgYYSL>ddZ3?O`bx^&!)J65~n&fvt^&6R=?|_=wEXsF4lYcP_Wba;@^Y4Hg zTj>3h?|^5T1RMm=L|f7+jsU~C>l!Yzv<_xbtrNv|TW{mxD|?txn=0oRZp6kYXSU%? z;D~*~=6>LWa%NlPoTa7|SgS%B;Vb)u>Z?uxcc{yTQ$U_QL3g32fPwb3Fn9zlHDf9x zg&_pLHy52O0$0^!4dl3}@&L!Q92a*zK*~EVtUD!&JEPJU(dQ(J=gd9AcU;tal|A#- z_98K~myxPs8YxqVPT?s+rbVt{yrU7tHXNc(l+Qdm7aJrm`L$5}I`Jl{Gqz*>37#SR>&h23$S za?MKqT;+Z0nIP7`pS|t0MQW(I`1ghU`!Q2W{$s#l_AqT+EnHN)SK_#Lit2-=V1a(H z7B2oVR>#j4E?!ZSm$BhU(48OMV!$+-F()uzNxxIlB$DC*siBh28vXydfOp-%dYHf^ z+?AdVv<$lm-z2h#UUsL%aOVKdWs1ieOC}lCex9xO!bYRc~Zu^a$jNmtaV}0kk_|1RQ_>yD)f4^ytOL!CG&Hlkb z5|RGSNGD_(=@dD}aMNOJOD|8CB=DaJ`+sljo$|e&2;{nWm!7p6o~d%m)zyE34t9z- z+o8zW$ua*-sh!5s$_W#@!P3r! z#k0zr3-u0UI~UJ3<~qG`A=Ty<33oLJ$BqV#O=sW&t#M)L8LP5A@hs=7cJBG|pQqUK zWShk`enZX3dTFZ_)ZWFlcZVP6?pC2Wv-R%ppk@u0%#3JDf=X-U&d8V+(3Uo^A=%zr|s=tqpZfeBNoCO;|aK zVT+WfxrlntMaI+Q=@fojbG6){3EUi9EjM`F;FT@nvA=R7^;a^~Qo=}D{zVo1ZI+j5 zT$X43HC^q$RAEi-6!gckh6!|b@n7wa#7`Gwv4#VwNr05FpKQt1`fvWmA<(5X?~4dM!6^ZKaDh!N3spA|Jy`cIK8xS^>z8UP865jqAKiuF05z5_WI0g;YF~1 zE`IWwG{kE(=BjN;e-~F@$I*0vi<{`2D7y!@cb4S4`d$uxJz6h+<+~VTkK@7G1bYL2 zHrS)h3OL`zY^n`nnC+~fqGN@_kmpszBbnj%%B)j5pw}6lY8jI6{tvT#BI~qm<-6Dv z4A7^ShK~WI6z<k`H5MWV+`16|zrYl-34?R3x`Qvu@keORK)kXQ;8{@ok`T`{_@#T@hcn*mC!^9 zudtN=Y)6$qJL#oc96@4;;QQxJlu*tC|gN^PWMAxjOG*RQOvPh2AlaIE?#|4 zuTBhcQG}MN7#rdu@w}f!8`!)Ep087))Ix(-OW{}FlMJ0rRke^8!Fy8RmZ4ab)bu?% z=Zu@9-uL7^$;gW&3;9k6|KUAZEES_=NQYjgU z3L{)R@lSUDiA;)9*G&76Eq!sw-r`6UTb$!`!*|?Ssm8_R`btkR2pw0YD4|y+3$OA5 z%_TTwABAM$D3-Z)@)9g_S4M0OqhzB~R?O3zHq~*+9tO$8F!0&7>X;5PR=3Fbot@9b z^PQ#YNo1+Fj%T~;yszp1`D?EW&jysSo8QfsIJ)_Z`Lo#9Lk)cEuZ-~1O?1)I?d?h( zNIjJ>2$wdzD-zO}Bz3m~CKo!Xldt$ign`lo9sLE6>;mIzn z)$FmWrIU`LlU@8)A0kfKKtCCThtP7ga8oC@ycPjc%%oYP6rU2tE0v zMDgUCWSi>Z7f-TWCBQT{a1vNhRr;c!>Ii*IW15TV&q@r>7F2bT2b!Uv>ZGcikfyoV zLrrVkBKV2DnVxMn+vGabOliF%8`aCo#z;<8o0Io67gH~oc>yi2XI?Op)*2%#O|C?A za;4Q()DmQxiw3WU3$@q(S!(QQ#CfWyjG1XJPrlcA>Mx zxq!}EN(WagaR#9rP4j*Pt=)*8)6llMwcA<;WnS^oe`xJ4H`7TeX}PqLW|10ieJiK{KWi{zt?<^1B#^3DaEksxYzwo7glLMHSXm2$%kADvZeRI4yHG zG|k1~NY}LRQ+G}4m1fMv)Q$_i(nh4G;97B(da#_EA$>77V^xMku_|LvhLb$m40|$$ zRFo(RE*N@&#PLz34V5I0aXMRjd!X6L2w-m646fl{Qpz=xT05rGm;l zo|@+3&;@NO@%ZVyo8Yu#V4At3S57{e%Qm4{W91cp_fRhh%u zG#A4vuOOvsmF`s}j^0%Yssy9enFV}V=~Sg4-4w87qzXo;>2Ba=VVdr;rn`9H*|3E_ zn_sGm__qSuO1?IZ8E)Y3WSAy0!$p+`d1CqCFk>BmdU!ACrE8ZDT~zxe8~2AUZoO6F z+?K`h2={>5mVD&m+DF3NKGL%^xBphaJJM5gJJUtAH~1ZAy14z1d~d0px!*C%g>@Mf z0rj{S{Mbd4MViTvT_kDwpJ@@)2UnsB&ayk{S^8JKq0-6 z(trPF8Y|+@>Snu>bm!{0J6$Em@u9R!W1#Y_7Men%uU%l%Mi~Y66yu!c0D8F+MX!{! zhPc~Zmi!afxOe3WvlpCXA0w*R{zkVDpSYOjEY}N#pSalW=5pbe>y7c8WZ&i(<4yDe zAxy&8jZhvmp@?zUL(f>BxT(pJvs`A*A@>|X-}IWBI! zgKLO8Chid@{Jmif<00?)o=C!fbl|IM+r z$i*+8WnIj5Vbx*Zo$I37zt~LXy12O^*M}vPdEvQjr>YzU=DGOg^Ag4LovND0D(1QP z(*4?Xk~^E>Yj-?1ALG6K>5A`9KdI)*^IW{^E%qc6i@nd&__aSz+mgl{%yZGvTda>t zV6jG9+sq<0U#TlY>Pkc1!6`Sc4AUxnsH>EDE~aEm%aAyhWgN<2FFur!S5e}~tN3{p zqMuhO)`?@Di@_BOE3*F>~QEj>%<-DdFb^KXZde<6o7&c(scTD2A56 zE4$Tci9w?%$8yCV*i1fiQR5H%ke|7@=@0AzpSkEGQ+TRDuO(AthJ-t{&s@A|HbbAe z=+D=xGA)9Ie5@o(!EJIfjCoRW^l~JnFe`I(DP_;y!ZBW#ylS`nYQI3WA& zK#X|@zig3SEh$YYw8-A1HM+!w^(Z}ksxR@sE z!f5NHI5k>tno7l0!iO?Tf~X7F^s+5kYR>gg^-=cfr7o)KbCb(lSk)xTqqwLQUv9)Y6*x|EXH$*(^2-31Kxn@b!^=%sh09$`q=q-D0i$H1W?_X3 zz5{6mKth44di!>TOCW|Cw=wz(7x%uvR%K!s#?vX*AIk)-bYbb?-AWhLAEca>F4`LZ zctd59*vU*x;x%(R^KT zIC!;?-du^HH``xKBTTWU&<)HKd#28Rt6U5trH%%kIo4`Z$GCB^uJVZ9!jC{e-KMN| z@w0m*hIgroLb{*P(;-P)IuzgRjQokttX>(UL}*$r~xRY zOiufR%O>Mu1)rqNPxGlJk8@;N<6^Xo=K+RA_!C(rPO@urtWp=|U7I6^_i{5$p#7QZ zcGtL=!D@^ub-#Vo4t#wbwL3dLjTWfD=P9jf>g-Z0ZPR(=0h^$yzt?M@`^ye!jIX zDpcYKvev~VSF&ttT|Cl2V))xTI$EuD5qn2R=(R2y{DpE)kMp_FkXs7GD3_FXboBUK zQ{eM&nnIQ`Ju`$WyCLk82_-a}uI=J;7cX?x&Ut#YbuKJ@{idrFQE6MmFA&9N_G;Zg=Zva-O)7olv`WJfZihP|oMR!uZ$|$nugNAy_R~f6* zcj%QY@^zIIV_T-4p1dS4lB~{#gCcvRn-VBryd8NTdUTLt(s}sgndF$;1`e5cd7px_?{eV!X4RYUMpnXLZB0&c&C`PDkot zk2BJhdKl^QC9_&M=8ueU*S*d~hwurctz5!mt2<6x);bsOq7}5Ca58U&0emXAzER~4 zKz}zakr$%6WF8bhnnHY;|+J3Slg!k?W20)B!Wab116R9KW?1@UL^x)t^G$I;Qv=`K!UB{gv8x z%x9OVeje$2j>dLgP4Wx+p>2S%X}i;;L2Ax6F`zz%rG1!2J5V@ts~bc9r2^L2j7 zoNtR>895MPmt5zjf}10UBJ7SV`+0Va)@gY<6tANhXGtqOQ^oeBwlc0|Ia6?0@9W** zy`Bv&=)1upa9MT6+~A_Rn&WS9%TqQ6n)4CzF~8ZAw0UWaLGmAkL%~2E&S9Vw=@?Ly zKB@u>y3xgDw@O`n%xHBj;#b|tzOd270$C&hxJKP5Ac=eq5c!y|7aKOZScSpbK{mSB zhb=BY)y804j$*IN%gQQrFGuP+*y|3{Qbu#7g;2WwxkijaJa4&@DXM^N4fE(lDN4@k+fWHUGXYz)C7=gt#S z*75gd7r#8Ao6gNNp6D;^44Yldr6Y?vSRg0#(#RGUnJ-iJ7oqv`QeFFf;o@gY%}W|z zxVY&y?nrKXi2omYhV0v1SQkqa7k5(jZ7wdqlAHD`ALswapO-kE|0BUaa+I;i(CCjG zp0~NU;XwjlhB{s&p*YYu-CtY!qV{x^TI}Mo+a!*s^)~HvF4{1i{Wxkd?6#*FMsgFy zQ`k<&x4W>OX2k6-F3XZkWEl?&ukr(IcX4A)w~o%uHE-h!Pn^{5$qJ#Y7fY>~Ud+`e zpt8A`-0tF|}lXr87sj&hiB>-r)-|-*6XqG`)p2U&?uI{?6o&qPI$Ff}L`LI|NI1yMfQ&C%BjH z&rP%vyGnQU^xK_QZS*;wYtspEuH9evO1oVc@4UO+z&r157h~C&;u>9dzA;O_a`Aj4 ziJ{RlUE&!ufkvdd~#Irs+@HjNfeFdX*2!G#YSx8#lMYcuN`o&3u|DB5%fSQ zl<;Xb6v_lUL@YAC+)7un`&?XcgG6ydA5+*L!uPwd-VSy3Z+9(CpsRFnN)y=c;(Hm+ z?bL9)*mjaT-?m6wY;V(@UF9Py5ODP=rfzR0P?oZ!3angbKgR?1aAGsD8K z3QA14T#5N1jDiep?2dN$KqD(cw}0H2Zn2APlS`Tvp;*5ci>ByMcT})BRFqYg!pw>* z5%iUJ#0yf;>OO3Dc1-P~volw(hA@kBolgU&m~H43^HYcIqZa*A5FMiFm7pfQ5LL0Q zq?e8-2VBITmrS(r=j)>RfQth9CQ2w*M|YGfbv3?f{13SJ-d*akryp?fj`wN!!T*L zus-$rhg_O(Mp1*KYPLoEp+>`(xbsKK3SS=!lCa)Nb-jfk#coECN;3yY4X;)~yo7JJN=mZO^X|BiAbG@WinjyyxxiWIS?%GNW zIm?hQG2|>OAZHqKbwkcna1`nAtnQRSO%8QvXyla!h$An$9wkK0DJcEOB zHg?z@9mz&VhYYne&c9#Cd!Ay0Xa4{Fdiu9xu5?yAbbN>}aFo;IQRD`W5RbX&=S-#v z-()RjBU8+#XROTcoqp=w<3B3J7d*J6+mrB2gg`gW>fPjss$!1VWA!R~5D*UnD&vUV znhmM7)865{*xnfnofwb1_(L~7tZO9HHWK*avWv;Gg{z@0@}(Xuc5?wIsmuVrP(R<%g})KeS&Koz8Knq&RScbB^%Lt%vU#_47{9U#t9eIE|(ZCzfLl7T`h^> z@h2q{&&DJZv9~3LxAVB9LY~agJe+Xx)5|53mq)U2-Q$vpdT-mwMtHl0-l=VY9khkq z0oFhxi|jpQojyH$(uFleYmCrfJ-#~W;-~NETI!^WYK5wGJ?Y|#PO6pst|SH_6XUy* z7~hq|_^u?zDHqlNHk|=N6B110VRF<``XDw}?vRQ8g6)^Uy=-VP1YSF6-exm++~Ad!@tW5hK8E_*LA6{{QWqJl(`joD z4>2O&nfK7%SqeTMqAb>HWu=Uh^;CCiSQmSi&daSm^mRsS+0Kye2hLJ105nExOjO4j zZ9G_aNfdXPx3mqKAgYyb@VLP%TLGSB@Jm9xBzjv9R`3!{cb*D%Pv;A;*pRV_R?C|* z`c>rgOs>wV^dyw0TU|okM6%FLx_Z3J(bemx28aE;Z+w3357TnfN#`>d{Oz8eTY+1Y zwjQc=Q!`9LZMgJmBi+-v7FCu^ivfvlT)B0V?&(f)dUJG7pOr4z8uF>Vm>Lsp9dWd9 z+H)n`-f5q1C!fNGjBOeG-Imd>qMdw7Bee4R>7uZ$hpv(N5&9d6v`BN3)0%7Wo(gR; zbW6}CW0(PkWo*dcn9$Zk=d^D0Z@Irhn+!WyTUq$49C3}_Cu3Mf0FJ6KuL2G4=2cj( zzsoDEt`G#}rxj4nIXKSmGmOC$)n}*FKCA8$Q*~LIqa$uBPqI^sz1HpLarf8O!xsc& z!AGivZ~XRpu+`QxOwl03RP}Pd*bjuB^+U}{UmJdzwdv~=Z0EuHr9|<|-MTC$T3-^V zzndFdOYw&!j)w}lX+a@!T`qQWU4}O@;q@iU@L!_%3F`S1$aP1#+7q|96ZB0T4cx-O zXb?!ZlTdY;jwemk(Qcy>(T8rW}^f@&6^>-h-t|k@wWj zrGp3S?-Iq|^LVnFXHQfmvY{qOrM^JP35(EwSklRZ^$&@nyWNk4 z=|`6%eZi+g*y9+wNmz^kiffD-n{=%0< zG0#IabyGFU47+(AeoioI5qzbE%`TDWVJ3~sqLy^^a7}^E7F|8uua+C7q;4Lb%{8{{ z-8@{Xp5D8Au&!dv?jA0RtC4kg4_DnxQV);sHPI)4hsX5xR1XiYHsk2l+f=Bfz`I&n zlJvm=0v$Q3b`;v)U4lXOB;88)_HbPT8URuE|4K(LyjII*9x7q8w6Rs6*xN(B1`@}M zs(Nap5==53Wg=rtlx0+yVY3`3dR!wmLK~V{M$yAq0h)a2{psFbIaUpOrL6cqp1uf& zr}}fDwTDl+@!@d~_vq0@e=eQTpN2Uxyl)TXx4=+)4}UF2uGvVk(5OE%q+D5a|IU*X zyl+p@Up2cjG6(T8N)(G_9lr(E1)r{uhxhFFI4!(q57mjakB9H*2r*OBID0gca9{hZ zDTY$|da!CU7X*fK+dEV~dt3OPWR~qL(wiIi3>rriYoOREm+> z&(l*Z+`pW)xSZ7$wUqj(B=G2RO=&sruO*cH+`l--{wql(V^SELt@PNxIFbGUK5Q_9yg5rJy@?w6tBL^Uiz-xO1meiPe=k!yvx3C`~@W}lIy-M z3A|1+9V2*n>2--2JgVXSXj~Y>P~i!qVR`y>i5r$C%2FPmE}ye-pGzQhfa&SD>>BBd zYd+V}Wq^nKerqS6$8VpJIG$-Igxm2>N*wu8plE@7&R>hnZKNH)Tpl$+aTLe|ZABAg zvi8COUbzsBbKBi^vI*|~E&n&-&HdMzOp_lB3#I0Ju&6THCPj4ZU*s)Pb#K0hYQ1#` z%lB~HK28hUnW^o7Jw=#5k}hRcAR~N1Cg5^ty5JP~a7YY0jd=81}gxJ!$~a z(JRnNGv7lGn)fC6exSMAp+=EWOtFs}d>m#3E9QI;4PNAEp6{WEoi-CYgjR~?j_80x}oIoCXj&Hj2*X#7txPm=DFql`3?5Bd=G#82fJdvhjz?#rV2RB zYB#dZmF~I1jyv7Jc_iOMD>uoC<)_e#JZC1E>el2@iydJ>2oK#0_`0;f`Y< zhUgA#h;o}8T=KHS$fheKl^VlG&I+?oQgJa&{O@f_PFUN>7vk}RmB;r3JuJgYHp!J3 z!6rGvUF15+9?h|cdhUcqKHo$RqQ6<_^>>+r!sZ%#1qEdy*X!lc0a7n-fvRT*dKhbe zWlI9XosS%cZTchUdjov$bW{!cKo9xORH`SYY7jNq1HE$<<{ahxSiaDXJJ7>P`9PB=An|Z)HQUP7e`O$Bi-a_K7#A(r)TGX_)~ptzoQ%I^Q@qOG>3`mceA+;ALyNl zbcRne$b+R0mI*YG1e)wNFJlnDj~$FahtOv#I><u*gv2ULPPn zZPI8%GeABR^T;~Qv+<#HvP)5m(cY$Dy2A0$N(eVnvc4k}Q!t;-0pbddw>Rq2aSR*uC|Ja78)}ZzQqLR6tNIFzc2;SNKL0X3(J076DbOIu%6ZnxcBc<$>o(Xc z3m$V=`GdXGB5ky{ho$?%K5R=Jw0DL(LCM-vYfy%=mv880S@M3kjW7Ux2Pwq>sKyMZ zQm(k2lmbt$_*n`xCv$;^>dpDh85B}X^i)dC--gjc9#bc&JdOWBo*|y`aMNJDZo`N+ zmINA8rxdXQJZ|vHivQ+e$;88SG0K}o5=D;POP6s&JS>!TA~CF!FLedX?e9*msgsnw^dGm&L+{@qvdN;c=JprGP8^6}&RA z!rxMo!dX&Y>uupp)h+y~i!FS^n|^ORtfvKL9c=JE@Yz5=@Nk$9CDzrX&L*vkvq+os z2Ob7GA3Hz5ok#Uy?3@B;d9#?kS>8OgFevo!^Y?9Oi1+Qe7xUFx%)NNc#avVrdYCKU zi~3#oUN&7oz#?_A{Ta<1K6AzxuMcCqK0cow@#pbp71cvgw7a0w1^ilt9(MUheTn0! zUy$J>dp1)UCA4-C($Fr_DdHsao1s%=yaC2X4n`!7gOQ^KKN?xe>&DHnG_9CQwze6a zGE9sN%FQ=&UtvCbntbl7#u5d{j7p0tOAL!DPr6Xw203wI$BS4~g&t;9T3w0fm~^AG zy3)Ez>1s-r?A$Cbf_0T9U6_i0cH!y^gO%Ir3%|I~Se?BuwO!*F}7o=p$us%>)!Ui(@QZ9Zw47K6_w;YZO&c(~zaPTaUT z__<%AxIcKbo0m;3^3-`r;&@3P0-tR!)VIDD+PRLMtfj&Kc$*tadm#r&LpRYzkd#@5 z)J3;wcwgV?tBq;$35TI~?Do{rpuNrew6#^}6q&{`c!jJNhsO)+WxH_mzFo*0RbJN0 z2oI}eJNLpth{pa|4GzdPZsz}|UXqyRzR5X*MtZpGA<4u;ZFT4#>7kbDVcV)gf24={ z&6wUu4>cZQOBm^)gYfZqbfC>d)RIviEOoj!I+%?fk4s;~m*_drXb+QQsfr%Wm2o=` z{_X5#x=-0=Pp8*gOU8J{0RCCthR3(=N$#13M21DAFNQ_tXYjm%JWsJfjqy^9}O=>CIgAwpZBd zK4k@;d5qyidaDYGx2izN#|-lxyI4)n$9NcaUeZs>3OW_~FzrM-HQJQQLyIvURyhM9 zbukbh>c(rVcfzOs=7gV&aNr)}p^H1wl^7=`+~f_!3ss-`4N068vO#TXAA+%91batOvWt+CE0I{A$8 zu!L*E`bzzTpvl({m;L-QH69;=_mh6Gvc`I-UWY@~SP$1fEm1t(T%u^+MKzIKf~L%{ z>m{lT7O_TjI)z*zTO)LlCA#t$=fPU0Iu}Bh^U72_PYqixzrwh4qLIrL#e8i-&0J6{ zr+CFOSvillPEJW1+gAhKya?LZ6X*jf2%6v&Ibo1R@z>ldnYdStgfb-?R83~0KNlT* z31Su(D~!Y8ah!*1?v!lYd5>h{-eSFeK-#^Mg?oqSf@PeCV%aUhoWvLYeD2jbC2hBN z*~aQuJreG+-*-w;&B|`sFZ2tmW!NwKg@wxsuv3y|>VWpl?d?G}1NBW1@CG>p>lw7r zo~5XHBLuVTuTprwvcI+~a1WSgyk_BRyT4P0(BFCAp)?a+Gsfys;L*RaGmP^v#-3mY z*9|7vU)#K9P&dyo&$7R^B}?T}t)cOT@RiD}0Gy;XHQs~u1cx^QzYd?P|MhQO{ce_$ zAMs2k@W>Ocmd%n09xPqQ637apWz`L%k=!7R)}V2jXkK<+qz-dA(dg+c1^yZe`Rf~@ z5QhIF6!MR^Lm?!;T~>%^?nhcO$%FNQHd#WwB^26Q2Jq5-oXIle-canlACR6I(m#+A z!DRbD%1BP8+!dJY=|dov0?$YSb^gkusJ{+ZZS`;&O=ass_N0X2xt>P5uhER}HS$!( za2`y=F;Qj-sk3Aue+@IwZkh&%)8uY~;WCnwso`p(Tz6*j!%Z=V;+6vUNCNlNVMbni zi~rx6pxRyXoGZvE*l^DS)JCwkKJbjsE13kJsS^@kYZ^jL-zo`y>#dOZ)&y2%f>o*M zMf9#U``kSbq~}E_jDL9GnF@hyMW3mY8u(h%RIKS+Wmw;OD>d+~2^?z@nu?bGfoE7B zQ$1L}34uq$2Jq-}ni_{6JolEzl=C8ZYo-LCW#VzUYlgDlJj%Xl2C_%Xz`QLL2E@`? zl*-y!5PL2q=(#dTQ-;K~^PuTl=C&Pe`WrD5?WHWCy(A@tKPfZybsvpw$uuwU`Agtx zN#N=S`K2Fh$c8q}L*u`*@C^I~Ka!4HH{KS48vn-I%*YrZ<-!LbpL4){FG*pF>KIdw%SVC!1Xx2_;yqi+m7}iUG<(xP$-#ttJ4s*TZq?`(=L+#;q&@blN zp9VMeEz17X-pS?Gal3>1VVdFLmTXSBGq_fopbN?wp7EOy!xSzjV))el+^hj-ctQL? z)E=APZH9-oG#ob6wmg(fC`0xef#h{^P{*|y9@cRQWP&HyQ@AeIMBa zI;?h6iJ@s%9(^iRZ7uJ9kSk|0G$BLOFf>ty>Q_q~x7Fqft#(t|-ZO5t2IUC-c3Lvi zobMtl+exmqkiD!jf0cgAvda8;Gd+xh+tg_Xxu)}{oFeUN?&vlJY9!J-TJ;bwXmdcjZ=H=!ZB_wXNpAMO@C><>D^tg> z72f9_-*%uT_;YWAXUNL+qig@iMVRG**FEucvHG*0<)MYNmP*N`Y=e9pSzkg%Yw3PE zvAYbGw3yBf)S?WQF_NJXx#*)FWsFQLN0}&XoU%M^oGDI4jg<(aO>w3>r{!R}Gyinr ze5V-YIV~3A#$q^0I^X)(!xcA46gPF|8Jb1>jyoidJAN;5{JxXJF{G2z(_th1*uxh3 zRZHM&VF=v-dKhVcWJ>}c*(7DFgnXwsj4+g%x-6xxwmUi8H+}4-f@M;l9j;{v=+jcO zJ*2CedA5h@uSq7}=tZl!UaBX^wn+YMFX7hjlW^=Ka4W~hPdr?6t3+|@19~Po$Afh_ zN7(ZOF25pRt{Im&;&oXCb3D|nDRCuP#2$`vOESj`!izju4Z>F6;CI^Txh)!CbCONg z%8OutJ0?1^@WT;k%X?gH zZAPpPda33~q(9BKlhrZJXT<6_QDJUHRz;DAC$l9Bt7M2#GYqlk89*auVIOEpdaM}^ z;)GE=Xw)pt<7{US4=VRK4C2&K zgO?CYD435`2-?PHc;DstQRHDN9T~)wVM&EuYFf*78aCMLv#F{t@=DWbAw$&Ut;oYC z_!dF%w^-l?8F}BG8VXNI=TmRL7x0#3`736wp(S%YSS8Q7UwWBN`w?7KBkXZ=Jv^1K zi@~`bYS%Vo)NWI}x-t|jEvSc1j7DKT&JsITkwWa~kx@7HaSk<}8JP$YN4r`u= z-{@V_Iy%74_h7vk%6jo7J=fM9&P%7H6gegDl#Foi*ag9`W-)eGd0(95jOKWsP68A7 zNKPrPfe$>YPK8*!M-#fOp-Kmd`e{1DDpLPX72@XMKt=y_D%e>Cj!jg~emSL@j|CpA zOTye=iqJ9IBw@q2N9o2Dm`6$oJ;?sK$iveKiD5N2UfIg~^jngR zLG-yB#VP4zOMQ&tz1}n*Bd@7f#%qaDG%# zQZkW}>B*(}pTNx4!DRajHS}2IVRC7{Kep$q(Z?bWe|pu|dd`c`D{1Km=OJv$Cm1AS zQoiLF$+!I&Go2SH`*pPLOnoVsQZ-woM_5a1cwIKzP)v1iA*DXH&^Kum9a*9E(TUsC zsLI*RbzvP#7JIOsktm)C-lA!vdsb5KRjnS*!BYR9l7)Y^GF#QpJXl3kKom)5zO&KU z$#a+%>90z{Qs7>86jF+`M}Oww_eEUeFb=OObPg7-pLzHtZ=y0Z7KA>E&Q3pFkACJM zN7R>MKPjTt&P0Dq)0oDay-^g|oq1#UjHD$Vs;SBF5-+$2zce)0e1x0fWge_6B#J9O zQoGG%9v*mHqIkR~PrR(t@a3Vn<)OG094c7&6&`A3vdk+yWWEpp*Vjq`P)o1Ft@L2k zl_=`2ROJoPnohzr=FJim^9h(lNRC6vl~L>+QG*7`cwMp#`4U6U3b?~X!Ry6dgnkN> z*X2>(k#NTyut^>^e$5Y7e2=;~X4=oqQa}(AVQ{Uz*UG+{|)@@B;S`@S6S; z7>lh$x8fLo4F418OEce0l{lUJbuddMJ{fR4X*f)ptL&|6Cb!mQ0m;gLWRZj7it10k$FBLos^O@88;qsh0-fq@VT=RKmh6!$wD{&OL zU-8gX8OxJ4c&MgF)*C!jxsuX0daz!WC|=H0b;d>y*Zf7|_)9L0t}WtEKPxf(?k^I< zUp5(y}eY9M5BieTWebDaZ55QR`_o=8Yc4 z*&pi!z0t#EwYZYr=$)2exlEQrC&*mwiyMtGPEfFK_?mA_(<-0;K6L(Of((dp%x|VL zmlJ--&Q;~b8FE#=u0}a)9Ts4dxz&OirdBt3xS|FXCpCg<-NYGYG-syKve%6In>$VFh_+B@Uzj{9_dgKJKmY}}{ z-lL$iw=*XG?!{e=Ils9E6%#D^hQp^k}V#r>8zRQvR92ONccU6 z69Rg;y~V@tU!vVd5r_36=?gpgDEeY3?Brt@in;nb7fX1DV5mDyw{}}RTy+Cmfr*`k zmvZ?@);e2wi?SyQ=xU=tYaobj?zB$V1nU-sk2Rk7L1aawk7tsSI80JR&i zrET$0I}wn7SmY0L6kwcQie;sgudcoLTEn(_uwIZTUT8;4q;_^ko?8^~{%v2(vI}$! zFgywTVHv-|GHItaEA6<%OJIUMOVL^OE)E9R@X zbDo7icT!)3y?co^Sc4`|V~JVE7(8z9$`@VZFezZt_<`4YhUwU|>y)N9p z^icl|u15j@Z#0ukG&6z49;`;e<(x;{9UiQEC5n50&oOt0hZ@&P9Jk-g75%-B z@&9AL=l9*|!Maam(5j**klFGA`$A2iw9#5Y~un2IMh8jamGu)#YIw$_e zf3ixx@=)zk0((7Nc2@vYxl1x}*9)rl+Uwz(yXaQn1-&}5Pm3H!jkt!EL-%{Q@*0WZ zniHnf`#n_C}k{ynt_Q40dQuhCPiDPXl#Yp;E zSD2Z&vIiH27V(--NhY2m-bC?VJ|!`O*H-+RrzC3ds3iwISih1eewF92VIK5wZB08_ z8#P-Ae_Ba5ZEXiV)V#x}G;zmEftFKa_m%hc_RIUyQY}|o+Icp=(LoRIOJU%C358ND zsj+(7gKQ@`r8x%K-x%Nv&+20aq;zD3?69&X9vkaZ;FdP^HoIrOLp>&8HJX({$|iR0%V z8T%oIo(n%lJz`00Vn35OewJ%u{}6uMgLPjJ8~5!M&-jeCG=NTaZ{rHLvcd0`EZj}} z5`&*B{M$0wBBt4DrVEL3=m`(j17Rleq-TlpoB?-A9CvnLY0yFXY5O7hSCWNak$j0E zXIUh_(n=@OvooAY+{rRoC6tmrsrUNMD&~|2Ydg!aT|R;I#V2%aPETkatUWBp9{HY=1irTi zg1KkFYOVcK9$ssy4$@C~xPFh&(;Sz2b+E!_oX7Az)d+PghL5yY=v-cxCIt5PwmCT9 z`T6(u7Ij+6kS)#-x~f%U$5S4bIV-jIp5k%zS>mg7U1DY_xPo4jOaaaz`D{Vod$1mp zC>|Th${5MDcoZ{HtQ(f^Jv?lln;Xefb@X?KxpuM%hPlQ3waDc<27dbwz*Q*Hc`09WGSKY^fqlK?;gjfo^oR-iN z0v?sT+%%oGZb@Uv*0gy_Q1bzj8a6-*Bqg*!Hi^;AXK~Hg!bjbg*_>MVXd%5+|Mjj9 ztG+~0|7Gcmmy3L6#LyC`|FWHo;^pq#?V`J^U<+9xtw3rkbb%z$1$!0Uiy~hiy=UA< zN^M^8Nc{}vJA6L_^74-|kdgXJL!9;%)4=P1;$SwuUot z0vfkiZLDxmna8{hUj20l6njNJ0p(i9V|m&G17ool#*!51lp~g}qmX~slpd2q>z0kMx!3(BQJtwdfZOyTwwanB(a3+_daTLiW<3ZZ+vlq1V z@!Jj@Gdd`16T`||zZ5H>RbA_|F=Q(TYno+{_BtIuq}NweLD#HQU<}_`_+PJ%JVOH!fR3t`F<}pb6prLnZ#pErvY%9Np=R(}^OcyHyysvX3zh`sy|b0&>{bP%745vkXpOVLsZ~J*m{_X}1Osr2}z{Ggtf7Tz%-THa-=~sIB3- z_IrV)DBiPs+9mZsAu09H#>XCdPKo``q`WY3Jh*=z6M6bHi0OUVA^-mbTL6e|sPI>ZIC=yAq3djmKE1 z_CBtEjLV(&K5l-Dg=+8Pw#S%4dmnc{#_reNM`N1);{(~P_sw?Ot@Ox{0cwpuS_7gc zAfe)?^WlN0f{XdOPBwPPY;I*R+x}b+2Rit$4zuXsL!_UkE`eSj)Y_bjA!r27fynHV`dF;)+ zNOI+yc|gARam5r|#SFo*%I1mA4nB(A0zJ0q;3FqJSD(7-;A4$j;APa~<4b6ehq`%! z0&jtLL2?TPFMTr)d9;LG;4Svk4Ht10zTDrTKK$9Ecli7L^yFHaKkj@pFM|F4sER4L z@_*~MiUfl}t4LlXl04Q-Cnw}ZdPdR}OZI##FM^&CMrNqu8u>yoqQO&kizFnrYrENd5{rm?}|NVA#=1=_}WaU3M;}+;%un{759@(SW^oqR*)B6Lf(Ldx8B{Xn zseu$Hr1yMukWQ-0=p?=Lw-?=?#xPd;scNI2JxsR>@A>$EAyEyX8pD!KKCJ7*neO_1 zj?RzC+7zro5+_x+(w%&avd62Uy_1i}9t%3^V^d2E)27OFNqHheu)D;XhyD-BDMKIl2u01YL9hP!z^%-|2EY;_*|uQEFs z{MSom@t1Z>8G@1U8ppaWKCA~bw1FX>hparogV72cZv)g)7Hyv7eUgI^fhZ^WEJ?_Hm-GuwIs3_A&2~I5g2A#q zByX3UA-GfaCu_xJKHYV9Kd7?mZGwr1| z_n?HA+N(-fR@s{Z7K=rAIw6^8v|2krS0Ay2Zf7HC)F=cFlsGIpz#MY78&Ov(0hkjc zq!%a8S}Bvl?9MEk{+X-rf4Htv5Idi5`q?A@+Hy>6mk5Y5((Gji*yZN{;*Or75 z%!u60$0Ibjw+M~5r&HPUA+=s{tguH%^n|UOkC*=BX6JEe`O{x`^OVo?<;VcNg-UpU z43U6gyi5+kBFVKwZe~Fz5i{C%^D)VvsjH@LK5n>A;%CZPm0Y!k?&jn28`v$*RG@Pt z(%lb?CWg{s<51bG>L5c+;N_tl<1kc;_3mkRANRj3nIt>3?NxfFMfzhe(>)v6YZ-D} z$v56F@w3LMQj2S%?mjNRFjvZs%`)gNS$PFNNRKYd*?$zU2vFSCcW z2;f5It(T7re?g$PuU9?si=(=|C3>wSaP3i6utj`~j0wk}F|w#+&{!nP1F|K3d|25M zMRu-MMIRs6Jz`Ss<0FynBnzAK&1L=VPJK;G&No4ACu`K@&uE?_=;d zuDQ`pR>qA(OfB^FamT})oCrj@Q10ier&No;ubyGy`uni{P96exq-Y-R_!Gx3Jx;}+ z{^6^Aa2~h*MG_(wBR%ymX0^YM=iZ@~(UJi^tUJsU#^|cIB<&W-#4S`kRu6d6%nkIq zc@f;v3h6X>%iqRlyLfRBFk?3;;x zbi|seiG3wwbsIUrM+-VHjw^MU-O-V{n815db(J!aMj3G|V1VH{L9mVnPH;X|HGRGh z>)tX2#DkBO!7anX#PfaJe6PfC?}K*oRXq3@D@z41a3HnEWn$v0xGow>YHpBEYt}ew zJg?i=!PHBajdAnyWn&EX(R3*-ZkA#j6}(&A?Vcno(s}V#GO<7os0)X|K32;yt>(c# zzLUOMM}vLzwWo#POnaQerOIF*2kZmtW@E6APwYv$;2Z4Ym_1FMKn?cs9?!+;`;FVN zQ3#?fkRhss80=$`GfY(%gMEDE6uYd~!9H40iDse=!9bT4GuTI0nW1h(2m2U?kJSsx zU>}>&&t(-3_Hh`K^$2IMkA8gCjNavZ40IQ%g!g?^)AjZ$v!W~rRr3U1W_=LIkT|Md zDw(+S9)9lvAC)eZ7%o-U%R_wg#?-AGUoAzeO9It7Qqtd9+2BGyFqd7D!MUi=M}u5Q z9pqw3hLddF)FSDH7fS5dUwEO^zWs$4)1rAncf*UXne|@alH#@HsW$)1?XR6O!CyPa z9dqA@SDTVk_*&d?XIG>=ZCB(#Bqfmpk>h-d)|^n_cw|^w`3S?(`e&4<_0Jfbks@Sp zMqx&&Eqr0dxQr4j`EeN&Q-UUDEGY@ndT8(hy)WvF`@#zxA~Fs9B5ge~jgs-j*Esi@ zfY-jXQ$oJ9cZ4Ws=Nu1FceuE_C7ig?yWnQ&G` z8AAVzB^jj=v_$)PcAur-8p(y!M=o|noMfA(8eIWKqqexv=OK(Ob@HBW}md>cMA5RvamrQvaop|l1@nreax_zJ3Q7X^s(QenEd8wmDY+L$p{Qf zldK>@KHYoy$i*7%k%c~XqkDvXuh2&;yRGfB#yBA7+shrlF0s4_mebOvuD*TF!MV^s zt%$SIiYsUli!1a$t*krT9WGUR?lgS_q0q+?x3g--3Vn^q-dO0LyO7g7#>Odbs7`v{6TuEQRri)pA+G@R_J44dT}UVH$INg{b7aP z`s84tk3kVe!Jx>fFlML7#4u(~g&q}Ho}oUh3;B75`n2?jS~ASXWp_&qcmG|jT!#6m za+Sn!)!)_k`*0sr07o%g2GP`TkUdH@3;{i%bh@b+#PiMs7O1zy2-e#>q3ei%F&;2h zHjJDQ$xzt}7)sl|GG>HlgPg)ON(#dQdnd{;sF3X8Jbz|3?PMbivOlz?j3W-jko|L|LlCGmB$j4mYe=H|- z<`NcInfYc{feSkxFF)-CK; z1n!mu?w)TJ^~818M!d2W;8{@ok`T`{cy+}a&e#2y#x#DpsC>BZK03>(oTq31xbIm` z-X-X>^8-~?5N^p>AC{h-WpNQP&WH6+W{Ge|U}SvGM+C zjCnR)2P;n*858`#c5$@smL~Z4{XZ#UijT_b)NzWBd;ZQLe~J%RuL!3GWxvjixNaGb3N4x8!}@he{24y3x`tLA z*9>DD*5E6yV!eIH^Sly1VwbRQepH4$(=Rpu!i_UaQ=L_Y?c*|;8)CEf*JhVzfM$-z zewD2=;lFPZoYFQaC^ej$T}J#TKCIFU$&VWKoJI2GPcqMQd{}jYqM*(!&1ICyGvHZ? zO2ys+mP{%p%zgkVQVMM?-l9GOT2STjZnqqY}fTNsXA4(YkRh z3P;wtKCIB*4c8WEM)V`iD^s5NKCEAb@_&^K#3L#DwdQoUPoBWGg3gdCTT1D`p{s*Z zE-{p(s@CTF_*)B+C|byV+ewaYZpi{4)-z!u&lHtPUZCwfhC7PPkhs9dz0XLDJWg_q ziCLDguuSehE%UK+Rj__+2I$AmrKP?li@1jjb8v5gGU`OYfLl3q)7Cd&yo;}A5x>Ml zOvM+Msjtz29#+%TAc^BwqfMH`@02XuN&FJ)H2eUWKxV%)>KMX2U2`I+dQ0isv~iH* zR~%Q*4g8}y=g$(JB|faifn1B&4UO5f8XspvQNqvlLda4dR;+}N*yU`&OMUz%CNae5 z7V%X}@uwKDjEaVUA9oDZI;>57dXUPp z7?2&T%E?+wDQ%oiy7*Y;qm9#57YoaL^r1_{dX_9tY19V$8I3sq+vcZRkx;&}c;GqSDnVO<&2g(a(f zb-sq*tkW@gwU4G9xCZDT>*#FTk~Kc6X>+8ryGDxNrMF=l^X@_8zep4_gkiNbEJu2> z1NBr=Z6&p*O=qShYkgP+3@i|8wvcP@u{mRrPG8j+B#R?9(;#ISb1_UQl{M}|Pe?YN z7)Ha$VZy$Vja*wV2z0k6sN!L*kE2do>N5t>yf21vI6&+jx0NfI80ro+KSlg0au?E> z>n?X6u}*xzIuoP4vHD8GS|0~+T#YW)`pA)CT!J!HCpo7nCVI0x1!j4RBLp;mPn=eU zlzi>657?54d~b9pKcB{6QRFkls0!%n^l~Hn>G&hX?r1?2hM-KDCF zwLU&~KW90VVt6Jw(|N|;jm0&TZf-Z;JXA{DlFxluKV=YsYf}_?*WSc!zMgq1*G(^w zYaLZj62(=YY7-%Pog{GGr)K@L&d1AtDWhkyNW1mXP=BH5eJ`>JtT(OAQecSCS6Jy0 z>kt_#mDTi!&(#MWATU%$oPjz*MxTK?S~gcWUBc!H#TCk?w7bH>j0&oz%nE$;F=OK- zMa?))CQAl4DbzD2f0-A-WZ7JyEQNwgqiCVbyVVL7*rQZ+w%*5mPn)YJR6#u7$PX_1 zD_61X4n|MxmgDL+P}MUyE*)(>yEJG=drgH>RHN?qCq*QNNs*$6>Vk_RpXu{>>wUZ% znH1rmv);#Gr%)f^Tkm6wv&E76*y3yew<_y>jBpCT0eHQSPpEL?jb0zKFbmQEvuMlK zK$F@4M!^PB>MJE5JN0z~W#6qjg7rSyr?*e%Qw8Y->D<|_r`90HmHOD`@AQM1pAj@- zmCvNPBC>`q)SF{Xv2)UTrg3w<-pBB?;c1eI z;b}!_fxI~$qhOBLJxq6$x7Z6}4o_Q^9*EwOwk3`Gi?k2ZsVrFUqr1;2=bFN&Wqr$ z14TXQY8mq)7%0P~BEz}-G<)RX@{wFnO5$=5ADxl5R4Rph^z_L?=cTg0d}NDRy7(H` z+z7ko!SHbI!3TrGy9fUokd*UZIqamY$sFkw7%fE5i~fMLQ0OHeg=9-MQ3t>;7U)bL z`BPe7<4!d%frK99kLTu@G#J_BW1zFxvAG)BM9n74(7idmraV&kR_^#-ttebsUmq-+swAQI`vyVrg=8U@8$1V2+K&?l)3)<}Co<~X9 z!X<5)=@C6-fs~~zkd*n5vaq_l|X%|i~38%PTqzkvQjvx zk=&Sr|JG6i5w@KS2a`VI47E9i5*ndLJ6nD9v5ljTtv(w6 zk;VJMhgG|58UC4Dz6fN}Yj-rit!^oPFN?r{T2cqKn%l|i(L7hr?6&!^DsjC*pogAB zZ}ai5cT`nUO8e3ep6r~bpMYphNucIDvl=0OdvF5`x6@Cuo+^InY59Xcb?LHEJ;68&Q%8BelWlARc@F}%S zjkH-->37=hBPa0Gu-(T#dmk^)?z7kF+IYK_6A{q>k` zyN{YrNG7wUdg2{E%nQ!h(ihqFSwkc}$r{?}<1#g`enAf$clxMunZ$9KxmLN$hk00c zrCMANy_w!*Rz|e8cClj*V`m>`fA2f`7Va(|?~#@WXdRkcq;(-J9?&>Y?D8?$-arG5 z4fbZ6S=r@dvpwDk!14*5pc*V2C+OQHTSd+!7*lWy!MX}==GjTYfX-%C)@EnEu5EVt z813`}zsW8iMNS`GQ|HNp<%$O7>TJq7 z-DM<9r$KP0=Hz=E)6>meKJwj9R71VX$3C}@ABZX78;6-F@cQ^clp}7D$FXOZkM^V{ zEZH4?t8Cq2?J>GzOMxrNu*b*sHTikJ;<>Rzaq|%EU_^gKhOd15=JAXYCt}*IzVh+2 zYnY9_J}iB~d~-zlVsm7x9u<)qExYuNHvWk&@uzUT60Gylcyr_o)XkAEBV{9h85xpR zcHO?$N20MrDM8D%*GDIrqSe0FN8?w_CK#{0sfz(FlJI7ZnD+w|%aP5IQq*Fs9Djr_ zF^ur1YqP|3e~wo5ULQ#YWh>VK*=%!^-Ronz^wB=H*T*qh57beH_vp7Krr>USnI60C z_0h*6FZwv`^w4{+kKJ5?nJ78VdhL&ADAh7&hOW2w`e+BnMLV1bMSSjb(F*{3Ir+`i zrn%S047R^|nt>C_ixb%G2HBs3clA(xua7M}NjDi?;x6H=zl3)cO_p}MjDp>64?U6F z>tl@D!YgI>f)xIZs@~`8J7lQdRELj!K5EnmtmSHyUTt|QAT?-ezNJORYSq~y_xqsZ zxu`|xuX^`;zmEyRTbh;}2YxuRBhaNF<3^KJOUai?9FILA*?6J>tDKxY z^rpcdC*73%`D?Vzf`~z9l~e<^i4Py-o&im z^Z}JC@7f(5VjZc1idxDutmIjRVKUO(_2Cv{q)ZWyy)iF>DUxHS5mu+B$g$g`qHXMn zspv#|p1CPQDf8?_rN|L=C5Weq*Y+bzRcca>noegfC9NrN1gwEr0i>Ma*a{t6e$wE z+fgqsf+A_7=9^qb(Rsn^P}Cz?lRr`wD4_WL(J zuDOZr>TuRZlN2dlLz#xxJAVoY;W6>$yRO%G9-mK^e7{haNFK(5e2Qi{!q z?;#&Qryl6_W_B`)X4B1r`;d>pe3Ceh$wE>SmC=`^L_m5>Nw?ol2Ns!3?~sp1F9(TC z))%a5W1(H3?Y6)ft6J2<#+1%dps6I#w7@L04*U4U)e^;r!X;l6^W=!h$zdONJSTBH zr{;ZAw5?2$MXHWI?4z%Y(C$OFJ9v`y9J6xR$4)uPZ*o%QG|>e2+btZbH(NMc^mmK% zRhX$9wy~%sN6hGD0L@=tJumM|CvXWRSO0}X(d0GO)@uxmqOYB=D$gT6?$)d}Wgd?B zC}1oj?C!fIuAwR;$(IA;C=d#0qNFkQCpLTdCw9KpSiUntGk264W(qm# zIZ-4NZSD5DtUt=zlf9W}c4vcqq>0j*4g>N-L7iVO)LC zIF2IOp*4Ke$7{2QEvWaA_lb?3h<##tm=gi3`hvy0y%Cxy#u(@jsBw~Q1~?#Yyx zRVuNJsL$P#x>Y>pV<%_4dib6zuzD!)4*E{A=bI{`&>P`}+B=N!W_nZ!s2&kBz1d!h zd(YY4dJWW_m&X0hE}*8Rz}e$C$u@6dkFyB;SjT+iM?Rn;?}JE>3Qlr{DVupou=AUk z6rql}@J%d^?4+LWP3(*uj5rMXHqtFkqUe^kBTezcX>-#hin-~O)7fPCK*)Tiz{xU4 zPu`9jTTeXo*~Qdm5v8%fv!7k8OLgLUf%cP z?M%@X$Z;R7Fa?r@Dadz?ii?t-3g^qGp7jH@2vZ7S%P`U9$XRpK$ZQXf`}h#E=~jT| z^RuxMy2{IMmKVWFY(RQ4pQ}v8Hz40l!8H|L)-{U??8XU%`8t7j-Ebz?44*SE)_)Y7 z^o#vK(S#9Izp5JEwPO*!s=B3G3a(k6O8z>!S;ti6KR}(IKJsYzOK|01b>WhW=xB-8 zU6x$*VYT$+PV%>*{12X%g|fiJ6`F!+h8125!|cihLyi;iE!_epX5qP%KU+En|Xn&KZ+8&QqT{f8@bbfs~X zoaC5h7*}bY0p?X2Qdy!HQu$~V4LDlmWR*bHu1dozOC1c?Aa0?K`{+@5cxBopDUH|s zj{E3TWlR-`Voa5-Rj7^9@=mNWKNRzyeZ>DP_L2(AD>%u*W>{WfhXHm}n2;e+Ovvb5 zNdr1p>ZXd3Gt}J=G`{k^xGej4cF%;?MS0GZDAo<@=}G4J1NFA&xkJh&T%7v6eQyI%ifA^xX7X?kFd{B8jWc`lkqm1)?2;@Xyg*G0j(a4MqC~$^3 z0r;Uax8znGnJ20$<(&wutI~`S8kf0O{kqfw_Scyls?5}p*_H3{8Mv!VmC}%@GQTuv zzMM!2I>B32Y6zScQG3i%+0>Vo1m)S?Q-ZqN`K3Ym_TZGD!S=AypkemNl%SFJ_>v$? zi&aaq(1u=vvr2GFT0~$C)Qc>HE@#VX5kZX+y1Hl)!HqXdCT@;$m1(dn+}xg)7@cId zYF1iAuvK^m1;sMdmW00O#NKUxrj0QSkD*vPIJ5(El2lqJo#`%jq%9)2`DTgZ=9Ri4 zX%Rs&#l=x9--nc5wDhl|nf(;GE|)qjI9s$+9ZQP{_PcyoSQ)-|2k1%cyAfD4w5wOF zJ4vE+>J=|GD#v#t#yQ&^I!E$Uz6l2K{%;(GGJ}>2#%E&OBJ25a)l;PgWzl6v1XdF+ zCD*Cm(Vz*7z*gBNQV+YNyRDm>?z|{k5994E2H$D#RX~}iI$t|d4_`ZN;3WGt$9uH; zuZL~u?JC~e9j{vMmJy7@R6Q8XjlgQk&OqR*N(o)hWaS}~FFZwX)wLItkPPM8LaAs- zyjRG55$oV5x-a6+k5oOBnIYfWr;{xvbkz*5SjnI&{oDv19mRd!DEZcQk|UZc(?fsZ zwk9`%=iZ=>KR1H=9||brWCnM_MnG*#T18+z$U+z(p_Hp$GE0&+5sc++jW|AaJ`Y}A zY!kss+9<@3YoE0BJmjR^jxDYjqg~<_dFsp+sf)SN%^abau)^jT?M`s1QEU^z8*{mZ zA`5o`8j#Sqb16Tn@@e66($3X2e47a7%6{!JZ6fGKwR;Ss?R|Qmc%OZeI?qE+e_A!X z&mlUdsp{RdHW3_kj&hB8)LG7_xt3$JYqR`qBjz>sJ9vPPPuVA;mUM^&iu60BFYbIO zAl;d(=3`t?qYf1*)@Wl^(&Be1RulfCX{ zlNRoN#AL~mUJ+O~NEA01>&#ve+;T4+QjVr6um;y|BvCXX-bC>i-(chJ6+tt4@35qA z1XfjMs$V4V2uk1yGNl4lp5V~fFH#Cz`v5CwKm^uh62)albl4pb!Fawa8?_`qQs$=9 zph$2k_XmFM_am@wXBzKEaMA5#C!i)w*GUZ5-OkhsBCzyfcLLXuyC8xKt|Rx5Fo=K# z4GDuj2!jY{&<9~qVHiX}g9^i-pp~*5zwHKVbG{Bh=2x-3WG+6K?F2tbQm-y0%I#=fP^JuLuLY1Nf{SX2qig=&Qo&)i?Twu zHD!~Y5@w%(W`Al(Ay8EkmQ0JlYADhFAA5fuUsZMWkK=pov+lX)+}s4z$7+YCrGu@t zT5WCB)>dizP^oS8=H8@+fQoIYSUXsUYHfv(kPsk1ZZ30B0un^VFb^UGFvvWE2r`HU z85JW6ipcNvUi+MTPZF@$_W69DKYp)QUb$h z4lD8U0^nklq~1>jKxmfu7yyD<>aY^+5CHMrvP=f)grXOuQR2NRhgCN60&pQP%N_5{ zI0GaUz36iWAe>kDoB;^ul|BZ5U{*P-E&*o)BowWdOk+NfP_)M93_v)q^=S-%S5JHz z07A3g=WKw4q76O`0HN9FV*m(dlaB!)n9UBWn;?$?5{kCSc$-kPRl1DiTOGJV z%U85bBuprJ$p^Okz{@_c!v}UcjFqke&?XK(I~{Hp$69-*!`BMt6-Nw66@V+dQZ9G> z3#Ok(<+6uhzxzLw&t1i!f0x7RUW)O(8^n|OE{DHzFTBEUfY(KZ@WTVs>yLqy@?N2W zscwpDEIBBbP>ogVh2UrsTQRW-lF&qJS|Bvd+Q8{J$>P&rK>>C-AD0)>z-d`W=sy0} zNRN8!m0fskeWom3TGvpE3j0LCYHMQJ^~*o2mQO-k$?#QiN1N;PCB#60K9te2d@qk8$)lVtLmDmK2;$bufJC2EP(mslJ;g)zz3DM6e z!9RNi(-psh`69vdWHs;bn|c>+cuhDb8cXCx49V^-dV0@6^L(?=ak&k5>=o zTkEjIUxzH>CX4fu<9vw5Tl3|#19{-|V;zc<`w24nWQF}i)YDP?JVpB*R$>BwD~;k? z#Xi}f68wW(an0@4`%sw%7vtt+1%F0y_B$EiqKi?BZ$QBVkIz@rD8Bjzd^>|m@MTwn z>D4!YslmngpO78E!WvEuucuLb{SUzns4r83zkEHoUH@}%15Q?@*W>2h^*@Io2b&4< zJtQ@#v>@L@(nGDu45}5`_hjFDxS2#h2V0;568#ixfomzz5r3CB8#fDQ%g$N}_UDw~ zpU;+-J)4%umi36kSKdr9z8UE5LRLnD&@%=RP`_1lROYCdqBk8@vY$8zf4m1_Amd?0 zw{j3?j(m{c70nNbR2|Xa{dPd;}Vigrc_`xQ&kScYcZ;#4(3eJKHGkxZT&hozd5>g=uCI`nGFt z@J}oFhDUr*(c7r2fWq&}?>eHPgYxgY--lXH)F=G?o5em;9zS@;j5^#N{`gmRySb4Xprc(5?yPK$|uuQRLFeUJ~3RvQjFKo zVB|GekC5sa;k{=54laj21FeOmN|;m$lPY0SC4_lkq3EQ;pOLP7lNgn}Z&Y{61EA~{OkB|i4lA)z zN&~0-R&X1Q;@k4jSb)M47T>xJAKBG* z$on+r=v-la&WL}WbUD2&r~SQL-Ux+DTv4&h>PAQt1TJjnMP2`=;N? z`I~QOA67ynH}Kvt*XBxGF2#{*j9>82>q}hj1~UppykZMJe?}C5htb;lx%_i^C<+)q z%D$i_aT;?jpufxNVdzz9xRe!PR6heD_1oX&uK%DozmC;dTp`>t#U+oU){wLXt8~Q2 z04>v`kWcPe^9e+KJXqn%2+03!)F2fu*V1X6zn>Nt{#_MNuLG6vPz8eeZ&`-a8sEGX zZmTec)d17q4FJy+nQmO+8ia6aK{?fL<+}-b?cMacbhZljV`2{KGopg6aMR!bmzA6f z1Nb!B0RXwmIl$$we;ZB20C7bFG3NNQ@=x#ZX5~A6m!6e>cfa2=`ToDB!M_jHNQ5~U zZP+yjiCMX$H&;;3q4Rm>p95$TnA^3W|USuEWay_15PbeCM@e!QGmu6+N zP_kL5s#z%2EHt=TXh^frP&cTcy~z|<(J)t3)d66*%W9>306^npi~ux7IyInmVtMF6 zc?!@M*U6kc+~v#H5fupTyLaMfY`Dvxnk3t#W|5f$lW3hpk8t_tcjCE=1sEa}!rhWs zTCAmQgduDjJtb?4r#$PX5iZ|+CpKMYNjZsDoQr8M{gix_kuGO{25q9+JSnJ?VJA+TH8QBz#cSX<4pZOD}sPQg;LAEKa zfoFWeCqVd&PuLom;Bvs-!E@=ulnLnD>(ER$Q?>N!2`>9?#RbIM7Yg9H(w~L;S(m?e zN5BB=j-hrNF$6V6G_bl+Ai85AVZg$j8(v5Yj21*f3-k)@7N(po#=2*P9%&(1V1gs9 z@fIv|%puTz>lH?%NBasN<{Yu67weysu>%kgPFuSb|;_heIbqnJi9( zdfDMZNS1QAmtA7}VoNa^XodC>yh0DMuEmtnl04|6(GDN8Kn+-c$(LvMx<^kjH5bUft9-ClIk<0y-Spw4^N zezW67)-qDo-5{f4I7z3OYGufvxKtj171tklC@d9n`C0v<*2zCe!U-576N>6wZ|gGM z^ub2p91joKer=>X3}M)o{ialc+I> z!WoDAaa2hjBMt%=xI7_LFNPc+pI~$7ATGfl3{9{xqAPk%sv%~8n^7QOWE=3D%U}GN z7nOfEfQ2qA%rCvNMrToi-_QCe3n_rE6w?tvcR_3b@{9w3T@lLTU6Gktc;IPf*2*jl zl?z>N_o~=PE_C^~hhXP=NWTjwZ|~v-gaUqxhlZ$-r-WyQsS6)>&%n%cP-Itx?&MvO zR20g&RCIh4my#E{CUF55g{t8^Z9#Z_mn;2$h_)*-DvM$~Dhn0bomXb1aV&yR{BwOX`BK75AXDvy-Zv9`4R)IO0+v}ta@A&`>w)I;be6~&*8Acf zbD_&KLkq%Q#sRP~v_LM_EObA<%7mL&se7C^96Uejj6+?HgEPMvPjsib)S0Kb%YaaV-n`ylard~=3HP|K8^HS#&qnt|y+)CAUKZy93*8pAE+U+(T_}3qW#!#-e;Iz} zUe>Q_^rz=tUQMrH-~S5LVv+cX9%L%$AXP>+oezEIaHySO2)MA(0 zeGM9$o6r((@=UD+8%jY}$KSaLu3>P2{H6@l4HJ+cyFFsuw1D7|BAL|%E}|meV-Xio zX(pnSO4AXg)Grg!k4F0u%1Gdd1UTCI&P{L%hKF{L&rQuk-NZ$hAKC2@b5iYxA1R_{ z@enM?48k=dfPg^)2-%DPf(8gYVgLV?=s6ld&Hrnac$1j>J%T;cBSo^4t9QKxhyfDH z1LBI7xU4+Y?YmJL#qUNpWTQ(hak*WIZ0eS{+=osQ<#8%h8?ut+J-9Yh7ZIQ?vLJ#< zXo<_Uq1WL$=Jil*#7YkD!L^YI5s%Tq(0rR>Jl{stgdURV=}?_bxm+i>N`vcb&$TI! z=L)XI;0Dq_7;g^Lr7)H2!KJuopWp?Nol!9b-x+;3iUNI1InGtS!FsUop?kLkT1wOkcCMxYgAOiqoNHm)Gcv& zue~aa&7~CjT#|eu{8HCb>MnKpx>K?%U+VIojTiQ%F8{q)KHE~4i>cBFEAV7Uo&xqs zn&M-p(M$}QGcnWTN~BR2FLn9SODWEmUWX>qET1$%tQ)>=vz({EQ)5E-z~+~_@Me8N z!=Q4R%jzb-f^Qm6(z-=&GM=W8A2Eeykgu3PitoXR7#{3x%j0R5loaUeDW9(g`cfWG zM?@L1%;lTp=_sHJ6uscG>h7^Mq2ExPf71s}tGEv(h1Ux%x9^T}2Z7r`V>oGSNwl4Oq<*M?Xb%*Neh70$;*ZG2i{TGVo{DPrsdMpSGb;?-S=n|e@~7JSGue| zjc;Xugz|v6qE#-dD=EfTj=u;??Nu({QjG>tP1|hg-`ngr&`T#e3*b_?2QP5OUxWxr zdW2TPf^@Y8@roWiCA+kZ1)#JIs8$%NcbpDNIHpjfV8^o8{oLWA}R zzsjrlDtC!6pX6hXhRfbON!H@K3a+0$!}hp-()xPGdXc@PA6s?HLnlKX-6s+JJ!}>} zd3iTwznbmU?(XcyY^2MC2fo_g3rjBo zcD2i@uXIU3qscXP?BeKPwab@{7X9REmp^w8#*8)o487K66^HJ@&~yBWjH4cYVo2c9 zMjg!!I{%rA%O29rpq-viZ{f0s91$zSl=306Zj2&)JA{rztQD(Uk-_Ogui^9?R!MqrNeIU;V3VfiGq9d+6(CY&UMMqtj z5|8o#$wN>g@0+f752;G_69{&s1b2k*Nk`FJu9y>X`+LCvp&wF$e|WEOIp(su0FnX_ zi^#YAF9qN-WZvYml2;1=$g>RZNZE0(9fy;kIqu4FCEqBSAaoNY_$HIdyS@s?0JxLv zV+@c`bi!}M04@RMq_0~7-~#i$>zPyn_#!YL_~HW)@lRp30MP*a7&ITc-ZLc6NcEUt zg1`C)?5_}m> z%N6yC$l^)Uv(&5;NibxaX}Yk}!w`0!;9 zK@by4@MRB4Ny;N~bw~+tCCK~w7yyFl=VxMogrfct&$JVO)MiD5mDv~o>Mch+10t+O zLytY0UXj%b5b}I)HN9dEJISjC|5u~W#FYF7hd0Y>YV0+qZNiUPb;wY0hi$rr6FVU= zGk2$yN6;=MzTK18q#Q-Iyr#wv?v^%SvnmYmE9^@cGL3$FZ1dNv;i?vbhFN_pkmOs1 znG_Ff$Rt-FEX;_a$dQuBZ!-p6&c(oC)g{QlWp%=a2rjEz0wUZ}-0TtVVk&cjk8+ne z1Dyc%K&QsZ;wcT^*_O-dmzga2mkF$bQc{(PKZuEtT=Q_b3-$+GO#4VDyYN0- z9m?gsdYnbMJkC080ec!AWpa6lebA;(yeWJn4D1msWpWj{e0MRX5W}N@_t8`EYdgU@ z1U>j6;ZeY+ts30d9&R78sS9rk?+L>t`=0QTFeY1OW2QVg;#|E?Icb+nHt&iiOEXrHu?NvC#sA@FzeKH3tZyLn4!Qy5#| zO%VES2)!a*jqu&xo8!lDWr~l%vMiSm>gi~p)2%_QlfUIbJPg;?p0WFesS`Ja zD;!`eobjSpjBvLKG|&p+(Up(UX*AH;)`u45^M}HtD-Yr+XrRy9{lk>cP2n?PDDTdM z$2*wYBm9R7G*IxMJU&Lt(LjqBFB|h9o(%^QlkI`fM_tc+<(NUa_Q61d$ zPO~e+7>6Ug5)Vq<&BtiGgBBU#ts)=F_!L@Xt1?!jMOHW?9LnVpC|jYT2)`n!P^yh+ zORY+E0RC1FaFg^|$YzRF;T7u`J%_D%x+E*4g+?+`PtoY05nf92hLI;_cRogqXpxN? zpP)Mr;x%ZIB3CEK)gT|EIkZ(fDVol>P-6&fC5t;7w}Sy7ExpMT z<#>$_%3SNqp(vZx08F|A=p-y+Pl}n8pdSg8_>lqBY_1gY=9rVzTL)6~)_rm1+RP0+ zPCuIo3HK6lby8rahY|w+z*Ot;4`NPIU!9p<{x9*EM4RN}X7m5`I?It=6W`n<*Uu!B zKuFeh2nJt-E%RRJXrD6oP|9c>1>)Dy#ufw{X>jP$Q?AtkH*bfXN|!yamB= z{eF<(ecfc4B~@O^QAS47XbMvf-+aG0BEV%fT*J5@GIvkOof<)u(aRK|f0^4)f{cH$H{b%4wdAMu_;G5$z}$+LVlL3LK#Zr`^`%DUZ|waB!}UfX(yJO zJN0Qx!l$h=EHulwzj3MDpQ}QcrrYvBX!#2zC64kpV})7Bi)G!|-lQv(bcpTG&oWjK zZFvMNlREKc`0whZ=mOEp@Hf}W<_e+AbYfyN!#}7{A(x{lh`jxNYzr<-?;vMo({}EK)p0z0|XDIjzyfgY*G{fq( z=m9)@Xi9t_dMp|!$-JH^hmS?8vRmS+vTL)OS9@*ttQMGA+4b3|OmAgTpWTohSbbf{ zDj8f^B<0fv*AeE^1~HBNCTGhmg^xV}!oA7wv_#>>cSF zG3IL-96oDHTv67pEKIV-B5zk#->B=cNI85UT9w_R80K3A>(P9xAZAu}Lw2UqDtw*M z@G&x)W@YQ-4HA#B;6mPrGlP6yt!vpzZs@_aybR0R&HN5FAgFsQxxEJ;3pLtQ$c^>{ zN5T`FIS%}uNZkl9-R&EpLMfq~1ff_|DCLn9eWO#OR44^P7&2`^-g#c$$B_4CthFWY z&R*Voy}Ubfqg~@z2-is7<$m7fJx$)7y}S?fFnM?O^4{CS%#HD!Pc(JS;)x(MJ}I;*aov zYfR98Kzs8>)<2XrQ@oVXlxE>6^pO?R6oN}8wQ#Erj>OeeN6}_YtBz*2z|5qNtmZVr zHLa2ru46EQjG+n*WnKkduqqUM8CEZ4g73~1xIEaI8}u<4WAQOP2#Ue|C~0S*uEYAf zKqjs_`UrarG}n|PIfkXvVQGF54c8!^Bebm^S$3ZMHhQ?7khN_8y9>WbP~=}^eK<-Qy!iC#sx2Q63$a}iAQ&d zM<>5=!3&*)^Augmd#N1v)61=QLeOEo6M9e1un@7ALdMD6UK(K8-eKtgs|v33;wgvU z4^`M9@Awn+=VWx&h)=T;uYI59FMJ(U6d2nKco*Kf6=*ehVzMPK8s z)(#H?_Ydt-4)3t`dM;pm%wFrY7MRzp6D=?&tV)i=q%^{%l2c;X?rlm^TnZhS2`N&1 z{p}Rv+aY9E1mD18iZOnl;Pf~K%zR!i`yR!w^M?Wvs{)jiSmjcJbN?U(w0H;Y$6&Re zj@vM9S7dR69)hnj#5!)vb&RtT?d83`JJh`#G?+++B4Wj##O(r3hPK+l@K$@T?HN}f zt4Y=imX$o&gI}`_L;ZW$YG4T;vLO#HR9SqOW~Q$X_=(z>)wnFW@Q+`zUbDjTyiUpy zZdpQ+o5G*)ZIuLhv_yhG$`8X$>F%h7^eP;&=OyyL&7w%LYDY2O-F=bb^K*A!q}aH- z@1ZACZG00QyM-k5XWBk2Faq~i~ZpzGbeA7#0_AG5v((m}Cw3f-oyh5*fS|;>4 zEQwyFVV)8R7+A&*)7337)p~3T%vg(uaf)l$Yr9;-wO;d=^U=?u zWSZ5NGLoaMnEQ&%3hyod@^lDizRbl`3o|%~RZGVSo~4i@AEy^B7!gC)_M-KHm5%tp zI%7#2l&CXSHD^NAT+3;~T3#bBRD#1Ae#?gyG0&h)$P`Q6y~q^X7)XFj4L5{dwAM;5 zlE7N)jL3tCGghr|i=~9yaj@`T;6ebUG2``dO6q`pOHz*r=pf;n2fhO`YD-{7>m^om z%o6KGxg=!9eaL^DM&pQQ8(y->2V$GvmL3%(kP%^auWy{Uh?6#Z$4S2$_kciG;u^{(GI>3;W{ z-joUSrb_X}ga%e=63c;&#xu>vh@fN0=qoxFN`pm5E&L4{JfPq77OQrEy#vWDR$Y9t zyHtgwVM3W8z3VDps;dy=4F@KM@_-bxAd7#{#e8J$QX`qbIKGCa%eR%lbeffpm_=0~ z2qkX_2h&cKhhUL*s;<{k1O)1JN_q+4lr`4$S_H;g|2vFcv{qZ8T(m-7Z8chO%AG*3 zgsf+;D$Q!5ECMW6<(TcuX`qa?7OVcU-Sob0f2wRT+Ex;}FP0u&_3{W^ry~jToFG1S zot~$&WYp|xKJPre+#6VNw*W)Ya$V#tczjF|E-0GLopN}*HOWHA6l%7$!V1e;DCO`r zYaj=5!^eHJ66duLOdP&iyihOK6qj6dvLnU@)q!H%;SD*55wjDM6~yiEhSh97K_^vj zp;F$WSJ+C{D}IIdNNt6n1Ni)zCU8m9X zLjmY{UGL#C&|1C0!)2gddasAeKu577iXpcQbXpgQE7(u~8fK03a2evm}VSF4uyr0tF7l!itX_4iTBDlypYBh^LYSo5=2pIPK>S4;` zezZznm9V++VJZM$Xsq|sA{@0Z;@YryrnUL5hp7`{MK3PVog^AzF#GkxRA}PsdYbq` zFCOpx+5EeQsk4cn;Kg_L;wMP@-#<)UOni+O-^Gi^8-h0f>0!Fl#1Hr4@ATqvGu-9} zAEvuaJT8OMBBr~&_)6h_O>Njp9{U?a-7iJv6x5>|W+r(_|-sEVfwX(xGjBSv;X}qeLE) z$P##`i-T-bXogLld4@?MLiWP!F~iHht-d~ zULo_I%R}{0|DMTEJtlqqWQ?AaULTwnkz)1Zz=G<>w`LYPx2}es#jLxw!zM0mAH16m zWui)TKb?u{rw2h}AtNmIit2LLoNr;MzPu~M7X^*PCCTlS!f7zhRg<(D=R$=k3GJr0 zG9eIOz8VAbYVzGVyd|z>6sya<1@z^&+A(7~eCukgF5og1q0l-*n1qU0Wk za(f_3?SUw>2cpCth!T1rO5}m%RuZZPh#>VCSPJoOH65rHLEWMKHe8^ z$P8gOz;;z?hX0TZeA6U}4%x)i(QARI*XVTS3!kR`fsfE%55e~_sfOs`fvDknY$j@~ zt_?)h>S>v%X?jK=YKESdj#4PcS1`+HHoq3DW-k0Xpj@T}6wy!-*@RxkSSkBu=q_jK zm9krvI4o(^b(GK7^|v%GG+51pMT*s>m|*~1K?%O%3d-dx{s?P^BE?_1g5rF|M=+Qj zphx4r)@c2VxUnu${L?Ntw?{(Z&`YrzAWK6)OXX;+m*TH{jdJ;G?-J$mO6!n?B)t^Z z!~b6=uBXKobyC25@oSXFU&EUjkB`OFKbEH8uwXuJX6N%8^e#g0(n&BX!V6;goYZq6 z(p)_Zv-U8|+PPe7&9JDDXTU5kpI2JzWh(EbcoZ#_2Hs2Y>)@6z*$&h-+5X0)hRxKY1|@zV>vXbs^& z<-H1J9aK>7T@5shnmNAz)RCCmRNMw$Y=uLoNK47SE$Zs2iNgZwsFa~Th2 z@>&}y*V-e+0v%G-VhxVGWC`YB3OvjgO{TR(ojlCtX-uJqxh9S2>|t&Y%tm{37;Uyp z@fI53h^7Tf{YTKByngVO9$-2CG^Zr`UZert@3FuE@JA}(H)$vgw3p}?q^Sb^0CnOI z^#IFCZcseH8ft){)@+%6`Y2vb?-1Gnh{0n4y@{>!n^Xha`Xx}96e>AYSO#^@GFpJ+ z_XSp2m||QOUgTm?i9?6y;IH;MS^&dbUH})_30`Myl;B2dkt^#>5dM+k{6~X-o zd7(4c!!J;0iF}D)X6nLwc({!kELYqsRDwNr-%wc?R~5 z{hlw~KcW05(Zj;e$c5^1#r5HOxFo6%S2|wVHiwT$WM9SV)rUeF%4;X`HU-?;UEpLHYZmkryo(wCkJ(+IAd#qZS{k+IpZs3gc>v?Z5axcFZ zh&;tZLxJiV8hSdEN%?fBF%(9jP|BuIxeY@QFOPD&LgrL2SYeN_GYLo7eZuL4Bf@jT zfrN9z&xZp!J|Er~3~me`31|3x6z=07WESgvoPM}WGfbsew~g{u(2Ma0F*#$zhaH`jA^XJda$%? zudxo+{-LPf5QpiJEj0Bb_2?Fu(YmxnV}*YJ*Z4a}(-~q-3N+T4*3w|)QfpAKc@E-f zf#$i2*J2&#ee$)u6_!D!HEiX@(o`j6DoYvwYVEul00E$T zwaTJAUS&}RFUQ5YI&(<-yk6={18w3`y*wCPuJ=enqg?D|&Gy?7XlGmN zyheu50qcWc^|Nwhftqze+*@YXN}X~1QIqlD;=-uK)TY| zCL^Mu-R6`<(mB>dUW^11z8F~>F)az{)<%v*0&I>%YO~M=44c(i`?3O&`?9K{=}0AC zZxFU9gY@uhQ9s~&4#lEns9@R|nrd06A?_OL#J4;|x!ezKb~^F1GzrVesk9V7OQ`{i zpa!bORl;gv0r%31!J^Q^zz4Nr^))O{Dim+!*BMT>xj1Ac7xm=gPzuf$M~0^3XL@Lp zL2nAZE%)^*6z`;d*n;;H{@q2yKlYZhx(dZb7}2{3atoZWn1}NT3?_nirhd3G@o^{E6?W_rJ|5P9?u6* zgo-K>ic5JuE|}(3L`=pG}X0ESy|#cHe+8qk+0pT7im`RzNR zT7Y^%6j_55|M!>htp+K+tUVQQ`-AeKK(Pt?y-m0y8RJc~8I#&(e6g63C&trxKK6?9 zc`aP1uH_Rl!wpirh+cy%uDwin3}7u z9caKtI^fzpLs=-4auzBw7xJslen-OlohET`nIXnoxe&Iap~Y*sVAoM7D)A^+p2rRc zv6YJ7hKtr*9&C-p&TXuP7ikK3zcn4J&}67qwl$TCPk=6ur$Z$g=$<^sT8UIECDrJjyq!~F3qVRzjW($cL$)}t z!zKJU_u}ao)3@_M{2X?U!j>;$fkcHr*}NSsU6Q z@8VV}UJzOvq6BXb?SP}(9ic{q8be3@_?`CaHg)3Hp`pl=#Qo9kOnaCq78hJEg~0(o zT93T$!>g>6%~`tK)6(Uh4s^LE zo$(HJ;_=SLW@*n>pi`z3o(gutlTMLKow&%IEZWH=mY#TYwoz942o}k>1*k;ERX}C3 zZUwX-gXMl`=?j%04r1Or2t;Q=hsucI5QBs3ditqgg#kn8V_=1eZ+f#8xDTTw@m;sE#A)p@i z;=cDw%H?0mW9})%zwGQ&O8j8Ost_0e3+4L(nksn=R{U#OrT~x!Vg@UIq*h7*sE-_D z3|8ESp7lX5=@2D8wUhwALro7++@JcBXFt%NhLL9mOn=KSq z=`-3_Oz;_9V_~~(wq7;Xg7hgbZ{Q_Z^_yML5@&^DR_oX>uW;5PN;Znh%a8JTBo;XN z`~jDQ=uR#PRf*>bh*KqpVu04lDjd*GS=J3zJd)}#i?5@@Sd<;6*_f?o>;5=lJ7x95 z{l2YY&&DNC`E}uv(18$j<^!Q3_-rk*pN0#b%J5idNymn(9LnV?XOs-nLlyt30otDFVmjAf|eH|g`VvnyE|tEhvH|R+$xrtt0H2amAO*to|)9{jG);m8 zOVl?V%`JuZtpRck#19YPrBIL{m~wb2uW1&$hBq_|Z{X5UkXvbJG48H=sTYTun#DGS zYH_E;i>|ojkyCD#Z5Wa>-++a7QiSZ4Z?51FJlYKfJjk-B)7ln~JVG3Ck9^vhd z5m_6aAB>zIu61xR)>Md+hEVGFQZ6MtfpMw12g3{WTJswF;tdPEsoOs+^Ca>`Fa{t=wYrfn7^b)fWMRy#C4=IGhAY4 z1)ESQJ=3C5op;OgvydED>9tyxq-aBUUeBztKD167E57=0t<07}+~2CVD8>!gj&xQu z9Je&Q`p4I57cf#NEcIJBVj#XK9Q;6L%qpewAZ0mj#QEnu{1lxIM4B9LpnSfeP$w_t zLT}GkDsu>=xta3#=F_B;pEI-_sDL{Z>g0bz9?V%F>Oh?Y`FTNhGL$X2DjLyppvVs% zv2p&J&ClT8q_+c-^4rwM>(VH3f1UF6Q3$5sR3}}=DSBQoa-QB64d!Gd$)|kI$5pmV z`JGU2TPJ_Zz3s{JJK26-j_RJbhq|bhqR7w);)h16<+nPrEh_34lqesoSOXOJoICMo z8c#a;c^*%1%I}-hM<#(0il?|UT}tpw_YLvEI70Eb$V9xoFfp=Q{2v--`JA80WivI( z3_C*ckgVZ&reb*3HgUD>V+we9*3(%O=ZScVq7$#odOHgf`UvG=jBL#Y>VKpD(a3{! z%4^SE$(dr9O83Ge6{}C%MiLM@RmB4bOMEB8@;6Aznp-L+y>pYK00eEbjtk0?yAM(~r z)rznCp}E4yKkQ%|Exph-(fbeut?+&7qk~cP)|=w%-e7*u#KM=e_++hVDfnzzl!bb0qzr`0`7xFn&E3O zN^GsfR2W8A;-LEEM6EACkgd$VGkkx4Q`U`l^E1VLHb_+73|+mNWr#gxf-vPFT#r7g<& z1X7eJyw00zs=&vjfvyjFQ3gO=Nb=r_|L1N*^;UdIHy^-m>XPq3wBCxp@=Y`i)RkP) zD^;BPI0#D>e>n|&nd1EAA0b7l;%jci#tL=$c}P~KxIlb3mnj}gYh)lOQ~XsQ_tmdb zoWI%;iOUpU`(rQh{b|bk@25E5UnQe$8CJ;fJ7}yU{_5A!U*FK4k@Oq-gdG}{!iw*N z{ekUar-o-^;R1`#x5E7$gEc(nrSDzMqodkbmGM!1!nR?El5+TjT@?1vBg6B+CO_ry zyzqJ-dpvx?!y1l5WWxbFizeA{^iiA)tq)NrULWdXTcSnjqj-oOhTb<)H{qvApRl16 z5*D*zs$j}^f|_u?fM`)B^np&X30tZz@_ZRL=_ZYLLf{FleG1`~mOPT-BUV~#ydy{- zv&K4Vn=^GEbJTvzZkFgREK*al76%bG*(dBkSfo73A7Ok%ZIp-BheC3jN6Khp)<`+r z$F8vrS`rlFJ%xk^7GtGYjM8NUbKnhanqDI{b*Ag{b#v26=Tbdpw**Y(iWGnP>*$RbFusK{0BC0?%!0bzwZ;j689C%0BL)l% zY2P=`)0QY6O}G+M)RQOBViGSki(!Lm?j?V-gCpAx>@_-ch^8F5EiV7K!{gc5@ZtKn z{Nu-W=r-o=dKxF=(KcvD5(57F{4jQ10_gyJjuQO2N5ndzx8iGm2t0w!-G1x5yL&_~_8U)9-M8sLQ2H9~{t58K zFH$al@kc0TAI0DOA^Z|qGsQbvAH^L8q7mY1^CA6q`ex_bx?lP-XFqE&ga^rB>nZU; z0r)ADNmLP%%a78gy8{UeERYPOA>9`N}(@E_SOfnDCfWr(u z-^ZR?yr(?z{_n_vf$Ucl<6rI6u)Qf){EeO7*DP1ubqJ~)tI;7eme3`Wm2d|(mL^j~ z6f-6~ndUVMFQJ_}5U;?i!qin^%HbJ$skXbwB_&>}ccSeTVr*$GOi(rqwLhX5|EQrY zMD45i+dri^|Fog4#?fqF#b5n4#rfL}Z7sR>*H`gRe@!v|H3+(kg;4`Fpg@?D8)#9w z{V&o>bVN#$+}a6bx6iO|=b%Q*DPg3lDEQx~<>ZN4FiHUOe?x+(2u|o5|PG z3)+#v3e(pMdZqTUOm&yQuU4r4F`nvuUE>>g*cy>Hdm4R725S zO{H)>Wr&;Scou^Hv<#}lIVEqAI~Hjb5V(SdMljF-F^K#+8AL+^6Z%kUbkbGCjn3PS zPxrR-o@2K7V%DOn(>hVunTul-aU4`D9K4$3M&~`p6VxQDCBIq?p;xbl&{Vh(?w~$U zA@;FOhM{&8T!l4P&igAAAG8np`;vq9o4!9Eekc4+*lTg`ginUOMk|T;T5n^r!YZs! zs2}W3WfJFpp}G)e1QV(Y4YngPu}fG|{;Ng(7i;MMT{0}z@fzaSK9qii;1&MozTztX zV*`Cn;C}8k*E({I6?b*7y+O3K1?U(96yI=zSNS*m&8vF;+lM4RW1vqr`-DJ_u*bQK zVtm;O5kP{T0AkvrF3aRAh|8`pg9u;ooiG;8P^^9Ddk!`V0)G#i1-Q@Y3W)})PnZDK zET6^%g%kKO=rhmJc9@iG*HdB1KNXfsF`mZn%Zy#AcnP0@9mO8|{kG=WtxCl+J*5Q9 z2KcC0AXO?p7#W;JdEAuMHwtY)rQ&bj>=owb`_S?+iQo6MKb_a;H9AxvtF|?Im-Y!3 z@cV7UGQpyN5P)7p60AZt(Jmd57qEG~wO(G|P7cRZyx!`=cA;!SxDStHP@9;MynrJ~ ztPzrxpalCsSWf7VTJNCa!2fA6_vVnf;X8mAFzyM%_|K$T0gt5RJv_M}fjrWP%qf@q zFluOc4`I2H&tTV!W#$TdkB!@AGUxLiyD01}8umh-qVS|}CU#P|(ha9P(n@!%n~56h zZil6!Nwqz)E0T%Y6+PPH!U{kDEUM41$x20jLDLw0La$j;I$q7h`JsiK}ZjS`QlC&mT6ZN`EP4wBQ-&wY0pUOt9 zm@7o5vWs#e$r>;^CY6yhu}vT>wgKu^it()jWU5OlR@b;@--;F4H675VQh~nq?E;T9KXxDrgYe5thbtuuDh*l(YNGe?T$*!4MKhY*kA9KZHzUnn3d@9QWME z&!-nNhhdU(KHP7{_yoPEb+SO=j9gs|8F0AX4%^wmiZ8s_V|MXnsD@m$pTUYR{hYVT z{M=>Ut#3*H38-=SetV!gZl4)#;dVtRD35P173Yqn^fHVyUZ$B*5!XZ)@a<1f%tRLO z%T!{+(5b{8B3dXOVh?h#S>!=ZwS$;y2g$uN|J$jP;og-?X$w&&-a@0WYT!{&4|n28 zdXNKC#X-(cF-I|Z7D8B^yg`ftZZ9P(IjEs;9I{8>mieTf|tCDWc9SY9LM%! z!q0|rmye$fmpT9>vJSUxV?4})tiyZqhR9Yt+1iu0MkZ!ajAv)9&O$>NqPPzClM?)< z9xZeG5XF=26E=0>6E>=*qwxDEGz230l`Xgudy`EG-XtHuD@tvsF68n3=imM;-&;`l z%9(E{sJv#aVwrA)?OB>ZF;Lx`b%GF%#Xh(`(Q=g?uEqak6~@DB->I+SN3%HWSELwtiFE))UF2^Q*JOjkjb0+sKfx|;*! zs{>!->T82?U;BKsVmz-02ET?$eJ$njwF6Pzez?GdFE`;;FiBmdL2->>uD+J?`P%jv zW7-dfAv|WYvy7usK+rAGDY8r$`f#6SbMF>M%MQ9SvoF;`vU<{O~ zB3+8(t*5Ly+-u=Ft6qNVVgFme2dr(3OGw*zw^$3~4ERjY+t(RdZ#5f~dA)VeckPsq zsQ^ig7Z*U^ghn602N+8~p!JqcUc>9HJz^*}Lh(1h>rwpd4D2e-P!$SOr9s${*IO@( zLG=j5znnpIH_s3b?Il;bn-5xtvFSLB6VAK&O)vIMnka< z;h`E2_;=-6y&QUAUanUPex(N60^Y1&#uLc=var3}uq}{7c-4(ML1?r($*q%*E;Q&H z47$)qcLv&mOE)3!Z17#YO%8YIn%Qi3J(y8W2(?FKl1Me2qnJRq6!U*so&mHC=dau3 zOPDI7xG(AdH&rwM3#?eP#(|~4NX5sg7NbD$dwVge+xb@%Oet;*wCo!bnMozlsQpJX zUZK&9pHwY28+Qb!vOAt@wy=1P#&aNAnbfEuVM7Ic&3G2y z5hmQpgs<@l3r+Y2Kb*(c+<~d;4qQ&*7YMJNcjRGu3g+=CdJ9f_x9CY0XeL?HaU*5A zwFv%S7U614p76OtSaf{>lcHe}<6$k+j%Tc*Mk`hwDaIWah@Jw$4wT>y3(VWFFtq4K zG43`&_F)KirZ{(=fGhUKBmuWgx*>e2gs}v$QKe%EIJ`bzqIdzVL(^ZU-?l7l*WR{Hg(#m-g+K$(lO?bTI73!) zMGrnhWw>Hfrjw|aq+W;6I^Ael$?ZM35lf*sABG8MoZq%qL5Z`9cbI2dc7zs1glth{ zV+2=ivXH}y9wH?U!70v*!N1V(j~o7l9{;`I|2FKy-?j$9nrRN7!o8YP!gg;D!?uvi zgzW*tw!~_%s0+{GRd^F}6|V|W7p`~uy68lG-ANJrPKqpwV1rtseELD572?@{MMx5? z5(oeMUZ|HN2lc(2RtZmq%4PpsqByCamCs+Ic&}R?!Hf(Aa(QHccX%ZQZt-0`3HL_I zvZxc6WtBu_OI8wnFN$HkL~$d%3l++pDTm*sGvwTz@?LsEfg?#AtR!*#CETCEm+8do z^eJt@70fB!Agf&wZyh!x=A-TH(X##|`5aW6u^d<_pZPY!R+{%B`U#^cG{-lt9bKe;RJi`*pq9!qazFG2`%_i*dialW>NU zB`o~TBEdU2O^A!46Vo!^GD&=7LtT0+CHU6SSa0xXS|ApHgB7o%t(XK3;N{?$q&q~L zFph5$zfE&{@?f}%@K~Om$GOnAP*8x6s8|f~pzOMvVtn_UEE>ggvKo9np1O?^JjYrl zK8%*(x@Lm=^0SPKw9oQx{3=PEpg1SyWMN$*$>RonzRAy%?x1zVvY^d8V)Zq5Yy0vz zSk#W=1`}?8QX=nvCG&IC(Ai5~L$k5|YmCgvLQ@fe_Pf)i(W17`v%sGy&z0eoxJI=y zyaFoZ6;6eVhR+r5P#4R6WPA3HeD)$t4owbGE>8|kf=YOj{kAPC@we?$w$JW-N&zL| zA1MYWp*U?@;;r(4UJTpz#d?Xft|jo-m=HywdWhou5YTOTiM2)CAcFn{42>@c{iP-% z-_VOQtkn|H&O{iE5YTW5I9MhOzU%9#(f|rIzqTTi2k?x`_ zO7Nnr=b}RRToeVLU~*g<-V5TrLX3s(FvWXu^VgJUW7djjI;*qPWpUf$wiM&yw$I?_ znYL0%hVxmdU&?tYhhqFv&WHHNV~e+4Hl}`*1PqJNWbfzJ+Q8@IA~T2FMq^^touDvHcd31NaV-1CZnf@NRUS zg9e(#K4~-JKGZMsbV)xN6!N`Q^Ptes5Kc33rav@P6EX+=MnCodoalH~M-L33Hs+{U zj+(dsBjxY_s%nj_qCufnl!HRGp$s$O{y-H}1faH(8~!1ve^g;Hi1i=PIQ-u559o|W zDz2x~*bDxHPt%8NC0CdPXY^8BG+)ZV9Wc1(Gy=!|AzCT%zLJI#0=0h-pP(97wFk@E zwOV;NsG!8C0>BqQ4&X}^=Pz{;T?2GJ7vY;crC9yK13sM#eora>YA&XbAaKPGF=j&v zBgCVXM?Bgm4g!X_MtKiB!BZ{Q(qy?sQlt3p-{XtdD89Ts@*AVr-0DaJV|^bgY2az) zT?%xOKeZOB-dwGG6g}^d6cD8tr>DJ#9@oUC)6VzmGkm_#nTBClcNZt;T%v4_+Nz>kDzf zKHwXU9k36j4aaK3wZ0ihZFr_{2BN@hL}Wz7v(*?8c`nP(^0};KStd(f99`{Mb4Y?S zk%dt!S@#bKY@k7+8GlCc2HN1;v~QqO>FPbDOJHAvTJOyju!{k>J=z%Lxk!JA<*ea( z?uz_(vIt1JmN&}fI((3GezcbLw9(z`hEgzJ;l1wasGSm%EUAQ}?%;@tlHez<(Q^nf zLGgu`pm9B`Sgk{|TSpCI;t0eGXl*Z0Lqv9_z^ueBd!=C7s&>SM~9 zho5=Y862{jd|Hy$xHT@txyD_IpOr3(-~Q~RP{BX|XCZzTIwfK{a1PpVDh%52Jp9ZH zpYg0RCn{Dy4P5R1>Aexy-oXYh zDTiyUXEOdqW?Bo<0foG3@Jdfyz#hefjxkm7Af1xyozoPn`~9)>{y(QzPJbR|`>+zC zhchc2et57p&C3`@4%VYg*jr7F($9xv&_ONp^PvqPq4r`ogoYWH42bo3Qvp9b%$AcU z2-G6nh^weEKBgB~(srgP9;HWlb0Lq?v(jVuEWOIVJUN(a(rsXx;`<*)Lk$8C*Ga>i z2B&wWvKea*uGhO|eyvli{zftW+lY3U91-k63GOkXoeXFQ!?#}{!flm=Z=-y^4dF{A zJOGEZG2TWa+F8kYj|;Apwqsn}F1TZl32q4O!pOKwaC`qDxK??!;62l9bQdtZX91-ISsDY~Hd1ysQ07c9I0EBQLb)f!1onyhzP2w;&}r+6mM|ALjQH-a|t zv@clF_39MYWKGXT*Q--JJL~l*b>Y{eN84J-y2p9L=Rf*9G|P307e$w16#%vR(&)Mz zyC5a-x}4Q*JX6{YZI8AM8EnI6wtvQ8rMxS%*Jg)|9(rwd?>4wcb$80)-fdR5@z9b$ zC?vu0YzRizte&G&wlthEHUd8SF}Y63?_Ao&wOg`XDmN?{UuXA z?Ga75sNz%TDk$ki+dq{i*(m`p@$?H{xsc)_#wvg@0k;862za)yyfsoGy1F{`&!Pjh zy&-FyXddbmzYX6Dckc5L{YuV20FD?JHC*q7tU%J#2Jn+F zev6o{Smgyq1G5^)3eRO&(hT938vCkxh3sOuvNdRn~CtM-rtO1zn*UeL{cd7mAA+l?0$_| z90VFBp3~vYdZ~8v#ICp#wx`n-KmIr+`0+8K?4GW;gr1a$6E#?mz`SjuYW2iG)K0xG5Zb5T$Pm7%-wi~)t3OCbDG~lRDCtba z*FS`k&QyHMZD`{&72o<0N;*^VA0NWEnW=ant&}e|6IY~HidtYMv<_H^3%A#BMw}3^ zNaHwKz}BqOm;lGLBuTuGlPf8Q&}cuG0OfYPF=g1TG5=AZK!jGN$0!Li@-mR#HTVVtdUfcSppx_{nJY)UT7`L1lC*IGJ$>8 z!E`_}{ZcoIbGH#@WjjZ4Z(XiY&^d}X>3tesApp)*tiC}p{>D8titiaAU&i1P{Ed5X zPIJ%iDWN=ETySmqo_oP&uHtX}5KsNwBP$`3tU0TBiq&O-n!N0qbPaL)4ry4?{FZCf zD>Aip#rHE+zCf|MGr;i9n!s|kh9+g|Zyp`a)NC(32~E~i^CUere739%?oXEN(<5kE#zeOIf>Eqwt2h9m2zV9I)dZ^56 zT|9`AJ~)P^_;oi6SNvs4P{`n>`zV+1dkE(<4?T|YpkDEP4^fOCDv?gVM6vpTFT)Qy z__=f_m1i)KbtzS(^>7t5+|LL8zx;fbDDEKp@duF41CL@hSfcpO-+Mr-$fb(ay@7Um z?;g%9aKs>Csp4;Sg}h7Q^l60!oAVXcK8s?!&)UN>wJ%k?N53trhqv`9O}TtZ&ozsP zWpFb`F>d!L_DjnYUv`in|Of-rI(+CVgQ{j$v6z`{%Vjr+lah0Bl z^TwIF)=UN~6?gl+SJdA>9EMH?3W|rHko_^Ln~&;JD-$ZUR&qL|kp8rF+VVJ@wg$4_ zUIy|oZ-2|fxRwotB)IWsm?>5&zVSwbD99I=GLHPhq) zT(8_=jpUZeM)K%ZxY0bW6>i*FaR)h&5fpI1-Fro_1Rh086End&XIQ<`Ecy5{3C{`T%Wdnzjfw(zTk4SSXSQc>UJ?0wPn2A!7b9>4c;C$IDS_^q8#vRv)X*_}*U>c8? zdmKgU{F!Z?;xEX#eHvKrM{U5?3e~$&vHBE%P3E{((PpevfZ1Z^c12rd6En&KL=(DA z9AS=92FRe~a9JY7t?5fxmxC98D=`soR}60iN`P+w^Ri-f2WS9v21c>!9H`FD3+y&Y z7_1s2U0j-11S`$uxJIy?R%xWv#B)c4xWWhTzJ`vZ#Ge%K5cnHZW^h)c#G%`K<}=&3a#Su*6l? z3|vc@Va>Iy_ALE>c>t-eUc`_&h1zldVI{i4!~5k`&& z{_BJPLX#!#cX8FlR`OU6uDW>i#T4Vw7q9pf!l0V;spq9@5!ba{-qz6f&#BJwp{H|9 zP6xOcrhLVrV)NSTGoRl0X$v6qWwvLSvYR4kT##FgQxuq2!sUg&^~pS!g2QUIVY z594GJ&`6vy<^uZ28R$}+>n~Yz3CMWOCGQD;7#Acpy?9Y;#&P+r*nL*mi-p>e+Td z@r8b##TWLw(0d;iOAzjN;n-&3u@_G9X~9o|Mmvh&feYWgkYfDqg;Ou`OWEh5;TPd! zYF1YU2COT4q9L1&a?it=t?YZ&semp?vuYpc80{-DF?hF7DrpbKFXNfApWcprt^^cm zzVf$tUh0V1bZG9`lM>w1Jn^i#5}t_*xWRhMXi+rpp*^rq+5^YWc}U+6&yp7KAZxx) zvR%u|xZB|{H&0$B|KWwk$|EkF%#-SeKfXYgkIj%jzNIZ@$7Z-&e$mFmrTnzN$jwQk zWJ$=mzse3^&{vt&F$&zdJ4RF6HoK{;K+=fPG}6?z^3*M}I*eP#hXrvtD5l{EE|<5_ zMBD=H!4vgT>{*xUiI$bD?*Wp#euqBFzZ(cE;DI!mtmK9sJed}`0xWVTWC_89ta(uZ z=0&Hr1%Skz?*i1EM?Kye?93DNL|g`+sW;=^-ez5c=WKg$jWyAt&dr!?LxNneJC8E| z?sp>A?iqA@{m(yQ1r3C}1F4F{F18BxW1V?|y%)ak zd+=Tx-wLJ#%wAkC{)nkFf5afE0m%n8-obI6aw(U`yNl#8>JY2N!A7>2n(StcjGOFD z7e%0wNlMFBCJE*7V%l_(G&W5Wg+Rl$E^53bUSF=H*ZMzVz>jpQbndG`<*TZn=rdyym!jAbg| zvAmouL@ehf12jR90v-~E?}y8INVr)c(xu|2=#Xr=wKgQXstw+dYqRMhgdw?FjHZaX z)5&W*%40ovNcPAa>ck^+4&_K6Ih51bMt}nsZn_AEGa;_aJ_IvgKgMT^}=xscp#bRh;Zn;#yDw}?hM859a5 ziT#gRr`aHJlj-iT2&$2XMQX&;$tMx!Zz+tm(`}fe zSN4Q>y(ooVWl%+e>e#CM$GHf%6pKRBZ7SsH;xFew592RKs_gu$qnf5e6*JwcmHjP5 zQ0<$2j@H>1=^{-rF4E(%Zys;;GhL`34;8cfqGp=qySz0=F_n_9gW*z21w1h$L6?Aa zO_$#qXHj`6hktv26yGuF`|-Q~PtC)BDyQa%a!OgvFiQA$rvmPtvSeT?!hxW%%2KR; z?5~7=d`nusTar2rCv_^V?Mvw)P*Y)<)u?A()4#w%mOrTLu5BVkM>7h9x z=r)sea9=wov?}DvW=DdnLX*OQ;G}R7Ioo<>@jU5LKt*G z%DwjPu;H-@M*gxjFp5+2d>XO3(e_joH#UX>0|@brxfJ8v#*myp694Kkit!*?N>;L_ zCoiR`ApxdBwiuSk5B`<%crdNUl--lp3jwrD#OtXX{pdY?Qd`LhJ^7?AMTaW2zyr6W ziA!k}8SzG16JT>W!CC^ECDtBG>SmAimfSTWZsIa=IRy#R>X}>!-yKUztR<;MA0=N@3uts!%G-JqYh(k0~Qtz}jl(dUYe z$0%i%S*u$kSDzg@T%2HeYNO#iG^Z7}p*bUbq$q<#?=vE2Vr%5YoY}3BvvaoQWQ&A} zlqcWToE<(L6jO%dj+{NMk$ZBcwQVU7Dr9k+#cjN=y0}e48~>{|v{~22_gHAjLorrz z=o37g7vUPfq7Z6y=o4VDlL6!Ym&GkU&?%#1FVas*5Bk@>Sbukm8`ynsaT{GRkwST) zuWNLRB6zL~;grKgk?Kf*ygKq!q>Xru6!oC!#`vkonAXTKk@2mO<0G@8t&-1*E{u9r z36-PaxiGr88B)IO;^?Z@$W_rzt&y9eTU#TyMki(mGZLN?vrBVYiBy`iu1zb-b#1n_ zX+^oM4cYvI;BjuF3rkOES5dnWQOYJaKucK^86WZDkZQ8q=u#(c zbkPRNrH7pbRi9m-jc1;-OLHiXcea_<7RzO3^~J!K`r?-}b}GzYzJlU>#SxhE7ljt# z#qdp`qxd-%sx(@d5AXsD7;TTmw z-r$1=c{nb;$+15F-x6jG9)}PG7H`noHEIWK=R>z7Vwx1HuM$pq@&Fyt?}#BG^We~M zxd_Qz!6`Pe6}XupbZe~5qQGT7;;C}k)T7C&Qdde=sHaM^ob@W_rO1wm=eD;oa?l44 zMvg|{vo{Zu1M_6AV~VtpvNdEeSPp~(8K2)1tRvWXo$t6nW z$!G#bvO1beKKb@&-|P&(mC<$Lv4l-CG2|QQr8N0~=<_hR_PBqPRpOCLQ!?bo=tP=) zX;u@iOnKxpv*u-C$H_d|s#EB8I!PN<+SZUrJY9xjipXb?=8h3K??KjVrSovQ0-aErDyZXJ?xaJtn&r8s=J1 z#2}AJ@rqj>F)7ZgfgN>^x>UeNk*XlWPK@rGfa(GwR zq&OmNo0o()hE0m+!%L822`B<7j@z|f3e#7(JUrY>Q5uGO9WD>oinkT!D~8J>a=v(r zWavbd3=WMAQ66s!?eO~D`p_m!=z@^Uj)msQi3EEekn8Muw)u{e?K)0%N8}lK zq>#_AYu9Aez$I=?)-!T6#5^&p$xCm#2p-nfDgXw)dJNmrW z38zPAN2$;tgtk7~*K5`%q}`p|Byg62P$_Yg4m0&|(X$c$ij1W$UGVZy8~ z#K#S@d08&ZZS$d=4s-ilO7Np%P95e;E}uqR^9>uRc3LrtArzp%1Jxciq zEwXNvi-9h&unBVq8y8Yn_bo z)4GqjLfBjJ&pV@40QkAL!}jc(cV@__}21fxd7T*aV`5s zUR=xb*k>}2*I=$k+BLjAZGiPJ=a?4wyvjYA>dn5so~FX*-&8%%5^pc_tOYU)_lCte z+~#%U`tTVe0O!xbxpV6Dxcum{{Rt86V4oCCw+UX8#kFDOO+c zzsgs>o^C2%pBvarmnr^w7m9J0xu*S{S0Xrpius&lkES_4nqnW8cc0Xwxc`WHc{IhM z{>9c--bhln`RQ-V^%~s>s!g}Fa>eQ=e&V0pl1|IF;7yXYDPhDzsV4otAZ}jxtF&^( z?SF-#0Zyp8`WbcojhE5iO1*rz951fRX`vF@d_T+lPKFm3QK`q4t7w=93UOQ>DeJ5F zx}RYr>8JR!GU)VE{IzdFmj%C8uR}BOI;|JaQb1hq?@X^>AJk&?SA6}CD9%4R2xap@ zf?ZX6PWmdQnb0)Nw2re*w&&wGLCWJ1c=a$(c}epENiFEElGaEZ%7~UKkB{?l7PLt$ zslIVNw2!+#f8B9}715pQf@}Y(flDQ@)@&I%|i@(}1r@)I+c+Ay< zSMzc9V9D_VDnh4$8hM!BphKOY2jdMoY1uHgNI9J1J?z1fVh_y2V%(qin1kg0isx8! z;EQICHIThpZb=09VP6XzgnaEv7|F;n8DDvASWKqS0|zS3{t`yRN*KyxSO#ztxWYM@ zm;?b3lb|Z)z1{}k$Dk?lEsrqji=rI^y*#B*Vl0^W3k_oXgy3aJQUcRsG5Lw79KP(E z7zsoi{^o6gJ>+eMdQ{Z;euuYTV={QIJyflCoJWNg-D2vIQ<4pT=AQWzj6 zKIatXiN6oVQznCTwOlVPRjhghs;@^c%JL5y(U*Hs|7Jd1`_qJG;R)0rN0(lWH_%|6 zssF)xtPb!VtDiw+JO$3@5(s0`gMKeAqB5h;lfkkKkF`l%Vdxx7bI7NgLyZBtM(Uf< zm-p4X(*b$gPkqbp_uu-7*YAJwi!h$AxHRSPFK+i^Z-3B>MMhFSAuw5agOV^DNn_=z z1L!DM2sAvnmMNY?FUV4%S)p1K7TmK|x|rAz z^Rs$}?V_VfP(<0M-; zH9ZGB%Z1+`ZOcXAcZ}rxXLjGs0o99mO-D z;q@L~^7+sy_LbOG?(43X;&r!wBrNB+DTn(qbmE!Y`+g9 z(fPHPC$ras0S19LvY*TGqrY9t}x8kmHlW7D^(%8FB(z|3N?XCC^4}{EvGY{0t zvLAZRTAHN21tU+_Yo+T0zgF+^@v;OeqtRmLKU&y!tWA3OV83?dNB9V$RV9TB&wxTaz_-)tsnZm`-eCC zJ(_PW4Q!@L6@RnTH1JZzi!oZ6_1iqO^@OaPcGGMA!RBl9h8%3-o2}FzY4dS-Qs8;u zQpMBtbWD`f^~-Xu1;@h=W7I2C{Ml>3zfAGht^xNnfKl+X6jRh!vHBXt{(tPfd3;sX z)i=KPIqTeW?#;+3f+%1a6tz}Sl%euiaB8jhX7C9Rl-7qz)jrg=K998pA{dg8FkEgR z10jSV%ySq51c+c5A|S{hqed`-U=&0ULG<_eu6@qECjqUsefmDX_w)Xd+`ZObd+qV; zz4qE`uT33iv4o4!)J7&!n^}_0?6U-Q#LJidXQiK>eG&8SXhApfFqouvnxh4E`FG^< zy(6%H>fy9hj}>8YgLevp(_xt!P#1mDWqR0S!Qc?gTUJCo77WI+f;|GiOO61S{5dW- z!1gGv3M32mI(!exIY!Vec>55&e0z7hV+D2*qKouXmn(p)c@2gP%#hC6jyaRnr398ySgzwCAiTS}kM%UW~^Hkg6M4_x{|u zf%Wsp4Yr`=9GvoCD|HugMc_vmLutl9D-p1Bb*iR~;R zA?(EXwq;OI`}@Hs9Ke>W4e~;u74kx$1!yJ+YX4{W${B}IN8{uc&H(6PPCN(TbooB; zK!6850zU$c3M8|S=3YrQMg?-AjzcO0ZuTk*6$m9acUBuaggf66*>mO!y16q-m@DYM zap4tCsZZ>~}q9DLD`A%`e0r=A7u0nvj3IXn_f@^mb(A-1e zK%t35 zJ+OhaUmj#>HofHDK! z7T@_uLCNVjI7yfLG%ffv*P&AyG~8*78OWG0u?j#t+0&J2&(*pQ6EqYoklbDq)U&58 zuJkOCjyza`=TacsVNXKe$Wh8Bj-gTdYTc$|(R-SR2UZkM!X|+tL3R(-7Pj(%6TT$PI%+45J@DUUMP0jJ$9>%<% zE@+=zrrE(Aat5|lGqjyC>hWuio`c=S9LA*$=d}Noj)qT{AiFq>QA_1I;MOtjjKWpp zE?9uZ>7c7oxK-M!NEHB}=~NX~XGO1$+5rw(G*3UuH)ZY4 zII5542{c6!kEZG5BA!m`IMya!N0k)IqZ7=6cyNX)Y137>O|fAImwu2xXL}vnCB*+XRKy0peniASPlQ*5g;QCmNsTH}m6pEJgnZt^dUOkPs#d@?QnA{oyl#+uX}d6!CjtRk$eC|^4aD2fGO>KKJc8cpU-(un(4|X&CKGJ&dw*x%y)zN zW z5JBI>t8f#2g;(JUeaoU7rd<}jGP+&?S4MBIkJui)H`-O=z0t?&#~+KnTpw{c`m#I0 z<>*l{xJS=laLA%jF`4z?%$P|rF8-5Z3SwOCT@X_e6X}eSn4K{Z^Uria6;?WoxyrrU zyjV8SdiXu)E%ZSEP8X5sGdz!e&q*=mX_-KoY>h~HJX_i`V}(^=$&|m9HO`;L@zQUP z95|4Q#v;qp8MG!rncD!9E4MemY?tr3n5g5X0_kW53gmXlYI$gxX}jDPj@c*Q!9j8o zR`gMV?g+>L6n|(OB+`O@=7n&00^BYSNh<}54?MLTQn!!!8{zB2VLDtQj!cxb@baEKSgtW?YsW;r$5(+|NNm|51}}uxt#^1NzmB$p@M9)pCL%3v9ExBu;rE? z>Q?a1kQKZS)rAhNpQ{M{N+0#|$q{PCrvjp%S2?$VCbQo$8SUmy$K)v)6UOk8b4q^J z5cQe7Bx9K>vNBzgBeaIN5!z@i7TxWl8?EJBh0D?EoJAFGbnCS9SCPQ!e?wMjdX3Q# zS7V%XH#Z|Z!y|-SKc5CnKJ`MS0_X+hu;UaAj>N7cl;kKCE zX9i;;rJd1dRv#Wy5!9?ca~lxN?X#c(WPP185%xR`-`&<4Fq zw=Ilz==*eA@4QdnOeWYR!A%bBAXn-gl-<<@W_O+3)nPWd>x`~8v&ydf+*JFzVgh~y zANNpue*Se1wkkPV6jqbjZRQT5tWra;|fGUYG|{tYq(O^`Y8-Jo!|#GEN84XBMBuX(S<&p~f_lnhXL6xt?p7>(=} z>ZT(Q>V|sa0#2r&=l)>t^8b(_O)Szlc*&4Cwt;2Iun@);`Yt78G01sV>6>ufW)p#V znrgBteTQvOKs%rTw(Sh+^fc&o!FLH&^5%-~2vH^NB6SmXJaXAZ7u?_l$_u~YlxGwg zs+gSlcMt9#Hj4lDaQ)0P&y@XWo{1`N9r`VUY;^A#y*;T)sW7AWir!Xg$O^)<9nDmicT_>#@teJi%g}1w|;c1Or@&M`#Q^1T6kOKZD97} zB|ny_^z~1&?vkk#ysHF1SMC~fw@jtlyVJVJRQjaL*Z4Vn&*^()DqX%eyQ@s4vE4%5 zki2JgPnk*|J^1;9GL=5>Q}B>X<<=_03q}Dt7;`E{rcz#PS*#wc97<)eS#cc5iW?UP z-AUYH{4C~HfMx?A>bjAv?I~OkR2+Rb24lc3Y#9fLStIm>sdy3$Q@N~!n zJH;N@bB5KQ2TLAW`;g7)?@2(X9=`OjOr=W?S3V*WsPd80NA0G()-?Y8IDh`YxL$u( zQB(q&^Rx1w$y6%;Sz2$IKxw_xd)q~s(z>oSj^Z-~z1_2}r_EbospNs`2V{V%ANc$M zJ$O=0-HM7vKalr;4nK2w50t1t$pc$(5WnStPZj(rr#hfgU8}~cZrpvNo!_#qJG$CI zg{8gSzUU_X^hLLV?t1XdP%7wN(%q&|#I(NK-fnnxk@j{Q(Om{;M0b$ZfV32FY4>H_ z5zmG$;%R3pe)>Pmq7a|p(0^2UoiG!_!Vvk`)7ET9l>?l`sa!XBsvA&Md*+**F3f4ZS=InkX6|l)*=>%o__ZG=`!Uu|i`fitFgu~f z!6wc@qlWK%W?A z44F)4j37>)f}R59;I6g?Qo>nGA#m6SUES zUYW_~mXQquS1+8+1qF5Ghb@DGdiAnfxYrX-3qO%+qf&qEG<8tWQu!7S zYYr}TjLnwHN;Wn_mX-2t2mQ9Jg@%ufCeWPqTld+9R~TNYU$IyIw=~xeuJZ&zg8jct z5Y+lEEanpg<>SqRB!S@7kzRgJXo8?S?zd^ef%{+O1M_cTHB>#MFhNkiRcupOKPM+s z*fDX5LW0`N=R?bopyx;MsqM8Ry&dtnh6MejN5eo5#}evVtRhS!q2+QDn{?F6Js(K* z{QWe!)QMjNY=EDw7x%d4{Cge<>)JW;0f;saEa5?!E9j*em}}?C`FLSrzFY#R3Km}p zv;}^95~*5_;uGCmK}*mT_7n5pn#V+8=dqOYm?-F_rQo_tUf_m?(N(=CHXg&LsJ`gO zo^X!31Zxn;X4at zJ%j*QVk_LN}>_LN{bBrNj*w$0}sapFC z*Q-9$YDpH+r*wfUj99m~zCW#!7!S(8d6i7lO&%Q~i}o1V9tRLecUvDcc7dQYIfkc& z0zoA*SCdKfmX;2~CQ8?9)rC3E#+lXU%2nZ+dX-$mVQj$H$TY`54w->G08Lndpmco_ z-;n^H@2<@M%GgQz#sB%@r&DdXOMhr96bjq3*JHAX9{YC)SCa)P*`!F2xXWYftnQi` zwtJ?j@}SE`9Auqj0(Ck_>{+Tv(693G$>qz%Y+bupzR9x~2!9yAqjCZqq)pK5$3$R* zc!TuQ4RFDhD+}0&uRvbl-wW~za-XTq)wJMXnyVevQ7x4wk#4{Uwb*cRuC^G1cQLNF zCDCd$XCM-hN{h7>(64UP-e$9%DS~eQolW7w?bkkVqEG92eA#M>pws$!9swNuMu*VJ z!92nm%fWOu+v4D9=V6o6`X$|I@=JPN_!>J5Cx3-%6bl;gv371KVKOF!)T{3I|?+M{!jm4`gxwOJkn2Jc~DZQ;-?B)?AhjldGAy~+dS`jWFo!G z(QqR+RnR)`F)s$(RB_FE_UffJM%1gXGfMiVda+&LhXg3~uyjF(<#8LFg9Fp!GE3uk zyA+tEK`%ZyWP>?chUJRQu;$w!UMQgX)+#r+%G%@xH(6WV;8yFHSFz%>$GrJI1#&U+ zeT6=!@P)o2i~^|BclolTB2?V||D(12fn^%QLk34BQw2?D;G`NY?;7U~Ofj%JzR#)P zeccQ;yQLvpT;5VkgzXxRinV1t)h)wBl^_taOsnKcsYK9gvPfcBmk9d#5Sc(jUXyqo z?KN4&(hW?JA98Yf=uvxUJo>o7`#F021t;!>mz~hdqcK6Cy+_M@HhExG4&}?juw7)K zEOLkr%Q3p@6exUXjJ`?7fgOa!P5N;hkRW(m|47HDuFfAn(x;I_G>wiDUf1D+$)i*V z6DtK2dNz6RFfL|@O`h4NJuS>Ot4+v7kTI&wTA&fEHA^k3DjWD>oQ4=vPTx!k7*zZu5(F?bF=z`=aD3GmZfRH&3Xg`Paj zH;c@D_?cjh@+qaxG(m-)_t{13G(oe?jV8Y9Y2tgdc7tZUMmZPC!?0;QEU!qOpsvW# zSkL&1Lcm?xcW}v84Y8Bjr;dY!e_ls%lZ?^xc_E%ARKXIsU|R4Ix*|W|-O)5bS7cDb zU`1o-3o$k@%*t^htH$HI`bS}}4R2GS$G(KeIn1WnB>U2AI!ZE`j#3t1&Y&z~HS}z* zcK*OPWjlkT&y6&X&H7tLlY69aHie#*?63)Cq7+E}dp=-OJX zHY`h0Eq!R&EtKM=iwCh~Xa_^c5u*O>zrmFHDibNdp9`R!C;Kz07i1!%J8I?GkJ?0Jh!rVf> zeLR71%s$_QXv`)IOo%=ZjVUTMWYK}>_c($VwD+QG!x6R7g}CYpn!=cx7|dO$U`@>N z7<;3)B6fT{cyiM5@%apJ2Kn)&@lFkKLm68(uHoVM3*m?h@e3O{L<<`&Xkv#2`nQC( zlJwJu#vG4KrdPcSaW{HG^y+Bor^1-SINL0ZKNt_;bGo1pjRKDy?76`9ct2fGY5cqK zSbC-lde!@?PbSemUq0N%4(B{3M1K=46KP@Wc;JRHt}y0e3}(yef=X=Bd%B?C{@E5o z|D4Bb3ECk~7GNzwa7m*TjnrDQ1f8&=(SjxsklCt|(3kOPjbwn*8m(v~lW0YwnnsZG zsHV|^CJ?~6$cN)0x?*WSTdAX9THpAM#zu&T;9HGr8ml2_a}Kuxvkfp?eD^htK51lE zrKZvOMz~ioUC<&~=@h$CZVlIFt32qS56V+&`J98wtTn4lC3rDdWxnclfvr1C))AJBg~yGbUd@KVz0yGO1yg@R+e%VVAYvva4HTov`ew5TA-O zYVoDm=)dtfI4QpMYv7`)5yn9-Zon>X`EWX8e$5^?rVBb_rdxJ9ihtz<^rgrCHt0*w zS03j;@Vh3M`d`xmtrlx+t(L9xc~Ul3y#UQ-(ZD^>Kh{5UmbWYVYozQMn82O^xrNln z$wB6vU~?`RPTBgGyu+L>C|jSZ+l^J^osMCGsdb@uh1VA6_IZyviw#dt_QFG<`22V~ z%y~@0+XVqC@$K-z|GFAupk^oG2$^htUHW|Q>YdNlI~ zM=;y1XM3Um;Ez3=qNv#(=+tKjIs@Hf0xi&YLmj(YKd8$D8s*7mxe1iX(ocUamNGzV zap|p}0QQI+$(xoLaEu(=mOl zfu6E6V&0V~N`QmNynbLVRj?x2o#105o3H835OmO}F_6PdLE?4{DSTbGcTqB(kfnk| zC0{`*6|_?Ol>Mlb3M!JHa&_v(yO5*hB#9{#19Xzy42g(Ot?JSDdDLXVH)PSHk7KRk zqh@-1bl5iG+Y3;j?bIMkptw6B*Yj9tK)*-6BTbG7S@e!PEHOOP7&EYR7l&h4#5>^?@sk>dXV^)Nr!;mV zrZk?_*eJr#qFIfn-Dn4=-8lcoF!}r&=eLgl?N_#U(ynYjt)tUN(>l)UsEVVC{|9eE z2-Qo1^XXS2kBhuAR-);-6=%x7BMfq1N+um)CqATbBeg!!a~I8N~i$8p*N@dx77XrTk~GaEUBXlA2X zjcoL?MjILZbjYHOjXr9m9$^IZQKKu3e9C;0mz67xCN+-4&b@K|jqX?sUKh-{r^RPM z1O>CK_&4}LKZM_i-{PS6pnM_5&S^BiksVyzXqg>kHW)?f1}99JKWOw(BiLFWKyTq6 z7nE(S0r5W`Xptn_8gFYH%No511;$`Q9EM{992elz7T>fmc?t+w^i7NOR=(iC5JnFM zPeh z;mQ1*9IYEXYtwf99XrT;PwFRefO%41%4^FkL6?k!9&EH{2^!U5R0kQLQ5`BeVx^iT zXj|i&CU$UC^YP8Grp*#mdHv{CG6C(@3fAFtq}6f2<6GyqmI<8Axt8Z!$^lFP1JzdN zTgd?&F}l@}R?;5{CUS7oO?z&#gK2HwXe$$Fb-VQTI-6(D5|q|GF2rpEWp=I;bsMfG-tuh?N+z;v}9h3e~sl>qg$hW~}30g|4 z$(crJHLWAepNh>o`ZOHHY+U^|)!bp)?Sr7nOH9b38uKhJOeXW_r?cjC3zw}G$ULg8 z3znS_!EnNn-dwL*obb)$dh@)Igp}vqV#C3wEsO%x0bxCGpH`HoKY9ztPfWb2%pZpDYXzxn&keLSrz`2FS+u8ZO8 zgD^E?k_p#cxh{@T!Ah(*Kr-LF>sDQ7&rCHf&$skEtr}+g4e#DyJdG@vYC_AxmL`KC ziwav7x5R)@l*KJ;!ck1SvhA9-_7e(g+HP#?N#bvgHnx4EEwp4MC^2Rx2jE0$KYU8U z6n6Erc=ZH$p>0(=+`efy_GUepKa|GaT{!+jhAw06) z?y$c@WMu8{klE3R5^}a6@ndHyO-NGntAkdeEj{vt?W?kHUlmV%Bi7E(*;GQ#5hR8< ztcF}{?#R+kjLmJx#pdirEZ71w${ayM%5WT7Cii&wR>K}o8V)o*kNGl&;V)w@Hb;Qj z3>l)dXEdZde9&ATQHm9qML3$_J;Qvy?15&NL2E}|ZkESl!Ayg&fB$5^a#jFCmK_5nuMfNj0NoWNNI2v7)uRxEszzuON~9A2*^nnnUnY! zn^ge5Pkr_mi;-e0k}yu}AjW}BaG=@6<_gPf-ZM{{Sgq#>8f#6mAXCf{RK^$bncxT> zbLz{%vEDqd-TbS(mHhPN96?`1eFgm;>hM)mb~Hwm+Q?)_kB{~Sd)YVLXngddm zx;T3ig?Ia@E-u^If&u2lp~m@8=A&Bqjb}IJYVp;&*^L)8_9-W93@>P0-NXe8Iag4t z_82v6#oiI!U{alYbIX|b`#a8}(tMvXmha6M{`S|hx11`z4U zTH~VO>d1@6WsX!GdD$50angku*&SJH)HypGsx!_YJG3)g;Mg6R>B$S9t>$^=cx<_Z z=6IIia23?mo?4F;%pcBzKh=6JdAtna?lN8Syvnu~Abi!#!h(+AY-_H&+O<@R2Elh& zOJ~Dxww$GlHku25kw%0YXM~aAs1L^)C0yMg8*Y>s(_PSXW3d4t4^hlxg}DZ0Ku&AS zZR{Q{$Yvee%$=s1xftAO=2{U@$OVGLb574Zw>Ny6%rVccfY7r-;&IpUc--|hSQb)_3T3N8qN%4!wq4U=3v(&OpyL#-ryk=FfTdnkq9c&lIgxQ-V>% z833wvHEyHxd;<0c8?`N0kzk!{m~6YY*KB~}Z2zA2{CjA>v+e%{ExOQIG!_YJKZeb# z5VG$VQTtLp1jg7b)+&{Zfg8_MsjVjUd=G=GXMh{6o%p{?Q?jUrq8u$Y=E;!P^DI**+iv$xrW}72PKZO56S`! z%ibbErLYi76mqek-~S#aW51t(VLw5x&;hN`*TbTIy?z)VlZ}E~pajZeiWElW$r(sA zgHeNpTp~zZig7-)OEH-(Y>_6j#nv0tno3)5IC+DOJ$b{a)(Xo>n>LjR)HE+vCNQ|Q z)z((BFKumgxfP_FC4x5HaOMU`H%kOfxM7wPT<%$e$qZ&NGjRUaN&%?&P^b zz@MvMAkgM!b*F%C?qe?tLau~SIN}kw2g75PAmx4+flk1bJ2VI|&1#1|0-ZsVc9lC* zc;$T985j?rkBSIN9ODv{9zGw{;AQs_f*wxCI7^r3qu_9p9a~P8Gn^NfX2|2xVtEf= z-*I_X+S1+`><`b%^HDCC6Mv+A1u+2AT^4P?H;CW{+RLjv!h7i;d3iPAmGB^a>IOfh zYD~HytTsO4MQRQVXg@M8IQTEDTCc&55yCl2Kdt4fsB?yMG93XVL6eUVlX`G>Dgz(8pu3tL2*u}@y>b@mHJAf7%q)EABC;U2tf-yUwfdZ9w8{h zWMfWve41?@GNBe?nd^`_-D0%>L({D)D;!m2owi`Qfd&V6JIsan;R9i$uwN5|dr*E3 z5q|T1=nZ%>i$efjxB5vx^;<7xk#f{EKqk_F^->F3v|fJ9Q*DNzTYo7N=$AnIX+7NV zCouJX1Opsw*TE6{sl)ARHvs+O?Ss5Z!v$c_AYVwe0hawi6P1T~I1&RqQO>k)Z#tNn za&CQ+xpIAd#Co}&-PSuP*2~kWFs6HhjIjfY}6}*9Cc* zCmDn<%a0+_Btm_Jhjg?x8s@l6L1G{t57m~KXf(jT^Qw&s#OB}qJ_=mn!5-l6U-3C$ z$f8%$H8+^9E!65!FVxo7N37MV>LaSOC8mpoK>qJ(^K_X)^Ykj>=R&Gz2VciE8_y9IDn1XeQ<)bU>A7iD_#chNBZQ1?786 zaKpUBQ-z-@&q0q4i<*NlL{^?K9vvIM44{w4Eqg z3Kxz0{reYb3T=ivrp>g;;O_aJDdEq7)jkMZ4?zW<2@<8B5|4P$tN@>siS**7Vh0<$#s19ZXI3)Nu(>dY&(EP>f>PkHD9mu$VB?wn&y>>G|juz z%PzU z$YL5NW;Bh3#0BYptWjjBQ#V>|R65wt%%hO}`(k>`5!4|FyR@LJ^J#D%o+Ic@&v|sj zc~7RP1qV>38De#7j-c%*OMlAM=4!G(Ezr(;AlK&z8mW)e<~G zmqYKq+^mL7RE^6ZDYVb5HDzC_HRoD#0L`_kVfg>4^*Nx=t*u^}Ok45h_5j-IP4hw7 z?o0E@0hH#e^V#jwEhtSsA`Mv-lxxF!J=!QwOYCTrX!yBy-s8}5Zzct$?9VO7ZzAOg zI)wTp&}sR#)Phej(IoCL^&kyP{_wuh&9zo{>#6n}Sg3pcj1LeI{MldX`=7ikAh z5pESapdZs6x?}nYT^&o%Dq2GkqBT_L3qvaNUho5u#4;uIs$;7|HWpy^KAblO+k{XZ zlnet(#crU@GUxo0U&sLc;#rtnj2E;-E7SOMfLU~z zwo1c>MqMpfrR~*VE|H?Zz1kI5t)EbeYt^Hjoxs=PWo@3a(4vSEweR; zDqEYZMHo-kmb;+k+9?}~4_S0dt8+nhT5V+HvR9fvPI1}MqDJ3dK_~Oamxs&o_vO{4 zyX;Suu#E0cpJ{nInmP|Imilv1I3{4n45mDNCHrOyimM9QJwYZ=!VnD0A^BK4$7_@M zp;oj657Hl$Xo4WYj!lwf5hV}8))uInWuidfX4d3P5Y+T>lSK+Fmsf*|3~pWS!5j7! zJkXF#+Z%TnR&J_G569KGqH5w!MWRl{)w!bT;uhmhAgAKb;Fy-D z%OuLvm*{%1Y8c}>@D~8YfnnV5dN6G`6!NF8AdtqS$Lcu2N{`(T zEBkVbFg;oVI-Fc#^VmF$imjvYsd>}_kIlmrkA9pf#+M#^12o$p%x>d+-T=+P7znU7 zpQr&Li*3tcKZr76 z=7oapcg-hr?=|>&(`+ICf$H zw`etC${Ovsrn*+Jm!8L^pB^6*g*q1t5`LLT{xMOkPWm43MEx5z8x#v#udm033nOK{ z{&Cns=41UU$4Cuj`AVNcw#^#ADYQC#yu6w!+&+fZdDh{e6#UkCPJ5i;b=q^;v0MX# z%bqDNl`>5+SDOyyYO}(0sybDWV3)oKw1-fp0?I>)Xea&DZYUZ7&QAIu9s&0JS0d=T zJ~BXkhHAkfLc&K$PspPPFr^A4sHi>|UDE{#emxk0UXTS(?QRisez)ZuhACTAIHK@kBvi%b@PD!$@o=QMU<4LlZVy9+g_K z1s%l&#zYk{1~U2>8PxFxA(F`ij(EOU%Dz<05zh*V>xEnc1nzMrzp*le(Z>cv9D&Nt z6x6FP8iX<5LONM4=PCPJX-Wl&-$+0GW^D|*0pRnl@2^D8aD6{BWGTaG<6;x~HSgcLYvEH#{fZnkx;MIIMRe0a@LPu08C|l0sp8kJ5 z82YvzblU$P?y+^AuP{aWA!n4r{}z-$JS@hT3ru{I|F0LW)eE>j{3q$BKb^EN*#LaN z_4PjR-}pe48Rvh$!gmI-@Z+?1isc5USS413w8UbcPMnS}d5GaMiH4uF_*=!LIy_YG zr)+Jy#Yh{g8 zhQ^%fcNG0aICu0j)kydQP2pg!oWst>W(zmgMerS&eMeM{tbuN}MxJG-hul&PTdpB| z?Uuy7mqF4`^Yn`>fC26$6X}nOaa6Tful4D{5B|!=MYa&369HU_@s;Dtq5b9qA1hwT6V7XT1qN~!jX+GXeD$;G*Y26K<)@yyRam8(A zE*SE(z10APKzhH_n%|h5BVSJV#vEzcDH$GVt@gU%)!te!IB{gHcO0_?z-&=On{5?+ z3>jG6Tlkpr3|6@CErvW}wPEvQc(qY$y5N5%r-gz?UiWp!Kwqm-{@OYjH z(SL-9sXo!q>Bg^E@;IlbQCNOlDCn$_#!ej<3L0k?uphXEf^v+I0XcG^papuFZU@)F zbR&^DZr69|c3pPq`<$Z~jyh(37!hg1tYR-#0j~IN>njWTtc8M(Sr^#SbD?l!T?GGvMq1##%mN;l zZKbz{g}jA=ws;S-d(wr%jdcc$bH zy5)C+G`>jCSN8yyIh<22y!qnI{2a~2o5%i0259V$-n6Bb;K+L zT|-^RFFX49`K#bA+*19rV{RumoZD$tC!3bpWL{T%9gZxqB`>}%c)c%}kGr*)w5j5* zO?SEBO?Rc=1=q3=+m&nRj1>%)C4Oo;alp4|C%}jK911ZasK%C>2KnZ{IBw=G8sPkKQ z0OrUEa)M*;J3+1wOG)cx6_!h$UKRXspk;h^RVjZf2*txN`bCs>=>bbFR*Mr}V{X9(jdF&Ul zY8W%e5%I6Z%YHO2J~tj(^hJX9UcdKxJCD8BXSa0n$Zk2grH>^8c9cPrTW+ogZ*F-q zOnb8B>6WfM_TIGjCWqrq*=@oc+e~ib3vR^;7H)CVHWm^@Vl? zQTFi&_}na$`GUeEIIT_QWsnLX{o0Nd7Q-0k7#n9S7PLsKV1tvzf(8U(xxs0l{XI;V z7Yq8Y(GFo46%Kqy20tunleUk`f~yowoo;LDjE?RPsve;SAF_M*u_si3FFdqqBNM1i z0N>gYK?zSogMh&E4(1ui6+eu1nCmk#kuv3Y%--YWc5WKv0l(!hjfa+Oyj;f?IZFhW zg^)dEBSiv@$F-9L^?*iz%k1!2s_qrggD=P;dSR86Mf{uq_S}fNM<&w4Pr>H;sa0%x zy-Mb==`|v|I!gbp{q0$~|BD818)==)f%w7dUXZ>hljucoXrnkJ2^*cF(47=Ih0!lg zU~9Fdf?iZ_bs#i`D~Ed=U8SG8hICmZ80;n!sar^ARWG8y3&!maPM*!l0Wx{EO8&QC z+~`P=eoCpj8M!lvg$3V6irXCYuMu(iuT?jP0d*%N#bu?FQgw5^M?sh(B@I$Qnw*UH zwkB(P)G62=>wOCjj_G}Cyq5#xy%|24K)ZYsqokh-qdvjUxu~jWbanLEXg+B?8~t{S zOrUcy*|E}3<6}?Z=TvNIQ?^SjZJO0g_NA<570q-UtyMJ3X)gQHh1O?oWcbXDJ8$B^ z&YRBM#3|0)G_frQCbk_9b#qy}bM2VoT)Qgv%dlKfl{wDhf{lg(t}yC2e$K^Qiopl5 zT%dw_@XRnu!-FmX+9K0P!Bk|(02LWaO@^16Rj>xCGBe;)H^a*Eu@!liuMn;j7x;GJ zXO}MnjuZ={4&&!=)ZQ4}8>GE4wJ|c0&c$TL%0wC;TZf;z*eP+$c}iS)oJ^$hxPuBl z7>5QqGmQ4OK69fC(DAlo+Q~%P+-`R}rr+J}lXm!WmJ3Rg`MhCXF6gt^{5a{SDRGr? zFj#CfzmXn1GK}Uon%kHIa~o$hk^Q+(KZ~7$45!2`kFy(Lgi&PZ!NbF-$XMy&z)D=! z=tsFXp1Bb#)N(Yv?WUZYWD@1vwDTrR1j~hr2q;W|Nw3XC5hhXwk}94qEFY1?<(@({^)hQzhG(1Nzd+sZ^b-nO)zU0B78+5bJ=R&o7- z8~FHnxu8?mpK_L^Q`Z-V1(D*Giy)-ntydg3)1sDp>%n_lUI^1(X!&JWXu;jIKGIKp zPVkHf@HT8>ZI%CRcj9p0K9RT+m+1k7EV}oE?mAjtA*lQFdT_&VdVUL5s4eocWbm@A zg86!twpeF)F-#(p_$FMR=b@DcJ!XK#Iv|b?*wGe-Ee-?eRXJMPLwvN{BKaN$M)(%F zSPxTiD!UX{gY+co|Dg0Qvq0{Mkx^Kw=_ej!IW zM;}6}3xa;?)E6rfp!PD6+V|z<5O)P1b`|pB^;QE@V5X2s^zchsFoj;KLg!Vj&QW2MX1kncd1v@a)l_$56!bvTm;xLgb}?*Q`#9HuNDPA|Qr^G}t;1KARV zDITs8^s^Hbs|4eXWms#zF)}>7sAPC{bAY*}L0;-_=U8qt=+9Bv+J{h|eW=aGGvC8$ zwqAnU!`sXnR(+QX%EpzVMB1o*!je+CpaSEtA^X!|0~LIP*_@?xL;Az*#kdQ!-jIp3 zp4-qaX1cKwbSsTZ20oBW@Sv)ivy>K+9sJv!j0Q>K`g}{zf&l%A68cqx)$ctJT*YCg zYivx`%5jrwwz-r2O_vL*(Mt)fwi-TOUBmG9jeG{GB*l$#f8-4GjP`2yu`I#qYIIB& z;P3#XLlx-DarbY=Cv--B&Y#X|K|SB%H-T0QN@a85)p)35HS5k-3o6sr^D_vm1-+x4 z;NY5u1^XmYFu}2(u%Hb(&Rzxja@_q#SqxYssOM3Bc5jWK|H|Rq)(9$;OE|bzkZ9tn zdy}y`c1&D%!LehxhM;SP8m<-8sF@55sJp&Y@rTPvtV8<|XP#&Q+c3VKufnCq|> z4~9*L3TLdg!NKf?H-&!8e~z|_ZwIawv=G7mj6I6f0S+ERu)mNMg2ZXJw&}Di(d$hY zRHCoaBXvuhuI7Rp?82kXH!$KhEugk#5NzOZ%xicQR%PFHL-#}m+xi}uG)VtmsgcL z4G(oC3AtX7=6PMAkZM9%Wq{0Snx%m4T6$> zD+Bb~LdY9bh;KR|fSxU@B!q-2`7!@~EKh1EZ>1m+=PFFx=E#T`>ou zB*nFq$<*>?nM5xaOPNf?ax=G5rJ#00WPpZ5umMgL_>G-&nk?o~R4Hg2g8hYjz2Q{% za=15CQa(Z3RfM>ycqag7YEO!VD zhdBMJAhj9a$^zM|f;x4U0qQ(R258W5EOWzof!HiFur6h2(=;HbX_t6LeN})ofk_9` z@R1VRsaFLJU&X!us-Rs+m|!OyNYk`Zeo^ODLDS$YJz3yZy=W!<)atJgzHqNS3Pr@d z_IstD?#*CBeSmk%M7lcz8WbVZ9UCq|;(ABdbp4G=p+p@AI;erq*jlG&hC8U?uVMQJ zHET#ZZ$Swus9O=QMUqt~fbhv*D#oF)@! zoEF4T2x`l?CpGF*$_zEygLyJZp!Tg$6P-kJWU5p9+ZEjYF4Txf@MG3Q;ugdci8eOA z3q}OPNkS6c{}AwLf^K|L`svBFkaPtGPjlWy3}Fk^5rS?^kbX+YR{GD8f*Su+CeTlZ za`bD0;yNRH1iBzUI7nbQGeeMQil_`hZEleX^xVrbKrd&34`sn(AV72EMg}*^Bit$( zf^PqJlnVE%Uyy!!VKo~Z1MC3qnSug#v#nOMl8s5ED#rMQgTc}QniR&|aF#BeDulYG@EQm6daBZnH4xB`>1Aep zBF=b*ER&e=aadX=)5Dtikf3xe-7z0X*G`7b2Tp3IK*i^KAweT3q#SsJ@En|<%8cFE zmF_k^GI;Ym##7C{vO6pvYwGq6n_>|HNY%kJ`GLui!LxL`VB!Ls3)30DxaYZj_5Ll5}VM;$$n8*$03ljzH(%;6r zCN=tGLX%8{Inyk|_92K17qg6P(*@?5(`?^?G|k*(s*fAr!zOd9n{KOF;EN>VTC27W zH`@_;9sd=W8DVoaiK|5;z#5r6l{?jWu3HU(qL%iI%`jF_~tP zj}Ly?*T+PSxHe16|t~R?KJX>2Ird_XX41YtozjZxIoF+)Lhy)T@Dt?cO zv7!_k=^hq+UllY%U(Pp4UKRAN{;3lzr1`878X;^+`j1RMQcyb0+6_A+Sl61PD zmK~&@I{Y_NLyT^1FU{0C5V>SFO3-b$*^|L-0h>bk=i-gtuQ5qMUgnJ+#MZl|pL#zm zi|FAp77&2D*R`4-4}UF%g`fjFH(hjIJ^i+}B+{oM_LjZ6o7q*8}G85ztRBNKq+O@@M#72JYd74>=;704EN(whx= zsMpiZSbX|d&RE3msGsyxzh$`LwM<{ZA0x2;?hya`fUuPV4Opf}fMt5QV?ayg>S`U< zxAl5?#FvR+UV~sV1fNQSrf4kS!d9QAXwz+IaLA(R+9E9?UN6#+)4-4tyqD?gb%%vw z-y+03*yD|O%x)uw9>W>dSXm}vu}Wp~2;XE-QIFXz@mN}TB&Nxcm^4xFiLxxLD8#x!aV#mDgpXfa-l|kRyn$-c9x1S!;Tynv2@?#!-ZGJT?lekd_nk}hZK_X7pP$Z zytva}GG+bPOgXjzW~|&x^=LTnO6QdIkOuwaA?>0D9mse=hP^QeU2j@$;G#t!AA~#c&wnM8|ZQ7x|*8uaL_JJ0S{Uq|^ zR3B(3wU{tw4tnFHcCI1noK~tgpeof5>GiWaq@QenIjPUC-xD;O7El953urGzxhleq zlBR9dWFl?V4rzQ;%iWd8J6&&|!7yJSsw++82O`*OpJ(D3+jHx#@< zL3i`59Vh_?(p)I*{hY(ycdO2#yB~Bq^TCJJWJ7^~9Tynt#0?!|@60g+jFE-mK~yNG z@GTn*fhlq>hcSW7m5akPi{)ke)8(wkM93JvRtd4?zsYV9x~YQ_2Lz+p=mzoEW28?I z)cH5469S#>a(DiTQ|_PG)l^CUxg4QCXpXGmgry}Un;x==dMsoa1h|Oyq-$g{?z%8? z3s*4NHInvtO8V)kg-S5U6*Oca>$!3T^>_vYCRflSZW8R-*u~Ek9g`QpC!8jD;!0#G z;GKbZLM8*tDqbd)NeXVm@r$(OFc@KH+9_0{UD37RZ#d?O{?bqV0S{*Qm>h%gyj)w0 z1Z%ZQK%6d#<0l}(&%qk)98X6R1zpl|dGUhxg-%W%cdFGRt0u?D1R#0URB*C_TL{3# z@J{HbRq_}g$Q;AHWIxAT(P|y`xw2T=){w>Wb;&v>2tu#R&GvAHRI^#aeG*)x48(SH z5+0xUVd+Gt`h|RQkW8XMf0ilq=Ui5#UZWzyT~X`?ekA?$qYIGnE@)S{&GH1*%1xN7 zVeGzLJE~zPf$&l7JzF5d<+}H@3lWI-C!JaJ`U?x(d4keqy0e`~mkYy|xw}kB6ORyt>I0^R&Bej!n#(r?o#9ndae?x%yR7nFZgsq8)rcTU`G_s zJ!gNy&sBw|k1AQN*@`H1*T>rDc3$+kmaQxO70zR_^@+NRZlXTUih#_0qj8L1V!&i| z%s63qd1>YqR>&XXVZ3y^Q}wP)nM-Q0;A(5$Y@ z7xc_?7>@41KTz^S%t8n%HksQ9GPfCqNtX#?$KePeiZi5xL&tP_fD-G5Qr?aF5 zu$?nmwd<(XAOZBCJ=j$5k?Dl_q)5Cmqf^3esYlofy~5S`5QIfw{wkg-V|WG)=y zMMJw&$(soMwaN{kw{TZcw zn5?U`(tgC+?iM|55ikXFptK)R*Q4E7jS4+eW0qc`u~qNV$T4{ab5IMV{kYk()^rO^2#*YPIo26-zud@$%zCJ!S6{2U57#>y;rgRwEKCW~) z=IZ$xJN0o*B3XCV=%NdSDAZ*F%k*4<^Yu<0{7s^>LG8VqKSPDC$03CoDQ(5Kw}#;JAfd{|EM1Oa!K)5}1dB zzzM{8tS#(me_+RXi5@z8T|MM_lRZ>=3#GQOyZnK@kiYE;Z=HutUa}8eeLZ|A@s0NJ z0#df}&&yT>)&x);*c`x)K#wri7S4v>IKXW$C=Gn`JtpXBn&@wEs?gs@JMasB4PmSn zqZ5Q5qvJI?okp-QS!V=cnnwC*1PiltUJ!QI=y4js!UEkt2n#j(okp;5pe_!=VH(3v zBUo6f#|GgTjq=k77H-#jG!E-%gE-SkzWv`g@xSM=K{qqn@I$%&PCT$*V>1qbq?tP2vg^38;!R&UYR8YK4ULsD~( z-lwrYNF34a4f{T#k7*na5*Tl7wi7_-Qr-o#p@*aH^F?Qoyc5uvj9Q)M}p>>SofK@J?TPneV0 zMGo?T{@CZ?fOpu#Q7_JiT%YS~wwY@}6>3kbpmZe0>A4!4^;XUi-3&S3aN>!?T)kP7 z+^XYEaKcfAex(&I>d411+hU&G4Kgmd5}$sFz51X=f3wD56Q_kfc8EnMqyXt~)e2An z%)(+|sc;N#x*c!)h+p{j$tt7DFU0yg>hH$-dk)@%f_sZw^C(>1_&UUT(n?XkDP72rD zs&se}u4y9d^{lLg_>|#6%@SOjbCrjUu@8N&$&FS1X;O(6kvYyc)ONj)&jD z6urVR8#P69C;tMO#G>*+S{A3b);jyB}6MQz7yN0WZwbN@poF zKOdLgkB6QT;`vt)tz%q4j?UM?$tz0aLtv~78@)vD}*Sr!!3r}6_zFW=m+$}e7SUAZ;*#U-V(WVoG-BNjqBnKlG$&pw}_L&Q4i6+Vn0joeIFnHR$A;~tT)NStl!nMh;e4!NjH@rG3aJ{S=4gXPD3Uz1Y?0o$nenD8|)RN0ooIA0q?f0~BCew+sEC6I-F+yp<)C6m*E6|%h<5RVSF>v zY@z*Q?fwDO1!ASb4fi5jBo>WS-9uxvugb@tH6gNRBzi~ojl`hHl1Pk-921H0krN{^ zJ#tngE39d_sUhK}hT9vWzTt5pdPeq^!oHD%1eQdO5;!JuyugW((*@3nTo8!`k;{b` zdDghI*wcZtj-3^H3~};RoPT|Yekg>r3?Xr+UhE8cTO*tCey@L;J@IiGw7xu4_u~-K zCWL$vLXMm#E60WRDy|&&y(u7FkI=G&-1{otd>ga5U)XQPSWLBxESB0eR!ZOI92KL` zRgZGjC!2Lf7L{IlpvFL5rZGWRX>_9O+@0Ug9JAbDligvlAW$2Sx_k6tjcC)!V7Xb% zg>jd`K{Lpr*p^x>v`eg1OSI`08FrAPO6@3%C3dyN4%-(>$0|Jh0^V4p1%2UVy!?jJ zaSC(I27`@evq4`w%m!JjjUeknWkPK+xvsp%9^W#hBt#v*_C>Xu71S(h6SN&@)fGC4|Kxg!Ql?Z1Sne?eM8yrfc@l_HtZz z18(M1SGa?~Nd#Oi9d>i5lTi<_!TC4y|J~2?{|jI9{rG&nMU59Lv+;V{*TX1pzRav` zZbz(dyS(~QZE)*6*+#ax9*@0+S%weYk9!}(Zaqt+&#js-XaI$#!YCbvKDwvDLQd#= zO{Ndsd=q?_>Z|diM_^Ha6>i{S9fqE!pFuw(Gwxo4MS(Q|ijvJ+;r62%9ZZ^`(01V! zxbj{+{yAc0XBvUOK91fZ6GR}>jF39B%x0r>1N1xX%Kdy8hPfr(5vC%hS(+_a0A@YOS94M#+e?O7pZhZ+>*=bsJkKF zPw&<^qGN-^P>oT)an8au!!<_g9tOS4I*ZLV#|!;5h4;x97j>O{Ls3m}%lm@0xmiGp z9^CN`sYuG)~Oof^>U44I^Lj* zTXjnfR&iHaYR6g3wyP|vygi<@*-cTt)QmBhX66~JFslsKnhc9v+ry&B4z^Nlk2z`( zZ8_g`Qs`^@2fxvAcDBVTyVs)5#&ZoD=ES!am*V=H@xfOj%eA;b=_G|EW}gwHio4h~ zs0O8-M~O~~yXR3nm#@)V4{`LP&xsKC=o5JIIg!p%b&ke7z17KZTb)?O zpQN+GR?aIYb+nP1qy#z2O3@}*NvQTAYKhtHYVUR;U6Pe1yUhuMSR3~nCG7ULqeWb( zl2GwNby2wTMeH(NtjLF-e~GVu2w~qi?8~q513%y{is+9-Qxz_{T2Vxdp|Cw&&(hdU z>8XNSlO>$2_L+1mHFE4)N)6i`_54eC>uupb|B`^0-o)GQ1Tg{KHD{1Cg;{#H7S4X1 zXE4_+F+%0ZcbulML4KYzg|)WUVvjxKt`Ma5^A`@Xj@gcow#V#Ai)fHaR|s5%JNjuw zwE1u7>S+1vOObu!$NKhg7{QLMI-eIrf<4=V3I{&}^K0f)>A^iX8 zXZYr4BJTgs(UhQFL*mq4u(Xq&FR)5R<)KP%)5tI!B8l}HTjcF`1Y4|k8~AOf2&Mgq zjR$O+Ib6*1QyYhFKzZ zJx57fBT1KNEY#f2{{}C)pHfN7OL6rB6oS9W(kSJ2dcUsMWHb|YvkUoK+*+hDTKD1> zlB=kP$W!ZP=Yf=Y6!z$PPHmkH=9%SOBNgUw-a5vwHs0>yQc9^LPa|KKa$;WS8V)c+ zj0iY4{1e}PhiF}HP=r|!-V;IIF^!TC?~6a-{qtM5Icu*_ZpL#9v(}*6Y&Y0phFa;W z-W)ebwn1v4-Dt7N_HmclOZ7xYkcv@=Y9f>VS3lsVXpIg!$q7Ivh*m8C3>mGdVScHok?AGrK}NGK7a=v7VOdIWKQUy@Y3t}@*8y2X|CNj-y=4;e9Tnbw2j4{VE6L%;ZiyQKGEJ62ff?$gJ`U=4ta?-K z1hxc>fyi;!-74VLXYkAmg1z)M-g#fJPyT^1m;lVg0$2AR-=MQj*XSinVMm2@P6b1C ziI(~~nyYn3g(Z45zg5m49Tg7ibb~CzWtvS)CiP4OZiquoiNS7D=cqC>(IJ&)uEA>R zW$R46W6d=S4Yrt_j#?~2V@HJ*oCFWs;}#u};6}OM9->l;Ur0xV$(Zie=X0B)SzB46 zAAUh3%&1nl?nRC@kp0q_6Qy@+v& z?_{*5i6KrQjXzPYJeoL#UYgTvyuyw5;*C{KQh5It5mSHZ%r7*^yXHph({&n0snyAr zyNy>NYLSr!eNB-;X^4904XieEtd#6D`wa5!6h~crr6_bBco5G#hu1#9zkU`v2+qF% znYx=6)&+Os&U*#B?*TmcuwYL-gO^?t?75c&eDE&;KeyNDrt@9Hk(`j@3^gzb3gvnt z#Q>^u5)>wK=ANnN@Rguskf5+#cQhh}oH3?EC1Xld98;F=uBA$FOo=KqrbHDQQ=*bF zB`O(HqLMLn*9ER&nI5Z=V3OUH#_CBL3-nr7Hi;^SS$dwvTG5{+D(uo!8YZbT$CTv8 zlyusdl2m9+NvFn?bZSgVZcIsTOi8DYDM`68^)h{(%yrtBlGGo@l%x=6@{LGf2Pq!( zHiHdDQ7oKolBikEx9BVTv1Ikf8qrR6yYoTm;PrO|eE1Im|M*%!p6P8c*o-n5V`jSx zCD|N{eB0M5>hfNH17CcJ7~M%DTNi1Jr)E6g^fOXgc?Na;?ZUkMIXw5GU@yOpH{M2x z9_@t1Prkr6G1T&Bb3qyJm|uQ_6rJvH&LeKmIor;2*N>L_Pf_S6YRVLap&acvGue$` zwW;Rjw4DF6gs2s66r-k>( z<9PQI)QfDLs_^2wSS6Ehs=_DVqrL8;(M60asVexbrz+&>39j`#?o6s=;z?E5#$8jQ z>1>c^D6gd|bT;V*0}Vy5RE4f4&&XAmP%PPJ4moNUH;{+SQ76DY{50Nt58r-|u2f6) zrw~#`_2Ue^N@KmQp{BY6b$H#lUJd4Rn#HBNmaD-&{-(!VNBTy^(cJ6?pf`ov9g2M zhSC&j_`}RH^9{Dk<|R$xq>eG@B=b<3!eBFu>d++y`^-UikF{pIQ951W+2`@<7x=M5 z@GI#-UGXG+NTUJ^fW_Df9Kc~9i?+b&-X0H!ymTK5d__Kt^o{mmif_M9dis4ITQzp* z16r2yGdddOVcdW1Z6@ZPy7DM5bmiDTz4B%^tUoXOlaU_MLyZ&MrkJVR64y|0+sjv= z1GjS-7Nw|w+*KkC*&^gg-SwyHCY@1jn4NF2ic;PVyUXIRJ!+AJQrE|r|5O;PN?TRB zLeyX6s$cvlpaW$y25xf#bBze&FKn@xPz}TI}AF= zwl!U0zFlRdr;T_8Zz!xO{NCp<0!NfBKlkRH)J0%Id8A}}LjVFaoo zRz#pSVo(Fw1Wx#UwMh|}AF(h(3fp}npi1xHgj!9jeHV*t>gLPs9*c=IlrO+SU_E+y znBkrD5bqn}L$R-vU%@ONR?@p6#$V{i6#qUyq60+%3<-!fYIOh`1DgWa8#o-m$v}1( zx`(X@lPj)sS1fcJ)f1XF;7o-TTxWI~nv^mX^4vI9{eB$M!yNKwpb|nY|NY2joElko zKYE@vyq*E{abuhiz|{Xb(EYCC0Y15m+;+tooeO`hyY)Zzy)v*m5UjD059bdxq%bQ? zo=tD}Y|8XhjXgFINW?^Udpl$W%2cQ}TMf2~be^fu#d7J&RLHQ|R(k5?JSu1r%2X(_ zL##NhNLeK(w}n{Yx?F+v4ms(?yPF&0D|Se!Z=w$q<<-nonB`mU!*btBS9Zb|<8Hao zU+l+7|9D3g`$zgQ-ao}r`}`*yQWO~C?r>&cZUBb^Cmpq#ipoua9gf-&*z1t&u zU`0f=qpBlnBb3foh`IvVbW5nv^SGmqGf3r*uD9t!JKZ?WDAVZcQfyIX=Th~*(n`bs zL4&cj;{2}krrd@GAm_cMGVeWNHi&Q=on@&g8PW2bbY9%DznCHGuUPJ z7#S~O#X%~UANGbb`E=@FMg)wHSW3S;X zy}{HtY8%DVI`=}vy2r3b6b-qYfT%*xRTycUK;)NFfHYMb${x8${odhq_8<3qfXQl-4u>ccaUbXjg%eNogI>G zMi`WsQI47*micZ9o1Myx8uxC>J-8Dl#v0DPI9+VY3Ev{yDKczYbe%p zU)Y_SwV?*1%|t4+=TjHA$zZqHZ*asMHHha@Fw`!zSY@|c9J4XN02BjLov+Obpccn~ zXs7P0TE5zw&2EDt zQDb&j*u=MQzd2&0!ch|+{3+}*i}Bt(H=ONMkL|bhuB_f3v*?U&K!VrVRp^ERKrww1 zCt|9*R)uI=yDO}~8pl~9cE9cl#}MtIy@-?D6*_te9yWSATu&KZwud~gm!opMJckVO zMtCUk#yV<&S4~N+i%;nup^xslm(knyqg|`SVw7EArEH9y>W~ypg~fblM{)(M;)GPo zd*8;#vELjpIBt5oy21~#G94@41`&R0J=~}Elw5%mRS$(2TIWQO>6COmsHE#lb2*Lf zJrw%Wc+TCcw3;ARhaQ}{_tOcF8uK0s_0&^yjg(5qbc#c=OfhGSk&YT?svNR`8{N%j zo1^60sF!7;hr$8Tc=k}}XN%k|l-N;L>AcYMdj1s*wIe7%O|+P5H@dP4Q3T~Fh=R3@ zYxG#|LDuq7$vkxWBt=zto^t%){FLwdNe&@=UU}|$JuO$~)Rgzf=aqNH^U6EpdF83# z^U9-@;4ob)PiX1v1U;BDMJb%8(B7mQVRtuWoLt8`YKmFwkkjU-ywm0;Y2v1(JcSV= zUgjxGlvk%G_0ZKCn>5uJJrxG>9U8~aYLQuDP)m_j)`ycO+KPDqCG=pZwFfQYkph%p z9Nlym086n6*o$}%NnWytT(75x-ddjAxFsJ3^?TpYb)2o!{($BD%vJH_G)^k&lDf8o(c!)V0A()mOT|Z zA|A*^A<)&!b>l5W2`~<`=zKZPU26fBI%E^JIAja6rI@6_W z80W{SUImx5t={jh81ui-yWENl>`%rd*Uw>0!M__^SDj1!?G%k73PR(}bc2~jT>NPJ z%U2jpE8G-O;pQtWH!BU+n=S5Pt}*MW-r4V{I#E*RD-7b)Um~(tzQP!KM9i}*Eh^>5 z&sSJ!w^;0@W&5P<;6@pXM2F-e&mno}=a6xj1Wd*>M~PQ`g{+AA3X8EE;^!c@>Xx{C zh3(h}?8i~ZK8^%0xJKqH4EBb37~xIzu+`h^DP5qjTA$Q{l$l9<9rhTsw@DVMR790< zr&VcZQG{G%vCLLkY_)YX?WS_E9YdEw`Z5+MS1nj$_n9>oLas0+gj{~L&>g9C7XRO# zVoNGe=%YNky9|kv1Xff27NHVQ8ko*kD5*von%VvrI2O2yXVl;PH@OPzAlUT*oI() zYq!d-v#7EAERIu99)WVlueLQ->D~(YTca?Mi4T)}6+Tq@X8W+-x7~*WzGOer{JDM% z_mB5uhCe%i(!j_7M0-+A1=^snAz>&BTNox|l2Jrcod5sU6=m4L5!VJseC9Z1V7cf4 z`AsmN)4-n$eFcLsVrlTmwtcPJMvt;%EhgCM7PBxP(otVK*do^}^pNZC=@-^?egX64 z-R`Y0hic{;(dzbA*eMctZzZxd<_4ob~@9k zI70gz%EfD`kHUP~|JP~ieEKLXi#zc!0+0FJ<*w59n zH--XrUcD!+Gi?roOb1z^WDza3V;wcl&Tz;^yV)U!oG=v&NKoKrZ-<9^?}Y0r7MtI#=UOau+Jd}Kuc3!g(2Pu*THCSl0)Wt%Q$7Nbkq*7&O;wx ze;R70yM9l29efxcHrw5Jf8QV z?DQXT)HeT4KaTjL-2+_{SRcUFz)nY%gpCbTx=^9@-74rbx0H@((a{R(*x4ao>^byF ze1Dn72!Ct<0|Tky$PDitj!EHh4Oq0^ztxXju53~OReay~haC>Xn26~S*c(wF!CD)` zwuj+(SZp{d!-oo|z^SDwEVkKbAP0Fs2|u)2-+mwR{rx3Wtq&DlqrD0b3BFZ6_B76{ zH-r`9kE;+H7#JX(8lD-B-r-}z=}G>tZW_le9oV_$m3bh^Aj2%8?6glJaW4E9y3rEv z{B+!HZUrZ0y)Qdge( zr6xv%29q8Z!sah;j)J-tv3V^qY5(N|>Mj?*KYFU5znNl$qWVY>+GfRtsqN(=4`!vGO$Sp5ltAF$SQ9dKhlnV=^(+MO6josX;gZM8XGnvOe#$c zA^XElI8I#%iH=B$kUH%J8K}_gd_~awe5D5|7tJ_KK7k<$m;FTvxK_ZkZwh$Tfkd6D zg-#M=xI&Ch(GpjTNjlXalWX`QQDYBRD8z7JB+9r$FY+){!s&)9B>J=bSn6NqSEQbK zNx-uX&{+u?x~~>ycX2fvuF#jxWi-%$hANyeg^&s{;z}@FVK_!I3Yc4i;R+K()jC{Z z2Ic`p-cS!~y<y=;Ku_0Vn;3W?{dfie{4Wn92BHf zp<9Fe2I$>jLjy&sGi{0DV9=4WMHW_a-RB%$s<7J7Zc?hSCWP#vYPB|eX9V^{q&7fC zgG|@Wo`_BkIVL*iQibCobVe7#u)xU~~Xu0#kp_Di_vB_qL2=#HG?@ie5)Obb-bYMm&xZ5wuL9Ko6m@iD6M? z3Pop7gY0mN(RQmF1||Xa)oo7qw+i(;~e> zV~uQP$0@w^lNA0!_s?+(lk_5u6?%;pZoDWu$17a-5S;uuUSX5oqLrSY(BeW|w@zb$ zJ!pkKsH==1n+=!miEdVz=w_9P3V(Z25!~;=MIi0G5T8pE$ z2mqf}9{H{Sv*sL%t3yTg%EM0DnM$zu)Fp zMDG4rz!&uBou|;nOfte)A(r5I3f*M`HBVuiud8b^-*T0jr%+)IxXNQ?cQ{WW(VOK- z^CO1N0rM1Q_~-bQUZBu?ftD}^r57q(aSKNKEBp?>TEH#eD7{GG&bx8{FpZI#cF#o$ z&%BF|PN%;XVuw+BvBIri321(vfD5m2;Bs7XjnYdLzW5s1A}lOX;y$hPQiaQ|!C&sg zz3s4oPMND{zt~2nguQg$lcW+oOd+lRr8JXFFqlG}^it~Sx6poji1u!3n2;gXkfn4Po$unEU}JD)$gcN>;!uBCNY&l zn#nTK@tM>qb|LKrrPRtD7E9hzs$Pres85UgQYB^5!2~mfxiXH>-GaevOO<1}(6%5Y zHD^*mI*$<$RrDkjYw!_s+!Zb~OKAckLY|k$A&un&Y?AIbtsj7$*#M zx=U$K-a;jIj77ZdWTpNlvd*A{Ev~+G=8!>*rDw%bg=9;G@G=E|7_R$^a+rW7tp&8c zR_Wym{&4)|`XDBtX&V7;u2Z^7!Mjqx_4f*R{Zj#-|D^N^h4BnkUQ450ciYbjE2=5( z=uNMPd?R#kdPU?jlB&O949p6eA6C(XwboVGYZ$}3QsJi06u~WT3i#{?3=~hcl?vYx zAHi>&&ebaw20Ch^My-S|tWs#y8kgRuh&5>y!h8*c(fU%QS1UY`q($?N)%=9F&{W)w zUP}GUI3pF37*jZyvcYmH%c?~LSgo*MGo#5Gg_c+0#s?L#^De?Ar-+8<3h$~Lm0qh{ zpv_kL7pKtYqKvOe6@RoNrp#hKEmG|f2c%LLyOi?YZnXCh$2DuRH=p*(X+F&JE%srf zZ-HO>lHBSng?@~cnRzYa zNq7DCK9f8QI<0-K9|eA9gjuWp?CHq;pZ_2pdJIoIgXdntD{tcM_wnJU`1~t;{T*`n zey!$wvX>T(IHUqAvD&q}>?&#ANhfIu^AKtibvW(4I1k0%WKUSL#R*`o!WQw};7G(Q zx>jMDZ=O%;EcUIX?tP2(UXevcH%2(nJ$HR63J%+AnKcy3z(jT~rut5831gSk%O zN5X1_Yv{~gqwwgL_&Qo6T9aSHV1Qy8j>a<-f?1tv6xQm^l(|l5v=_gtT7^lpnTz2! zi$7txoksJ*LaSI<$ysTUiM8k~E_a(1Uibu`^MadcRGFhOpZ3%$iAdPYIGaR+6qCi6 zusoy4n#e_LF6|{7#6xRKa0XjStJE+%(_#VhaMjqIRy0Ju{h~{;<>E5Mq@AJZomKlAoLZBgP$h zSS%lP3X6qv!2JRT6@HD;vcbnF6K|1dsU>NNUQy#Aih6KK?)qL*l_)a$g;hZKGps&P;sbHpsZ%Uksh9ZVZVJMK)R(>1N1 zC)Hg#sXy}ngmq>^34J=oaH<%Zo^nlhgOLoOU0@b5Uq+3=W*Qah%t(vnv_I^$z3DMG zliM4*Tpdyn_b{5&vBuR%gN$muqxM-Oql;rTz6H16g?nEJY9bD)5a$6r`GPe1^g98g z-7}e`F-I1^Ln^o*J48pLT!Zf7+jNK_4r>jvy&mpKtf$imy*keg>(|wzpOKh;o{jSbwq~-TdACnCIW&$6@~wKavCe0vHvj zWLlkN0jv*f31CkkEeyTFhK8XmY-||jhE<1QQ&?@7^i&(RH4F*iLn5%bL0to3b<=s; zsg*0j=7!-wSadjML@bWLwul`Oh;ES600j*eH$ZH|evyi!N!Q)zAvRHC0ToNVD0au% zP8R7j6E3h_edy(j^`kPdFo2<9WnmZ+F+KvTMHO>Ix%MlWNFs)U<*?J*54ITdpE{8N z??#P>QeTBns5|b%-=08q*d}*_dCZB&&kEgXp4k%E6OjBtQgjp%ssS~p&sKSX1I~`3yca#$7x}??h1<| zwnbn^#0giP)1aV%(nl2@ejXo1GX-Eav-u6y!o2)O0ndLVfOb1U=!~TFF@?9kL#$4A z_@}4gm;6PFFLmI-=Y)FjQ321tD&PeNzM~%IghGZc;;LD~U#vuAQ-`WQsL2OrSA7k1b31m z=@9LLY$uE9@}$CKkq1t4{qAQl#0;m~A)RhcDy+Z;V57taMQcnGJ!iBI4us;eB6;Qg z$dqu)XpQ?H71nzn;o~pxhfQ|XQx&%cQGzQNC#ObI8k^BULRCM6F&kJsLF*pmW&_*pUnm;Mqprkjp9*%z zeT;^G<`sPTlcFZA+3mRN85BwOn*=T7L%)%)6)~~qy3o!%LF2Wzl}^^!Z>juE*7ELr zFRUzGs0GOmA;Wa3#!9|ATTPV)}#cMT9QY7(lDm7D@e(p>a-QG;XFCL)`Q>0WDh#XnnPS z3$Ir6+-h)^fYuiXxa+U@>mzvK4Iw`LLg_3mHL_fHZDgaDrSaGMmCokAVg_Bi;usX8 z@y0uN=TkSqWrtqXY|2H<^qWI-AX6XYFa(A3NRAc@UyjD--zb8v{8vEl=3An;apfa;>|1Gm<(&dzbq?b`7$q{uA^k&0u^#C-3?7!sd7Lnh zn-q)A&grS#VuLu`b5vbDpg<^XLI`l#cisZLh(rpDXU} zuR4YO?RjBDHO4i65v*xTMSqm=NCA8tT)`se4%yp4LD&2THy_O>PGClcQlpBm^Y}Gu8I_{|vXn+Qd z(ewr!70Xypjdl+snzKqLS$cbFJp3qLdKGWHEgkNV#Irp$4rxv^JvFA9c}6i<89v4N z>Cqn$Cs98I++Z;EO)rg)KE_vNG{g-wST9Q2UK$^Nr3gN8piHK--Wq|Xxb+S^`W`+K z$HtKw74*y6$Sr>d(}hkt1=NBLWgyE!hE7x)95l=i)mx)Gm-itetTFkv3jU_n9nu%xBy#8j~d@t^MP_fy&9|>^U=RTYx$7oE_l^Pqk`9I2i zNLSe-7i!>aoO>;9_!wXOfM42cbmSCMz!u|m0i~Ux-1^NmSU|nvW~R~YVV7D|*-^k4 z6w>*6qK7G7sSl&*QaI1I(T7^!E+69keYhi@@5dtl5?ctx63E}N>-T#R0ythW2N%4P~-a_(2+}6 zg&X$uFDm zf=i*sOuk*QHq)YqEw(7N6;?)f?F|Bc_(ecAKlMDBaSHhsFkeQYMuF*Lq5pHW;aKl%|JnGB{v_vMUQX0Y3) zK#IyW!$AjV^pzmbB8|K5$NL{BhY6S^R>C5UESa#1G{%_6Iv$2-5(SY=#LBPH!Ks(tudoh1g#`94VWI^YOqEc*P`x{US%*>?rj=FMUonYc$0?&z^AtnV#9)nuhRH++ zYpgR{80#Hm&!P*%YS4>du*MvI3@g3$RD5;xNxzGHn;bHLvDl;h;DnHiy`#Ng3|G2Ak+#eS}e1PmB8u6NP!Vrx+@5R&aDPo`gsyGv#(^%2_ zJ?eDk+E>Gt6f{E;kHLm}g@ZR`PaZlImVgbz^_$f&X z`7>dO7Ha{H>oVGVSNU$nR}Z?K>Nb~MO8OR-f~E?UKm zyWM2|r%e|`O4Iqjzf5+x@W=fAQYRV;zsx_zFUTtY0f$5fk^)Ey6b6J{6WATV?m+u6 zp$a1ULZpGhJV{ru$CkZ|MP1FYYe4EXBs`Cc2e{`eKf%u3t1Q(V35^?D{U`RXcWCR z51C^IoooVQLAqM>mQ+fkG@7{xqI>IfOmyus7dBH&bRX zMxx+HY4oEf!U$91u0%>&yw^t~#dHe=VU6N$vX7RcNqV6}CWnxPdYQ(0y_MN(;!QA| zbd<(1#wK(XZQCe~1hMCh)^tg^<{`ZKA)?)qKZ*N~6&fS$D8@ZiF^ac_4vjmgH{Q=| zE8T$t^alnrcieP}-)m6^#opfg5Gkknng2iR=gclHyabnCgNNRf{>IRBF^NLn3SDf` z!$+sm(Hg~K!ye5kkSR<@YfQj&Am8inVFmpvHhD2V>0u#O04ZLkhasY<7_CoZ4Gpp8 ziiKvh#$0cqhebk-VWNX{F7fm_8>4aY6^fv(16STC)Qz_bc=ucUoIqDJ+C|1_eEYMo zxtkYcfb6r!XiS&iX^h5<5F+-$HJZMEV>FmwNLt7YA1witV>EimoIOS(PmpqrOKw-p ze|EV8cRZ~01dZq3LBx;(T2Dx?tyD zB|!XyG@9S0DB8wTY)YpZ%0pW}aqw8RWMnU-SaN)#9WJd_HOLU7B8p|7OZXldV4Hq;Nq`xHiuF&Xw zR?oA9bLg!2vjtgy_8y1KIA{4e!WkMhAxeF}n4WH;~MT#)D%sVxNA+~SZ!3eOvPo^X=S?{U5$ht7|0Ey()TdmJ*O&GI(F z8G6Bl3k1o&u>XaE9KNvAMS`rjXsbi!Uwrst#9o)~o^fT{6>X(_;3egk2$FPZ_e%wd zyKLBHIB;$3b&9Hj7b`UeS*FgOaT;>l15$Xq11DtLG(+QL2uZfxtICop?4R)FmAIABn`LNKp z#*ZETk^rUzx`$yySX}t2M)90cbGf%3&qjyn{!T*x`a9F(Qkq>881ngBZ!vzv`~Pc~ z{pjw}k)as9+1n%9WUZU9Pv-`hV5$B{#@JxYU7V@tRqi7=q~$-)!>|AD~h`EGNAbCmVXs2$SM31=x z68$ts^sotG!kHMxGbpMwwukKw!=A91aAC)UcMV7P z@O(%04IdDW0pVqiIuYJELMnBR$d5n)O?VX%Gw5l#(D7GCY;nj|iTYTr(eipl@K65C zt2GA88EAsg8+NtERvk+hAdPHO>ySE8M6A|G62~T1mN3uN8pU3PC;iRymW2==y~5_Q zh_2CSdO5DUQ4ymy`3Xf$S^& zVt7m8pLZK>e_ycoKNIjxyhgHY*{ZpBOQIIJHx(fa2dUQR&&aeg2~`q`$tkKeCP@TB zwZL@VOT~$%njhc@zpzXE6CE&ksX-gYFHa#^wM+k*YP01(L&rQ!aa~YK`=Ui9!G1vdB4+nAm1n8>!GQYKZ#< zhD4P*q%>-*LncN|cSw2T$?hka+jxmX>YEidN9}nNT_1(bOPZs!`CRv#P6{EDL&%2a zCz~U+MZXpp+M=w5Ty|sgZOwn9id#-_zwL++(y3KuE2)?eLRPif>^Lds<((%*Joyq%f(_h=lU)}td`FUOhj)C%t~$YGY0X^gi@M^B(X<}&6v&4)3* zavvu6vcnWR=`FGJTWES1s4+;a4O@6grdZlSJJ)Wynldf)7LB8lzjzC6E%fKxqA}hq zGni?T9FM8kO1)7K3GOqPer>BpHUI6>JR5GShVy*fs!?nv80mRL2$?9l|83M1 z9;4R0Bl9~9^AuS;C<2TBgm0KIf1Adn5OPd+Fz9G_x$XRYds9@*vWisO>jhlm0HxOL z8h1P(%m?Y*MOAq}Ei^OD3Q{WX%67hB6^`R}R@*}siS11NOw+`6D$F@0Z>L2gi1aah z4%?{=3nEJl<72jKEDa%}?0z@E(II4vo#;5#k~(aM#?vMA16{8biMKPD0RMDO>#L-o zjRWnT7U}^9Cdj~dYFu;=?tcm|zl+a*R;2H_A5XoEcR$C^4C>mc5i1hFPW}WG1$Jue z(nqz_-5o-X=>#L3;~}J0Ud^2vn+4gWarJGeGF299?QyHf=tq^_U_Fe^NQ^|;tr2+^ zqApg%2=-{SX^U&yDPqmq2x#{No_toYm)^rCKMD5on@aC>jV}}|2=0Ci&%7s$d;YGd zae3iobfIrrwjM?ghjIMWBpqIgK|1s7^8(5QigE;t_#OZ5Vy9}J+eU_c>pcSQyLDIkein0f3ThRK|= zkDK;h27{?c87-0D`!o`5u2u9FzV-&(cxMQ^$zk_DC=8xV&|Xs(+^>a*5u#2b>RdFr zOc4u$k3Pfa-wNZS&+vuAeq|c$I=(E4bfn|?10gCoM5WU@*<14Z*J)&E9u{~&!ylj) z{;r4Q^nq{j3#GsV8t@|!p@>C9321sdZodl;JtD*-Zwq+;8^yyGgXj7k)M$Ajwu)Bc zpvKjIlfrf%;=@ny?e{|bLS5@YjbDQ>Q}@MayN%GeqjLWA?p2i@k3%(#(IrMo>l~pKCSc-jd!9o zYPfeDOaHE_0FO;NqS1#MmmR)x=A5na2W?eH=$$cJBbVaHdfyJZY-#lLRroO1x71PV znGt&qlP0J5avf_SElS7bXs9Ec5yvxC+*peY%<*IlPrf4{RaAXPG*-wobA(F~#r-21 z6GDi~MQ~K(nwu2?Gm0P6xcN5441phiim$jl9M|GZd|Zn&@o_ByWydw1eqE}){*Igr zaa`lWkA*QYoOCv^K$ozG%czGxd7 zSAXULk){6qzQC1#SrSc#cG zp;3AU6)mwL^v4b%nKs{It{A>fXrZLvefFTmLGk)Lsd394is0VADVlR{c#^@wm2!Gb zw84R}Lt$tio*a%Y;bq~N96lvns%`dd^1EXxB2FppNR@ej=cxd6+Te(8Pe(Ey&e|oE zV?LVcPia($rhROtEAM58Sm|{t-4YjxLus_ZRB^b9HrOkER?!CC(T`Hz4xTV%FbYf^ z9&J!TGu8YMJIhzdQ{4k+N{d_M;$fYzr-O+sA*aoV)qUV zW1MQ4yY1eAB8N;4OmoQgz`+0x1;&NR4aA3KIwUi!XBhIsg6u6}2f}bD?3gRt61LkR z$>CkxPg52?IUG~M<0GV#Md9lmdwuxUaBLHH2ZJ}iLPxrFEzv7@XpBK8+Oj04O$NHr z^^7cLx$j`)NB}8Qq_V~9se_TTLWNTzr?GS}C^6+c>&zg=_OenGBRPyZ7%Z{1eCLi> z^gu7yzst+j!C*dCK{2qAI>L@dbc7uZT3wFI?-2eIuL^kbRYm##+_nN3$PjDrI0qSP zXfgQk3n}>WYkc#QU_Z6jh|!#;V|f=mH7C|!B<&V-mykM>^*o162_efR=quJpb|0w| zXP9*`&ftSYjUvkZ%k)m3`(hB)Lb0IjUmUnfz`c(N_^Sg9_>VJ4)M+%uQX>~lAvQ;|$WM*~}CrS6t8zTp$|zI4Vdhja*>7%-eB129+k)IOb2I5-*Oq zREfkJEc9gki1j1IKa;`WOZ|%clt6v}{R0C6C=bjGV0K_m01LQC4GA9>j`Hv+;g}wt z7$J3eTk!^$TqV@acL}(~fgHN!^wC2#hEocgz>0~YfR8sAE=FfTsMLrzL18uCV1_*I zc!QZCWINx5Q%#HQ7-oy!$CGIc;=%l;@dlkjNTr;A7;muCf837~{*-`>DnHOafB}K> z0OpI{h8GCT4q%Sx-}pReeT@kp5{_Zv<@`{1c51vqal~kMvm-*t+y+$*q|bSRbTY`7 zy-X*A31%h@A+bDO&mc}50y`Ou;F~p0Po_+;muB-6gHbjE>Qv!oiuV-^nB7mML>hvH zjho@Zwn`@$Mv5lTl|oz}>0pO7dIOE76$Wc*P|x6lnQJ>UKUEKn7?1r%y}XUv z`e4!5u^NdKp)R-Qj1lVflVm}_x^(R zd`pTYyBhSj#a1|D?P7TeGVc;-a`FTN|_-A@GYG_*{Pl+h+cB>8?O zCn{#)&op3Se`MR9Jhj3Eku2^K7SfB1xzV$jI$q`0^a#p zK(-uimSs>bp6OXea)?Ng+s0)XY!vNOmcdR5Y0okki#d=wb5P}wDr|DdCeeOp8SKF^ z$I0^b^-1Lw{>^@D@iR3{mO)(zIUq>3!DCP0tsmU+;gfYPSHl5xFBj44s~<3#OWR&< zClk!+rBTflTmR5Yqp$8ym#E1+>%ris-#Ei{@CI`XrrF+5M5rywG4wJ1{ugW(4M2{; z!%yIs4%7*cc(8)1Kai2RqzjF*m>8U&+!Qj~srK4wG5g;CQg>+S$CSl$=2F1)^ zJYH7yASE?>GY!I89(1tV?By5AEk%wwjendUZGyyr+8`u`i~Xiq7QH z-(swE2;&@uQ^=f+#U4`+=NL@&7CQDqZzZ#BlAUY7lXr6sF1%b3FdIa!!8JDtb^Bih z-1m@xr{2WdpE~SC0bhPCAeJh!G3Tt$pFmAb5={y4+5D+*^z*$WM zoZnW!jkhY@)8LDLDxGi8=2G1B5FUM72!`C}8$?D4#XKAN2IsX=1eabTfR8ob;M*UC z`jM)yd=uO}|Z^L8H;pOSf9%)3i)f&&eETwlorsx3Mv>Do5hx;GJbH!R1)PJ>+1S~3y^5gS z^8#LaM?ksZNq_yAt&@l7^$YE>`x&e;yNuM?E5Qu?m{o#va(`YbmKsBT;sXpCw^Rg8 z9JuBo#aOIMF2z;t@bY^?utt%Q8bwAXzaoQnFH6}|&kK0(V^B3(WQcwGrD9w_Iz^R2 z?m9+sC11!SAIbD=D>S9_0_G99(|7<5Plp?5(7LTOYvTY4OW2l{*Zp8afn9W-YXyL&1cMEm>ok|Zexbszf_%Fd;{ZQ$l27kE?cmEBKFJV?O zgSJ;mK-zGFt8T#4FDPO++>aODblB|zI8h8Y*lmt8-B`92W}*!uNrDs`MC;BPDV+C4 zGB?C>&U>91T{_1m0fp$`VIcPsOTD!ows>(qbfaQ&wr`nFvHi6Ft5&tHb2|MM8w~VD zd(s&<2E}e;P;4-ormN|iQ%SMG3JRGq{w{v0$wg3HFoilIJCtJXIhGsY?3MSjn9J=P zE1Vf2BuNay#Rh3$TJU0n4qk%0KF_O=$_ZX?552vCj;a*jmSTgo-WJD+^L2B#&maP6 zG2O?Ml*I;hz9WuvK#&mzmpp(69#X_!{LUQ!JHjBFTgFkM=pSJ)i@T0(qFWeYaDc)@ zrpYnVsGpf)Fx5;qimK38-@p&Qx@d$9Jw_wJ%kre4p7~u8Y??)$EwCuGb7-g8ZKY6H z!_HvQQ3i+XF^dt*a5oKefdyF0`~(|$zzS37ml(`CgD;&u{Gv=XB?hTxph1?+a~D}^ zHn}m*v1=WtOfsyO7|aC^2re~f^r9kY(i|^x0H=IA+$z>?dM^%& zny1W&IjYQnhgXytJoB1VNT%F4knY|S#IdH#AlWUZbmA;C$fPWNW`zPqMGVjb8E!d& z@rj$X^yPMf3~-PsrOb#~7}50oO_ozM%8VRCFSV1!sjiGx&d#)D<Sf$7VPfV1Wb`%8pbcZL9TWH{@;dT1+*#@FKLm8;^d7FMdS4 z=DR!9;DaE^gIuN>w7w7*wN=Fa`ltZvbf+4ZpJtlDosZzBY`U!vV2(h8^Db6opYOmU zp9Etsrx`IQSI7hF$|%kfx|gnJfZZO=ldCEW9(_p>Jm$bxRLoXzzex{)3WIJTq@R2p z6(;C2Ajl9cZO4l#t%7?nei9W1n*^C*&|ar&WKru>Pvt_iiJ|^vyuoNY3W_zdxDh`^ zjX0yKRJhj7tJ&O4TR%-?~&NR63X597!p8g#F_?IGm`@I65 zepl&AgEzl(OVPeovF8h85)EGq>}rcxBml|CVw&h-z!WUyssL}6+Kd|MnmeOdZZxeImclKji zlbR;j-XygtvYHNPieXJBHpSeg^P6Hx)3r^puj%oo=+vyR8A_U!HpAg&@y*ev`OxMV z-F!@QY;V4!x#F!R`2Sz^)WZ#8WK)!%Hj)(_$>FA_jpTT<7&j8R9U1VxGinHbseh#( z+x>g|ILe&Y^fANw#ycBhcjH5iainq2CMa(*u8Gv`$4H*pR1H_77O3@a_v1j|NB~2_ zXNKc&R6-*RY%;6~HZ#SF1-%6r52@@hj>*|%edR(fCNBS8f^L< zwZ%%4-S&V*0+JzAmiWt78Vq8>_hFdg*e96FEyhdsq|y{d-Bo+Td>GJTqI;`ni!r6r zV7>&qR~iiSmHAw4-%N*W@@;p>mfw(Vj?+Vqwx~21D!!9^P=S;HI`iz@J%IxOq=eCX zjt|)Jy9cE25rL5onHV-bOgM$%L&F8>7eY!S7DNbV3p4E0OGHPdanz9zwIkxNtF)@& z=7v)HV8iH0ltwZtuF{|`GW9HBcRy>LLvqg^dA4wloZa~xLHeFEz##+98F~(epHtze z;pa?p$jo!n-OIM5@#@CXVrS#s4ms5Lh(pFTnbrgwn{RK99nG`cqpfMOr3tn)+3sHZ z)TS9tk(Y&y3o<~LmuqNY1!XVZO6(X)A<<`~+1cyna67~uMhZPv+s zafQuFnxVAWVMk4Ew$LF9o2_hyRU!89=A)ZqO!JNIsxMg)X|nFTViIbxo&z)Yt`2w{aO{Z!oXJJTPZ!q;L2MC zT;;$k9}302>s(q&ahTe{llq{+2{YefiOm7>m>4UYR;w}MKR?%?m-%lsYLV_KRvjyX z>0A>`7Ce^%$VM$i>qXr@*WidgsZr{U@lY=DSaS^yntCHUC8;@|P790VSiHGT@s94` zo^Y-~x|i)C$1C-eo@a1_3ymjsyD(mOS-_{?;=6Hl*Efh4Npc=H2tDb=7i3paf*N2b z*UsZ0DICo+SSfjF<{3%FAYElLI;5|pE@3AUt38k5cRYx69>s^L7Sk+`w3Z<^HDxv~wU%XK110L&yqU>o_Yz$YD)q&t)`)29fci5?f|4kA}20vc0Y{II9V+ zyi4g7r?8afiegD|Zee-|$&^#THW)0I!_7As)QTt427}EZ zWG|0ajq&0=xr@Cx?4C@roNBnipu5-4Lw`AJb%Vhui3{Pyym=nx3wxu%C70vl&(NPn zluEsTNuFaZ_A~Fz33TuwiAO)p^ws#V$9IguZJqqc@Q?6gAp_Vu1Udzf5||lK>}oEB zxXtvSkG7#sI(XBW>f%55w9N04gKs~r&%)pLnd}vK=);^`lm5qjR#OU%4ur-T>~qR# z?T7kM_U~_T8QUNBhuU{@W7_7O@X*CI9qaqkr?cyituol)PoAVax_&I2`^U#Dg%@9f z%iqSkALBdvTy8YD;yU5~?L`6klq-kY5*wtL>3E|-i7j)SjdqtqHieK_WB^^oBDm3@ z4|vMnMuTFM17kz%6{vyq=uTnSXi($h*P|Wu6?$`LC2hEJLG~y&1)ixsqIXnxuf}!WL zB34BRiziHA2Y8s{HiJ!c2suUVw}&la7$rE8Pbis!zyLdCj?(vIyf@iHnyMLN%n<0M8^3UOSchD~_XNAuR$CB_Z z;W!w6C|p?gKPupjcLmV4vCY{wwuSV3+vr_Gh2J)+nBxpqMQn8sfajD+vw1GS!eE4? znc2oSslrN8e~g46142lpsByO$EWj#PwD32C7UymBYN2Q4HiKB%cWh(MPadf%NKf|| zdP=IpZ3YGI^to|9xz0G>Q6HxIXSq8bCmQu_2D5!jTzQpmpF`?=M;%h&AM7d*@lW+* zj(?$}4*FvP(&L6eokKPTb~|Kv_$c@E#)r=e$DHtmA!?dK7KSfz$d>Sf;W!jN&ONXA zh)joM^3aC-2(q^u45jjh)=V7JX$D=HyCv83r~bLbU@X1GYH%Eib&uNPOt9LSLS{dp z6cZf(gU{Uwn-nRe;x9289k@=XiRa>WbBZIy&Z%X)L5-wH-A;SKF;}$D9Odsf$@L>t zX^1*14ouq(CP@s^c7x@1y{ovMM@k&G$v|gx2l_E-(Q=s&wi|4e2N-H)#h@|2&f$t)q}_tw@Tf69~W@KBv{eX3zcRW7$sp@ zlsf504sySuW(nAi<;#w-)DW&%teDt+|j~)1?Tw|JOx%V3^ce<7erbXe8zMmT<+EDgW zugds}{RY#`LL)uQ2q6p2Iy$}{H%iyhhx`y@fsSc$NkbPZt^1h8OpZ*mRGq<9w@JmT z9iTv6XGEZ`GdQG=Y8+#p{5aFabyy%aiaNgQ8x1xYCV;Cm=ps9_I)l^@(!=dH=z&yc zP-&N0xy~#>4!A|~fRRP=0R6OZ&#Q{qYaa`E{bR+QanzNVsh4Ri7oqZ?8}30P!#!wZ zxCh;E4^k>E;z~D$d8fH`o8=I?iX1Yy&p2)v${8&<+qGQDO?{|^KKsj&lunGEm9_E8v%KXl-JRak|VG0Ddaj?l8r@CPi!GDw;1 zWcH&DDDZlF!n*BtJoh|eHRGd>8T9u0c$nZ#^sw4n<7(Xgpj5oWfd`+%^ZyjaKV#k4 zz79fXq&OBbQXDfV(UX}DmkG*`8LZPgT!Zx?DUR_x z5++tVMkj|L!e3TKQ6(nGV{|gzPaOsw;EvG&j^~#iGe{31!|XWsfQE;VaXj{Vwx}_W z8Eml!T=~`zk|Zah9WzJ{Avs(w`->_0n87qT?fMu4HWs?)>z1lx230bpa#TDvw+?CU zQKoqX4oMFoeZ=qgm_gqVGMuO9PV`p0ifg=W4p}S6af2tHW)zgH%1m0U$SdSrPHhv8 zQ`3}1Q|DeBqHl3(u!b4T8EONk#u)34cbv?8Lbccn>EnWX@W3N@`eS_BL1VthQ!tMk zJn|$4GhxF9T98iCE3ZNnwZ{!yirnJ{Ig%^)IA_oG8iV;tZlJw8#uiw(-5(3-30!37 z@~n^T7CX6C43wlr#|?@k-NJE$fgxlCYTdY2hLA&Gj@IK`ScsgUfs0{wC!GG`gc1G4 z34_mmPz0aM}6zwfmo1OHbobEc>Xto+m4pBvRxWybf)1tkV1Jb3tJPCA)vCgG=HDk~B z@JAb9W*S94RWvbHrmq-_Pref-bLYocbk?-I##nR-A?138M!cLf6l1YK)8Qw^qE?KH zF&1g2uj{t2_+WOh7{%Xq3Da!twEHYNU^=je+fi!b6>Ie4{ZHf}N!8pv)tQ0x2p?rJ zCa{r-@DB$N8&(k}br16miVZ7tR4m1vMPVDlu#K@tvEiM=(KEbHI7-8phfDo#|Fy;j z?ya_?9!T{{Jk0Xucv$MydN}Uw@nd6PyBp*m3gdl9@b&Paz*oVo(CKwg@8dsITk5Uk zRbtpt=RfV}swd?pp8OQwQlji&k}|5sdUK)<&;kAz6IXu%L1H*@hE8Wpz2jA5rrmMv;JB=Zc$E@&sGildEy+)&6cV+-l zjX@>m0h^KJA;T-=u_Hq~jNyd0!aM4rJ0EkAugr%9zD+*t^9}GzPZei2E%;+osdoQs z0$%$P!^NAaqs5m$2K~7^TKw2vqcct7<(e^|9WA;u-lGrIYHM`8MrT^omilYmMOmm= ztA>iDo#u!^S38V~??o2lP|0{PD}_>P9yS9yh(LM7k_fDbI2nQX21N}pw84%B*xR6eL!>qw(GW8mE^3JN4L3Bz zk%noJ$cgM0iN2Amqp&~fa1;if+ov(cG@jZR3mPwKjBSliHb#7t60tO%g`|LjTU zMWFDs$Gw3kdz|Dm@6`Z98!Tvm!A(k=0K7m$zbO8y1-wLQppwUl{9j#X$eGvK7jfuM zud}Jajs~dbFI^l}7KJhY{cX}sjan04dl5BV?@0WXR%rgQgJpI$P#BU zRJ!*_sA8Y8%h4JQUtOLdupz ztcXBSgP{$uqru(=7}0QSLyT=Wvmq8WT;CA88`d>c9B})FsSU9@YJU_CN2Q-DEjKjW z?aJyJ9%+cQ$ec)Yi|iXIW$EV*bd~xv9^4p%8;@y>sf`yj#=6Fv8cT&mjn_G3Q{!!o z5#1!d36h&+HBma=qWKlz36k*^H{2qOTOP!N4@-KMcq?A#@fII^B1NBkj?cdo?AssY zxQ9*_AN_(Pox+4kc}zw#R%0&HL>y(D%rKh>Te#`5`??>1kmF7k`y{5Qlf?lB`z2Z)%+tvtQRMdoi$*PR?URaF zC1cQz+h`yjDL^j_h4AwjOG-~{tmZDB3I}BJ)!S)0T4dT>T8ySxoJ2<+0oQ}Ua&tT^ z@z(MT^ei9xac&+i3d018p=|mmb>&(uuaXp%2^ODyFO2U~_{^B&Ccz>_XKVBlooRwa zHXrIhdP*|)jnp1R56?+_5Qi8NS>xN!NJuMusgPnad3LzPT(gA2|4xIB%v`WfQkDhz zQZrXl)g@R=v6ZxeRasOsLcd1R%mw*Uv&vR8;X$T*S_i2}jHZG*6TJ}J^&~LL`nW~= zKk`$+bDk0`I(oU(J}>dG*4yo&%E#Pj2^Ps-rh7JPy&8w~kbO~tMP3NmO;I7*m*7K| zub&UIeO2zh$(Jz}Nk;QTi+E9a*Xz}V>c$d1k5sdcvLuvEtapQA}^(N@$ z8Y{$?JJBMRX6J4qn*92zlDEvXJ*T7`afN%2H1Am<@Wyf%hd+{ z>~h_8DHV$~%-Gu1U=G#*Rlco0><@GdQyfsR#ysXMIjaAx#ymFP!K@~nKBQ5sNpdmi zLM6%0Bsutn%`^K9CfLaqd%}*mYfND9#bU#RjL8<$_#G}Y>s{lmW`{#|$iY6z+`CL* zTq@lnf1`F;?BP!40CSV~7Q5(gh0E!(-jNZxJmqYc#Q}TBq9d~LTh-2>E;`x%#@R<- zsciJ7c{zBo?&#@y>51MB8GurUl!oev8M>P{(}#s&tK9tsS;IZlcw0Q|@b-Gh@MZgu z=R3Wc6b|$a@yS!m^9}T2hPfAa= zp=ureOSL$EvR`havp>U+q5jg-sNxW{)ql*fcZ86bKz2Zy#RSp<=o+GWN)n7@i~PVK zS3Whc*dfyeNwK)|aYgX>^8#LY1*0WzWs1eCZ;Hb<#_K6OrpMxwFY!xzhkp5!RPV;* zZk5#gR+&}wVdzF}^l*!T7{%izrUQ$x6v4WK3E&=@n&4R$&f z087Q(mSP=MB~)jNMpr4_#p1kYluos1@-PoZamjbmEM9sAZ^hA{rza%rkWc9c(9y6q|09PP3?^!&Ev?eeNeJ{WOb4&86hBJ8<9QXwUhf zUUa=_7I*v=k24yX`-*OiPam$)iHDUPvT=}Bag!7H)6*)=qKECn6Dw&QPq*l63KZdhl~=)8)a1C;1;gHSZ@04z_+n%sU=d3%}D|18NP5XM?ANk#=!^@LNHpAkTx6xhm zU z^yZo+R*^rCnKNq6VAV5yPbVIwn8HLt=}fb7s{Va1O0#GN7iLN{ah63_+Hs2{u07l0 zwXYOGqb6w526w)ubdH6358r%?B<6SNugf*I$j_8xao#n!;op_L_YJ;H;&DY=7^`fN zru*}_Dhu@wbkIrEmW*_%PqA3utX#{3MXvah(sddeLRJ3Jffh!3a^+f#)H8XC;bN_H zH;aodQ@V!*!=~~qnqQ!FPm2q$P&(hbY$IbRlr0pyQ$B^>9#jboqakP^GalAcy*!LD zX!8x$(^{LyApen+$m9H}e)RNL_@%d<{QculLU;cvV3cp64{Lo}eNuSs%>tMsC*O)@ zIN##&=Y*Lk3FPv*&!D?Pz7?&l6fH4p4AzJckx#Wm4%2cErqDdkVymycAF2LKcljLJ zA8V+4YwwS9tR-T^%xAKO8Y}&kV7!x=(Ze^wjch!o0JYvO4KBoEEwJbzNH2>^pT-L>;s<&!_OiIfQF7uffBaq+Z7x*=PrrZ{e^9!& z#n(R~Q8VbNx5YO<3MCL6B!YJL>qUnXW{93`9T`1twuLOMNK(hQ~ zechHx=|YRBTX1_clTGqkLHYqa`ivAl^98=5T99S^ENF>-439DJ*y8y&6nSraAz&@# zy4{jnqrb&vSK_V*@X(8R?QMKerZJAQ@;b_>C%FI0;nN4lbSXpKJ zTekbOj2ZT(@P(eE{|Aj#u-!zq>0lDfpIt#JE#)`3i(zjGOs0L1A4iJKw&*7*D*9Vw zNcM^T7CC02k;E9%u$LrK>2J}?P@~`9V!cSk11usRRswFr-yHV%yGj>XG(I1fJp`&z zv4v?yhnfPFCv+z7SA1cDFd*+`UcFaDkP@Zsn9@@G_vKS!Cx&sm(zN9s^GtHocR#tU!Y&3B}WApLTPc$FoJ>@3}1 zqrXhYqbxrB1s!C93E}h`8O23BDYWj4wurhGH{Z*Tbr=s$*kt!oYf%7n3S zvgZzFAQLG~H;Og$PUADUzue;Xze&jhj|$)sedQL9K826}A&f_#!Y2;<@>gLE@);UPY<(8Y( z4yg(utIc|YYLV>9X|r5!upvb4H0`Z4+sX5y>xCL?nfA4H8(eTT{vy$Q&pwAYKSn3{ z^v7D9+e{HOa-dmjw7CGx{5jT=2x3?NMJheS(|lMUdc?6b4CPbzMy=vli+*~ftJIIG#}OfFwB%?SYvB_4 zjpZvc$5osWLgwi8j^pI!u@<{UG8=1gK*t*y9#ecNJZ8vE{O~KHb-Wwz55J;)2=64HXiirCo? z2{`XETz*9mQ+k5MxfclFp+l1`0?l#x-AI&=YqCYN=A3O#kdT3saI zj{5{~$(zdUV+sRpEzWN%xn`zYyvqBZZt>!)$YI8zMD9G(%n+U{xyWEI9Z}kIU)Rl! zuqd-#fi>a}GTlP*-b|!-Rn2KlGYoqF`}XrKR@jaVVLiR49h8PoFg;z8>1-4`-}7gz zefqZwr)t(yQ0Oc=wCNT@Bo#YHCnw}jw-`xV;UZ?>*iWlgB6Sx|89d!$f}Dyro!SI? zde5-9psgaf@fHEM-zDI#d-3q|O3$=-`Wd|Z8NT>Ri13C2F24$YeNr%{@2a%m0X~%$ z%`Z^|EgWFJ$4ZNv?-wSMj#pYqIzG!v*!V1qi!WCMSKJ`ruD=R+@M!^mci^QrgnHQl z3ZAnp{`I5Mvn|eRjW(BJmR_n+rDJ#?jm2yUo1bk_rFZhs8jBlm#y4?1M2iO-&bIg$ zt@N|G(dOZ0vn}qq4}W`#OJ+GGwHZ_-bd<+C+v1VOyXQtu)yNV#}Um-Zs%+2#FPaV z?f#T2p*-C}wG}Her_2J27OjPP<2?ayI`EHZjiFRd?9kne6u6{#3pmxvIkMZe(hFq~ zpJx^u!S8ufbVP!u=uC}FU8*r#FVIRavbgkGJp8#}*WRG?V$1N7%m1$Q5{o-O!Iz`7 zpi!4Az0{(onPf55#slf-Mt$vGpo16dA;HV`kngScu-iN0Axpfxms*sW7>hVc64I*o z?;BM8zCopphg>R-I;Y+k4s1QGQ97?u=@k|q{fGq07(+BuiLbP{`dvkk5u(?~lU!|aLr;y}I08jtC3EYo zwKa}vevvTw{j9OL@M=8&5k9ZcsMoZathE>|y=|~)d9l*97I!^}$DhEnzamy#C2B3M zx={+Qx?aG8kArJ-ttIy4v%;HBHNa9`#p6&j4RWaNq~L)n!?dxr7W*B)*J-TT?xWnV z!3;FmZB82LY#5KF9A~PS)02r5YAvSGD{BsxKq^clHCL$37H2h91l<_eP~c;xxh)n^ zt$6zNb$2NeZ7&mW?=wmtvUvFoy!|dd?nbpwv6gfDgn#Xgxan@Z@pg!~`4n;Y--P$K z#|7N~xYG4hXcYlvjH@X0hI+y(vRObbkM=ml`tlq6iz`<>cdtAVq26K^GkOd+Bk3@_ zliH?ai!85uun%#NtEU#3Clu9N#CmC-_{7LHNGW~9;`vXNK58+O2Et)xn-OY<=6R_n zEIt~*lSAfc%om5Q6BYwR8+O8Cj?8{1ExzeNHSJQZNPO@qmgrZ7(u%cI z>Gr_+7vh>b1uGM2BnF6TfmZk8pZ}5roWbx-MfS7L z0@q%z2q;IU0S(VqIvu$7I^6yX;&msDLOs+7@Ah8=TzM_7yIrtf;x#&n18D{j)fCtK zqKLJ=2p3-?*l!iO0#{#)n?A-Y$r_gloPRCy#4|AyxH(2EfzRN%ZyffZ(pkV1+l4XS zw6bLZZ@-TZ{w4elI#+{l!gdcWgL=iFEoq7LdzBNY!)cIhDg4&8vL^U?ZMMT~(B-5`Mt-9Q_O zaDQf;OE;j16X0B(!JlBXk)jc@#p(u(l`UL%AgU1>KQCI7?m!Hut6qA$#&W~clDb3o z-HJ#TiDm8%k<5j&Q`bABUNdjF=z18{BzbYV1N{Z*0YtUMRS#iX__1(B@}Z~jd@NmF zmeD#kllG$F7SruQi&%yVFT*NeJ*qsc;lX|*e6>Ct^L6s0uYZ*v2mRxDQ03GBR){B3 z56F@p{qH&)3sjuybsKx#%wEU&s(pwJ{GqQ`K4MJ7z&{<^SYXzvu6A?nlUU}-zBN9% zv3Ebh=l?{Z9Y|l%-)0keJffNTqT08cS+ zA%&;lE(2CE-)WV%hDSS6o9@cv{!{rfy++HpW|~Dtkm1ZXxX>=K*lQ13VJ)HV=%9^7 zklO9owZ3C+NS*wB{Zh1E0wj9?;{#Iym>O8&e$1(XSpg}Z7DDES(T3jxSRhCqaB*8) z^(emm4)rFwsv&J6YHV}fPe z^z{PwQ%`@Qh`sK>8=olM8@T=+0XN*EbRXcdn*>~blhS>GRhq|S4FJmZB8{DTs!^n9 zo)l!NnQk!4@L3K(h}uIHEO6e1N)LzR?N+)NRD|9!%wPnQ9_}_gy1y9sV4y}R_bwCY zqRf|@=iK`RDUP%pOLP*N=hcGETLgw2LrBT+m=ih(3SMleOy3ZP0p zm=QoKUCIi5BYhYv&Ke_tIA5wy5&8N%e4k4{BnCB)0Dg_ri0863AjIjQg+*)32q0bO z@?>~msB8mA0Le1{i~v?(Eii>{Mr(X~UGI5bA5Shb)vI(!t+&%5?nIIiz)0U%pVDQ( z1Fs7B@EZXiIq=QTLUFrY21H9zk20Wr2uYL4eKZi&OzAN|paB{-#bs9uarN~AZg|Ln zdj#}m#-_nstY>&US!fJ!+%gTx7~q5;YI4$ zOU1(PeilG29E#0YgK^BBunoH*{9`u3A;&`qFOKo}ZaC|D5u z{Yjx7a^U$7gc76KWT2}q)JjhQqS^@H8C_F=Ywi&0S_f89p}$pkWL7&MOO}x-K(-)L zfyi^w_ESZyQCk6x9bg>HRDf|X)1=llx8lwhWEVaSxan5h*$$6Atw=PwM!>CiN^YtO zpwX4M=|g;-t1*e^DKF%SRqjgaVrhs%2x26Nd;)O7s-+Lv~T{3OSbfB6m^maMH zXF6~~GdbmSAW6>coDL+1kSe>zN{?$9%~}4GEL7wLE|KI7)m% z9Y-e*oxE&MsKwqU51YLGjvC{e?kbgskQwq_PY2fewz#5tUp(_#&}=*%m>{78(}9VC z%y93;44};=ir}(ql&%CGct*g34#bHxT*-~ec;)~DCP+L}B``6BEHNvX`lAN}o7OWU z7cjtVFeI*c$G$2j#(ar&ywN?aF;>-csUXCkn!~>5A?~xZpk2VK#%-9?tRurUj zaC&}{Ilv^zPcnx~&2p;N4ryu5mlpjD=jAzEYUVL1{R&9gd@(u90TzT1=Z-{NzT^C!*);sluo{QV_G@XCkyhv8`Bc=!># z@R}lh)BOS-co>ho5W*gPLFom+hyOqtvp^5x!nMqO@w+rS(l2U|S!A$VOs4yC_z&^$xCV2flxlk;V+w{ReU=sbfRx@s85B5O{ zw_^`5h+c)0yagUsQWbX4>*$lh5w^@?GlPcudxJa-_eOdc?kn~M_n8ZT=ibIY=w6$q zu}I#^1$-?AQ6&g$(pxnW{ayUX_2>Jg$6Ye1EdWwwYFPj*GFzw;1TrOzd;w3FndHW{ z0IMCcA5?}d02WA;;sRj3chKEjv2TV?>BT_gIXLHHJpQT>m)@fE5};*kw7L2ecBNof zUxynWR(dIL$Fl-1yiMt4vW2+$VLbje)=FxFWkBnjm97F_e;e<7hBV2CPz97y-8)sR zJS%~7uTXjwWEx%#95&tfy^ZF!YJ)`$z0I?z3M|JIV1~;oR6{Ksu+*#eaL8lMpVdHb z|6)J(1bT#_IIJ>Ek=5b?-0^oj{DcrRQLF}-CU!OO{+IZQdg;|b2RRjSHTSRGG`fqr zdNr^}VsKXji$lnI+64}4o@llj=wz~ayfcsxLY6XAbd%ZY*h^)nxfqvl9#=W1Ys_`9$iS?*>fn@Nr2bzTjO_7=Nl)!re8>a+jeg-&`PVySzeKsMM)2^6)j(VzBOrC+L&(0c=y0Xi zKvD~@0gk!UVngZOwa8#24nT@KJJ!V0SZyvw+bhL%wFbEHPN{a|?E+r=Qo#ETeD$kP z|8!uG*yh&&gG0zD@f%$OFnPRGt&o*)4fncS64y}0LOu8zU|k65D2Y_p07FIjwgy-u z(MM~51e;~0pAxF6r-;wV8lc3MJN67YeQph~%vM`$usp(d4X{td^fg=n5`aXBA>}xk zZ^mWhSqn6N98W!?^g7`CZd&PTzV=KuTMgX%l+fLDsVnHFnY*?c*ejVps)6xlmXSIW zLdZtZMpgsc<)GneAkH!YTQv|LLJGuVwHm?0^s1?!pzCTiw~BI%Hm8UnGD6~es(C!l zM98%!a%x+MZH`(e$Odlsnag1VuvOA&ZU8o!LW`kxs>MuONU!XnKq*E7Nu2Bla-Q4c z_3$ay&9lGN6`NeOMev}(PFy@QfCBv6PGWt?_62)d!_E3}f9HVo+u|Zze1+J!HURfO zB)o^77eE8r24E}?37IaN)D8FzVW>Z*TBdv002E4|IU?e!zJYs*QeY%HF;5pTO%4~@ z04&04>K23SPO?qh0OWfEJq+^pxY}by?B4)XdW)DyE6A?!EpQJb!Jp-qAtr{9a{mm+ z85=@o_?P;z%zud4q#)M2jS%bIMnJL$Y=p}munBngJ$x0bG1)GqX0$imjRyfEcn9Ob z9mpp9hYE$BVudAt+Pc6M)|~29qEO>KCL`ShT>TeZf0s0W?<)cCJJ5kS;*#w^E6k;K zJ^lFmL8w6(;gC{{cgV=!kg<+aDQ>)*fLVgn0H2op`VL9Ss&lUg8A z%!akV9GP@$ff~bu^lE`vn`)6}tE_a>#}+%J*lF&_-VEIStRgt819NR^Y;G@t2#GV3!N{;#UFmk`<(f?$2%K4o8iW3^rSV z3O$eJgdqEbj^pXuK%8ODmaRY+IYwYB7kj495+uvj=`Jeutw4_uQX;;8TY=ILQY(|i zR$!3GtXqM>lE`5zFkb>OwgL+T*#?{)g{G}=(KWd47CiWOy#Jxw3+K??X|zbD+aUJ- zZIG1K+kn5nuLwS*7t=NhkvUoz{bUi{4xHUa5nTE=0hiq>;I_Xhy#r`=v4FOJ6>!PT z0&e-M(mO?|ed!f=>?3?0PuaCxV={Bqb9;hpx`{27+`T)2rY+HmiG+ZgZkIKFClD{f z>`tIl2q~ANO?HOXhn=DIVJEO#AJ#amxtQ;yePV>c2s7U(y$fjl03PY9u~1S)$qRTa)(XFq;J#!0f+ z{j^+FYUz&Sb|-j`$7Tr2X{+}0lcYjvKW6}H^7jL^A!MJ4wrFp8z}J3i^2fT-4htb; z8OS%wZg%V=wmp=t1ET(hL3CrE#x#*-b`0|a0S`STg-lCb=blv^@bNdor1M!F@I4f~f};SzVu-Suy*wIXlV zG5`BcSJp)oZ*@SPY&hzGVPaUVLomp{4qSkOmAxLGj5;-hjPNEpPDuz^Cwq}Ppr4OA zf;ynTAO|4sC7%nfsLE!Rh6v4w!DSZgw8TJHigko6JA>gggg?ih8ANa)`;+r{(_HJN;Acui; zPEx%MEyqIT8bxHVc*`E9bTZvwhDo-t1G-}QV< zI)CZ;h`*@(BBkqrEoU7*3n}MKJP&iu+jnM-B3C2xKNZHb{LfcTIZuXPan9UxkQ>!4 z3jL$1qOd-yItoeWt~eKm&OLlCIyQ=Jgf5M88=-fjqDClgG^!CQ8m(%CEsahzLPFy% zjgi$jw=pUj&uWZCjh8pZn)BD5kE7=&w8p^JgIlAx_0rZ@)B0d*bZisb2D94iY=fE$ z}%Vb2ySCMn+&% zL{)@z@Q1>T2y~4Y=?X_hOm&D$!&wg;^hXDzUB^Ib2uXBEZlJ&+g@ICste2cX^}zPP z{s8s|4mx&m*r+h+V@%j2hinQ<3>VIhu;Y$%BE%WkU|0jpyJ*`**m+U>#ZqbIMKu?p z_M*-1T_PpyUJZvf6r^_u8QQSSafUW5b;zuS%N;U1gw!_d*T z>g??f*%9KbK4;T8QhD_`8yr%5&aM!$!yyOGX&)tZx<&PmLdSEH&K0UU>X2ivIQNj_ z96q;WBPoh))WspWje0v|RHF)qj1D2I8f|f$)gk0WqqxS1YnCp#v$b) z&auV`O_038P#N}LsmE0)&$#|40Eg2ktUrS(y3`$Q=~Tyvb#3x(F}Rb z#=7r{l=N2FY>h(}g^)GPwl%|!W_ulbO|wl7$!wn2Tw0VhpVk}|%@f_iw50h4$KK$M zc?hygTa0(*om!T*lx8_C2RNiC#F^1@NlPqixyrF;w4CRVlP%+0NsGc(C9P1}>ae4x zwp!?rg{@Y$!m1E^;(2r26g(+}>^m>|d}-hH{7DY!dw!`yMus>`&tG#s)}DXVu}Mjb zgw}bj1xXJfd94Sw#^Bb)j-A)KuS1r$UgMC1tvj|sY@1nbj_ul}w?lfj8Po=YL+qVx z+PhizUZXOR%G$?{v*Jpm*d74 zh4C`eNCGdvDb%}PIPkFmNe*@d$dpCm2o+l6T{j~HISO2M7v6qPGN@oOEiapSnCUR? z1S=TUvDmC<2GJ~wBFamXC?*wQFvBEg11pi~p~$KKXZeuiAMHnlf39D#=c{jE&;Ki( zF8%i-O<=&yza4IZhtA$_W8UUr_rDu>@XmkNUxmf2-#>$`Kt2Af=Mx?}`*MBg>HEXO z*}$B|U6BL#G*@CBkmS$yqtHLvJ-(G#2PFBk{rLaT_Sf-QUD>}lzV|uredKv!1b1nn zLMhPF(o&&Dsk=;Nrb>M(Q=iJjiIIc|F(d>+2nh*^=bU}^Uh-a7MLqbAZY1MCJW?C}gKc#D%XIt&`g$%aq|i|((NWXw z96emyoAJr#{J%W#J_dXOLge{gBduNlzW)^=Y%eQsqJzzG&LcH{(FMoi^s+Y5y3Qvei^RbvS@1M93f zJ#Z1*ZY~g7z-I8trm0NDOs2{qV_E^(kt)f_;Z3564125!Dr z5V@rXlfLh2@+1=mPQ46`NqL!+>Ql%^mWGN}+7n2YSZa~ZhA%oU(nb9Cq_i1RdJLNSUV3OC#(WT-;#zIgH( zJof@#dmmrLQ7V-&&=F2 zp`i-X$ft28=IJZ0!XjW1mg&+F^Q4KByebY=IB$;9;?;~pAF5ExsY5~)4zklosKOy1 zbB=PnUBPO7!@^aZLXw)VOIk4>O3HV_LJCz_>tmv=1dFeIur*vW$v$Q{c@U4b&S>#i zYl4Nz9QPioppjof70z0bHupcrOqjwd);%z@fxM;_wz7sMOrgfd>}Ml>n8E=cGnCD? zVG1)DoFGhLmXBFXFtD50nj5BY$RJ&Hm|}Ew#G7R92~$WjGfZT%<|a%b#~fv1v{`5h zDwUcQnwjKds?7~rvV@s%g}@eqNKgwQBNU2gQmZmDXo||C=%g|e)65MfcAEPMt!EHT zRGC06>wY2>s{aq#mi})?3vTXv)FTv!zDFp0M=2)4B__&g1SY94LoSe5C@H031U=}x zw0M_1Byrd$jx#dIAW&rRWpQA+HV;H7UCLr(L4-oCuTic>;b`cRXOTJQl1SWOb4P>xDq4vQldg1g}6p1A*EeEg}PGEESX3cc^gBTurR z_x*TObMFs89?uPt3ZMVNmA{kMS)@V<>3wF%E0U$vobf$UVIzT>?(m7P$an*Tj2wd- z#Tr~nzNVDmD-pCYH)^;m4P8bW80nF;41+6MW24V+A>z5F!+vfU(8ER?b!% zsj!nG2lg10Q#?}P6elQ%R5RW}rgwy}0io zA!8If)5IutrioFUPANv=t6%XODcoWd{`o6ulDX+Wf7M}JF$z(#p*l)N%Ld?<7=jSlO}q%Luak|T7wNif%v z$xXL1x%*)}5+#{^@f{{UC!j%eeX~tek-zUIb0?w6oHudFykZKf3~rA8l&fKo!cP$d z?WB;*$r}bK+;kf^8OK1ggA``dN|KynSxhBncCx$hAcc5BP#VUG5(X)x>k{F8`AWlm zr7^xnW5@tn#H9p<`=1puQNif~y6cIG?|Py_0jq!$6~fF|lbeK@(VDsDo0X`rnKd!Y zkOx?jLgyan`I8{-(HEJ#_$J=^Nyrq%AoeK=L2bCy=0;6AF}dkZCO2zBASx*e4?inp zs$!F4ssbhBNmZO@AXTBq19+$(SNG`60h>EkmLUV4`+-u)DxktADTxr{TA!t-vnLW?f!p-Nl_LFDUinSB2XLip^m6%yG^ zEn8`Pho zgUS#sPCgdwULb0!Oj-p5Smb-D!1A+>)2+dJwaSNAPYVyw4TzXfNuYclFj7+5f zOG>Ssqd4l6#gho*VwO+5Sw{d}&~^2?bm*GWK40mWuhB75;A1J5WAM0_d`yf% zMur@P*&G7P%zWKuF~f`G&_|naMsgI67?hL!%#2j%-V?naK;Kt|%v0$1B%XQ$8+g-_r`TgQkJg{G{LNFy_b~?z z&N*=2;9guho7 z$x{g9e35ws(Osdq^)jDiliIDYSCLb8p28+ho0_LE!&+u>!(^Mh?D7;w*!ea~s_a!Z zR@<2lmv-65wfH!tuD!_Oe1-9H30aA2X*S+1aYaTNI>LQCZ5+!eEO7&Aoc9}1CQ1lK z^(5Uyo=3OSkAlHe1#f=VjuGrYJE%;1?qdOJC^46t$punF5{m zWh_l-@uU+eFoi5s=yM<5cn2R2K!T(^frScp-NP04y}{(24>5qIutJ3|zQWhvv+#HN zKo{x*BayBAg$hF`cpyuTklbc0Kjej~!NH?YVJdri6e?`zz!7F>{aUCnhR>N9`fmyq zR`QfysIZEeA_ek#{Z`0g1y7)mC7MjoWTGaenoQE9Oq0n9buz`k4lB~eAbYiq{q}Ji z=j{wfP~{O!urSYBX<>)G-)0fzWS>mSYeL_ctgzZVYGSp$S=Vmkj1p56Mo>g4IYjW{ z0>|i4k2g{X%c$BA)FO*=_l%(je=>*Pl`9b1;G=@LCarMaBY5=lYYLC)!qd;;`PW$d z{-;cS{2d|e2V1U)5B2fUGEO3%H@M{r!+Bj-u5j5TsKsdtEjrVY0l z2;#n_pw($g>ky_X=wuwz6f$^>>9o_zrU(Uv6BuL(lEfMb2C|V0WD{_1sa0m7!m6^c zm~LL3RcqsbeZoda<3WwN!(PgQcID6Vc?3DW-r4Bjuyf48Sts5_raPN3v6s0h@+|gX zzh^uF##ef=$h*Oddhc#8P6dbPasQWH`N&n)Vhc5ty!n`O%0ap(*MoAuxqg`MSLcU4 zev$szAABm9+gAH+_QR3uL}RQ%3oEU47Ir!N9OQcPJs2L47l49*nE_ZBa3ugCfsuiT z4onWjsKD|-%ne)-hz)^T1FUcQ+O2oGVwa!kigA^uEBwrnvCAZ1BzZ0|Ly@MK;1e&Wj4OL6+F>yJQcYJ_ z%@Os}71sEe1S?xV0Es@P%&M?ZYag(2!cKO0++t^hOehuQ`{)PTrKf=ch4j3N5lH{vQ;HW_41uxP6Q+eR*Knw~h4MJH^ej^k% zn$`$&8m(@GnBc@$s4_^)Q?HY64cr-sU4fSZxz**supnl_g9Zi( zIfK_$xAw-p4|3sF0wq#-=us{`|0dodhnX1)pa0B~pTj7&nq)H-$Z;r+yo3@Mb8@Ca zCa)D|UtOCL*HRFnwdrhyelOrfvJlT!_~09^_~xJZg(MXUAABNYrCuaf>P2Fu!o82P zu+QT{&QS=XK;uGVF=5oKq8vA83=E{@(hzgBi7AwXrHWJ~^(KWq&QbqIZHEw?!)Y@W zd?a>B%A@+vJM)%ObWIo?6K z&{G(pP^_vIwy7fu$5obv3D#Z2EZQ{Wa0|BJja1(fM0Yo0>S{@l1{KPJz< z$>cdrDk&~F-56@Ha2x;4^AxtRN`0QfQF&T&)i7h6ZZq7+jAL)Nd5VEnxy=k?j%K3F zG?OI*eGH{BW=Xo4ry2cR%=0}L^AvWQ$92^nA9KRItR=Z9(Km9GkC}@VS~Abati%rB zAWo43?=r;)8==N%W(-4m%u^VnC<L1zvEgK% z!Yq59%_A?e*J)<4k6CZ;(2@;4=DeM#|E4G>PBU3fzGkwSna`6_=R5JxB8g>kvqT*& zX_iuy)n>|wKsU>$$=qzB#yn=?q8S2Ik-cgsLBJoN2Y5OqDqhVw$mT1&@>je+@BhJo z8+-#UqZp@s|M#aR=&V|FX3|oVNMj&n?O$rJFq)A7<}1YbnB~S=UA5Mr6e9B#Vi^@= zzCxUj$@@cVI-hiVMJ5ln*`yf3`3lF(izY%?|21DBi4i5{D-1_I1rCsYY`#JrEBogw z)Z+j>J`?oWUf`G|IwJ!AN#R@u^A#45bNzO;TQ{PTkW~r+kD~wc94op&;q`wCCZ7wr zkg_Fhr4Yj$3%S-24~xPrE{&y0W`&EjZauBP8Y(Af(I%IhCVM0On3?D|-4Cn$vi&j2 ze^W4vR{7QYA=3X^(S=40b)5*(OX2yqx#bI*y!}3x{{9P-a7m~N3l#>lQgWffc>*n~ z;7mykC2kmj1Rz7@s;ebtX80}Ar#92aWc!cu=c?VoC-f=qVP>(y%L4?FS2UqO*Tsqh zT^B2)@Ctabf)1`-ObU$glH2F;x_hxg8Eaz}D@^t=RXkNKrky7#o);@b`IrW2hsixgSj>LGfV9l3XY4<>>Z>#lZxLQw^)e2n*mrSwJtXkp0zIf_2L0ki28(_84 zHo$6yG+qcWlP$Sn4xfCr!bl%8mCw0aVVaNG%6THI751{qrCK%UR;pElZl#(G2YCh$ zNFkU)E>U>xJxn3k^HVa>VC;34w(H8Iy(Z7UhWDlr2AaZqpW@#5{0E~Mp~k<%*SXm(h{E;X^BE2zmrQ9T6Pd}nL?o~q7N^bot7y)@jPDr3*P*l ztCGn8yg^17TuPA{q>l*GrGxT_#1a0SEK@iuFA&}(nHiQTj3)2vBDr1)xm+RmQ9SoD zbFX~H5v)%VQ(5>MN&REcakk(AMMB(4~VCfCiAYbDmwU%OMDk|6lZ1`Mj@im~&M z%&xG@6|(pnwwzW?>kRH($RD%i3Z?AUyj)>APfp88GeK=uD0b0Zq0s9oLFC2v@&0G{ zCWi3dG9+g6j(P>@J`2h6I!R(W1z2pC2PKM)*#>tyD@onCQlWh}Ay+B{>pR`&v#3!v$Xx0jyI^*6xf(lYGY1N5I=bLeJSLXV>FXUQ< zwXEJ>tFYO}-24Dn^!#4tM8brrvGZcq_~o`3WN&R0C&7r z;R@x_4dckUwF+@Yf`J4h#Sn6x!cG0~M3}@t8ArE0O=1%L-`gZPTCP)gE{r6V^yeq( z4g;^Y%rGkcESjQ4gu#b6GJ6bTkb?~heO|(c|G*EU2+4Mm#7wz@aDq|{1_{PIW0ArN zwN7D~RcvFJ6Xl}ZUE~VtI`NtgyJbT%n+*y-{ERr#VC*J9`DD^tdf%Y1f_(@!D5OwUf(j1H-k`8Ztx%{{%PbVzlWYuQf5#09lk8y* zqTFGwkQ)`0!^8<>64ZoAi}p;~-^%3fM}*v@TW(U^a+Bhgo3vSMlj4?}6#70b+XYo^$pNZmH*(gcFtU16*?P)j+)hI__%u-rrG z-#05(V4~XD=72KBC$H42??L9gC; z=t%;ve)}D~|1qjONnT`nOT1j~6AbqjXu)yP_GdYH4#qmeT;#e{9&SkR|22xu5j6@u zda#Y)+o%R8F_)EZ$pj3Tr!@Ir}&jkN#4BX2 z!b}xqF%xGb8yG`6>~eY{W*N065|Bj^C=-FDY=^H^y0S!z)_{A`eymn6F=`cFe;bi% zh~k=m{tLA@ty`UtjNqtn(#Mcd3{wq)iKtb`B0%gMlmHVsp?)nnWR)o)w<$dN0X~nH zNTd(&XgPtVL2@_RMyiUT(kEI#m>Ui9PWK2|r&m99`qb(ao_hta{><`!k`lU3;TNLY zwS{rJwlHp2jA*f4q5sQ5?$Dc<2idn^hr)x8;<=CTah?=%r$WH3==(Wy&2L5D$MA|S z+}c;jUHYiI6yu-lQs{gi3qSmZ$$)Q!tXJsV1-JLc!^t!c7Z48VM7o9*k}C;+x?b^f zU$4;RcHI9k%Wt~MCG*r-PnO_p3W=kP8TASi zAy&?!%=M9ECrLE~iMrm!WEQUr4=TL+Cf*%@kB88rfOJm>6*l4wggm71Bq#VF-UM94OxnwABM5_V z3y}m^RBkP?u+G|LVV||q#yb0$EvV;h-SOn>cyE4Oc5!RBP@vp))KWr8js1 zsdmmNgjL8djK~v3jxJ}5D3Vq)tOU>s$aG`Bybnn@~G3osP z6SCHxRCxD8E)Dpc39WceDvcF-N})qfJkSqM55Q-iBbQ_EPboasAQ4m`o7*Xco)2)T z-_uM8!{wC1fY0#xH!K+N86(@BQuyX~{A;LW;r9*fX@y7nbrcmtUWOE-curxrkC|xBGKD;^@Et8M))3n7S$ak)ES9cO zrxebr3ksK2goT0Z(sx18iut3@5h}xgJ*0~{hH;jVmle9-qsiS&dT4TrH%FHhPBU{w zq0`-h$z4nagi1}xXY~rXV=d8w5=mixSLlAxs+pN|f_EhgJLSn^hQN8RC?v9yjTs7y zy`r#!m9Na~GWiIJj5{kkgCEp@p~ zraMtHTivrh=8R^>@pAHt!Z=TrhZ~>pBzhZ|1h0@G7MkCLJ9{;7Om4cJ$eFPI@vg%F7b@ z6n?qHSZZJ|J3@q7+At7mAzv<}uiH+t+Nmd^07)YxlamRW5I8D=4XTq!Ml z{+p2D7J{F`Gp{k%=V2!IY4X~8_~cW3_BqR5d+!g91@C==Pd{tmnEXcZNj8rLlV`ZaB^Kw=XL%gkq{A&Pv3NS8P={N*7#A|a0wu_ZuuyGmrL0V0lyuxe zs!5Ra%na3%`6fa4MOc_`t~9ag+7b`5fwZ2xK@iQ67G9t^Fw%k(AczEa*MyShS_tkd zWR$KOXyLLk)Wk?L*F+UoLXc>@Txl>f)EsFtlWUS*FxtZBKj247J04?k3PMbFW;mGT zR5+ODoN*B5M!JY|hq%ab^IVMcF7jfzcY_yuyo3Fa?pN%GMSffTgp9EeL2Z-VG#CG& z@__e<7wLY({E+82&X3!t{rj-%y)0kN-j6XBRvV-fjj<4Arjp+uG07%Ut}Stoq2yOv zW==6NmHY)~Q4qjVlUycZEDYtjfrexE+!zZd83mo0i+X?Eq0BrT!r_Zj6_3beh zs!$Cq@s+aG7)sSle@l#oVqTZVSlCEO>Qk)@3rB3i6^XGhOlRDkXra`ih-;tdx)Ps0 zC5FXB4sVRHxWr6B=u#F?y_NHpADadC!GwMAwygkYk>+9-Q->|D(j>Mc(E5 z$swyPO9uO;>wmG>uS_##eseW5m({qj7N(I`@p7yHR?-Tu9>t_#m~IKGyg(SQgochq zD$PQ^Ri@>2@+5hJS~w}A3~qUnP?{o*Yf3D>U?iCh4C(D+EmUGTv@uR&Gx|i=m4uA9 zP{dgfnW6n=f`!g^3z=x)Z=Y)No{&iv{2yiV6n#PmTWH)BT^}Utyr5FwZ-q>;@X`19 z;j9!g)k4SH@X2SK8H$Uy-H-b}VeYdqi7+`tVk2X4q|&6fS_+wF;c;^3NVCwiBRV}M zzbPFq4#hq>;${^g8EDO!sqtjoJYm6fBV+$XCjPHIHRB@V-i?b|@VmHcc3q$2_ ziQ@zVd7k_%Ll`zD+qzCf32m}1a;SwD-(~WWCR61si8;KL8D`;`5Ae~Cg1CpD(c~#6AAH0ln{lUx(RmU8)o=@u zq~XkB&za#CzW5FyGE551GfC$=HQd5-)>RL;u!5Np7J|Ft?l19OEGY#!nQ@gAByZgn z4?KdW|5H^GU-SqIUGL_mw`ua_cU&Sh%tX0^c5-lWKc^1Z6RTg(TiL(R;e_iEx-g4wvc;$U8p){U*NjV!u-gASD2?k~v zrwkN=!m@K{T8}f4LCIY5%?gTqTVf)Q*IPLj-v1C4ygJOW*rt*AyK7VFJG)j`t(a|q$IGF1QoM|&@BpS z`!Sn*0Zy4$OhmI0aHNGb)Z+R|K9?t+XY%U%`0!KBz0Ks?ADBebOOeVU5+f~);x*7n z3lnt+#YV}EPRWS|#+Zd>!;TsC;fiRNs5ex*KUw|PQK_V}19W;D35ws?=Ukk6*?krrmEg^H_Y`IuNM(ZWcJ za&6>VlxyRm*YVc-_CuOq12YLN1eDZhZqGYT@GS6wb76{!Dlr;yrq!-WQIWX74bPlo| zWg&$pgi#i%NR59=UXkEjuGz*C18a=+luk@sI(oV~7n|EKWu zyWEq4F|m`Lu-)b*J=l6ms1{*T9=H45CkA4?&j78qh_x=@MeTSca;qrGsV=C`~$5_}bw@K`inFeq1x%#gI zm<;#|zY@6T7z>|$g#?*QuUUhD0+uk=ASaJ87MFy~w-6%fie6`*3COo_-k`K{`82H%9ALhMVdh8^xxUgsq!2!IhtV&EzLqMF%g%(Cr9Ike|DYOu6#2G>sS!mK6o&Sc9e?uB0Q5IPgS$fa)l51-Vj7bKj zQtIoIz77dA5iI9a4n-D*>C#1sP&O17(e30?ls;nf&V1k&n*8l!F1@1(N1YW}_??ow z7Fi8ZzC~6;l<#;8ZU=OHLJ;@s?}(9P&@Qp?ZZ^f2R8XqoNJIMo4>PI^EHYLZIB(1_ zamXxJf||bmCdcNNSg0WV_X5s9RbnBMqSjK3VFV^`q1M=KU^mAClvtQy&NDHOw?!ou zHkh?0YRyBYkP|E{qbVfRC^xW#LMF$#r7o)73m&9+3;no7q)e3BRy)QVYhs-_3YdXe zz;J7fh1ph=gW=9Nm)jn|At0Gn5%bh~g;Wj;onRqdsbBu{uw0GM{9xsO&3uc1Fu*n!}uG3dJ zisRxYSeWEv%GEr@0o;~>Tv?r^Epo$O+=TkQ%t(L#r3nY{D{-u*(S z?U_jHyjYs7OQevc7Mk}&@8|H!H)L->87mx1EgT^|Ni==@P8%U6B2B_gE2XD6gPih<3$2U>9!ru?~6V!-?W?p@D| z#R?0Da0EDpg$kuXv-HTv5T>}%F`NW5aaO8@QC5+KVr#0!q9wfjuCNdi5Uz(DM2^}a zK^c0G(7rCK~MaH$s03tSK=q z%_#CorG;vwiCB|1j}0a6x{O0Y=33}>2k!e`$axmJ_7HNug?2YH2?#<^W9FK+!_6-- z_tKj}R#~{UEAD;*PyYpc1jyZlh^7w#|Q z0t>;d(e_r&HDz+^6GASu(EM>c{e}+OU1)*o7FujjS!ChoSc!O9F0n{nmVzYDzJT|> zV6I%wmY6LG?q#usb~g%Ht;rG#9d8nHsf9sSMfC0vK@Ng%Y~Nhm^}6z zUi(V}$K(w{g+o8-A*=vg9c78cJ3AnOUyb-+jG#wVG4;lWljVpVKOk(8gB_|@#?!w zNN=&lV!g!{3*Y^MUr9r?#kxvEWP?C|kG5D?&Tsh^3)^YwUT+YP{}xL}MNY6PEX?IB zPFqL?J6RuWuC-b-XN-#mLdzx_G!slDniO$dL!UtkjIObe;*+HMm`pRD)^1g%khK;%zlC>WB?ghl!)VG; zQ9_!z1@us_BVFJ?W0rxX#t;+3Db#hWc}(|v_AR{o84mr&CgV+1@!GJ~!dvh1VPg5+ ztF@3VM@!72#q~n|+W15Z<#Nd_SNO#GYg9{SB3l2|lGd|<$@ejr>khZ#uOA31e2^lMP97(d3F@ax?x!@XDZo&S1PUl8M=9aVoT2Ytfm)!r(|7;G z@7!+~?E>;iaWYX}H^MTC>WMWmh|mn?m|>JEI-y~}>&GIO+d2!k_T!Pec4yL~7n5gS zWb)!$Oj3A`t+OzNbscq9gOhKa)er|-XJJ2UiR&zkL;)r0CM;m`# z9hF$T0&9VF6!2WnuV$Tv8up*Bqs1e=taTPLRDlw5yM@ocMiH%uwy;@xyG2R<@9W1^ zjhi!}VEpYCUigShAAPR#G;g<{lbdcQWkx+EDzMnH&#mfwlC%8cY$wAW=@_ZGR-XaNmlrn{cPmgVWIOKxbuEN-0gSZ&byeqTMK$?^7&UR4B_v_4hx|^ zCd|hyAdS%ye$IE0j-*;*DL?)@NKH*SS$9|skN*w}5k{IJ9y~U!H z>Wo5&HP}KXE97?4hGH9eQ(27c%Z+ zfs?F8+)2~V89m5ZpX8j68OD)WJ1yk+n28Jxw$s8S*5U57Q08Outzv!ZV|`44kJ)RT zw}jkfq4Ud}4IoCwNiP2*lH#cw3gjNQ%VM;IT^3#@IDZSTyu}UQ{(#9~2J+ZtA(@$a z3;mwNi$5b)PL?<$$#Jfp*L8wu6n|;!E#&YAcDIGkSJP^$h`s|=1_qjAOiVV{nAl`e z{MBv?Uww~w$&L0%%rGjiZFJbzXpBk8H+NfzVifA#bblzZz#a>2Z$h_cgxqVP-&3GO zFMBP#J%9zIecNj>D#2b0AN|V0apcQgA_-<_ucb>9c*nZe!X%%VGS>;YpT~UqRUh}v zRqk2MMKhH5e!4kBXu_%>oO62S_LI_M9(kS7OzbOFv4Ls7g+)H`VjpvmS2hPMv~G)b zodt0Xq`fZv^Gp^S5CV$N9YqJgH7!6Hce;Y|OiBG2Q z8$o-c#8>s{*F4Br_QOA7p}@y1Y9xJ2^W^b#jJ})W2^HT$J~PKH1jkE3G+xFU%*67(?Sut+ zlVIX7A^-0a7Mir-Qj=Cp+Uq6+_;$iVkNzy|*&55Or44?f8yg2X$m>rPu- zciO^h0|XJu5paf0p9DfpFUa@dM4l|Hz6c{KbU;f2pF4$;{2c~iy)fEeUALEA4JkMkpDX$8QVnV)H zOq)lVtFKts$tl@dh|L{CZQRfqoo^K~%*Llb;$K6^yT-;hKOe5W^gniN zXg-8Ik*5KNDCqJMrH_qOiR3rON1RORt80h&^c##|1#Y;F97X9W!)+9hTipdK%I4FX ztSVIlGgUJoR+1%TgpJX1EPYc~5pLFC0~6`AHg+WCyD`^Um_;tE zm-KMM^l-t=(5x-mKcbzUY`pjvy!jg+h$dgGqQ`EPvDwhMU{0CmOoSsE7=+=JQ8!Xy z5UEkh)MkYmZShPYty_VG5(0TWVxP6I8SX!fluVFuJjgUX$U%if8YJ(}gYfALB3GXb zIzGY5@!4H#`B_!jhg8H70&ZKL0_g2)0=V=v>kH`>MlNntV+ZY68^aAko?~W=fqX8- z+Zbe|8Q`~cnGt0o9P0>xa3^J;I7rbc`xFkUDTKkXisHkMS{Pzy*vPlbZB*KG39o0H zjU)C^8*$_^HrF}lBE%ExL6T>P2je|6JlNpL@kfFG9Dng|N2G(*=|hbF|9XgYe-!%; zarpZ4yei+}kS9~TjVMA~oa2AMAF%=P0hk<6POuv@gZKmsOpc)6Z~oU7%l>u;K+m$xvGK_M(jaAlRng)m1$guNmZbd-6*k&KGanz1;aE|lS#oHL-$?+h^Gv33k zW_YSRsPb&kr33!40f-Nn9DwqGg#kh)*!XG&%>-nCO0ZGM$|YvzNv>Yaafb;u>Ww3Y zkcl>eZ$$qriOFmxNwo3G8z7e_;$G(l5A??{3P03ObE1txStc8Hgo!q$8WjdA7^NuD z#(Hwdt}zZ8LJqd^`j0}U*a*H8e1E{h{L-Qfwp`h4dApAj4D} z0Z-!D7qQZ)H3S!L!R>blnPy`ggZ!k~2<{2J6Hl{o|D&kpHD{WQ2d?2M78zq%OPFTk zgro?bG#iy_qe2q<=%v}%tfDO;hu8?dgLl6}Z2V5&#dI5e9~VR((xi-|Jko7U_A$qK zr;uecOh=ZD4?Yz{;yKzf%ZAS8kY!^O;pdLwpv){A6Ig?gZKFRe&a!Ph)E^^RhcMJe zm%e!Hah%|-(@+~9{9O>K;jqYIHUgRmIow94n}r-_l>e~NroB-B|yp% zR)NK>h8j5ra*QJS7Ed>jW{uEt0?flg;{a*9#+$gL(zLvk(wFgxQjBqeFK=q@yN1`f{mfUm?fZ`0!(V^$*(l0|}I* zXf*Z%rxBu%t40xg`B8-gN|L(6-f3fuGtI#qf?HbU)H)i?=$tdoMXX<{ABOwQAU6UV zv83)v#AqPZZ@3oiC(BNV8m@538>>abRW7aJ4k(OsH@c|zUh?uO9UzdBqiVK=jc%j| zE4=kuKHHj0O50c)k)Ej@%n~;MxtG7W2&d#g97giEw8nA*w|^0aZv9m@CsRA zBj7b5$Jw~$ZYH;C^7u>m=nED+{*sV|^tDJN#V==9oIt>}^vD(3Xw{Lcd-TQ+zcBaU zvrL|Q6~C~bf{jd?)@RVP9%Cd>P)MDD2__*wH&k+m0)wzn3vJYq&--EXw24?`fa~^> z=gnz$t|_#UXyy>E8ZlX9+ioabW>4lK8_nCH!_8!Ye)|U`7^4hKp=EarLr4_ac;jsx zHcpWHPmHP2HcCi~y@Eb=2k1{uVFZyP8#mKNQV`eg1-ziSJMLn^4}{lRWFv_buGzE_ zA#EbUwF88W5;;ktjH1KpWw?PT#&`WgK2`o9I!?;hOER21Cv1%3_~arRi;cqu_N%js zk9UI9#(5N>Q);d-Sv1#NW@4GIwBI}c7Dvz%n}}@6URw?9MT2}8ZEbQXhNqfiK8kFg zXpHW-0Xu*lzPb>VqK`aARnvNOzY=o1jTW~t>Gv7F{$0ot8?SzfFTy1T%W-tQ^C_wD zI*B@&WncvFs!D8p`UN))Czsd~`Z{G9Bgh5A*4Fa*>|Io1V;yhuC)((EvmkOyFCj~9 zJn6-&G6%5FWm)aOAaPrfSTBpa{1ijU&RF?5VX5t##K^9OK}je8#C z280MT$;N~Ix%BEsOyXpc#285~xsz-zvG^c4Bc>SH2A57Ve!(OgLwVgX$;L2d%51z# z;mF}q5FgweEpKD4WhZpHmAPkLM667t(@N8ge1gF~!JF@L!=J+lh>DaV3Ia;gof zUKKYUhkH5$%B39Y>Zl|vPE{Z@dX3#s-AmN3F_oqf9pAUcM!Q=DksfyoxzLpa_B8$Ac$=h;#a`_KUVTuxvK4dpgHS={DkCLevv?r?8w*AkR4&E{=O*^?}b(+L91! zk%bJm%Eda*aeaVA)-nsrtt=Z=3C!r7tjp zRL%>?iE6oAPtWdQiL(Tq!5Hutjmri?&4DHc(MPM!JYwRC84ILi7_bq$X?b-K7)*I$ zMsYwL?MwS&4yWtcmqxuRlV*<*`Vm4C{jx(rBGn%*K*h8!7xU*4jvA6GE+x98y$FQ&oDv z0+bONfX#Vn1l8|me1L5>TC^5K+IGfGj|;ioMj!*nc$3way>zuiUxVaH}NcINIy+T=N~CC z4GX~HNK!?{(utI?@9u6J`Lqo!k|PWZW5l-IHb&6$YcyG?Hq%BW4#RZs1k$xtn|n=M zHsiGDxOtXRCEGZ`%w8KEZx=*5Y4Z5XTq5{^eKuO%iU%IXBkv;5ndAtPy!S7pJ2ac@ zv+=KRi3piRiD@nn7)ykKD|WhWmtl+}ANFMi_i8B3(4~#!*HdTgA?KdU29DcTY;JX# zLZ-tBeSWijT#I&^dnmeoDX_>U%-VgVeOd{uLWRNF{dx|aO zetl{CZN9YqHXeMMg@mti(59tBs|N*f&08^f@-zH1S_*l{#tLhbg<5N;g|k+ajaWO* zMuI)j#x#3|Ev|3TP?%4+92S+6`KVIvk~k{I8<^~@*7EVj zMB3?XFtEcIVPcpwS<6RIs)c3dDwBJUGV@Io(2KpwOaLNTA$rJ00%;eMQBGM}uK@cv z?f)SgR}iicL75F_sKW}Y?0x#g4y&;iW>TKRO;)XiorK1C)H-cpzP(Df*h@OCC_C0h z9PMu=+S6-bucKZ@z=Se~;hxYB8-(j%)SiahsPG z$8Gd~N)U;WWS2Q%6P!;xt&Wz`hv0-U$P^?^|Ffu57kn5`-~Ht@!(O6ib`W7#(W>Hv zjV`xf5F?$PusK`o2^-59)8ho~(kX81gpK{i2}8(}Hagybu3uq>NpI>&8!vu^Dj%O~ zKE;pe5_yy;Q_?z+aV|5MwmmCKsuH^by+vnZ7#>0D1a7#En> zm^*HEwo&O!Fd`c_h)M&e2cjZyVIWqLzww#tDo;6cT<$o>-KLpsF6RKXkwGcWF1c4c zsPr5~fnRRvT+MQT90-`$+#2Zx6OH)E;SLXp@mmaHh${CVb65)yV~hd=hg6b{ z5q7C9WVnMKcQSc*fRGUmLS!_-CJmwys-X@5UHX+VIw|lwNr$3YX`U@HFaMqsNaQD4<^l@Eo z$>iQg@%RAdUV9JkeZbror1Fb*Fh~|lMDQmo-a)aPMs^)C(#1PiED0(s-oXZ0C$XKy zgB(;Fq{kcNu!44wgT^fdkq$j^=QBbkI_S|8z3;(uA0teLOB9g8sY))8Tz6X!^z4m$ z9_GTs&oOyKlaIgS(kGgH$EuYA-D@0w)e;>P`WU)fSil?aR7c2E2M_+lwod`^e-t z>?4!ou<}30Vdej52X;3h^Be?p5^{_t`I?N?q(GB#niOhMq{(i1tDWt@4iK`!f$cBkJWbX)u%E+U|0akdOUf1p z_C10K-JvZG0-JHE(=ANyc}B<@2hYC5PeogGmTIE!xwx#z7P-8EPC%BqdHI z1M<{3Sizd78V5Cm-nhpnjxYw(DlCr>6n0grbuf%vGYS~Nua+hkdJ$_KERZWCR{F$; z!C5V$HLI_5(N~I~Meo&?5k?d#&S{rh=Ro=kB1QvogxuxOvboExLe@J-mvhJs&_Oam z3XG(f?=o`^;at_2+~7}gau%(3u$sU`b7`$M)i_5EfaFqK?_d(a{hj0cT<>5aWfq*n zHrU+`nsz{kJJI`oJlqdYy(36;XKx|*IB@R}^032e(8CU|K@U67OV7g&GI{BFnEv}J zgBwoqF$dWp`-Fo#p2P@#dQLdl%uEKT75id#gxxa&Sai(dC|>6sUp#E|MGkNgl9vkEuPWpoX$ zi{mLN&P59EE8<)>9&y!lS*e*d`kU#3Ov>>tBag(pc=_-6n%>QL7jOO*e|ujL_cfUy z;$5WCHgmY#Dp}f4%99vFU(U&Lx|UCu3pG<>OgFgY1Rt}HXQ+4=hnY!mF^(<&2`+l| z5kxLaGJ_?!n9t#+=`I4B2$|uMFBC=RWVpDcCkt-Xgu@2%6e-iii+|bRo0)X;$oQY>VhF_{Tpe&Zd-Y|ySm9$1>gk5y z)3RL*liQ_`LtT85OD=R1$xotEVxC-XV7;-?0OyVxM=S6!L;{0ZUpLf6l`J9@6qmJO zLtW&`5{Zc%6v8E1Pz-fZNm@|SvGYMT8aoZ_;?4F@7bgtzSRd*l)QmL|XO1(u(Qz{j z%tT@^gdFBVHWhNX3o`il2|2<=*Sqn-0DS%>7Xq6yp_s1`F7Cbu_kAGbNSANj2SSc< z@z4uQ9)3Z{d=~+Y@#06!kv!jJ`B)dTBw>P%rB{fSHU%zz$)Ip<4ye9BYRqVbB+|!b zsd`dh9aDl@wdyHkAq^{Jv5OX6n6%WSl_uGoU#i%}P#-glnaM6EgPrOk;6@>*yZDk! z2Gd={F%rgf7n=-%y_oJI)|_r~lMu36jv@ThY_(MVA<9v=>56a)w=e)tKKe}qVc(*j^G1^9#* zGY!lpJ^l)^nk@B?)Qt-m^JAvVt+-#5nL?Yri=@r0q!i)Hfi>6+r1&rO=N1%P&dg;q z2Fzru5enKPa;A$)wM=29I!#dkvn*6tAvVJ71RE)KmW^$GDSDX0>a^nC6;`#y%wGGX zjWhOE2YZ}+7bWft7t7pDF2?w6^Wzp-POihu9_P4bws>p3Le6q=`@aW3=NX#}u6gir zCL91g%f)7cvaVLRxa&bY{ug{tKKyg&b&MsP?nDElNvT?DR2W!n45k>PNQH%-yBB#nqWLVWXE>bDEE~O`7XD1Bi zknlM!rh688u-ALiE96`kfp;^x?@2s00AqNgH`hffFKXtw95FiIMQ|&$d5B;2`7Yl0 zM93-^o+pJ|;Nr$ULRP!Dq(jupO;W9yEILidKbH`zkfAaai`H7}$&IYo=29{HZ&tfl z?d)-I%(>to#4UF*-(Bcpx4YkiBc4bvH!ULhZg(#wX{smrDGx6BDlhpJ_+z~PEPu@L zpYM-F{;U0Q**`P@xdGz>xck&b(;A_o(XvKZ(`a2Ij0mm@#){yT!CZO0oJF1QXY#;f zc=jdiagI4Ccc;5J=6}+kEAk0kc!DgWWCye!AS-RPiyECDm_Wd)T~PX8BvT5bBKE7S zb}@oq`)U{Kj2#Aw$ZfcW^5vgZVHU!zkrt1Bly<=(W~9E2NBK{wb~&pM7LbuB6+?g= z8W5AYb7dy>^CJz0%!9Mb;Azwdkz7Iz=6HEH5OjV=*nz3q%W~QhLg-V||*DBQ~ zHHiV8s$G=%m<`q*U3Jcma(L+TKIVcG;$phXu;aU3?Drh;aDxr*4i`JzT`reWyi>i1 zY@E`VOUM0A`{9h=B|jASkJq<(mj4`o%=cg9j}Za6`k!3wf6N~z{V)3?G+=~Jv{^S# z2*?aTrcXRBpp1MtHw0jNz|H_13OJ&hUkpeI`zKa3QRrz^=Ju#-Ki27-+}ZLV6G@7n5attHdFhNLt@94vF)+8>7mE) zk8er*&|~!b*pvA7JI&DVlP}=A?=?feFT9HHW2NTkcQKh5qU;#b2WIgx|Bxg%NMgvW zl+CG0J-RTop%6#kq#QesJ91%ULm|OVvN6UU$2DBY=Yp3vFG!Lp5(RX1OJ$wJM5D$) z9+}XN^Jm+OJ!C+Q#Bn90m(x36Eku%Y|B!~77G1DdE|pluuIFC#yc3^%i|>ENWPVRs z@ac7eLFCnw!XJJw>R7eo#ZFmF(HrEo?nR9;ifr;^M)1eGQM6M^lS2CAv+p&Svp)*i zMJe!_+Q6%|fep36m`@>qt4M9RMhsHXMm7E(l?U`0V=3?~Pwouueq zZf-GY1eWZ&Rzit&sHV`EVqh*UmozY0V-)?HqyNK;4?b!3u0Kp&P+gi%CI6;NlLw5TrC$TN_~COH#n6irA7Z&WkW zoKMR8111idNB>Q7s6len3?*fLGN{8!b%KtFmFn=-^Q4cezMK?W$Pj^M9fS-O6f)7Z z7for8JdI~x)1sC_h6&Eq8!iygl$$aqWTZgTwn7dRXx|$Tyr8)*40cSiF20*l97q%F zL2}WUW>P$(M6U-)q#dAvw*7Df%M{iq3a3!mL2Kez0#F*q8$^X+q&y$TJ8;SZv5aOe z(m)dUH^rkV#2bVyrmrg55z@dDZ}RUebQkV_9B+R_5ySuZ5g~G=#Ci((xM;G$cs<{! zHFBc_M`lc-D62R#g>P>bDcLePme)ikqrjNhZtOFBSGTgYk9*3;WVEMaKJ+YJ&or>y z+{yz@=8WD7Yk5sWbc197K_L~!8`BI;Cy*6f7iJlh@K<4$QE6aKLt&Xg>t=;z#!3R> z*M%BBA4+aqXEdCg!XbkIZipT=aIB$liMKsUuPQ@L+5;)1b5?AkS>)tODY6yv%}FMm zQa(946>`DCceUh>azY~7*i3NiNrWGn4vfGNOGpccb={z=bu`mi$fGtTm({N7?dU2 zMG5;|=m#P?^Xf9->c2JYs)&|@0)0N9SGMm{c$%33pWyE_{Rw;@A`!|{p&o42&A$Kn z6fH0C$ZOQ`k(Yha1y>)9I6tb{z`$m*fg13iYS81+e9P5#<8tX-1X|pPJMKr{w=sgR zAw*!J&OJ%-$DsnbavFKRRT7BzDTzF@!sM6q6lMI0B+M|%=*OZ;a%0vx?ZO0p{uKjd zqC_&iMn&Z0KTV#Ls5g(3JxkzUVWggzMxo9j2BMACMnnF@FoFI9cm@ceiKp7!KrvSW z9iGFBe@BuroMN#AzWEUwxnkN5h)+R*=5ZvmuqDX-gG$RZ#2)Hj& zpxDjyU^FGandn*LL7gYUi$rg_7rEXdFHbfhwXLYTG9 zLa9B%!9mW36DiQW5BDQq(18Lsb|KgCSb2bA)Gt!3mp~B(4J%4w6YOa=_S*+-ggW6A&6nw*-idXEj27g8@_*554LO-)I`s~ZaqPNMx}8r}?fo_m z+My1@Inzvxzy`U%#4L7ci519_vnVd+qJh=e0zo2rJ2BmyX=1LqT8rkI3r(yrQ-Ms< zXv{=|e1#bgEaXzWz(=GVix(OMHHAX@axfN{fXTpgQ~=97Dli`y=PmUj-EV}Skb?y7eFd+7D2V%#R=0x$>U7lHX^9$be6BaK*F0ou^X5e} z9zrGv^mqX;zsDRw&L;?LyxOl`e<%-|IA)$Uan1|}qA&zPCJJ=wjwfEitAl9SmrSNM zf%#+?*ea6<5p^GgOcKa5&KZa>qf9P+{w2Qo4ZoGqGf$p{NdgyTyn!q=OyRN$wJ^)7 zw76*sxhWK3BCx<-Y-1_mH?Fof*w}3EvayFT$CCsu*&zhe|=^W8{kxLw;ye%>$;8DFSbOBxI_5qR$- z>{Vw-pOj=F#oB3Ow|&6IVSBnGsD7_HrVy(uhgrz6a&06#4V7OJ5_7E7*||>QJa7r~ zC|@llf*2wYONx8SLXTXTXkZZzf(MN!n|dzYma+1H#5$k+qtEc!-?_zibp7c9pHfnu zbb)fxiLazMiAW>Oz;OE1PBqeLzn@5&l#2?HU{>j{r{}lA0@ef4fr|`~4JoPtkiT1A}grD?C!6^eH1^)7`kWm6;*lNODt2XHL zH>_s^MwGySu@oS%UUJ=7*1<*z6fiSTU=h{MdRce^(;0 z*jR3$$ee5<*)FnKJcZDW=NpR+M3d+4I8~x>#!9yNThOutI^2tgp21(psY1ZNrJ%{* zKZvJzD#Z#e>3c=_#570!1p<1~O%Aw?*3}*FqTg(W`7qGpnlgO?|Gk!E7yQUG+G+j`P zz;e$8f4=8iNHM=hrV{2_je#AsfR3bp0LMT$zcM4JDCmzCd#S}$QH`;K{Ifz#j8H`c zLsn{Gs#R&B*4}AjpMA{6IA@)Mac-H*V;nWk63AI3LB&ipG0hyI5XqR9F#?lGG@WMm ztD+(`O>xHxiry}@rdp^ZC)J~Yk@}=6)LilaKBE2*owCkaIA_(`*lF*xam+4oP!o7G zkjLT5^H!eyhiIKs=U_WU6qdQmJ*W<>(M?&t!%1}i5MA&e7=VERNdZU>$O+&9a{{IW zU`oJ@089<64&>*yT_<$vif;FD;q7Sq!*YL8S|$nX zA!N;CWHHIMlU!5>tq$VuNiL<^ND_LSZPp$OCp?!t*zB$GVz2kO7Ztz%XO7foW{8N)>3+nLoj8JM$;_`SOP7#(E>x zL83FpL4~u(L4{|X2l?JbUT(#~hx5o+d>8TvBeO!WyqsX*>x_B>>pb~hEb?yB?Z;38 znng~9N6=R*xGQ=_8W?2E)?cw&0`xtlZ4Y4z@$^|wwK8?(c^PSdOd%LW|KS8$SXY|G zk(<;P5q!nxN2W*5al{hFVnQx{*DQ(IVjbzg66F^F)z)DJOTTA=&?M9X< z=quLYAK1qr%2@)xhtrZ}2BjR^DY1rkz*z$OX^uTf5FSH}G6M^Ev}}Pc_u=io;lmKA zW3LMt`ypGP>m9iBzf^tr1%CZg`}>{+<-5+N9nC1xa21l7S0MO)JXRu6#*^+)p@R>n z$*nYfkhAnKfgXh>CYno4-$XpTB`Vv*w?$5e>Kl-AA{nT!P$Wv8S!804Nr=s10`Fu}rB)W)^#z(nx4ivV76zK2kG9?UKDvJdQs<{;Ku_mrZMZh z(YlnVDi!9r3tTLB3q6?Vo#y3MH}%JtzcTmb*GvisYJ3g78dc^plQ*9%KQAK;tn$Qq zG1^izC1YKO3yh=K zlsc2V*uw=zp%~oxA_giHdS-jLG>@WULun7O#~JFfD1uP2k2q%>E*((>n;9-Jh?VN$ z0>kWaHg=HhZ?-$nVrc4nygGj|QUpghS9!sl}A_j9zCH2S@ z$#utNl!1Yyw_9N(nn>j&RH*`&Wt71)UXoF*nJMNxf;_DSiyKN+z#7i4nkFc9!W0f+ zPZJz3nI;e=2hm(JL?VYC*$EWTGQypx=gc9L_q~7(RA~Y;N$D}4bk7m?V3&_kPxt4L zj5DwYr!5xEGZ&b=NBMRO<-H`-w;=-GmP$<4+8q{`(d>VQe0L*E#GBMAoo33#5?4r1 zlS(hlzQDtQyiv&{tMp2PR5uiIe!*BnJ_4aYC@n`$`IY)(um5p>Ob@6G_@hI#nPXxW zLDgOUb6ai}s-l#y`+k2M@;|QS$+U;rPoY6W?BO=X()!|zljUNz_ktH?epCFod-Z>j zpK&fa|JHOdW$msb#nimOeStU}csPiMI7taLLI@9NT)^}IR0Yh_S3KHSNvISX42)nX z!*l_i?KoZF1if+RRWhNSkDz2Ogiq;54g!Bv5}Ft73wg6oolQ#Km@ZIfQj|%$z-dB! z3Pq?wBpbZb1o* z`UCwJ`eUL0UVj{CKaUJryrb{C{8bFlT$aGs-}5Igg3@Rd@YW-X9(eN7$`){*z!zU3 zoUz-o1)g{w&wqih!fB44A%z?+(6l9!TW@F5^B%nTx{xEZe1u^62;r0G2t3jcFTI9$ zJ`+@E){@E1-I#Rm#iZX$c~NP`Ra@sRT(Pd$LXH!-<*#`EOQdl0=Qx3f|H{%%UC`wgAqxdMJb?aRGuQtW zA&Y2{K=Iclu#imQtgSpiQ_yLeVZ#gx+2YFO`V10{A%>780;$G)1M5f+a?TiOVzHSAmJ>u! ziNNRIap?<9z9Xv>`AU`uoaU#rL^QyPmI%z}kew2NDj&0grg1WV@E{r?zeM1GK^C17 zfsy6}ldE!>nINzRWeSVbVucWjS>XYU0iNAt-YXT2R(;9IPMAaBHo+e zMVhz3i;3PTUL^Ys@xv0oHGWv;UlNGffm;HxGw@IlMmC<-9F@&0TOhjStd^*1xxXdS zTa~p&W$XQ|QPn2CEmGT#Zi}L}E8F5y+wtv$oFFjS>l?FSxHW+|5%|Y&SDGWNMM4V< zZovajrh%8WL`}>6*N=X&4I3qM>HN`QT3gI%%jcA(%v z|EAZ)fS5pR{BK8CppTHGk1(`#S!>^Yx!49}bPm_I*y6v_A4}+NgaoY%!hvSf=;r>f z??zQCOlv!*E&qRi^k4Kp?qB{N`;Tn?=R;@EA5pLWgKK-$2Jt_)u^FGk1|O3hJSrH) z!Lx(0K6q;|*G;*uRHhb@CYKymCeW92k>WEETIUM6sF4Dk<;gbI zI(3xvMIgkDaJgT#U2CI`023mexw?)-TsOqYYLK|KF6!JV9u`xHnK)0PmQ3*+^x!b# z4@?j^;$sSE12M&$?1v$Kll(BlZ>}Gp0u%L_rGH{!j66l>UsF0$PpEWfR^+f$fsQ zQzr@>kVka=6^0U;D3Hd>|58DdFNHq{;`=?tC})?Y?8o3 zk8&%zlt}_lJ;$Y&-oW3#WiCMylF1~2!7`1IWkyIA5A`u=GE+-N%CY3GO$MS#^ugMt z#oO4eXOh4!GCPDY2veDWH$ceA0xjAJIYr>62k_P}$RhCQ<)qlyL>q#A)Zrvq!a|G) zouwbSv}k8D_EZ8nn@2v;lsEx9j4K8vn~O{yugy(N9(W5|%p+R#z{4!+{|u8~vLqI< z-^>)D!LX*#wuPK$rwB|Y_q9s$##%}HzJL43r5fo5Ml-TdLt7p)g1>!JgwDj5Z;U5s z;;9Cv8}kfQ@jiEoz-nWM!Od5*D?+(Im-hsbfM9lUo#y+5_aI~Xbb(sRy%c3q(t+s$ z--ghhY9fWB)53PTK-<>^k=Ng65<;7+Ox6xhCyh&lWHDhIF*A{OnA6EdO2G%y1yabz zXqK^(Lcpj*Aym@^GL0NV$Qk+{nkjJ0-Jrz!vjr%D`fP#cUJ*o|*W?#6jm;MLm1qU6 zFfrf<{6>eb5d3#k2=p6(dj294|D+&7-75rs_>D{1j0##Iu$i>t5qvIOs*_y5L++76 zRth}xI^Lr={!0D$RtofcM#wn=_x8hcFEiKgNj&+CkaGpf%?cBXNJFvStTA!WECS0n z8$~8FU3=n=I~zDAFH@izqGTeek#gvLnkX@YcB&Qpgv}+R=`xA6jD0^>Aj!xz7)F@8 z&tg2BxdO|~^(Jb}gQk%4w9`$r4P^-#6i`qTFHL3iDE~XXR0X@QA&SAc6pNj*XqFygvlCXrUMRJUD7YT5$^&gw%JQZ)8TO4_#BL=t}o zmk2)SBAO?5=tHF#$TLa}Y`*3w$-roR6ap;%Uv}EoaB4BsX}pfH=gVy9Ih{Q42Ac6E zUvxu>;BJ=)#G8bwyF}n3Dakir7r0SFX{RpjV!QfM!7)oq1wId@U>NO(RX%TgH==l-_c7-565b9TC6AcR#H?6tWfI})~hp$jAKQfVh@gaPLKkx z#EY5U*>-{^J8MHDG=K76+^jKt$lAK+F!T4#Wz=z8w@avk{H6-9IG&lN#wlpAX5R_DdwW z%TY=asXb)(l16HgvBtm#av0ms0I+|m^NEvXzQkPmdTi!QZW5Pdn1S;~q={t8<~xi- zK(7%CDKXGYCf~4O6t+NLF^Lo8KOc`=dXT3O?!#hW3F?4VEeE!uE_Gx~A4pCZIjU5l zjG(0!s;vsyEmyVF4Gw{+I;e11QPxh0Fe{UM3MN{ZWG%8#ZEdztXC1R}f-E`pEwZ_5 zG$Geavgg}aU~i#SP?QjQ?PL_j`7WFM?U@{63%_4isSf|9HLM8cX)H$aeoNivi z%}zT*T;#Y@T+C>Gx;eEu?Ob*+qj@D4rnpO8Y;bqE*zKNhamo$xP@AdlOqb|fQbliY zw{T&Qr^th1&teZEo6q3hi#^9YIN?d~Vo|fm2FWT97BySejCv=Ky1T?X(~H^OC0;D^ zZuF8k$(zbF!#mWA67M7~)O*i)5#cx34=H{*e#rG3&yVpQ?*Xnp;ypptq$Zb`=s(vV z^Zl3bLpeTRY5?X3EDoTdR|iC}1LuZ-i(K9mP{Z_Kz)_}?0%r%JI&cLy+88(}h&t5- zMljtLxRd240?#m=*=S)StZ#In5zaSC45pSOA-b^9`bId^=sXwFf~N&zM(|v2F|ct) zV`McR*_fIQYMR^>=}n883TdFrhxjN`;)qGOk_Ni;Bw-AF*cQ;6KaCel24dLB+CaE5 zhXDBN41}B0Olli$kf8-wLwl3{IHBN^_}v&6oAOUy@3=S5G{)& z#*r`5Wi!P>wzkBUIhg5eBJ1gFS4f59ekX!8P4-8De@p-p0u}^fN#M#r)CJZDqCQwR zs1K|T#+u-IZvE+(IBp&HOtm|#t^u+$nx4N4tU zlJ8WQJJ7{s|B->X98A6=67PP7?|#7Vu@c2B$O7}7{WW(bLgUu#%&Qc~2D5_3uObI>?KU-n27C(ICl zu+^{dy?1ypwkpWm9 zxQ}0i=E=06Tw`FPaoE5KBf&(9x!lAWa@IRR09`5A2OOj%v>|GULV+q)*i4>QiDW&P zN&$$K)*K5ftV0$DdtjqoVW4adX)FEL`eTp(L4SH_H>*R6XgQm@fZxbl5Paq&j#NXq zn$DWabTLHB*^&glBr2bgX$GGF6?l(LXG4V)LLa;KiuPJ0e&;t-`LE?u_~{QeU6xBe zQ)*IrZK0fAT)G;nS$%DRJ#6WUspdGX6n%vhiVjS3MmtnfdTk+%oPwi0>3oKz*A~iY zNq9(IqkNt*d_8OZ_xR(W ze^LO=VZm+C?%4+BR%Tv$y}^oL5xa|3wKjt96Vk?g&(QBP&*80q`PQVh?Ge&pRY#uq zI`_m~eKmJ0lP6wg`}#fi;k(eP@(3Wy=L)|v|?~l+I7Qfbv)$`6gWxq-Vw4PM_EI)|6dh00yWsKaDlCi0|j3AgvTyrW!gZ2Nj_#K zYfuIX%_HhV@K8UbbCgK5z-e;d z4dY1H7=cf}0q66XOt#~-WL+-CLh%1O(QmFFs%dIJ;CIpw=V-Mu%zwQ*_t6gk$mwIrNW1VA!mZ#8X@~UX5XSD~bJsUjO z?m6n=i>dcc^uwsYv4JQEtfFOadJyu0rs})G@^K`;D%$L~!w<>+!~9VYP^`V=*86Xv z&r(zX;sOc+FezYEAf^T_(L-?MUaHh0y|!9*H$v_id%vDm3MER(|7M|FBKcjozmQ^v z$ZIM>;2%FCR7Od}$xH)dNEtTKSM`Tz*>xgaiWI0Phmc}}R&tR7&wa-)*K^ugwsjBf z-f?T zvbl)%mbDDzlO*ueOL+ei{Pa5#z_D}EL8~vZfyC2~Pvf-r& z^wXtZ5zo=}sREBY#q~Z(n!xRMY~i9c(B@7H=(ewyn*5QqO*j@1jh8w&pwI{V>6AhaZ<7 zc#h%w>CyWXUsOqy8>w|3&(k`+cwzu2e4q5G|w?}pRx(+znA*mxWIxg&p4IN85p`ugj4H$jH zp&N1O#$lb2*Lizq9P2!|3o^Sb>w=mthi<~9o7UZo`kP1Iiix)l>WX1qPjyA)Z4&_{?u%Oqd zyD;gld3R&Y-EqBY-&1q2{5J4sfk0SQ5nfpD27n$Y_CCR;7jG)_DuLPM(AK8*^?%+D-L0VZzOmZx%9IV13KlmYCaW zVJpNwR@M)xk7quP(fy11qpbhJ{z66z)U`R-1|jVt+hO{B_4lLdf$9ga^1+P{qVB;H z4+TJ~S$`*X_d47QYwp@|7f#%L;ci6rN$7)N59B?7?T_qx z1PA+_>?dTjKsr6<_4JsJ^OSi|)OcwV6g3^u3@e&#XohXg>YL$EvsKLz(;~42(pyBe z#HyC-TMFv>+dv7*u8ahkCHd`1BoojGBi}%Zlj|VQnc!fO6Xhbxjd4*JJSiAS9Wy#& zSm(UX7~CbZ3+lSJ!ba6ik=&K;zIB6d$9H1UH4+qgDW3IV&C+>nDFS- zM{&IW`Tl~AyxQJuqqgOhmKfM7wG}d3&25Fu#}7V^h5fho$FBbS`{R87u}`4zi3Lvx z>Tv#=w!>Rt)%DGaea%wbY!@@!c`gpN8P=9BX=?D|;09)aW>Q;aw!(^5A*~VFdT48m zZe7_L)ooX_MN<2;_Bh$M>LElvn)N7hA07KBrarp&Q5<+Q_Ax=H!Clk;L$Aa~uj_F5 z(Ma7Pr)_mxtY{n34%H82JcttyCiX>o-;;g$j3(*IwQX_fI(cNf@^;*yyxsP8*xfFv zJ<{4|wMS|13%!xoXK5d-?KAy8ZgKW%Wr>z&-iMs~O7G(}%BMf$*9eKhl(%uQc>yR_ z`7YN?A*kh-e>Op zaEW1ZI4#^aQnczoavLTS-(Z2^>?AjsLI=tDI9Wj6jys;kZ=n*=lJ--{0(aa?WnMuvT>z3YCw@CtK1@4_puvy-XLlr4Ro4J=bjHODCsB+NaE<&HH9!n}I{daf2I-pnrQL#)7>K z4;aVE$9jl~vu30g-TgY(9G5c;W=_cAnhEFooGGxvIAGv7BPukMxZx09_hbqTp?vAA zg_y=!Q8NXmnUp3wQy>!c`Y?>NF%n~dvAjCU6c~$Hy6y-DDIRi|nxU6f9d1SUe!Rfy zaI+xqdYZy?mXo0ZzeQ>n{8cgu$Yhn|Fo9b>6hvl_Kld(Ux6zPKV35;A8R)7%t%W!T)w%vJJL!Ep~A1)(aP9n8|#vyYa+&iM{foWUohZ(i=kl z`Qv20#D4OQo@^|p$NP|hBeV*M@+5n>=~Rl#-9vFLBe5SiLLny+Y8q{()+nq~=O~Oa z!NO%XN_UuNEw-?o@LlTd5(j0@dv81av4T!Jhi3lCPNjo-avVS9jCWB< zBkyoiJgD~^@?a_fg)H~3^x`yqFr)mY`JsZgj(hxO`eUyDS%1t8s187V!2SSa2Id5! zG_X7nn*-|uaXc_Oh>v)jJRmB~xdcgFXCe#{Kngz=xdK1`3rR9Z;&f12FtUQj2ctB2 zZ7}MB4+SH#aYADrp^_8}^>V*Nq@6>nxyhstt|wfwQw}nM@`AV-C#NYeD#^QiJ>hMH z0THB)I%g43POiXsx01B!J6xoA>OD9_OM&U$BwDwx zq!^m5s!rhuJBf}Gy2TFjpy_L|hN24(fJeD#M9~^H&qR?q&t%b7Qd}Q3CsD40mAVy+ zj++@k28w`XKKTKh0J2q{!dNw5p-vr9Jj4lPQx?3j3iH)gCCG<1fK8}FB}N%Vgxf#G z(BfbOKZHjJ)+djAOGgAtL+S6zp{>M9N!~bP1Zm*MP-c_aj219PAl8gG zv4x-x&zl!agen3-86z-GomU96A}JQOz(R-}Y2#eyq%O#yB}UdwlWs!!O*3y|=idJ& zuM4UV!jYhpK?rRW(+I~JU224q;MKv{9K0nMn;O?O#;(Q(8{=f-Q;l(?$;l=NX%^ND zQO))>!-D2RT3~pK@)oFQv9Sdo=HG@Zp{394;5q;M;f6QEm}dWZ%=(}sK{y!{+6Xa? zG8)H;dTH#Wwtk%eF z{hzyDYL(m?XWAsS<$(&@S9HYujw?IjP{$)3v8>a&PN=*gyE9iF?ND)56sbj}9seYH z_B;GAM&b&E{O#rN!7)PbAD0sl#Ti*?@Ms%oqj;S3lOYIIENUp_0|ht&7M)jN6bF`X zp@1w(GjCd;|KbsLK4rQcXQRkoX=AmW>)=r5Q~Io`oz)IjJLeo+aOSzF?|ewhSG${C zY<43&i1MuTU}NWcEk8@j;SldQiVZsL#mdebwLHo%!4C<36Z}x>cf}7?omXo4K>tL4 zB>GSB$5j6-{+QCaO3M=i(gTnlP#J)^0Yd^&*m;VUpC!-J(4d?kzNFQ`n`xHX)EIS* zcQr4Y7h-=g5gbeG~re!n_O&ykftHc z5Y{ZJ*|nnlrYLB7s40u~HCxaeV>%b=Q(DtXOj~nO(BXRN+pw#^O?*U2db2k`&Lkx7ivmGQ>%6FoZrWlsyMCDR<>^XRCvKWKa(wU0k|5 z+C_m|PM@1)WNV6`uS$3Tk^<5KkP|RD0Mi4O1Yl*rNYc7(55&-*A&rpLC?^p;xab zt`T3rW&iL16p*^LB4BX$R znGxVN1CgScJhfA?q};BwG1s1NbE(eWYvZIH;&3U!8A4xEN^CS%U=o?%7CXyzU7fSf z!D%PN)h%i5G1@KArE>a=E^}AuQiNZHA2$jQND9D6QX3Cxl%=mPjU$D}3QP`|9>9&3 z1gzA|_Q0V*EXfJp63ih6tvhqXK&Q9y&inYPjtr@}2FhsPu*Rr2xbm5|5l_CA2mV}j z%2-1m{ZwEKCIO{Rg~MImeh2S=g*w{A#~JBr}Te5RMmg!y9)ck=W~`cb_t3A$O;AK+7wbzB($f{Y{F}gH%C(z z&IVD%AENPs?@_V9G#mrQDzZ@&3ydTCHn+x!)){RurcGfRRJJ+V1{d1w*FMKpt+_I}4TiM2wgr!K z>Z-hedd_QeRLiTZT^2XUamuxu_U6_nTH|P&3%Wj|&6qYQY*X2WeR2E#%D%KW^z)h7 zJgD4eubu2+Mde~vR2mme9Ki;KZ7R}Y`5KDfI3|yiuU?ITNQ=Te!vuyH!wrnmqRkd| zSi3A7BtL>&r`Ex4@*}8sCwnm6bIgNto^UUcybWEWtsH85ivIV_HV)cp^nU!&E6;-p z&wP(|*=guH!OLf}pC-%Wk}#{o1TL8s8u_3=788QP1%f{wq)`hVejJg;MoMtJ-QvAH zn_SbZDHdi}o3!Y~zv8d7*N7H4XGEHaG>d?J);V2UM@F@ih_`r;f6oKhN6OHFMw)>v zqs(AwzZdc1PRZ;VW1E3(2F(>Q0=eXiH3jpu-LA(Y=>Io-A4*W*0&{2;NAnCqj93Hl z6mOVKtG_bsNJCDxaRO1~1U7J``2Ky0yba_t>^3;@d7o@ZU+aQ zBMvS*VJ;%wi7sOY-PQ|_9+1r5@dx|p<9PaIy!Iu&&6UWPizJq?+h>A6^IqutCB983 zk4*|pP7)e{@i;kCT#@mlTQ4vWMj+V9X1<9j<|2Y|J#XR)`NOQlMxYL}73QdVg`=v9 z+{c!ZH^M3lYY9GKBnzzsz9wlK|HAf`(+-jxN8k$J9!?Ar%2;u?^x1| z*Hge{1trTLABcs4O9NSLut~RN>lnKo&2g~6$)T=Qv{k#}L9%xg^CJZA z8+jw%_68;tWjIWr`yIUJzW-4?`ZV6mmdK;$eG-n+ND1wHP^13%$kq{104@YBCA zP8Jxvv1fjrL=7QH9gt@wOAE-6y_i3l!v&Hk+fI&IYI0qcIYKizW|3xQlcx}S*z7VH zdn#UW%XkXoPEvKcbe#4v=bTU%XWeiQB0SqY+$!9n5K}sgHO#^&P9!>9U=}@ItE^fJ zdueVtYsEXxKGF%{>Frkv%vo7!0ns&y`@8F|v@N)z?W3G@49f&=0I2k)8 z7#L`h5KO=$yK~|8GtcIP;M+pok0E`KoW@5C!LvL^dWj3p*@&rg9f@1no zj-l+{m7YBwgnOgC$n#G2V!3yt7h!$_{diM&PDNW7On#Sn);J4u?L#(B*?A78I>NV=28>O%tZQIDgHfAU=qs7KQvqK zOh*$;&IEOi9?ZcOGOav|&tjHU<73v)>w2{`UyIlA?3pJpi1Byw1P&NTpd;8)s9g2EyUlzm>Ms+%Udv+KH^%m)%5pE*Z9BOidLh|LWAf3=EbEk>joLPH} zzyTZ~(2T{D25r0ENUc#D6gH4M>~=NT;@(x(Ml!oo;;DRrR_~ya-mRUqC5xm9yT%ww z&SKL|R_~z%6M$p5O#W$w3TxC_x}|9rHd3m`3xr{|+evjX!p(89&pl5*S1~#^^-lv| zFc5A2|84Z$hM+5Yht#L}0>6)!T#lB5G&5WhVq(5P8HbH9Lt)SP0@ac}bNK?Nu9;bC z^ms!}f*HyeU@yAq=2=4iEdh@`ftFC0$^WlVtx(vcYE^@LE3ivfhATv>BrRV-`nt7h zlR_=M4QW=fh3WKv)LJ`rsBf_~o&KF#3p=edHV<}&+!{ih-42&h-4Ue0+o#vfKNd)o z85=3UVuy*N<|z}2G@UQ@EcIZmr{2S><8OaN2`zZ*?J+J2-3jE>N0Dt~1*)(NSVIog zQ?1oDHrQMBB7Y?*ln&AwA(qynW7ZhRJ$wSBxmrb+ICG!Ek;j~AHApdsT0*4U8_H_mrTX<``ea;c90qwxeC zaa6J-nxmJ;3zRySb&sV!X1%jTOAb0mHIwh|(Bn+>F*DpsEm`HR)64~TsE0?6^u%f= z)-%MztKxC~WrSxx&mXJ&cMzU^LI6euj0wQ*z@veP2#OCva!_gz)&}hg!l9r@QrpBf z!k$J)8{s0Um+~pdaYOK)U}QBeZ;XVyX55X~-nqT8qW9X~D7mNn9?ZID**(~BPxiei zzPIFFMDYK3orX4q7 z#Vy-!5wug7^&cx&HB>gMYKBG4YMSAAvy;sb*?d8BtZu%xIgT|?ZGp-bRV}c(#pV_m zamR)`as1AccjD5WA-xdOE1?&L^vdalf?mbFP~NM$m!M;n{eSGA*dmpVRelGe@BAMf zaK-IO-7&O#QFqMjKEFGvyD#mIwcWRN$FA-NyW?c{%pTa*V^0s9?UB?IGkR9_ehQixs-G;Po!@41_ z+x%`=-EDI>?CQ3!o1hE+^a~#FA3rns7x|kO3#>Bg4D4rf43{=sQ;Ic*m`FD%nQF1X zT6&f1utIVFdIm%+7C5P5EX2{rH-|pHMFg8$Nolkr@2|LDH?>Y$IBjuWk3<`T?M%w+ zU0|coo@!&seUZ9ZwY}Qb?d^SQ{8#xSk2L0M1GfjVe24#Of1DN3! ztD9nT(_KxuRb8{=&2X|=WOFVpXueuAYnva_OlpgvEijaV*sEHsZo!RqwAkMQ$6K6l zfzXyQEs@i*v?a<~QfzOr!2TA;TVP_lIqi_qb!=Cx=(?#ZwshUs700@s>xyCB^1AUD zYg_JZiM=h4wnRv)v{uMyHLEqtm$crXnN6*0TBEM@j5aKu-)3nW6t|ty7G>RMcE|kg z)!nhQ``YeYxwOrSHdxVSZ5wQC6V?_9ZAY|4VcX)in9{bQEw|Xzc2`^MYCEwVHn!W{ z4u{)?v}bu_`*_WaX+NnwCbzHBrM2z%w#VM~N6Gg!tphST%<9O^XLYRXh&>&Tcf|3I zGdf{&r=6W}pwpR7Tv>kOyc;p^#v?c4%#9_TSv0Zp%+8qE`BG;@c1fUgJ_2LA%<6)L zUAF4d*y>Xw*W5Od24UD|!ixm$4VmIb%+cu8F|x?vpmmPIarko!b|6FYAu2-FJ0IW{xC`0$7T=4Kd!zcWXvWGT=-$Fw>Z=(Iv9Toc_NUiD1@C1a|x%4Egd9 z$>;e#!FH4geDO6GLpXf5L|_QzOCQ0(u?;1zA0sDAl#`1BFE^Lc=4}IS6iWmS(xxHA zh@rsg1OrJFkC{W;`$A3uQz9^r5&~2ks|?mOwYeRApTJAsV+tpKo*?k}^~Kx!60j+^ z9V5p}jOU$DsX%aRw7&^Ay@hwkXP{Kzt#=Sh_mXy~9KggIKK zqH)AvP1f~8P5Sqte1hwTnsoh8gfw3!@Rz^ghoA9l23dC3N?t3(+jANI!y|C|6!2nY*AY+zC#G6E+AA|xm&2q{4sK^PyjqcQe3 zPH%#oCV5S;s_oHs*xY_gdxUgM>xjCJgE}Fj(}Yf#(W$-@_IA41NysvRxBdn~*Nc%! z5-DUn&-tHR*HMMDiZ3p~#t?hV|Klq>9)R=LUnbdeF+M0N7zx3{gE1;NwK0Y^E^3U4 zjjI}CS>u|<*x&ekV}vzH{y)3^b@XqIAnjY_HCI(^VG3PZkv-8yOuM7)u%Oc>eZxNb z0YCqW46?V@n<2m;E6qXy<=YQ&qg|YLFS*#|x6cn%l-Fl$z=QxK1!e>yJ7`o8b~N7K z80k%Nn&42A<4rKQ>By$I*tDn_CO13L3|E?kG{?B+70t1+c};U9w#aCK|uTdei-Lh>W6Z_U4Gc-SK`m|wX_{L?0>=^X&vi2Vo;|H-Fs=rH65|0 zBXkp;zYA6%@ERjTyrdJ zzOgxKnkTlvls2>5aAigFh0RgcvbH6c=C@p`nWZf^Y9^z_=oT2=VtEVHwm8xPCtH+g z`TmxdTjFxds8&d6HMSKBTP}o@>F=YbF+iz}T4mB#W2Av&#vn@ikYi$;NjXg>3-suR*Zzi&K1ZVDR$FMkIYx@>kp?bP z`>RbedEY%*U=-u9P8Jx;^XO!Ok;Yg9V~t}52ASC=a?EiWxuW^ch?dC`E6LmZIK?rA zGK$6&fuBR9&X}D{!Gn89S#(B*8VoQIYYa9p*w|?ZIaQ#4vVjT4Bm+~8(@C5r##YJ(FC459_9<3FR#W;SdB@@#G&_Xx0_aJcn1lLppubb2x$PY=PFDaKnGDdF5Sv z{qL)5^QOxK((wlkYJ9bcvr?F7%F`|20}=I8=s=z&QDRiXY1SPm*S)h-1azy95=p= zew%a<(qxIr5|br*ykQ)6{_^4czw>3K?3~z{`aa(+J>poI#mHbf2?#aBxWQ z_Xv@LB+}$)DJZC@Nq;=`2HvH|AzBcO_7|hcj55?hld`=v*gVfN7(X*q#*IL&vSGK)jow6e?|=b zGaJ|tnJDn$%UoL^7iv36Kw~t&gUu?BJcDQ65@a*Eu^Vo`k2%U2!X}gNf5a$RBr(I> zVX|N!Ig#;NtI8m!ycB^6%41f6)KaSGJ372G>Nf8YoZ7Bl#%_AnxnwL!EU=$FiC_|A?9!EJ#hp4Fvi`8<) zC$RbJAe&5l z6DcXwG9(bv{Cb6*l+iH6I%#2*z1YK@ZhQ=HHJpE%XgL2gfxYsSqQX#)Edsrk}riJmR7kW&?<3*0zFB3tH3tY(0wbb&4({F~s* zuh~dcMX)V3a+vlut)b8VLHcxDk^>3RnBSKzk0GSr`2PHIjWWx0!kSu+)ejZOLLs^z?SP7YiBHDpsI*2i*H8OCNm#PdD~`T@c@(xuU_FBc-ufJ2oP&9= zz>9C;```G1Z_<@bbT7Wa|6Qb!ZeS>HZ_)%V$Z*1{A4>bTX$Hva0}*Dbes;=8beJ%s zv}lMxaA$N`E(NhSzlPUG`F1!%1SM4H_ZnXR8{Yp1#ZY0qEYS^n--mwBa+j^V??@Nu z+5^4r;=lHndHPT7a3gN)iavc=*rONj>Vv+&2$>;JOi_VT)fR;!&Y71XaM&We%M5|L zKHw&!34UyXF~Q{01Y@d!X`GBFLtq*QGG+)&FsGZSH4mFOY@RW3&ZID>41q)nAD^J6 zD(<{R6L+#38d_~p^5C>pF%x6SW9}hAODoYx&L!{9^3Usff1dAm z?d-L8)^@q}a@=UM#q5*Tj~QdLJr;E;AhD_EjfcGQP-lwl#+AGDomCncL9C(RV-_iCxxFw2zTv zv5dpME0*1{?};%1`+;Z&VLKRe6wgQ75p6VfnPcg;Gxl+4g7+hIC3S$3dJAE)bet9+X8yBvmH zmKEsy541z{ydH{n1eUL0d=<+#uu4CY_HSX8b~GK|!F~+daag`Dk4HZN%SmXbV4a9P z<>?sbVw3$m_4)Eb>=$ERDu0Cazc8-Eeii1=v0RJwdW>IUzZK)x*kt()9mDxL_~)>0 zf_+o8+qll}#CjL{G)&oKY3xnbd+2&Ew)OS68(_#Po3sr%Z!$Nc<9;5e%JKjmvdS*& zbe%tlO_qn~@-Vs#nX<|zyR=8RugH*9+M}F@Y%(8X_s6kIe}ZmLVwr*V6s9as(;@3K zv^^`I!!G@Ky8IRWMGWb$(kcCQwe&aWblnGZ6m|Q?{Rt4dR1uK2h*U$U(#z11UZfKk z2oVqhN=Mq#MY@!Lv{4KoL`pzV>LP>|YLG6lJ`iaMNUwo+cJJ@~o|Apw|D5yq$Y-xy z^PAt)-NaBLW$>^SQ^O7otPwGKRkkkg8jZYDVH@m8WT#uXgcHGm47k^%1=FOkBb)Uf%Tm2M&v~%=|X15w9^|e;c81cchCtLg1 z%sX?|(P1klji0zXV|?7Bc1aaOS6@k7Qg?ADtNV;Sy?1?>a;x*k{bjEFHmp^ncX^yg z*uncpY1pAw3;tW8^Sj(B zS(bFOF69dUB&pcuL6fWJS{AbUcEQQpbHD1GRC9Im#Jzd{9p5>-Su;oc{(KX|4&@v+ zzRZVdsB-JZx5_(zf=Car$-cBJhOa6_xi6RE>4*8Hg`xTYth5@HO5a%DHD?CwP(Yf zXG4=N*kLmx%KTeybC2nX-wdp3?a7(a>0<<)W2}}bm7DETE5DiG|szV*ouj7-@jaGN!nGbuVl$rZuOo?rFX?_ioJ60 zV)TiQ`z9wB>GvRfygTer_ZoE$E}veZ$l1na$A*=eX=RoDuwmiJ={^vXYHT<+s1HV>W?k!4`9&4Z`^QTv0#7lZDV8W?`F z)$N+$vzoli^=#sH1GMuH1HxzvP<3YNk&fHLzmq&sR+uk-6iYwFyPz`)_M` zuuQQ#3&Z~X_*v+U7CVntn|X5Ume1-=Z`FT6|GL>ZkY;@ux0s2739T^)45#AN4R}Sm8x;j~?DqcSfDG?YFPJ{V419z7@XcJvvkS zSMkGkPkWYr+Q5XjQCB05g~o@RTo$vn|CnxjCx^Blap_v(?%mVh2CdmveOvsJ=4%JW zo^*ci#OC=z%SflOl!=j2--0)UP+s zFNvLNN7hVA*s*rQlS2K9%!pX{cg}H@r#;y+tkB$uP6J~fthtus%eXv|7{PRsUhF8n!+@al!4-dzZGYnw;2u+tGZnEq;uD z`6$ikq9g9)JySX**KZ+p+r&R=P$RU#yPRvb#h**q^mB5VCmXhOpImg=lL42OZTn@| z{Llt#Vq%Ymrlh|=xN7ger?fo3?9T4ADXq5EwW6MeKD~PTZ21R^p7;8rXRE_yoM~S9 z=e(5uJ8R8+Sg_-zm7VsvBTB59)ha4#%dxka3vK#wU+DEeL$8GFe4948cGf#X&pp1` zq~GHT5h3=8#^uIdjY*sL$8i~}bg5G>s?nLe3z}P9GKcry7O^>WX7bs72R0umlTzt& zCr^pwy0<&$zqfwLhVY)b$3@26vwxjFE_~pDErTXKTQ#Kpz-7G#&T08-@wUjR!)t~V zJJY1s{co$J-w+ahr$l()8W9DW?0A@Om$e|GQ;j~0B1S&jp5aiBA%kOHTlZ&H3Az`R zSoC7HMm4jxe|F%`qv0QQ*W0R>pDsxlWZ#>; zWZ%KxGi}(m>`sZ~4{Aj0TeSVUb#2<)ZY?fNSi85-jnJ6bmaAXa?)Us?-5;;deOfMG z_Fkj&JPctPYd~v$v<+H<%h2H3rd@4NUlUFl4N1yn)eb=?iW<}i|m4Da3 zO{d?MIbXH>^@Urv?3w*G+v1UDt{q-d?AEdDTiz5%DK;?SgZz&l_RO%M!RC;u)xXOB zbV$AiiH|zhnH`yLah~!W4|NZArgT+Ym9IYCxoOVu_d*8z^y}1!LM{5=ODOs}{je)J z!i$9zi2rBFvL7Sol#XnEdB>rOX_h^Syc1h!&A=;XBQhi$8$wDZj4m_c z&e}6oa$foPNu@2zi=R3)FR|d|65*}t?aL7#moLihIVbVMtX(3a-oC5e`clNQ8ejZ# zz0_tqY^U_`kU&QnMHxaHGhV&fmxtr68>M3=YghIWZq5O#Uh?;D#& z6>a}2rF-6r(RJ-G|EUsL<3ip$75gk|Iii2|yLFs@J62{{QbOT`l!OT9fhHvT&~Zn~ z>+PN+7orMu4u0{{9 z(euv|$q_r9aeR_gdPB&j*y%9|J;PgH>N8P;cktk&fv?Pypc9$=;rKi=FOQ~K4MWsoiRs3BZI~)=>KN# zpapxs?fR_fwz6|V+gIt|{Hwx~Pjxy}^3sN+gu2mTBf2c^GdN-KPhpD_`xW?Zc%vta zD*XIw{av#@?cc52#>p)g55Kc;cz!&^`kQ5C+g9kHn8;6X)eU*Qx{y0;d!}Kv zb3Ash(`aLc#Tq6-}x-pCok(a_}bVdb2sfB#YO&)=(h?spzbH^usn z1OMLNyXJLt{IlUB-ZicJu1{2jp1YE=-0uD6?Z&c`!nPM37BVrSe^Pv}{NF{6uAVhK zc-x}Q@$GK!EODr7=ktd`@71_l&MFvpD^HX72jSI&!m2n^xNd0viV=&#E`PSO2kSig zcc(s4iSLi_O;G#lJI-!CvM_A%jOEjvXX=k{br{iq?bf_Y-`%_ycFQ^*SEJ2^ONoC+ zCA^!tF=2d*y!m54dza}}WR($zSztYXK7gxo-N;4vVxrE`l7H@d+;+rB}YyWUH`@$x@x_xl3&8&#i!zXO2)#`1* zE1%}O(0fIhO$SEQ{`bAkgAcy>^!)W+`wHE=+WG#0w@seZ4WE$laocSr%lAEU>t>cA zB^n=8Yb8I3&3bF+#dPgEro0}OKkmZ0#_{`K2WOdZ?N!v*S>BClQpX*3 zd036Y2kZRTt486tx7A)P==`osPtUWDo^`sp;bM;lV+uWvjCo+cE7YJ$|DhM-Yt4Gu zufdBqL)y-Y+fr^~a{GSG!;f_T;nt63CvOQk|6}Kl2Ny=>*?Bi*V#`;3FYT(lXYgrf zOf@b{O^UsJukHDNb|3BDb5GIBYi=*=Rb=8H?=6mu4v*TKc17&0pO?j~?sq-svRP+_ z<)0ks?fi65zg^);llPW8`~IaN0wdr@G;;NH8Z*2%Kr_{e@^@;2z$f5L^(Wg+_#hZl=j^sMHy z(8tg3UODri;j(Faetg%U$gSD~6Rvl1E-jaDS&OQ4Dtn{B<+rn2zN+=xte3-jx*~5) z-E?bs&f`TNB>Ky=bG2bE*^8+xj=bS9yP!7dV=@SupEGsdV1ftTRwf2G31IJIkWWDJ9R!Bm$gcl!wJLt9)CC_?~^^7 zlcy)fKl-Z0rRXC2dqj*~u>I(gTfJKBuT>;|>c!AMzxi!<`joB%6E?2(-HVR*?6oWZ zy|PPo{FIV@*Q_i3b~(S2dSCYVB3q7?c-%TE?B7-W_Z8U?GNEn!BP03jmO9@i7Iog+ zu1U@r6I-jXWG-@ z+p3<9IQFWyovn9D?1~w@_4Tfc5u@jxe7>dmyOLpb=MCL_JZ|CJqREeM zj!nvP`h&#R#riDRyX0B%({*PQZTar{>=Kbjzy9Kj#m(nbs?ob_@Lk}G85~GsIIrd~Q@!*)?kX!@HjTVo5Vy;TALT=-61b;=cze7&ZdDS0 zFRcU4Qp5S5Z@r!>rtgnNY4S~-fA_gnxryH^`~PVeEpZ$F1n+=*@sjvp-1VvSyoZm% zO}tOAyf6j#)R6oM@(Xagrg%Mk6>ii@?Hh0l?@0c4+=KVT<8W7P=}}L>-2v{!y#an7 zH|t1GxbtV<^2Yg}f1U$8voqg5H|wU(=f^z(UP|+3>U>r8QQ`yrYc&kt{`d78^_%;} z`8WU42ltd};s0|{d>C$&7WW+ozGW(I;Z=l<#kh^L8;!NN2QNzgckOXYzw<}`av1*# zhmCWN2N{_S_qgtU%Et@0aXY}BjrHd}0iGN826%DY7@xYoD((vK&u}xqyWv)VM{3U` z>31>yDY!czzf}7J@>~6$$@nUr^yHxD5bn(=UJ1X1mm;s{T@w9PKz|13dJyEByzK4# z4WHAK8@KX``wk`Fl3zVvYIoswT5;d?%ePeZduGF&n0lVulQ-%~Ubia@&k^8#aaVv3 z)}H#(^PjBN7>S$YUHC-JH(>nEru&v@xD}9}kGlhWIc^8|Hrx~79=~Tc?0|W=hkFA2 zgLHB{djq^4Zm=Kqycp>BG{(2D+xoXBqyL|VF$td+;7jl&0lpEp1N3*tutydr)&z?ZKhW>apwr6sYR`DQ z9`mC<-|v|XE5KLc-!ng&kMVmJ!w&G@@o&%f(5V0a+{S0&xt&AG*Kg)D{qhA~7Wd#k;0!+L+#d^_=x z&V9(Y9K)@yrvJ}F>3N{}ZsK}=<T=^HqUxe2aRdcmw~dhVhT~;El-V%*Ff+mV7h39&UXl9)Sudg4f2qtHkHg(;0XFB)$?Kio2r4 zzX~?|%WT|UFTR_+ja$Epe?vYN_r{73^tU+Yk>cnf2lpV_GHb+afR0* z|1s|Tf>FP0z$Mq3U*eiK zoC)+lKb#XkK+k))abCPH?!w*p9K4+NT#$TSydiGkl^IV*+`cIJF!Fw1skUOb$h3%Hdm zdFS-;FL!V^K7$kf74CW>d7X!x&KnK?ys2}YFO1vIB(LMCj(c%EKHK2tbII58kH&dU z!`=88d^m2rkbHA|3U1+_vtL%?o|lq;Nq&#|EAi}%|8Ly;M*J-K2bxb2*W)nQIgtG4 z*IV&5y#q$P9kB!?!#4j`cSj`s`zd-(g_7@jl%KV(cjS}LC_%-#C;=j@J4EK~0*W)}# z5t$#Ow0I?Y%HSqG4sV8g@W1HqkK3O}&s*}d)kDPV;~Q|Jthm0v*oC`8#r1jVBJRas zFmHF%D@y(h{jYIPCGm}TPM2)2yR!IRyd-XZDjtE?#Xa}{yrt%=q|RHK#}@|47YxO% zYLfSTpVGI?b1|M8;z!A^!>wB4)yN;i?K=7krMJ4a9rX(--&PQ}Hpl)ll+Z;#Tt8ov01^*ql zzn1)mtoOL)hooNbP24q9yf!^)K9=pZ@H1?00o)uR`F!+O)BH&B?)0?8J-8m9y>Mfc ze}^V1%8PY`dMR`P>!?2=`6{>zuR*>8Zq1Oq@8>^!OF!J6DL$Y4B;1@W{v2PaK3DuNsWE=X zUGu~fnV)0o3&r)h`Wo)Vd(-m*w-!m>_wy*eC8u-2_dUOPqQw6Q5)Z}QOU3`io2xGq zKP_=161SF%4^Ag9%*4I6_(}2`aOaow`9+`q58~!e;&J5v#$Bt$`{Ivq8_&viWhljV zMN2-Kd_mm3Mtpq+d7&g#Utrip!qH2gXD$vnvWH~iyu?pCf=3(cpG>9 zF5a4aaA_I8`wyJ;et>)NHh2l#+$s6#_@|o3|HGTA?~?pQ*4rKT>=EBVeh}{7E3W6= z6x`S+uJ2QpYaaiOo=v#RBl(5+LEM863X&Jj;pU%`FU}jbJKBF)JO@3ma2s!_$9Wmf z>#LG)M*d^m!u2`77Vg3I`rHwBU1Pn$QZx`ZZ-^gFFFsN8cg5GU-sQL#?}G2po_mrH z4w4s6<5rS*4*V|eP8QEgPg>`Vs_(qEaosPuaMyjw>vg6WZaffgKu;~)3h)lN2iN=W z2+jW|JrC%at^QE_8uPOPH(!X4BEMDp@h13D-1SoO_3`Vt8z0O0^$0goB>x@xEY6M9 zKR@_-{A1jDD|uaSZQO(RCf^e`-${Ni2jXbl$RQ)t{7<+WuT0OM+K=n?>VKNgDLp00 zrztDr_uw6ZHLSRTVEweh+R{6OZ70Ig5L1if^Ll9&XnXKg@M6sJv{iyS}({ zJM%Ava1So88b)>8)j&k=gKe~@v3P%a`r}3u@iL5mJZ?4>pN=oay`PB(XONW)aASaY0oHpOcMTH%o&G1dcd&RJ z#`B@`L6HCRz+vLG$T!5T;o^*rKQwdWggJsy&AdyM!vdI~rn zK>5crR(vzw0{7w*80XixHD2=X$S+c#Ag;&f8tuXLyxxP`ZpojdCkb~?6xYus3OhFf z-#D#F;wQ+r#m&j$&+&okQ^e;o{wcV3s(2i}8uv^W*XQj&wdY%U=>JE1@J4Cmg`iJm zoaRi)%Rdbx1h?^M*4qrXW~c5C!;LxOImwT~U310t{<{iy<0r^(*Z%pEKgMx&0XG+k zFDHKw_bwLyjC|fIY}XIsam>S~xNDhsB;Fpk@y+yqjaw@u|A_oN^_AkI$Zy2GtHkS& zKZUz&@s0RH&EtBX%28Fu@BLBo$H|A{o}a|CvEJsIUoEcBnFDbnTKq%uvvJoN@oxAQ z+`Tq+&oSIyCq9^blJ=|@*Zq>AnvCDGL0mtdDTLcG;`;nh7B@GF>wUa6?#1=EwKTs) z@--OGINTj8UOBr=+z+^Czqme!oYMTCsXbkFj-LbK`kYV__u}u;NPlDXLy`~0E!=ff zd_Mbe1n$9G;IlPv8VIjg#W%=zoQKah-?vYsmK6rzHQ9 z{^IIq#P$5Gg&PUtt>|xwTLB)1dvJaK@)d5Lm7bOKxN$H3AHH1uFUjx5V{!Xj>YkI@ ze_nhK+w~7_TofNg&lB8@zayWyrp%A~vgB)VohgQUu8Pm$zF7~q{wMwe{he{+hWOX` zaP7xC;@{(5T(>s{w{J>MBgT1L`~MaHo1XjHgBRxgTBcgET}HCx0%6vMp_=)tSw zo=4&pS#JmJc_KcZ`5BJ8pNsckJTtW)kDzBQZoQQJEAj`_Ux^n^BQKoS{x{-!ULe<9&SZ_M#2ISjbFRsr^g>gH(PfhXQv@*^;+EYh- zCfjulch?oy``SzGH^qyy-aMvkmsJmEyUOEs1MwnhWd-eUPh)X?ei*FYMEq;|=iugN z;@Rlmh+ECY{~~`<^DV{oI(7&5;!g}&GS}mHYbANTe|>@*ZN&SMZ-Kk<#dr_g)mHK? z@L`${aNo!E{^K0)M1Cc1wv!(HKA`X8U;p!YNAY9i4{E-X`0w~N+~_QRDM(&;jC;F? z>-*(w^`+m{Rs0A&zK>0O>ovNGuf~0E^L_5YH?m(^;_e=j55@=Kwk1BAo>{o7mv}Ve z^gSl|=cl*0URQm$MZbrOzo5r=8T5M}@vig~!_6ufm(u@1BiSw+ zUyj$t&2f^a-e`ea_!{zG;I8qK*YjdFZcGrLMt&P^B1d#oOZ66v^xHGZ6RStH@8&yzhW?mJgVqWw?8qcuDfxwFmEm zpTN!Ol6OA7@Gp ziR(DO(SBTy=Q+4*rSy!VXEko)!||<}UnTh$Y2}5}>Z`@8;7PdUyI?wtUT2KYWZqnB z#XCri@jmXx7viNgzfSV%b#YICcgC$>Qs;-@#`@Gg6So6A8aFqj&hNs#0iJ-nV^Zgn zaM!P?J#90Np8zj{TN@?cl6k18dE7ZH{YzuqwMp_<@NT%VS-cxQMEh}lKQ$RQw@O~` z*HO3!pU-)*QG0%q{HOSzxV>HcJ=Xh=_TWRwzr@YoC9n72Jk4c(yno>IRKVRk#Utry zj~lzit8qS##?8IrdVbB+9=tF8tF&jIZsJ+!`9^z=OJ1+5 zi*VzFcu9KJY7d^C`QM{GrzF3R{5jlpTKoZ?tbRuP4W7n15q$IDN)TU9|NFT2FY)^1 zLvZt)xZdv?;%>Yy+uIlSoR@q@d>Zb$D6T&b8LjzC;`4%K#oKY~y7(4)4r$Ln;?C~& zFMs21T;KoR!_EImJ~*x9(>Whx`R2h&6xZ{m0B+n8*Y_7?HIM6cqaNe%jcjL|<{mUfWi&tlVEy7(%jGz2^+=E}ncjI=laI{ZbJ3;`)424)^>g{f*eJhPdmoc%F1JptiXAT>K-(*#~#y1Ms1^ z@j~*Y@JZT(2XkC4!L65)|B(DT?Z+$QdvOo`7vn#R+i#>N1;4HSPF&CTm$)~;v$Wwn zHJmpB&Qg~3euP`N9#_?I8~=}dNA)1-DT)uly|_MC%+`Dw$?NwER^UcDaeXe`sCitU z4-eq3V9D!!?Fw$C7eB`No{XEmH*U_N?{|XQ$~<`S_Kg31+*dn$IpikbHgI z$|3Gt9{o!%+=G8l&tT2xmV6@lY3lEbKS(Pt{G^^w{4V)j>iNaDlRt^O3W@7=^_J%G zdGr|VWFCydk{^#3#ohRKcum|@MDi=}4!8%``@)y%F3Hy=KNUBMis$8gS*HCTiB}}Q z1GjLU=aaYx*XO8zwWpZ$l;e0v>+ER%^|_SzX7)=_-1Uk0RMuMo_u&8G_0&Touh;E% z+EZ41F8!9~%ZclMuQXcomBp9QKTq?x-d|SX-l~$<`PrsD)x_^JpMR>?5ZC$n3wPuC z^FnvDzoz6fGydneQAhkFocPN0HbtSL!Qx>;z{XTUK&DWE>e!kRHy}o!H z>+OTP8i+r^$EY_H&&52<(4I!(S;;TME&LMOyI%86CEt_tYZq=e7ypv{DcsdcT=(Nm z+=J_V;R)_-EqVRELptXTm+$!Wv=z^vTLzjR_x2Ij>p&Ux2=P~pvzB^)@jA>yJKQxu zTt6@Ek9+X-TZ1GY&3HVxYl!&g%-b35A13a6T<>50#Xa~c z@=vvYq~veonVbX9e_nqh9zst6+{VY@m2uY?$(O^MY7c&${_eOrUh<`wpFy~Lf_OAN z<2CP2-9Jb3_!Ih9sZW-?e(n^5dvSfPK7iZPB(K+JFK$j3KgM`caM!n~{X^$L)HiR& z4DqhytKb$Mt;Y}Unko6`J>T|?(-u}Y9xN{lzFZXeCuH^MOC!_O$ ziEsRFJce<)aC5%oL+Pobd3+|`3wM1ldFL?mFW+e%kHXh!eu3o6u^;!~)*^AeULC{D z#p2cIxr)2-a6B0|q9orNPwRYu>EB*_YdU%1ecW6w`Dn&d1~*oT*TAc3{zvgR=B+XA zS}m^UV^`e5^?E)C_uwPx8L$1((leUzFVZ}&`*9O)tdV>^^80XCfS<$70Dpj60sb0y z2Y6xUfcMR(9pEPJ3Gi097ymq+473MsZjkZo&+~kxJu%|?JTMivHi`FOdpBqv*W>39 zZf}+u)3v0MB+p4E9U@Xdo4{}nHU+j}IhKVSE$=J$#_ zfAlZSadV$|ajySixcg7>$K;2r9}xd8oxCswcO4Ob#c{hpJzjhSJ!^0q{{{a8w~kAG zF7tLAH_nUe@%%{hxPA_nt2^^^LGt>(qXO=_B(9$)e}-F^#q~Vuh}(D}&X>Ws=ZfU@ zd~|F6s(1wX`MB%4xbtlS|FRx;-R*Lv?JTGp%71!%_CESgl zCf`VV-bsEg-c5UqMt)Hrj@x)5`K9VXl3$5$!p$_|WAKBx2X_v8|8fR*rIoyHuU9>t zxc*%66YUQc|BvG#lk))Mn;$cSxc(ewUfh*MyfXPuaVx9%GRD&mH{KJUMScKoXA{q# zPQJj6dvb}FrsoIk$u0f?z7cofL-E7fk1xjm#m)Dn=LjBT$@aSPh_Ax);vQVzuhqm2 z-#1>Jr5(pxYuw5&UV-agIBpjZzes)@?k*zU6km#aKN4@ky#0=wAE)+<+FzU=^7n9~ zr1+&^!@s=2y&>X9g^hx}WF9CuY1#$g(hCaBblDM9C zqj9UUxct-k@830#4`Dvn<7QP!?qj`ixKT}fMS6MRB5v0a|B`u5!d>;nvjs^$jdSAr zkN*bZdcVkzyBmt@_XWz~Mq_dPe5M|5<9hvXhr60eej4Ks$36ITd<1TNCiz16Ox%r! zGX9^nrjSpohDZsYoM zT=j5Q8|l%{6FTFbw&GJ+?;!1OFP_3YOjrM0T<76O-0mR$|K3k=cSrGW(n`?@+~_Jk z6~B(#Vd8fg&olL&;sw%3Pv$e+#4?W66xd%!f;nV z@x(0RgVcwKbNCzMaTEWV^)AFc_}^^TI@}&1Jv#sUw1>Y0bpt*B!;R6B*UuT=;TC?1 z`S~b<^J}c+n=;V)xIIZ+kJ}FFlf_>!&H?IE#b+^|iMTmUd>0;t8`H&a;lHYXE1r}7 z1GsC3_)GFv)xQ(phCjl+xL%jCej(dy&rF>!j9atB2eaNvxYzfCH_mbiZ-#s3iZ5in z-Lz+(cu{-^ZsTn;$_q1aW2xjL*e^fg?q%XR@ZYp&g}8nn^Do@9Qv6HS`xrM@i4Vau z_2s<452cqE^5gaz$;Xi|i@Vo~|Blze&2{3%nV$}t|3$nv`Tn@EUfda;e;KcN+&NtR z%L2`Bko+|IZQPC#k0ZZNdwvzy^XL|CZ4%e#heG{09&mZpFzVsPW|6)0N8lb@d!jVI zMe@46`*An^Ipe>Adt)V^9e=Jp+r)?AIs3~v-M@*iWjy6^bGvvMyo2WPrt}QJy*nhY z*OTd*|3h4#i&o**9&!CVa1Ubt)i2gH-{pX@7#a z9{-PV`>ePgCwT|R_Ih#sTs;&w&Pjd^{Y`KWuKm3=e?juS$xp-Gcsu+@+>4K4-ge{G zCF#-qbwPWsi0k*2p5XT1;tS~caG-3Lc}=`M>kY+i+<6=5U)tivb;;}a2jgyhEcrQ_ z_ex&R#~9p$PbGg8xBiiQGM=pdKk@N+j!5xK9edb+5;6i>tltG^PD#;4(~*Wwv;dvP;Gd>r|0xCakqoQH8EsIfn(*Lg2)rx72O zQHmbpt}NnB>CZY?=E05Y^Wi7z?@9hX$89_H?BW;c>4#f6#P8yx)qOt@>nu7CGjUfg z@g?-zxF@%G4}7QQ^N5#Yza(gXe(_A?@8j+d#f#uMzLM>=3y8f zX8--7gycKn)o=^{g5#<^?kz3(bL5BNo-*Py@cG*FiTE%0THF;PuE&E%y{vfa^zsGg zaCaqf=d|@N_i&@KxPDHO!P!9HcDeC>oJWOmqpIXfvt4y?vxfK)`onNnP4NZzXxxMA z^?$zh)K1;=oA%TZ*YD$;!rk~M91jn*zpmul1MpyZ+@YZhaJ#8dkoSs{_(N4S^ zp3eD3hVOVY+l%+b%ix~R#dRKpaJ&)H7F?at!PAN|WuxT}l!DtsUA z!S~=daCcY9x5YD#l<|A<%Xl%|(@pZn@P@eEUA!gtkxsbLOI(l75x5(Vr)M#4^_G0I z^zuRs?&&AKlIzkz%?}WNK+hF*-+$=gEIGMeJ;L3C#ix_cJW96L^_94O?iYfaUyFBU zKYoteL&Oi!GYWSP72km`(Vk)Ahwaf_w3H_)oYwUh-@3{kSm!XTP7vJ(IAKTFRB?Vl&E z=ld?)nlFAjSO$7Vd!ocoW)Z)sJ*&mbvRy%AWFD*lFN)iEN%FOEFWwOEikm-6zvf5d zZd~(AwFf`M_Qv3zHPTZF--jFP#Q)~}x~@GN#1k3MbM+YU%gl2Q=Z%%`c=l`(|Azi@ zxVc4qAw4GU#b@E2aCfZa_t8H@dwvtw@yt^9{RdpmqWAY0+`_xje;D`RdLR5BZu~Ai zM;ZTX-1~>P&VP324T%3aWT*IR`b(?tN}aEdTeyz16Yj<<((@(m!7JmFabvgi>vh}4 zy||8Z7w+04`A_Ki3%Bs^@O#?7SMttn(ZA$y-q`xK*Ny9Q*QdC-Px95tchx+e0iS@| zcy4?p?!leMTK}?3`#sX155J0A_%l33eZS;8;YFMe^8-Ggv3%oP##&x}e za2wbC^)2qjwf`sFbwK*ncjH!opV$5X|4;h^JhPkQ@L=lxk8vx&>)>{PcgDQ|J`{Jw zrS6}j{Q>@q_TwKKvK+)cxbFAsxHrIG;l?3ZujcbiWP1a=JZ=VfbKDB>FK{;=9c1{I ziQ01{^?H|UPk`^x9$b48wC7mro@DI_@QjleI;TX-F| zYdCHP`{~34vC0?5z4{qVv@PD))r^X1LCgZp9^yJIpo&fKNd+|Hu z2dJNG?3db%e=P30DDHgh=3i#xHeL;n#l3iQ{2XpxlAgNwecT=3>8Hzfd2oF{SPFMt zmL7d>uZ9~}#6#$BikrATS9jL@HOcoT-w$`=OYyO|>$>El@wvDc*XN>j+V7RT{`abX zX#YRrdOTmkJprDiJ^z#ZV%D32+qgbo75rA_&5P@KR}pvJke-h8)W_YpelFA&xA6hw zztEnW(xd%ja4W!9Y7ef@&%fbjqV(wakK%5;2J5|nd+>am0MBsmztYo}d`{;Lsqgx1 z-NMONz`eJ{>yvMXn|H*Wk0bm`1n$A_;9sfVmApP*EyBIH{(h2exS1q*eg6ClHqOulMUuaqB;EeXeVudAw^{SwVZ;cp`ay?(Kn_0X`OY2lx`) zgX?~b(f+5Y`#su^YyO({mmAK-4Ii9cD4d@;@AI?g({865vNw;9GD z+{`3B>T|RQFGqfp_Tbuo8h2%uo*&33;~u;Mo^_6l-^e0)z0c;wP5dwBxfJfoF8Ras z)X<(B;!`uo3oURvFV24Kr~UcG_4#uQZsDuwpQZgDN?z{^%e4p3o>9JFyY~2gv5d1! zWW9&A=VS3Ci5r*Hi;M5WALAZe-!G?~%k`mz)pG+z*pTlG`- zcyTwLo}MSTjpxME&*OT6_rTxBT@9rFJ-jk*;*0Q>xEI&^PJiubC_R(OyKxVm8DFVA z_(uFU+-@X2>L+olvABL8{FZuCap!RIFRyXqv(%n_zRZIi;Kgv$_lu95MUT&FxY1nv zi2qf?Xo-6Ryf^M?kvcyLw_B$6Ik>x(_-Xoo#;w+=eJ5_VN$qEFSKHKn8#mge_IJ29 z!1I33ane3@z8r3Up4yw>?hdKl!mW;}eKc-%O6}j{uFk1_18#Il?Qys_z^~(;uBr1c zal2b;e}92&ue*C{FNa%Usl6F)_DJnt;I5wHS=o={G>_|bV2SqQ!Q^-0ZhSp{R(mYz z&yFW*51tNxp*?sJJjX(ge_Wp*O5k2RH~A{K(M#5=_mRf93)koT&bWowq~}ZA#tY$Y z+=FMp7vf$#7rp^EddqmS;0JJ5fM3DQ0Dp{I0iMOV(D|Ob+yPz;w*$N;?g{YrxHrHD z;6}KNe+18^6L5Egcxkq4rS{|8evK{KAK-D?|3&Kg`5Siy_!Ha=@GOgE9;^T_hP!bc zPgUFw@D{iy!296d03VGTeP#UGzW{gPdYu1=oA}q9V6nKTpY%-R`RV{}4HUn{{Cm|0 zi{Ide`k(p;@lEt+j*|ItjS|=Mw*cWEzm0HXwDjn6MJL?E^?VtKTezN& zZrqLMr~iB0#`QUN4erHtoI7#j8yU|>^qjy=JP&>ow{U%ac!9g|yyUYk;rfH?_)Fj( zyfFECxEIfXcf*Y_GESY(uW%Q>nf!F^!9($%G(T2)T=;I>#4F>MaW`HZzmI$Hfp|LS z11R71#2hF6yYND|F6jJ#OjO^*+XJyc+ooKgc|IaXqgK;>JYj(fwExcj2SxX^q?XRlGCqo+Lfa z?cTo()t<@XRq?sFg?Gbaa1XBg@hEOikskGA&Evi4$-IpHK2`GSWpEGPihMKNHBIv6 z@Go!+*W+y>ZsXO-uf)B$-Z!`7#&qddKZd*TeDqw#&4Bz<+`@G{S(bBN2lz+0H=w5u zZhV`1JY8{DfRDk=0RJAh0(>p*4)EQ$jqCW&;9mR_=HUkJnvr^(&u}xqvpOe;?|ioc z{A1i5;3jV4U0LtvxbdB=w=CWlH}Q)2H`@=SOg3mgMDA zDZ{vqTe!a8e2cqreQqhRQs%*f>v}8U)@p0ir9$e@Dr1pHDy62wu1bDD5<9Fkg7|(~e2Oo-8#mxoM-x2SI+W|fa zHx{NI&noS~b-NDW9^Co%pMUuWw-!myRG!CP@lN;%+*m4kJ>Hh%F1#xFINZY9d?di%-Dwk$5@HpA&z?IGd~hda&dl;B(Z|i~onOSI;1xz`Vubu1w-R$Y00ZxZc-ZYENd#mn0v& zj_Uxf`@InE!N-uVj++ic;ihJG@pGwai+{z|ioO!PC3;QL9_=xoK z1@mxMUU8lOhqxPG$^4k>IiB-Nekr~H_k0-8qxk~jAL#sSkRB7)^XOCD!kv!|{Yy*S zC@4MS@krc_=f$VuHm?2Wa1XA>XO0-gQ%L%w=xK_3aqU@z8-*objC`W@;F_=StMprV zQSuFO8`t&D!ObGluRY1ygX{QfIN!MOU01!hj%OzBa;5IMh?}_HUlP@Qe-VSTTxb8j z(4OMr-!cAR=S1*d&+)w+Z-sGpDan6=SJQkM@lcM#&vB!IxE{A(<7OrCA2P~{r{G@S zUku?a-wPX2xVu(rUx&MDi|h7!)J^eS^!%;;cz66EZq}202p;Tw@a3BaH?HF;h`Z{i z9?z$^2R}tmOWbZC`33kFxYUuh?Jr)mw{)(vx9}%!AQJyiu^cPyn|&i0k&2#m$c5=~?e*n(rk3DaTJQ-0mX& z2kRZH`L5!(=~<}WT|94E`GO6&yQg^m9O8Sme=z;@oY9`I#q~KVQG14n|4RNPZVnUA z#W=IZ%KTV(Z@e(>#q;a=f*T{HXDRunxE6j4#)|9v&m-!d+9uGvbljgEz##)t&%fi5pX;N1t)@5{>IF5h2F<1Eg%ll)5)+=D;Cd#cZsyt6w0G6Hwc6K{{t!@YPI z9)p|nC0`Z)Q+x2C_yyefUh)(02b#w#;^}{r?e*ZF;3aWmf%NEgxGrwtdK`As9$b4y zYtKUI=|=xz?ZIm>|1r27CHeX9$rr@oX0*8eoZCORXRUZN{VBM6op@+Qd7;&Iws*65 z0tdv8xV=;Sd#<04aML5cfb%G52gk{N@on_v!R>f)z28+)KPmo(akf)GC0;J0yb!7V z7sRV)5TAs*u8U`(XAN%R`un>M;%=|x^?1028~=zGr6<_=K-G6YHF16Zsg7H?o_FE6 z7cb5He2cqoNWb0(*J~b+W4#Hu=ceSRbN=4Nty|)i$ft2`K)!L>xU)I_d~U*xmy(}G{;1~hHTYHB zdL{YAc#`(K7I!Wq{w1SxL-%hl{w4kaZoZNHaJ-D>@fmDyP274b`3Srj?tLeIhn~*r zzQ35vS#m zskatyCpAWQ?Z@?bZ!m7Ok-T1)#^V;Q=h1h#7w=C0LfmXCJt6pyxEpWJ_Wr6p?If?q z=RVwSFMfcYbK3K{xV}I7SMwdjOVa;b`#Xw%Lr?mBGS5~g@uyth3aNJ$UrPQ{+~_8r zMdulJ^$^$hp?z^1uT0Mb+>3W%y>oE0m-Iy7Yj97vxSpqnwZD(}OrDDpwC79m%)>vp zYoK^#eU8Gduf=0B$v}fVGC!^{;<~*#adW(QSGKDRZsUVlZyoJ%OFsC0`GT&vxllY> z*cgEui^NCqAT=9z+v5N4{RQ{pztFP}w||uU7|xe7xND8Lp6}OjV}0uW7r2GjV!vnD z&w2W*#eC_g)lVMb8_}UlP~n z*nEd%yIhyWotS^Ah#ObLx26?uj(e_&Hy}S)d#;O*50)2Z;P%bb`HkA2D87{OAJP23 zsq@L2za?Ic_2zPJK)&PL#H-^WxEl{<{FQP0uJq`2X(;ZxCmuu3Hr$Krarhj!k|aNp zeDDz&j~8!?XUENC$@j)f;T~MCGxfCx-%7q6Zr_)l>C9WW_WvgyMSigM;QD*6CgHBf zlJ8E>9NhgxT(3XT+Jo1pe=BZ2mAw9aRflo=x%g}Hf2qF^{{sITcfAyEj3?u6T(6U9 zkFwuiNj`<~XH$P8o`@Gwe=DxvFDs9G--%~WCo8Ul8|j+)L67rJxP{AC8AdPMgNHNz z{&u)nWabX zgD%{SN0P6k`7DyJfY--O{3+Lgmg+er&tsI)2e)uN{>S3(fc!$-#`(5@@hk4Z_3sOa z!_C~y{3G1Vb>J`D$SeLZjcmXj^$*4M{oOmL34EwhlZkqoOYi|M{XP@BSq^hQt?u6~eeeP9Y^t&qVaY4E2)p0=vFGBq8<$@aypn!-FVPp}NL0rZw z=>4DboM$`lTU8l9`p0ib{m*&MbDr%y=Q+>v8vfTxeSWLp2Qvn@>&=&1e)>Q;{#pB9 z7kpoEyAS&vg72L-{C_R_{E)@x4Q}5p|0Th9Z!!3ng#R5DzhLnH@n}PMpWu^4gKvnv z`jp`Nw;6mv%JoIThwe1^LxMl~kEA}!2DknECc$?V{u03t6#i<#Co8d>J;C<`|5eHN zHw0gOx#9nwXPR=oTkzyR8{B>`>4Spr{uhIzIQ;Lgt(@;QxQ#>po8|vLgWEX#6W=4{ zeVxJW`MPHdzW;iIzw-%3{__Rj`)PxJO8V^ui~p>_?L4w7_~aW6ZtdqaD_`($k@osA z!INJ!{0AcccLhKAC4>K+l=p8e{>uh`o$!CnADeRR(ueHvZ$$d}jNtqC8vFwyXG8G4 zUo-f71^)pn=hqFsA@biMc=DSDx8w99f*%OJEpi@xztLy)&4%B`{hliL&~F*s&Znb- z?+Py6Bsnkmp5S&pRT4aTi;?qA$#+Na1HtV)`2AM?eTM&KQm$XL{BMo%`vjl-9|pfq z?ELQwp1i~0e=KtT+{)QE_-|cj6#A^-d%tIJ{N#WCD){d28{Eb#o`i-VKlVWIqrzVh zJoy9Rmwdlj@YO#wxQ&C|YUK!SzcW>}_tepErj`W{DxLs$BS^NQm!!6{0 z=dGN-GWZJxf0^LPCk?(MdR`NJ_wNk;SSigIe!M{uJ9}#@=^9KJ~k^ifLuL^Gc z)&C{= ztAYZI(f@uc=c^3=iv|B%E9WT&zbbN`3d2i!CQlW9 z!OsZ3D)?=JzgX~n!L9#RxBNF6Ikw+^hv2(kZScoSd+iCn_jH3>yZSCGU+@Q|AAZK- zLx$g;Q+?D2js6E;XK?#n-4lZEJYC^ij=ou`JZk0hXnsg z!B+*pAo$y?9KnBC@b?P7J8a~%1^;`&lj8<|y5Px&Ou2>xxBkzQ1m6|>=Y_u@_~Z#A z$L`lpTl{$jw|ZU?d{6MBQqMmq`2MKjxAXhkEj}jlrNREv;%5wQ`~O$MG3I`Cv%zh7 z3xe+o{zhq+%Ysk7!0;EPpL~ztd*5vEPfK~@e-S8y@I$*ACag4cw9QShN@BmX_3{}sWL8G~Cp^It4JYw$6V^J{`n3ce)xp9;Pw z__*NzD)@ommjoaCbEE&{oRR;KwBv@wFBtqaqUTQwzQ15_>j&O%`4l;I9{aRq)41eg03uce{rFKGEl&1fRTS@K=hQ zultzMXJ2qT&lUyW+cEra5c%7JPrl0Fsf_ENvU0x7;BOK6zbE+aw;TKkqR$ts{O>UM z-C}6I?m*h>euF#Xo3p zd!J(XFQvWy%;0a7c3&2J?=KAg|0L$$D=fd@uNM5L1YiBA;otrWL-+&B|1pE#|5$_n zi{SfzYj9iMulcyqC;2;r+kMPg!FL}t_#Kk(U4kD7ZsqI=KJ} zeDt^nq+JAmjqslqeD&Xq9BY44!FL6}Rm$}PR?fd0e%p?37JNufKw$+PqLk}bt(@Bo{yCBJfW>b&xLv8skTjCDp#qVf^}JNVV^j$Qe@Q zx+MIQioY!QlES-!uPXdif;Sa@593FYRi)?82!8%6P5s;c_GZDC6#n~y->&eFF@7|8 zx2m_lXZ-qP?=^8heB|GodOrA&!AGROJxTDPKa25a3x2DfwPu{8;keRDXDl;QN1J`pK^F|A64TA2#@q;6KCo(d0n%KO^{Ch5r|o{P!?^ zeX^m(`+@MUe$dFb?eclSC!exnB;R0q?qgu+o{$_%eo4u{QSkf!%*e6*_JrWStoRoh zzdmUyeKv&uzbpP%3;rt#|9-)LRpCD?_`M2$3*#7fNAKivHNVyr?_I+GYl{B?#;;HI z?=kIokJR%Q1)mgrSMaC)1Jw`s)wp<`;P)&1EaOL#{Tqz@>%>6Z&iJupL$ymq@TS7M zB7a-e=l2M{`@2T|4L2A?e?ssBrO&Sk{-DC&DR}YaXr10b)Wjv~J z^d!MQt?+LW{6U4^EcinTzaV(>KB-U9^JR=5OCD7H^Ob_{sqys_j6Wh-J$}hZkb&_V zjH7*(oq4Co*;eiHVZnD5{sqDBR`_H8$&9Z(>4$baK2z`;L{B@u#st4d$+?B`qsio- zn|j!@^&tGeuKIaf@HZ$quNOH7BIhBI^D~SeNggToS=^K4ErMUC@IMgwzoGK|n8?3Q zwb$naA5wPpx_>tPXHwxqjH7);zSU=3@I6(oX_0e-%6Ccl-=z5O6#QO=zg+Ouj~Kf* zDfRPe!S7T2uNQn@;Xf<*yA}THj2}scr9K}e^4}@^|DekIr-DDE@V{dme!}Zaf3SK! z4h>3v$m)T?-z5I((-}XOd{N0cCHP+pF6_yI$hlF~XUWP}?R8b;>`A?ymvVhC<42N2 z%4_|lpA`OszcTfFw-A0q@O?FY{}{7CA$VW8Qy^$=Bjzg6}DOM(_iL-y!nvSN)+S_=gmJx8Mf~ z{~5tYl-+n6<42Pr)n0!j_^`r1D)@-PKPUJZg(v^V)K4P%*zx!z!9T6^e6HXRDtv}< z)Ti_ZYY$&4_*bZY@=C_9PgYew|F6Qoq2&C6;7x`9Hsi;Vhm=0=7yRHKO}n3$4)lQF z$%hPX_57mXk5u*W#D6vQvnTVBOv}l$7{5LlQS;^mz<*51$u(ZulbA+mU2jwbwT=etj~j>g}}POA4PCd{yCh3cjoSs5;}vl4Gjg{lM#=v^#^DdEc*4(% zob#&x|BlFci>lA}3O=die@5{03jdPG-}`8+=dpjI`aGH}seGTw__1V3$vG+fyK27s zX5qhG@h=Gf?MnWg!hfGCS6%S8D*XEx$2|4_8vSLtmb_8;57hklyNn-6ZcujP{enMP z;U5<{t4jaR3;tx){~!JDrXSv@@Mj2qAoJ!K8P_KT9}<7S;*)~!sd;u;@ZVPT@N&U7 zl%CfZKa$+7#>;Dkzp40tit+1{d(^o5Eym$jsP=u2$k|qM{#y7~KV;fvNXq*q;ZLMo z7JocqhwP83{_qWg@2h-oV*FUL`@qXs@~Rlyuq&2Yz1oHpAjI)k4SIe#Gds=^;&{AhBw(*N^}A4{I4%KOMiP`w>Xh86xa#^G{5d00Q9xjNSRVC-eg6}H)ir{++e~rlBQ}TaA@DZiYn+2ax`1=HZn;I7%7yNe= z{ttq`UEz=ZN>gudQu=(g;P)#0xs0PepK~X-;QqImhWuvXe}|I)V!?MmWBSi^(jPVi zU;Tu^A1UMgRf6v;Ij^()e`)wJeE8oR1wW(e=N*h+pS(fYy^jn33l;wt7(bfaqx#!p zpcvV)Hz@q+j2}xTm7Gz*?^E^mt%9#AKk!z@VSj#J?9!u6xvIjyujKqEE9c{JJ$%35 zd!IG9y|?vqj2}tvQS-%H1%HFW-z)etl>Pav$iG+l`Ntk*?A4x<^R(4w-@YU}%_=w0~Wc*n2yQ=?}7>7Mq z^KV!9?^pHny@G#8;XlPV`kS)b_X)nI@IMwg2TIPT8ArV3&tmr` zd{^Ob7JN_Pzc2W{!au?|)-`JX=(B?F$$V$q{hvh6fs%6_0_og;zAY~AGZ@FZUbV|} zgnv)jy$Qi5UvK1WNPn9Y{7s7g7RC`*`EbntV#bdp=fw^j6FD2gpL{UZ^FImy?TY_L zh5tS^UVe-5>yrnSKlZ+0(pMPN->#xTfee8U33>VnRzuK4nGbH+dli)+D zU!7tcr4;>bJY7}QSeEHmqh+Osy{SE{{N@O`>TY1Px1e-@L#9&`BmY+SMk4t zajg54eY;=qRfRtwcvInD5PVnRkGh`f6Z59Rzlw3}&k267!heJD>ysN^Y5JRu zgS}JmA%%ZX@DYW7M)18~Hgc9EKAPxh$K6won)*Lb96?{qIZxDP{;r9yORQMkWzN_%h2!4;kkK%$J*}Xl54>Nu&*;e&1CH(g)JN9DX zKd=0>?_~T)a=)sdA7vctV%4AjyYL?<{&xxggQ`A1A^1ZI|98O?IoDzP)iZHHgT~Qy z3jb!t;fE;uw#@jE|4hLjRQQPC4=H?>@ngyVRCaik z@gvEn)p&m;)ZX9{MDc21O9}|2?;V%_@MB(2h z_@u&rj&YoKI%@L0Tjs%cTU^e2oD=--1W%qMdWvKB6gadrpC%&b+l2oGg6}`h@MC+0 z|E)0&|5NqnZN`r!L&^^Su;5FoKJOEJSn+?Dag1NpF8@dHA=Q7Lfdlh2?uHfq0>MWV zeh1@-H>z=TRrvRmKm1z3_mw_B$M~^iMCt!_!R@}eR9o@^#;;HARqgoqg6}K*2~RZr z=YEAhNALrMpBMZ=g_jvWk{l?1=|3}mEcst*y!@K*-+L+U&+ij_UvTS>{e#7ehTq~( zd6MZ@leY^l`!c63{?Zt~)#9r$e%0cY82=%QZ^Za-S$s3bKVb1@jQ^9xJ2CzgT!^D_ zF{Jv*3mHF_yi?WNs_-YWe>x-$|6dqCn%t}AqhA*}|69rVOW{9I{q3vpkOJwms{FB= z7{@wY$$u&1$C7s``L7cE-3tFvk-w|t|E|d2`;e)hVJX)GB4=5R>qk9>^gNdQo{~Q* z_}yyWzs&g2kpLt3BmtR;cJ5bk-}du_3H}C!|GnV*3V$jtGCU!9MDl5cKVR@0j>P%COz<-b-xd5JrT@W+&#Kl( z@Lh$!Oz?Z89nXlIZNbm{tI@}<2VX1rh|=e$1)o&-y9HlT_+JaYs__JOY{-{ z{B?{UOAbCS`%{Adr0@@^`q>wJQsIBeIQAKpU-wzT&nx^(f-foj>CYzpk0!S(d|2>R zg^vrqq3{cWHx<6Z__5^EYTnrr{%ytoor3Qw{B?rgt?(Ze{2qn>H^JYa@ZS-9PvP%p z{Q6|+)n?wb{pz0>KbGu^Uv2TD!&Gm_lG~Mk{`HI_9;M`*7QCtO3nJ&GO3o_dM{$41 z=(GPc({byJBd(?5E#EEtZ&Ky@agl#Ut)uS~d{W_m!uTVS^D-}3d;1B&S5-fJy18kzWd*TPb&F;W92LS8P7HO z?kaqlam0%bjGg&G(I*vr_hSbCUct8opOkYf-zE5K1>aY4_85OevZwNWpWyonf5P*O zo(Bp)FZda$he^rz+XUZM_-_k-pTa*ccp`q|kjQy@!Q{KA+VSO#UzhAY!i=wN;lIi_ z_5~hm@COCITkw07pZpUde@OCuw(!42@Kxzohye1x`vu?qdeiQ=h=2Zo<$sF79}xb3 zXPgdM8hxxkf9#|w*X^p`evRPCJ?7r1h)JF$_`a%#q}nJ{8%e8M@6@)^LMPqsB;~E< zTBEX)lp8?#udQ?|y^H^<)hp?C@YhZ!2#3t^r=(hr(J!u zYt?3}vA)=-&oypqm6}cHUu`^}s&T#6Y@_V0PO_42H%s-(%9V6yg>;X8-#k{*z*+>L zfH4PvwJ?*kUUB-Q+<@zwrD~zj*=eRz?RMJg6h;#R)#{zJwO%TxMQ)D5h~NS1ShBv= zi4ucJArPmKY^w1L&xAp;I;5>uqt%Y`98D~kWw(75{dNuAuU!~(#dpwn7Mk!-R_0o% zvQln#SGL;KWUJJ^(oWhNwe?Qhcdgl0X)9fBG%l1{)if!$cAA~W$#Si^k+veu$CI`8 zwNi5dLV~}l<*lX0=1zicz?AjPT62XmN^YSJ+}`M}udj@ZoSl+C&rOevBumBfmuF`$ z%&eS!@$$^d?1lNIm4zw1M@0WwdGW%+%7u$o5iU`^w0V+x-RQ)~$&Ia2Inrz7@T{YU zXB|5{>v;04_0toDiScwK{AbWJA%wpTI-agYxS;E2CT=RFqw=3Mi&k=ZE92om%bTae z_<9_Q#a?K zI*Z|ZU%}y)B7NNqKVX{DIBFWZm*6LTBIjD6?YZ1^aZ`Of1hILGO>)m=e z#EjyOB~`yEleVYEKb`8HAq$b}o*w;LYX_pkA1oyV(hO2=TrGE6LHZ03&L+Ah()P|) zSPRv1q*$tcSIdzy<|kUT-D%b8)xrsCU8-38WsN#`YMpifNRH0HmDXy7ThJhdA`3|( z!ZND;;(Ck?pCI&j;X+!^C(LX&8?8=SSx7q@jY>OaT7=av%(u^Ome6COyqH4&$$^(k zRfVS4vaG`=s36C4OEP?778TP|^QBJ9(>f^M^9$)+g-E~4^>Sltt5GMuB%kF{TAgZD z+dZiB%NPSrBxc9Le7)AGl{Ray2+B-2T%dB=ZeMJ6YK?kOfkdk`YDg8!0bZ*V(2^rD zzFkc_Ygcm!z~?SbEu1?&v6)^?Hw{O&Oq3st1(fhby5puhqcRBHVbbwV!dWd`tf%d1 za(dxyW~GOmi;d2F{d`(#`T|&Tsngi1l?%fs=IfpDF>2jLE2ysw+hwS7dcyMc&Q|0? zYG3Vy%}Djyk*8GJtQOd(pG)g$t5(({$nsEw!8LSMWbx_7RysQ08)@ zezh>wY1GgZP8~S= zy3NA$(&fVRW}}_cH-&?=16NN8q#x)PYFl|_6d|PLt)q+Gtu-u-GIb?P=vM7?AQy`o z>z|#GG=OOqX18C#gnlXAEOlyE)1^{pqrd79b|_}_n@*`#ZyzG_1+qg+FrIha@ zHHc!?o(gth)XJr1|5Bh&V@95#g;;_A+f&@|3HVAWHGSGTUZ_*NpjWF7p9sVr?1ZXBACOVuN9Xp2&CvyAm%>DDQlLN&9D>Ar6}hGh?nGh1q7zUm=g znn3gCnZJJW;kNY8KpL&l?V!Y5d88KXcK>8ruigpj>9u}UJ$#}=m3X|+N)f8+nW-=x zqKgS9e7TLZd?l?cnGQ5aauY)x(T1(HcBg^y+mn4B@touAmknT{?pUW@w3%)VU|q}| z)6%T2dZ@qk6eL{jzN6GuA~f^fK&e?OlP@{CvTpjL*t`PrHfkEU^;2TMq}|r{+lO7PT3`_0qVDuTFpBUhKBuWrUjGqPM^R|dg(T- zd82WqaH$(CW7m{bHxofytoN zkr$K8aw%P}g;PuqrgoYJj$g>Mw%I6O$!Uj>ht+W%kXK)}-?L|L%3>k--!zmdE^Q$+ z0YS5v4c8F6cli4F7>|0(hj3o>yR3ghXvDydLWEfFf5c^C5wDI#Mt#`bLYS|tG8PJl zEbUrgtr3|R$4DjZ*=QdmK(FB?q^(O+EpJcflIuFADZ5+M;!c9 z@xTU`%DnovQXQf=X?;Xp>v9wEqZCUO>=U8e27L`fn94JEabfEArArs5i#)ol+zi#J zUaGvj+wM&B62~WYWB^qLO67Ry2R0vQg|eggXkV>G*;HC^ z{i77;YOS>F+YCRhDyU2gv{+$_^vEvVYIb&9E|iqS#>FEf!=luVc<4uu{3r)3x~@>L z3HHynP&?g3(duhaEfFuR@huaZBd^-<2?TJKu$@*T53E%2)tjO4hMSXG^IN}cBt18ueCa?Z#Ei%nwI_Wjc7hT`y2x@PpX^eCjT49y3 z2O9}6v>d4Fuy%@#;7BMrdAA}$yS}qfq+q*Kl$^ithc^@5I(vqeXntz)*lj3IB;XRZ zHSGR_O-oUbMmg#t@Fq48?1ta5)1D%#69V88mddTG>C`5cV8MuWY2c!mwkXkN(fMu_ zp{Q!GI|jxwwY`ZZ&9+jnqEV$|vhNB1F z?DN|?X|~b@u9j-~934+_;+lM!((2K z+8<6K#US9k;h>AHEde=@V6kg!FTp0MW4X4r(75Uwx|Obzzd(NBR)~|?sF><^|LwkpkqJGgkT0Sl1XwrVIC&hX|sta)C{wY*0oYF5v+ALugIAu#OnEolM|am zE4IoP>o_7}<-20gNTjW%v)CBTQfXZFr+cxoP-}enfKr zG#16B4=~m1Q35eOrjRa)Cvf7}9C%_2er~A^sgrNPA@_X+Ym*!lOGt-05va~ASSFF!6XBiT zR(lHKQ0RKM>9gp~qUw7PBaW)srd@BeW>ENIGc9A&*}-u3P3|x1rd>=LY5gwxqbGfR zGo0&nYMS6((!AL%nvcM+5V)Y0|6(x|ak<`Yrxk@|5IYaXe3%ntevENm+{chiyD0jK>SZVyZZft5m2&RWU7 zvlP>gLtTC$$bH5;FBy{WBf}@wYuh|PX@4k~VnYJcT}B~gJ#;pzvCmV*5GBXOCA0t* z!n6?a`YD3fjy8aCZjfUlrOn$Ktt)A324T!VOqF{K^oH=hG^->M^vrS|)f->k+rmcLJt(LPy=?7T}CUH=9lh(khZ<|sDyzxv? zEz>ZEhsk;cixaYet!AUK>2<`J+{NNld!gv04WFo%u|Lc^=IoNKME@K*`feq~oJ!)v4In=1cOqY;=j(`bb8Wa;E7oSWKr zk1fb)A&q0VKo7pN(TdJ8(|k0CK3WbYcuONU!S@GP4J?4NmbO|pixT)O!9p%VGo3U@ zp|p+C`NCGqwAMrxCS|DA66(RAt|qGGS(>HKR@Fj@)^_Nx!K#;#bvlZDjjd@Q;an76 z#X60CFFFQFXm(F*>l31uSOiU@lVCl|CqI1Z4J;zvuWBpZQ7MsMcdL0ZUa{fSH_Dn_ z$%!e$qL$mhR}S}WoDpeZ;&202&1)FanuCY*^bTx-Q!!UkE4^C7c^_K=xjf`74wjVG zH?hK0hAvkg>__ENx6V`P-1K2m@L0Zpb2t4JrK&aJis7)$RU*$Q=}M%9)Lf&q$-$*C zjFT%7Ytlw*hb=2Do+x2|brI;JiM~IRa;#`OVeaDGv`b`wg_(s5^9%FK4$jpOQNZ~M zVqmVC`m?uCpsVS_Nx}2dcAUa@<*=Jh$9n^?taJsXj5|X0xJoeo33oh!>A_%)g>Wf2y*w z9274iC8q6iI52Vq;}oltZPx8NFxM=R=|6{PoCz(HU}}nKM3Lecs4pt7?0dF|P^0B& zCM}r)r&)%|I`t+;u6iah#UF60wRduK)|qH$%!uHqo2iJr`~$MezghEESdPJYv&vHz zDzz@%7z-{s`P7>MyEy$BFqqZcf`b#K)=m*#V0mLXT*e@{K&H$nMIJ#;_<@!rS8)jp zK?zzw`&KdDUYiIjTTWqgX%)ZV-|WEQ_)({|GgZE;i_J`zg6ZLE16sAA3+2}CwkYeS zcZARuB%kHsJUst!qVkA1bRmVLW}!mzcp3kXB=ReS+??Xm60R6<4RLAUB{-o~3qsi@ z=8M9mP36gm%_EAM1ZWP+j4QZL45R0u-GgGqgtH~ORO$PbrJ_aV(jF-*wKjs*zNu*P zFqy5DaLXaEF0{88jTsAQL6(Z(SCSi-3M)Ls0__X%B*$Q{Zztj z+}KKQ>y0Xn%$6K=I9ECJQki8qqLr=yW} zxS>n;pzz|n2MzLQH;Wf{8Hsk$gw6Lxy*k@!Y~7ZYyo-ahvI(@tJH}2GQ@?SX6w40s z{i3}AnkEJ0qeHl&5ZDi-V059hb2;8lj2sv>S^^J5b&P;wLTNl=Y=ATD&jDw~RS#%9 zOt58%3%IrsBF)IbKm-(9MpN-#M<9cEdZ*3|&>*EA*a3;^-wDNWpK4m{;t&(KkPwhfi2^E1Z-pxaL8mGN_Zdg*~tJ6^@$ZVam`NZnICV*bS)qy+{vx zHOEfcrLAVT5~61wn1EY(xTsl{IBo!Q zWGTo;M{}JLZewK`K>)G8HQda_FDHqrs4(GvB1h~K@`+aH7#9se1v#5R zt|+Y!QN<;-dcr1U9-Hhn2l?Q@&R0!Py`{*;qDk! zGcL%tH~5Ud6%*27-g!*m{GU^<4qpWVMdc;Whd}>JXJ0ofdDKc!!04cenl7Qb@PHTXZU|5g#X{ z#)GM;H2lg5n|{HjCwdFcg*ye!&vse>58`!QWErZkp<0%t<-8s6a z;{-BaE5$7C2NQ(_+yP?WHS@6H6Wm&hwM{;TtWP};pIBjy$pwQ?;&SykEskQ1Vj8~y z)FU0!)Ohl#sr&O~Y8*4Ia2CEOWSsB@M^F!)4LJWw);eOfe!k&fibJG$v&l>>%jxVP zm6!=!JuxpsikGX82|rn{%_FzF2xZhObFSdX1^e0lcd5Mtxp3PNj`R9b_bZ%oiBPM= zRxGWX)02)sU8q$-xOj=oo`ZR?!8!617bEJ#aWy{X&Ma+j)cAMZcVYg$hVu(?HaNpy z@?(>kFy&*-u^sbuao9RWGQfsT(`Xn{f67YA86s9H#w0w_EN?JIveR`7$Kg+`R0va?+H)q(eL^7v)#5I>*7Eb zyOrP|q_~SQh$ERe@EF8qE#SKg5OiRB9M|bbjIdQaQCCNLz-q8ZX)x)B+p6Y8Ktv5% zxS0`84bZO|y3KMT$oF##TnabC;GfKy>!>q$O2k(VR~S{c(-}30Gm`w^hr_lm;Rp$i zvCp6L5P6%8dXy9+6f;O2Zx(DcI#@IK9?lewWe{8x7AGwBY02w-1>PH?V+@;q=~FJX z*AetvnZqU_9en6?{X_v8Zk*&3s##uvxIxC977!8=yzK&EZn;6Pm7wRt1QfNYF$`i` zXm2Z&IY*&Np#`0l&eJS!P(VK@V+gA5f>N|PISB$Zlfvf^?2|`=BHXjn;}At$y>R7- z5@o3Lj%BOk`@Q+?E71Jj3K;07u@f#mc4?kWP&f?Ws5(nL>njyQpi%Z7wE3wDUq|HM zv%sx~1B0V6E>A3+7t?Dq1bVNz(rPq43z>CJXc;9`Y2%jpm{CrEf#d$F+;qsw&pGYiw9iMi>Jo7Q-X@Id(BkCOp&i^Ysuj#LtwvzO5z%t;kSUEKEE1!cEQXcg%OUz1FNf%L z3-h8UZ80M_O(I&ge2pG^!hohbe%R>tI&$nnX_Qa*O6Ak@^>svkZ{ctYNU^>EXM}7Ou@Z4*H=)!bIr^2>s6I z@^wLB)G2I;!)gRCCRW$6z3pyqb-m(B2mUGS0uvmhpb zJ7q8dFm=?!FN8~im0QmhFS?B3>Y;-D?moh5nLsiSJ<6F)zyi5Q=5CIv(3lHnynTyN ztHNf6cP6DH`sv~t&Z($hDg$wxJ(C3%8@#XKiit%Wbxt$SIFzS@3gS{(M0EpYO%VIQ zEZ|a!DaF*Uq3~c08-9x4`-BLj#BV2D<}=wd@pT5X(YjFDUP7g{=j^_P%gZQdlIGl? z=X{MwdWpg%quV9!M|HEYhCY55#>iT9RD}n1$jLxBlq^uxvmeO|Q_AmlxK$%qoF{?t z8w`Zt4!3{ll>naKT?s0Ud8{0K4@2ie$L6p)d|yz zQ*%@Ei%tezh{tJlx>lJn0`h*)d>y+C_z#|SKzJ;A$V52s;3lp^JDFT(i1X1NtOKLy zb+w^>;+cf@O1EC~kp#MPlQDHXdYwpkQSJ1Sv3Gp&_f`Oo3WpKfA`UMib{QHCPR#E& z;B8BvieV|16Gg7WCEO%z)C)NL5xTz^6y)aM-F7c~_(bd47VQ=w)QA7>o~K(Tg-3Z>9}$yj51lRbKDf$CPk? z#<7dDnAG(mmOLbR52S@vyjF#zUuex;{5^(A)IHu_-D;#8sbI8dw%FuKgiGcd5 zHZb3(aq;?Xe3C(C3E5t63+@C!OZH|cpjr_j5nMqic$|x|B|iX)k-@Wig@9i?s4Jx!(tlr zHS?{M>1xAJU2a>A3c?%lRx?7bj8^z210BwsiqHLpWa@CyJYb$!$37vhdH8YMq<4yf z>*SQIc?BC&cmvqZAcWDur3ip0U0gbsZUzoXlb^?rtw5l0$R0yT%Wg(py_q%yMxMc>rIJ89ti|E1xO8L*S|)%Y!|v!}A#7U|8S%katk`qt;&#@BZ)=hYbz z&fzUvC(5TG+uOelr!gpWLf^tfRW-{V%rbSKD5Mg+Y z^c3S_BRuE#78UbF;)YLjo#Cc#Iu#BpPeSafv57rMy-5@%r(-Ivv?0N)M*F74$5etf zMwqx<#qn)gWvYhfGrZFtR{E4Nfk7`HwxSj2+d&}RRh`a`uWvEir17;0>8`D(J2#G=lNtRVst>#EZW;jc2!^} z4g1K~Vh5ZNNUPD4%65XUwe4qWFcp#YbQByQGAE8VVBCY1pDSJ)SEm}-YR+p~Pg8oj z)aT7f2bY~DsC2cqiA=k(;1Y7R!LRF4p%?3EE|KaBgyPQDT7$kkDzO;QihR&Nt{<_-T&bplV>%xJ?p^be91f{E(ZzXUB}M0q zz1@(?VI1qnWg3R~u^8@eggm@e>J$PY{d%(ksCXt>Tsz&noXb;_G#p+Yx=3&=k zl&1=s2TYvYyI!1TN}c)+!Q2E=gAH$rILRgR1X-N5@Xj^1N_ZU*0sdSvh5j+A1;-&> zY49eIq2?78#JOqZ+=nUWrh7}Bn~t6#3X>I=Z|j{7#GVO}AKk3&o!BzKm&mOMq4&vv zuFaWOOZw&Fxq6Gji6?qmD_@UBAL)&mATl4;+ZA+=!Jj6%5sA^2il1*TBsN zI$@B@WVT_2BTgTYl&N4PP+NhoAl8FHe$O}8$c1EzPP;7W2NUtgs`q~>0J%)|P=`n& z+zG*6Gy9(E4p!S3VmNFp%dvAQ#Ef5~r;Dj~WqUx#A!Y=d3?DOi%BQ?Fo(*~^aaFZ;w$u{Jyi1 z=`dN~P#o@JaQk0i(m#S(eTwUaC^Y9Uayi!rsp$Kiacq#1w&N>j9MbRB+c@}asw5Nd z4~cl?Iyx9kXQG9kyH^m?{E{Thl@bn+Ysfx1qG)h=P*y)-r1-LA!HCqVmylR>TE}}W`!IQp`!J2W zA>W5-!ZGQ04wcIG`9*pf1iojis=TJ=-4L*cP^6y`Ft7W zFu%#uoxV<}!sTIB#jh#8vNff*G+!{C#w)BTK2E7AK02u>{u4c#aaIAN2jQ6gM@!qu&d;?17HQu{6#(USsc<^5^7SF~~EI zJ_2XnShyB^Y+PLXlZJu?3@)D8FA1}1czzwD^?6|_>0LRtk;3PHwRtqwNMfVFKhStSc zaiF-TC)!RPPPe1^s%8J7L0b)5^c`J9N^uBeD)ssXYf@=2UE;$pwk)GuCber^KIs1?(_zu(@IWg;xez^hrwDHyn;i)1Ua}eC#M8YZCx?C%>Gol3o`T4|ndlaIAM!MP9Ig-j65S{2Lyx~! zo>5T(g06_`c$mp|h^n~*Zfb~EIZ3&-)9f@(y7FKZLyN?Mp*lF~!ivUqhuaT$h{y3W zrueK3`8Kvm;rb7!Z?s*7ucf$-6YRGdB~qAe=bujdr>Bo8enp$W)opJ^=}qCif)wNuJsYI3&I7w^W0H%pn{%GIgLx7@6h&; zh;u)ej1-D7xs*}gFS--Xal-+ZQ_emi_f6Y2KH&nL*3P7sbJLe@WAY_0MQ1-S8_N)7 zkI$9Cyj{Tg4t7So_?QdNp!1G0dI4tk=x8(jjvfPWhhJTXqeuVD!9Z7ebXJgVVJ%<` zEjFUJDcu!vAI_%3pC&*lt?6bBIk4?d@fmyV{R=Yb6bx|6&<}K^)U#kVn~zS;B~(`1 z{TE?YqTi?s10dZpIZWmkJdmdOX~$U{>kLCuP8k|!wCnEbB^r%1rxoMKtPhDf85p>< zs;M`yzq5k-$1A0+O5jcqf=?&oGs@mSF17hMbPSs7$uWknN2;Xp(*UpnxMK!qwtQv9 z+}4lN2XsZaR>Qlr;njYxo$CN`5(lr;8+7(Krs4`Lz9fKCCov4)CZ8bVo()}8H-wCy zj-?PbK9HC3OEanoyw7TYBWdW=*U=((6Nz;9H|o{H|K~wGkSBL+|%P{as-c$ z-E=xf^668j#&blEj*pJqlp}oX^w{V`j`UMw6Juv`#E+slr*6v0U~J^(n?_INKi`FpI}yLHhLhrm%*zMk-1x^}mLrlM?KsU z|5mS~!lhxo9AiaVNJj5{F{^`f*~9xS0ekfNn8*FwV*!>Y zH-2}_GY7AZc@T%WFu!+PX|M(kuDn&c>+3lE0TC@?JSGet8iA*~g2qCHLoZv^j3r7>6+r&Cu0 zU9=<3x3Ci(r)YInK*Kwwu#z!_CU80;m`_C_Tuh5o0E5T2)r?UXTFA&DfZe=Hx6(@m zxIznX)s#G=SC&BBxU|-bKZmqcs?euoiXn0#B#jXIx4Pn@MtbR!G&n!C46T1ctLr)xX|oAC!T$+&tHhm zNtpUim}gTmkuCzcub5|fg71}U;ue3Cn~qXqr;#2X!^-V>>;9>Qyw7cibZ5$W%@tRb zqfzKLCIv1l8LF5VZ1F~IYo6YWd8G-R zagK)0h|q1En7bGCs~9#b7A4HM3Cd_-sg^Z%6vv1K=6E?snKLk)y2^gW7ujOrLDd8` zZ+$lhvFSG{Y5n*7LVA~7iWc}ZSCWCv>X+TD3=Kzbt|Hir&)#Q9E4(hwz)R#`EY>!0 zcRnvwFfMXL@?4$8%_|)_cpC|FVA`FaphT90eSTcE&f@k94w>Rq^W|(%SUxMpJWF0g zU_H$ec?n-cCqX=@-RR;mz8;K-mY8sHRz~O)Fb z<_f2?GL|uFa10lIBHVx;0>W=Ic?oxn-DzVtW~BMBjEOs$C?OTw&Q!S^IBmTZgmf}# zX9mzOgb{o(8$s4)`-wk1o0~nJa3B?r7Y@zXTOlW_r%sMT=5B80c7d#QB7YB`;6Dt5 zolJ!`-?m&egu$>8Xi{$BGOhUw?{Uyswv+f^(3!$HKI(Rfj=J^0=j)@?w zm?}=s&&TQMiy(ac(9_$U7VWy>J5o4bSHr&6@d6$0t|Bro6nw64qf^|#Z9@lb;^B!H z36l3DTxxU?k+)*2X&rk`<=IZ7h-eJz; zU|E*n3~?xf^7OVXq);-{b#5x6p>6SBek|-HrNbED-G}1^vdR3#iyrjL=A%G8sOFw8 zr;o)-G665*UfuD+xrW&b89rftV-t(sZ;E&fhD*|TcEWscyDxdvrl!qUybq1LM8a=? zIqZB!02VZ1riP2Q(jqPZxm*N#-6V7NV$q>ZwKk#1IrE$i9s6%@&@7!#Y&84P_+t}+ zP~1hX;hE_?k(QJd=qX+t>+32`wxNy}F2zme@Rq@SeNYMQzPIic*n-E032Ss|>kjNQ zv^OxsVD!=R)-k&ez4F!1%|-9T8&TlN^wIygI4nN#)ZUj*J@Z1vmr>w_MyYZUm#E2p zeEHN2q=xbX05awCc1;i9d~pZbu*itJ7e2g zAld~w71Rr#x7WF&d^jBs$~c|QKdugJc;fxSa_0_)?MMoL+!^Tiu+w@~;-onL(Aw#f zr%y-K)xQXXWCi-)F?#yW!2;{>Ap7VR$oq23D$wlZ%br(fxkj#s>GP5XD{5--;Ybha zAy(A&sfQ!Y>)NZ4oHV#p-EwE>l|w4q_6+r^A-plTIrQp4yS&0bTw$_6FslrgZw-EL zVV-<%S$G+`{ejO{t<{R@UHlphwwoPSM7UJGisNQ-R>4zn7EG29Sz3wL7W4JaqDzR- z)6`lGS6^^=L1aNqy1#=<%OSLkwqFnEH7^?2cybjPsw>ZW3f=hf#0x8;Vxte>mlRgn0|2rij$CtK#F@hm+z?J}i6*oD##9!bc7 z>P|2g?8W|*8MhFEOxhYH3Xigkv+E?XQ!IjNJcYb51iD9JAk3m1RGVMfibG7RdXnp z`8J@=-<(*I3Rm@Th_vnyn{CiW<|QaqT%cY%2$1*}07EQc>N( zIoeaTwa@M--LH~?fz2`wyAx!3C0Fb;dZG?yOLnUHhfh!i_?;Z5l#J&b3g-}oxqMNC zrYcp?xH1G(Wy9VRY6rzWUI^K9tf++fMPWbodX$^9W5eJlNUJy5ZnDqQ^tK|^xe5P^ z=%8M`SB>}%F4lGmMf@j=md#Q1ECJ{4CCHGaktnfxd~6Sg{%~M@Go9XQl5&2n*0$3& z=7J$x1}@(VhWP(tkfv}ea0kO5@0))aMVhx=zZ?Q+1rJA~oFRTA@^PRzYI$!JZRk%g{F7jmP?MdwFx$4VEs zJ@6oNa4y)X8&L7MJ3bWTdYhJnC}_n~^E@w|f@R<7)wV}}hfm;WFFn1fUvhQ=ayab) z`o7}0vkm5uPMyhav(dPMcbPM|1Zy%lJhS)!bB4n_B$$|Gjy}}#SxXOW#2-hk&JQVZ^%T;Mi)0sWbAPTS09y0qWMhNpvvEU4Bc1}Xr?1p$rA;+cJ>G5&g zXJzWO4a`gY*YF9-0#*s}&8C5-*5Omqpff-5)UW%S`jSC$lBu1pV{W2S;c0|YMF^#} z_PNqdyMRZ35qcr2MRw9s>%|q^Yh#qMB~lSD=Xd~n9KZtLB^YMGPg50VYxNS|1op`= zw2+gON0R!h>O>@J^i9BV?1gixtV@v$a;c;}dpX z0W)i|;y-!)$w~Pq?zhDBOZ2@cp||k&GHT5HrpJxx8^5Nr@_%g+=&j}Xi^mJtv^--l znk8(($f7L2gulwbf;T~1eg!>8LKH0AFScG%lWq{xdc&dpQ$mdbB*-dY5b7?o^guK7 z+g}+$WEUwRBDd68Tsz1d%^*FuiHUW3qg%%r3}WL+g#WT#ARc2D?bBz%)*n6*)wZ<) zZZ74VUctT8E*Ttz6B&2}-Lc*=dZ#1hqb5PEadKTY@y(x9R||-uTJvF0`l4=UgInKf zl|#Ub)buE)Q^K&gl!;AZ^KeT&H(OKr>3wZJ1#y-hy>}$q?A~lt3rlot311e$!P}Ak z)Uc5=Vq_N1;PJIeF~BDgL)%^oeO(HIjM|46cf@`3m?5B z9|7wI@iStM;S+pX2v@phw#)eXOj?Qiqut5o5@9G+WG@?Mj88YQR zPmZvbY>I?sKR&#dN6W8DN_B}Gx4a}o$Q_Z=UX)lZS2Sf%a^w8vWzLj-$)oOPO`JDm zQQw+9M?NJFJo*2;(Z*_+UAZCdsKdvHP@(AE2#@e69J6BngUK-@|NrIBTgA%{Q zGU6|+?TmLE4YVAB7=EUZahGlo{5CBMfb1Kwkm~sC0yERBfh-83USi4i@37s#LLNSR z2f6@)UoA<1D(zJ+04A{gWz%O9* z1rygJjSNno0X>knv_?wo8`JyL!E)MzJ~6Pc`9uAvWoVU;m?Vz_@sj8#ca?Rf44;sO z^;;vv5y8@P6pk+rupBK0c$5Ii#DFegy9Pr`5-^V2aq6e|S|&5*!GCD~1>ZvmS%F?Q zrx6u8k3LpKU@wq>$S8f;B@Ho8BZi^m~@$kfx1_!QTewB<9aeQ!7}tn9T++3?6VDUtU;-8yaMRQ}OU{ z)&c}6cz?^M#9t%ZICvHzalNwgTOD6K#6hV%TOEpI(+A+A#8ImXBgHjrjSFOVc6?ei zTX}=s$v~ia&Bp9mgc2g$kMFP`Jy^zjN%vC52D6L?EKNFjNSo5WEDV|GD1o4pU9&tK zF2oOPGOa~@q?Ci96%A`3eyfIUYkUnf%Cpi~B1P!K|gt`MR7Urwx(53CbG{#3GhzSMeRR zewuEjm0GF4nroQ3u3&A107$*vR~WtKG*C19F_gZNYJ&?JwGvv24!ok}X)Ze3t!<)p z+^Q{yLOW4wq8gGsEsZ zpGx3a*?2wtn-2~k9giXu5*p{?CWgi&>anvp;Q&KH=N#Z-;Qx^Dc;VJs8=v;T8%a#4 zwaHzP;YpkUVCPPPFV{J&cDY@`F-0ws2irOGP0VbD5jB(r1B%anc7@bWp80qm|J`DyC82+=&{mTx(+AynwY9UIMqN1cU?I z3!KHt68`9pC?Y9_PgKiWmWB@p;`@pmr&fX$Tai985DUVLhBH+>mB2NMX~;qd)AW4V zoVl@+Wt!^ZmWl2uqG+I|B#deW2FYB&z=Ir^d`{x#5iS4fl~Sw1naaC7qF5HcyPhQjQl8qAHqB76%7edD!=ebcfqOB*jT_9wU7?qR>8av!Pw{cL z%czL+kBwFZ`*JNDaqVe%tO4(*M#zK6Ob=x(7b4F*863wmX5s$cp_4^hiHA<5w-XPY z(74+J*wIf9Uv8efI07|5s;?^JsvnxYh{1knW{$KSA_bp)P*z*_D%;51_p&o^VWNROCuF|H-=&Es=Da2j)6hhAh+YvU z8PM56)^>O6P{P*`qoNpP&$glJhGR5c+`wtk{%kbqGaD|=_UAlo-yAPoNz3KZmD$nL zBl7Y_A4TbjyFvEW^7QXL#O3?XKuZ?059~QH`+!D`nGa)gD8*rI4)G4QIZk4~R&}CA z2HPAbaInqU*eaC=jd$i5K5>3wY8oMvJ~ljJ?B7li>si{A?K|^C?EO0ovmR;$FegTp z4@BD7L9bEJGsS>58ITT}e7$ny9yzFi$oua*GW)M6GNzQ+VU#X*JM_3}Z|h}N9o%}y z3xgXmVi#|?XTgctv|?WtsL2`kWx`s3JnzAbw8MWW#!%v*iGWHH3O)?Kxpa)B!eN*P zSQu07(`Top%4vBw?TXp%TXViTaC@(O7-fb}ASW!wD1-lCPDp*ooR%*gC;x}my&X*W zy}e7R1I<6x>0kOzi~G!gI4AQ`Oq5}+u?KM_lI z^9SAgH-Dw(v>~scZu6%P`8S{Et~@WwR(kah2E6kP}`Z5H?}}=zb$vYpR8-W^_){e+(CoHsc!Y%r@SPTyVSq zSNI`ghtvVl zg=~!mkGsy|Ub#zT@Wr%>e%ok8B-zTJyLjp1MXxV^+1Xxeu+rCYn;J)E$N1CLPByOo z_`h+_9M^5GM1W!k3nOIu93ScYQ>4_LKJzrt;M1BmJEKEO+a>ziZ zCn6{uS(1>?qeM*URPi@Z92t}`4k+*$YcPEq~^v> zdkH&`X6Ogt`EE7sPvD0xZBx=a`?SOB8lbv~-rQuPM{LF9b`Vye}HPNbEB5-!`7z#3P20m~-h`w~GDIEA;7Omx5kIlh$>ZIqAUt z+UJ92Ub=jtw#6_0+4h4ZT|wZ3MQ!0?3}#QuXTM^d0Ron5FoKTGU&vhXKj|H6o=oCV zWFDdBsm5!%i7)x#`e)En7%{KDT8$XG(7@;Eaf-<0Lk8L$HjRuQ!R(l3(1mns3ZFT+ zn&wevyPa+;rfjXLPQ0dpZ4JX89w9{TbzeJM& z-NJORobtjqI($Ab;^sppa_Pe{<3tjUeXN8YQ=8@pb`cyGJD@RyT1P9#7+R=S#+x)Q z9h9%53-B>~_)heZG+Q3#g>M>E4yE4o_NY>;n6J{hG!@hdbyf%9(N=gsc;E$Rt&!N_ z;kOVl^1~E#xRoCKrCUEvmjFbJAo%(Y`&m(z=&T#*cEvo}Yz5%$e!BbY1aA57pjG{! z=KXyCTT-vo&{1hU<|~Iz`Y^mw3GFSxC=NZI=CTqY)Y+HmUbj_=gre(3^G_9Ca9|7g z7g}J>B|B$$^$YJP**r02ZC2W$A5bkvKXH#cTq2mCIJ#a9hKK>_ z%5B&sQ5<}b=0aG0nw%p5VXdw6bPN+8k+dq+>UcW=-*TGXz!7?fVyt_X^#;B(P%UE^ z)*7A#Rny{6?6^cGj$v0E3SKB}!*rJ0zP)g6nm$`%36Z97*-D#yVZe#ujz(|pI|N6q zS{U5dmT^|s7KgR!HZP%+4l|Bn1d)Lvpn-#$mDe^1P%neFpMq)HJwV*S2UqwNPR>x2ISUPucTaKPjhl`gA3>{~o8OTwKx?zQ!E< z@D#edzVeFT(`O9OJt)i0h4jDHs#kH;%>Aa@yZHm5*5+&B^(s18v5T)`)GNO6Ywh{U zW{Ris@e(WL=;Wav`_I@^8=2lz?{2{zV?%=XlB|x>Mcr@obegWbnh@KJdKdwYy#`&H zk#A2fb}NS9a_2CS+4oKr&&TWis?9=2p~ zOIqa(_*o7q<2F|tmD%(yN)R60y&6C;D_Eumo>H>~2jtd;sW2{NGr_l~!kM10i@_9z z80K1h6sH|T9e|LTp!fAcLlnc815dwHd3m=@b{&GyfaNP=6w`*@zV)qDcZH?CffVS~ zzO@AFwu4m#a>bGM`D(p^J6bL^p($NCs8 z7`Xt$;9CbBmebSX&GA_fk7VL(LXb7tP^xH>94&~k7PQZ&QU&wJK=55~-=X0NxS*PK z{m7wUcMp&Lc+PctfCZC09#5fnQM^(NwTqEWTZhv}ecy4hJ&Au!Rj!ukOnW$wFD;?CAugBOwxnXALD6*)%|Q4D0lllZ_Kl{Yeq{mr*azZ~w0^Kq1z$j;84X{pz^J^qzHIKjaeDTI z$H&;)v;2&mo`^7tGo7AjM&G@tnXgKW;%O<-riP$mhc9qo#DSN-wj5qh>r-eJnXJ_SAPLw8zdaD}yk$3Mcih0<2*Y^l=g zt)b;t@QCDc;r?F-)7Tc}S>#VUwc{_u@Hmd4Qn`#U1RNP!%F~tlpeFezOb1O=_>!L? z+u!C_V-wv3RGiSqw{Cn8oPQ#OMS)#(1-S)+s5*Tw+p}dzaOLQlrFl#z;NzEku-dY_ zD~16;u)*Rmv#H{Kx@{JR$W9B)J=$$E(8nThe9*X8+_Puo2n96%C>*^D91?NQrFw~< zdt2lHflIYQ!AAGXY}{Ch_(i)n!B4cg`-#72rsk&R7b9eT@z$ve5wdu32C;T41gWr# z~PJ(kCX(>U|W*$T0469XKv=yxJv0&k0lar3XsK3F=BkC z)uL-4)NC~OWr)FMS%#L`H_LF44MGrvU7eVStr#p2Xl<$T}U-QDDv@I}n04ZKa}s)#5Y z{iVyi?2knxntwC=@a?d%2Y(!M1|HlX4s!A7Yf!Xt>;#CfebVgd(e~?NV;Z;2!&hGw z4@TNTX_I8pd<0*d#0EZ@M5!h3v;!G!|A?$bIsrJ>d+wTIPpgcmrb@ z->ik(GJt2%gro;>ovU44C{^J(^x`v_alU5`!$cPooGe+UV|yLjunx8DV(qh;4>IHw zs|-BDaD_io74|rr-Z21}YlJVRWjIJq&@(gixrW(^?ybat3=mSq3kNQ_eFWDJ^$f`b z8Zt0%rDX+~Xb>EaOoBE)0qk+STS4<(c>cnr3233SV5hQxEPg`{#k&wWMDZs2oLvd5 zWIrxBZPA{ZDfT-WgfHq`+wRNIUTbXkKH!K`2nFKke{8pt_LtB}2c_?S-nWyE_bme) z_yP0*`Bw(yU+I}Y-|qIkxJQ#Qm)&vf!Q&3p4V30M(UJL?#6c2+&T^O|czo#V5?)j1 z>4nc8^s3&$y%o1@@fZf)GC+X4=Sgzh`!&08m2&dn^?E$OyqE`duGB59>h6yY2oz5d1Tt#1UadecWL4Bg$o09u-v%d*>hlK zM1vW3x*m?~o`c%SzHss4;v5(9LebW&zv!;3F5sRz1bcD(=8@YES$_d9u8s^WWXQgJ z>EW>Vu@A0Rtn``bbH%Ac3-5c_H6xpv{?zG13kUn)*0g#bvZvh6=h6<-yM@yYzTRC# zIBy-?nH=ZfE^S!PF3ca6b!qwn#^H<3y=Y+l8quh*!*cS0y+M{kq~dg6w$b=S8$?>> zqt|MB+O3laNS3c;tX-xBgb%UO+IkJnat{@SjRG+JE^*4!I0J2^y;Hw(OV12N6yjfW z5%mykXpF`AethGHs14*w7hn%n0AlQ40OA-_0KVSo^Q$(VU&B5~;RMbW)8zMD90}&L z!L7uR_y~3dGbAl-IYjVWhD@{|W|P4304@ZRF?kPQGV$m`Fb!bAs^av-V4mI_M%#8l z-kZy-B71U~KY;#R`-=}+y87Ye4sNf}s;9^m@Lc}ezBq_yQ1OwU=t(^LuGI;y?^!%g z<=zT2-1-5mo{aiMt)7hkx3z2AZ5xK6`JSNjfc`uw@F9)G|dPqGhs1WV2i! zq;06)q_IpFiD8*2mcKGpPVfe(xh5xpMNjA4CG$*IX|z-6!{o=*UpV*FO<#JjZLG0v zP;#&?GIs-a5kWgx`*A4uvpXd;2^puNuFeE_@E4@GxlL3c7Z}Mgu@hAp)=`!Vfnd?m%%i`Sw8;L;0&;L zF2Qn?a4nsM8#Wx8f*j{VBX&x}Xuu2JSGFBaex3|on8{|Yb}KzUNg<3bgQ}vP;NCJT+eHmzA;X>z&CvmFr2ihLMhTjocyJ(S^kG)@qc9Km>y4K`Yat$!eTohB@ zHWY_!V)oEd_6+0_Y$Y$<{A=|`LRL6+)9ubO6f3OdmFAmI^S0eaxO#;y_fe3`?aOw# zlBsoO3?`VC1{CGjnyMvZG4<_&=S2jY>EK7~uf-_Aan9Nj;{h-SP5I3l`JECx2kM|_ zUOZvmNf6f&7W@6P3z0wpWF3XUL^!rpkT9L>7Hgja{{O)S&gip@#wd$r|4Z?8PJCpS z`SQ|+X?n{`TOE3NHZN``psWiX&i-BAvDmlqj;5ZqMd#2c$0=#w$ec{OfGT>MkEE;V zM40}Df{YL{t|IuTzYAxDXh}@TuraLu{D4ii{x)y-PHimeX_(r6yhd8jVy=v8Tskzf zonWJ`ltM#(N|_6rH9w69+G{qsnS42E<&$X91Td)h1w&(XNqbT6a-mzbJV;S-i$AzM zTHjcH67I71PMw=%L3yqp@j9r<=8hjvq0`gm3-w3$I$0q4&a~*Z0q_X0*rcH-u$*VF zG9J{{8$=-<-JY_OM69lF^Y5J`9PMZmLa&~i7d@PP;ST{68P%7bBuQ(mF0Vqk0fX~ydy5ot|2*a z8f>{2G5C-ejHUS7*#c}3$1|7n3HfmsvZD)45f0lX%Hsh?G1Uv@EuX%&Ige@pA>%h7JehV#cP+hc>&*_nlwOBk^#1Uu GZ2SZ4a9;QT literal 0 HcmV?d00001 diff --git a/back/src/main/resources/build-assets/zzd-binaries/gen-key b/back/src/main/resources/build-assets/zzd-binaries/gen-key new file mode 100755 index 0000000000000000000000000000000000000000..68e53ed74d6c2cbefa5e3fcc65a264b6febe497e GIT binary patch literal 2533982 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�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 z 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

#WHIOPX=- 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%0ANxYu>ViG7QWP6 z5#6B+id77>gd|YOKP45$Do{S`(xv& zNTZeyox8u$DNxesMIE6N{TrQlMn~FGNHn|~%VV+`bMT-8phoLIgf2j5&2Mxv7#)d; zkk~@UhWf&uBR}OeqeI8p?0y~jjgHhzkx$%8Nd8pP@sQ!2VlWa=qdFMs$;(st8=YiE zM`kJ{iD=9deiBpwYP38C94w%IUi?NUht+8- zB)6c$jvMK7^!RP6WI3Qp?D$yuIr{J3H_2u0{}v+;9k)r^35kGtAqlHw9rU(<3_y!T zA4Sj@#@&N7cI+=hJ}aesC$Np)>Rv!huj4cyruwLUQv%DmP7mc*lU^mzSl>a}#W%Nz7Y;3ut9fYKbZ{zdw3-G@Oz5`a5 zeV*7q4f91H90UO6ad1YY;`+yaKU3UENTxv7P*Enb9sc{kA;9WNnDP$Zh~HvDI<@PF zCCK#a^;#31FKrhhOCd>xE_?mE1OFPp+tH{SAXg$hXQT}R-r!G=i%hEKiv2fc?yFw5 zW^_9XNi|d64mcp;@J|7$fGsc6;o1)Oh^t}9iURqwMjgaX1 zHMXbC@S)$!sZ`Qs?*r-gay~J-|LuD@`OxhNUDm!LAARpgGrh4r(LM`pRYQn01Hhgm zY}-_h?=n&%TO2nf-B!rg6^H@7{?Yc2Q-zwUFVd!ixqzNS?7Z(?oXgTKLb6;*XEA(M zfo*{5Ool)6k*X(=b{*US%4M{~G0N{MB!`rA*m3z|_|o%+>ipArL%IpcJ>+N4=St-J z0!W79xYGRW`?Xd`>j4IWK0tZCCpz)x^QHp2!BZNq3*+G*4&nhM<>g}9D6a@7y(vSB;P`KJ&`s5P#cC=f{upMhCddN^fb#!^{>t>zk5K9Kxk5!x zA*qEfTfg-E7W(c=A@Z@h-Q}N%c@8C`KFamWmYMCBh@+5noZ9%_?j`8m0uPmRof+L~ zqdId)5evyl=(0xVBc>qFG{Ck~bQ9EmJ0BrN{%6R~)?+yG#e=ngEtjJr zKX2n&{NHk<9|e1{tXz-AVTy9G^;iL2v43MdUPPW};2U6d`!c#cc4EF6_=7Rv<`B$* z*(vmHGVN4?{Z+*dRk#R=uad45{wu)-!0KYyK#iA&v0SBl|8>v1r1e5kJc z__(6qfG%_HkeBeU0V=y1b!{14bEMgT-oSyPhu=B@^u6j{Og%)JEBbe_n~)ShSFFfO z+~GeF!~wRv>~-!2(jI{t@D>>2d@aRG0kpgpeH!oc=<6p^cOhvvt?|8T^W7*1@Bpl? z7gOFSq%8!i!7`xyUUmFwg+BXUwREtMEQPL!DeqqR=YlhU)uq=L7_Ss*55QaS0w}+y zIi9IscAi5tL`e2Qm#yE=@K@b~?@0qzw;iLq3~3v|Zjc7(b(wwta}3@UqV+-Rm-YkF zVL~!Cpm7}W9R44{7r^QUGrBbDm_t)BUyE3IUWRH?W54ex_7IW<&}GYO5B~wc3$VIL zawYr@0n)aB{U96A0Rvm!Bh2|q%S-1!B!6K1tE5{3|9jveV0G6qy0u7C+e=f?uh1B} zzm8AXb|x8)`$y=qW(a?K;0oC59-1gM)j*^z1*?D!WGQstzo4x7BZXvQVB>!8fDa`L zP>V*SNf*%N8KN(y`IJpY2feU-Pa#Qwj!3=+Kjf#V>}%AK_oLAH@|$sfGOIHh*I}jd z>_vXcK_#6u`WevC_)U58%@hR?jTe%K(2*!sNw)*4q#tkvwASc;Mbic7*c?Y&Zt|b| z=7#q}C84-Kz?l2*jMl~9ZTmO<-x~P02L7#qe{1018u+&c{;h$3YvA7+__qfBt$}}Q z;NKefw+8;X1{y@JA)?8V{((OJ!G1mIFN^$a8dE}SJKJ^ZY=2SJCTfo`B|?MS=iw>d zvm1ubLcJoUY6gV_g;>v;69I{8NXvWeayL81PEzF|&NyqP2yUq7$NzzEl2F8%v@x;lA>1V?!J z2m6Kvc}0hYg?NWM*;xAcdWVF0Mfe7Jg@*bEPoYUuLLz*_Bc_Fi1j|1r!Z$j?$<}ge zL`0}}NJxM`T%&^{gWwnhZGXBi5#FI#4f6{23HSB&2@a93g?riB*mVu^55}g5!0@2J z0AHF3i{VqfY@rw#>LdT)A7zOQ3xxLcNMBTfSD052ElhMbn;v1|Ufy95%nCBwb+Zo- z@b_U7BEx+{LjwK1{e9uj?07%dVUzj~?bCN~f0rL=uEY8a@8{;^735>zm2q<&Hf)4b zpnq^=^pAu-LtN~;I?b?U5@w*1ruauRu5}Lco#7wuAA-H|we|9`ap>C3&(@)fjf0P0 z7aJd2+a3;GynK8oJJ@u$@#$gH&96Iqc%ald!avB@$gg?qw4SH@9X0f5f92@o z!xetwXhocSRS_>|VudrFS#cC|^mOKl&N0!LBA>TMz^>1Jn^$um>HSsutI)I!D!D2m z#0Qa*jI%~QcZYQyKuwY|QG?&!onlpaR7z`@ie{ zYr2^67d2JX!&;T2DE|r56Q&B~|F&+8bJseOo*W@TjMR^aD#+ft|Gd5q6V;5-?V`4B z@-=IE%36o}2KrfhMMlq%r_t@w6ZC0;!sPy&f6iZFDP}jzj|n;bvw!}TpVvhl6zzj% zqI%+vKd4ps@!v(vyD{uD{vZ95kjbF=cNy7q+Bnfmva3JENJaliyU>3!W^<#{fBJL& zR!5b|rpd(GipL!C=i>B3^_cahjq*R0#X5XuxOISUSg>!Pbx@FX1bVP=T$X)5KP-+^7o-^5WHA0zb zI>P;?XH0d;2h$HskJ_li!=kKEEKjJorijTGMJ%4%Po6G)$iy{@n0#SkMFsy;$mP&K z6|M?JRZX44Q&&?}QR8tnd1@Led>&In@{jq?02SOG(CW)Ue4SYiRpXFZfTqtuyo6a^ zia3Q?UWs@Wvn&;I$UZ>*WriGb63}IFFo#S6beX*5;D4kt%XA%PfzKPpeCAEW^~~~W z=x$-Ow;|rkEbm8rlvzHGIG0(@$7jE1K0hCy{eby=2|jxs^ZBju+5LX`e8Qo1rlzjW z<7sGUYV!F4fl#QWrLC=_qpRDbiJqQ5tSPJ+tU1g8W(YHawScu`%$P9~##%9E`iqHv zF-0}}L#TlffgwRxN5uGMqJ)GJDM=<0KSu#2g7n%8_yMITakj6?3KB zd@fOu;-5!J@vu{>M$`!p7XeKvp9rNwsTM@_@#37J)JWO_&p$=@QIK}TLfR1@(+w|W zi;0`m9Ute30oFA6NB&I-<;NA<_{?@6CdR+fm78vRMJ$U|c#8WfMo7}sHHr-nKdH$V zF0ONw$4>+!>Gg!XnBgYBcIl>-zM$)}u%U*uvl7qJ{V)!(v|#&R`2A)=PKCUWZG(8p zgy{Zwagq3>?>|(5C!a4CtF}46L`d4>{~YutNt{h8v(*yRR;Z<_ZB^T+c1$gnx0k1> zVXrYtRG+ zQuVTCWhP}k%bd&lm;F&DyUnX;UD3AU`SZ%>Z=W|jSFI9NwW+eG>Qd!U)w9a2%DrlE zl}FWxsxejLs>WAMtddlDR|QmsS4CINt(sT0q-uLrR#kp=N%e`k74;s>^`9L#B>ysg z@L<-fnOLN*7%%8D`6`%nF|#gawo94xd?wCeVj1IyFNvYf36U`A5++|LlP=;Zwu_lq z%K9^L1^YQnOc*~Avt7*S+A!-S%)0zTl>(7j>o(?X+qP@hzP*KoMTZU@I(F)0NzF=b zonh8;v$3(UwYBT=!@B;kZr$u*-KjY=T8|&rQ*MsGSg&8qiCXVQbN*rC-&&vFS>HzM z_g{>ugX;gs_U`|;asScuRg|oaib_{iRaF&JFD8|q)E`|N$l`N_=N zOUEqDcV2p3%PWJT*OfZo7jC>_ z_UMK)-@tV7v6i=-WeXf?Uc26L)_rAHF=EO+=MwcxQy0y8;Jl;f4Wf3c!dbgyRV&+* z51p^ga&}cY^2m9LnXGPq_G9M+x!x1!t{d01TM+Zq`EtB#BANEgc_eS>4bwr-ox?*< zlxbSLaK6GN&7YB%&IfcozTGN&>0H`W`bi`GmGf;o&!m+Rubn+*54UdUUFn>v(JshY z?TvHbC+F<7g>Rh0r%m2p5MSl&sa~8=(f_TpV*jg|n5~V*co#`fS*Eg8#YQbz%~<`O zy0gY4jYk?Wnwgq=HScSx@;mTt`Ca+l`A+-^{1|>BKZT#k-^)M1FW_IqKLAw~bP(7I zx(X%=Vg$jfJH7X(_uCc+NF{=$L6(ZXfI<-$zi0pU5}1>pl>H7=Y@wEAle)RJn& zXzkO=)hg2}*IKB(NIOA$yY>$4bK2*%i?!W!*68HvT+u1e9jG^2Z!G%Ry?Q70PU)5F zHPM$g4>Fi;plxVsG^xeBmSarEnT|J|XnN4}oatTDPo_zt)K2ZJ(yjJb9kx1Yb>6Dj zs@&><)w<4NoZ3mYrgqoTOVf)o8#3ExTW5E#cdB=;?_1x$e#G}F-y^?2|8d9DST{;b zA}s4>XJygLn8qkiPkK#K?b@Y_3evbM%m>m-5;x5FJZIh7wH(6<5ghn%_USuv+*8as zdQFqiC{Od!Zlpo?@d zNqX!+@L6PALTBXbNT7*y5h+psbw7Pphgb^k8Tul+;5J-SX2lVrvHB8QoOV%Ib74U_xjt18N?7zLrR z$Ww8Y8Gje~pa=h&fr-UK75>b-dl;Pk-?4lxMSU>uNVD%s|BuDla{rGr{1?YQWE|=u zpWHxrlia8E!s(3rD?5%o!X6w~gzlVZgflsF5zgl}-$5aC76C4^Tvcd+$7rwZXa&U>VMo|5?F6qg2QE*+TYibRuCYGgR=sZk1RS9*I|3f?33+Dv2swlBBX6VT#HYq-;~kMp&$J zQ$>^9R;fbzN0l0!rL`(`NdKl{jk_ZWM)cTg)qI4fRIeZ`QSGe8CAMm#a4#g&J%_J- zo!7mht4?J4pY(B$YJAL?L-LGIA|$O=w&Idat-d4FFxA1Gs-dX~LJL!Cgbt?82;EGF zARKEt$&^c`nucIG-ZTl}8q>`PGfej&JYt%Qu+a1}!kead5WX<2MEJ$@E5deW9nJ6- zwwaw7k1RAJmUH^|a4%~0=SckB6dgpYsjr3rIGt=iH{3|^cVM6*cgvseC2vgIu5N4Js zpRxRfG$7<~tkDL#aJnPx$#FwCh!cfy7UwU7^KdW5AqzQ+upG}>iguO4S;bKyYdPzX zzKOF1%iB0<*qXuF#Zexkushu;o^N; zt}mATxKk1Oa|5w8ggYI}VcbY4%*Fkq3R%Eii1aw_QiLhojrfK9P2Bz}>14V}I9g_u z%51dfIVy9}GIy%%LAY0?2w{oJRfJ_K{|#+eY8;Q&JJV#Y34VdbgwraVsI*#z zHoU&omR9OyTdM{v6H^}Ao~~&#gd)@S2yINeBNUtVNBD>7XtZaE=@cx7n$AU-W4agN z3DXi&J#y8w!c>PmG<|A{Z>^ifnAsB>%SmXBUY5R=_+GMQAX;O%(jpwIwpN%!!R>}R>9ZpUt#tm5^$6RfTceG2Ne@9dD}4^adFgQom!>aIS0gLZ zSE4npPR~Tj!Spi-3)3$myqaE~u1;>He@-_cBeFxYO~~}@liBE18pbvJu9b?&Ut|l8 z#VnP1Dlsaf)W)hUQCp^VUads!mD*dikC@od;_33-c|&lwo6oD`edAea*l7HzF;Qch zMyN)j#u|;Knn{|;nkzK-X&%x%s+p^KR`a}Oq2?vcBF%FA-PL@o`AqY>CWp`G>+p5? z`uraJUi`j%SH2s65Pt~&5B!bgPvraYXYl9qkMeW*SNJ9PtKdK8KjXjS*Ybq|9f7Vu zU(iF)OW-1K6}Slo35Ezf1)~My1>OQbL6l&DV2L16uu8C2uvxH8uw9TL$P(-p92J}p zoE8)at_Vu-SB}4Df;s^a_7L_G4iS0^#|bCkPa^ad`UyjXQNp9bT;UaAnec}2p762o znec;!TJ&yQj9Z_Mh7Gwez&| zwQp(P)qbE&bOLpz>)h5My0jm?qFbUnM9)KSf}TWgnO?HqLA@h-=kyBon&}(rkJF!^ zKSy7xzgD02wY060rW=~enty3d44N638rU0%4MrG948jeh2KNjq3|<;k8`K&QLnA|x zVYH#tFv~E<@RK1i>SSbNWbQnY(x`9p`z)c2+;n=&ZmHs~6GQ%dLM}{~fDnpuaE~6;pRz^j} zh|Ecub26oww=(Z%+GmNghGcnUjm;XLbvo-#*5@ovwtBW^wsv-t?B>~`>;Rk&r=PP+e%ulk$OchUEN z@1ftne&;ltXgJ+)zTsj+al^HS@`l?DcN;1isv5pFeEZE^0tdfq#lchP^Z*iV4%r2Az+UhTwAaCQFcyS@ z#UKY<0u|shAh-q%0mDHgmQy zf~%Vl=m1@y4_tsNa07$D5a7Wu8Zl)8@CJS$5QKsVK=2^M3rN9vPy%kzb$q%1AUFc< z0n!|w13bW3AOV3u3XXz&Z~@RSjkf?MpcAkG0|3GNx+9=-{qI2oP&eWbO$H%i2jB<> z01q%0$Ur*C0_ET~cmhZZ>@)BHTR{%^0!T|}0)p#*f8YTEK`2N9Dc}$wCTQ=VCvXKG zU@VY;H9!V3K}9R*0)lIQPtY5T1vw1)h~I$*u(l2S%u%mE3i`FhXxbY43y2MeSa-$w z1v~-$pLzoZAfAf-o`!Vb4}!r3Py*h7YVZk=0PGL&08wBrNCNbE$vQwn&@X^tAPyvf z&7cB20oC9gAfX&m6Nfk+{l!wm%WzJE>OCCNdM}6AF?b*z4m=rr5KjT2pnf0n<#Nb0 z5DMl1DToKjU@h1HHUm324;g-fLz1=f_!itRDchF zT*v4XNI*0orT84Ml_3rB4v-1>W%!>wU;uCjX*X~`0xp$fbPN)1bI5wI2apFG(h9T% z9RaDpy$)yzIszLI1d& zkZPQlzy_=UGO!!$1)l(JS;;Q&3~=A0j{%NA4BUVR_yb6QFCZUqUVi3~yPy(OgRcO$ zyJQBK4Q_!7@ETMzkS`n}0J^{xc!04$0^&e2*adRHUO;L&WJDddgCGzJ<^U;(2g%?Z zC<1pt1$Y6f!54tr9&!;>gIa*;Ied2?cL5kf_c+WYVqkKFOGIEx5trnEir3iw1?lvk z72rv{yS@tgRTZ+Q4d$FU+T<JB817xm4Xr@d-^6SbJ9 zYd3i93(rW|X*#^_p{M%OiTP)0Hzf~0ygL0^^tjiqn`Y#E9Phnu#kCU_$q9xZDqFi( zt?BYr>JV3D7qR=kbw>8x4kw)*x2Si>a&9v2hGWW&5g)5~=ghZ{y?EaJi?44>zXM}d z4g74SRh>I?!ix6XUG``F#^nYxyiH$RRDDO&Pi?YNizF9t1H-;y_TrP`@Oon$wc z$2__pxZsi7>e>O*x7!yEJ=Au6bB#7FTYfvdX4w#zS&tsqh_s)6s|&LdH*9n2_(g9u_AStV)=e61xvPz9?tZ^c3HNO;?=kbZ~pnp^!3cj30TLE3HCppziI84#V1~sZcsm^)%Ie)SE1_pGc1GK@?G5CS&W`IEk#|` z;o=>i(Xkz`q`t5Gq}p9Wtsqyg_nY>S(%jBdyA-&%$JPX_UiM;oitM$CSw;P{s{OOJ z{WZom?oXE$J{@izQ+p*DJ9yLXZ&SmX9m(sG*dfmRXvl;9Eu2(}TRa<8@nvS|q3dbc z0XFKBPn9GE8b+;IyxlD+H8wZy*t@g) zf@8PtPG5R!$die?t1EdSEfXztM<(v^@Y?wKmgS2J?pycTHGMh#&b9HadukZlo(NXk zvw5d&`^D-Xv)uUJc|jKo)}1%_9-6JOx9s-i)xn_oi9f&^;*6+YOkh!jZ<;fW&`tSZ;yW3SZ1xay&=zzug{Dn7MMs;1xjy-YGGU-x5lK}h5 zH<>p+*flM$ncc(L_%(Oy{*@yx=Q!WG{OLAt*yFT?ODc{|ZP<{cBi?`YLhJEK=DWtc z*kXG6sHTQ)n-iI_`|Q!< zlqvIWr916*{5mgrmubne4N-j_6gRoD*1dl7$2IL;_AV@IQr>TrSZq>v-gchts(WE= z1N>ffvv3>!)p*|9-gB1}SE&)-es;J09G^_gPBh-NU>Z-{yYxh|*nOk@sD0iymv2>l zvBM>5+|;41A4Lx99cO<@ZEH!hX4AQkvc`>gtd*(KJMN;(pX0O^uJg3D82`uh)3GbR z^Jk8Ev0Pf#YV9q(n6QiAjk#U@ zp3O;|XB-I&vbo{i zpw^k?&5WA9tj|2)Bst?86>{*xj$KJRr*FJ&HpJn8j_0M;b9C44Hz+#mHcc{M%ZQGD zby`1io@^uMb5P%}ms}!lcdoBkIV^^IYOThI`tP1ime=(#n&sWLpKbod_Q(Fp_Utw@ ze)E{zsufPv_s4X#PVhW6=%DxEZjp1^r8W=Y)W6BvyJ>5$whL2Q<{ZC!@zl)BW)lZo zN~mfR)q2CL3$2VQ7hkCpOfra^v1HYjl-Yi!Q!1skMfMZ)n!c$Uc({f5xwQSm!wX_Q zHXoZY)A*@wd~(i_%6(U_joIB@^RjJHv#bMmpQW3|^4_jpByj$|Z(Zrb6&1_Vr(Bpu zrW6(BJ*Y4b8gF^Lup+Oog!I{RBf&5@KD_2--I$;e(c%F^k zFAbhEp!2gudSQD`tLi`C7hwhIsvxzMu5IHXFOfepLMs`<1$< zH?G)cF#FGt-e)__df4yi%J+RY6jxcURZAcDXJz2U4;xmGSh%obbVGrMZCHDaJe!z2 z3m@}tqfUO=o;TFiy|UNS%C#r#AFhgO8E1cW;%@x`IxC%nW5&+*d}I-`==0h&2Kq;f z7eBSW{i;Qmr|ly0|GGE&x!H`j6AG_YI}cmq<`D8KH92!cK;$gm8y)WxUbE?XM(o#b3%LvDeY*8JZ^*$|jS(5MznP!$ zeh^mTXSHQ=#>FYuf?Cy#-E_&duOxT7Q|5i?LXBq;)r(Ht8Xo;xYuSkAUfUB)Qv%y} zPM$K#?~l+If6lyeXvCduH;s>x&kyVdOurQ%-EML1vi9~D(hsXouD((nE4#8!U8;Ab zwO94rJ>T*dW)#k!q#+tD4VW1?p{3K_?bqsZ9MvN1Z606pZed$cwd2f&4NKIUxO}~H z;;SStDl7Tp85b+JW}fvaRgOn@HY+>u?xVZ$5UXbQT1KAxkYJ_O#9-|NZ&~!X(#h8j zRaZq1C>wvW>zu;o(-+srKH1sPU=6?PTiFn&-OsvBDAnFMOM7#ybd~0o%VQ-ABNpq8 zHo7(-qvF`03(lbq+pl&i-?=$<%(XQyi|@4`uua{2w)*8ZBcm2JEbm*k*I;m=V+)Ow zmK{8O<{et$zR6_Lv5TUMRZ-#7oJ^l=s9F#<`@ml#dw=Q}KRI?qV!y05!`69idbw<0 zw~qBw9QKv2tb9c3)O>7B&wQ+%UvOXb-t}eT+=ci5`cUi8t4Xhf{-thvJ+jSHCx$w> zCNvZ59<90lp81Vkjx(038&%z2cI(DdDZe~z@Y4$h+lxyxzQheSHM=snc=Nz^}Cr}z`m8P^$|d_!X{$3>KeSM(ttZ&h}1bS}8|ptLY1dCYJ!U3cn7&Elx@2K@T8 z166+FWla}MzkJ&B=*G)4i)}o`ZZ`+ND>%8fY{etfo%82R^BAWs+jYd6TynV6-e9Zo zq9@f&G;Zy1%>Sl-?cIip-aAZ!n!6_@yW8Z&59sXj?R%V^>d?fo>pJ(I(X4WV`=)Ol zoEJTO_f~xD%JmlQUGANm^D*(+*s4xfEl$Nhv{^N>rf7_j&6kMd3ywyATH5>G%5|JQ zroHTkPka3QhMi4f)1IeJt!sbj%rlp}Tb4TK?p+)I?r8p(i9L0*J3Kv{-b_dGDfM~v zq0erm%_8onHMygAWM8h?E}yP{4s6+El+KOi27V7-cda}@ZfWhkxavaN<3*bTR`gA; z5)b=hf>XWYTmRE>1CHqZ(Xq+4v;)FAet_!!4r88WhaG#h=y|p4-DjsC>9nXx8l~g! zJH_nH*cGzyJ1Z(a?zbFqruQlTsWwOAJ+2)wd|y$o-}Ag--#)FvJ;N4;4_m#oZjDCm zrj)Bsi(d`(@o1B`r^iY6!vkviKUkPHd9Hcj&N~YaC)zDNmJkq@KA|Y}%%H_SzIM*m znZfGAntYl+=fI}yyzk$&XOG|)ZuJb?y51}HhW(^TFD)a~^A<(M_2+!kk5%n{xQ#;- zH_PY4U#9l?x+eXcYMNzivvD=D^le}NaGZCwzV-7v;X~G}JL~q$Nq^DX1;<`jTq}|E zKK1GLB&{)i+1oPOt>vlJF0cq4Nd~hxsxf|taLWmG ztKMdR?C@&7VKV#gd8Y32+cVqlEFRbH-I@CR!_@isV_R~ncR(NTEv%J zv3nb9K5_Z6QRez7Yv*enkv3#y>^gYRKk&`!&kYZCpGi)Z=^3?oe9HS{t2*ap7n3%u zK9~LW)3SYY_U`zSWRtgl;kQGn0pWY^-$3&`x*cUJY4fG66Y zPiR?gT~aXO!nHMlr(!pz?C;#X$^!FogT76&HFU{`N_YRX=JB9adpSiDXhJywJhRnV0Tb*xFA4<0x4c>C}f6XHY9ESR-* zZgt2P-LfydX+1sL9b3M1(a_WcdryANV%5d3_E;Qx+wRfv&M~hpN9^q1>FJ$)+p=?R zzuK6hM=m4?978S)8xXL&FvX?2f%tfEk>;i2j}6ps&pO;p%|v$^zwhO|th*cIlE*%^ zYNO}AU_j>PL$5MVkE|P=RGXc8xAmG8u0FmaKfL^6wZCRm?ut%-MUTzAZE3gpbl&hA^E0k}J*e_9Ytq2#0bPQg5867MH)os6+Oj)V zqlDt^p&@B2VyvEBoVRHB(EuHfD}yg39(j6XbKZj;>FERXw-eD{``VeN^LDK+DYOtS z^&_Q;*3sdG=9ecfsI$|tmXgI`j-!(6tmb{*dbHtMPno#Wu!{j*XV$hla4F+t+`vNJ z&yR9r=a(ODx8?hV@{d+?FW%C!|Csa4?uhy3<62=ejh#G}_p_ZKbAI-wpyTqQ)!u`* zN3NV35nnth^UJXo4||{36MJD!k=wzCcl_5+^;%oB>(3YUExGGk-yZIA`pi-1zH7rC z)onf9Cp&pc+4~lyQ!Z?dyK*bNiKWgAWh zeTUwzZKjyNG4tKS4?85Q6+Dg=&d9f4>EHX$W!g_Pz7(y`NLmrs>>=5;O$xZD)qn5{I z#eCUS{_?=-&3djc*StEm^40L;mt)N*?{_U<)5baNWskJ>ujXa!>M1kQnpc^q?GvB4 z_sQmKJIz&XrWTJdTHL!?ZB^B_h2>Uiz51TXnA9n8+~KTOOScc1seVcCV@mf!wzj^W zZF8=a2%elsxOx0hlaswCc^V{2&uVYny}M}IpErWF4|m+SR~9+kW!$(0{og0>ulYo( z-Y?XfQdV94=tfoNW$GtV7B#C6kSt2QF=T+Jli}Gzv%}wPh&|G-*Wib%vj(?{+&-?v z;%tVW&7jS`+T~LR@%KcPi~&!x=glrDc;vjcOG#ds>hLx9Qzxy9iN2F` zQtT3xw8*b_>YSSVv{Bc>-)6XPpQ%rt-`llrP5T+%8LNg*&>CZyFYz3E>aV>c=I9;Y zaX>e?RrQgqJ=5=RoZ7Pd<%`gZs`{z>FKY3!C!0-gm(p76+qu=VB~DihJ&*fqf`;ePtZ~@@PrfRl(DH*HXg$*P5GW`Mhu$ zR4{bns<^0Yn!ep;hNVtRIXm1j(&b9i6YEwiFwp6x zb~v*Al9W|sUC=29?|XU)1?D66TGkwsk_+!tCSH8q{OjlIRkLqLc*smv$9&4&VSM~! z{HqpCQhYR?=03FxEP4FWwo{tdszY<#TU8cs?l-x!T|-aa1e27+s+1L{<4Ws}em|i( z@W6I^yR4`q`!g5MoO+`3;6EP@IeTK|s%N_V&x1JKH|pHlnbWgoj!E*$r5`q(iisJa zb920Ax4Y&Cj)vr@zfbeE(APdNsIPwKhu8aeb~zm9Tze_rBSiCZ?ozk%9?jDHlWM+a znO_n#yx!AtNc=c$m+*rp%HHG^eSdbx#%9%*c>Yw|)oBOp`%DU5>@???eJ8Ec-@X=k z#Wab!oISXuzv_%+JEz>fv!0f%Jb6QB@2S%R*E#RNg?*1&Uia7$CShIfN{e;c#JIL} z-FYrOWBX9CzSo)SoqUZRjWIZU^<_I-jh)8hd>kJPTedn_Z{fC3Cw{q7aQPSeWj z%ew5B^}=7)>waDrZ}@rL-aooIlGKl`cd@kpwha`HgnQrrFGSl7g{DNt)J_hyLE5lx)>um!TrW{#rG!~ z*C+2h_d0slk9FxRgTw90Z~jK)Uh_Shc||ZO z)kHvK1in4@4g0tK3JODnj}niw1#j6R<8h$iYIIkW90f@T?~^Uk)P~mwcS@eV`TpF= zuZw+K8m!hV$pRixzU5pzaJ?sB%MFDa#N=N>HMY#ol|)kHc=mL~D1@h7yX-lclP1>Q zsix@faiA|d2sZ|Zv)g9x%!_DvU%bIi(D6q%T@8>?7{N4TevvzS+*{*EQ#JR8R^@T$j=C|*R zv*7f$*m6Dz5E$hlGF4Z^Y6po!aITsWd zUgN>UO}Cg^+&ua<$mzb8cwdWs&&+-LhZRgSv+BpE8(QT;&rFsNo|-ky%+5H?z4rS3 z;RmKOR&$OgvYIBsmYLQ#4HXRT$sgDis`2aN)1za@W=p!9p3{{?+h@M)HFSI=YTF=dEg-w%NxA zlIL5-nfLvi4otaf^vpe^`4q35;Ug}I%*PGEV&+5n6-kRU&nV`UclEA5@&CPhP*l#v zM#xg+N>2UHgS3?g1BuT^Z}X_BR*qh|d93%)^W3eIHyaHcg$LG_`fi8a_}u-_e&+ZK zo)pVpeG^5oxnKQvzPn8RMTqqEOKJ}Gs_xv(aDtlfG%2$02fA|j^2vMJS4Uj?>MZuV z_M7K0hLYC%i0LrgEhV^mL0w8KwVl_)DVG4fdV3ku;4Q_2$wkN;$-a;_p_Vm0);v z|9(jw?azDD`7Ko=Y{| zi`8bk=pRI#{m^r(PS5mh`>MRjd5gTLdv92k>?kHCL#{Hy=fHh~10J*?B-;-cYWs)( zJ|Yva`+c&`*ORBphh{U5obW!E&Dgl@YA!>PP4t(xS8qR#zt0~euCs3WNR%YR5nJ_` z(vNAk|Fe@m{k{t8)|G;>8tJBS7Um3QyFSsapPX~^m}qgYGLi~&Z$5j)FnD*~*?F(T zuVR$K1{I^q&U10G4{aaR@^9;?{Qg*vNi&DQbLaNvA4?7O0uH?nrhIgSDQBC1t;P)w z{Q7Lc&rpV2dhW>sv)PUA8ZboIDy`_Dp(N z$n>3>>XgF$n4TJmtZ}uH^)-S=te5D^E8Z0a(y)v_T)!dvE>lW>T9lyRsEx2)-Ei=v zXQN_Sq_2#=yyBJ}rSxApKD_A7EFHh=xLc=+Je_&krk9VZZ+Wf2^KJF08rFGl!Z^xsF@ec3tJ zCRFH|_rlT33mN-+cgjU|ZxdQK<9H%ZIM-IXZ9=*Hle`BD%k>#6V-cV9?IsB}taOH- z(>pS2pA)802H&W;o!NghHJp@2^fJ-gJNNnf&MPZdo9T{Qv$L^#+LS+|%8u_}_?17j z)jEqWr9OB3-aYRKyW{%N4Ye8~4J-64Wyd(2n}ff-5Py~_ySjEogO>O@-+H%8vG27k z%Y-tO%X|E2zW*lg{>arr<6C2?Uu}3vyU@KY_~KkOgZVe(8@+2%&H{6UVzHgB0+lhV zJuR!0nrqoa{_85*YqJ$%Ej}w6d}UF-7M1#k%CcvDj2CV?>fReHOu4_RSHu@JwlW(> zZNGAFIN56n{$gN^`^{cXn6l8}_1{yW5Ra4_n9F zypT=*$>TsOUCEBplwW@Cx-<6@hYhbQCyZ`$@U5T;)26*uJ+(IZSSGX~j4Q9F@QM!8 z(@!_d_OutgSj_7UHEgmQHB5;uPd+(xUYlI$`OP7&SqWm#E{^Fyt30Z>3`VUdlPV;v zZ}>AF6ZhToJePW4Z(8D3p7kZFURhH8ukG?{;r{HzuIq8xw>4B8bGOlni|`y!xKJq* zl{KMv_x_#u0NDj6x`P4{s~yyRWxqv?qb#3oKjfw#@8%m7Zt3pxd{vGs_*nmu>i9=5 zz7ZLWS*+YUG`DS6bs=HPcbDq2*^#}>x4z4tdj7=NONidVlCRgqY(R)EDPwP0>hFOT zBB!vMw<0U!{q8>CX*%(~Mqb^aPtWBE;b)QN!k-ytt5!4tq~bY`jq?dA=Mr0gl`1e~ z2a?k5xxrMOXI-msoclu3`Nx45lVcuUi>v!_%HQS3$Md;IV@;*fZ?j6q4Q9|4?X|k# z>mk_^mc`Rt)p?F7^?|&tdGd~$#g2tvU%8$Zq>ZbXCCh!*ae4hLDSDP=h&);U_-$I_ ztI@^gCtrTa&}`dUyfaJ3b>~m^^$SxA0iCj(^BLQFnr{y}tLc6ChRbweUWYjOc;-6BU*49tyhU(jg(^{t(bXv}EH^8vX27daR8xN1s5>I$ zDLw7YZyFz8_*rRk9}8K!Iubhl#>LS#D$*pXRIW7cWN;z(miuiDH^g3)rJuhfqx~f5 zaMe$eo3UH1EL$fl&mHO~Vp%R9oo12$D8s%$zxXqtX8-laOcUIly6xGsg94H{j`o4 zKh*nmTV7e-+U9VC-uNV8)pWws{e%h^)4an8^jEqGzjFH;<;&7tQ+@bNrf4aIX!`IY z7rl=7cdT#h)l;49i7njUMGH5`67sJ5clb`$nQWnyIl)?Pnd+K+ne@{`mz!1UZK{{~ zUvJm0_YNF>qZM~GL&4ZvRAlF&mAfT7q^}#L2Q<4C$fnl6j2B6xXpLNMvHIPwA9-gb zr&>;R*Xz`EY4*WP=7C%Vt>(eN&p{{Z+TQ!!i#ST(ec(it?T16}M7OGBJQpP% zd{Yv#+7Mgzs`fS$^~3{qGU})NmAmVD!@z|wepjyn_Md@pn zCx1v>kmdeBamvT`@jLCmr@!cNx;qSI*{6jl8uH&hk;&!wSf5IZHk7H z3{Ll~W9QWd?Xu%~uUeOT3p7fOu1Bi}?$9KlQmwNwy}7nypi=U4?wM?Y^CL@&lDexD z9dkR_dhE%(j!duSn!D&tjyh$~-X>Q~mB~29;1T_FR8OO=H&ZQT`z~swp?z*COh+;m zs5*p*Pv(%#s6?uc-DSOV;ERuxAH9}pCiAk9e#vF)lCiYVONlu~ySR=%|28VSb&UDl z9wS+6w*%d;7G2&=`sB5HvMlfNzxd;Hp&-5N7^m*GRPK`d77M~7Lq8MLnezmd8w(!Z zBAAnop_7!)Nb7ZrUpwPJYZS%968b1`Vk_w(nZ%FWe9y}k(&x`Sen1-NdiQmIfun`x z6RNxi+RcH}-NoN|=Q2l9ZJTn<=_|Vy)9=PE@^xsFZU03tHF)JsKGl!P{O{Z5PM`WY z7Mtk_jMX_-wulk z5bqokv5wT^5%v?Ezr0w+cPCJb=wzwGf=9aFLAiWOrvnH1eJ`X_&9SsP{N$xD;gv{; zFZ^L)^_|8$>}Jv3vPESdS;KDxwF@y19OBrHW?g0Cq;a;Fy)GctOhu6G(-F)u)BT>j zLt~BNceb>O5V6?NZ(AQtn9}MuCUv$BoFJ)_D^Pm((M_l5W*r&Hy-@#W_XhjC_72#k zGA{4;VcJu);MT-*`0nI*8ddz)ho&sX0!+WI-TH3I^6ves>0n8VV-lt>TLu;+GYC`` zXnOoch)60r37Y8rNoB6wXE>A5BkzCkl=sWT43#$J@+7u4imuPggAWe)ol8_mm?kQg zZ5$7O(WD@;sP3KfAyUpHQi8?F>Nq`F{$~@qzS8&NE@T$FYIi2?Oh{der}|vW8Fb0> zz#8|*+XM=?jIF|d%pCMI`ms{bHz^Z6u;N?fV0kQM*}=u&oLTz|4YBkO6CXItZYu5Q zFLXE6JTkg1#H5e^cpT%qyM_iYtq2B`^Vg_c-bkm)Twijst|idW8%QK;_GloU%qrtG z-TUF`^E>iy%ad0XOwaRvlQ8LJ<;jhbeyr$9@0KMz-&97)E7Yy8^yW=Y=!|kzSH=4* z9S?s;-StYXh0A(ZgJ+d#1z9qw53k!zAM$Ys7O>LW^K*ZDj{AD8#;?N-&-AN%!}PU% zGz~nSWxAaxFU>#al_!0yeEr^ zP|Zt{58an)&h0sBpqY}f`g`$!VsD7_k@V!6VylL8&wqdY5p%Xc?N!+ABc^_<9m2_4 zag}XX+IybA-(nD%(6B{u+Ec)vY}?t1?Or0KHLF$H+{Q1s-!_<-ocId;42D z$DsDR>XXZl$E0XRm=(R4&K*)LN#}6x9i(K?9*;>i9bf4gRl5Eq1_B8d zp?x&t=gi-<^E*g&iAeYm6t-y_q`P};TRz)ziim8?YshfhaF?^jiG z5}PWFt9v-A;#!?Gb0-o3NDIW<}eB>xMR>%+?_N?lyvQ+EWg++GyjjDZ6|%Ksvm5#$=W+w z6w$vY$&Z1P#35*VedNczCeG?9wHf(0(Lmx}d25Z`Sv0|~yHXiFr88f$Wb*uO=;!R_BacK+MCLzE zD}KR!^jd1vZ?Di=@=~T=@`u?4HSe}dxU(-DrT$cqG4Y7(tT@}Ju~5wm)lFfEZ6;^m zehkQBNm}5WxmNy^#9!i}=iaHv42367nJu{^HNqs91m~~43x ztZ$yNmetn25M_VL!b#rJtms4=y#M--{|E?)NvT++J@+29>B&)z6EJmizPVs+S1U+h zao~4%&h;km##^g8ieDeUfAAzB?ON5dUy=KdN-e!P@@RbCzUT06;(Cr6hG>$wZM!@_ zPH`7HZTI14++AqKUwFXD*;3jegB|@N zLgHk~TRHWrJ916#Mvb|CZVDq2PyNkRD3C#M8@J zc>Ur7W!s=Ou4~Ib%=|CM(7jL7d!uh&5@B;yN$uCn^FD*!Cog1#B>IN;4j(YcQ7pC60(eOwWp zU~(G1qBzfJ&&*qU$L^U4MK(jl-k(}0PSv%$vc~mS{dRe)`;BEVQ#fE-{o#ULR65+A z(}qUpJhJu_tB@ZYzmXi-m@tV>3_MyxUY0b=(u=dS%9ENM+O*>>KF z5eSsMD=%u_vQ}06sK0pfVzt(>dFpwcG;gVhP6MW#nCxvm1Q&gFRrnp|thu#Q(^0?T z+V6rP&y1}D@s9KHuU=WkkNGQB%+)2nqI{FOlhclW@?f^Fqsl;5zFuFz%60W~Ct_^t z4rexS6Xn-?=u|H57^zddo6&jBv}vmaFE6bh*)wi2w!Mdb+VZQ?kc(-YcOWx=L1uOR zP-C^1AOE$8)QQRmr!FyOUJW``{qUSk{ho=4HqNcFU49}I->#-uQPI6h+e2z2y6?2J z5_j>-{3B0yG96RN?m2U|DdFOx-J#2~-eHb*rjJ}`T^f8z3G0Jb*DJ!OY}C@09kn6k=Pbb0yIvX+d8B4#i5!d%?GUXewE!Lp+ z*@$n>@TJ*}7*h!q9o&es6x&+NMy$su<|;PgT^*Krd?V&u8Dx#<+aSn^D{t8wn9_ z+KursBbqF)jI){U_etT?}QW4svD=ejn=j4I1qb7S22SnV%tj9qEl z3!#niYiMmR-55iS9GP<);}|;?I=nHKnM&~=HpbIbx3P9(Oq&B`MmNUQVbs)lV{9Lm z7Itln@6W@(%q81u<;@1c!x-|bpgxH0wz zG_H+qjDLdh{x9!Na5rtMu41b%^>5oZHS)IWR^Jzwwkxw9XK#Hkv!lP3ZSi$$dQ8KE z+FPb%dE_e<#4%FTdli{zM0VKk^}R*R@!h$fCz7*X`esg((94R(-DDL>lBdb35?)&{ z%azEUXs&o)dz#PP{AU8MhtBkBmw1j{vmw2F5+TDk_HV`1*Bi`tf5Ki}&xJ{(A7h@XM1Y3Y7^797}0w z8qwFTja{s&im!h5%!%pOuMc^Vk;kp}??0`3^r(%Dl$1N+($e>{Z{GOm9XWFO+oMP4 zj*O4Dk`Kg!M%Hr7GAkBFsrLuw3d=`@rSy4nyQ4vb>ptCXYZaqy>*k9SN+83=%BrTfWduV zUz+b48tIqY+MaJ*C-V2+S>UlEiHc5t*tNk=H_b0v$N0At*y<}%E|e6tE$Fn3JYJl=jTV=qM#5+ zwX!m~m5`9w%fsXMS2eX1c}Yo^vgBm0(ag+Ki|5Wg`2FyqewDPeXJA+u$>WF!sh5d~ zY)WEcSBbxWpC5kt()3kHiL+B*-xE_dHd#R`Dptnz^>K>D#b37V?UkqBzV)KrxsySJ zhGxIO^mNS!L&J&(_wS3mk(UoRMo)j}!t2+O3mF-j-tO){6^|dkk!xnw@L6BK?7*Hq zLQxJ5pHhsBs$#sn_OzWno8&Pt@bD5lySzIe-}#*Ia0#D)fE`UIPX_bO&enI7mRd{F z((m6apNwr<^N!^x>Mrl(h` zUtOJirK6*KE;m;$-NfW|=H0u;(xRdci;j(T1i88{2z~zCDc8^tq$4VN<(aK*UvNkW zfmvr~LHn6A2_soq%0)IdJ*!$;x!b;dTjcukrR9mac~j_(8{a1L@)Q|JNvS%^%IpGM zT)x_Iaj9H8bV!eriAiieJ^ey`QIW&By1M%u6BEsDgM+CFj*b&-EG#DmPo0V$C@(km z4+y(k==LriaGH)rKM>Rx1}S zccC@eUbXgM`i$Mepe?eyg2 zjD3}r=7&Fh>aLTO^)0=8xwS?{#ydYYme={$7XbhBz+Vmc69NA$;C~DF?*aZM!2c8Qj|2V+z&{cAO9TJg zz<)RJp921kz<&qup9cOG!2cKU{{s9gf&UWlj|To1fqymdX9E6tz~2h^>jHln;7}5afqyda-wXVcfWIT~KLq>_0{=qbKMVZVfd3ERuL}H)f&V+;e-rqh z0RHyC|32{l4*V}~_y_*{z~2b?vjG1F;6DTWD}et=;NJ`UPXYfS;Qt=@PXK=v;BO85 z`GEg8@TUX*wZOj{_-g|Hd%(X1_)`J@t-#+4_ThrMd1G%_*Vh{K;Zuv z_`d}HO2D5O_zwgBSHRy1_?rTMLEz5_{3(FHE$}}L{Aq!|2=EsG{vUw<1K|G#_#XrQ z7l8i)@b?D(ioib?_2v{CR+0)GbJ-wFH!fWIB^ zzXtp{f&V=4uLu6;fIkQDcLV+jz@H8H4+8%I;O`Io{eXWs@RtStuYrFy@b3ZsB*32l z`0oP#4}rfS@LvJ`t-${-@Ye?Z+`#`i@D~IAG{FBU@D~RDFM$75;C~1BX8`{a;Li^H zBY;0E@K*-@>%iXy_}>8jVZeVG_df?v&{0{^FI^bUl{A+-JKJa%2{?x#~8TkJM z{$0Rd9Qd08e>dRo4E%2b{{`T04gBqZe=YDA1pWlT-vam_0RF#$e>d>Y0shy4e-rTc z2LABicKa6aUj_a;z+Vyge+B-Jf&Y8p{{Z+u0saZVKMnX_1O8RO{~7TA1^gp{|9;?q z6!=R4|0Ur62KXNV{*QqFIPjkb{`SDX2lyWb{=0!cG4QVk{v5!+2KX}o|7hS(0{r8E z|2E*i3;25i|Bt|b3ixva|3cvJ1pK!He;?q_5BwQ{|6bsK1^DX%{}kY_4*Vs6e;4q7 z3jBG2|0wVm0RFzfUjz96>p2(r&j5c`;Qtf&7Xbf@z~2h^Hv|6);C~GGs{sGgz+W5q zw*dcE;6Dfavw{B_@RtMrs=!|u`1=8W1>nB|{0V`-2k=(|{*u5y8Te-c|8v0qA@G+5 z{$apB0{ABae=*?y9r(Wl{w2V_5BReIe=6X=4*VB^e>?Dh3;cHie;VLF4g3v(|9#*u z5B%wY|7+l%0sP&8|8d}N2K@Db{~qA)0Q`-BzZdX73;YLwKRfW}1ODN_KLGfj1pc$Y zzZCe>0{%D)2u6{ELDACE%|F z{E2}758zJ${EdOX2=KoE{GS7Vf8hTL_%j3lB;fx6_%8$h7~oG0{4IfhFYpfq{`Y`? zJn-KK{BHyQN#Oqu_-g`x9^gL={DXmiD)7Gp{K_&)>wzkq)v z@ZS&oj{<)w;J*a?-vIw3!2c2O9|!*Pz~3JD_W=LHz<)RJCkFoYz@G#7*8qP8;2#bA zNq~PG@ZSdfcL9G-;QtZ$PXT{!;9m&*oq+##;O_(c`GG$p@ZSskuK<5t;GY8g)q%eR z@b3ctPk}!#@E-;K0>IxF_-g?FfB6UgGr*q}`2Pg{1;GCz@V5f~&A`6`_#XrQD!~6V z@Ye?ZEx^AO_|F0VY~a5J{N;eZD)1Ku{(it;0r;-~e?s8z0sPf~za;Qa2L74A{~Yjt z2>hjie;DwO0RD-or zf&VPq_?rO# zyTCsR_>Td9SK$8{_%{H5QQ&V2{6m0$C-6T5{Ih_+4e-|j{@;NA7vOIW{BHpNJm60X z{L6s93-IRx{)d1+6Yx(5{zbsQ4){+1|3TpI2>e-q|0&>K4*Y|Fe<1L`3j9w1|6<^O z3HU1ke;EX3HW~i{>#8W2KZA0e@o!s3;aWY z|2^Oz5B&E5|J%TS68OIZ{+hs_2lx*I|6t&s3jFT?e{$eo3H(0+e_7yv8TiWp|5)IE z5cpF9|9s$o9{9fi{&Z>?-_B{6%edF1(KALHaEmuv=Sa*eB(mgaujWP)jh@n66=eNk zd;Rg<>u&OJ%37@;K;GtnM;%rvnn`ixkDaGse zy#u5x+Lx8C?)jwKq*oIUfm6UAqv*GRl{ud1)KZR3TAyu|V9-TK`9+ zq8$sjc?1g|RdSSB5fSQn)6E=UY6*-ea?SXg*f{GI+f zQRC2&6wSp4LLUN2oTh%H9h>&qkv(T5y*1h1;2hPJ@|8fg6UVm>M(2^77(7}2NwqlS z{9O|L*YT+_8#Tc`M?CFV!DuQTsB_pyUV&3Y-x*wXbb zx1GNjFMjAKXMqLTPRh?0BHN>9i<0if?>$>v<@tbhz}WJXvo0OuE+(?F$dBWEDOGYK z?2^p%dw1<1-%jzkK4{^2X34>ZtbMe8d*&~k-sbe=#bE60FC&kK$@>YGUVgu(S1WNz z{)qe9#p}FsK?8Lh&6j`9l$g$sl{O6=?xylHm6c|{Ps2)G=X;A&#c%oei*{=g$uLS zpHaU3s!go-_k*V3qdVTmw0M0_=NY>3Xn*LlzO>56Qe%nY{NxcXYVmIjtxQh3pRc9~ z`7u%26K=3{?9lfoHQp7T`pIsQt(I8_JMEtC;%bqI&27lI#Xxn%G-2ZA3yJ>iF8kkp zW6-%m@GNdRGw!U9`IV?o?w{7PNn2jBeVW)k*Bk%2?A=j2mG|3|zjV3f^FAp|JA9Z! zicj#~qq?d~4@5*?o^ecX^8T6M8a}eSCqq~zhIoFdZ(4>z?X8EvkGu3CU8>hr^XKHw zt7g9$CXOB(EpAKN8(?+1l!gD5$2s$p#9s^&^+kdS92{@29+KIyrps*cRK2}FLRgDx zl18!1HrTj3?#9kk(NNlDojZ~=Q?uMz??f7npUsRjeUL0ryrDDPo*?D0py~RKmZ4I# z-r>vJC5?;OzYJ;Zjt~3y%e#0jK507f`to!BBb;J}(hsYz)iBfT{_0{<}JuMGSxfPW0|{{Z|6f&VVx{}A}s0sqgyKMMFy z0slVWe-QY00DoTKFAw~!fWHgyw+8-$z`q3e(*yrYz`qFiGXQ^Y;BN%{6@dQ;@b?A& zOu+vh@Ye?Zdw~CK;QtKxa{&L3!2bg9=L7!RfIkuNj|2XXfd2yUF9iN&z+Vse9{~Ol z!2c`oPX+#Ofqy6PX950!z&{cArvQH;;O_+dj{*N|;J+34p9B6Y!2dY#&jbD^fqyaZ zCjtI;z<(Y1a{~YS!2b>KcLx6Dz&`-^ivj;GYZp&j9~5 z;I9Sz_W^%y;BN-}*?@l=@D~UE1;C#Y__qW9B;bD*_&)&tmcXA5_>%$uao{fp{F#CO z4&eV7_&*2!4Zz+E{M~?m7Vv)x{3U>Y2JpWE z{BHvP?ZE#V@FxKNnZVy1_`3stQsDmy`1b<;cfkKW@b3ctPk{en;4cXLtAM{K@J|Q+ z`M`fS@K*u;OTeE3_zM945a6#0{LcgbVcUHp;Lis9ErEXn@V^86U4j1z;BOE7&jSC)z&{uGivxcZ;C~qS3j==- z;O`9l^MHQ}@IL_jF9ZKyz~2V=F9ZJ$;7z@D~IAoxuMX@b3lw>A?Ry@IM6nEr9=L;2#bA?*ji?;Li&DPXT{M;9my( z`GEfj@TUj<`2Pg{^T2-q z`1=8WcHmD9{5gUDao{fm{8NFy0Pq(C{_4Pg6!_Bt|L4G83HX}<|6t%R1^oGee>m_z z1N_^7{{!H^1NeIZ{~_QX3j8a9ef>e z|5)IE3;3G?{};gD1^6=n|7XBI4)|XI{-1z<3-F%+{_(*7DDdA7{M~?mA@Jt_{`Y|Y zCE))O_%{LnR^Z9Ow{tm$Z6!2#O{+hsl3iuZT|61TL3jBG1 zedC5zaH?v2>g!%e=^{I0r<}X|9IeE1^fqqzccXP z1^gp{e+uwt2mX73|90RX1pG^Y|32V95B!~ge=P9#0R9BP{~GYW1pL>4zZ~%A0RA(; ze+>8^2L7hN|32`q1O6(&{{`@00sg$eKMD930)Kzt{|fj&1^!oo|2^P83H*No|F6LR z7VsAa{>s2#4EQ$z|M$TEJMg~&{GS2;$G~44_`3jqL*VZY{6m0$5AZ(*{A+-}KJae^ z{++;|3;5>(e+J;60Q@C@|9;@F1N`HF|5@N41^m~6|4ZP%8~A?){&v7W8TjV||1{t) z1^gcY{|CVT4Dj~`{^7ts1NajI|7qa=7Wm%<{;I%#4)|vS|7hUf2K=pnKR@s{2mS`Y zKN$Gm2L3yMzXkB`2mVyRzYF+x1OHUuzYP3ofPWV7HwOMpz+Vygw*&tL;7<$u>w*6g z@c#w;j{|>s;Qs{pUk3i1!2coeX9oTsfxjg1CkOtSz<($3F9QBrz`q&z3ju!{;9t7& z%=v%&Pw*q|`!9?-^#0YX$^(~rzQUy=_`Kj4uH6<-Tsi9~E-xL1&-dX{>Cd?Gksy3| zFfKL1rz!Y!1fQ~o;Op^e3O*gdrwk;x@7bVo#kEr~#BHAze^OYzAijRY1XquJuOe3O ziLb}<;YdCO$>-zq?oznz?ZEHPP!^ZRzMt`#ATAGoq4HnFu2{HP4 z?qBUl@$HRHtN!a>)7bZ$;Pdj`2LCD_!RIG>aCugIUiv*Q9x2+m{v%zueAOy0{}pS$ z6}SJC8@PH_e7+UQkKps3?6~$S8-Icm{xhP(<*{E>9`(na-@k5Vp#F#{E|0}~-5i(4 zzV{M7IQ-XksQ&f;En9Su@q70gSAQ5^kB!fko4DgO48_&6;OkTH^Pv>~y)jt63duJh z`Bo&~gXBl>d2Ijr`2Az~Dtvwfizj}(u=-Yfy@@XFcz5BC*ER{4=fLMR6mWTL{!~%m z#tUD)C1o2f-fC|6@x|wjko;97?}_BYk$eh1FMuBpO8gGZPUDUTo3C14xI7cS{shwg z8q$7V2G<_*fyHCXE!^>Ac`78&isS{6yfi+4Ssk~1Z2p93;quh@j&cVok!1IJDbpQa?>>SgeGEXA6o;I9+d`iZ4j{Z;(; z3S#4rrC9w4DQ^41_yR1&>Ur?}$?*kPiq%&|;QF)S3$PTcpA^T3eGN-?fp2w#AuSpC1Z zzsdUWr}g+SW1HWC_DAcn^#^~s-U8pv@vpwpnYa|KufjVv!l!6G*1xI{*ZUI~AHiI)Gj`OE*%KjH$;4|cshGK^b~|J&`etK#}o;ydvFVSo2ce4722 z+qr@G9~t_y5tAgG_K0X{h!i*dEVG|GLiek=|9}R=yBZn zW8ZK573q)dXR|a7nLpqDu>WCX{%HT<`R5Abj(;ou0GI!8|I^6$|NO)L&B*i2mOniH zQe^yy|FC~B;)nDP`wNTU;)h)~cK%`iZshz0|FFN;G2H&K`vbK<>@S1NU#dUsKZ+bb z?H~5v?Eb^(zuZn7GJf(%KhMqJ;me&4Ho4Hwx0&%_&NWyKeB#^{b7If{+I{p z|4-L1LTTLjW7kguWIca`%rES`vD5{r--FDDW@LV0{jgLJ|K0&RPAtXh|0(sP!`+|S z;2UD;7BXBpc3s3$^!bE!JFXqJeqrf9?H8+O-Gl3Ii!Z=Z&rSA^Imh}h{4A675+Ka9Wh|FA#9|M2)Jk@VK{$XAE$8?EQ?25-v}U-+Yz5{=Lh%A+8?VzSFb_Im1J_ zE7JIS9yMG&_FSni%eAFa=aKIS{{1I4{`!K>d+hlCU#IRzaL0$;-}xf>@uRqU?EXbb z5|_su>Wbm=*!zJ%q`wJLkDd>^6|O%$zM?n?m&e|xqWSkoJ!2TIJuALHniogvb&-5~ zI<7zV{n%y|xcnjf_LysMc|m->G7^_(!{-@Z;qn6bd?NC`_+Tuqo*iF*7RfvJ;OcSz z$2a=l5?AE@!y9=XlR@q;m5}^dB>%r{`d|ONg=`ar95Nplk>k0B9M26T&+3D_J{aNC6nr{@Pg#BO_4qUe zpN`;DRzG|_K25==BlwinA7778Q}F2sK4ra)ug9k;_;duHvIgMm@o5S^9l@upckuQ2 zGzFiI;8WH>d_6u*!KWkmlokJe6CHOf&yUO>W@J6IMbO#NKK!OVF24uA zgZoanJR?4Df?OX8k^UUW^`r~Ae>sTM@4kfFKK6Of0VID6S$_sxaP6`8Y+lIuVR6Ay z^!`i>iI+Z-FT~G(Y`r~o9=AU%KF1M{3CQ(u4SAj_yM^n|gg>BWB%TJy`w!HwB{H7u z+PLju@2i^e{xH9`Nc_~1^$3d#mVU!~#jYO;$oUl@pHC_y^(M&vEs(qol0S&_w@2!) zBk^}b^4>^(4LSZ#NPY&%uj1Di?E2V&U!So1+v~{js3OOE4#^WC<6nlvs~pMSM7FPu zoKFCfXF|MT|F7eRjU$%UX#D4TBaW<3YRGyRg{*&b$okWVJYQQN{Uee2 zi>`m~k>e>r<|EHZ-0@=f<2}gpIJzF9>*Z@?d(z1D>7G4q``GnJ6S;n%>t8#vy|c*n zrjdAzBj+cGthX}Adin!tKY^U@Z)E%EdTxjuKYG2ULe}>zWPC3m$FuGHf6kX>+vITlvCqXskn!|E_9utLhZK2!s>gr+&y1hYG|2X`dA?biB8)p8?D{KyUrg-Cri5`SA{`}Rn_ z7-^5*-<(AJ>mu>-LB_KNxju&>_3=nP1Ia%{@@q&uP9fv<85y5%Nd6fTpYuq65@h|* zN9r#j`IpG{Dv{%@LgM)fX-|Nx2X#oj2@+o`B>x@pNP%o`FOuJET|mpR_fD7b$3 zG!J73_A~wP0EiW z@9D7iFHhl)2m5(3?75?9lk&PVxc1okjad;wrI8c`x!F6Kl_Jj@v%=^NrXz$ZS&Hc^TIpdmhHx6WZd+vG;ITxz1Hw zIrjMr_Bn+8CgmiKxc1on6xKf38CQ-y=VSK+&o?O#al^I8p2M;B5uUhm?0FZv4&`l9 zuHuJlkKHd|?c46)%J<>nW94kf&vRn)NP3g<)w{Uuu;RCawRgLZD`&%(Q{vOaP0H=! zaqY49gjoCdL|i!szCSh}D>f;oeSmAviL^I-ge&Jl%6T$z<@@pF*t|ZoN%@CtTzhV$ z{i!@$IS;-Zo9_;rl*YLNI4dV*h{$f*t+6{-_S@T zuAB{jLe%(jaU>4d`4%|g+GFbk*8T~yeXN||-v4>~aY#G~@cXro#kI$r;fuHIa>bQn zh?HadZQrE4f0OdDP0D9BDgUua`T8d1q{!zF*l|*CQqH(ZImagD{F{`EY*Kz= zlXAsP%GEY0*V&}pWRr61P0AfNDZjNzx&J2RVVji4Y*L=ONqP1rrO%3T!V2P{8)!qu=_zz1iDwexDG1sBByzqL$oNJf^CbW|zigy_ z5m^snkp0yl<0Fp5k5m_T{@8lKh~)c``RV%z*B-l1XG7vm=!L7t?q?4o>w{w^t{%Ie zZ$O?0+(U8o*w0tYR^##l_<_hm_Rm#^t3Qmd7k-Y*AHe7Nk@HbP&YwIG*B-l1-G>~1 z3=;nXNP9Wt{B)3fJmS>}SugtiaQnyZqo$DO5j$jiiAa11@$b7S@#pJ@wC7I3Z6A9+ z(?!-70()FN_WWFg9IrqQt{!`yQbD#ST7j!Sh~NKRdyw-PLgxQ&f874CcppcOuN2w;W5hQ#67MQx|7ytjNgzI0k^bk9{jVU$ zON4(viM{WrG{+r3_CDp^BU~Qyh#!W3Clko|6dl2}$KFT%yWuAPKz~1jR|wZ1yWhv& z$CwG?%CY-jY;)-EM-YhO+F|dju=|>eoAgJ2KjIOxzG3@8Q|vg<-;elj_mB1OK*s-1 z<42B+A1QMF=y9WYN~FCfvVNiMu{`!%_Y+ya|Md8`$>PQfdr$VK{R@%t`qTcO{vrO@ zy0zK-Gep*xLgf07&EL)D-!A0*%aHz9yf;hfk@5d;`(yKu`G2_oWyC+ae=L5RoxeG< zKB4`w=ZMYXZ;izNPx}kt-%DWcJ<$Hx_-(fTN@V_3|Ka$lBJ1@VWP6*9J2rkR$ovic zFa2WM{fJy&{*T}Z|pj_ z+4%b)=YLxgx1M32PoT$*=6Mi*X#anT9~qWev{n6t^^XWe__b{`_uiS zzn_E7e{BDo`B_BP4^HHKHj5uN4#XF5Ua-$yp5X7(u+M?9@!xF!QpkM!)Be`T{72*W zPy6>r?#Go84_N=r#!neJezgBT#V-L_@6rDMG=8ooxcFo5C(!=bIBk~RK;nn?|EJ@> zgY-X-#Ba0x{<|ETDE}__`;Wg7_#1)05%?Q{zY+Kwfxi*>8-c$O_#1)05%?Q{zY+Kw zfxi*>8-c$O_#1)05%?Q{zY+Kwfxi*>8-c$O_#1)05%?Q{zY+NJ2w*=4atZ%AG?sTE zI3GTkx9c#iU*#F*+!-SY)}3TLJOP!QoScl@=j^x#L|#oeEE8tByt7Vr6i?VQ`{UV< zFS@IdRWu?`*6-M3FkRG+VG2PnkSfh=2lNVDH;}XX;qODQX)EZdf#^m`%0*St%j!Ef4J+o(0{>&W+amisdWiYQmnw?S=!WIi}%`{Z* z5?gwuhZm`O4ob5R+BV8w@3_Kao2n^lL}XLaSTcCD(SJH!wa@ICx2_tWI_-Js9fW)h zMC_TKvb)c_5fHn{u(T4A@l~X%7wukKcbiL$RqY>HOk4PQVqH2iR$}S$U!VG$$fa!yA=XcCO=|i~hJ@Ho4h_9G8jZ6V z&3cvI;l0RsQ#hx+W^%S}z{uHjX{15=M1Ra=OUAY}jq;EjVWQ_6!}cnsS8TI;yJLPC zc6Z!wdSa^=)%bb8#)K;4oHFGYa8Gi=&n8G_On2~Dd*>N!Cm1s`|btxzrB`h zyv+AT$LEZ{kn6yO@ya}s?LC7^HWz)Hxh{n4Ea4!Fy{1esBl?k#S1Vq#EKT5Z)K9-B zP04Z%gkzy5ulXV~-0T;RBtFi$H*?=+3!5XGWzb;_H35#qQ})z^r{*;{D_jq}8NJSN zXN}rVXVIDSUG--!oe-*hGU}lfoMAy!ekt1s_PkBnevyn^iPekeitCHsF=4TkgK`GX z3>^BbV>sl|fq!yO zGtx<%;>tUk{qqT{jJ9;}c(?#<&iG+Nnz30EBDUWg!%vms$c0Mi)e9)u^vq;_t7o31 zy`Wi`IY)pEikqC^Osc9dARL~HahU&vgl~p>*!MnyV$Qst(UDdu=z95 ziF>TP@zC45#MZPTb@<91$M;_gjQ1Oyq}`sQF3wJ>S=) zIQQfS92Y}d->x_?_}mI|ec2)yeZeYPoSmldJ+IyFQ?}x^LuL(Y@q%QvEHAeC@7LHp z`O|E{qA;@YmXV_HEu!*B-t~3L$q!ZM#0p!@wm#7LV5SmIeI{G_ld8*aYMS|<1G*mv zvWIMzZV2fvET?yTTN9%gJ}&tBNqTc)qRnaH05P}l)6pLMXAZJ7=@}b&Xc<(n3_lZ* zjH#@CJfOvKDI)o75|!Fyj^M@78@9TfBx7^5MAO94F|@X~c7LJ}mFtaH+Wul-XLv^x z1GV2TiA7RXOWpp&FKiZCD=ZE%ecwr6NzSrNyrz=<^sQTzEF#_h3~2+oPMFN?Gn5zgZ?*Lxe>F-m8nQ{$$%M0M0!oVqZ*vF)r*xCk|m z!48MfD|%+CJ_l7L_#2K97hPH4WnsHTkt448%yrg)xMDbP%1o|!Qt0EOA^8(C0cQ^T z=GGWBuU@w?f7%(w*UsX7Pk_EiyMuE$mpi9$DEl6Bt<{bNqT$oleF;7hET-2+Wg5eT zkGZHV(44-mK5Eb@W%i}<%(bjq?pU{WnMS7XMKsj;d~D9Ut@#@|Dq=~S)Z~BGlm-Qe za9xqv%HbxNyX{=);bG4$ak(lh35;(ep$SK@?v!A|O>q&X(EUtlrQ}iy4N=9{4=q+L zl|2a*X}^&j!NLOj(cg}`m8xPDxf-nYo%OGr7h%PsmjswqtOuz zy>VB!ST-%WsSxP+?V$G5mwwC~U3R14)6J=H>94B4McDJVT>E|0mo8!c0B=})@e{i$ z{$qThx;@6vSwpta$$g1qO-v*;=8~N-BI>AbMyJf*62>~JjYy!p~bk@xZLH%Inve{lSY3~jOktw?K&i5|zpnL}RR9y>NgpG$Y2 zmQ;*Bp_E?2>ap~e)7$^~Sgug7Zm`<_0z*K&zj5PIPFRNvl`5+Yf3^wR6g66>DHzZOe7{z z$jJ|8(tW7`3MNM1P(D4Zo(6~d^O-(v{^-eeGtKl>m|I?N_wmOP`I%xsVzi(YwdjJJ zP~RjQBWIA`$mR$_wpcA<#l>t{D0W=dMP1vN{5p84p8@VJtxb3F7-`L=YEgk&7b`)T366+t7yT5W7k4D7L$XnOS>0DBeph zB4!JsOXnckCL7ybp{=Ww=@IgaFao$$!9*(KvLS*H8CZ2aYVm|@YDW#E$T55#0WwIL z(u7(R5JDm*g?8D{j@MADwAG6pt?%K94XO5|FqDWk8N_@`FnSV(Ua!5>s>~sk;{fyN zT>g*>52m`)lPTydJH2G5Pqbvav;93>jR&vQCY5~Pl0M+w%#RGHRW06HH?#fSD#_r( z>Y;wM+Kqv1Rw+E{;o_n6kj~@uP_;F<(W}JRn9AhW3}%NkA#q5Cq;uUV-JaE#8c62S zz3J|JGPg04Q<{)Y^xoXQSuV`kN?CuFP~;O%hCu`MenFr+$sTk1_G^Eh+3G__sQ73$YHum z1$O<@>2!sB$5&;Q%d%~VvKvkHSb`Kp@wG{z$AoPw?AeR_%`B$M?(Y7dJVlF4ynqRg)2bStsw*^DwiMoW+G=zk=u(A$Y`XUK-t`m+GWVeSha_rWloQ#$nS`s^V!-N_0|+f ztidXm2;1FWq*f>=t42kZ%L*b!_+)8pf(%!)$fKM>mRgvdjS!wv$e0)m)=ROoUg=mF zM6s19C;iu=R?3Ev;~q;CwJuV(!t2X6BDLzvF{&?V_GQMnzC7XcI`dzIihP@PaLDW? zsBWxQ-4Mg+h*a#rz&s?lIFMX-J{Q_*t}=_=$a~Y-TJCk>C+q^|<7Ptk9BfshauQK{ zX)Bf@dNM9UXdc2=x%{Hgz{xRMta5p-P)#V@V#Jj=Eykm4%IL>dNFkza%|9x1T2th1 z=7&7ST@7oBe8J#)5R-b$V~EOF(xUwnh5%N+EmzdP3!<^)M*`|EoAi-Eid@38( zBl>)ShVm(pXN)+KI9;dS!2xsL`Q=&F{`G|#M6pB< zFllr)m|{2!weCvU;(nhhzgNV&Tn3sEwWdO zM3t;tN$gFu$)?y|3o2_{IywA_rMU$!sXxG>v2)R*qh>(@X&m0RDa_q2PHsWs{TeB-7~J&oOc zJ&pNnwl~+9?pH?q!20yaWW>$Yy{R>m%HKCBF*ut$gh6=%yBg7?wqqu@c|-knGJ{X| zcV~Mt{gVQiH<*EJ@1%ByawEC!)MSfH&P3q#jnejH0KxpOZDBH+ShjI>&Zqn|PM%1! z=}Tq$Dcc*wNTQIy=~xQp0o}lY^_fo12^Gv;XC)Mz@^XEIY`r`}bpf zihj;^ug?ysswr4mCL8{rJ6UT2eZlU?7&E=la`}M9>zVeinEn`j)2Qu zcbu_ArC6qvRrw#YME;7EmMPmtR#4Rb3uam-Z29oWt9*UcdA)MvWxoCyGhxg1=A)J= zf7+l5e57Kgyf~JHTyMsaP-S1;@DQSS90|*Ue8${^gwpQHPdYoGCE!<0)k~uF!-~_alGJVU-^g!WWY)}%uoiBag>3!c> zxOap0DPZMaGahs*f243N$G6+=$5zXfS2k2DmVT3H1~g_)$bkHZ!hk#yqF3YOsKI1% z>-rl9Q~f=YDK<%dN#P_4IOFfm46L1;XP{Fsm3Aoa z)QKps&ctKzak9aa%KX!?$t_%Uhw6*r~GE8^p}b?-F@nv zX5{(a!ktRNef}Uq)NRjI+g9A)T0;37=8V51MB^=0pslc9K>!0-qNFbKMA^I-OOT`V zHv6BTw580Ov=G-?hOu6zhu5Y=`Bo+D5 z*a+9r?Pe1K2w0~4cq}r$hWfv=<=AfO2DwY!9f%zfuW4-|54B;CP0c|7uP7Z}kInuW z3l=GpuZFz@)0JX0#}N|;j0smMCS{xb5Ab<%qEs$F*q!a)Fd2;YNQdVPF`3f2-b}ZP z(&ZDObO~8ql+NU{DdL}a4f4&>HSw{Gh;yQzJ^yuO_hEyFH zw~pDDICb2)5?f&&E9>`4)v%Mcm1(xDzFnq!N%R_Bp)68oB7sC3n^#YkA@>%w%IfUG zwFdMMEmJw5`w zSX=^|b}q3WrCDi=E`QwnTS^DJg$SwDL_M~^^*etd0@Tyg_UK(qIUOM>Tq>W)9Jw5ljX*{tkf9M>YA$MV4FWiW z0Bc6_by+(i#cdd)rER6w!*`zQ0r#^)$|lbnEx7SLv)Qf`s>LF%$R`WU+-y|TAPW0& zT54!PwL?cKDnR3NlULIgi!_WI`T>}=V(vlh7Rcrr)XIWu5+49SKFma!CfjE1IGW0D#M=a^u<;GzCtUUQ4jf+FCNAc%xl#DxsQ;| z4f2&@Gw3%F;GXCoN59KfACM-#eSNe6NxpwV;*gEaa%vCcg4-7&AhVWBG6Hk%ZbWGX zlzfdHowY;kxtkF8Lo!eysxy|zcZ{S*`M>ak9`%U&*6^#9PT7EZ!_b_Sq5zR z@mMqYqFL)RjBhyd+~~yn%;uyxg-4C)Ua3?xYa*)ZILsN{14GH-@H|pYc6i71R#y(c zk)}K>z{Ru}M`LFphmdQp^Ud(G-A@ZSLS2}SI*^|)gS6B`7H&hJ9-V5raGzoWq`6+A zmqN;i5x}`z0q6b$A!Q=Hj7~DOh`yn=3&d*Sdapx5Hm!h6QAB5W8=_{LtltZ%9;O?S zjB=f`lsQv2(sO%Qj$_sgLS*yE;{N5{SzlZzrUD=x{193O9647zUIhXnGuK4Ox_=)|+!#D`0XFfUUtSK$HFE>Sx)#*=F>SwmSmMFSAxqS9nA1O}qeW)b zOS0MfO-Luzrz!Q2!}%V9tYXSCgrvNmjs&M6sWh+`5W;!ka9EBYa>zP5?spx~LpehA zYJlJJM5zvhaK8GdhxKCJInLAlEGlc;<$V6H)G7)M^?CyQIqpkchXAfK>0M1K?tZ$C zzEjW1J}kjwbfURnpPIhuWm+k_%bHN!JA6`$Sc~zEl-(yIOHEGHa~jFG-nfplaAVKl zs!3VK9M|W`mznJFCaD9y$@sGUy_2%6YQ1SP({NzJ9OqCz)BB&)u_`+_NrUBqy>O=Y zKj}nnB*(G*CspWiH0BNgUFfc@Iohm=I2wDV*w}%JZ$3S&OjZWxK#WpZ zCa+w?Ew_@g^zZ<)mi5!5r%ILer}EudtuC`^nKi`)c%(*>MowuCnBlfb6a*dXJwApEQY!&_4 zyoND9`JGaHu4FboOvz=`$HNFvCQS8Jn6nzR{9>7MOqD;!qiJT%ZYg+&*S3^0%%x+- z{F|*~q`#W{my*rfxa~xz1S;{f5k;a5(Jl7x^4IF4L-F!BX-pH~ILgZk4qKy35lsVVB=FDSCb=B+{eH z6yzphV45VAR9z!#Os0*=F&d4klc}FZ_Q~dNbbKTy0V)r~lWuSFRuaAZCj|vg&18nd z{F+K03Jg4B%4ABj(4@wc)8?K->7WC_-b~){sylrmHZTFbH57eMdV4-pdPw(7MWtPu zZn*SUR5CLdgV~{eZWQ)qd(z_wxZyAkU9rmPuoQzUi3yg&K&%o2A*b*f(@csy#o;5Y z)8QkA$46MLSH$BKGM+&ggPK9`ua|W&RpKCq8jimQ@=TfUx zAEuO256@-R^smY&FicrJ=(gUQU3049nNu{nvdL+K{fcW^<2rlI#oX=`?Ic3 zrqQ3#xJdcab<|I!TgB=aQ+M+GO$Y z+wxVIU2{Dt?I=3D(!J9%wLcS&Q=Coh);0d_EM=alM7pSD!Ir9(+)RXA{4@fPvF{>@ zr(IL!mU>;kvR46eT->ewVat%U5iEf=Sxt8)c;{Ox(7D*{aB>AYdqN_g=W&Q}6g`(8 zTBXsNWN6$|a^5?f^=%CGr+PDM`qPR&8^hge*MtdlqVFsReJx9ls;W~cOlv?X^~+{e z&`s`gl1sbSTlP=+sI&A3-wNP3c8>NWIF$P{L!h=TGQRsm;N5Vhd}5 znr=Ro59{DQ>R`Vb0aGj1xMadovQnx)pUgW39-FQ8Q>s-JSRA@~@;jpur^-rx@v5A1 zP(_tAEx%n;9LtoWX9#o^df{$(dFYRp5K8jzb#y=i+z+C zuoEGC6#@JHwu@UgoCq1~bo;X77_Toc8dV6n40vZL{da2ji5sdRd|PdFB)P?7OIZ*1 z5?=K^s|HXjmvTe&U%nnBxXUfui=EaH@`=J;%alJDJ=PJjy}%vJXUrbCsyHIA_C(OcF1t2POfp2d*oNX9;RJ4)`?ndNAh}>|DEyFxdfOx9CJT+B9hz&vcJWhhq?RO z)Jg7TyZN@@btHd#5o9DnXMr>L3-X#TF2xaYs}UxsRP}4Ix!l>@p5;OIYo^8>#*nB( zEjLCk#G<7LSVux0E;P#+hwz9I#}b%L++wxQlTGbri>_(c@m@`jn%vx;5A&3NA(4kv z@_f0B+>Uy?%f1LATew$mIRD*PX&ot77TD_ZMw4}< zTvwLpdE6WOGCzw65*SiE8_;s_p7Rwe>c+qsVQ0 zRm-={IApXPxA2%sj~>;II&W0JP@_tS(F2=Dp*u8WZZ0%);f(Q+gb8_3!)I_elI-Li zV`{IPoqXDiOCjxg6|{Q7gq_3}cRTXgxf9E0hj`wY1Sr)zsofb**re%ENtsh7 zqUBD_q?JZ@wr?Ocm`?Vm@|g`v%{IETL;d;j3F(W6QoTv#{GQPyU#E;Klj-kC4?D6f zgS+YX12eGU@rk{uTwdi=AI3M}Y;*I#^A1Nyr;Fv0RcJ$P7~#``2;yA`puVKb?s7Ah z3#if8VGOg2F7as%YfaZ5LdKlyGmQ5-8-E*p6B>#qr|7>4d!_QJBr7!;6#2=2Qhf|? zr$zpqJxkTby;xN-{j+XeI2`MUty>rC=vW%-h{a+{m&RhTj-~qF(pbzL($2I~b^*(V zgX?<3OZWR($N$20{LNpzOV3#vTdJyC*cA(Rg%_O9iaXc_>-E~;$6$D=*UlQJogD@n zipAEg3oZ@Ubu693zIJp37j`V|2N6Jg#$6*LD2Qj|YNd zAqO2^KnUs94$Kf<7_18~j4cfZ>lS*Do7L-#=r3lK-%fONIX~gxx^=NwEF6x7gTYun z91I4#I+mWVAO1JL*Ev5f5SFSc{x9CVCR7XF+qx18l+x z*>Qfy(hg^I>xhwhZq<*<)6o%I+7aVF)g$%cP`<1iU)#Elakaf=Upz3Vo{qy9?}J(j z|97zLSg7IMvH$W=>z%7)X~+K0<5tN3=6T$RSWhgtbOF<1u~=_-X?UGGYW^HVB4P>! zV(ZlO9#~O0yfCw?FnxH(HgseYE$1%t<)G8C>02E*ZSur7F9j3W)t?rIFr3C@X~8mgCvWLpU5Gh=TkZ>^PQ0uQYU@BMnMfmACHbP2naJB4G^= zIPe&IOX1;~%yZ6;v?5h9k+kmaxUqj*oc>XDv8xc%0x;ST_!4B3I=f%i1gUC*tfwYZ z$LMC~AcsXNk_3+%2QZtb9t!lCIqCrL28(F|9!Q7I--us{FYH^v>)+0PV`*m&Th?hSRjoM^_caPdOW`s!><9 ztCVvmkCG##vPwliGqaQow!@x|={$R)56*nr*@4*be-B|QBdJ`U29yIpt&(gxUBf_* zt_lW&jdi5%9UaF8JCr`~55G2{e|BPJcXC^V9>;cR6ow;a*{;22osRnez5HY7ny^>uITBO0^0Fu&x^WEK`n&FQ)B7nOqQc!yasD z(a2(%D#H172*n^HX(S*OPa%Qxz-Tp5tV7^zh!I!OaF&>hrEHIEj^u^M6gEc~JHrmD zED~n`75`I=uf?ML#0~7ZtWut(bJ`${m2xBlelNum^Nihjv@#omZoAn6ki^TVmE#`# z1Y`Es|i5!sZzSo$snedBG0Vz3`kgOcU$R_d%)Z$O`-A$2~Dgj0AlZ5fhsaXVsN z2w*M(JR`#})j4%;gkI&}Cbf{!5jI}Dl>I&IQB`|iyIXr})T{jg)=roCC3u8>cPcO% z1FwZJmnTt}!1F{m(TH^$u9CrOFE&EVtK~`7f(00Kg7H0~s)(wX)k_$|jb~;qU|$M1 zBJo*7rQij1*;T6M1y&=6@!-(E?aWSg=22WH8Lu-t+Pu!ZSJ~Xo!@r!){9YXxNJ=pc z+Tjwg|4ifO>90HAIj`p+slP@U{Q(7>09ps{)P0Y_{x~~de2%{h>i5`l1Tcpq{ykEN z@chU4VYSA+4&A1WqGFlbHrVpK(XVK>QI91uRwHH{=4|$f_S6)j zXe6f`=|t@g$j4Ri5gs4ZD1$Q<5|t9<31hkG1Qu^S%j?3A6igPtegiq43W_4ZI8e(F zI16I&+=IO@#uLax7UW0DNcxy%$`{NyYIicYodx-!N`A#Md<6;I>$W)S zY_G*Xs1_44tw)Nt)4KoQST!=RgI!%n;;zfEmEyxU%zB>efGq38Rk%x^Y!mYuT%{jx zi1E?^7XMVC9Z9q)Gp{W#8p|z_PZw&CB#nUVGI}6#n=_@4s40)q94&=wx!}!uce>qg zk01oI=^u#VMI_BRV(*cS5%3^UK--$_dt@|%g~XK;_Nf)WjnhB+G=Fb=n$th`9);uy z{5QX!vyDrA-=K^|F0w?n7b0hHFGC(OX1W&KUFUcJdPJQI3&lk08EbF}0_v0k9WmI& zdPS~6upi*BrL2F9=lAR=(ZOC$p{e~MM0o(j3EwqqxmWq~x^l?tqqjpYmmb+YT8*pZ zgFMOf+GsUwxk6r=02g@R9p-T*WccP2q3Bw`52$nA2(HPYVM=8f4_#A}r z20<>Q?=tl%Zm$h<;(Oq6{67ge^y1!M-G6Gr*M$D-M}ajBMj~kx+!S z;ZezS4)Pr2$agQ2(TF9oXS7pe^7l;-le<=WbN8{bDze-SJUPXXW%}GU=?ghXJ{T60 zr*p*5Qh~!5$gWFLFMB7~|v#Zl- zS|Alw@a(C+3*2=10mFkaVl#M3lln&+jz48KPD1nn+=q~P>h3en(2_tAS8+m)-OclQ z?Q&R;WE*&FZ0;&1*O3yRgD5U%SFPnd+?s~?;|Q6@NY`G-td)=pYNWOY;%D$My05>7 ztRMn8KZ07wN!&M-{5HiD6ZkaKHgpmIo3>U>1M*MD zEvV(xn{V?O#{GwB;rFPC{YT!K+Zs}7y=O29$AY)aqeS@wbPo(ortyEX_wg1~qk2Ty zK1(Okx1hqepo&9wIPsJf$E9J?XSUu9*8jOfNTzRc7iV}wDb0=2+}}Qt#^d;UjQ1W{ zQ*!%ocl*S1+%USG+nS8-zLef_bX+X0aW0ltcYbhvkG@ul)s5dkCn zNb~K>kibVQ$Txh;FT)bNfE>QT1EGJg47sl8oon2=P*-J@i(SQdm6M*1U7X#_tn=SN*9N^rAUg|!-%8|d_e80!>W+Sd~EYR%a$*x*xt5C zlT{&)_+}!BS1en;#$>C)mWO>;Fo@l~#ZE+3U`@Lgtys@EpXM=poS+PLfNj}SP;Rv< zq{XdeQG{$(G7YA4NyC=29H>;vj4JmD8F^)YQF~UgVi>kgh{=SOg?3~m1 z?7n#%W5j0=a)-Zju~Vlh)72T@5Pl9NA5Z6%KPF_&Lr9D$nLa@NX*I#-rWm5+mWNXX z4N)kC^biF$j}P9z>uj&`rYa`}`X>QW`w?rdS#!wotWvAy{b55rn#a5BuuXxnpzoKF%s7D;g1Qq7;Lugl?1KHpp4@ub~{~qcG?HxosA1hXEa$tqSVs0*mY~JH8cDYF zFrxHng1KO~tV^?_yMp(U~7-k=l`YN8gamO;$+j;xl^mVc- zq_KsONSAa%&;uD}__1fK3VBBpqPWjGijn7&%rUycI!Y>=tSPdp!I~;(S4-tg>nM^p zYl^IC=2&kbE%`L%a;rj4dh-g_vx4Y;6=Zb8C#SbzOvpG?-H59I$ zTdXP4;3*$lE^yj~RMY-_`j4!`A;kmby{{tB4dL zPgzIFw^U4EKprTru%^oDY76pqD&dQeyqY8g+v%kj!0!=~{Iw82BMIE8o9<(wR z)LiQ*`KB+vA3i}AHCRWr}TT=?(KYzAArc0cx>C#UW?lWn!&VG%0O0 zfUsnQ1KX0%wy5lz-q*;?dioDA}|`p*&<`mWX4-_{f+ zU@pgE>nO?G2)ovT95K@!WLL`T!ADgO=EFW30j#6NEWea-H2$T`k9W4oW^dJXt#V40 z%akhrE4&t@3pu2az^}=fj0dd>IdRyoWD>Dh|_1%l1($iRHZZmaTY{b{jSd$_O| z-0vAgF@`7>XeZVq8L}w=6{>d~+0gA|MY&5E=TGe+pK59C?VsYC=xEo}L-GFi#9&9F zr<3wzGz_o&nyd<~Kbf+VKK(k<-|e2!X`Do;y#9Q8us_v1>Gr&Wt1`KC_oU+1dPNZ>EAxHvGn$)ss^f=S`;n|E8wXPHlRNYHHT%-H{WicjIuMx{={b z8lO%2+Ver&%Ja#&S+5lWcuHprXeKMeFKoeho8b zsm!hAp!ql@w^m;(d>9{++S6rC*2hSuU5@L%pON^V@i|J`eMR#6$FPKFh6c5)cZG9g z==;`$L}S`pK{O_fJ6y?=nOEjNgq8nhh;?X3I=wZW?#~Zq2PQ`*--;zV8HgP?bfOw# zb^IrFyai!coR!v-9yr8%$`2{l08Jc|qQGyNJ+g7~hAj`x*lTSxREDV;6HzWsD`|qk zGx2;dT{2}d<>Dddlzi#Zp6>i0DLwPL$D3%A;yeF(N0}TXXL^}yD>5|wf zWql%wv54(VG@sQ6K2&%k;+x++YhUSBU(WBUCsXtUFxz2{Eo&-2DsV9MaJsV0(;}Ha zVVQDxlit=r5(!ejfwULm*~!LCyo+QV60ncuu2^xKo5vT@RavGSL9KvSDn?=7>fQL$ ztGGx_0DCcV$>#Hz#xEac&o*IIbuY_#sY-0$vbK9&6u^id(M5>knt3qVCRgO+< zwWdSDyq5Av$PL92%ckle)>D)ccXxHk@TEvv<*?;=-bZ26ETA;d0%R206UM zn4s!HHEy{}S^T0Nb%kf(5!{KSGUai{i~yDI`L4lq%a(hK5wszM-3Sl{-}BXPKoaMp z7Q7NQN-=&h9T}1uZl~U+jWM`Iiq)=CG3yMcQzpY0@qXNgW5hEZ-ISNcW!kV6b{9|e z&gM0kGW1YP-C}p4x}LKYOKyeWB`{<$kCG)|9`kp|rZfbPE>f{RWiX9fWW$XvB(}7A zjW%nnrEAA!9@6eCeLs=kuflzJc-*y6QK$G6p-R(%9Lyymd-(S(=kI)~EBaRPnDe&{ zSD{O3)-gQ7S*mqR3f!ioSz*TVIjFVX4zVu5RwihhTG#E$WBQ*yt;BraH=#T-xx+>W zayf@+y%kJO(cB3`SUQc63>hx{HEHd1WHQT?w-MZ@gNNVp48KTy^fGUz9GVKV)5JklmpB%kR^PbPmR%iAj^ zqqt5=X!M5e45e|XKQla;<}}aMh=20G6*gQAXaePE0-eZWcE!uf813D=t)Xn(1sWKC zfd*gLd3}KfHmUO3SFof7l{>>cD1G(87ih4;)x_A+#Sz|JANKg0H0;$3yQkDQ=W+Z^ z8n&w1xk&?V`|K%c!Tuc@pQ4{jH)+6m)?MdUFU76QP0%-KVB?^BlLmUm-K0_0qwmNl z(;@RK2wqlKrsXuhb7w|5(@R4y+wabdvI*|YC?i=*u1V+lH><+Ixc4!d`=wTH989Tr zg;R39>9l%caQP?x)!5=T* zG8tNN%rfPHF`n73rF6n8q_5W#bMk4LEjLVjx5t0z7TuQ`n3VqI(XvCma@FxbR_fwp zXrR}yTS0Ebp@g5nwiz9Zk5fX(#`w~05_QF zcLRLUG255ajz(6w{EOCfuJv2-LEbV6?nlcJ!wAUQd*$s-jtg6Y3u+y$`Q?zKmRlmd zHI%%i@Q@|k?0P}HolpMc_2`;|X!EJOj81Y{G2&oU^$=w7rL>cW>Kj$e z>?qEY%{4soz|_JD^;z$UVgxNTfw# z4c+n`UlW4zl_I+)kNOyKdkoR`0h|JdN~=>mn_VhAEA2IU4;xkElVWz>B6(k-!4k&d zj#}lm++@}hFD@Oc;X7|3P@z8lI$ABy7)@5We3lbF8qqEby9y(gC7&tqzQ6ZM%Q2S7 zUq-7Dl9!A)=5u#gZY)HIVDFQ7-6`6I;g^M15q*GU{03F<1JY!P>?&02B$a)uEU#h+F0IUWS1f8`0x>|G05ItH*Wi#N+y<#Mx1H{o2u2p*D?vD=X_5>qIUs z&<-5BnqT!;f2GEH$$n$~wE~Um`Jcwxt#bLe8Mj0}s$xr&Lw;|@_19O%+C6CR(Y^Y| z<^!SK%pz|{oPhYnSWTR-H<yv620z+jmB*HEiw^ZeBN%8uU_` zkMfk5ba~hQ&&qi7`vZ&Go9IbU`}<*gS1M0u(JQn;2Is$*#` zz&tm!J19cQF-?$Ejm%mJnOe{4mu+K?njS>E(8{p-2;rG?P>WTFeh3j#^>u1@H41wg z)8(8_&-B#ijb#rw-5J;k_m+tr?#BwfLp*=pBCA}EA#9V3yHa6`C~9?6vBs*9U;7kt ze6m0!b6iZhEeZiwX-7mpTa1w2Srzi>VubbW^0inMa=mVGUqP?X zVMPxsXj`O1_RBsxRHyVc;U6z{vUBHETNTpKq!vfA-8{voBw=6H0g-=VDEP-i!9N}f ze(Vng|3OgjPY4A+#zDcq9~5rWQ1IhTq2Mn;A#V96hJwEY1v-)u3Vw`(f`1$o96yz= z0S^ifQwq?y@LGv?`E9w|P}uO-u^Ph4RM_y!ST*auf>(cWZkF@gDT-6QFy`QbAJW{S zdDFWn6A%;pkds=72!AQGTYmYN(c+=PUkW#RP`K9M$r~3LK3gbZgP&LLu*Lg}5z8;% z^r<#^0*BmDV}l>^u#Xk+=H9KANR<5C0mm6VIELjvSE#o9WWEkwz<$1DX5MwN1C~my zzhuxhZ&BS0u5}fWNJeWck@uNR2w9bKX<-HA=(wi)&lo$bO8HzNG9ze}t2^iwfd1PT zw<_fpb*<6q4NX$yb?bEqe6y|9J~k3>Jwsj3#EaKuO*?N9DrDK%6=t}R4nu%Gg> zQP1~}7b1w_gQ&$~R#eQgjny98@Z~)?=!>+qSn#x>7^~<^C+dtx)T*@QTfUim-BqZu zD&_M!mf24V)okX|1~2p$$UBx>m9kXPj3){jPt*dgge;37M5sPhh{$h@xRVr+DlU%g z#Rv`AyqVgnvaiDrF1JgbL_BHDP5|M59~)}Dj_MdB1T)Qbyrv=}^F(Fy$XtTy0V8gi@mxea2T|&f4fTv`aNaUo6>hBS#PUq0tgXsFi-0cNOT!YhTZH%en7D zE+uBY^egl_l0>vm);7;qP?jI5gP{v}AzDF^@#A?Zy$QL1r&Z?4;j}a)WSA5@!JT<$ zFQawX>(35qPjWOR>YEWFKix!O*MxlDW(VZ?jWu$M&4%C2uRq;LAJQh>{y!BbSzwjR z-U2l%kz_NA&fd{x3716FGNs`f0_*#}ddrkWoc+!QDN4D#70eKRd3!KfCt87*~6Dz}tWzU)$$I?vkV!S8iaXKJ5nV@6sg(>9^0*nVT? zqg0$!)$dllx=cyX^f?vCJ|%VDu6prvqXp9`GRtdY)s`tAGFzlaEuqEkBK3`Wu-;8o zPM-9&@YI^zS){|FU>}yuA#LUTR!KJSaA5=?1?BsV7TWlJq*M!2zF-dX`_-IX_Q?{t za!ezm{Mn?t{2ZQ0Ta}g>O_EMSX4ljATxrz-*d3P0wS^j|?%xnsskK<9{Eiw7G)>$$wbxLxt}; zjr~mzWUDife^9Kq>MfDKZ>D$nK53aTgC+j8xf-&eLAmBcIcZ;#7DO4xFTh}g8@I`O z?v=xuq>|15gOeyP8%?-MucQY?+vOR9KG&a7Snyr9+OLkX+D;`(?vH{$(E6ZkdX>Sm zMgF1D@GnN3dxyKV67-od;?(Eu7WvAi21Lc*57}9E4MKRg+`Y-+R(BTKoxwh-AbYzW zD;QDm7!`ga0Q(k6dd9{bMGrCF&+~j?UR+O;hZv6{N{L_OnpQM(g&@TJY$+e+O5Lsw zbVSu{S#&_BM#r;i_wm$SSO(j@W9PfJcuo9_O<+EWB%UE5=4JiXQ)J7dq+SB~uhHgl z%CE=^Mw7RqAYUnSh>1yivRqVZtwrT?9OH3Y-k=tmhc3*+d^xMVO|CN-8T0`}c~p@E{JG6(goqk9TP^c2gDp@& zTA;y%tyCF93N}*7R;eQ~Lx0#3`Q&DbMd6nuV><&&*GCU8w zesLZ#>tx7glC2jv)##CYx{S2@g-uFRRr)6s^1r{lzHSkV*9H*+kM&Yln->hr7K5BI z1_x4*j}@st(*J@cw1Cps`@@vTAU{zS<@xp*yLN78Y6}|20g)_mtxH1q7tK6H?a{v_wA4b}?Vk zdnIuELaogk8ITq7RC&BkDu^A+R74bcTb#P50;VPMsbN}yOvtax8L(w8qViDjY0wpW zktK4eYW0os7DVN~;-!%9_!OzSa+sv*5<4ztT;bmkA^qnO2h!U?NdIZ11Zm5Zzn90^ z;?`lxU?${^@}?Oo)EFs)>L62^-rWWnX|P0ov)LK@CzS>5<<0wzeGB4sSQHH6q%apykZ9^4*)qKt zTb;$&#(k5hdr-FPGH=m8!y{@^Nz@7=T8B{VoESr2%+~tw&Qi`jwz2@yj=X8F@?htk z?E!362e6WA>xSZ66k=ZkdgzFs=NYzYlh1=Abm!;deF!aViFc0%a3@`RxEXhfKNDcGRb<6$&`(*_5YjqWwoSBDkyiX}e?`x^Be5hCeBk*{*YPaT39Pta6sc z!NcTH*he7RWXo8m(vxV>T_kHFa(G+@BWGG7*NpJw)Ehijl8(S0gXed&>w_K~Z>u15 z7T_}o(Bky2Hd4DUj-)j$_$?mg`!yrgp1qsx1Sqa(o3|24|BU5bvX->K0ma8Wp#6qG z3v&mHtN*287errhLG-&}Vu}Z%y$VDx4=a3eLG({GaBV;Pi@RVxUd~nZTb+|2;sW>= zb87q9i?pAy_t%dQcuyccWQn|@mg=qLaY%2(l}vYD;lQIpDaa(|%Dd~thyAmP3V6;HK>1$%zwn8&^LFA|%B!%UWYskF#<@aN0t6Z)z z>-qA%F&f10HJiF=mKvia^q;;a$R`Zy;T4eIstkYkwa^0%^1dPm`1uNo{}qs*Z)lKo z4ZWL`akSVDZga{L70W3huvsb{0(md{id9yH>{S)+b!}eSJO=q}QSqZ^d^1_^?L`u0 zf&9)_j~r8W6d#(QMV*b!R)x^O3E5n2Rgl9m=T8k9&puyJO6Fj-5_nkf3naku0G&W$ zzhkOfe7%iQJ{c;)JYk=Jq}oZbPnl^|$YP46b*N>1^{no^W=07JljBIb-JIjB>koS~ z^+R8kzcf{!bEoR32Tav(Da^C^Ym0iSUiPV}Qaya?z^QuD*rBa$Uo3E{p5(|NSJI3t ze>T)q-B#dKg~rd+f2r@y&O|NdJpIac=IPTLYxbL`PCZW_te}K3@jUHR)y;E^4DK}b zs#!WxP0|2)wJ1?rO;P$GOy}VxrrllSEPYqoIlrv!oZoQgd^-muf7DZT$2dFZt7@)3 z;?C8dN^?cRXsKyPb#l7$)fykmsJ1vW)>~s$$dN5-#t?pS@trcrWwnT0#M0j@E zQ0CDG3f1yGUz1fXC#f!;?rb$p=YZCzRez6T{$gG0*uFrHB@?WG^fXu^$MJYr1tiVd zkD!J~8(sysg)I@XNu;a_L7X=3T?K9gRX}zaG_j}~d|6|@fr&@;AXE=pXQF6p!2ot!6) z+w|m=Xt{qsTJArHmisZDmisZDmiuuKEl>YXwA?=tE%&2D%l+7&miwV;`3wy{(sKWJ zTJEPV;xGo%a{ocJ+z--ncXkdY-j?5`+-ib)xz%xGOC59SIqG!nojX46%}0x_0V)f&Y{XS_KzpqM9Tfhw%;Y&G|Z+f|9)hfus(=v`=Q8o)0>j*s}$L$ zmJ)L9|6SL6`1eO`FAhNDB?td9SkxMO6Z~5ivHWrxRa(js`w0wRFGkL={IcLd8P5JY zqF*iQoR%cZ6(0lZcqdV2 z`IUML;ZoXiUou*lV>^+6T>EbYDz$z%y9KXtn}M&}C|$^Jm2H^9fFJS|wNqM)ld%+0 zOXRV_3MSvJwizB%gC@G&ZN&8+)jeYr6gU^ABPh_^{Ai(pY2Ty~Zz4hAGO|=xP;fSP zc757Gwn}-PiUmSq_Apu>3C^`9H{pIW*6b*O^iH#7N{^E84W~45%dmlEo>aniCFI@$jf>^rW?p~^%c(&;r!2`ooBnCjym@hBaS3ijh7l5_&s+m_f-7UXDcBad45SC zAK(DweM(L~vI#kU>w8(VjN6MhT9xuctzg{AT|(lw!<0Ux3cjWlj9VQALrrfbX|%H| zx=rth?k=cZGx@3gtR?bGwPCeGZC?F!gS$KWcjZ>o&|_6f#|pJ~?1~)cdK%b+@yB=&QFX*{jdICV!QgE1|91QMPLd3UxP2Ev%_fuU^eyFx)eE+qaKP;@9Bj@o#Llb^eF|sQ#iDmI4;yo z;csB@Q_pTtNpq1>>fXaWqUmw5dx$Yfx~S~OA3E9o4r50bt-)#swGwi(agD~A_t48B z++&IS&HZ)-DSSy~B$aq>%w-pqkS7jM8DAWW5VbVB_?yEnDj|0o!y5N4VjCx@t=T_2 ze8MFRwf)*#;;&;7*IS~N{Kb?T%b&F><&z^cs4wo6_t2F0{c=)+zYS;AVCCn^>u(Ry zQZ6;(_^29^St}RqMG_BKl`>1IvB_$yQeN=&2&e4qPD?~N@{sjZLZ0&VC^N1(L+&op zPMoqT?YPZB z_NnrFG8z?ahUoTwlg7rWkUeA7SOPI0b&#Lu2p)!V^<}=V8}vM!3i-YwP+u|X`Es{< zd5qL*syw2o)o~2Ia-L!}-&XW$D&$d@UQH)>r$V~PYQCrF)l|rRq*ogwAND+i>*gSU z%_^Fl+)?b*X3@Q6EvRMoA8g{%_;Oi2Le^CI?xs#GMB+k>vXF)mLv#l~0j~orIh<6@S!7bX9Q#QP%f6&40MtZp!B>Xbb+O zvhD6r420ef<#KB=!n|p+8G-!Wb9$I6yR@&>)#F(d4bE8>_?0hShrn#*u0#)V+NVER zZ04t5`YywETm0nD^a$XrQkQzt zp=Ch?G%ee$!v0)QJdm0xLiZBwT~p<%;&U>5`@f@S&uMyQ_Z&darb0fWh}bU)V9?NK zmCHvo-dwLNUFUcdZ7Sr`MTev?^28}c{!6fQU@5##^CFbk>q^HrET z8lAeYHV&*?HK2a6fYares#3Rg@T0nC>P>&~CNlj?_d)--J=t}t)06GyoA*R@Xa8SI zo!*6G;c(s3AbZFg&r~n?u;71{q7M6)Pju$lX(7nFd7RvQHr2Hz3Q;w?Q$xqykfI20?9fuxhLjV}~Tb%+k$OS+!3e!Yznu1q>G` z_k(EL4Um=Gy5+8IK)GFT50|bFQf${p$rvzHtVb84R)WiEA;o0~2$su8gh%rht7B?% z*3$^-W2NCoRTTJWN(G^e%UGwF%a^zE_?o#;#`a1@k8J4d5<4y%wy94ssC!K8B3;B7 zu%1%MGWK=TwfwD&B#a)KkPRzrDYRcCJtL|oT?k0|ayrO<8-aFp-IJufo*Xl-CufKi zmrd7F`#S?6bCGP`E0sNx?_70;RH{yHYPZTHL#e)&rd5a?x60^=t33I3T%DAnLUv+_3RCyR#r8uk*>v(uY(fae1_5wqT@7< zO^#jZeyx*>vSK{Tn`(H>IbeT#_#F!)Z9L2|r-{d6hynpDMF(h-p@Z2AVFc{&$6`{v zJ^qeZTcqs_G2*R+7tTSVeX|rgH}mf4YV3a#({AUktJmMs^)@)vpU?EA8`Hy?{Mu}G z{UI$nw~|yo+n4G7H*1eA=#{4;0H|f9o|5z4u_}{S@45WYD*Z)QH*N)z#DCqHE>oJS z{MX&piWqO$5h}xw3mIqHgdF~ozPem?8!IhSE?0*}iMmhnkaU-J+IgLJMCQJ{K%Z{A z5eWvGiBgK;4O@40AaML_IW3v8U2LEtfjanb_I&*rJC9>PNh7PJ=jV7b> zGxqjFUY_~PliH@U%&d2F>@4MoIA`ngNZoHCVJ>WEO3Gj-%`GO}XEj2Sb; zn27{<1xGZdq{$p)Y=w-qn@()-ou!H%rv@Fy65jYkPeB!4Jb@1HpR)2-#F4=L$l-E^ zM;(%Wuc{s2@cQ&SR)zU!#gZ<}_8*goGGt;|cW_tHJ^TOTcg|C|z zswbwH>DTMTSWeMn-MapKKA$fX3I&P}6fXF)Zr!@|u{IMv!`SPf_WwJXHkHopH|U-9 zj->Vzqe|Z?8r1TQufp^-INx6Mz6E*3XmCHNv&+r3$IGQNxx(NMH^Ya_DmBl(3UjBM z_7U^oW>0jQ)i-dfD@^O^JmFf2*#}A3Zeu&$!XYJiPOk8llbn`+>#OolSF+|Q!EiXp zTcg8EoeP-{{=4v?v^fW7TDmA444y*4HXN)AhfULG7+Zd0`pSH^?~Q3bpJ5pP^jGqa zLGJ9uc6*zG(cTO3Ny z9a^9W#37X*7&@eKZol>IgNIZ_cXmU1FtsM#IGFFwZaAbW++G}VM3ekrsynTJZgqi=PlKc!-M8GUQI z_2)I+rGgxhP8~?CNvC>yv)v^<#pom9R-gIcQE;YMeS@j~H9A6?(U;4o^18wPbbcV) zt6S{PsDYHZ14(9A>)NP}u@WOU(%+p==QMN&QvDg-#oS0=KDElp;XcYh@6eh|ze*h# z)WN8YTt3yUVUWwL=}+}~vB-?98+>g|x<5VWRNU8{>RzjxP7S8}Q>wDQRhfLBjtE8| z5je^I?3%%Jx<8*zm7tc}nAXw4jDbvle_EqUpMsgLhafC9pkDX;O6ac2IDqO~)1B*1 zZAka8(fv_aS%SUUy*8!Csxe2kpvbVZj7tdw{*Z>g%8=fW?$`PI)^zt~`n1x?myOv$ z4XnP6t5yx^x#;WeO{e;YG!}HP$#$pQ*~{kB$!vbDGf+-qGB=Rvca&J&mRpNz*qhe= zZ2!hge@}K}E=g5abycmk-s~C=3ET-%UmgC;XLM%=^(uC7DVg>#YG5#(?iF+o9?8!JNsm!X|t5OaMtsd&{9#C^B8+vkCr;b#gV;wU}iK=j_ zSJRH}fg!$^_jNPKrn}m51F8O8T6eW?b!J%2KJyu=T&n*R{iKWu$$_k5lPaluO*Yx* zVk2L2nKh~Id?u@rVZ!UTomun7!FSRJr^3PP&^S_Pt4{-Yr@3t30N1t7n(Z6tP3yra zsiQkPMA1)uRMH2ZD%7XGbY8EX?t!7zy{R=CvhK?`pmh%nk$A8qm+fj7W4Z^1@|kYO zRMI_|PUX`{l9-$rE>2Y+|V+wv}iu?+?Y|XE}B~>@oyMvVn zg+tr#!}xG$`+dlb4~OPgi*L0XxnD5@j$YveLa9ABlExLTH$V8&V4sZ|;F zS8}}>9XZYDOKV_|t@LO6bE{mcv8KBOoxU~Q1F3GOj=oHPUrHlRpNHLjxy&XF^Kwpv;EnDbpNWH;!R9( zV^X>#gIOG!qzVmg^n!kBDxlWq>cO<`ac&^37Hg#Os*siudCRb$#GyMuPj9j{VXfTr>(c@P|y(F*SxpbdiV7YWYxuGwWRH&#R zrb}ejTs)K>a=PohBuTXO=j`gd5?nl-FEvl=qcabDY}N(%I1cpseO!C$J1KiAr_m#) zSaYUd!y`A6KAp>tYdD|j<2Y8Q*6We*-EA`8 zJC36$;$1>W_3A83{W6$dNp=#T3%Ls~tFRSmOpD9S1uV2{oA>)RZ*S&$)2p5tg}e3a2>SAm#F# zab{xObA^V7sXZHN0^{~*rn=Y8AMU6ZrN*Z*t1@D;yGQhAlRa7>DDy1UZ&RWixu3dv zQ8gnIahhpd(cQhNTuuvqM=F}fdaJZ-&d2I>t~)iLQpG`jCi@xu{!wjx^XHJkn0bue zuHNCMUT0NEMT>T_yv~6th54<+xP-`s3MM#bvopM4J))A)G{FJwyA*Lz6x{J_jG5hW)+4b7e)ei9<8j= z?5g|qe8xu4(O+-W>u~AY(>`@iT>#&3Kkh1he2%8n@O%WY9)Z(z6x0WhLpySK)tu>! z{`*ch&Q}&GH-G5O@^}5f%m12gJPLaaa;SA9f$c2i*fVjCS&w;j>|-D2)9GeT7*XpX z19EARULxm1E;U-{GGYHrMa6eeszq6En1=CgdNZQtX73Qwew=2ym zgv@EW4c`-9U$?9DBXs(#Qu@bK`dppfRZ9Q3N^j8VJ4)%FG}XpN2+yDGqJr;-UMn}K zufZT{PYZ`Jb{297xQ%v|vfcD=vh6Ho`>d%V*#%S_fEWU5+MZL+J?BGiF(fs2jq-#W|Kc2kzHA)>*;0sD@070X{>Y~(;69goLbsyyR ztN9YQ+{AcP&Q_l2XK;IMvAZsI{>ba$W#v`=970%cEs`rXRa>?4zGAi2D1XBhNFZ-5 zlGj!0O@(SCFx_gDR}hz?cVEY>A3Iegc^zsfOu!lSyc@&}{vCA33?PMCwK4w+qULcL zoW7rU#TZqZtC>O1xF2uVVKJt=z}!;Gc(TG~29rs^eq@=Nv?#Vqq1`21vwrH;8q~wG zx0;9P7Q5VUU2-@#TI?Qn)p}|b5_O9rvauO5Wf+0Q;9a@h480;{dys%xFGG9P>$H0i z#awy@zvo^#CGHV{XPid6c;70A@&amc9T9xmVs{w6pE;S@cw=2G%JFSS! zRm(Af?FjJjm=tOtQ*P5GF~*}|;rAelTugFAkn1~SteqS8UWl(8%drO9b%Z`3Uw@XLp5FW z{0u8@hY>m#0dCQ4Zg)c{&U)6Ve1Uw6m4_Fo0H7f$wDZVM3`=YVt&zj%>^Bwif^RvZ z)&lUpWAN@(d0QM?)mmquZ=2W63t0*-v)(CRD%LRX4}2}Qn8V&D)Mn5U25qUYX}(~+-9DRD;;EHnFf-rc}=Lb7QmKImCeKw zd45xk+)&meFKnt&?l=K3;#EKvXCdGA4cA$VL}psGiu6QTQhs8@QF|_8u`V9(=|*C9 z8zTG`4mUO=>1hHS$UK~_W|ad8 zhvlZt&GHkATh{y-!kOboDWBNf%+c@!&I0+cvC=A&8|kY2?rNgZN0g)Gr;xyXB7~~V z@URRMKUPqoe`=Aq2;qYEH~JP)gnQuZ*w!vcAp$Y4As-^ti|wb&4MheXdM9Liv6&70 z$Y?qN*KKC(z6 z)1}3Sq`Un9w;f-F+4_>x{S)QqqPYRlCK~gI@)kG80@=k*D_rkbkX&Ebe{z0owkR^g>8(Su9`me4a%GWx^`-)0_Oz)cD7(Vt#jP(p z-CQ7-sBWroIK?bNG5j1+JR&3QUg(@s-Ya!pvjqdp$pDLw$QTcCN6n+0`TLDm^T!!z zjwrvyp(BZf$Z&AG$4W%tg9=>*!{;G`RS>%#9oC7mV_u83KwexNA=s~^1; zk)YzRcgk(--*0<+XAljk=zkU~H{RrV~F$PY$WsJ?Ggec!G7 ze)*gC9rElbJw(ny0wIJ*BBE@IL0H+%8`#Yk+-_bkPyOfJyar1kPmgjH-d-r-$9*z9 zyI`G2Vat{+^Zu;)*p{;!>Z}vx>UlF6h>Ef-qZ}-N{9$p8#M`Y!^1NFIH6d$(yjMt7 z5{%82Kw>c^E7u8P$L}4yc+-Wk%gwPsULu~&N0cESxo`;)#u;L8&;VrYQiSk6Pi^>v zQ}Y59+AzfR8*>@kmeR)$SXKV(7~z%2E`$*$ey_;HLK%+8>>kMKPK7{ZwNeq63{z!g zCuB-JLXuk{vwLKHr(}36~#OEl5)E*c{;;K4!%cDuR+DwGl-+6kWe& zTw6b|cxm;`#ZJgCe2O<-HSX(^)-YCld%Mk*n zvN`_1**+twQ&l*o2&l*2fXqKNFCDyo~03aVNG=p59NBzX#BNjbI zcX62V5k5oyq+UL3DXuwM4VPqF;=uIOUYP zYCAL~D4+||2|d}sJN=_jp=AG85(~W$HKbgEQU|$wOi3)yj(KF|&sZWq8>^99%qICc zFPPXA;e|EzvZ+BaaQjxadRMX2RnTYs&07pVQ<3l$`@C(e-NT=+SRViEDrNpE12b~v zjN_keiK|WVRB&oFADS6@m%s z>)El}8T*NE#(Q_UITpxEWmW#17%f(rd~EXyYk~YxVaGQ%d)Vrr^N;GK?b4^qZVIvKW-^~{DS@%pal2QNR72XZUE1HU4*(Ct4v-RQA_eu zAV~?JErQb#t(gm6Gq8+QX1E=-SnLu!-`|~9d*$e=4%FfqZDv@5xg^r-U87Hzo3BrP z<*V{v$JJ*agXqW9CGb(%@DTg6*GB=l4>n1VRVH^7Ypg!`sgD{d1#-I!DeKDy4Rc);%bi>~j0*|-}~dfAQL+C7VZmlx=CufHYpALKjBKy*jK#@&$w6D3I}+wMbg{ zuJfLLg;gu(R$Gv!D-dN(w;DYbhsu`G(iK zzfy!I$)cBWfRgWQL=hq%rJ2dGG|u{$Q{ZCScth(Eg0kn7T|x=oJYG)R!xqjZk>hPA z36s8iG%J=0o}?Y>*Wu)K)?COJ5w|XuCq^3Rp>JQ0vzYjhJ#1Yp-`?23m;3CQ*2R!V zH{K39?1mW5-dyl6gn8%w>H82mo2w}RJ_lePzfWg@d8SnC5#jQAf##08thsV4&2g;l zxe;Et-{piJyEn$L{-_+woxFZpN`L5ecv`%YwsFYRdN%Rd`tLN02j z$a(X5D&>-fCceGVp#;!p>sRvaiSwG-=<@|)r+Ko}?UAE77Yy?^MNV4IkBJ86JW-9c zV+Ego>{Bmy6jyK>I$TL*SE*mLXJ1F_)Jp2})H+EugNh?Xm1vt(Yx!fGSux{SqQ&F? z_6@ViTZ^jApXqi!TU4#S&rq%Yv#>&pOWwnNJf^>YsklP=X*h^Vz62!81JYv}g}7t` zpoxuIB|Ei+l^jW^Wh2%UnWpL=SRuz>A@A}mbz4dmZK1aF#dXBtHe#Z!_01R)ax!!; zZW(u5NtxIFR+@sTBZIL5qgdyd;I5`^!$II3ZuXo|=TKr?0*s3LUwDraFep84M_?CZ z+Oz72ja(c#GuGPH%DaadEw`3uxYZ?Y_8in+06Kgp&r%AWE5h!A)2VWqQ{{i`JS6dZ zEiIB2Hi*#!DP)PJF(f~Nk98q=Gp^>@zAqWO5&dM|7MUrdS@6Ew5b<(4e|4c2uP~4# z|1P9~?Qhr$vvoIrH%fjlGu_DrIF`ov%Yyx0cYaJ;nHqHz_6Zc~TwbxOlxf#~nM@}z zYHOui3*>LUD#ToZ+U0#v(VkwR7-SbvOC>;zKg(#R$g{W*OQ`ba?9n1>d0jHY$I9Wu zXEGbaS}voV2(0cpPvjCA?KIo|d6x`!%HdZ?_Gu~Wkxdbq)`M7E7=g39uoMBww?j77 zC>Q>a*)E6gIirgurYx6S=dB_vqB3G30#axV^Q`^rxtUFmMlMn(iOxX)qX*R?dwlhfieWXXsQkT<#yXtTBrp1UdT14)u26@Z z{W1BK?hcrhX``|h!whaNsWr@j?=gJ;qi&u`I7v~_vi%e)H~Jx@7t-q0`lHsWSeX;hNUnfVSy6S|C-9mVa`@-oWK5``3=7E33DC?`WU-AI{{2YPw#Hwwu=%>!TK5;i<7 zNAFi;_3biKCj=0du86$NeIs>{-iPEUoxr%#QOMd?MajQjCx&m78E%gIxeBDGQ;r>< zBCB2|sbnuWNm0n?cV&vs5l}`N8N5-Z=>%eV6w-gYR52lnfF9bq8+ma@oiG0%YkvaXWR<>;<4Nw5 zyP<%i4prMKK~tqgQ!zzSG?Av*R4tChF{rI$t5aqM)K+OJ-IB1VpcWNW7zGtnT)=%B zcNq75-$g}K-1i+^{?~opB<;*+KELmLuIqozHPXEAdC&5kbDr}o_w$?wRhqb3TJW?o zr2Se9D;UEr4aD(LQOsW4LJV2TAj|hE$E-4Q6yDZ%xG-6!9;BwIUU!6JiQyp}Rz{2s z;p8&xG!lJ&a}=KSgv@OGJy~E5$FvF+-YtS;*;p#Snv(tu8jPPYGh3YPAav{7&pZug zwg$~v+I^&%&3(bpKzr)QbPaGkb+Km9Jnbf?PcyUS<=Y`7!q1q7k?Ik2 zt3_V{Gyj^SnCaoU$ue^|PO8ZCa8_A)xg2}ws!%2FdImi{M=M@mnwzzWBg|2FUcDr) z6CKS`5pgfJvmD)J$RCN$g6#S4D$9NMpi3XVLsX@S(td>AoB}I`M$E8k}u`yM~H0@=27rSj= zHl?O;u|4|^`BN|3kuG36V_DPt$~`l5?Z#yqgjmjbY@ejyQ_98YLu!g9(c8*IrLItA zJEz+J>nNQzLH4r{b?=8tqw53i%{}!&$CNBrTe(-JjiKTPnuOb&YhIpkX?ay6ue2Pq zwSulqmocn7MlZS|yJ%L$-PZU#O9CD@~fZy2&wKq0^DuKP&P^9& zB=-bm2u-MOpSVIi>Hx|TEH*k*vUv9`%6okxB ze5;%(SgcHO%D`&2vA(0Bn4=^?xY$15WXmjbD?8Fq;zOGl=O~h(!WD8p{F-J@o< zlaG6z153Lktt+;cEXgQkV5?5u$&B%7rDL*&o1v&`BxX07ZIl}`uGVi&x0NvA12+)8 zYxUw$=f%3TzhnXf#Pn{)U_$7Pn(eSbFL7-;id2*b*lr$D%@fRac+HjR+Ewa^x;D?< zYKG#MR2yTS5)9v>Ws>cm-oIoCx+dLbw!v4PxHGVGm2}UC^j8f^G{7dbEkkcxH`~S0 zGN_S_q1?*)MH`kAWS?1MP+weRhzYBR*lcq^|DiVNW>O? zkTDAZLT9e*`hl`fNf%cS1^iI*gDG17ob$yE&;c=Zi}F#ShLG3sPm}*}_}g4GiuZXHAPa7SHt+h>74D zl$v9SS9A$6V~m@jxEygk+R#_Vm>OAwrQ%}Uy0=2M#URmHDmO^FkVIU9E{PcB zyQvHLA!1em%jFOWTfP_M)B0S}Mi8@V*%j=b3#bKD$@k9(b|IoH^!`Lb6x?>JL)rm<7=U2kS; z78_@3#H!FcjX|6+jmvk+Y9Gy!+#ug}2Km2E$p)QP?=fugQvWt3`H%kFl*}HOlA$5$ zrh{_lpg>AwdsS1S|2xwn%Z1xsU8%P)w-SAg*1QFbB)X$^a@y4IZ0^4Ua{XQ}e?%n{ zn!1ctYh(6p54IPSU_YLsS>LSz$JSD^E}FU-JDz)%6C*>kH&*g2^yKzp^TbT`JxZWq zS-_9G$W?m)V=UZ3oa^8$L)e%tJKv2F7V{=|T7aV&lN_bfR$JNQx5`b<);y;w)5Lr? zcCqNM%3r0K!yQ5DO~PBMle$~B_JHi7oGQUgSeWUI@ft1V1Y-vcw*AeC4OBUPM1Gk2 z2@12f9MHDbWt4d#fQ}05hWJaAF*Dyy1n(2_PZ%m}ThHJaYWflu&XCfeCFaTVq=wJ4 zP?i|~U~YHgK;;Zp4Uzj8TdDC9f7LNG&~E`>f}%sh+89Vw^fq{dMr;%dnGIR z2xB}*^O-RsdK~Vteo)yj9M!Lqk7yAIOx&zLMC$~nXTDRi2%&@z9BAV-`B5xc!qz3e zfU(0kh5_!u1e;Wau8k8gBarRl#_Z=ccvUi-IoFNq3R8mv8d#_wv09yK6F0MtrRH2W zad+B2S#NJO^wj;Jq1YGqrlV{(HHePP9a=S+zSUr{TPwBF1gNvo)Zk}VTz9V)dWJdU zcg1sMFVJZ!Qa#Lo)v3idd;L|`nH!WhxPd5^QaY^>?Gh&COxPRw^~8{nW*7M($ueQe zRUmWkTn0Ig4QQ$$MulV`oKJXlc9bw!&tN%mil5l7lmXPQ#oi&@n_fpeC};iMX+K`n zg*&s$SSf5RaYuR)6L{U#O3a*+xr|GWe%*bC;UVIfD29gcm*^x6iNhDiHuXezt)!I& zDunGq|FOudTA9pre{Fr{sPG7&t~FSY z_VaV%J1c&Md6XF1b_^bcHR*2RB`e{#*@RNqOF&&TuSI7Rp*Uw+*Ba`|Fs_xT6#!uu z;zbC+ScA2Fe!HPISz&J%;s_R+^QU*;8ZpwVP;eY?| zRaYwt1U8M~zO)tJ56T{=qYlX}h+hX5kKAN6c+35-i>G;@>}e8zSV>Hffev-kFrif9 z!G&qR4lb)QnHs$2vIm!3B7=Xh92FtzE|eO)?P}my#`N)#W!eO#T@2umGGQ_qz_?Q4 zaGgvgNJS-~?$XL2g%&M2kTKEWW;vrw4W8;-r=Pc=UbQ^cCu5N8m0@-=z!}FO?YLQb zECHfKUm101Bq*3|5WRkTki_n(lED!>&r{BQ2H7uNQl^!Fy?t#;DUq0;C12I#9Qj@l zNh&ptv(xrC&Jy7ZhY07U%S?^#KT?sU=?oD9IHXbi{oK{CI|CE7?X=BGaJ2{(;8YWj92{_csGVOXTrh;LkRoDjw6dXa8) zgvv{-LE*XN6b5mVCxT~_c0ph3iEwWQcheyMS}Uj??o9jfu&b1KKj~)z&$#1e7-D{P z_e^(4`_qqC^uDuE&Zuuvep7>s^rp8=?fuSiVkd~>wS6(FzMO@;Qf@&VST83F80*l% zqUDWiJaM9}R`a6TK1;Q!q4TWnSgLQ}Zm`MEFeFrpzbqqWHttnEl|lT$FOnV{H8s$+ z@Ur|RxMV8-$Y&YGLAT+?R21i{`o1WtqdBm~VVSx6Jub33>mIBdMcIY}^0Xad_cqeH zQU4$&vaEbg_N#ID%Feh88xlHgDCRmzg6Z~DoY~|RLKle=01NP3% zA@y7!k(4J1IB+8Il&h2z9cN>VIKE8_QLK1-5fbh%87}$jRN@&|sqtb%ZvorQIN}P^ zg(p*Xaa+hg{wfXf2n!&eEr)DIYLEgSqh~iOLZ|`ZD(RVMe`kJF+aGp^IAm0I^g#Bc zsrqAvV&ia{S6==GZXIn7$EM^IGY-o=4d!s-y<~wIhl@S^gu7q_&7% z?POe$ZZyTXDIGQA5a}|?qhmkF&7|&^*Y%Apqn_`?}yZxnWi^hFUIy3OEU0qD*$)faa3~R{Wx*|LZ z$q(QO&EXiei&7@QQfZn>MI9iD1%wl{C|V6q{Cw*j_5_p>*0)JeN?e}KU2d?lA-4zC zDbj~wB{VyQK^wdCVvo3TPz|s3#x+A0@_`YpjA?V6+Be1N)=yX?Gr8=V$Cw>(tCl97 z@k*(yHap;k9!<*gUK!!=w{F^PNX%;VWYJcQxo-Soy8#oiAmxCzokYQ&jD^IE!x!vg zj3-Ss0W9s7Soo%}|LWv?Z$wg}EvrL+95yp#=UhfA)*p7-!Aya;9Vz z@?&y6xg*c&AOU)cevlk|F~fhM+d3yX19Za}hR8Cllf0SX-6V*HaY~ff zUP4*AQHJ3dJ$qS(l-)7fK&sd$BZBWi3B`8Mi+AF5hI2hSHrJzPb=#<^$K`z$W*pu^ z#MBd)^%Zck8HW!MG3B_dt4-b%X_jH`YR2LFyoL#^(`Xd2fb~qQ(YIDkUFusx4`WpPG6s=+#VZaVkDtL^9)Ws#i00`s{z3sULg|&V>BZrC8B%#QeNlohLtc zJ?4=6w7dxG_=9)`@nnxB$t~Ux7n))`VRzka-ncm&^Sb@w8xOea%{biV4G}AnFZ}1( zFy(l=dzqR$bx54JJn1*(*w9_3Czo4U<=^J$HBXBvC$@I_O+7wGTm=_+wa?-_+RoLM zPTR55heU|cEfaBUbRu>PnR2|*DX#gCA$GF<7B%Jgq0=(yGlrBhV5uI2i4UGRI#Z_; zNWSOkgqo5(thsxzC$kRk@!ECxK+nKByxa00X>*!8{aYuO0b{7EJ>|4h*p8D6veHv? zOK$4!&mWPmsk^C1Q}?Xbu8A8}uNj9o?bKb@`7cv? znuZ^p9kh^|nj5+XP0d1vh{yVLCB%w<+bGrAC^ussp6&Y&8|7MPqm<+7&N4d-U7CfB z-Z-l%{K#Ynm$Y{rM@J8+EiX4?@vddU%e`^z6UU(ymh(u>4VtmcynN0ZM>=Z8;uc$W zl{bEX8H;zEWp=rJsk_7((<%W;&PAPUnZ+1Jk*bI!**|%1&oEGo62|MaQCZxEO5(o{^4aVC7$P z^Lg=5XFgx<)_ks|ozIt4u^ERusIBir>k(rdho8I=&GYGEEA(NDw}oLq7nvN6$2x2o;*O!MOboEu-!J9F%=BuHXH3-m|(LPODSnoBuey?ZiQ~r<|kBdSY&JT_@xwcS~k+f9lfYF3hu&`(u}L zBA$~MG3D6WF?e!`^c1^cT1CI}^Wt)85%_^W!HN!P zW1ELI$jv3GaA`*yMl~>?EG#K;krX@CB0bO7VvTv0HEfzS zMSZv=Kf+09l><5KDRkJ+oVMIxRt&evEGkcjq*Nw2OSZq`gpbYSN7y(}_;wYp z54^o(8*#>RY?@hNr_3V}Rx;z?hf7750a%yzCMZPQP`#9$yqtJzre7mpF?@|6ZB00( ztg5=Iyo%-3aLiH%Wj#hKPs#P=pWBQOwpc#oOWnk!?X?58wQ=}ln6)IG-(I#?Ijup> zPVlwETJ*sXV$&&6GfQzJK?;gJmBBPq49ZsWq)lV=2)->yvM2E4fHi(pd#=RRZDfs>uP4UlgR_2>=79pI z{cc45@(6=e0Dyu+PZa4q_pwf{KNNJCdS}*fRlem@ug=ox78WV5&X-ubf&rdjYVh4D z8EuCNhYSFTKz6@p|1PfEY3gy$EM;H#E~!0#+pxGoAM7dBt{_ga<6&z!zS178Pcg&FQS#dfSfDkqdic3o9<=H}%~kzGJPb2zrn6ar-& zaoX@MhS^!}?9}gPTJqqG;a#j2*2HfP$CjCP1N9H@Vz8O7GGB$`xih*lRV{GiI=N+}$}Gv6>P8KEdzaqoD7q{Awt3K71!^dA(QHE;zD%_R z;|#oilK|gLC^{`HT#Q-h#``HhgJyx-hYQ{J>A~hynOWc_E^;r`682qf7P^UdQng&2 zMNTz$4|MZgkG2gHYHCx;&#?XYVt0rE>zj>nlI2>dY(Lj$KOT!O6*0m6h}Ya}Ofhb7 zN6c7U>5a%|62#5jjl>nH`9;zVeI@qth@ht+L-d|Z7sg_)NC8j86yq_dhJW;x{eew7 zwAZ85jK#x!eg^Di4CRV|lo4P%;-0jF5*mvKUDAfD&33pW9hJcIh^u8Lu`EA?k7mg{ zHH`tByv!nq9%I0>jnjouy}2&fxl#u%wH?^lS7x@uYTG~!!>aOiS1J8!_NsK78H+bv zEsRwWbMr&^&9<>>pyHJS6@R9&X=yViLsF31zw&(Ox(=D5Qf2m_sAEu-znrR7yrCJ3 zHEsbuZHup_+9*KUSmIiDyc(xOF+9!(apRB%;+am#gh3>iQ2>dcXIISFir?2;m_UG- zeVhI}?Kb&J-4P6lWT(-U%#Hp6EdeG zm^8)V4iQaG%@hjDIIB$90qoG_4CIQ`G&2@&xg#3Ys#GHnGh?yA9pSEeaCyp3(>v}K zdC6u+4N483L2)>V9YN-1^@69L|LN6_HMj&VJ|GPO$2K6bZE ztb!*Vomgd~Eqy20JN0CdT){eecK$&_YM4f(Dr8MRgMYj!^H#Bi_HB0~1Nhy_f%Hss z(mC?{D)NXl&pL|X+wOWvwa>X4Sje$Vs1J`PD@-x&^OPQK!(xIK`7@TdGr7vqiUoiE z%vCDhDp1f^VsW~H0WI?3{l<&8lLclhmU|+OVc=tzXnTQ*SnC)5QcDy+%CH%Rk5W_g z{Ze-vMpkL5fOU-74H8rk5gNKp42!G@UdG9>#PuG@0~&-(S26nLGG=1LGs#tsTzZpd zo_;@VyK$|j_4R7~d^Tw(YONPu%LSvFyv!JPK!;Y%gdC@>Me$AGqoV z5ph{stbDo+HA&dR^3^}cX_H`6cA;;amz&8KVR>CpGb!Y_sDHY71?9iAF4NPS^WwO(+rK9# z;9=I|&h9ci%nFDnXQn4B4U8HnyH4im%1V)ZEDx$uWbg!r zJBVIC14=v+XMhuw8Ksh8^tB-`PJwp9mid%xZOKXpZ@qwJ(l@3uxEC*B482p7SqD%J zl{~V8ho}{_*V+$G)OxPNz%4G#XXFN8EwMHw$xBv}kVEoDQ;e&+3mnGj7{Gq@MHsZ$ zo-b%!cxUx|AmZU$Z#_MK{DCo>vgT-ptxsoqlrehtlR1C2RQ+U|*djV|*AA~dE7yZp z^94MDJ(5>xWmoQPIq}-rmJ`3|i?e(Tu`D^CL44vVWnjXdHaa6noR{?DbFp>qUdg~g zQqh78qGy_{pdD5;2#Op2z0>dTpAUyb^m|!n!3<~9zCR=`wAhgNMD#>W468e3<9T&R z9NWcJa&f1Km*K(h4-ummySTJd>cLpzogop+elM}#Z5-07k5=jj@w!v@@VJ8ad!iT? zH!)nQcX%q-z>2{Qyk#5ER10rDDa(SY7UigQ`p^uQl@@gv6G&Py=Gj>ahRldlx8;vC z+u_k!es%DH{CE|*qhmSIQuX^zOVu-m#2J`Gr2Pu+m3*ROpadKGDsTltyYDSXTBV8M zOREQsCC<%v2KvP;ai`PsTj7aUS0fqKfPb82kFwM9<1ld~qi}1e-|8vm=f~r4$L*2# z>_tIg3J>jjkg=+2u_*^% zc*()mfO5dGY~OJ(j&#(yxhg&xIwHRic>*k5 zQ(38qpIi}QLAq=LgZN_w_6iXjduwrt39*P4G4+9K9``Ee2C3n@*+InTY+nAsyeW=gt#$XhG$$M-bn0SiX+Ncr*6pnfgPSFPAV&97#+13 z8j`FWW3U{)5YZ`ZK!V}q@eH590r}~;eHoTaej~P|{FYYdr^`@NMl5iLFgsdaREcwX zEAXkYRolUl7<)sUl4cCS)@ol;Z#aSC$wrzH`-^a-1d9Aui8lHz>#Wei^8wa(kQ(H(!wJ z@Hy@gN^23I?@)$cqH>8{tV2bhzgC9%;~CsdhxK(>YJ2!XUoFS8d}cMz&vpq+ zYl?~S050A!8&0r(S+4u9sQbeF`gUfJ6U^bbOu;Ck?1oeYJTmZpW{R=NGt!jfl4OB( zH^-FRuDcB0dcrM$pArjkGQ9nweX`Mb@rFw=R^`N1%GvRSX`l};q#B*^UF+U#im^b5 zLlv2uQWCqV)r_ILmS}IY9k)C&b}*L@GLT-F>-YlUjtGx5R(Xu7k7FH=qoQZgCO$d2 znu&=@v=QctKghXqyT`5(;xWaYr7M;q-4^GPvlu(icMh=1Xs$+tP-!M zthIc#d!%)J{+A~127zPP!~>C#YUIP7amGxDil~m624!oM3K)=me0puJJH6^nUa8vc ziozYz>jTuWd(zpCJt3Qi3fz{q9edpMf9n`=Z(pO`Ns|1oE}tM+@@raV2~Ia5G%3n% z^hguJ(dv1pP57b=dm-ZHbax4BO#}Yy*Uw!_O#_aXt)!YbtQKP*6|4odWkjWt*6E=n-6Hmmme_xb ziqbE-MCxnPSyI2@(xf#ISEg%OXd*ViNrLi!y$EjTYwTc9bwAA2X&OXN8N8+3g8^)osp?{P1XixcE2;UiEUj@1XODMM ze*L=4{iwK3uV0i2fN?ZRHjN#e^=zzPm22)h*-)jazU)!Tp$6ibq+cQ53+Lg1Dxy`FWj-#zC?#+Eoid&KBo+RRF&kqkhsVD>D4i-*IW~as7r4UhbNeEOIcKT>`$9T7mvQP2(t)oZ*Cv;KL44|qbGj+O`LoN29qSz{ z(H4}lU6l!9Rb)j$QPldKwn|RWySb@a2068o!Kp;62&%2Ihml>&mihgaNm;!Hvum@K?e(j3gZODd|(L;0`&GqtByy8k^u-Ljgg9@U6t`M*5!3=#Pray@X*DZa~{wiiycjRKnOS zHYp9z(EZO9>Yc8&4C=}IY+;irpbr;z%b7Zwo)Ua+w%CVQ())taC(O3F6~_Ij zld0UP{5iAI6yS!zl`qX!{zd)dlYIF+~C$}6^rsEpZ_9h=A) z;)*`QKhgy(%!*Z2*XO2UmG&?va9@VcpwdKz*~}P{wWkoi4NRchPbuv&Pw@|Iw~$KNX!HQbDcE(TbTgM`6R{KwDSGJ^%N(NsQQutQNVT`06j^2Z@F^BEN74o)d zK`9!}r={(;@`@ARp$xHBQNPzyZwhdEQf8TQ#xS}bm!;RDZxP$E@5rpfX#I`3QQjoh zAst5RbP~g+i1^wD(rU)e7Nbi0t$rG;kxe1vrmeauSHl$&1eB@47j|ri*H`mI@%kMj zlHS_6yFhIWj+!);dk{Spgg3-6di;*1=^~4I3G*cR_bfIJv!d|E#pQd-@VhSbM$ug> zE1Ns6y|XSAWpJvb&vovQDZqLwXFW)qP|L+g7vye&)eWIHXZl%_}(p-cGy%CXQZOSr!PrQ(XY>yKuuN`ZSr|3KSoDPHJ*2ej28=2ep8JfZI-)# zqzZ^53iQbqcRdSD4{=p8YN`dRR}6`^UNZ|>iZk6IaUsd_DI6)A%WQ>6)nlb+o~g!i zjm*^JJ-5Jb)x?UF^}4@nyC~E_H7-gu5+9^$O%HBLEwWa6bF15C7!RuD;~G&g5+~U1 zJnaeT(ap&+22C~I_S{FzRwX>SC^hx?(9&>DGKzEDaklfQG0OauJDhIK(fw|W$bU+)LL&BRcZ;dUa}MQi zaF0}-ugO)dmshzrIP>so>M<(r=l3CH&c&aXF-UXt#-SsM8jj6|V?ng~EHl`Y}O-PI7 zlT=jW{L!_P`*4hcU2H~)zA^y@WJ32s9=Q{u{sq+?^fQa ze0Ny^X4D^X}; z3@Non-2m~LtCV%xMKAU(QqR_+p13BhL%LdlRi(t*wBLAfcB+7dD)FtmOCNe=Z1)m# zQ-0_9Tu%e1cMx5QNBJa~^}<)<2XvV+czEA>Q;mlXiJCFQUHgu7vbUWzkleT$_a7qn z_ZZ@deM?O>ZaZX(8G|SGjf=|Eh1PXy|JFm)z=lB$EHGUiIHJ}HK)-VYzBzYh=yj%B zS^C11tq46bYo?4BD?QQ`4QgC8lZ2!U>%yZzG>Q= zDIp>Sa$e3DMrnN5FKs1>qdbt%ZHZBnk3WhlSW6qyaesqOwmlegYN@t@s12sc)iCG2Tn+C? zkW~#e=5YMtmO(CCj*rT_i#JE4da=wOwzXW*IRi!i zZqMEQPBDjIi+7~ifj*qxBbDwG`&pKcj#{6q{9^ls<}YA#MwKvdunk9T7m1L{g)yuj z_~y!t4D+&0Efqe5vl-)A;`;(%iNb>K3&atl=Py>Y;UGg58!vL^r1@^D~LxX_!$%Q94SA6r%1IbB*rIw&j+kw05_o2f`Y0T zw1{5ivPmp+lFl)K2?F{fdYG|K>wf`zEOdr2MPpL;Iu&nwy4120|myZ55IZ_hB;T}nYw`j z`>Tc@JOh1Lr~(HL6u94|5?>Cg;vAK@H$70~F^iS)v{JaRt+lhzAf_9pdvS`$VG#Sd}{mh@Sq}hs>peR zihN^hSvROJ>s6$GP?0UR$Q^@fc|=7{8&qVjTco>bP?4uqy=@z*~m(_!c*euXr z4=QrLt>uzIeR)T55qsbKMw?(cTRLg5B^39+k z=h-5c4l1%yMLr!=sasROE9Nd23LSt8I}p2Nn5F zMP3h3vRv|GEJFZAA_&ihuhnsVV-q zXIfC6O3Z~WZkldW6$a@U`8}I zCXATcoORW&%s$@Sux}>YA#U%}z1ZY9JC66U5Li;Vbm0 zQlXqn1Z11Q#O~GAlutY`$tEe0rhW!vR_&AO*Qf)#y1Ker$*VtgsWm}Kcfw~b;g^1O zNp9SUn3rxNdTPoG8E_REA$`K;u?1S0rnPAZDgbQS%C(_;7UX6MyH=Mg=3?n=ZY&JFc<3N?y z*(w7TYLnnABICXpDsq9@2~WBs=x)?#Zb?ToO3O~T+1)=irBrF~#CKxC1c>KTQB#9A z-TjP-KvTYAf}arE$K{p6pBsst;{vpyT#CH<|C>!W<&12ur$1vUoS`oKO&zooN%b%; z{0rm6j7A29!)b=E6buo^mf=BHe8LRIj>g^T3TzWEw*^m><{*Qy4080r$O1frL6%aX z0*dzEF^-Zr8lxzR<4;i~KioY;99h8_{x;v{^e7SBhY`#!vmM`7TK*#p^ACbsy$Mg~ z4D^;Mn7N!WU(vDDos2_?+m$fG?1TqWwfec2{@BVms-Eu}PBsvB@4bK3rj| zI?Ww1J7G&oVe7@j9CxYN3BOtUeaU`ol5!<1@HfdtW+(Iuf2Vk4%!sP9@YOSn&P6J* zU!03Ym?NWqP?9$)&rbVfJvn7Q!!}Cd4-Bj1w*N0WXy(5X975MtQugbUrJD)_0sqf+$g*Xz!&)o%OiN9Wf9=hxTHuSL$U7c&j4 zb*kCuyu8$T`Ly%vCg<0$`h~gb8o#6#MvSA)P{bQWbz(xq48_4x5-pL+<7Ov}h@(Ba zPgH&CnIh8U2olK3JSCC>GP%uYVV;bW?FM9;3k792Z` zc*NDpN^>~gNhu@CPPoL~#l7gn`8oq3R=Qhvmet?$7hsDTPEHfuJH_fAkXp1WPTK0E z>h+BJS#~hz@%XK5q7NrOrX`BbC_YO~<5cdYWx^n(^#rVjm9YPlZQ*ZHjwKL?fjfR+ z?Jb+z*7viX=5)AGALo|%d1i!@vp&V}_!%~iLYHV2(}x>}kCXyucEWQrqvFdtF%M=i zyHPd{)Up91>}7^C>~gZ2pO)+u+h_%EI7OK_Pmp~#b2e0cl4<`KCAhLWwbBEhAlRk2 zPPcdumH9T2xot%U;rPnRNo;0iv!2RVS6QuSj|7_zmIP5K9WaoEM>Kz$YvMuY01mQM z&}pHf@894CuT!ZhrMlj4Hg(1vg_f}?K7g`K^J0m(f3&q0>~a;KbT2 z$wqyislE_TxGj1j!Ml{?u22>g&(1P826XZVs;BDs&UZ9y*B80Zp0VfWVlK~W|IGvM zp8ofD>j&Ol@6ig`&*m8VT$xwof?MKQsuWzGC8s#QBKBmF2jNhLkLJ$|@E)0Ix<1JE zr1{HSPc~`IRWcz8yxHeBg}4kd?|sjJ&Fa1fZCciAdiClRqgV15YT-Ynco2oWJ4K$g zU*BZER^%SKYK<39B@Od>^k%4i(@|gwk!KCe-DL_9G=&)2!UUJvg{Z9JO-u=WlX_D)j(VbNE$dX7&xY2{ zR_sxb`i?C`L92{bKC~4O1&^B5ZhWMSdQ6%^{N3n8l~@aJ3+o8a8f#A9J!Dq9iJN*~ zpi^*-iQ6YH^%n2}Q;6&Jp}fei^>#b%>q26_iI@m}6CJ4wg!KNjzBW^c2NB|KJkCV$ zKwp$iDts^M_5S|80=vlnO9yr#@x8Ke9U)$WzjSLjmn(N8!=?~#+FRnj!%nSo-WV^o z^j4TcoMseR0dJS$Iu(m~9_zS}b=*%Nn7&$5h%3=BHN}`+br19?k5wUYpZC6MneeV; zf}WkXm{GXF9iM6n@w|-uwkEijPBLEH*e7A2kaz*}v{$cYjQ8nA4j!SKKkwCaTe_+__lkBDa2Wf zm{Iu16|Yo>mf4$cJZ1205k=0i6C5`6xE4xbT1c#S+mO$7wqn`O3yEJ{vdzy;H=08H z>XIMix0L>#<1Qr*Xwj{1_xEWvu1CMBRh9#X&|5V9;)2S5g45CkLJ-h%v<`|G?o7Xs z-2_!X<~G6gy4{FjKFF{Zq*WdiEPfP&TsVkZuOIm29dDM;D=R~cPxD{~axChnT-E1# zN8~3g*W9g=zSORRabR->ihacq`A_jEyO1yJkuc!Mca#Pv}fW(=+!GLq9xAwA`Izf-~= z-1VyaN?S5!3h}yEvbi|h7hRbJv^eie9X_%&#_YoL0gswdI6kU*9^D|-!w0iLAXl+` zAFfZopjw}_wKkbT-0GD-V{7*`jILLA=dqNi=&E7Bp10F$YNl#4<3&40?VB4I7bs5g z2V7l^uT#^qY}EBjj>s?N*~zeL^6{=|8UXQ+Ji8_z?XrydA9)coiil6Mi*i36SCvG2B9xE9}#vWi}%5JP?%v2Gpd;Lt9Li+HA5xQz03UrlA^VIxn zu5-6KofEFo?#q8!DeJT4#^ByYeO zKAfRk``>jJ*xO~S2WZ@HrVy`q8>VUs&$Cmg&1;sLLR^T5o-XcE0_l1@Yg)wc^_WRh z@5AQ4GFz&JE2MG@L7WgXNYLX4w`iU{J7dU}478!$MDSKuG{reYn9l zQeBQ%zPhMzha7MSDU6vBJddn7-7~B*$ zyWo@V0w{^L7bg{%T@(TruxI-wT3`53A<1?jW=%6n^XNC;{~c>Kt;7`K=e&9o!kjKY z4>5(9<7?nl0qOAT?5HWkTwkft-#Yc?`bv3%U}--{;16aDzTrqNFd@%4yn6ChQQ=;naxnUdi6M#Jj;BL6j@_*zZj!`+H%w@fwJ4=H z(+Z8g4q?qoSbYo5?`$sTLODUN4<5YDk@RKZ36SuNy zAL`y3-J|gb-Hq|tOEyH(H`aLZQm3-<77}ZRtSmQPywE9W97=7rRFX^w4xPM5<97f#R zsna#c5>tbl+!MWt3rscEdKwzUhH#y?L70i2+pNTLggqXv z^OhPfE>;mzXfdl_`uRZV|Q+gZP z10Q#LTg*PVv~$|AOmGi-cE{-P+ulgA`<{v2c#AzpV=~($xIhI){=k?Iz1=V^rVyVD zkE5@Rc*#i#H&I#%Zg3FHIH;CSF_@M3UE5ac3N_b%%XjBw%NOF}Ar7C0cSAM0b85-@ zff83MB#D(gg5he4^OO+irvM4sPH|S@$?gIwMWT*s{>B6sO7jw<&w$$JY?tX^Ay)XD z1}lL$(Ob)0$kh6QLhJKK%`};?^$aKvB7Jl_RB4u!&+qW5HjVg1r|L_4q*A=&RDFs4%2xf3SN5cOoeWp>D2lR> z*x=Ny4lHDluSo_VKJ?b(PdR}7sNd%X`bxeAI!G^@Gw|;0p%Rc#93iGPG8T~-vXC*~ z71c}&OY~}Ifd>AYCt?b*ns(r?+k88PxPlQCndh+U-+a35HX(~$f_IvFJkYIO{kHSX z%FH)!Io~`g9zxtPq?IwInczGI%`jZsxlH4GWr!psVqHgp3>JFy&!G~ok7Seh#j~A$ zGZ9a?8n8`7Hi={X(q>C!>|LGqn~BsdmHO%_Unzs!PcCWJ=@k1%hgi$jp>blW+`T^R zv4w?Jz**2y;0QQdhAz#h1*df=C0-%%)6h6$DJsd4p#3*YVyuHJS!cyHcw3p|Y%4P2 z*Y;X5jx2{NqbgsXU#eg4%@RYpIA0@*p*31+mLn!bE5$kK`;IX#)HA`MWQ|D+9`7j;uZ-bN3quZPVIEaG&wq7LU;vn$qo`M zc0Y0|F6*lmuO)7p+GuL9#a3sTCO#e7Z;J4`)Gtb+UTZbL#nm`b#DSWk{}}WdJmngvVvhA6Bx2tc1s*5&xfvFfcsX({d+y7;grzg~*7j%ZnM_F$7m8Z5-IwUUQDQQ1#!7M$)O#&~A-4_&{R$#CjZ zu#@cO$J8oTeUg$qdLDOcP7|;Ws@TCd7Cdy4elOP{Gs7G zEK9`P&oDK(e9&h)`qmAPGa%!b%z#yNklv#i;~XuVh}i{~%=&Ldzd~#oTB@l*vt2l~ zS=u&_%F*`oEN$V5n;Kj)sHdB3Pwz4{I3wFhQ;64w_lr>opYMA@Yk>(~AgEbKw<*L^ zLmQ?NDZxBDbKZdFU31N?*k**HmW06#YW0|7iT;^yiekh!!$Ydj9E;OtT7e8d4Y$`L zESTA*6!hApKMvRR3H>uyDIHiLF=v}nQ-e=zDi&?VLM+`z1m2y0vgo@KL4{=Lyf|xU z(quhnhiP{kasD>_azt$$sDrqCn|_|aKPk$1pbCM#Wihr@JUCDVF4?AEBp!4Ky8^i( zzpPX}HuN>YNj!yn%cgfq1;cw2zl+OUOWA4LY)rQ{R)=Fl?IW;e4BM^Uc2DuJ=`zLd zRVlv^!!t5sv5j9Yu>rVYt&^~=1Hap#e9lO(J*JOM4F8d1vvRZ}MhnzveO}HtGRwlV zL8>Uy-N!JQb;7!K&kSTD-g1q|Z^wh~2=1{^$9YPdqNC`S)LMlC$`@@EMj6zaYgW9x zIJbl3qOYB?tcbFHpwe$0!?HD2{!s~8CK449<@oRu=#n~v!ynVry|rcQ3(Q1}ja1@F z_ekS)Q2i5$y##<2OaNE8#iU|*G9_c$KrF_bJ#yo?M21?N>W<)NJC6HZA=+`=n_f2h zP=M*}3Ep>S!vDqc^ z9LAtUA1+T9FsR2LxO5fw;U=nx>g2d^UxGUZ`3k|)acO+?1oWs;<%_^ zd59(oDOkc7Mn$NCHl2Kl@nS_OP9!$< z*>#1ZbMU~bvK-mxd*nB;pkj`-E8OO05cEx!WG}uvX%Jt#LL%m!wkMC+v3)pD_%U}J({x1IAQ){I$&4r}RiYKiR{1)r>T0w0&5dJn zqo#}!8Jm$)D=r%#)Uv=5RKa!MDu%4)OgX5dHRVbE?yv= z+KUSnf~r1z;Az35NjsV&3iRL$Pd_?qk!P6#@s}tLs-PUkvE1#KsanryXLPP$W^_du zKO#Q~gv@B%h8Bxi2~z&gIBt?D2(3=xpR*ub8EWqtkY~Y8S&ZAhTSf=MZT~i0>c&!!| zefv1?p7@t{H`{k#Si_kv+_UMbw2;pBl#0?{C#Og#Vja(5fU;I>u;Oom`XsAqRLwtaTesL zaIT`O#Dtx@>^{q)g|v805zg~y#&2Yj9i~PBKmjWzj={YX1VJhyoMxsg1kk-&)!6#}V}iRUC$yZ-Z(njuzegeD-MA-qz`qU zKoRD-Mf<6m+uf~t5F`pq8TR4WvIE5^cGcg>vIAKoNw|pz*zMC5wKqXYne<88k7*Nc zNk-M=uNJ)Kli|KyOCRlv&Ruja7vSUqdHM>o66wbMb0qz{4xRmlk!mo-c-s|GJBmLXP<4c}WC$yIj?M&v&wt@unS#JeyNm2k-f(td_G z9qC59UajwYb}c+D7||->;vix|J^C7T!hG7-X1v&WBt|!wBHZ8!VW)cIMOA@R9{lXm z(~0trtxMZSrU?JE9*J^Cj=V3u%oGXHrodDq_4wgF6}lb`#5N7sJ3<_?h?reQ4AJo@ z^7~Fal6BnMc=2Xm855$y6}m3z55)GR#P6&l=9RvT_oj&W(j)K9lB8ch?nNB6Z3?@B z4X=MB*=UMzlP4tRpFp3Vla6PToaHYh<(V!buJhO`pHIrS6Z~&|J4M7nji?NvR(K#e zJKdYvB&)-2Pb`s>B#FH*dql^0*bG^C_BKu2ymh5W~&va>?CHUw6u}h1yUHZc5(z*Y~E{TIb z#X77=*V>DuBDiM|nJ%?tdeC#kY8NB1RL%byQe_aMYq#QCm z#X91U?yRw6PIYcC-e+Vc4zu~d0pt6?+XwK!JKRT2%2l$!YpqcM`qF9$4JpdBj$vIg zzs?gVLW|{BleMwV?OkNNIL8zD|FY;7;eC%{%9k*qYJTmRW+vioPh3@9ZdqxphGAaG zcFXJ*SSE6=@#2@h0&KM$V7@0V7PF6I3l?`WJ@}%>KiFEU>VD}`!2i6AJ!HX=YVT#; z*Q1wfz1eQR*wbx_u*Ms)XW4yywR-)4Qkt2Gcsw~pKX3OmSj+PBJ$Ch7?-c`Y;-pEG z3uT7!;v%6oRCKe6D{)Dhm{D74ZOopsjL~;mw9>|8dkV$FQ#y(9x14e}C!Qny>lQj; zLC|;i>U5OFXAJI|AdAmrHmRMnCyNhM>hxaSr!ung6yaX4a_MAY7OvV{o$qTpojPyK z)~OztBD|pCv{;Mn3Nr>bRg6qAVTR$+gRG(Vj&PR*fkzw@?_J@Lem&$wGTsyJ*RKbh zU-yO^M2gy4Q-tfivLlo*AupjBh97z@>CP~vEHuOLeXl(#&oT|B1`7ra;0{Te#~*W=#>RtlM0=i!tuteE z^{eq_WZos-dPlc<#iQfc5rmg{b>lbO)>@EY@Gt@^8Rmf#80J0}jUM1svnOuQeisFw zPga;caf_$aq1Thi3gg9`9>0(-dt$M#lmQicIH|<(%X|E0Ph95{u};=RsMS3&&sWbn zt+>fwF>LlEDmFM@-JSF^fImf1)FN}5l8B6wT7w}ictfDG3(}2p5XjsN-}ACz%CCF` zKJR{0tzN(u#>^4K$K799uU8SVlyTw^`D02L&@4UWkxZeU6yZWEV|u1bi-g=;vO-xc*IGc3uV~m0QS2*ktRfws*}E2JMqY+EnVnif-+cWYi4R>u)d7i+QX60XMvYF?mD{oa3|7tl!zZ-A$h#Y@V6nv$; zObZ!ik=YZQ@=DpR-EN9-Nph>I{m4`5Bym`poTA?!ds@`WFMSmlQ7YX@nb0ROsDI5c zTrZK6cssdBZBbo-J7mV-$w7J#F%9KK%5Z8&T(Cbgze=wK3?C=C?j@i zkSL5D8(7L1^=blktH;b*iy8=)aO(I8QWr{;d!v$J$(6EZp{uQGBJ#UrLiJ~b@~S`P zhI*FnJCd%T`{k4(Gt5$DytIO;bfJJuh%)}*4OauPDt#Ma8t}JOaChMoR~#28jncLa z+HxDarkEmJniogMG;DOmi51EWhHY9DoI)HH#n=$`j$>HJRN?F{5%>MPI8hg60*CoA z*5a6Nn%(Be_m}xXZ1Q2fs~+u9VrrC4vIkcYPj*F35w6XPqduw%*Sq5AviV!9h)cVo zvf|IRh8BC|-jXMdEKF}rHF0v=zkh`p(Q+Qbpv4^rrO(Cqs`28RPRrYIjW4cC0$Yh~ zOBLc#!tnW6u7SVjYN+I~RL;xYt$L4P>WQhsMP1tFKQN9DUGZwrFfKlE z5|*Z;c+VAA`IDl!EFHzSuFx3jl1ki|_B+2~S|d_A2Y2Kfm#+ItGj^Otdv2Ew(?xkp z3B_QY6qQ2xmMe~{(ot#L#La?l;3Pk`x8!|hwz9inbg3!AsiLrQCkZi|;EByzZ!ftb zINSCoPF$UiqUql}2uJ3Y&niJCoaJ-t{;b$%Ay?Dd2CwZDcKT7*NE{{o>Sm=6^A<|5=hIPtBx9PlnD7Cd#5!v?NwLn#P65Wy z)@UPNFiJI{u&Z2Ar*=;Tl}jT=r53!vAtCX}ka#7Jol!N>D(+!Zg}Y|AiSf+wEfr@u zIP1e$d30`^-)fxjNF(wEbqgqiVgf8-1w#z%N&LPPC)Z+Vs06yw!Mdu%{+b!|A2^Zw zQ`f%-t<^^dveHFYTh4}L6n#;-5;`j4!VBA_-r1Hl*0>sTow!8!4W+BUr^ntd;#@aa zM69C}^jmuhSlD5T=))7m%xZMh!{3I)%~+h?>4!T4w>1WQHYB1C#IM2_x>BZ-Cgamlt7LZnc9M`wS!cF+EBd0h3xm3p%^Cg05kJ)ussF42dYao^^jtG*wv8 zsVzA#zXg5Tp=WklfphZ^JMFR+3fEIi+}so8VpD|syd$$~Vbrm?X*g||92Z4e3ql=r z3|bEGtQ6>=5=v=LC5c3P1%`#t(YBXO+c_F-WqaAh8n#Xu7})}wl&S@7(JBQM6~!?* zYV-C9@PT3RW+&0{qkXle7(ew?IF~I*{+z8#Gm407F;6qKzORf2nqvHYcm!@GlHW8_ z>FF;XE(FF{Y??V=h3`R#nC}0#=7Mo4RoZDcXgdGpEw=0Wm)}%e-|)CH4a#S7bS}KJ zxj9Fq&BK(CoMAHzt7cgZ>8<>@a1}x2vpSPOHXSHbcqT{F#(^)t7kPE_;XcHY_BTy2 zR_~zkIE(PH&Tj5tigD+VkcNEEDf3Mc&a|hx)|1W3|5LA`SVf8Jl>Qno%q%m-xOsS7 zUpzI_N;dP1t~9u_PYEB3h-VP`gBgq4`jpe9n0UCS44=3qE7&mSc9mIU&0z0#`B|i& zEAs9$#dxnv_iz+pdEPvI_kPzji8Qz0z5w3C8y5~95E@E||r1%(R?*;0~0Q+=N+ZiH2}~R|S2_aU(n7bFKy!au4Se{WMR^ zXGNF)=v+*q&B66Xo(48UjTQ^}a*|^$eW6nlcoD805;w*4%yr|VSzC3MB?}x0^qyg| zt}YND{wdiieYn5fny%Lk)AIK0A}Ccc@y;yi0dFUDkUTg{k&l07)1mt`5;MD%8wtZC zfL$t+*92NoMY_41tjf{WWJzI@AJMlrW9VqBX~fV5F)MgWWpeiw%g@3RP2SIWOHDCO&8(`ed0qPOw5~=`8e8(Z_%sVSHUp_o>E$o8 zEl>X<&vG$4zuCMyE2rm1F;{RCA&1_vt~9033HEl#l`n<_6r1`M`vx;chx*2 zQ^O)RK9kd-vbh-xtjA|rXMvu6G^BJ7eYr?Hf0NvU&hwW&rTB*}xZHX0{t&q<=BzCrZRWgi1HvYQ|m`&(STXxrmnY;h5NardP3YF<%_Zk z*ta)2KcC6Gy~+8ht1k5I&CbuKGH-8jem>CT4w^%f4HR*l`_Bv}!x~{o$0{8~S~hJFWr`$$frdY8n&v>6K7t zU+XK&{^Fl;4den`(yNq-`tkv{^nAs`cyW75vw_Fm4cg+wZ7JtVJwU5)8KWgqH!`N% z<*|#VNc!M>Vn~R-ZH*US zr7AE#9TlZSqH{GAd~RnGU%MM{Z+gJC(KbMs=M_sx%`Vn?Hsg+D^e8sjtM^MBH0sB_ zt5n|wMy4wa-9ofCibyXfyY>7!W&CfWq7F**`1RiTdc0c2o!`qcWPHGgjQ8|90E9q$ zzr?V=h^vzNDeF4J6fX^OBRr96#!85zWCpPZh&FE>Q-am|;i*^fjZ6k%-o-i!AKx*_ z(%SP!T-Cdkf*EdB76LmrpL_086W$ljHr(b#$&gwbK{XJvmFoou{$@rnkeBD@7 z{c7ETD_hlIRK5JV8sO>PIId6E@)Z{51g4Gwvfd)gqVeK_-U^le#5ABFB%i#155BPs zOy;Osq%Cq_0)_CJ%sO+an|P$pk4b(B-U%*YyT1MsA>AE!PM@XK7tXJ9`^qwx&*6|p z+wE_lDJqemzJLj<5~pgdtvH6>imMchQ~d{_yx4>zb4jC{XASiFQ}x>FHn`Oe{B}34 zvDRTPO70WnZp+ZPdUR*Ew&$}wQ8N+uyBad+{tr!;Ho2YmXL;GtKl6&1F*={Ld*DHMI|TZO+Aedjv)CqHv3S`lz>Gymn}*p8?98b){4H>uS~IWXlb#^xfyeLZ&bZ}*XU|YONc9SW8UD5`SSnsnBR4`=*-VR_ttk0jPheeb;@3= zbNknBNynO)dHk}(S&lzS|sjdy!a_sb^%*z4i+tia#_vXRh!K{1I_$o ziAkHddU?-U$D^l8*Lx$}O@w%$N8=_Q_1arehSnP|p0KnaRvRtE*R6jgB8!D(y7Rmt z?#dc*OcTMWIV%0^P-%XKO8;eKvO3f6X_1mB7^if24f7e>=pr|6H*xMd2`ATjN*$2J z%HDzDU1P%+BM0aRSlH{FuQtdw$QVX-5zSHIM}D)+MJ+A&Mlhp-CjKcK-V&VFvfQ+i*YH!2dgqpg669HMVfPu{mdJGy5`Rt?7` zv%O^|Lmm}u_k=QCZxY6^5XZ^;g@V#ExsUPULT68WOmzpk(1MYWHAeQv+nWXUGT!I9g%jUH*&!se6aL__9k3 z4d>@+_kP_q1#b!vWvO=e5M2_WnSQrRu76Q>g@V;^@*?~$^aX`^P~hCVT@}LaIcMxW zc@45#{;gKo-o5nfgU7qJ+EtR5LqayE2fB7){)l{=$uVUen=$jGo4BS^s$-Krdv!=@ z$)Vy(eYKuU(u<9QK7S-X6YZb?p-pT**rraLJo+FV{SHa`auWT@IRov!l`qvstMbL! zA^~x&FQlly*>-hY=hG3|%z%|qM~m|FRrtNxIxj?ut9>iQdTrRQ@#5*(RzSYa=iDhD zqi0{+d{!TsGk2ivxe8CQZQqn>`x)EzjZWLo`5N|Ou#)>L6;PX6{b#P#hkYxpbanM? zEB`+0i%+s)8=eZa){fM`{LdR`@Pb@}7iSuL*EVQ_?d)jY_i3_6=UTij*W&9MCwgVl zym+>lIkDLn*8nq1E>-K<+1dA1^9P!|PEDE+mUl^k{W)1+LinBBQ=;7)LqfW_LNY?Y zMDWh6K?mQjLv-+++UZB7l`z%~QT(s2iFmX#D`ej>#GY`Ebn1lruLFP=xv`$YSSrp% zmgR-B#8~lTz9KL?(3V>$Ri|bKQi*HaBLt$_(9kDvCRZlnHv0<^k=etiI#}-l1eiMCBm$(HkCLgs#@9)@?;ET zRSR>qTtBFm4+d(%h0uyT)OexDoq*~UzyuLaXR{fDRpTQmd1=l%sKAUNz6-}qCEh$( z7rzuL!Put0sJ^)r@&(^-L3;89S7IgNIJs872u~)P@Wz4OJmKh)>@ziZe4sbP?TGUc zHX(n5y-4R0@vjjIQJFue>bGs5aq?plu5a#>VW^UL8d>PYO?}FMT#2U{w|btM&a0N~x#sUxES%k5e#tQox5((*>pyn{5o6ZXP;qpa-;%4`7TZ zTE~Li22B?AH37bQt@_bFF=p?ITR58StYka)I}TT;H9fI|?&vp)|o zHTa+}H`BP#X4a@w;8apQa|nE{iPvT)dgVYRcUf(8f?Y0e3{+wT9zJXW*kmC#HN>0l zxW;|2rxd$}l(daa*!5P)6{7mE4f?Nlr5#)X-D`zX@C4!B!r)a@Hp6)~S8mgvb!H27 zj6F*QeT0&+6CR;yt%H}C5tVsZ3;ZCi9*~Ti7v+XN-$GO5LzJ+jNS_!J)o1E#Dp@H0 zV0WCl`#ZtgHfiH7L2|RK1l~vn7u*nSA%L$am$Fq-Yd=}~> z5jNy7;Zf)0D!IaTCrq10dI^;T<1vCOwWkKEm{?ebG5c(@MWkfTwb=4jNF#1>-} z>eK#{LTxIs+S|`MAy8xJE69T7n-}K>^s@SDg)E<-3$y$-4^`OSv6yJpYFN`dMf>^G zv@AQ7c-d>wj&&sW#<#WW^1E`>-@x;$P6>QR;^+wEi z`tWg|pLU^y3{nWlG8K7|`-nJcom@!^s#~8E;%I_4wK(ZLvOvq<5=58Zh7%o*&Akd*t0b12F2T^>>(y$$%!Dv1goDd4K29`7Cor)$ zM#a$-r4lMm5_)hl!=sSjg5Sq+VwC7>6FM~@K#a1a#mvKQ;-_wfXIByn_$9|^k#R43 zcVF1P{;|88VN;0}oJY(oqoBoIa-3kMtQZ*H%9x^_9(H3>w>{>Tuz@HH5iL;$j}%|A z6@A|Qm?|QcaHWNz2jMNP!tX-XMb|gQ-pdk%XCC^d2)_eQt2rFk=x$l{?iJIuivZi^ za9q_}rp;$UES9B*NTt?D8q;@KW8m74^xB$BnBy&W#Ta-hN+ zebQ8{+bO`33C54BZHK=|70?c9iCe1e?hMM?+pg-Lw->I%T`Hr?)wYFK#ei^qm%gdQ z3waF`OhyQo7S{1lhIxZ*$K9H^A85f63%d(c*(%#tG*sYmR|G>N#I-3u@rt_?hiDrq z_)1~Cbqbqf7zZ@)Uj4W>Rbih8IogcJRkme6K5|7==_eHBzw7c(RfJMdkLIXPGi^xK z-@Bw``H62`EoMB{qy=BCB%X1)vQ4Y;ax6 z1wZLpX1qAl&i40;UNe>WnWdWVT(cj!`Xxl-+`LlmNu1&z2!(CFI5*Rq?OJqfK~Hvn zU@HHOaF^!E$0r}T010TALR1w{cPhdyHo9Er%Ezvy zh*-}EvCmTDwY~CVL#oVKWgq3WmcSFq&BYff?V$6ONmQM`+-|Pdy9&4uaf?ER9+hdO z&u=Pmz1;7lH`ZX;#C6K#$*>VGbhYzfJCDP3|dd%JygDKz}aDw^ofH-;mr|so-0o?a`=H z5qpG)qYH@A5OvPUD(sU=Yq@6oLAe0VRk`uqkgZQ9L8m^{tFs7&h(imA3NiUwTh}L& z+*atO&0JOMFUwW+M!rI8Q;eA*#O<>xbeei2NYB>-A|A~bI|=IL&9iE?44%r5D0@vz z01H8F_EPne88$=c!)3D;*{>^{uV2e=(U+@cStj{beq?`mN20f}k}-wx#)#Ge>@X5D z^uz;)Rc^dI)46^(9&~32_a4V_bO~*$CXXZ&dmy?6I+a8)$tpSr9vEe?+1lBHlEteh zF^0Slb;H^co%My>kvP#x1qajDJ`gG7T0PL_2Tq$YYc7|)pD8sqnlUwnWVFM|r^gGV zw{0BB=tRaUD^*1PzGjQScQR#EYm$$X4W^Mdpf%^28Fl4AYYU~<>v|Eb#NF)|mBi2p zI-^`H?^f}T43Aq{CJeNVf%D^2R^c6X|1mu~M$4TOU%(<~ot1~#4i;>Dzw({ysWg)z-fY1q5 zI)nb)M(m=fw6JNw48M^Vd5zAHK1G4TTe=qu7oIc0<_^w_mU3bg$l$>197x{ zKm>o&A@~OzQ-D-~lZ^|bOL3%rQol>k=ll_nmsNWxKpa(TpN^>~jwxdR{!%oTA@HS4 z&obRFE)YIBtEP=wlN-YdXYT8a7ex)~_VhC2#SSA)2s0#ny)KKcj+O^)dVvrzX<7;E zco-9$tXw266`h-&;`qvbb+s~KLYSM5(sQx<$A##vm9kyusFSgSJ-a(=e*M~97v519 zGWuJKtaP(wY8z|u=cs4hWa05dqID@J6HwkrhD`{!xtB6reX_`h^{F!TZJk@$MZwZD zs?n-;V`$IW#8zILYf>SY%ZVK$6apsYkdxLG=sc=L0-Zjd#$h_ zfgvH1XIw)Y2fo_q?1I?-(gQ{E(VE4hwN^X-^$OQ%C9EZ0aF^n`lpoK#l~SQv%9jGJ z;b~aNpl~d^$ps>iPYA!bLz0-pklX?f#u(!XLc(eVGRC8g7uzb?afs+##Q=6MB^t_b zQ_63=JDJd-35CCVmzqi& zrH^ZMYnWPj&n=a|LvD4fPu6OAdLs5b^6aAhXU~cl{^D54GWo}3c1r_{M!=4ns8P!rt$KvBgXY|v>)_WIk^jk2>fm84W9c48 zu}+h6Nm`a`A-2Nm&H^a%x$A|afw+kf=Ov?#uP#J9X@lKvPx?&=&v@d*P3|}=sQe>n zuT|+&Q>#n}i##D7!s8g$qbN%L7Q1lZZ$k?PvlJ!Jy7#H56nV*Ix4 zNI^M6#G|c^rWp5aS877|rnO8B^lvwk%}y7-X)R+lHoI2h-Bbns?ryP{@)hIHQGHt! z7o@pT&0OG(n-D(hDbtT7PMcr!v@w9Rf4qe{1ndb6Wi-PvQKB-$fLc3qXg?Fg@(w>X zxFT#<#i=Q#7z>AvWD(C{_&8R|EujRor+i>ScnA#)2>y0$ui$SX;w4kcCbfJwHke|Z zzKuYdCWKpC3)u82v1?ou_tcwWEZZ((Lin&XgOppg-BMNXVQU3_<;N47Qc*j9_)}B> z$aK_%@RYlr_{rwNA4~MJ-W-k>W>=UHF84%aUeJAaV~Lx5OGREnk;u9mQP)yZV z@Vo`H${1#f0i>qD9Y?BBG<&4WwrIxUQ?I&qPLEn%nr+#PMZb3`Qe|-4yjNe@+ajuU zDH#y2oNOZaxK~VIscDcT<0w10X87rn*0eU(W) zpz@oI)N>#Br8Ci+Qp)5TB34@c_)x|$FBQcv?s=vdZ>H>ux=Nx?f-yDZ+4*}VKJyYU z^sQApxKul6nWsyq=UqMagFC!k5@NNk#eAAs=e!iQDMUQv-OL!%&eLnNPhYT4krbgH zqZ~OQdUnS@y0IoT9r ziCy@gwfY%W-|SD~v(~b6If;M5rc|3<_@;PZ?O|&ws)hZK(PmoEt82h!I2-M0)5@R* zd{b{3>&#gC@U%sig{Vi;CnChHeT^oBC(*!zCh7OJeFHrDMzL)?-zVmGE8;|V6tB4A z10DFnQ;#*tXf4i5NA>ZkDOL7WhGhqtFcBCFwe2)lmb#J47ykbUjakY>$Eujmd*j%#-ZF|O#oe*gYoP|x`b-30*%Y@S;vBC<8+_UGe?l9Cl*^)JlFe?@ z?I()rC+TcCtyi1keL-M}i+T%GZnJ5Sb1G)HBj`&Br@trQMp zIZ@$NiZm;cqEjhPQjCS3VGfrV^?0VkRahz&n(#zy7-5jgr#ISS8=T8$Zj@a{>#_`{ zU#XN#z4FbBs%A{6>x8z91h63Io+(!>m*b>=%`8*(M6R)CZDV0c*K8U>DP08c!QbQI}T@WiXC4?Li%$~su)o{5>YazSeI zR5EsbXci%96`otU7$@gkZKIybb>t`8kqN9SR}5`oHDmi&*-v@S3Byl?gEdoge@{&>@E^cI8v;)gc<^OS63&nsnnXx z;!LpmIFZG8afNUxf(LJK$B)yT;rN^_Y~w&X&t}`{WSB?Wwwl-+8HHr6TAM89j#lg5 zPcU#GPOi-XQ&v8c>+ShcPbb*fE);IKKUv0@8vog&t{plMEQK@oL%7 zN+gzDWoq!6HD|~uQH3+1L}AjYeGkjnV`{Kz(ASqh85ES;|EWPAU5I`W*&AUUy31rc z0iU;d6pHbXE3QyYnMqmQtfk`VQX-?>IXgO9rJ?I27&ATirZ4)x;hxKBiTau6QCXGI zS-_ZLdiR7A83ojP@}0f0^0^#&uCs_6T|beq*9kW(_%I72a6O+Z_oOXXNmR>Lh66;q zL^Wde)WZJto(!0$dEk?mv!C3Yw%5U|+%iW3acw z8SGU~6T0C%NZClRI@8FDuKMh7s$R$q=Oe8Oi{yxEo!-Tu9Dm_ytYMh1cQ97#m^Cxj zgpC7V{$#(j=}A^(CtU)Hslt1(Xau+P!nOGVo}fh0;MdLan;Og+v=}x)^3p3d*YbGQ z5mS{%4&|ZIOt9Qub(tX2Is$>srV1C%YLxnyVB(Qter z!nZchW-+lUztmLWtyzs)?LXL3&T{?)TA7E+(1*PHObtFv7MNmOH%yvLfcWy1sJ=gE zn|^JZ&t^65EiCZwGfU~ETrbX1FIdUz89sC>bxlU0q@8wkJoYCgPuq2OE}5!x&|Qk2 zoTyXva;A!6T%s!W7tX{daKQPTeh_u)Des^?GmKP8?Mc;~n9AU1IwU#Mh9f1oqJ-fy zn_0z#K(h*sIOHN?wiJyRX5|hu*Q>b}-}8>Z>FEMl5ubB4>@2eHT93c_2jT#OxHGkg zcte>ItUeypMXSonA&XuyzBJN&aYIwoy-7>8O)wsa|Rk)Y$dsjVO0y-Ur@qvyrfy?#@SMW%u6 zrk+0BlG2pF;EpI2>$Uc(C2PdGqA#Oi6-!Qvn+U$?@vF?8kc$4adkx(6$4Q-%7E_F8 zjVxeU5$hklLUa}rk2%2f+buBte>Z{IM6e%wMpBF941M^VTNgd8P06VAvOCUyGT}Rt z=1npB%42_ZRBn-Aql3U*y${lJh!F$e^`ZOge9KOx=(ISKUTt8 zee$cf)Pyjv$9_Mb^>+VA-4uThJRtsu4r_Q4*KAKsP0t2%ha8Sn|57EP@|&SCN+clU}Xv z3)sYem>S$|*TiMoImFd@U7CVBd%n`l6OU+qh&!y*{lBmHG7(lQUVRo}pYpT_ZE7zC zI@KO(g$fDJlaCXP(}mZMpo~~Cia>dJU1{BEY4Ggya2INoS4zAWc=CAm||R!*Tp7m zyTym&rYKExe#>6iA&$1_-bl6~(1JggA^8=0x)+_xCd?}E#9eN;Ylx?LQ?+xUN-;iE z#K`_LSVzw|V2h-<*|k|PpzGb-Li$YQ9IMkCy>A+CPTR1yt$L2)HJrrNOt4wNnk@2h zpn@}eBl6pYYuTezy!K$7(g$-n3q3nHXV=DovP*m;^27US*e~@>(_wH8T3E!tGB{ys zmg%P|sPqu2GAWSay;@K<6~|Pq_h-dra04Q%;I1#TAH`V60$ujAIC~hZd@nb?H4eNZ zK$Ml1GdR8yN0#l0y+Tw@<%|$>uhW@-qJ@Psc(;;D>G;{EUjLoh$RPHJ5GQssfs@*> zW5~Jz0!Y;A?;wM6qze5O&hKnQ?%P3%~QJBeThdp*P3G7GbCP8;-nj0H@h+8cPz&GA#pC|tEL8PyRtsQ zV&cXjOLYmP+{a;gAEgs2nKi>aib0!nWfR_aH85ad0+oy*5W;4mRdi`EPF#nJ-CejQ z9Ysf!m=-lPxNf#T*X&nA;*8-fR|62og=u9XZQ?6x-E7avz1t6zOR8{Gm3b=M^2+kDh$CC zW6cnqnQ)7VjAaz9{=b{w~?Di;4Alx-$p&%qn2aOvKlo`}FjwyarqU-Lvd*@KC;pnw-RT4fWJcg)bE= zi3{81^-s!$*hE>=53J5WYu}jFD1W?LGOcj6b5mBRy*xjDkNxOrHc)J*F7v+vt|#BsX=o zu}LKsIl(PocNSQ*(Q*G>_-3(_q>VV$Y6UUk-Bgr|@wjUwpJLdP@nX9^%WdQf6`{lZ z>8oOpDaNdNSIO9)iGNyiAnrwJNwe|dty#6E7?3lB zJ8u`2;lbM4u9+8wd^9_(hJ|r&&w1O^egn zh`iFN=5V~$sZCc*tQ^un>)Cs~Q!yXK#8pGObQ{hK)Q$5ALH^}-`jKI-)OF{pkl$6K zlpYD%yNV~D$tK_LtE*7dBL2jnKpz4aIJx1k?_A}0VV)b(Wk%tNE@hGQt_@&35Dsf7DVwi-fb{XM6?Z_P?_`XF+Ld9K)l{tOMHVaCRm4UWz~xj4|YX~>+;q}DB0AxRviCg z9hvl0|Hc?Iv5VU4F{%BTOA_Y%kfZQ-!$(YLt`yE}>g3AiNtKf)>uTbkXG-fkb$A4e z)px4%qU!0xBUy23<3JtDl?6ssbXlqdEoVB^O2WGIKFH%HG*ed8E2Tv+ELc1}XS;up z;j2~%%UquD=AUvsyVa>RNZ;=*Vn4_O8Rpmt<(0&&TKUcC*{fp0e#)`rIt~ zh&G>|{8&y%`}?+C*k+`ILmfn(M?{wCZFcek#15ql;AB5#vu+~p74n<|U=iYpp$)h@ zT_AD$oD5WiK0N4JX=?Dqec4`LwJ;$gxh8F1hor$@9ZJsau-j$KXjJ!7th?zV)zvNBzJ|W!2Vr7X2kkY7TTM*`1~y7pg{lI4q*-m$m1%`iJ%`ikW&`+@6u8-yLT6*^2gp z%oU5p*fgvKojKYxdvZN~S3TzbGa0rD#$_}8cxSj0jFsbcIa#r-ZJ9E%AueHfH~`<#Vsj)EU2OxE=gkGjoGqH9U^5PZ5K{Iom`WcK1u`0J~eTeHX44Oi?V4cvOA7 zWAH$pQ`etz269WPkpX<9GO4Kc_B}~!Q(r#7@17jqfV+|fES#*po#l8Bd7hWW#ZIzT zN&NuJyKlIiihtTJJA3=y;acqnni{;)nq3xyR^hAL+0}kk>%eM%c{{CkXA!CXVyxU= ztB|-oS$i(7_bhd0MpRFU>ec*DRL?6V5{+4^Z}jG<{;a63Wp%AhiTL91xEAe0x!x7y z?(MUS_QvcenXq=Ho41c^@ewQSx9>Ppbcr3WWwq}Nm#h>p^|)eYW_@mOh;q3t zB8kG+ggrv020zbGegDRHZYi)c{^X48jMw0NcY_n?DUrYwV@p9C4Kgo&ue%^~T^+P+ z`khL?`A?OYV!Tk$f1vSV?nxDn@b=sG>dwXgsiq}PO`rXznj{{YV!T!mmk`rzYOpL@ zgHn#MYn%#R{$DEivY=mW&&_`N?<2V0`TV*6^tm&F?+Qu*;mv>ePxE?*^Zg^a?~8GJ zfp+pH+oH3oPTisZASO6nK#5FpoKZt_tMuI+M&vJ#Op!d?Q6U*p*K>*r&a4(B~yc|vfZ*nE5?Q$;+QstLEq@M{fQk~byZ}H3t2}LM9^w0 z*mk9Ian_(QS8h}*nxqvkl(-Qb0Fdn;gpoi6L2=jkRmuF4xi~wM1Pcg;N&kKuHK~ zoD^q*=XoZ~c;c62ROat3?v|omcp!Znrs9XBU#YVfc;Z$Bs3yMCt!e!p3r%fMAhxa# z@DpdJrm$VLeC3{3!WjIm#NX`sez>BGaf1R&pB4Q10at`!t`>SKCc`myj1zyGCJpl2 zq+J*N0s|jXjc<}|#5o>K@}~@P4WSOpO?(h1S+D{n`!B{(1-TtNB1fS+p>+lmsnV&UOY{Sl-;c*y3+k9VHIq%e znspA0@6Mc8e>`rmWzf0S)L{Lf=a->h2{*#B$P>$xtJseBJRzY$r{P(Tz~vPF??8>= zv}Dwb#}A$^#>PgmJlV$8nviWHD#@TywQ@E7mOE+Z6y$op3D&@_`<{}G(hYHXvO)=8 z{Mh2zvIm3qqN86tt%M0l6$;-)e65>3iElj-e542>oawGdUJE`--9}vKzK_#=&(RlS z*i(d4wi3VVXJC&;?#(&^+EI8lwU)Tl-6EXH12{if!5}{IM2N3b^O?XRcRjvNMTxWB z_sN)~TbkZsmr%y8C~Nd*9&3G=rCFeI|i)T$0W=AoklvNC3b~lAS57TqV_reJII(FCXsLEU#XVss4IJ^L#r5drw zy_7gVskq+tgBR7w0OVnvy0csCUJwi6wjujTK7}vtos|?5l;8vnUtT)G0#2H${zI9 z4d7C%igWY%4>K^ITX)wna&zB&Z8y1Jg2V$@T0-32=O-S)Qa!ofY5pNZ@OR1oSe&W_ zyNGACV|W}BxFFR?A3k(P7{)Tq@kj2JibASp6Bov*+i*3 z#Q7rf*X|Y~xkv-Jy-!CG9!JFHQmMly?s}Z>&~Sc+hM&1x8RiXAdQBMbSp@;tr5lO2 z#0_X~28a>QpaEy6WOl;W?o!6E$PQh9hp|YD_8a#~iGohwCti%RQc?BxjpU+Z#SO9A z(?DMUuee8Mc}257H#-}RVo^t%;v(_9Ag5b>H^#6TA0w#Mgq~W~acW6D6X?qziZX`! zQKNDdo$4Kt{|WJ=?x46ey%uGqQ>RFQ$(I7}!*)wc*aUCr7~)x%U1V3Mr!b6HT=m47 zbd-sG_hvuG5`xUa6X7_9bq9M$o}IoIfEK0mWrA4KTgG-gZ8iu~FIa(WWzB5DkXE#G zA767kwrfGSg*ZqiW}MaAtvPWP^Dmxy%u6;7Ud-<+WJ(5VdEM&ww}}&0^voyT@x_v9bQUB>A6aI zTeHX7S}QW*`BW`dyWQL_*0sLBl4my)d$thA zHSz$_cWLhj>iLVbv6L648X3mY-Zp%0ek0y>cd1{Oq`Ikl=3?0BUPE25A0RGEMX}MX zaJx%WZFtEY!kSbWl@>IJC*7r3k+K(6=vInWJn7zGqmpTkGe(##G$O{p(q{++I5G3w(%a42z+woMY3^%)Fg-$KQ zy>@8bZP+G`yVTA5-CGzS?oCDUknOyldM>dOpmT<`P57d=tqrw^@uHi;8l>Ah9`7^-8$v)cWOO|v7%U6XigW^v$!R2i;S=UzxH z!aoLeZhfkD&*O-D+`0h;52T`4=d6$QsoF^rxQJL3CpGd;T%9hktTyZLUb$y(`>pfp z%n!5D`0=VXh0&@4%|Cl9uzf^3oVd!g?qU46uUB^yj3@3l5r&N)FZTK=|NAh7 z&W$H-Hi|{_6HoP4FqnA)g++GNR#jVZBt}o!_l)sQ{Z;r@u3!vjFjBeK5|)?Bm?X!7)=5G)$ zFWnh=Zyr~c$d`a4yR}mCS~J6n5x;fM*FSS;pOx965}SLYINvN~QW%j8OWe43Zq%1q zfWL_SsnZ0`vsj-a=-Zw_-lzfJZ}kTsEI=A}Q)l4=LgL>?dlo)n-{UnMUAc4cL5oCg zW&)Rq2pi0!Cae@UW;|XoEy{-G9D%CH((P99&DV^Mzhjj`RA0Sn?lWOrr^yw!#|t*| z*=h~*(QYYvSj$KeZt5-2$175`4B}mPy^fX-l5aXI;!{RU*kQw)9jWy**5jV;T4mxh zVH{SWb@LgOVl;;B`9$xL4O^np;g6m-m*L z@pw~trMQ4Gtmuu>w;K1G-0?W4A~%I+`9|a`xu75hm^Dqr$={$z38g&^a$ioJ3~zlk zcjrWFV+?XKJP~+8>PTyu{s=3FS2@v90W`p~63IrT`ce{x-7(X4b@&E!Y6S9d?8~WB zbw9FFUnGdfT>ZE+?U%$pUefA#Jfw6m_-ifOJ#NPwzj0iqzdPfft`Ygm_mlXScPr6V zL0uAxt}=%0a+@PnX}PDt>`c6$tYv|IU*cKAu+>BUk(wfBhuImor0v-$frFm1A7o#W z>erJCUm7(&*XCPlQ^%$cdpyd(!`eH)ix4NP)ii}Ifjh2+kpCsy&CWP!l^KuiL!7L$ z3G)8#Oq{qDM@Eaxc&t~Jl3xE2W?yVtGr9m;eEPsr?Sk0~Xq!?E2=Oi+;@-FAK~ zCVpq)_EZHL3yPdK5hTmZ&Nw^Orr)d6(->5l4X#KLMvr8S4>Ez-%ivQFU(-t*u8kDT z_OG4^eQ?CuBD5A10V8)-FUO4}di<4p{Yx)Z?zB{`IUHNu5nP=t%V;~eA-T%zj5Bo{ z9?^(k+>CEBJKxD_;>0pyd#Qz29wIIHutiKv5~+e3bcZF7I&iqox%?d3stY zUUQ8US%~70*ky~~?2O{L@ngm`voi|1^jB16%WPS*Ge(6J;#?LtejldQ>eV9qiZQt7 zsp;Z~YC6_WtV*x4uguO$j2JLdhp$L0UHiYFT(n*E$$s&{wW+GzS{T6bw_(?~8IM^S zgglcqI>bkQPgK{Uyz68f(G7nW@rQ1)^u5Ktryu0W*t*v5pIgdzx<}+|DH~gv|XfOvok(QO<|#EeX&zaav{7SDgp5r zL2J)&$JO^AQe`YH=bkn(fzpDyZ#6sPmsA^1VbIQiPS>BL*cd-`eUTiW9?IM4z+VrF!zMQfWB9*RV-XzH{hzrt|)XRHFk}S?G>y7#F81 zROn^bQvJFn?MG@6-2LijZgIV>u4p|diJ8y$g@1&SKZmW;eE0FJv``6|)SDURO^hk= zFa!K+Rs&b-iknWv0lwj#tqPvV^GVXnGSnE(1YVgM76cfhdo>HA@Lt7cv z!z+{1=-iRsJf7hjP1uL~J*8$}+>wmdRCoTVs+=*MM(Phzk1@c%Wc$$^%k|??mpY;w z{6a#pb(--g2#LF})$EIAn?rKMNU-1Ri4!(sLdrRLOLXws^EIJ9{hG8N%=JWc zmVc03N8A7Hbz#gSo7?IRsnDsZ+Zaajv8qW#o|rZ~@xj&L_^8esT7FR~ZL z354-;szN{Kxs{d^Q!V?RC!*tuWUbj3^W72jMVa721~jTux^1(SW;|Y1n^s`_+%4Mr zaCIsp@OMR7XZ96xbv#~icd3PIL{k$c&PzL%9~r(fo%!0O&WtBcs9;P??H6~0*%ue4 zIBf$b|9}dv7GrYoBKdsGH*Uf+lBi@aMHkJI$B@?bq68e%mZo;@D zxyp>kBc7#ZU#w0oViMg2S(o1WcxF<=*eR2-@Ka5oG(szXDU<1;w6Jpv){oIwj#;|J z6Nu1eU@{Tp)gQ|`j+X18TG`dnwP*kyy}mZr#{G8TFpz0PIH*c0iI0Qkxr<>RW-Ws! zUJXzDDkbctuNu9zYIgGfXqI4ok8NtF?BP{)aIUTQoVLU)0;<`zG>2jI)z(xiBn+vo zaL2FgjP5NnBcnvB0`3q!Wy=rN-Fo*X@4cR*^fb`@7#}J6Gmzs;c20B(b5F&J3(t? z9p^Ej`MQ=f-&WLm2htc1sgmZzmrinsC;Q*R_;As zN$=6sHkRld#K-Mjy^|Tv`eCXL%~55OP9MvjhmJFLP5N=GUScK zDH6klk^f6}nl}zqx6q^9c1bqb5UolE|HK-3-S=SdQtE1+4$f0*RpTf(@(_UZvb zI(E0bg$f|ks3$RCQEYoK_{Lbuq!+YN(&Np;ZfvlZS%qvPU#%gzHcZS)K zJ#!g!O23dPeO;#X^D0eTCb>EuI z?d^iERKtYvw0g-EscB{xylth%gPkujp{H{OzSycSRP7$-aQxl!+g&K5x4gBngBfGM zGt3#+#(}R+&1MTb(;SX-vR{4WwqH#@INMIu;klkIvL3YwKHWxgr+cItc_cMOx~#m~ z>5Qk+?1HWCe&fY$DuO3$39Vw8&1T4_W4yS>Eok~K#A~TqoU9Op>H1vm)LpR1(@%6L zIR{cv{ZZ74S#6wL?ubAvF5HohqPVq+lQBkyfZJqG8WkFCYVekIZV6AD zU|8c)s(bbQYngGWY>@$LRV`uoyNfZg z4(fV>t5i4fi|bL^$`_^;J@$huWOl(B>9PqzM7Sl)?*LIiuD?S3ma=L1=7_*bdswCr z4oiQBfVX*UV;+ZN$S$mYfA}vQy3*93c7W!~olka_b$D{7c+3B!j`9W^Oq^(M?nZB| zr`B0l%~Nx$E7$ifM9VbpO5E&j!ILS!HFU;!m#M+h zEVa!p_}J6WuzZsjq&~>_CU?Onp14x~&lxn{O`a}OgWvR}?9~NKm|gI$C$0msUgtnx z0Sj{+_sCpV&yoXl0s~ALFDBRpT|0$SmGQ&R<${H#qTUWK2mS4MhT zOcIPea!jf9@-NuYv%NzU*HO8aZct0s1#h-6f!=i(@*8n#>M`Tp*>0G6S%K%JreLd; zFy3YGU>?Z?eG97@xTuLl*7tEZ$-VLrDo)s{1!)Q? zB`u_2tE6ahYzj?;BBW@FwrUHuqKK{10xh%&78TG|1raCIq5=vks5qfORa8`Pz$-9MaIGz$W#hhR%^8v>dB~s+p{hX8T4uB=lmd4t;y>iRq|a7Bt~4dBiHX zf=P~4uHY%nH1rd^JEt~FB65dBnq!$kJGn8m`014 zY(9Nhduk7qfPAL<;WY${#j8#cysVgkub<$aIrHegZbTds&1bQ26dDSoa434_j};H5 zhuG8oEzPPQK1GwXq9#pj&OegY<<4po3Rl=uQyO>zUTrDhIcbn`Y0w9#fCp$yB?n!h zbK^0z>#8|U1Nghtrjx`aNw>U7W|zzDl$AwukWIBxfCIup?~*&?=XPC7h$AJ=?L*M# zc56!xT5=eH^RLhDkUXB=>YU`^UCAQ2ocj8foIf4&A$h7Fo-?|Ape5&TcepCi;r%T+ zT=sS_gq*x#W+7+-lQ7Q75gZYuSHV=&g*3n7NEdIyqO^0e35F*OFhm3gg+~}pQaB_i zUYU5_NQsE8kOJ8<8Bi#bP!kfQhgm=W#%>vbcb;W-_P(7?01;sdgaC^S1*D1wYerR6 zNxB><^AArT@|`v2XFqTbDvY_Y0@V)}8-e}5M$@ER^`rDVLL!s`GzTE_#Y>k0#He>J zEFMuVUNmi1VeF0a+;&)l#wFMzfyr|6c`{f#9Bm(^uO+^s)qqf2YOPaY!I#lA&GGVL zE}?0^IfQ1vw03h3Ejrtr=ruHOi5K>uV9s*!$YKe|NL7O0W|J1v+p-@P2+8-y6dK2h zcVMxcAV!Gu7A+1|$>I^r(PGBT_rpSIN1a(AcJ35&ytV`4I;qR3urD?k!USu#Guf{F zfK;XyBN|&3rQ&W;lcu7AZ9Ox@D-IoIdC$xcSN>YheC*N0{oz@}{vRJyYgkve42zMsKR)e@Fk5G+-r>U0>=@rd@EJ#t5b-hgvkaHUk8n^1+$xx5+wd~ zq_Z?dH(!*w=bB)9nBXXsfXWpI_O*tTT?0h#`@9sgRbi@Ob=YPM02=<@6YiY(79FR^Y?pm(o{d&QCP0_sa(82XTR#_z)F>I zugb+6b1GFotW^2c;uy?%ZV?xweFNg4+c1Mb_4E&ss#C;4>&-HD`P!UXX;A&JRAn%< z+H3O|bZ6)J@fA4HH#MbUgfz%6T5dVf*4JoJU1zo!&brWVF>R?}Rje6r(vr(R%UHP3 z&A)PZZKvWTn%oWA#k2gL`8+;+Bx$6@IXWH4k6qUw6lHu+0+2{jnU$rWROp z8WE{7T?LEk&58U%0VbwP4N=Vd-<71 zD2q}~!R4_Bqyax#DsXwsFSsR^gSU-U>|-eQblAq|I1gQS~!unWyWp5q|c8l{t6H|v7nS!+I%ntAYis}pzG zn(#_%Ek2==|5+=;E(i|?o`}AJjaHvzF^er4Df&C`c(fLqs08fcCRNCj(!oQ=9L>5r z;uuG`i^o>72P^3l^%k|AxGVY!Ua@XMi$AYFjU{ySyCb?8Z&;U7V+jYz`7FOC%cZzI zT8Ve8t1;a_QoDju1>32a9LP)2=?mSIOTF8V*WGl--ZjD{r;i{JBkF%(JHnRc&!7N8Io>AQZ2JgI6g{lnnT`4 zyFj6Mzm+QSG6uz%M7f-(OaCNJTUX-KN^G|fO#?n*PE!`oa>+Dh$3k0%1XQKqm(~J^ zTb1AjYlhO*j0DYoRf!X{EN%7C&sM^!1o?bVdQ(XcGG)i_)_5OndKp2ix3U=cYHNn7 z#7(W#2HEkEEy!VRY4ywTGGQ3eM7adYvZ*#yc6@4UGG5nXjsAL_7FjC=ovqq}o#OM$ zd1= z;$@2~TbbJWE@ZG9K6tVq3;znj!hx{ zduUkeLF`6%7sm=XZ^~gFWk-F1s>Ck+kxR003;hhqAPMdyAr#!f{OO$Xu4o5cL0#0F zAsWK_q^0c20Kv~11Y2w-2!7ES$>;0hUzLKJ7f$99u4>INvEqIbgUmUD%42T~~W9e&Y zC`5a5>Fekl!p%O>sVXso&PjGbkUvhy*9H8U?M2zKpxZhgjOO4N_WS2$C_85Oxv10q z_)*XFeol)|&fuZ@(2`-m;g(ndH=Mo?+6Pld*AL-=%?t{bX<(3tjYvZ%fm$ssCO96| zx;QnPWgsO>JbC}7*tfPcfZoHL!Xg`MIuM8JbY|z8J$EL1-cO`0g#0nWg93fxm882@wsKyfQ)?2-b;5K9v*cT$)&(!OwF- zY}lrB?+hlXO7z~D?ES7DG-J?C&$7pv_?(r%1=lsF@#8OyU)LJHzLZXJL25?OaaGxI z6(3`l(dJ725d0t&d4+|?X%p>nT&3Wm=Fe3o{tzAWD!FXqR@$XXjEP%kpajE>SRnF2Jd??(}|4LLe=ikxH0NfeX-(jAFOU8XGS93 zKCQ0-!Q(^fA*vQVqj1Ju(=?CwO{x4!)*`pWvscQySR( zx>g3y{{*+BFXr#dS{_h+u?HM3fQ2p1jN$qjG?fRipko+$FTO>M3gD$V9nz4NxYu*% z#W$uuuCb;bFD@AZYt3N(SK0BnwMO;Fa!F3x-;4R8Y|2Bj#YLXz+PrjwKG6m3#72GWGXuf-28TYj(D%*U81H# zKj`3S_6+_#lWCsv2Z(FxiISalRAP|oi_JB>O6HiKPb=UDsl12Tssx*~0+t+v9V@Ku zsy~k8t1RX`ZRSnuFL2bU67(adLB(``Y_zu1@iVSr>`D%D8xf%3AfKSI-nf>#q6t-J ze_U-N!p5Ux4Up63PGDmL@8Hu`1@|&o?YhSVQ|a1)=Ybb*uhZJVpy% z-yWMG<$tQo%iUZ&hF0xd!PmO&HAdU7{$<;1#?bhli!Y81bEUh+QZ23H@-)q*Tzu;Qy zXEgNCDV0BOuUX4s#>q#Nt+(EAn`v#uVTu8~tD44Wd+U`odNx z_At8YD7WCO0>M#koRuTEtks!ldrYFOHHfxWCw?=!$P0=K4P}A@>Av?Qr}uu;&)M8= ztxU7a*)taxO++HOS(JuJ6NK%FE+Ngf$5=gMTJQC8cnwD371idOIvDpb^VE4>iZ zNusT2i%G`#Y!ttqMZVLqF7b@g&q)+G5<2pj8I`#6a&uaPc`oH#sb)8rRmn|y%vPgL z!poX9_7rm-6k4elQ72J^^>7Xe8EtO6*;hfgzEDr|sDob|Lf2DM^=9cX+xwY@!H(OR zO_d9HFZ$k~^AK|y?&uBlPNxE69Itm+vIaW3>Ue|>Kmx@P9#X%l(Durh& zGtw4|3v)UI4xh}|j=QBaaMSPODDshuC6jU!YRhQ9$`TK%mJ71-VGWa$a%@p{6q0W~ z3F~9EIJ=TQJZQYOg!OiB7$;uJwy=fOdMnn%XfTDHTk*W5qK~p;gEd2)gr`YBLu_(a z%ulerX{nJaSteC<20~Rm`z=@v2}_Ao;fz-pDa_qtTq)~SH3(Y#%z!%CboC5i8Xao9 ziICi#i{hI!1}9L1slY}*BJ!*_#+O=vDdS^&6f1FQ? zfMbOOWO0Je>RfDgyTg*;qP<3^8?+!uCw_5!F98{w_^9Y=^P_hx^iuT77EZii&uEjJ zCYyX@NlkH-GIN<>Af)M?UGpg1YLrMX7q?`~1-0Um;{M_qE)AHGW=k=UOy?4Fv{hsU z1&8^BYw-x|byF!iac@hBTz+Y#G1vjM8dM=fbR~7cyl4jxjLk=jf+m%Fau{wuMzFA) zw}Pf4+wps}??yyQncv=lbCXD8sX2x9Z7?UIui+3wQXRo7^_@rc9`>blV!?tNQoN`xE~^FL24M$DPmG|Mux z(dV3#J`7dFYea8>>DA)k0pXopEP<1!OT*y_PbbSw=BEoml*{dO9MbJX6eU}{ubH{2 zQ_nQXkCOp%O`>AcRFdrW>P^A@we?^J5acG@?X zzwok_5C(?+y^sNwP!gb-W zFse5TGTl1S(2y)`XTPkqgP;6lbE`?%J|~AJ$A_&=L~YMdry|j~VRMhAi5hQhLRFJ+ zQA{`e&5}=hycROX2!6EHs7ZKXPJ!@`- z3q~tE@7$K`X@q9x9-cI~>7OEadCs+J4A!#v2q@dfm%L6@;N)6C|2jFI2{#XsD(RAd zZK?H_6Xj)UU438F z+${Pv55j5*;!+zGSJ{H+W)-MGc-TQv_BpHImRTL**}@<@|Akbh86w-eLAfC+Eb0{Z~!9l1=yi zmtL#yG<*HyzcjV1Hk)3e>A}CBYiF|Q<^QE=(HgVYtN%;Woyn%x|Cgq7?=pM6<-at| zyxVO0=znS2m2CRde`#8MkJ&3Ls%q!JEll0Q{~wXSzFo;y)W znBnJ;Ab;2A^w-QStsln!ncjpPmWwJYW^=h*yi#y{A(9$S%Q|yL&k{cuN}&MlRNc@= zi>#?v3abloUWn8rfp)8tpS3G`Z%~AE(A#>k43`BmTnZYy|we z3c){iNrXL&%;9K0_V1B}DjUU>+K~*i=%6rvL;@W1ajV9AYrYqV0O9FYFL?Gn7X_@qDTi1Ps z%(;!0TIzjaT`gYi3q%hl@i<;Q0oHsXw7ufBW&TX1_elP=mw%Bpx}Z5BT2?<`&TS#3 zVQ&61stw-O+)==Blpb%U7=@f$N##!xuaFyY96@>DXtKzd9YSj;sXu?v?DZ*oszegU zrz`pVHsSGMpjuF$ILAmW14ZOp3CQKzavYJW7$a`f1ng`sAark$d~p=33HZSH-O1n6 z#?kYUR|>NWWPx}YH!!vadTGxg=OYk5}%Z`s4g z6}Ekv<>HBOaN8X`v3WSJwi?Z~2`<(0m^tc}9tJgD#tI)70dbuN?&3>F^IBI3765MNA$0L%ivhkoJn7HGe$q`=quhYrwGN<#6 zgQof8m_ed#%)yG=1g!2L&;g~JhPnU>sm|2gWEDX;Qa_w4O&if}+zA?&> z(jUb<;zjgy!xh9sF+Vc-m*8Z-L~xE%a0CtLE(zeAHT*FmYpzrwJFL^~yO8S^oaSF3 z0c}2FO%UjeLz=cqKz`eB$CB+~YQ#Bfdb(aKWmUv|Y~!mcx3#k>@Zy9vIP%2+EfW1n@OFnLHW1P8BL-ycz4J##Z@}h&zMWxXH2F_4G{-! zoAJ4U{NGEdP(!d{hST73-bvZSOm8zJ;OG)<22H1{qEy)yTKB0_aKoHRm5m?OdNMCp z&8Q?*b&!K(IRY2GD^)h$O7Tgl3S;>UeMLV=@#%OkF9^puRP9kCMIH4w>J%)Rlg3gL zf)_zIzXxY>gzr*(r;lNvsRU%fVs#3Z{JT~%MwkMaS=0FchzjG18Ly~p>`tMNzO!7N zf}PPc&iBEY^9a0OQs`Lnu?8M3M|SMDwo8Do=Js4lH=6HfBmukVfOb`a@A$+pP`9cu zdIb$|U2;G^%0lA!FiFuepC4&NJ!K2a6Hu5(wJyo$@6Buw=MXnZjkx3}!d4u3w0X1sP&eDq%yYg)D)`M~%{k(d87do3>M#1L zTzs$X9E+OR?BW!bXbuY=Y@TNf_MFkn1KkGO%1_y7eRG=8=w+kPJ>43;C^boE`s!_o zmE(2#G15qbUT*QCp&7k2-T8esH}@iYJ}=KnY7q9$u2$fC?5h2%pdPt884)ue>rvTnW5VlxSPP@J)J}6lqn5&d|Fw8pC72p+PMt5-xLWDO_E z?c$*tNfv~~#Y|u{uIasf7KG%w7QMGGB14C16bRO}_(!Q>xSYuwF{_p$&kwUIl}>ob z%0;Sxj^FssLL2ii!TcW7$rK1KZJI})1?=tN=Fi2>lK}SfxIumU%x=R<} zGiGF_xk;TVI;hSg9{!YoD!^Q8g&KzMdgK#M3l0o&oS&yV+3hDR41{}9ed1LGczWk%R`E2pnh4WMaZkU~eJLw>YY4w8CHVk#6EC=FdW{#8$Nte^HIaYwe3DC6Rc9C+P`3xtKZvIqs3v2cpc0{uu&ChT!xZRf53@ z`+XR%>Gb#og7;c#i4ktq0H)MhfOlFdd25Y=Ehr^#5D6tR!S8(4+`>&(!+b%(g3Nh2 z6O_AbHsg2|y=FFC7jIZC-a>H|pJ;DwxKPw^@Q*;R@}u1-B1p@_=AKkp7nH zOdb9oVT;|&dXw1afOzQDSRiP()%90r63v>;)S3D$&>rhDny@bFOusZ>mgzRq^}4z8 zE*nLWzJfmv^vQ*)02fbp^5^~oGi0K6_zDQGUAB1Td!x(!7qX<%4u-&KKA-^i*xDtc z^TKY_o*E=@1z4%${H7A{V_B{KK=Sh}lfb=um%iAX;8(4zaj@jT3|$q>tCNST0&(Em z=?@fgCW{UXUnPrpRHvJMs+n(^3wX^~0KPcTrwVYLG5gOGv**-_F`hB-*y8CO;^i)@ zzWC(8Fvnju-LGpHRB^1!jXB=f7%%0(2EXYEF4PmeQoNI-P=mpNJ<~U<5Z;5^(E0n?&<>kVr7ow&TjB|!qzUdEd_Mxz;=no~^MD3Fru$J> zi=%z~_OTxHbRl&fhmEl^xO`G7IC6(DTtA}h;uxaFX@mGCvYHam^@7fuEMB4GO#>