From a047ddb409f77874f9564e805daa58a0bc9af23b Mon Sep 17 00:00:00 2001 From: JUN Date: Sun, 27 Sep 2026 23:42:35 +0900 Subject: [PATCH 01/47] docs(devlog): plan release train 4 clients-proxy lane --- .../clients-proxy/000_plan.md | 125 ++++++++++++++++++ .../clients-proxy/010_recipe.md | 34 +++++ .../clients-proxy/020_macos_proxy.md | 59 +++++++++ .../clients-proxy/030_qoder.md | 46 +++++++ .../clients-proxy/040_kilo.md | 42 ++++++ .../clients-proxy/050_droid.md | 42 ++++++ .../clients-proxy/060_jev.md | 40 ++++++ .../clients-proxy/070_memory.md | 58 ++++++++ .../clients-proxy/080_held_items.md | 38 ++++++ .../clients-proxy/090_final_ci.md | 26 ++++ 10 files changed, 510 insertions(+) create mode 100644 devlog/_plan/260927_release_train_4/clients-proxy/000_plan.md create mode 100644 devlog/_plan/260927_release_train_4/clients-proxy/010_recipe.md create mode 100644 devlog/_plan/260927_release_train_4/clients-proxy/020_macos_proxy.md create mode 100644 devlog/_plan/260927_release_train_4/clients-proxy/030_qoder.md create mode 100644 devlog/_plan/260927_release_train_4/clients-proxy/040_kilo.md create mode 100644 devlog/_plan/260927_release_train_4/clients-proxy/050_droid.md create mode 100644 devlog/_plan/260927_release_train_4/clients-proxy/060_jev.md create mode 100644 devlog/_plan/260927_release_train_4/clients-proxy/070_memory.md create mode 100644 devlog/_plan/260927_release_train_4/clients-proxy/080_held_items.md create mode 100644 devlog/_plan/260927_release_train_4/clients-proxy/090_final_ci.md diff --git a/devlog/_plan/260927_release_train_4/clients-proxy/000_plan.md b/devlog/_plan/260927_release_train_4/clients-proxy/000_plan.md new file mode 100644 index 00000000000..79012cfc11c --- /dev/null +++ b/devlog/_plan/260927_release_train_4/clients-proxy/000_plan.md @@ -0,0 +1,125 @@ +# Release train 4: clients and proxy lane + +At `origin/dev` `24b2f39b77` on 2026-09-27, this lane has a mix of useful client +integrations, routing changes, and proposals whose current diffs are not safe to +land. Carry the bounded changes through ordinary PRs to `dev`, correct the observed +regressions, and leave concrete reasons on proposals that need a new contract. + +## Loop specification + +- Archetype: satisfy the release-train acceptance contract, one dependency-ordered + work phase per PABCD cycle. +- Trigger: the release train 4 `clients-proxy` lane assignment. +- Goal: land verified client/proxy changes that help the next release and record a + disposition for every assigned PR and issue. +- Non-goals: `main`, `preview`, releases, version changes, other lanes, writes to + contributor forks, automatic third-party installer execution, and eager client + activation on the three core request paths. +- Verifier: the focused commands in the decade docs, `bun run test:changed`, + `bun run typecheck`, `bun run structure:check`, `bun run privacy:scan`, + `bun run skill:surface:check` when CLI capabilities change, exact-head PR CI, + and a successful post-merge `dev` CI run. Conditional branches have explicit + activation cases in their phase documents. +- Stop condition: every row below has a supported merge or hold decision, each + landed PR passed its actual required jobs, source PRs/issues received the + appropriate links and disposition, and the final `dev` run succeeded. +- Memory artifact: this numbered unit, its phase evidence, and the lane's + session-bound goalplan/ledger. +- Terminal outcomes: DONE means that stop condition holds; NOOP means no + candidate survived review; NEEDS_HUMAN means an external contract or approval + blocks a specific candidate; BLOCKED means repeated external failure prevents + all meaningful progress; UNSAFE means validation found an unresolved release + blocker. There is no user-specified token or wall-clock bound. +- Escalation: a new scope, an unresolvable security boundary, or a required + external account decision goes to the coordinator. PR push/merge and issue/PR + disposition within this lane are already authorized. + +All source edits, Git operations, and tests use this lane's dedicated +worktree checkout. The native +session directory is used only for ignored FSM and goalplan state. Local full +suite may be omitted due to seven concurrent lane worktrees; focused regressions +remain mandatory, and each PR's Verification section will state the exact +commands, results, and coverage left to CI. + +## Source ownership and selection + +`structure/clients/integrations.md:27-55` assigns pure client builders to +`src/clients/config-export.ts`, detection paths to +`src/integrations/registry.ts`, and snapshot/classification/writes to the shared +integration modules. New clients stay explicit and use those seams. The three +core request files (`src/router.ts`, `src/server/lifecycle.ts`, +`src/server/responses/core.ts`) must retain the Lab import boundary enforced by +`tests/lab/core-lab-boundary.test.ts`. `src/config/proxy-env.ts` owns process +proxy activation (`structure/config-proxy.md:1-20`). + +| Item | Current head/state | Decision and evidence | Work phase | +| --- | --- | --- | --- | +| #6051 | `987b8097`, open | Carry the disposable-home management-API recipe with a discoverable contributor link; `.agents/skills/` has no existing entry point. | [010](010_recipe.md) | +| #5893 / #5853 | `3743320a`, draft | Carry only after safe bypass translation: every macOS exception must be faithfully representable or discovery refuses before any environment write; inherited SOCKS keeps its existing path. The PR currently appends exceptions only to `NO_PROXY` while Bun may read lowercase first (`src/config/proxy-env.ts:267` in PR). | [020](020_macos_proxy.md) | +| #5950 / #5660 | `ef03f5ab`, open | Carry Qoder after current-base revalidation of opt-in config writes, restore, and path handling (`src/clients/config-export/qoder.ts`, PR test). | [030](030_qoder.md) | +| #5272 | `7dd796d7`, open | Carry Kilo after checking all merged config candidates; first-file-only selection can be overridden by a later legacy file (`src/clients/config-export/kilo.ts:57-63` in PR). | [040](040_kilo.md) | +| #5193 | `91090f80`, open/conflicting | Reimplement a focused Droid slice on current `dev` only if its client contract and export provenance can be proven. The PR's broad rewrite changes shared loopback export behavior. | [050](050_droid.md) | +| #5871 | `ba2d2600`, open/conflicting | Carry after conflict repair and an outbound decision-payload regression (`src/combos/jev.ts:588-595` in PR). | [060](060_jev.md) | +| #5983 / #5982 | `cd45810f`, open | Carry with explicit non-memory metadata taking precedence over the subagent header fallback (`src/server/responses/memory-models.ts:65` in PR). | [070](070_memory.md) | +| #5905 / #5679 | `19948a38`, draft | Hold: opening regular Cursor integration status can automatically fetch an external installer manifest. Decide explicit opt-in and cover timeout/status before carry. | [080](080_held_items.md) | +| #3833 | `d47e376b`, draft | Hold: Command Code rejects the exported literal `apiKey` placeholder; the PR test only checks presence. Needs supported client credential form and live client proof. | [080](080_held_items.md) | +| #4854 | open | Hold OpenScience until its actual config schema and ownership paths are established. Manual OpenAI-compatible endpoint is available. | [080](080_held_items.md) | +| #3494 | open | Hold VS Code extension integration until one named extension's supported settings and reload lifecycle are verified. | [080](080_held_items.md) | +| #1416 | open | Hold Orca launch manifest until the stopped-proxy, secret-free consumer contract is pinned; live-catalog config export is the wrong bootstrap path. | [080](080_held_items.md) | +| #2811 | open | Design only: #5016 was closed because `plan` required `managed: true` that the production inspector never reports. A reachable provenance proof precedes apply. | [080](080_held_items.md) | + +## Dependency order and merge method + +`010` establishes the verification recipe, `020` owns outbound proxy activation, +`030` proves the existing client path on current `dev`, and `040`/`050` reuse that +verified roster with one client at a time. `060` precedes `070` because both touch +`src/types/config.ts`; that is a merge-conflict dependency, not a runtime one. +`080` records held items after each applicable outcome. [090](090_final_ci.md) +checks the latest integrated tree. Each carried source PR becomes a new ordinary +`dev` PR from this lane, with a `Co-authored-by` trailer in the PR description +or branch commit. Git authorship alone does not satisfy the carry policy. A +large or conflicted source diff is reduced before +landing; the source PR is thanked, linked, and closed only once its replacement +is merged. No GitHub native stack or tip-only CI exception is selected. + +For every batch, fetch `origin/dev` again, inspect the source PR's current head +and diff, check the file-size ratchet and merged union/locale/count consumers, +run focused tests and typecheck, perform explicit security review for any +credential, proxy, installer, or authentication boundary, then inspect +required CI at the exact new PR +head before merging. GUI changes need a screenshot in the PR description from +the separate `pr-assets` branch, never committed to the PR branch. Merge only +when the new PR head contains the latest `origin/dev`; inspect the new `dev` run +before the next batch. + +## Consultation and uncertainty + +Architect proposal `01a0e33a-811b-7471-a32a-52455283082e`: D1 existing +integration ownership and D2 proxy ownership accepted; D3 Cursor discovery +amended to hold pending opt-in; D4 managed clients accepted with #3833 held; +D5 new client proposals held pending primary client contracts; D6 JEV then +memory accepted; D7 Codex updater remains design-only. Source PR reviewers: +`01a0e338-ca02-7620-9ecd-cb6970740789`, +`01a0e339-163d-7240-9e96-4e7552dc9e47`, +`01a0e339-17ce-7540-b189-1241d837662b`, and +`01a0e339-18b6-7403-b450-5d3d86fee0be`. The architect's first reflection +found three gaps: attribution trailer, explicit security review, and the +recipe's OS-home isolation condition. All three were folded into this revision +before independent audit. Their findings are proposals; each carry is +rechecked on the actual integrated diff and current `dev`. + +Baseline verifier preflight on `24b2f39b77`: `bun run typecheck`, +`bun run structure:check`, `bun run privacy:scan`, +`bun run skill:surface:check`, and `bun test +tests/lab/core-lab-boundary.test.ts` each exited 0; the Lab guard ran 25 +tests. These check the baseline and this planning tree only. New PR behavior +still requires the phase-specific commands after the relevant diff is present. +`bun run test:changed` on this docs-only staged diff selected zero tests and +exited 1; it is not evidence of test passage. Docs checks and semantic audit +cover the roadmap, and implementation batches rerun changed tests. + +The same architect rechecked the D2 safety amendment and returned ALIGNED. +Independent A reviewer `01a0e345-7fc4-7bc0-bef9-dd4c8e927d59` first +reported six blockers, then one remaining test-layout blocker; every finding +was folded into the relevant decade document and its final verdict was PASS. +This closes the roadmap design review, not any proposed code change. diff --git a/devlog/_plan/260927_release_train_4/clients-proxy/010_recipe.md b/devlog/_plan/260927_release_train_4/clients-proxy/010_recipe.md new file mode 100644 index 00000000000..1a2904d7698 --- /dev/null +++ b/devlog/_plan/260927_release_train_4/clients-proxy/010_recipe.md @@ -0,0 +1,34 @@ +# Phase 1: management API test recipe (#6051) + +Depends on `000_plan.md`; docs-only carry, then one ordinary PR. The source PR +adds `.agents/skills/testing-opencodex-management-api/SKILL.md` with a +disposable-home setup and correct Lab requests. It needs a repository entry +point before another agent can reliably discover it. + +## Exact change map + +- NEW `.agents/skills/testing-opencodex-management-api/SKILL.md`: carry the + source recipe after verifying every command and path against current + management routes. Preserve its disposable OS account/home, container, or + VM prerequisite; redirect client homes and disable integrations before a + smoke. `OPENCODEX_HOME` alone does not isolate client writes. Keep explicit + token read, bounded process cleanup, and authorization before live-provider + requests. No secret values or real account identifiers enter examples. +- MODIFY `AGENTS.md` near the Commands and `skills/ocx/` guidance: add one + contributor-facing link to the test recipe. Before: only the runtime-control + `skills/ocx/` reference is discoverable. After: code-change agents can find + the isolated management API test procedure without treating it as a user + operating skill. +- MODIFY this unit's outcome document after validation with the carried source + SHA, attribution, and exact command results. + +## Acceptance and proof + +Read the recipe's executable examples against the CLI parser and management +route signatures. Run a smoke only in a disposable OS account/home, +container, or VM with redirected client homes and integrations disabled; +otherwise record it as unrun and leave request behavior to CI. `rg` of the +final linked paths and `bun run +privacy:scan` observe the docs. A docs-only CI skip is recorded as skipped, not +as a passing suite. PR template Summary/Verification/Checklist, source author +credit, exact-head required checks, and post-merge `dev` CI still apply. diff --git a/devlog/_plan/260927_release_train_4/clients-proxy/020_macos_proxy.md b/devlog/_plan/260927_release_train_4/clients-proxy/020_macos_proxy.md new file mode 100644 index 00000000000..e7f139832b7 --- /dev/null +++ b/devlog/_plan/260927_release_train_4/clients-proxy/020_macos_proxy.md @@ -0,0 +1,59 @@ +# Phase 2: macOS system proxy discovery (#5893) + +Depends on `010_recipe.md` for lane order. Carry the small proxy change after +rebasing its draft head onto current `dev`; do not change Windows discovery or +explicit proxy precedence. `structure/config-proxy.md:1-20` owns the contract. + +## Exact change map + +- NEW `src/config/macos-system-proxy.ts`: observe and parse macOS system + proxy settings only on Darwin; return no discovery for disabled, malformed, + or unavailable settings. No caller imports this module on every request. +- MODIFY `src/config/proxy-env.ts`: before, `proxy: "auto"` considers the + existing Windows path and merges loopback bypasses. After, Darwin discovery + is considered only for that explicit setting and when no inherited scheme + proxy wins. Translate macOS exceptions only when their matching semantics + are proven equivalent to Bun's `no_proxy` semantics. A bare name such as + `localhost` must not enter either effective proxy-bypass variable as a + suffix. If any system exception is not faithfully representable, refuse + macOS auto-discovery and leave process proxy variables unchanged with a + privacy-safe diagnostic; silently dropping it could send an intended direct + host through the proxy. Preserve the address-only loopback bypass. Add + proven-safe entries to the bypass variable the selected HTTP(S) transport + actually reads. If inherited `ALL_PROXY`/`all_proxy` selects SOCKS, + macOS discovery must not add scheme proxies or discovered exceptions: + the SOCKS wrapper reads uppercase `NO_PROXY` while Bun reads lowercase + `no_proxy`. Preserve the inherited proxy path and assert both transports' + effective routes when the two bypass variables disagree. Redact + credential-bearing proxy URLs. +- MODIFY `tests/server/proxy-env.test.ts`: retain source tests and add a case + with lowercase `no_proxy` distinct from uppercase `NO_PROXY`; assert the + effective bypass after activation. Drive a request to `localhost` and + `app.localhost` (or the proxy matcher used by that request) and prove that + the exception does not widen direct egress. An unrepresentable exception + must refuse discovery before the normal `mergeNoProxyEntries` tail; assert + a full byte-identical snapshot of `HTTP_PROXY`, `HTTPS_PROXY`, lowercase + equivalents, `ALL_PROXY`, `all_proxy`, `NO_PROXY`, and `no_proxy`. Cover + safe wildcard/IP entries, malformed/disabled `scutil` output, explicit + environment precedence, `proxy` unset, inherited SOCKS `ALL_PROXY` with + conflicting uppercase/lowercase bypass lists, and unchanged Windows + behavior. Tests use + a mocked system command; they do not claim a real macOS Settings session. +- MODIFY `structure/config-proxy.md` and the English plus affected translated + `docs-site/src/content/docs/*/reference/configuration/server.md` pages to + state the actual opt-in/automatic precedence after code is verified. + +## Acceptance and proof + +Activation scenario: Darwin with `config.proxy: "auto"`, no inherited scheme +proxy, and valid system settings sets the proxy and safely representable +bypass list; inherited lowercase `no_proxy` remains the effective source. +Negative scenarios: unset `config.proxy` never reads system settings or mutates +egress, inherited HTTP(S) or SOCKS proxy wins without mixed bypass semantics, +unrepresentable exceptions refuse before any environment write, disabled or +bad system settings leave egress unchanged, and Windows keeps its prior route. Run +`bun test tests/server/proxy-env.test.ts`, `bun run test:changed`, `bun run +typecheck`, `bun run structure:check`, and `bun run privacy:scan`. Build +`docs-site/` if docs change. `tests/lab/core-lab-boundary.test.ts` checks the +core import rule. Perform explicit security review of credential-bearing +proxy URL handling. Recheck exact-head CI and live `dev` CI before the next batch. diff --git a/devlog/_plan/260927_release_train_4/clients-proxy/030_qoder.md b/devlog/_plan/260927_release_train_4/clients-proxy/030_qoder.md new file mode 100644 index 00000000000..b80d8c1ba45 --- /dev/null +++ b/devlog/_plan/260927_release_train_4/clients-proxy/030_qoder.md @@ -0,0 +1,46 @@ +# Phase 3: opt-in Qoder client (#5950) + +Depends on the preceding lane batch's `dev` result; Qoder reuses the existing +pure export, registry and journaled writer seams rather than adding request +path code. The source PR touches 36 files, including GUI and translations; +carry one coherent client slice and remove unrelated drift. + +## Exact change map + +- NEW `src/clients/config-export/qoder.ts`: build the documented provider + contribution and exact managed fragment paths. Loopback may use a + non-secret placeholder; remote bind must have a supported admission header + or refuse. +- MODIFY `src/clients/config-export/contracts.ts` and + `src/clients/config-export.ts`: before, Qoder is absent from the export ID + union/registry. After, `qoder` is a named opt-in export with a derived + roster count, not a hand-written total. +- MODIFY `src/integrations/registry.ts` and `mutation-plan.ts`: resolve a + Qoder-supported user path, validate file/directory safety, and use the + common status/preview/apply/disable/restore classifier. No automatic + detection write or core request-path import. +- MODIFY `src/cli/help.ts`, `src/cli/registry.ts`, the GUI integration lists, + routing, marks, API IDs, and affected locales to expose the same client ID. + Update `docs-site/src/content/docs/guides/integrations.md`, + `structure/clients/integrations.md`, and + `structure/dashboard-and-usage.md` in the same change. +- MODIFY/NEW tests under `tests/clients/`, `tests/config/`, `tests/gui/`, and + `gui/tests/` for exact generated shape, absent client, foreign keys, + symlink/unsafe path refusal, drift, snapshot-before-write, disable, and + byte-exact restore. Register new test names in both test-layout manifests. + +## Acceptance and proof + +Activation: an operator explicitly enables Qoder against a disposable config; +the generated provider is present and a later disable/restore recovers prior +bytes. A hostile or changed file refuses without overwrite. Windows path +tests use a Windows-shaped home/env and confirm no POSIX-only assumption. +Run `bun test tests/clients/qoder-client.test.ts +tests/clients/integrations-state.test.ts +tests/config/client-config-export-new-clients.test.ts`, relevant `gui/tests/`, +`bun run test:changed`, `bun run typecheck`, `bun run lint:gui`, +`bun run build:gui`, `bun run structure:check`, `bun run privacy:scan`, +and `bun run skill:surface:check` if the capability registry changes. +Perform explicit security review of admission and config serialization. Check +the file-size ratchet, test-layout manifests, locale union, screenshot, +exact-head required CI, and merged `dev` run. diff --git a/devlog/_plan/260927_release_train_4/clients-proxy/040_kilo.md b/devlog/_plan/260927_release_train_4/clients-proxy/040_kilo.md new file mode 100644 index 00000000000..f7e578e92d4 --- /dev/null +++ b/devlog/_plan/260927_release_train_4/clients-proxy/040_kilo.md @@ -0,0 +1,42 @@ +# Phase 4: Kilo managed config (#5272) + +Depends on `030_qoder.md` to reconcile the shared export-client union and GUI +roster once per current `dev` head. The PR's 57-file slice includes a JSONC +writer extension; preserve comments and all unrelated client state. + +## Exact change map + +- NEW `src/clients/config-export/kilo.ts`: generate the documented Kilo + provider block and resolve the active global config path. Before, no Kilo + export exists. After, a config is selected only when later legacy files + cannot override its managed `provider.opencodex` block. If two candidate + files can supply that block, status and apply refuse with a clear conflict; + no first-file-wins write that appears successful but is ineffective. +- MODIFY `src/clients/config-export.ts`, `contracts.ts`, + `src/integrations/registry.ts`, `target.ts`, `state.ts`, `writer.ts`, + `mutation-plan.ts`, `config-io.ts`, and `src/lib/jsonc.ts` only as required + for source-preserving JSONC and the common ownership contract. The parser + must reject non-roundtrippable syntax before mutation and keep unrelated + comment-bearing bytes recoverable from the snapshot. +- MODIFY the CLI export/help/registry entries, GUI integration registry and + affected locale keys, public integration documentation, and + `structure/clients/integrations.md` for the actual Kilo path. +- NEW/MODIFY `tests/clients/kilo-client.test.ts` and adjacent config/GUI + tests: add a two-file precedence conflict fixture with distinct provider + values, byte-exact restore of an initial comment-bearing file, unsafe path + refusal, and Windows-shaped home/path resolution. Register test files in + both test-layout manifests. + +## Acceptance and proof + +Activation: explicit apply to an unambiguous Kilo install writes only owned +fields; disabling and restoring leave foreign JSONC and original bytes intact. +Conflict activation: a later candidate file contains the same provider key; +status and mutation both refuse before snapshot/write. Run +`bun test tests/clients/kilo-client.test.ts +tests/config/client-config-export.test.ts`, the relevant `gui/tests`, +`bun run test:changed`, `bun run typecheck`, `bun run lint:gui`, +`bun run build:gui`, `bun run structure:check`, `bun run privacy:scan`, +and `bun run skill:surface:check` if capabilities change. Check screenshot, +merged file-size cap, union/locale counts, explicit credential/path security +review, exact-head CI, and post-merge `dev`. diff --git a/devlog/_plan/260927_release_train_4/clients-proxy/050_droid.md b/devlog/_plan/260927_release_train_4/clients-proxy/050_droid.md new file mode 100644 index 00000000000..c33d2aa7b26 --- /dev/null +++ b/devlog/_plan/260927_release_train_4/clients-proxy/050_droid.md @@ -0,0 +1,42 @@ +# Phase 5: focused Factory Droid integration (#5193) + +Depends on `040_kilo.md` for shared roster reconciliation. The source PR +conflicts with current `dev` and changes 83 files, including broad export +behavior and unrelated test harnesses. Reimplement the narrow client thesis; +if the installed Droid contract cannot be verified, record a hold and leave +the source PR open with a reason. + +## Exact change map if verified + +- NEW `src/clients/config-export/droid.ts`: build only Droid's documented + settings and per-model rows using the documented user settings path. Do not + add an automatic startup/config write or copy provider credentials. +- MODIFY `src/clients/config-export.ts`, `contracts.ts`, + `src/integrations/registry.ts`, and `mutation-plan.ts` to add the typed ID + and exact managed fragments. Before, Droid is absent. After, explicit + export/enable uses the shared journal and restore path. +- MODIFY `src/cli/export-command.ts` only if Droid needs a distinct + loopback catalog source. Preserve the existing catalog provenance for every + other loopback-only client; the source PR's all-client redirect is not + accepted without a separate proof. Update CLI help, GUI roster/locales, + `docs-site/src/content/docs/guides/integrations.md`, and + `structure/clients/integrations.md` for the verified client slice. +- NEW `tests/clients/droid-client.test.ts`: assert exact client-consumed + settings, foreign model preservation, symlink/unsafe path refusal, Windows + path, drift, disable and exact-byte restore. MODIFY + `tests/cli/cli-export-command.test.ts` to prove existing clients retain + their old catalog/selection source. Register the new test in both manifests. + +## Acceptance and proof + +Activation: a disposable Droid config is explicitly enabled and subsequently +restored. Negative: a changed user model or unsafe target refuses before +overwrite, and a non-Droid loopback client exports the same catalog as before. +Run `bun test tests/clients/droid-client.test.ts +tests/cli/cli-export-command.test.ts`, relevant integration and GUI tests, +`bun run test:changed`, `bun run typecheck`, `bun run lint:gui`, +`bun run build:gui`, `bun run structure:check`, `bun run privacy:scan`, and +surface check if needed. Do not claim live Droid behavior from a synthetic +fixture alone; verify the documented client schema before committing the +implementation. Explicit credential/path security review, a GUI screenshot, +and exact-head CI precede merge. diff --git a/devlog/_plan/260927_release_train_4/clients-proxy/060_jev.md b/devlog/_plan/260927_release_train_4/clients-proxy/060_jev.md new file mode 100644 index 00000000000..1970ba9786e --- /dev/null +++ b/devlog/_plan/260927_release_train_4/clients-proxy/060_jev.md @@ -0,0 +1,40 @@ +# Phase 6: operator JEV model profiles (#5871) + +Depends on the shared type/roster reconciliation from prior batches; the PR +currently conflicts with `dev`. Keep profile settings optional and off the +single-provider/no-profile request path. + +## Exact change map + +- MODIFY `src/types/config.ts` and `src/combos/types.ts`: add an optional + target-keyed profile shape. Trace creation in management input, persistence + through config serialization/deserialization, and consumption by JEV; a + missing profile retains the old request shape. +- MODIFY `src/combos/jev.ts`: insert an operator-authored per-target note into + the outbound decision payload while retaining existing candidate bounds + and built-in profile behavior. Validate and bound that freeform text at the + input boundary, document that it is sent to the decision provider, and + review privacy/security implications explicitly. Do not widen the target + model set or create a new unsolicited model request. +- MODIFY `src/server/management/combo-routes.ts`, + `src/server/responses/core-combo.ts`, GUI combo workspace controls/data, + relevant locales, `docs-site/src/content/docs/guides/combos.md`, and + `structure/providers-and-adapters.md` to expose and describe that same + optional shape. Keep locale keys exhaustive. +- MODIFY `tests/routing/jev-decision.test.ts` to capture the actual outbound + request and assert the selected target note appears. Also test absent + profile, wrong target, and bound candidates; update management and GUI tests. + +## Acceptance and proof + +Activation: configure a note for target A, invoke JEV with A, and observe it in +the outbound decision payload; invoke B/absent profile and observe the prior +payload. Run `bun test tests/routing/jev-decision.test.ts +tests/routing/combo-management-api.test.ts`, relevant `gui/tests`, +`bun run test:changed`, `bun run typecheck`, `bun run lint:gui`, +`bun run build:gui`, `bun run structure:check`, `bun run privacy:scan`, +and `bun test tests/lab/core-lab-boundary.test.ts`. Recheck the current-base +union/locale and file-size ratchet; inspect the actual transmitted note, +its bounds, privacy handling, and user-facing disclosure in a security review. +GUI screenshot and exact-head CI are +required before merge. diff --git a/devlog/_plan/260927_release_train_4/clients-proxy/070_memory.md b/devlog/_plan/260927_release_train_4/clients-proxy/070_memory.md new file mode 100644 index 00000000000..0538c26c26a --- /dev/null +++ b/devlog/_plan/260927_release_train_4/clients-proxy/070_memory.md @@ -0,0 +1,58 @@ +# Phase 7: selected models for Codex memory (#5983) + +Depends on `060_jev.md` for serial changes to `src/types/config.ts`. The source +PR spans 51 files. Its metadata classifier currently falls back to the +`x-openai-subagent` header after explicit non-memory turn metadata and can +reroute an ordinary turn; that edge must fail closed before any carry. + +## Exact change map + +- NEW `src/server/responses/memory-models.ts`: classify memory phases from + validated turn metadata. Before: no memory-specific target. After: an + explicit `none`/non-memory metadata result returns no memory route, and the + legacy subagent header is consulted only when metadata is absent. Reject + malformed target settings without silently selecting a different model. +- MODIFY `src/server/responses/request-prepare.ts` and the related normalize, + options, availability, and config modules only to thread the selected + memory target through both HTTP and WebSocket admission. Do not import Lab + from `src/server/responses/core.ts`, `src/router.ts`, or + `src/server/lifecycle.ts`; do not add a timer for no-memory users. +- MODIFY `src/types/config.ts`, `src/types/request.ts`, config schema/leaf + validation, CLI/config docs, management config route, and GUI Memory panel + and locale keys so input, persisted value, reload and consumers agree. No + hand-counted preset/capability totals. +- REVIEW and MODIFY the relevant mapped source-of-truth documents: + `structure/config.md` for the persisted setting, + `structure/transports/responses.md` and + `structure/transports/responses-failover.md` for routing behavior, + `structure/gui-and-management-api.md` for settings exposure, and + `structure/providers-and-adapters.md` for `src/types/` ownership. + Check the other documents mapped to `src/server/` in + `structure/INDEX.md`; update any whose described contract changes. +- NEW `tests/responses/responses-memory-models.test.ts`: send explicit + non-memory metadata plus a subagent header and assert the normal model + serves the request. Cover real memory metadata, absent metadata fallback, + unavailable selected target, HTTP and WebSocket entry, and no-memory + baseline. The WebSocket case must enter through actual WebSocket admission, + not merely call the classifier with `transport: "websocket"`. +- NEW `tests/config/settings-memory-models.test.ts`: cover accepted and + rejected persisted memory targets, load degradation, and management-save + behavior without dropping unrelated config. +- MODIFY relevant `gui/tests` for model selection and disabled/unknown + targets. Register both new test files in `scripts/test-layout/layout.json` + and `tests/fixtures/test-layout-expected.json`. + +## Acceptance and proof + +Activation: a memory-phase request with configured target routes there; +explicit non-memory metadata never routes there even with the fallback header; +no setting retains current behavior. Run `bun test +tests/responses/responses-memory-models.test.ts +tests/responses/responses-shadow-intercept.test.ts +tests/config/settings-memory-models.test.ts`, a WebSocket entry-path +regression, and the relevant `gui/tests`, +`bun run test:changed`, `bun run typecheck`, `bun run lint:gui`, +`bun run build:gui`, `bun run structure:check`, `bun run privacy:scan`, +and `bun test tests/lab/core-lab-boundary.test.ts`. Inspect user-facing +English/translated docs, current merged type unions and file-size caps. +Require GUI screenshot and exact-head CI before merge. diff --git a/devlog/_plan/260927_release_train_4/clients-proxy/080_held_items.md b/devlog/_plan/260927_release_train_4/clients-proxy/080_held_items.md new file mode 100644 index 00000000000..e4b1bf32ad1 --- /dev/null +++ b/devlog/_plan/260927_release_train_4/clients-proxy/080_held_items.md @@ -0,0 +1,38 @@ +# Phase 8: source PR and issue disposition + +Depends on outcomes from `010`-`070`. This is a GitHub triage phase, not a +product-code batch. It is complete only when each row has a current link and +the correct open/closed state. Comments are in English and name the specific +missing proof or replaced PR. Do not close a source PR until its replacement +has merged into `dev`; leave a genuine enhancement open when held. + +## Exact external change map + +- COMMENT, then CLOSE replaced source PRs #6051, #5893, #5950, #5272, + #5193, #5871, #5983 only if the corresponding carried behavior actually + landed. Include the lane PR and merge SHA and thank the original author. +- COMMENT, KEEP OPEN #5905: opening Cursor status currently fetches a remote + installer manifest without a user action. Ask for an explicit discovery + policy and timeout/status regression. Keep draft and no installer launch. +- COMMENT, KEEP OPEN #3833: the literal `apiKey` placeholder in its export is + rejected by Command Code; require a documented supported keyless/reference + form and client-side proof, then refresh against `dev` and security review. +- COMMENT, KEEP OPEN #4854: require OpenScience config path/schema and + override/restore ownership evidence; the manual endpoint remains usable. +- COMMENT, KEEP OPEN #3494: require one named VS Code extension's officially + supported settings, reload behavior, and per-scope ownership contract. +- COMMENT, KEEP OPEN #1416: require a versioned, secret-free Orca launch + manifest that can be generated while the proxy is stopped; do not insert + it into live model export before the consumer schema is agreed. +- COMMENT, KEEP OPEN #2811: record the design-only judgment. #5016 was + closed unmerged because `managed: true` was unreachable from the production + inspector. Establish a real provenance predicate and read-only plan before + considering an apply mutation. +- CLOSE linked #5853, #5660, and #5982 only when the exact behavior is on + `dev`, with the lane merge link. #5679 remains open while #5905 is held. + +## Acceptance and proof + +Fetch each PR/issue after each comment/close and verify state and URL. Do not +count a `gh` command's exit alone as proof. The issue-close list is conditional +on actual merged outcomes. Re-read source authors for attribution trailers. diff --git a/devlog/_plan/260927_release_train_4/clients-proxy/090_final_ci.md b/devlog/_plan/260927_release_train_4/clients-proxy/090_final_ci.md new file mode 100644 index 00000000000..b91f5043f86 --- /dev/null +++ b/devlog/_plan/260927_release_train_4/clients-proxy/090_final_ci.md @@ -0,0 +1,26 @@ +# Phase 9: final integration and CI + +Depends on all selected carries and triage. This phase writes no product code +unless the last `dev` run exposes a lane-owned regression; any repair gets its +own new PABCD work phase and ordinary PR. + +## Exact evidence map + +- MODIFY `devlog/_plan/260927_release_train_4/clients-proxy/000_plan.md` + disposition rows when outcomes change. Before: candidate judgments at + `origin/dev` `24b2f39b77`; after: each row names actual lane PR, merge SHA, + source PR/issue state, and any residual hold. +- NEW a numbered outcome file under this unit, recording each PR head and + merge SHA, focused commands and their exits, required CI run IDs/URLs, + final `dev` run URL, and remaining cross-lane file overlaps. + +## Acceptance and proof + +Fetch latest `origin/dev`; for every lane PR, retain the exact pre-merge PR +head SHA and required-job run IDs that passed before merge. Separately inspect +the post-merge `dev` workflow on the integrated commit. Missing, skipped, +cancelled, pending, failed, and older-head results do not count as passing. +Compare changed paths +against other lane overlap in the final report. `git status --short` must +contain no unaccounted files, and each source PR/issue closure must point to +the actual integrated SHA. From 8c570a225468ee8c4b7cc3b966b10fc09c6f4e27 Mon Sep 17 00:00:00 2001 From: JUN Date: Sun, 27 Sep 2026 23:54:30 +0900 Subject: [PATCH 02/47] docs(skills): add isolated management API test recipe Carries the development recipe from #6051 and links it from contributor guidance. Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com> --- .../testing-opencodex-management-api/SKILL.md | 108 ++++++++++++++++++ AGENTS.md | 3 + .../clients-proxy/010_recipe.md | 70 ++++++++++-- 3 files changed, 169 insertions(+), 12 deletions(-) create mode 100644 .agents/skills/testing-opencodex-management-api/SKILL.md diff --git a/.agents/skills/testing-opencodex-management-api/SKILL.md b/.agents/skills/testing-opencodex-management-api/SKILL.md new file mode 100644 index 00000000000..48cb900277c --- /dev/null +++ b/.agents/skills/testing-opencodex-management-api/SKILL.md @@ -0,0 +1,108 @@ +--- +name: testing-opencodex-management-api +description: Exercise the OpenCodex management API in a disposable, isolated development environment without touching personal client state. +--- + +# Testing the OpenCodex management API + +## Isolation is a prerequisite + +Use a disposable OS account, container, or VM with a disposable OS home. Do not run this +recipe in your normal desktop account merely by changing `OPENCODEX_HOME`. +That variable relocates OpenCodex state, not every client or shell integration. +On macOS, even disabling `claudeCode.systemEnv` can remove an existing managed block +from the OS home's `.zshrc`; `CLAUDE_CONFIG_DIR` does not redirect that file. +Raycast integration can also update existing OpenCodex-owned entries under the OS home. +A temporary client directory alone is therefore not a complete isolation boundary. + +Within the disposable environment, allocate a unique scratch directory and set all of +`OPENCODEX_HOME`, `CODEX_HOME`, `GROK_HOME`, `CLAUDE_CONFIG_DIR`, and +`OPENCODEX_CLAUDE_DESKTOP_CONFIG_DIR` to distinct directories inside it before startup. +Confirm the effective OS home belongs to the disposable account. Do not copy personal +tokens, client configuration, shell profiles, or keychain contents into this environment. + +Save a scratch `config.json` under `OPENCODEX_HOME` with an unused loopback port: + +```json +{ + "port": 19100, + "hostname": "127.0.0.1", + "codexAutoStart": false, + "clientIntegrations": {"codex": false, "grok": false, "claude-desktop": false}, + "claudeCode": {"enabled": false, "injectAgents": false, "systemEnv": false} +} +``` + +Use both redirected client homes and integration disables. Disabled integrations may +still remove owned artifacts. `codexAutoStart` alone does not disable startup sync: +desired-state checks also consider integration settings and the hub/loopback-listener +role. Do not depend on any one flag as an isolation boundary. + +## Start and authenticate + +Install the repository's locked development dependencies and use the Bun version named +by `package.json`. Check that the selected Bun executable is available in this shell; +do not assume a particular developer's PATH layout. Start one foreground instance: + +```sh +bun run src/cli/index.ts start --port 19100 +``` + +Avoid `ensure`, tray, and service installation paths for this exercise: they can spawn +detached processes or alter persistent service state. Do not enable live providers or +submit billable traffic unless that separate test is explicitly authorized. + +Prefer reading the scratch instance's generated `admin-api-token` locally. Alternatively, +provision a randomly generated `OPENCODEX_ADMIN_AUTH_TOKEN` used only for this test. +It must differ from every data-plane API key; a collision makes management authentication +unavailable. Never paste the token into a PR, screenshot, log, or tracked fixture. + +Management requests accept `x-opencodex-api-key: ` or +`Authorization: Bearer `. Missing authorization is refused. A valid token +does not bypass route-specific origin, session, or policy requirements. Keep requests +loopback-only and do not follow redirects with credentials. + +## Focused Lab automation exercise + +Read `GET /api/lab/automation` for policy and live scheduler state; inspect recorded runs +with `GET /api/lab/automation/runs`. Enabling automation is an explicit state change, +not a requirement for a basic management-authentication test. + +A policy write uses `PUT /api/lab/automation`, for example: + +```json +{"policy":{"enabled":true,"layers":{"protocolConformance":true}}} +``` + +Serialize policy writes. The read/merge and save do not share one lock, so concurrent +writers can overwrite each other's changes even though publication itself is atomic. +Re-read the policy after changing it. + +A fixture-only manual run uses `POST /api/lab/automation/run` with this request body: + +```json +{"evidenceLayer":"protocol_conformance","scenarioId":"responses-core.protocol.request-shape"} +``` + +For `live_route_compatibility`, include `providerName` and `modelId` in the POST request +body, not as substitute top-level configuration fields. The named provider must already +exist in `config.providers`, and live calls require authorization and suitable test +credentials. Consult `planManualLabRun` in `src/lab/automation/planner.ts` for accepted +combinations instead of guessing a scenario or provider. + +The manual endpoint awaits dispatch and returns a run/trigger result. Inspect the returned +status rather than assuming success or a terminal run. Scheduler work is separate and +may not appear immediately; read the configured scheduler limits instead of sleeping for +a hard-coded interval. + +## Stop and inspect + +Send one interrupt to the foreground process and let its bounded cleanup/drain finish. +A clean shutdown exits with zero; cleanup or drain failures may exit nonzero. A second +signal requests forced termination and is not proof of successful cleanup. +Check that the test listener and any test-owned children have stopped before removing +the exact scratch tree. Do not clean directories based on a name pattern or age. +Capture only redacted status, exit code, exact test commands, and observed results. + +This is a development testing recipe. It does not replace the operating reference in +`skills/ocx/` or the consent rules in `AGENTS_INSTALL.md`. diff --git a/AGENTS.md b/AGENTS.md index 64c6c3106fc..e4c1ce1b41f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -208,6 +208,9 @@ bun run build:gui # Vite GUI build proxy, as opposed to [`AGENTS_INSTALL.md`](./AGENTS_INSTALL.md) (installing and operating consent) or this file (changing the codebase). Its surface map is generated: +For development tests of the management API, use the isolated +[management API test recipe](./.agents/skills/testing-opencodex-management-api/SKILL.md). + ```bash bun run skill:surface # regenerate after adding a capability bun run skill:surface:check # what CI asserts diff --git a/devlog/_plan/260927_release_train_4/clients-proxy/010_recipe.md b/devlog/_plan/260927_release_train_4/clients-proxy/010_recipe.md index 1a2904d7698..82dacd6b4d3 100644 --- a/devlog/_plan/260927_release_train_4/clients-proxy/010_recipe.md +++ b/devlog/_plan/260927_release_train_4/clients-proxy/010_recipe.md @@ -1,6 +1,8 @@ # Phase 1: management API test recipe (#6051) -Depends on `000_plan.md`; docs-only carry, then one ordinary PR. The source PR +The preceding D concluded that the reviewed roadmap is locked at `d264a776fa`; +the next action is this bounded recipe carry. Depends on `000_plan.md`; +docs-only carry, then one ordinary PR. The source PR adds `.agents/skills/testing-opencodex-management-api/SKILL.md` with a disposable-home setup and correct Lab requests. It needs a repository entry point before another agent can reliably discover it. @@ -8,7 +10,8 @@ point before another agent can reliably discover it. ## Exact change map - NEW `.agents/skills/testing-opencodex-management-api/SKILL.md`: carry the - source recipe after verifying every command and path against current + 108-line source recipe from #6051 head `987b8097624e50e6c39b00aca145fe4755043c4b` + after verifying every command and path against current management routes. Preserve its disposable OS account/home, container, or VM prerequisite; redirect client homes and disable integrations before a smoke. `OPENCODEX_HOME` alone does not isolate client writes. Keep explicit @@ -16,19 +19,62 @@ point before another agent can reliably discover it. requests. No secret values or real account identifiers enter examples. - MODIFY `AGENTS.md` near the Commands and `skills/ocx/` guidance: add one contributor-facing link to the test recipe. Before: only the runtime-control - `skills/ocx/` reference is discoverable. After: code-change agents can find - the isolated management API test procedure without treating it as a user - operating skill. -- MODIFY this unit's outcome document after validation with the carried source - SHA, attribution, and exact command results. + `skills/ocx/` reference is discoverable (`AGENTS.md:207-209`). After: one + sentence identifies `.agents/skills/testing-opencodex-management-api/SKILL.md` + as the development test recipe, while `skills/ocx/` remains the operating + reference and `AGENTS_INSTALL.md` retains consent guidance. Do not change + runtime imports, CLI capabilities, or the generated operating-surface map. +- MODIFY `010_recipe.md` with a short outcome addendum after validation, + naming the carried source SHA, attribution, and exact command results. The + carry commit or PR body + must contain `Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>`. ## Acceptance and proof -Read the recipe's executable examples against the CLI parser and management -route signatures. Run a smoke only in a disposable OS account/home, +Read the recipe's executable examples against +`src/server/management/lab-automation-routes.ts:119-180` and +`src/lab/automation/planner.ts:278-340`; POST fields are body fields and PUT +policy fields are supported there. `bun test +tests/lab/lab-automation-management-http.test.ts` ran at the base and passed +2 tests; it checks route cancel/pagination, not the prose or POST example. +Run a smoke only in a disposable OS account/home, container, or VM with redirected client homes and integrations disabled; -otherwise record it as unrun and leave request behavior to CI. `rg` of the -final linked paths and `bun run -privacy:scan` observe the docs. A docs-only CI skip is recorded as skipped, not +the current desktop account does not meet this precondition, so record the +smoke as unrun. Confirm the carried file against the pinned PR head, its +frontmatter and link target. Stage every changed and new file before running +`git diff --cached --check` and `bun run privacy:scan`; the scan uses +`git ls-files`, so an untracked skill would be invisible. After commit run +`git diff origin/dev...HEAD --check`. Also run `bun run structure:check` and +`bun run typecheck`. These commands protect +the tree and paths; semantic correctness of the recipe needs source review. +`bun run test:changed` can select zero tests for a docs-only diff and is then +not passing evidence. A docs-only CI skip is recorded as skipped, not as a passing suite. PR template Summary/Verification/Checklist, source author credit, exact-head required checks, and post-merge `dev` CI still apply. + +Architect `01a0e33a-811b-7471-a32a-52455283082e` proposed D1-R (carry the +isolation and route examples), D1-L (one AGENTS discovery link), and D1-V +(attribution and exact gates). All three are accepted. Putting the recipe in +`skills/ocx/` would confuse development tests with operating guidance; a +PR-only link would not be durable. + +## Local carry outcome + +Copied the recipe byte-for-byte from #6051 head +`987b8097624e50e6c39b00aca145fe4755043c4b`; `cmp` against that Git +object exited 0 and the file has 108 lines. `AGENTS.md:212` links it beside +the operating reference. The same independent A reviewer first found that +an unstaged whitespace check would miss a staged change and the privacy scan +would miss an untracked skill; the plan now stages all files before both gates, +and the reviewer returned PASS. + +After staging, `git diff --cached --check`, `bun run privacy:scan`, +`bun run structure:check`, and `bun run typecheck` exited 0. `bun test +tests/lab/lab-automation-management-http.test.ts +tests/lab/lab-automation.test.ts` passed 24 tests with 0 failures. These tests +cover the route and planner baseline, not the prose; route and planner source +were read against the example fields. `bun run test:changed` exited 1 because +the docs-only diff selected 0 tests. The live smoke was not run in this +desktop account: it lacks the disposable OS-home prerequisite. Full local +suite is omitted due to concurrent lane worktrees; CI remains the broader +gate. PR-head and post-merge `dev` CI evidence are recorded after publication. From d446876bcb5e65d1b681eb6080cf8b2195d4820a Mon Sep 17 00:00:00 2001 From: JUN Date: Mon, 28 Sep 2026 00:02:52 +0900 Subject: [PATCH 03/47] docs(skills): isolate Codex state before management API tests Redirect SQLite state and require Lab activation at startup for optional live route exercises. Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com> --- .../testing-opencodex-management-api/SKILL.md | 11 +++++++-- .../clients-proxy/010_recipe.md | 24 +++++++++++++++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/.agents/skills/testing-opencodex-management-api/SKILL.md b/.agents/skills/testing-opencodex-management-api/SKILL.md index 48cb900277c..bcbfdb4d8ae 100644 --- a/.agents/skills/testing-opencodex-management-api/SKILL.md +++ b/.agents/skills/testing-opencodex-management-api/SKILL.md @@ -16,10 +16,12 @@ Raycast integration can also update existing OpenCodex-owned entries under the O A temporary client directory alone is therefore not a complete isolation boundary. Within the disposable environment, allocate a unique scratch directory and set all of -`OPENCODEX_HOME`, `CODEX_HOME`, `GROK_HOME`, `CLAUDE_CONFIG_DIR`, and +`OPENCODEX_HOME`, `CODEX_HOME`, `CODEX_SQLITE_HOME`, `GROK_HOME`, `CLAUDE_CONFIG_DIR`, and `OPENCODEX_CLAUDE_DESKTOP_CONFIG_DIR` to distinct directories inside it before startup. Confirm the effective OS home belongs to the disposable account. Do not copy personal tokens, client configuration, shell profiles, or keychain contents into this environment. +Start from a clean environment or inspect inherited path overrides before launching; +`CODEX_SQLITE_HOME` otherwise takes precedence over `CODEX_HOME` for Codex state. Save a scratch `config.json` under `OPENCODEX_HOME` with an unused loopback port: @@ -28,6 +30,7 @@ Save a scratch `config.json` under `OPENCODEX_HOME` with an unused loopback port "port": 19100, "hostname": "127.0.0.1", "codexAutoStart": false, + "syncResumeHistory": false, "clientIntegrations": {"codex": false, "grok": false, "claude-desktop": false}, "claudeCode": {"enabled": false, "injectAgents": false, "systemEnv": false} } @@ -87,7 +90,11 @@ A fixture-only manual run uses `POST /api/lab/automation/run` with this request For `live_route_compatibility`, include `providerName` and `modelId` in the POST request body, not as substitute top-level configuration fields. The named provider must already exist in `config.providers`, and live calls require authorization and suitable test -credentials. Consult `planManualLabRun` in `src/lab/automation/planner.ts` for accepted +credentials. Lab must also be active at proxy startup: a later policy PUT alone does +not register the live route executor. Enable automation in the disposable home, stop +the foreground proxy, and start it again before a separately authorized live run. +The fixture-only protocol exercise above does not need this restart. Consult +`planManualLabRun` in `src/lab/automation/planner.ts` for accepted combinations instead of guessing a scenario or provider. The manual endpoint awaits dispatch and returns a run/trigger result. Inspect the returned diff --git a/devlog/_plan/260927_release_train_4/clients-proxy/010_recipe.md b/devlog/_plan/260927_release_train_4/clients-proxy/010_recipe.md index 82dacd6b4d3..d90b4e96d98 100644 --- a/devlog/_plan/260927_release_train_4/clients-proxy/010_recipe.md +++ b/devlog/_plan/260927_release_train_4/clients-proxy/010_recipe.md @@ -1,6 +1,6 @@ # Phase 1: management API test recipe (#6051) -The preceding D concluded that the reviewed roadmap is locked at `d264a776fa`; +The preceding D concluded that the reviewed roadmap is locked at `ce5a406862`; the next action is this bounded recipe carry. Depends on `000_plan.md`; docs-only carry, then one ordinary PR. The source PR adds `.agents/skills/testing-opencodex-management-api/SKILL.md` with a @@ -11,7 +11,7 @@ point before another agent can reliably discover it. - NEW `.agents/skills/testing-opencodex-management-api/SKILL.md`: carry the 108-line source recipe from #6051 head `987b8097624e50e6c39b00aca145fe4755043c4b` - after verifying every command and path against current + with source-verified isolation and activation corrections after checking every command and path against current management routes. Preserve its disposable OS account/home, container, or VM prerequisite; redirect client homes and disable integrations before a smoke. `OPENCODEX_HOME` alone does not isolate client writes. Keep explicit @@ -60,9 +60,14 @@ PR-only link would not be durable. ## Local carry outcome -Copied the recipe byte-for-byte from #6051 head -`987b8097624e50e6c39b00aca145fe4755043c4b`; `cmp` against that Git -object exited 0 and the file has 108 lines. `AGENTS.md:212` links it beside +Imported the recipe from #6051 head +`987b8097624e50e6c39b00aca145fe4755043c4b`; the initial `cmp` +against that Git object exited 0 at 108 lines. C-phase implementation review +then required two source-grounded corrections to the final copy: redirecting +Codex's SQLite home and disabling resume-history sync in the disposable +configuration, and activating Lab at startup before a separately authorized +live-route run. The final recipe therefore intentionally differs from the +source PR. `AGENTS.md:212` links it beside the operating reference. The same independent A reviewer first found that an unstaged whitespace check would miss a staged change and the privacy scan would miss an untracked skill; the plan now stages all files before both gates, @@ -78,3 +83,12 @@ the docs-only diff selected 0 tests. The live smoke was not run in this desktop account: it lacks the disposable OS-home prerequisite. Full local suite is omitted due to concurrent lane worktrees; CI remains the broader gate. PR-head and post-merge `dev` CI evidence are recorded after publication. + +After the C-phase corrections, the scratch `config.json` example parsed as +JSON with `syncResumeHistory: false`; `git diff --cached --check` and +`bun run privacy:scan` exited 0 on the staged revision. The independent +implementation reviewer rechecked the SQLite and Lab startup paths and +returned PASS. A separate token/isolation security reviewer also returned +PASS on the amended recipe. Neither reviewer ran the live smoke, and the +24-test route/planner run and typecheck predate only these documentation edits; +no runtime source changed between those checks and this revision. From b56ad993b81bd8fa644e601a71df5bf1ec420888 Mon Sep 17 00:00:00 2001 From: JUN Date: Mon, 28 Sep 2026 00:16:11 +0900 Subject: [PATCH 04/47] docs(devlog): dispatch exact-head dev CI after merges --- .../260927_release_train_4/clients-proxy/000_plan.md | 5 +++-- .../clients-proxy/090_final_ci.md | 12 +++++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/devlog/_plan/260927_release_train_4/clients-proxy/000_plan.md b/devlog/_plan/260927_release_train_4/clients-proxy/000_plan.md index 79012cfc11c..9706e76c930 100644 --- a/devlog/_plan/260927_release_train_4/clients-proxy/000_plan.md +++ b/devlog/_plan/260927_release_train_4/clients-proxy/000_plan.md @@ -89,8 +89,9 @@ credential, proxy, installer, or authentication boundary, then inspect required CI at the exact new PR head before merging. GUI changes need a screenshot in the PR description from the separate `pr-assets` branch, never committed to the PR branch. Merge only -when the new PR head contains the latest `origin/dev`; inspect the new `dev` run -before the next batch. +when the new PR head contains the latest `origin/dev`; dispatch `ci.yml` on +`dev` manually as specified in [090](090_final_ci.md) and inspect its exact +head before the next batch. ## Consultation and uncertainty diff --git a/devlog/_plan/260927_release_train_4/clients-proxy/090_final_ci.md b/devlog/_plan/260927_release_train_4/clients-proxy/090_final_ci.md index b91f5043f86..c80f76faf0d 100644 --- a/devlog/_plan/260927_release_train_4/clients-proxy/090_final_ci.md +++ b/devlog/_plan/260927_release_train_4/clients-proxy/090_final_ci.md @@ -17,9 +17,15 @@ own new PABCD work phase and ordinary PR. ## Acceptance and proof Fetch latest `origin/dev`; for every lane PR, retain the exact pre-merge PR -head SHA and required-job run IDs that passed before merge. Separately inspect -the post-merge `dev` workflow on the integrated commit. Missing, skipped, -cancelled, pending, failed, and older-head results do not count as passing. +head SHA and required-job run IDs that passed before merge. `ci.yml` does +not run on a push to `dev`, so after the merge resolve the integrated `dev` +commit and explicitly dispatch `gh workflow run ci.yml -R +lidge-jun/opencodex --ref dev -f lane=all`. Identify the resulting run by +`workflow_dispatch` event and exact `headSha`, then record its run ID, URL, +attempt, requested jobs and final conclusions. If `dev` moves before dispatch, +refresh the head and verify the run covers that newer integrated tree instead +of claiming evidence for an older SHA. Missing, skipped, cancelled, pending, +failed, and wrong-head results do not count as passing for requested jobs. Compare changed paths against other lane overlap in the final report. `git status --short` must contain no unaccounted files, and each source PR/issue closure must point to From 77abedec7cda520b08766ad04dbdb087f8513a06 Mon Sep 17 00:00:00 2001 From: JUN Date: Mon, 28 Sep 2026 00:18:34 +0900 Subject: [PATCH 05/47] docs(skills): require disposable HOME for proxy QA --- .agents/skills/testing-opencodex-management-api/SKILL.md | 4 +++- .../_plan/260927_release_train_4/clients-proxy/010_recipe.md | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.agents/skills/testing-opencodex-management-api/SKILL.md b/.agents/skills/testing-opencodex-management-api/SKILL.md index bcbfdb4d8ae..32c1a6441cf 100644 --- a/.agents/skills/testing-opencodex-management-api/SKILL.md +++ b/.agents/skills/testing-opencodex-management-api/SKILL.md @@ -15,7 +15,9 @@ from the OS home's `.zshrc`; `CLAUDE_CONFIG_DIR` does not redirect that file. Raycast integration can also update existing OpenCodex-owned entries under the OS home. A temporary client directory alone is therefore not a complete isolation boundary. -Within the disposable environment, allocate a unique scratch directory and set all of +Within the disposable environment, allocate a unique scratch directory and set `HOME` +to a fresh directory inside it before startup. A `HOME` override in a normal desktop +account is not a substitute for the disposable account, container, or VM. Set all of `OPENCODEX_HOME`, `CODEX_HOME`, `CODEX_SQLITE_HOME`, `GROK_HOME`, `CLAUDE_CONFIG_DIR`, and `OPENCODEX_CLAUDE_DESKTOP_CONFIG_DIR` to distinct directories inside it before startup. Confirm the effective OS home belongs to the disposable account. Do not copy personal diff --git a/devlog/_plan/260927_release_train_4/clients-proxy/010_recipe.md b/devlog/_plan/260927_release_train_4/clients-proxy/010_recipe.md index d90b4e96d98..2285c3c5e5f 100644 --- a/devlog/_plan/260927_release_train_4/clients-proxy/010_recipe.md +++ b/devlog/_plan/260927_release_train_4/clients-proxy/010_recipe.md @@ -66,7 +66,8 @@ against that Git object exited 0 at 108 lines. C-phase implementation review then required two source-grounded corrections to the final copy: redirecting Codex's SQLite home and disabling resume-history sync in the disposable configuration, and activating Lab at startup before a separately authorized -live-route run. The final recipe therefore intentionally differs from the +live-route run. The isolation instructions also require `HOME` to point into +the disposable scratch root. The final recipe therefore intentionally differs from the source PR. `AGENTS.md:212` links it beside the operating reference. The same independent A reviewer first found that an unstaged whitespace check would miss a staged change and the privacy scan From bf47a1047095860fffd56377bc28f271c6d27dc4 Mon Sep 17 00:00:00 2001 From: JUN Date: Mon, 28 Sep 2026 00:36:00 +0900 Subject: [PATCH 06/47] docs(skills): document sqlite_home precedence in management API recipe resolveCodexSqliteHome reads a root sqlite_home in CODEX_HOME/config.toml before CODEX_SQLITE_HOME, so the recipe now requires the effective SQLite home to resolve inside the scratch tree before startup. Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com> --- .agents/skills/testing-opencodex-management-api/SKILL.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.agents/skills/testing-opencodex-management-api/SKILL.md b/.agents/skills/testing-opencodex-management-api/SKILL.md index 32c1a6441cf..60f5453a2b6 100644 --- a/.agents/skills/testing-opencodex-management-api/SKILL.md +++ b/.agents/skills/testing-opencodex-management-api/SKILL.md @@ -22,8 +22,11 @@ account is not a substitute for the disposable account, container, or VM. Set al `OPENCODEX_CLAUDE_DESKTOP_CONFIG_DIR` to distinct directories inside it before startup. Confirm the effective OS home belongs to the disposable account. Do not copy personal tokens, client configuration, shell profiles, or keychain contents into this environment. -Start from a clean environment or inspect inherited path overrides before launching; -`CODEX_SQLITE_HOME` otherwise takes precedence over `CODEX_HOME` for Codex state. +Start from a clean environment or inspect inherited path overrides before launching. +Codex SQLite state resolves in this order: a root `sqlite_home` key in +`CODEX_HOME/config.toml`, then `CODEX_SQLITE_HOME`, then `CODEX_HOME` itself. Keep the +scratch `CODEX_HOME/config.toml` free of an outside `sqlite_home`, resolve the effective +SQLite home with that precedence, and abort unless it is inside the scratch directory. Save a scratch `config.json` under `OPENCODEX_HOME` with an unused loopback port: From d71b37e4faa66be381d8d7709cb89265d0e2ef39 Mon Sep 17 00:00:00 2001 From: JUN Date: Mon, 28 Sep 2026 00:43:15 +0900 Subject: [PATCH 07/47] docs(devlog): drop internal agent ids from clients-proxy plan --- .../260927_release_train_4/clients-proxy/000_plan.md | 11 ++++------- .../clients-proxy/010_recipe.md | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/devlog/_plan/260927_release_train_4/clients-proxy/000_plan.md b/devlog/_plan/260927_release_train_4/clients-proxy/000_plan.md index 9706e76c930..198f53f26b9 100644 --- a/devlog/_plan/260927_release_train_4/clients-proxy/000_plan.md +++ b/devlog/_plan/260927_release_train_4/clients-proxy/000_plan.md @@ -95,15 +95,12 @@ head before the next batch. ## Consultation and uncertainty -Architect proposal `01a0e33a-811b-7471-a32a-52455283082e`: D1 existing +The architect proposal: D1 existing integration ownership and D2 proxy ownership accepted; D3 Cursor discovery amended to hold pending opt-in; D4 managed clients accepted with #3833 held; D5 new client proposals held pending primary client contracts; D6 JEV then -memory accepted; D7 Codex updater remains design-only. Source PR reviewers: -`01a0e338-ca02-7620-9ecd-cb6970740789`, -`01a0e339-163d-7240-9e96-4e7552dc9e47`, -`01a0e339-17ce-7540-b189-1241d837662b`, and -`01a0e339-18b6-7403-b450-5d3d86fee0be`. The architect's first reflection +memory accepted; D7 Codex updater remains design-only. Four independent source +PR reviewers examined the candidates. The architect's first reflection found three gaps: attribution trailer, explicit security review, and the recipe's OS-home isolation condition. All three were folded into this revision before independent audit. Their findings are proposals; each carry is @@ -120,7 +117,7 @@ exited 1; it is not evidence of test passage. Docs checks and semantic audit cover the roadmap, and implementation batches rerun changed tests. The same architect rechecked the D2 safety amendment and returned ALIGNED. -Independent A reviewer `01a0e345-7fc4-7bc0-bef9-dd4c8e927d59` first +The independent A reviewer first reported six blockers, then one remaining test-layout blocker; every finding was folded into the relevant decade document and its final verdict was PASS. This closes the roadmap design review, not any proposed code change. diff --git a/devlog/_plan/260927_release_train_4/clients-proxy/010_recipe.md b/devlog/_plan/260927_release_train_4/clients-proxy/010_recipe.md index 2285c3c5e5f..4af0bdb796b 100644 --- a/devlog/_plan/260927_release_train_4/clients-proxy/010_recipe.md +++ b/devlog/_plan/260927_release_train_4/clients-proxy/010_recipe.md @@ -52,7 +52,7 @@ not passing evidence. A docs-only CI skip is recorded as skipped, not as a passing suite. PR template Summary/Verification/Checklist, source author credit, exact-head required checks, and post-merge `dev` CI still apply. -Architect `01a0e33a-811b-7471-a32a-52455283082e` proposed D1-R (carry the +The architect proposed D1-R (carry the isolation and route examples), D1-L (one AGENTS discovery link), and D1-V (attribution and exact gates). All three are accepted. Putting the recipe in `skills/ocx/` would confuse development tests with operating guidance; a From 2e55fd3c72f3f3e0063bfa15ce33874cc05369b5 Mon Sep 17 00:00:00 2001 From: JUN Date: Mon, 28 Sep 2026 00:45:23 +0900 Subject: [PATCH 08/47] docs(devlog): state clients-proxy carry requirements without defect narrative --- .../260927_release_train_4/clients-proxy/000_plan.md | 2 +- .../clients-proxy/020_macos_proxy.md | 8 +++----- .../260927_release_train_4/clients-proxy/070_memory.md | 6 +++--- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/devlog/_plan/260927_release_train_4/clients-proxy/000_plan.md b/devlog/_plan/260927_release_train_4/clients-proxy/000_plan.md index 198f53f26b9..48cb5c03cdd 100644 --- a/devlog/_plan/260927_release_train_4/clients-proxy/000_plan.md +++ b/devlog/_plan/260927_release_train_4/clients-proxy/000_plan.md @@ -55,7 +55,7 @@ proxy activation (`structure/config-proxy.md:1-20`). | Item | Current head/state | Decision and evidence | Work phase | | --- | --- | --- | --- | | #6051 | `987b8097`, open | Carry the disposable-home management-API recipe with a discoverable contributor link; `.agents/skills/` has no existing entry point. | [010](010_recipe.md) | -| #5893 / #5853 | `3743320a`, draft | Carry only after safe bypass translation: every macOS exception must be faithfully representable or discovery refuses before any environment write; inherited SOCKS keeps its existing path. The PR currently appends exceptions only to `NO_PROXY` while Bun may read lowercase first (`src/config/proxy-env.ts:267` in PR). | [020](020_macos_proxy.md) | +| #5893 / #5853 | `3743320a`, draft | Carry only when every macOS exception maps faithfully onto the bypass variables the active transports read, or discovery refuses before any environment write; an inherited SOCKS proxy keeps its existing path. | [020](020_macos_proxy.md) | | #5950 / #5660 | `ef03f5ab`, open | Carry Qoder after current-base revalidation of opt-in config writes, restore, and path handling (`src/clients/config-export/qoder.ts`, PR test). | [030](030_qoder.md) | | #5272 | `7dd796d7`, open | Carry Kilo after checking all merged config candidates; first-file-only selection can be overridden by a later legacy file (`src/clients/config-export/kilo.ts:57-63` in PR). | [040](040_kilo.md) | | #5193 | `91090f80`, open/conflicting | Reimplement a focused Droid slice on current `dev` only if its client contract and export provenance can be proven. The PR's broad rewrite changes shared loopback export behavior. | [050](050_droid.md) | diff --git a/devlog/_plan/260927_release_train_4/clients-proxy/020_macos_proxy.md b/devlog/_plan/260927_release_train_4/clients-proxy/020_macos_proxy.md index e7f139832b7..a1d612e8e5f 100644 --- a/devlog/_plan/260927_release_train_4/clients-proxy/020_macos_proxy.md +++ b/devlog/_plan/260927_release_train_4/clients-proxy/020_macos_proxy.md @@ -17,13 +17,11 @@ explicit proxy precedence. `structure/config-proxy.md:1-20` owns the contract. `localhost` must not enter either effective proxy-bypass variable as a suffix. If any system exception is not faithfully representable, refuse macOS auto-discovery and leave process proxy variables unchanged with a - privacy-safe diagnostic; silently dropping it could send an intended direct - host through the proxy. Preserve the address-only loopback bypass. Add + privacy-safe diagnostic. Preserve the address-only loopback bypass. Add proven-safe entries to the bypass variable the selected HTTP(S) transport actually reads. If inherited `ALL_PROXY`/`all_proxy` selects SOCKS, - macOS discovery must not add scheme proxies or discovered exceptions: - the SOCKS wrapper reads uppercase `NO_PROXY` while Bun reads lowercase - `no_proxy`. Preserve the inherited proxy path and assert both transports' + macOS discovery must not add scheme proxies or discovered exceptions. + Preserve the inherited proxy path and assert both transports' effective routes when the two bypass variables disagree. Redact credential-bearing proxy URLs. - MODIFY `tests/server/proxy-env.test.ts`: retain source tests and add a case diff --git a/devlog/_plan/260927_release_train_4/clients-proxy/070_memory.md b/devlog/_plan/260927_release_train_4/clients-proxy/070_memory.md index 0538c26c26a..ae1384f1629 100644 --- a/devlog/_plan/260927_release_train_4/clients-proxy/070_memory.md +++ b/devlog/_plan/260927_release_train_4/clients-proxy/070_memory.md @@ -1,9 +1,9 @@ # Phase 7: selected models for Codex memory (#5983) Depends on `060_jev.md` for serial changes to `src/types/config.ts`. The source -PR spans 51 files. Its metadata classifier currently falls back to the -`x-openai-subagent` header after explicit non-memory turn metadata and can -reroute an ordinary turn; that edge must fail closed before any carry. +PR spans 51 files. The carried classifier treats explicit turn metadata as +authoritative and consults the `x-openai-subagent` header only when that +metadata is absent. ## Exact change map From 0bbc1ee522245a0e3bee45ab3afd58d0e1be28b7 Mon Sep 17 00:00:00 2001 From: JUN Date: Mon, 28 Sep 2026 00:57:20 +0900 Subject: [PATCH 09/47] docs(devlog): pin macOS proxy activation and per-transport bypass precedence --- .../clients-proxy/020_macos_proxy.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/devlog/_plan/260927_release_train_4/clients-proxy/020_macos_proxy.md b/devlog/_plan/260927_release_train_4/clients-proxy/020_macos_proxy.md index a1d612e8e5f..4890553c7a8 100644 --- a/devlog/_plan/260927_release_train_4/clients-proxy/020_macos_proxy.md +++ b/devlog/_plan/260927_release_train_4/clients-proxy/020_macos_proxy.md @@ -43,9 +43,13 @@ explicit proxy precedence. `structure/config-proxy.md:1-20` owns the contract. ## Acceptance and proof -Activation scenario: Darwin with `config.proxy: "auto"`, no inherited scheme -proxy, and valid system settings sets the proxy and safely representable -bypass list; inherited lowercase `no_proxy` remains the effective source. +Activation scenario: Darwin with `config.proxy: "auto"`, no inherited HTTP(S) +or SOCKS proxy (`ALL_PROXY`/`all_proxy` included), and valid system settings +sets the proxy and safely representable bypass list. Bypass precedence is +asserted per transport: Bun's native HTTP(S) fetch reads a non-empty lowercase +`no_proxy` before `NO_PROXY`, while `resolveProxyRoute` honors an explicitly +defined uppercase `NO_PROXY`, including an empty value. Tests keep route +assertions for both transports when the two variables disagree. Negative scenarios: unset `config.proxy` never reads system settings or mutates egress, inherited HTTP(S) or SOCKS proxy wins without mixed bypass semantics, unrepresentable exceptions refuse before any environment write, disabled or From 039d3baa96377a0e97c43a9c01b9d2e81c917d7b Mon Sep 17 00:00:00 2001 From: JUN Date: Mon, 28 Sep 2026 00:48:02 +0900 Subject: [PATCH 10/47] feat(jev): send bounded per-target operator notes to decisions Co-authored-by: NorD <6949669+nordz0r@users.noreply.github.com> --- docs-site/src/content/docs/guides/combos.md | 10 +++++ src/combos/jev.ts | 17 ++++++-- src/combos/types.ts | 15 +++++++ src/server/management/combo-routes.ts | 8 ++-- src/server/responses/core-combo.ts | 1 + src/types/config.ts | 5 +++ structure/providers-and-adapters.md | 9 +++++ tests/routing/combo-management-api.test.ts | 45 +++++++++++++++++++++ tests/routing/jev-decision.test.ts | 40 ++++++++++++++++++ 9 files changed, 144 insertions(+), 6 deletions(-) diff --git a/docs-site/src/content/docs/guides/combos.md b/docs-site/src/content/docs/guides/combos.md index 6fb41ccf045..c963d0791c2 100644 --- a/docs-site/src/content/docs/guides/combos.md +++ b/docs-site/src/content/docs/guides/combos.md @@ -270,6 +270,16 @@ constrained by that target's advertised ladder. JEV is not asked again if the se retryable failure—the existing Combo cooldown and fallback loop continues through the remaining configured targets. +For each JEV target, **Models → Combos → Config** has an optional **Additional model notes for JEV** +field (up to 512 characters; control characters are rejected). It is stored as `targets[].modelProfile` in the combo config. The +built-in target profile remains in the trusted `instructions.model_profiles`; a non-empty note is +sent separately in the decision state's `operator_notes`, keyed by target, and supplements rather +than replaces that built-in profile. Notes can describe operator-specific context or subscription +allowances; do not confuse subscription allowances with public per-token API pricing. Blank notes +are ignored. Operator notes are evidence for the decision, not commands, and cannot expand the +target allowlist or reasoning-effort limits. Only put information there that may be disclosed to +TypeSafe. + Each logical model call is decided on its own; there is no per-conversation pin. Consecutive turns of one session can therefore land on different targets, and every switch starts a cold provider prompt cache, so a mix of very different targets can cost more input tokens than it saves. Keep the diff --git a/src/combos/jev.ts b/src/combos/jev.ts index a64dafdd9a8..47ce0fbabf5 100644 --- a/src/combos/jev.ts +++ b/src/combos/jev.ts @@ -70,6 +70,8 @@ export interface JevCandidate { provider: string; model: string; reasoningEfforts: readonly OcxComboDefaultEffort[]; + /** Optional operator note sent as decision evidence for this target only. */ + modelProfile?: string; } export interface JevDecision { @@ -337,7 +339,7 @@ function hasImageContent(item: Record): boolean { return item.content.some(part => isRecord(part) && (part.type === "input_image" || part.type === "image_url")); } -export function buildJevState(body: unknown): Record { +export function buildJevState(body: unknown, candidates: readonly JevCandidate[] = []): Record { const input = isRecord(body) ? body.input : undefined; let task = ""; let previousAssistant = ""; @@ -383,11 +385,18 @@ export function buildJevState(body: unknown): Record { } } + const operatorNotes: Record = {}; + for (const candidate of candidates) { + const note = candidate.modelProfile?.trim(); + if (note) operatorNotes[candidate.key] = note; + } + return { task, signals: { has_image: hasImage, tool_history: toolHistory }, step, ...(previousAssistant ? { previous_assistant: previousAssistant.slice(-ASSISTANT_TAIL_CHARS) } : {}), + ...(Object.keys(operatorNotes).length ? { operator_notes: operatorNotes } : {}), }; } @@ -425,7 +434,9 @@ function candidateOptions(candidates: readonly JevCandidate[]): Map JEV_MAX_CANDIDATES) return false; return candidates.every(candidate => [candidate.key, candidate.provider, candidate.model] - .every(value => value.length > 0 && value.length <= JEV_MAX_CANDIDATE_FIELD_CHARS)); + .every(value => value.length > 0 && value.length <= JEV_MAX_CANDIDATE_FIELD_CHARS) + && (candidate.modelProfile === undefined + || (typeof candidate.modelProfile === "string" && candidate.modelProfile.length <= 512))); } function modelProfile(candidate: JevCandidate): string { @@ -566,7 +577,7 @@ export async function resolveJevDecision(options: ResolveJevDecisionOptions): Pr let requestBody: string; try { - const state = buildJevState(options.body); + const state = buildJevState(options.body, options.candidates); if (!hasJevDecisionState(state)) return failed("no_state"); requestBody = JSON.stringify({ model: JEV_MODEL, diff --git a/src/combos/types.ts b/src/combos/types.ts index fb7c8d318c8..5df6b8d1643 100644 --- a/src/combos/types.ts +++ b/src/combos/types.ts @@ -27,6 +27,8 @@ export interface NormalizedComboTarget { /** Emergency-only target, deferred under `cooldownWaitPolicy` (#5691). */ lastResort: boolean; reasoningEfforts?: OcxComboDefaultEffort[]; + /** Optional JEV decision description. */ + modelProfile?: string; } export interface NormalizedComboConfig { @@ -333,6 +335,16 @@ export function comboConfigIssues( message: `targets[${i}].lastResort must be a boolean`, }); } + if (target.modelProfile !== undefined + && (typeof target.modelProfile !== "string" + || target.modelProfile.trim().length === 0 + || target.modelProfile.length > 512 + || /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/.test(target.modelProfile))) { + issues.push({ + path: ["targets", i, "modelProfile"], + message: `targets[${i}].modelProfile must be a non-empty string of at most 512 characters without control characters`, + }); + } if (provider && model) { const key = targetKey({ provider, model }); @@ -389,6 +401,9 @@ export function normalizeComboConfig(raw: OcxComboConfig): NormalizedComboConfig ...(target.reasoningEfforts !== undefined ? { reasoningEfforts: [...target.reasoningEfforts] } : {}), + ...(typeof target.modelProfile === "string" && target.modelProfile.trim() + ? { modelProfile: target.modelProfile.trim() } + : {}), lastResort: target.lastResort === true, })), }; diff --git a/src/server/management/combo-routes.ts b/src/server/management/combo-routes.ts index c0cb63a2d2b..3c802aba67e 100644 --- a/src/server/management/combo-routes.ts +++ b/src/server/management/combo-routes.ts @@ -244,9 +244,11 @@ export async function handleComboRoutes(ctx: ManagementContext): Promise - lastResort ? { ...target, lastResort: true } : target, - ), + targets: normalizedBase.targets.map(({ lastResort, modelProfile, ...target }) => ({ + ...target, + ...(lastResort ? { lastResort: true } : {}), + ...(modelProfile ? { modelProfile } : {}), + })), ...(normalizedAlias ? { alias: normalizedAlias } : {}), ...(normalizedNativeAlias ? { nativeAlias: true } : {}), ...(normalizedDisplayName ? { displayName: normalizedDisplayName } : {}), diff --git a/src/server/responses/core-combo.ts b/src/server/responses/core-combo.ts index 26d4b731c3f..4e4901bb6af 100644 --- a/src/server/responses/core-combo.ts +++ b/src/server/responses/core-combo.ts @@ -226,6 +226,7 @@ function eligibleJevComboChoices( provider: pick.target.provider, model: pick.target.model, reasoningEfforts, + modelProfile: pick.target.modelProfile, }, }); } diff --git a/src/types/config.ts b/src/types/config.ts index 940aa2b06fd..275610f661e 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -1240,6 +1240,11 @@ export interface OcxComboTarget { * target currently advertises; an explicit list must be non-empty. */ reasoningEfforts?: OcxComboDefaultEffort[]; + /** + * Operator-authored capability description sent only to the JEV decision + * service for this target. The built-in model profile always applies. + */ + modelProfile?: string; /** * Marks an emergency-only target. Inert unless the combo sets * `cooldownWaitPolicy`, and never makes a target permanently ineligible — diff --git a/structure/providers-and-adapters.md b/structure/providers-and-adapters.md index fc9089732dd..63d18633c4a 100644 --- a/structure/providers-and-adapters.md +++ b/structure/providers-and-adapters.md @@ -228,6 +228,15 @@ and caller-cancellation propagation. Missing credentials or safe state, transpor answers fail open to the first eligible target; no response can escape the configured choice map. Telemetry never retains extracted state or credentials. +The optional `targets[].modelProfile` note is validated at the Combo management input +boundary to a non-empty string of at most 512 characters without control characters +and stored sparsely. +`src/combos/jev.ts` sends a configured target note as `state.operator_notes` on a +JEV decision, keyed by target; built-in `instructions.model_profiles` and the +target/effort allowlist stay authoritative. The note reaches TypeSafe with each +applicable decision, so operators must keep secrets and private paths out of it. +An absent note leaves the prior decision payload shape intact. + `src/server/responses/core-combo.ts` computes current eligibility, asks JEV once for the initial pick, applies the validated effort, and removes caller `service_tier` for that child. A retryable child failure re-enters the ordinary Combo fallback loop from the untouched request without another JEV diff --git a/tests/routing/combo-management-api.test.ts b/tests/routing/combo-management-api.test.ts index 3267d8d968b..5fc34835fae 100644 --- a/tests/routing/combo-management-api.test.ts +++ b/tests/routing/combo-management-api.test.ts @@ -275,6 +275,51 @@ describe("combo management API", () => { }); }); + test("model notes round-trip across strategies and reject invalid lengths", async () => { + await withTempHome(async () => { + const config = baseConfig({ combos: undefined }); + saveConfig(config); + const created = await comboApi(config, "PUT", "/api/combos", { + id: "jev-profile", + combo: { strategy: "jev", targets: [{ provider: "a", model: "m1", modelProfile: " Low marginal subscription cost; 1M context. " }] }, + }); + expect(created?.status).toBe(200); + expect(config.combos?.["jev-profile"]?.targets[0]?.modelProfile).toBe("Low marginal subscription cost; 1M context."); + const listed = await responseJson(await comboApi(config, "GET", "/api/combos")); + expect(listed.combos[0].targets[0].modelProfile).toBe("Low marginal subscription cost; 1M context."); + const switched = await comboApi(config, "PUT", "/api/combos", { + id: "jev-profile", + combo: { + strategy: "failover", + targets: [{ provider: "a", model: "m1", modelProfile: " Low marginal subscription cost; 1M context. " }], + }, + }); + expect(switched?.status).toBe(200); + expect(config.combos?.["jev-profile"]).toMatchObject({ + strategy: "failover", + targets: [{ modelProfile: "Low marginal subscription cost; 1M context." }], + }); + const switchedListed = await responseJson(await comboApi(config, "GET", "/api/combos")); + expect(switchedListed.combos[0]).toMatchObject({ + strategy: "failover", + targets: [{ modelProfile: "Low marginal subscription cost; 1M context." }], + }); + for (const invalidNote of [" ".repeat(513), " ", "Unsafe\u0000note", 123]) { + const rejected = await comboApi(config, "PUT", "/api/combos", { + id: "jev-profile", + combo: { strategy: "jev", targets: [{ provider: "a", model: "m1", modelProfile: invalidNote }] }, + }); + expect(rejected?.status).toBe(400); + } + const invalid = await comboApi(config, "PUT", "/api/combos", { + id: "jev-profile", + combo: { strategy: "jev", targets: [{ provider: "a", model: "m1", modelProfile: "x".repeat(513) }] }, + }); + expect(invalid?.status).toBe(400); + expect(config.combos?.["jev-profile"]?.targets[0]?.modelProfile).toBe("Low marginal subscription cost; 1M context."); + }); + }); + test("PUT preserves explicit cooldown knobs and omits sparse defaults", async () => { await withTempHome(async () => { const config = baseConfig({ combos: undefined }); diff --git a/tests/routing/jev-decision.test.ts b/tests/routing/jev-decision.test.ts index d0a4a61f94b..2049d612a8f 100644 --- a/tests/routing/jev-decision.test.ts +++ b/tests/routing/jev-decision.test.ts @@ -341,6 +341,46 @@ describe("JEV decision client", () => { }); }); + test("transmits only the selected candidate note without changing built-in profiles or choices", async () => { + const bodies: Record[] = []; + const post = (async (_name, _provider, _url, init) => { + bodies.push(JSON.parse(String(init.body)) as Record); + return Response.json(validPayload); + }) as JevPost; + const noted = [ + { ...candidates[0]!, modelProfile: " Subscription allowance for Astra. " }, + candidates[1]!, + ]; + await resolveJevDecision({ body: decisionBody, candidates: noted, fallback, config: jevConfig("secret"), post }); + await resolveJevDecision({ body: decisionBody, candidates, fallback, config: jevConfig("secret"), post }); + await resolveJevDecision({ body: decisionBody, candidates: [candidates[1]!], fallback, config: jevConfig("secret"), post }); + + expect(bodies).toHaveLength(3); + expect(bodies[0]?.state).toEqual({ + ...buildJevState(decisionBody), + operator_notes: { "openai/gpt-6-astra": "Subscription allowance for Astra." }, + }); + expect(bodies[1]?.state).toEqual(buildJevState(decisionBody)); + expect(bodies[2]?.state).toEqual(buildJevState(decisionBody)); + expect((bodies[0]?.questions as Record)).toEqual(buildJevRouteQuestion(candidates)); + expect((bodies[2]?.questions as Record)).toEqual(buildJevRouteQuestion([candidates[1]!])); + expect(JSON.stringify(bodies[2])).not.toContain("Astra"); + }); + + test("rejects an oversized note before an outbound decision", async () => { + let calls = 0; + const post = (async () => { calls++; return Response.json(validPayload); }) as JevPost; + const decision = await resolveJevDecision({ + body: decisionBody, + candidates: [{ ...candidates[0]!, modelProfile: "x".repeat(513) }], + fallback, + config: jevConfig("secret"), + post, + }); + expect(decision.gate).toBe("invalid"); + expect(calls).toBe(0); + }); + test("resolves environment references and supports TypeSafe and provider-derived key fallbacks", async () => { const previousTypesafe = process.env.TYPESAFE_API_KEY; const previousJev = process.env.JEV_API_KEY; From e9610e81e0bbed1f41a4d41121eb02454faa18e3 Mon Sep 17 00:00:00 2001 From: JUN Date: Mon, 28 Sep 2026 00:48:24 +0900 Subject: [PATCH 11/47] feat(gui): edit JEV notes per combo target Co-authored-by: NorD <6949669+nordz0r@users.noreply.github.com> --- gui/src/combo-workspace-data.ts | 18 +++++++++++++- .../components/combo-workspace-controls.tsx | 14 ++++++++++- .../combo-workspace-detail-panel.tsx | 2 +- gui/src/i18n/de.ts | 4 ++++ gui/src/i18n/en.ts | 4 ++++ gui/src/i18n/fr.ts | 4 ++++ gui/src/i18n/ja.ts | 4 ++++ gui/src/i18n/ko.ts | 4 ++++ gui/src/i18n/ru.ts | 4 ++++ gui/src/i18n/tr.ts | 4 ++++ gui/src/i18n/vi.ts | 4 ++++ gui/src/i18n/zh-TW.ts | 4 ++++ gui/src/i18n/zh.ts | 4 ++++ tests/gui/combo-workspace-data.test.ts | 24 +++++++++++++++++++ 14 files changed, 95 insertions(+), 3 deletions(-) diff --git a/gui/src/combo-workspace-data.ts b/gui/src/combo-workspace-data.ts index 0695cd5eada..28750c0ef87 100644 --- a/gui/src/combo-workspace-data.ts +++ b/gui/src/combo-workspace-data.ts @@ -91,6 +91,8 @@ export interface ComboTarget { weight?: number; /** Exact efforts JEV may choose; omitted means every currently advertised effort. */ reasoningEfforts?: ComboEffort[]; + /** Optional operator note that supplements the built-in JEV profile. */ + modelProfile?: string; /** UI-only stable key for React lists; never sent to the API. */ clientKey?: string; } @@ -111,6 +113,7 @@ export function newComboTarget(partial: Partial = {}): ComboTarget ...(partial.reasoningEfforts !== undefined ? { reasoningEfforts: [...partial.reasoningEfforts] } : {}), + ...(partial.modelProfile !== undefined ? { modelProfile: partial.modelProfile } : {}), clientKey: partial.clientKey ?? `ct-${++comboTargetKeySeq}`, }; } @@ -257,6 +260,7 @@ export function parseComboList(payload: unknown): ComboItem[] { model, ...(weight !== undefined ? { weight } : {}), ...(reasoningEfforts !== undefined ? { reasoningEfforts } : {}), + ...(typeof tr.modelProfile === "string" ? { modelProfile: tr.modelProfile } : {}), })); } out.push({ @@ -448,7 +452,8 @@ export function draftEquals(a: ComboItem, b: ComboItem): boolean { return t.provider === o.provider && t.model === o.model && (t.weight ?? 1) === (o.weight ?? 1) - && targetReasoningEffortsEqual(t, o); + && targetReasoningEffortsEqual(t, o) + && (t.modelProfile ?? "") === (o.modelProfile ?? ""); }); } @@ -479,6 +484,9 @@ export function toPutBody(item: ComboItem, options: { renameFrom?: string } = {} ...(target.reasoningEfforts !== undefined ? { reasoningEfforts: [...target.reasoningEfforts] } : {}), + ...(target.modelProfile?.trim() + ? { modelProfile: target.modelProfile.trim() } + : {}), })), strategy: item.strategy, defaultEffort: item.defaultEffort, @@ -515,6 +523,7 @@ export type ComboDraftError = | "invalidStickyLimit" | "invalidWeight" | "invalidReasoningEfforts" + | "invalidModelProfile" | "noEnabledTarget"; export function validateComboDraft( @@ -565,6 +574,13 @@ export function validateComboDraft( || new Set(t.reasoningEfforts).size !== t.reasoningEfforts.length)) { return "invalidReasoningEfforts"; } + if (t.modelProfile !== undefined + && (t.modelProfile.length > 512 || [...t.modelProfile].some(char => { + const code = char.charCodeAt(0); + return (code < 32 && code !== 9 && code !== 10 && code !== 13) || code === 127; + }))) { + return "invalidModelProfile"; + } } const targets = new Set(); diff --git a/gui/src/components/combo-workspace-controls.tsx b/gui/src/components/combo-workspace-controls.tsx index 7096fac936f..8f4f3a9d81d 100644 --- a/gui/src/components/combo-workspace-controls.tsx +++ b/gui/src/components/combo-workspace-controls.tsx @@ -179,7 +179,7 @@ export function TargetEditor({ const replaceModel = (index: number, patch: Pick) => { onChange(targets.map((row, i) => { if (i !== index) return row; - const { reasoningEfforts: _reasoningEfforts, ...rest } = row; + const { reasoningEfforts: _reasoningEfforts, modelProfile: _modelProfile, ...rest } = row; return { ...rest, ...patch }; })); }; @@ -381,6 +381,18 @@ export function TargetEditor({ })} )} +