Operit ToolPkg 发布来源注入与受保护归档:市场溯源与 AST 剪枝实战 AI Agent人工智能大模型AI 应用工具调用本地部署MCP ClientsAgent 记忆【免费下载链接】OperitThe most powerful AI agent and AI chat software on Android/Operit是一款Android上能力最为强大、发展最久的AI Agent项目地址https://gitcode.com/gh_mirrors/op/Operit点击查看免费下载本篇技术指南聚焦 Operit 中 ToolPkg 插件的发布处理链路如何在直接上传时把当前登录用户写入市场来源market origin以及在开启混淆时如何基于 manifest 入口与静态模块引用构建可达条目集合剪掉未到达的src/、source map、测试与构建文件形成既保留运行能力又保护源码的受保护归档。读完本文你将掌握 Operit 的发布期归档处理模型、市场来源编码与注入格式、可达性剪枝算法以及它们与 GitHub Release 资产发布流程的边界关系。核心设计记录见 docs/TODO/toolpkg_publish_provenance_20260729/index.md。现状与变更动机在引入来源注入之前Operit 对插件制品的发布处理并不统一直接上传DirectUpload过去只在开启混淆时处理脚本关闭混淆时文件会被原样上传因此不会写入市场来源market originToolPkg 归档中的src/目录和 source map 会随包原样保留即使开启混淆未进入执行路径的开发文件也会一并分发。这意味着市场侧无法可靠追溯这个包是谁发布的、从哪个市场渠道进入同时发布者的源码细节会通过src/与 source map 泄露。本次变更确立了两个目标直接上传始终写入当前登录用户的市场来源无论是否开启混淆开启混淆时生成受保护归档只保留 manifest、主入口、子包入口、声明资源/WASM 以及这些入口依赖的模块未到达的src/、source map、测试及构建文件一律排除。实现集中在两个文件ToolPkgArtifactMinifier.kt归档处理与剪枝与 GitHubForgePublishService.kt发布编排。市场来源的数据模型与编码来源信息由ToolPkgMarketOrigin承载定义在 ToolPkgMarketOrigin.ktSerializable internal data class ToolPkgMarketOrigin( val market: String, // 市场标识固定为 Operit val toolpkgId: String, // 包唯一 ID val version: String, // 包版本 val author: ListString // 发布者登录用户名列表 )注入格式由ToolPkgMarketOriginCodec负责采用异或XOR混淆 可执行语句注入private const val XOR_KEY 0x5a fun encode(origin: ToolPkgMarketOrigin): String { val encoded encodeBytes(origin).joinToString(prefix [, postfix ]) return ToolPkg._m(${encoded},$XOR_KEY); }编码细节值得注意载荷先序列化为 JSON非 ASCII 字符会被转义为\uXXXXescapeNonAscii随后每个字节与0x5a异或得到一串 0~255 的整数列表最终包装为ToolPkg._m([...],90);这样的可执行语句追加到脚本/主入口源码末尾。选择可执行语句而非注释是为了让来源信息随混淆一起被压缩进执行体内而不是被 Terser 当作注释剥离见 ToolPkgArtifactMinifier.kt 的minifyJavaScriptBytes与注释说明。运行时侧则通过validateForPackage校验来源是否匹配当前包fun validateForPackage(origin: ToolPkgMarketOrigin?, packageId: String): ToolPkgMarketOrigin? { val normalized origin ?: return null if (normalized.market ! MARKET) return null // 必须来自 Operit 市场 if (normalized.toolpkgId ! packageId.trim()) return null // 包 ID 必须一致 if (normalized.version.isBlank()) return null return normalized.copy( toolpkgId normalized.toolpkgId.trim(), version normalized.version.trim(), author normalized.author.map(String::trim).filter(String::isNotBlank) ) }即市场来源只有在market Operit、toolpkgId与当前包一致、版本非空时才被接受author 会做 trim 与空值过滤。对应编解码的单元测试见 ToolPkgMarketOriginCodecTest.kt。直接上传的完整发布流程GitHubForgePublishService.publishArtifact是发布编排的入口其PublishArtifactSource分两种DirectUpload本地文件直接上传走发布期处理管线来源注入 可选混淆GitHubReleaseAsset引用已有 GitHub Release 资产仅校验本地文件与远端资产 SHA-256 一致不做任何改写。DirectUpload的关键路径如下GitHubForgePublishService.kt登录校验githubAuth.isLoggedIn()不通过则直接失败本地校验validateSourceFile检查文件存在、是文件、可读validateSupportedAppVersions校验支持的应用版本区间获取当前用户getCurrentUser()得到currentUser.login作为来源 author确保 Forge 仓库ensureForgeRepository在用户的OperitForge仓库下发布资产——仓库不存在且允许创建时自动创建allowCreateForgeRepo仓库为空时写入 README 初始化若用户未初始化且不允许创建返回NeedsForgeInitialization让 UI 引导初始化确保 ReleaseensureRelease按 tag 查找不存在则创建、存在则更新均非 draft、非 prerelease构造市场来源并处理制品val marketOrigin ToolPkgMarketOrigin( market Operit, toolpkgId descriptor.runtimePackageId, version descriptor.version, author listOf(currentUser.login) ) val processedFileBytes ToolPkgArtifactMinifier.processArtifactFile( context context, sourceFile sourceFile, isToolPkg descriptor.type PublishArtifactType.PACKAGE, marketOrigin marketOrigin, minify source.minifyArtifact )上传资产uploadAssetReplacingExisting先删除同名的旧资产再上传新资产避免同名残留计算 SHA-256对处理后的字节计算sha256Hex随MarketRegistrationPayload一并注册到市场registerMarketEntry市场侧以github_release_asset形式记录ghOwner/ghRepo/ghReleaseTag/assetName/sha256。整个流程通过PublishProgressStage向 UI 回传进度VALIDATING → ENSURING_REPO → CREATING_RELEASE → UPLOADING_ASSET → REGISTERING_MARKET → COMPLETED。脚本与 ToolPkg 归档的差异化处理processArtifactFile依据isToolPkg分流独立脚本processScriptFile读取 UTF-8 源码 →injectScriptMarketOriginIntoMetadata把来源写入脚本的METADATA块 → 关闭混淆时直接返回改写后的字节开启混淆时先注入再 AST 压缩。ToolPkg 归档processToolPkgArchive来源作为容器级信息注入只改写主入口manifest.main解析出的 zip 条目见 ToolPkgArtifactMinifier.kt。脚本的 METADATA 注入独立脚本要求存在/* METADATA ... */块否则直接抛错private fun injectScriptMarketOriginIntoMetadata(source: String, marketOrigin: ToolPkgMarketOrigin): String { val metadataBlock findMetadataBlock(source) ?: throw IllegalArgumentException(JavaScript package METADATA block is required for marketplace origin) val metadata JSONObject(JsonValue.readHjson(metadataBlock.content).toString()) metadata.put(SCRIPT_MARKET_ORIGIN_METADATA_KEY, ToolPkgMarketOriginCodec.encodeForMetadata(marketOrigin)) val updatedMetadataBlock /* METADATA\n${metadata}\n*/ return source.replaceRange(metadataBlock.range, updatedMetadataBlock) }注入的键为__operit_market_origin值采用xor-v1:前缀的逗号分隔字节序列encodeForMetadata。读取侧可用decodeMetadata还原出ToolPkgMarketOrigin。ToolPkg 主入口的来源注入ToolPkg 归档不要求 METADATA 块而是把来源编码为ToolPkg._m(...);语句追加到主入口源码末尾injectToolPkgMarketOrigin。主入口来源构造为val mainOrigin ToolPkgMarketOrigin( market Operit, toolpkgId manifestPreview.manifest.toolpkgId, version manifestPreview.manifest.version, author marketOrigin.author )这里market固定为OperittoolpkgId/version取自 manifestauthor取当前登录用户——这印证了文档中市场来源是 ToolPkg 容器级信息由主入口承载的设计多子包分别处理但来源只挂在主入口上。开启混淆时的可达性剪枝混淆模式下的核心是collectReachableToolPkgEntriesToolPkgArtifactMinifier.kt它构建可达条目集合种子集合manifest 条目本身所有可执行入口manifest.main 每个subpackages[].entry经resolveManifestRelativeZipEntryPath解析为绝对 zip 路径所有资源根manifest.resources[].path与manifest.wasmModules[].path资源根会按name root || name.startsWith($root/)展开目录资源整棵保留静态引用扫描从每个可执行入口出发用staticModuleReferencePattern正则匹配require(...)与from/import ...扫描源码解析出模块引用并加入待处理队列模块解析resolveToolPkgModuleEntry只处理以.开头的相对引用按./、../语义归并路径段依次尝试候选形式val candidates listOf( modulePath, $modulePath.js, $modulePath.mjs, $modulePath.cjs, $modulePath.json, $modulePath/index.js )BFS 迭代循环从队列取条目、扫描、解析、入队直到不再有新条目仅isJavaScriptEntry——js/mjs/cjs/ts/jsx/tsx扩展名——才展开扫描。剪枝写入与安全校验写入阶段ToolPkgArtifactMinifier.kt未命中可达集合的条目含src/、source map、测试与构建文件被跳过命中主入口的条目混淆时先注入来源再 AST 压缩不混淆时仅注入来源命中其他可执行条目或 JS 扩展名条目的AST 压缩资源根下的文件不压缩manifest 与资源原样复制。同时有两条显式require兜底防止剪枝剪出空壳包require(manifestEntryName in reachableEntryNames) { Minified toolpkg lost its manifest entry: $manifestEntryName } val missingExecutables executableEntryNames.filterNot { it in reachableEntryNames } require(missingExecutables.isEmpty()) { Minified toolpkg lost executable entries: ${missingExecutables.joinToString()} }源码注释特别提醒剪枝以 manifest 所在目录为根若包内混入嵌套 manifest 导致选错 manifest真正的入口会被判为不可达并整包丢弃产出能装但内容为空壳的包因此必须显式校验。这里的可达集合本质是让受保护归档与运行时依赖图对齐——复制整棵作者树既破坏源码保护又增加加载开销。AST 压缩实现Terser 与保留策略混淆由 ToolPkgJsAstMinifier.kt 完成它在 Operit 内置的 QuickJS 引擎OperitQuickJsEngine中加载assets/js/terser.bundle.min.js调用Terser.minify_sync压缩选项如下root.__operitToolPkgAstMinify function(source, entryName) { var result root.Terser.minify_sync(String(source), { ecma: 2020, module: /\.mjs$/i.test(normalizedEntryName), compress: { defaults: true, passes: 3, toplevel: false // 保留 CommonJS export 与词法声明顺序 }, mangle: { toplevel: false, keep_classnames: false, keep_fnames: false }, format: { comments: false, ascii_only: true }, sourceMap: false }); ... };要点toplevel: false刻意保留顶层绑定与 CommonJS export 名称保证外部调用接口不被破坏comments: false会剥离注释因此市场来源必须作为可执行语句注入这正是ToolPkg._m(...);设计的原因对含 METADATA 块的脚本/条目minifyJavaScriptSourcePreservingMetadata会先把/* METADATA ... */块整体抽出、对剩余 body 做压缩再把原 METADATA 注释拼回头部避免 HJSON 元数据被压缩破坏压缩产物仍是普通 ZIP/脚本可被现有市场路径、文件选择器和调试安装直接加载对应 docs/TODO/toolpkg_protection_ast_minify/index.md 的完成标准。多子包与容器级来源文档明确多子包仍分别处理subpackages[].entry会被逐个加入可执行入口集合参与剪枝与压缩但市场来源只注入主入口因为来源是 ToolPkg 容器级信息。这也意味着即使包内含多个子包运行时解析到的来源始终归属于容器整体而非某个子包。边界与限制GitHub Release 资产不可变一个重要边界文档原文已有 GitHub Release 资产是外部不可变引用。加载该模式PublishArtifactSource.GitHubReleaseAsset时不会改写远端文件——GitHubForgePublishService只做 SHA-256 一致性校验本地文件必须与远端资产字节一致远端 Release 资产保持原样。要获得来源注入与剪枝保护必须通过直接上传发布处理后的资产即选择DirectUpload走processArtifactFile管线。因此给发布者的实操建议是需要市场溯源 源码保护的新版本一律走直接上传引用历史 Release 资产只适合登记已有制品不会获得来源注入混淆开关minifyArtifact决定剪枝与压缩是否生效关闭混淆时仍会注入来源脚本走 METADATA、ToolPkg 走主入口但src/与 source map 会随包保留。验证与回归仓库中已有配套测试覆盖本主题的核心行为ToolPkgMarketOriginCodecTest.kt验证 XOR 编解码、xor-v1:元数据格式与validateForPackage的字段规约JsToolPkgRegistrationTest.kt验证来源在 JS 注册侧的解析与消费涉及ToolPkgMarketOrigin在 JsToolPkgRegistration.kt 与 JsEngine.kt 中的使用。回归时建议覆盖四类场景脚本 混淆 / 脚本不混淆 / ToolPkg 混淆含子包与资源目录/ ToolPkg 不混淆并分别断言来源注入位置METADATA vs 主入口尾部ToolPkg._m、剪枝后条目集合以及压缩后仍可被市场路径与调试安装正常加载。小结Operit 的发布处理把市场溯源与源码保护统一进了同一管线来源信息通过 XOR 编码以可执行语句注入主入口脚本则写入 METADATA混淆时借助静态模块引用分析构建可达条目集合完成剪枝再由内置 Terser 做 AST 压缩最终产物以 GitHub Release 资产 市场注册含 SHA-256的形式分发。理解这条链路后无论是排查来源未注入多半是走了 Release 资产引用而非直接上传、包能装但功能缺失多为 manifest 选择错误导致的剪枝误判还是src/ 意外泄露混淆未开启都能在 ToolPkgArtifactMinifier.kt 与 GitHubForgePublishService.kt 中找到对应实现依据。赞分享AI Agent人工智能大模型AI 应用工具调用本地部署MCP ClientsAgent 记忆【免费下载链接】OperitThe most powerful AI agent and AI chat software on Android/Operit是一款Android上能力最为强大、发展最久的AI Agent项目地址https://gitcode.com/gh_mirrors/op/Operit点击查看免费下载相关推荐Operit ToolPkg 市场来源溯源导入通知与自动化测试实现指南Operit ToolPkg 市场来源溯源导入通知与自动化测试实现指南 本文基于 Operit 仓库 docs/TODO/toolpkg_market_oriAI Agent人工智能大模型AI 应用工具调用本地部署MCP ClientsAgent 记忆GUI 自动化Operit ToolPkg 市场溯源Market Origin机制详解从发布注入、导入校验到来源提示的完整实现Operit ToolPkg 市场溯源Market Origin机制详解从发布注入、导入校验到来源提示的完整实现 导读 本文以 Operit 仓库中 doAI Agent人工智能大模型AI 应用工具调用本地部署MCP ClientsAgent 记忆GUI 自动化Operit ToolPkg 直传发布脚本 METADATA 注释任意位置定位与市场来源注入实现解析Operit ToolPkg 直传发布脚本 METADATA 注释任意位置定位与市场来源注入实现解析 本篇技术指南讲解 Operit 在 ToolPkg /AI Agent人工智能大模型AI 应用工具调用本地部署MCP ClientsAgent 记忆GUI 自动化上一篇Path of Building PoE2流放之路2构建规划终极指南下一篇Windows虚拟声卡终极指南3步实现局域网无线音频传输创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考