news 2026/9/18 18:42:48

AI Agent 驱动 Unity 自动化构建与测试:批处理模式与桥接层实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI Agent 驱动 Unity 自动化构建与测试:批处理模式与桥接层实战

前阵子,我们工作室一直在做 Unity 项目的自动化构建和持续验证,效果还行,但人肉参与度还是太高——每天打开编辑器、切平台、点 PlayMode 测试、看日志、传包,这一套下来少说半小时。后来我决定把 AI Agent 引进来,让它直接驱动 Unity 编辑器编译与测试。这个项目不是什么黑科技,本质上就是把 AI Agent 与 Unity 的批处理模式打通,用一套“命令桥接层”让 Agent 能自己跑编译、跑测试、读结果、判断下一步动作。

这篇文章就来完整复盘一遍:我为什么这么做、整体设计是什么、哪里踩了坑、有什么可直接抄的代码和配置。如果你也有把 AI Agent 引入 Unity 工具链的想法,或者你只是想减少“人肉点编译按钮”的重复劳动,这篇文章的内容应该能给你一套能落地的参考。

1. 项目整体设计与思路拆解

1.1 核心痛点和目标

先说一个最常见的场景。早上到公司,打开 Unity,老板说“打一个 Android 开发包看看”,你打包;同事说“测试用例挂了”,你切回 Editor 跑一轮测试;过了半小时又发现远程分支更新了,你还得重新拉代码、重新编译。

这些事情单看不难,但加起来就是巨大的注意力耗散。尤其是测试和编译的“等待结果→判断结果→决定下一步”这个过程,完全适合交给 AI Agent 来做。我做这套东西的初衷,就是让 Agent 不只当一个“查资料、写代码”的问答工具,而是真刀真枪去调用工具链,把“验证”这个环节从中解放出来。

所以我给项目定了几条硬目标:

  • 让 AI Agent 能触发 Unity 编辑器的编译任务,比如切平台、打 AssetBundle、出包。
  • 让 AI Agent 能触发 Unity Test Framework 的 EditMode 和 PlayMode 测试。
  • 让 AI Agent 能自动解析编译日志和测试报告,判断成功还是失败,失败还要能定位原因。
  • 全过程要有日志、有痕迹,AI 的判断结果必须可审计,不能黑盒。

这四条目标放在一起,才真正像一个“工具链”,而不是简单写一个命令行脚本让 Agent 用。

1.2 方案选型:批处理模式与自定义执行器

Unity 官方其实早就给了我们一个不依赖可视化界面的操作入口,也就是批处理模式(Batch Mode)。原本的用法很简单,执行一条命令行指令,传入项目路径、目标平台和方法名,Unity 就会以无 UI 模式完成指定操作后退出。这种方式既可以用在本地,也可以直接抛到 CI 上跑。

但是这个官方方案有个让人头疼的问题:所有东西都依赖-executeMethod指向一个 C# 静态方法,方法内部你要自己写具体逻辑。如果你每个需求都写独立的静态方法,比如BuildAndroid,RunTests,BuildAssetBundle, 方法一变多,Agent 要记忆和调用的成本就上来了。

所以我在设计上做了一个“收敛”:不管什么任务,都用同一个入口方法,任务种类通过参数传进去。在 C# 侧维护一个简单的任务路由表,把字符串参数映射到对应的构建或测试逻辑。AI Agent 侧只需要知道“我调用python template_cli.py build --target android”,剩下的事情全链路自动完成。这个方法的好处是,Agent 不需要了解 Unity 内部有多少个静态方法,也不容易说错参数。

1.3 为什么让 AI Agent 直接驱动,而不是写死一个流水线

可能有人会问:“既然都自动化了,直接搞一套 Jenkins 或者 GitHub Actions 流水线不就行了?” 确实,传统流水线很适合固定流程:拉代码、构建、测试、归档。但流水线有个硬伤:它对异常情况不灵活。

举个具体例子,流水线在出包阶段发现 Shader 编译警告,传统方案要么直接忽略,要么直接失败,几乎不能自己决定“这个警告要不要紧”。而 AI Agent 的一大优势是它能够“看”日志上下文、结合常见经验做判断,然后决定继续执行还是停下来等待人工确认。

我真正想实现的效果,是让工具链不只是一条铁轨外加一辆定时火车,而是让 AI 在里面当“火车调度员”。它能根据结果自动调整下一步动作。比如测试挂了,它会去翻测试报告里是哪个测试类、哪个断言失败,再根据失败信息决定是否需要重新跑一次(排除偶发性失败)。这种动态判断,传统代码写起来很笨重,但 Agent 做起来非常自然。

当然,这也带来一个问题,就是它需要一套非常清晰和结构化的输入输出协议。如果日志乱糟糟、返回值不统一,任 AI Agent 再聪明也没办法。所以整条链路的架构重点是定义好“统一命令行接口”与“结构化结果输出”。

2. 环境准备与工具链搭建

2.1 本机安装与版本选择

先说一下我使用的环境,方便大家参照。开发机是 Windows 10,Unity 版本是 2022.3 LTS,代码仓库是 SVN(是的,老项目还在用 SVN)。Agent 侧我采用了 Python 3.10 写的桥接脚本,提供本地命令入口。这个组合不一定最优,但胜在简单,而且兼容性很稳。

这里有一个重要提示:如果团队里有多个 Unity 版本,建议在桥接脚本里强制锁定 Unity 可执行文件路径,不要在系统 PATH 里找“unity”这种模糊指令。我在做的时候发现,不同电脑上 Unity Hub 的安装路径差异很大,有些在C:\Program Files\Unity\Hub\Editor\2022.3.21f1\Editor\Unity.exe,有些装在 D 盘,如果每个人都要自己改脚本,这个工具链就推不动。我的做法是把配置文件单独提出来,统一写在一个 JSON 里,脚本启动时读取,换机器只改配置不改代码。

配置解析完成后,可以通过一个简单的 self-check 命令验证环境是否就绪。这个命令会检查 Unity 路径是否存在、项目路径是否存在、Python 版本是否满足要求。别小看这一步,很多看起来像“Agent 不聪明”的问题,其实都是环境不一致导致的。

2.2 Unity 侧的批处理入口

Unity 侧的代码是整套工具链的心脏。我先建了一个Editor/BuildTools.cs文件,里面只有一个静态方法作为总入口。这个方法会读取命令行参数--task--platform--output等,再调用不同的私有方法去执行具体逻辑。

关键代码大概是这样的:

using System; using UnityEditor; using UnityEngine; public static class BuildTools { public static void ExecuteTask() { var args = Environment.GetCommandLineArgs(); string task = GetArgValue(args, "--task"); string platform = GetArgValue(args, "--platform"); string output = GetArgValue(args, "--output"); if (string.IsNullOrEmpty(task)) { Debug.LogError("[BuildTools] Missing --task parameter"); EditorApplication.Exit(1); return; } switch (task) { case "build": DoBuild(platform, output); break; case "test": DoTest(); break; case "assetbundle": DoAssetBundle(output); break; default: Debug.LogError($"[BuildTools] Unknown task: {task}"); EditorApplication.Exit(1); break; } } private static void DoBuild(string platform, string output) { // 这里根据 platform 切换 BuildTarget,然后调用 BuildPipeline.BuildPlayer } private static void DoTest() { // 这里可以直接调用 TestRunnerApi,也可以让 Unity 自己处理 -runTests } }

有人会问,为什么不用 Unity 自带的-batchmode -runTests参数,而是要自己写DoTest方法?原因是-runTests参数跟-executeMethod一起用的时候,如果测试没有运行完就调用EditorApplication.Exit,测试结果文件会半截丢失。自己通过TestRunnerApi写测试执行逻辑,可以精确控制结束时机和退出码,对 AI Agent 的判断更友好。

2.3 Python 侧的“方言翻译器”

Unity 的命令行参数风格和 AI Agent 的语言习惯不太一样。如果让 Agent 直接记住Unity.exe -batchmode -projectPath xxx -executeMethod BuildTools.ExecuteTask --task test这一长串,它也能做到,但容易出错而且不直观。我的处理方式是在 Python 侧写一个“方言翻译器”,把人类的自然指令通过参数解析转换成 Unity 能理解的命令行。

比如 Agent 说“跑一下 Android 开发包”,对应执行的 Python 命令是:

python cli.py build --platform android --dev true

Python 脚本内部会解析这些参数,最后组装成完整的 Unity 命令,然后通过subprocess执行。执行过程中,脚本会把 Unity 的标准输出和错误输出实时接出来,在控制台打印的同时写一份副本到日志文件。这个日志文件,也是后续让 Agent 总结问题的重要依据。

这样一来,AI Agent 只需要掌握“我这边有buildtestassetbundlelog四个子命令”,剩下的事情交由桥接脚本去处理。对 Agent 来说,接口面小了很多,出错率显著下降。

3. AI Agent 接入与交互设计

3.1 让 Agent 学会“观察”而不是“瞎猜”

很多人在做 AI Agent 自动化时,最容易犯的一个错误是让 Agent 直接根据记忆编结果,而不是根据实时输出判断。比如,你让它跑测试,它跑完告诉你“通过了”,但实际上测试压根没跑完就已经超时退出了。为避免这种情况,在 Agent 的提示词和工具协议里,我做了几个硬性要求:

  • 一切结论必须有证据。Agent 在说“编译成功”之前,必须引用日志文件里最后几行的关键内容。
  • 每个任务执行完必须返回结果文件的绝对路径和摘要。这个摘要由桥接脚本生成,Agent 要做的是解析摘要,而不是猜。
  • 如果任务超时或退出码异常,Agent 必须先尝试读取错误日志再响应,不许直接重试。

这些规则写起来不难,真正难的是让 Agent “记得”不犯错。所以我的做法是把这些规则写进工具描述(Tool Description)里,而不是放在很长的系统提示词中。当 Agent 决定调用工具时,它一定会先读取工具描述,这比一层层传系统提示词可靠得多。

3.2 工具调用协议与结果格式化

我实现了一个非常简单的“工具调用协议”,也就是让 Agent 通过一个 JSON 格式的请求去触发本地脚本,桥接层再把结果转成 JSON 字符串返回给 Agent。虽然现在很多 MCP 和 Agent SDK 都提供了成熟方案,但我在这个项目中刻意没有引入太重的东西,原因很简单:Unity 工具链的触发逻辑不复杂,越简单越不容易坏。

下面是我和 Agent 之间的实际调用示例,简化掉鉴权和网络层后,核心逻辑是这样的:

{ "action": "run_command", "command": "test", "params": { "mode": "playmode" } }

桥接层收到后执行:

python cli.py test --mode playmode

执行完之后,返回:

{ "status": "success", "summary": "PlayMode tests finished: 24 passed, 1 failed (12.3s)", "result_file": "D:\\workspace\\reports\\20250217_1530.xml", "log_file": "D:\\workspace\\logs\\20250217_1530.log" }

Agent 拿到这个 JSON 后,如果发现有 1 个测试失败,它会接着要求读取result_filelog_file末尾的内容,找到具体失败断言,然后向用户汇报。整个过程可以完全脱离人工监控。

3.3 会话上下文与状态记忆

AI Agent 驱动的另一个问题,是它没有真正的“记忆”能力。今天问它“安卓包构建路径是哪个?”它能告诉你,但明天新开一个会话它可能就忘记了。所以我额外做了一层“状态文件”,用于持久化每次构建的关键信息。

具体实现是:每次构建或测试完成后,桥接层把关键信息追加到一个toolchain_state.json文件里,包括时间戳、分支、提交号、构建产物路径、测试结果摘要。Agent 在每次开始新任务前,会主动读取这个文件,以获得“上一次构建发生了什么”的上下文。这种“外挂记忆”的做法,让整个工具链在长时间使用过程中显得智能很多。

状态文件示例:

{ "last_build": { "time": "2025-02-17 15:31:02", "target": "android", "output": "builds/android/dev/app-debug.apk", "status": "success", "revision": "12345" }, "last_test": { "time": "2025-02-17 15:30:11", "mode": "playmode", "passed": 24, "failed": 1, "failed_names": ["Test_Inventory_AddOverflow"] } }

这个文件不仅是给 Agent 用的,也是给人看的。谁在什么时候构建了什么包,一目了然,比在聊天记录里翻半天靠谱多了。

4. 编译与测试环节的实操过程

4.1 编译环节:踩到一个 MSB6006 的老坑

做工具链期间,我遇到过一个非常经典的编译报错:在开发机上构建 Android 原生插件时,日志里直接出现VS2010编译报Error MSB6006: cmd.exe已退出,代码为3。这个报错乍一看是编译器 Code 3,排查了半天才发现,问题根本不在代码,而在cmd.exe的环境变量 PATH 被某个自动化脚本改掉了,导致启动后在找不到system32下的findstr.exe,一执行字符串查找就直接异常退出。

这个坑让我意识到,Unity 批处理模式虽然不会打开图形界面,但它调用的编译环境依然依赖系统环境变量。工具链跑在自动化环境里,必须有一个“干净”的环境快照。我是这样处理的:

  • 在 Python 桥接层里,强制指定PATH,把C:\Windows\system32放到最前面。
  • 不让 Unity 通过AssetPostprocessor去调用外部cmd.exe来做字符串操作,尽量用 C# 内置 API。
  • 如果必须要走外部命令,写一个 Wrapper 脚本,把环境变量问题固定下来,避免服务器上大家改来改去。

如果不把这种环境细节处理干净,工具链在本地开发机好了,一到别的开发机就崩,这种问题特别消耗人的耐心。

4.2 Unity 编译失败不会给你干净的退出码

Unity 的批处理模式有个让自动化工程师很头疼的习惯:即使编译失败了,进程也会用 0 退出码正常返回。它把错误信息都写在日志里,但把进程结果弄得像“一切正常”。这对传统脚本来说非常不友好,因为脚本判断成功还是失败,最直接的方式就是看退出码。

我在桥接层里做了一个“双保险”逻辑:

  • 不直接看 Unity 进程的退出码。
  • 编译完成后,主动去解析日志文件里有没有error CSerror :这类关键标记。
  • 如果发现错误标记,直接生成一个status: failed的 JSON,即使底层退出码是 0 也不影响最终判断。

也就是说,我把“进程是否正常结束”和“任务是否真的成功”分开判断。前者来自subprocess.run的返回码,后者来自桥接层的语义判断。这样 AI Agent 拿到的状态就非常明确,不会出现“退出码 0 但实际包根本没打出来”的错判。

4.3 关于 GameAssembly.dll 的一个冷知识

在跑 Android 和 Windows 的 Il2CPP 构建时,日志里经常出现GameAssembly.dll这个名字。很多人不知道它是什么,简单说它就是 IL2CPP 方式下,把项目 C# 代码编译并转换成 C++ 后再编译出的最终原生二进制,是玩家真正运行的游戏逻辑本体。

在做工具链时,我特意把“产物完整性检查”加进构建流程。也就是说,构建完成后,不光看日志里有没有报错,还要检查目标输出目录里是否存在对应平台的产物文件。比如 Android 必须是apkaab文件,Windows 必须是exe文件和GameAssembly.dll。文件大小如果低于某个阈值(比如 1MB),大概率是异常,需要重新打包。

这种“检查产物”的习惯,对我的 AI Agent 来说尤其重要。因为 Agent 不懂美术资源大不大、代码多不多,但它能通过“文件是否存在、大小是否合理”这两个简单标准判断构建是否靠谱。这比任何花哨的语义分析都实用。

4.4 测试环节:设计一个“不自欺”的测试入口

测试的自动化相比编译更微妙,因为测试存在“假成功”和“假失败”两种极端。所谓假成功,就是测试进程退出时报告全部通过,但其实用例根本没加载进去,因为 Assembly 引用缺失。所谓假失败,则是环境因素导致偶发失败,比如 PlayMode 测试里因为网络请求超时挂掉,其实代码逻辑没问题。

为了让测试环节能被 AI Agent 放心接管,我在DoTest方法里做了这些事:

  • 使用TestRunnerApi注册回调,等所有测试跑完后统一收集结果。
  • 把测试报告保存为 NUnit 格式的 XML,这个格式有标准,Agent 很容易解析。
  • 成功和失败都返回明确的退出码和 JSON 结构,不让“通过与否”停留在日志文字里。
  • 在测试命令里增加--retry参数,供 Agent 判断是否需要二次验证。

在 AI Agent 的提示词模板里,我写入了这样一条规则:“当测试失败时,先看 XML 报告里的failure节点,记录具体堆栈,优先怀疑环境问题而不是代码问题。” 这条规则帮我省下了很多麻烦,因为 AI 一开始总喜欢把一切堆栈都当成代码 BUG,完全不思考环境差异。

5. 常见问题与排查技巧实录

5.1 Unity 进程没有正常退出,导致工具链卡住

这是我们使用过程中最频繁遇到的问题。Unity 批处理模式在某些情况下会进入“半卡死”状态,进程还在,但既不输出日志也不退出,最常见于资源导入阶段被外部工具弹窗阻塞。后来的解决方案是在 Python 侧做超时控制,超过wait_time后直接terminate()进程并生成超时报告。

实际代码如下:

import subprocess def run_unity_command(cmd, timeout=600): proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, encoding="utf-8", errors="replace") log_lines = [] try: for line in proc.stdout: log_lines.append(line) proc.wait(timeout=timeout) except subprocess.TimeoutExpired: proc.terminate() return {"status": "timeout", "logs": "".join(log_lines[-50:])} return {"status": "ok", "exit_code": proc.returncode, "logs": "".join(log_lines[-200:])}

这里有个细节容易被忽略:日志读取和超时等待必须放在同一段逻辑里,不能先proc.wait()再读proc.stdout,否则输出管道一旦填满,进程会阻塞在写日志这一步,永远跑不完。我用了一段时间才发现,之前好多次所谓的“Unity 卡死”,其实是脚本自己在读取管道时堵住了。

5.2 日志乱码和中文路径问题

Unity 的日志文件默认编码格式在不同平台上不太一样。Windows 下经常是 UTF-8,但某些系统语言环境下可能输出成 GBK。如果在 Python 里不指定encoding,乱码会让 AI Agent 完全看不懂错误信息。

我的做法是,在subprocess.Popen中显式设置encoding="utf-8"errors="replace",同时在项目路径上避免使用中文。如果确实有中文路径,我会在桥接层的配置文件中维护一个“路径别名表”,例如project_demo: "D:/work/项目A",命令里永远只输入别名。这是一条很土但极其有效的经验。

5.3 编译警告和错误混在一起时,Agent 分不清轻重

AI Agent 在理解日志时,最大的困惑是分不清“警告”和“错误”。Unity 日志里有一堆Shader warning.NET Standard compatibility warning,如果真的让 Agent 逐条判断,它会把大量时间耗在无关紧要的警告上。

我在桥接层里做了一个简单的“日志分级摘要”,把错误、警告、普通消息分开统计,并且只把错误部分的上下文提供给 Agent,警告只给计数。比如:

{ "error_count": 2, "warning_count": 13, "errors": [ { "line": 1245, "message": "error CS0246: The type or namespace name 'XXX' could not be found" } ] }

Agent 看到这个摘要后,能第一时间聚焦到真正的错误上。如果它觉得关联的上下文不够,可以自己去读取原始日志的某一行范围。这套分治思路,让 Agent 在处理“信息噪音”时的表现大幅提升。

5.4 测试用例偶发失败与重试机制

PlayMode 测试因为需要启动场景、加载资源、有时还要连服务器,经常会出现偶发失败。这种失败最折磨人,因为它在本地跑十次都没事,一到自动化环境就跑挂一次。AI Agent 如果看到偶发失败就直接报告“测试不过”,很容易误导开发者。

我的解决方案是在测试命令里加了一个--retry 2参数,对每个失败的测试用例自动重跑两次,两次都失败才判定为真正失败。这虽然是“土办法”,但对工具链的稳定性提升非常明显。Agent 拿到最终结果时,也能知道某个用例是“首次失败后重试通过”还是“重试仍然失败”,判断结论更贴近真实状态。

5.5 桥接脚本本身也要有日志

最后一条经验,是关于桥接脚本自身的可观测性。我在做工具链初版时,一度只关注 Unity 日志,完全没有记录桥接层发生了什么。结果一旦出问题,根本不知道是 Agent 发错了参数,还是 Python 脚本解析出错,又或者是 Unity 自己失败了。

后来我在桥接脚本里引入了自己的日志文件bridge_<date>.log,每收到一条命令、每解析一个参数、每执行一个 subprocess,都会写进这个日志。排查问题的时候,先看桥接日志,再看 Unity 日志,能非常快地定位到责任边界。这个小动作,可能是我整个项目里投资回报率最高的一步。

6. 扩展:从“能用”到“好用”的一些经验

6.1 给 AI Agent 定义明确的工具边界

最开始我试图让 Agent 能操作的东西非常庞大——既能打包,又能调 Shader,还想让它直接改 PlayerSettings。后来发现这完全是个灾难,因为它会在一次任务里连续做多个操作,一旦中间出了问题,整个人都懵了。

防御式设计很重要。我现在给 Agent 提供的工具只有四个:buildtestassetbundlestatus。其余操作一律不让 Agent 直接执行,如果有需要,它只能输出“建议命令”供人工执行。工具边界收窄以后,Agent 的表现稳定了很多,因为它在已有能力内做决策,而不是随意尝试。

6.2 与 Unity 编辑器其他问题的联动

在跑自动化测试时,我经常碰到像“Unity 阴影问题怎么排查”或“Unity 串口通信在测试环境里不稳定”这样与工具链主线无关的诉求。这些话题虽然在搜索结果里很火,但在技术工具链的语境下,它们都应该被当作“被测试对象”而不是“工具链自身功能”。

我建议在工具链里为这类运行时行为预留日志采集入口,比如通过Application.logMessageReceived去捕获运行时日志。这样一旦 PlayMode 测试暴露了阴影渲染或通信相关的问题,工具链也能顺藤摸瓜把完整上下文抓到测试报告里。这种“多留一手”的做法,能让工具链的排查范围比一般 CI 要大得多,因为你把运行时问题也纳入了可观测范围。

6.3 未来可以补充的走向

如果后续继续演进,我更倾向于把这个工具链与常规的“代码评审”打通,让 AI Agent 在构建通过之后直接去阅读未提交的代码 diff,结合编译警告信息给出评审意见。现在这套“编译-测试-报告”的基础设施,完全能为那种更深度的任务提供支撑。

另外,针对不同平台(微信小游戏、Android、iOS)的打包产物差异,我也打算在状态文件里补充更多平台相关的验证逻辑。尤其是微信小游戏这种特殊运行环境,单靠 Unity 的常规构建日志还不够,工具链里得要专门的产物体检脚本,比如检查视频播放方案需要的远程资源路径是否正确。这套思路在这些场景里其实完全一样:先定义好“什么算成功”,然后把判断交给 Agent,把精力留给人。

实操到这个程度,我最大的体会是,AI Agent 真正适合干的不是决策,而是“带上下文的重复验证”。它不需要多聪明,但它能记住上一次的结果、快速解读日志、按规则重试,就已经帮我省下了大量机械劳动。如果你也在折腾 Unity 工具链,建议先别追求大而全,先把“编译一把跑通、测试一眼看懂、结果一屏存好”做到位,再考虑让 AI 进来。这一套基础打好了,后面接什么 Agent 体感都顺滑很多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 18:38:30

Node.js+Vue+Express旅游景点推荐系统从零实战指南

前阵子帮人搭了一套基于 Node.js Vue Express 的旅游景点推荐系统&#xff0c;从环境配置、数据库设计、推荐算法到前后端联调&#xff0c;完整走了一遍。这类项目在个人作品集、毕设和中小型团队内部工具里出镜率非常高&#xff0c;技术栈看着简单&#xff0c;但真要跑通一个…

作者头像 李华
网站建设 2026/9/18 18:36:56

Arxiv论文批量导入NoteExpress:题录、PDF下载与文献管理自动化流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 18:36:46

IIS安装与配置完全指南:从零搭建网站发布环境

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 18:35:45

STM32F407嵌入式网络系统重构:FreeRTOS+LwIP资源协同设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 18:31:54

走兼容通道 TaoToken,Hermes Agent 记忆调用能通吗?

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华