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
33 changes: 33 additions & 0 deletions devlog/_fin/261003_claude_1p_agents/000_plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Restore roster registration after Claude first-party setup

First-party setup can report success without regenerating the selected OpenCodex agents. Reconcile the existing roster at the successful setup boundary so a subsequent Claude session can discover its routed workers. This fixes a demonstrated missing lifecycle step; it does not claim to explain every missing-agent report.

## Loop specification

- Class: C4 care for the management API surface; one satisfy-spec PABCD cycle, wp1.
- Trigger and goal: reported missing third-party subagents after Claude 1P connection; restore registration and merge a focused PR to dev.
- Non-goals: no new settings, catalog schema, GUI redesign, account changes, release, installation or live configuration mutation.
- Verifiers: isolated management/first-party regression tests observe generated files and rejected writes; typecheck checks the TypeScript tree; privacy and structure gates check repository contracts; exact-head hosted CI gates merge. No live provider request needed.
- Stop: reviewed fix, passing required CI and verified dev merge. DONE requires that evidence; unresolved failure is reported honestly, never as passing.
- Artifact: this unit and 010_registration.md; scratch command output stays in .tmp/claude-1p.
- Escalation: new routing/auth semantics or an irreconcilable client-specific report requires a revised scope. No token/cost/wall-clock budget was supplied; use existing local tools and authenticated repository access only.

## Evidence and decisions

- D1: accept existing syncClaudeAgentDefsBestEffort as the registration owner. The dedicated CLI 1P return at src/server/management/agent-settings-routes.ts:1681 bypasses its ordinary call at line 1952.
- D2: preserve all rejection/rollback, disabled integration/injection and sibling-ownership behavior. Do not change low-level first-party settings reconciliation.
- D3: cover the equivalent Desktop first-party apply success boundary if confirmed; no gateway behavior changes.
- Architect: inherited V1 agent 01a0fff4-fd56-73e3-8aaf-1d9c594927ed. Initial GUI navigation proposal rejected because no hidden-control gate was demonstrated. Runtime tracing identified the skipped registration step instead. Reflection: ALIGNED; main folded exact Desktop placement, pre-existing-file preservation and best-effort wording into 010. Independent audit pending.
- Main verified buildClaudeAgentDefs can build the configured roster without writing files. This does not establish why startup did not leave files in a particular installation.

## Delivery

One ordinary PR to dev. Main owns branch/commits/CI/merge. Inherited worker owns runtime and tests; main owns documentation and independent review coordination. No new dependencies. The user subsequently prohibited all local testing. Final acceptance is exact-head hosted CI plus source-only review; no further local tests, builds, typechecks or QA are permitted.

Baseline verifier evidence: `bun run test -- tests/claude-integration/claude-management-api.test.ts tests/claude-integration/claude-desktop-first-party.test.ts tests/claude-integration/claude-agents-inject.test.ts tests/claude-integration/claude-agent-startup-sync.test.ts` exited 0 (123 pass, 0 fail). Each explicit argument observes an owning route/generator/startup surface. Initial wrapper failure was the unexecuted pinned Bun dependency postinstall after ignore-scripts; running its checked-in dependency installer restored the wrapper. No lockfile changed.

Independent A review: inherited reviewer 01a0fffb-254e-7563-8a0f-77ab1d70ca03 returned PASS with no blockers. Explicit security scope covered management admission, refusal/rollback exits and helper ownership gates. No auth or credential format changes are planned.

## Verification correction

The user prohibited local tests during C. The in-progress full suite was terminated (exit 143), not counted as passing. Local checks reported above predate this correction and are historical only. Remaining verification and final acceptance use GitHub Actions against the PR head. The implementation remains the audited two-call reconciliation fix.
24 changes: 24 additions & 0 deletions devlog/_fin/261003_claude_1p_agents/010_registration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# wp1: Reconcile registered agents at successful first-party setup

## File changes

- MODIFY src/server/management/agent-settings-routes.ts: in the dedicated cliFirstParty branch, after reconciliation succeeds and rollback/refusal paths exit, await the existing syncClaudeAgentDefsBestEffort before response. Apply to successful on/off writes so idempotent saves repair missing files; disabling first-party alone keeps registered agents when the integration remains enabled.
- MODIFY the same file: for Desktop first-party apply, reconcile after picker reconciliation immediately before the success response, guarded by modeSaved.ok; saved:false partial success must not register. Reuse the existing helper; never put filesystem registration in the settings transaction or TLS startup primitive.
- NEW tests/claude-integration/claude-first-party-agent-sync.test.ts if existing large test owners cannot accommodate focused cases; use isolated config directories and real management API/file assertions. Register in scripts/test-layout/layout.json and tests/fixtures/test-layout-expected.json.
- MODIFY docs-site/src/content/docs/guides/claude-code.md and structure/runtime.md: state successful first-party setup attempts best-effort reconciliation of the owned roster and new sessions load it. Review adjacent translated roster prose and update contradictions only.

## Activation and proof

1. Empty isolated agents directory, non-empty configured routed roster, CLI first-party enable succeeds: expected marker-owned file exists before response, with correct ocx-route directive.
2. Repeat successful enable after removing only the test fixture's generated file: file repaired.
3. injectAgents=false: no registration; existing owned file pruned according to existing policy, user-authored file retained.
4. Refused or rolled-back enable: no registration is created, and pre-existing files remain byte-identical; rollback checks retain existing semantics.
5. Successful CLI first-party disable with enabled integration: roster retained.
6. Existing generator/startup tests must continue proving enabled:false and sibling-ownership gates.
7. Successful Desktop first-party apply with persisted mode: roster generated; persistence failure never adds a new registration side effect.

Verification is now hosted-only by explicit user correction. GitHub CI must execute the new focused test through its registered domain and the applicable existing suites, typecheck, structure/privacy gates and documentation build. Do not execute these locally. Baseline GUI tests (20 pass) were investigation evidence only, not proof of this fix. No GUI change planned, so screenshot requirement is inapplicable.

## Security review scope

Preserve existing management admission and settings-file ownership. The existing generator writes only marker-owned definitions; regression tests exercise refusal and disabled registration. No credentials, request bodies or private local configuration enter repository artifacts. Independent security review must confirm the new call occurs only after successful reconciliation and does not weaken consent or ownership checks.
9 changes: 9 additions & 0 deletions devlog/_fin/261003_claude_1p_agents/090_summary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Implementation outcome

The dedicated CLI first-party save and persisted Desktop first-party apply paths now attempt the existing best-effort agent-roster sync after settings/picker reconciliation. This closes the missed registration step without changing settings, authentication or agent ownership policy.

The ten regression cases failed six times before the runtime edit and passed after it. The existing related suites, typecheck, structure/privacy gates and documentation build also passed before the user prohibited further local testing. The subsequent local full-suite run was terminated with exit 143 and is not acceptance evidence.

Independent source/security review of implementation commit `25dd0288893d85f9bdf4d27f429c9cec0842abfa` covered all nine changed files and returned PASS with no blockers. A file-write failure after successful setup remains best-effort, as in the existing helper; no new success guarantee is claimed.

Delivery is [PR #6484](https://github.com/lidge-jun/opencodex/pull/6484). Required hosted checks for its final head and the maintainer integration decision are recorded there. Final acceptance is hosted-only. No release, installed-app update or live Claude-session validation is claimed. The missing setup-time sync is reproduced; the historical cause of every absent local definition is not established.
6 changes: 5 additions & 1 deletion docs-site/src/content/docs/guides/claude-code.md
Original file line number Diff line number Diff line change
Expand Up @@ -634,8 +634,12 @@ Proxy startup/ensure, `ocx claude`, and relevant dashboard saves sync your featu
overwritten or pruned; your own agents are never touched.
- Files are atomically synced per file (write + rename).
- `enabled: false` or `injectAgents: false` prunes all verified-owned definitions.
- Successful CLI first-party saves and persisted Desktop first-party setup also attempt best-effort
roster sync, including repeated setup to repair missing definitions. Refused setup and rollback
do not sync agents. Turning first-party off alone does not disable agent registration.
- GUI PUT and roster changes resync immediately; every foreground or background proxy start/ensure
reconciles the owned files before a later Claude Code launch reads them.
reconciles the owned files before a later Claude Code launch reads them. Start a new Claude session
after registration so it loads the generated definitions.

Dispatch: `subagent_type: "ocx-gpt-5-6-sol"`. 1M-capable targets carry `[1m]` automatically.

Expand Down
3 changes: 2 additions & 1 deletion docs-site/src/content/docs/ko/guides/claude-code.md
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,8 @@ Claude 페이지에서 압축 값을 조절할 수 있어요. **경고:** 모델
사용자가 만든 에이전트는 건드리지 않아요.
- 파일마다 원자적으로 동기화해요(write + rename).
- `enabled: false` 또는 `injectAgents: false`를 설정하면 소유권이 확인된 정의를 모두 정리해요.
- GUI PUT과 로스터 변경은 즉시 다시 동기화하고, launcher/system-env는 실행할 때 동기화해요.
- CLI 1P 설정 저장이나 Desktop 1P 설정 저장이 성공하면 에이전트 등록도 시도해요. 같은 설정을 다시 저장하면 누락된 정의를 복구할 수 있어요. 연결이 거부되거나 롤백되면 등록하지 않아요. 1P만 끄더라도 자동 등록은 유지해요.
- GUI PUT과 로스터 변경은 즉시 다시 동기화하고, launcher/system-env는 실행할 때 동기화해요. 등록한 에이전트를 쓰려면 Claude 세션을 새로 시작하세요.

디스패치 예시: `subagent_type: "ocx-gpt-5-6-sol"`. 1M을 지원하는 대상에는 `[1m]`이 자동으로
붙어요.
Expand Down
1 change: 1 addition & 0 deletions scripts/test-layout/layout.json
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,7 @@
"claude-desktop-remote-hub.test.ts": "claude-integration",
"claude-dotenv-provenance-transport.test.ts": "claude-integration",
"claude-estimate-projection.test.ts": "claude-integration",
"claude-first-party-agent-sync.test.ts": "claude-integration",
"claude-first-party-union.test.ts": "claude-integration",
"claude-gateway-cache.test.ts": "claude-integration",
"claude-inbound-cache-stabilize.test.ts": "claude-integration",
Expand Down
2 changes: 2 additions & 0 deletions src/server/management/agent-settings-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1194,6 +1194,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
const picker = modeSaved.ok && pickerPreferenceOn(loadConfig())
? await ops.enableLocked({ persist: false, context: "server" })
: await pickerStatusFor(loadConfig());
if (modeSaved.ok) await syncClaudeAgentDefsBestEffort();
return jsonResponse({
ok: true,
mode: "first-party",
Expand Down Expand Up @@ -1678,6 +1679,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
const finalDesired = firstPartyDesired(config, observeClaudeDesktopMode(config));
const residual = !finalDesired.desktop && !finalDesired.cli
&& readFirstPartyProxyStatus(config, bound?.proxyPort ?? null) !== "none";
await syncClaudeAgentDefsBestEffort();
return jsonResponse({ ok: true, enabled: config.claudeCode?.enabled !== false,
cliFirstParty: body.cliFirstParty,
warnings: [
Expand Down
2 changes: 1 addition & 1 deletion structure/runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ This ingress honours first-party model bindings (`claudeCode.intercept.modelMap`
Only a client with its own first-party intent enabled (Desktop mode or `claudeCode.cliFirstParty`) enters the router for Messages paths; other paths use the configured upstream relay.
Every path from an opted-out or unknown client, and every path while Claude routing is disabled, relays to real Anthropic through `relayToUpstream` with `CLAUDE_INTERCEPT_UPSTREAM`.
The User-Agent split is a routing hint any local process can forge, not a trust boundary.
Two independent intents can want that settings env: Desktop first-party mode and `claudeCode.cliFirstParty` for the standalone CLI. `src/claude/first-party-settings.ts` owns the union: `reconcileClaudeFirstPartySettings` writes the owned pair while either intent is on, keeps the file untouched while an intent is on but the intercept cannot run, and removes the owned pair only when neither intent remains. `firstPartyProxyStatus` classifies what the settings file currently points at against the bound listener (`none`, `live`, `stopped`, `disabled`, `broken`, `foreign`, `local`, `unknown`); it reads the proxy token and never mints it. `ocx claude` with Claude routing off launches natively; when the shared settings env carries opencodex's proxy it adds `NO_PROXY=*` and `no_proxy=*` so this launch bypasses it, unless an inherited foreign `HTTPS_PROXY` or `https_proxy` is present, in which case it warns instead.
Two independent intents can want that settings env: Desktop first-party mode and `claudeCode.cliFirstParty` for the standalone CLI. `src/claude/first-party-settings.ts` owns the union: `reconcileClaudeFirstPartySettings` writes the owned pair while either intent is on, keeps the file untouched while an intent is on but the intercept cannot run, and removes the owned pair only when neither intent remains. `firstPartyProxyStatus` classifies what the settings file currently points at against the bound listener (`none`, `live`, `stopped`, `disabled`, `broken`, `foreign`, `local`, `unknown`); it reads the proxy token and never mints it. `ocx claude` with Claude routing off launches natively; when the shared settings env carries opencodex's proxy it adds `NO_PROXY=*` and `no_proxy=*` so this launch bypasses it, unless an inherited foreign `HTTPS_PROXY` or `https_proxy` is present, in which case it warns instead. Successful CLI first-party saves and Desktop first-party apply with a persisted mode attempt best-effort roster reconciliation through the existing management helper after settings/picker reconciliation; refusal, rollback and unsaved Desktop partial success do not. Disabling first-party alone preserves registration while the separate integration/injection gates remain enabled.
Auxiliary listener bind failures carry the listener key and effective address through `AuxiliaryListenerBindError` in `src/server/ports.ts`. `src/cli/index.ts` reports them without retrying the public port. Startup still rolls back every earlier socket synchronously.

A failed public, loopback, or management bind rolls back earlier sockets; a failed hub-link bind warns
Expand Down
Loading
Loading