OpenClaw Cron 模块超深度分析报告

分析对象:openclaw/src/cron
代码规模:约 9400 行生产代码,85 个测试文件,15 个 isolated-agent 子模


📑 内容目录

  • 第一部分:模块定位与整体结构
    • 一、模块定位(业务职责、系统位置、核心价值)
    • 二、模块整体结构(类/接口、核心方法、内部调用、数据流)
    • 三、核心类型系统深度解析
  • 第二部分:服务层与定时器引擎
    • 四、服务层架构深度解析
    • 五、定时器引擎深度解析
    • 六、作业生命周期管理
  • 第三部分:执行引擎、交付与辅助模块
    • 七、Isolated Agent 执行引擎深度解析
    • 八、交付系统深度解析
    • 九、辅助模块深度解析
  • 第四部分:逐行代码走读与业务规则验证
    • 八、关键函数逐行代码走读(locked/delivery-plan/delivery/heartbeat-policy/session-target/normalize-job-identity/initial-delivery/CronService/run.types/run-fallback-policy)
    • 九、业务规则验证(调度正确性/退避策略/锁安全性/stagger/超时/交付去重/启动追赶)

OpenClaw Cron 模块深度分析 — 第一部分:模块定位与整体结构

一、模块定位

📊 架构总览图
在这里插入图片描述

1.1 业务职责

OpenClaw Cron 模块是整个系统中时间驱动任务调度的核心引擎,承担以下职责:

  1. 定时任务全生命周期管理:从创建(add)、查询(list/listPage/status)、修改(update)、删除(remove)到手动触发(run/enqueueRun),覆盖定时任务从诞生到消亡的完整生命周期。

  2. 精确时间调度:支持三种调度范式——

    • 一次性触发kind: "at"):在指定绝对时间点触发一次,执行成功后自动禁用或删除,失败后支持瞬态错误重试。
    • 固定间隔kind: "every"):从锚点时间(anchorMs)起以固定毫秒间隔触发,支持基于上次执行时间或锚点时间的双路径调度。
    • Cron 表达式kind: "cron"):使用 croner 库解析标准/扩展 cron 表达式,支持时区指定,提供确定性抖动(stagger)机制防止整点任务同时触发。
  3. 双路径执行模型

    • 主会话路径sessionTarget: "main"):向主会话注入 systemEvent 文本,触发心跳唤醒,由主会话的 Agent 自主消费事件。
    • 隔离会话路径sessionTarget: "isolated" / "current" / "session:xxx"):创建独立 Agent 会话执行 agentTurn,携带独立的模型选择、Skill 快照、认证配置和交付链路。
  4. 输出交付系统:执行完成后,将结果通过两种渠道投递——

    • announce 模式:通过消息通道(Telegram、Discord、Feishu 等)向指定目标发送文本/媒体。
    • webhook 模式:向 HTTP(S) URL 发送 POST 请求。
  5. 容错与自愈

    • 指数退避重试(errorBackoffMs):连续错误时按 [30s, 60s, 5min, 15min, 1h] 递增延迟。
    • 瞬态错误识别与 one-shot 重试:识别 rate_limit、529、network、timeout、5xx 等模式,对一次性任务最多重试 3 次。
    • 调度计算错误自动禁用:连续 3 次调度表达式解析异常后自动禁用任务并通知用户。
    • 超时保护:systemEvent 默认 10 分钟超时,agentTurn 默认 60 分钟超时,通过 AbortController 实现。
    • 卡死运行标记清理:运行标记超过 2 小时未清除的自动释放(STUCK_RUN_MS)。
  6. 运行日志与审计:每个任务执行完成后生成 JSONL 格式的运行日志,支持分页查询、状态过滤、关键字搜索。

  7. 会话回收(Session Reaper):自动清理已完成的隔离 cron 运行会话,默认 24 小时保留期,5 分钟最小扫描间隔。

1.2 系统位置

Cron 模块在 OpenClaw 整体架构中处于基础设施层与 Agent 层的桥梁位置

┌──────────────────────────────────────────────────────┐
│                    外部触发源                          │
│  CLI / HTTP API / Gateway RPC / Agent 指令            │
└──────────────┬───────────────────────────────────────┘
               │ CronServiceContract (公共 API)
               ▼
┌──────────────────────────────────────────────────────┐
│                  Cron Service 层                       │
│  start/stop/status/list/add/update/remove/run/wake    │
│  ├─ Service State (运行时状态机)                        │
│  ├─ Timer Loop (调度循环)                              │
│  ├─ Locked (互斥锁)                                    │
│  └─ Ops (命令处理器)                                   │
└──────────────┬───────────────────────────────────────┘
               │
       ┌───────┴───────┐
       ▼               ▼
┌─────────────┐  ┌──────────────────┐
│ Main Session │  │ Isolated Agent    │
│ (systemEvent)│  │ (agentTurn)      │
│  路径         │  │ 路径              │
│              │  │ ├─ 模型选择       │
│              │  │ ├─ Skill 快照     │
│              │  │ ├─ 认证配置       │
│              │  │ ├─ 执行器         │
│              │  │ └─ 交付调度       │
└──────┬───────┘  └────────┬─────────┘
       │                   │
       ▼                   ▼
┌──────────────────────────────────────────────────────┐
│              外部交付层 (Delivery)                      │
│  Announce → 消息通道 (Telegram/Discord/Feishu...)     │
│  Webhook  → HTTP(S) POST                              │
└──────────────────────────────────────────────────────┘

向上:通过 CronServiceContract 对外暴露统一接口,供 CLI、HTTP API、Gateway RPC 调用。

向下:

  • 主会话路径依赖 enqueueSystemEvent + requestHeartbeatNow / runHeartbeatOnce 与 Agent 主循环交互。
  • 隔离会话路径依赖 runIsolatedAgentJob 构建完整的独立 Agent 执行环境。
  • 交付路径依赖 deliverOutboundPayloads 与消息通道基础设施交互。

横向:

  • 依赖 SessionStore 进行会话持久化和管理。
  • 依赖 TaskExecutor 进行任务运行记账。
  • 依赖 FailoverError 模块进行错误分类。

1.3 核心价值

  1. 确定性调度的可靠性:通过 stagger 机制、MIN_REFIRE_GAP_MS 安全网、stuck-run 标记清理、多重超时保护,确保调度行为可预测、不遗漏、不重复。

  2. Agent 与时间维度的融合:将 LLM Agent 引入定时任务场景,不仅是传统的"触发-执行"模式,而是实现了"触发-Agent 推理-交付"的完整链路,使定时任务具备上下文感知和自适应能力。

  3. 渐进式容错:从瞬态重试到指数退避到自动禁用,每一层都有明确的安全边界和用户通知机制,避免静默失败。

  4. 操作安全性:文件权限 0o600/0o700、安全写入(tempfile + rename)、备份机制、legacy 字段兼容,确保数据不丢失、不泄漏。

  5. 性能可控:串行化锁(locked)、最大并发数限制(maxConcurrentRuns)、启动追赶限流(maxMissedJobsPerRestart + stagger),避免突发负载压垮系统。


📊 附图 1-1:模块在系统中的位置(架构上下文图)

图描述

  • 节点

    • 外部触发源(CLI / HTTP API / Gateway RPC):矩形,标注"Triggers"
    • CronServiceContract:圆角矩形,标注"Public API Interface"
    • Cron Service Core:大矩形容器,内部包含子模块:State、Timer Loop、Ops、Store、Normalize
    • 双路径分支:两个箭头,左侧标注"Main Session (systemEvent)“,右侧标注"Isolated Agent (agentTurn)”
    • Main Session 框:矩形,内部含 enqueueSystemEvent / requestHeartbeatNow / runHeartbeatOnce
    • Isolated Agent 框:矩形,内部含 Model Selection / Skill Snapshot / Auth Profile / Run Executor / Delivery Dispatch
    • Delivery 框:矩形,内部含 Announce(消息通道列表)/ Webhook(HTTP POST)
    • Session Store:圆柱形,标注"sessions.json"
    • Task Ledger:矩形,标注"TaskExecutor"
    • Run Log:文档图标,标注"runs/{jobId}.jsonl"
    • File Store:文档图标,标注"jobs.json"
  • 边(带标签)

    • Triggers → Contract:调用 add/update/run/remove/list
    • Contract → State:读写运行时状态
    • Timer Loop → Store:加载/持久化 jobs.json
    • Timer Loop → Main Session:enqueueSystemEvent(text)
    • Timer Loop → Isolated Agent:runIsolatedAgentJob({job, message})
    • Main Session → Delivery:主会话摘要交付
    • Isolated Agent → Delivery:隔离会话结果交付
    • Delivery → 消息通道:deliverOutboundPayloads()
    • Delivery → Webhook URL:HTTP POST
    • Timer Loop → Run Log:appendCronRunLog()
    • Timer Loop → Task Ledger:createRunningTaskRun / completeTaskRun
    • Session Reaper → Session Store:清理过期 cron 运行会话
    • Store → File Store:读写 jobs.json

二、模块整体结构

📊 架构总览图

在这里插入图片描述

2.1 文件组织与职责划分

整个 cron 模块按职责域组织为以下子目录和文件:

2.1.1 根级文件(核心类型与工具)
文件 行数(约) 职责
types.ts ~120 核心类型定义:CronSchedule、CronSessionTarget、CronPayload、CronJob、CronJobState 等
types-shared.ts ~15 泛型基础类型 CronJobBase,被 types.ts 引用
parse.ts ~30 绝对时间字符串解析(ISO 8601 / 纯数字毫秒),供 schedule 和 normalize 使用
normalize.ts ~350 用户输入标准化:schedule/payload/delivery/sessionTarget/wakeMode 的规范化逻辑
delivery-field-schemas.ts ~60 Zod schema 定义,用于 delivery 模式的字段验证
normalize-job-identity.ts ~20 任务 ID 规范化:将 legacy jobId 字段映射为 id
schedule.ts ~120 三种调度类型的下一次运行时间计算,使用 croner 库处理 cron 表达式
stagger.ts ~50 确定性抖动机制:检测整点 cron 表达式,计算 staggerMs
store.ts ~120 文件存储层:加载/保存 jobs.json,原子写入(tempfile + rename),备份机制
session-target.ts ~15 sessionTarget 安全验证:禁止路径遍历字符
active-jobs.ts ~30 进程内活跃任务追踪:使用全局单例 Set 标记正在执行的 jobId
delivery.ts ~80 失败通知的 announce 投递实现(调用 outbound 基础设施)
delivery-plan.ts ~150 交付计划解析:根据 job.delivery 解析出 CronDeliveryPlan
webhook-url.ts ~15 Webhook URL 规范化与验证(仅允许 http/https)
run-log.ts ~300 JSONL 格式运行日志:追加写入、文件大小修剪、分页查询、过滤
session-reaper.ts ~120 隔离会话回收:扫描并删除超过保留期的 cron 运行会话
heartbeat-policy.ts ~40 心跳交付策略:判断是否跳过心跳-only 回复、是否入队主会话摘要
service-contract.ts ~30 CronServiceContract 接口定义:公开 API 契约
2.1.2 service/ 子目录(服务层)
文件 行数(约) 职责
state.ts ~120 CronServiceState 类型与工厂函数、CronServiceDeps 依赖注入接口、CronEvent 事件类型
store.ts ~100 ensureLoaded(带 mtime 检查的加载)/ persist(带变更检测的保存)/ warnIfDisabled
normalize.ts ~50 服务层标准化辅助:name 必填验证、agentId 规范化、legacy name 推断
jobs.ts ~550 核心任务逻辑:创建/补丁/验证/调度计算/stagger/退避/维护重算/nextWakeAtMs
ops.ts ~400 命令处理器:start/stop/status/list/listPage/add/update/remove/run/enqueueRun/wake
timer.ts ~500 调度循环核心:armTimer → onTimer → collectRunnableJobs → executeJobCore → applyJobResult → armTimer
timeout-policy.ts ~20 超时策略:systemEvent 10min / agentTurn 60min 安全天花板
locked.ts ~20 互斥锁:基于 Promise 链的 per-storePath 串行化
2.1.3 isolated-agent/ 子目录(隔离执行)
文件 行数(约) 职责
run.ts ~350 隔离执行入口:prepareCronRunContext → executeCronRun → finalizeCronRun,完整的准备-执行-结算流程
run-executor.ts ~300 执行器:创建 Agent 运行上下文、调用 runWithModelFallback、处理 fallback
delivery-dispatch.ts ~400 交付调度:announce/webhook 投递、心跳检测、消息工具匹配、子代理后续等待
delivery-target.ts ~180 交付目标解析:从 job.delivery + session 绑定 + config 推导出 channel/to/accountId
helpers.ts ~80 辅助函数:payload 结果提取、摘要选择、心跳回复检测
model-selection.ts ~150 模型选择:优先级 chain (payload → agent override → subagent config → session → global default)
subagent-followup.ts ~80 子代理后续处理:等待子代理完成、读取子代理结果摘要
session.ts ~100 会话解析:新建/复用/过期重建 session,注入 cron 特定元数据
run-session-state.ts ~80 运行时会话状态:持久化回调、pre-run 标记、live selection 同步
skills-snapshot.ts ~50 Skill 快照:版本检查、按需重建 workspace skill 摘要
run-config.ts ~40 Agent 默认配置构建:合并 global defaults + per-agent override(排除 sandbox)

2.2 核心接口与类

2.2.1 CronServiceContract(公开契约)
export interface CronServiceContract {
  start(): Promise<void>;
  stop(): void;
  status(): Promise<CronStatusSummary>;
  list(opts?: { includeDisabled?: boolean }): Promise<CronListResult>;
  listPage(opts?: CronListPageOptions): Promise<CronListPageResult>;
  add(input: CronAddInput): Promise<CronAddResult>;
  update(id: string, patch: CronUpdateInput): Promise<CronUpdateResult>;
  remove(id: string): Promise<CronRemoveResult>;
  run(id: string, mode?: CronRunMode): Promise<CronServiceRunResult>;
  enqueueRun(id: string, mode?: CronRunMode): Promise<CronServiceRunResult>;
  getJob(id: string): CronJob | undefined;
  wake(opts: { mode: CronWakeMode; text: string }): CronWakeResult;
}

这是整个模块对外暴露的唯一接口。设计要点:

  • start/stop:生命周期管理,start 执行启动追赶(missed jobs catch-up)+ 初始调度计算 + armTimer。
  • status/list/listPage:只读查询,listPage 支持分页、排序、关键字过滤、启用状态过滤。
  • add/update/remove:写操作,自动触发 recompute + persist + armTimer。
  • run/enqueueRun:手动触发,run 同步执行,enqueueRun 异步入队到 CommandLane.Cron。
  • getJob:同步查询,直接从内存 store 返回。
  • wake:向主会话注入文本并可选立即触发心跳。
2.2.2 CronServiceState(运行时状态机)
export type CronServiceState = {
  deps: CronServiceDepsInternal;      // 依赖注入(不可变部分)
  store: CronStoreFile | null;         // 内存中的 jobs.json 副本
  timer: NodeJS.Timeout | null;        // setTimeout 句柄
  running: boolean;                    // onTimer 是否正在执行
  op: Promise<unknown>;               // 操作串行化链
  warnedDisabled: boolean;            // 是否已打印禁用警告
  storeLoadedAtMs: number | null;     // 上次加载时间
  storeFileMtimeMs: number | null;    // 上次文件修改时间
};

这不是一个类实例,而是一个纯数据结构,所有操作函数都以 state 为第一参数传入。这是典型的函数式设计——状态与行为分离,便于测试和组合。

CronServiceDeps 定义了所有外部依赖的接口(共 15 个依赖项),包括:

  • nowMs:可注入的时钟(测试用)
  • log:日志器
  • storePath:存储文件路径
  • cronEnabled:全局开关
  • cronConfig:CronConfig 配置
  • defaultAgentId:默认 agent
  • resolveSessionStorePath:会话存储路径解析
  • missedJobStaggerMs / maxMissedJobsPerRestart:启动追赶参数
  • enqueueSystemEvent:主会话事件注入
  • requestHeartbeatNow / runHeartbeatOnce:心跳控制
  • runIsolatedAgentJob:隔离执行入口
  • sendCronFailureAlert:失败告警发送
  • onEvent:事件回调

2.3 核心方法与内部调用链

2.3.1 调度循环(Timer Loop)

这是整个模块的心跳,流程如下:

start()
  ├── 清除 stale runningAtMs 标记
  ├── runMissedJobs() — 启动追赶
  │     ├── planStartupCatchup() — 在 locked 中收集过期任务
  │     ├── executeStartupCatchupPlan() — 串行执行 catch-up 候选
  │     └── applyStartupCatchupOutcomes() — 在 locked 中应用结果 + 推迟多余任务
  ├── recomputeNextRuns() — 全量重算 nextRunAtMs
  └── armTimer() — 设置下一次唤醒

armTimer(state)
  ├── 计算 nextWakeAtMs() — 最早到期任务的 nextRunAtMs
  ├── delay = max(nextWakeAtMs - now, 0)
  ├── floor = delay === 0 ? MIN_REFIRE_GAP_MS(2s) : delay  // 防止零延迟死循环
  ├── clamped = min(floor, MAX_TIMER_DELAY_MS(60s))         // 最长 1 分钟唤醒一次
  └── setTimeout(onTimer, clamped)

onTimer(state)
  ├── if (state.running) → armRunningRecheckTimer(60s); return  // 正在执行则只重设检查定时器
  ├── state.running = true
  ├── armRunningRecheckTimer(60s)  // 看门狗定时器
  ├── locked → ensureLoaded → collectRunnableJobs(now)
  │     ├── 标记 runningAtMs → persist
  ├── 并发执行 due jobs (resolveRunConcurrency)
  │     ├── markCronJobActive(jobId)
  │     ├── emit("started")
  │     ├── tryCreateCronTaskRun()
  │     └── executeJobCoreWithTimeout(state, job)
  │           └── executeJobCore(state, job)
  │                 ├── if sessionTarget === "main" → executeMainSessionCronJob()
  │                 │     ├── resolveJobPayloadTextForMain(job)
  │                 │     ├── enqueueSystemEvent(text, {agentId, sessionKey})
  │                 │     ├── if wakeMode === "now" → runHeartbeatOnce() 循环等待
  │                 │     │     ├── 最多等待 wakeNowHeartbeatBusyMaxWaitMs (2min)
  │                 │     │     ├── 每 retryDelayMs (250ms) 重试
  │                 │     │     └── 超时后降级为 requestHeartbeatNow
  │                 │     └── else → requestHeartbeatNow()
  │                 └── else → executeDetachedCronJob()
  │                       └── deps.runIsolatedAgentJob({job, message})
  ├── locked → applyOutcomeToStoredJob() for each result
  │     ├── applyJobResult() — 更新状态、计算下次运行、退避、告警
  │     ├── emit("finished")
  │     ├── if shouldDelete → 从 store.jobs 移除
  │     └── recomputeNextRunsForMaintenance()
  ├── persist(state)
  ├── [finally] sweepCronRunSessions() — 会话回收
  ├── state.running = false
  └── armTimer(state) — 重新设定下一次唤醒

关键设计决策

  1. state.running 串行化:同一时刻只有一个 onTimer tick 在执行。这避免了并发执行导致的状态竞争,但也意味着如果一个 AgentTurn 执行时间很长(比如 59 分钟),其他到期任务要等到下一个 60 秒检查周期才能被拾取。

  2. Locked 机制locked(state, fn) 基于 Promise 链实现 per-storePath 串行化。它同时等待 state.op(上一个操作)和 storeLocks.get(storePath)(上一个 store 操作),确保所有涉及 store 的操作严格串行。这不等于数据库事务——它只保证 JS 事件循环层面的串行。

  3. MIN_REFIRE_GAP_MS = 2s:防止 computeJobNextRunAtMs 返回同一秒内的时间戳导致 setTimeout(0) 死循环。这在时区/croner 边界条件下可能发生(#17821)。

  4. MAX_TIMER_DELAY_MS = 60s:即使下次唤醒在 1 小时后,定时器也最多设 60 秒。这确保了进程暂停/时钟跳跃后能快速恢复。

  5. 并发执行resolveRunConcurrency(state) 从配置读取 maxConcurrentRuns,使用 worker pool 模式并发执行多个到期任务。默认并发数为 1。

2.3.2 任务状态转换(applyJobResult)

这是最复杂的函数之一,处理任务执行完成后的所有状态更新:

applyJobResult(state, job, result)
  ├── 更新即时状态
  │     ├── runningAtMs = undefined
  │     ├── lastRunAtMs = startedAt
  │     ├── lastRunStatus = result.status
  │     ├── lastDurationMs = endedAt - startedAt
  │     ├── lastError = result.error
  │     ├── lastErrorReason = failoverReason (if error)
  │     ├── lastDelivered = result.delivered
  │     ├── lastDeliveryStatus = resolveDeliveryStatus()
  │     └── updatedAtMs = endedAt
  ├── 错误追踪
  │     ├── if error → consecutiveErrors++
  │     │     ├── 检查 failureAlert 阈值
  │     │     ├── 检查 bestEffort 豁免
  │     │     └── 冷却期检查 → emitFailureAlert()
  │     └── else → consecutiveErrors = 0, 清除告警时间戳
  ├── 判断删除 (shouldDelete)
  │     └── schedule.kind === "at" && deleteAfterRun && status === "ok"
  ├── 调度计算(非删除情况)
  │     ├── if kind === "at"
  │     │     ├── ok/skipped → enabled = false, nextRunAtMs = undefined
  │     │     └── error → 瞬态重试 or 永久禁用
  │     │           ├── isTransientCronError() → backoff + nextRunAtMs = endedAt + backoff
  │     │           └── else → enabled = false, nextRunAtMs = undefined
  │     ├── if error && enabled → 指数退避
  │     │     ├── normalNext = computeJobNextRunAtMs()
  │     │     ├── backoffNext = endedAt + errorBackoffMs(consecutiveErrors)
  │     │     └── nextRunAtMs = max(normalNext, backoffNext)  // 取较晚者
  │     └── else (正常完成) → computeJobNextRunAtMs()
  │           ├── cron 类型:max(naturalNext, endedAt + MIN_REFIRE_GAP_MS)
  │           └── every/at 类型:naturalNext
  └── return shouldDelete

瞬态错误重试(#24355)的设计值得特别说明:

  • 仅对 kind: "at" 的一次性任务生效。
  • 通过 TRANSIENT_PATTERNS 正则匹配错误文本,识别 rate_limit、529、network、timeout、5xx 五类。
  • 最多重试 maxAttempts(默认 3)次,退避使用独立的 retry.backoffMs 配置。
  • 超过重试次数后禁用任务但不删除,保留错误状态供用户查看。
2.3.3 隔离执行流程(runCronIsolatedAgentTurn)
runCronIsolatedAgentTurn(params)
  ├── prepareCronRunContext()
  │     ├── resolveDefaultAgentId / normalizeAgentId
  │     ├── buildCronAgentDefaultsConfig() — 合并 agent override
  │     ├── resolveCronAgentSessionKey() — 会话键构建
  │     ├── resolveHookExternalContentSource() — 外部内容源识别
  │     ├── ensureAgentWorkspace() — 工作目录准备
  │     ├── resolveCronSession() — 新建/复用 session
  │     │     ├── loadSessionStore()
  │     │     ├── evaluateSessionFreshness() — reset policy 检查
  │     │     └── isNewSession → crypto.randomUUID()
  │     ├── resolveCronModelSelection() — 模型选择链
  │     │     ├── payload.model → agent override → subagent config → session → default
  │     │     └── validate against modelCatalog
  │     ├── resolveCronDeliveryContext() — 交付上下文
  │     │     ├── resolveCronDeliveryPlan()
  │     │     └── resolveDeliveryTarget() — channel/to/accountId 解析
  │     ├── resolveCronSkillsSnapshot() — Skill 快照
  │     ├── buildSafeExternalPrompt() (if external hook)
  │     ├── appendCronDeliveryInstruction()
  │     ├── resolveSessionAuthProfileOverride()
  │     └── persistSessionEntry() — pre-run 持久化
  ├── executeCronRun()
  │     ├── createCronPromptExecutor() — 构建 Agent 运行器
  │     ├── runWithModelFallback() — 带 fallback 的模型执行
  │     │     ├── primary model → if error → fallbacks[0] → fallbacks[1] → ...
  │     │     └── return {runResult, fallbackModel, fallbackProvider}
  │     └── return CronExecutionResult
  └── finalizeCronRun()
        ├── 提取 telemetry (model, provider, usage)
        ├── 更新 session entry (inputTokens, outputTokens, totalTokens, estimatedCostUsd)
        ├── resolveCronPayloadOutcome() — 从 payloads 提取 summary/outputText
        ├── dispatchCronDelivery() — 交付调度
        │     ├── announce: deliverOutboundPayloads()
        │     ├── webhook: HTTP POST
        │     ├── 心跳-only 检测 → skip
        │     ├── 消息工具匹配 → skip duplicate
        │     └── 子代理后续等待
        └── return RunCronAgentTurnResult

2.4 数据流

2.4.1 任务创建数据流
用户输入 (raw JSON)
  → normalizeCronJobInput() [normalize.ts]
      → unwrapJob() — 解包 data/job 嵌套
      → coerceSchedule() — 规范化 schedule
      → coercePayload() — 规范化 payload (kind 推断 + 字段清理)
      → coerceDelivery() — 规范化 delivery
      → normalizeSessionTarget() — 验证 sessionTarget
      → normalizeWakeMode() — 验证 wakeMode
      → inferTopLevelPayload() — 从顶层字段推断 payload
      → stripLegacyTopLevelFields() — 清理遗留顶层字段
      → applyDefaults() — 填充默认值
  → normalizeCronJobCreate() [normalize.ts]
  → createJob() [service/jobs.ts]
      → crypto.randomUUID() — 生成 ID
      → resolveEveryAnchorMs() — 计算 every 锚点
      → resolveInitialCronDelivery() — 计算初始交付配置
      → assertSupportedJobSpec() — 验证 sessionTarget ↔ payload 兼容性
      → assertMainSessionAgentId() — main 目标只能用默认 agent
      → assertDeliverySupport() — 验证 delivery 配置
      → computeJobNextRunAtMs() — 计算首次运行时间
  → push to store.jobs
  → recomputeNextRuns()
  → persist()
  → armTimer()
  → emit("added")
2.4.2 执行-交付数据流
Timer Tick
  → collectRunnableJobs() — 找到到期任务
  → executeJobCoreWithTimeout() — 带超时执行
      → executeJobCore()
          ├── [Main] enqueueSystemEvent() + requestHeartbeatNow()
          │     → 主 Agent 在下一次心跳中消费事件
          │     → 可能产生回复 → 可选交付
          └── [Isolated] runIsolatedAgentJob()
                → prepareCronRunContext()
                → executeCronRun() — Agent 推理
                → finalizeCronRun()
                    → resolveCronPayloadOutcome() — 提取输出
                    → dispatchCronDelivery() — 投递
  → applyJobResult() — 更新任务状态
  → appendCronRunLog() — 记录运行日志
  → recomputeNextRunsForMaintenance()
  → persist()
  → armTimer()
2.4.3 持久化数据流
内存 state.store (CronStoreFile)
  → persist() [service/store.ts]
      → ensureLoaded(forceReload) — 重新从磁盘加载(防止外部修改丢失)
      → JSON.stringify(store)
      → 对比 serializedStoreCache — 跳过不变写入
      → shouldSkipCronBackupForRuntimeOnlyChanges() — 仅状态变化跳过备份
      → 写入临时文件 (pid + randomBytes + .tmp)
      → 复制 .bak 备份
      → renameWithRetry() — 原子重命名(3 次重试,处理 EBUSY/EPERM/EEXIST)
      → chmod 0o600
      → 更新 serializedStoreCache

📊 附图 2-1:模块整体结构图

图描述

  • 布局:从左到右三个大列,分别为"入口层"、“核心层”、“执行层”

  • 入口列

    • 节点 “CronServiceContract”(圆角矩形),列出方法:start/stop/status/list/add/update/remove/run/wake
    • 节点 “Normalize”(矩形),包含 normalizeCronJobInput / coerceSchedule / coercePayload / coerceDelivery
  • 核心列(最大区域):

    • 节点 “Service State”(圆柱),字段:store/timer/running/op
    • 节点 “Timer Loop”(矩形),内含流程:armTimer → onTimer → collectRunnable → execute → applyResult → armTimer
    • 节点 “Store”(矩形),内含:ensureLoaded / persist / loadCronStore / saveCronStore
    • 节点 “Locked”(锁图标),标注:per-storePath Promise 链串行化
    • 节点 “Schedule”(矩形),内含:computeNextRunAtMs / computePreviousRunAtMs / croner cache
    • 节点 “Stagger”(矩形),内含:resolveStableCronOffsetMs / SHA-256 hash
    • 节点 “Jobs”(矩形),内含:createJob / applyJobPatch / recomputeNextRuns / errorBackoffMs
  • 执行列

    • 分叉为两个子区域,上方标注 “Main Session Path”,下方标注 “Isolated Agent Path”
    • Main Session 子区域:enqueueSystemEvent → requestHeartbeatNow / runHeartbeatOnce
    • Isolated Agent 子区域(大矩形),内部节点:
      • “Session”:resolveCronSession / evaluateSessionFreshness
      • “Model Selection”:payload → agent → subagent → default
      • “Skill Snapshot”:version check → build
      • “Auth Profile”:resolveSessionAuthProfileOverride
      • “Run Executor”:createCronPromptExecutor → runWithModelFallback
      • “Delivery Dispatch”:announce / webhook / heartbeat-skip / messaging-tool-match
      • “Subagent Followup”:waitForDescendantSubagentSummary
  • 底部行

    • 节点 “Run Log”(文档),标注 runs/{jobId}.jsonl
    • 节点 “Session Reaper”(扫帚图标),标注 24h retention / 5min throttle
    • 节点 “Active Jobs”(全局单例),标注 Set
    • 节点 “Task Ledger”(矩形),标注 createRunningTaskRun / completeTaskRun
    • Contract → State(调用)
    • Contract → Normalize(输入预处理)
    • Contract → Ops(命令分发)
    • Ops → Locked → Store / Jobs
    • Timer Loop → Schedule(计算 nextRun)
    • Timer Loop → Stagger(计算偏移)
    • Timer Loop → Jobs(创建/更新任务)
    • Timer Loop → Main Session / Isolated Agent(执行分支)
    • Timer Loop → Run Log / Task Ledger / Active Jobs(记录)
    • Timer Loop → Session Reaper(finally 中调用)
    • Isolated Agent 内部节点间的数据流箭头

三、核心类型系统

📊 作业生命周期状态机

在这里插入图片描述

3.1 类型层次总览

Cron 模块的类型系统采用泛型基础 + 具体特化的设计模式,核心层次如下:

CronJobBase<TSchedule, TSessionTarget, TWakeMode, TPayload, TDelivery, TFailureAlert>
    │
    └── CronJob = CronJobBase<
            CronSchedule,           // 具体调度类型
            CronSessionTarget,      // 具体会话目标
            CronWakeMode,           // 具体唤醒模式
            CronPayload,            // 具体载荷
            CronDelivery,           // 具体交付配置
            CronFailureAlert | false // 具体告警配置
        > & { state: CronJobState }

CronJobBase 是一个纯泛型容器,定义了定时任务的不变量字段(id, name, enabled, createdAtMs 等),而将可变策略维度参数化。这种设计的优势:

  1. 测试隔离:测试可以使用简化的 mock 类型替代真实依赖。
  2. 渐进强化:未来新增调度类型或交付模式时,只需扩展联合类型,不需要修改 CronJobBase。
  3. 文档性:类型参数本身就是维度清单。

3.2 CronSchedule — 调度类型

export type CronSchedule =
  | { kind: "at"; at: string }                          // 一次性
  | { kind: "every"; everyMs: number; anchorMs?: number } // 固定间隔
  | { kind: "cron"; expr: string; tz?: string; staggerMs?: number } // Cron 表达式

设计分析

  • 判别联合(Discriminated Union)kind 字段作为判别标签,使 TypeScript 能够在 switch/if 窄化中正确推导各分支的字段。这是类型安全的核心保障。

  • at: string 而非 atMs: number:设计选择了 ISO 8601 字符串作为外部表示,内部通过 parseAbsoluteTimeMs() 转换。这既保持了人类可读性,又通过规范化(normalizeUtcIso)确保了不同时区表示的一致性。atMs 作为 legacy 字段被兼容处理。

  • anchorMs?: number:every 类型的锚点时间。当省略时,默认使用 createdAtMsnowMs()。锚点确保了"从某个时间点起每 N 毫秒"的精确语义,而不是"从上次运行起每 N 毫秒"。但在 computeJobNextRunAtMs 中,如果 lastRunAtMs 存在且 lastRunAtMs + everyMs > nowMs,会优先使用 lastRunAtMs + everyMs,这实际上实现了"从上次运行起"的语义——两种语义的融合通过优先级链实现。

  • staggerMs?: number:cron 类型的抖动窗口。当省略时,isRecurringTopOfHourCronExpr() 检测是否为整点表达式(如 0 * * * *),如果是则默认 DEFAULT_TOP_OF_HOUR_STAGGER_MS = 5min0 表示精确调度(无抖动)。实际偏移量通过 resolveStableCronOffsetMs() 计算——对 jobId 做 SHA-256 哈希,取前 4 字节取模 staggerMs,确保同一个 job 在每次调度中偏移量一致(确定性抖动)。

  • tz?: string:可选时区。省略时使用 Intl.DateTimeFormat().resolvedOptions().timeZone(系统本地时区)。通过 resolveCronTimezone() 规范化。

3.3 CronSessionTarget — 会话目标

export type CronSessionTarget = "main" | "isolated" | "current" | `session:${string}`;

语义解析

含义 必须的 payload kind 执行方式
"main" 注入主会话 systemEvent enqueueSystemEvent + 心跳唤醒
"isolated" 创建新隔离会话 agentTurn runIsolatedAgentJob
"current" 当前会话(创建时解析) agentTurn 解析为 session:${sessionKey}
session:${id} 特定会话绑定 agentTurn runIsolatedAgentJob (sessionKey=id)

约束验证assertSupportedJobSpec):

  • main + agentTurn → 抛错(main 只能是 systemEvent)
  • isolated/current/session:xxx + systemEvent → 抛错(隔离目标只能是 agentTurn)
  • session:xxx 的 id 部分经过 assertSafeCronSessionTargetId() 验证,禁止 /\\0(防止路径遍历)

"current" 的解析时机:在 normalizeCronJobInputapplyDefaults 阶段,如果 sessionContext.sessionKey 存在,"current" 被替换为 session:${sessionKey};否则降级为 "isolated"。这意味着持久化到 store 中时,"current" 已经不存在——它是一个语法糖,只在创建时有效。

3.4 CronPayload — 执行载荷

export type CronPayload = { kind: "systemEvent"; text: string } | CronAgentTurnPayload;

type CronAgentTurnPayload = {
  kind: "agentTurn";
  message: string;
  model?: string;
  fallbacks?: string[];
  thinking?: string;
  timeoutSeconds?: number;
  allowUnsafeExternalContent?: boolean;
  externalContentSource?: HookExternalContentSource;
  lightContext?: boolean;
  toolsAllow?: string[];
};

设计分析

  • 对称但不对称systemEvent 只有 text 字段,而 agentTurn 有 9 个可选字段。这反映了两种执行路径的本质差异——主会话路径是"注入一段文本让 Agent 自行处理",隔离路径是"为 Agent 配置完整的运行时参数"。

  • model + fallbacks:模型选择链的入口。model 是首选模型,fallbacks 是备选列表。执行时通过 runWithModelFallback() 依次尝试。这实现了声明式容错——用户不需要编写重试逻辑,只需列出备选模型。

  • thinking:思维级别(low/medium/high/xhigh),对应不同的推理深度和 token 消耗。xhigh 在不支持时会自动降级为 high。

  • timeoutSeconds:单次执行的显式超时覆盖。resolveCronJobTimeoutMs() 的优先级:payload.timeoutSeconds > 默认值(agentTurn=60min, systemEvent=10min)。

  • allowUnsafeExternalContent:安全开关。默认情况下,外部 webhook 内容会经过 buildSafeExternalPrompt() 包装,添加安全边界提示。设为 true 时跳过包装。

  • externalContentSource:不可变的外部内容来源标识,用于异步调度时的安全审计链。

  • lightContext:轻量启动上下文标志,跳过完整的 bootstrap 文件扫描。

  • toolsAllow:工具白名单。设置后,只有列表中的工具会发送给模型,实现权限最小化。

Payload Patch 类型的设计体现了局部更新语义:

export type CronPayloadPatch = 
  | { kind: "systemEvent"; text?: string } 
  | CronAgentTurnPayloadPatch;

type CronAgentTurnPayloadPatch = {
  kind: "agentTurn";
} & Partial<Omit<CronAgentTurnPayloadFields, "toolsAllow">> & {
    toolsAllow?: string[] | null;  // null 表示"清除 toolsAllow"
  };

toolsAllow 的特殊处理:string[] 表示设置新白名单,null 表示删除已有白名单,undefined 表示不修改。这三种语义通过联合类型精确表达。

3.5 CronDelivery — 交付配置

export type CronDeliveryMode = "none" | "announce" | "webhook";

export type CronDelivery = {
  mode: CronDeliveryMode;
  channel?: CronMessageChannel;       // 消息通道 ID(telegram/discord/feishu/last/...)
  to?: string;                         // 目标地址(频道 ID/用户 ID/webhook URL)
  threadId?: string | number;          // 线程/话题 ID
  accountId?: string;                  // 多账号场景的账户 ID
  bestEffort?: boolean;                // 失败不告警
  failureDestination?: CronFailureDestination;  // 独立失败通知目标
};

export type CronFailureDestination = {
  channel?: CronMessageChannel;
  to?: string;
  accountId?: string;
  mode?: "announce" | "webhook";
};

交付计划解析resolveCronDeliveryPlan):

  1. 如果 delivery 对象存在:

    • mode 已指定 → 使用指定值("deliver" 被映射为 "announce"
    • mode 未指定 → 默认 "announce"
    • channel 未指定 → 默认 "last"(最近使用的通道)
  2. 如果 delivery 对象不存在:

    • sessionTarget 为隔离类型 + payload.kind === "agentTurn" → 隐式 "announce"
    • 其他 → "none"

failureDestination 的独立路由:允许失败通知发往与正常输出不同的目标。例如:正常输出发到 Telegram 群组,失败通知发到管理员私聊。isSameDeliveryTarget() 检测两者是否相同——如果相同则不重复发送。

3.6 CronJobState — 运行时状态

export type CronJobState = {
  nextRunAtMs?: number;           // 下次运行时间(ms epoch)
  runningAtMs?: number;           // 当前运行开始时间(有值表示正在运行)
  lastRunAtMs?: number;           // 上次运行开始时间
  lastRunStatus?: CronRunStatus;  // 上次运行结果 ("ok" | "error" | "skipped")
  lastStatus?: "ok" | "error" | "skipped";  // 兼容别名
  lastError?: string;             // 上次错误信息
  lastErrorReason?: FailoverReason;         // 错误分类
  lastDurationMs?: number;        // 上次运行耗时
  consecutiveErrors?: number;     // 连续错误次数(成功归零)
  lastFailureAlertAtMs?: number;  // 上次失败告警时间(冷却期用)
  scheduleErrorCount?: number;    // 调度计算连续错误次数(≥3 自动禁用)
  lastDeliveryStatus?: CronDeliveryStatus;  // 交付结果
  lastDeliveryError?: string;     // 交付错误信息
  lastDelivered?: boolean;        // 上次是否成功交付
};

设计分析

  • 全部可选字段CronJobState 的每个字段都是 ?: 可选的。这是因为新创建的任务没有任何历史状态,且 store 中可能存在遗留任务缺少新字段。这种设计避免了迁移脚本的必要性——缺失字段等价于"未知/无历史"。

  • 双重状态别名lastStatuslastRunStatus 的兼容别名。在 applyJobResult 中两者被同步设置。这种设计允许外部消费者使用旧字段名,同时内部代码逐步迁移到新字段名。

  • 双维度状态追踪lastRunStatus 追踪执行结果,lastDeliveryStatus 追踪交付结果。两者独立——执行成功但交付失败是可能的(status: "ok" + deliveryStatus: "not-delivered")。

  • runningAtMs 作为互斥锁:当 runningAtMs 有值时,collectRunnableJobs 会跳过该任务。这是一种乐观锁——不在执行前持久化互斥状态,而是在 locked 段内设置标记后立即 persist。卡死检测(STUCK_RUN_MS = 2h)确保标记最终被清除。

  • scheduleErrorCount:独立于 consecutiveErrors,专门追踪调度表达式解析错误。连续 3 次后自动禁用任务并发送系统事件通知用户。这避免了损坏的 cron 表达式导致无限重试循环。

3.7 CronJob — 完整任务实体

export type CronJob = CronJobBase<
  CronSchedule,
  CronSessionTarget,
  CronWakeMode,
  CronPayload,
  CronDelivery,
  CronFailureAlert | false
> & { state: CronJobState };

// 展开后等价于:
type CronJob = {
  id: string;
  agentId?: string;
  sessionKey?: string;
  name: string;
  description?: string;
  enabled: boolean;
  deleteAfterRun?: boolean;
  createdAtMs: number;
  updatedAtMs: number;
  schedule: CronSchedule;
  sessionTarget: CronSessionTarget;
  wakeMode: CronWakeMode;
  payload: CronPayload;
  delivery?: CronDelivery;
  failureAlert?: CronFailureAlert | false;
  state: CronJobState;
};

字段语义详解

  • agentId:Agent 标识。省略时使用 defaultAgentId。main sessionTarget 只能使用默认 agent(assertMainSessionAgentId),因为主会话是 per-agent 单例。

  • sessionKey:会话路由键。用于确定任务应该绑定到哪个会话。对于 main sessionTarget,它决定注入哪个主会话;对于 isolated,它作为基础会话键(实际运行会话键为 ${agentSessionKey}:run:${sessionId})。

  • deleteAfterRun:执行成功后自动删除。默认对 kind: "at" 任务为 true,对其他类型为 undefined(不删除)。

  • failureAlert:失败告警配置。false 表示显式禁用。CronFailureAlert 类型包含 after(连续错误阈值,默认 2)、cooldownMs(冷却期,默认 1h)、channel、to、mode、accountId。告警逻辑在 applyJobResult 中触发,需要同时满足:consecutiveErrors ≥ after、不在冷却期内、不是 bestEffort。

3.8 辅助类型

CronRunOutcome
export type CronRunOutcome = {
  status: CronRunStatus;    // "ok" | "error" | "skipped"
  error?: string;
  errorKind?: "delivery-target";  // 错误分类
  summary?: string;
  sessionId?: string;
  sessionKey?: string;
};

"skipped" 的语义:不是"跳过不执行",而是"执行了但结果为空/不适用"。典型场景:

  • main sessionTarget 的 systemEvent text 为空
  • isolated sessionTarget 的 payload.kind 不是 agentTurn
  • 心跳返回 skipped(主会话正忙)
CronRunTelemetry
export type CronRunTelemetry = {
  model?: string;
  provider?: string;
  usage?: CronUsageSummary;  // input/output/total/cache_read/cache_write tokens
};

遥测数据从 Agent 运行结果中提取,随 CronEvent 广播,并记录到运行日志。

CronEvent
export type CronEvent = {
  jobId: string;
  action: "added" | "updated" | "removed" | "started" | "finished";
  runAtMs?: number;
  durationMs?: number;
  status?: CronRunStatus;
  error?: string;
  summary?: string;
  delivered?: boolean;
  deliveryStatus?: CronDeliveryStatus;
  deliveryError?: string;
  sessionId?: string;
  sessionKey?: string;
  nextRunAtMs?: number;
} & CronRunTelemetry;

这是模块对外的事件通知格式,通过 deps.onEvent 回调发送。消费者(如 Gateway)可以据此更新 UI、发送通知、记录审计日志。

CronStoreFile
export type CronStoreFile = {
  version: 1;
  jobs: CronJob[];
};

文件格式版本 1。注意 stateupdatedAtMs 字段在备份比较时被排除(stripRuntimeOnlyCronFields),因为它们是纯运行时数据,变化不应触发备份写入。

3.9 类型间关系图

                    CronJobBase<TSchedule, TSessionTarget, TWakeMode, TPayload, TDelivery, TFailureAlert>
                                        │
                    ┌───────────────────┼───────────────────┐
                    │                   │                   │
              CronSchedule        CronPayload          CronDelivery
              ┌────┴────┐       ┌────┴────┐          ┌────┴────┐
              │    │    │       │         │          │         │
             at every cron  systemEvent agentTurn  announce  webhook
                              │         │
                              text    message
                                      ├─ model
                                      ├─ fallbacks
                                      ├─ thinking
                                      ├─ timeoutSeconds
                                      ├─ allowUnsafeExternalContent
                                      ├─ externalContentSource
                                      ├─ lightContext
                                      └─ toolsAllow
                                                    │
                                             CronFailureDestination
                                             ├─ channel
                                             ├─ to
                                             ├─ accountId
                                             └─ mode

CronJob = CronJobBase<...concrete...> & { state: CronJobState }
                                        │
                                   CronJobState
                                   ├─ nextRunAtMs
                                   ├─ runningAtMs
                                   ├─ lastRunAtMs
                                   ├─ lastRunStatus ◄── CronRunStatus
                                   ├─ lastError
                                   ├─ lastErrorReason ◄── FailoverReason
                                   ├─ consecutiveErrors
                                   ├─ scheduleErrorCount
                                   ├─ lastDeliveryStatus ◄── CronDeliveryStatus
                                   ├─ lastDeliveryError
                                   └─ lastDelivered

CronJobCreate = Omit<CronJob, "id" | "createdAtMs" | "updatedAtMs" | "state"> & { state?: Partial<CronJobState> }
CronJobPatch  = Partial<Omit<CronJob, "id" | "createdAtMs" | "state" | "payload">> & { payload?: CronPayloadPatch; delivery?: CronDeliveryPatch; state?: Partial<CronJobState> }

Create vs Patch 的类型设计

  • CronJobCreate:排除自动生成字段(id, createdAtMs, updatedAtMs)和自动初始化字段(state),允许部分 state 覆盖。
  • CronJobPatch:所有字段可选,payload 和 delivery 使用专门的 Patch 类型实现深度部分更新。

Payload Patch 的合并语义mergeCronPayload):

  1. 如果 kind 相同 → 逐字段合并(patch 中的非 undefined 字段覆盖 existing)
  2. 如果 kind 不同 → 从 patch 构建全新 payload(buildPayloadFromPatch
  3. toolsAllow: null → 删除已有白名单
  4. toolsAllow: undefined → 不修改

这种设计实现了声明式更新——用户只需要发送变更的部分,不需要先读取完整对象再修改。

3.10 设计模式总结

  1. 判别联合(Discriminated Union):CronSchedule、CronPayload、CronDeliveryMode 都使用 kind/mode 作为判别标签,实现类型安全的分支处理。

  2. 泛型基础 + 具体特化CronJobBase 参数化策略维度,CronJob 填入具体类型。允许测试和未来扩展使用不同的类型组合。

  3. 全可选状态CronJobState 的所有字段可选,实现零迁移成本的向后兼容。新字段自动获得 undefined 语义。

  4. 双维度追踪:执行状态(lastRunStatus)与交付状态(lastDeliveryStatus)独立追踪,解耦了"Agent 运行结果"和"消息投递结果"两个关注点。

  5. 语法糖即时解析sessionTarget: "current" 在输入规范化阶段就被替换为 session:${sessionKey},持久化时不存在歧义值。

  6. Patch 类型精确表达三态语义undefined(不修改)vs 具体值(设置)vs null(删除),通过联合类型在类型系统层面区分。

  7. 确定性抖动:stagger 偏移通过 jobId 的 SHA-256 哈希计算,确保同一任务在每次调度中偏移量一致,避免调度跳跃。

  8. 乐观锁 + 超时安全网runningAtMs 标记 + STUCK_RUN_MS 清理,兼顾了并发安全和卡死恢复。

  9. 事件驱动:所有状态变更通过 CronEvent 广播,消费者(Gateway/UI/审计系统)通过 onEvent 回调接收,实现发布-订阅解耦。

  10. 依赖注入CronServiceDeps 定义了 15 个外部依赖接口,nowMs 可注入用于测试,整体设计面向接口而非实现。


📊 附图 3-1:核心类型关系图

图描述

  • 中心节点:CronJob(大圆角矩形),内部分区显示所有字段

  • CronJob 内部分区

    • 左上区"Identity":id, agentId, sessionKey, name, description
    • 右上区"Schedule":schedule (→ CronSchedule), enabled, deleteAfterRun
    • 左中区"Execution":sessionTarget (→ CronSessionTarget), wakeMode (→ CronWakeMode), payload (→ CronPayload)
    • 右中区"Delivery":delivery (→ CronDelivery), failureAlert (→ CronFailureAlert | false)
    • 底区"Meta":createdAtMs, updatedAtMs, state (→ CronJobState)
  • 从 CronJob 向外辐射的连接

    • schedule → CronSchedule(展开为三个子类型节点:at/every/cron,各自显示专属字段)
    • sessionTarget → CronSessionTarget(展开为四个值:main/isolated/current/session:xxx)
    • wakeMode → CronWakeMode(两个值:now/next-heartbeat)
    • payload → CronPayload(分叉为 systemEvent 和 agentTurn,agentTurn 展开所有可选字段)
    • delivery → CronDelivery(展开 mode/channel/to/threadId/accountId/bestEffort/failureDestination)
    • failureDestination → CronFailureDestination
    • state → CronJobState(展开所有字段,标注类型和语义)
  • 辅助类型节点(右侧纵向排列):

    • CronRunOutcome → status/error/errorKind/summary/sessionId/sessionKey
    • CronRunTelemetry → model/provider/usage
    • CronUsageSummary → input_tokens/output_tokens/total_tokens/cache_read/cache_write
    • CronEvent → 合并 CronRunOutcome + CronRunTelemetry + action/jobId/nextRunAtMs
    • CronStoreFile → version/jobs[]
  • 变异类型节点(左下角):

    • CronJobCreate = Omit<CronJob, id|createdAtMs|updatedAtMs|state>
    • CronJobPatch = Partial + CronPayloadPatch + CronDeliveryPatch
    • CronPayloadPatch → kind + text? / kind + message? + Partial
    • CronDeliveryPatch → Partial
  • 边标注

    • CronSchedule 的 kind 字段标注"判别标签"
    • CronPayload 的 kind 字段标注"判别标签"
    • CronJobState 所有字段标注"?"
    • toolsAllow 字段标注"null = delete, undefined = no-op"
    • CronJobBase 标注"泛型基础<TSchedule, TSessionTarget, …>"

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐