Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions devlog/_plan/260818_260818-zcode-client/000_plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# 000_plan — ZCode client integration (rev 2, post-audit)

Issue: https://github.com/lidge-jun/opencodex/issues/2022
Branch: codex/zcode-client (from origin/dev)
Goalplan: .codexclaw/goalplans/add-zcode-client-support-to-opencodex-issue-firs

## Audit synthesis (grok-4.6 reviewer, round 1: FAIL — all findings ACCEPTED)

1. ACCEPTED: mcode/opencode are launchers; the real managed-block write surface is
EXPORT_CLIENTS + INTEGRATION_CLIENTS + applyIntegration (src/integrations/writer.ts).
No private read-modify-write. rev 1's homemade RMW is dropped.
2. ACCEPTED: no live admission token on disk. Loopback data-plane ignores the token
(src/server/auth-cors.ts:256,436); emit LOOPBACK_API_KEY_PLACEHOLDER
(src/clients/config-export.ts:128) as apiKey (ZCode requires a non-empty value;
placeholder satisfies the UI). Never serialize opencodeApiKey().
3. ACCEPTED: "byte-for-byte" relaxed to structural preservation — JSON writer is
parse-merge-serialize; user provider entries survive structurally unchanged.
4. ACCEPTED: 'ocx zcode show' must never dump file bytes (would print user Z.ai keys);
integration-style state/path report only.

## Verified ground truth (pre-code live validation, 2026-08-18)

- ~/.zcode/v2/config.json provider map, entry shape observed + proven live:
{ name, kind: "anthropic", options: { apiKey, baseURL, apiKeyRequired }, enabled,
source: "custom", models: { <ID>: { name?, limit: { context, output? }, modalities } } }
- ZCode 3.7.7 + live proxy: model picker shows the provider, chat routes through
/v1/messages, tool-call round trip works, slash-form model ids accepted
(curl "model":"xai/grok-4.6" -> end_turn).
- Restart required after config change (docs + observed).

## 010 — implementation (single phase)

Scope (in): src/clients/config-export.ts (zcode builder + registration in
EXPORT_CLIENTS), src/integrations/registry.ts (INTEGRATION_CLIENTS entry),
src/cli/ thin alias 'ocx zcode' -> integration enable/disable/status wiring
(pattern: existing client aliases), tests listed below, docs-site if trivial.
Scope (out): src/router.ts, src/server/lifecycle.ts, src/server/responses/core.ts,
src/lab/, GUI, release automation.

Design:
- zcode registered as an integration client: id "zcode", target
~/.zcode/v2/config.json, ownership = provider.opencodex key only,
loopbackOnly: true, apiKey = LOOPBACK_API_KEY_PLACEHOLDER, apiKeyRequired: true.
- Models from the shared export-model surface (exportModelsFromProxyRows /
loadExportModels), provider/model slash ids, limit.context from authoritative
contextWindow; baseURL from exportContextOf/opencodeProxyBaseUrl.
- First run: ~/.zcode missing -> not_installed (never create the home dir);
file missing but dir present -> create file with only our key.
- Writer guarantees inherited from applyIntegration: unparseable abort,
compare-before-commit, symlink refusal, snapshot/restore history.

Tests (model on): tests/integrations-writer.test.ts, tests/integrations-invariants.test.ts
(EXPORT_CLIENT_IDS lockstep), tests/client-config-export-new-clients.test.ts
(update hard-coded loopback-only list at :65), tests/client-config-new-clients.test.ts
(runtime-assembled fixture secrets; assert serialized text has no real secret).

Acceptance: focused tests green; typecheck green; full suite + privacy:scan green
before PR; PR to dev with template + Closes #2022.

17 changes: 17 additions & 0 deletions devlog/_plan/260818_260818-zcode-client/010_phase1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# 010 — implementation record

Executed as one PABCD cycle (session cli, goalplan add-zcode-client-...).

- A-gate: grok-4.6 auditor round 1 FAIL (private RMW / secret-on-disk / byte-for-byte
/ show-dumps-keys) -> plan rev 2 -> round 2 PASS.
- B: commit 1ec6c8d65 (registry + builder + CLI alias + GUI lists + tests).
- C review: grok-4.6 reviewer FAIL (GUI page maps missing -> gui tsc red; guessed
limit.output; --json-before-verb; CLI untested) -> commit 7668adf9a -> re-review PASS.
- Live E2E: real applyIntegration wrote 21 catalog models into ~/.zcode/v2/config.json;
ZCode 3.7.7 picker shows OpenCodex/<provider>/<model>; marker prompt round-tripped
via anthropic/claude-fable-5 (usage.jsonl 490587->490594). Screenshots: 020 (GUI tab),
021 (ZCode live response).
- Full suite: 13286 pass / 12 fail; the same failing files fail identically on clean
origin/dev (11 fail + 1 error baseline) — pre-existing, environment-bound, none touch
the zcode surface.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions gui/src/app-routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ export const INTEGRATION_TAB_HASHES = [
"integrations/kimi",
"integrations/gajae",
"integrations/dsh",
"integrations/mcode",
"integrations/zcode",
] as const;

export function hashBelongsToPage(rawHash: string, page: Page): boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"] as const;
export const CLIENTS = ["opencode", "pi", "omp", "hermes", "openclaw", "kimi", "gajae", "dsh", "mcode", "zcode"] as const;
export type ExportClientId = (typeof CLIENTS)[number];

export const CLIENT_LABEL_KEYS = {
Expand All @@ -21,6 +21,7 @@ export const CLIENT_LABEL_KEYS = {
gajae: "api.clientConfig.clientGajae",
dsh: "api.clientConfig.clientDsh",
mcode: "api.clientConfig.clientMcode",
zcode: "api.clientConfig.clientZcode",
} as const;

/**
Expand Down
3 changes: 3 additions & 0 deletions gui/src/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,7 @@ export const de: Record<TKey, string> = {
"integrations.tab.gajae": "Gajae Code",
"integrations.tab.dsh": "DeepSeek Harness (DSH)",
"integrations.tab.mcode": "MiniMax Code",
"integrations.tab.zcode": "ZCode",
"integrations.codex.title": "Codex CLI",
"integrations.codex.body": "Die Codex-Anbindung wird vom Proxy-Dienst verwaltet. Beim Start von opencodex wird sie angewendet; beim Stoppen des Dienstes wird das native Routing wiederhergestellt.",
"integrations.codex.openService": "Dienststeuerung öffnen",
Expand Down Expand Up @@ -959,6 +960,7 @@ export const de: Record<TKey, string> = {
"integrations.semantics.gajae": "Gilt für eine neue Sitzung oder beim Öffnen von /model.",
"integrations.semantics.dsh": "OpenCodex verwaltet nur llm-pi-ai.providers.opencodex in $DSH_HOME/settings.yaml. DSH lädt diesen Anbieter im laufenden Betrieb neu; Ihr Standardmodell und deepseek-official bleiben unverändert. Derzeit nur über Loopback; es werden keine echten Zugangsdaten geschrieben.",
"integrations.semantics.mcode": "Verwaltet nur custom_provider.opencodex. Standardmodell und MiniMax-Anmeldung bleiben unverändert.",
"integrations.semantics.zcode": "Verwaltet nur provider.opencodex in ~/.zcode/v2/config.json. Z.ai-Anmeldung und andere Provider bleiben unverändert. ZCode nach Änderungen neu starten.",
"codexAuth.mainAccount": "Hauptkonto",
"codexAuth.logLabel": "Log-Kennung",
"codexAuth.codexApp": "Codex App",
Expand Down Expand Up @@ -1242,6 +1244,7 @@ export const de: Record<TKey, string> = {
"api.clientConfig.clientGajae": "Gajae Code",
"api.clientConfig.clientDsh": "DeepSeek Harness (DSH)",
"api.clientConfig.clientMcode": "MiniMax Code",
"api.clientConfig.clientZcode": "ZCode",
"api.clientConfig.copy": "Konfiguration kopieren",
"api.clientConfig.download": "Herunterladen",
"api.clientConfig.loading": "Client-Konfiguration wird erstellt…",
Expand Down
3 changes: 3 additions & 0 deletions gui/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1332,6 +1332,7 @@ export const en = {
"integrations.tab.gajae": "Gajae Code",
"integrations.tab.dsh": "DeepSeek Harness (DSH)",
"integrations.tab.mcode": "MiniMax Code",
"integrations.tab.zcode": "ZCode",
"integrations.codex.title": "Codex CLI",
"integrations.codex.body": "Codex wiring is owned by the proxy service. Starting opencodex applies it; stopping the service restores native routing.",
"integrations.codex.openService": "Open service controls",
Expand Down Expand Up @@ -1444,6 +1445,7 @@ export const en = {
"integrations.semantics.gajae": "Applies to a new session or when opening /model.",
"integrations.semantics.dsh": "OpenCodex manages only llm-pi-ai.providers.opencodex in $DSH_HOME/settings.yaml. DSH hot reloads this provider; your default model and deepseek-official stay unchanged. Currently loopback-only; no real credential is written.",
"integrations.semantics.mcode": "Manages only custom_provider.opencodex. Your default model and MiniMax login stay unchanged.",
"integrations.semantics.zcode": "Manages only provider.opencodex in ~/.zcode/v2/config.json. Your Z.ai login and other providers stay unchanged. Restart ZCode after changes.",
"codexAuth.mainAccount": "Main Account",
"codexAuth.logLabel": "Log label",
"codexAuth.codexApp": "Codex App",
Expand Down Expand Up @@ -1735,6 +1737,7 @@ export const en = {
"api.clientConfig.clientGajae": "Gajae Code",
"api.clientConfig.clientDsh": "DeepSeek Harness (DSH)",
"api.clientConfig.clientMcode": "MiniMax Code",
"api.clientConfig.clientZcode": "ZCode",
"api.clientConfig.copy": "Copy config",
"api.clientConfig.download": "Download",
"api.clientConfig.loading": "Building client config…",
Expand Down
3 changes: 3 additions & 0 deletions gui/src/i18n/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1305,6 +1305,7 @@ export const fr: Record<TKey, string> = {
"integrations.tab.gajae": "Gajae Code",
"integrations.tab.dsh": "DeepSeek Harness (DSH)",
"integrations.tab.mcode": "MiniMax Code",
"integrations.tab.zcode": "ZCode",
"integrations.codex.title": "Codex CLI",
"integrations.codex.body": "Le câblage de Codex est géré par le service proxy. Le démarrage d’opencodex l’applique ; l’arrêt du service rétablit le routage natif.",
"integrations.codex.openService": "Ouvrir les commandes du service",
Expand Down Expand Up @@ -1417,6 +1418,7 @@ export const fr: Record<TKey, string> = {
"integrations.semantics.gajae": "S’applique à une nouvelle session ou à l’ouverture de /model.",
"integrations.semantics.dsh": "OpenCodex gère uniquement llm-pi-ai.providers.opencodex dans $DSH_HOME/settings.yaml. DSH recharge ce fournisseur à chaud ; votre modèle par défaut et deepseek-official restent inchangés. Seule l’adresse de bouclage est actuellement prise en charge ; aucun identifiant réel n’est écrit.",
"integrations.semantics.mcode": "Gère uniquement custom_provider.opencodex. Votre modèle par défaut et votre connexion MiniMax restent inchangés.",
"integrations.semantics.zcode": "Gère uniquement provider.opencodex dans ~/.zcode/v2/config.json. Votre connexion Z.ai et les autres fournisseurs restent inchangés. Redémarrez ZCode après toute modification.",
"codexAuth.mainAccount": "Compte principal",
"codexAuth.logLabel": "Libellé du journal",
"codexAuth.codexApp": "Application Codex",
Expand Down Expand Up @@ -1699,6 +1701,7 @@ export const fr: Record<TKey, string> = {
"api.clientConfig.clientGajae": "Gajae Code",
"api.clientConfig.clientDsh": "DeepSeek Harness (DSH)",
"api.clientConfig.clientMcode": "MiniMax Code",
"api.clientConfig.clientZcode": "ZCode",
"api.clientConfig.copy": "Copier la configuration",
"api.clientConfig.download": "Télécharger",
"api.clientConfig.loading": "Génération de la configuration du client…",
Expand Down
3 changes: 3 additions & 0 deletions gui/src/i18n/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1265,6 +1265,7 @@ export const ja: Record<TKey, string> = {
"integrations.tab.gajae": "Gajae Code",
"integrations.tab.dsh": "DeepSeek Harness (DSH)",
"integrations.tab.mcode": "MiniMax Code",
"integrations.tab.zcode": "ZCode",
"integrations.codex.title": "Codex CLI",
"integrations.codex.body": "Codex の接続はプロキシサービスが管理します。opencodex を起動すると適用され、サービスを停止するとネイティブのルーティングに戻ります。",
"integrations.codex.openService": "サービス制御を開く",
Expand Down Expand Up @@ -1377,6 +1378,7 @@ export const ja: Record<TKey, string> = {
"integrations.semantics.gajae": "新しいセッション、または /model を開いたときに適用されます。",
"integrations.semantics.dsh": "OpenCodex が管理するのは $DSH_HOME/settings.yaml 内の llm-pi-ai.providers.opencodex だけです。DSH はこのプロバイダーをホットリロードし、既定のモデルと deepseek-official は変更しません。現在はループバック専用で、実際の認証情報は書き込みません。",
"integrations.semantics.mcode": "custom_provider.opencodex のみを管理します。既定モデルと MiniMax ログインは変更しません。",
"integrations.semantics.zcode": "~/.zcode/v2/config.json の provider.opencodex のみを管理します。Z.ai ログインと他のプロバイダーは変更しません。変更後は ZCode を再起動してください。",
"codexAuth.mainAccount": "メインアカウント",
"codexAuth.logLabel": "ログラベル",
"codexAuth.codexApp": "Codex App",
Expand Down Expand Up @@ -1665,6 +1667,7 @@ export const ja: Record<TKey, string> = {
"api.clientConfig.clientGajae": "Gajae Code",
"api.clientConfig.clientDsh": "DeepSeek Harness (DSH)",
"api.clientConfig.clientMcode": "MiniMax Code",
"api.clientConfig.clientZcode": "ZCode",
"api.clientConfig.copy": "設定をコピー",
"api.clientConfig.download": "ダウンロード",
"api.clientConfig.loading": "クライアント設定を生成中…",
Expand Down
3 changes: 3 additions & 0 deletions gui/src/i18n/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -871,6 +871,7 @@ export const ko: Record<TKey, string> = {
"integrations.tab.gajae": "Gajae Code",
"integrations.tab.dsh": "DeepSeek Harness (DSH)",
"integrations.tab.mcode": "MiniMax Code",
"integrations.tab.zcode": "ZCode",
"integrations.codex.title": "Codex CLI",
"integrations.codex.body": "Codex 연결은 프록시 서비스가 관리합니다. opencodex를 시작하면 적용되고 서비스를 중지하면 기본 라우팅으로 복원됩니다.",
"integrations.codex.openService": "서비스 제어 열기",
Expand Down Expand Up @@ -983,6 +984,7 @@ export const ko: Record<TKey, string> = {
"integrations.semantics.gajae": "새 세션 또는 /model을 열 때 적용됩니다.",
"integrations.semantics.dsh": "OpenCodex는 $DSH_HOME/settings.yaml의 llm-pi-ai.providers.opencodex만 관리합니다. DSH는 이 provider를 hot reload하며 기본 model과 deepseek-official은 변경하지 않습니다. 현재 loopback 전용이며 실제 credential을 기록하지 않습니다.",
"integrations.semantics.mcode": "custom_provider.opencodex만 관리하며 기본 모델과 MiniMax 로그인은 변경하지 않습니다.",
"integrations.semantics.zcode": "~/.zcode/v2/config.json의 provider.opencodex만 관리하며 Z.ai 로그인과 다른 프로바이더는 변경하지 않습니다. 변경 후 ZCode를 재시작하세요.",
"codexAuth.mainAccount": "메인 계정",
"codexAuth.logLabel": "로그 라벨",
"codexAuth.codexApp": "Codex App",
Expand Down Expand Up @@ -1269,6 +1271,7 @@ export const ko: Record<TKey, string> = {
"api.clientConfig.clientGajae": "Gajae Code",
"api.clientConfig.clientDsh": "DeepSeek Harness (DSH)",
"api.clientConfig.clientMcode": "MiniMax Code",
"api.clientConfig.clientZcode": "ZCode",
"api.clientConfig.copy": "설정 복사",
"api.clientConfig.download": "다운로드",
"api.clientConfig.loading": "클라이언트 설정 생성 중…",
Expand Down
3 changes: 3 additions & 0 deletions gui/src/i18n/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1316,6 +1316,7 @@ export const ru: Record<TKey, string> = {
"integrations.tab.gajae": "Gajae Code",
"integrations.tab.dsh": "DeepSeek Harness (DSH)",
"integrations.tab.mcode": "MiniMax Code",
"integrations.tab.zcode": "ZCode",
"integrations.codex.title": "Codex CLI",
"integrations.codex.body": "Подключением Codex управляет прокси-сервис. При запуске opencodex оно применяется, а при остановке сервиса восстанавливается нативная маршрутизация.",
"integrations.codex.openService": "Открыть управление сервисом",
Expand Down Expand Up @@ -1428,6 +1429,7 @@ export const ru: Record<TKey, string> = {
"integrations.semantics.gajae": "Применяется в новом сеансе или при открытии /model.",
"integrations.semantics.dsh": "OpenCodex управляет только llm-pi-ai.providers.opencodex в $DSH_HOME/settings.yaml. DSH применяет этот провайдер горячей перезагрузкой; модель по умолчанию и deepseek-official остаются без изменений. Сейчас поддерживается только loopback; реальные учётные данные не записываются.",
"integrations.semantics.mcode": "Управляет только custom_provider.opencodex. Модель по умолчанию и вход MiniMax не меняются.",
"integrations.semantics.zcode": "Управляет только provider.opencodex в ~/.zcode/v2/config.json. Вход Z.ai и другие провайдеры не меняются. Перезапустите ZCode после изменений.",
"codexAuth.mainAccount": "Основной аккаунт",
"codexAuth.logLabel": "Метка журнала",
"codexAuth.codexApp": "Codex App",
Expand Down Expand Up @@ -1716,6 +1718,7 @@ export const ru: Record<TKey, string> = {
"api.clientConfig.clientGajae": "Gajae Code",
"api.clientConfig.clientDsh": "DeepSeek Harness (DSH)",
"api.clientConfig.clientMcode": "MiniMax Code",
"api.clientConfig.clientZcode": "ZCode",
"api.clientConfig.copy": "Копировать конфигурацию",
"api.clientConfig.download": "Скачать",
"api.clientConfig.loading": "Формируется конфигурация клиента…",
Expand Down
3 changes: 3 additions & 0 deletions gui/src/i18n/tr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1323,6 +1323,7 @@ export const tr: Record<TKey, string> = {
"integrations.tab.gajae": "Gajae Code",
"integrations.tab.dsh": "DeepSeek Harness (DSH)",
"integrations.tab.mcode": "MiniMax Code",
"integrations.tab.zcode": "ZCode",
"integrations.codex.title": "Codex CLI",
"integrations.codex.body": "Codex bağlantısı proxy servisine aittir.",
"integrations.codex.openService": "Servis kontrollerini aç",
Expand Down Expand Up @@ -1434,6 +1435,7 @@ export const tr: Record<TKey, string> = {
"integrations.semantics.gajae": "Yeni oturuma uygulanır.",
"integrations.semantics.dsh": "OpenCodex yalnızca $DSH_HOME/settings.yaml içindeki llm-pi-ai.providers.opencodex bölümünü yönetir. DSH bu sağlayıcıyı çalışırken yeniden yükler; varsayılan modeliniz ve deepseek-official değişmez. Şimdilik yalnızca geri döngü desteklenir; gerçek kimlik bilgisi yazılmaz.",
"integrations.semantics.mcode": "Yalnızca custom_provider.opencodex bölümünü yönetir. Varsayılan model ve MiniMax oturumu değişmez.",
"integrations.semantics.zcode": "Yalnızca ~/.zcode/v2/config.json içindeki provider.opencodex bölümünü yönetir. Z.ai oturumu ve diğer sağlayıcılar değişmez. Değişikliklerden sonra ZCode'u yeniden başlatın.",
"integrations.semantics.omp": "Kataloğu yüklemek için OMP'yi yeniden başlatın.",
"codexAuth.mainAccount": "Ana Hesap",
"codexAuth.logLabel": "Günlük etiketi",
Expand Down Expand Up @@ -1723,6 +1725,7 @@ export const tr: Record<TKey, string> = {
"api.clientConfig.clientGajae": "Gajae Code",
"api.clientConfig.clientDsh": "DeepSeek Harness (DSH)",
"api.clientConfig.clientMcode": "MiniMax Code",
"api.clientConfig.clientZcode": "ZCode",
"api.clientConfig.copy": "JSON Kopyala",
"api.clientConfig.download": "İndir",
"api.clientConfig.loading": "İstemci konfigürasyonu oluşturuluyor…",
Expand Down
Loading
Loading