acdev 1.0.8 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/public/app.js CHANGED
@@ -129,51 +129,18 @@ let settingsSaving = false;
129
129
  let settingsTab = 'ticket';
130
130
  /** Sentinel: no model selected (jobs blocked until user picks one). */
131
131
  const NO_MODEL = '-';
132
- const OPENROUTER_AGENT_MODEL_PREFIX = 'anthropic/';
133
132
  /** @type {string} */
134
133
  let modelSourceHintBase = '';
135
- /**
136
- * @param {string} modelId
137
- * @returns {boolean}
138
- */
139
- function isOpenRouterAgentCompatibleModel(modelId) {
140
- if (!modelId || !String(modelId).includes('/')) return false;
141
- return String(modelId).trim().toLowerCase().startsWith(OPENROUTER_AGENT_MODEL_PREFIX);
142
- }
143
134
 
144
135
  /**
145
136
  * @param {HTMLElement} btn
146
- * @param {{ id: string, agentCompatible?: boolean }} model
147
- */
148
- function appendModelCompatibilityBadge(btn, model) {
149
- if (currentLlmProviderSelection() !== 'openrouter') return;
150
- const compatible =
151
- model.agentCompatible ?? isOpenRouterAgentCompatibleModel(model.id);
152
- const badge = document.createElement('span');
153
- badge.className = compatible
154
- ? 'model-picker-badge model-picker-badge--agent'
155
- : 'model-picker-badge model-picker-badge--reference';
156
- badge.textContent = compatible ? 'Agent' : 'May not work';
157
- badge.title = compatible
158
- ? 'Supported for acdev agent jobs (anthropic/* via Claude Agent SDK)'
159
- : 'Listed for reference; agent jobs require anthropic/* models';
160
- btn.appendChild(badge);
161
- }
162
-
163
- /**
164
- * @param {HTMLElement} btn
165
- * @param {{ id: string, label?: string, name?: string, agentCompatible?: boolean }} model
137
+ * @param {{ id: string, label?: string, name?: string }} model
166
138
  */
167
139
  function populateModelPickerOption(btn, model) {
168
- const titleRow = document.createElement('span');
169
- titleRow.className = 'model-picker-option-title-row';
170
-
171
140
  const title = document.createElement('span');
172
141
  title.className = 'model-picker-option-title';
173
142
  title.textContent = modelPickerLabel(model);
174
- titleRow.appendChild(title);
175
- appendModelCompatibilityBadge(titleRow, model);
176
- btn.appendChild(titleRow);
143
+ btn.appendChild(title);
177
144
 
178
145
  if (model.id !== modelPickerLabel(model)) {
179
146
  const sub = document.createElement('span');
@@ -185,23 +152,12 @@ function populateModelPickerOption(btn, model) {
185
152
 
186
153
  function refreshModelSourceHint() {
187
154
  if (!els.settingsModelHint) return;
188
- const provider = currentLlmProviderSelection();
189
- let text = modelSourceHintBase;
190
- if (
191
- provider === 'openrouter' &&
192
- !isNoModelSelection(currentModel) &&
193
- !isOpenRouterAgentCompatibleModel(currentModel)
194
- ) {
195
- text += ` Selected model "${currentModel}" may not work for agent jobs — choose an anthropic/* model to run jobs.`;
196
- }
197
- els.settingsModelHint.textContent = text;
155
+ els.settingsModelHint.textContent = modelSourceHintBase;
198
156
  }
199
157
  /** @type {string} */
200
158
  let currentModel = 'claude-sonnet-5';
201
159
  /** @type {Array<{id:string,label?:string,name?:string}>} */
202
160
  let availableModels = [];
203
- /** @type {'claude' | 'openrouter' | null} */
204
- let availableModelsProvider = null;
205
161
  /** @type {'github' | 'jira'} */
206
162
  let ticketSource = 'github';
207
163
  /** @type {{
@@ -234,11 +190,6 @@ let ticketSource = 'github';
234
190
  * anthropicApiKeyMasked?: string | null,
235
191
  * claudeOauthTokenSet?: boolean,
236
192
  * claudeOauthTokenMasked?: string | null,
237
- * llmProvider?: 'claude' | 'openrouter',
238
- * lastModelsByProvider?: Record<string, string>,
239
- * openrouterAuthOk?: boolean,
240
- * openrouterApiKeySet?: boolean,
241
- * openrouterApiKeyMasked?: string | null,
242
193
  * llmAuthOk?: boolean,
243
194
  * stubAgent?: boolean,
244
195
  * }} */
@@ -254,7 +205,6 @@ const els = {
254
205
  modelProvider: document.getElementById('model-provider'),
255
206
  modelCurrent: document.getElementById('model-current'),
256
207
  modelCombined: document.getElementById('model-combined'),
257
- overviewLlmProvider: document.getElementById('overview-llm-provider'),
258
208
  overviewModel: document.getElementById('overview-model'),
259
209
  overviewAgentPicker: document.getElementById('overview-agent-picker'),
260
210
  overviewAgentTrigger: document.getElementById('overview-agent-trigger'),
@@ -340,8 +290,6 @@ const els = {
340
290
  settingsModelList: document.getElementById('settings-model-list'),
341
291
  settingsModelSearch: document.getElementById('settings-model-search'),
342
292
  settingsModelHint: document.getElementById('settings-model-hint'),
343
- settingsLlmProvider: document.getElementById('settings-llm-provider'),
344
- settingsLlmProviderHint: document.getElementById('settings-llm-provider-hint'),
345
293
  settingsMaxTurns: document.getElementById('settings-max-turns'),
346
294
  settingsTimeout: document.getElementById('settings-timeout'),
347
295
  settingsTimeoutMs: document.getElementById('settings-timeout-ms'),
@@ -382,10 +330,6 @@ const els = {
382
330
  settingsClaudeOauth: document.getElementById('settings-claude-oauth'),
383
331
  settingsClaudeOauthClear: document.getElementById('settings-claude-oauth-clear'),
384
332
  settingsClaudeOauthHint: document.getElementById('settings-claude-oauth-hint'),
385
- settingsOpenrouterStatus: document.getElementById('settings-openrouter-status'),
386
- settingsOpenrouterKey: document.getElementById('settings-openrouter-key'),
387
- settingsOpenrouterKeyClear: document.getElementById('settings-openrouter-key-clear'),
388
- settingsOpenrouterKeyHint: document.getElementById('settings-openrouter-key-hint'),
389
333
  settingsTabs: document.getElementById('settings-tabs'),
390
334
  };
391
335
 
@@ -3042,34 +2986,21 @@ async function readJson(res) {
3042
2986
  }
3043
2987
  }
3044
2988
 
3045
- function llmProviderLabel(provider) {
3046
- return provider === 'openrouter' ? 'OpenRouter' : 'Claude';
3047
- }
3048
-
3049
2989
  /**
3050
- * @param {'claude' | 'openrouter'} [provider]
3051
2990
  * @param {string} [modelId]
3052
2991
  * @returns {string}
3053
2992
  */
3054
- function agentDisplayText(provider = currentLlmProviderSelection(), modelId = currentModel) {
3055
- const providerName = llmProviderLabel(provider);
3056
- const modelText = isNoModelSelection(modelId) ? '—' : modelPickerDisplayText(modelId);
3057
- return `${providerName} • ${modelText}`;
2993
+ function agentDisplayText(modelId = currentModel) {
2994
+ return isNoModelSelection(modelId) ? '—' : modelPickerDisplayText(modelId);
3058
2995
  }
3059
2996
 
3060
2997
  function updateModelLabel() {
3061
- const provider = appConfig.llmProvider === 'openrouter' ? 'openrouter' : 'claude';
3062
- const providerName = llmProviderLabel(provider);
3063
2998
  const modelText = isNoModelSelection(currentModel) ? '—' : currentModel;
3064
2999
  const modelTitle = isNoModelSelection(currentModel)
3065
3000
  ? 'none selected'
3066
3001
  : currentModel;
3067
- const combined = agentDisplayText(provider, currentModel);
3002
+ const combined = agentDisplayText(currentModel);
3068
3003
 
3069
- if (els.modelProvider) {
3070
- els.modelProvider.textContent = providerName;
3071
- els.modelProvider.title = `${providerName} — switch in Overview or Settings`;
3072
- }
3073
3004
  if (els.modelCurrent) {
3074
3005
  els.modelCurrent.textContent = modelText;
3075
3006
  els.modelCurrent.title = `Model: ${modelTitle} — switch in Overview or Settings`;
@@ -3082,29 +3013,16 @@ function updateModelLabel() {
3082
3013
  refreshModelSourceHint();
3083
3014
  }
3084
3015
 
3085
- /**
3086
- * @param {'claude' | 'openrouter'} [provider]
3087
- * @returns {Array<{id:string,label?:string,name?:string}>}
3088
- */
3089
- function getAvailableModels(provider = currentLlmProviderSelection()) {
3090
- if (availableModelsProvider === provider && availableModels.length) {
3091
- return availableModels;
3092
- }
3093
- return defaultModelsForProvider(provider);
3016
+ function getAvailableModels() {
3017
+ return availableModels.length ? availableModels : defaultModels();
3094
3018
  }
3095
3019
 
3096
- /**
3097
- * @param {'claude' | 'openrouter'} provider
3098
- * @param {Array<{id:string,label?:string,name?:string}>} models
3099
- */
3100
- function setAvailableModels(provider, models) {
3101
- availableModelsProvider = provider;
3020
+ function setAvailableModels(models) {
3102
3021
  availableModels = models;
3103
3022
  }
3104
3023
 
3105
3024
  function clearAvailableModels() {
3106
3025
  availableModels = [];
3107
- availableModelsProvider = null;
3108
3026
  }
3109
3027
 
3110
3028
  /**
@@ -3391,76 +3309,8 @@ function renderAllModelComboboxLists(selectedId = currentModel) {
3391
3309
  /** @type {AgentPickerInstance[]} */
3392
3310
  const agentPickers = [];
3393
3311
 
3394
- const PROVIDER_UNAVAILABLE_TOOLTIP =
3395
- 'Not configured — add credentials in Settings → Authentication';
3396
-
3397
- /**
3398
- * @param {'claude' | 'openrouter'} provider
3399
- * @param {typeof appConfig} [cfg]
3400
- */
3401
- function isLlmProviderAvailable(provider, cfg = appConfig) {
3402
- if (cfg.stubAgent) return true;
3403
- if (provider === 'openrouter') return cfg.openrouterAuthOk === true;
3404
- return cfg.claudeAuthOk === true;
3405
- }
3406
-
3407
- /**
3408
- * Disable provider tabs and select options when auth is missing.
3409
- * @param {typeof appConfig} [cfg]
3410
- */
3411
- function syncLlmProviderAvailability(cfg = appConfig) {
3412
- for (const ap of agentPickers) {
3413
- for (const tab of ap.tabs) {
3414
- const provider =
3415
- tab.dataset.provider === 'openrouter' ? 'openrouter' : 'claude';
3416
- const available = isLlmProviderAvailable(provider, cfg);
3417
- tab.disabled = !available;
3418
- tab.classList.toggle('is-disabled', !available);
3419
- tab.setAttribute('aria-disabled', available ? 'false' : 'true');
3420
- if (available) {
3421
- tab.removeAttribute('title');
3422
- } else {
3423
- tab.title = PROVIDER_UNAVAILABLE_TOOLTIP;
3424
- }
3425
- }
3426
- }
3427
-
3428
- for (const select of [els.settingsLlmProvider, els.overviewLlmProvider]) {
3429
- if (!select) continue;
3430
- for (const opt of select.options) {
3431
- const provider = opt.value === 'openrouter' ? 'openrouter' : 'claude';
3432
- const available = isLlmProviderAvailable(provider, cfg);
3433
- opt.disabled = !available;
3434
- if (available) {
3435
- opt.removeAttribute('title');
3436
- } else {
3437
- opt.title = PROVIDER_UNAVAILABLE_TOOLTIP;
3438
- }
3439
- }
3440
- }
3441
- }
3442
-
3443
- /**
3444
- * @param {'claude' | 'openrouter'} provider
3445
- */
3446
- function syncAgentPickerTabs(provider) {
3447
- const value = provider === 'openrouter' ? 'openrouter' : 'claude';
3448
- for (const ap of agentPickers) {
3449
- for (const tab of ap.tabs) {
3450
- const active = tab.dataset.provider === value;
3451
- tab.classList.toggle('is-active', active);
3452
- tab.setAttribute('aria-selected', active ? 'true' : 'false');
3453
- }
3454
- if (ap.iconEl) {
3455
- ap.iconEl.classList.toggle('agent-pill-icon--claude', value === 'claude');
3456
- ap.iconEl.classList.toggle('agent-pill-icon--openrouter', value === 'openrouter');
3457
- }
3458
- }
3459
- }
3460
-
3461
3312
  function syncAgentPickerDisplay(selectedId = currentModel) {
3462
- const provider = currentLlmProviderSelection();
3463
- const text = agentDisplayText(provider, selectedId);
3313
+ const text = agentDisplayText(selectedId);
3464
3314
  for (const ap of agentPickers) {
3465
3315
  ap.valueEl.textContent = text;
3466
3316
  ap.valueEl.classList.toggle('is-empty', isNoModelSelection(selectedId));
@@ -3468,7 +3318,6 @@ function syncAgentPickerDisplay(selectedId = currentModel) {
3468
3318
  ap.hiddenInput.value = isNoModelSelection(selectedId) ? NO_MODEL : selectedId;
3469
3319
  }
3470
3320
  }
3471
- syncAgentPickerTabs(provider);
3472
3321
  }
3473
3322
 
3474
3323
  /**
@@ -3492,7 +3341,6 @@ function setAgentPickerOpen(instance, open) {
3492
3341
  if (open) {
3493
3342
  instance.activeIndex = -1;
3494
3343
  instance.search.value = '';
3495
- syncAgentPickerTabs(currentLlmProviderSelection());
3496
3344
  renderAgentPickerModelList(instance, currentModel);
3497
3345
  focusWithoutScroll(instance.search);
3498
3346
  } else {
@@ -3629,19 +3477,6 @@ function initAgentPicker(opts) {
3629
3477
  }
3630
3478
  });
3631
3479
 
3632
- for (const tab of instance.tabs) {
3633
- tab.addEventListener('click', () => {
3634
- if (tab.disabled) return;
3635
- const provider =
3636
- tab.dataset.provider === 'openrouter' ? 'openrouter' : 'claude';
3637
- if (!isLlmProviderAvailable(provider)) return;
3638
- if (provider === currentLlmProviderSelection()) return;
3639
- syncLlmProviderSelects(provider);
3640
- syncAgentPickerTabs(provider);
3641
- void onLlmProviderChange(provider);
3642
- });
3643
- }
3644
-
3645
3480
  return instance;
3646
3481
  }
3647
3482
 
@@ -3674,33 +3509,10 @@ function reconcileModelForProvider(models, candidate = currentModel, storedCandi
3674
3509
  return NO_MODEL;
3675
3510
  }
3676
3511
 
3677
- /**
3678
- * @param {'claude' | 'openrouter'} provider
3679
- * @returns {string | undefined}
3680
- */
3681
- function lastModelForProvider(provider) {
3682
- const stored = appConfig.lastModelsByProvider?.[provider];
3683
- return typeof stored === 'string' && !isNoModelSelection(stored) ? stored : undefined;
3684
- }
3685
-
3686
3512
  /**
3687
3513
  * @returns {Array<{id:string,label?:string,name?:string}>}
3688
3514
  */
3689
- function defaultModelsForProvider(provider = 'claude') {
3690
- if (provider === 'openrouter') {
3691
- return [
3692
- { id: 'anthropic/claude-sonnet-4', label: 'Claude Sonnet 4 (Anthropic)', agentCompatible: true },
3693
- { id: 'anthropic/claude-opus-4', label: 'Claude Opus 4 (Anthropic)', agentCompatible: true },
3694
- { id: 'anthropic/claude-3.5-sonnet', label: 'Claude 3.5 Sonnet (Anthropic)', agentCompatible: true },
3695
- { id: 'anthropic/claude-3.7-sonnet', label: 'Claude 3.7 Sonnet (Anthropic)', agentCompatible: true },
3696
- { id: 'openai/gpt-4o', label: 'GPT-4o (OpenAI)', agentCompatible: false },
3697
- { id: 'openai/gpt-4o-mini', label: 'GPT-4o Mini (OpenAI)', agentCompatible: false },
3698
- { id: 'google/gemini-2.5-pro-preview', label: 'Gemini 2.5 Pro Preview (Google)', agentCompatible: false },
3699
- { id: 'qwen/qwen3-235b-a22b', label: 'Qwen3 235B (Qwen)', agentCompatible: false },
3700
- { id: 'deepseek/deepseek-chat-v3-0324', label: 'DeepSeek Chat V3 (DeepSeek)', agentCompatible: false },
3701
- { id: 'meta-llama/llama-3.3-70b-instruct', label: 'Llama 3.3 70B Instruct (Meta)', agentCompatible: false },
3702
- ];
3703
- }
3515
+ function defaultModels() {
3704
3516
  return [
3705
3517
  { id: 'claude-sonnet-5', label: 'Sonnet 5' },
3706
3518
  { id: 'claude-opus-5', label: 'Opus 5' },
@@ -3712,28 +3524,18 @@ function defaultModelsForProvider(provider = 'claude') {
3712
3524
  }
3713
3525
 
3714
3526
  /**
3715
- * @param {'anthropic' | 'openrouter' | 'fallback' | string | undefined} source
3716
- * @param {'claude' | 'openrouter'} [provider]
3527
+ * @param {'anthropic' | 'fallback' | string | undefined} source
3717
3528
  */
3718
- function updateModelSourceHint(source, provider = 'claude') {
3529
+ function updateModelSourceHint(source) {
3719
3530
  if (!els.settingsModelHint) return;
3720
- const name = provider === 'openrouter' ? 'OpenRouter' : 'Claude';
3721
3531
  if (source === 'anthropic') {
3722
3532
  modelSourceHintBase =
3723
- `${name} model for agent runs. List loaded from Anthropic Models API.`;
3724
- } else if (source === 'openrouter') {
3725
- modelSourceHintBase =
3726
- 'OpenRouter models for agent runs. Full catalog from OpenRouter Models API. Agent jobs require anthropic/* models (marked “Agent” in the list).';
3533
+ 'Claude model for agent runs. List loaded from Anthropic Models API.';
3727
3534
  } else if (source === 'fallback') {
3728
- if (provider === 'openrouter') {
3729
- modelSourceHintBase =
3730
- 'OpenRouter models for agent runs. Showing curated catalog (live list unavailable — set OPENROUTER_API_KEY in Authentication). Agent jobs require anthropic/* models.';
3731
- } else {
3732
- modelSourceHintBase =
3733
- 'Claude model for agent runs. Showing curated Claude Code models (live list unavailable — set an API key or use claude auth login).';
3734
- }
3535
+ modelSourceHintBase =
3536
+ 'Claude model for agent runs. Showing curated Claude Code models (live list unavailable — set an API key or use claude auth login).';
3735
3537
  } else {
3736
- modelSourceHintBase = `${name} model for agent runs.`;
3538
+ modelSourceHintBase = 'Claude model for agent runs.';
3737
3539
  }
3738
3540
  refreshModelSourceHint();
3739
3541
  }
@@ -3759,92 +3561,55 @@ function modelMatchesSearchQuery(model, query) {
3759
3561
  .some((part) => String(part).toLowerCase().includes(q));
3760
3562
  }
3761
3563
 
3762
- /**
3763
- * @returns {'claude' | 'openrouter'}
3764
- */
3765
- function currentLlmProviderSelection() {
3766
- const fromOverview = els.overviewLlmProvider?.value;
3767
- if (fromOverview === 'openrouter' || fromOverview === 'claude') return fromOverview;
3768
- const fromForm = els.settingsLlmProvider?.value;
3769
- if (fromForm === 'openrouter' || fromForm === 'claude') return fromForm;
3770
- return appConfig.llmProvider === 'openrouter' ? 'openrouter' : 'claude';
3771
- }
3772
-
3773
- /**
3774
- * @param {'claude' | 'openrouter'} provider
3775
- */
3776
- function syncLlmProviderSelects(provider) {
3777
- const value = provider === 'openrouter' ? 'openrouter' : 'claude';
3778
- if (els.settingsLlmProvider) els.settingsLlmProvider.value = value;
3779
- if (els.overviewLlmProvider) els.overviewLlmProvider.value = value;
3780
- syncAgentPickerTabs(value);
3781
- if (els.settingsLlmProviderHint) {
3782
- els.settingsLlmProviderHint.textContent =
3783
- value === 'openrouter'
3784
- ? 'Agent runs route through OpenRouter (requires OPENROUTER_API_KEY). All models are listed; agent jobs need anthropic/* models.'
3785
- : 'Agent runs use Claude via Anthropic API, OAuth token, or claude auth login.';
3786
- }
3787
- }
3788
-
3789
- /**
3790
- * @param {{ models?: Array<{id:string,label?:string,name?:string}>, selected?: string, source?: string, provider?: string }} data
3791
- */
3792
3564
  function populateSettingsModels(data) {
3793
- const provider = data?.provider || currentLlmProviderSelection();
3794
- const cached = getAvailableModels(provider);
3795
3565
  let models = Array.isArray(data?.models)
3796
3566
  ? data.models
3797
- : data?.reconcileProvider
3567
+ : data?.reconcile
3798
3568
  ? []
3799
- : cached.length
3800
- ? cached
3801
- : defaultModelsForProvider(provider);
3569
+ : getAvailableModels().length
3570
+ ? getAvailableModels()
3571
+ : defaultModels();
3802
3572
  if (!models.length && data?.source) {
3803
- models = defaultModelsForProvider(provider);
3573
+ models = defaultModels();
3804
3574
  }
3805
- setAvailableModels(provider, models);
3575
+ setAvailableModels(models);
3806
3576
 
3807
- if (data?.reconcileProvider) {
3577
+ if (data?.reconcile) {
3808
3578
  currentModel = reconcileModelForProvider(
3809
3579
  models,
3810
- data.reconcileCandidate,
3811
- lastModelForProvider(provider)
3580
+ data.reconcileCandidate
3812
3581
  );
3813
3582
  } else if (data?.selected != null) {
3814
3583
  const next = isNoModelSelection(data.selected) ? NO_MODEL : data.selected;
3815
- currentModel = reconcileModelForProvider(models, next, lastModelForProvider(provider));
3584
+ currentModel = reconcileModelForProvider(models, next);
3816
3585
  }
3817
- if (data?.source) updateModelSourceHint(data.source, provider);
3586
+ if (data?.source) updateModelSourceHint(data.source);
3818
3587
 
3819
3588
  syncAllModelComboboxValues(currentModel);
3820
3589
  renderAllModelComboboxLists(currentModel);
3821
3590
  updateModelLabel();
3822
3591
  }
3823
3592
 
3824
- function modelsForFetchFallback(provider) {
3825
- const cached = getAvailableModels(provider);
3826
- if (availableModelsProvider === provider && cached.length) return cached;
3827
- return defaultModelsForProvider(provider);
3593
+ function modelsForFetchFallback() {
3594
+ return getAvailableModels().length ? getAvailableModels() : defaultModels();
3828
3595
  }
3829
3596
 
3830
3597
  async function fetchModels(opts = {}) {
3831
3598
  const refresh = opts.refresh === true;
3832
- const provider = opts.provider || currentLlmProviderSelection();
3833
- const reconcileProvider = opts.reconcileProvider === true;
3599
+ const reconcile = opts.reconcile === true;
3834
3600
  const reconcileCandidate = opts.reconcileCandidate;
3835
- if (reconcileProvider) {
3601
+ if (reconcile) {
3836
3602
  clearAvailableModels();
3837
3603
  }
3838
- const query = new URLSearchParams({ provider });
3604
+ const query = new URLSearchParams();
3839
3605
  if (refresh) query.set('refresh', '1');
3840
3606
  try {
3841
3607
  const res = await fetch(`/api/models?${query.toString()}`);
3842
3608
  if (!res.ok) {
3843
3609
  populateSettingsModels({
3844
- models: modelsForFetchFallback(provider),
3610
+ models: modelsForFetchFallback(),
3845
3611
  selected: currentModel,
3846
- provider,
3847
- reconcileProvider,
3612
+ reconcile,
3848
3613
  reconcileCandidate,
3849
3614
  });
3850
3615
  return;
@@ -3852,17 +3617,15 @@ async function fetchModels(opts = {}) {
3852
3617
  const data = await readJson(res);
3853
3618
  populateSettingsModels({
3854
3619
  ...data,
3855
- provider: data.provider || provider,
3856
- reconcileProvider,
3620
+ reconcile,
3857
3621
  reconcileCandidate,
3858
3622
  });
3859
3623
  } catch (err) {
3860
3624
  console.error('Failed to fetch models:', err);
3861
3625
  populateSettingsModels({
3862
- models: modelsForFetchFallback(provider),
3626
+ models: modelsForFetchFallback(),
3863
3627
  selected: currentModel,
3864
- provider,
3865
- reconcileProvider,
3628
+ reconcile,
3866
3629
  reconcileCandidate,
3867
3630
  });
3868
3631
  }
@@ -3982,13 +3745,6 @@ function claudeStatusText(cfg) {
3982
3745
  }
3983
3746
  }
3984
3747
 
3985
- function openrouterStatusText(cfg) {
3986
- if (cfg.openrouterAuthOk) {
3987
- return 'Authenticated via OPENROUTER_API_KEY';
3988
- }
3989
- return 'Not authenticated — add an OpenRouter API key below';
3990
- }
3991
-
3992
3748
  function fillAuthSettings(cfg) {
3993
3749
  if (els.settingsGhStatus) {
3994
3750
  els.settingsGhStatus.textContent = ghStatusText(cfg);
@@ -4047,25 +3803,6 @@ function fillAuthSettings(cfg) {
4047
3803
  ? 'OAuth token stored in <code>.acdev/.env</code> as <code>CLAUDE_CODE_OAUTH_TOKEN</code>. Settings cannot complete browser OAuth — that still needs <code>claude auth login</code> on this host.'
4048
3804
  : 'From <code>claude setup-token</code> for non-interactive subscription auth. Stored as <code>CLAUDE_CODE_OAUTH_TOKEN</code>. Settings cannot complete browser OAuth — that still needs <code>claude auth login</code> on this host.';
4049
3805
  }
4050
-
4051
- if (els.settingsOpenrouterStatus) {
4052
- els.settingsOpenrouterStatus.textContent = openrouterStatusText(cfg);
4053
- els.settingsOpenrouterStatus.className = cfg.openrouterAuthOk
4054
- ? 'auth-status ok'
4055
- : 'auth-status err';
4056
- }
4057
- fillSecretInput(
4058
- els.settingsOpenrouterKey,
4059
- els.settingsOpenrouterKeyClear,
4060
- Boolean(cfg.openrouterApiKeySet),
4061
- cfg.openrouterApiKeyMasked,
4062
- 'Paste an OpenRouter API key'
4063
- );
4064
- if (els.settingsOpenrouterKeyHint) {
4065
- els.settingsOpenrouterKeyHint.innerHTML = cfg.openrouterApiKeySet
4066
- ? 'API key stored in <code>.acdev/.env</code> as <code>OPENROUTER_API_KEY</code> (not committed).'
4067
- : 'Required when LLM provider is OpenRouter. Get a key at <a href="https://openrouter.ai/keys" target="_blank" rel="noopener noreferrer">openrouter.ai/keys</a>. Stored as <code>OPENROUTER_API_KEY</code> in <code>.acdev/.env</code>.';
4068
- }
4069
3806
  }
4070
3807
 
4071
3808
  function fillSettingsForm(cfg) {
@@ -4145,23 +3882,17 @@ function fillSettingsForm(cfg) {
4145
3882
  els.settingsBaseBranch.value = cfg.baseBranch || '';
4146
3883
  }
4147
3884
 
4148
- if (els.settingsLlmProvider) {
4149
- syncLlmProviderSelects(cfg.llmProvider === 'openrouter' ? 'openrouter' : 'claude');
4150
- }
4151
-
4152
3885
  if (els.settingsModelPicker) {
4153
- const provider = cfg.llmProvider === 'openrouter' ? 'openrouter' : 'claude';
4154
- if (getAvailableModels(provider).length) {
3886
+ if (getAvailableModels().length) {
4155
3887
  populateSettingsModels({
4156
- models: getAvailableModels(provider),
3888
+ models: getAvailableModels(),
4157
3889
  selected: cfg.model ?? currentModel,
4158
- provider,
4159
3890
  });
4160
3891
  } else if (cfg.model != null) {
4161
3892
  currentModel = isNoModelSelection(cfg.model) ? NO_MODEL : cfg.model;
4162
3893
  syncModelPickerValue(currentModel);
4163
3894
  }
4164
- void fetchModels({ provider });
3895
+ void fetchModels();
4165
3896
  }
4166
3897
 
4167
3898
  if (els.settingsMaxTurns) {
@@ -4354,16 +4085,14 @@ function setOverviewLlmFeedback(message, kind = 'ok') {
4354
4085
  */
4355
4086
  function fillOverviewLlmControls(cfg) {
4356
4087
  if (!cfg) return;
4357
- const provider = cfg.llmProvider === 'openrouter' ? 'openrouter' : 'claude';
4358
- syncLlmProviderSelects(provider);
4359
4088
  syncAllModelComboboxValues(currentModel);
4360
4089
  }
4361
4090
 
4362
4091
  let llmConfigSaving = false;
4363
4092
 
4364
4093
  /**
4365
- * PATCH llmProvider and/or model immediately (Overview switcher).
4366
- * @param {{ llmProvider?: 'claude' | 'openrouter', model?: string }} patch
4094
+ * PATCH model immediately (Overview switcher).
4095
+ * @param {{ model?: string }} patch
4367
4096
  */
4368
4097
  async function patchLlmConfig(patch) {
4369
4098
  if (llmConfigSaving) return;
@@ -4385,7 +4114,7 @@ async function patchLlmConfig(patch) {
4385
4114
  if (currentView === 'settings') {
4386
4115
  fillSettingsForm(data);
4387
4116
  }
4388
- setOverviewLlmFeedback('LLM updated. Next jobs will use these values.', 'ok');
4117
+ setOverviewLlmFeedback('Model updated. Next jobs will use this value.', 'ok');
4389
4118
  window.setTimeout(() => setOverviewLlmFeedback(null), 2500);
4390
4119
  } catch (err) {
4391
4120
  setOverviewLlmFeedback(err.message || 'Update failed', 'error');
@@ -4394,29 +4123,6 @@ async function patchLlmConfig(patch) {
4394
4123
  }
4395
4124
  }
4396
4125
 
4397
- /**
4398
- * @param {'claude' | 'openrouter'} provider
4399
- */
4400
- async function onLlmProviderChange(provider) {
4401
- const previousModel = currentModel;
4402
- syncLlmProviderSelects(provider);
4403
- clearAvailableModels();
4404
- currentModel = NO_MODEL;
4405
- syncAllModelComboboxValues(currentModel);
4406
- renderAllModelComboboxLists(currentModel);
4407
- updateModelLabel();
4408
- await fetchModels({
4409
- refresh: true,
4410
- provider,
4411
- reconcileProvider: true,
4412
- reconcileCandidate: previousModel,
4413
- });
4414
- await patchLlmConfig({
4415
- llmProvider: provider,
4416
- model: currentModel,
4417
- });
4418
- }
4419
-
4420
4126
  function applyConfigSnapshot(data) {
4421
4127
  appConfig = data || {};
4422
4128
  if (data?.model != null) {
@@ -4432,7 +4138,6 @@ function applyConfigSnapshot(data) {
4432
4138
  if (els.repoBranch) {
4433
4139
  els.repoBranch.textContent = data?.baseBranch || '—';
4434
4140
  }
4435
- syncLlmProviderAvailability(appConfig);
4436
4141
  if (currentView === 'settings') {
4437
4142
  fillSettingsForm(appConfig);
4438
4143
  }
@@ -4468,7 +4173,6 @@ function readSettingsForm() {
4468
4173
  const patch = {
4469
4174
  baseBranch,
4470
4175
  model,
4471
- llmProvider: els.settingsLlmProvider?.value === 'openrouter' ? 'openrouter' : 'claude',
4472
4176
  maxAgentTurns,
4473
4177
  agentTimeoutMs: Math.round(timeoutMinutes * 60_000),
4474
4178
  testCommand: testTrimmed === '' ? null : testTrimmed,
@@ -4482,8 +4186,6 @@ function readSettingsForm() {
4482
4186
  if (anthropicApiKey !== undefined) patch.anthropicApiKey = anthropicApiKey;
4483
4187
  const claudeOauthToken = secretPatchValue(els.settingsClaudeOauth);
4484
4188
  if (claudeOauthToken !== undefined) patch.claudeOauthToken = claudeOauthToken;
4485
- const openrouterApiKey = secretPatchValue(els.settingsOpenrouterKey);
4486
- if (openrouterApiKey !== undefined) patch.openrouterApiKey = openrouterApiKey;
4487
4189
 
4488
4190
  if (ticketSource === 'jira' || els.settingsJiraBaseUrl?.value) {
4489
4191
  patch.jiraBaseUrl = els.settingsJiraBaseUrl?.value?.trim() || '';
@@ -4651,12 +4353,6 @@ bindSecretClear(
4651
4353
  els.settingsClaudeOauthHint,
4652
4354
  'Saved CLAUDE_CODE_OAUTH_TOKEN will be removed when you click Save settings.'
4653
4355
  );
4654
- bindSecretClear(
4655
- els.settingsOpenrouterKeyClear,
4656
- els.settingsOpenrouterKey,
4657
- els.settingsOpenrouterKeyHint,
4658
- 'Saved OPENROUTER_API_KEY will be removed when you click Save settings.'
4659
- );
4660
4356
 
4661
4357
 
4662
4358
  document.addEventListener('click', (ev) => {
@@ -4689,38 +4385,6 @@ document.addEventListener('keydown', (ev) => {
4689
4385
  }
4690
4386
  });
4691
4387
 
4692
- els.settingsLlmProvider?.addEventListener('change', () => {
4693
- const provider = currentLlmProviderSelection();
4694
- if (!isLlmProviderAvailable(provider)) {
4695
- const fallback =
4696
- appConfig.llmProvider === 'openrouter' ? 'openrouter' : 'claude';
4697
- syncLlmProviderSelects(fallback);
4698
- return;
4699
- }
4700
- for (const cb of modelComboboxes) {
4701
- setModelComboboxOpen(cb, false);
4702
- }
4703
- void onLlmProviderChange(provider);
4704
- });
4705
-
4706
- els.overviewLlmProvider?.addEventListener('change', () => {
4707
- const provider =
4708
- els.overviewLlmProvider.value === 'openrouter' ? 'openrouter' : 'claude';
4709
- if (!isLlmProviderAvailable(provider)) {
4710
- const fallback =
4711
- appConfig.llmProvider === 'openrouter' ? 'openrouter' : 'claude';
4712
- syncLlmProviderSelects(fallback);
4713
- return;
4714
- }
4715
- for (const cb of modelComboboxes) {
4716
- setModelComboboxOpen(cb, false);
4717
- }
4718
- for (const ap of agentPickers) {
4719
- setAgentPickerOpen(ap, false);
4720
- }
4721
- void onLlmProviderChange(provider);
4722
- });
4723
-
4724
4388
  if (
4725
4389
  els.settingsModelPicker &&
4726
4390
  els.settingsModelTrigger &&
@@ -4763,9 +4427,6 @@ if (
4763
4427
  list: els.overviewAgentModelList,
4764
4428
  valueEl: els.overviewAgentValue,
4765
4429
  iconEl: els.overviewAgentIcon,
4766
- tabs: [
4767
- ...(els.overviewAgentPanel.querySelectorAll('.agent-picker-tab') || []),
4768
- ],
4769
4430
  hiddenInput: els.overviewModel,
4770
4431
  optionIdPrefix: 'overview-agent-model-opt',
4771
4432
  onSelect: (modelId) => {
@@ -5096,9 +4757,8 @@ els.logJobFilter.addEventListener('change', () => {
5096
4757
 
5097
4758
  applyTheme(loadTheme());
5098
4759
  void (async () => {
5099
- const cfg = await fetchConfig();
5100
- const provider = cfg?.llmProvider === 'openrouter' ? 'openrouter' : 'claude';
5101
- await fetchModels({ provider });
4760
+ await fetchConfig();
4761
+ await fetchModels();
5102
4762
  })();
5103
4763
  fetchJobs();
5104
4764
  setInterval(fetchJobs, 3000);