Skip to content

Commit e1f334f

Browse files
committed
chore: resolve conflicts and skip flaky test on CI
2 parents d2def91 + 4d0603e commit e1f334f

114 files changed

Lines changed: 2456 additions & 786 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "0.11.0"
2+
".": "0.12.0"
33
}

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Changelog
22

3+
## [0.12.0](https://github.com/Gitlawb/openclaude/compare/v0.11.0...v0.12.0) (2026-05-16)
4+
5+
6+
### Features
7+
8+
* **opengateway:** add Gemini 3.1 Flash Lite + GLM 5.1 FP8 to catalog ([#1194](https://github.com/Gitlawb/openclaude/issues/1194)) ([4d04f5b](https://github.com/Gitlawb/openclaude/commit/4d04f5bf4f1acc9d3cc00dab0b1b697e13711207))
9+
10+
11+
### Bug Fixes
12+
13+
* **openai-shim:** surface in-stream errors and truncation hints ([#1174](https://github.com/Gitlawb/openclaude/issues/1174)) ([6174d75](https://github.com/Gitlawb/openclaude/commit/6174d75e983e80601ba369a595d7b64580e3bd51))
14+
* Reduce stable stringify heap usage ([#1104](https://github.com/Gitlawb/openclaude/issues/1104)) ([c433d20](https://github.com/Gitlawb/openclaude/commit/c433d20fdc5666736c800a942bbcc0f0d85cc296))
15+
316
## [0.11.0](https://github.com/Gitlawb/openclaude/compare/v0.10.0...v0.11.0) (2026-05-14)
417

518

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ Advanced and source-build guides:
139139
| GitHub Models | `/onboard-github` | Interactive onboarding with saved credentials |
140140
| Codex OAuth | `/provider` | Opens ChatGPT sign-in in your browser and stores Codex credentials securely |
141141
| Codex | `/provider` | Uses existing Codex CLI auth, OpenClaude secure storage, or env credentials |
142+
| Gitlawb Opengateway | `/provider` or zero-config fallback | Free smart gateway at `https://opengateway.gitlawb.com/v1`; routes Xiaomi MiMo and GMI Cloud partner models by `OPENAI_MODEL` |
142143
| Xiaomi MiMo | `/provider` or env vars | OpenAI-compatible API at `https://api.xiaomimimo.com/v1`; uses `MIMO_API_KEY` and defaults to `mimo-v2.5-pro` |
143144
| Ollama | `/provider` or env vars | Local inference with no API key |
144145
| Atomic Chat | `/provider`, env vars, or `bun run dev:atomic-chat` | Local Model Provider; auto-detects loaded models |
@@ -161,7 +162,8 @@ OpenClaude supports multiple providers, but behavior is not identical across all
161162
- Tool quality depends heavily on the selected model
162163
- Smaller local models can struggle with long multi-step tool flows
163164
- Some providers impose lower output caps than the CLI defaults, and OpenClaude adapts where possible
164-
- Xiaomi MiMo uses `api-key` header auth on the OpenAI-compatible route and currently does not support `/usage` reporting in OpenClaude
165+
- Gitlawb Opengateway uses one OpenAI-compatible base URL. Switch between `mimo-*`, `zai-org/GLM-5.1-FP8`, and `google/gemini-3.1-flash-lite-preview` with `/model`; do not pin the base URL to `/v1/xiaomi-mimo`.
166+
- Xiaomi MiMo uses `api-key` header auth on the direct OpenAI-compatible route and currently does not support `/usage` reporting in OpenClaude
165167

166168
For best results, use models with strong tool/function calling support.
167169

docs/advanced-setup.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,22 @@ export OPENAI_MODEL=llama-3.3-70b-versatile
153153

154154
`GROQ_API_KEY` matches the built-in Groq gateway preset. `OPENAI_API_KEY` also works as a fallback on the generic OpenAI-compatible path, but `GROQ_API_KEY` is the preferred variable for Groq-specific setup.
155155

156+
### Gitlawb Opengateway
157+
158+
```bash
159+
export CLAUDE_CODE_USE_OPENAI=1
160+
export OPENAI_BASE_URL=https://opengateway.gitlawb.com/v1
161+
export OPENAI_API_KEY=anything
162+
export OPENAI_MODEL=mimo-v2.5-pro
163+
```
164+
165+
The Opengateway route is a smart gateway. Keep the base URL at `/v1` and switch
166+
models with `/model` or `OPENAI_MODEL`. Current partner models include:
167+
168+
- `mimo-v2.5-pro`
169+
- `zai-org/GLM-5.1-FP8`
170+
- `google/gemini-3.1-flash-lite-preview`
171+
156172
### Xiaomi MiMo
157173

158174
```bash

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@gitlawb/openclaude",
3-
"version": "0.11.0",
3+
"version": "0.12.0",
44
"description": "OpenClaude opens coding-agent workflows to any LLM — OpenAI, Gemini, DeepSeek, Ollama, and 200+ models",
55
"type": "module",
66
"bin": {

scripts/build.ts

Lines changed: 37 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88
* - src/ path aliases
99
*/
1010

11-
import { readFileSync, readdirSync, writeFileSync } from 'fs'
12-
import { join } from 'path'
11+
import { readFileSync } from 'fs'
1312
import { noTelemetryPlugin } from './no-telemetry-plugin'
1413
import { CLI_EXTERNALS, SDK_EXTERNALS } from './externals.js'
1514

@@ -69,53 +68,42 @@ const featureFlags: Record<string, boolean> = {
6968
// so the previous onResolve/onLoad shim was silently ineffective — ALL
7069
// feature() calls evaluated to false regardless of the featureFlags map.
7170
//
72-
// Fix: pre-process source files to strip the bun:bundle import and
73-
// replace feature('FLAG') calls with their boolean literal. Files are
74-
// modified in-place before Bun.build() and restored in a finally block.
71+
// Fix: transform source as Bun loads each module, stripping the bun:bundle
72+
// import and replacing feature('FLAG') calls with their boolean literal.
73+
// The working tree stays immutable while smoke/build runs.
7574

7675
// Match feature('FLAG') calls, including multi-line: feature(\n 'FLAG',\n)
7776
const featureCallRe = /\bfeature\(\s*['"](\w+)['"][,\s]*\)/gs
7877
const featureImportRe = /import\s*\{[^}]*\bfeature\b[^}]*\}\s*from\s*['"]bun:bundle['"];?\s*\n?/g
79-
const modifiedFiles = new Map<string, string>() // path → original content
80-
81-
function preProcessFeatureFlags(dir: string) {
82-
for (const ent of readdirSync(dir, { withFileTypes: true })) {
83-
const full = join(dir, ent.name)
84-
if (ent.isDirectory()) { preProcessFeatureFlags(full); continue }
85-
if (!/\.(ts|tsx)$/.test(ent.name)) continue
86-
87-
const raw = readFileSync(full, 'utf-8')
88-
if (!raw.includes('feature(')) continue
89-
90-
let contents = raw
91-
contents = contents.replace(featureImportRe, '')
92-
contents = contents.replace(featureCallRe, (_match, name) =>
93-
String((featureFlags as Record<string, boolean>)[name] ?? false),
94-
)
95-
96-
if (contents !== raw) {
97-
modifiedFiles.set(full, raw)
98-
writeFileSync(full, contents)
99-
}
100-
}
101-
}
102-
103-
function restoreModifiedFiles() {
104-
for (const [path, original] of modifiedFiles) {
105-
writeFileSync(path, original)
106-
}
107-
modifiedFiles.clear()
108-
}
109-
110-
preProcessFeatureFlags(join(import.meta.dir, '..', 'src'))
111-
const numModified = modifiedFiles.size
112-
113-
// Restore source files on abrupt termination (Ctrl+C, kill, etc.)
114-
for (const signal of ['SIGINT', 'SIGTERM'] as const) {
115-
process.on(signal, () => {
116-
restoreModifiedFiles()
117-
process.exit(signal === 'SIGINT' ? 130 : 143)
118-
})
78+
const featureFlagTransformedFiles = new Set<string>()
79+
80+
const featureFlagPreprocessPlugin = {
81+
name: 'feature-flag-preprocess',
82+
setup(build) {
83+
build.onLoad({ filter: /\.[cm]?tsx?$/ }, args => {
84+
const normalizedPath = args.path.replace(/\\/g, '/')
85+
if (!normalizedPath.includes('/src/')) return null
86+
87+
const raw = readFileSync(args.path, 'utf-8')
88+
if (!raw.includes('feature(')) return null
89+
90+
let contents = raw
91+
contents = contents.replace(featureImportRe, '')
92+
contents = contents.replace(featureCallRe, (_match, name) =>
93+
String((featureFlags as Record<string, boolean>)[name] ?? false),
94+
)
95+
96+
if (contents === raw) return null
97+
98+
featureFlagTransformedFiles.add(args.path)
99+
return {
100+
contents,
101+
loader: args.path.endsWith('.tsx') || args.path.endsWith('.jsx')
102+
? 'tsx'
103+
: 'ts',
104+
}
105+
})
106+
},
119107
}
120108

121109
let result: Awaited<ReturnType<typeof Bun.build>> | undefined
@@ -149,6 +137,7 @@ result = await Bun.build({
149137
},
150138
plugins: [
151139
noTelemetryPlugin,
140+
featureFlagPreprocessPlugin,
152141
{
153142
name: 'bun-bundle-shim',
154143
setup(build) {
@@ -185,8 +174,7 @@ export async function handleBgFlag() { throw new Error("Background sessions are
185174
],
186175
] as const)
187176

188-
// bun:bundle feature() replacement is handled by the source
189-
// pre-processing step above (see preProcessFeatureFlags).
177+
// bun:bundle feature() replacement is handled by featureFlagPreprocessPlugin.
190178
// The previous onResolve/onLoad shim was ineffective in Bun
191179
// v1.3.9+ because the bun: namespace is resolved natively
192180
// before the JS plugin phase runs.
@@ -456,6 +444,7 @@ sdkResult = await Bun.build({
456444
external: SDK_EXTERNALS,
457445
plugins: [
458446
noTelemetryPlugin,
447+
featureFlagPreprocessPlugin,
459448
// Stub missing internal/optional modules (same pattern as CLI build)
460449
{
461450
name: 'sdk-missing-stub',
@@ -860,9 +849,7 @@ if (!sdkResult.success) {
860849
}
861850

862851
} finally {
863-
// Always restore source files, even if Bun.build() throws
864-
restoreModifiedFiles()
865-
console.log(` 🔄 feature-flags: pre-processed ${numModified} files (restored)`)
852+
console.log(` 🔄 feature-flags: transformed ${featureFlagTransformedFiles.size} files during bundling`)
866853
}
867854

868855
// ── Validate SDK bundle for React/Ink leakage ──────────────────────────────

scripts/no-telemetry-growthbook-stub.test.ts

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,34 @@
1-
import { afterAll, beforeEach, describe, expect, test } from 'bun:test'
1+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from 'bun:test'
22
import { mkdirSync, rmSync, unlinkSync, writeFileSync } from 'node:fs'
33
import { join } from 'node:path'
44
import { tmpdir } from 'node:os'
5+
import { acquireEnvMutex, releaseEnvMutex } from '../src/entrypoints/sdk/shared.js'
56

67
// ---------------------------------------------------------------------------
78
// Setup: dynamically import the source-level growthbook no-op stub.
89
// The stub reads ~/.claude/feature-flags.json for local flag overrides.
910
// ---------------------------------------------------------------------------
1011

12+
const originalFlagsFile = process.env.CLAUDE_FEATURE_FLAGS_FILE
1113
const testDir = join(tmpdir(), `growthbook-stub-test-${process.pid}`)
1214
const flagsFile = join(testDir, 'test-flags.json')
15+
let envLockAcquired = false
16+
let stub: typeof import('../src/services/analytics/growthbook.js')
1317

14-
mkdirSync(testDir, { recursive: true })
18+
beforeAll(async () => {
19+
const envLock = await acquireEnvMutex()
20+
if (!envLock.acquired) {
21+
throw new Error('Failed to acquire env mutex for growthbook stub test')
22+
}
23+
envLockAcquired = true
1524

16-
// Point the stub at our test flags file before import
17-
process.env.CLAUDE_FEATURE_FLAGS_FILE = flagsFile
25+
mkdirSync(testDir, { recursive: true })
1826

19-
const stub = await import('../src/services/analytics/growthbook.js')
27+
// Point the stub at our test flags file before import
28+
process.env.CLAUDE_FEATURE_FLAGS_FILE = flagsFile
29+
30+
stub = await import('../src/services/analytics/growthbook.js')
31+
})
2032

2133
// ---------------------------------------------------------------------------
2234
// Tests
@@ -29,8 +41,19 @@ describe('growthbook stub — local feature flag overrides', () => {
2941
})
3042

3143
afterAll(() => {
32-
rmSync(testDir, { recursive: true, force: true })
33-
delete process.env.CLAUDE_FEATURE_FLAGS_FILE
44+
try {
45+
rmSync(testDir, { recursive: true, force: true })
46+
if (originalFlagsFile === undefined) {
47+
delete process.env.CLAUDE_FEATURE_FLAGS_FILE
48+
} else {
49+
process.env.CLAUDE_FEATURE_FLAGS_FILE = originalFlagsFile
50+
}
51+
} finally {
52+
if (envLockAcquired) {
53+
releaseEnvMutex()
54+
envLockAcquired = false
55+
}
56+
}
3457
})
3558

3659
// ── File absent ──────────────────────────────────────────────────
@@ -145,4 +168,4 @@ describe('growthbook stub — local feature flag overrides', () => {
145168
writeFileSync(flagsFile, JSON.stringify({ tengu_disable_bypass_permissions_mode: true }))
146169
expect(await stub.checkSecurityRestrictionGate('tengu_disable_bypass_permissions_mode')).toBe(false)
147170
})
148-
})
171+
})

src/commands/cacheStats/cacheStats.test.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
* label padding, conditional N/A footnote, recent-rows cap) which can
66
* silently regress — these snapshot tests keep it honest.
77
*/
8-
import { beforeEach, describe, expect, test } from 'bun:test'
8+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
9+
import {
10+
acquireSharedMutationLock,
11+
releaseSharedMutationLock,
12+
} from '../../test/sharedMutationLock.js'
913
import type { CacheMetrics } from '../../services/api/cacheMetrics.js'
1014
import {
1115
_setHistoryCapForTesting,
@@ -50,11 +54,21 @@ async function runCommand(): Promise<string> {
5054
return result.value
5155
}
5256

53-
beforeEach(() => {
57+
beforeEach(async () => {
58+
await acquireSharedMutationLock('commands/cacheStats/cacheStats.test.ts')
5459
resetSessionCacheStats()
5560
_setHistoryCapForTesting(500)
5661
})
5762

63+
afterEach(() => {
64+
try {
65+
resetSessionCacheStats()
66+
_setHistoryCapForTesting(500)
67+
} finally {
68+
releaseSharedMutationLock()
69+
}
70+
})
71+
5872
describe('/cache-stats — empty session', () => {
5973
test('shows friendly "no requests yet" message', async () => {
6074
const value = await runCommand()

src/commands/init.test.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,30 @@
1-
import { afterEach, expect, mock, test } from 'bun:test'
1+
import { afterEach, beforeEach, expect, mock, test } from 'bun:test'
2+
import {
3+
acquireSharedMutationLock,
4+
releaseSharedMutationLock,
5+
} from '../test/sharedMutationLock.js'
26

37
const originalClaudeCodeNewInit = process.env.CLAUDE_CODE_NEW_INIT
48

59
async function importInitCommand() {
610
return (await import(`./init.ts?ts=${Date.now()}-${Math.random()}`)).default
711
}
812

9-
afterEach(() => {
10-
mock.restore()
13+
beforeEach(async () => {
14+
await acquireSharedMutationLock('commands/init.test.ts')
15+
})
1116

12-
if (originalClaudeCodeNewInit === undefined) {
13-
delete process.env.CLAUDE_CODE_NEW_INIT
14-
} else {
15-
process.env.CLAUDE_CODE_NEW_INIT = originalClaudeCodeNewInit
17+
afterEach(() => {
18+
try {
19+
mock.restore()
20+
21+
if (originalClaudeCodeNewInit === undefined) {
22+
delete process.env.CLAUDE_CODE_NEW_INIT
23+
} else {
24+
process.env.CLAUDE_CODE_NEW_INIT = originalClaudeCodeNewInit
25+
}
26+
} finally {
27+
releaseSharedMutationLock()
1628
}
1729
})
1830

0 commit comments

Comments
 (0)