DIFF-506 深度解析:MooTool 代码运行 `runtime.options` 长度规范化设计与实现
桌面应用开发工具【免费下载链接】MooToolHandy tool set for developers项目地址https://gitcode.com/gh_mirrors/mo/MooTool点击查看免费下载本文基于 next-compose 项目 DIFF-506代码运行runtime.options长度规范化变更记录展开剖析其背景成因、与 ElectronnormalizeSettings的对齐逻辑、Compose 侧CodeRunRuntimeOptionsNormalize实现、会话持久化集成点与验证方式帮助读者理解跨产品Electron / Compose运行时草稿与运行参数字段长度一致性保障的完整链路。背景为什么运行参数字段需要长度规范化在 MooTool 的跨产品架构中Electron 版与 Compose 版next-compose共享同一套产品语义其中「代码运行」工具F05在 Electron 端的设置存储mootool-next.json中保存了四语言Java / Groovy / Python / Node的运行草稿与运行选项。Electron 端normalizeSettings对以下字段执行截断规范化见 settings.ts字段Electron 截断上限说明runtime.drafts.{java, groovy, python, node}1 MiB1024 * 1024四语言源码草稿runtime.options.*.arguments2000 字符运行参数argumentsruntime.options.*.workingDirectory1000 字符工作目录具体实现位于 settings.ts 的normalizeRuntimeDrafts与normalizeRuntimeOptionsfunction normalizeRuntimeDrafts(value: RecordRuntimeSettingsId, string | undefined): RecordRuntimeSettingsId, string { return Object.fromEntries(([java, groovy, python, node] as RuntimeSettingsId[]).map((id) [ id, typeof value?.[id] string ? value[id].slice(0, 1024 * 1024) : ])) as RecordRuntimeSettingsId, string } function normalizeRuntimeOptions(value: RecordRuntimeSettingsId, RuntimeRunOption | undefined): RecordRuntimeSettingsId, RuntimeRunOption { return Object.fromEntries(([java, groovy, python, node] as RuntimeSettingsId[]).map((id) [id, { arguments: typeof value?.[id]?.arguments string ? value[id].arguments.slice(0, 2000) : , workingDirectory: typeof value?.[id]?.workingDirectory string ? value[id].workingDirectory.slice(0, 1000) : }])) as RecordRuntimeSettingsId, RuntimeRunOption }问题成因此前 DIFF-151 将 Electron 的runtime.drafts/runtime.options迁入 Compose 代码运行会话时仅对源码应用了CodeRunEngine.MAX_CODE_BYTES截断而运行参数字符串arguments与从磁盘恢复的会话快照session snapshot可能仍超出长度上限。这会带来两类直接后果CodeRunEngine校验阶段拒绝运行Compose 侧运行引擎在校验参数时对单个参数长度、工作目录长度有硬性约束详见下文超长字段会导致运行请求直接失败。持久化不可预期的大 JSON会话快照落盘时若写入超长字段会导致存储文件无谓膨胀。parity-gapF05 代码运行引擎链即由此闭环同时刻意避开 DIFF-501505 的控件半径外观链。核心实现CodeRunRuntimeOptionsNormalize规范化单元DIFF-506 的核心产物是 Compose 侧的规范化对象 CodeRunRuntimeOptionsNormalize.kt其设计与 Electron 侧逐一对齐object CodeRunRuntimeOptionsNormalize { const val MAX_ARGUMENTS_CHARS 2_000 const val MAX_WORKING_DIRECTORY_CHARS 1_000 fun normalizeArguments(value: String): String if (value.length MAX_ARGUMENTS_CHARS) value else value.take(MAX_ARGUMENTS_CHARS) fun normalizeWorkingDirectory(value: String): String if (value.length MAX_WORKING_DIRECTORY_CHARS) value else value.take(MAX_WORKING_DIRECTORY_CHARS) fun normalizeSnapshot(snapshot: CodeRunSessionSnapshot): CodeRunSessionSnapshot { val limit CodeRunEngine.MAX_CODE_BYTES return snapshot.copy( javaCode snapshot.javaCode.take(limit), groovyCode snapshot.groovyCode.take(limit), pythonCode snapshot.pythonCode.take(limit), nodeCode snapshot.nodeCode.take(limit), javaArguments normalizeArguments(snapshot.javaArguments), groovyArguments normalizeArguments(snapshot.groovyArguments), pythonArguments normalizeArguments(snapshot.pythonArguments), nodeArguments normalizeArguments(snapshot.nodeArguments), javaWorkingDirectory normalizeWorkingDirectory(snapshot.javaWorkingDirectory), groovyWorkingDirectory normalizeWorkingDirectory(snapshot.groovyWorkingDirectory), pythonWorkingDirectory normalizeWorkingDirectory(snapshot.pythonWorkingDirectory), nodeWorkingDirectory normalizeWorkingDirectory(snapshot.nodeWorkingDirectory), ) } }要点拆解两个常量与 Electron 完全一致MAX_ARGUMENTS_CHARS 2000、MAX_WORKING_DIRECTORY_CHARS 1000源码注释明确声明Aligns with ElectronnormalizeRuntimeOptions/normalizeRuntimeDraftsinsettings.ts。四语言同步截断normalizeSnapshot一次性覆盖 4 种语言 × 3 类字段源码、arguments、workingDirectory共 12 个字段其中源码统一采用CodeRunEngine.MAX_CODE_BYTES1 MiB。幂等安全当值未超过上限时原样返回不会破坏既有合法快照。CodeRunEngine.MAX_CODE_BYTES定义在 CodeRunEngine.ktobject CodeRunEngine { const val MAX_CODE_BYTES 1024 * 1024 const val HISTORY_LIMIT_HINT 2 * 1024 * 1024 ... }运行引擎的校验约束超长字段为何会拒绝运行理解 DIFF-506 的必要性需要先看清 CodeRunEngine.kt 的validate入口private fun validate(input: CodeRunInput) { if (!requestIdPattern.matches(input.requestId)) { throw CodeRunException(CodeRunErrorCode.INVALID_REQUEST, Invalid runtime request id) } val bytes input.code.toByteArray(StandardCharsets.UTF_8) if (bytes.size MAX_CODE_BYTES) { throw CodeRunException(CodeRunErrorCode.INVALID_REQUEST, Code exceeds 1 MB limit) } if (input.arguments.size 40 || input.arguments.any { it.length 1000 || it.contains(\u0000) }) { throw CodeRunException(CodeRunErrorCode.INVALID_REQUEST, Invalid runtime arguments) } if (input.workingDirectory.length 1000 || input.workingDirectory.contains(\u0000)) { throw CodeRunException(CodeRunErrorCode.INVALID_REQUEST, Invalid runtime working directory) } ... }可见运行引擎对参数有比存储更严格、维度更多的约束参数条目数上限 40 个单个参数长度上限 1000 字符参数与工作目录均不允许包含\u0000NUL 字符。参数解析还来自parseArgumentsCodeRunEngine.kt它实现了一个小型 shell 风格解析器支持双引号/单引号配对、反斜杠转义单引号内不转义、空白分隔并在解析后校验result.size 40 || result.any { it.length 1000 }。因此若从 Electron 迁入或从磁盘恢复的参数快照未经规范化就进入run(input, ...)极可能在validate阶段返回INVALID_REQUEST。DIFF-506 将规范化前置到导入与持久化边界从源头杜绝超长字段进入引擎。集成点一Electron 设置导入链路规范化DIFF-506 将规范化接入 ElectronNextSettingsImport.kt 的两个入口1.parseCodeRunPatch从 Electron 商店 JSON 解析runtime.drafts与runtime.options时即调用normalizeSnapshot对输出规范化private fun parseCodeRunPatch(runtime: JsonObject): CodeRunSessionSnapshot { val drafts runtime[drafts]?.jsonObject val options runtime[options]?.jsonObject fun draft(key: String): String drafts?.get(key)?.jsonPrimitive?.content?.trim() ?: fun option(key: String): PairString, String { val node options?.get(key)?.jsonObject val arguments node?.get(arguments)?.jsonPrimitive?.content?.trim() ?: val workingDirectory node?.get(workingDirectory)?.jsonPrimitive?.content?.trim() ?: return arguments to workingDirectory } ... return CodeRunRuntimeOptionsNormalize.normalizeSnapshot( CodeRunSessionSnapshot( javaCode draft(java), groovyCode draft(groovy), pythonCode draft(python), nodeCode draft(node), javaArguments javaOpt.first, ... ), ) }2.mergeCodeRunSnapshots合并当前会话与导入补丁时先对 patch 规范化再对合并结果二次规范化fun mergeCodeRunSnapshots(current: CodeRunSessionSnapshot, patch: CodeRunSessionSnapshot): CodeRunSessionSnapshot { val normalizedPatch CodeRunRuntimeOptionsNormalize.normalizeSnapshot(patch) fun pick(patchValue: String, currentValue: String): String patchValue.takeIf { it.isNotEmpty() } ?: currentValue return CodeRunRuntimeOptionsNormalize.normalizeSnapshot( current.copy( javaCode pick(normalizedPatch.javaCode, current.javaCode), ... nodeWorkingDirectory pick(normalizedPatch.nodeWorkingDirectory, current.nodeWorkingDirectory), ), ) }合并语义为导入侧非空字段覆盖当前会话对应字段pick逻辑最终输出再次经normalizeSnapshot兜底确保即使当前会话已存在超长字段也会被收敛。这与 DIFF-151 描述的mergeCodeRunSnapshots行为一脉相承并在其基础上补齐了参数/工作目录的规范化。集成点二会话快照写盘与恢复双向规范化DIFF-506 的第三个落点是 SessionManager.kt 中的CodeRunSession会话存储键为ToolId.JavasnapshotState()写盘fun snapshotState(): CodeRunSessionSnapshot CodeRunRuntimeOptionsNormalize.normalizeSnapshot( CodeRunSessionSnapshot( tab tab, javaMode javaMode, javaCode javaEditor.text, groovyCode groovyEditor.text, pythonCode pythonEditor.text, ... nodeWorkingDirectory nodeWorkingDirectory, ), )restore()读盘fun restore(snapshot: CodeRunSessionSnapshot) { val normalized CodeRunRuntimeOptionsNormalize.normalizeSnapshot(snapshot) tab normalized.tab javaMode normalized.javaMode if (normalized.javaCode.isNotEmpty()) javaEditor.setText(normalized.javaCode, recordUndo false) ... }两个方向都经过normalizeSnapshot因此新写入的快照必然合规持久化 JSON 体积可控磁盘上遗留的旧会话快照含超长字段在restore时也会被收敛兼容历史数据——这正是 DIFF 文档中兼容旧会话中的超长字段的含义。会话生命周期由 SessionManager.kt 统一管理codeRunSession()通过store.load(ToolId.Java.id)读取快照并调用session.restorepersistCodeRun()则调用store.save(ToolId.Java.id, ...)写入snapshotState()结果。验证测试用例与运行命令DIFF-506 的验证分为两层1. 单元测试 CodeRunRuntimeOptionsNormalizeTest.ktTest fun truncatesArgumentsAndWorkingDirectoryLikeElectronNormalizeRuntimeOptions() { val longArgs a.repeat(CodeRunRuntimeOptionsNormalize.MAX_ARGUMENTS_CHARS 50) val longDir /tmp/ b.repeat(CodeRunRuntimeOptionsNormalize.MAX_WORKING_DIRECTORY_CHARS) val normalized CodeRunRuntimeOptionsNormalize.normalizeSnapshot( CodeRunSessionSnapshot( javaArguments longArgs, javaWorkingDirectory longDir, ), ) assertEquals(CodeRunRuntimeOptionsNormalize.MAX_ARGUMENTS_CHARS, normalized.javaArguments.length) assertEquals(longArgs.take(CodeRunRuntimeOptionsNormalize.MAX_ARGUMENTS_CHARS), normalized.javaArguments) assertEquals(CodeRunRuntimeOptionsNormalize.MAX_WORKING_DIRECTORY_CHARS, normalized.javaWorkingDirectory.length) assertEquals(longDir.take(CodeRunRuntimeOptionsNormalize.MAX_WORKING_DIRECTORY_CHARS), normalized.javaWorkingDirectory) } Test fun truncatesDraftCodeToMaxCodeBytes() { val huge x.repeat(CodeRunEngine.MAX_CODE_BYTES 100) val normalized CodeRunRuntimeOptionsNormalize.normalizeSnapshot( CodeRunSessionSnapshot(pythonCode huge), ) assertEquals(CodeRunEngine.MAX_CODE_BYTES, normalized.pythonCode.length) }测试断言了两件事超长 arguments 与 workingDirectory 分别收敛到 2000 / 1000 字符且截断结果等于take语义即保留前 N 个字符超长源码收敛到MAX_CODE_BYTES。2. 导入链路集成测试 ElectronNextSettingsImportTest.ktDIFF 文档点名的loadCodeRunPatchTruncatesOversizedOptionsLikeElectronNormalize用例模拟 Electron 商店 JSON 中写入超长arguments与workingDirectoryval longArgs x.repeat(CodeRunRuntimeOptionsNormalize.MAX_ARGUMENTS_CHARS 10) val longDir /w/ y.repeat(CodeRunRuntimeOptionsNormalize.MAX_WORKING_DIRECTORY_CHARS) ... val patch ElectronNextSettingsImport.loadCodeRunPatchFromStore(store)!! assertEquals(CodeRunRuntimeOptionsNormalize.MAX_ARGUMENTS_CHARS, patch.javaArguments.length) assertEquals(CodeRunRuntimeOptionsNormalize.MAX_WORKING_DIRECTORY_CHARS, patch.javaWorkingDirectory.length)同文件的loadsRuntimeDraftsAndOptionsIntoCodeRunPatch用例则验证了正常导入路径DIFF-151 产出runtime.drafts与runtime.options被正确解析为补丁并合并进会话。3. 全量回归命令./gradlew :composeApp:desktopTest --offline需要 JDK 21 环境--offline使用本地 Gradle 依赖缓存。变更边界本次未做DIFF-506 明确划定了本次变更的边界避免范围蔓延产品窗 F05 运行参数走查帧UI 走查未做六套 CSS 逐选择器皮肤控件外观链DIFF-501505 范畴未做设置未实现分类面板未做Git/Vault 新切片未做P7 三平台安装验收未做hero/claude 内容区 shell 走查未做。这些内容属于后续 DIFF如 DIFF-627、DIFF-629 等 F05 接线切片的覆盖范围与本次纯数据规范化主题解耦。小结一条完整的长度一致性保障链路DIFF-506 用一个小而完整的变更补齐了 Compose 代码运行会话在长度约束上与 Electron 的一致性其设计可归纳为**一处定义、三处接入、双向兜底**一处定义CodeRunRuntimeOptionsNormalize集中定义 2000 / 1000 / 1 MiB 三档上限与 Electronsettings.ts常量语义一致三处接入Electron 设置导入parseCodeRunPatch/mergeCodeRunSnapshots、会话快照写盘snapshotState、会话读盘恢复restore双向兜底导入与合并输出均规范化旧会话超长字段在恢复时也被收敛保证无论数据来源Electron 旧存储、本地快照、历史遗留都不会触发CodeRunEngine.validate的INVALID_REQUEST也避免了不可预期的大 JSON 落盘。从源码结构看这套模式独立的*Normalize对象 在会话边界统一调用在 settings.ts 的normalizeSettings及其他会话如 JSON 的normalizeInspectorIndent、翻译的MAX_TEXT_UNITS中均有同构体现可作为后续跨产品数据一致性变更的参考范式。赞分享桌面应用开发工具【免费下载链接】MooToolHandy tool set for developers项目地址https://gitcode.com/gh_mirrors/mo/MooTool点击查看免费下载相关推荐MooTool Next Compose DIFF-515 深度解析Vault Git merge/rebase 状态 UI、push 引擎守卫与 HTTP/翻译代理运行时规范化MooTool Next Compose DIFF 515 深度解析Vault Git merge/rebase 状态 UI、push 引擎守卫与 HTTP/桌面应用开发工具MooTool Next Compose 跨产品迁移Electron runtime.drafts / runtime.options 导入代码运行会话的实现MooTool Next Compose 跨产品迁移Electron runtime.drafts / runtime.options 导入代码运行会话的实现桌面应用开发工具MooTool 设置页运行环境检测与可执行文件选择DIFF-145实现解析MooTool 设置页运行环境检测与可执行文件选择DIFF 145实现解析 本文聚焦 MooTool next compose Compose Multi桌面应用开发工具上一篇API 集成测试Integration Testing完整指南组合验证、故障暴露与分层工程实践下一篇Veo 自动化实战指南通过 Rube MCP 与 Composio 驱动 Google Veo 视频生成创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考