From 6b28478ef1df94d2bb481927724c88900e4c3e09 Mon Sep 17 00:00:00 2001 From: JUN Date: Sat, 12 Sep 2026 01:58:39 +0900 Subject: [PATCH 1/3] feat(clients): add omo as an export and integration client omo-ai@beta is a launcher around @code-yeongyu/senpi, and senpi reads the same models.json contract Pi, Prime and Aside already use. That was verified rather than assumed: the document buildPiClientConfig emits validates true against senpi's own compiled validateModelsConfig, while an audio input modality and a keyed models object both validate false. Because senpi validates compat.sendSessionAffinityHeaders, omo opts into it the way pi does, and the flag is passed on both the build and the contribution path so ocx export and an enabled integration cannot emit different documents. Path resolution mirrors omo's own published precedence -- OMO_CODING_AGENT_DIR, then SENPI_CODING_AGENT_DIR, then PI_CODING_AGENT_DIR, then ~/.omo/agent -- with each variable reporting refusals under its own name. Detection is the agent directory rather than ~/.omo, because the older v4 launcher creates ~/.omo for its binary-runtime without ever creating agent/, and detecting the parent would report a v5 install that is not there. Loopback-only on OMP's and Prime's grounds rather than Pi's and Aside's: senpi's provider block does accept a headers map, but the shared Pi builder emits none, so remote credential wiring is deferred instead of generating a config that 401s. Registration is one change rather than a backend half and a GUI half, because tests/gui/integrations-invariants.test.ts binds the backend client list to three gui/src lists and no ordering of the halves leaves the tree green. Two repairs ride along. The seven translated destination tables in reference/cli/agents.md have been missing their aside row since 2026-08-31; they are restored so omo does not land beside a known hole. And the Gajae Code label becomes gjc across the nine locale catalogs and the docs, following that product's own rename -- the client id, config path, API route and OPENCODEX_GAJAE_API_KEY stay gajae, because the id keys the stored enable record and renaming it would orphan the state of anyone already connected. Design and evidence: devlog/_plan/260912_omo_client_integration/. --- .../260912_omo_client_integration/000_plan.md | 88 ++++++++ .../001_omo_contract.md | 95 ++++++++ .../002_registration_checklist.md | 132 +++++++++++ .../003_brand_mark_provenance.md | 43 ++++ .../010_wp2_backend.md | 205 ++++++++++++++++++ .../020_wp3_gui.md | 91 ++++++++ .../030_wp4_docs.md | 19 ++ .../040_wp5_verification.md | 25 +++ .../050_wp6_gjc_rename.md | 49 +++++ .../060_wp3_rendered_proof.md | 75 +++++++ .../content/docs/fr/guides/integrations.md | 15 +- .../content/docs/fr/reference/cli/agents.md | 10 +- .../src/content/docs/guides/integrations.md | 20 +- .../content/docs/ja/reference/cli/agents.md | 8 +- .../content/docs/ko/reference/cli/agents.md | 8 +- .../src/content/docs/reference/cli/agents.md | 9 +- .../content/docs/reference/configuration.md | 2 +- .../content/docs/ru/reference/cli/agents.md | 10 +- .../content/docs/tr/guides/integrations.md | 19 +- .../content/docs/tr/reference/cli/agents.md | 10 +- .../docs/zh-cn/reference/cli/agents.md | 8 +- .../content/docs/zh-tw/guides/integrations.md | 15 +- .../docs/zh-tw/reference/cli/agents.md | 8 +- gui/public/provider-icons/README.md | 19 ++ gui/public/provider-icons/omo.svg | 42 ++++ gui/src/app-routing.ts | 1 + .../client-config-clients.ts | 6 +- gui/src/components/integration-marks.ts | 1 + gui/src/i18n/de.ts | 7 +- gui/src/i18n/en.ts | 7 +- gui/src/i18n/fr.ts | 7 +- gui/src/i18n/ja.ts | 7 +- gui/src/i18n/ko.ts | 7 +- gui/src/i18n/ru.ts | 7 +- gui/src/i18n/tr.ts | 7 +- gui/src/i18n/zh-TW.ts | 7 +- gui/src/i18n/zh.ts | 7 +- .../integrations/FileIntegrationPage.tsx | 2 + gui/src/pages/integrations/integration-api.ts | 1 + .../pages/integrations/integration-tabs.ts | 2 + .../pages/integrations/overview-clients.ts | 1 + gui/tests/client-config-panel.test.tsx | 5 +- gui/tests/fr-localization.test.ts | 2 + gui/tests/integrations-api.test.ts | 4 +- gui/tests/integrations-overview-rows.test.ts | 2 +- gui/tests/locale-parity.test.ts | 3 + scripts/test-layout/layout.json | 1 + src/cli/dispatch.ts | 2 +- src/cli/help.ts | 2 +- src/cli/registry.ts | 4 +- src/clients/config-export.ts | 96 ++++++++ src/clients/config-export/contracts.ts | 3 +- src/integrations/catalog-refresh.ts | 2 +- src/integrations/registry.ts | 19 ++ src/server/management/config-routes.ts | 2 +- tests/clients/integrations-state.test.ts | 4 +- tests/clients/omo-client.test.ts | 161 ++++++++++++++ .../clients/sync-client-integrations.test.ts | 6 +- .../client-config-export-new-clients.test.ts | 6 +- tests/config/client-config-export.test.ts | 4 +- tests/fixtures/test-layout-expected.json | 1 + tests/gui/integrations-invariants.test.ts | 6 +- 62 files changed, 1334 insertions(+), 103 deletions(-) create mode 100644 devlog/_plan/260912_omo_client_integration/000_plan.md create mode 100644 devlog/_plan/260912_omo_client_integration/001_omo_contract.md create mode 100644 devlog/_plan/260912_omo_client_integration/002_registration_checklist.md create mode 100644 devlog/_plan/260912_omo_client_integration/003_brand_mark_provenance.md create mode 100644 devlog/_plan/260912_omo_client_integration/010_wp2_backend.md create mode 100644 devlog/_plan/260912_omo_client_integration/020_wp3_gui.md create mode 100644 devlog/_plan/260912_omo_client_integration/030_wp4_docs.md create mode 100644 devlog/_plan/260912_omo_client_integration/040_wp5_verification.md create mode 100644 devlog/_plan/260912_omo_client_integration/050_wp6_gjc_rename.md create mode 100644 devlog/_plan/260912_omo_client_integration/060_wp3_rendered_proof.md create mode 100644 gui/public/provider-icons/omo.svg create mode 100644 tests/clients/omo-client.test.ts diff --git a/devlog/_plan/260912_omo_client_integration/000_plan.md b/devlog/_plan/260912_omo_client_integration/000_plan.md new file mode 100644 index 0000000000..1dac5ccb34 --- /dev/null +++ b/devlog/_plan/260912_omo_client_integration/000_plan.md @@ -0,0 +1,88 @@ +# omo as an export and integration client + +## What this unit adds + +`omo-ai@beta` (5.0.0-0.beta.53, bin `omo`, repo `code-yeongyu/oh-my-openagent`) +describes itself as "omo native edition - the senpi-based OMO harness". It keeps +engine state under `~/.omo/agent` and its allowlist of carried-forward state +files names `models.json` — the same custom-provider catalog Pi, Prime and Aside +read. The user asked for its preset to appear on the Integrations page beside +the other thirteen clients, with a real brand mark. + +This unit registers `omo` as the fourteenth export client and the fourteenth +file integration, reusing the Pi builder the way `prime` and `aside` already do +rather than restating the document shape a fourth time. + +## Why the Pi family is the right precedent + +`prime` is documented in `src/clients/config-export.ts` as "the pi coding agent +shipped under a different brand": it derives its config directory and env prefix +from its own `piConfig` block, so `models.json` is the same contract. omo reaches +the same place by a different route — it is a harness around +`@code-yeongyu/senpi`, written by the same author as Pi — so the claim has to be +verified against senpi's parser rather than assumed from the family +resemblance. `001_omo_contract.md` carries that evidence; the builder is only +reused where the bytes are verified to be accepted. + +## Path resolution + +omo publishes its own precedence in `bin/lib/agent-dir.js`: +`OMO_CODING_AGENT_DIR`, then `SENPI_CODING_AGENT_DIR`, then +`PI_CODING_AGENT_DIR`, then `~/.omo/agent`. The third entry is Pi's variable and +is deliberately honored by omo itself, so mirroring the chain is reporting omo's +contract, not inventing a shared one. Relative overrides are refused for the +reason MCode, ZCode, Pi and Prime already refuse them: a background proxy and a +foreground client can have different working directories and would otherwise +disagree about which file is named. + +## Work phases + +| id | phase | contents | +|----|-------|----------| +| wp1 | roadmap | this unit: contract evidence, registration checklist, mark provenance, per-phase docs | +| wp2 | registration | the whole atomic change: path helpers, `EXPORT_CLIENTS.omo`, contribution, `INTEGRATION_CLIENTS.omo`, CLI help and count, catalog-refresh fan-out, every GUI list and record, the mark wiring, nine locales, and every test literal and allowlist that moves with them | +| wp3 | GUI verification | build and serve this worktree's GUI, confirm the row, tab and mark render, and copy-edit the semantics prose against what the page actually shows | +| wp4 | docs | `docs-site` agents reference and integrations guide, English plus translated locales | +| wp5 | verification | typecheck, focused tests, GUI build, and the rendered dashboard proving the row and tab exist | +| wp6 | gjc rename | the Gajae Code label becomes `gjc` on every user-visible surface, with the `gajae` id untouched | + +wp3, wp4 and wp6 each depend on wp2; wp5 depends on wp3 and wp4. + +**Why wp2 is one phase and not two.** The first plan split backend from GUI. +Two audit rounds failed it on the same ground. +`tests/gui/integrations-invariants.test.ts` asserts sorted equality between +`EXPORT_CLIENT_IDS` and five lists, three of which live in `gui/src`; the GUI +test literals and the two translation allowlists hang off the same edit; and CI +runs `cd gui && bun test` unconditionally. There is no ordering of the halves +that leaves the tree green at the boundary, so registration is one change. + +## Scope boundaries + +In scope: registration of one new client id across the surfaces +`002_registration_checklist.md` enumerates, plus its brand mark and its docs +rows. + +Out of scope: any change to how the Pi document is built for the existing +clients; any new remote-bind credential path; any change to the Integrations +page layout or to the journal/rollback machinery; publishing, releasing, or +pushing anything. + +Also out of scope, deliberately: renaming the `gajae` client **id**. wp6 changes +what the user reads, not what the system keys on. The id is the segment in +`/api/client-integrations/gajae` and the key an enable record is filed under, so +renaming it would orphan the stored state of anyone who already connected that +client and leave our ownership record unable to match the block it wrote. The +user asked for the short name and chose the label-only scope. + +## Terminal outcomes + +DONE requires all five criteria in the bound goalplan to hold with fresh proof: +the row and tab render on the running dashboard with a real mark, the exported +document matches the schema omo parses at the path omo resolves, `bun run +typecheck` is clean, every exact-list test passes with omo included plus a new +omo test, and an enable/disable round trip writes and removes only the owned +fragments. + +BLOCKED is the outcome if senpi turns out to reject the Pi document and no +honest mapping exists. NEEDS_HUMAN is the outcome if omo publishes no usable +first-party mark and the user wants something other than the monogram fallback. diff --git a/devlog/_plan/260912_omo_client_integration/001_omo_contract.md b/devlog/_plan/260912_omo_client_integration/001_omo_contract.md new file mode 100644 index 0000000000..9cb482980b --- /dev/null +++ b/devlog/_plan/260912_omo_client_integration/001_omo_contract.md @@ -0,0 +1,95 @@ +# What omo actually reads + +Evidence: `omo-ai@5.0.0-0.beta.53` unpacked at `/tmp/omoprobe2/package`, and +`@code-yeongyu/senpi@2026.9.10-2` unpacked at `/tmp/senpiprobe/package`. Schema +claims below were executed against that tarball's own TypeBox compiler +(`typebox@1.3.18`), not read off documentation. + +## omo parses nothing + +`bin/omo.js` either runs setup or `runLauncher()`, and the launcher brands senpi +and spawns it. The catalog is senpi's. Branded with `configDir: ".omo"` and +`flatLayout: false`, senpi resolves `getModelsPath() = join(getAgentDir(), +"models.json")` (`senpi dist/config.js:506-508`), which with omo's default agent +directory (`bin/lib/agent-dir.js:41-43`) is `~/.omo/agent/models.json`. + +omo's own setup only *inspects* that path — `setup-detect.js:139-143` lists it in +`detectedFilePaths`, and `setup-models.js:19` tells the user to define the +provider `baseUrl` in it. Setup writes `auth.json`, never `models.json`, so +opencodex is not fighting omo's installer for the file. + +## Path precedence + +The launcher resolves, first non-empty trimmed value winning: +`OMO_CODING_AGENT_DIR`, `SENPI_CODING_AGENT_DIR`, `PI_CODING_AGENT_DIR`, else +`~/.omo/agent` (`agent-dir.js:15,33-38`). It then *overwrites* the first two with +the resolved absolute path before spawning senpi, so senpi's own lookup — which +reads the same three names in the same order (`brand.js:126-140`, +`config.js:456-457`) — always finds the launcher's answer and never reaches its +project-local `.omo/agent` walk. + +Home is `env.HOME || env.USERPROFILE || os.homedir()` (`agent-dir.js:27-30`). + +That third variable is Pi's. omo honors it deliberately, so `omoAgentDir` reading +it is reporting omo's contract rather than asserting a shared one. + +## The provider block senpi validates + +`providers` is a keyed object; each provider's `models` is an **array** whose +identity is `id`, not a keyed object — a keyed object fails with `must be array` +(`model-config-schema.js:201-220`). That is Pi's shape, not OpenCode's. + +Accepted provider keys include `baseUrl`, `apiKey`, `api`, `headers` +(`Record`), `compat`, `models`, and `modelOverrides`. `api` is a +bare string at schema time rather than an enum; an unknown value loads and then +fails at stream time (`provider-composer.js:274-277`). `openai-completions` is a +known api (`types.d.ts:25`) and validates. + +`thinkingLevelMap` accepts exactly the keys the Pi builder emits — `off`, +`minimal`, `low`, `medium`, `high`, `xhigh`, `max` — each `string | null` +(`model-config-schema.js:65-72`), so emitting `max: "ultra"` as a *value* is +fine. + +`input` accepts `text`, `image`, `video` (`model-config-schema.js:170`). `audio` +is rejected — and rejection is not local: a schema failure empties the whole +`models.json` snapshot (`model-config.js:483-487`), so one bad row takes every +custom provider down. This is Pi's failure mode exactly, and it is why +`buildPiClientConfig` drops an audio-only row instead of claiming `text` for it. + +`cost` is optional, but a *partial* `cost` is a schema failure: all four rates +are required (`model-config-schema.js:141-155`). The Pi builder omits `cost` +entirely, which is the safe side of that line. + +## Verdict + +**The bytes `buildPiClientConfig` emits are accepted verbatim.** Nothing needs +renaming, adding, or removing. That was confirmed by running the document +through senpi's compiled validator, not inferred from the family resemblance. + +`compat.sendSessionAffinityHeaders` also validates +(`model-config-schema.js:111,210`), so omo takes the builder's +`sendSessionAffinityHeaders` flag as `true`, the way `pi` does and `prime` and +`aside` do not. + +## Why omo is still loopback-only + +senpi's provider block *does* accept `headers`, and it interpolates `$ENV` and +`${ENV}` in values (`provider-api-key-auth.js:107-117`), so unlike Aside there is +somewhere an `x-opencodex-api-key` could live. What does not exist is a builder +that emits it: `buildPiClientConfig` writes no `headers` at all +(`src/clients/config-export.ts:854-866`), which is exactly why `pi` itself is +loopback-only. + +So `loopbackOnly: true` for omo is OMP's and Prime's stance rather than Aside's: +the field exists, the remote credential wiring is deferred, and a non-loopback +bind refuses instead of generating a config that 401s. Adding `headers` to the +shared Pi builder would change four clients at once and is out of this unit's +scope. + +## Left unverified + +- Whether the dummy `apiKey` is copied into an `Authorization` header at stream + time (would need `pi-ai/dist/api/openai-completions.js`). Irrelevant for a + loopback bind, which admits without a key. +- `oauth: "radius"` appears in senpi's `docs/models.md:139` but not in the live + schema. Not used here. diff --git a/devlog/_plan/260912_omo_client_integration/002_registration_checklist.md b/devlog/_plan/260912_omo_client_integration/002_registration_checklist.md new file mode 100644 index 0000000000..dcf057d8b8 --- /dev/null +++ b/devlog/_plan/260912_omo_client_integration/002_registration_checklist.md @@ -0,0 +1,132 @@ +# Every surface a new export client must reach, as of 2026-09-12 + +The Aside unit wrote this list on 2026-08-31 +(`devlog/_fin/260831_aside_client_and_integrations_ux/002_registration_checklist.md`). +Two things have moved since: `tests/` was reorganised into domain directories, +and `raycast` landed as the thirteenth client, with its own app-side status block. +The table below is re-derived against the current tree by word-boundary search +for `raycast`, the freshest template. + +## Backend + +| file | what omo needs | how failure shows | +|---|---|---| +| `src/clients/config-export/contracts.ts` | `"omo"` in `ExportClientId` | typecheck, everywhere | +| `src/clients/config-export.ts` | `omoAgentDir`, `omoConfigPath`, `buildOmoContribution`, `EXPORT_CLIENTS.omo` | typecheck | +| `src/integrations/registry.ts` | `INTEGRATION_CLIENTS.omo` | typecheck | +| `src/cli/registry.ts` | the `export` usage union and the prose summary | no exact-list gate; `tests/cli/cli-help.test.ts` checks only a prefix of the union | +| `src/cli/help.ts:84` | the `(13 clients)` literal | `tests/cli/cli-help.test.ts:79` | + +The count literal lives in `help.ts`, not `registry.ts`, hand-written on purpose +so `ocx --help` does not import the export registry. +`tests/cli/cli-help.test.ts:79` asserts it in lockstep with +`EXPORT_CLIENT_IDS.length`, so it is a focused-test obligation rather than a +cosmetic edit. + +`/api/client-config` is served from `src/server/management/model-routes.ts:516`, +not `config-routes.ts` as the 2026-08-31 doc says; it reads the registry and +needs no per-client edit. `ExportClientId` likewise moved to +`src/clients/config-export/contracts.ts:84`. + +`src/integrations/ownership-policy.ts`, `state.ts` and `writer.ts` need nothing: +the first is a `zcode`-only exception, and the other two read the registry. +`bun run skill:surface` is not implicated — a new `--client` value creates no +capability. + +## GUI + +| file | what omo needs | how failure shows | +|---|---|---| +| `gui/src/components/apikeys-workspace/client-config-clients.ts` | `CLIENTS`, `CLIENT_LABEL_KEYS`, `CLIENT_MARKS`, possibly `MONOCHROME_CLIENT_MARKS` | invariant test + typecheck | +| `gui/src/components/integration-marks.ts` | `INTEGRATION_MARKS.omo` | typecheck (exhaustive record) | +| `gui/src/pages/integrations/integration-api.ts` | `INTEGRATION_CLIENT_IDS` | invariant test | +| `gui/src/pages/integrations/integration-tabs.ts` | `TABS` and `FILE_CLIENTS` | **silent** — only `gui/tests/integrations-tab-coverage.test.ts` | +| `gui/src/pages/integrations/overview-clients.ts` | `FILE_LABEL_KEY` | typecheck | +| `gui/src/pages/integrations/FileIntegrationPage.tsx` | `SEMANTICS_KEY`, `TAB_LABEL_KEY`, `FILE_INTEGRATION_CLIENTS` | typecheck + invariant test | +| `gui/src/app-routing.ts` | `integrations/omo` hash | **silent** | +| `gui/src/i18n/{en,de,fr,ja,ko,ru,tr,zh,zh-TW}.ts` | three keys each | `locale-parity` | + +## Docs + +`docs-site/src/content/docs/reference/cli/agents.md` plus `fr`, `ja`, `ko`, `ru`, +`tr`, `zh-cn`, `zh-tw`; `docs-site/src/content/docs/guides/integrations.md` plus +`fr`, `tr`, `zh-tw`. + +## Tests that fail until updated + +`tests/config/client-config-export.test.ts`, +`tests/config/client-config-export-new-clients.test.ts`, +`tests/gui/integrations-invariants.test.ts`, +`tests/clients/integrations-state.test.ts`, +`tests/clients/sync-client-integrations.test.ts`, +`tests/clients/integrations-merge.test.ts`, +`tests/cli/cli-export-command.test.ts`, +`tests/cli/cli-headless-parity.test.ts`, +`tests/server/management-integration-routes.test.ts`, +`tests/server/management-client-config-route.test.ts`, +`gui/tests/{client-config-panel.test.tsx,integrations-api.test.ts,integrations-overview-rows.test.ts,integration-marks.test.ts,client-marks-assets.test.ts,locale-parity.test.ts,fr-localization.test.ts,integrations-tab-coverage.test.ts}`. + +Of these, the ones verified to hardcode a list or a count are: +`tests/config/client-config-export.test.ts:812` (ordered thirteen ids), +`tests/config/client-config-export-new-clients.test.ts:68` and +`tests/clients/integrations-state.test.ts:799` (loopback-only set), +`tests/gui/integrations-invariants.test.ts:94` (`toHaveLength(13)`, plus the +typechecked `SEED` record), `tests/cli/cli-help.test.ts:79` (the client count), +`gui/tests/integrations-api.test.ts:20` and +`gui/tests/client-config-panel.test.tsx:174` (GUI literals), and +`gui/tests/integrations-overview-rows.test.ts:293` (row count 18 to 19 — the +overview carries five non-file rows on top of the clients). + +`gui/tests/integrations-tab-coverage.test.ts` still exists and still reads +`TABS`, `FILE_CLIENTS` and the routable hashes, so the 2026-08-31 silent hole is +covered — conditional on `FILE_INTEGRATION_CLIENTS` being updated, since that is +what the coverage test compares against. + +## New files + +`tests/clients/omo-client.test.ts` needs an entry in both +`scripts/test-layout/layout.json` `explicit` and +`tests/fixtures/test-layout-expected.json`, or `tests/test-layout-tooling.test.ts` +names the missing one. + +The explicit entries are not optional here: the `clients` domain regex does not +match an `omo-` prefix, so the seed cannot place the file. Model the test on +`tests/clients/prime-client.test.ts` rather than the Aside one — Aside's test +lives under `providers` because of that same regex, which would be the wrong +precedent to copy. + +## Deliberately not copied from the neighbours + +Aside's profile machinery (`aside-profile-*`, the per-profile journal routes) and +Raycast's app-side install block and live-server export branch are client-specific +surfaces, not part of registration. omo has neither. + +## One list that is a judgement, not a checklist item + +There is not one owned-catalog fan-out list, there are four, and they disagree: + +| call site | list | what triggers it | +|---|---|---| +| `src/integrations/catalog-refresh.ts` default, used by `model-routes.ts` | `pi, aside, raycast` | model visibility changed from the dashboard | +| `src/server/management/config-routes.ts:240` | `mcode, pi, aside, raycast` | the `/api/sync` route | +| `src/cli/dispatch.ts:455` | `mcode, pi, raycast` | `ocx sync` (Aside follows immediately through its own server owner) | +| `src/cli/index.ts` | `raycast` | proxy start, via a helper literally named `refreshOwnedRaycastCatalog` | + +`prime` is in none of them. Updating one and not the others is the failure +mode, so the decision is taken here rather than in passing. + +**Decision: omo joins the three general fan-outs — the `catalog-refresh` +default, `config-routes.ts`, and `dispatch.ts` — and not the fourth.** The +refresh only touches clients that are *already connected* +(`catalog-refresh.ts:10`), so this costs a user who never enables omo exactly +nothing, and it is what stops a connected omo catalog going stale the moment the +user changes model visibility. The fourth is not a general list at all: it is a +Raycast-specific startup helper, and adding an unrelated client to it would +write a file on every `ocx start` for no reason. + +`tests/clients/sync-client-integrations.test.ts:68` pins the `config-routes.ts` +list as source text and moves with it. + +That `prime` is in no list looks like an oversight from when it landed. Fixing +it is not this unit's business; it is recorded here so the next person does not +read prime's absence as a deliberate pattern to copy. diff --git a/devlog/_plan/260912_omo_client_integration/003_brand_mark_provenance.md b/devlog/_plan/260912_omo_client_integration/003_brand_mark_provenance.md new file mode 100644 index 0000000000..02cf7a9cfa --- /dev/null +++ b/devlog/_plan/260912_omo_client_integration/003_brand_mark_provenance.md @@ -0,0 +1,43 @@ +# The omo mark + +## Source + +`gui/public/provider-icons/omo.svg` is `https://omo.dev/brand/omo-mark.svg` +unmodified: 4021 bytes, MD5 `c33f72d7c4612c290834ba860f644557`, +`viewBox="0 0 1024 1024"`. The identical file is committed as +`.github/assets/omo-icon-light.svg` in `code-yeongyu/oh-my-openagent` and +rendered as that README's logo, so the same artwork is both the site header mark +and the repository logo. First-party either way. + +**Correction.** The first draft of this doc cited the GitHub raw path on `main`. +That URL 404s: the repository's default branch is `dev`. The asset is real and +the bytes match — verified by downloading both and comparing MD5 — but the +branch in the citation was wrong, which is the kind of unreproducible provenance +the README exists to prevent. Both citations now name a URL that resolves. + +The project's SUL-1.0 licence says trademark use is "subject to applicable law" +and imposes no distribution ban of the kind that disqualified an earlier +candidate elsewhere in this directory. + +## Rejected candidates + +- `https://omo.dev/icon.svg` — a single `O` glyph. The client-mark + asset test refuses ``, the same rule that sent Hermes to a trace. +- `omo-logo.png` — a superseded 3D rock illustration, and a raster. +- `omo.png` — a landscape screenshot, not a mark. +- The npm tarball carries no `.svg`, `.png`, or `.ico` at all. + +## Which maps it joins + +`CLIENT_MARKS.omo` — yes. + +`MONOCHROME_CLIENT_MARKS` — no. The artwork is two inks: an `#F4F4F4` rounded +plate with an `#041617` face on it. Masking a plated mark discards the plate and +the face together and renders a filled square at 20px, which is the failure the +monochrome set exists to avoid rather than an instance of it. + +Provenance is recorded alongside the file in +`gui/public/provider-icons/README.md`, in both the per-file source list and the +masked/not-masked ledger. That was done in wp1 rather than wp3: the asset landed +in this cycle, and an uncited file in that directory is exactly what the README +exists to prevent. diff --git a/devlog/_plan/260912_omo_client_integration/010_wp2_backend.md b/devlog/_plan/260912_omo_client_integration/010_wp2_backend.md new file mode 100644 index 0000000000..3fc5ccf671 --- /dev/null +++ b/devlog/_plan/260912_omo_client_integration/010_wp2_backend.md @@ -0,0 +1,205 @@ +# wp2 — registration, as one atomic change + +## Why this phase is not backend-only + +The first draft split backend from GUI. An audit round failed it, correctly: +`tests/gui/integrations-invariants.test.ts` asserts sorted equality between +`EXPORT_CLIENT_IDS` and five GUI lists, so the moment the backend knows about a +fourteenth client and the GUI does not, that test is red — and leaving it at +thirteen is red against the backend instead. There is no ordering of the two +halves that keeps the tree green. + +A further binding runs the same way, though not where it first looks. +`CLIENT_LABEL_KEYS` is an `as const` map, not an annotated +`Record<…, TKey>`; the type pressure comes from its use sites — +`t(CLIENT_LABEL_KEYS[client])` and the exhaustive +`Record` maps — where `TKey` is +`keyof typeof en` and every other catalog is a `Record`. So a new +label key has to exist in `en` to compile and in all nine catalogs to keep +parity. i18n is part of the same atomic change rather than a follow-up. + +So wp2 is the whole registration: backend, GUI source lists, marks wiring, the +nine locale catalogs, **and every test literal and allowlist that moves with +them** — including `gui/tests/client-config-panel.test.tsx`, +`gui/tests/integrations-api.test.ts`, the row count in +`gui/tests/integrations-overview-rows.test.ts`, `ZH_TW_KEEP_ENGLISH` in +`gui/tests/locale-parity.test.ts` and `INTENTIONAL_ENGLISH` in +`gui/tests/fr-localization.test.ts`. A second audit round caught the earlier +version leaving those in wp3: CI runs `cd gui && bun test` unconditionally, so a +finished wp2 would have been red on five GUI suites. + +The `integrations.semantics.omo` string is therefore written in wp2 too, in all +nine locales — a placeholder would fail locale parity just as an absent key +would. wp3 copy-edits it against the rendered page rather than creating it. + +**wp2's closing condition** is that all three are green together: `bun run +typecheck`, the focused root tests, and `cd gui && bun test`. + +## Files and what each gains + +`src/clients/config-export/contracts.ts` +: `"omo"` in the `ExportClientId` union. Every exhaustive `Record` + in the tree becomes a typecheck error until it is filled, which is the point. + +`src/clients/config-export.ts` +: `omoAgentDir` and `omoConfigPath` helpers implementing omo's published + precedence (`OMO_CODING_AGENT_DIR`, `SENPI_CODING_AGENT_DIR`, + `PI_CODING_AGENT_DIR`, `~/.omo/agent`), a `buildOmoContribution` that stamps + omo's ownership on the shared Pi fragment, and the `EXPORT_CLIENTS.omo` spec + with all nine fields. `filename` is `omo-models.json` rather than a bare + `models.json`, for the Downloads-folder collision reason `prime-models.json` + and `aside-models.json` already record. + + Each of the three variables is resolved separately and reports `ClientPathError` + under **its own name**, so a user who set `OMO_CODING_AGENT_DIR` is not told + that `PI_CODING_AGENT_DIR` is wrong. An empty or whitespace value falls through + to the next name, which is what `agent-dir.js` does. + + One consequence is worth stating rather than discovering: a user who has set + `PI_CODING_AGENT_DIR` and neither of the other two now has Pi and omo + resolving the **same** `models.json`. Both write the same `providers.opencodex` + block through the same builder, so the bytes agree; what does not agree is + ownership, since two enable records would claim one file. That is omo's own + contract — it reads Pi's variable by design — and the honest response is to + document it, not to silently diverge from the client we are configuring. + + One divergence is deliberate and worth naming: omo `resolve()`s its override + against the process cwd and does not expand `~`. We refuse a relative override + and do expand `~`, exactly as Pi, Prime, MCode and ZCode already do, because a + background proxy and a foreground client have different working directories. + + `buildOmoContribution` calls `buildPiClientConfig(ctx, true)` — with the flag, + not the default. Prime and Aside pass the default in their contribution while + their `build` also passes the default, so they are consistent; splitting the + flag across `build` and `buildContribution` would make `ocx export` emit + `compat` while enable and refresh wrote a file without it. + + **Slot: last, after `raycast`,** in both the union and the `EXPORT_CLIENTS` + object. `EXPORT_CLIENT_IDS` is `Object.keys(EXPORT_CLIENTS)`, so the object's + insertion order is the public order that three ordered assertions compare + against. The existing order is append-only landing order — `pi` is second and + `prime` eleventh — not a family grouping, so appending is the edit that leaves + the other thirteen positions untouched. + +`src/integrations/registry.ts` +: `INTEGRATION_CLIENTS.omo` with `configPath` and `detectDir`. JSON, so no + `sourcePreservingYaml`; single-writer, so no `writerLock`; paths are a pure + function of env and home, so no `resolvePaths` and no `unresolvedPathHint`. + This matches `pi` and `prime` exactly. + +`src/cli/registry.ts` +: the `export` entry's static usage string. Acceptance comes from + `EXPORT_CLIENT_IDS` through `isExportClientId`, so this is help text only — + but `tests/cli/cli-headless-parity.test.ts` reads it. + +`gui/src/…` and `gui/src/i18n/*` +: the five lists the invariant compares, the two lists only the tab-coverage + test compares, the three exhaustive records the compiler forces, the mark + entry, the routing hash, and three keys in each of nine locales. `020` + enumerates them; they land here because of the binding above, not because wp3 + was abandoned. + +`src/cli/help.ts` +: the `(13 clients)` literal on line 84. Hand-written so `ocx --help` does not + import the export registry, and asserted in lockstep with + `EXPORT_CLIENT_IDS.length` by `tests/cli/cli-help.test.ts:79`. + +`src/integrations/catalog-refresh.ts`, `src/server/management/config-routes.ts`, +`src/cli/dispatch.ts` +: omo added to the three general owned-catalog fan-out lists, per the decision in + `002`. Not `src/cli/index.ts`, which is a Raycast-specific startup helper. + `tests/clients/sync-client-integrations.test.ts:68` pins one of those lists as + source text. + +## The detect directory question + +`detectDir` is the cheap "is this client installed at all" signal. For omo the +honest directory is the agent directory itself rather than `~/.omo`: `~/.omo` +exists on this machine carrying only `binary-runtime`, written by the v4 launcher +wrapper, while `~/.omo/agent` does not exist yet. Detecting on `~/.omo` would +report a v5 install that is not there. `agent-dir.js` creates the agent directory +on first launch, so its presence is the fact we want. + +## Loopback-only + +`loopbackOnly: true`, on OMP's and Prime's grounds rather than Pi's and Aside's. + +The flag is a policy bit, not a schema observation: `isLoopbackOnly` is read by +the writer, which refuses apply and refresh when the proxy is bound +non-loopback. It does not block `ocx export` or `/api/client-config` for a +Pi-family client. + +senpi's provider block *does* accept a `headers` map, so unlike Aside there is a +place an `x-opencodex-api-key` could go. What does not exist is a builder that +emits one — `buildPiClientConfig` writes no headers at all, which is why `pi` +is loopback-only too. So the honest wording is deferred remote wiring, and +`010` must not repeat Pi's "no header field" line, which `001` disproves. + +## Session affinity + +`buildPiClientConfig` takes a `sendSessionAffinityHeaders` flag. `pi` passes +`true`; `prime` and `aside` leave it false. omo's value follows the same +evidence rule: true only if senpi is verified to read `compat.sendSessionAffinityHeaders`. + +## Tests + +New: `tests/clients/omo-client.test.ts`, modeled on +`tests/clients/raycast-client.test.ts` and the prime test before it — path +precedence including the two inherited variables, relative-override refusal, +emitted document shape, and the ownership stamp on the contribution. + +One assertion must **not** be copied from Prime. +`tests/clients/prime-client.test.ts` asserts Prime's document carries no +`compat` block. omo's asserts the opposite, the way the Pi case in +`tests/config/client-config-export.test.ts` does, and it asserts it on both the +`build` output and the contribution so the two cannot drift apart. + +Updated because they assert exact lists: +`tests/config/client-config-export.test.ts` (ordered `EXPORT_CLIENT_IDS`), +`tests/config/client-config-export-new-clients.test.ts` (loopback-only set), +`tests/gui/integrations-invariants.test.ts` (client count and the +`Record` seed, which forces an omo fixture in omo's +own JSON shape), `tests/clients/integrations-state.test.ts` (loopback-only set), +and the layout guards `scripts/test-layout/layout.json` plus +`tests/fixtures/test-layout-expected.json` for the new file. + +## Focused test set for this phase + +`tests/clients/omo-client.test.ts`, `tests/config/client-config-export.test.ts`, +`tests/config/client-config-export-new-clients.test.ts`, +`tests/clients/integrations-state.test.ts`, +`tests/clients/sync-client-integrations.test.ts`, +`tests/cli/cli-help.test.ts`, `tests/gui/integrations-invariants.test.ts`, +and the two layout guards. + +## Execution notes fixed at P + +**`omoAgentDir` semantics.** Three variables, in omo's order, each resolved the +way `piAgentDir` resolves its one: `env.NAME?.trim()`, and a falsy result falls +through to the next name. That reproduces `agent-dir.js`, which trims and then +tests truthiness, so a variable set to the empty string or to whitespace is +skipped rather than treated as a path. Each variable reports `ClientPathError` +under **its own name** when it holds a relative path, because telling a user +`PI_CODING_AGENT_DIR must be an absolute path` when they set +`OMO_CODING_AGENT_DIR` is worse than no message. + +**The order matters and it is not cosmetic.** omo resolves its own variable +first, so an opencodex that checked `PI_CODING_AGENT_DIR` first would write to a +Pi directory omo will never read whenever a user has both set. + +**`sendSessionAffinityHeaders: true`.** The flag is not decoration: the +generated provider tells the client to supply a stable session identity, from +which opencodex derives canonical OpenCode Go affinity +(`docs-site/.../guides/pi.md`). senpi validates the same `compat` key +(`001`), so omo gets the same benefit pi does. `prime` and `aside` are left +false because nobody verified their engines read it — that is an absence of +evidence, not a decision to copy. + +**Slot.** `omo` goes last in `ExportClientId` and in `EXPORT_CLIENTS`, after +`raycast`. The ordered assertion in `tests/config/client-config-export.test.ts` +reads `Object.keys` order, and appending is the only edit that leaves the other +thirteen positions untouched. + +**Layout.** The `clients` domain regex seeds only `^aside-profile(?!s-routes)` +and `^(?:desktop|omp|pi|prime|remote|sync)-`, so `omo-client.test.ts` cannot be +placed by pattern and needs the explicit entry in both tables. diff --git a/devlog/_plan/260912_omo_client_integration/020_wp3_gui.md b/devlog/_plan/260912_omo_client_integration/020_wp3_gui.md new file mode 100644 index 0000000000..74706af780 --- /dev/null +++ b/devlog/_plan/260912_omo_client_integration/020_wp3_gui.md @@ -0,0 +1,91 @@ +# wp3 — the Integrations page + +**Scope note after two audit rounds.** Everything above the line lands in wp2, +not here. `tests/gui/integrations-invariants.test.ts` binds the GUI lists to the +backend list; the locale keys are reached through `TKey` (`keyof typeof en`) at +their use sites and every other catalog is a `Record`, so a new key +must exist in all nine locales to compile; and the GUI test literals and the two +translation allowlists fail the moment the source lists move. This document is +the map of those surfaces; wp2 executes all of it, including the test edits. + +wp3 keeps what genuinely cannot be done before the code exists: looking at it. + +The page the user named, `#integrations/`, reads five lists that are compared +against `EXPORT_CLIENT_IDS` by `tests/gui/integrations-invariants.test.ts`, three +exhaustive records the compiler forces, and two lists that are neither. + +## Compared against the backend list + +- `FILE_INTEGRATION_CLIENTS` in `gui/src/pages/integrations/integration-api.ts` + (the GUI's own list; `INTEGRATION_CLIENT_IDS` is the backend's, in + `src/integrations/registry.ts`) +- `CLIENTS` in `gui/src/components/apikeys-workspace/client-config-clients.ts` +- the keys of `CLIENT_LABEL_KEYS` in the same file +- `FILE_INTEGRATION_CLIENTS` +- the hashes in `INTEGRATION_TAB_HASHES` + +## Forced by typecheck + +- `FILE_LABEL_KEY` in `gui/src/pages/integrations/overview-clients.ts` +- `SEMANTICS_KEY` and `TAB_LABEL_KEY` in `FileIntegrationPage.tsx` +- `INTEGRATION_MARKS` in `gui/src/components/integration-marks.ts`, which is an + exhaustive `Record` — so a new client cannot + reach the page without an explicit asset decision + +## The silent hazards + +`TABS` and `FILE_CLIENTS` in `gui/src/pages/integrations/integration-tabs.ts` are +neither exhaustive records nor covered by the invariant test. Omitting omo from +either leaves typecheck and the invariants green while the tab simply does not +render — exactly the failure the user would see and we would not. +`gui/tests/integrations-tab-coverage.test.ts` stands in that gap and must be +confirmed still to do so. + +`gui/src/app-routing.ts` is not a third. `tests/gui/integrations-invariants.test.ts` +already requires a routable hash per `EXPORT_CLIENT_IDS` entry, and the tab +coverage test re-checks that every tab hash is routable. It still has to be +edited; it just fails loudly rather than silently. + +## Mark + +`CLIENT_MARKS.omo` points at a first-party asset in +`gui/public/provider-icons/`, with provenance recorded in that directory's +`README.md` and in `003_brand_mark_provenance.md`. Membership in +`MONOCHROME_CLIENT_MARKS` depends on the artwork having a single neutral ink: +a single-ink mark is drawn as a themed mask so it does not vanish against one of +the two themes, and a multi-ink or brand-colored mark stays an image so masking +cannot flatten a palette or repaint a trademark. + +If omo publishes nothing usable, the honest outcome is `null` and the monogram +tile — `integration-marks.test.ts` currently pins that no client is in that +state, so the pin changes rather than a lookalike logo being invented. + +## i18n + +Three keys across nine locales (`en`, `de`, `fr`, `ja`, `ko`, `ru`, `tr`, `zh`, +`zh-TW`): `integrations.tab.omo`, `integrations.semantics.omo`, +`api.clientConfig.clientOmo`. + +"omo" is a product name and stays untranslated, which means adding the tab and +client keys to `ZH_TW_KEEP_ENGLISH` in `gui/tests/locale-parity.test.ts` and +`INTENTIONAL_ENGLISH` in `gui/tests/fr-localization.test.ts`. The semantics +string is prose and is translated in all nine. + +## GUI tests to update + +`gui/tests/client-config-panel.test.tsx`, `gui/tests/integrations-api.test.ts`, +`gui/tests/integrations-overview-rows.test.ts`, `gui/tests/integration-marks.test.ts`, +`gui/tests/client-marks-assets.test.ts`, `gui/tests/locale-parity.test.ts`, +`gui/tests/fr-localization.test.ts`, `gui/tests/integrations-tab-coverage.test.ts`. + +--- + +## What wp3 still owns + +- Building this worktree's GUI and serving it, rather than reading whatever build + the long-running proxy already has. +- Confirming the row, the tab and the mark actually render — the mark as the omo + face rather than a filled plate, at the size the row draws it. +- Copy-editing `integrations.semantics.omo` against what the page shows. wp2 + writes the string; wp3 is where it gets read in place and fixed if it reads + badly beside its thirteen neighbours. diff --git a/devlog/_plan/260912_omo_client_integration/030_wp4_docs.md b/devlog/_plan/260912_omo_client_integration/030_wp4_docs.md new file mode 100644 index 0000000000..48b4b0a3ae --- /dev/null +++ b/devlog/_plan/260912_omo_client_integration/030_wp4_docs.md @@ -0,0 +1,19 @@ +# wp4 — documentation + +Two pages carry the client list, and both have translated copies that commit +`42adf4996` established are synchronized rather than allowed to drift. + +`docs-site/src/content/docs/reference/cli/agents.md` +: the `--client` union, the flag table, and the destination table. Translated + copies exist under `fr`, `ja`, `ko`, `ru`, `tr`, `zh-cn`, `zh-tw`. + +`docs-site/src/content/docs/guides/integrations.md` +: the client table. Translated copies exist under `fr`, `tr`, `zh-tw`. + +The omo row names the destination `~/.omo/agent/models.json`, the download +filename `omo-models.json`, and the loopback-only stance with its reason, in the +same voice the neighbouring rows use. + +The Aside unit found the integrations guide missing a `zcode` row. wp4 re-checks +that every registered client is present in both tables before adding omo, so a +new row does not land next to a known hole. diff --git a/devlog/_plan/260912_omo_client_integration/040_wp5_verification.md b/devlog/_plan/260912_omo_client_integration/040_wp5_verification.md new file mode 100644 index 0000000000..e9a1956048 --- /dev/null +++ b/devlog/_plan/260912_omo_client_integration/040_wp5_verification.md @@ -0,0 +1,25 @@ +# wp5 — what counts as proof + +The goalplan's five criteria, and the evidence each one accepts. + +1. **The page shows it.** Not "the list contains omo" — a dashboard rendering an + omo row in the overview grid and an omo tab that opens `integrations/omo`, + captured from **this worktree's** built GUI rather than whatever build the + long-running proxy on `:10100` happens to be serving. The silent hazard in + `020` is precisely the failure that passes every other check, so a + source-level assertion cannot discharge this one. +2. **The bytes are right.** `ocx export --client omo` emitting a document whose + provider block matches what senpi parses, naming the path + `omoConfigPath` resolves. Evidence is the emitted text plus the senpi parser + citation in `001`. +3. **`bun run typecheck`** clean. +4. **Focused tests.** The new omo test plus every exact-list test named in + `010` and `020`, run by path — including `tests/cli/cli-help.test.ts`, which + is the only thing that catches a stale `(13 clients)` literal. The full suite + is the PR gate, not this one. +5. **Round trip.** Enable then disable omo through the integrations API against + a temporary home, and show the config file returns to its prior bytes — the + ownership claim, not just the write. + +Anything short of these is reported as it is. A rendered page is not inferred +from a green test, and a green test is not inferred from a compiling record. diff --git a/devlog/_plan/260912_omo_client_integration/050_wp6_gjc_rename.md b/devlog/_plan/260912_omo_client_integration/050_wp6_gjc_rename.md new file mode 100644 index 0000000000..4ddceebc44 --- /dev/null +++ b/devlog/_plan/260912_omo_client_integration/050_wp6_gjc_rename.md @@ -0,0 +1,49 @@ +# wp6 — Gajae Code reads as gjc + +The product shortened its name. The repository is `Yeachan-Heo/gajae-code`, the +published package is `@gajae-code/coding-agent`, the command is `gjc`, and the +config path this repo already writes is `~/.gjc/agent/models.yml` — the path +rebranded before the label did, which is why "Gajae Code" now sits next to a +`.gjc` directory in the same docs table. + +## The line this phase does not cross + +The client **id** stays `gajae`. It is not a caption: it is the key in +`EXPORT_CLIENTS` and `INTEGRATION_CLIENTS`, the segment in +`/api/client-integrations/gajae`, and the key an enable record is stored under. +Renaming it orphans the stored state of every user who already connected the +client — the integration silently reads "not applied", and the ownership record +no longer matches the block we wrote into their config, so a later disable can +no longer remove it cleanly. The user was asked and chose label-only. + +`OPENCODEX_GAJAE_API_KEY` stays for the same reason: it is a variable a user has +already exported, and renaming it breaks a working setup silently. + +## Surfaces that change + +| file | from | to | +|---|---|---| +| `gui/src/i18n/*.ts` × 9, `integrations.tab.gajae` | `Gajae Code` | `gjc` | +| `gui/src/i18n/*.ts` × 9, `api.clientConfig.clientGajae` | `Gajae Code` | `gjc` | +| `src/cli/registry.ts` export summary | `Gajae Code` | `gjc` | +| `docs-site/.../guides/integrations.md` + translations | `Gajae Code` rows and prose | `gjc` | +| `docs-site/.../reference/cli/agents.md` + translations | `Gajae`/`Gajae Code` prose | `gjc` | +| `docs-site/.../reference/configuration.md` + translations | `Gajae` in the Fast-rows client list | `gjc` | + +`gjc` is a product name, so it stays untranslated in all nine locales, which +means the two keys keep their places in `ZH_TW_KEEP_ENGLISH` and +`INTENTIONAL_ENGLISH` — they were already there under the old spelling, so this +is a value change, not a list change. + +## What proves it + +No test asserts the literal `Gajae Code`, and the two translation allowlists key +on key *names*, not values, so changing the value needs no list edit. The label +is never derived from the id — every surface reaches it through an i18n key — so +the two can honestly disagree. + +The proof is `rg -n "Gajae"` restricted to **user-visible text**: the nine locale +catalogs, the CLI summary prose, and `docs-site`. A bare tree-wide search is the +wrong check and would report itself failing forever, because the internal +identifiers (`GajaeGeneratedConfig`, `gajaeConfigPath`, `buildGajaeClientConfig`, +`OPENCODEX_GAJAE_API_KEY`) are exactly what this phase is not touching. diff --git a/devlog/_plan/260912_omo_client_integration/060_wp3_rendered_proof.md b/devlog/_plan/260912_omo_client_integration/060_wp3_rendered_proof.md new file mode 100644 index 0000000000..a66b407c54 --- /dev/null +++ b/devlog/_plan/260912_omo_client_integration/060_wp3_rendered_proof.md @@ -0,0 +1,75 @@ +# wp3 — what the page actually showed + +Built `gui/dist` from this worktree and served it from a proxy on port 10177. + +## The environment mistake, recorded because it cost something + +The first attempt ran `ocx start` with only `OPENCODEX_HOME` redirected. That is +not isolation: `start` also syncs the Codex catalog and the Grok Build config, +both of which resolve from the real home. It rewrote +`~/.codex/opencodex-catalog.json` and pointed `~/.grok/config.toml` at port +10177, which is a dead port the moment the test proxy stops. + +Both were restored: `ocx sync` against the live installation rebuilt the Codex +catalog (29 models), and the Grok base URL was put back to 10100. A diff with +the port normalised on both sides confirmed the port was the only difference. + +The second attempt redirected `HOME` and `CODEX_HOME` as well. The reviewer +argued for keeping the real `HOME` instead, so the badge would act as an oracle +against the machine's real `~/.omo`. That is rebutted rather than ignored: the +condition under test is a `.omo` directory containing only `binary-runtime` and +no `agent/`, and that condition was reproduced exactly inside the isolated home. +It tests the same thing without a second chance to damage the user's setup. + +That the three agent-dir variables were unset is not asserted, it is visible: +the page printed `/tmp/omo-vh/.omo/agent/models.json`, which is the +home-relative default. Any of the three being set would have shown its value. + +## The oracle, pinned before the screenshot + +Muted **Not installed**, the literal config path, Apply **disabled**. "Not +applied" or an enabled switch would mean `detectDir` had matched the bare +`.omo` directory. + +## What was observed + +| check | result | +|---|---| +| omo tab in the strip, routing to `integrations/omo` | present, selectable, reachable | +| overview row, `data-client="omo"` | present, last in the grid | +| `.omo` holding only `binary-runtime`, no `agent/` | **Not installed**, Apply disabled — the v4 false positive is rejected | +| `.omo/agent` created | flips to **Not applied**, Apply enabled | +| Apply toggled | wrote `models.json`: `providers.opencodex` with `baseUrl`, `api: openai-completions`, the loopback placeholder, `compat.sendSessionAffinityHeaders: true`, and `models` as a 12-element array | +| the written file, through senpi's own compiled validator | **valid: true** | +| negative control, `input: ["audio"]` | valid: false | +| negative control, `models` as a keyed object | valid: false | +| Disable toggled | file returns to `{}` — only our block removed | +| mark | `, `mask-image: none`, at 14px (tab), 20px (row) and 24px (page); no monogram anywhere | +| mark in dark and light themes | the dark face carries it on both surfaces; it is not a blank plate | +| API Keys tab, the client-config row | `omo` label present, same unmodified 20px `` mark | +| both `integrations-tab-omo` and `integrations-tab-raycast` measured | non-zero box, top >= 0 — the tab is reachable in a twenty-tab strip, not merely in the DOM | + +The validator run is the one that matters most, because it is the difference +between "the bytes look like Pi's" and "the engine omo actually ships accepts +this file". The two negative controls are there so the check cannot be vacuous — +a validator that returns true for everything would have passed them too. + +The last two rows close the reviewer's remaining non-blocking notes. The API +Keys row matters because it is a second surface reading the same +`CLIENT_MARKS.omo`, and it is reached through `CLIENTS` rather than through +`FILE_INTEGRATION_CLIENTS` — a different list, so a different way to be missing. +Tab reachability was measured rather than eyeballed, because "present in the +DOM" and "the user can get to it" are not the same claim once a strip holds +twenty tabs. + +## Semantics copy, read in place + +`integrations.semantics.omo` wraps to three lines where Prime's wraps to two, +because it names three environment variables instead of one. Kept as is: the +three names are the fact a user needs when omo and Pi can resolve the same file, +and shortening it would mean dropping two of them. + +## Cleanup + +The test proxy is stopped and `/tmp/omo-vh`, `/tmp/omo-ocxhome` hold everything +it wrote. Nothing under the user's home carries state from this run. diff --git a/docs-site/src/content/docs/fr/guides/integrations.md b/docs-site/src/content/docs/fr/guides/integrations.md index 9dd4e5bc2e..b803c9f2c5 100644 --- a/docs-site/src/content/docs/fr/guides/integrations.md +++ b/docs-site/src/content/docs/fr/guides/integrations.md @@ -1,10 +1,10 @@ --- title: Intégrations -description: Connectez opencodex à OpenCode, Pi, OMP, Hermes, OpenClaw, Kimi Code, Gajae Code, DeepSeek Harness, MiniMax Code, ZCode, Prime Agent, Aside et Raycast depuis le tableau de bord — un commutateur par client, avec une sauvegarde avant chaque écriture. +description: Connectez opencodex à OpenCode, Pi, OMP, Hermes, OpenClaw, Kimi Code, gjc, DeepSeek Harness, MiniMax Code, ZCode, Prime Agent, Aside, Raycast et omo depuis le tableau de bord — un commutateur par client, avec une sauvegarde avant chaque écriture. --- L'onglet **Intégrations** écrit le bloc fournisseur d'opencodex dans le fichier de configuration du client, -puis peut le retirer. Treize clients fonctionnent ainsi, chacun avec son propre commutateur : +puis peut le retirer. Quatorze clients fonctionnent ainsi, chacun avec son propre commutateur : | Client | Fichier de configuration | Format | Prise d'effet de la modification | Identifiant | |---|---|---|---|---| @@ -14,13 +14,14 @@ puis peut le retirer. Treize clients fonctionnent ainsi, chacun avec son propre | Hermes | `~/.hermes/config.yaml` | YAML | dans les nouvelles sessions | `OPENCODEX_HERMES_API_KEY` | | OpenClaw | `~/.openclaw/openclaw.json` | JSON5 | immédiatement, sur une passerelle en cours d'exécution | `OPENCODEX_OPENCLAW_API_KEY` | | Kimi Code | `~/.kimi-code/config.toml` | TOML | au redémarrage ou avec `/reload` | valeur fictive de bouclage | -| Gajae Code | `~/.gjc/agent/models.yml` | YAML | dans les nouvelles sessions ou à l'ouverture de `/model` |`OPENCODEX_GAJAE_API_KEY` | +| gjc | `~/.gjc/agent/models.yml` | YAML | dans les nouvelles sessions ou à l'ouverture de `/model` |`OPENCODEX_GAJAE_API_KEY` | | DeepSeek Harness (DSH) | `$DSH_HOME/settings.yaml` (`~/.dsh/settings.yaml` par défaut) | YAML | rechargement à chaud | jeton porteur fictif et non secret pour le bouclage | | MiniMax Code | `~/.minimax/config.yaml` | YAML | dans les nouvelles sessions ou après l’ouverture du sélecteur de modèles | valeur fictive de bouclage | | Prime Agent | `~/.prime/agent/models.json` | JSON | dans les nouvelles sessions | valeur fictive de bouclage | | ZCode | `~/.zcode/v2/config.json` | JSON | au redémarrage | valeur fictive de bouclage | | Aside | `~/.aside/u//models.json` | JSON | après avoir quitté complètement puis rouvert Aside | valeur fictive de bouclage | | Raycast | `~/.config/raycast/ai/providers.yaml` | YAML | immédiatement à l'enregistrement — Raycast surveille le fichier | aucun — bouclage uniquement | +| omo | `~/.omo/agent/models.json` | JSON | nouvelles sessions | espace réservé de bouclage | La prise en charge gérée de DSH exige au minimum **DSH 0.1.0-rc.6**. OpenCodex ne possède que le fragment `llm-pi-ai.providers.opencodex` : **Appliquer** et **Actualiser** remplacent ce fragment, **Désactiver** ne @@ -128,7 +129,7 @@ niveaux. Dans ces cas, le commutateur est verrouillé afin que rien ne soit modi **OMP, DSH et Hermes** ne sont pas affectés non plus par les modifications voisines, mais pour une autre raison : leurs outils d'écriture ne modifient, octet par octet, que leur propre plage `providers.opencodex` ; le reste du fichier n'est jamais réécrit. Pour les autres formats susceptibles de contenir des commentaires (OpenClaw, -Kimi Code, Gajae Code, MiniMax Code et Raycast — documents YAML, JSON5 et TOML réécrits en entier), ou lorsque les propres entrées +Kimi Code, gjc, MiniMax Code et Raycast — documents YAML, JSON5 et TOML réécrits en entier), ou lorsque les propres entrées d'opencodex ont été modifiées, le commutateur se verrouille et la désactivation est refusée plutôt que de deviner quelles modifications vous appartiennent. @@ -154,7 +155,7 @@ les convertirait en chaînes entre guillemets, y compris dans les tableaux et le tables en ligne. Les dates déjà écrites entre guillemets restent prises en charge. Pour conserver une date typée sans guillemets, modifiez manuellement la configuration. -**Pi, Kimi Code, Gajae Code, MiniMax Code et l'intégration DSH gérée fonctionnent uniquement avec une adresse de +**Pi, Kimi Code, gjc, MiniMax Code et l'intégration DSH gérée fonctionnent uniquement avec une adresse de bouclage.** Les quatre premiers n'ont aucun champ de configuration pour l'en-tête `x-opencodex-api-key` qu'exige une liaison hors bouclage. DSH possède une table d'en-têtes générique, mais rc.6 ne documente pas cet en-tête d'admission dédié comme contrat d'intégration pris en charge ; l'outil d'écriture géré échoue @@ -205,8 +206,8 @@ ocx mcode ``` Une fois l’intégration connectée, `ocx sync` et `POST /api/sync` actualisent les catalogues MCode, -Pi, Aside et Raycast gérés. Le démarrage du proxy actualise aussi le catalogue Raycast géré. -Les changements de visibilité, de fournisseur ou de préréglage actualisent Pi, Aside et Raycast. +Pi, Aside, Raycast et omo gérés. Le démarrage du proxy actualise aussi le catalogue Raycast géré. +Les changements de visibilité, de fournisseur ou de préréglage actualisent Pi, Aside, Raycast et omo. Les blocs absents, modifiés par un tiers, non sûrs ou supprimés manuellement restent intacts ; réactivez explicitement l’intégration lorsque vous souhaitez la reconnecter. diff --git a/docs-site/src/content/docs/fr/reference/cli/agents.md b/docs-site/src/content/docs/fr/reference/cli/agents.md index 749119f70a..0d781042d9 100644 --- a/docs-site/src/content/docs/fr/reference/cli/agents.md +++ b/docs-site/src/content/docs/fr/reference/cli/agents.md @@ -164,7 +164,7 @@ Gérez et appliquez la clôture du modèle Grok Build. ## Exportation de la configuration client -### `ocx export --client ` +### `ocx export --client ` Imprimez une configuration client connectée au proxy en cours d'exécution. La commande sérialise le bloc fournisseur `opencodex` — URL de base, liste de modèles et référence d’identifiant du client @@ -175,7 +175,7 @@ les modèles Codex peuvent actuellement voir. | Option | Actions | | --- | --- | -| `--client ` | Requis. Sélectionne le dialecte de configuration client. | +| `--client ` | Requis. Sélectionne le dialecte de configuration client. | | `--json` | Imprimez le document généré en tant que JSON sur la sortie standard pour les scripts. Il s'agit de JSON même lorsque le format natif du client sélectionné est YAML, TOML ou JSON5. | | `--out ` | Écrivez le format de configuration natif du client dans ``. Refuse de remplacer un fichier existant. | | `--force` | Autoriser `--out` à remplacer un fichier existant. | @@ -205,7 +205,9 @@ propres valeurs par défaut à ces lignes. | `mcode` | `~/.minimax/config.yaml` (`MINIMAX_DATA_DIR`, puis l'ancien `MAVIS_DATA_DIR`, l'emportent une fois définis ; une valeur relative est refusée) | `mcode-config.yaml` | aucun — espace réservé de bouclage | | `zcode` | `~/.zcode/v2/config.json` (`ZCODE_DATA_DIR` l'emporte une fois défini ; une valeur relative est refusée) | `config.json` | aucun — espace réservé de bouclage | | `prime` | `~/.prime/agent/models.json` (`PRIME_AGENT_CODING_AGENT_DIR` l'emporte une fois défini ; une valeur relative est refusée) | `prime-models.json` | aucun — espace réservé de bouclage | +| `aside` | `~/.aside/u//models.json` pour le compte que le fichier `accounts.json` d'Aside désigne comme courant ; un manifeste illisible est refusé plutôt que de retomber sur un compte | `aside-models.json` | aucun — espace réservé de bouclage | | `raycast` | `~/.config/raycast/ai/providers.yaml`, sur macOS comme sur Windows (Raycast n'honore pas `XDG_CONFIG_HOME`) | `raycast-providers.yaml` | aucun — bouclage uniquement, aucune entrée `api_keys` n'est écrite | +| `omo` | `~/.omo/agent/models.json` (`OMO_CODING_AGENT_DIR`, puis `SENPI_CODING_AGENT_DIR`, puis `PI_CODING_AGENT_DIR` l'emportent dans cet ordre une fois définis ; une valeur relative est refusée) | `omo-models.json` | aucun — espace réservé de bouclage | L'exportation Raycast est un document `providers.yaml` autonome contenant un seul élément `id: opencodex` dans la séquence `providers` : `name: OpenCodex`, l'URL de base `/v1` du proxy et chaque modèle routé avec @@ -241,8 +243,8 @@ le proxy se lie au-delà du bouclage ; voir [Accès à distance](/fr/reference/configuration/server/#accès-à-distance) pour savoir comment les clés d'admission sont délivrées. Clés pour les fournisseurs en amont eux-mêmes sont une chose entièrement distincte, configurée par [Fournisseurs](/fr/guides/providers/). -Gajae est l'exception : `OPENCODEX_GAJAE_API_KEY` remplit ses informations d'identification de fournisseur à partir du -environnement, mais son schéma ne peut pas envoyer l'en-tête d'admission à distance, donc le Gajae généré +gjc est l'exception : `OPENCODEX_GAJAE_API_KEY` remplit ses informations d'identification de fournisseur à partir du +environnement, mais son schéma ne peut pas envoyer l'en-tête d'admission à distance, donc l'intégration gjc générée l'intégration reste uniquement en boucle. La même charge utile est servie par `GET /api/client-config` et rendue sur l'onglet API du tableau de bord, donc diff --git a/docs-site/src/content/docs/guides/integrations.md b/docs-site/src/content/docs/guides/integrations.md index 166a848614..16242c9f77 100644 --- a/docs-site/src/content/docs/guides/integrations.md +++ b/docs-site/src/content/docs/guides/integrations.md @@ -1,10 +1,10 @@ --- title: Integrations -description: Connect opencodex to OpenCode, Pi, OMP, Hermes, OpenClaw, Kimi Code, Gajae Code, DeepSeek Harness, MiniMax Code, ZCode, Prime Agent, Aside and Raycast from the dashboard — one switch per client, with a backup taken before every write. +description: Connect opencodex to OpenCode, Pi, OMP, Hermes, OpenClaw, Kimi Code, gjc, DeepSeek Harness, MiniMax Code, ZCode, Prime Agent, Aside, Raycast and omo from the dashboard — one switch per client, with a backup taken before every write. --- The **Integrations** tab writes opencodex's provider block into a client's own config -file, and removes it again. Thirteen clients work this way, each with a switch: +file, and removes it again. Fourteen clients work this way, each with a switch: | Client | Config file | Format | When the change takes effect | Credential | |---|---|---|---|---| @@ -14,13 +14,14 @@ file, and removes it again. Thirteen clients work this way, each with a switch: | Hermes | `~/.hermes/config.yaml` | YAML | new sessions | `OPENCODEX_HERMES_API_KEY` | | OpenClaw | `~/.openclaw/openclaw.json` | JSON5 | immediately, on a running gateway | `OPENCODEX_OPENCLAW_API_KEY` | | Kimi Code | `~/.kimi-code/config.toml` | TOML | on restart, or `/reload` | loopback placeholder | -| Gajae Code | `~/.gjc/agent/models.yml` | YAML | new sessions, or when you open `/model` |`OPENCODEX_GAJAE_API_KEY` | +| gjc | `~/.gjc/agent/models.yml` | YAML | new sessions, or when you open `/model` |`OPENCODEX_GAJAE_API_KEY` | | DeepSeek Harness (DSH) | `$DSH_HOME/settings.yaml` (default `~/.dsh/settings.yaml`) | YAML | hot reload | non-secret loopback bearer placeholder | | MiniMax Code | `~/.minimax/config.yaml` | YAML | new sessions, or after opening the model picker | loopback placeholder | | Prime Agent | `~/.prime/agent/models.json` | JSON | new sessions | loopback placeholder | | ZCode | `~/.zcode/v2/config.json` | JSON | on restart | loopback placeholder | | Aside | `~/.aside/u//models.json` | JSON | after fully quitting and reopening Aside | loopback placeholder | | Raycast | `~/.config/raycast/ai/providers.yaml` | YAML | immediately on save — Raycast watches the file | none — loopback only | +| omo | `~/.omo/agent/models.json` | JSON | new sessions | loopback placeholder | Generated catalogs include only enabled models from each provider selection. This applies to both downloads and managed integrations, including Pi and Aside. The management model list still shows @@ -175,7 +176,7 @@ than 1000 levels — which locks the switch instead, so nothing is silently chan **OMP, DSH and Hermes** are unaffected by sibling edits too, for a different reason: their writers patch only their own managed provider ranges byte-wise, so the rest of the file is never rewritten. For the remaining formats that can carry comments -(OpenClaw, Kimi Code, Gajae Code, MiniMax Code, Raycast — JSON5 and TOML +(OpenClaw, Kimi Code, gjc, MiniMax Code, Raycast — JSON5 and TOML written as whole documents, or generic YAML without source preservation), or whenever our own entries were edited, the switch locks and disable refuses rather than guessing which edits were yours. @@ -211,7 +212,7 @@ typed values into quoted strings. This includes values inside arrays and inline tables. Quoted date strings remain supported; an unquoted date must be preserved by editing the configuration manually. -**Pi, Kimi Code, Gajae Code, MiniMax Code, Prime Agent and the managed DSH integration only work against a loopback bind.** +**Pi, Kimi Code, gjc, MiniMax Code, Prime Agent and the managed DSH integration only work against a loopback bind.** The first four have no config field for the `x-opencodex-api-key` header a non-loopback bind requires. DSH has a generic headers map, but rc.6 does not document that dedicated admission header as a supported integration contract, so the managed writer fails closed instead of @@ -261,10 +262,11 @@ ocx integration client enable --client mcode ocx mcode ``` -Once connected, `ocx sync` and `POST /api/sync` refresh owned MCode, Pi, Aside, and -Raycast catalogs with the current model selection, context windows, and reasoning-effort -ladders. Proxy startup refreshes an owned Raycast catalog. Changes to model visibility, -provider selection, or presets also refresh connected Pi, Aside, and Raycast catalogs. +Once connected, `ocx sync` and `POST /api/sync` refresh owned MCode, Pi, Aside, +Raycast, and omo catalogs with the current model selection, context windows, and +reasoning-effort ladders. Proxy startup refreshes an owned Raycast catalog. Changes to +model visibility, provider selection, or presets also refresh connected Pi, Aside, +Raycast, and omo catalogs. Missing, foreign-edited, or unsafe blocks stay untouched, as do previously owned blocks you removed manually. An enabled Aside profile is an exception to the usual owned-only refresh: if its account diff --git a/docs-site/src/content/docs/ja/reference/cli/agents.md b/docs-site/src/content/docs/ja/reference/cli/agents.md index a223362a56..a74f20a817 100644 --- a/docs-site/src/content/docs/ja/reference/cli/agents.md +++ b/docs-site/src/content/docs/ja/reference/cli/agents.md @@ -125,7 +125,7 @@ Grok Build モデル フェンスを管理および適用します。 ## クライアント設定のエクスポート -### `ocx export --client ` +### `ocx export --client ` 実行中のプロキシに接続するクライアント設定を出力します。このコマンドは、ベース URL、モデル一覧、およびクライアントに応じた認証情報参照または `opencodex-loopback` プレースホルダーを含む `opencodex` プロバイダーブロックを、選択したクライアントのネイティブ形式でシリアル化します。 @@ -133,7 +133,7 @@ Grok Build モデル フェンスを管理および適用します。 |旗 |アクション | | --- | --- | -| `--client ` |必須。クライアントの設定形式を選択します。 | +| `--client ` |必須。クライアントの設定形式を選択します。 | | `--json` |構成 JSON のみを標準出力に出力するため、リダイレクトはバイト正確な出力をキャプチャします。 `--out` 書き込みメモを含むすべての診断は stderr に送られます。 | | `--out ` |設定を `` に書き込みます。既存のファイルの置き換えを拒否します。 | | `--force` | `--out` が既存のファイルを置き換えることを許可します。 | @@ -160,7 +160,9 @@ ocx export --client opencode --out ~/opencodex-opencode.json | `mcode` | `~/.minimax/config.yaml` (`MINIMAX_DATA_DIR`、次に旧 `MAVIS_DATA_DIR` が設定時に優先。相対値は拒否されます) | `mcode-config.yaml` | なし — loopback placeholder | | `zcode` | `~/.zcode/v2/config.json` (`ZCODE_DATA_DIR` が設定時に優先。相対値は拒否されます) | `config.json` | なし — loopback placeholder | | `prime` | `~/.prime/agent/models.json` (`PRIME_AGENT_CODING_AGENT_DIR` が設定時に優先。相対値は拒否されます) | `prime-models.json` | なし — loopback placeholder | +| `aside` | `~/.aside/u//models.json`。Aside 自身の `accounts.json` が現在のアカウントとして指す account を使います。マニフェストが読めない場合は、既定のアカウントに落とさず拒否します | `aside-models.json` | なし — loopback placeholder | | `raycast` | `~/.config/raycast/ai/providers.yaml` (macOS と Windows で同じ。Raycast は `XDG_CONFIG_HOME` を尊重しません) | `raycast-providers.yaml` | なし — loopback のみ。`api_keys` エントリは書き込まれません | +| `omo` | `~/.omo/agent/models.json` (`OMO_CODING_AGENT_DIR`、次に `SENPI_CODING_AGENT_DIR`、次に `PI_CODING_AGENT_DIR` の順で設定時に優先。相対値は拒否されます) | `omo-models.json` | なし — loopback placeholder | Raycast のエクスポートは、`providers` シーケンスに `id: opencodex` 要素を 1 つだけ持つ独立した `providers.yaml` 文書です。内容は `name: OpenCodex`、プロキシの `/v1` ベース URL、および `abilities` 付きのルーティング済み全モデルです (`tools` と `system_message` は常にサポート、`vision` はカタログの入力モダリティから、`reasoning_effort` はモデルに effort ラダーがある場合、`temperature` は推論モデルではオフ)。Custom Providers は Raycast Pro の機能で、Raycast はこのファイルを監視しているため、保存した変更は再起動なしで反映されます。形式は [manual.raycast.com/ai/custom-providers](https://manual.raycast.com/ai/custom-providers) に記載されています。`api_keys` エントリは書き込まれないため、このエクスポートは loopback 専用で、loopback 以外のバインドは拒否されます。 @@ -170,7 +172,7 @@ opencode は `{env:OPENCODEX_OPENCODE_API_KEY}` を補間します。opencodex `ocx export` は実際のクライアント設定を書き込むことはありません。宛先は手動でマージできるように出力されます。`--out` は、`--force` なしで既存のファイルを上書きすることを拒否します。これは、設定を置き換えると、その中にすでに含まれている他のプロバイダー、エージェント、および MCP エントリが破壊されるためです。 ::: -キーはシリアル化されません。生成される設定には、文書化された環境参照か、秘密ではないループバック用プレースホルダーのいずれかが入ります。ループバック プロキシ (`127.0.0.1`、デフォルト) にはアドミッション キーはまったく必要ありません。プロキシがループバックを超えてバインドする場合は、対応する `OPENCODEX_OPENCODE_API_KEY`、`OPENCODEX_HERMES_API_KEY`、または `OPENCODEX_OPENCLAW_API_KEY` を設定します。`OPENCODEX_GAJAE_API_KEY` は Gajae の provider 認証値を環境から渡しますが、remote admission header は送れないため、生成される Gajae 統合はループバック専用のままです。アドミッションキーの発行方法については、[リモートアクセス](/reference/configuration/#remote-access) を参照してください。上流プロバイダー自体のキーは完全に別のものであり、[プロバイダー](/guides/providers/) ごとに構成されます。 +キーはシリアル化されません。生成される設定には、文書化された環境参照か、秘密ではないループバック用プレースホルダーのいずれかが入ります。ループバック プロキシ (`127.0.0.1`、デフォルト) にはアドミッション キーはまったく必要ありません。プロキシがループバックを超えてバインドする場合は、対応する `OPENCODEX_OPENCODE_API_KEY`、`OPENCODEX_HERMES_API_KEY`、または `OPENCODEX_OPENCLAW_API_KEY` を設定します。`OPENCODEX_GAJAE_API_KEY` は gjc の provider 認証値を環境から渡しますが、remote admission header は送れないため、生成される gjc 統合はループバック専用のままです。アドミッションキーの発行方法については、[リモートアクセス](/reference/configuration/#remote-access) を参照してください。上流プロバイダー自体のキーは完全に別のものであり、[プロバイダー](/guides/providers/) ごとに構成されます。 同じペイロードが `GET /api/client-config` によって提供され、ダッシュボードの [API] タブにレンダリングされるため、CLI、API、および GUI は同じバイトを使用します。 diff --git a/docs-site/src/content/docs/ko/reference/cli/agents.md b/docs-site/src/content/docs/ko/reference/cli/agents.md index 76cef4bda7..82f776b782 100644 --- a/docs-site/src/content/docs/ko/reference/cli/agents.md +++ b/docs-site/src/content/docs/ko/reference/cli/agents.md @@ -152,7 +152,7 @@ Grok Build model fence를 관리하고 적용합니다. ## 클라이언트 설정 내보내기 -### `ocx export --client ` +### `ocx export --client ` 실행 중인 프록시에 연결할 client config를 출력합니다. 이 명령은 base URL, model list, 그리고 client에 따라 credential reference 또는 `opencodex-loopback` placeholder를 포함한 `opencodex` provider block을 선택한 client의 네이티브 형식으로 직렬화합니다. @@ -160,7 +160,7 @@ Grok Build model fence를 관리하고 적용합니다. | 플래그 | 동작 | | --- | --- | -| `--client ` | 필수입니다. 클라이언트 설정 형식을 선택합니다. | +| `--client ` | 필수입니다. 클라이언트 설정 형식을 선택합니다. | | `--json` | config JSON만 stdout에 출력하므로, redirect가 byte-exact 출력을 캡처합니다. `--out` write note를 포함한 모든 진단 메시지는 stderr로 갑니다. | | `--out ` | config를 ``에 씁니다. 기존 파일이 있으면 덮어쓰지 않습니다. | | `--force` | `--out`이 기존 파일을 덮어쓰도록 허용합니다. | @@ -187,7 +187,9 @@ ocx export --client opencode --out ~/opencodex-opencode.json | `mcode` | `~/.minimax/config.yaml` (`MINIMAX_DATA_DIR`, 그다음 레거시 `MAVIS_DATA_DIR`가 설정되면 우선. 상대 경로는 거부됩니다) | `mcode-config.yaml` | 없음 — loopback placeholder | | `zcode` | `~/.zcode/v2/config.json` (`ZCODE_DATA_DIR`가 설정되면 우선. 상대 경로는 거부됩니다) | `config.json` | 없음 — loopback placeholder | | `prime` | `~/.prime/agent/models.json` (`PRIME_AGENT_CODING_AGENT_DIR`가 설정되면 우선. 상대 경로는 거부됩니다) | `prime-models.json` | 없음 — loopback placeholder | +| `aside` | `~/.aside/u//models.json`. Aside의 `accounts.json`이 현재 계정으로 지정한 account를 사용합니다. 매니페스트를 읽을 수 없으면 임의의 계정으로 넘어가지 않고 거부합니다 | `aside-models.json` | 없음 — loopback placeholder | | `raycast` | `~/.config/raycast/ai/providers.yaml` (macOS와 Windows 모두 동일. Raycast는 `XDG_CONFIG_HOME`을 따르지 않습니다) | `raycast-providers.yaml` | 없음 — loopback 전용. `api_keys` 항목은 쓰지 않습니다 | +| `omo` | `~/.omo/agent/models.json` (`OMO_CODING_AGENT_DIR`, `SENPI_CODING_AGENT_DIR`, `PI_CODING_AGENT_DIR` 순서로 설정된 값이 우선. 상대 경로는 거부됩니다) | `omo-models.json` | 없음 — loopback placeholder | Raycast 내보내기는 `providers` 시퀀스에 `id: opencodex` 요소 하나만 담은 독립 `providers.yaml` 문서입니다. 내용은 `name: OpenCodex`, proxy의 `/v1` base URL, 그리고 `abilities`가 붙은 라우팅된 모든 모델입니다(`tools`와 `system_message`는 항상 지원, `vision`은 카탈로그의 입력 모달리티를 따름, `reasoning_effort`는 모델에 effort 사다리가 있을 때, `temperature`는 추론 모델에서 꺼짐). Custom Providers는 Raycast Pro 기능이며, Raycast가 이 파일을 감시하므로 저장한 변경은 재시작 없이 적용됩니다. 형식은 [manual.raycast.com/ai/custom-providers](https://manual.raycast.com/ai/custom-providers)에 문서화되어 있습니다. `api_keys` 항목은 쓰지 않으므로 이 내보내기는 loopback 전용이며, loopback이 아닌 bind는 거부됩니다. @@ -197,7 +199,7 @@ opencode는 `{env:OPENCODEX_OPENCODE_API_KEY}`를 보간합니다. opencodex가 `ocx export`는 실제 client config를 절대 쓰지 않습니다. 대상 경로는 손으로 병합하라고 출력되며, `--out`은 `--force` 없이 기존 파일을 덮어쓰지 않습니다. config를 바꾸어 덮어쓰면 이미 들어 있던 다른 provider, agent, MCP entry가 사라지기 때문입니다. ::: -어떤 key도 직렬화되지 않습니다. 생성되는 config에는 문서화된 env reference 또는 비밀이 아닌 loopback placeholder 중 하나가 들어갑니다. loopback proxy(`127.0.0.1`, 기본값)는 admission key가 전혀 필요하지 않습니다. proxy가 loopback을 넘어 바인딩할 때는 해당하는 `OPENCODEX_OPENCODE_API_KEY`, `OPENCODEX_HERMES_API_KEY`, `OPENCODEX_OPENCLAW_API_KEY`를 설정하십시오. `OPENCODEX_GAJAE_API_KEY`는 Gajae provider 인증 값을 환경에서 전달하지만 remote admission header를 보낼 수는 없으므로, 생성되는 Gajae 통합은 loopback 전용으로 남습니다. admission key가 어떻게 발급되는지는 [Remote access](/reference/configuration/#remote-access)를 보십시오. upstream provider 자체의 key는 완전히 별개의 것으로, 각 [Providers](/guides/providers/)에 맞게 설정합니다. +어떤 key도 직렬화되지 않습니다. 생성되는 config에는 문서화된 env reference 또는 비밀이 아닌 loopback placeholder 중 하나가 들어갑니다. loopback proxy(`127.0.0.1`, 기본값)는 admission key가 전혀 필요하지 않습니다. proxy가 loopback을 넘어 바인딩할 때는 해당하는 `OPENCODEX_OPENCODE_API_KEY`, `OPENCODEX_HERMES_API_KEY`, `OPENCODEX_OPENCLAW_API_KEY`를 설정하십시오. `OPENCODEX_GAJAE_API_KEY`는 gjc provider 인증 값을 환경에서 전달하지만 remote admission header를 보낼 수는 없으므로, 생성되는 gjc 통합은 loopback 전용으로 남습니다. admission key가 어떻게 발급되는지는 [Remote access](/reference/configuration/#remote-access)를 보십시오. upstream provider 자체의 key는 완전히 별개의 것으로, 각 [Providers](/guides/providers/)에 맞게 설정합니다. 같은 payload는 `GET /api/client-config`로 제공되고 dashboard의 API 탭에도 렌더링되므로, CLI, API, GUI가 모두 같은 바이트를 사용합니다. diff --git a/docs-site/src/content/docs/reference/cli/agents.md b/docs-site/src/content/docs/reference/cli/agents.md index ab96881d11..32399c59df 100644 --- a/docs-site/src/content/docs/reference/cli/agents.md +++ b/docs-site/src/content/docs/reference/cli/agents.md @@ -235,7 +235,7 @@ Manage and apply the Grok Build model fence. ## Client config export -### `ocx export --client ` +### `ocx export --client ` Print a client config wired to the running proxy. The command serializes the `opencodex` provider block — base URL, model list, and the client's credential @@ -246,7 +246,7 @@ models Codex can currently see. | Flag | Action | | --- | --- | -| `--client ` | Required. Selects the client config dialect. | +| `--client ` | Required. Selects the client config dialect. | | `--json` | Print the generated document as JSON on stdout for scripts. This is JSON even when the selected client's native format is YAML, TOML, or JSON5. | | `--out ` | Write the client's native config format to ``. Refuses to replace an existing file. | | `--force` | Allow `--out` to replace an existing file. | @@ -277,6 +277,7 @@ client applies its own defaults for those). | `prime` | `~/.prime/agent/models.json` (`PRIME_AGENT_CODING_AGENT_DIR` wins when set; a relative value is refused) | `prime-models.json` | none — loopback placeholder | | `aside` | `~/.aside/u//models.json` for the account Aside's own `accounts.json` names as current; an unreadable manifest is refused rather than defaulting to an account | `aside-models.json` | none — loopback placeholder | | `raycast` | `~/.config/raycast/ai/providers.yaml` on macOS and Windows alike (Raycast does not honor `XDG_CONFIG_HOME`) | `raycast-providers.yaml` | none — loopback only, no `api_keys` entry is written | +| `omo` | `~/.omo/agent/models.json` (`OMO_CODING_AGENT_DIR`, then `SENPI_CODING_AGENT_DIR`, then `PI_CODING_AGENT_DIR` win in that order when set; a relative value is refused) | `omo-models.json` | none — loopback placeholder | The managed DSH export requires DSH 0.1.0-rc.6 or newer and owns only `llm-pi-ai.providers.opencodex`. DSH hot reloads that provider; the user's default model and @@ -328,8 +329,8 @@ the proxy binds beyond loopback; see [Remote access](/reference/configuration/#remote-access) for how admission keys are issued. Keys for the upstream providers themselves are a separate thing entirely, configured per [Providers](/guides/providers/). -Gajae is the exception: `OPENCODEX_GAJAE_API_KEY` fills its provider credential from the -environment, but its schema cannot send the remote admission header, so the generated Gajae +gjc is the exception: `OPENCODEX_GAJAE_API_KEY` fills its provider credential from the +environment, but its schema cannot send the remote admission header, so the generated gjc integration remains loopback-only. The same payload is served by `GET /api/client-config` and rendered on the dashboard's API tab, so diff --git a/docs-site/src/content/docs/reference/configuration.md b/docs-site/src/content/docs/reference/configuration.md index b1136ee86e..8ed7b97e69 100644 --- a/docs-site/src/content/docs/reference/configuration.md +++ b/docs-site/src/content/docs/reference/configuration.md @@ -61,7 +61,7 @@ a known configured model id. Cursor may require a model-list refresh or restart `fastRows` is an optional boolean and defaults to `true`. The raw OpenAI-style `/v1/models` list, Claude Code discovery, and client config exports (including pi, OpenCode, -OMP, Hermes, OpenClaw, Kimi, Gajae, DSH, MCode, ZCode, Prime, and Aside) add a `--fast` selector for every model whose +OMP, Hermes, OpenClaw, Kimi, gjc, DSH, MCode, ZCode, Prime, Aside, Raycast, and omo) add a `--fast` selector for every model whose resolved Fast policy is eligible. Selecting one routes the base model and requests the `priority` service tier — the same Fast the Codex app exposes through its picker toggle. The base row stays listed, so the row is an addition rather than a replacement. diff --git a/docs-site/src/content/docs/ru/reference/cli/agents.md b/docs-site/src/content/docs/ru/reference/cli/agents.md index 8df8175173..eadeb03d61 100644 --- a/docs-site/src/content/docs/ru/reference/cli/agents.md +++ b/docs-site/src/content/docs/ru/reference/cli/agents.md @@ -152,7 +152,7 @@ override, но файлы на диске никогда не меняются. ## Экспорт client config -### `ocx export --client ` +### `ocx export --client ` Печатает client config, направленный на работающий прокси. Команда сериализует блок провайдера `opencodex` в нативном формате выбранного клиента: base URL, список моделей и, @@ -163,7 +163,7 @@ override, но файлы на диске никогда не меняются. | Флаг | Действие | | --- | --- | -| `--client ` | Обязателен. Выбирает формат конфигурации клиента. | +| `--client ` | Обязателен. Выбирает формат конфигурации клиента. | | `--json` | Печатать только JSON-конфиг в stdout, чтобы redirect сохранял побайтно точный вывод. Вся диагностика, включая заметку о записи через `--out`, идёт в stderr. | | `--out ` | Записать конфиг в ``. Перезаписывать существующий файл не позволит. | | `--force` | Разрешить `--out` заменить существующий файл. | @@ -193,7 +193,9 @@ ocx export --client opencode --out ~/opencodex-opencode.json | `mcode` | `~/.minimax/config.yaml` (`MINIMAX_DATA_DIR`, затем устаревшая `MAVIS_DATA_DIR`, имеют приоритет, если заданы; относительное значение отклоняется) | `mcode-config.yaml` | нет — loopback placeholder | | `zcode` | `~/.zcode/v2/config.json` (`ZCODE_DATA_DIR` имеет приоритет, если задана; относительное значение отклоняется) | `config.json` | нет — loopback placeholder | | `prime` | `~/.prime/agent/models.json` (`PRIME_AGENT_CODING_AGENT_DIR` имеет приоритет, если задана; относительное значение отклоняется) | `prime-models.json` | нет — loopback placeholder | +| `aside` | `~/.aside/u//models.json` для аккаунта, который `accounts.json` самого Aside называет текущим; нечитаемый манифест отклоняется, а не подменяется произвольным аккаунтом | `aside-models.json` | нет — loopback placeholder | | `raycast` | `~/.config/raycast/ai/providers.yaml` одинаково на macOS и Windows (Raycast не учитывает `XDG_CONFIG_HOME`) | `raycast-providers.yaml` | нет — только loopback, запись `api_keys` не создаётся | +| `omo` | `~/.omo/agent/models.json` (`OMO_CODING_AGENT_DIR`, затем `SENPI_CODING_AGENT_DIR`, затем `PI_CODING_AGENT_DIR` имеют приоритет в этом порядке, если заданы; относительное значение отклоняется) | `omo-models.json` | нет — loopback placeholder | Экспорт для Raycast — это отдельный документ `providers.yaml` с одним элементом `id: opencodex` в последовательности `providers`: `name: OpenCodex`, базовый URL прокси с `/v1` и каждая маршрутизируемая @@ -222,8 +224,8 @@ MCP-записи. env-reference, либо несекретную loopback-заглушку. Loopback-прокси (`127.0.0.1`, по умолчанию) вообще не требует admission key. Если прокси слушает не на loopback, задайте соответствующую переменную `OPENCODEX_OPENCODE_API_KEY`, `OPENCODEX_HERMES_API_KEY` или `OPENCODEX_OPENCLAW_API_KEY`. -`OPENCODEX_GAJAE_API_KEY` передаёт provider credential Gajae через окружение, но не позволяет -отправить remote admission header, поэтому сгенерированная интеграция Gajae +`OPENCODEX_GAJAE_API_KEY` передаёт provider credential gjc через окружение, но не позволяет +отправить remote admission header, поэтому сгенерированная интеграция gjc работает только через loopback. Как выдаются admission key, описано в [Удалённом доступе](/reference/configuration/#remote-access). Ключи upstream-провайдеров — это совсем отдельная история и настраиваются в [Провайдерах](/guides/providers/). diff --git a/docs-site/src/content/docs/tr/guides/integrations.md b/docs-site/src/content/docs/tr/guides/integrations.md index f068b0233f..03f64377f2 100644 --- a/docs-site/src/content/docs/tr/guides/integrations.md +++ b/docs-site/src/content/docs/tr/guides/integrations.md @@ -1,10 +1,10 @@ --- title: Entegrasyonlar -description: Kontrol panelinden OpenCode, Pi, OMP, Hermes, OpenClaw, Kimi Code, Gajae Code, DeepSeek Harness, MiniMax Code, ZCode, Prime Agent, Aside ve Raycast'i opencodex'e bağlayın — istemci başına tek bir anahtar ve her yazmadan önce alınan bir yedek. +description: Kontrol panelinden OpenCode, Pi, OMP, Hermes, OpenClaw, Kimi Code, gjc, DeepSeek Harness, MiniMax Code, ZCode, Prime Agent, Aside, Raycast ve omo'yu opencodex'e bağlayın — istemci başına tek bir anahtar ve her yazmadan önce alınan bir yedek. --- **Entegrasyonlar** sekmesi, opencodex'in sağlayıcı bloğunu istemcinin kendi -yapılandırma dosyasına yazar ve tekrar kaldırır. On üç istemci bu şekilde +yapılandırma dosyasına yazar ve tekrar kaldırır. On dört istemci bu şekilde çalışır, her biri bir anahtarla: | İstemci | Yapılandırma dosyası | Format | Değişiklik ne zaman geçerli olur? | Kimlik bilgisi | @@ -15,13 +15,14 @@ yapılandırma dosyasına yazar ve tekrar kaldırır. On üç istemci bu şekild | Hermes | `~/.hermes/config.yaml` | YAML | yeni oturumlarda | `OPENCODEX_HERMES_API_KEY` | | OpenClaw | `~/.openclaw/openclaw.json` | JSON5 | hemen, çalışan bir ağ geçidinde | `OPENCODEX_OPENCLAW_API_KEY` | | Kimi Code | `~/.kimi-code/config.toml` | TOML | yeniden başlatmada veya `/reload` ile | geri döngü (loopback) yer tutucusu | -| Gajae Code | `~/.gjc/agent/models.yml` | YAML | yeni oturumlarda veya `/model` açtığınızda | `OPENCODEX_GAJAE_API_KEY` | +| gjc | `~/.gjc/agent/models.yml` | YAML | yeni oturumlarda veya `/model` açtığınızda | `OPENCODEX_GAJAE_API_KEY` | | DeepSeek Harness (DSH) | `$DSH_HOME/settings.yaml` (varsayılan `~/.dsh/settings.yaml`) | YAML | çalışırken yeniden yükleme | gizli olmayan geri döngü bearer yer tutucusu | | MiniMax Code | `~/.minimax/config.yaml` | YAML | yeni oturumlarda veya model seçici açıldıktan sonra | geri döngü (loopback) yer tutucusu | | Prime Agent | `~/.prime/agent/models.json` | JSON | yeni oturumlarda | geri döngü yer tutucusu | | ZCode | `~/.zcode/v2/config.json` | JSON | yeniden başlatmada | geri döngü yer tutucusu | | Aside | `~/.aside/u//models.json` | JSON | Aside tamamen kapatılıp yeniden açıldıktan sonra | geri döngü yer tutucusu | | Raycast | `~/.config/raycast/ai/providers.yaml` | YAML | kaydedildiği anda — Raycast dosyayı izler | yok — yalnızca geri döngü | +| omo | `~/.omo/agent/models.json` | JSON | yeni oturumlarda | geri döngü yer tutucusu | Yönetilen DSH desteğinin en düşük uyumlu sürümü **DSH 0.1.0-rc.6**'dır. OpenCodex yalnızca `llm-pi-ai.providers.opencodex` bölümünü yönetir: Uygula ve Yenile bu bölümü değiştirir, Devre Dışı @@ -149,7 +150,7 @@ hiçbir şey sessizce değiştirilmez veya düşürülmez. **OMP** de yanındaki düzenlemelerden etkilenmez, ama başka bir nedenle: writer'ı yalnızca kendi `providers.opencodex` aralığını bayt bayt yamalar, dosyanın geri kalanı hiçbir zaman yeniden yazılmaz. Yorum taşıyabilen diğer biçimlerde (Hermes, OpenClaw, -Kimi Code, Gajae Code, MiniMax Code, Raycast — bütün belge olarak yazılan YAML, JSON5 ve TOML) veya +Kimi Code, gjc, MiniMax Code, Raycast — bütün belge olarak yazılan YAML, JSON5 ve TOML) veya kendi girdilerimiz düzenlenmişse, anahtar kilitlenir ve hangi düzenlemelerin size ait olduğunu tahmin etmek yerine devre dışı bırakmayı reddeder. @@ -178,7 +179,7 @@ diziler ve satır içi tablolar dahil bu türlenmiş değerleri tırnaklı metne Zaten tırnak içinde yazılmış tarihler desteklenir. Tırnaksız tarih türünü korumak için yapılandırmayı elle düzenleyin. -**Pi, Kimi Code, Gajae Code, MiniMax Code ve yönetilen DSH entegrasyonu yalnızca geri döngü (loopback) bağlantısına karşı +**Pi, Kimi Code, gjc, MiniMax Code ve yönetilen DSH entegrasyonu yalnızca geri döngü (loopback) bağlantısına karşı çalışır.** İlk dördünün yapılandırmasında geri döngü olmayan bir bağlantının gerektirdiği `x-opencodex-api-key` başlığı için alan yoktur. DSH genel bir headers haritası sunar, ancak rc.6 bu özel kabul başlığını desteklenen bir entegrasyon sözleşmesi olarak belgelememektedir; bu nedenle @@ -229,10 +230,10 @@ ocx integration client enable --client mcode ocx mcode ``` -Bağlandıktan sonra `ocx sync` ve `POST /api/sync`, yönetilen MCode, Pi, Aside ve -Raycast kataloglarını yeniler. Proxy başlangıcı da yönetilen Raycast kataloğunu -yeniler. Model görünürlüğü, sağlayıcı veya ön ayar değişiklikleri Pi, Aside ve -Raycast kataloglarını günceller. Eksik, dışarıdan düzenlenmiş, güvenli olmayan +Bağlandıktan sonra `ocx sync` ve `POST /api/sync`, yönetilen MCode, Pi, Aside, +Raycast ve omo kataloglarını yeniler. Proxy başlangıcı da yönetilen Raycast +kataloğunu yeniler. Model görünürlüğü, sağlayıcı veya ön ayar değişiklikleri Pi, +Aside, Raycast ve omo kataloglarını günceller. Eksik, dışarıdan düzenlenmiş, güvenli olmayan veya elle kaldırılmış bloklara dokunmaz; yeniden bağlamak istediğinizde entegrasyonu açıkça etkinleştirin. diff --git a/docs-site/src/content/docs/tr/reference/cli/agents.md b/docs-site/src/content/docs/tr/reference/cli/agents.md index 04e72a766c..9f38ae6495 100644 --- a/docs-site/src/content/docs/tr/reference/cli/agents.md +++ b/docs-site/src/content/docs/tr/reference/cli/agents.md @@ -191,7 +191,7 @@ Grok Build model çitini yönetin ve uygulayın. ## İstemci yapılandırma dışa aktarma -### `ocx export --client ` +### `ocx export --client ` Çalışan proxy'ye bağlı bir istemci yapılandırmasını yazdırın. Komut, `opencodex` sağlayıcı bloğunu — temel URL, model listesi ve istemcinin kimlik bilgisi @@ -203,7 +203,7 @@ yalnızca Codex'in şu anda görebildiği modelleri yayınlar. | Bayrak | Eylem | | --- | --- | -| `--client ` | Gerekli. İstemci yapılandırma lehçesini seçer. | +| `--client ` | Gerekli. İstemci yapılandırma lehçesini seçer. | | `--json` | Betikler için stdout üzerinde oluşturulan belgeyi JSON olarak yazdırın. Bu, seçilen istemcinin yerel formatı YAML, TOML veya JSON5 olsa bile JSON'dur. | | `--out ` | İstemcinin yerel yapılandırma formatını `` konumuna yazın. Mevcut bir dosyanın üzerine yazmayı reddeder. | | `--force` | `--out`'un mevcut bir dosyanın üzerine yazmasına izin verin. | @@ -233,7 +233,9 @@ için kendi varsayılanlarını uygular) gelir. | `mcode` | `~/.minimax/config.yaml` (ayarlandığında `MINIMAX_DATA_DIR`, ardından eski `MAVIS_DATA_DIR` öncelikli; göreli değer reddedilir) | `mcode-config.yaml` | yok — geri döngü yer tutucusu | | `zcode` | `~/.zcode/v2/config.json` (ayarlandığında `ZCODE_DATA_DIR` öncelikli; göreli değer reddedilir) | `config.json` | yok — geri döngü yer tutucusu | | `prime` | `~/.prime/agent/models.json` (ayarlandığında `PRIME_AGENT_CODING_AGENT_DIR` öncelikli; göreli değer reddedilir) | `prime-models.json` | yok — geri döngü yer tutucusu | +| `aside` | Aside'ın kendi `accounts.json` dosyasının güncel olarak gösterdiği hesap için `~/.aside/u//models.json`; okunamayan bir manifest, gelişigüzel bir hesaba düşmek yerine reddedilir | `aside-models.json` | yok — geri döngü yer tutucusu | | `raycast` | `~/.config/raycast/ai/providers.yaml`, macOS ve Windows'ta aynı (Raycast `XDG_CONFIG_HOME` değerini dikkate almaz) | `raycast-providers.yaml` | yok — yalnızca geri döngü, `api_keys` girdisi yazılmaz | +| `omo` | `~/.omo/agent/models.json` (ayarlandığında sırasıyla `OMO_CODING_AGENT_DIR`, `SENPI_CODING_AGENT_DIR`, `PI_CODING_AGENT_DIR` öncelikli; göreli değer reddedilir) | `omo-models.json` | yok — geri döngü yer tutucusu | Raycast dışa aktarımı, `providers` dizisinde tek bir `id: opencodex` öğesi içeren bağımsız bir `providers.yaml` belgesidir: `name: OpenCodex`, proxy'nin `/v1` temel URL'si ve @@ -270,8 +272,8 @@ döngünün ötesine bağlandığında ayarlayın; kabul anahtarlarının nasıl görmek için [Uzaktan erişim](/tr/reference/configuration/#remote-access) bölümüne bakın. Yukarı akış sağlayıcılarının kendi anahtarları tamamen ayrı bir şeydir ve [Sağlayıcılar](/tr/guides/providers/) bölümüne göre yapılandırılır. -Gajae istisnadır: `OPENCODEX_GAJAE_API_KEY` provider kimlik bilgisini ortamdan -sağlar, ancak şeması uzaktan kabul başlığını gönderemediği için üretilen Gajae +gjc istisnadır: `OPENCODEX_GAJAE_API_KEY` provider kimlik bilgisini ortamdan +sağlar, ancak şeması uzaktan kabul başlığını gönderemediği için üretilen gjc entegrasyonu yalnızca geri döngüde çalışır. Aynı yük `GET /api/client-config` tarafından sunulur ve kontrol panelinin API diff --git a/docs-site/src/content/docs/zh-cn/reference/cli/agents.md b/docs-site/src/content/docs/zh-cn/reference/cli/agents.md index 89203420e9..f141dee649 100644 --- a/docs-site/src/content/docs/zh-cn/reference/cli/agents.md +++ b/docs-site/src/content/docs/zh-cn/reference/cli/agents.md @@ -132,7 +132,7 @@ ocx claude desktop import [--apply] Validate and import JSON ## Client config export -### `ocx export --client ` +### `ocx export --client ` 输出连接到正在运行代理的客户端配置。此命令会以所选客户端的原生格式序列化 `opencodex` provider 块,其中包含基础 URL、模型列表,以及该客户端适用的凭据引用或 `opencodex-loopback` 占位值。 @@ -140,7 +140,7 @@ ocx claude desktop import [--apply] Validate and import JSON | 标志 | 动作 | | --- | --- | -| `--client ` | 必需。选择客户端配置格式。 | +| `--client ` | 必需。选择客户端配置格式。 | | `--json` | 仅在 stdout 打印配置 JSON,这样重定向即可捕获字节级精确输出。包括 `--out` 写入提示在内的所有诊断信息都会输出到 stderr。 | | `--out ` | 将配置写入 ``。拒绝替换已存在的文件。 | | `--force` | 允许 `--out` 替换已存在的文件。 | @@ -167,7 +167,9 @@ ocx export --client opencode --out ~/opencodex-opencode.json | `mcode` | `~/.minimax/config.yaml` (设置后 `MINIMAX_DATA_DIR` 优先,其次是旧的 `MAVIS_DATA_DIR`;相对路径会被拒绝) | `mcode-config.yaml` | 无 — loopback placeholder | | `zcode` | `~/.zcode/v2/config.json` (设置后 `ZCODE_DATA_DIR` 优先;相对路径会被拒绝) | `config.json` | 无 — loopback placeholder | | `prime` | `~/.prime/agent/models.json` (设置后 `PRIME_AGENT_CODING_AGENT_DIR` 优先;相对路径会被拒绝) | `prime-models.json` | 无 — loopback placeholder | +| `aside` | `~/.aside/u//models.json`,对应 Aside 自己的 `accounts.json` 指明的当前账户;清单不可读时会被拒绝,而不是退回到某个账户 | `aside-models.json` | 无 — loopback placeholder | | `raycast` | `~/.config/raycast/ai/providers.yaml`(macOS 与 Windows 相同;Raycast 不遵循 `XDG_CONFIG_HOME`) | `raycast-providers.yaml` | 无 — 仅限回环,不会写入 `api_keys` 条目 | +| `omo` | `~/.omo/agent/models.json`(设置后依次由 `OMO_CODING_AGENT_DIR`、`SENPI_CODING_AGENT_DIR`、`PI_CODING_AGENT_DIR` 优先;相对路径会被拒绝) | `omo-models.json` | 无 — loopback placeholder | Raycast 导出是一份独立的 `providers.yaml` 文档,在 `providers` 序列中只有一个 `id: opencodex` 元素:`name: OpenCodex`、代理的 `/v1` 基础 URL,以及每个已路由模型及其 `abilities`(`tools` 与 `system_message` 始终支持,`vision` 取自目录的输入模态,`reasoning_effort` 在模型有 effort 阶梯时设置,`temperature` 对推理模型关闭)。Custom Providers 是 Raycast Pro 功能,且 Raycast 会监视该文件,因此保存后的更改无需重启即可生效。格式见 [manual.raycast.com/ai/custom-providers](https://manual.raycast.com/ai/custom-providers)。不会写入任何 `api_keys` 条目,所以该导出仅限回环,非回环绑定会被拒绝。 @@ -177,7 +179,7 @@ opencode 会插值 `{env:OPENCODEX_OPENCODE_API_KEY}`。opencodex 生成的 Pi `ocx export` 从不写入你的真实客户端配置。该命令只会打印目标路径供你手动合并,而 `--out` 在没有 `--force` 的情况下拒绝覆盖已有文件,因为替换配置会破坏其中已有的其他 providers、agents 和 MCP 条目。 ::: -任何密钥都不会被序列化。生成的配置里携带的要么是有文档记录的环境引用,要么是非机密的环回占位值。环回代理(`127.0.0.1`,默认值)根本不需要准入密钥。当代理绑定到环回地址之外时,请设置对应的 `OPENCODEX_OPENCODE_API_KEY`、`OPENCODEX_HERMES_API_KEY` 或 `OPENCODEX_OPENCLAW_API_KEY`。`OPENCODEX_GAJAE_API_KEY` 只会从环境中提供 Gajae provider 凭据,不能发送远程准入 header,因此生成的 Gajae 集成仍仅支持环回。关于准入密钥如何签发,请参见 [远程访问](/reference/configuration/#remote-access)。上游 providers 自身的密钥则完全是另一回事,需要按 [Providers](/guides/providers/) 单独配置。 +任何密钥都不会被序列化。生成的配置里携带的要么是有文档记录的环境引用,要么是非机密的环回占位值。环回代理(`127.0.0.1`,默认值)根本不需要准入密钥。当代理绑定到环回地址之外时,请设置对应的 `OPENCODEX_OPENCODE_API_KEY`、`OPENCODEX_HERMES_API_KEY` 或 `OPENCODEX_OPENCLAW_API_KEY`。`OPENCODEX_GAJAE_API_KEY` 只会从环境中提供 gjc provider 凭据,不能发送远程准入 header,因此生成的 gjc 集成仍仅支持环回。关于准入密钥如何签发,请参见 [远程访问](/reference/configuration/#remote-access)。上游 providers 自身的密钥则完全是另一回事,需要按 [Providers](/guides/providers/) 单独配置。 同一份负载会通过 `GET /api/client-config` 提供,并在仪表盘的 API 选项卡中渲染,因此 CLI、API 和 GUI 使用的是同一字节内容。 diff --git a/docs-site/src/content/docs/zh-tw/guides/integrations.md b/docs-site/src/content/docs/zh-tw/guides/integrations.md index cd767e0a9b..bc9277656f 100644 --- a/docs-site/src/content/docs/zh-tw/guides/integrations.md +++ b/docs-site/src/content/docs/zh-tw/guides/integrations.md @@ -1,9 +1,9 @@ --- title: 整合 -description: 從儀表板把 opencodex 連接到 OpenCode、Pi、OMP、Hermes、OpenClaw、Kimi Code、Gajae Code、DeepSeek Harness、MiniMax Code、ZCode、Prime Agent、Aside 與 Raycast——每個客戶端一個開關,每次寫入前都會先備份。 +description: 從儀表板把 opencodex 連接到 OpenCode、Pi、OMP、Hermes、OpenClaw、Kimi Code、gjc、DeepSeek Harness、MiniMax Code、ZCode、Prime Agent、Aside、Raycast 與 omo——每個客戶端一個開關,每次寫入前都會先備份。 --- -**整合(Integrations)** 分頁會把 opencodex 的 provider 區塊寫入客戶端自己的設定檔,也會把它移除。共有十三個客戶端以這種方式運作,每個都有一個開關: +**整合(Integrations)** 分頁會把 opencodex 的 provider 區塊寫入客戶端自己的設定檔,也會把它移除。共有十四個客戶端以這種方式運作,每個都有一個開關: | 客戶端 | 設定檔 | 格式 | 變更生效時機 | 憑證 | |---|---|---|---|---| @@ -13,13 +13,14 @@ description: 從儀表板把 opencodex 連接到 OpenCode、Pi、OMP、Hermes、 | Hermes | `~/.hermes/config.yaml` | YAML | 新 sessions | `OPENCODEX_HERMES_API_KEY` | | OpenClaw | `~/.openclaw/openclaw.json` | JSON5 | 立即,在執行中的 gateway 上 | `OPENCODEX_OPENCLAW_API_KEY` | | Kimi Code | `~/.kimi-code/config.toml` | TOML | 重新啟動時,或 `/reload` | loopback 佔位符 | -| Gajae Code | `~/.gjc/agent/models.yml` | YAML | 新 sessions,或當你開啟 `/model` 時 | `OPENCODEX_GAJAE_API_KEY` | +| gjc | `~/.gjc/agent/models.yml` | YAML | 新 sessions,或當你開啟 `/model` 時 | `OPENCODEX_GAJAE_API_KEY` | | DeepSeek Harness (DSH) | `$DSH_HOME/settings.yaml`(預設 `~/.dsh/settings.yaml`) | YAML | 熱重載 | 非秘密的 loopback bearer 佔位符 | | MiniMax Code | `~/.minimax/config.yaml` | YAML | 新 sessions,或開啟模型選擇器後 | loopback 佔位符 | | Prime Agent | `~/.prime/agent/models.json` | JSON | 新 sessions | loopback 佔位符 | | ZCode | `~/.zcode/v2/config.json` | JSON | 重新啟動時 | loopback 佔位符 | | Aside | `~/.aside/u//models.json` | JSON | 完全結束並重新開啟 Aside 後 | loopback 佔位符 | | Raycast | `~/.config/raycast/ai/providers.yaml` | YAML | 儲存後立即生效——Raycast 會監看該檔案 | 無——僅限 loopback | +| omo | `~/.omo/agent/models.json` | JSON | 新工作階段 | loopback 佔位符 | 受管理 DSH 支援的相容性下限是 **DSH 0.1.0-rc.6**。OpenCodex 只擁有 `llm-pi-ai.providers.opencodex`:Apply 與 Refresh 會取代該片段,Disable 只移除該片段, @@ -80,7 +81,7 @@ opencodex 從自己的環境讀取這些變數。如果你的 gateway 以 profil - **Restore this point…** 會出現在較舊的操作上,或當檔案在那次操作之後有變更時。跨過這樣的變更做回復會再詢問一次,才覆蓋你的較新編輯——並且也會備份它們,所以那次的回復本身也可以復原。 - 每個客戶端保留十份備份。超過之後,最舊的快照檔案會被移除,其歷史列顯示為 **Backup expired**。 -停用只移除 opencodex 記錄為自己寫入的條目。如果你的檔案在我們寫入之後有變更,後續行為取決於我們自己的條目是否完好,以及檔案的格式。對於嚴格 JSON 設定檔(OpenCode、Pi),在我們的區塊**旁邊**進行的編輯——例如新增 MCP 伺服器或你自己的 provider——會顯示為**需要更新**:重新整理會在保留你的條目的前提下合併寫入,但格式可能會被正規化。例外情況是 JSON 無法精確重寫的內容——例如 `1e999` 這類非有限數字、重寫會被四捨五入的數字(極大的整數,或小到會塌縮成零的數字)、`-0`、同一個物件裡重複出現的鍵,或巢狀層數超過 1000 層——此時開關會鎖定,確保沒有任何值被悄悄改動或刪除。**OMP、DSH 與 Hermes** 同樣不受旁邊編輯影響,但原因不同:它們的 writer 只逐位元組修補自己的 `providers.opencodex` 範圍,檔案其餘部分從不會被重寫。至於其餘可以包含註解的格式(OpenClaw、Kimi Code、Gajae Code、MiniMax Code、Raycast——以整份文件寫出的 YAML、JSON5 與 TOML),或當我們自己的條目被編輯過時,開關會鎖定,停用會拒絕執行,而不是猜測哪些編輯是你的。 +停用只移除 opencodex 記錄為自己寫入的條目。如果你的檔案在我們寫入之後有變更,後續行為取決於我們自己的條目是否完好,以及檔案的格式。對於嚴格 JSON 設定檔(OpenCode、Pi),在我們的區塊**旁邊**進行的編輯——例如新增 MCP 伺服器或你自己的 provider——會顯示為**需要更新**:重新整理會在保留你的條目的前提下合併寫入,但格式可能會被正規化。例外情況是 JSON 無法精確重寫的內容——例如 `1e999` 這類非有限數字、重寫會被四捨五入的數字(極大的整數,或小到會塌縮成零的數字)、`-0`、同一個物件裡重複出現的鍵,或巢狀層數超過 1000 層——此時開關會鎖定,確保沒有任何值被悄悄改動或刪除。**OMP、DSH 與 Hermes** 同樣不受旁邊編輯影響,但原因不同:它們的 writer 只逐位元組修補自己的 `providers.opencodex` 範圍,檔案其餘部分從不會被重寫。至於其餘可以包含註解的格式(OpenClaw、Kimi Code、gjc、MiniMax Code、Raycast——以整份文件寫出的 YAML、JSON5 與 TOML),或當我們自己的條目被編輯過時,開關會鎖定,停用會拒絕執行,而不是猜測哪些編輯是你的。 ## 誠實的預期 @@ -90,7 +91,7 @@ opencodex 從自己的環境讀取這些變數。如果你的 gateway 以 profil TOML 日期與時間值也會阻止自動重寫:合併步驟會將這些帶有型別的值轉成加引號的字串,陣列和行內表格中的值也一樣。原本就加引號的日期字串仍受支援;若要保留不加引號的日期型別,請手動編輯設定。 -**Pi、Kimi Code、Gajae Code、MiniMax Code 與受管理 DSH 整合只能對 loopback bind 運作。** 前四者的設定沒有非 loopback bind 所需的 `x-opencodex-api-key` header 欄位。DSH 雖然提供通用 headers map,但 rc.6 並未把這個專用准入 header 記錄為受支援的整合契約,因此受管理 writer 會選擇安全拒絕,而不自行猜測。請改用 SSH tunnel,或由本機 forwarder 加上該 header 後再以 loopback 存取。 +**Pi、Kimi Code、gjc、MiniMax Code 與受管理 DSH 整合只能對 loopback bind 運作。** 前四者的設定沒有非 loopback bind 所需的 `x-opencodex-api-key` header 欄位。DSH 雖然提供通用 headers map,但 rc.6 並未把這個專用准入 header 記錄為受支援的整合契約,因此受管理 writer 會選擇安全拒絕,而不自行猜測。請改用 SSH tunnel,或由本機 forwarder 加上該 header 後再以 loopback 存取。 **產生的 OMP 整合也刻意只支援 loopback。** OMP 確實支援 provider 層級的 headers,但這個最初的整合不會發出遠端 `x-opencodex-api-key` 憑證連線。手動的遠端 OMP 設定目前不在受管理的整合範圍內。 @@ -127,8 +128,8 @@ ocx mcode ``` 完成一次連接後,`ocx sync` 與 `POST /api/sync` 會更新 OpenCodex 已擁有的 -MCode、Pi、Aside 與 Raycast 目錄。proxy 啟動也會更新已擁有的 Raycast 目錄。 -模型可見性、provider 或 preset 變更會更新 Pi、Aside 與 Raycast。若區塊已刪除、 +MCode、Pi、Aside、Raycast 與 omo 目錄。proxy 啟動也會更新已擁有的 Raycast 目錄。 +模型可見性、provider 或 preset 變更會更新 Pi、Aside、Raycast 與 omo。若區塊已刪除、 遭外部修改、不安全或由你手動移除,sync 會保持原檔不動;只有在你確定要重新 連接時才再次執行 enable。 diff --git a/docs-site/src/content/docs/zh-tw/reference/cli/agents.md b/docs-site/src/content/docs/zh-tw/reference/cli/agents.md index d04c099ebf..d9585b2520 100644 --- a/docs-site/src/content/docs/zh-tw/reference/cli/agents.md +++ b/docs-site/src/content/docs/zh-tw/reference/cli/agents.md @@ -130,7 +130,7 @@ ocx claude desktop import [--apply] 驗證並匯入 JSON ## 客戶端設定匯出 -### `ocx export --client ` +### `ocx export --client ` 印出連接到執行中代理的客戶端設定。此指令會用所選客戶端的原生格式,序列化含有 base URL、模型清單,以及適用的環境變數參考或 loopback 佔位符的 `opencodex` provider 區塊。 @@ -138,7 +138,7 @@ ocx claude desktop import [--apply] 驗證並匯入 JSON | 旗標 | 動作 | | --- | --- | -| `--client ` | 必填。選擇客戶端設定格式。 | +| `--client ` | 必填。選擇客戶端設定格式。 | | `--json` | 僅在 stdout 印出設定 JSON,使重導向能擷取逐位元組輸出。所有診斷訊息(含 `--out` 寫入提示)皆送至 stderr。 | | `--out ` | 將設定寫入 ``。拒絕覆寫既有檔案。 | | `--force` | 允許 `--out` 覆寫既有檔案。 | @@ -165,7 +165,9 @@ ocx export --client opencode --out ~/opencodex-opencode.json | `mcode` | `~/.minimax/config.yaml` (設定後 `MINIMAX_DATA_DIR` 優先,其次為舊的 `MAVIS_DATA_DIR`;相對路徑會被拒絕) | `mcode-config.yaml` | 無——loopback 佔位符 | | `zcode` | `~/.zcode/v2/config.json` (設定後 `ZCODE_DATA_DIR` 優先;相對路徑會被拒絕) | `config.json` | 無——loopback 佔位符 | | `prime` | `~/.prime/agent/models.json` (設定後 `PRIME_AGENT_CODING_AGENT_DIR` 優先;相對路徑會被拒絕) | `prime-models.json` | 無——loopback 佔位符 | +| `aside` | `~/.aside/u//models.json`,對應 Aside 自己的 `accounts.json` 指定的目前帳戶;資訊清單無法讀取時會被拒絕,而不是退回任一帳戶 | `aside-models.json` | 無——loopback 佔位符 | | `raycast` | `~/.config/raycast/ai/providers.yaml`(macOS 與 Windows 相同;Raycast 不遵循 `XDG_CONFIG_HOME`) | `raycast-providers.yaml` | 無——僅限 loopback,不會寫入 `api_keys` 項目 | +| `omo` | `~/.omo/agent/models.json`(設定後依序由 `OMO_CODING_AGENT_DIR`、`SENPI_CODING_AGENT_DIR`、`PI_CODING_AGENT_DIR` 優先;相對路徑會被拒絕) | `omo-models.json` | 無——loopback 佔位符 | Raycast 匯出是一份獨立的 `providers.yaml` 文件,在 `providers` 序列中只有一個 `id: opencodex` 元素:`name: OpenCodex`、proxy 的 `/v1` base URL,以及每個路由模型及其 `abilities`(`tools` 與 `system_message` 一律支援,`vision` 依目錄的輸入模態而定,`reasoning_effort` 在模型有 effort 階梯時設定,`temperature` 對推理模型關閉)。Custom Providers 是 Raycast Pro 功能,且 Raycast 會監看該檔案,因此儲存後的變更不需重新啟動即可生效。格式說明見 [manual.raycast.com/ai/custom-providers](https://manual.raycast.com/ai/custom-providers)。不會寫入任何 `api_keys` 項目,所以此匯出僅限 loopback,非 loopback 的 bind 會被拒絕。 @@ -180,7 +182,7 @@ opencode 會插值 `{env:OPENCODEX_OPENCODE_API_KEY}`。Pi 與 OMP 的匯出不 金鑰永不被序列化。設定只帶有文件化的環境變數參考,或非秘密的 loopback 佔位符。loopback 代理(`127.0.0.1`,預設值)完全不需要准入金鑰。只有客戶端 schema 支援、且代理綁定超出 loopback 時,才設定被引用的變數;關於准入金鑰的簽發方式,請見[遠端存取](/zh-tw/reference/configuration/#remote-access)。上游 provider 本身的金鑰是完全不同的事,依[供應商](/zh-tw/guides/providers/)個別設定。 -Gajae 是例外:`OPENCODEX_GAJAE_API_KEY` 只會從環境提供 provider 憑證,但其 schema 無法傳送遠端准入 header,因此產生的 Gajae 整合仍僅支援 loopback。 +gjc 是例外:`OPENCODEX_GAJAE_API_KEY` 只會從環境提供 provider 憑證,但其 schema 無法傳送遠端准入 header,因此產生的 gjc 整合仍僅支援 loopback。 相同的 payload 亦由 `GET /api/client-config` 提供,並在儀表板的 API 分頁渲染,因此 CLI、API 與 GUI 使用相同的位元組。 diff --git a/gui/public/provider-icons/README.md b/gui/public/provider-icons/README.md index 1aab8d651c..235fa7bf27 100644 --- a/gui/public/provider-icons/README.md +++ b/gui/public/provider-icons/README.md @@ -57,6 +57,19 @@ Export-client marks (used by the API tab's connect rows, not the provider list): behind — is removed because the path never leaves the frame and the rect would read as a second ink to the mark tooling here. +- `omo.svg` — fetched 2026-09-12 from `https://omo.dev/brand/omo-mark.svg`, the + 24px header mark on omo's own site. The identical file (4021 bytes, MD5 + `c33f72d7c4612c290834ba860f644557`) is committed as + `.github/assets/omo-icon-light.svg` in `code-yeongyu/oh-my-openagent` and + rendered as that README's logo, which is what identifies it as the intended + square lockup rather than an incidental asset. Note the branch: that repository's + default branch is `dev`, so the `main` raw path 404s. Copied unmodified, + `viewBox="0 0 1024 1024"`. Used for the `omo` client (`omo-ai@beta`). + `omo.dev/icon.svg` was rejected for being a single `O` glyph, the + same rule that sent Hermes to a trace; `omo-logo.png` is a superseded 3D + illustration and `omo.png` a landscape screenshot. The npm tarball ships no + image at all. + - `minimax.svg` — fetched 2026-08-31 from `https://raw.githubusercontent.com/MiniMax-AI/MiniMax-01/main/figures/minimax.svg`, MiniMax's own symbol as committed in their own model repository. The API-docs @@ -148,6 +161,12 @@ Decisions that are not obvious from looking at the file: - `raycast.svg` **is not masked.** One ink, but that ink is #FF6363 — Raycast red, the same case as `openai.svg` and `deepseek-harness.svg`. Legible on both surfaces as an image. +- `omo.svg` **is not masked.** Two inks: an `#F4F4F4` rounded plate carrying an + `#041617` face. The plate is opaque and covers most of the canvas, so masking + — which reads alpha, not color — would paint a filled rounded tile and discard + the face entirely. This is the plated case `qoder.svg` already established, and + it is why a plated mark is not a candidate for the monochrome set however + neutral its inks look. Both directions are enforced in `gui/tests/integration-marks.test.ts`, including a luminance check that fails any single-ink near-neutral mark left as an image. That diff --git a/gui/public/provider-icons/omo.svg b/gui/public/provider-icons/omo.svg new file mode 100644 index 0000000000..21c1b35c6e --- /dev/null +++ b/gui/public/provider-icons/omo.svg @@ -0,0 +1,42 @@ + + + + + + + + diff --git a/gui/src/app-routing.ts b/gui/src/app-routing.ts index c5971ffb6b..cf9762ab71 100644 --- a/gui/src/app-routing.ts +++ b/gui/src/app-routing.ts @@ -101,6 +101,7 @@ export const INTEGRATION_TAB_HASHES = [ "integrations/prime", "integrations/aside", "integrations/raycast", + "integrations/omo", ] as const; export function hashBelongsToPage(rawHash: string, page: Page): boolean { diff --git a/gui/src/components/apikeys-workspace/client-config-clients.ts b/gui/src/components/apikeys-workspace/client-config-clients.ts index afd4484551..2c6e198600 100644 --- a/gui/src/components/apikeys-workspace/client-config-clients.ts +++ b/gui/src/components/apikeys-workspace/client-config-clients.ts @@ -8,7 +8,7 @@ * with EXPORT_CLIENT_IDS by hand; adding a client server-side renders no row * until this tuple changes. */ -export const CLIENTS = ["opencode", "pi", "omp", "hermes", "openclaw", "kimi", "gajae", "dsh", "mcode", "zcode", "prime", "aside", "raycast"] as const; +export const CLIENTS = ["opencode", "pi", "omp", "hermes", "openclaw", "kimi", "gajae", "dsh", "mcode", "zcode", "prime", "aside", "raycast", "omo"] as const; export type ExportClientId = (typeof CLIENTS)[number]; export const CLIENT_LABEL_KEYS = { @@ -25,6 +25,7 @@ export const CLIENT_LABEL_KEYS = { prime: "api.clientConfig.clientPrime", aside: "api.clientConfig.clientAside", raycast: "api.clientConfig.clientRaycast", + omo: "api.clientConfig.clientOmo", } as const; /** @@ -73,6 +74,9 @@ export const CLIENT_MARKS: Partial> = { aside: "/provider-icons/aside.svg", // Raycast red (#FF6363) is the brand, so like `dsh` it stays an image. raycast: "/provider-icons/raycast.svg", + // Two inks: an #F4F4F4 plate carrying an #041617 face. Masking reads alpha, + // so it would paint the plate and throw the face away — see the README. + omo: "/provider-icons/omo.svg", }; /** diff --git a/gui/src/components/integration-marks.ts b/gui/src/components/integration-marks.ts index eca38510bd..f345866336 100644 --- a/gui/src/components/integration-marks.ts +++ b/gui/src/components/integration-marks.ts @@ -58,6 +58,7 @@ export const INTEGRATION_MARKS: Record = { prime: CLIENT_MARKS.prime ?? null, aside: CLIENT_MARKS.aside ?? null, raycast: CLIENT_MARKS.raycast ?? null, + omo: CLIENT_MARKS.omo ?? null, }; /** diff --git a/gui/src/i18n/de.ts b/gui/src/i18n/de.ts index 68363152b5..5a6c9d3872 100644 --- a/gui/src/i18n/de.ts +++ b/gui/src/i18n/de.ts @@ -1097,13 +1097,14 @@ export const de: Record = { "integrations.tab.hermes": "Hermes", "integrations.tab.openclaw": "OpenClaw", "integrations.tab.kimi": "Kimi Code", - "integrations.tab.gajae": "Gajae Code", + "integrations.tab.gajae": "gjc", "integrations.tab.dsh": "DSH", "integrations.tab.mcode": "MiniMax Code", "integrations.tab.zcode": "ZCode", "integrations.tab.prime": "Prime Agent", "integrations.tab.aside": "Aside", "integrations.tab.raycast": "Raycast", + "integrations.tab.omo": "omo", "integrations.aside.profilesTitle": "Aside-Profile", "integrations.aside.profilesHint": "Wähle, welche Profile die ausgewählten Modelle erhalten. Das aktive Aside-Profil bleibt unverändert.", "integrations.aside.all": "Alle Profile synchronisieren", @@ -1264,6 +1265,7 @@ export const de: Record = { "integrations.semantics.prime": "Verwaltet nur providers.opencodex in der models.json von Prime Agent — ~/.prime/agent, sofern PRIME_AGENT_CODING_AGENT_DIR sie nicht umleitet. Andere Provider und Modell-Overrides bleiben unverändert. Gilt für neue Sitzungen.", "integrations.semantics.aside": "Verwaltet nur providers.opencodex in der ~/.aside/u//models.json dieses Profils. Andere Provider bleiben unverändert. Beende Aside nach dem Anwenden vollständig und öffne es erneut.", "integrations.semantics.raycast": "Fügt einen OpenCodex-Provider-Eintrag in die providers.yaml von Raycast ein, damit jedes geroutete Modell in der Modellauswahl von Raycast AI erscheint. Raycast Pro erforderlich.", + "integrations.semantics.omo": "Verwaltet ausschließlich providers.opencodex in der models.json von omo — ~/.omo/agent, sofern nicht OMO_CODING_AGENT_DIR, SENPI_CODING_AGENT_DIR oder PI_CODING_AGENT_DIR sie umleitet. Ihre übrigen Provider bleiben unverändert. Gilt ab neuen Sitzungen.", "integrations.raycast.proRequired": "Custom Providers ist eine Funktion von Raycast Pro. Die Datei wird geschrieben, aber Raycast ignoriert sie, bis ein Pro-Abonnement aktiv ist.", "integrations.raycast.planUnknown": "Es konnte nicht festgestellt werden, ob Raycast Pro aktiv ist; Custom Providers erfordert Raycast Pro.", "integrations.raycast.revealConfig": "Öffnen Sie Raycast → Einstellungen → AI und klicken Sie einmal auf „Reveal Providers Config“, damit der Providers-Ordner existiert.", @@ -1589,13 +1591,14 @@ export const de: Record = { "api.clientConfig.clientHermes": "Hermes", "api.clientConfig.clientOpenclaw": "OpenClaw", "api.clientConfig.clientKimi": "Kimi Code", - "api.clientConfig.clientGajae": "Gajae Code", + "api.clientConfig.clientGajae": "gjc", "api.clientConfig.clientDsh": "DeepSeek Harness (DSH)", "api.clientConfig.clientMcode": "MiniMax Code", "api.clientConfig.clientZcode": "ZCode", "api.clientConfig.clientPrime": "Prime Agent", "api.clientConfig.clientAside": "Aside", "api.clientConfig.clientRaycast": "Raycast", + "api.clientConfig.clientOmo": "omo", "api.clientConfig.copy": "Konfiguration kopieren", "api.clientConfig.download": "Herunterladen", "api.clientConfig.loading": "Client-Konfiguration wird erstellt…", diff --git a/gui/src/i18n/en.ts b/gui/src/i18n/en.ts index 89fc5ec0d7..9007a52761 100644 --- a/gui/src/i18n/en.ts +++ b/gui/src/i18n/en.ts @@ -1630,13 +1630,14 @@ export const en = { "integrations.tab.hermes": "Hermes", "integrations.tab.openclaw": "OpenClaw", "integrations.tab.kimi": "Kimi Code", - "integrations.tab.gajae": "Gajae Code", + "integrations.tab.gajae": "gjc", "integrations.tab.dsh": "DSH", "integrations.tab.mcode": "MiniMax Code", "integrations.tab.zcode": "ZCode", "integrations.tab.prime": "Prime Agent", "integrations.tab.aside": "Aside", "integrations.tab.raycast": "Raycast", + "integrations.tab.omo": "omo", "integrations.aside.profilesTitle": "Aside profiles", "integrations.aside.profilesHint": "Choose which profiles receive the selected models. Aside’s active profile stays unchanged.", "integrations.aside.all": "Sync all profiles", @@ -1837,6 +1838,7 @@ export const en = { "integrations.semantics.prime": "Manages only providers.opencodex in Prime Agent's models.json — ~/.prime/agent unless PRIME_AGENT_CODING_AGENT_DIR redirects it. Your other providers and model overrides stay unchanged. Applies to new sessions.", "integrations.semantics.aside": "Manages only providers.opencodex in this profile’s ~/.aside/u//models.json. Your other providers stay unchanged. Fully quit and reopen Aside after applying.", "integrations.semantics.raycast": "Adds an OpenCodex provider entry to Raycast's providers.yaml so every routed model appears in the Raycast AI model picker. Raycast Pro required.", + "integrations.semantics.omo": "Manages only providers.opencodex in omo's models.json — ~/.omo/agent unless OMO_CODING_AGENT_DIR, SENPI_CODING_AGENT_DIR or PI_CODING_AGENT_DIR redirects it. Your other providers stay unchanged. Applies to new sessions.", "integrations.raycast.proRequired": "Custom Providers is a Raycast Pro feature. The file will be written, but Raycast ignores it until a Pro subscription is active.", "integrations.raycast.planUnknown": "Could not determine whether Raycast Pro is active; Custom Providers requires Raycast Pro.", "integrations.raycast.revealConfig": "Open Raycast → Settings → AI and click Reveal Providers Config once so the providers folder exists.", @@ -2173,13 +2175,14 @@ export const en = { "api.clientConfig.clientHermes": "Hermes", "api.clientConfig.clientOpenclaw": "OpenClaw", "api.clientConfig.clientKimi": "Kimi Code", - "api.clientConfig.clientGajae": "Gajae Code", + "api.clientConfig.clientGajae": "gjc", "api.clientConfig.clientDsh": "DeepSeek Harness (DSH)", "api.clientConfig.clientMcode": "MiniMax Code", "api.clientConfig.clientZcode": "ZCode", "api.clientConfig.clientPrime": "Prime Agent", "api.clientConfig.clientAside": "Aside", "api.clientConfig.clientRaycast": "Raycast", + "api.clientConfig.clientOmo": "omo", "api.clientConfig.copy": "Copy config", "api.clientConfig.download": "Download", "api.clientConfig.loading": "Building client config…", diff --git a/gui/src/i18n/fr.ts b/gui/src/i18n/fr.ts index 4cf6818072..c5e3c54c5a 100644 --- a/gui/src/i18n/fr.ts +++ b/gui/src/i18n/fr.ts @@ -1602,13 +1602,14 @@ export const fr: Record = { "integrations.tab.hermes": "Hermes", "integrations.tab.openclaw": "OpenClaw", "integrations.tab.kimi": "Kimi Code", - "integrations.tab.gajae": "Gajae Code", + "integrations.tab.gajae": "gjc", "integrations.tab.dsh": "DSH", "integrations.tab.mcode": "MiniMax Code", "integrations.tab.zcode": "ZCode", "integrations.tab.prime": "Prime Agent", "integrations.tab.aside": "Aside", "integrations.tab.raycast": "Raycast", + "integrations.tab.omo": "omo", "integrations.aside.profilesTitle": "Profils Aside", "integrations.aside.profilesHint": "Choisissez les profils qui recevront les modèles sélectionnés. Le profil actif dans Aside reste inchangé.", "integrations.aside.all": "Synchroniser tous les profils", @@ -1769,6 +1770,7 @@ export const fr: Record = { "integrations.semantics.prime": "Gère uniquement providers.opencodex dans le models.json de Prime Agent — ~/.prime/agent, sauf si PRIME_AGENT_CODING_AGENT_DIR le redirige. Vos autres fournisseurs et surcharges de modèles restent inchangés. S'applique aux nouvelles sessions.", "integrations.semantics.aside": "Gère uniquement providers.opencodex dans le fichier ~/.aside/u//models.json de ce profil. Vos autres fournisseurs restent inchangés. Quittez complètement Aside et relancez-le après application.", "integrations.semantics.raycast": "Ajoute une entrée de fournisseur OpenCodex dans le providers.yaml de Raycast afin que chaque modèle routé apparaisse dans le sélecteur de modèles de Raycast AI. Raycast Pro requis.", + "integrations.semantics.omo": "Gère uniquement providers.opencodex dans le models.json d'omo — ~/.omo/agent, sauf redirection par OMO_CODING_AGENT_DIR, SENPI_CODING_AGENT_DIR ou PI_CODING_AGENT_DIR. Vos autres fournisseurs restent inchangés. S'applique aux nouvelles sessions.", "integrations.raycast.proRequired": "Custom Providers est une fonctionnalité Raycast Pro. Le fichier sera écrit, mais Raycast l'ignore tant qu'un abonnement Pro n'est pas actif.", "integrations.raycast.planUnknown": "Impossible de déterminer si Raycast Pro est actif ; Custom Providers nécessite Raycast Pro.", "integrations.raycast.revealConfig": "Ouvrez Raycast → Réglages → AI et cliquez une fois sur « Reveal Providers Config » pour que le dossier des fournisseurs existe.", @@ -2092,13 +2094,14 @@ export const fr: Record = { "api.clientConfig.clientHermes": "Hermes", "api.clientConfig.clientOpenclaw": "OpenClaw", "api.clientConfig.clientKimi": "Kimi Code", - "api.clientConfig.clientGajae": "Gajae Code", + "api.clientConfig.clientGajae": "gjc", "api.clientConfig.clientDsh": "DeepSeek Harness (DSH)", "api.clientConfig.clientMcode": "MiniMax Code", "api.clientConfig.clientZcode": "ZCode", "api.clientConfig.clientPrime": "Prime Agent", "api.clientConfig.clientAside": "Aside", "api.clientConfig.clientRaycast": "Raycast", + "api.clientConfig.clientOmo": "omo", "api.clientConfig.copy": "Copier la configuration", "api.clientConfig.download": "Télécharger", "api.clientConfig.loading": "Génération de la configuration du client…", diff --git a/gui/src/i18n/ja.ts b/gui/src/i18n/ja.ts index c77edbe2c7..ca358a950b 100644 --- a/gui/src/i18n/ja.ts +++ b/gui/src/i18n/ja.ts @@ -1527,13 +1527,14 @@ export const ja: Record = { "integrations.tab.hermes": "Hermes", "integrations.tab.openclaw": "OpenClaw", "integrations.tab.kimi": "Kimi Code", - "integrations.tab.gajae": "Gajae Code", + "integrations.tab.gajae": "gjc", "integrations.tab.dsh": "DSH", "integrations.tab.mcode": "MiniMax Code", "integrations.tab.zcode": "ZCode", "integrations.tab.prime": "Prime Agent", "integrations.tab.aside": "Aside", "integrations.tab.raycast": "Raycast", + "integrations.tab.omo": "omo", "integrations.aside.profilesTitle": "Asideのプロファイル", "integrations.aside.profilesHint": "選択したモデルを同期するプロファイルを選んでください。Asideで使用中のプロファイルは変わりません。", "integrations.aside.all": "すべてのプロファイルを同期", @@ -1694,6 +1695,7 @@ export const ja: Record = { "integrations.semantics.prime": "Prime Agent の models.json 内の providers.opencodex のみを管理します。場所は ~/.prime/agent ですが、PRIME_AGENT_CODING_AGENT_DIR が設定されている場合はそちらが優先されます。他のプロバイダーとモデルオーバーライドは変更しません。新しいセッションから適用されます。", "integrations.semantics.aside": "このプロファイルの ~/.aside/u//models.json 内の providers.opencodex のみを管理します。他のプロバイダーは変更しません。適用後は Aside を完全に終了してから開き直してください。", "integrations.semantics.raycast": "Raycast の providers.yaml に OpenCodex のプロバイダーエントリを追加し、ルーティングされたすべてのモデルを Raycast AI のモデル選択に表示します。Raycast Pro が必要です。", + "integrations.semantics.omo": "omo の models.json にある providers.opencodex のみを管理します。場所は ~/.omo/agent で、OMO_CODING_AGENT_DIR・SENPI_CODING_AGENT_DIR・PI_CODING_AGENT_DIR のいずれかが設定されている場合はそちらが優先されます。他のプロバイダーは変更しません。新しいセッションから適用されます。", "integrations.raycast.proRequired": "Custom Providers は Raycast Pro の機能です。ファイルは書き込まれますが、Pro サブスクリプションが有効になるまで Raycast はこれを無視します。", "integrations.raycast.planUnknown": "Raycast Pro が有効かどうか確認できませんでした。Custom Providers には Raycast Pro が必要です。", "integrations.raycast.revealConfig": "Raycast → 設定 → AI を開き、「Reveal Providers Config」を一度クリックして providers フォルダを作成してください。", @@ -2024,13 +2026,14 @@ export const ja: Record = { "api.clientConfig.clientHermes": "Hermes", "api.clientConfig.clientOpenclaw": "OpenClaw", "api.clientConfig.clientKimi": "Kimi Code", - "api.clientConfig.clientGajae": "Gajae Code", + "api.clientConfig.clientGajae": "gjc", "api.clientConfig.clientDsh": "DeepSeek Harness (DSH)", "api.clientConfig.clientMcode": "MiniMax Code", "api.clientConfig.clientZcode": "ZCode", "api.clientConfig.clientPrime": "Prime Agent", "api.clientConfig.clientAside": "Aside", "api.clientConfig.clientRaycast": "Raycast", + "api.clientConfig.clientOmo": "omo", "api.clientConfig.copy": "設定をコピー", "api.clientConfig.download": "ダウンロード", "api.clientConfig.loading": "クライアント設定を生成中…", diff --git a/gui/src/i18n/ko.ts b/gui/src/i18n/ko.ts index 34d5ceae87..5eb49f8235 100644 --- a/gui/src/i18n/ko.ts +++ b/gui/src/i18n/ko.ts @@ -1133,13 +1133,14 @@ export const ko: Record = { "integrations.tab.hermes": "Hermes", "integrations.tab.openclaw": "OpenClaw", "integrations.tab.kimi": "Kimi Code", - "integrations.tab.gajae": "Gajae Code", + "integrations.tab.gajae": "gjc", "integrations.tab.dsh": "DSH", "integrations.tab.mcode": "MiniMax Code", "integrations.tab.zcode": "ZCode", "integrations.tab.prime": "Prime Agent", "integrations.tab.aside": "Aside", "integrations.tab.raycast": "Raycast", + "integrations.tab.omo": "omo", "integrations.aside.profilesTitle": "Aside 프로필", "integrations.aside.profilesHint": "선택한 모델을 동기화할 프로필을 고르세요. Aside에서 사용 중인 프로필은 바뀌지 않습니다.", "integrations.aside.all": "모든 프로필 동기화", @@ -1300,6 +1301,7 @@ export const ko: Record = { "integrations.semantics.prime": "Prime Agent의 models.json에서 providers.opencodex만 관리합니다. 위치는 ~/.prime/agent이며 PRIME_AGENT_CODING_AGENT_DIR가 설정되면 그쪽이 우선합니다. 다른 프로바이더와 모델 오버라이드는 변경하지 않습니다. 새 세션부터 적용됩니다.", "integrations.semantics.aside": "이 프로필의 ~/.aside/u//models.json에서 providers.opencodex만 관리합니다. 다른 프로바이더는 그대로 유지됩니다. 적용 후 Aside를 완전히 종료하고 다시 여세요.", "integrations.semantics.raycast": "Raycast의 providers.yaml에 OpenCodex 프로바이더 항목을 추가해 라우팅된 모든 모델이 Raycast AI 모델 선택기에 표시되도록 합니다. Raycast Pro가 필요합니다.", + "integrations.semantics.omo": "omo의 models.json에서 providers.opencodex만 관리합니다. 위치는 ~/.omo/agent이며 OMO_CODING_AGENT_DIR, SENPI_CODING_AGENT_DIR, PI_CODING_AGENT_DIR 중 설정된 값이 있으면 그쪽이 우선합니다. 다른 프로바이더는 그대로 유지됩니다. 새 세션부터 적용됩니다.", "integrations.raycast.proRequired": "Custom Providers는 Raycast Pro 기능입니다. 파일은 기록되지만 Pro 구독이 활성화될 때까지 Raycast는 이를 무시합니다.", "integrations.raycast.planUnknown": "Raycast Pro 활성 여부를 확인할 수 없습니다. Custom Providers에는 Raycast Pro가 필요합니다.", "integrations.raycast.revealConfig": "Raycast → 설정 → AI를 열고 「Reveal Providers Config」를 한 번 클릭해 providers 폴더를 만드세요.", @@ -1628,13 +1630,14 @@ export const ko: Record = { "api.clientConfig.clientHermes": "Hermes", "api.clientConfig.clientOpenclaw": "OpenClaw", "api.clientConfig.clientKimi": "Kimi Code", - "api.clientConfig.clientGajae": "Gajae Code", + "api.clientConfig.clientGajae": "gjc", "api.clientConfig.clientDsh": "DeepSeek Harness (DSH)", "api.clientConfig.clientMcode": "MiniMax Code", "api.clientConfig.clientZcode": "ZCode", "api.clientConfig.clientPrime": "Prime Agent", "api.clientConfig.clientAside": "Aside", "api.clientConfig.clientRaycast": "Raycast", + "api.clientConfig.clientOmo": "omo", "api.clientConfig.copy": "설정 복사", "api.clientConfig.download": "다운로드", "api.clientConfig.loading": "클라이언트 설정 생성 중…", diff --git a/gui/src/i18n/ru.ts b/gui/src/i18n/ru.ts index 488f87d55b..621b3e11a1 100644 --- a/gui/src/i18n/ru.ts +++ b/gui/src/i18n/ru.ts @@ -1597,13 +1597,14 @@ export const ru: Record = { "integrations.tab.hermes": "Hermes", "integrations.tab.openclaw": "OpenClaw", "integrations.tab.kimi": "Kimi Code", - "integrations.tab.gajae": "Gajae Code", + "integrations.tab.gajae": "gjc", "integrations.tab.dsh": "DSH", "integrations.tab.mcode": "MiniMax Code", "integrations.tab.zcode": "ZCode", "integrations.tab.prime": "Prime Agent", "integrations.tab.aside": "Aside", "integrations.tab.raycast": "Raycast", + "integrations.tab.omo": "omo", "integrations.aside.profilesTitle": "Профили Aside", "integrations.aside.profilesHint": "Выберите профили, в которые будут добавлены выбранные модели. Активный профиль Aside не изменится.", "integrations.aside.all": "Синхронизировать все профили", @@ -1764,6 +1765,7 @@ export const ru: Record = { "integrations.semantics.prime": "Управляет только providers.opencodex в models.json Prime Agent — ~/.prime/agent, если PRIME_AGENT_CODING_AGENT_DIR не переопределяет путь. Другие провайдеры и переопределения моделей не меняются. Применяется к новым сессиям.", "integrations.semantics.aside": "Управляет только providers.opencodex в файле ~/.aside/u//models.json этого профиля. Другие провайдеры остаются без изменений. После применения полностью закройте и снова откройте Aside.", "integrations.semantics.raycast": "Добавляет запись провайдера OpenCodex в providers.yaml Raycast, чтобы каждая маршрутизируемая модель появилась в выборе моделей Raycast AI. Требуется Raycast Pro.", + "integrations.semantics.omo": "Управляет только providers.opencodex в models.json omo — ~/.omo/agent, если только OMO_CODING_AGENT_DIR, SENPI_CODING_AGENT_DIR или PI_CODING_AGENT_DIR не перенаправляет путь. Остальные провайдеры остаются без изменений. Применяется к новым сессиям.", "integrations.raycast.proRequired": "Custom Providers — функция Raycast Pro. Файл будет записан, но Raycast игнорирует его, пока не активна подписка Pro.", "integrations.raycast.planUnknown": "Не удалось определить, активен ли Raycast Pro; для Custom Providers требуется Raycast Pro.", "integrations.raycast.revealConfig": "Откройте Raycast → Настройки → AI и один раз нажмите «Reveal Providers Config», чтобы папка провайдеров появилась.", @@ -2094,13 +2096,14 @@ export const ru: Record = { "api.clientConfig.clientHermes": "Hermes", "api.clientConfig.clientOpenclaw": "OpenClaw", "api.clientConfig.clientKimi": "Kimi Code", - "api.clientConfig.clientGajae": "Gajae Code", + "api.clientConfig.clientGajae": "gjc", "api.clientConfig.clientDsh": "DeepSeek Harness (DSH)", "api.clientConfig.clientMcode": "MiniMax Code", "api.clientConfig.clientZcode": "ZCode", "api.clientConfig.clientPrime": "Prime Agent", "api.clientConfig.clientAside": "Aside", "api.clientConfig.clientRaycast": "Raycast", + "api.clientConfig.clientOmo": "omo", "api.clientConfig.copy": "Копировать конфигурацию", "api.clientConfig.download": "Скачать", "api.clientConfig.loading": "Формируется конфигурация клиента…", diff --git a/gui/src/i18n/tr.ts b/gui/src/i18n/tr.ts index 2e46e2792f..2c4f1a57cf 100644 --- a/gui/src/i18n/tr.ts +++ b/gui/src/i18n/tr.ts @@ -1616,13 +1616,14 @@ export const tr: Record = { "integrations.tab.hermes": "Hermes", "integrations.tab.openclaw": "OpenClaw", "integrations.tab.kimi": "Kimi Code", - "integrations.tab.gajae": "Gajae Code", + "integrations.tab.gajae": "gjc", "integrations.tab.dsh": "DSH", "integrations.tab.mcode": "MiniMax Code", "integrations.tab.zcode": "ZCode", "integrations.tab.prime": "Prime Agent", "integrations.tab.aside": "Aside", "integrations.tab.raycast": "Raycast", + "integrations.tab.omo": "omo", "integrations.aside.profilesTitle": "Aside profilleri", "integrations.aside.profilesHint": "Seçili modellerin hangi profillere aktarılacağını seçin. Aside’ın etkin profili değişmez.", "integrations.aside.all": "Tüm profilleri eşitle", @@ -1782,6 +1783,7 @@ export const tr: Record = { "integrations.semantics.prime": "Yalnızca Prime Agent'ın models.json dosyasındaki providers.opencodex bölümünü yönetir — PRIME_AGENT_CODING_AGENT_DIR ayarlı değilse ~/.prime/agent. Diğer sağlayıcılar ve model geçersiz kılmaları değişmez. Yeni oturumlarda geçerli olur.", "integrations.semantics.aside": "Yalnızca bu profilin ~/.aside/u//models.json dosyasındaki providers.opencodex bölümünü yönetir. Diğer sağlayıcılarınız değişmez. Uyguladıktan sonra Aside’ı tamamen kapatıp yeniden açın.", "integrations.semantics.raycast": "Raycast'in providers.yaml dosyasına bir OpenCodex sağlayıcı girdisi ekler; böylece yönlendirilen her model Raycast AI model seçicisinde görünür. Raycast Pro gerekir.", + "integrations.semantics.omo": "Yalnızca omo'nun models.json dosyasındaki providers.opencodex girdisini yönetir — OMO_CODING_AGENT_DIR, SENPI_CODING_AGENT_DIR veya PI_CODING_AGENT_DIR yönlendirmediği sürece ~/.omo/agent. Diğer sağlayıcılarınız değişmeden kalır. Yeni oturumlardan itibaren geçerlidir.", "integrations.raycast.proRequired": "Custom Providers bir Raycast Pro özelliğidir. Dosya yazılır, ancak bir Pro aboneliği etkin olana kadar Raycast bunu yok sayar.", "integrations.raycast.planUnknown": "Raycast Pro’nun etkin olup olmadığı belirlenemedi; Custom Providers için Raycast Pro gerekir.", "integrations.raycast.revealConfig": "Raycast → Ayarlar → AI bölümünü açıp sağlayıcı klasörünün oluşması için „Reveal Providers Config“ seçeneğine bir kez tıklayın.", @@ -2113,13 +2115,14 @@ export const tr: Record = { "api.clientConfig.clientHermes": "Hermes", "api.clientConfig.clientOpenclaw": "OpenClaw", "api.clientConfig.clientKimi": "Kimi Code", - "api.clientConfig.clientGajae": "Gajae Code", + "api.clientConfig.clientGajae": "gjc", "api.clientConfig.clientDsh": "DeepSeek Harness (DSH)", "api.clientConfig.clientMcode": "MiniMax Code", "api.clientConfig.clientZcode": "ZCode", "api.clientConfig.clientPrime": "Prime Agent", "api.clientConfig.clientAside": "Aside", "api.clientConfig.clientRaycast": "Raycast", + "api.clientConfig.clientOmo": "omo", "api.clientConfig.copy": "JSON Kopyala", "api.clientConfig.download": "İndir", "api.clientConfig.loading": "İstemci konfigürasyonu oluşturuluyor…", diff --git a/gui/src/i18n/zh-TW.ts b/gui/src/i18n/zh-TW.ts index b9a26da41c..797011c886 100644 --- a/gui/src/i18n/zh-TW.ts +++ b/gui/src/i18n/zh-TW.ts @@ -2204,13 +2204,14 @@ export const zhTW: Record = { "integrations.tab.hermes": "Hermes", "integrations.tab.openclaw": "OpenClaw", "integrations.tab.kimi": "Kimi Code", - "integrations.tab.gajae": "Gajae Code", + "integrations.tab.gajae": "gjc", "integrations.tab.dsh": "DSH", "integrations.tab.mcode": "MiniMax Code", "integrations.tab.zcode": "ZCode", "integrations.tab.prime": "Prime Agent", "integrations.tab.aside": "Aside", "integrations.tab.raycast": "Raycast", + "integrations.tab.omo": "omo", "integrations.aside.profilesTitle": "Aside 設定檔", "integrations.aside.profilesHint": "選擇要接收所選模型的設定檔。Aside 目前使用的設定檔不會改變。", "integrations.aside.all": "同步所有設定檔", @@ -2371,6 +2372,7 @@ export const zhTW: Record = { "integrations.semantics.prime": "僅管理 Prime Agent 的 models.json 中的 providers.opencodex;預設位於 ~/.prime/agent,若設定 PRIME_AGENT_CODING_AGENT_DIR 則以其為準。不會變更其他供應商或模型覆寫設定。對新工作階段生效。", "integrations.semantics.aside": "僅管理此設定檔的 ~/.aside/u//models.json 中的 providers.opencodex。其他供應商維持不變。套用後請完全結束並重新開啟 Aside。", "integrations.semantics.raycast": "在 Raycast 的 providers.yaml 中新增一個 OpenCodex 供應商項目,讓所有已路由的模型出現在 Raycast AI 模型選擇器中。需要 Raycast Pro。", + "integrations.semantics.omo": "僅管理 omo 的 models.json 中的 providers.opencodex,路徑為 ~/.omo/agent,若設定了 OMO_CODING_AGENT_DIR、SENPI_CODING_AGENT_DIR 或 PI_CODING_AGENT_DIR 則以其為準。你的其他供應商維持不變。對新工作階段生效。", "integrations.raycast.proRequired": "Custom Providers 是 Raycast Pro 功能。檔案會被寫入,但在 Pro 訂閱生效之前 Raycast 會忽略它。", "integrations.raycast.planUnknown": "無法確認 Raycast Pro 是否已啟用;Custom Providers 需要 Raycast Pro。", "integrations.raycast.revealConfig": "開啟 Raycast → 設定 → AI,點一次「Reveal Providers Config」,以便建立 providers 資料夾。", @@ -2409,13 +2411,14 @@ export const zhTW: Record = { "api.clientConfig.clientHermes": "Hermes", "api.clientConfig.clientOpenclaw": "OpenClaw", "api.clientConfig.clientKimi": "Kimi Code", - "api.clientConfig.clientGajae": "Gajae Code", + "api.clientConfig.clientGajae": "gjc", "api.clientConfig.clientDsh": "DeepSeek Harness (DSH)", "api.clientConfig.clientMcode": "MiniMax Code", "api.clientConfig.clientZcode": "ZCode", "api.clientConfig.clientPrime": "Prime Agent", "api.clientConfig.clientAside": "Aside", "api.clientConfig.clientRaycast": "Raycast", + "api.clientConfig.clientOmo": "omo", "cws.tabsLabel": "Combo 詳細區段", "cws.field.nativeAlias": "原生 OpenAI 別名", "cws.field.nativeAliasHint": "讓此 combo 擁有受支援的未限定原生 OpenAI 模型 ID。帶有帳號或供應商限定的 OpenAI 路由仍保持獨立。", diff --git a/gui/src/i18n/zh.ts b/gui/src/i18n/zh.ts index 46866680b0..3bdf1e7630 100644 --- a/gui/src/i18n/zh.ts +++ b/gui/src/i18n/zh.ts @@ -1114,13 +1114,14 @@ export const zh: Record = { "integrations.tab.hermes": "Hermes", "integrations.tab.openclaw": "OpenClaw", "integrations.tab.kimi": "Kimi Code", - "integrations.tab.gajae": "Gajae Code", + "integrations.tab.gajae": "gjc", "integrations.tab.dsh": "DSH", "integrations.tab.mcode": "MiniMax Code", "integrations.tab.zcode": "ZCode", "integrations.tab.prime": "Prime Agent", "integrations.tab.aside": "Aside", "integrations.tab.raycast": "Raycast", + "integrations.tab.omo": "omo", "integrations.aside.profilesTitle": "Aside 配置文件", "integrations.aside.profilesHint": "选择要接收所选模型的配置文件。Aside 当前使用的配置文件不会改变。", "integrations.aside.all": "同步所有配置文件", @@ -1281,6 +1282,7 @@ export const zh: Record = { "integrations.semantics.prime": "仅管理 Prime Agent 的 models.json 中的 providers.opencodex;默认位于 ~/.prime/agent,若设置 PRIME_AGENT_CODING_AGENT_DIR 则以其为准。不会更改其他提供商或模型覆盖设置。对新会话生效。", "integrations.semantics.aside": "仅管理此配置文件的 ~/.aside/u//models.json 中的 providers.opencodex。其他提供商保持不变。应用后请完全退出并重新打开 Aside。", "integrations.semantics.raycast": "在 Raycast 的 providers.yaml 中添加一个 OpenCodex 提供商条目,让所有已路由的模型出现在 Raycast AI 模型选择器中。需要 Raycast Pro。", + "integrations.semantics.omo": "仅管理 omo 的 models.json 中的 providers.opencodex,路径为 ~/.omo/agent,若设置了 OMO_CODING_AGENT_DIR、SENPI_CODING_AGENT_DIR 或 PI_CODING_AGENT_DIR 则以其为准。你的其他提供商保持不变。对新会话生效。", "integrations.raycast.proRequired": "Custom Providers 是 Raycast Pro 功能。文件会被写入,但在 Pro 订阅生效之前 Raycast 会忽略它。", "integrations.raycast.planUnknown": "无法确定 Raycast Pro 是否已激活;Custom Providers 需要 Raycast Pro。", "integrations.raycast.revealConfig": "打开 Raycast → 设置 → AI,点击一次“Reveal Providers Config”,以便创建 providers 文件夹。", @@ -1609,13 +1611,14 @@ export const zh: Record = { "api.clientConfig.clientHermes": "Hermes", "api.clientConfig.clientOpenclaw": "OpenClaw", "api.clientConfig.clientKimi": "Kimi Code", - "api.clientConfig.clientGajae": "Gajae Code", + "api.clientConfig.clientGajae": "gjc", "api.clientConfig.clientDsh": "DeepSeek Harness (DSH)", "api.clientConfig.clientMcode": "MiniMax Code", "api.clientConfig.clientZcode": "ZCode", "api.clientConfig.clientPrime": "Prime Agent", "api.clientConfig.clientAside": "Aside", "api.clientConfig.clientRaycast": "Raycast", + "api.clientConfig.clientOmo": "omo", "api.clientConfig.copy": "复制配置", "api.clientConfig.download": "下载", "api.clientConfig.loading": "正在生成客户端配置…", diff --git a/gui/src/pages/integrations/FileIntegrationPage.tsx b/gui/src/pages/integrations/FileIntegrationPage.tsx index 2eef2c5cf0..52e1c97733 100644 --- a/gui/src/pages/integrations/FileIntegrationPage.tsx +++ b/gui/src/pages/integrations/FileIntegrationPage.tsx @@ -59,6 +59,7 @@ const SEMANTICS_KEY: Record = { prime: "integrations.semantics.prime", aside: "integrations.semantics.aside", raycast: "integrations.semantics.raycast", + omo: "integrations.semantics.omo", }; const TAB_LABEL_KEY: Record = { @@ -75,6 +76,7 @@ const TAB_LABEL_KEY: Record = { prime: "integrations.tab.prime", aside: "integrations.tab.aside", raycast: "integrations.tab.raycast", + omo: "integrations.tab.omo", }; export default function FileIntegrationPage({ diff --git a/gui/src/pages/integrations/integration-api.ts b/gui/src/pages/integrations/integration-api.ts index 85ffdc7be4..bd72c61dfe 100644 --- a/gui/src/pages/integrations/integration-api.ts +++ b/gui/src/pages/integrations/integration-api.ts @@ -15,6 +15,7 @@ export const FILE_INTEGRATION_CLIENTS = [ "prime", "aside", "raycast", + "omo", ] as const; export type FileIntegrationClientId = (typeof FILE_INTEGRATION_CLIENTS)[number]; diff --git a/gui/src/pages/integrations/integration-tabs.ts b/gui/src/pages/integrations/integration-tabs.ts index 99502bde87..b37ceab85f 100644 --- a/gui/src/pages/integrations/integration-tabs.ts +++ b/gui/src/pages/integrations/integration-tabs.ts @@ -47,6 +47,7 @@ export const TABS: readonly TabDefinition[] = [ { id: "prime", hash: "integrations/prime", labelKey: "integrations.tab.prime" }, { id: "aside", hash: "integrations/aside", labelKey: "integrations.tab.aside" }, { id: "raycast", hash: "integrations/raycast", labelKey: "integrations.tab.raycast" }, + { id: "omo", hash: "integrations/omo", labelKey: "integrations.tab.omo" }, ] as const; export const FILE_CLIENTS = new Set([ @@ -63,4 +64,5 @@ export const FILE_CLIENTS = new Set([ "prime", "aside", "raycast", + "omo", ]); diff --git a/gui/src/pages/integrations/overview-clients.ts b/gui/src/pages/integrations/overview-clients.ts index 7932cf5648..2d4770f3d5 100644 --- a/gui/src/pages/integrations/overview-clients.ts +++ b/gui/src/pages/integrations/overview-clients.ts @@ -153,6 +153,7 @@ const FILE_LABEL_KEY: Record = { prime: "integrations.tab.prime", aside: "integrations.tab.aside", raycast: "integrations.tab.raycast", + omo: "integrations.tab.omo", }; /** A file client's block is in the file for both `current` and `stale`. */ diff --git a/gui/tests/client-config-panel.test.tsx b/gui/tests/client-config-panel.test.tsx index ea8210e7e4..37f2df193b 100644 --- a/gui/tests/client-config-panel.test.tsx +++ b/gui/tests/client-config-panel.test.tsx @@ -170,12 +170,13 @@ function rowButton(container: HTMLElement, name: string, label: string): HTMLBut .find(el => el.textContent?.trim() === label)!; } -test("the API download surface includes DSH, MiniMax Code, Aside and Raycast as clients", () => { - expect(CLIENTS).toEqual(["opencode", "pi", "omp", "hermes", "openclaw", "kimi", "gajae", "dsh", "mcode", "zcode", "prime", "aside", "raycast"]); +test("the API download surface includes DSH, MiniMax Code, Aside, Raycast and omo as clients", () => { + expect(CLIENTS).toEqual(["opencode", "pi", "omp", "hermes", "openclaw", "kimi", "gajae", "dsh", "mcode", "zcode", "prime", "aside", "raycast", "omo"]); expect(CLIENT_LABEL_KEYS.dsh).toBe("api.clientConfig.clientDsh"); expect(CLIENT_LABEL_KEYS.mcode).toBe("api.clientConfig.clientMcode"); expect(CLIENT_LABEL_KEYS.zcode).toBe("api.clientConfig.clientZcode"); expect(CLIENT_LABEL_KEYS.aside).toBe("api.clientConfig.clientAside"); + expect(CLIENT_LABEL_KEYS.omo).toBe("api.clientConfig.clientOmo"); }); test("each row fetches its own client and its dialog renders that client's exact bytes", async () => { diff --git a/gui/tests/fr-localization.test.ts b/gui/tests/fr-localization.test.ts index 87250bb74b..8a1f34390f 100644 --- a/gui/tests/fr-localization.test.ts +++ b/gui/tests/fr-localization.test.ts @@ -121,6 +121,8 @@ const INTENTIONAL_ENGLISH = new Set([ "api.clientConfig.clientAside", "integrations.tab.raycast", "api.clientConfig.clientRaycast", + "integrations.tab.omo", + "api.clientConfig.clientOmo", "models.reasoningEffort.minimal", "models.reasoningEffort.max", "pws.pacingRpmUnit", diff --git a/gui/tests/integrations-api.test.ts b/gui/tests/integrations-api.test.ts index eea7dcfa0c..7b540be78f 100644 --- a/gui/tests/integrations-api.test.ts +++ b/gui/tests/integrations-api.test.ts @@ -16,9 +16,9 @@ import { const originalFetch = globalThis.fetch; -test("DSH, Aside and Raycast are file integration clients", () => { +test("DSH, Aside, Raycast and omo are file integration clients", () => { expect(FILE_INTEGRATION_CLIENTS).toEqual([ - "opencode", "pi", "omp", "hermes", "openclaw", "kimi", "gajae", "dsh", "mcode", "zcode", "prime", "aside", "raycast", + "opencode", "pi", "omp", "hermes", "openclaw", "kimi", "gajae", "dsh", "mcode", "zcode", "prime", "aside", "raycast", "omo", ]); }); diff --git a/gui/tests/integrations-overview-rows.test.ts b/gui/tests/integrations-overview-rows.test.ts index 54809a4422..777ae3e8ec 100644 --- a/gui/tests/integrations-overview-rows.test.ts +++ b/gui/tests/integrations-overview-rows.test.ts @@ -290,7 +290,7 @@ test("every client counts toward the summary, not just the file clients", () => test("an unsettled file list renders unknown rows instead of dropping them", () => { const built = buildOverviewRows(sources({ clients: [], clientsSettled: false })); - expect(built.rows).toHaveLength(18); + expect(built.rows).toHaveLength(19); expect(rowById(built, "omp").state).toBe("unknown"); expect(rowById(built, "mcode").state).toBe("unknown"); expect(rowById(built, "zcode").state).toBe("unknown"); diff --git a/gui/tests/locale-parity.test.ts b/gui/tests/locale-parity.test.ts index 9754b98051..883827baa7 100644 --- a/gui/tests/locale-parity.test.ts +++ b/gui/tests/locale-parity.test.ts @@ -132,6 +132,9 @@ const ZH_TW_KEEP_ENGLISH: ReadonlySet = new Set([ "api.clientConfig.clientAside", "integrations.tab.raycast", "api.clientConfig.clientRaycast", + // "omo" is the product's own lowercase spelling, identical in every locale. + "integrations.tab.omo", + "api.clientConfig.clientOmo", "integrations.codex.title", // Provider proper nouns kept in English "provider.name.commandCodeAuth", diff --git a/scripts/test-layout/layout.json b/scripts/test-layout/layout.json index 7b6e068d60..8ade65ef45 100644 --- a/scripts/test-layout/layout.json +++ b/scripts/test-layout/layout.json @@ -926,6 +926,7 @@ "ollama-show-enrichment-v7.test.ts": "providers/ollama", "ollama-show-enrichment.test.ts": "providers/ollama", "ollama-show-ignore-abort.test.ts": "providers/ollama", + "omo-client.test.ts": "clients", "omp-path-contract.test.ts": "clients", "omp-yaml-source-inline-comments.test.ts": "clients", "openai-api-virtual-models.test.ts": "adapters/openai", diff --git a/src/cli/dispatch.ts b/src/cli/dispatch.ts index 2cd69d59ed..94e6fe7b3f 100644 --- a/src/cli/dispatch.ts +++ b/src/cli/dispatch.ts @@ -452,7 +452,7 @@ const commandRunners: Record = { }, config, port: live.port, - }, ["mcode", "pi", "raycast"])); + }, ["mcode", "pi", "raycast", "omo"])); } catch (error) { console.warn(`Client integrations were not refreshed: ${error instanceof Error ? error.message : String(error)}`); } diff --git a/src/cli/help.ts b/src/cli/help.ts index c8f71cdbbd..70d53f9eb2 100644 --- a/src/cli/help.ts +++ b/src/cli/help.ts @@ -81,7 +81,7 @@ Usage: ocx memory [--json] Alias of ocx observe memory ocx api-key Alias of ocx access key ocx access External API keys and endpoint information - ocx export --client Print a client config wired to the running proxy (13 clients) + ocx export --client Print a client config wired to the running proxy (14 clients) ocx integration client Enable, disable, inspect or roll back a client integration ocx grok Grok Build model selection and apply ocx system Runtime settings, startup, sync, OpenCodex updates, and Codex CLI inspection diff --git a/src/cli/registry.ts b/src/cli/registry.ts index 929aa7f1fe..6a693780b8 100644 --- a/src/cli/registry.ts +++ b/src/cli/registry.ts @@ -336,8 +336,8 @@ export const CLI_COMMANDS: CliCommandEntry[] = [ { name: "api-key", usage: "ocx api-key ...", summary: "Alias of ocx access key." }, { name: "export", - usage: "ocx export --client [--json] [--out ] [--force]", - summary: "Print a client config (OpenCode, Pi, OMP, Hermes, OpenClaw, Kimi Code, Gajae Code, DeepSeek Harness, MiniMax Code, ZCode, Prime Agent, Aside, Raycast) wired to the running proxy.", + usage: "ocx export --client [--json] [--out ] [--force]", + summary: "Print a client config (OpenCode, Pi, OMP, Hermes, OpenClaw, Kimi Code, gjc, DeepSeek Harness, MiniMax Code, ZCode, Prime Agent, Aside, Raycast, omo) wired to the running proxy.", details: [ "--json prints the generated document as JSON on stdout; use --out for the client's native format.", "--out writes the native config there and refuses to replace an existing file without --force.", diff --git a/src/clients/config-export.ts b/src/clients/config-export.ts index 6a94b74d70..8551f190a9 100644 --- a/src/clients/config-export.ts +++ b/src/clients/config-export.ts @@ -458,6 +458,51 @@ export function primeConfigPath(env: OpencodeLaunchEnv = process.env, home: stri return join(primeAgentDir(env, home), "models.json"); } +/** + * omo resolves its agent directory from THREE variables, in its own order: + * `OMO_CODING_AGENT_DIR`, then `SENPI_CODING_AGENT_DIR`, then + * `PI_CODING_AGENT_DIR`, falling back to `~/.omo/agent`. That is not an + * inference from the family resemblance — `bin/lib/agent-dir.js` publishes the + * list as `AGENT_DIR_ENV_NAMES` and the launcher pins the first two to whatever + * it resolves before spawning senpi, so the engine can never disagree with it. + * + * The order is load-bearing rather than cosmetic. A user with both + * `PI_CODING_AGENT_DIR` and `OMO_CODING_AGENT_DIR` set runs omo out of the omo + * one; checking Pi's first would have us write a catalog omo never reads. + * + * Each variable reports under its OWN name, because telling someone that + * `PI_CODING_AGENT_DIR` is relative when they set `OMO_CODING_AGENT_DIR` sends + * them to the wrong line of their shell profile. An empty or whitespace value + * falls through to the next name, which is what omo's own `.trim()`-then-test + * loop does. + * + * One divergence is deliberate: omo `resolve()`s a relative override against its + * own cwd and does not expand `~`. We refuse the relative form and do expand + * `~`, exactly as Pi, Prime, MCode and ZCode already do, because a background + * proxy and a foreground client have different working directories and would + * otherwise disagree about which file is named. + * + * Consequence worth knowing: a user who sets only `PI_CODING_AGENT_DIR` has Pi + * and omo reading ONE `models.json`. Both clients emit the same provider block + * through the same builder so the bytes agree; what cannot be shared is the + * ownership record, since two enabled clients would claim one file. That is + * omo's contract, not ours to paper over. + */ +export function omoAgentDir(env: OpencodeLaunchEnv = process.env, home: string = homedir()): string { + const omo = env.OMO_CODING_AGENT_DIR?.trim(); + if (omo) return absoluteClientPath(omo, home, "OMO_CODING_AGENT_DIR"); + const senpi = env.SENPI_CODING_AGENT_DIR?.trim(); + if (senpi) return absoluteClientPath(senpi, home, "SENPI_CODING_AGENT_DIR"); + const pi = env.PI_CODING_AGENT_DIR?.trim(); + if (pi) return absoluteClientPath(pi, home, "PI_CODING_AGENT_DIR"); + return join(home, ".omo", "agent"); +} + +/** omo's canonical custom-provider catalog, read by the senpi engine it wraps. */ +export function omoConfigPath(env: OpencodeLaunchEnv = process.env, home: string = homedir()): string { + return join(omoAgentDir(env, home), "models.json"); +} + /** * Aside's state root. Unlike every other client here, Aside ships NO variable * that relocates it: its CLI carries `ASIDE_DAEMON_BASE_URL`, @@ -1109,6 +1154,26 @@ function buildAsideContribution(ctx: ExportContext): ManagedContribution { return singleFragment("aside", ["providers", OPENCODE_PROVIDER_ID], doc.providers[OPENCODE_PROVIDER_ID]); } +/** + * omo is the Pi document again, and this time the engine was checked rather + * than inferred: `omo-ai@beta` is a launcher around `@code-yeongyu/senpi`, and + * senpi's compiled validator accepts what `buildPiClientConfig` emits verbatim — + * the keyed `providers`, the `models` ARRAY whose identity is `id`, the + * `openai-completions` dialect, the loopback placeholder, and the + * `thinkingLevelMap` levels. Evidence: + * `devlog/_plan/260912_omo_client_integration/001_omo_contract.md`. + * + * The flag is passed HERE as well as in the spec's `build`, which is the one + * thing Prime and Aside do not do. They pass the default on both paths, so they + * are consistent; passing it on only one would make `ocx export --client omo` + * emit a `compat` block while enable and refresh wrote a file without it, and + * the two would drift apart at the first refresh. + */ +function buildOmoContribution(ctx: ExportContext): ManagedContribution { + const doc = buildPiClientConfig(ctx, true); + return singleFragment("omo", ["providers", OPENCODE_PROVIDER_ID], doc.providers[OPENCODE_PROVIDER_ID]); +} + export const EXPORT_CLIENTS: Record = { opencode: { id: "opencode", @@ -1297,6 +1362,37 @@ export const EXPORT_CLIENTS: Record = { // remote bind would be a plaintext secret on disk. Refuse instead. loopbackOnly: true, }, + /* + * Appended rather than filed beside the other Pi-family clients on purpose. + * `EXPORT_CLIENT_IDS` is `Object.keys(EXPORT_CLIENTS)`, so this object's + * insertion order IS the public order, and three tests assert it exactly. The + * existing sequence is landing order — `pi` second, `prime` eleventh — not a + * grouping, so appending is the edit that leaves the other thirteen alone. + */ + omo: { + id: "omo", + // Not a bare `models.json`: same Downloads-folder collision argument as + // `prime-models.json` and `aside-models.json`. + filename: "omo-models.json", + destination: env => omoConfigPath(env), + apiKeyEnv: "", + exportHint: "omo reads a non-secret placeholder from models.json; loopback needs no key.", + build: ctx => buildPiClientConfig(ctx, true), + format: "json", + summarize: summarizePi, + buildContribution: buildOmoContribution, + /* + * Loopback-only for OMP's and Prime's reason, NOT Pi's and Aside's. senpi's + * provider block does accept a `headers` map and does interpolate `$ENV` in + * its values, so unlike Aside there is somewhere the dedicated admission + * header could live. What does not exist is a builder that emits one: + * `buildPiClientConfig` writes no headers at all, which is why `pi` is + * loopback-only too. Teaching the shared builder to emit them would change + * four clients at once, so remote wiring is deferred and a non-loopback bind + * refuses rather than generating a config that 401s. + */ + loopbackOnly: true, + }, }; export const EXPORT_CLIENT_IDS: readonly ExportClientId[] = Object.keys(EXPORT_CLIENTS) as ExportClientId[]; diff --git a/src/clients/config-export/contracts.ts b/src/clients/config-export/contracts.ts index c888a4c257..dc33732fe2 100644 --- a/src/clients/config-export/contracts.ts +++ b/src/clients/config-export/contracts.ts @@ -94,7 +94,8 @@ export type ExportClientId = | "zcode" | "prime" | "aside" - | "raycast"; + | "raycast" + | "omo"; export interface ExportClientSpec { id: ExportClientId; diff --git a/src/integrations/catalog-refresh.ts b/src/integrations/catalog-refresh.ts index 8b89762f30..45e9b7a97b 100644 --- a/src/integrations/catalog-refresh.ts +++ b/src/integrations/catalog-refresh.ts @@ -10,7 +10,7 @@ import { /** Refresh only previously connected clients; a refused file never blocks its peers. */ export async function refreshOwnedCatalogIntegrations( input: Omit, - clientIds: readonly IntegrationClientId[] = ["pi", "aside", "raycast"], + clientIds: readonly IntegrationClientId[] = ["pi", "aside", "raycast", "omo"], ): Promise { let models: Promise | undefined; const loadModels = () => models ??= Promise.resolve().then(() => diff --git a/src/integrations/registry.ts b/src/integrations/registry.ts index 8d67ac83a1..493a347819 100644 --- a/src/integrations/registry.ts +++ b/src/integrations/registry.ts @@ -26,6 +26,8 @@ import { kimiHomeDir, mcodeConfigPath, mcodeHomeDir, + omoAgentDir, + omoConfigPath, ompAgentDir, ompModelsConfigPath, opencodeGlobalConfigPath, @@ -280,6 +282,23 @@ export const INTEGRATION_CLIENTS: Record raycastAiDir(env, home), }, + omo: { + id: "omo", + configPath: (env = process.env, home = homedir()) => omoConfigPath(env, home), + /* + * The AGENT directory, not `~/.omo`. The v4 launcher wrapper creates + * `~/.omo` to hold `binary-runtime` without ever creating `agent/`, so + * detecting on the parent reports an omo v5 install that is not there -- + * and `installed` is what stops apply from writing a catalog for an engine + * that will never read it. Prime's agent directory and Aside's account + * directory are the same shape; Pi's parent-directory check is the odd one. + * + * No `sourcePreservingYaml` (JSON), no `writerLock` (single writer), and no + * `resolvePaths` -- unlike Aside, both omo paths are a pure function of env + * and home, so reading them in sequence cannot straddle a state change. + */ + detectDir: (env = process.env, home = homedir()) => omoAgentDir(env, home), + }, }; export const INTEGRATION_CLIENT_IDS: readonly IntegrationClientId[] = diff --git a/src/server/management/config-routes.ts b/src/server/management/config-routes.ts index 1faa056c8d..dfc369102c 100644 --- a/src/server/management/config-routes.ts +++ b/src/server/management/config-routes.ts @@ -237,7 +237,7 @@ export async function syncEnabledClientIntegrations( }, config, port, - }, ["mcode", "pi", "aside", "raycast"])); + }, ["mcode", "pi", "aside", "raycast", "omo"])); return out; } diff --git a/tests/clients/integrations-state.test.ts b/tests/clients/integrations-state.test.ts index b2d4f530cc..6b49ba79ee 100644 --- a/tests/clients/integrations-state.test.ts +++ b/tests/clients/integrations-state.test.ts @@ -794,9 +794,9 @@ describe("installation detection is independent of config state", () => { * from. Rationale and the per-client table: 020 §1 amendment. */ describe("the loopback-only set is one fact, read through one seam", () => { - test("omp, pi, kimi, gajae, dsh, mcode, zcode, prime, aside and raycast are loopback-only and nobody else is", () => { + test("omp, pi, kimi, gajae, dsh, mcode, zcode, prime, aside, raycast and omo are loopback-only and nobody else is", () => { const loopbackOnly = INTEGRATION_CLIENT_IDS.filter(id => isLoopbackOnly(id)); - expect(loopbackOnly).toEqual(["pi", "omp", "kimi", "gajae", "dsh", "mcode", "zcode", "prime", "aside", "raycast"]); + expect(loopbackOnly).toEqual(["pi", "omp", "kimi", "gajae", "dsh", "mcode", "zcode", "prime", "aside", "raycast", "omo"]); }); test("the registry restates nothing — it reads the export spec", () => { diff --git a/tests/clients/omo-client.test.ts b/tests/clients/omo-client.test.ts new file mode 100644 index 0000000000..aac1cffbcb --- /dev/null +++ b/tests/clients/omo-client.test.ts @@ -0,0 +1,161 @@ +import { describe, expect, test } from "bun:test"; +import { join } from "node:path"; +import { + ClientPathError, + EXPORT_CLIENTS, + LOOPBACK_API_KEY_PLACEHOLDER, + OPENCODE_PROVIDER_ID, + buildClientConfig, + buildClientConfigText, + buildClientContribution, + omoAgentDir, + omoConfigPath, + type ExportContext, + type PiGeneratedConfig, +} from "../../src/clients/config-export"; +import { INTEGRATION_CLIENTS } from "../../src/integrations/registry"; +import type { OcxConfig } from "../../src/types"; + +const CONFIG = { + port: 10100, + hostname: "127.0.0.1", + defaultProvider: "mock", + providers: { mock: { adapter: "openai-chat", baseUrl: "http://127.0.0.1/v1" } }, +} as OcxConfig; + +function context(): ExportContext { + return { + baseUrl: "http://127.0.0.1:10100/v1", + config: CONFIG, + models: [ + { namespaced: "anthropic/claude-opus-5", provider: "anthropic", id: "claude-opus-5", contextWindow: 200_000, inputModalities: ["text", "image"] }, + { namespaced: "openai/gpt-5.6-sol", provider: "openai", id: "gpt-5.6-sol", contextWindow: 922_000, reasoningEfforts: ["low", "medium", "high"] }, + // No authoritative context window: ships without limits rather than guessing. + { namespaced: "mystery/model", provider: "mystery", id: "model" }, + ], + }; +} + +describe("omo client config", () => { + /** + * The opposite of Prime's assertion, deliberately. + * + * Prime and Aside reuse Pi's builder with the session-affinity flag left at + * its default, because nobody has verified that their engines read it. omo's + * engine WAS verified: senpi's compiled validator accepts `compat` with + * `sendSessionAffinityHeaders`, so omo opts in and the generated provider is + * byte-identical to Pi's. + */ + test("is Pi's document including the session-affinity opt-in", () => { + const omo = buildClientConfig("omo", context()) as PiGeneratedConfig; + const pi = buildClientConfig("pi", context()) as PiGeneratedConfig; + expect(omo).toEqual(pi); + expect(omo.providers[OPENCODE_PROVIDER_ID]!.compat).toEqual({ sendSessionAffinityHeaders: true }); + }); + + /** + * The flag has to be on BOTH paths or they drift: `build` feeds `ocx export` + * and `/api/client-config`, while `buildContribution` is what the writer + * actually puts on disk when the integration is enabled or refreshed. One + * carrying `compat` and the other not would look correct in every unit test + * that only reads one of them. + */ + test("the exported document and the written fragment are the same bytes", () => { + const document = buildClientConfig("omo", context()) as PiGeneratedConfig; + expect(buildClientContribution("omo", context()).fragments[0]!.value) + .toEqual(document.providers[OPENCODE_PROVIDER_ID]); + }); + + test("adds only providers.opencodex, wired to the loopback proxy", () => { + const document = buildClientConfig("omo", context()) as PiGeneratedConfig; + expect(Object.keys(document)).toEqual(["providers"]); + expect(Object.keys(document.providers)).toEqual([OPENCODE_PROVIDER_ID]); + const provider = document.providers[OPENCODE_PROVIDER_ID]!; + expect(provider.baseUrl).toBe("http://127.0.0.1:10100/v1"); + expect(provider.api).toBe("openai-completions"); + expect(provider.apiKey).toBe(LOOPBACK_API_KEY_PLACEHOLDER); + }); + + test("native JSON round-trips and never carries a credential", () => { + const sentinel = ["sk", "live", "omo", "sentinel"].join("-"); + const withKey = { ...CONFIG, apiKeys: [{ key: sentinel }] } as OcxConfig; + const built = buildClientConfigText("omo", { ...context(), config: withKey }); + expect(JSON.parse(built.text)).toEqual(built.document as never); + expect(built.text).not.toContain(sentinel); + expect(built.text).toContain(LOOPBACK_API_KEY_PLACEHOLDER); + }); + + test("the contribution owns exactly the providers.opencodex path under its own id", () => { + const contribution = buildClientContribution("omo", context()); + // Reusing Pi's builder must not leak Pi's id into the ownership record, or + // the writer would stamp one client's block with the other's name. + expect(contribution.clientId).toBe("omo"); + expect(contribution.fragments.map(f => f.path)).toEqual([["providers", OPENCODE_PROVIDER_ID]]); + }); + + /** + * omo publishes three variables and reads them in this order, then pins the + * first two for the senpi process it spawns. Checking them in any other order + * would write a catalog omo never opens. + */ + test("resolves omo's own three-variable precedence", () => { + expect(omoAgentDir({}, "/home/u")).toBe(join("/home/u", ".omo", "agent")); + expect(omoConfigPath({}, "/home/u")).toBe(join("/home/u", ".omo", "agent", "models.json")); + + expect(omoConfigPath({ OMO_CODING_AGENT_DIR: "/from-omo" }, "/home/u")).toBe(join("/from-omo", "models.json")); + expect(omoConfigPath({ SENPI_CODING_AGENT_DIR: "/from-senpi" }, "/home/u")).toBe(join("/from-senpi", "models.json")); + expect(omoConfigPath({ PI_CODING_AGENT_DIR: "/from-pi" }, "/home/u")).toBe(join("/from-pi", "models.json")); + + // Precedence, not merely recognition. + expect(omoConfigPath({ + OMO_CODING_AGENT_DIR: "/from-omo", + SENPI_CODING_AGENT_DIR: "/from-senpi", + PI_CODING_AGENT_DIR: "/from-pi", + }, "/home/u")).toBe(join("/from-omo", "models.json")); + expect(omoConfigPath({ + SENPI_CODING_AGENT_DIR: "/from-senpi", + PI_CODING_AGENT_DIR: "/from-pi", + }, "/home/u")).toBe(join("/from-senpi", "models.json")); + + // omo trims and then tests truthiness, so a blank value is not a path. + expect(omoConfigPath({ OMO_CODING_AGENT_DIR: " ", PI_CODING_AGENT_DIR: "/from-pi" }, "/home/u")) + .toBe(join("/from-pi", "models.json")); + expect(omoConfigPath({ OMO_CODING_AGENT_DIR: "" }, "/home/u")) + .toBe(join("/home/u", ".omo", "agent", "models.json")); + + expect(omoConfigPath({ OMO_CODING_AGENT_DIR: "~/alt" }, "/home/u")).toBe(join("/home/u", "alt", "models.json")); + }); + + /** + * Each variable reports under its own name. Telling someone their + * `PI_CODING_AGENT_DIR` is relative when they set `OMO_CODING_AGENT_DIR` + * sends them to the wrong line of their shell profile. + */ + test("refuses a relative override and names the variable that carried it", () => { + expect(() => omoConfigPath({ OMO_CODING_AGENT_DIR: "relative" }, "/home/u")).toThrow(ClientPathError); + expect(() => omoConfigPath({ OMO_CODING_AGENT_DIR: "relative" }, "/home/u")).toThrow(/OMO_CODING_AGENT_DIR/); + expect(() => omoConfigPath({ SENPI_CODING_AGENT_DIR: "relative" }, "/home/u")).toThrow(/SENPI_CODING_AGENT_DIR/); + expect(() => omoConfigPath({ PI_CODING_AGENT_DIR: "relative" }, "/home/u")).toThrow(/PI_CODING_AGENT_DIR/); + }); + + /** + * The AGENT directory, not `~/.omo`. The v4 launcher wrapper creates + * `~/.omo` for its `binary-runtime` without ever creating `agent/`, so + * detecting on the parent reports a v5 install that is not there. + */ + test("detects installation by the agent directory, not the brand directory", () => { + const spec = INTEGRATION_CLIENTS.omo; + expect(spec.detectDir({}, "/home/u")).toBe(join("/home/u", ".omo", "agent")); + expect(spec.detectDir({}, "/home/u")).not.toBe(join("/home/u", ".omo")); + expect(spec.detectDir({ OMO_CODING_AGENT_DIR: "/elsewhere" } as NodeJS.ProcessEnv, "/home/u")).toBe("/elsewhere"); + }); + + test("ships as a loopback-only integration with no env var to export", () => { + const spec = EXPORT_CLIENTS.omo; + expect(spec.loopbackOnly).toBe(true); + expect(spec.apiKeyEnv).toBe(""); + // Not a bare models.json: a download would collide with pi's, prime's and + // aside's in the user's Downloads folder. + expect(spec.filename).toBe("omo-models.json"); + }); +}); diff --git a/tests/clients/sync-client-integrations.test.ts b/tests/clients/sync-client-integrations.test.ts index e051afb2f4..3a8cd9123d 100644 --- a/tests/clients/sync-client-integrations.test.ts +++ b/tests/clients/sync-client-integrations.test.ts @@ -65,7 +65,7 @@ describe("ocx sync fans out to enabled native clients and owned file integration expect(fn).toContain("grokIntegrationEnabled(config)"); expect(fn).toContain("claudeDesktopIntegrationEnabled(config)"); - expect(fn).toContain('["mcode", "pi", "aside", "raycast"]'); + expect(fn).toContain('["mcode", "pi", "aside", "raycast", "omo"]'); expect(fn).toContain("refreshOwnedCatalogIntegrations"); // Native clients keep their catches; the owned catalog helper isolates file clients. expect(fn.match(/catch \(error\)/g)?.length).toBe(2); @@ -651,12 +651,12 @@ describe("owned Pi/Aside catalogs follow filtered model selections", () => { }); }); -test("the direct ocx sync command refreshes MCode, Pi, Raycast and server-owned Aside", async () => { +test("the direct ocx sync command refreshes MCode, Pi, Raycast, omo and server-owned Aside", async () => { const src = await Bun.file(new URL("../../src/cli/dispatch.ts", import.meta.url)).text(); const start = src.indexOf("sync: async deps =>"); const command = src.slice(start, src.indexOf("v2: async deps =>", start)); expect(command).toContain("refreshOwnedCatalogIntegrations"); - expect(command).toContain('["mcode", "pi", "raycast"]'); + expect(command).toContain('["mcode", "pi", "raycast", "omo"]'); expect(command).toContain("refreshAsideProfilesThroughServer"); expect(command.indexOf("syncModelsToCodex")).toBeLessThan(command.indexOf("refreshOwnedCatalogIntegrations")); expect(command).toContain('synced.status !== "refused"'); diff --git a/tests/config/client-config-export-new-clients.test.ts b/tests/config/client-config-export-new-clients.test.ts index 5a381d6237..025718dfb4 100644 --- a/tests/config/client-config-export-new-clients.test.ts +++ b/tests/config/client-config-export-new-clients.test.ts @@ -61,11 +61,11 @@ describe("no secret reaches a client config", () => { // Pi, Kimi, Gajae, Aside and Raycast cannot emit the dedicated admission // header -- Aside's observed provider block has four keys and none is // `headers`; Raycast's `api_keys` is read literally with no env - // interpolation. OMP and Prime can carry provider headers, but remote + // interpolation. OMP, Prime and omo can carry provider headers, but remote // credential wiring is deliberately deferred from those initial generated - // integrations. + // integrations -- omo reuses Pi's builder, which emits no headers at all. const loopbackOnly = EXPORT_CLIENT_IDS.filter(id => EXPORT_CLIENTS[id].loopbackOnly); - expect(loopbackOnly).toEqual(["pi", "omp", "kimi", "gajae", "dsh", "mcode", "zcode", "prime", "aside", "raycast"]); + expect(loopbackOnly).toEqual(["pi", "omp", "kimi", "gajae", "dsh", "mcode", "zcode", "prime", "aside", "raycast", "omo"]); }); test("every client that is not loopback-only carries the header on a remote bind", () => { diff --git a/tests/config/client-config-export.test.ts b/tests/config/client-config-export.test.ts index 17b3ce9c93..5524b1c338 100644 --- a/tests/config/client-config-export.test.ts +++ b/tests/config/client-config-export.test.ts @@ -808,8 +808,8 @@ describe("hub-resolved Fast exports", () => { }); describe("EXPORT_CLIENTS registry", () => { - test("covers exactly the thirteen file-toggle clients", () => { - expect(EXPORT_CLIENT_IDS).toEqual(["opencode", "pi", "omp", "hermes", "openclaw", "kimi", "gajae", "dsh", "mcode", "zcode", "prime", "aside", "raycast"]); + test("covers exactly the fourteen file-toggle clients", () => { + expect(EXPORT_CLIENT_IDS).toEqual(["opencode", "pi", "omp", "hermes", "openclaw", "kimi", "gajae", "dsh", "mcode", "zcode", "prime", "aside", "raycast", "omo"]); for (const id of EXPORT_CLIENT_IDS) expect(isExportClientId(id)).toBe(true); // The exception clients keep their own surfaces and are not export clients. expect(isExportClientId("claude-desktop")).toBe(false); diff --git a/tests/fixtures/test-layout-expected.json b/tests/fixtures/test-layout-expected.json index 36b7a2e1ae..826e44d13d 100644 --- a/tests/fixtures/test-layout-expected.json +++ b/tests/fixtures/test-layout-expected.json @@ -761,6 +761,7 @@ "ollama-show-enrichment-v7.test.ts": "providers/ollama", "ollama-show-enrichment.test.ts": "providers/ollama", "ollama-show-ignore-abort.test.ts": "providers/ollama", + "omo-client.test.ts": "clients", "omp-path-contract.test.ts": "clients", "omp-yaml-source-inline-comments.test.ts": "clients", "openai-api-virtual-models.test.ts": "adapters/openai", diff --git a/tests/gui/integrations-invariants.test.ts b/tests/gui/integrations-invariants.test.ts index 47b196b688..3ab941e508 100644 --- a/tests/gui/integrations-invariants.test.ts +++ b/tests/gui/integrations-invariants.test.ts @@ -91,7 +91,7 @@ describe("the client registries cannot drift apart", () => { const guiRouting = await import("../../gui/src/app-routing"); const expected = [...EXPORT_CLIENT_IDS].sort(); - expect(expected).toHaveLength(13); + expect(expected).toHaveLength(14); expect([...INTEGRATION_CLIENT_IDS].sort()).toEqual(expected); expect([...gui.CLIENTS].sort()).toEqual(expected); @@ -174,6 +174,10 @@ describe("every client survives a full lifecycle", () => { // Raycast's `providers` is a SEQUENCE keyed by `id`, so the user's entry is // a sibling element rather than a sibling map key. raycast: "providers:\n - id: lmstudio\n name: LM Studio\n base_url: http://localhost:1234/v1\n models: []\n", + // omo is senpi under an omo brand, and senpi reads Pi's models.json + // contract -- verified against senpi's own compiled validator, not assumed + // from the family resemblance (260912 plan unit, 001). + omo: '{\n "providers": {\n "mine": { "api": "http://keep-me" }\n }\n}\n', }; /** Where the seed's user-owned entry lives when the seed is a sequence. */ const USER_ELEMENT: Partial> = { From d470f5714b65883521ce0d1106f56664df298284 Mon Sep 17 00:00:00 2001 From: JUN Date: Sat, 12 Sep 2026 02:06:03 +0900 Subject: [PATCH 2/3] docs(devlog): record the rendered Integrations evidence for omo The Integrations page is the surface this change exists for, and it is the one thing no source test can prove: the two lists a client can be missing from leave typecheck and the invariants green while the tab silently does not render. This is the captured page -- the omo tab and mark, the ownership sentence, and the gjc label on its neighbour. --- .../evidence/integrations-omo-tab.png | Bin 0 -> 129453 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 devlog/_plan/260912_omo_client_integration/evidence/integrations-omo-tab.png diff --git a/devlog/_plan/260912_omo_client_integration/evidence/integrations-omo-tab.png b/devlog/_plan/260912_omo_client_integration/evidence/integrations-omo-tab.png new file mode 100644 index 0000000000000000000000000000000000000000..36f9809e12e52610657fb464947c3753e5e5177b GIT binary patch literal 129453 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¼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 Date: Sat, 12 Sep 2026 02:13:13 +0900 Subject: [PATCH 3/3] fix(devlog): stop the omo contract note tripping the privacy scan `omo-ai@5.0.0-0.beta.53` is an npm spec, but it is also shaped exactly like an email address, and privacy:scan reads it as one. The local run missed it because the file was still untracked when that check ran; CI caught it on all three jobs that invoke the scan. Same two facts, written so the version is not glued to the package name with an @. --- .../_plan/260912_omo_client_integration/001_omo_contract.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/devlog/_plan/260912_omo_client_integration/001_omo_contract.md b/devlog/_plan/260912_omo_client_integration/001_omo_contract.md index 9cb482980b..663ec0dd37 100644 --- a/devlog/_plan/260912_omo_client_integration/001_omo_contract.md +++ b/devlog/_plan/260912_omo_client_integration/001_omo_contract.md @@ -1,7 +1,8 @@ # What omo actually reads -Evidence: `omo-ai@5.0.0-0.beta.53` unpacked at `/tmp/omoprobe2/package`, and -`@code-yeongyu/senpi@2026.9.10-2` unpacked at `/tmp/senpiprobe/package`. Schema +Evidence: `omo-ai` version `5.0.0-0.beta.53` unpacked at `/tmp/omoprobe2/package`, +and `@code-yeongyu/senpi` version `2026.9.10-2` unpacked at +`/tmp/senpiprobe/package`. Schema claims below were executed against that tarball's own TypeBox compiler (`typebox@1.3.18`), not read off documentation.