3647 Commits

Author SHA1 Message Date
Wesley Liddick
d3a8db8084
Merge pull request #2833 from alfadb/fix/haiku-strip-context-management
fix(gateway): 按最终 anthropic-beta header 对 body.context_management 做能力维度 sanitize
2026-05-29 10:31:12 +08:00
Wesley Liddick
433f8dcd13
Merge pull request #2834 from DaydreamCoding/pr/openai-codex-cli-allow-claude-code
feat(openai): codex_cli_only 新增放行 Claude Code Codex 插件的机制
2026-05-29 10:30:33 +08:00
Wesley Liddick
6bc1983506
Merge pull request #2853 from wucm667/fix/system-update-already-up-to-date-response
fix(admin): system/update 在无更新时返回 200 + already_up_to_date 而非 500
2026-05-29 10:29:49 +08:00
shaw
6010c3cca9 test: 修复内容审计日志异步断言 2026-05-29 09:57:02 +08:00
shaw
514ac5c6a1 feat: 适配 claude-opus-4-8 2026-05-29 09:56:48 +08:00
shaw
37044b83eb fix(openai): clarify endpoint capability UI 2026-05-29 09:23:06 +08:00
shaw
ed1b57c597 fix(openai): gate routing by endpoint capability 2026-05-29 08:58:10 +08:00
Wesley Liddick
8c1a07852c
Merge pull request #2858 from wey-gu/feat/openai-embeddings-gateway
feat(gateway): Add OpenAI embeddings gateway
2026-05-28 22:15:19 +08:00
Wesley Liddick
6b0ee8594c
Merge pull request #2860 from fofoj/fix/oauth-401-credentials-overwrite
fix(oauth): 401 handler 不再回写 credentials,避免 refresh_token 被陈旧快照覆盖
2026-05-28 21:59:02 +08:00
fofoj
be3613593b test(oauth): update OAuth 401 tests to match new no-write behavior
Two tests in ratelimit_service_401_test.go were encoding the bug behavior
itself:

- OAuth401InvalidatorError asserted updateCredentialsCalls == 1
- OAuth401UsesCredentialsUpdater asserted updateCredentialsCalls == 1
  and lastCredentials["expires_at"] non-empty

Both assertions exercised the exact write-back this PR removes. Update
them to reflect the new contract and guard against regression:

- OAuth401InvalidatorError: assert updateCredentialsCalls == 0
- OAuth401UsesCredentialsUpdater is renamed to
  OAuth401DoesNotOverwriteCredentials with reversed assertions, so it
  now serves as a regression test ensuring the 401 handler never writes
  credentials back from the request-start snapshot.
2026-05-28 20:32:16 +08:00
Wesley Liddick
c3cd2b9f47
Merge pull request #2862 from lyen1688/feat/pre-block-risk-control-runtime
feat: 完善前置拦截模式的风控运行态与审核记录
2026-05-28 20:25:43 +08:00
fofoj
6aec505016 fix(oauth): don't overwrite credentials JSONB in 401 handler
The 401 handler in RateLimitService.HandleUpstreamError set
account.Credentials["expires_at"] = time.Now() and then persisted the
full credentials map via persistAccountCredentials, which routes through
accountRepository.UpdateCredentials -> ent SetCredentials and replaces
the entire JSONB column.

The account passed to the handler is the request-start snapshot taken
by the gateway at SelectAccount time. When another worker has just
rotated refresh_token via oauth_refresh_api.RefreshIfNeeded, the
snapshot still holds the old refresh_token; writing the full snapshot
back rolls refresh_token in the DB back to the stale value.

The next refresh cycle then calls the upstream with the stale token,
receives invalid_grant, and tryRecoverFromRefreshRace re-reads the DB
only to find currentRT == usedRT (because the 401 handler just poisoned
the DB), returns false, and the account is incorrectly disabled.

Drop the credentials write. InvalidateToken + SetTempUnschedulable is
sufficient: the account is held out of scheduling during the cooldown,
and after the cooldown the next request goes through token_provider's
NeedsRefresh check, which routes through the locked, DB-re-reading
RefreshIfNeeded path.

The "force background refresh by setting expires_at = now" semantic is
intentionally dropped. token_refresh_service will naturally pick the
account up when the real expires_at enters the refresh window, and if
the real expires_at has already passed by the time the account becomes
schedulable again, token_provider's NeedsRefresh returns true and
RefreshIfNeeded fires synchronously on the next request.
2026-05-28 20:05:38 +08:00
lyen1688
1b2d8873b0 feat: 完善前置拦截审核运行态 2026-05-28 20:05:24 +08:00
Wey Gu
ccace69d4e Add OpenAI embeddings gateway 2026-05-28 19:39:52 +08:00
wucm667
b15375dfb4 fix(admin): handle already up-to-date updates 2026-05-28 17:27:01 +08:00
alfadb
ddf91e9a7f fix(gateway): 按最终 anthropic-beta header 对 body.context_management 做能力维度 sanitize
上游 Anthropic 在 body 含 `context_management` 但最终发出去的
`anthropic-beta` header 不含 `context-management-2025-06-27` 时会拒收:

    {
      "type": "invalid_request_error",
      "message": "context_management: Extra inputs are not permitted"
    }
    (HTTP 400, request_id 形如 req_011C...)

该 400 在 haiku 路径上触发,因为三个 beta header 构造器有意排除了
context-management beta:

  - HaikuBetaHeader          (messages,     OAuth / mimic CC)
  - APIKeyHaikuBetaHeader    (messages,     API-key)
  - CountTokensBetaHeader    (count_tokens, 所有认证类型)

但 body 中仍然带着 `context_management` 字段,原因有二:

  1. normalizeClaudeOAuthRequestBody 在 thinking_enabled / thinking_adaptive
     打开时为 `clear_thinking_20251015` 主动注入;
  2. 客户端 (Claude Code CLI >= 2.1.87) 原样发送, 网关透传时一并转发。

修复方案: 能力维度对称约束
==========================

对齐已有的 Bedrock 模式
(`backend/internal/service/bedrock_request.go` 中的
`sanitizeBedrockFieldsForBetaTokens`):
根据 **最终** 发出的 `anthropic-beta` header 决定是否保留
`body.context_management`, 而不是按 model 名或路由分类来决定。

新增纯函数:

    sanitizeAnthropicBodyForBetaTokens(body, betaHeader) (body, changed)

如果 `betaHeader` 不含 `context-management-2025-06-27`, 用 sjson 把 body
字段 strip 掉; 否则原样返回。

在所有 Anthropic / Anthropic-兼容 上游出口都接入:

| 路径                                       | sanitize 接入点                                       |
|--------------------------------------------|-------------------------------------------------------|
| /v1/messages OAuth mimic CC                | buildUpstreamRequest                                  |
| /v1/messages OAuth 真 CC 透传              | buildUpstreamRequest                                  |
| /v1/messages API-key                       | buildUpstreamRequest                                  |
| /v1/messages API-key passthrough           | buildUpstreamRequestAnthropicAPIKeyPassthrough        |
| /v1/messages Vertex / service-account      | buildUpstreamRequestAnthropicVertex                   |
| /v1/messages/count_tokens (全部 4 条路径)  | buildCountTokensRequest,                              |
|                                            | buildCountTokensRequestAnthropicAPIKeyPassthrough     |
| Antigravity Anthropic-兼容 上游            | AntigravityGatewayService.ForwardUpstream             |
| Bedrock                                    | (已由 sanitizeBedrockFieldsForBetaTokens 处理)        |

为什么要重排 (而不是加一行调用)
================================

sanitize 必须 **在** `signBillingHeaderCCH` 之前运行。CCH 对整个 body
取 xxHash64 摘要后写入 billing header 里 5 位十六进制的 `cch` 字段;
如果先签名再 strip, 上游对发出去的 body 重算 hash 会和 `cch` 不一致,
请求被判为 third-party。这就要求在 `http.NewRequest` 之前算出最终的
`anthropic-beta` header, 所以把原本内联在 builder 里的 beta 计算逻辑
抽成了两个纯函数:

  - computeFinalAnthropicBeta              (messages 路径: mimic 不透传
                                            客户端 beta)
  - computeFinalCountTokensAnthropicBeta   (count_tokens 路径: mimic 不
                                            跳过白名单透传)

两者逐位保留原行为:

  - mimic 路径在 messages 上跳过客户端 beta, 在 count_tokens 上合并
  - API-key 路径尊重 `InjectBetaForAPIKey` 开关
  - dropSet (`defaultDroppedBetasSet` + BetaPolicy filter) 应用在主路径,
    passthrough / Vertex 路径有意不应用 —— 这条原有的不对称行为本 PR
    不动。

一条语义测试 (`TestSanitizeMustBeBeforeCCHSigning_HashConsistency`) 把
顺序约束文档化并强制守住: 它证明 `sanitize -> signBillingHeaderCCH`
产生的 `cch` 与最终 body 一致, 而 `signBillingHeaderCCH -> sanitize`
产生的 `cch` 会被上游 hash 重算判失败。

为什么是能力维度 (而不是 haiku 模型名匹配)
==========================================

最朴素的"按 model 名 strip"方案
(`strings.Contains(modelID, "haiku") -> DeleteBytes "context_management"`)
有四个真实失败模式:

  1. 过度删除。CLI >= 2.1.87 的真 Claude Code 客户端在 haiku 上同时
     发送 body 字段 **和** `anthropic-beta: context-management-2025-06-27`。
     一律 strip 会让该用户的 `clear_thinking_20251015` 静默失效。
  2. 别名漂移。未来的 haiku 别名 (`claude-3-haiku-...`,
     `claude-haiku-...` 等) 改变匹配面; 任何新别名都会悄悄绕过 strip。
  3. count_tokens 漏覆盖。count_tokens 有自己的 builder 和不同的 beta
     header 集合; 在一个地方做 model 名检查会漏掉这条路径。
  4. API-key passthrough 早退。passthrough builder 在 model 名 strip
     之前就 return 了, strip 根本不执行。

能力维度沿着 header 端到端走, 上述 4 个 case 都由构造方式保证正确,
不依赖任何 modelID 匹配。

防御项
======

  - 当 `sjson.DeleteBytes` 在 `gjson` 刚验证过字段存在的 body 上失败时,
    `sanitizeAnthropicBodyForBetaTokens` 会记 warning 日志 —— 这种情况
    现实中仅在请求中途被破坏时发生, 日志把此前会静默发生的 body / header
    不一致暴露出来。
  - `header_util.go` 新增 `deleteHeaderAllForms`: 在白名单透传已经写入
    canonical 大小写的 `Anthropic-Beta` 之后再覆盖, 否则会同时留下两条。

测试
====

`backend/internal/service` 下新增 44 个测试:

  - 纯函数: anthropicBetaTokensContains x 5, sanitize keep/strip x 6,
    computeFinal{Anthropic,CountTokens}AnthropicBeta x 12
  - normalize 回归 x 5
  - buildUpstreamRequest 端到端 x 4
      (OAuth mimic haiku strip / mimic sonnet preserve /
       真 CC haiku 带客户端 beta preserve / API-key haiku strip)
  - buildCountTokensRequest 端到端 x 2
  - buildUpstreamRequestAnthropicAPIKeyPassthrough x 2 (strip / preserve)
  - buildCountTokensRequestAnthropicAPIKeyPassthrough x 2 (strip / preserve)
  - buildUpstreamRequestAnthropicVertex x 2 (strip / preserve, 含 outgoing
    `anthropic-beta` header 对称断言)
  - CCH 顺序语义测试 x 1

unit 套件全过 (本机 88s), `golangci-lint` 0 issues。

已知局限 (本 PR 范围外)
========================

  - Vertex 路径用透传过来的客户端 `anthropic-beta` header 作为 sanitize
    依据, 而不是 Vertex 侧的能力矩阵。最坏情况是过度 strip (= 当前 main
    的行为, 主路径本来什么都不 strip); 不是 regression。完整的 Vertex
    能力模型属于单独的 PR。
  - Vertex builder 仍然不应用 BetaPolicy filter / dropSet。这是该 builder
    早 return 的既有架构决策, 本 PR 不动。
  - count_tokens mimic 在 haiku 上仍然注入 `context-management-2025-06-27`
    (因为原 count_tokens mimic 逻辑并不像 messages mimic 那样排除它)。
    本 PR 逐位保留 main 的行为; 是否要让它与 messages mimic 的排除策略
    统一是另一个问题。
  - `sanitizeAnthropicBodyForBetaTokens` 目前只处理
    `context_management <-> context-management-2025-06-27` 这一对。如果
    Anthropic 后续推出更多 beta-gated body 字段, 可以在后续 PR 重构为
    `{body 路径 -> required beta token}` 注册表的形式。
2026-05-28 00:02:50 +08:00
DaydreamCoding
56908d3c4c feat(openai): codex_cli_only 新增放行 Claude Code Codex 插件的机制
适用场景:在 Claude Code 中使用 https://github.com/openai/codex-plugin-cc
插件时,插件经官方 codex app-server 以 clientInfo.name="Claude Code" 完成
initialize 握手,请求头被设为 originator=Claude Code、User-Agent 含
"Claude Code/",不在官方客户端白名单内,原本会被 codex_cli_only 拦截 403。

在官方客户端白名单未命中时评估两层独立放行(OR 语义):

- 按账号:account.Extra.codex_cli_only_allowed_clients 引用命名预设
  (目前仅 claude_code),detector reason=allowed_client_matched
- 全局开关:/admin/settings 网关服务 OpenAI 区块新增
  openai_allow_claude_code_codex_plugin(默认 false),开启后对所有
  codex_cli_only 账号统一放行,detector reason=global_allowed_client_matched

签名仍要求 originator=Claude Code 精确等值 + UA 含 "Claude Code/"。
上游转发保持透传不变。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 23:55:34 +08:00
github-actions[bot]
89d96f4b25 chore: sync VERSION to 0.1.132 [skip ci] 2026-05-27 14:28:22 +00:00
Wesley Liddick
cc077862b3
Merge pull request #2797 from wucm667/feat/account-list-created-at-column
feat(admin): 账号管理列表新增创建时间列
2026-05-27 22:10:21 +08:00
Wesley Liddick
bbe847ed3e
Merge pull request #2805 from StarryKira/feat/configurable-pool-retry-status-codes
feat(account): configurable pool-mode same-account retry status codes
2026-05-27 22:09:55 +08:00
Wesley Liddick
69657b2fa1
Merge pull request #2827 from ttt132/fix/api-key-responses-sse-fallback
fix: fallback to SSE body for API key responses
2026-05-27 21:56:00 +08:00
Wesley Liddick
61ce79533e
Merge pull request #2800 from wucm667/fix/scheduler-model-not-found-per-model-cooldown
fix(scheduler): 模型 404 仅冷却该账号-模型组合,不再封整个账号
2026-05-27 21:01:52 +08:00
Wesley Liddick
c949d22725
Merge pull request #2821 from Pluviobyte/fix/long-context-cache-creation-multiplier
fix(billing): apply long-context multiplier to cache_creation price (follow-up to #2816)
2026-05-27 21:01:27 +08:00
Wesley Liddick
8461e42a97
Merge pull request #2822 from lyen1688/feat/group-custom-models-list
feat(group): 支持自定义 /v1/models 模型列表
2026-05-27 21:00:19 +08:00
Wesley Liddick
7579058e91
Merge pull request #2820 from Pluviobyte/fix/antigravity-passthrough-message-start-input-tokens
fix(antigravity): capture message_start input_tokens in streaming passthrough
2026-05-27 20:59:51 +08:00
haichuan
32ea9cfe1f fix: fallback to SSE body for API key responses 2026-05-27 20:24:52 +08:00
lyen1688
f597c1581b feat(group): 支持自定义 /v1/models 模型列表 2026-05-27 18:00:45 +08:00
Pluviobyte
ed2aac25a6
fix(billing): apply long-context multiplier to cache_creation price
Follow-up to #2816 (already merged): the same long-context pricing
exemption that affected cache_read also applies to all three
cache_creation price fields (standard, 5m ephemeral, 1h ephemeral).
computeCacheCreationCost reads these prices directly from pricing and
never sees the LongContextInputMultiplier that computeTokenBreakdown
applies to inputPrice / outputPrice / cacheReadPrice.

For GPT-5.4 / 5.5 above the 272k threshold, this causes the cache_write
portion of long sessions to be billed at roughly half what it should
be (default multiplier 2.0). Cache writes are conceptually input-side
operations and should share the same long-context treatment as input /
cache_read.

This patch threads an explicit multiplier into computeCacheCreationCost
so the function can be unit-tested in isolation and matches the existing
pattern used for cache_read. computeTokenBreakdown captures the long
context decision once and passes LongContextInputMultiplier when it
applies, 1.0 otherwise.

Adds three regression tests mirroring the #2816 cache_read tests:
- positive: long-context triggered -> cache_creation scaled by 2.0x
- negative: below threshold -> cache_creation stays at base price
- breakdown: 5m + 1h ephemeral prices both scaled when applicable

Refs #2816

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 09:59:58 +00:00
shaw
a391635191 更新 OpenAI 使用密钥配置 2026-05-27 17:29:20 +08:00
Pluviobyte
1e6d0b602a
fix(antigravity): capture message_start input_tokens in streaming passthrough
The antigravity upstream-passthrough path (account.Type == AccountTypeUpstream
forwarding to a Claude-format upstream) drains the SSE stream via
streamUpstreamResponse + extractSSEUsage. The extractor only reads top-level
event["usage"], which matches Anthropic's message_delta but misses
message_start where usage is nested under event.message.usage.

As a result, every streaming /v1/messages request through this path drops
the input-side fields (input_tokens, cache_read_input_tokens, cache_creation_*)
and writes a usage_logs row with input_tokens=0 + output_tokens>0. The user
in #2332 observed 2,728 such rows attributed to claude-opus-4-6 / haiku-4-5
streaming requests; their billing on output is correct but the input-side
accounting is missing. (Their "duplicate write from message_delta" hypothesis
isn't borne out by the code — RecordUsage is invoked once per request and
writeUsageLogBestEffort dedupes by request_id; what they're seeing is
single records produced by this buggy extractor.)

Branch on event.type so message_start reads from event.message.usage and
other events keep using event.usage, matching how parseSSEUsagePassthrough
already handles both shapes for the Anthropic OAuth / API-key / Bedrock paths.
Adds two extractSSEUsage table cases plus a TestExtractSSEUsage_StreamingSequence
that drives the message_start → message_delta sequence end-to-end; both fail
on main and pass with this change.

Fixes #2332

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 09:02:15 +00:00
Wesley Liddick
b0142146af
Merge pull request #2816 from Pluviobyte/fix/long-context-cache-read-multiplier
fix(billing): apply long-context multiplier to cache_read price (#2293)
2026-05-27 15:59:11 +08:00
Wesley Liddick
2387cf9934
Merge pull request #2799 from siyuan-123/fix/ws-rate-limit-failover
修复 OpenAI WS 限额时不自动切换账号
2026-05-27 15:14:28 +08:00
SlientRainyDay
b9509e823a fix(billing): apply long-context multiplier to cache_read price
When session long-context pricing is triggered in computeTokenBreakdown
(e.g. GPT-5.4 / GPT-5.5 above the 272k token threshold), the multiplier
was only being applied to InputPricePerToken and OutputPricePerToken.
The cache_read price was left at its base value, so CacheReadCost was
silently undercharged whenever a long-context session also had cache
hits — which is essentially every long Codex / Claude Code session.

Concretely for gpt-5.4 with 300k cache_read tokens, the bug
under-billed the request by exactly 1x the LongContextInputMultiplier
on the cache portion (e.g. 0.075 instead of 0.150 in the regression
test).

Cache reads are conceptually input-side replays, so they should scale
with LongContextInputMultiplier, matching the treatment of
InputPricePerToken.

Adds two regression tests:
- positive: long-context triggered -> cache_read scaled by 2.0x
- negative: below threshold -> cache_read stays at base price

Fixes #2293

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 07:09:28 +00:00
wucm667
8f3b211997 ci: retrigger after transient GitHub Actions codeload outage 2026-05-27 09:34:58 +08:00
StarryKira
21033dceb9 feat(account): configurable pool-mode same-account retry status codes
Pool mode currently retries the same account for a fixed set of
upstream HTTP statuses: 401, 403, 429. Some upstream pool deployments
also need same-account retry for transient provider/proxy statuses
such as 502, 503, 520, 529, but hard-coding more statuses changes
behavior for everyone.

Add a per-account credentials option `pool_mode_retry_status_codes`
that lets admins choose which upstream HTTP status codes trigger
same-account retry in pool mode:

- Unset (default): preserve the current 401/403/429 default
- Explicit list: override the defaults with the configured codes
- Codes normalized to the 100-599 range, deduplicated, sorted

The standalone `isPoolModeRetryableStatus` helper is kept as the
default-only fallback. All 15 gateway call sites switch to the new
`Account.IsPoolModeRetryableStatus` method so behavior is preserved
for accounts that do not configure the new field.

Frontend admin UI gains a "Retry Status Codes" comma-separated input
under the pool-mode section in both Create/Edit account modals
(en + zh i18n).

Fixes #2731

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 11:24:25 -07:00
shaw
f7ac5e5931 fix(openai): preserve chat responses usage billing 2026-05-26 21:33:28 +08:00
wucm667
a31b507484 fix(scheduler): 模型404仅冷却账号模型组合 2026-05-26 20:29:48 +08:00
Wesley Liddick
4b9b63443f
Merge pull request #2790 from Arron196/from-arron-main
修复 Ops SLA 本地限制错误统计
2026-05-26 20:21:11 +08:00
siyuan
08061717b8 fix: enable account failover for OpenAI WS rate limits 2026-05-26 20:07:00 +08:00
Wesley Liddick
4a5c5367cf
Merge pull request #2796 from DaydreamCoding/fix/account-reauth-keep-extra
fix(account): 重新授权不再清空 Extra 配置
2026-05-26 20:06:48 +08:00
Wesley Liddick
b9f421d647
Merge pull request #2751 from wucm667/fix/bedrock-strip-context-management-when-beta-removed
fix(bedrock): v0.1.130 回归 — beta token 被移除时同步剥离 context_management 字段
2026-05-26 20:05:43 +08:00
wucm667
b6a38ddab7 feat(admin): 账号管理列表新增创建时间列 2026-05-26 19:59:12 +08:00
DaydreamCoding
11fe7de926 fix(account): 重新授权不再清空 Extra 配置
Claude / OpenAI 账号重新授权走通用 PUT /accounts/:id 时,后端
UpdateAccount 会全量覆盖 account.Extra(仅保留 5 个 quota 用量键),
导致 base_rpm / window_cost_limit / window_cost_sticky_reserve /
max_sessions / quota_* / privacy_mode 等持久化配置全部丢失。

新增专用接口 POST /accounts/:id/apply-oauth-credentials,沿用
现有 /refresh 路径模式:Credentials-only update + Extra JSONB
key 级合并(UpdateAccountExtra) + ClearError + InvalidateToken。

作用域:Claude OAuth / Claude Cookie auth / OpenAI OAuth 三个
调用点。Gemini / Antigravity 现有路径本就不传 extra,保持不变。

顺带修复:旧重新授权路径未调用 InvalidateToken,导致重新授权后
首请求可能仍用缓存中的旧 token 而立即 401。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 19:46:08 +08:00
benjamin
03ae510c68 fix(ops): exclude count-tokens from metrics errors
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-26 17:21:56 +08:00
benjamin
9c56fe0b0b fix(openai): mark fast-policy entrypoints business-limited
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-26 17:21:45 +08:00
benjamin
5d7df678b1 fix(openai): mark local gateway denials business-limited
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-26 17:19:50 +08:00
benjamin
47fe90eab4 fix(antigravity): mark whitelist denials business-limited
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-26 17:19:37 +08:00
benjamin
c3e7476992 fix(gateway): mark local platform gates business-limited
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-26 17:19:23 +08:00
benjamin
c782c2d9c3 fix(ops): classify local policy denials outside SLA
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-26 17:19:09 +08:00
benjamin
00eb3abbe1 fix(auth): mark Google group denials business-limited
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-26 17:18:55 +08:00