Commit Graph
1290 Commits
Author SHA1 Message Date
JATMN 804e331c53 test: keep gitSettings tests independent of the temp dir location (#2347) 2026-10-23 05:05:50 -07:00
JATMN 4772c395b2 test: run the unit suite without --isolate (TA-001 round 2) (#2346)
* test: run the unit suite without --isolate (TA-001 round 2)

* test: clean up leaks exposed by running without --isolate (TA-001 round 2)

* test: clean up remaining leaks in shared-process runs (TA-001 round 2)

* test: reset query engine trace state before each test (TA-001 round 2)
2026-10-22 23:32:21 -07:00
JATMN ada7f0fe38 test: clean up remaining randomized-order leaks (TA-001 round 2) (#2345)
* test: clean up remaining randomized-order leaks (TA-001 round 2)

* test: keep file suggestion abort test deterministic after module restore (TA-001 round 2)

* test: bound lifecycle trace cleanup and avoid Bun isolate crash (TA-001 round 2)

* test: reset lifecycle trace state without crashing isolated runs (TA-001 round 2)
2026-10-22 22:22:59 -07:00
JATMN 11c6afe8d0 test: isolate env, process and global state leaks across randomized seeds (TA-001 round 2 batch 2) (#2344)
* test: isolate env, process and global state leaks across randomized seeds (TA-001 round 2 batch 2)

* test: keep batch 2 state isolation from leaking into later files (TA-001 round 2 batch 2)

Drop the standalone processExitCode helper self-test. Adding a discovered test file changed native shard assignments and seeded file order, exposing existing cross-file mock contamination. The existing finalizer and skills integration tests retain exit-code assertions and continue to exercise the helper.
2026-10-22 18:06:42 -07:00
JATMN 357b8da08e test: restore module exports after mocks (TA-001 round 2) (#2343) 2026-10-22 18:01:26 -07:00
JATMNandjatmn 0f8b4cbc87 test: restore leaked mock.module registrations behind randomized-order failures (TA-001 round 2) (#2342)
* test: restore real modules after modelOptions crossProfile and catalogDedup mocks (TA-001 round 2)

* test: restore real modules after leaked mock.module registrations (TA-001 round 2 batch 1)

* test: restore resume mocks without overlapping sessionStorage restore

* test: drop withRetry snapshot change from batch 1

* test: restore leaked mocks in batch 2 polluter files (TA-001 round 2 batch 1)

* test: keep discovery cache and retry tests isolated from restored mocks (TA-001 round 2 batch 1)

---------

Co-authored-by: jatmn <12479882+jatmn@users.noreply.github.com>
2026-10-22 17:10:33 -07:00
JATMN 0724ca8783 test: restore leaked module mocks behind the remaining non-isolated shard failures (TA-001 round 2) (#2341)
* test: restore leaked module mocks behind the remaining non-isolated shard failures (TA-001 round 2)

* test: restore real sessionStorage after resume tests (TA-001 round 2)
2026-10-22 14:47:51 -07:00
JATMN 56da2c4b40 test: restore real ColorPicker and CustomSelect exports after wizard step tests (TA-001 round 2) (#2340) 2026-10-22 12:55:21 -07:00
JATMN 90b34cbd9b test: restore leaked settings, sleep and fast-mode mocks and bound loadSkillsDir readiness waits (TA-001) (#2339) 2026-10-22 11:49:18 -07:00
JATMN a63ad4a9e0 test: restore real secureStorage exports after githubModelsCredentials hydrate tests (TA-001 round 2) (#2338) 2026-10-22 10:08:10 -07:00
JATMN 4eae36abd6 test: restore Shell and GrowthBook module mocks after BashTool sandboxAnalysis tests (#2336) 2026-10-22 09:03:45 -07:00
JATMN 5c9b3c92dc test: stub stdin ref/unref in cli premature-exit regression test (#2337) 2026-10-22 08:59:41 -07:00
JATMN c823efe56a test: add snapshotEnv helper and restore leaked env and auth state (TA-001 slice 4) (#2335) 2026-10-22 07:30:08 -07:00
JATMN c84411b9dd test: run attribution suites in a child when auth, beta or routing exports are polluted (TA-001 slice 5) (#2334) 2026-10-22 05:46:03 -07:00
JATMNandjatmn 0d85ac1be5 test: restore leaked provider, betas, sessionTitle and discovery module mocks (TA-001 slice 2) (#2332)
* test: restore leaked provider, betas, sessionTitle and discovery module mocks (TA-001 slice 2)

* test(model): restore real providerProfiles module after model command tests

---------

Co-authored-by: jatmn <12479882.jatmn@users.noreply.github.com>
2026-10-22 05:45:16 -07:00
JATMN 92d71e6b87 test: restore leaked settings, config and path state across test files (TA-001 slice 3) (#2331)
* test: restore leaked settings, config-home and path state in single-process runs (TA-001 slice 3)

* test(printMaxTurns): release shared mutation lock if setup throws
2026-10-22 05:44:44 -07:00
JATMN 3a646df821 test(gateways): assert OpenGateway route default model and credentials (#2330) 2026-10-21 22:29:48 -07:00
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