ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ClawX 定时任务摘要截断修复:从运行转录恢复完整的 Cron 任务回复

ClawX 定时任务摘要截断修复:从运行转录恢复完整的 Cron 任务回复 人工智能AI 应用桌面应用交互助手【免费下载链接】ClawXClawX is a desktop app that provides a graphical interface for OpenClaw AI agents. It turns CLI-based AI orchestration into a desktop experience without using the terminal. China website is https://clawx.com.cn.项目地址https://gitcode.com/gh_mirrors/cl/ClawX点击查看免费下载导读ClawX 将 OpenClaw 的 CLI 型定时任务Cron管理封装为桌面图形界面。OpenClaw 在cron.runs中持久化的运行摘要默认受2000 字符上限约束超长回复会被截断并以省略号…结尾导致用户打开已完成定时任务会话时只能看到残缺的回复。本文基于仓库中的任务规格 fix-cron-job-summary-truncation.md 及其关联规则完整讲解 ClawX 如何在 Electron Main 进程中把有界摘要与其对应的磁盘运行转录run transcript拼接恢复完整的最终助手回复读完你将掌握截断检测算法、会话键推导规则、前缀匹配约束、降级路径以及对应的单元/E2E 验证方式。问题背景OpenClaw 的有界运行摘要OpenClaw 为每个 Cron 运行生成一条摘要summary并持久化在cron.runs中。出于存储与控制考虑该摘要有明确的长度边界。从源码常量可以确认这条边界的具体数值// electron/services/cron-api.ts const OPENCLAW_CRON_SUMMARY_TRUNCATION_MIN_CHARS 2_000;当最终助手回复超过该边界时OpenClaw 的摘要会以省略号收尾形成所谓的bounded summary envelope。任务规格明确指出的用户可见症状是打开一个已完成定时任务的会话看到的不是完整的最终助手回复而是一条 2000 字符、以省略号结尾的 Cron 摘要用户需要看到完整回复同时希望运行时长Duration和模型Model元数据在恢复后的回复末尾依然可见当运行转录缺失或不可读时界面必须继续回退到有界摘要而不是报错或显示空内容。修复架构Main 持有 Cron 历史ACP 回放保持权威本次修复的定位是runtime-bridge类型的运行时桥接任务归属gateway-backend-communication场景。其架构约束在任务规格的 acceptance 与规则文档中写得很清楚可以归纳为三条主线Electron Main 是定时任务历史的唯一持有者。Renderer前端不直接读取运行日志而是通过类型化 Host APIcron.sessionHistory向 Main 请求Main 则继续通过 Gateway WebSocket 查询cron.runsRPC。ACP 回放始终是权威。恢复出来的 Cron 历史只在 ACP 回放为空时才被投影project到渲染层的时间线上绝不能替换或与已有的非空 ACP 回放重复。规则原文见 acp-chat-state-and-history.mdWhen ACP replay for a cron session is completely empty, scheduled-task prompt and completion summaries may instead come from Mains typed cron-history host API.以及must never replace or duplicate non-empty ACP replay。摘要替换必须满足更长的相同前缀条件否则保持摘要原样。这三条约束共同保证了修复只作用于有界摘要 可用转录这一种情况不会引入第二条历史事实源。核心算法截断检测与转录恢复1. 截断摘要判定isBoundedCronSummary是恢复流程的入口判定函数cron-api.tsfunction isBoundedCronSummary(summary: string): boolean { return summary.length OPENCLAW_CRON_SUMMARY_TRUNCATION_MIN_CHARS summary.endsWith(…); }只有同时满足长度达到 2000 字符阈值且以省略号结尾两条条件该摘要才被视为 OpenClaw 有界截断的产物。短摘要不会被触碰——它们要么是完整回复要么本来就不需要恢复。2. 定位对应运行转录每一条cron.runs条目都可能携带sessionId或sessionKey恢复流程先解析 Cron 会话键再推导出本次运行专属的转录会话键// 解析 agent:agentId:cron:jobId 与 agent:agentId:cron:jobId:run:runSessionId function parseCronSessionKey(sessionKey: string): CronSessionKeyParts | null { if (!sessionKey.startsWith(agent:)) return null; const parts sessionKey.split(:); if (parts.length 4 || parts[2] ! cron) return null; const agentId parts[1] || main; const jobId parts[3]; if (!jobId) return null; if (parts.length 4) return { agentId, jobId }; if (parts.length 6 parts[4] run parts[5]) { return { agentId, jobId, runSessionId: parts[5] }; } return null; }键格式有两种形态形态示例含义基础会话键agent:main:cron:job-1定时任务的基础会话对应会话列表中的一行运行级会话键agent:main:cron:job-1:run:run-session-1某次具体运行的转录会话resolveCronRunSessionKeycron-api.ts遵循任务规格的 acceptance 要求——Run lookup accepts an explicit run-scoped sessionKey and can derive one from agent ID, job ID, and sessionId优先使用条目自带的、且能解析出runSessionId的显式sessionKey否则用sessionId拼出agent:agentId:cron:jobId:run:sessionId。3. 读取转录并提取最终助手回复loadFullCronRunReplies对每条有界摘要并行执行恢复cron-api.tsasync function loadFullCronRunReplies( parsed: CronSessionKeyParts, runs: CronRunLogEntry[], ): PromiseMapCronRunLogEntry, string { const replies new MapCronRunLogEntry, string(); await Promise.all(runs.map(async (entry) { const summary typeof entry.summary string ? entry.summary.trim() : ; if (!isBoundedCronSummary(summary)) return; const runSessionKey resolveCronRunSessionKey(parsed, entry); if (!runSessionKey) return; const transcript await loadSessionTranscriptByKey(runSessionKey, 1_000); if (!transcript?.length) return; const fullReply getFinalAssistantReply(transcript); const summaryPrefix summary.slice(0, -1); if (fullReply.length summaryPrefix.length fullReply.startsWith(summaryPrefix)) { replies.set(entry, fullReply); } })); return replies; }其中loadSessionTranscriptByKey来自 sessions-api.ts它按会话键解析出agents/agentId/sessions/下的转录文件通过sessions.json索引解析路径并只读取最新的 1000 条消息记录export async function loadSessionTranscriptByKey(sessionKey: string, limit: number): PromiseRawMessage[] | null { const parsed parseSessionKey(sessionKey); if (!parsed) return null; try { const sessionsDir join(resolveOpenClawStateDir(), agents, parsed.agentId, sessions); const sessionsJson await readSessionsJson(parsed.agentId); const transcriptPath resolveSessionTranscriptPathByKey(sessionKey, sessionsDir, sessionsJson); if (!transcriptPath) return null; return readRecentTranscriptMessages(transcriptPath, limit); } catch { return null; } }4. 前缀匹配约束防止错误拼接恢复并非无条件进行必须同时满足两个条件与任务规格 acceptance 完全一致replaced only when the corresponding run transcript contains a longer assistant reply with the same prefix更长fullReply.length summaryPrefix.length共享完整前缀fullReply.startsWith(summaryPrefix)其中summaryPrefix summary.slice(0, -1)即摘要去掉末尾省略号后的文本。getFinalAssistantReply会从转录末尾向前扫描跳过非 assistant 消息、跳过空文本取出最后一条非空的 assistant 回复支持字符串与结构化文本块两种RawMessage.content形态。前缀匹配的设计意图是截断是掐尾不掐头的转录中的完整回复必然以摘要内容为前缀前缀不一致即视为转录与本次运行不对应宁可保留截断摘要也不冒险拼接错误内容。数据来源与降级cron.runs 为主旧 JSONL 为辅恢复流程的数据锚点是 Gateway 的cron.runsRPC。readCronRunHistorycron-api.ts以 8 秒超时调用const result await gatewayManager.rpc{ entries?: CronRunLogEntry[] }(cron.runs, { id: jobId, limit, sortDir: asc, }, 8000);sortDir: asc保证运行记录按时间正序返回与时间线呈现顺序一致。对于尚未升级到 SQLite 型 Cron 历史的旧 OpenClaw 版本调用会失败并自动降级到 Main 持有的遗留文件回退——readLegacyCronRunLog读取cron/runs/jobId.jsonlcron-api.ts逐行解析 JSON只接受jobId匹配且action为finished或缺失的条目。这一降级路径正是场景文档 gateway-backend-communication.md 所规定的direct run-log file reads are allowed only as a compatibility fallback for older file-backed runtimes。在sessionHistory服务中Main 会并行发起三件事cron-api.tscron.list含禁用任务——用于匹配当前 job 的 name / payload / statereadCronRunHistory——获取运行记录readSessionStoreEntry——读取agents/agentId/sessions/sessions.json中的会话标签与更新时间。随后调用loadFullCronRunReplies做转录恢复再由buildCronSessionFallbackMessages组装消息序列。消息组装与元数据保留buildCronRunMessagecron-api.ts负责把恢复后的完整回复写回消息内容并保证任务规格要求的元数据仍然可见const meta: string[] []; const duration formatDuration(entry.durationMs); if (duration) meta.push(Duration: ${duration}); if (entry.provider entry.model) meta.push(Model: ${entry.provider}/${entry.model}); else if (entry.model) meta.push(Model: ${entry.model}); if (meta.length 0) content ${content}\n\n${meta.join( | )};要点Duration 格式化小于 1 秒显示毫秒如500ms110 秒保留一位小数如5.0s更长则取整秒模型元数据优先输出provider/model只有 provider 缺失时才只显示Model: model错误标记status error的条目会被加Run failed:前缀并标记isError: true恢复流程不会触碰这类内容时间戳归一化normalizeTimestampMs兼容毫秒/秒两种单位值小于1e12视为秒自动 ×1000也兼容 ISO 字符串。组装出的会话结构为一条携带任务提示词prompt或任务名的user消息 逐条assistant运行消息。若匹配到运行记录但任务仍在执行中无转录可用会插入一条任务仍在运行的占位 assistant 消息。最终按limit默认 200最大 200截取尾部消息。边界行为与降级清单综合任务规格的 acceptance、规则文档与单元测试恢复逻辑在以下情况下一律保持摘要原样场景行为依据摘要长度不足 2000 或不以省略号结尾不视为有界摘要不恢复isBoundedCronSummary判定转录缺失 / 不可读 / 为空保留有界摘要loadSessionTranscriptByKey返回null时跳过转录中的最终回复与摘要前缀不一致保留有界摘要前缀匹配失败转录回复不更长等于或短于前缀保留有界摘要fullReply.length summaryPrefix.length不成立无法解析出运行级会话键保留有界摘要resolveCronRunSessionKey返回null这些边界在 cron-schedule.test.ts 中被逐一固化。测试验证单元测试与 E2E 双轨单元测试前缀匹配的三种情形test 文件 通过 mockloadSessionTranscriptByKey精确验证恢复路径SQLite 摘要直读summary: Time to drink water.短摘要不会触发转录读取loadSessionTranscriptByKey不应被调用有界摘要恢复成功构造summaryPrefix A.repeat(2000)、fullReply prefix B.repeat(500)、summary prefix …断言转录以agent:main:cron:job-1:run:run-session-1与会话键、1000条上限被读取最终 assistant 消息内容为fullReply \n\nDuration: 5.0s | Model: provider-a/model-a—— 同时验证了完整回复恢复与元数据拼接前缀不匹配保持原样转录回复以X.repeat(2000)开头与摘要前缀不符最终消息内容仍为原始A.repeat(2000) …。第一条测试还断言cron.runs的调用参数为{ id: job-1, limit: 200, sortDir: asc }且超时为8000锁定了 Gateway 查询契约。E2E 测试空 ACP 回放时的历史投影cron-run-live-status.spec.ts 中的shows cron run summaries when ACP replay is empty用例完整模拟了用户路径侧边栏出现Cron: 喝水提醒会话打开会话后Main 被调用cron.sessionHistorypayload 中sessionKey为 Cron 基础会话键界面上同时渲染出用户提示提醒我喝水、完整回复的开头该喝水了与结尾完整回复结尾——证明不再是截断摘要acp-chat-empty-state数量为 0即空状态被历史内容替代。同文件的另一个用例验证了 Cron 运行中的实时状态tool_call / tool_call_update无需切换会话即可通过 ACP 事件呈现与本次历史修复形成互补运行中的任务走 ACP 实时事件已完成任务走 Main 的 Cron 历史桥接。权威边界与工程约束总结最后回到任务规格 fix-cron-job-summary-truncation.md 开篇给出的定位这是一个Cron history fallback repair——把有界的cron.runs摘要与磁盘上对应的运行转录对接起来。整个修复必须遵守以下不可逾越的边界均有仓库证据ACP 回放优先Cron 历史只在回放为空时投影见 acp-chat-state-and-history.md 中的 cron 例外条款Main 独占Renderer 不直接读日志文件、不建立第二条历史源所有 Cron 历史都经类型化 Host API 由 Main 提供见 gateway-backend-communication.md恢复是有条件的只有更长 完整前缀共享才允许替换任何缺失、不匹配、不可读都保持原样生成作用域与内存态投影出的历史是 generation-scoped 的进程内存数据不持久化不污染 ACP 语义。这套设计既解决了打开已完成定时任务会话只能看到省略号摘要的用户体验问题又守住了ACP 回放是 Chat 语义唯一权威的架构底线是典型的有界补丁式运行时桥接实现值得作为参考模式。赞分享人工智能AI 应用桌面应用交互助手【免费下载链接】ClawXClawX is a desktop app that provides a graphical interface for OpenClaw AI agents. It turns CLI-based AI orchestration into a desktop experience without using the terminal. China website is https://clawx.com.cn.项目地址https://gitcode.com/gh_mirrors/cl/ClawX点击查看免费下载相关推荐Quartz.NET任务中断与恢复终极指南如何优雅处理长时间运行任务Quartz.NET任务中断与恢复终极指南如何优雅处理长时间运行任务 在现代企业应用中长时间运行的任务调度管理是每个开发人员都需要面对的挑战。Quartz.任务调度后端PrivateBin备份自动化从cron任务到灾难恢复的完整指南PrivateBin备份自动化从cron任务到灾难恢复的完整指南 引言你还在手动备份PrivateBin数据吗 作为一款采用AES 256加密的开源pas后端密码学应用安全WePush定时任务完全指南从基础配置到高级Cron表达式实战WePush定时任务完全指南从基础配置到高级Cron表达式实战 WePush是一款专注于批量推送的小而美工具支持多种消息类型的定时推送功能。作为一款强大的批开发工具上一篇【限时体验】 【保姆级超详细vnpy 新手指导】下一篇【限时免费】 【保姆级超详细还免费星火应用商店Spark-Store 新手指导】创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表