news 2026/9/9 12:56:11

识别 AI Coding Agent:解析 Cypress `@packages/agent-info` 环境指纹检测的实现与扩展

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
识别 AI Coding Agent:解析 Cypress `@packages/agent-info` 环境指纹检测的实现与扩展

识别 AI Coding Agent:解析 Cypress@packages/agent-info环境指纹检测的实现与扩展

【免费下载链接】cypressFast, easy and reliable testing for anything that runs in a browser.项目地址: https://gitcode.com/GitHub_Trending/cy/cypress

@packages/agent-info是 Cypress 仓库中一个刻意保持"小而纯"的内部包,它只回答一个问题:当前这个 Cypress 进程是否由 AI 编程代理(AI coding agent)调用,以及具体是哪一个。它通过比对进程环境块中的特征变量(如CLAUDECODEGEMINI_CLICURSOR_AGENT),返回一个来自封闭集合的固定名称(claudecodexcursor等)。本文以 AGENTS.md 为主线,结合 lib/index.ts、测试用例与 CLI 消费端,完整讲解其设计动机、源码结构、检测规则、调用方式以及"如何新增一个 Agent"的操作指南,供需要在自有工具链中实现或扩展 Agent 指纹识别的开发者参考。

为什么要单独做一个"Agent 识别"包

随着 Claude Code、Codex、Cursor 等 AI 编程代理被越来越多人用来直接驱动命令行工具,Cypress 这类 CLI 工具需要回答两个问题:

  1. 当前进程是不是某个 Agent 派生的子进程?
  2. 如果是,它来自哪个 Agent?

Agent 自身会通过环境变量暴露身份——CLAUDECODEGEMINI_CLICURSOR_AGENTAI_AGENT等等。agent-info做的事情就是"对环境做指纹比对":把环境对象与一张标记变量表(detection table)做匹配,输出一个固定名称。

关键约束在于输出值会被上报离开用户机器(CLI 会将其作为遥测数据的一部分发送),所以返回值必须来自封闭集合,而不是环境里任意字符串的原样透传——这正是本包存在的核心价值。AGENTS.md 的第一段即明确了这一点:"It fingerprints the environment block against a table of marker variables and returns a fixed name from a closed set."

设计基石:纯函数、零依赖的 TypeScript

该包被刻意设计为纯净、无运行时依赖的 TypeScript:

  • 不碰fs、不碰http
  • 无任何运行时依赖(package.jsondependencies为空,仅含 devDependencies);
  • 不读取除"被传入的环境对象"之外的任何东西。

正因为如此,Node 侧的各个包以及cypressCLI 才能放心地把它打进 bundle,而不会引入新的运行时依赖。从 package.json 可以看到包入口为dist/index.js,构建产物仅含dist目录,类型声明由dist/index.d.ts提供。

整体架构:一个文件承载全部逻辑

AGENTS.md 的 Architecture 一节指出:整个包就是 lib/index.ts 一个文件,它包含三大部分:

组成部分作用
AgentName联合类型可返回的封闭名称集合
AGENTS检测表名称 → 一组环境检查条件的映射
detectAgent()/isAgent()对外入口,扫描环境并给出结论

配套的还有 lib/spec/index.spec.ts 中的 Vitest 测试,以及面向使用者的 README.md(其同时被 CLAUDE.md 以@AGENTS.md方式引用,仓库内约定以 AGENTS 文档为单一事实来源)。

AgentName:封闭的名称集合

源码第 1–14 行定义了AgentName

export type AgentName = | 'auggie' | 'claude' | 'codex' | 'cursor' | 'devin' | 'gemini' | 'goose' | 'junie' | 'kiro' | 'opencode' | 'other' | 'pi' | 'replit'

detectAgent()只可能返回这些名称之一,或undefined。其中'other'是兜底值:当通用变量AI_AGENT存在但表内无法识别时返回,代表"确实有 Agent,但无法命名"。

AGENTS检测表:字符串标记与函数式检查

EnvCheck支持两种检查方式(源码第 16–48 行):

  • 裸环境变量名(字符串):只要该变量存在且有值即为命中,如['claude', ['CLAUDECODE', 'CLAUDE_CODE']]
  • envMatcher(key, regex)函数:变量"存在"不足以说明问题、还需校验取值格式时使用,例如pi需要检查PATH中是否含\.pi[\\/]agent模式、devin需要检查EDITOR是否以(^|[\\/])devin(\.exe)?$结尾、kiro需要在无 TTY 前提下检查TERM_PROGRAM是否匹配/kiro/

整张表(含所有分支变量)如下:

Agent环境标记 / 检查条件
claudeCLAUDECODECLAUDE_CODE
replitREPL_ID
geminiGEMINI_CLI
codexCODEX_SANDBOXCODEX_THREAD_ID
opencodeOPENCODE
pienvMatcher('PATH', /\.pi[\\/]agent/)
auggieAUGMENT_AGENT
gooseGOOSE_PROVIDER
junieJUNIE_DATAJUNIE_SHIM_PATH
devinenvMatcher('EDITOR', /(^\|[\\/])devin(\.exe)?$/)
cursorCURSOR_AGENT
kiroenvMatcher('TERM_PROGRAM', /kiro/, { noTTY: true })

表序是有意设计的:IDE(如 Cursor)排在最后,以确保"运行在 IDE 内部的 Agent 被识别为 Agent 而非 IDE"——因为同一环境里可能同时出现CURSOR_AGENTCLAUDECODE,先命中claude行便立即返回,这由测试'reports the agent rather than the IDE hosting it'明确验证。

detectAgent()isAgent():两个入口

export const detectAgent = (env: NodeJS.ProcessEnv = process.env): AgentName | undefined => { for (const [name, checks] of AGENTS) { for (const check of checks) { if (typeof check === 'string' ? env[check] : check(env)) { return name } } } return env.AI_AGENT ? fromAiAgent(env.AI_AGENT) : undefined } export const isAgent = (env: NodeJS.ProcessEnv = process.env): boolean => { return !!detectAgent(env) }

逻辑非常直接:先顺序遍历AGENTS表(每个名称下依次尝试各组检查,字符串标记做存在性判断,函数标记执行匹配),全部未命中后再看通用的AI_AGENT变量。两入口均接受可选参数env,默认为process.env

fromAiAgent():把自由文本"收窄"成固定名

AI_AGENT是自由格式且常带版本号——例如 Claude Code 会设置形如claude-code_2-1-221_agent的值。源码第 55–61 行先转小写,再用正则^${name}($|[^a-z0-9])从已知名称中寻找前缀命中:

const fromAiAgent = (value: string): AgentName => { const normalized = value.toLowerCase() // The name has to end where it ends, so a short one like `pi` cannot claim an // unrelated `pipecat`. return KNOWN_NAMES.find((name) => new RegExp(`^${name}($|[^a-z0-9])`).test(normalized)) ?? 'other' }

这个边界正则防止了短名称误伤:pi不会"认领"不相关的pipecatpipecatpi之后是p,属于[a-z0-9],不满足$|[^a-z0-9])。该行为由测试'reports a value that only begins with a known name as other'覆盖。

API 使用方式

README.md 给出两个入口的完整说明,可直接在 Node/TS 环境中使用:

import { detectAgent } from '@packages/agent-info' detectAgent() // 在 Claude Code 下运行 → 'claude' detectAgent({ GEMINI_CLI: '1' }) // 'gemini' detectAgent({}) // undefined(未检测到任何 Agent)
  • detectAgent(env?: NodeJS.ProcessEnv): AgentName | undefined:检查传入环境(默认process.env),返回命中的AgentName,无任何 Agent 时返回undefined
  • isAgent(env?: NodeJS.ProcessEnv): boolean:仅返回布尔值,便于快速分支判断:
    import { isAgent } from '@packages/agent-info' if (isAgent()) { // 运行在某个 Agent 之下,无论是否被具名识别 }

由于包自身是纯净的,你甚至可以传入任意手工构造的环境对象做单元测试或离线演练,而完全不需要真的启动一个 Agent。

CLI 中的实际消费点:遥测事件里的agent字段

agent-info的真实消费者是cypressCLI。在 cli/package.json 中它以 workspace 依赖"@packages/agent-info": "0.0.0-development"被引用;检测结果在 cli/lib/tap/events.ts 中进入上报事件 payload:

const payload = { command: trace.command, flags: trace.flags.slice(0, MAX_REPORTED_FLAGS), agent: detectAgent(), sessionId: identity?.sessionId ?? undefined, userId: identity?.userId ?? undefined, exitCode, errorCode: trace.errorCode, durationMs: Date.now() - trace.startedAt, }

随后该 payload 通过 fetch POST 发送。这解释了 AGENTS.md 反复强调的一条铁律:只有固定名称允许离开机器(Only fixed names leave the machine)AI_AGENT被收窄成已知名或'other'后才可能被上报,绝不会把未经审查的环境字符串原样透传出去——否则一次简单的环境探测就可能泄露用户环境里的敏感值。

扩展指南:如何新增一个 Agent

AGENTS.md 明确给出新增 Agent 的标准流程:三处改动,全部位于 lib/index.ts 及其 spec 中

第 1 步:把名称加进AgentName联合类型(保持字母序)

export type AgentName = | 'auggie' // ... 在对应字母位置插入新名称,保持字母序 | 'myagent'

第 2 步:在AGENTS表中新增一行

const AGENTS: readonly (readonly [AgentName, readonly EnvCheck[]])[] = [ // ... ['myagent', ['MY_AGENT_VAR']], ]

选择判据:若某个变量"存在即足以证明"(例如该变量仅由对应 Agent 设置),直接用裸变量名;若变量在许多场景都会出现、仅靠存在性不足为凭,则用envMatcher(key, regex)校验取值。例如pi之所以用envMatcher('PATH', /\.pi[\\/]agent/),正是因为普通用户机器上也可能出现含.pi的路径;kiroenvMatcher('TERM_PROGRAM', /kiro/, { noTTY: true })则是因为需要叠加 TTY 门控。

第 3 步:在 lib/spec/index.spec.ts 的detects %s表格里新增一行

it.each([ // ... ['myagent', { MY_AGENT_VAR: '1' }], ])('detects %s', (name, env) => { expect(detectAgent(env)).toBe(name) expect(isAgent(env)).toBe(true) })

每次检测逻辑的调整都伴随 spec 更新,保证表格与联合类型、测试三者同步演化。

工程细节与设计陷阱(Gotchas)

AGENTS.md 记录了若干经过真实权衡得出的工程约定,直接决定了实现的形态:

1. 表序即优先级:IDE 必须排在最后

IDE 环境变量(如CURSOR_AGENT)会先于 IDE 内部的 Agent 变量被扫描的风险,因此实现把 IDE 相关的行放在表尾。意图是:在 IDE 内运行的 Agent,应被上报为 Agent 本身,而非承载它的 IDE。测试detectAgent({ CURSOR_AGENT: '1', CLAUDECODE: '1' })期望返回'claude',正是这一约定的回归保障。

2. 宁可用"锚定的具体标记",也不用"子串匹配"

对路径类变量做正则时,若匹配可发生在任意位置,则可能误伤——某位用户的主目录恰好以某个 Agent 命名就会被错判。因此必须锚定在路径分隔符和字符串结尾上:pi的正则用\.pi[\\/]agent(要求.pi后紧跟分隔符与agent),devin的正则(^|[\\/])devin(\.exe)?$要求匹配到行尾。测试'does not match devin in the path of an unrelated editor'验证了/home/devin/.local/bin/vim不会被误判为 devin。同时两个正则都使用[\\/]兼容 Windows 反斜杠路径,对应 Windows 风格 PATH/EDITOR 的两个测试用例。

3. TTY 门控:检测默认偏向"人类"

某些环境变量同时被IDE 的集成终端它的 CLI Agent设置。此时若该进程的 stdin 或 stdout 连着一个 TTY,基本可以断定有人在终端前操作,而不是 Agent 派生的子进程。因此envMatcher(..., { noTTY: true })会在任一端口为 TTY 时跳过该检查——两端都要查,因为重定向其一仍会留下另一个,典型如cypress run | tee log.txt(stdout 被管道占用的场景)。其背后的原则是:

把人类误标成 Agent,比漏掉一个 Agent 更糟糕。

测试套件通过操作process.stdout/process.stdinisTTY描述符来验证:TERM_PROGRAM: 'kiro'在挂上 stdout TTY 或仅 stdin 为 TTY(输出被管道化)时都不再命中;但同时给出CLAUDECODE: '1'时仍会识别为claude,因为显式的强标记不受 TTY 门控影响。

4. 空值语义:变量存在但为空 = 未设置

源码第 30 行return value ? regex.test(value) : false,字符串分支也只对 truthy 值命中。测试'ignores an env var that is set but empty'{ CLAUDECODE: '' })确认空字符串不会触发检测——这是对"环境变量被设置成空串是常见 shell 状态"的防御。

构建、测试与运行环境约束

在仓库根目录下执行以下命令可构建与测试该包:

# 用 tsc 编译 TypeScript 到 dist/ yarn workspace @packages/agent-info build # 运行测试(Vitest) yarn workspace @packages/agent-info test

补充说明:

  • build脚本实际为yarn clean && tsc(见 package.json),dist/tsc生成,禁止手改(AGENTS.md 明确提示)。
  • 该包没有独立的lint外的运行时脚本依赖,日常开发还可使用watchtsc --watch)增量编译。
  • 运行环境约束:由于该包被cypressCLI 消费,而 CLI 运行在用户自己的 Node上,因此兼容范围受 cli/package.json 中engines.node约束(当前为^22.0.0 || ^24.0.0 || >=26.0.0)——这是一个比开发或打包 Node 版本更低的"下限"要求,意味着代码里不能使用超出该下限的 Node 新特性。这与包的"零运行时依赖 + 保守语法"取向一致,确保它无论被加载到哪一档受支持的 Node 都能正常工作。

测试覆盖一览

lib/spec/index.spec.ts 用 Vitest 系统化地验证了本包的全部行为契约:

测试主题覆盖点
空环境detectAgent({})undefinedisAgent({})false
detects %s参数化表格14 组 Agent × 环境变量组合全部命中
Windows 风格路径piPATH含反斜杠.pi\agent)与devindevin.exe
空字符串变量存在但为空不触发检测
优先级同时出现CURSOR_AGENTCLAUDECODE时返回claude(Agent 胜过 IDE)
误判防御/home/devin/.local/bin/vim不命中devin
TTY 门控stdout 为 TTY 时不识别kiro;仅 stdin 为 TTY(输出被管道化)时同样不识别;显式标记(CLAUDECODE)不受影响
AI_AGENT带版本号值收窄为已知名;大小写不敏感;未知值返回other而非原样;pipecat不因前缀pi误判;表内显式变量优先于AI_AGENT
默认参数未传env时读取process.envvi.stubEnv模拟)

这些测试同时充当"检测表语义"的可执行文档——新增或调整 Agent 检测规则时,先改 spec、再改实现,是仓库推荐的开发顺序。

小结

@packages/agent-info以约 77 行源码回答了一个看似简单实则布满陷阱的问题:进程是否由 AI 编程代理启动、由谁启动。它的方法论可以提炼为可复用的四条经验:输出必须来自封闭集合以保护隐私上报;识别依据应优先使用锚定的具体标记而非宽泛子串;表序即优先级(具体 Agent 优先于承载它的 IDE);默认偏向人类(TTY 门控只在无交互终端时放行)。配合一次调用即完成的环境指纹比对与高度参数化的测试表,这个模式非常适合移植到任何需要"区分人与 Agent、并安全上报 Agent 身份"的 CLI 或开发工具中。若需在 Cypress 中扩展对某个新 Agent 的支持,只需按上文"三处改动"的流程,在 lib/index.ts 与 index.spec.ts 中同步更新联合类型、检测表和测试行即可。

【免费下载链接】cypressFast, easy and reliable testing for anything that runs in a browser.项目地址: https://gitcode.com/GitHub_Trending/cy/cypress

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

Opencode本地AI编程代理安装与故障排查指南

1. 项目概述:Opencode 不是“开源代码”的泛称,而是一个真实存在的 AI 编程代理工具最近在开发者社区里,“opencode”这个词被反复提起,但很多人一搜就懵——它既不是 Linux 内核里的某个模块,也不是 GitHub 上某个明星…

作者头像 李华
网站建设 2026/9/9 12:55:31

ECharts 世界地图实战:从 GeoJSON 注册到 visualMap 排错与 3D 球体实现

简介:面向需要构建全球数据可视化地图的前端开发工程师、数据分析师及大屏展示设计人员,这份压缩包提供了基于 ECharts 的完整世界地图 js 与 json 数据文件,可直接用于人口、GDP、疫情、贸易、航班航线等地理分布场景的快速展示与二次开发。…

作者头像 李华
网站建设 2026/9/9 12:55:28

hermes-agent:轻量级AI Agent信使架构解析与实践

项目标题只有"hermes-agent"这一个词,说实话一开始我也愣了一下。但干这行久了就明白,这种命名方式背后通常藏着一个很具体的痛点。Hermes在希腊神话里是 messengers——众神的信使,负责在神与人之间传递消息。放到技术语境里&…

作者头像 李华
网站建设 2026/9/9 12:55:05

身份证号同步指南:从踩坑到沉淀的完整方法论

接到一个把用户身份证号从老会员库同步到新用户中心的活儿,刚开始以为就是个普通的数据迁移,结果第一轮联调就把我干懵了:源库字段类型是 varchar(30),有的带空格,有的是全角数字,还有一批老数据是15位身份…

作者头像 李华
网站建设 2026/9/9 12:53:51

SpringBoot+Vue3汽车维修预约系统全栈开发实战与部署详解

1. 项目整体设计与思路拆解 1.1 这个项目到底解决了什么问题 先说结论:这是一个面向汽车维修门店和车主两端使用的预约服务系统。你如果开过修理厂或者去4S店排过队修车,一定体会过那种“到了门店发现师傅在忙别的车,白等两小时”的尴尬。这…

作者头像 李华
网站建设 2026/9/9 12:53:38

从0到1实战:用MCP服务器打通Figma与AI编程的上下文断层

最近在设计稿转前端的过程中,团队遇到一个反复出现的尴尬局面:AI 编程工具能写代码,但它看不懂设计稿的图层结构,只能依赖开发者手动截图、标注、测量间距和颜色。整个流程一旦涉及多页面、多组件,效率损耗非常明显。后…

作者头像 李华