- 人工智能
- AI Agent
- 代码智能体
- 大模型
- AI 技能
- Agent 记忆
- 工具调用
- MCP Clients
【免费下载链接】cindy
Consider it done. The open-source AI agent that works out of the box · 想到,就能做到。开源、开箱即用的 AI Agent。
本文以 Issue #6454 修复记录 为主线,剖析 Cindy 在使用设备码授权(OAuth)的 MiniMax 连接上发送消息时,为何先报provider.auth.oauth再报provider.auth.apiKey的双重错误,并给出"注册契约补齐"这一最小修复的完整实现依据、验证矩阵与边界条件。读完后你可以掌握 Pi 原生供应商别名的注册链路、auth元数据的语义边界,以及在真实 Pi 子进程上如何构造可复现的 loopback 回归。
问题现象与失败判据
修复记录的验收目标非常具体:使用设备码授权的 MiniMax(中国大陆)连接,在自动化中选择 MiniMax-M3,发送「回复:你好」应当得到回复。实际复现结果是:
session_start阶段先抛出provider.auth.oauth扩展错误;- 随后请求以
undefined is not an object (evaluating 'provider.auth.apiKey')结束; - 全程没有生成任何回复。
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(原实现) | 1 | 0 | 无,两个 auth 属性错误 |
无 keyEnv,auth 为{} | 0 | 1 | fixture-ok |
| 有 keyEnv,保留 envApiKeyAuth | 0 | 1 | fixture-ok |
| 仅 models.json、不注册别名 | 0 | 1 | fixture-ok |
四个组合分别验证了四件事:
- 原实现确实在"无 keyEnv"这一分支必失败,且一个请求都没打到 loopback;
- 空对象即可满足契约,最小修复可行;
- 有 keyEnv 的分支必须原样保留,不能被"统一清空为
{}"误伤; - 别名注册并非请求成功的必要条件(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 为什么不等于伪造凭证
方案自检给出了三条边界,值得逐条展开:
auth是注册元数据,不是真实 OAuth 凭证。空对象只是满足 Pi 的类型/运行时契约;它不携带任何 token,也不写入磁盘。- 不声称支持 Pi 自己的 OAuth 登录。Cindy 的 OAuth 走自己的 loopback 代理与订阅 header,Pi 侧看到的只是一次普通的、由代理转发的请求。空
auth不会被解释为"用户可以在此 provider 上执行 Pi 的 OAuth 流程"。 - 不增加凭证落盘。有 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 的三道安全约束
刷新路径并不只是"换一批别名",它还携带三条必须保持的约束:
- nonce 绑定:
parseCindyProviderRefreshSnapshot要求payload.nonce === nonce,不一致直接返回undefined(native-provider-adapter-source.ts)。 - 环境变量白名单:只有
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之类的名字会让整个快照被拒。 - 注销范围最小化:只
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的原生序列化重写。
恢复语义有两点必须说清:
- 失败只影响当前 Pi 任务,不重启共享服务或其它 peer。宿主在目录重载无法确认时选择终止本任务而非继续运行(index.ts 的
[PI_CATALOG_RELOAD_UNCONFIRMED]路径),这是 fail-closed 的取舍。 - 修复发布后必须重启该任务运行时。已失败的进程里可能存在部分注册状态,Pi 不会自动撤销,因此不能声称"正在运行的旧进程会自动修好"。无需用户数据迁移;回退代码可恢复旧行为,但也会恢复此 bug。
复现与自检清单
若你需要在本地复核这条修复,可以按下面的顺序操作(仓库只读,以下都是"查看/运行"而非修改仓库):
- 阅读主文档与关键源码:docs/fixes/issue-6454-pi-native-provider-auth.md、native-provider-adapter-source.ts、pi-host.ts、index.ts。
- 跑共享单测:在
packages/maker-core下执行pnpm vitest run src/agents/pi/__tests__/nativeProviderAdapterRefresh.test.ts src/agents/pi/__tests__/nativeProviderAdapterAliases.test.ts。 - 跑真实运行时用例:在
apps/desktop下执行pnpm vitest run src/main/maker-host/__tests__/piNativeAdapterRuntime.test.ts;若本机缺少apps/pi-bin/<platform>-<arch>/pi,用例会被跳过,此时应视为"未验证"而非"通过"。 - 做一次反向验证:把
auth改回"仅在有 keyEnv 时提供"的形态(仅在你自己的分支上),确认共享用例报Cannot read properties of undefined (reading 'oauth'),再恢复修复。这是确认测试有效性的唯一可靠方式。 - 按记录执行门禁:相关单测、
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。
相关推荐
一份 PDF 两种内容:Umi-OCR 双层 PDF 转换完整指南
一份 PDF 两种内容:Umi OCR 双层 PDF 转换完整指南 在扫描版 PDF 里按 Ctrl+F ,什么都搜不到——因为整页文字只是一张图片。 Umi
桌面应用OCR计算机视觉Superset 提示查询失败 issue code 时怎么根据错误码定位真实原因?
Superset 提示查询失败 issue code 时怎么根据错误码定位真实原因? 当你在 Apache Superset 里运行查询、添加数据库或调用 SQ
数据可视化数据分析后端UniHacker 免费上手指南:3 步跳过 Unity 激活,4.x 到 2022.1 版本通吃
UniHacker 免费上手指南:3 步跳过 Unity 激活,4.x 到 2022.1 版本通吃 UniHacker 是一个跨 Windows、macOS、L
桌面应用开发工具逆向工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考