文件系统即接口:发现与编译
30 秒导读: eve 是一个"文件系统优先"的后端 agent 框架。你不写一份大配置文 件来声明 agent 有哪些工具、技能、指令、入口——你把文件放到约定好的目录里,文件的路径就决定了它是什么。
agent/tools/refund.ts自动成为一个名叫refund的工具;agent/instructions.md自动成为系统提示。本章讲清楚这套"约定优于配置"的两段流水线:discover(读目录 → 产出 manifest)和 compile(规整 manifest → 写进.eve/)。
本章只讲静态结构:目录怎么被解读成一个 agent 的"形状"。运行时怎么真正跑这个 agent(session/turn/step、harness 循环)是 02 章 和 03 章 的事;instructions / skills / subagents 在运行时如何被注入上下文见 04 章;channels / connections 的前门与安全语义见 05 章。
1. 这是什么(零基础也能懂)
一句话定义
eve 用目录结构当 agent 的声明式接口。 你不调用 addTool(...)、不维护一个 agent.config.json 里的工具数组;你把一个文件丢进 agent/tools/,它就成了一个工具。文件路径 = 它的身份。
解决什么问题 / 给谁用
假设你要做一个后端 agent:它有一段系统提示、几个自定义工具、几个技能、一个对外的 HTTP 入口。传统做法是写一份中心配置,手动把每个东西注册进去——配置和文件两头对账,容易漂移(文件改了名,配置忘了改)。
eve 的取舍是:让文件系统本身当那份配置。 约定好"哪个目录放什么",于是:
- 写工具 = 在
agent/tools/下放一个.ts。 - 写技能 = 在
agent/skills/下放一个SKILL.md包。 - 改系统提示 = 编辑
agent/instructions.md。
没有"注册"这一步,也就没有"忘了注册"这个 bug。这套思路对熟悉 Next.js 文件路由(pages/about.tsx 自动变成 /about 路由)的人会非常眼熟——eve 把同一个直觉推广到了 agent 的所有组成部件。
用起来什么样
一个最小 agent 的目录长这样(flat 布局):
my-agent/
├── package.json ← 项目标记文件(让 eve 认出这是个 app)
└── agent/
├── instructions.md ← 系统提示(必需)
├── agent.ts ← 可选的 agent 配置模块(模型、构建选项…)
├── tools/
│ ├── refund.ts → 工具 "refund"
│ └── billing/
│ └── invoice.ts → 工具 "billing-invoice"(路径被拍平成 slug)
├── skills/
│ └── triage/
│ └── SKILL.md → 技能 "triage"
└── channels/
└── webhook.ts → 一个 HTTP 入口
你不需要在任何地方"列出"这些工具/技能;eve 扫描目录就知道了。
一句话直觉
把 agent/ 目录当成一张填空表:每个约定目录是一个"槽位"(slot),你往里放文件,就是在填这张表。 discover 是"读表的人",compile 是"把表誊抄成机器能读的卡片的人"。
2. 顶层全景(它大概怎么转)
从"一个目录"到"一份机器可读的 agent 描述",要走两段流水线。先看整体数据流(从左到右,每个方框是一个阶段,产物用 → 标出):
起始路径 (cwd)
│
▼
┌─────────────────────┐
│ resolveDiscovery │ 向上走父目录,找到 appRoot + agentRoot
│ Project │ → { agentRoot, appRoot, layout: flat|nested }
└─────────┬───────────┘
│
▼
┌─────────────────────┐
│ discoverAgent │ 扫 agentRoot 的每个 slot,逐目录套语法规则
│ (走 grammar slots) │ → AgentSourceManifest(还没加载任何模块)
└─────────┬───────────┘
│
▼
┌─────────────────────┐
│ compileAgentManifest│ 规整每个 slot:加载模块、派生工具名、组合指令
│ (normalize-*) │ → CompiledAgentManifest
└─────────┬───────────┘
│
▼
┌─────────────────────┐
│ writeCompilerArtifacts│ 序列化 + 写盘
└─────────┬───────────┘
│
▼
.eve/discovery/*.json
.eve/compile/*.json + module-map.mjs
各部件一句话职责:
| 部件 | 干什么 | 文件 |
|---|---|---|
resolveDiscoveryProject | 从任意起点向上找到 app 根与 agent 根,判定 flat/nested | src/discover/project.ts:52 |
discoverAgent | 按 slot 语法扫 agent 根,产出未加载模块的 source manifest | src/discover/discover-agent.ts:59 |
| grammar / slots | 定义每个 slot 的命名/字符集规则、碰撞检测 | src/discover/grammar.ts、src/discover/slots.ts |
classifyAgentRootEntry | 把目录里的每个条目归类成某个 slot(或 "unknown") | src/discover/filesystem.ts:116 |
createAgentSourceManifest | 把所有 slot 结果装进一个版本化的 manifest | src/discover/manifest.ts:251 |
compileAgentManifest | 规整:加载模块、派生名字、组合静态指令 | src/compiler/normalize-manifest.ts:31 |
writeCompilerArtifacts | 把编译后的 manifest 等写进 .eve/ | src/compiler/artifacts.ts:201 |
两段分工的关键: discover 只读目录结构,不 import 任何 authored 模块(注释明说 "without importing authored modules",discover-agent.ts:55-58)。它产出的 AgentSourceManifest 里只有"在 tools/refund.ts 有个工具"这种引用,而非工具的真实定义。真正执行 refund.ts、读出它的 description/schema,是 compile 阶段的事。这条分界让 discover 又快又纯——可以在内存树上跑、可以被静态分析,不触发任何用户代码副作用。
3. 核心原理之一:从起点找到 agent 根(resolve)
它要解决的小问题
你可能在项目里任何子目录下运行 eve(比如 my-agent/agent/tools/ 里)。eve 得先回答:"这个 agent 的根在哪?整个 app 的根又在哪?"
思路
像 git 找 .git、Node 找 node_modules 一样:从当前目录开始,一层层往上走父目录,直到认出一个 agent 根。 eve 支持两种布局,resolve 在每一层依次试三种判定:
当前目录每往上走一层,依次问三个问题(命中即停):
①「我自己叫 agent/,且父目录有 package.json?」
→ nested 布局:agentRoot=我, appRoot=父
②「我有 package.json,且我下面有个 agent/ 子目录?」
→ nested 布局:agentRoot=我/agent, appRoot=我
③「我直接就含 agent 的 slot 文件(instructions / tools/ …)?」
→ flat 布局:agentRoot=appRoot=我
都不命中 → 向上一层,重试;到文件系统根还没命中 → 报 PROJECT_NOT_FOUND
这三问对应 resolveDiscoveryProject 主循环里的三次尝试(project.ts:60-94):tryResolveNestedProjectFromAgentDirectory(project.ts:116)、tryResolveNestedProjectFromAppRoot(project.ts:137)、isFlatAgentRoot(project.ts:158)。
flat vs nested 两种布局
| 布局 | 形状 | agentRoot | appRoot |
|---|---|---|---|
| flat | my-agent/ 直接放 instructions.md、tools/… | = appRoot | 同一目录 |
| nested | my-agent/ 放 package.json,agent 内容收进 my-agent/agent/ | my-agent/agent | my-agent |
nested 让 agent 的源文件和项目的其它东西(package.json、构建脚本、.eve/)分层;flat 适合"整个项目就是一个 agent"的极简场景。
关键细节:什么算"项目标记"
nested 的判定依赖"父目录是不是一个 app 根"。判定标准很窄——目录里有 package.json 或 vercel.json 这两个文件之一(PROJECT_MARKER_FILE_NAMES,filesystem.ts:20;判定在 isProjectMarkerEntry,filesystem.ts:109)。vercel.json 出现在这里点出了 eve 的部署目标:Vercel 上的后端 agent。
而 flat 的判定(isFlatAgentRoot,project.ts:158)是:目录里至少有一个条目被归类成已知 slot,且这个 slot 不是 unknown 也不是单纯的 lib/(只有 lib/ 不足以认定为 agent 根)。
4. 核心原理之二:slot 语法——一个文件如何获得身份
这是本章的心脏。slot 是 eve 对"agent 根下某个约定位置"的称呼。discover 扫到 agent 根的每个条目,先用 classifyAgentRootEntry(filesystem.ts:116)给它归类,再交给对应 slot 的发现逻辑。
4.1 slot 总览
agent 根下支持的 slot,以及它们的形态:
| slot | 文件系统形态 | 产出 | 必需? |
|---|---|---|---|
| instructions | instructions.md / instructions.{ts,…} / instructions/ 目录 | 系统提示 | 是 |
| agent 配置 | agent.{ts,cts,mts,js,cjs,mjs} | agent 级配置(模型、构建…) | 否 |
| tools | tools/ 目录(递归) | 工具 | 否 |
| hooks | hooks/ 目录(递归) | 生命周期钩子 | 否 |
| channels | channels/ 目录(递归) | HTTP 入口 | 否 |
| connections | connections/ 目录 | 外部连接(file 形或 folder 形) | 否 |
| skills | skills/ 目录(包形或 flat) | 按需技能 | 否 |
| schedules | schedules/ 目录(递归) | 定时任务 | 否 |
| sandbox | sandbox/ 目录 或 sandbox.{ts,…} | 沙箱定义 + workspace | 否 |
| subagents | subagents/ 目录(递归) | 子 agent | 否 |
| lib | lib/ 目录 | 包内辅助模块 | 否 |
discoverAgent(discover-agent.ts:59)把这些 slot 一个个发现完,再装进 manifest——可以把它读成一份"目录契约清单"。归类成 unknown 的目录会被忽略并发一条 warning("Ignoring unsupported directory…",discover-agent.ts:67-76),不会让构建失败。
4.2 两种文件:markdown vs module
一个 slot 的内容可以是两种来源之一:
- module(
.ts/.cts/.mts/.js/.cjs/.mjs):一段导出defineXxx({...})的代码。支持的扩展名列在SUPPORTED_AUTHORED_MODULE_FILE_EXTENSIONS(filesystem.ts:7)。 - markdown(
.md):带 frontmatter 的纯文本,用于 instructions / skills / schedules。
discover 阶段只引用文件,不读 module 的内容(只有 markdown 会被立刻解析 frontmatter,因为那是纯数据)。
4.3 instructions:必需槽 + 废弃回退
instructions 是唯一必需的 slot,它的发现逻辑(discoverInstructionsSource,grammar.ts:171)最能体现 eve 对"约定 + 兼容"的处理,支持三种形态:
按以下顺序找 instructions(命中即停):
1. instructions/ 是目录?
→ 目录形:收集里面所有 .md / .ts;
若同时还有顶层 instructions.md/.ts,它排在最前
2. instructions.md 或 instructions.{ts,…} 存在?
→ flat 文件形(单个来源)
3. 都没有,但有 system.md / system.{ts,…}?
→ 【废弃回退】仍然能用,但发一条 deprecation warning,
提示改名为 instructions.*
4. 全都没有?
→ 报错 DISCOVER_REQUIRED_INSTRUCTIONS_MISSING
真实代码里第 3 步的废弃逻辑很清晰:找到 system.* 时,它照常返回 instructions,但额外塞一条 DISCOVER_DEPRECATED_SYSTEM_SLOT 警告(grammar.ts:232-246),措辞是"运行时暂时还加载这个旧槽,但未来版本会移除"。第 4 步的缺失错误(grammar.ts:262-272)会逐字列出它接受的所有文件名,对作者很友好。
system 槽的废弃常量本身也带文档注释,说明它"resolves successfully; the warning prompts the author to rename the file"(grammar.ts:30-36)。
4.4 命名 charset:为什么 tool 名和 channel 名规则不同
每个 slot 对文件名的合法字符集有不同的正则,这不是随意的——规则反映了这个名字最终会流到哪里:
| slot | 正则常量 | 规则 | 为什么 |
|---|---|---|---|
| tools | TOOL_SLUG_PATTERN (grammar.ts:107) | 字母开头,字母/数字/_/-,≤64 | 文件名逐字就是给模型看的 tool name;64 是各家 provider 最严的上限 |
| hooks | HOOK_SLUG_PATTERN (grammar.ts:128) | 同 tool 字符集 | 内部 slug,无 URL 语义,不许 [param] 形 |
| connections | CONNECTION_SLUG_PATTERN (grammar.ts:114) | 小写 kebab,字母开头 | 不直接暴露给模型,用更克制的小写规则 |
| channels | CHANNEL_SLUG_PATTERN (grammar.ts:119) | 小写 kebab,可带前导 .,或 [sessionId] 参数形 | 段会变成 URL 路径段,所以允许 .well-known 和路径参数 |
tool 名的设计取舍很值得注意(normalize-tool.ts 的注释,normalize-tool.ts:23-34):tool 是唯一一个会把嵌套目录拍平成单段 slug 的 slot——tools/billing/refund.ts 编出来的 tool 名是 billing-refund,因为大多数 provider 拒绝 tool 名里出现 /。而且不允许在代码里写 name 字段覆盖——文件名就是名字,想要 snake_case 就把文件命名成 snake_case。这是"约定优于配置"最锋利的一刀:连"给工具起名"这个自由都收走了,以换取"路径 = 身份"的绝对一致。
charset 校验在 discover 阶段就发生——discoverNamedSourceDirectory 的调用方把 createToolNameDiagnostic 等当作 validateSegment 传进去(discover-agent.ts:132-141),在 compiler 加载模块之前就把非法文件名挡掉(grammar.ts:407-422 的注释明说这一点)。
4.5 碰撞 diagnostics:同一个槽不能有两个来源
"约定优于配置"的代价是:必须无歧义。如果一个 slot 同时被两个文件填了,eve 不会猜,而是报错。碰撞分两类:
- slot collision(markdown + module 都填了同一槽):
DISCOVER_SLOT_COLLISION,如instructions.md和instructions.ts并存(grammar.ts:291-300、grammar.ts:531-541)。 - module slot collision(同名不同扩展的多个 module):
DISCOVER_MODULE_SLOT_COLLISION,如refund.ts和refund.js并存(grammar.ts:302-312、grammar.ts:546-556)。
每条碰撞 diagnostic 都把冲突的文件名列出来,作者一眼能定位。
4.6 flat vs nested(在目录内部)与 named-source-directory 走法
除了 4.3 的 instructions,其余目录型 slot(tools/、channels/、hooks/、schedules/、lib/)共用一个通用遍历器 discoverNamedSourceDirectory(named-source-directory.ts:108)。它的行为由两个开关控制:
recursive:true时深入子目录(channels/hooks/schedules/tools 都递归);子目录优先于叶子文件遍历,产出深度优先 + 同层字典序的稳定顺序(named-source-directory.ts:99-107注释)。allowMarkdown:true时除 module 外还收.md(只有schedules/和instructions/用到)。
每下钻一层,目录段名也会过一遍 validateSegment(named-source-directory.ts:238-244),所以非法的子目录名同样会被挡下,而不是只校验叶子文件。
4.7 一个特例:connections 的 file 形 vs folder 形
connections 是少数允许两种布局的 slot(connections.ts:51):
- file 形:
connections/linear.ts→ 连接名linear。 - folder 形:
connections/linear/connection.ts→ 连接名也是linear,文件夹名就是连接名。
两种形态混用同一个名字会触发 DISCOVER_CONNECTION_FILE_FOLDER_COLLISION(connections.ts:136-154);folder 形里缺 connection.ts 会触发 DISCOVER_CONNECTION_FOLDER_EMPTY(connections.ts:180-191)。manifest 里的 ConnectionSourceRef 会显式带上 connectionName(manifest.ts:36-43),省得 compiler 再从路径反推。
5. 核心原理之三:manifest——发现的产物
discover 的最终产物是一个 AgentSourceManifest(manifest.ts:138),由 createAgentSourceManifest(manifest.ts:251)装配。它是个版本化结构(AGENT_SOURCE_MANIFEST_VERSION = 12,manifest.ts:23),每个 slot 一个字段:instructions、tools、channels、connections、skills、schedules、sandbox、hooks、lib、subagents…
它的关键性质:
- 只含引用,不含定义。 每个条目是个
ModuleSourceRef(有logicalPath/sourceId/ 可选exportName),指向"哪个文件",而不是文件导出了什么。 - 诊断被汇总进去。
diagnosticsSummary由summarizeDiscoverDiagnostics统计 error/warning 数,挂在 manifest 上(manifest.ts:262)。 - agentId 从路径派生。
deriveAgentIdFromRoots(manifest.ts:290)优先用 app 的package.json的name(去掉 npm scope),否则退回basename(appRoot)。注释解释了为什么:CI 里工作目录常是/vercel/path0这种合成路径,直接取 basename 会得到没意义的path0,所以优先信 package name。
logicalPath 全程用 normalizeLogicalPath(filesystem.ts:320)统一成正斜杠、去掉前导 ./——保证 manifest 在不同 OS 上一致。
subagents:递归的 manifest
subagents/ 里每个子 agent 自己又是一份小型 manifest(LocalSubagentSourceRef.manifest,manifest.ts:62-69)。discoverSubagents(discover-subagent.ts:74)对每个子 agent 包重跑一遍同样的 slot 发现(tools/hooks/instructions/sandbox…),并能再向下递归(discover-subagent.ts:276-281)。两处差异:子 agent 的 agent.{ts,…} 配置模块是必需的(discover-subagent.ts:211-221),且子 agent 不许定义 schedules/(会报 DISCOVER_LOCAL_SUBAGENT_SCHEDULES_INVALID,discover-subagent.ts:317-337)。这点呼应了 04 章 讲的 subagent 边界。
6. 核心原理之四:compile——把 manifest 誊抄成机器卡片
compileAgent(compile-agent.ts:62)把三步串起来:resolve → discover → writeCompilerArtifacts。中间真正的"规整"在 compileAgentManifest(normalize-manifest.ts:31)。
compile 干的几件实事
discover 留下的是"引用";compile 负责把引用变成可执行的定义,核心动作:
- 加载 module 并规整。 对每个 tool/channel/skill/instruction 源,真正 import 模块、读出
defineXxx的产物,跑对应的normalize-*(normalize-manifest.ts:83-198)。这是第一次真正执行 authored 代码。 - 派生最终名字。 tool 名在这里从
logicalPath算出:stripLogicalPathExtension去扩展名、去tools/前缀、把/换成-(normalize-tool.ts:49-51)。 - 分流静态/动态。 一个 tool 源可能是真工具、是"禁用某个框架默认工具"的标记、是"开启 workflow"的开关、或一个运行时动态 tool 解析器——compile 把它们分到
tools/disabledFrameworkTools/workflowEnabled/dynamicTools(normalize-manifest.ts:83-103)。instructions 同理分静态/动态(normalize-manifest.ts:132-146)。 - 组合多份静态 instructions。 若
instructions/目录有多个文件,它们的 markdown 会被用\n\n拼成一份(normalize-manifest.ts:148-159)。 - 递归编译 subagent 图。
compileSubagentGraph把子 agent manifest 也编出来,形成 nodes/edges(normalize-manifest.ts:38-45)。
产物是 CompiledAgentManifest——运行时直接吃这份。注意 channel 的一个细节:一个 channel module 的每条 route 会展开成一个独立的 compiled channel 条目(normalize-channel.ts:51-62),URL 路径来自 route 的 path,channel 名来自文件路径。
compile 把东西写到哪
writeCompilerArtifacts(artifacts.ts:201)把产物落到 app 根下的 .eve/,路径由 resolveCompilerArtifactPaths(artifacts.ts:117)固定:
| 产物 | 路径 | 内容 |
|---|---|---|
| discovery manifest | .eve/discovery/agent-discovery-manifest.json | discover 的原始 source manifest |
| diagnostics | .eve/discovery/diagnostics.json | 所有 error/warning + 汇总 |
| compiled manifest | .eve/compile/compiled-agent-manifest.json | 规整后、运行时直接读的 manifest |
| module map | .eve/compile/module-map.mjs | logicalPath → 真实 import 的映射 |
| channel 类型 | .eve/compile/channel-instrumentation-types.d.ts | channel 的 TS 类型声明 |
| compile metadata | .eve/compile/compile-metadata.json | 各产物的 sha256 摘要 + status |
compile-metadata.json 给每个产物记 sha256,并算一个 sourceGraphHash(artifacts.ts:185);status 是 failed 还是 ready 取决于有没有 error(artifacts.ts:193)。
失败语义:产物先写、再抛错
一个值得借鉴的设计:即使 discover 有错,compileAgent 也会先把产物(含 diagnostics)写盘,再抛 CompileAgentError(compile-agent.ts:66-81)。这样错误信息既进了 stderr,也落了盘——工具链可以去读 diagnostics.json 而不必解析异常文本。warning(如废弃的 system 槽)则只打印、不阻断(compile-agent.ts:88-98)。
7. 巧妙之处(可借鉴的技术)
-
ProjectSource 抽象让发现可在内存树上跑。 所有文件系统读取都走一个
ProjectSource接口,默认是磁盘实现(createDiskProjectSource),但测试能传一个内存实现跑整套发现(discover-agent.ts:38-45注释)。发现逻辑因此纯函数化、易测、无副作用。 -
discover 不 import 用户模块。 把"读 结构"和"执行代码"切成两段(§2),让 discover 又快又安全;只有 compile 才触发 authored 代码。
-
charset 规则跟着"名字流向哪里"走。 tool 名直进模型 API → 用 provider 最严的字符集和长度;channel 段进 URL → 允许
.well-known和[param](§4.4)。规则不是统一的,而是贴着下游约束定的。 -
tool 名零覆盖、强制路径派生。 不给
name字段留后门(normalize-tool.ts:34),用"嵌套拍平成-"换"路径=身份"的绝对一致——把约定贯彻到底。 -
agentId 优先信 package name 而非 basename。 一行小判断躲开了 CI 合成路径(
/vercel/path0)产出垃圾 id 的坑(manifest.ts:290-306)。 -
产物先写后抛。 失败也留下机器可读的 diagnostics 落盘,工具链不必解析异常字符串(§6)。
8. 边界与局限(诚实)
-
必需且唯一的 instructions。 没有 instructions(且没有废弃的
system.*)直接构建失败;同一槽两个来源也直接失败——eve 绝不猜,无歧义是硬约束。 -
system.*是过渡期产物。 它现在还能用但已废弃(grammar.ts:30-36),会发 warning,未来移除。读老项目时会撞到。 -
unknown目录被静默忽略(只 warn)。 放错位置的目录不会报错、只发一条 warning(discover-agent.ts:67-76),所以"我加了个工具怎么没生效"常常是放进了非约定目录——得去看 warning。 -
subagent 不能有 schedules、必须有
agent.*配置。 子 agent 的 slot 集合是主 agent 的子集,有额外约束(§5);这是刻意的边界,不是 bug。 -
本章不覆盖运行时。 manifest/产物怎么被运行时加载执行——session 持久化、harness 循环、上下文注入——分别见 02 / 03 / 04 / 05。
9. 代码地图(导航索引)
| 主题 | 文件路径 | 符号名 |
|---|---|---|
| 解析 app/agent 根 + flat/nested | src/discover/project.ts | resolveDiscoveryProject |
| nested 三问之一/二/三 | src/discover/project.ts | tryResolveNestedProjectFromAgentDirectory / tryResolveNestedProjectFromAppRoot / isFlatAgentRoot |
| 项目标记文件 | src/discover/filesystem.ts | PROJECT_MARKER_FILE_NAMES / isProjectMarkerEntry |
| 根条目归类成 slot | src/discover/filesystem.ts | classifyAgentRootEntry |
| 主发现入口(走所有 slot) | src/discover/discover-agent.ts | discoverAgent |
| instructions 槽(三形态 + 废弃回退) | src/discover/grammar.ts | discoverInstructionsSource |
| tool/channel/connection/hook charset | src/discover/grammar.ts | TOOL_SLUG_PATTERN / CHANNEL_SLUG_PATTERN / CONNECTION_SLUG_PATTERN / HOOK_SLUG_PATTERN |
| charset 校验诊断 | src/discover/grammar.ts | createToolNameDiagnostic / createChannelNameDiagnostic |
| 碰撞诊断 | src/discover/grammar.ts | createSlotCollisionDiagnostic / createModuleSlotCollisionDiagnostic |
| 通用目录遍历(flat/nested/markdown) | src/discover/named-source-directory.ts | discoverNamedSourceDirectory |
| 槽候选收集 | src/discover/slots.ts | collectFlatSlotCandidates / collectNamedSlotCandidates |
| connections file/folder 形 | src/discover/connections.ts | discoverConnectionSources |
| skills 包形/flat 形 | src/discover/skills.ts | discoverSkills |
| subagent 递归发现 | src/discover/discover-subagent.ts | discoverSubagents |
| source manifest 结构 + 版本 | src/discover/manifest.ts | AgentSourceManifest / AGENT_SOURCE_MANIFEST_VERSION |
| agentId 派生 | src/discover/manifest.ts | deriveAgentIdFromRoots |
| 路径规整 | src/discover/filesystem.ts | normalizeLogicalPath / getSupportedModuleBaseName |
| compile 入口(resolve→discover→write) | src/compiler/compile-agent.ts | compileAgent |
| manifest 规整(加载模块、分流) | src/compiler/normalize-manifest.ts | compileAgentManifest |
| tool 名派生 + 路径拍平 | src/compiler/normalize-tool.ts | compileToolEntry |
| instructions 组合/动态分流 | src/compiler/normalize-instructions.ts | compileInstructionsEntry |
| channel route 展开 | src/compiler/normalize-channel.ts | compileChannelDefinition |
| 写 .eve/ 产物 + 路径布局 | src/compiler/artifacts.ts | writeCompilerArtifacts / resolveCompilerArtifactPaths |