项目实现架构

s3下载器

flowchart TD
    controller["NAS 下载主控循环"]
    schedule["根据云服务器执行端空闲槽位请求切片"]
    taskStore[("SQLite 任务切片库<br/>任务 ID、字节范围、状态、租约、下载路径、服务器 ID、S3 官方校验值")]
    claim["SQLite 写事务原子领取切片<br/>查询 状态=等待 或已过期切片<br/>写入任务 ID、服务器 ID、最后检测时间"]
    launch["NAS 启动该切片的独立 SSH 会话"]

    cloud["云服务器执行端"]
    cloudScript["启动云端下载脚本"]
    inspect{"检查云端临时切片文件"}
    complete["已有完整切片"]
    partial["存在部分下载文件"]
    fresh["没有可用下载进度"]
    resume["读取已有进度<br/>计算下一个 Range 起点"]
    requestRange["发起 S3 Range 下载"]
    s3["S3 对象存储"]
    response["返回指定字节范围"]
    remoteFiles["云端临时切片文件"]
    remoteCheck{"切片是否达到预期大小"}
    remoteComplete["云端切片完整"]

    rsync["NAS 使用 rsync 把云端切片拉回 NAS"]
    nasTemp["NAS 临时文件<br/>xxx.download"]
    verify["检查文件大小是否等于准确字节数"]
    publish["临时文件原子改名<br/>xxx.download → xxx"]
    verified["更新 SQLite<br/>状态为本地已校验"]
    assembly["所有切片完成后合成文件"]

    expectedChecksum["读取任务保存的 S3 官方 ChecksumSHA256"]
    compareChecksum{"完整文件 SHA-256 校验是否一致"}
    completeTask["更新 SQLite<br/>任务状态为已完成"]
    hashFail["删除不可信合成文件<br/>相关切片重新标记为等待状态"]

    fail["SSH、S3、下载或 rsync 失败"]
    requeue["保留可复用进度<br/>切片标记为等待状态"]

    renewScan["主控循环每约 15 秒扫描活动切片"]
    renewDB["校验任务 ID、字节范围、服务器 ID、租约标识<br/>更新最后检测时间 = 当前时间 + 60 秒"]
    expireScan["检查是否存在过期租约"]
    expire["超过 60 秒未续租<br/>状态更改:下载/回传 → 等待"]

    keepalive["SSH 保活<br/>每 10 秒探测<br/>连续 3 次无响应则断开"]
    stalled["约 45 秒没有下载进度"]
    stop["终止卡住的下载进程"]

    lifecycleTimer["Agent 工作流定时器<br/>当前配置:每 3 小时触发"]
    lifecycleLock[获取订购生命周期全局锁先恢复未完成的退订/重购事务]
    pendingTx{"是否存在未完成的生命周期事务"}
    resumeTx["按事务当前阶段继续执行<br/>禁止重复退款或重复下单"]
    txStep["执行当前阶段<br/>退款确认、退款核验、购买、初始化、测速或接入"]

    healthCheck[巡检云服务器节点读取实时工作状态、下载指标和固定 S3 测速]
    retireDecision{"是否满足连续两轮不合格条件"}
    keepActive["节点继续保留<br/>维持下载服务"]
    drain["节点标记为 draining<br/>停止分配新切片并等待活动切片归零"]
    retire["Agent 工作流通过网页工单手动提交退订/退款"]
    refundCheck{目标实例是否已从服务器列表消失}
    purchase["按实时余额与实例配额重购替代节点"]
    init["发现新实例并完成初始化<br/>SSH、curl、rsync、procps、配置"]
    benchmark["执行公网双源、三段 S3 和 NAS 回传测速"]
    benchmarkDecision{"新节点验收是否合格"}
    enable[原子写入云服务器配置重启控制器并恢复下载]
    replacementFail["新节点验收不合格<br/>进入下一轮退订重购事务"]
    lifecycleWait["记录当前阻塞阶段<br/>下一次定时器继续恢复同一事务"]
    lifecycleDone["记录订单、实例、测速和状态变化<br/>释放生命周期全局锁"]

    controller --> schedule
    schedule --> claim
    claim -.-> taskStore
    claim --> launch

    launch --> cloud
    cloud --> cloudScript
    cloudScript --> inspect

    inspect -->|已有完整文件| complete
    inspect -->|存在部分文件| partial
    inspect -->|没有文件| fresh

    partial --> resume
    fresh --> requestRange
    resume --> requestRange

    requestRange --> s3
    s3 --> response
    response --> remoteFiles
    remoteFiles --> remoteCheck

    remoteCheck -->|未完成| resume
    remoteCheck -->|已完成| remoteComplete
    complete --> remoteComplete

    remoteComplete --> rsync
    rsync --> nasTemp
    nasTemp --> verify

    verify -->|大小一致| publish
    publish --> verified
    verified --> assembly

    verify -->|大小不一致| requeue
    assembly --> compareChecksum
    taskStore -.-> expectedChecksum
    expectedChecksum --> compareChecksum

    compareChecksum -->|一致| completeTask
    compareChecksum -->|不一致| hashFail
    hashFail --> claim

    cloudScript -->|命令失败| fail
    rsync -->|传输失败| fail
    keepalive -->|连接断开| fail
    fail --> requeue
    requeue --> claim

    controller -.-> renewScan
    renewScan --> renewDB
    renewDB -.-> controller

    controller -.-> expireScan
    expireScan -->|发现过期租约| expire
    expire --> claim

    launch -.-> keepalive
    launch -.-> stalled
    stalled --> stop
    stop --> requeue

    controller -.-> lifecycleTimer
    lifecycleTimer --> lifecycleLock
    lifecycleLock --> pendingTx

    pendingTx -->|有| resumeTx
    resumeTx --> txStep
    txStep --> lifecycleDone

    pendingTx -->|无| healthCheck
    healthCheck --> retireDecision

    retireDecision -->|否| keepActive
    keepActive --> lifecycleDone

    retireDecision -->|是| drain
    drain -.-> schedule
    drain --> retire
    retire --> refundCheck

    refundCheck -->|未生效| lifecycleWait
    refundCheck -->|已从服务器列表消失| purchase

    purchase --> init
    init --> benchmark
    benchmark --> benchmarkDecision

    benchmarkDecision -->|合格| enable
    enable --> lifecycleDone

    benchmarkDecision -->|不合格| replacementFail
    replacementFail --> lifecycleWait
    lifecycleWait --> lifecycleDone

api聚合系统网关链路

flowchart TD
    client["客户端<br/>控制台页面或模型 API 调用"]

    subgraph console["用户面板"]
        web["前端页面"]
        nginx{"Nginx 按请求路径分流"}

        web --> nginx
    end

    client --> web

    subgraph business["前置网关服务"]
        bizEntry[前置网关 API 入口]

        bizRoute{"接口类型"}

        loginProxy["注册、登录接口<br/>原样代理给 New API"]

        bizAuth["读取 Authorization"]

        tokenLookup{"在共享 MySQL 中<br/>查询 users.access_token"}

        jwtVerify["调用 New API /api/user/self<br/>验证 Dashboard JWT"]

        bizExecute["执行业务接口<br/>套餐、订阅、订单、支付、发票、代理商、价格等"]

        bizEntry --> bizRoute
        bizRoute -->|注册或登录| loginProxy
        bizRoute -->|受保护的业务接口| bizAuth

        bizAuth --> tokenLookup
        tokenLookup -->|找到 Access Token| bizExecute
        tokenLookup -->|未找到,可能是 Dashboard JWT| jwtVerify
        jwtVerify -->|验证成功| bizExecute
        jwtVerify -->|验证失败| bizUnauthorized["返回未登录或无权限"]

        bizExecute --> bizResponse["返回业务结果"]
    end

    subgraph newapi["New API"]
        newEntry["New API 请求入口"]

        newRoute{"请求类型"}

        dashboardAuth["控制台身份认证<br/>登录、注册、JWT、刷新会话"]

        apiTokenAuth["校验用户 API Token"]

        userPolicy["读取用户、分组、模型权限和额度"]

        modelRateLimit{"检查用户或模型请求频率"}

        rateReject["超过限制<br/>返回 429"]

        channelSelect["根据模型、分组、优先级和渠道状态<br/>选择可用渠道"]

        userQuota["请求前额度检查或预扣"]

        channelType{"选中的渠道"}

        buildMaxxRequest["按渠道类型构造上游请求<br/>携带 网关 内部渠道凭证"]


        newResponse["接收上游响应"]

        usageParse["解析输入、输出 Token 用量"]

        userBilling["按用户模型倍率结算<br/>补扣或退还预扣额度"]

        apiResponse["向客户端返回模型响应"]

        newEntry --> newRoute

        newRoute -->|控制台登录、注册、自身信息| dashboardAuth
        newRoute -->|模型调用 /v1/*| apiTokenAuth

        apiTokenAuth -->|无效| newUnauthorized["返回 401"]
        apiTokenAuth -->|有效| userPolicy
        userPolicy -->|无权限或额度不足| newForbidden["拒绝请求"]
        userPolicy -->|通过| modelRateLimit

        modelRateLimit -->|超过限制| rateReject
        modelRateLimit -->|通过| channelSelect
        channelSelect --> userQuota
        userQuota -->|额度不足| newForbidden
        userQuota -->|通过| channelType

        channelType --> buildMaxxRequest

        newResponse --> usageParse
        usageParse --> userBilling
        userBilling --> apiResponse
    end

    nginx -->|前置网关<br/>支付、订阅、发票、价格等| bizEntry
    nginx -->|/v1/* 和其他 New API 路径| newEntry

    loginProxy --> dashboardAuth
    jwtVerify -.-> dashboardAuth
    dashboardAuth --> dashboardResponse["签发或验证 Dashboard JWT"]
    dashboardResponse --> bizResponse

    subgraph maxx["多上游代理网关"]
        maxxEntry[请求入口]

        internalToken[校验 New API 使用的网关内部 API Token]

        tokenConcurrency{"检查该 Token 的<br/>单实例并发上限"}

        tokenReject["超过 Token 并发限制<br/>返回 429"]

        parseRequest["识别客户端协议和模型<br/>提取 Session ID"]

        sessionSources["Session ID 来源<br/>Session_id 请求头<br/>previous_response_id<br/>prompt_cache_key<br/>metadata.session_id / user_id<br/>X-Session-Id<br/>均不存在时生成稳定摘要"]

        routeLoad["加载启用的 Route、Provider<br/>路由策略、模型映射和重试配置"]

        routeFilter["生成并筛选候选 Provider"]

        concurrencyPrefilter{"Provider 是否已达到<br/>配置的并发上限"}

        cooldownCheck{"逐级检查冷却状态<br/>Provider<br/>Provider + 协议<br/>Provider + Model<br/>Provider + 协议 + Model"}

        modelSupport{"Provider 是否支持<br/>当前模型或协议转换"}

        candidateList["得到有序候选 Provider 列表"]

        stickyEnabled{"是否启用会话亲和"}

        stickyGet["从 Redis 查询会话绑定<br/>协议 + 分组+ API Token / Session ID"]

        stickyUsable{"原绑定 Provider<br/>是否仍在候选列表"}

        promote["将原绑定 Provider<br/>提升为第一候选"]

        normalOrder["使用优先级或<br/>加权随机顺序"]

        choose["选择当前候选 Provider"]

        mapModel["把客户端模型名<br/>映射为上游模型名"]

        convertCheck{"Provider 是否原生支持<br/>客户端请求协议"}

        requestConvert["转换请求体、URI 和字段<br/>客户端协议 → Provider 协议"]

        keepOriginal["保留原请求协议"]

        acquireSlot{"原子占用 Provider 并发槽位"}

        nextOnBusy["当前 Provider 已满<br/>直接尝试下一候选<br/>不写入错误冷却"]

        adapterExecute["调用对应 Provider Adapter<br/>向具体上游发送请求"]

        upstreamResult{"上游执行结果"}

        releaseSlot["释放 Provider 并发槽位"]

        responseConvert["必要时转换响应<br/>Provider 协议 → 客户端协议"]

        maxxCost["记录上游 Token、耗时和成本"]

        exactSuccess["记录成功<br/>清除精确的 Provider + 协议 + 模型冷却<br/>重置这一层失败次数"]

        stickySet["将成功 Provider 写回 Redis<br/>失败不影响本次响应"]

        maxxResponse["响应返回 New API"]

        maxxEntry --> internalToken
        internalToken -->|无效| maxxUnauthorized["返回 401"]
        internalToken -->|有效| tokenConcurrency

        tokenConcurrency -->|已满| tokenReject
        tokenConcurrency -->|可用| parseRequest
        sessionSources -.-> parseRequest

        parseRequest --> routeLoad
        routeLoad --> routeFilter
        routeFilter --> concurrencyPrefilter

        concurrencyPrefilter -->|已满| routeFilter
        concurrencyPrefilter -->|未满| cooldownCheck

        cooldownCheck -->|仍在冷却| routeFilter
        cooldownCheck -->|没有冷却或已经到期| modelSupport

        modelSupport -->|不支持| routeFilter
        modelSupport -->|支持或可以转换| candidateList

        candidateList --> stickyEnabled
        stickyEnabled -->|是| stickyGet
        stickyGet --> stickyUsable
        stickyUsable -->|可用| promote
        stickyUsable -->|已冷却、已满或已下线| normalOrder
        stickyEnabled -->|否| normalOrder

        promote --> choose
        normalOrder --> choose

        choose --> mapModel
        mapModel --> convertCheck

        convertCheck -->|不原生支持| requestConvert
        convertCheck -->|原生支持| keepOriginal

        requestConvert --> acquireSlot
        keepOriginal --> acquireSlot

        acquireSlot -->|占用失败| nextOnBusy
        nextOnBusy --> choose
        acquireSlot -->|占用成功| adapterExecute

        adapterExecute --> upstreamResult
        upstreamResult --> releaseSlot

        releaseSlot -->|成功| responseConvert
        responseConvert --> maxxCost
        maxxCost --> exactSuccess
        exactSuccess --> stickySet
        stickySet --> maxxResponse
    end

    buildMaxxRequest --> maxxEntry
    maxxResponse --> newResponse

    subgraph cooldownFlow["多上游代理网关 错误分类、冷却、重试与降级"]
        classify["Provider Adapter 解析<br/>HTTP 状态码、响应体和网络错误"]

        committed{"响应是否已经<br/>写给客户端"}

        committedStop["记录失败并结束<br/>避免切换上游后重复响应"]

        scope{判断错误影响范围}

        requestScope["当前请求级<br/>参数错误、请求过大、内容过滤等"]

        modelScope["模型级<br/>某个模型不存在或暂时不可用<br/>冷却键:Provider + 协议 + Model"]

        keyScope["密钥级<br/>额度耗尽、Key 限流、认证失败<br/>冷却键:Provider + 协议"]

        endpointScope["端点级<br/>某种协议对应的上游端点异常<br/>冷却键:Provider + 协议"]

        providerScope["Provider 级<br/>DNS、连接超时、整个上游不可达<br/>冷却键:Provider"]

        noCooldown["不写入冷却"]

        duration{"确定冷却结束时间"}

        explicitTime["优先使用上游返回的<br/>明确恢复时间 CooldownUntil"]

        retryAfter["其次使用<br/>当前时间 + Retry-After"]

        localPolicy["没有上游时间时<br/>按错误原因和连续失败次数计算"]

        policies["默认冷却策略<br/>5xx:5、10、15 秒递增,最长 10 分钟<br/>网络错误:5、10、20、40 秒递增,最长 30 分钟<br/>额度耗尽:1 小时<br/>限流或上游并发限制:5 秒<br/>认证失败:1 小时<br/>模型不可用:5 分钟<br/>未知错误:线性递增,最长 5 分钟"]

        saveCooldown["写入冷却状态<br/>相同键保留更晚的结束时间"]

        currentCandidates["当前请求继续使用请求开始时<br/>已经生成的候选 Provider 列表<br/>不会重新执行冷却筛选"]

        requestRetryable{"当前请求级错误<br/>是否允许重试"}

        requestFailed["直接返回当前请求错误<br/>不冷却,也不切换 Provider"]

        retryable{"当前 Provider 的错误<br/>是否允许重试"}

        attemptsLeft{"当前 Provider 是否还有<br/>同渠道重试次数"}

        waitSource{"本次重试等待时间来源"}

        retryAfterWait["上游返回 Retry-After<br/>按该时长等待<br/>不受 RetryConfig 最大间隔限制"]

        retryConfigWait["上游未返回 Retry-After<br/>按 RetryConfig 计算退避时间<br/>不超过配置的最大间隔"]

        waitState{"等待期间请求是否<br/>仍然有效"}

        requestStopped["客户端断开或请求超时<br/>停止等待并结束当前请求"]

        retrySame["等待结束<br/>再次请求同一个 Provider"]

        nextProvider{"是否还有下一个<br/>候选 Provider"}

        failover["切换到下一候选 Provider<br/>该 Provider 使用自己的重试配置"]

        allFailed["所有候选均失败<br/>返回最终错误"]

        classify --> committed
        committed -->|已经写出| committedStop
        committed -->|尚未写出| scope

        scope -->|Request| requestScope
        scope -->|Model| modelScope
        scope -->|Key| keyScope
        scope -->|Endpoint| endpointScope
        scope -->|Provider| providerScope

        requestScope --> noCooldown
        modelScope --> duration
        keyScope --> duration
        endpointScope --> duration
        providerScope --> duration

        duration -->|上游返回存在冷却时间| explicitTime
        duration -->|否则存在 至少冷却时间| retryAfter
        duration -->|都不存在| localPolicy

        localPolicy -.-> policies

        explicitTime --> saveCooldown
        retryAfter --> saveCooldown
        localPolicy --> saveCooldown

        noCooldown --> requestRetryable
        requestRetryable -->|否| requestFailed
        requestRetryable -->|是| currentCandidates

        saveCooldown --> currentCandidates
        saveCooldown -.->|只影响后续新请求| cooldownCheck

        currentCandidates --> retryable
        retryable -->|是| attemptsLeft
        retryable -->|否,不再重试当前 Provider| nextProvider

        attemptsLeft -->|有| waitSource
        waitSource -->|存在 Retry-After| retryAfterWait
        waitSource -->|不存在 Retry-After| retryConfigWait
        retryAfterWait --> waitState
        retryConfigWait --> waitState
        waitState -->|仍然有效| retrySame
        waitState -->|已取消或超时| requestStopped

        attemptsLeft -->|没有,当前 Provider 重试结束| nextProvider
        nextProvider -->|有| failover
        nextProvider -->|没有| allFailed
    end

    releaseSlot -->|失败| classify
    retrySame --> adapterExecute
    failover --> choose
    committedStop --> maxxResponse
    requestFailed --> maxxResponse
    requestStopped --> maxxResponse
    allFailed --> maxxResponse

    subgraph data["数据与分布式状态"]
        sharedMySQL[(共享 MySQLNew API 用户、Token、渠道、套餐、订单、订阅、支付、发票等)]

        maxxMySQL[("Maxx MySQL<br/>Provider、Route、模型映射、重试配置<br/>请求记录、成本、冷却持久化")]

        redis[("Redis<br/>会话亲和绑定<br/>冷却状态和冷却版本号<br/>发布冷却变更通知")]

        localCache["各网关实例的本地内存<br/>用于快速查询冷却状态"]

        redisWrite[写入 Redis 冷却值]

        localWrite["更新当前网关实例的<br/>本地冷却状态"]

        mysqlWrite["将冷却状态持久化到 MySQL"]

        versionBump["在 Redis 中原子递增<br/>该 Provider 的冷却版本号"]

        publishEvent["发布冷却变更通知<br/>Provider ID + 最新版本号"]

        instanceSync["其他网关实例<br/>接收变更通知或主动检查版本号"]

        versionCompare{"本地已知版本号<br/>是否落后于 Redis"}

        reloadCooldown["从 Redis 重新加载<br/>该 Provider 的全部冷却状态"]

        ignoreEvent["版本未变化<br/>忽略重复或过期通知"]

        saveCooldown --> redisWrite
        redisWrite --> redis
        redisWrite --> localWrite
        localWrite --> localCache
        localWrite --> mysqlWrite
        mysqlWrite --> maxxMySQL
        mysqlWrite --> versionBump
        versionBump --> redis
        versionBump --> publishEvent
        publishEvent --> instanceSync

        redis -.->|主动检查最新版本号| instanceSync
        instanceSync --> versionCompare
        versionCompare -->|是| reloadCooldown
        versionCompare -->|否| ignoreEvent
        redis --> reloadCooldown
        reloadCooldown --> localCache
        localCache -.-> cooldownCheck

        stickyGet <--> redis
        stickySet --> redis

        routeLoad <--> maxxMySQL
        maxxCost --> maxxMySQL

        dashboardAuth <--> sharedMySQL
        apiTokenAuth <--> sharedMySQL
        userPolicy <--> sharedMySQL
        channelSelect <--> sharedMySQL
        userBilling --> sharedMySQL

        tokenLookup <--> sharedMySQL
        bizExecute <--> sharedMySQL
    end

    subgraph upstreams["具体模型上游"]
        upstreamA["Provider A"]
        upstreamB["Provider B"]
        upstreamC["Provider C"]
    end

    adapterExecute --> upstreamA
    adapterExecute --> upstreamB
    adapterExecute --> upstreamC

eks 交付系统

flowchart TD

    developer["开发者"]

    %% =========================
    %% 外部镜像来源
    %% =========================
    subgraph imageSource["外部镜像来源"]

        release["业务系统发布流程<br/>构建并发布容器镜像"]

        imageRegistry[("镜像仓库<br/>保存容器镜像")]

        writeImageTag["自动更新 IaC 仓库中的镜像版本"]

        release --> imageRegistry
        release --> writeImageTag

    end


    %% =========================
    %% IaC 仓库
    %% =========================
    subgraph iacRepo["基础设施与部署配置仓库(IaC 仓库)"]

        appConfig["修改应用或平台配置<br/>apps/** 或 platform/**"]

        appPR["创建 IaC 仓库合并请求"]

        manifestCheck["Kubernetes 资源清单预检查<br/>生成 Kustomize 配置<br/>校验资源格式和字段<br/>检查开发与生产环境的密钥隔离"]

        appReview["人工评审应用或平台配置"]

        appMerge["合并应用或平台配置"]


        infraConfig["修改云基础设施配置<br/>infra/**"]

        infraPR["创建 IaC 仓库合并请求"]

        tofuPlan["OpenTofu 变更预演<br/>展示将新增、修改或删除的云资源"]

        infraReview["人工评审基础设施变更"]

        infraMerge["合并基础设施配置"]


        mainBranch[("IaC 仓库 main 分支<br/>保存应用、平台和云基础设施配置")]


        appConfig -->|创建合并请求| appPR
        appPR --> manifestCheck
        manifestCheck --> appReview
        appReview --> appMerge
        appMerge --> mainBranch

        appConfig -->|直接提交<br/>跳过合并请求预检查| mainBranch


        infraConfig -->|创建合并请求| infraPR
        infraPR --> tofuPlan
        tofuPlan --> infraReview
        infraReview --> infraMerge
        infraMerge --> mainBranch

        infraConfig -->|直接提交<br/>跳过变更预演和人工评审| mainBranch

    end

    developer --> appConfig
    developer --> infraConfig

    writeImageTag -->|写入新镜像版本| mainBranch


    %% =========================
    %% Argo CD 同步
    %% =========================
    subgraph gitops["Argo CD 应用部署链路"]

        argoPoll["Argo CD 每约 180 秒<br/>读取 IaC 仓库 main 分支"]

        renderManifest["读取 apps/** 和 platform/**<br/>生成 Kubernetes 资源清单"]

        compareState{"仓库配置与集群当前状态<br/>是否存在差异?"}

        unchanged["没有差异<br/>保持当前运行状态"]

        sync["发现差异后按仓库配置同步<br/>创建或更新发生变化的资源<br/>删除仓库中已移除的受管资源<br/>同步后集群状态与仓库配置保持一致"]

        rollingUpdate["多副本服务滚动更新<br/>先创建新 Pod<br/>新 Pod 通过就绪检查后接收流量<br/>再逐个停止旧 Pod"]

        recreateUpdate["单副本服务重新创建<br/>先停止旧 Pod,再创建新 Pod<br/>更新期间可能短暂中断"]

        configResources["其他 Kubernetes 资源<br/>Service、Ingress、Secret<br/>ExternalSecret、PodDisruptionBudget"]

        pod["运行中的 Pod"]

        clusterChange["集群资源被手动修改<br/>或运行状态发生意外变化"]


        mainBranch -->|读取应用和平台配置| argoPoll
        argoPoll --> renderManifest
        renderManifest --> compareState

        compareState -->|没有差异| unchanged
        compareState -->|存在差异| sync

        sync -->|多副本服务| rollingUpdate
        sync -->|单副本服务| recreateUpdate
        sync --> configResources

        rollingUpdate --> pod
        recreateUpdate --> pod

        clusterChange -->|下次检查发现差异| compareState

    end

    imageRegistry -->|创建 Pod 时拉取镜像| pod


    %% =========================
    %% 用户请求
    %% =========================
    subgraph requestFlow["用户请求链路"]

        user["用户请求"]

        ingress["Ingress<br/>根据域名和路径转发请求"]

        service["Service<br/>提供稳定访问地址<br/>并把请求分配给可用 Pod"]

        user --> ingress
        ingress --> service
        service -->|只转发给通过就绪检查的 Pod| pod

    end

    configResources -.-> ingress
    configResources -.-> service


    %% =========================
    %% Pod 状态检查
    %% =========================
    subgraph healthCheck["Pod 状态检查"]

        startup["启动检查<br/>判断应用是否完成初始化"]

        readiness["就绪检查<br/>失败时停止接收新请求"]

        liveness["存活检查<br/>持续失败时重新启动容器"]

        pod --> startup
        pod --> readiness
        pod --> liveness

        liveness -->|持续失败| pod

    end


    %% =========================
    %% OpenTofu
    %% =========================
    subgraph infrastructure["OpenTofu 云基础设施链路"]

        applyWorkflow["GitHub Actions<br/>检测到 main 分支中的 infra/** 发生变化"]

        acquireLock["获取状态锁<br/>防止多个 OpenTofu 任务同时执行"]

        lockTable[("AWS DynamoDB 状态锁表<br/>code0-iac-tofu-locks<br/>us-west-2")]

        readState["读取 OpenTofu 状态文件"]

        stateBucket[("AWS S3 状态文件存储<br/>记录 OpenTofu 已管理的云资源<br/>启用版本控制和 KMS 加密")]

        tofuApply["OpenTofu 执行基础设施变更"]

        cloudAPI["调用 AWS 接口"]

        cloudResources["AWS 云资源<br/>网络、Kubernetes 集群<br/>数据库、权限及其他基础设施"]

        saveState["变更完成后<br/>把最新状态写回 S3"]

        releaseLock["释放 DynamoDB 状态锁"]


        mainBranch -->|infra/** 发生变化时触发| applyWorkflow

        applyWorkflow --> acquireLock
        acquireLock -.-> lockTable

        acquireLock --> readState
        stateBucket -.-> readState

        readState --> tofuApply
        tofuApply --> cloudAPI
        cloudAPI --> cloudResources

        cloudResources -->|变更完成| saveState
        saveState --> stateBucket

        saveState --> releaseLock
        releaseLock -.-> lockTable

    end

    cloudResources -.->|提供运行环境| pod


    %% =========================
    %% 监控与日志
    %% =========================
    subgraph observability["监控与日志"]

        prometheus["Prometheus<br/>采集运行指标"]

        logCollector["日志采集组件<br/>收集容器日志"]

        loki["Loki<br/>保存和查询日志"]

        grafana["Grafana<br/>查看指标、日志和告警"]

        pod --> prometheus
        pod --> logCollector

        logCollector --> loki

        prometheus --> grafana
        loki --> grafana

    end

支付系统

flowchart TD
    client["客户端<br/>选择支付方式并发起支付"]

    subgraph create["一、创建预约订单和支付订单"]
        preview["价格预览<br/>计算课程价格、优惠和应付金额"]
        booking["创建预约订单<br/>锁定教师时间 15 分钟"]
        payment["创建支付订单<br/>锁定价格并预留优惠券"]
        channel{"本次选择的支付渠道"}

        preview --> booking
        booking --> payment
        payment --> channel
    end

    client --> preview

    subgraph attempt["二、创建或复用支付尝试"]
        sameChannel["继续使用原支付渠道"]
        reusable{"原支付尝试和支付链接<br/>是否仍然有效"}
        reuse["复用原支付尝试和支付链接"]

        newAttempt["创建新的支付尝试"]
        paypalOrder["创建新的 PayPal 订单"]
        checkout["返回支付链接"]

        channel --> sameChannel
        sameChannel --> reusable
        reusable -->|有效| reuse
        reusable -->|无效| newAttempt

        channel -->|切换支付渠道| newAttempt
        newAttempt --> paypalOrder
        paypalOrder --> checkout
        reuse --> checkout
    end

    subgraph paymentFlow["三、用户授权、扣款和主动查询"]
        authorize["用户打开支付链接<br/>完成 PayPal、Apple Pay 或 Google Pay 授权"]
        clientQuery["客户端通知平台支付完成<br/>并查询支付结果"]
        queryOrder["平台主动查询 PayPal 订单"]
        orderState{"PayPal 订单状态"}

        approved["订单已批准"]
        capture["平台向 PayPal 发起正式扣款"]
        queryCapture["重新查询 PayPal 订单详情<br/>读取最新真实扣款记录"]
        captureState{"真实扣款状态"}

        authorize --> clientQuery
        clientQuery --> queryOrder
        queryOrder --> orderState

        orderState -->|已批准| approved
        approved --> capture
        capture --> queryCapture

        orderState -->|已完成| queryCapture
        queryCapture --> captureState
    end

    checkout --> authorize

    orderState -->|处理中| processing
    orderState -->|失败| failed

    captureState -->|扣款已完成| settle
    captureState -->|扣款处理中| processing
    captureState -->|暂无扣款记录| noCapture
    captureState -->|扣款被拒绝| declined
    captureState -->|扣款失败| failed

    noCapture["暂无真实扣款记录<br/>暂不判定支付成功"]
    declined["扣款被支付渠道拒绝"]

    noCapture --> processing
    declined --> failed

    subgraph processingFlow["四、支付处理中和异步恢复"]
        processing["保持支付处理中<br/>不提前标记为支付成功"]
        waitEvent["等待客户端再次查询<br/>或接收 PayPal 异步通知"]
        webhook["接收 PayPal 异步通知"]
        reconcile["主动查询 PayPal 最新订单状态<br/>不直接相信客户端或通知中的结果"]

        processing --> waitEvent
        waitEvent -->|客户端再次查询| reconcile
        waitEvent -->|收到异步通知| webhook
        webhook --> reconcile
        reconcile --> queryOrder
    end

    subgraph settlement["五、统一幂等结算"]
        settle["进入统一结算入口"]
        lockPayment["在数据库事务中<br/>锁定支付订单和支付尝试"]
        checkSettled{"支付订单是否已经结算"}

        settled["直接返回已有结算结果"]
        markSuccess["支付订单标记为已支付<br/>支付尝试标记为成功"]
        confirmBooking["确认预约订单<br/>正式占用教师时间"]
        consumeCoupon["核销预留的优惠券"]
        commitSuccess["提交结算事务"]
        notify["发送学生和导师付款确认通知"]

        settle --> lockPayment
        lockPayment --> checkSettled

        checkSettled -->|已经结算| settled
        checkSettled -->|尚未结算| markSuccess
        markSuccess --> confirmBooking
        confirmBooking --> consumeCoupon
        consumeCoupon --> commitSuccess
        commitSuccess --> notify
    end

    subgraph failureFlow["六、正常支付失败"]
        failed["确认支付失败"]
        markAttemptFailed["当前支付尝试标记为失败"]
        retryChoice{"用户是否重新支付"}

        retrySame["继续使用同一支付渠道"]
        changeChannel["选择其他支付渠道"]
        abandon["放弃支付<br/>等待预约订单到期"]

        failed --> markAttemptFailed
        markAttemptFailed --> retryChoice

        retryChoice -->|同一渠道| retrySame
        retryChoice -->|其他渠道| changeChannel
        retryChoice -->|暂不支付| abandon

        retrySame --> newAttempt
        changeChannel --> newAttempt
    end

    subgraph timeout["七、超时扫描和最终支付核验"]
        timeoutJob["后台定时任务<br/>扫描未确认的预约订单"]
        expired{"15 分钟预约锁定时间<br/>是否已经到期"}
        nextScan["暂不处理<br/>等待下一轮扫描"]

        lockExpired["开启数据库事务<br/>锁定预约订单和支付订单"]
        reread["重新读取最新状态<br/>防止与主动查询或异步通知并发冲突"]
        currentState{"重新读取后的支付状态"}

        alreadyPaid["已经支付成功"]
        keepBooking["保持预约订单有效<br/>不释放教师时间和优惠券"]

        noAttempt["没有有效的支付尝试"]
        clearlyFailed["支付已经明确失败"]

        needVerify["支付处理中、状态未知<br/>或存在尚未确认的支付尝试"]
        startVerify["记录最终核验截止时间<br/>预约到期时间 + 1 分钟"]
        queryLatest["主动查询 PayPal 订单<br/>并读取最新真实扣款记录"]
        verifyResult{"最终核验结果"}

        verifyApproved["订单已经批准"]
        verifyCompleted["真实扣款已完成"]
        verifyProcessing["真实扣款仍在处理中"]
        verifyNoCapture["暂无真实扣款记录"]
        verifyDeclined["真实扣款被拒绝"]
        verifyFailed["真实扣款失败"]
        verifyUnpaid["确认用户尚未完成支付"]
        verifyUnavailable["PayPal 查询超时、网络异常<br/>或服务暂时不可用"]

        withinWindow{"是否仍在 1 分钟<br/>最终核验期内"}
        waitQuery["短暂等待<br/>进入下一次主动查询"]

        abnormal["标记为支付核验异常<br/>不能直接判定为未支付"]
        preserve["继续保留预约订单<br/>教师时间和优惠券"]
        adminAlert["向管理员发送异常告警邮件"]
        autoRecheck["后台继续自动重查"]
        manualReview["管理员在后台人工核对"]

        timeoutJob --> expired
        expired -->|没有到期| nextScan
        expired -->|已经到期| lockExpired

        lockExpired --> reread
        reread --> currentState

        currentState -->|已经支付| alreadyPaid
        alreadyPaid --> keepBooking

        currentState -->|没有有效支付尝试| noAttempt
        currentState -->|明确支付失败| clearlyFailed
        noAttempt --> releaseTx
        clearlyFailed --> releaseTx

        currentState -->|处理中或状态未知| needVerify
        needVerify --> startVerify
        startVerify --> queryLatest
        queryLatest --> verifyResult

        verifyResult -->|订单已批准| verifyApproved
        verifyApproved --> capture

        verifyResult -->|扣款已完成| verifyCompleted
        verifyCompleted --> settle

        verifyResult -->|扣款处理中| verifyProcessing
        verifyResult -->|暂无扣款记录| verifyNoCapture
        verifyResult -->|暂时无法查询| verifyUnavailable

        verifyProcessing --> withinWindow
        verifyNoCapture --> withinWindow
        verifyUnavailable --> withinWindow

        withinWindow -->|仍在核验期内| waitQuery
        waitQuery --> queryLatest
        withinWindow -->|核验期已结束| abnormal

        verifyResult -->|扣款被拒绝| verifyDeclined
        verifyResult -->|扣款失败| verifyFailed
        verifyResult -->|确认未支付| verifyUnpaid

        verifyDeclined --> releaseTx
        verifyFailed --> releaseTx
        verifyUnpaid --> releaseTx

        abnormal --> preserve
        abnormal --> adminAlert
        abnormal --> autoRecheck
        abnormal --> manualReview

        autoRecheck --> queryLatest
        manualReview --> queryLatest
    end

    payment -.-> timeoutJob

    subgraph releaseFlow["八、取消预约和释放资源"]
        releaseTx["在数据库事务中<br/>再次锁定并复核订单状态"]
        releaseCheck{"复核时是否已经支付成功"}

        stopRelease["停止释放<br/>转入统一结算"]
        cancelPayment["支付订单标记为已过期"]
        closeAttempt["未完成的支付尝试标记为已结束"]
        cancelBooking["取消预约订单<br/>释放教师时间"]
        releaseCoupon["释放预留的优惠券"]
        commitRelease["提交事务"]
        expiredResult["客户端再次查询时<br/>返回预约和支付已过期"]

        releaseTx --> releaseCheck

        releaseCheck -->|已经支付| stopRelease
        stopRelease --> settle

        releaseCheck -->|仍未支付| cancelPayment
        cancelPayment --> closeAttempt
        closeAttempt --> cancelBooking
        cancelBooking --> releaseCoupon
        releaseCoupon --> commitRelease
        commitRelease --> expiredResult
    end

agent 系统

flowchart TD

    user["操作者"]
    web["Web 工作台"]
    client["接口访问层"]

    user --> web
    web --> client

    subgraph core_group["核心服务入口"]
        direction TB
        http_in["1. 接收 HTTP 请求"]
        request_ctx["生成请求编号和调用链编号"]
        middleware["执行访问日志、超时、限流<br/>跨域和请求体大小检查"]
        request_route{"识别请求类型"}

        http_in --> request_ctx
        request_ctx --> middleware
        middleware --> request_route
    end

    client --> http_in

    subgraph management_group["系统管理"]
        direction LR
        manage_agent["AI 助手配置"]
        manage_model["模型接入配置"]
        manage_skill["技能配置"]
        manage_module["可插拔模块配置"]
        manage_schedule["定时计划配置"]
        manage_system["系统运行配置"]

        manage_agent --> manage_model
        manage_model --> manage_skill
        manage_skill --> manage_module
        manage_module --> manage_schedule
        manage_schedule --> manage_system
    end

    request_route -->|系统管理请求| manage_agent

    subgraph call_group["统一功能调用"]
        direction TB
        call_entry["根据功能名称查找实现"]
        call_find["读取当前已启用的功能实现"]
        call_actor{"是否由 AI 助手发起"}
        call_permission["执行权限检查"]
        call_execute["调用对应功能模块"]
        call_result{"执行结果"}
        call_success["记录成功并触发后续处理"]
        call_failure["记录失败原因"]
        call_return["返回调用结果"]

        call_entry --> call_find
        call_find --> call_actor
        call_actor -->|是| call_permission
        call_actor -->|否| call_execute
        call_execute --> call_result
        call_result -->|成功| call_success
        call_result -->|失败| call_failure
        call_success --> call_return
        call_failure --> call_return
    end

    request_route -->|业务功能请求| call_entry

    subgraph module_group["可插拔模块运行层"]
        direction TB
        module_config["读取模块声明<br/>功能、订阅、页面、设置和模型能力"]
        module_snapshot["生成当前可用模块清单<br/>配置变化时递增版本"]
        module_lookup["根据功能名称找到目标模块"]
        module_kind{"模块运行方式"}
        module_process["独立进程运行<br/>通过标准输入输出交换 JSON 消息"]
        module_wasm["Wasm 运行<br/>限制内存、时间和计算量"]
        module_response["返回模块执行结果"]
        module_diagnostic["模块异常时记录诊断<br/>失效进程按需重建"]

        module_config --> module_snapshot
        module_snapshot --> module_lookup
        module_lookup --> module_kind
        module_kind -->|独立进程| module_process
        module_kind -->|Wasm| module_wasm
        module_process --> module_response
        module_wasm --> module_response
        module_process -->|异常| module_diagnostic
        module_wasm -->|异常| module_diagnostic
    end

    call_find -.-> module_snapshot
    call_execute --> module_lookup
    module_response --> call_result

    subgraph conversation_group["会话管理"]
        direction TB
        msg_append["2. 追加用户消息"]
        msg_save["3. 保存会话、线路、分支和原始消息"]
        msg_identify["生成消息编号和分支内顺序"]
        conv_db[("会话数据库<br/>原始消息唯一保存位置")]
        msg_return["4. 立即返回消息写入结果"]
        msg_notify["通知前端显示新消息"]
        reply_background["5. 后台启动回复流程<br/>不阻塞消息写入接口"]

        msg_append --> msg_save
        msg_save --> msg_identify
        msg_identify --> conv_db
        msg_identify --> msg_return
        msg_identify --> msg_notify
        msg_identify --> reply_background
    end

    call_entry -->|追加消息| msg_append
    msg_return --> call_return
    msg_notify --> web

    subgraph selector_group["回复处理选择"]
        direction TB
        reply_received["6. 收到待回复的用户消息"]
        reply_find["查找可以处理会话回复的模块"]
        reply_order["按优先级稳定排序"]
        reply_ask["依次询问是否处理本次回复"]
        reply_choice{"处理结果"}
        reply_continue["跳过、失败或不适用<br/>继续下一个模块"]
        reply_claimed["确定本次回复处理模块"]
        reply_none["没有模块接管<br/>记录未处理状态"]

        reply_received --> reply_find
        reply_find --> reply_order
        reply_order --> reply_ask
        reply_ask --> reply_choice
        reply_choice -->|不接管| reply_continue
        reply_continue --> reply_ask
        reply_choice -->|接管或完成| reply_claimed
        reply_order -->|没有可用模块| reply_none
    end

    reply_background --> reply_received

    subgraph assistant_group["AI 助手运行模块"]
        direction TB
        assistant_default["7. 默认回复模块"]
        assistant_dedup["领取消息处理记录<br/>避免同一消息重复回复"]
        assistant_load["加载 AI 助手、模型、技能和生成参数"]
        assistant_source["读取当前用户消息"]
        assistant_history["按当前分支分页读取历史"]
        assistant_skills["加载已启用技能"]
        assistant_tools["准备命令和技能调用能力"]
        assistant_context["开始组装模型上下文"]
        runtime_db[("AI 助手运行数据库<br/>处理记录、执行状态和历史摘要")]

        assistant_default --> assistant_dedup
        assistant_dedup --> runtime_db
        assistant_dedup --> assistant_load
        assistant_load --> assistant_source
        assistant_source --> assistant_history
        assistant_history --> assistant_skills
        assistant_skills --> assistant_tools
        assistant_tools --> assistant_context
    end

    reply_claimed --> assistant_default
    assistant_source -.->|读取单条消息| call_entry
    assistant_history -.->|分页读取历史| call_entry

    subgraph context_group["上下文管理"]
        direction TB
        context_fixed["加入系统要求、AI 助手信息<br/>技能说明和工具定义"]
        context_budget["计算可用输入容量<br/>模型窗口减去输出预留和安全余量"]
        context_request["请求记忆等模块补充上下文"]
        context_accept["按优先级和来源上限<br/>接纳补充内容"]
        context_history["将剩余容量分配给会话历史"]
        context_check{"历史是否达到压缩阈值"}
        context_find_summary["查找已有历史摘要"]
        context_validate{"摘要是否仍属于<br/>当前分支的有效历史"}
        context_summarize["按完整对话回合生成摘要"]
        context_save["保存摘要边界、内容版本和历史指纹"]
        context_final{"调用模型前再次检查总容量"}
        context_error["停止生成并返回容量错误<br/>不静默删除消息"]
        context_ready["形成最终模型输入"]

        assistant_context --> context_fixed
        context_fixed --> context_budget
        context_budget --> context_request
        context_request --> context_accept
        context_accept --> context_history
        context_history --> context_check
        context_check -->|未达到阈值| context_final
        context_check -->|达到阈值| context_find_summary
        context_find_summary --> context_validate
        context_validate -->|有效| context_final
        context_validate -->|无效或不存在| context_summarize
        context_summarize --> context_save
        context_save --> runtime_db
        context_save --> context_final
        context_final -->|超出容量| context_error
        context_final -->|通过| context_ready
    end

    subgraph memory_group["记忆系统"]
        direction TB
        memory_prepare["收到补充上下文请求"]
        memory_scope["根据当前 AI 助手和记忆分类<br/>确定检索范围"]
        memory_search["检索相关记忆"]
        memory_db[("记忆数据库<br/>提炼内容、来源引用、向量和关系")]
        memory_return["返回预算内的记忆内容和来源"]
        memory_evidence{"是否需要核对原始依据"}
        memory_get_source["根据消息编号读取原文"]
        memory_manage["显式新增、查询和审查记忆"]
        memory_future["自动从会话提炼记忆<br/>当前尚未接入"]

        memory_prepare --> memory_scope
        memory_scope --> memory_search
        memory_search --> memory_db
        memory_search --> memory_return
        memory_return --> memory_evidence
        memory_evidence -->|需要| memory_get_source
        memory_manage --> memory_db
        memory_future -.-> memory_db
    end

    context_request --> memory_prepare
    memory_return --> context_accept
    memory_get_source -.-> call_entry
    msg_identify -.->|未来只传消息编号| memory_future

    subgraph model_group["模型调用与工具执行循环"]
        direction TB
        model_call["8. 请求模型服务"]
        model_permission["检查模型调用权限"]
        model_endpoint["读取模型地址、凭证和能力"]
        model_convert["转换统一请求为目标模型格式"]
        model_external["外部模型服务"]
        model_parse["解析模型响应"]
        model_result{"模型返回类型"}
        model_final["得到最终回复"]
        model_tool["模型要求调用工具"]
        model_continue["将工具结果加入上下文<br/>继续请求模型"]

        context_ready --> model_call
        model_call --> model_permission
        model_permission --> model_endpoint
        model_endpoint --> model_convert
        model_convert --> model_external
        model_external --> model_parse
        model_parse --> model_result
        model_result -->|最终内容| model_final
        model_result -->|工具调用| model_tool
        model_continue --> model_call
    end

    subgraph tool_group["命令与技能执行"]
        direction TB
        tool_type{"工具类型"}
        tool_command["解析本地命令、参数<br/>工作目录和超时"]
        tool_skill["读取已启用技能的入口和配置"]
        tool_permission["检查工具操作权限"]
        tool_run_command["启动本地命令"]
        tool_run_skill["执行技能脚本"]
        tool_state{"执行状态"}
        tool_done["返回执行结果"]
        tool_pending["保存等待审批或异步执行状态"]
        tool_resume["审批或异步操作完成后恢复"]

        model_tool --> tool_type
        tool_type -->|命令| tool_command
        tool_type -->|技能| tool_skill
        tool_command --> tool_permission
        tool_skill --> tool_permission
        tool_permission --> tool_run_command
        tool_permission --> tool_run_skill
        tool_run_command --> tool_state
        tool_run_skill --> tool_state
        tool_state -->|完成| tool_done
        tool_state -->|等待| tool_pending
        tool_pending --> tool_resume
        tool_resume --> tool_done
        tool_done --> model_continue
    end

    subgraph permission_group["权限和审批"]
        direction TB
        permission_input["根据 AI 助手、操作、目标<br/>会话和任务生成权限请求"]
        permission_policy["读取权限策略和命令匹配规则"]
        permission_decision{"权限结果"}
        permission_allow["允许执行"]
        permission_deny["拒绝执行并记录原因"]
        permission_approval["创建待审批事项"]
        permission_user{"操作者审批结果"}
        permission_grant["生成临时许可<br/>单次、当前回复或当前会话"]
        permission_db[("权限数据库<br/>权限事件、审批和临时许可")]

        permission_input --> permission_policy
        permission_policy --> permission_decision
        permission_decision -->|允许| permission_allow
        permission_decision -->|拒绝| permission_deny
        permission_decision -->|需要确认| permission_approval
        permission_approval --> permission_user
        permission_user -->|批准| permission_grant
        permission_user -->|拒绝| permission_deny
        permission_grant --> permission_allow
        permission_policy --> permission_db
        permission_approval --> permission_db
        permission_grant --> permission_db
    end

    call_permission --> permission_input
    model_permission --> permission_input
    tool_permission --> permission_input

    permission_allow --> call_execute
    permission_allow --> model_endpoint
    permission_allow --> tool_run_command
    permission_allow --> tool_run_skill
    permission_approval --> tool_pending
    permission_deny --> call_failure

    subgraph reply_write_group["回复写回"]
        direction TB
        reply_prepare["9. 整理最终回复格式"]
        reply_append["调用追加消息功能"]
        reply_save["保存 AI 助手回复"]
        reply_db[("会话数据库")]
        reply_notify["通知前端更新会话"]

        model_final --> reply_prepare
        reply_prepare --> reply_append
        reply_append --> reply_save
        reply_save --> reply_db
        reply_save --> reply_notify
    end

    reply_notify --> web

    subgraph event_group["事件记录与订阅"]
        direction TB
        event_publish["记录已经发生的事实<br/>只携带编号和必要信息"]
        event_db[("事件数据库<br/>事件内容和投递状态")]
        event_find["查找订阅该事件的模块"]
        event_create["为每个订阅者创建投递任务"]
        event_loop["后台执行投递"]
        event_order["按优先级和稳定顺序排列"]
        event_invoke["调用订阅模块"]
        event_result{"投递结果"}
        event_success["标记投递成功"]
        event_failure["记录失败并保留恢复依据"]

        event_publish --> event_db
        event_db --> event_find
        event_find --> event_create
        event_create --> event_db
        event_create --> event_loop
        event_loop --> event_order
        event_order --> event_invoke
        event_invoke --> event_result
        event_result -->|成功| event_success
        event_result -->|失败| event_failure
        event_success --> event_db
        event_failure --> event_db
    end

    msg_identify -->|消息已创建| event_publish
    call_success -->|功能执行成功| event_publish
    tool_pending -->|操作状态变化| event_publish
    event_invoke --> module_lookup

    subgraph subscription_group["主要订阅关系"]
        direction TB
        sub_operation["异步操作状态变化"]
        sub_restore["恢复等待中的 AI 助手执行"]
        sub_run["编排任务已创建"]
        sub_workflow_start["启动任务编排"]
        sub_stage["编排阶段变化"]
        sub_workflow_update["更新编排状态"]
        sub_artifact["任务产物已创建"]
        sub_artifact_record["登记任务产物"]
        sub_schedule["定时计划到期"]
        sub_schedule_run["启动定时任务"]

        sub_operation --> sub_restore
        sub_run --> sub_workflow_start
        sub_stage --> sub_workflow_update
        sub_artifact --> sub_artifact_record
        sub_schedule --> sub_schedule_run
    end

    event_invoke --> sub_operation
    event_invoke --> sub_run
    event_invoke --> sub_stage
    event_invoke --> sub_artifact
    event_invoke --> sub_schedule
    sub_restore --> tool_resume

    subgraph workflow_group["任务编排"]
        direction TB
        workflow_entry["显式创建任务<br/>或由定时计划触发"]
        workflow_create["创建一次任务运行"]
        workflow_plan["生成结构化执行方案"]
        workflow_steps["从方案生成任务步骤"]
        workflow_run_agent["调用 AI 助手执行步骤"]
        workflow_check{"检查步骤结果"}
        workflow_approval["等待确认或审批"]
        workflow_retry["重试、调整方案或结束"]
        workflow_artifact["保存文件或结构化产物"]
        workflow_accept["根据完成标准验收"]
        workflow_finish["生成最终任务结果"]
        workflow_db[("任务编排数据库<br/>运行、方案、步骤、尝试<br/>审批、产物和任务交接")]

        workflow_entry --> workflow_create
        workflow_create --> workflow_db
        workflow_create --> workflow_plan
        workflow_plan --> workflow_db
        workflow_plan --> workflow_steps
        workflow_steps --> workflow_db
        workflow_steps --> workflow_run_agent
        workflow_run_agent --> workflow_check
        workflow_check -->|需要确认| workflow_approval
        workflow_check -->|失败可重试| workflow_retry
        workflow_check -->|成功| workflow_artifact
        workflow_approval --> workflow_steps
        workflow_retry --> workflow_steps
        workflow_artifact --> workflow_db
        workflow_artifact --> workflow_accept
        workflow_accept -->|通过| workflow_finish
        workflow_accept -->|未通过| workflow_retry
    end

    sub_workflow_start --> workflow_entry
    sub_workflow_update --> workflow_check
    sub_artifact_record --> workflow_db
    sub_schedule_run --> workflow_entry
    workflow_run_agent -.-> assistant_default
    workflow_artifact -->|产物已创建| event_publish
    workflow_finish -->|编排阶段变化| event_publish

    workflow_note["普通会话默认不自动进入任务编排<br/>当前主要通过显式创建或定时计划进入"]
    workflow_note -.-> workflow_entry

    subgraph schedule_group["定时计划"]
        direction TB
        schedule_db[("定时计划文件")]
        schedule_scan["扫描到期计划"]
        schedule_trigger{"触发方式<br/>单次、固定间隔或 Cron"}
        schedule_executor{"执行方式"}
        schedule_command["执行本地命令"]
        schedule_workflow["启动任务编排"]
        schedule_retry["失败重试和退避"]
        schedule_history["保存最近运行历史"]
        schedule_delivery["可选:将全文、摘要或结论<br/>投递到指定会话"]

        schedule_db --> schedule_scan
        schedule_scan --> schedule_trigger
        schedule_trigger --> schedule_executor
        schedule_executor -->|命令| schedule_command
        schedule_executor -->|AI 任务| schedule_workflow
        schedule_command --> schedule_retry
        schedule_workflow --> workflow_entry
        workflow_finish --> schedule_retry
        schedule_retry --> schedule_history
        schedule_history --> schedule_delivery
        schedule_delivery --> msg_append
    end

    subgraph trace_group["调用链追踪与日志"]
        direction TB
        trace_root["HTTP 请求入口记录<br/>请求编号和调用链编号"]
        trace_feature["功能调用记录"]
        trace_module["模块调用记录"]
        trace_model["模型或工具调用记录"]
        trace_event["事件发布记录"]
        trace_async["建立原请求与后台任务的异步关联"]
        trace_delivery["事件订阅投递记录"]
        trace_reply["回复处理模块选择结果"]
        trace_db[("日志数据库<br/>日志、调用节点和异步关联")]
        trace_text["核心服务和模块文本日志"]
        trace_stream["实时日志通知"]

        trace_root --> trace_feature
        trace_feature --> trace_module
        trace_module --> trace_model
        trace_feature --> trace_event
        trace_event --> trace_async
        trace_async --> trace_delivery

        trace_root --> trace_db
        trace_feature --> trace_db
        trace_module --> trace_db
        trace_model --> trace_db
        trace_event --> trace_db
        trace_async --> trace_db
        trace_delivery --> trace_db
        trace_reply --> trace_db

        trace_module --> trace_text
        trace_db --> trace_stream
    end

    request_ctx -.-> trace_root
    call_execute -.-> trace_feature
    module_lookup -.-> trace_module
    model_call -.-> trace_model
    tool_type -.-> trace_model
    event_publish -.-> trace_event
    event_invoke -.-> trace_delivery
    reply_choice -.-> trace_reply

    subgraph live_group["前端实时更新"]
        direction TB
        live_center["实时状态分发中心"]
        live_conversation["会话消息变化"]
        live_operation["AI 执行和工具状态变化"]
        live_permission["权限和审批变化"]
        live_module["模块状态变化"]
        live_schedule["定时计划变化"]
        live_logs["日志变化"]

        live_center --> live_conversation
        live_center --> live_operation
        live_center --> live_permission
        live_center --> live_module
        live_center --> live_schedule
        live_center --> live_logs
    end

    msg_identify --> live_center
    reply_save --> live_center
    tool_pending --> live_center
    permission_approval --> live_center
    module_snapshot --> live_center
    schedule_history --> live_center
    trace_db --> live_center

    live_conversation --> web
    live_operation --> web
    live_permission --> web
    live_module --> web
    live_schedule --> web
    live_logs --> web