feat(local-agent): sync and report ClawPro model configs - #393
feat(local-agent): sync and report ClawPro model configs#393jeff-r2026 wants to merge 3 commits into
Conversation
Handle apply_model_config delivery into CodeBuddy/Claude, then report the local model inventory on POST /api/local-agent/report. --story=1020422209136345350 Fixes #294 Co-authored-by: Cursor <cursoragent@cursor.com>
Treat omitted, empty, or 0 (Go zero value) as the design-doc default so models like kimi-k2.5 are not ack-failed for a missing output cap. --story=1020422209136345350 Co-authored-by: Cursor <cursoragent@cursor.com>
jeff-r2026
left a comment
There was a problem hiding this comment.
Found three P1 issues that reproduce locally against this head with an isolated HOME. Inline comments include the reproduction and impact.
| ...(fullSnapshot ? {} : manifest.providers), | ||
| ...Object.fromEntries(models.map((model) => [model.model_id, model.provider])), | ||
| }; | ||
| await reconcileCodebuddyModels(models, fullSnapshot, manifest); |
There was a problem hiding this comment.
[P1] Apply model configs to the reporting agent before acknowledging success
applyModelConfig never receives context.tool and unconditionally reconciles only CodeBuddy and Claude. I reproduced a sync with tool: 'workbuddy' and a seeded ~/.workbuddy/models.json: that file remained byte-for-byte unchanged, while ~/.codebuddy/models.json and ~/.claude/settings.json were created and the command was acked as success. The backend therefore will not retry even though the reporting WorkBuddy instance cannot discover the candidate; scanModelsFromDisk also returns no inventory for WorkBuddy. Please route persistence/reporting to the actual agent (WorkBuddy uses its own ~/.workbuddy/models.json format), or fail unsupported agents instead of acknowledging an unapplied task.
| } | ||
| Object.assign(env, desired); | ||
| settings.env = env; | ||
| await writeJsonAtomic(settingsPath, settings); |
There was a problem hiding this comment.
[P1] Preserve symlinked user settings files
writeJsonAtomic replaces the destination via rename, and its own contract explicitly says it is not for dotfile paths that may be symlinks. With ~/.claude/settings.json symlinked to a dotfiles-managed file, I reproduced this call replacing the symlink with a regular file while leaving the real target unchanged; the task still receives a success ack. This silently disconnects the user's source-of-truth configuration. The CodeBuddy write at line 2144 has the same issue. Resolve/follow the symlink (or use a symlink-preserving writer) before modifying these user dotfiles.
| const env = { ...(rawEnv as Record<string, unknown>) }; | ||
|
|
||
| if (models.length === 0) { | ||
| for (const [key, hash] of Object.entries(previousHashes)) { |
There was a problem hiding this comment.
[P1] Treat Claude gateway ownership atomically when removing a snapshot
The non-empty path treats any conflicting field as ownership of the whole gateway, but this empty-snapshot path deletes each still-matching field independently. I reproduced: apply a managed model, let the user change only ANTHROPIC_BASE_URL, then deliver {"models":[]}. The code preserves that URL but deletes ANTHROPIC_AUTH_TOKEN, ANTHROPIC_CUSTOM_MODEL_OPTION, and its name, leaving a broken half-gateway and acknowledging success. If any field in the previously managed gateway diverges, preserve the whole gateway (or track a bundle-level ownership hash) rather than stripping the remaining fields.
The backend cannot resolve user-owned models, so user_level.models now includes only entries that still match an apply_model_config delivery. --story=1020422209136345350 Co-authored-by: Cursor <cursoragent@cursor.com>
Summary
apply_model_configtasks from/api/local-agent/sync, persist the candidate set into CodeBuddy (~/.codebuddy/models.json) and Claude (~/.claude/teamai-models.json/settings.jsonwhen there is no user-owned gateway), and ack withtype: "apply_model_config". User-owned models are preserved; credential files are0600; sync responses are redacted so ProxyToken never lands in logs.POST /api/local-agent/reportasuser_level.models(provider+model_idrequired; omit the field when empty; never sendapi_key).sourceisenterprisewhen the on-disk entry still matches what TeamAI delivered, otherwiselocal.--story=1020422209136345350.Test plan
npx tsc --noEmitnpx vitest run(2546 tests, including 13 new cases inlocal-agent-model-config.test.ts)npm run buildand run the built CLIhook-dispatch session-start --tool codebuddyagainst the ClawPro test endpoint; report HTTP 200 withuser_level.modelsapply_model_config, then a second session reports the delivered model assource: "enterprise"and a user-seeded CodeBuddy model assource: "local"; acksuccess; ProxyToken absent from report/ack bodies~/.claude/teamai-models.json, reported with the delivered provider, no token leakMade with Cursor