Commit Graph
1273 Commits
Author SHA1 Message Date
JATMNandjatmn dea4d393b3 test: restore leaked provider mocks and Concentrate env in single-process runs (#2328)
Co-authored-by: jatmn <12479882+jatmn@users.noreply.github.com>
2026-10-21 22:29:27 -07:00
JATMN 227fa02e3b test: consolidate stub walls in compact and extractMemories.abort tests (#2329) 2026-10-21 22:21:43 -07:00
JATMN 3a299281ba test(aimlapi): consolidate malformed lease-receipt tests into one table (#2327) 2026-10-21 21:28:33 -07:00
JATMN a8b9fa9aff test: consolidate Z.AI GLM-5.3 registry catalog tests into a golden table (#2326) 2026-10-21 21:24:45 -07:00
JATMN 19bb39c884 test: consolidate gateway catalog tests into per-gateway contract tables (#2325)
* test: consolidate gateway catalog tests into per-gateway contract tables

* test: assert opencode-go catalog excludes removed model ids
2026-10-21 21:00:41 -07:00
JATMN 6baae01922 test: consolidate vendor catalog tests into per-vendor contract tables (#2324)
* test: consolidate vendor catalog tests into per-vendor contract tables

* test: assert full descriptor limits and capabilities for nearai, longcat and xai
2026-10-21 20:59:55 -07:00
JATMN 7844534867 test: stabilize QueryGuard keepalive interruption test (#2323) 2026-10-21 18:18:19 -07:00
JATMN 75c73ea381 docs: add CONTRIBUTING notes for CI caching and subprocess test timeouts (#2322) 2026-10-21 18:08:10 -07:00
JATMN 1c42a10628 fix(shell): honor an abort that lands before the listener is attached (#2321)
* fix(shell): honor an abort that lands before the listener is attached

* test: spawn node timer instead of sleep for Windows portability
2026-10-21 18:00:49 -07:00
JATMN ae83f6e4cf ci: use shallow checkout for smoke-and-tests (#2320) 2026-10-21 17:34:16 -07:00
a4ed687b84 fix(agent): await per-agent output before completion (ORC-1337) (#2318)
* fix: synchronize agent completion with output eviction

Agent task completion could become externally observable before the
output eviction and flush sequence completed, resulting in callers
observing empty output files.

Root Cause
----------
completeAgentTask() returned immediately while evictTaskOutput()
continued asynchronously. Callers could observe task completion
before the eviction/flush chain finished.

Fix
---
- Make completeAgentTask() async and await eviction
- Await completeAsyncAgent() at both call sites
- Clarify ORC-1337 vs gh-20236 in comments

Testing
-------
- AgentTool: 59/59 passing
- LocalAgentTask: 6/6 passing

Refs: ORC-1337, gh-20236

Co-authored-by: Zolow-kuni <285616838+Zolow-kuni@users.noreply.github.com>

* fix: move output barrier before terminal state transition

Review follow-up for ORC-1337 addressing both P1 findings:

1. Ordering: updateTaskState('completed') ran before the eviction
   await, so TaskOutput(block=true) readers released by the terminal
   status could still read a flushing file. The durable-output barrier
   now precedes the state transition.

2. Sidechain transcript: local-agent output is a symlink to the agent
   transcript written by the session-storage queue
   (recordSidechainTranscript) — no DiskTaskOutput entry exists, so
   evictTaskOutput alone resolved without waiting. completeAgentTask
   now awaits flushSessionStorage() (drains the queue and in-flight
   tracked chains) before evictTaskOutput and the status flip.

Also per review:
- Remove ORC-1337-FIX.md from the PR (P3)
- Add deterministic regression test gating both barriers
  (completionBarrier.test.ts) — verified to fail on the previous
  ordering
- Update call-site comments to describe the corrected sequence

Testing: AgentTool + LocalAgentTask 67/67 (incl. 2 new); tsc clean.

Refs: ORC-1337, gh-20236

Co-authored-by: Zolow-kuni <285616838+Zolow-kuni@users.noreply.github.com>

* fix(agent): await scoped output writes before completion

Co-authored-by: Zolow-kuni <285616838+Zolow-kuni@users.noreply.github.com>

* fix: cancel agent transcript waits without delaying cleanup

Co-authored-by: Zolow-kuni <285616838+Zolow-kuni@users.noreply.github.com>

* fix: preserve agent output ownership across session changes

Co-authored-by: Zolow-kuni <285616838+Zolow-kuni@users.noreply.github.com>

* fix: await agent output link replacement after clear

Co-authored-by: Zolow-kuni <285616838+Zolow-kuni@users.noreply.github.com>

* fix: preserve agent output destination and finishing input admission

Co-authored-by: Zolow-kuni <285616838+Zolow-kuni@users.noreply.github.com>

* fix(agent): preserve output destinations for background and resumed sessions

Co-authored-by: Zolow-kuni <285616838+Zolow-kuni@users.noreply.github.com>

* fix: preserve agent output ownership across replacement and clear

Co-authored-by: Zolow-kuni <285616838+Zolow-kuni@users.noreply.github.com>

* fix(agent): bind retained output links to the recording producer

Record successful sidechain append destinations by agent ID and require
retained output links to match that producer record. Preserve the record
across session clears and project overrides for retained resumes.

Cover swapped agent and project targets for captured and live writes.

Co-authored-by: Zolow-kuni <285616838+Zolow-kuni@users.noreply.github.com>

---------

Co-authored-by: jatmn <jatmn@users.noreply.github.com>
Co-authored-by: Zolow-kuni <285616838+Zolow-kuni@users.noreply.github.com>
2026-10-21 16:49:38 -07:00
JATMN 87e6ae6359 test: stabilize watchdog and settings timing (#2319) 2026-10-21 16:25:50 -07:00
JATMNandMatthewFeroz a13ed45b14 feat(gateway): add Merge Gateway provider (#2317)
* feat(gateway): add Merge Gateway provider

Co-authored-by: MatthewFeroz <136640686+MatthewFeroz@users.noreply.github.com>

* fix(merge-gateway): preserve scoped credentials across profiles

Co-authored-by: MatthewFeroz <136640686+MatthewFeroz@users.noreply.github.com>

* fix: complete Merge Gateway credential and catalog handoffs

Co-authored-by: MatthewFeroz <136640686+MatthewFeroz@users.noreply.github.com>

* fix: constrain Merge catalog and admit provider env-file credentials

Co-authored-by: MatthewFeroz <136640686+MatthewFeroz@users.noreply.github.com>

* fix: preserve Merge credential ownership across profile and host settings

Co-authored-by: MatthewFeroz <136640686+MatthewFeroz@users.noreply.github.com>

* fix(merge-gateway): scope ambient key adoption and per-page discovery timeouts

Co-authored-by: MatthewFeroz <136640686+MatthewFeroz@users.noreply.github.com>

---------

Co-authored-by: MatthewFeroz <136640686+MatthewFeroz@users.noreply.github.com>
2026-10-21 15:46:34 -07:00
JATMN cb9f9ee0ca test: stabilize timing-sensitive tests (#2316) 2026-10-21 14:16:47 -07:00
JATMNandRepository contributors 1f6f747309 ci: skip unaffected pull request checks (#2315)
* ci: skip unaffected pull request checks

* fix(ci): run the PR intent scan on every pull request

---------

Co-authored-by: Repository contributors <undisclosed>
2026-10-21 14:09:41 -07:00
JATMN 0b8daaeb04 ci: run unit test shards in parallel (#2314)
* ci: run unit test shards in parallel

* fix(ci): address shard-selection review feedback

* ci: cache package downloads in the shard and dedicated test jobs
2026-10-21 13:19:52 -07:00
JATMN c5f53195ba ci: cache package downloads in PR checks (#2313)
* ci: cache package downloads in PR checks

* ci: cache package downloads in the suite job

* ci: fix suite cache key and separate root and web cache prefixes
2026-10-21 12:45:10 -07:00
JATMNandContributor ae9b3192a4 ci: isolate launcher checks from the full suite (#2312)
Co-authored-by: Contributor <>
2026-10-21 11:16:37 -07:00
JATMNandContributor e81a91784c test: include context efficiency feature tests in full check (#2311)
Co-authored-by: Contributor <redacted>
2026-10-21 10:36:00 -07:00
JATMN 76b4402057 ci: drop duplicate provider test steps from PR checks (#2308)
* ci: drop duplicate provider test steps from PR checks

* docs(contributing): drop provider suites from required preflight
2026-10-21 09:44:33 -07:00
JATMN 9254846b52 test(mcp): remove always-skipped CHICAGO_MCP toggle test (#2309) 2026-10-21 08:51:13 -07:00
JATMNandOpenClaude CI Maintenance 5a1fbe03e6 fix(tests): isolate skills homes and stabilize reload assertions (#2307)
Co-authored-by: OpenClaude CI Maintenance <ci@example.invalid>
2026-10-21 07:05:35 -07:00
JATMN a0c5c7d023 fix: stabilize settings writes and enable fork PR scans (#2290)
* fix: retire settings locks before cleanup and stabilize watcher coverage

* ci: scan fork pull requests with CodeQL

* test: allow full PowerShell permission parser retries
2026-10-21 18:31:02 +08:00
Kevin CodexandOpenClaude 8c46032db2 fix(aimlapi): derive the by-key identity without hashing the API key (#2293)
Resolves CodeQL js/insufficient-password-hash (alert 70):
aimlapiByKeyIdentity fed the API key into a plain SHA-256.

AI/ML API by-key top-ups (src/integrations/aimlapi, ProviderManager)
- The identity is now `key:v2:` + HMAC-SHA256 keyed BY the credential over
  a fixed label, so the key is never the hashed input. It stays cheap on
  the interactive path (no KDF stalling Ink before the async lock), and a
  known-answer test pins the value so the derivation cannot drift silently.
- Receipts written by earlier versions carry `key:<sha256>`. That value is
  deliberately never recomputed, so they are handled without hashing:
  - one that stores this credential (a settled saved-key top-up waiting on
    its profile write, which a claim otherwise refuses to replace even with
    abandonExisting) is matched by its stored key and upgraded under the
    lock, in both reconcileSettledAimlapiTopupStateAsync and the by-key
    claim (new `byKeyApiKey` claim option, passed by ProviderManager);
  - one with no stored key (an in-flight by-key checkout, or a settled
    env-key top-up) cannot be attributed and holds nothing unrecoverable,
    since a by-key payment is credited to its key server-side, so the next
    claim may replace it instead of refusing a re-run that can never match.
  Both are skipped while a mint/exchange lease on the record is live.

Skills watcher test (src/utils/skills)
- `disabled sibling edits stop triggering hooks…` flaked on loaded CI
  runners ("Expected number of calls: 1, Received: 2"). Its edit helper
  retries writes every 50ms until a hook fires, and a retry already in
  flight could land one more hook after the exact-count assertion or after
  the sibling watcher closed. Assert that each phase's edit was observed,
  wait until hooks are quiet between phases, and give the real-filesystem
  test a 15s budget. Under 12 saturated cores: original 2/15 failures,
  fixed 45/45 passes.

Co-authored-by: OpenClaude <openclaude@gitlawb.com>
2026-10-19 13:17:55 +08:00
Kevin CodexandOpenClaude 933019b6a9 test: use crypto.randomUUID for query-engine fixture uuids (#2289)
CodeQL default setup flagged js/insecure-randomness (alert #72, high)
on the custom-pricing budget fixture, which built message uuids from
Math.random(). The values never leave the test process, but the field
is typed as UUID and the same pattern appeared in three places across
two fixtures.

Generate the uuids with crypto.randomUUID() instead. This clears the
alert, matches the Message uuid type, and drops the
`as Message['uuid']` cast in the manual-compact cooldown fixture.
Nothing depended on the old user-/assistant-/test- prefixes.

Co-authored-by: OpenClaude <openclaude@gitlawb.com>
2026-10-18 12:00:39 +08:00
67f3e044e5 Add MiniMax China endpoint routing (#2285)
* Add MiniMax China endpoint routing

* fix(minimax): preserve regional routing and secure credential handling

* fix(minimax): isolate active routes and enforce secure request boundaries

* fix(minimax): reject insecure DNS root-dot quota URLs

---------

Co-authored-by: Octopatch <octopatch@users.noreply.github.com>
Co-authored-by: jatmn <the@jat.mn>
2026-10-18 10:03:20 +08:00
8ee936cfa1 feat(skills): load skills from ~/.agents/skills and opt-in ~/.claude/skills (#2258) (#2283)
* feat(skills): load skills from ~/.agents/skills and opt-in ~/.claude/skills (#2258)

Read user skills that other coding agents keep in their own homes,
read-only and after every OpenClaude-owned skill source:

- ~/.agents/skills (shared cross-agent home used by Codex and
  `npx skills`) loads by default; agentsSkillsEnabled turns it off.
- ~/.claude/skills (Claude Code's private config dir) is opt-in via
  claudeCodeSkillsEnabled, keeping the #1875 isolation default.

Both settings are honored from user, local, flag and policy settings
only; project settings cannot opt a user in. Sibling skills never shadow
anything: same-file dedup keeps the OpenClaude copy of a symlinked skill,
and a sibling skill is dropped when any skill-dir, dynamic, plugin or
bundled skill or built-in command owns its name. When the skill listing
is over budget, sibling skills go names-only before any OpenClaude skill
description is trimmed.

/skills and `openclaude skills list` group and label sibling skills and
hint at disabled homes that hold skills; /config gets a toggle for each
home; `skills remove` refuses them; enabled homes are watched. The
skills CLI now registers bundled skills and built-in plugins like a
session does, so name collisions resolve the same way.

Co-Authored-By: OpenClaude <openclaude@gitlawb.com>

* fix(skills): respect pinned sibling settings in /config and start the watcher on toggle

- /config hid a sibling skills toggle only when managed policy set it.
  Local and --settings values outrank the user settings the toggle
  writes, so flipping it there changed nothing. resolveSiblingSkillsHomes
  now reports pinnedBy for any source that outranks user settings;
  /config hides that toggle and the /skills hint skips that home.
- Turning a home on in /config added it only to a running watcher. With
  no skill directory at startup there was none, so edits in the newly
  enabled home never hot-reloaded. The reload now starts the watcher.

Co-Authored-By: OpenClaude <openclaude@gitlawb.com>

* fix(skills): reconcile sibling source state and CLI bootstrap

* fix(skills): preserve activation during command refresh

* test(skills): wait for observed watcher edits

* fix(skills): reconcile sibling sources and fence stale loads

* fix(skills): reserve owned invocation names against sibling display names

* test(skills): allow bounded subprocess regression to finish

* fix(skills): preserve sibling identity priority and watch linked skills

---------

Co-authored-by: OpenClaude <openclaude@gitlawb.com>
Co-authored-by: jatmn <the@jat.mn>
2026-10-18 10:02:32 +08:00
LM40R4Yandjatmn 66cb795034 fix: fall back to global disabledMcpServers when project has none configured (#1935)
* fix(model): enable model discovery for remote custom OpenAI-compatible providers

Only skip descriptor discovery for local custom providers (Ollama, LM Studio)
that rely on the legacy scoped-cache path. Remote custom providers (custom
gateways, self-hosted APIs) now proceed with descriptor discovery so their
dynamically-discovered models appear in the /model picker and /model refresh
works.

Previously, the routeId === 'custom' guard unconditionally returned null,
preventing ALL custom providers from using descriptor discovery. Remote
providers fell back to hardcoded default options while local providers
correctly used the legacy path via getAdditionalModelOptionsCacheScope().

* fix: fall back to global disabledMcpServers when project has none configured

When a project directory has no disabledMcpServers entry in its per-project
config isMcpServerDisabled() returns false for every server, causing all
configured MCP servers to be enabled in that project.

- isMcpServerDisabled() now falls back to the global disabledMcpServers list
  when the project config does not explicitly set one (undefined).
- setMcpServerEnabled() persists toggles both to the project config and
  the global config, so the enabled/disabled state applies uniformly
  across all projects.
- GlobalConfig type gains the optional disabledMcpServers field.

* fix: skip global disabledMcpServers write for builtin default-disabled servers

Builtin servers track state through enabledMcpServers (allow-list), and
isMcpServerDisabled() returns early on that path without reading
disabledMcpServers. Guard the global write with isDefaultDisabledBuiltin()
to avoid polluting the persisted config with builtin server names.

* fix: address review feedback on #1935

- M1: Only propagate disable to global (enable stays project-local)
- L1: Add disabledMcpServers to GLOBAL_CONFIG_KEYS
- L2: Add comprehensive test coverage for MCP config toggles
- Update PR description to justify model.tsx bundling

M2 (empty-list key removal) intentionally omitted — conflicts with M1:
  enabling a server that exists in global list would remain disabled.

* fix: make config tests resilient by directly manipulating test config objects

Avoid relying on saveCurrentProjectConfig for test setup since its
test-mode Object.assign implementation cannot delete keys.

* debug: add NODE_ENV logging to config test

* fix: use saveCurrentProjectConfig instead of direct property set

Direct property set on getCurrentProjectConfig() returns different
objects between calls in CI, but saveCurrentProjectConfig correctly
updates the shared test singleton.

* fix(mcp): handle global fallback enable/disable edge cases

- When enabling a server disabled only via the global fallback, explicitly
  materialize an empty project-level disabledMcpServers to override it
- When disabling a server on a project with no override, initialize the
  project list from the inherited global list to preserve existing disables
- Add sharedMutationLock to config.test.ts for suite safety
- Reset disabledMcpServers to undefined in afterEach cleanup
- Add regression tests for global fallback enable/disable interactions

* fix(mcp): preserve inherited disables and narrow PR scope

* Preserve concurrent MCP disables in first project override

* Publish stale inherited MCP toggles even when disk matches

* fix(mcp): publish local toggles after peer project overrides

---------

Co-authored-by: jatmn <the@jat.mn>
2026-10-18 10:01:53 +08:00
LM40R4Yandjatmn 6e1694892d fix(model): enable discovery for remote custom providers (#1895)
* fix(model): enable model discovery for remote custom OpenAI-compatible providers

Only skip descriptor discovery for local custom providers (Ollama, LM Studio)
that rely on the legacy scoped-cache path. Remote custom providers (custom
gateways, self-hosted APIs) now proceed with descriptor discovery so their
dynamically-discovered models appear in the /model picker and /model refresh
works.

Previously, the routeId === 'custom' guard unconditionally returned null,
preventing ALL custom providers from using descriptor discovery. Remote
providers fell back to hardcoded default options while local providers
correctly used the legacy path via getAdditionalModelOptionsCacheScope().

* fix: restore routeId === 'custom' fallback for legacy OpenAI discovery

* test(model): add remote custom route regression test

* fix(model): forward custom auth headers to descriptor discovery for remote custom routes

- Add OPENAI_AUTH_HEADER forwarding in getOpenAIDiscoveryRequestOptions
- Update remote-custom test to verify descriptor discovery path
- Fix test for pooled credential to expect {} headers instead of undefined

* fix(model): address review feedback - auth headers, cache keys, fallback

- P1: Properly replicate inference auth header logic (OPENAI_AUTH_HEADER +
  OPENAI_AUTH_SCHEME + apiKey fallback matching the shim)
- P1: Update findCachedCatalogEntryForApiName and
  getNvidiaNimDiscoveryCacheKeyForEnv to include OPENAI_AUTH_HEADER in cache
  key calculation so readers match the picker's partition
- P2: Fall through to legacy OpenAI path when dynamic discovery catalog has
  no usable entries (no static models and no cached discovery)

* fix(model): keep custom discovery repair scoped and auth-consistent

* fix(discovery): preserve custom credentials across refresh

* fix(model): retain filtered custom fallback and isolate cache mocks

---------

Co-authored-by: jatmn <the@jat.mn>
2026-10-18 10:01:06 +08:00
JATMN 0e9170fd37 fix: complete full checks and repair baseline test failures (#2286)
* fix: complete full checks and repair baseline test failures

* fix: bound CI tests and avoid synchronous compiler hangs

* fix: avoid isolated Bun stdio pipe failures in CI

* test: address CI repair review findings

* docs: cover fixture helpers counted by review
2026-10-18 09:22:51 +08:00
github-actions[bot] 864fdab6fb chore(main): release 0.33.0 (#2276)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
v0.33.0
2026-10-15 08:32:43 +08:00
Borges fc3be25e25 feat(sessions): add /sessions to resume or delete saved conversations (#2262)
* feat(sessions): add /sessions to resume or delete saved conversations

- New /sessions command: lists this repo's saved conversations; Enter opens
  Resume / Delete / Back; delete asks for confirmation (Cancel by default);
  `d` pressed twice deletes the focused session.
- Deleting removes the transcript and its sidecars (.cast, .replay.json,
  <id>/), refuses the active session and validates the id and path.
- Fix: late metadata writes no longer recreate a deleted transcript.
- Fix: launches that only run slash commands no longer create a session or
  print a resume hint at exit.

Closes #2223

* fix(sessions): keep writing to a transcript whose delete failed

- deleteSessionFiles: if removing the transcript fails, unmark it so
  later writes are not dropped while the file is still on disk
- forgetDeletedSession: also wait for in-flight direct appends to the
  deleted path, not only queued writes
- docstrings for sessionKey, isDeletedSessionFile, restoreSessionWrites

* fix(sessions): wait for every removal before restoring writes

A sidecar failing fast made Promise.all reject while the transcript was
still being removed, so writes were restored and could recreate it.
Settle all removals first; restore only if the transcript survived, and
treat a leftover sidecar as an orphan once the transcript is gone.

* fix(sessions): restore writes when the deletion barrier fails

forgetDeletedSession marks the transcript before flushing; if a pending
write fails, flush rejects and nothing is removed, but the transcript
stayed marked and later writes were skipped. Undo the mark and rethrow.

* fix(sessions): keep the list usable when a resume fails to load

loadFullLog ran outside any error handling, so a transcript that failed
to load left the dialog busy and ignoring input. Report the error, clear
busy and return to the list.

* fix(sessions): do not resume a transcript that failed to load

loadFullLog falls back to the message-less lite log when a transcript
can't be read, so resuming it opened an empty conversation. Treat that
fallback as a load failure, and test it with the real loader.

* fix(sessions): drop buffered entries when the session changes

Local-command entries are buffered until the first real message. If the
active session switched before that, they were written into the new
session's transcript. Track the session that owns the buffer and drop it
on a switch.
2026-10-14 12:36:28 +08:00
Kevin Codex e736c28257 feat(provider): add GMI Cloud as a provider and official partner (#2279)
* feat(provider): add GMI Cloud as a provider and official partner

Add GMI Cloud (https://www.gmicloud.ai/) as a first-class
OpenAI-compatible provider and list it in the partner roster.

Provider (`gmi-cloud`):
- Gateway descriptor for https://api.gmi-serving.com/v1 with a hybrid
  catalog: a short curated list plus authenticated /v1/models discovery.
  Defaults to deepseek-ai/DeepSeek-V4-Pro.
- GMI_API_KEY is a dedicated credential. A generic OPENAI_API_KEY is
  never sent to GMI, and the GMI key is never mirrored to another host.
- Reachable from /provider, `--provider gmi-cloud` (which also reads an
  optional GMI_MODEL) and saved profiles, including relaunch.
- `--provider gmi-cloud` drops an API format or custom auth header left
  by another provider, since GMI is Bearer-only chat completions.
- Routes by its default base URL only. The same host also serves GMI's
  Anthropic-compatible API, which stays a custom Anthropic endpoint.
- Forwards GMI_API_KEY to spawned teammates.

Partner:
- README partners table and the website roster, with light and dark
  logos under docs/assets/ and web/public/partners/.

Docs: README provider row, .env.example and the website provider and
configuration pages. Also corrects the README line that said the
Opengateway routes GMI Cloud models, which it no longer does.

* docs(readme): use Twigpine's dark logo in dark mode

The partners table showed the light Twigpine logo as a white tile on GitHub's dark theme. Serve https://twigpine.com/logo-dark.png there, the same markup the Zero README uses.

* fix(gmi-cloud): curate only models GMI's /v1/models serves

A live /v1/models call (79 models) shows GMI does not serve
anthropic/claude-sonnet-4.6 or anthropic/claude-opus-4.7 on the
OpenAI-compatible API, so drop them from the curated seed. DeepSeek V4
Pro and V4 Flash (GMI's own coding picks) remain; discovery lists the
rest.

Tests now use the item shape the live endpoint returns (`name`,
`context_length`, `pricing`, no output limit), and the GMI_MODEL
examples use a served model id.

* feat(gmi-cloud): default to Qwen 3.8 Max and curate Qwen 3.8 Max and Flash

GMI Cloud's partner models are Qwen 3.8 Max and Qwen 3.8 Flash. Add
shared descriptors for both (context as GMI's /v1/models reports it,
262,144 and 1,048,575; a conservative 65,536 output cap since GMI does
not publish one), curate them ahead of the DeepSeek V4 picks, and make
Qwen/Qwen3.8-Max the route default.

Verified live through a built CLI: read-a-file then write-a-file agent
turns succeed on both models, and `--provider gmi-cloud` with no
--model uses Qwen/Qwen3.8-Max.

* fix(gmi-cloud): keep the GMI key on GMI's HTTPS /v1 API (review feedback)

Address CodeRabbit's three findings on #2279:

- isGmiCloudBaseUrl now accepts only HTTPS on the default port, host
  api.gmi-serving.com, and a /v1 path or one below it. Plain HTTP,
  custom ports, the host root (GMI's Anthropic-compatible API) and
  other paths are rejected.
- buildGmiCloudProfileEnv no longer falls back to an ambient
  OPENAI_BASE_URL, which may belong to another provider. It keeps an
  explicit GMI URL and otherwise uses the route default, so the key it
  returns always targets GMI.
- The profile env mirror sets GMI_API_KEY only when the profile's URL is
  GMI's, not for any gmi-cloud profile, matching the startup checks.

Each fix has a test that failed before the change.

* fix(gmi-cloud): withhold the GMI key from a retargeted gmi-cloud profile

Address CodeRabbit's follow-up on #2279. Applying a gmi-cloud profile
whose base URL is not GMI's inference API installed the saved key as
OPENAI_API_KEY for that URL. Withhold it there, the same way this
function already treats retargeted ApiSmart, Concentrate, API Route,
LLMTR and Command Code profiles.

A gmi-cloud profile with no base URL now resolves to the GMI default
(like those routes), and the GMI_API_KEY mirror follows the resolved
URL.

Tests cover the retargeted profile in-session (neither key installed),
the empty-URL profile, and the persisted startup env of a retargeted
profile (always GMI's URL, never the other host). The first two failed
before this change.
2026-10-14 12:29:44 +08:00
Ricardo-M-L fc826031ae fix(statusline): reset context usage after full compaction (#2275)
* fix(statusline): reset context usage after full compaction

* test(statusline): cover mounted refresh after full compaction
2026-10-14 08:33:37 +08:00
Kevin Codex 86649c0cde chore(site): move the website to openclaude.twigpine.com (#2274)
The site's canonical address, sitemap and docs links now use
twigpine.com hosts (openclaude, api, ads, explorer). The old
openclaude.gitlawb.com address forwards every path to the new one.
The repo mirror link pointed at a path that returned 404; it now links
the Explorer page that resolves.
2026-10-13 16:29:44 +08:00
Kevin CodexandOpenClaude 79ced71282 chore(rebrand): remove one-time rename scaffolding, fix X links and install-page Node version (#2270)
The npm rename has shipped: @twigpine/openclaude@0.32.0 and the one final
@gitlawb/openclaude@0.32.0 are both published, and the old package is
deprecated. The pieces that existed only to get that final old-name release
out can go.

Release workflow
- Remove the legacy-scope-gate job, the legacy steps in install-verify and
  the publish-npm-legacy-scope job. The job graph is back to
  release-please, install-verify, publish-npm and docker, so a release no
  longer depends on a registry lookup for the old package.
- Keep what proved itself in the 0.32.0 release: skipping a version that is
  already published, publishing the verified dist/ with --ignore-scripts,
  and the provenance and trusted-publisher line in the run summary.

App code
- Remove src/utils/packageRename.ts, the package-renamed startup notice, and
  the early exits in `openclaude update` and `/update`. They only fire in a
  build published under the old name, and no such build will be made again.
- Remove the tests that covered them, including the release guard test that
  held the legacy wiring together.

Docs and website
- X links point at x.com/twigpine (README badge and community link, the
  website community list and llms.txt).
- The website install page said Node.js 20 or newer; the package requires 22.
- The upgrade notes for people coming from @gitlawb/openclaude stay.

Co-authored-by: OpenClaude <openclaude@gitlawb.com>
2026-10-12 12:39:24 +08:00
github-actions[bot] 88baafe316 chore(main): release 0.32.0 (#2243)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
v0.32.0
2026-10-12 11:33:10 +08:00
Kevin CodexandOpenClaude 50d4058861 feat: publish as @twigpine/openclaude (#2269)
The npm package moves from the @gitlawb scope to @twigpine. Everything that
names the package now uses the new name, and the release workflow can create
the new package and close out the old one.

Rename
- package.json, bun.lock, release-please config, docs, website, VS Code
  extension and tests use @twigpine/openclaude.
- scripts/build.ts takes the baked-in package name from package.json instead
  of repeating it; scripts/verify-clean-install.ts does the same.

One final release under the old name, then nothing
- Shipped builds have no working auto-updater (the no-telemetry plugin stubs
  it out), so people update by re-running the install command. Anyone still
  typing the old name lands on whatever is latest there.
- release.yml publishes this release once more as @gitlawb/openclaude. That
  build shows a startup notice, and `openclaude update` and `/update` print
  the same note and stop, saying the package moved and how to switch.
- legacy-scope-gate limits this to a single release. It only runs while the
  old name's latest version is still 0.31.0.
- There is no in-place migration. Both packages own the `openclaude` bin, so
  npm refuses to install one over the other, and a forced install is undone
  by uninstalling the old package. The docs give the safe order.

Tokenless releases (OIDC only)
- Both publish steps authenticate through npm trusted publishing and attach
  provenance; no npm token is used anywhere. npm only attaches a trusted
  publisher to a package that already exists, so @twigpine/openclaude is
  created once by hand as an empty 0.0.0 placeholder before its first release.
- Publish steps skip a version that is already on the registry, publish the
  verified dist/ with --ignore-scripts, and report provenance and whether a
  trusted publisher was used in the run summary
  (scripts/npm-publish-report.ts).
- The legacy-named build is built and install-verified before anything is
  published.

Co-authored-by: OpenClaude <openclaude@gitlawb.com>
2026-10-12 11:21:36 +08:00
Ricardo-M-L 76b12a64fc fix(api): validate retry count environment values (#2172) 2026-10-12 09:21:42 +08:00
GravireiandGravirei afa71563b6 chore(rebrand): update Gitlawb references to Twigpine (#2246) (#2248)
* chore(rebrand): update Gitlawb references to Twigpine (#2246)

The GitHub org has been renamed to Twigpine, so point repo URLs, badges,
CI repo-identity guards, and user-facing sponsor/partner labels at the new
name.

Scope is deliberately limited to references that are safe to change today:

- GitHub URLs (github.com, api.github.com, raw.githubusercontent.com),
  including the skills registry and release-please release notes
- The release-please release trigger, which gates on the repo slug and
  would otherwise stop publishing
- User-visible display strings: gateway label/name, AI/ML API partner
  name, sponsored-tip sponsor, and docs/site prose

Left on the old name because the rename has not landed for them:
- `@gitlawb/openclaude` npm scope (the package does not exist on npm)
- opengateway/ads/docs subdomains, which resolve only under gitlawb.com
- The `gitlawb-opengateway` preset id, tip ids, and gateway file names,
  which are persisted in user config and sent on the wire

* fix(aimlapi): stop matching topup intent on partnerName (#2246)

partnerName is display metadata that travels alongside the partner id, not
part of the intent identity. It is now excluded from INTENT_KEYS, so
matchesIntent no longer requires it to be unchanged.

Without this, renaming the partner on the Gitlawb -> Twigpine rebrand would
have stranded any in-flight topup: a receipt persisted under the old name
fails the strict equality check, so loadAimlapiTopupState returns null and
the user restarts checkout. partnerId remains the identity field, so
receipts are still scoped to the correct partner.

* fix(web): use current Twigpine artwork for the partner card (#2246)

The partner asset was a byte-identical rename, so the roster row rendered
the former GitLawb key/branch symbol under the Twigpine name while the
README pointed at the official tree mark. Vendors the current artwork from
twigpine.com/logo.png, resized to the roster's 256x256 convention, keeping
the self-hosting contract in web/src/data/partners.ts.

---------

Co-authored-by: Gravirei <gravirei@users.noreply.github.com>
2026-10-12 09:15:34 +08:00
Sakiandliuyang 5bc160fb8b feat: add AnySearch as an explicit web search backend (#2261)
* feat: add AnySearch web search backend

* fix(anysearch): preserve non-json error details

---------

Co-authored-by: liuyang <liuy.web@lingyu.work>
2026-10-12 08:39:56 +08:00
Monil Kantwala 5ff58c4ff9 perf(read): dedup touched-but-identical files and covered sub-ranges (#2259)
The Read dedup stub only fired for the exact same range with an unchanged
mtime. Two common cases re-sent content the model already had:

- mtime moved but bytes did not (git checkout/stash pop, formatters, touch)
- a sub-range of a file that was already read in full

Extract the eligibility check into readDedup.ts. On an mtime mismatch, re-read
the stored range and compare it byte-for-byte before deduping, refreshing the
cached timestamp on a match. Treat an in-bounds range of a prior full Read as
already seen. Edit/Write entries, partial views, out-of-range offsets, and any
stat/read failure still fall through to a normal read.
2026-10-08 06:37:18 +08:00
serafkulandClaude Opus 5 53c7ed9c51 fix(hooks): match the .sh suffix case-insensitively on Windows (#2254)
getWindowsBashHookCommand prepends `bash` so a directly invoked script
executes instead of opening in the default file handler. The suffix test
was case-sensitive, but the behaviour it works around is not: Windows
picks a handler by extension without regard to case, so `./hook.SH`
opened in an editor exactly as `./hook.sh` would.

A hook that never ran is the quiet kind of failure - on UserPromptSubmit
it blocks the prompt, and the only trace is a DEBUG_SDK session log.

Three cases added to the prefix list (`./hook.SH`, `./hook.Sh argument`,
a quoted uppercase path) and two to the preserve list, so an uppercase
suffix that is not the command word still changes nothing. Without the
flag those three are the only failures in the file.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-10-06 18:42:39 +08:00
serafkulandClaude Opus 5 10254f4140 fix(openai-shim): keep terminal usage from a repeated finish_reason (#2252)
Some OpenAI-compatible providers end a stream twice: one chunk carrying
the finish_reason, then a second, identical but for the usage, with
choices still populated. The final-usage fallback required an EMPTY
choices array, so that second chunk was skipped and its usage dropped.

The streaming loop ignores a repeated finish_reason by design, so nothing
else picked it up: every assistant message from such a provider stored
usage of all zeros. Anything reading the size or the cost of a
conversation reads it from there - tokenCountWithEstimation walks back to
the newest message carrying usage and trusts it - so automatic compaction
on size could not fire, the context warning never warned, and cost
tracking recorded nothing. A measured session reached 1763 messages with
preTokens: 183 recorded at its compaction boundary.

Dropping the empty-choices requirement leaves hasEmittedFinalUsage as the
single guard against a double emit, which the existing empty-choices and
both-terminal-chunks tests already cover.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-10-06 10:44:45 +08:00
rome-xi 3ed1c5a191 docs: clarify sharp opt-in for local image decode vs passthrough (#2226)
Maintainer confirmed sharp stays intentionally opt-in on published
installs. Document that clipboard paste / local FileRead decode need
`npm i -g sharp`, while pre-encoded URL/base64 passthrough does not.
2026-10-06 10:42:56 +08:00
Kevin CodexandOpenClaude 790c009ae9 feat(search): make Exa the default web search backend and add /search (#2257)
* feat(search): make Exa the default web search backend and add /search

Exa now leads the auto chain: keyed Exa (EXA_API_KEY) runs first, other
keyed backends next, then a new keyless Exa free-tier adapter (Exa's hosted
MCP endpoint), with DuckDuckGo as the last-resort fallback. Built-in search
for Anthropic/Vertex/Foundry/Codex is unchanged. WEB_SEARCH_PROVIDER=exa
uses the keyed API when a key is set, otherwise the free tier.

New Exa options: EXA_SEARCH_TYPE, EXA_NUM_RESULTS, EXA_FREE_TIER (opt-out).
The free-tier adapter handles all three rate-limit reply shapes, spaces
calls to Exa's default QPS, backs off for five minutes after a limit, and
parses Exa's plain-text result blocks defensively.

New /search command: pick a backend, add or remove an API key via a masked
input, show status, run a test search. Choices are saved to the env block
of ~/.openclaude.json (0600) and applied immediately; settings.json env
entries that override them are reported instead of silently losing.

Security: isSensitive arg redaction now also covers local-jsx commands
(/ads, /search), which previously recorded raw inline args in the
transcript and model context.

Co-Authored-By: OpenClaude <openclaude@gitlawb.com>

* fix(search): fall through when domain filters empty the Exa free-tier hits

The keyless Exa endpoint has no server-side domain filters, so when the
client-side filter removed every hit, runSearch returned a successful empty
result and never tried DuckDuckGo, which can honor the filter. Providers can
now set `fallbackInAuto` on their output; auto mode skips to the next
backend on that signal, and returns the filtered-empty result only if no
later backend does better. Genuine no-results replies and explicit `exa`
mode are unchanged.

Also adds doc comments to the touched functions that lacked them.

Co-Authored-By: OpenClaude <openclaude@gitlawb.com>

---------

Co-authored-by: OpenClaude <openclaude@gitlawb.com>
2026-10-06 10:37:04 +08:00
88a2286baf Honor session context windows and cancel stalled MCP calls (#2250)
* Honor session context windows and cancel stalled MCP calls.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>

* fix: address context and MCP cancellation review feedback

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>

* test: wait for MCP cancellation to reach server

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>

---------

Co-authored-by: root <root@nihalluffy.qnc1y4wdjnjulnzfea2dkakqzf.milx.internal.cloudapp.net>
Co-authored-by: Codebuff <noreply@codebuff.com>
2026-10-05 12:01:32 +08:00
John Costa 0bef4d2230 fix(openai-shim): strip store for Anthropic models on OpenAI-compatible routes (#2242) (#2247) 2026-10-05 11:50:32 +08:00
Renato Aparecido da Silva 87d4e3a6e8 Fix/2238 color prompt layout (#2239)
* fix(prompt): prevent color-only banner layout regression

* test: fix standalone banner regression cleanup

* fix: preserve team border identity and validate color-only prompts

* fix: suppress standalone banners while team identity is active

* fix: resolve prompt color from AppState team members

Resolve explicit self color, matching self/leader member color, then dynamic teammate color. Preserve mode priority and suppress saved standalone colors for active teams.

Validation (Windows, Node 24.21.0, Bun 1.4.2):
- PromptInput tests: 21 passed, 0 failed, 42 assertions.
- Frozen install, typecheck, type tests (10 files), build/smoke/deadcode, both CLI version checks, diff check and PR intent scan passed.
- Full check incomplete: hung at repo-map timeout cancellation; isolated PR test passed, isolated unchanged base 9a2910da also hung. Both stuck test processes were terminated. Seven launcher failures preceding the hang match recorded base failures.
- Provider: 1701 passed, 1 failed; watchdog failure reproduced on base in prior comparison.
- Recommendation: 164 passed, 1 failed; default-profile fallback failure reproduced on base.
No provider, launcher, or repo-map code changed. Clean-runner CI remains required.
2026-10-05 11:43:44 +08:00
DennyHo0917 9a2910da23 feat(gateway): add API Route hybrid gateway (#2225)
* feat(gateway): add API Route hybrid gateway

Add API Route as a descriptor-first OpenAI-compatible aggregating gateway with hybrid catalog discovery, dedicated API_ROUTE_API_KEY credentials, and route boundary protections.

* fix(gateway): address review feedback for API Route gateway

* test(api-route): verify auth header withheld on noncanonical URL

* fix(api-route): honor generic saved profile credentials

* test(api-route): cover generic profile credential fallback

* fix(api-route): protect route vars from settings overrides

* fix(api-route): allow dedicated vars in provider env files

* test(api-route): isolate provider flag environment

* fix(api-route): preserve env-only intent across startup profiles

* test(api-route): cover provider env file loading

* test(api-route): cover host-managed settings boundary

* test(api-route): cover saved profile ownership and switch cleanup

* test(api-route): preserve env-only startup selection

* test(api-route): verify saved profile credential boundary

* test(api-route): accept canonical saved profile credentials

* test(api-route): cover saved profile relaunch

* sync: apply upstream xAI OAuth state validation

* sync: apply upstream xAI OAuth state validation

* sync: apply upstream xAI OAuth state validation

* test(api-route): preserve env-file precedence and atomicity

* test(api-route): cover saved-only profile contract

* fix(gateway): register api-route for proxy credential withholding and sanitize model flag

- Add api-route to isNoncanonicalDedicatedOpenAILaunch to strip ambient credentials
- Register persistedApiRouteProxy in applyStartupEnvFromProfile
- Delete OPENAI_API_KEYS on noncanonical base URLs in applyApiRouteEnvOnlyDefaults
- Sanitize API_ROUTE_MODEL with usableProviderModelEnvValue in applyProviderFlag
- Add regression tests covering credential withholding and model placeholder handling

* fix(gateway): clear OPENAI_API_KEYS in canonical API Route selection

- Delete OPENAI_API_KEYS when dedicated API_ROUTE_API_KEY is mirrored in applyApiRouteEnvOnlyDefaults
- Delete OPENAI_API_KEYS in applyProviderFlag for canonical api-route selection
- Add request-level regression test verifying API_ROUTE_API_KEY precedence over OPENAI_API_KEYS
- Add providerFlag test confirming credential pool cleanup

* fix(api-route): complete saved profile credential parity
2026-09-29 12:59:42 +08:00