news 2026/10/28 1:47:02

Cindy Pi 原生供应商别名缺失 auth 的修复:Issue 6454 根因定位、最小改动与真实运行时回归

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Cindy Pi 原生供应商别名缺失 auth 的修复:Issue 6454 根因定位、最小改动与真实运行时回归
  • 人工智能
  • AI Agent
  • 代码智能体
  • 大模型
  • AI 技能
  • Agent 记忆
  • 工具调用
  • MCP Clients

【免费下载链接】cindy

Consider it done. The open-source AI agent that works out of the box · 想到,就能做到。开源、开箱即用的 AI Agent。

项目地址:https://gitcode.com/GitHub_Trending/cind/cindy
点击查看免费下载

本文以 Issue #6454 修复记录 为主线,剖析 Cindy 在使用设备码授权(OAuth)的 MiniMax 连接上发送消息时,为何先报provider.auth.oauth再报provider.auth.apiKey的双重错误,并给出"注册契约补齐"这一最小修复的完整实现依据、验证矩阵与边界条件。读完后你可以掌握 Pi 原生供应商别名的注册链路、auth元数据的语义边界,以及在真实 Pi 子进程上如何构造可复现的 loopback 回归。

问题现象与失败判据

修复记录的验收目标非常具体:使用设备码授权的 MiniMax(中国大陆)连接,在自动化中选择 MiniMax-M3,发送「回复:你好」应当得到回复。实际复现结果是:

  1. session_start阶段先抛出provider.auth.oauth扩展错误;
  2. 随后请求以undefined is not an object (evaluating 'provider.auth.apiKey')结束;
  3. 全程没有生成任何回复。

Issue 原始报告使用的是 M3.1-Flash-Preview,而本机用 M3 同样触发,这一对照说明模型名称不是必要条件——问题出在供应商注册层,而不是某个具体模型的参数或能力声明。

记录中提到的基线为origin/main上的9d3d13aa5639116988d9943f39b18b4c14dd8362,并且明确约束"原工作区、已有连接及自动化保持原状",即复现与修复都不改动用户既有连接配置。

调用链全景:models.json、别名与对象式注册

要理解根因,必须先把这条链路串起来。修复记录给出的链路是:Desktoppi-host.ts准备 nativeProviders → maker-core 生成 models.json 和别名 →native-provider-adapter-source.ts的registerAliases→ Pi 对象形式的registerProvider。

第一步:Desktop 侧决定"有没有 keyEnv"

在 pi-host.ts 中,apiKeyEnvVar只在authMethod === 'apiKey'且读到了 key 时才会被赋值:

let apiKeyEnvVar: string | undefined; if (authMethod === 'apiKey') { const key = readKey(cfg.id, 'pi'); if (!key && !headers) { onSkip?.(cfg.id, 'apiKey provider missing pi key and custom headers'); continue; } if (key) { apiKeyEnvVar = uniqueEnvVar(cfg.id); env[apiKeyEnvVar] = key; } }

而走 OAuth 的连接,baseUrl被换成 Cindy 自己的 loopback 代理端点,同时挂上订阅头:

baseUrl: authMethod === 'oauth' ? oauthProxyEndpoint! : rt.baseUrl, ... ...(authMethod === 'oauth' ? { headers: piSubscriptionHeaders(cfg.id) } : {}),

piSubscriptionHeaders返回的是会话级 header(pi-host.ts):x-cindy-pi-session-id、x-cindy-pi-session-token以及x-cindy-pi-provider-id。也就是说,OAuth 场景下的凭证注入发生在代理层与 header 层,而不是apiKeyEnvVar。这一步就决定了后续别名里没有keyEnv。

第二步:maker-core 写 models.json,keyless 用占位符

在 packages/maker-core/src/agents/pi/index.ts 中,原生供应商写入models.json时对无 key 的情况使用固定占位字符串:

providers[np.id] = { name: np.name, baseUrl: np.baseUrl, ...(np.api ? { api: np.api } : {}), // keyless(本机 Ollama 等)也要给 dummy key,否则 pi /model 不显示该模型。 apiKey: np.apiKeyEnvVar ? `$${np.apiKeyEnvVar}` : 'pi-native-keyless', ... };

这条注释点明了占位符存在的原因:Pi 在枚举模型时会读取apiKey字段,缺失会导致该 provider 的模型不出现在/model列表里。因此pi-native-keyless是结构占位,不是真实凭证——这一点是后文判断"空 auth 是否安全"的关键前提。

同一文件的 spawn env 组装处(index.ts)把别名列表序列化进环境变量交给扩展:

// BYOM 原生 provider 的 api keys(键名对应 spec.apiKeyEnvVar,models.json 用 $ENV 引用)。 ...nativeEnv, CINDY_PI_NATIVE_PROVIDER_ADAPTERS: JSON.stringify(nativeProviderAdapterAliases( nativeProviders, hostedGuest ? hosted?.hostedProvider?.modelIds : undefined, Boolean(hosted))),

第三步:别名构造显式省略 keyEnv

native-provider-adapter-source.ts 的nativeProviderAdapterAliases用条件展开生成keyEnv:

...(provider.apiKeyEnvVar ? { keyEnv: provider.apiKeyEnvVar } : {}),

于是无 key 的别名对象里根本不存在keyEnv字段。这一点在单测中被固化下来——nativeProviderAdapterAliases.test.ts 明确断言代理鉴权的 adapter「不要求 API key env」:

{ id: 'copilot-oauth', name: 'Copilot', adapterProvider: 'github-copilot' }, { id: 'generic', name: 'Generic' }, { id: 'cloudflare', name: 'Cloudflare', adapterProvider: 'cloudflare-ai-gateway', keyEnv: 'CINDY_PI_KEY_CF' },

期望结果里前两项没有keyEnv,第三项有。注意generic因为adapterProvider与 guest 列表都为空而被过滤掉——说明别名集合本身还承担着"哪些连接需要独立适配器"的判定。

第四步:registerAliases 的对象式注册(缺陷所在)

问题就出在 native-provider-adapter-source.ts,原实现只在存在keyEnv时才提供auth:

pi.registerProvider({ id: alias.id, name: alias.name, auth: alias.keyEnv ? { apiKey: envApiKeyAuth('API key', [alias.keyEnv]) } : {}, getModels: () => providerModels, stream: (model, context, options) => stream(model, context, options, false), streamSimple: (model, context, options) => stream(model, context, options, true), });

这段代码在当前仓库中已带有解释性注释:"Pi's native Provider contract requires auth even when Cindy's proxy handles OAuth."。也就是说,修复后的语义被写进了源码注释,避免后人再次"顺手"把空对象优化掉。

根因:Pi 原生 Provider 契约要求 auth 对象必须存在

修复记录把根因表述得很清楚:OAuth 由 Cindy 现有 loopback 代理及订阅 header 承担,因此别名没有keyEnv;注册代码却只在有keyEnv时提供auth。而Pi Native Provider 契约要求auth对象存在,即使既没有 API key 也没有 OAuth 方法,也必须提供空对象{}。

为什么会出现两个错误而不是一个

Pi 1.1.0 的registerNativeProvider在读取provider.auth.oauth之前已经更新了供应商状态,异常不会自动撤销已注册的对象。于是形成"部分失败状态":

  • 注册阶段读provider.auth.oauth→ 抛第一个错误;
  • 供应商状态已被写入,后续凭证解析继续访问provider.auth.apiKey→ 抛第二个错误。

用户因此看到的是两条错误串行出现。记录同时给出一条重要的诊断经验:rpc-ready/first-model-request阶段的成功标记不能证明上游 HTTP 已发出。这两个阶段只是本地握手与请求入队,真正的上游请求发生在 stream 内部;用它们判断"是否恢复"会得出错误结论。

此外记录保持了证据边界:原运行的临时模型目录已不可读,因此不能声称核验过该次实际的 models.json 或进程环境。这类诚实标注对后续复现者非常重要——不要把推断写成已经验证的事实。

对照实验:四种输入、四种结局

记录在独立隔离环境中,用真实 Pi 1.1.0 与 1.0.2 做了对照,两次结果一致:

输入退出码loopback 请求回复
无 keyEnv,省略 auth(原实现)10无,两个 auth 属性错误
无 keyEnv,auth 为{}01fixture-ok
有 keyEnv,保留 envApiKeyAuth01fixture-ok
仅 models.json、不注册别名01fixture-ok

四个组合分别验证了四件事:

  1. 原实现确实在"无 keyEnv"这一分支必失败,且一个请求都没打到 loopback;
  2. 空对象即可满足契约,最小修复可行;
  3. 有 keyEnv 的分支必须原样保留,不能被"统一清空为{}"误伤;
  4. 别名注册并非请求成功的必要条件(models.json 单独存在也能跑通),说明别名的作用是保留供应商专属序列化与连接独立身份,而不是打通网络路径。

记录也对实验强度做了限定:只使用本地 SSE fixture、假凭证与独立临时目录,不连接真实 MiniMax;它证明注册契约缺陷与最小修复可行,但不等价于真实账号端到端验收。这一点在后文"边界"一节会再次体现。

最小改动:一处三元表达式补齐注册元数据

修复方案的落点只有一行:

auth: alias.keyEnv ? { apiKey: envApiKeyAuth('API key', [alias.keyEnv]) } : {},

即"有 keyEnv 用envApiKeyAuth,没有就交空对象"。记录强调需要保留的既有行为清单如下:

  • 对象注册形式(pi.registerProvider({...})),不改成别的注册方式;
  • 供应商专属序列化(如 Cloudflare AI Gateway 的 header 改写);
  • 连接独立 ID(每个 Cindy 连接一个 provider id,不合并);
  • 原生stream/streamSimple两套通道;
  • models.json中的pi-native-keyless占位;
  • 已有的 OAuth 代理与订阅 header。

同时明确"不需要做什么":无需让用户重新授权、无需购买另一模型、无需增加开关、无需升级 Pi pin。

空 auth 为什么不等于伪造凭证

方案自检给出了三条边界,值得逐条展开:

  1. auth是注册元数据,不是真实 OAuth 凭证。空对象只是满足 Pi 的类型/运行时契约;它不携带任何 token,也不写入磁盘。
  2. 不声称支持 Pi 自己的 OAuth 登录。Cindy 的 OAuth 走自己的 loopback 代理与订阅 header,Pi 侧看到的只是一次普通的、由代理转发的请求。空auth不会被解释为"用户可以在此 provider 上执行 Pi 的 OAuth 流程"。
  3. 不增加凭证落盘。有 keyEnv 时仍然由envApiKeyAuth('API key', [alias.keyEnv])从环境变量读取,密钥依旧只存在于 spawn env,并被纳入 secret env 剥离面。

第三点可以在源码中找到呼应:适配器内部把占位 key 集合显式列出,避免把占位符当作真 token 转发(native-provider-adapter-source.ts):

const token = typeof options?.apiKey === 'string' ? options.apiKey.trim() : ''; const placeholderKey = !token || ['pi-native-keyless', 'cindy-pi-provider-auth-placeholder', 'cindy-local-provider'].includes(token); const gatewayKey = token && !placeholderKey ? 'Bearer ' + token : options?.headers?.['cf-aig-authorization'];

同一套占位符判定在宿主侧 transport 中也有一份(pi-provider-transport.ts),isNativePlaceholderKey与cloudflareGatewayHeaders会在占位时把Authorization与x-api-key置为null,仅保留cf-aig-authorization。这说明"占位符≠凭证"是这条链路上的共同约定,而不是某个文件的局部假设。

双入口:session_start 与 refresh 必须同时补齐

修复记录特别指出:"启动session_start与 refresh 共用同一函数,补齐两条入口"。源码结构确实如此——registerAliases是内部函数,两个入口都调用它:

pi.on('session_start', (_event: any, ctx: any) => { if (aliases.length) registerAliases(ctx.modelRegistry.getAll(), aliases); });

以及refresh内部在清理旧注册后重新注册:

for (const id of registered) pi.unregisterProvider(id); registered.clear(); const result = await ctx.modelRegistry.refresh({ allowNetwork: false }); if (result?.aborted || result?.errors?.size) throw new Error('Native provider catalog refresh failed'); aliases = snapshot.aliases; if (aliases.length) registerAliases(ctx.modelRegistry.getAll(), aliases);

这也解释了为什么单点修复即可覆盖两条路径,但如果有人把逻辑写进session_start回调体内部,就会漏掉 refresh。修复记录把"共用同一函数"作为方案自检项,正是为了防这类回归。

refresh 的三道安全约束

刷新路径并不只是"换一批别名",它还携带三条必须保持的约束:

  1. nonce 绑定:parseCindyProviderRefreshSnapshot要求payload.nonce === nonce,不一致直接返回undefined(native-provider-adapter-source.ts)。
  2. 环境变量白名单:只有CINDY_PI_KEY_[A-Z0-9_]+、CINDY_PI_SESSION_TOKEN、CINDY_PI_API_KEY以及CINDY_FIXED_ADAPTER_KEYS(CINDY_PI_OPENAI_PROXY_KEY、CINDY_PI_XAI_PROXY_API_KEY)可以进入刷新快照;出现OPENAI_API_KEY之类的名字会让整个快照被拒。
  3. 注销范围最小化:只unregisterProvider自己注册过的 id(registered集合),不改动其它扩展注册的 provider。

单测对这三条都有明确断言,例如 nativeProviderAdapterRefresh.test.ts 验证刷新只替换自己注册的 adapter、并保留package-owned这类外部 provider;同文件 则验证非法keyEnv(含CINDY_PI_OPENAI_PROXY_KEY但 env 中不存在、或写成OPENAI_API_KEY)会导致整份快照被拒。

宿主侧如何触发 refresh

宿主侧通过一个带 nonce 的扩展命令完成刷新握手(packages/maker-core/src/agents/pi/cindy-bridge-source.ts):

pi.registerCommand('cindy-native-provider-refresh', { description: 'Cindy internal native provider refresh', handler: async (args: string, ctx: any) => { const nonce = args.trim(); if (!/^[A-Za-z0-9_-]{16,128}$/.test(nonce)) return; ... await ctx.ui.input('cindy:provider-refresh-ack', JSON.stringify( code ? { nonce, ok: false, code } : { nonce, ok: true, runtimeSettings }, )); }, });

而queryNativeRuntime在发起前会先确认命令归属,避免把未知命令当成模型 prompt(index.ts):

if (!commands.success || !entries?.some((entry) => entry.name === 'cindy-native-provider-refresh' && entry.source === 'extension')) { throw new Error('Cindy native provider refresh extension is unavailable; reopen this task with the current Cindy version.'); }

这条防护与 #6454 无关但同样重要:如果扩展没加载成功而宿主盲目发送/cindy-native-provider-refresh <nonce>,这条命令会作为普通提示词发给模型,既泄露 nonce 又浪费一次请求。

回归策略:单元层执行真实生成的扩展源码

修复记录的回归计划第一步是把测试落在共享单测里,并且执行真实生成的扩展源,而不是对源码文本做断言。PI_NATIVE_PROVIDER_ADAPTER_SOURCE是一个String.raw模板字符串,测试通过ts.transpileModule编译后在node:vm沙箱中运行(nativeProviderAdapterRefresh.test.ts)。其中最关键的细节是假pi对象主动解引用 auth 的两个属性,从而复刻真实 Pi 的失败方式:

registerProvider(provider: RegisteredProvider) { // Pi's native object registration dereferences auth even for host-authenticated proxies. void provider.auth.oauth; void provider.auth.apiKey; ... }

这正是记录中"新增共享回归在原实现报Cannot read properties of undefined (reading oauth)"的来源:原实现给出auth: undefined,解引用立即抛错,测试因此在未修复代码上失败。

双向切换用例

#6454 新增的用例(nativeProviderAdapterRefresh.test.ts)覆盖了"代理鉴权 → API key → 代理鉴权"的往返:

const proxy = { id: 'minimax-oauth', name: 'MiniMax', provider: 'minimax-cn' }; const keyed = { id: 'minimax-key', name: 'MiniMax key', provider: 'minimax-cn', keyEnv: 'CINDY_PI_KEY_NEXT' }; ... expect(h.providers.get(proxy.id)?.auth).toEqual({}); ... expect(h.providers.get(keyed.id)?.auth.apiKey).toEqual({ label: 'API key', envVars: ['CINDY_PI_KEY_NEXT'] }); ... expect(h.providers.get(proxy.id)?.auth).toEqual({}); expect(h.providers.get(proxy.id)?.getModels()).toEqual([{ provider: proxy.id, id: 'MiniMax-M3' }]); expect(h.env.CINDY_PI_KEY_NEXT).toBeUndefined(); expect(secrets.size).toBe(0);

这条用例同时守住四件事:空 auth 可注册、有 key 分支保留envApiKeyAuth结构、刷新后新模型可见、以及切回代理鉴权时旧的动态 key 被清除且移出 secret 名单。

运行层回归:真实 Pi 子进程 + 本地 SSE fixture

单元层的 vm 沙箱只能证明契约形状正确,无法证明"真实 Pi 会不会真的发出 HTTP"。因此第二步落在 piNativeAdapterRuntime.test.ts,用真实二进制 + 本地 HTTP 服务完成端到端闭环。

三类供应商、一份 fixture

用例以it.each(['github-copilot', 'cloudflare-ai-gateway', 'minimax-cn'])参数化,并声明只有minimax-cn是代理鉴权:

const proxyAuthenticated = adapterProvider === 'minimax-cn';

临时目录里写入三樣东西:生成的adapter.ts、空的auth.json、以及一份指向本地 fixture 的models.json:

await writeFile(join(directory, 'adapter.ts'), PI_NATIVE_PROVIDER_ADAPTER_SOURCE + '\nexport default async function(pi: any) { await registerCindyNativeProviderAdapters(pi); }'); await writeFile(join(directory, 'auth.json'), '{}'); await writeFile(join(directory, 'models.json'), JSON.stringify({ providers: { 'fixture-connection': { baseUrl, api: 'anthropic-messages', apiKey: proxyAuthenticated ? 'pi-native-keyless' : '$CINDY_FIXTURE_API_KEY', ...(proxyAuthenticated ? { headers: { 'x-cindy-subscription': 'fixture-subscription' } } : {}), models: [{ id: 'claude-fixture', ... }], } } }));

注意apiKey的两种取值正好对应前文的两种分支:代理鉴权写占位符pi-native-keyless,API key 分支写$CINDY_FIXTURE_API_KEY环境引用。auth.json为空对象则确保 Pi 不会从磁盘凭据里"意外"补齐 auth,从而让测试真正考核别名注册。

启动参数与断言

子进程以非交互模式启动,显式禁用扩展只加载被测扩展:

child = spawn(binary, ['--print', '--no-session', '--no-extensions', '--extension', join(directory, 'adapter.ts'), '--provider', 'fixture-connection', '--model', 'claude-fixture', 'Reply briefly.'], { cwd: directory, env: { ...process.env, PI_CODING_AGENT_DIR: directory, CINDY_FIXTURE_API_KEY: 'fixture-key', CINDY_PI_NATIVE_PROVIDER_ADAPTERS: JSON.stringify([{ id: 'fixture-connection', name: 'Fixture', provider: adapterProvider, ...(proxyAuthenticated ? {} : { keyEnv: 'CINDY_FIXTURE_API_KEY' }) }]), }, stdio: ['ignore', 'pipe', 'pipe'], });

断言分为四层,层层递进:

层次断言内容排除的失败模式
进程expect(exit, errors).toBe(0),15 秒超时后 kill注册异常导致的退出码 1
输出expect(output).toContain('fixture-ok')只握手不产文
请求expect(requests).toEqual([...])恰好一条重试风暴或根本没发出
协议authorization/gatewayKey/apiKey/subscription逐项匹配凭证丢失、header 被覆盖、占位符被当真 token

其中 header 期望体现了三种供应商的差异:github-copilot期望authorization: 'Bearer fixture-key',cloudflare-ai-gateway期望gatewayKey: 'Bearer fixture-key',minimax-cn期望apiKey: 'pi-native-keyless'且subscription: 'fixture-subscription'。这直接验证了"代理 header 未被独立 provider 身份或占位 key 覆盖"这一风险点。

用例还通过it.skipIf(!existsSync(binary))在缺少 Pi 二进制的环境跳过,但它不会把跳过伪装成通过——修复记录在最终结论里专门强调"非本机平台、SSH 实机与真实账号验收未执行,不能用跳过的运行时测试冒充通过"。

验证记录与性能预算

修复记录的验证记录给出了可核对的时间与结果:

  • 2026-10-19,macOS arm64;
  • 新增共享回归在原实现上报Cannot read properties of undefined (reading oauth);
  • 真实 Pi 1.0.2 的新增 MiniMax 用例在原实现上报与用户日志一致的 oauth / apiKey 两个错误,而既有的两个 keyed 用例仍通过(说明缺陷是分支性的,不是全局性破坏);
  • 修正后两个共享文件共 10 条用例通过,真实运行时文件 3 条用例全部执行并通过,包含请求、回复与代理 header 断言;
  • 运行时文件墙钟 1.11 秒,共享文件合跑 0.54 秒,均在单文件 5 秒预算内。

"先在未修复代码上跑出失败、再修复后重跑"这一顺序是这份记录的方法论核心:如果只在修复后跑绿,无法证明测试真的覆盖了缺陷。性能预算的存在也呼应了仓库对桌面单测耗时的约束(参见 docs/dev-rules/desktop-unit-test-performance.md)。

逐端覆盖与边界

修复记录用一张表把"哪些端共享修复、哪些端真正验证过"分开,避免过度承诺:

端/入口覆盖与验证边界
Desktop macOS 本机 Pi、自动化共享注册/刷新行为单测;真实 Pi loopback 进程,既有 native adapter、refresh 与 host 相关测试本机执行;真实 MiniMax 账号待当前修复构建验收
Desktop Windows/Linux、SSH Pi同一生成扩展与别名代码;跨平台路径由现有测试构造非本机 OS 未实跑;SSH 不改配置/传输,目标环境仍待验证
iOS/Android 远控任务Pi 在所属 Desktop/远程主机执行,复用同一修复无手机内置 Pi 子进程;移动 UI、wire 不变;未做设备端到端验收
Cindy headless 消费者复用 maker-core Pi 注册代码;共享行为回归与类型检查无 headless 专属分支,不声称实际部署已更新

这张表的推理依据在源码中可验证:

  • 跨平台共享:别名生成与扩展源都在packages/maker-core,Desktop 只负责准备nativeProviders与 env,所以 Windows/Linux 走的是同一份代码,差异仅在路径与进程启动。
  • 移动端无内置 Pi:移动端发起的是远控任务,Pi 子进程实际运行在 Desktop 或远程主机上,因此修复随宿主生效;packages/mobile中不存在独立的 Pi 注册分支。
  • headless 复用:apps/cindy-headless的 profile 会把nativeProviders与apiKeyEnvVar传入同一套 maker-core 生成逻辑。其 profile.ts 会校验apiKeyEnvVar命名合法性,headless-integrations.ts 在缺少 env 时直接报错——也就是说 headless 更偏向"显式 key"路径,但这不改变别名注册函数的共享事实。

风险与恢复

修复记录把风险面收敛得很窄:唯一的产品行为变化,是让原先缺少auth而异常的注册满足 Pi 契约。重点风险与对应约束如下:

  • API key 分支被误改:用"有 keyEnv 保留envApiKeyAuth"的对照用例约束(auth.apiKey必须等于{ label: 'API key', envVars: ['CINDY_PI_KEY_NEXT'] })。
  • 刷新路径漏修:启动与刷新共用registerAliases,单测同时覆盖session_start与refresh两条入口。
  • 代理 header 或独立 provider 身份丢失:运行时用例逐项断言authorization/cf-aig-authorization/x-api-key/x-cindy-subscription,以及provider: alias.provider的原生序列化重写。

恢复语义有两点必须说清:

  1. 失败只影响当前 Pi 任务,不重启共享服务或其它 peer。宿主在目录重载无法确认时选择终止本任务而非继续运行(index.ts 的[PI_CATALOG_RELOAD_UNCONFIRMED]路径),这是 fail-closed 的取舍。
  2. 修复发布后必须重启该任务运行时。已失败的进程里可能存在部分注册状态,Pi 不会自动撤销,因此不能声称"正在运行的旧进程会自动修好"。无需用户数据迁移;回退代码可恢复旧行为,但也会恢复此 bug。

复现与自检清单

若你需要在本地复核这条修复,可以按下面的顺序操作(仓库只读,以下都是"查看/运行"而非修改仓库):

  1. 阅读主文档与关键源码:docs/fixes/issue-6454-pi-native-provider-auth.md、native-provider-adapter-source.ts、pi-host.ts、index.ts。
  2. 跑共享单测:在packages/maker-core下执行pnpm vitest run src/agents/pi/__tests__/nativeProviderAdapterRefresh.test.ts src/agents/pi/__tests__/nativeProviderAdapterAliases.test.ts。
  3. 跑真实运行时用例:在apps/desktop下执行pnpm vitest run src/main/maker-host/__tests__/piNativeAdapterRuntime.test.ts;若本机缺少apps/pi-bin/<platform>-<arch>/pi,用例会被跳过,此时应视为"未验证"而非"通过"。
  4. 做一次反向验证:把auth改回"仅在有 keyEnv 时提供"的形态(仅在你自己的分支上),确认共享用例报Cannot read properties of undefined (reading 'oauth'),再恢复修复。这是确认测试有效性的唯一可靠方式。
  5. 按记录执行门禁:相关单测、packages/maker-core的tsc --noEmit(pnpm build)、受影响 package 的 typecheck、测试卫生与 diff 检查。

方案自检五问

修复记录给出的自检清单可以直接当作评审清单复用:

  • auth是否仅作为注册元数据,未声称支持 Pi 自身的 OAuth 登录、未新增凭证落盘?
  • 有keyEnv时是否仍使用原来的envApiKeyAuth,而非被统一清空为{}?
  • session_start与 refresh 两条入口是否都经同一函数补齐,且刷新仍按现有 nonce、环境白名单与注销范围执行?
  • 是否按"同根因适用于所有无 keyEnv 的别名"来设计回归(用无 keyEnv 代表 + 有 keyEnv 对照),而不是只为 MiniMax/M3 打补丁?
  • 是否没有关闭能力、没有更改错误吞吐、没有新增 fallback,也没有改动上游 Pi、目录、数据库、协议或 UI?

最后需要重申记录中那句容易被忽略的话:本修复不等价于真实账号端到端验收。真实 MiniMax 账号、非本机操作系统、SSH 实机与移动端设备链路仍待各自环境确认;文章中的所有结论都以仓库中的文档、源码与测试为依据,未覆盖的部分不应被读作已验证。

  • 人工智能
  • AI Agent
  • 代码智能体
  • 大模型
  • AI 技能
  • Agent 记忆
  • 工具调用
  • MCP Clients

【免费下载链接】cindy

Consider it done. The open-source AI agent that works out of the box · 想到,就能做到。开源、开箱即用的 AI Agent。

项目地址:https://gitcode.com/GitHub_Trending/cind/cindy
点击查看免费下载
上一篇:FlashInfer Cake DSA 稀疏注意力训练后端:SM100/SM103/SM107 上 64 头 DeepSeek Sparse Attention 前向与反向实战解析
下一篇:oh-my-opencode-slim开发者指南:从本地开发、缓存安全测试到发布贡献的完整手册

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

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

一文读懂数据库分库分表策略(进阶篇)

本文深入探讨数据库分库分表策略&#xff08;进阶篇&#xff09;&#xff0c;涵盖背景分析、原理剖析、实战步骤、配置示例、优化建议和避坑指南。在后端开发与API领域&#xff0c;数据库分库分表策略&#xff08;进阶篇&#xff09;是开发者和技术负责人持续关注的核心议题。本…

作者头像 李华
网站建设 2026/10/28 1:45:04

01|Tcache Poisoning 理论:2.27 为什么完全不校验 fd

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

作者头像 李华
网站建设 2026/10/28 1:42:16

axure9.0 热区常见问题与避坑技巧

一、 点击热区没有反应原因&#xff1a;热区被其他元件、动态面板遮挡&#xff0c;层级过低&#xff1b;解决&#xff1a;右键热区 → 置顶&#xff0c;确保处于页面最上层。二、 编辑时看不到热区原因&#xff1a;关闭了视图遮罩&#xff1b;解决&#xff1a;顶部【视图】→勾…

作者头像 李华
网站建设 2026/10/28 1:42:15

2026年前端已死?看完论坛吵架贴,才懂活路在哪

这几天&#xff0c;某知名技术论坛里有个帖子火了&#xff0c;看得人心里五味杂陈。28岁入行三年的该前端楼主, 最近心态发生了崩裂, 对此问其缘由, 是因公司大力推行AI, 致使他目睹隔壁后端同事借助AI轻易就完成了本属前端的工作, 往昔前端那曾如“护城河”般的存在, 于当下AI…

作者头像 李华