Skip to content

Releases: jsun2020/superai-agent

SuperAI Agent v0.2.34

Choose a tag to compare

@github-actions github-actions released this 09 Sep 10:46

SuperAI Agent v0.2.34

修复:使用 OpenAI 兼容网关(经本地代理)时,同一句话反复输出、永不结束;以及流式回复要等 30 秒才出现。

现象

在企业网关上让模型写计划文件,界面里「Let me write the plan.」出现十几次,接着「Now I have all the requirements. Let me write the plan file.」出现二十多次,没有任何工具卡片,直到手动停止。

根因(用真实代理 + 真实命令行对一个可控的假网关复现)

  1. 代理没有把命令行的输出预算(max_tokens)转发给上游,上游因此按自己的默认上限(通常只有几千 token)截断回答。写一整个文件的 Write 工具调用一旦超过这个上限,就会在 JSON 中间被切断(finish_reason: length)。
  2. 代理把被切断的工具调用当作完整调用转发。命令行照常执行,Write 因参数不完整而报错,模型于是重新生成同样的回答,再次在同一位置被切断——没有任何上限。复现时 150 秒内出现 70 条相同消息、35 次请求。纯文本被切断时走的是命令行自带的有界恢复(最多 3 次),所以是 4 条重复加一条误导性的「超过 32000 输出 token」错误。
  3. 另一个独立缺陷:代理对流式回复设了 30 秒的整体超时。超过 30 秒的流被强行中断,命令行丢弃已收到的部分文本并改用非流式重发——用户看到的是 30 秒毫无反应,然后很久之后才出现完整回答。

修复

  • 转发 max_tokens(Responses API 为 max_output_tokens)。若某个服务商拒绝这个值(HTTP 400 并点名该字段),代理自动去掉该字段重试一次并记住,此后对该服务商不再发送——即被硬性限制的服务商行为与之前完全一致,只多一次请求。
  • 绝不转发被截断的工具调用:流式转换改为在工具块结束时整体发出;若上游以 length 结束(或流没有任何结束原因就断了)而参数不是完整 JSON,则丢弃该调用。命令行随即看到「文本 + max_tokens」,走它自带的有界恢复并明确报错,而不是执行垃圾输入并无限循环。非流式(回退)路径采用同一规则。
  • 流式超时只约束响应头(60 秒),正文允许运行数分钟;命令行自有的空闲看门狗负责卡住的流。非流式保留 300 秒总超时。
  • 顺带修正:文本块之后紧接工具调用时,同一个块索引会收到两次 content_block_stop;现在每个块恰好一次。

验证

  • 复现基准(假网关 + 真实代理 + 真实无头命令行):对照组 1 条;文本截断 4 条后报错;工具调用截断修复前无限循环,修复后终止;慢流修复前触发回退,修复后正常完成。
  • 新增 19 项测试:max_tokens 转发与拒绝回退策略(代理端到端)、流式/非流式截断工具调用的丢弃与保留规则、每块恰好一次 stop、响应头超时与正文超时分离。两条「max_tokens 应为 undefined」的旧断言正是这个 bug 的写照,已改正。

SuperAI Agent v0.2.33

Choose a tag to compare

@github-actions github-actions released this 07 Sep 22:49

SuperAI Agent v0.2.33

自定义服务商新增一种认证方式:OAuth2 客户端凭据 + X-API-KEY,用于接入企业内部的 AI 网关。

新功能

  • 「添加服务商 → 自定义」中的「认证方式」选项。除原有的 API 密钥(Bearer)之外,可选择 OAuth2 客户端凭据。填入五个参数即可:Token URL(AUTH_URL)、Client ID、Client Secret、X-API-KEY、接口地址(BASE_URL)。这正是许多企业网关下发给用户的那一组配置。

  • 工作方式:本地代理用 Client ID/Secret 向 Token URL 申请令牌(grant_type=client_credentials),以 Authorization: Bearer 发送,同时把 X-API-KEY 作为请求头附上。令牌在过期前 20 秒自动刷新;并发请求共用同一次申请;若上游返回 401(令牌被提前吊销),自动换新令牌重试一次——第二次 401 视为真实拒绝,不会在 Token 端点上打转。

  • 「测试连接」走同一条路径。测试成功或失败的原因与真实会话完全一致,包括 OAuth 换令牌这一步。

  • 接口地址容错:企业网关给出的 BASE_URL 通常已经以 /v1 结尾,直接粘贴不会再拼出 /v1/v1/chat/completions——之前这会得到一个看似「地址写错」的 404。

为什么是这样做

这类网关位于公司内网,请求不经过外网代理,也就不会遇到外网代理带来的 407 与拦截页。但它们只认 OAuth 令牌,而命令行本身只会带 ANTHROPIC_API_KEY,做不了这套握手。因此认证放在本地代理里完成——也正因如此,该选项只在 API 格式为 OpenAI 兼容(经本地代理)时出现;原生 Anthropic 格式由命令行直连,服务端会拒绝为其配置 OAuth。

Claude 与 Codex 的原有实现不受影响:使用 API 密钥的服务商,发出的请求头逐字节与之前相同(有回归测试保证);providers.json 中未启用 OAuth 的条目结构不变。

关于密钥

  • Client Secret 与 API 密钥同级,返回给界面时一律打码;编辑时留空即表示保持不变。
  • 密钥只保存在 ~/.claude/superai/providers.json;写入 settings.json 供命令行读取的仅是本地代理地址,不含任何凭据

验证情况

新增 42 项测试:令牌源(缓存、宽限期刷新、并发合并、失效重取、错误分类)13 项;上游认证(Bearer 回归、OAuth 请求头、401 重试一次、/v1 去重)13 项;持久化与打码 17 项;桌面端表单 6 项(仅在代理格式下出现、切回原生格式自动收起、必填校验、发送的字段)。按测试名与基线比对,无新增失败。前端产物已实际构建并确认新代码存在于 dist/assets

SuperAI Agent v0.2.32

Choose a tag to compare

@github-actions github-actions released this 31 Aug 23:06

SuperAI Agent v0.2.32

输入框新增语音输入:接入 TypeFree,说话即可把文字写进对话框。

新功能

  • 对话框中的语音输入按钮:位于输入框工具栏「+」旁边。点击后开始说话,识别结果会插入到光标位置——因此在已经写了一半的消息中间口述,不会覆盖或打乱已输入的内容。录音过程中按钮下方会显示实时音量条:麦克风没声音听到了但没内容,否则用户根本无从分辨。

  • 未运行 TypeFree 时,按钮不会出现——不是灰掉,而是完全不显示。大多数用户并未安装 TypeFree,而一个「一直在、但通常点不动」的按钮比没有更糟。应用每 15 秒重新探测一次,因此后启动 TypeFree 也无需重启本应用

实现说明

语音采集与识别由 TypeFree 负责,本应用只是控制与事件的客户端,二者通过本机回环(loopback)连接。这一拆分并非风格选择:TypeFree 的引擎依赖 Node 原生模块(sherpa-onnx-nodekoffi),而本应用界面运行在 Tauri 的系统 WebView 中,无法加载它们。

关于安全:那个连接令牌能够打开用户的麦克风。用户访问的任意网页都可以连接 ws://127.0.0.1:<端口>——浏览器连接回环地址毫无阻碍——因此仅绑定回环只能挡住其他机器,挡不住本机上的其他软件。令牌保存在用户目录下权限为 0600 的文件中,WebView 无法读取;只有 Rust 侧的 typefree_bridge_info 命令能取到它,并且会校验文件中记录的进程仍然存活——否则 TypeFree 崩溃后残留的文件会让界面显示一个永远连不上的麦克风按钮。

两处容易出错、已经处理的地方

  • 文字重复两遍:TypeFree 默认会把结果粘贴到当前焦点窗口。若不处理,对话框自己插入一次、TypeFree 再粘贴一次,每句话都会出现两遍。因此本应用发起的会话使用 output:'callback',在会话期间暂停粘贴注入,结束后恢复用户原本的设置。

  • 光标位置插入:采用「在光标处拼接」而非追加或整体赋值,并且只在确实缺少空格的位置补一个空格,避免连续口述累积出多余空格。

验证情况

新增 7 项测试(光标插入:位置、不丢失已有文本、空格处理、空语音、越界光标);TypeFree 侧另有 22 项。按测试名与基线比对,无新增失败。

并且不止于类型检查:前端产物经过实际构建,并确认新代码确实存在于打包结果中(dist/assets 中可以找到 typefree_bridge_info 与相应错误文案)。本项目的一贯教训是——代码提交了不等于部署了

SuperAI Agent v0.2.31

Choose a tag to compare

@github-actions github-actions released this 31 Aug 12:05

SuperAI Agent v0.2.31

被代理拦截时的报错,现在短得多、也直接告诉你该怎么做。

改进

  • 报错不再是一大片红色的 HTML:此前会原样截取响应正文的前 300 个字符,而企业代理那张过滤页的前 300 个字符全是样板内容——字符集声明、样式表链接、<script> 开头——真正有用的「URL过滤」和「access denied」反而被截断在外。现在改为提取页面标题与可见文字(去掉脚本、样式与标签),同样的信息一行就能看完。

    改前  body_text="<html><head> <meta http-equiv=\"Content-Type\" content=\"text/html;
          charset=utf-8\"> <title>URL过滤</title> <link href=\"../css/terminal.css\"
          rel=\"stylesheet\" type=\"text/css\"> <script language=\"JavaScript\"..."
    
    改后  body_title="URL过滤"; body_text="access denied"
    

    body_len 仍然如实报告原始长度——这个数字正是识别该页面的指纹。

  • 报错先说当下能做的那一步:这类拒绝是间歇性的,同一个请求过一会儿就能正常返回。此前的提示只给了两条建议——找 IT 把服务商域名加白名单,或换一个网络允许的服务商;两条都没错,但都不是「接下来五秒钟能做的事」。现在会先说明「本应用已经自动重试过,再发一次通常就能通过」,然后才是那两条较慢的办法。测试固定的是这两句话的先后顺序,而不只是它是否存在。

明确没有做的两件事

  • 调整拦截页的重试次数上限:基于用户 29 份日志的实测——9 次兜底重试中 5 次成功、4 次失败;按重试次数拆分:0 次重试 4 成功/2 失败,1 次重试 1 成功/0 失败,2 次重试 0 成功/2 失败。第三次尝试从未挽救过任何一次,这其实支持把上限调低以减少约 45 秒的等待——但该结论仅建立在 2 个样本之上,因此维持原状。

  • 每次重试强制新建连接:连接默认是复用的,因此重试可能仍然走同一条隧道、落到同一个代理节点,这能解释「重试同一个请求往往无效」。机制上说得通,但在本地无法验证,故不予改动。

一个被推翻的推测(记录在此)

请求体积一度看起来就是原因:连续四轮分别是 1,915 tokens 正常,随后 27,683 / 27,927 / 28,170 全部失败,且与长期存在的「发短消息总能成功」现象吻合。但把全部 38 轮统计下来后该结论不成立:正常轮次中位数 29,714,出错轮次中位数 27,927,且 30k 以上的分组反而大多正常。那四行数据只是巧合。

验证情况

共 36 项测试通过,新增 4 项。两处新增防护均经过「故意改坏使其失败」的验证:关掉标签剥离会让可读性测试失败,删掉那句可操作的提示会让顺序测试失败。另有两项旧断言按新文案做了更新——文案变更是有意为之,因此直接改写断言,而不是绕开它。

按测试名与重新生成的基线比对,出现一项 WebSocket Chat Integration;已确认为负载敏感的不稳定用例:该文件在改动前后单独运行均为 22 通过 / 0 失败,且本次改动不涉及任何 WebSocket 相关代码。

SuperAI Agent v0.2.30

Choose a tag to compare

@github-actions github-actions released this 22 Aug 23:33

SuperAI Agent v0.2.30

被代理拦截后的自动重试,这次是真的在重试了。

更正:v0.2.26 与 v0.2.27 的重试从未生效

这两个版本都声称「空回复会自动重试」。实际上一次都没有重试过。

企业网络机器上的调试日志给出了确证:withRetry 每处理一个错误都会记录一行 API error (attempt N/M)。在一次连接被拒的失败中,日志里有整整 11 行;而在「空回复」失败中,一行都没有——说明 withRetry 根本没有看到那个错误。日志中只有一条 attempt=1,随后本轮对话直接结束。

原因是结构性的,我本应在发布前就核对:

withRetry 的操作回调        -> 在 claude.ts:1848 结束
生成器在此被驱动            -> 1853
非流式兜底请求              -> 2554   <- 已在重试循环之外
v0.2.26 抛出的可重试错误    -> 2629   <- 直接进入终止分支

withRetry 的操作回调只覆盖「创建流式请求」这一步;非流式兜底发生在之后的 queryModel 中,早已脱离该重试循环。因此那个 throw 直接落到了终止处理里。当时为了让 shouldRetry 返回 true 而把错误类型设计成 APIConnectionError,是在推理一个根本不会被执行到的函数。

结论:那两个版本只改变了报错文案,没有改变任何行为。

本次修复

  • 重试改到实际发起请求的位置:新增 runEmptyFallbackWithRetry,在结果为空且判定允许重试时,重新执行非流式兜底请求(带短暂退避),耗尽后再给出与此前相同的终止报错。

    这件事也无法交给 executeNonStreamingRequest 自带的 withRetry 处理:代理返回的拦截页是一个完全正常的 200 响应,而非传输层错误,那层重试没有任何可触发的条件。

  • attempt= 现在统计的是兜底请求的次数:此前用的是 attemptNumber,它统计的是内层请求的尝试次数,即使兜底已经跑了三次,它依然显示 1——现场日志里看到的正是这个数字,其误导性与上述问题同源。

  • 删除 EmptyFallbackRetryableError:已无任何地方抛出它。一个名为「可重试错误」却从不会被抛出的类,正是导致这个问题拖了两个版本才被发现的那类东西。

验证情况

共 33 项测试通过,新增 3 项通过统计调用次数来验证重试确实发生——这正是前两次尝试所缺少的断言,也是它们连续两次带着问题发布的原因。

将循环故意改回「只执行一次就返回」(即 v0.2.26/27 的实际行为)后,3 项新测试中有 2 项失败——即这些测试确实能够捕获当初发布出去的那个缺陷。

按测试名比对失败集合,出现一项 SearchService > should respect maxResults limit;已确认为既有的不稳定用例:单独运行连续两次均通过,且在未包含本次改动的代码树上整套服务端测试一起跑时同样会失败,与本次改动无关。

SuperAI Agent v0.2.29

Choose a tag to compare

@github-actions github-actions released this 22 Aug 13:30

SuperAI Agent v0.2.29

日志与报错现在会写明「这台机器到底走的哪条网络路径」。

背景:第一份真实调试日志

v0.2.28 让调试日志真正可以打开之后,收到了企业网络机器上的第一份日志。它立刻回答了两个问题:

  • API error (attempt 11/11),从 12:35:51 持续到 12:37:51 —— 自动重试确实在工作,11 次尝试全部执行;用户感受到的「卡住」,其实就是这两分钟的重试过程。
  • code=ConnectionRefused —— 失败原因是 TCP 连接被直接拒绝(RST),而不是此前的拦截页。清空设置页里的代理之后,本应用完全没有代理可用,于是直连,而该网络会直接拒绝直连出网。

但四个日志文件里,关于「走了哪条路径」的记录一行也没有。本次修的就是这件事。

问题修复

  • PAC 未执行时不再悄无声息applySystemPacProxyif (!pacUrl) return null 是整个函数里唯一不打日志的分支——其余每种结果(选中代理 / 脚本判定直连 / 抓取失败 / 已有显式代理)都会记录。而它恰恰是后果最严重的一个:它意味着「没有应用任何代理」,在一个拒绝直连出网的网络上,这等于彻底不可用。现在它也会记录。

  • 启动时记录一次网络路径describeNetworkRoute() 早已存在,但此前只在「空回复」那一条报错里被调用过。因此从一台我们无法接触的机器上拿到日志,也依然看不出流量是否经过代理、经过哪个代理、这个选择又是从哪来的——而这恰恰是排查任何网络故障时第一个该问的问题。现在 init() 会记录一行。

  • 连接被拒绝时,报错中带上路径信息:此前只有一句 Unable to connect to API (ConnectionRefused),无法区分两种情况:直连被防火墙 RST,还是走了一个并未监听的代理。这两者的解决方向完全相反,而用户截图往往是唯一的证据。现在它会带上与「空回复」报错相同的 [diagnostic: route=...; proxy_source=...] 尾注。

验证情况

共 35 项测试通过。连接错误携带路径信息这一条经过「故意改坏使其失败」的验证;该测试对认证环境变量自带兜底,并会还原 process.env(该对象在测试进程间共享)。按测试名比对失败集合,与基线一致。

明确说明一处测试边界:新增的两条日志语句没有测试——它们是启动过程中的日志输出,此处没有对应的测试脚手架。它们是通过对打包产物开启 DEBUG=1 实跑、断言该行确实出现来验证的,而不是靠单元测试。

尚未解决

SUPERAI_PAC_URL 为何缺失,目前仍不清楚。桌面端只在 Windows 报告存在 AutoConfigURL 时才会传递它,而那台机器确实配置了 PAC(这正是 v0.2.24 的结论)。新增的启动日志会在下一次运行时直接给出答案。

在此之前,请先把代理地址填回设置页:在 sidecar 拿不到 PAC 地址的情况下,那个字段是目前唯一能让流量出网的途径。

SuperAI Agent v0.2.28

Choose a tag to compare

@github-actions github-actions released this 22 Aug 10:28

SuperAI Agent v0.2.28

调试日志现在真的可以打开了;报错中会写明自动重试是否执行过。

问题修复

  • settings.json 中设置 DEBUG 此前完全无效isDebugMode() 带记忆化(memoize),且在 applySafeConfigEnvironmentVariables()settings.env 写入 process.env 之前就已求值完毕,因此结果被永久锁定为「关闭」。而桌面版用户无法传 --debug(sidecar 由界面自行启动),settings.json 是他们唯一的开关——于是这个开关静默失效,桌面版用户实际上根本无法产生调试日志。

    现已修复。用三组对照在真实打包产物上实测(临时配置目录 + 假密钥):

    情形 修复前 修复后
    什么都不设置(对照组) 0 个日志 0 个日志
    settings.jsonDEBUG=1 0 个日志 1 个日志
    superai\settings.jsonDEBUG=1 0 个日志 1 个日志
    进程环境变量 DEBUG=1 1 个日志 1 个日志

    「什么都不设置」这一组修复后仍为 0,用以确认新逻辑不会凭空打开调试模式。

    需要说明的是,此前我在 v0.2.26 与 v0.2.27 的说明中两次写道「调试日志默认开启」,这是错的。 shouldLogDebugMessage 会在 isDebugMode() 为假时直接返回。我当时查的是 getMinDebugLogLevel()(其默认值确实是 debug),但那是级别过滤,不是开关——我少看了一层。因此有用户按提示去找一个从未被写入过的日志文件。

  • 报错中新增 attempt=elapsed=:此前连续三个版本都在改进重试逻辑,却始终无法从一张截图判断自动重试到底有没有执行。现在 attempt=1 表示重试从未发生,attempt=3 表示三次尝试均失败;elapsed 则可区分「瞬间被拒」与「卡满 90 秒空闲超时后失败」。

    这个信息本来也无法从调试日志中得知——原因正是上面那个 bug——因此把它直接写进报错,是唯一不依赖用户额外配置的途径。

如何打开调试日志

%USERPROFILE%\.claude\settings.json 中加入:

{ "env": { "DEBUG": "1" } }

重启后复现问题,日志位于 %USERPROFILE%\.claude\debug\,取最新的一个文件即可。

验证情况

新增 4 项测试,共 34 项通过。其中包含一项对照:仅执行刷新、而没有任何设置时,不得凭空开启调试模式——否则主断言可能因为错误的原因而通过;另有一项测试把「记忆化导致设置失效」这个 bug 本身固定下来,以防它悄悄回归。涉及环境变量的测试会对 DEBUG 做快照并还原,因为 process.env 是跨测试进程共享的。

明确说明一处测试边界:单元测试覆盖的是 refreshDebugSettingsFromEnv 这个函数本身,而非它的调用点——把 managedEnv 中的调用删掉,不会导致任何测试失败。因此该接线是通过上表那组端到端对照实测验证的,而不是靠测试保证的。

src/services/api 与服务端测试套件按测试名逐一比对,无新增失败。另外发现:这些套件的失败测试名单本身在不同运行间会变化(27 / 28 / 25),其中个别用例对负载敏感、单独运行时通过——也就是说这个基线并非一个固定集合。

SuperAI Agent v0.2.27

Choose a tag to compare

@github-actions github-actions released this 22 Aug 08:21

SuperAI Agent v0.2.27

代理返回 407 时会说明原因并自动重试;同时纠正 v0.2.26 中我判断错误的一处设计。

问题修复

  • 407 现在会说明发生了什么:此前用户只会看到 API Error: 407 status code (no body)。这句话字面上没错——407 响应确实没有正文——但它既没说是哪个代理,也没说该怎么办。

    407 的含义是代理要求身份验证。现在的提示会写明是哪个代理(自动去除其中的账号密码)、代理要求哪种验证方式,以及可以怎么处理。若代理要求的是 NTLM / Negotiate(Windows 登录态验证),提示会直接说明本应用无法完成该验证,而不是让用户反复填写一个根本不可能生效的密码。

    这套判断逻辑本来就存在——设置页「测试代理」按钮早已能解析 Proxy-Authenticate 并识别 NTLM/Negotiate——只是从未出现在用户真正会遇到的路径上。本次把同一个答案接到了聊天的报错里。

  • 407 现在会自动重试(有次数上限):此前 shouldRetry 对 407 直接返回 false,于是代理下一次本会放行,这一轮对话却已经结束。企业代理通常按来源 IP 缓存授权状态若干分钟,因此请求成败取决于这台机器最近是否有其他程序完成过验证。上限是刻意保留的:要求 NTLM 的代理会一直拒绝,把整整十次退避耗尽之后才告知原因,比报错本身更糟。

  • 纠正 v0.2.26 的一处误判:拦截页现在也会重试:v0.2.26 中我认为「拦截页是策略性拒绝,每次结果都一样,重试只会让用户白等」,据此没有给拦截页任何重试机会。用户的截图推翻了这一判断——同一个地址、同一个代理,前一秒被拦截,后一秒正常返回。也就是说,那恰恰是最需要重试的情形,而我把它排除掉了。现在拦截页同样会重试,但次数上限低于普通连接失败,确保真正被封禁的地址仍然会快速报错,而不是在十次退避之后。

    这两件事很可能是同一个原因:当代理无法确认连接对应哪个已验证用户时,它要么回 407,要么直接套用默认拒绝策略返回过滤页。

需要说明的(不含糊其辞)

尚未证实代理验证就是根本原因。 它能同时解释 407、时好时坏的现象,以及那个默认拒绝页面,吻合度很高,但目前仍是推断——调试日志尚未查看。

若代理接受 Basic(用户名/密码)验证,那么在「设置 → 代理」中填入账号密码可能就足以解决问题;而 407 响应中的 Proxy-Authenticate 头会直接告诉我们它到底接受哪一种。新版的报错信息里就写着这个信息。

调试日志默认开启,位于:

%USERPROFILE%\.claude\debug\

验证情况

新增 8 项测试,共 30 项通过。三处新增防护全部经过「故意改坏使其失败」的验证:删掉 407 重试、删掉 407 分支、把拦截页上限调到与完整预算相同,各自恰好让一项测试失败。

其中包含一项「接线测试」:构造真实的 APIError(407) 走完整的错误展示路径——「函数写对了但根本没被调用到」这个疏漏我此前连续两个版本都留了下来,这次一并补上。另有一项对照:与之无关的 400 错误仍然不重试,以确认本次改动没有把重试范围扩大到别处。

src/services/api 与服务端测试套件在改动前后按测试名逐一比对,失败集合完全一致,既有的 27 项失败没有任何变动。

SuperAI Agent v0.2.26

Choose a tag to compare

@github-actions github-actions released this 22 Aug 05:57

SuperAI Agent v0.2.26

在不稳定的网络上,连接中断后会自动重试,不再需要手动发「hi?」把对话「叫醒」。

问题修复

  • 流式响应中断后,整轮对话直接结束,且不重试:有用户在企业网络上反馈,几乎每次提问、模型刚开始「思考」时就报错,然后要手动发两三次「hi?」「hello?」才能恢复正常——每个问题都要重复一遍,本地任务和联网任务都一样。

    这个「手动重试」的动作,其实就是本应用自己该做的事。流式请求失败后会退回非流式重试;若该次重试返回的内容为空,代码是把错误信息当作助手消息「输出」,而不是「抛出」,于是 withRetry 的重试预算(10 次,带退避)根本没有机会执行,本轮对话就此结束。更直接的证据是那条错误信息本身的结尾就写着「请重试」——把本应自动完成的重试,交给了用户手动完成。

    现在这种情况会抛出可重试的错误,交由既有的退避重试机制处理。

  • 但代理的拦截页不会重试:代理返回的 URL 过滤页属于策略性拒绝,重试多少次都是同一个页面,只会让用户白等。因此仅对「连接层面」的失败(连接被中断、响应被截断)自动重试,拦截页仍然立即如实报错。

  • 重试全部失败时,仍然保留诊断信息:不会退化成一句笼统的「连接错误」。此前几个版本积累的诊断内容(route=proxy_source=、拦截页原文、被拒绝的地址)会随错误一起传递,最终照常显示。

需要说明的(不含糊其辞)

本次改动没有解释你的连接为什么会中断。 它把手动重试变成了自动重试,症状应当基本消失或大幅减轻,但底层的中断原因仍未查明。目前有两个尚未区分的可能:

  1. 本应用在连续 90 秒没有收到任何数据时会主动中止流式请求(可用 CLAUDE_STREAM_IDLE_TIMEOUT_MS 调整)。若代理对响应做内容检查并进行缓冲,「思考」期间客户端就会一个字节也收不到,从而触发该超时——这也能解释为什么「hi?」这类瞬间就能答完的短问题反而正常。
  2. 代理会关闭空闲的 CONNECT 隧道,于是停顿之后的第一个请求撞上一个已经失效的连接。

要区分这两者,需要日志。 调试日志默认开启,无需重新安装、也无需任何开关,就在:

%USERPROFILE%\.claude\debug\

出错后取最新的一个文件,把包含 Streaming idle timeoutError streamingStreaming stall detectedzero content blocks 的行发来即可定位,并据此判断是否应当调整空闲超时。

验证情况

新增 5 项测试,共 22 项通过。两项新增防护均经过「故意改坏使其失败」的验证:删掉保留诊断的分支会让相应测试失败;把重试判定改成「一律重试」会让拦截页用例失败。src/services/api 与服务端测试套件在改动前后按测试名逐一比对,失败集合完全一致,既有的 27 项失败没有任何变动。

此外,判定逻辑被提取为可测试的独立函数——它位于流式生成器内部,此前两个版本我都只是记下「这里无法测试」而没有解决,这次补上了。

SuperAI Agent v0.2.25

Choose a tag to compare

@github-actions github-actions released this 22 Aug 00:47

SuperAI Agent v0.2.25

PAC 支持已在真实的企业网络上跑通;本次改进的是失败时的报错措辞——让一条报错足以定位问题,而不是把人指向一个本来就正确的设置。

PAC 支持已在真实企业机器上验证

v0.2.24 的说明里写明了一个尚未闭合的缺口:测试用的 PAC 是自建脚本,没有针对真实的企业 PAC 验证过。现在验证完成了。

在此前一直无法完成任何对话的 Win11 机器上:

  • 诊断信息显示 proxy_source=pac——PAC 脚本被抓取、执行、并按结果设置了代理;
  • 脚本选出的代理与之前手动填写的并不是同一台,说明 PAC 确实在按自己的规则做判断;
  • 该机器返回了第一条真实的模型回复。

同时得到一个明确结论:设置页里手动填写的代理会覆盖 PAC。v0.2.23 曾建议用户手动填写代理地址作为临时办法,而这个办法在 v0.2.24 之后反而会盖掉自动判断的结果。如果你曾按 v0.2.23 的提示手动填过代理,升级后请清空该字段,让本应用使用机器自己的 PAC 规则。

报错措辞修正

  • 代理放行连接、随后拒绝该地址时,不再说「有东西在拦截你」:请求确实经由配置的代理发出(route=proxy ...),代理接受连接后返回了自己的 URL 过滤页。此时旧措辞仍然说「代理、防火墙或强制门户正在拦截请求……这是网络配置问题」,会把用户推去修一个本来就正确的设置。现在这种情况下会明确说明:代理已接受连接但拒绝了该地址,这是代理上的过滤策略,既不是服务商故障,也不是代理没配好;能改变结果的只有两件事——请代理管理员放行该服务商域名,或改用网络已允许的服务商。直连情况下的措辞保持不变,因为那时「有东西在拦截你」恰恰是准确的。

  • 报错中会写明被拒绝的具体地址:部分流式错误自带地址(InvalidHTTPResponse fetching <url>),另一些则完全没有(Stream ended without receiving any events)。后者出现时,报错会写出代理、代理来源和拦截页,却唯独不说是哪个地址被拒——而这正是区分「服务商流量被拦」与「本应用访问的其他站点被拦」所需的唯一信息。现在两处调用都会带上当前服务商的地址。

已知限制(明确取舍,而非默默出错)

  • PAC 目前只对服务商地址求值一次,结果全局生效:启动时用服务商的 base URL 执行一次 FindProxyForURL,得到的代理会应用到本进程之后的所有出站请求。而「按 URL 分别判断」正是 PAC 存在的意义,因此访问其他主机(例如联网检索)时用的仍是服务商那条规则算出来的代理,而非脚本为该主机准备的答案。若你的 PAC 对不同站点给出不同代理,这一点可能导致部分非服务商请求被拒。上面新增的地址信息正是为定位此类情况准备的;确认后会改为按主机分别求值。
  • v0.2.24 中列出的其余限制(同步的 dnsResolve、跳过 SOCKS、8 秒/1MB 抓取上限、显式代理优先、失败绝不致命)均保持不变。

验证情况

新增 3 项测试,共 46 项通过。其中包含一组对照:同一张拦截页,在有代理与无代理两种情况下必须给出相反的判断——以此确认分支依据的是路由,而非页面内容里的任何字样。新断言均经过「故意改坏使其失败」的验证,避免出现恒真的测试。

需要说明其边界:新增的地址测试证明的是「传入地址时报错会带上它」,而非「调用处确实传了」——该路径在此没有可用的测试脚手架,只能通过阅读两处调用点确认。